Gate ambient fleet behind PlayerbotsV2.FleetBots (default off: alt-bot-only mode)
- ConfigReader: add PlayerbotsV2.FleetBots switch (default 0) + fleet_bots() accessor - Module::Init/OnWorldUpdate: skip population shaper, bot guilds, craft-order board, BG/LFG queue auto-fill, and AutoResume/AutoSpawn when fleet disabled - Services: FleetThread is not started in alt-bot-only mode - conf/playerbot.conf.dist + README document the new key
This commit is contained in:
@@ -811,7 +811,23 @@ void Module::Init()
|
||||
|
||||
initialized_ = true;
|
||||
|
||||
if (Services::Config().auto_resume_on_boot())
|
||||
if (!Services::Config().fleet_bots())
|
||||
{
|
||||
// PlayerbotsV2.FleetBots=0 (DEFAULT) — alt-bot-only mode. The
|
||||
// ambient fleet does not run: no auto-resume / auto-spawn of
|
||||
// population bots at boot, and the fleet subsystems (population
|
||||
// shaper, guilds, craft-order board, BG/LFG auto-fill) are all
|
||||
// skipped in OnWorldUpdate. The only bots that enter the world
|
||||
// are those a player explicitly brings up via the alt creation
|
||||
// path (.playerbot summon / login / alt ...).
|
||||
auto_resume_pending_ = 0;
|
||||
auto_spawn_pending_ = 0;
|
||||
TC_LOG_INFO("server.loading",
|
||||
"[PlayerbotV2] PlayerbotsV2.FleetBots=0: ambient fleet disabled — "
|
||||
"alt-bot creation path only (summon/login/alt). "
|
||||
"AutoResumeOnBoot/AutoSpawnOnBoot ignored.");
|
||||
}
|
||||
else if (Services::Config().auto_resume_on_boot())
|
||||
{
|
||||
// B-2: clamp the boot fill to Population.TotalTarget. AutoResumeCap
|
||||
// is an independent knob (default 100), so a 10-bot target still
|
||||
@@ -825,12 +841,23 @@ void Module::Init()
|
||||
"[PlayerbotV2] AutoResumeOnBoot=true; will batch-login marked bots over multiple ticks (cap {}).",
|
||||
auto_resume_pending_);
|
||||
}
|
||||
auto_spawn_pending_ = Services::Config().auto_spawn_on_boot();
|
||||
if (auto_spawn_pending_)
|
||||
else
|
||||
{
|
||||
TC_LOG_INFO("server.loading",
|
||||
"[PlayerbotV2] AutoSpawnOnBoot={}; will create new bots on first world tick to reach that target (hard cap 200).",
|
||||
auto_spawn_pending_);
|
||||
auto_resume_pending_ = 0;
|
||||
}
|
||||
if (Services::Config().fleet_bots())
|
||||
{
|
||||
auto_spawn_pending_ = Services::Config().auto_spawn_on_boot();
|
||||
if (auto_spawn_pending_)
|
||||
{
|
||||
TC_LOG_INFO("server.loading",
|
||||
"[PlayerbotV2] AutoSpawnOnBoot={}; will create new bots on first world tick to reach that target (hard cap 200).",
|
||||
auto_spawn_pending_);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto_spawn_pending_ = 0;
|
||||
}
|
||||
// #1B: apply the configured wedge-confirm thresholds + displacement gate.
|
||||
wedge_watchdog_.set_threshold_ms(Services::Config().wedge_watchdog_threshold_ms());
|
||||
@@ -1097,7 +1124,7 @@ void Module::OnWorldUpdate(std::chrono::milliseconds diff)
|
||||
// Spreading the resume kBootSpawnPerTick at a time keeps the bnet
|
||||
// write rate well below the row-lock contention threshold.
|
||||
constexpr uint32 kBootResumePerTick = 5;
|
||||
if (auto_resume_pending_ > 0 && boot_gate_open_)
|
||||
if (auto_resume_pending_ > 0 && boot_gate_open_ && Services::Config().fleet_bots())
|
||||
{
|
||||
const uint32 batch = std::min(auto_resume_pending_, kBootResumePerTick);
|
||||
// LoginAll's cap is "stop when active_count >= cap". Active count
|
||||
@@ -1132,7 +1159,7 @@ void Module::OnWorldUpdate(std::chrono::milliseconds diff)
|
||||
// Hard-capped at AUTO_SPAWN_HARD_CAP overall.
|
||||
constexpr uint32 AUTO_SPAWN_HARD_CAP = 200;
|
||||
constexpr uint32 kBootSpawnPerTick = 5;
|
||||
if (auto_spawn_pending_ && boot_gate_open_)
|
||||
if (auto_spawn_pending_ && boot_gate_open_ && Services::Config().fleet_bots())
|
||||
{
|
||||
const uint32 currentMarked = uint32(Services::Lifecycle().size());
|
||||
const uint32 target_total = std::min(
|
||||
@@ -1969,7 +1996,11 @@ void Module::OnWorldUpdate(std::chrono::milliseconds diff)
|
||||
// 1.5. Stagger-fire any deferred BG ports + LFG proposal accepts whose
|
||||
// delay has elapsed (see OnBGInvitationReceived /
|
||||
// OnLfgProposalReceived). MUST run before DrainIntents so the
|
||||
// freshly-pushed intents fire this same tick.
|
||||
// freshly-pushed intents fire this same tick. Fleet-only: the
|
||||
// BG/LFG queue machinery recruits population bots, so in
|
||||
// alt-bot-only mode (PlayerbotsV2.FleetBots=0) the whole block
|
||||
// is skipped.
|
||||
if (Services::Config().fleet_bots())
|
||||
{
|
||||
const uint32 t0 = getMSTime();
|
||||
FireDueBgPorts();
|
||||
@@ -1998,7 +2029,11 @@ void Module::OnWorldUpdate(std::chrono::milliseconds diff)
|
||||
|
||||
// 2.6. Population shaper tick (Phase A of WORLD_POPULATION_PLAN).
|
||||
// Internally rate-limits to its tick interval (default 60s) so
|
||||
// this is cheap to call every world frame.
|
||||
// this is cheap to call every world frame. Fleet-only — the
|
||||
// shaper owns the ambient world population (JIT spawn / login /
|
||||
// logout / BG+arena seeding), so it is fully disabled in
|
||||
// alt-bot-only mode (PlayerbotsV2.FleetBots=0).
|
||||
if (Services::Config().fleet_bots())
|
||||
{
|
||||
const uint32 t0 = getMSTime();
|
||||
Services::Population().OnWorldTick(static_cast<uint32>(total.count()));
|
||||
@@ -2008,19 +2043,23 @@ void Module::OnWorldUpdate(std::chrono::milliseconds diff)
|
||||
// 2b. Bot guild manager tick (GUILD_PLAN.md Phase A.2). Internally
|
||||
// 60s rate-limited; cheap to call every frame. Elects founders
|
||||
// when guild slots are open + sweeps stale name reservations
|
||||
// + aborts founders that exceeded the 30-min FSM budget.
|
||||
Services::Guilds().Tick(getMSTime());
|
||||
// + aborts founders that exceeded the 30-min FSM budget. This is
|
||||
// the bot-guild ecosystem — a fleet feature — so it is skipped in
|
||||
// alt-bot-only mode.
|
||||
if (Services::Config().fleet_bots())
|
||||
Services::Guilds().Tick(getMSTime());
|
||||
|
||||
// 2c. Craft-order board tick (#4B-2). Ages out stale Claimed orders
|
||||
// (timeout -> Fail + refund so escrow can't be trapped behind a stuck
|
||||
// crafter) and prunes finished rows. Self-rate-limited cheaply via a
|
||||
// local accumulator — the board only needs minute-granularity since
|
||||
// the claim timeout is 30 min.
|
||||
// the claim timeout is 30 min. Fleet bot-to-bot economy — skipped in
|
||||
// alt-bot-only mode.
|
||||
craft_board_total_ += diff;
|
||||
if (craft_board_total_ - last_craft_board_at_ >= kCraftBoardIntervalMs)
|
||||
{
|
||||
last_craft_board_at_ = craft_board_total_;
|
||||
if (Services::Initialized())
|
||||
if (Services::Initialized() && Services::Config().fleet_bots())
|
||||
Services::CraftOrders().Tick(GameTime::GetGameTimeMS());
|
||||
}
|
||||
|
||||
@@ -3779,6 +3818,11 @@ void Module::OnPartyChat(Player* sender, Group* group, std::string const& msg)
|
||||
void Module::OnPlayerJoinedBgQueue(Player* player, uint32 bg_type_id, uint8 bracket)
|
||||
{
|
||||
if (!initialized_ || !player) return;
|
||||
// Fleet-only: BG queue filling recruits population bots. In alt-bot-only
|
||||
// mode (PlayerbotsV2.FleetBots=0) there is no ambient fleet to fill a
|
||||
// queue with, so the whole fill is skipped.
|
||||
if (!Services::Config().fleet_bots())
|
||||
return;
|
||||
// SOLO bots ignore (they're queued by Filler intent emit, not by core
|
||||
// path — re-firing the fill for each would recurse). A bot GROUP LEADER
|
||||
// is the exception (audit B25): a premade (guild BG-night /
|
||||
@@ -3864,6 +3908,12 @@ void Module::OnPlayerJoinedLfg(Player* player, uint32 dungeon_id, uint8 /*role_m
|
||||
{
|
||||
if (!initialized_ || !player) return;
|
||||
if (player->GetSession() && player->GetSession()->IsBot()) return;
|
||||
// Fleet-only: LFG fill drafts population bots (and can JIT-spawn new ones).
|
||||
// In alt-bot-only mode (PlayerbotsV2.FleetBots=0) there is no ambient
|
||||
// fleet, so bots for a player's queue must come from their own alts —
|
||||
// skip the auto-fill entirely.
|
||||
if (!Services::Config().fleet_bots())
|
||||
return;
|
||||
|
||||
Fleet::BotQueueFiller filler;
|
||||
Fleet::BotQueueFiller::FillRequest req{};
|
||||
|
||||
@@ -195,6 +195,7 @@ The `playerbot_v2_altbot` table is auto-created on first `BindAlt()` call via
|
||||
|
||||
Configuration lives in `playerbot.conf` (same directory as `worldserver.conf`).
|
||||
Key settings:
|
||||
- `PlayerbotsV2.FleetBots` — ambient-fleet master switch (default 0 = alt-bot-only mode; only the alt creation path runs)
|
||||
- `Playerbot.V2.Population.TotalTarget` — fleet bot population target
|
||||
- `Playerbot.V2.Population.Shape` — distribution shape (Pyramid, Bell, Flat, MaxHeavy)
|
||||
- `Playerbot.V2.MaxAltsAsBots` — per-account altbot cap
|
||||
|
||||
@@ -662,7 +662,12 @@ void Init()
|
||||
h->craft_orders->LoadFromDb();
|
||||
|
||||
h->ai_pool->start();
|
||||
h->fleet->start();
|
||||
// FleetThread is the fleet-layer admin worker. In alt-bot-only mode
|
||||
// (PlayerbotsV2.FleetBots=0) the ambient fleet is disabled, so the
|
||||
// thread is never started — nothing posts to it and the worker just
|
||||
// sits idle waiting on its CV.
|
||||
if (h->config->fleet_bots())
|
||||
h->fleet->start();
|
||||
|
||||
g_holder = h;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ void ConfigReader::apply_from_loaded_config()
|
||||
|
||||
ai_worker_threads_ = c.GetIntDefault ("Playerbot.AiWorkerThreads", 0);
|
||||
fleet_thread_enabled_ = c.GetBoolDefault("Playerbot.FleetThreadEnabled", true);
|
||||
fleet_bots_enabled_ = c.GetBoolDefault("PlayerbotsV2.FleetBots", false);
|
||||
tick_budget_ms_ = c.GetIntDefault ("Playerbot.TickBudgetMs", 10);
|
||||
// #5 Phase 4 parallel snapshot Build. DEFAULT ON. The 2026-06-15 crash
|
||||
// (ACCESS_VIOLATION in Build->objective_blacklisted, serial AND parallel) was
|
||||
|
||||
@@ -23,6 +23,19 @@ public:
|
||||
bool fleet_thread_enabled() const { return fleet_thread_enabled_; }
|
||||
uint32 tick_budget_ms() const { return tick_budget_ms_; }
|
||||
|
||||
// Ambient fleet master switch (PlayerbotsV2.FleetBots). When false
|
||||
// (DEFAULT) the module runs in alt-bot-only mode: NO population-managed
|
||||
// bots exist out in the world. The BotPopulationManager shaper,
|
||||
// bot guild ecosystem, bot-to-bot craft-order board, and BG/LFG queue
|
||||
// auto-fill are all disabled, and the fleet-entrenching GM commands
|
||||
// (.playerbot spawn/create/loginall/population/wipefleet/pipeline/
|
||||
// smoketest/dungeontest/reseed) refuse to run. The only bots that enter
|
||||
// the world are the ones a player explicitly brings up via the alt
|
||||
// creation path (.playerbot summon / login / alt ...). Set to 1 to
|
||||
// re-enable the full ambient fleet. Read once at Init; toggling on a
|
||||
// live server requires a restart.
|
||||
bool fleet_bots() const { return fleet_bots_enabled_; }
|
||||
|
||||
// #5 Phase 4: parallelize BotSnapshotBuilder::Build per Map* across a
|
||||
// fixed worker pool during the world-thread snapshot window. When false
|
||||
// the snapshot loop runs exactly as before (serial, world thread). Kept
|
||||
@@ -282,6 +295,7 @@ private:
|
||||
// Cached typed values.
|
||||
uint32 ai_worker_threads_ = 0;
|
||||
bool fleet_thread_enabled_ = true;
|
||||
bool fleet_bots_enabled_ = false;
|
||||
uint32 tick_budget_ms_ = 10;
|
||||
bool parallel_snapshot_build_ = true;
|
||||
uint32 snapshot_build_threads_ = 0;
|
||||
|
||||
@@ -38,6 +38,18 @@ Playerbot.AiWorkerThreads = 0
|
||||
# HR-no
|
||||
Playerbot.FleetThreadEnabled = 1
|
||||
|
||||
# Ambient-fleet master switch. When 0 (DEFAULT) the module runs in alt-bot-only
|
||||
# mode: NO population-managed fleet bots exist out in the world. The population
|
||||
# shaper, bot guild ecosystem, bot-to-bot craft-order board, and BG/LFG queue
|
||||
# auto-fill are disabled, and the fleet-entrenching GM commands
|
||||
# (.playerbot spawn/create/loginall/population/wipefleet/pipeline/smoketest/
|
||||
# dungeontest/reseed) refuse to run. The only bots that enter the world are the
|
||||
# ones a player explicitly brings up via the alt creation path
|
||||
# (.playerbot summon / login / alt ...). Set to 1 to re-enable the full
|
||||
# ambient fleet. Read once at startup — toggling requires a restart.
|
||||
# HR-no
|
||||
PlayerbotsV2.FleetBots = 0
|
||||
|
||||
# World tick budget for V2, in milliseconds. Bots skip ticks if exceeded.
|
||||
# HR-yes
|
||||
Playerbot.TickBudgetMs = 10
|
||||
|
||||
Reference in New Issue
Block a user