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;
|
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
|
// B-2: clamp the boot fill to Population.TotalTarget. AutoResumeCap
|
||||||
// is an independent knob (default 100), so a 10-bot target still
|
// 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 {}).",
|
"[PlayerbotV2] AutoResumeOnBoot=true; will batch-login marked bots over multiple ticks (cap {}).",
|
||||||
auto_resume_pending_);
|
auto_resume_pending_);
|
||||||
}
|
}
|
||||||
auto_spawn_pending_ = Services::Config().auto_spawn_on_boot();
|
else
|
||||||
if (auto_spawn_pending_)
|
|
||||||
{
|
{
|
||||||
TC_LOG_INFO("server.loading",
|
auto_resume_pending_ = 0;
|
||||||
"[PlayerbotV2] AutoSpawnOnBoot={}; will create new bots on first world tick to reach that target (hard cap 200).",
|
}
|
||||||
auto_spawn_pending_);
|
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.
|
// #1B: apply the configured wedge-confirm thresholds + displacement gate.
|
||||||
wedge_watchdog_.set_threshold_ms(Services::Config().wedge_watchdog_threshold_ms());
|
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
|
// Spreading the resume kBootSpawnPerTick at a time keeps the bnet
|
||||||
// write rate well below the row-lock contention threshold.
|
// write rate well below the row-lock contention threshold.
|
||||||
constexpr uint32 kBootResumePerTick = 5;
|
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);
|
const uint32 batch = std::min(auto_resume_pending_, kBootResumePerTick);
|
||||||
// LoginAll's cap is "stop when active_count >= cap". Active count
|
// 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.
|
// Hard-capped at AUTO_SPAWN_HARD_CAP overall.
|
||||||
constexpr uint32 AUTO_SPAWN_HARD_CAP = 200;
|
constexpr uint32 AUTO_SPAWN_HARD_CAP = 200;
|
||||||
constexpr uint32 kBootSpawnPerTick = 5;
|
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 currentMarked = uint32(Services::Lifecycle().size());
|
||||||
const uint32 target_total = std::min(
|
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
|
// 1.5. Stagger-fire any deferred BG ports + LFG proposal accepts whose
|
||||||
// delay has elapsed (see OnBGInvitationReceived /
|
// delay has elapsed (see OnBGInvitationReceived /
|
||||||
// OnLfgProposalReceived). MUST run before DrainIntents so the
|
// 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();
|
const uint32 t0 = getMSTime();
|
||||||
FireDueBgPorts();
|
FireDueBgPorts();
|
||||||
@@ -1998,7 +2029,11 @@ void Module::OnWorldUpdate(std::chrono::milliseconds diff)
|
|||||||
|
|
||||||
// 2.6. Population shaper tick (Phase A of WORLD_POPULATION_PLAN).
|
// 2.6. Population shaper tick (Phase A of WORLD_POPULATION_PLAN).
|
||||||
// Internally rate-limits to its tick interval (default 60s) so
|
// 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();
|
const uint32 t0 = getMSTime();
|
||||||
Services::Population().OnWorldTick(static_cast<uint32>(total.count()));
|
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
|
// 2b. Bot guild manager tick (GUILD_PLAN.md Phase A.2). Internally
|
||||||
// 60s rate-limited; cheap to call every frame. Elects founders
|
// 60s rate-limited; cheap to call every frame. Elects founders
|
||||||
// when guild slots are open + sweeps stale name reservations
|
// when guild slots are open + sweeps stale name reservations
|
||||||
// + aborts founders that exceeded the 30-min FSM budget.
|
// + aborts founders that exceeded the 30-min FSM budget. This is
|
||||||
Services::Guilds().Tick(getMSTime());
|
// 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
|
// 2c. Craft-order board tick (#4B-2). Ages out stale Claimed orders
|
||||||
// (timeout -> Fail + refund so escrow can't be trapped behind a stuck
|
// (timeout -> Fail + refund so escrow can't be trapped behind a stuck
|
||||||
// crafter) and prunes finished rows. Self-rate-limited cheaply via a
|
// crafter) and prunes finished rows. Self-rate-limited cheaply via a
|
||||||
// local accumulator — the board only needs minute-granularity since
|
// 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;
|
craft_board_total_ += diff;
|
||||||
if (craft_board_total_ - last_craft_board_at_ >= kCraftBoardIntervalMs)
|
if (craft_board_total_ - last_craft_board_at_ >= kCraftBoardIntervalMs)
|
||||||
{
|
{
|
||||||
last_craft_board_at_ = craft_board_total_;
|
last_craft_board_at_ = craft_board_total_;
|
||||||
if (Services::Initialized())
|
if (Services::Initialized() && Services::Config().fleet_bots())
|
||||||
Services::CraftOrders().Tick(GameTime::GetGameTimeMS());
|
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)
|
void Module::OnPlayerJoinedBgQueue(Player* player, uint32 bg_type_id, uint8 bracket)
|
||||||
{
|
{
|
||||||
if (!initialized_ || !player) return;
|
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
|
// 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
|
// path — re-firing the fill for each would recurse). A bot GROUP LEADER
|
||||||
// is the exception (audit B25): a premade (guild BG-night /
|
// 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 (!initialized_ || !player) return;
|
||||||
if (player->GetSession() && player->GetSession()->IsBot()) 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 filler;
|
||||||
Fleet::BotQueueFiller::FillRequest req{};
|
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`).
|
Configuration lives in `playerbot.conf` (same directory as `worldserver.conf`).
|
||||||
Key settings:
|
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.TotalTarget` — fleet bot population target
|
||||||
- `Playerbot.V2.Population.Shape` — distribution shape (Pyramid, Bell, Flat, MaxHeavy)
|
- `Playerbot.V2.Population.Shape` — distribution shape (Pyramid, Bell, Flat, MaxHeavy)
|
||||||
- `Playerbot.V2.MaxAltsAsBots` — per-account altbot cap
|
- `Playerbot.V2.MaxAltsAsBots` — per-account altbot cap
|
||||||
|
|||||||
@@ -662,7 +662,12 @@ void Init()
|
|||||||
h->craft_orders->LoadFromDb();
|
h->craft_orders->LoadFromDb();
|
||||||
|
|
||||||
h->ai_pool->start();
|
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;
|
g_holder = h;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ void ConfigReader::apply_from_loaded_config()
|
|||||||
|
|
||||||
ai_worker_threads_ = c.GetIntDefault ("Playerbot.AiWorkerThreads", 0);
|
ai_worker_threads_ = c.GetIntDefault ("Playerbot.AiWorkerThreads", 0);
|
||||||
fleet_thread_enabled_ = c.GetBoolDefault("Playerbot.FleetThreadEnabled", true);
|
fleet_thread_enabled_ = c.GetBoolDefault("Playerbot.FleetThreadEnabled", true);
|
||||||
|
fleet_bots_enabled_ = c.GetBoolDefault("PlayerbotsV2.FleetBots", false);
|
||||||
tick_budget_ms_ = c.GetIntDefault ("Playerbot.TickBudgetMs", 10);
|
tick_budget_ms_ = c.GetIntDefault ("Playerbot.TickBudgetMs", 10);
|
||||||
// #5 Phase 4 parallel snapshot Build. DEFAULT ON. The 2026-06-15 crash
|
// #5 Phase 4 parallel snapshot Build. DEFAULT ON. The 2026-06-15 crash
|
||||||
// (ACCESS_VIOLATION in Build->objective_blacklisted, serial AND parallel) was
|
// (ACCESS_VIOLATION in Build->objective_blacklisted, serial AND parallel) was
|
||||||
|
|||||||
@@ -23,6 +23,19 @@ public:
|
|||||||
bool fleet_thread_enabled() const { return fleet_thread_enabled_; }
|
bool fleet_thread_enabled() const { return fleet_thread_enabled_; }
|
||||||
uint32 tick_budget_ms() const { return tick_budget_ms_; }
|
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
|
// #5 Phase 4: parallelize BotSnapshotBuilder::Build per Map* across a
|
||||||
// fixed worker pool during the world-thread snapshot window. When false
|
// fixed worker pool during the world-thread snapshot window. When false
|
||||||
// the snapshot loop runs exactly as before (serial, world thread). Kept
|
// the snapshot loop runs exactly as before (serial, world thread). Kept
|
||||||
@@ -282,6 +295,7 @@ private:
|
|||||||
// Cached typed values.
|
// Cached typed values.
|
||||||
uint32 ai_worker_threads_ = 0;
|
uint32 ai_worker_threads_ = 0;
|
||||||
bool fleet_thread_enabled_ = true;
|
bool fleet_thread_enabled_ = true;
|
||||||
|
bool fleet_bots_enabled_ = false;
|
||||||
uint32 tick_budget_ms_ = 10;
|
uint32 tick_budget_ms_ = 10;
|
||||||
bool parallel_snapshot_build_ = true;
|
bool parallel_snapshot_build_ = true;
|
||||||
uint32 snapshot_build_threads_ = 0;
|
uint32 snapshot_build_threads_ = 0;
|
||||||
|
|||||||
@@ -38,6 +38,18 @@ Playerbot.AiWorkerThreads = 0
|
|||||||
# HR-no
|
# HR-no
|
||||||
Playerbot.FleetThreadEnabled = 1
|
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.
|
# World tick budget for V2, in milliseconds. Bots skip ticks if exceeded.
|
||||||
# HR-yes
|
# HR-yes
|
||||||
Playerbot.TickBudgetMs = 10
|
Playerbot.TickBudgetMs = 10
|
||||||
|
|||||||
Reference in New Issue
Block a user