using System; namespace Server.Custom.Bridge { /// /// Which side may mint game accounts. Governs the bridge's inbound account.create verb; /// the in-game first-login auto-create is a separate core setting (Accounts.AutoCreateAccounts) /// the operator pairs with this (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PROTOCOL_2.md §2). /// public enum SignupMode { Website, // website is the account authority; in-game auto-create should be off Game, // game server is the authority; account.create is refused Hybrid // either side may create } /// /// Tunables from Config/Bridge.cfg. Key scope is the filename, so `Port=7788` there /// reads as "Bridge.Port" here. /// /// Loaded in Configure(), which ScriptCompiler invokes before World.Load. /// public static class BridgeConfig { public static string Host { get; private set; } public static int Port { get; private set; } public static int QueueCap { get; private set; } public static int StatSweepSeconds { get; private set; } public static int DecaySweepSeconds { get; private set; } public static int EconomySweepSeconds { get; private set; } public static int PageSweepSeconds { get; private set; } public static int ChampSweepSeconds { get; private set; } public static int GuildSweepSeconds { get; private set; } public static int CitySweepSeconds { get; private set; } public static int PresenceSweepSeconds { get; private set; } public static int HousingSweepSeconds { get; private set; } public static int PointsSweepSeconds { get; private set; } public static int MarketSweepSeconds { get; private set; } // ---- guild rosters (Protocol 4) ---- public static int GuildRosterMembersPerLine { get; private set; } public static int GuildRosterGuildsPerTick { get; private set; } // ---- player-vendor market index (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v3.md §8) ---- public static bool MarketEnabled { get; private set; } public static int MarketSweepBatch { get; private set; } public static int MarketMaxListings { get; private set; } // ---- points / loyalty leaderboards (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v3.md §7) ---- public static bool PointsLeaderboardEnabled { get; private set; } public static int PointsTopN { get; private set; } public static string PointsSystems { get; private set; } public static bool PointsProfileEnabled { get; private set; } public static bool PointsProfileRank { get; private set; } // ---- shard ruleset (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v3.md §5) ---- public static bool RulesetEnabled { get; private set; } public static string PublicConnectAddress { get; private set; } public static bool RulesetIncludeSchedule { get; private set; } public static string LinkUrl { get; private set; } public static int TownCrierMaxLines { get; private set; } public static int TownCrierMaxLineLength { get; private set; } public static int TownCrierMaxActive { get; private set; } public static int TownCrierMaxDurationSec { get; private set; } // Town Cryer news gump (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PROTOCOL_2.md §16). public static int NewsMaxTitleLength { get; private set; } public static int NewsMaxBodyLength { get; private set; } public static int NewsMaxExternal { get; private set; } public static int NewsAnnounceDurationSec { get; private set; } public static bool AdminWriteEnabled { get; private set; } public static AccessLevel AdminAccessFloor { get; private set; } public static int AdminBroadcastMaxLength { get; private set; } public static int AdminReasonMaxLength { get; private set; } public static int AdminBanMaxDurationSec { get; private set; } // ---- the event plane (docs/link/v6.md §8, EVENTS_PLAN.md Phase 11b) ---- // // **Its own gate, deliberately not AdminWriteEnabled** (org lead, 2026-09-04). Enabling the // admin plane is an operator consenting to staff moderation driven from the website - a // human pressing kick or ban on a screen. A lease and a participation ledger are the // website changing and watching the world on a SCHEDULE, unattended, at four in the // morning. Those are different consents, and one switch cannot express both. public static bool EventsEnabled { get; private set; } // ---- the asset plane (docs/link/v8.md §3, protocol 8) ---- // // Its own gate again, and for the same reason the event plane got one: enabling this is // an operator consenting to the WEBSITE READING THEIR CLIENT FILES -- art, animations and // the string table, off the host's disk, over the link. That is a different consent from // publishing world state, and one switch cannot express both. Reads only: nothing on this // plane writes anything, anywhere. public static bool AssetsEnabled { get; private set; } public static int AssetBatchBytes { get; private set; } public static int LeaseMaxDurationSec { get; private set; } public static int LeaseGraceSec { get; private set; } public static int ParticipationSweepSeconds { get; private set; } public static double ParticipationKillWeight { get; private set; } public static int ParticipationMaxRuns { get; private set; } public static int ParticipationMaxMembers { get; private set; } public static int ParticipationMaxRadius { get; private set; } public static int ParticipationGraceSec { get; private set; } public static int ParticipationSnapshotChunk { get; private set; } // The world verbs (protocol 7, EVENTS_PLAN.md Phase 12a). Each of these is the shard's // OWN ceiling rather than a mirror of the module's budget dimension, and each REFUSES // rather than clamps -- BridgeLeases' argument for LeaseMaxDurationSec, unchanged: the // bound exists for the case where the website is wrong, and a quiet clamp would leave the // two halves disagreeing about what was actually placed. public static int EventsMaxCreatures { get; private set; } public static int EventsMaxBosses { get; private set; } public static int EventsMaxNpcs { get; private set; } public static int EventsMaxDecor { get; private set; } public static int EventsMaxGateMinutes { get; private set; } public static int EventsMaxOwnedPerRun { get; private set; } public static int EventsMaxSpread { get; private set; } public static double EventsMaxBossMultiplier { get; private set; } public static int EventsOracleMaxLines { get; private set; } public static int EventsOracleGreetRange { get; private set; } public static int EventsOracleSpeechRange { get; private set; } public static int EventsOracleGreetCooldownSec { get; private set; } public static int EventsOracleAnswerCooldownSec { get; private set; } public static int EventsSweepSeconds { get; private set; } public static int EventsMaxGrantPerRun { get; private set; } public static int EventsMaxGrantStack { get; private set; } public static int EventsMinSaveIntervalSec { get; private set; } // ---- account provisioning (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PROTOCOL_2.md Part A) ---- public static SignupMode Signup { get; private set; } public static bool AccountCreateEnabled { get; private set; } public static bool RequireIpForCreate { get; private set; } public static int AccountNameMaxLength { get; private set; } public static int AccountPasswordMaxLength { get; private set; } public static bool Enabled { get; private set; } public static void Configure() { Load(); } /// Re-readable at runtime via `[bridge reload`. public static void Load() { Enabled = Config.Get("Bridge.Enabled", true); Host = Config.Get("Bridge.Host", "127.0.0.1"); Port = Config.Get("Bridge.Port", 7788); QueueCap = Config.Get("Bridge.QueueCap", 10000); AssetsEnabled = Config.Get("Bridge.AssetsEnabled", true); // The largest reply this plane will build, in ENCODED bytes -- not items, because the // ceiling it has to live inside is a byte ceiling. Clamped to half the sidecar's 1 MiB // inbound line cap, and the halving is load-bearing rather than cautious: a page // always admits its first item even when that item alone exceeds the budget (the // alternative is an oversized item being skipped forever and its family never making // progress), so the wire must still have room for one such overshoot. AssetBatchBytes = Config.Get("Bridge.AssetBatchBytes", 512 * 1024); if (AssetBatchBytes < 64 * 1024) AssetBatchBytes = 64 * 1024; if (AssetBatchBytes > 512 * 1024) AssetBatchBytes = 512 * 1024; StatSweepSeconds = Config.Get("Bridge.StatSweepSeconds", 30); DecaySweepSeconds = Config.Get("Bridge.DecaySweepSeconds", 60); EconomySweepSeconds = Config.Get("Bridge.EconomySweepSeconds", 300); PageSweepSeconds = Config.Get("Bridge.PageSweepSeconds", 5); if (PageSweepSeconds < 1) PageSweepSeconds = 1; ChampSweepSeconds = Config.Get("Bridge.ChampSweepSeconds", 10); if (ChampSweepSeconds < 1) ChampSweepSeconds = 1; // Social/political sweeps (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PROTOCOL_2.md Part B). Both change slowly, so the // defaults are unhurried; the pass is a handful of field reads over a small set. GuildSweepSeconds = Config.Get("Bridge.GuildSweepSeconds", 60); if (GuildSweepSeconds < 1) GuildSweepSeconds = 1; // A roster line is the only fat frame this plugin emits — measured at roughly 69 bytes // per member — and the sidecar reads a line with no length bound. The cap turns an // unbounded frame into a bounded one; a guild above it is split across continuation // lines. 500 members is ~35 KB, comfortably past any real guild, so the split path is // an edge case rather than the norm. GuildRosterMembersPerLine = Config.Get("Bridge.GuildRosterMembersPerLine", 500); if (GuildRosterMembersPerLine < 16) GuildRosterMembersPerLine = 16; // How many guilds may emit a roster in a single sweep. Every guild re-emits after a // reconnect (the diff caches are cleared), and building a few hundred fat JSON frames in // one Core-thread pass is exactly the stall this bridge exists to avoid. The sweep // re-arms itself promptly while a baseline is still draining, so this throttles the work // without making the site wait a full sweep interval per batch. GuildRosterGuildsPerTick = Config.Get("Bridge.GuildRosterGuildsPerTick", 25); if (GuildRosterGuildsPerTick < 1) GuildRosterGuildsPerTick = 1; CitySweepSeconds = Config.Get("Bridge.CitySweepSeconds", 300); if (CitySweepSeconds < 1) CitySweepSeconds = 1; PresenceSweepSeconds = Config.Get("Bridge.PresenceSweepSeconds", 30); if (PresenceSweepSeconds < 1) PresenceSweepSeconds = 1; HousingSweepSeconds = Config.Get("Bridge.HousingSweepSeconds", 300); if (HousingSweepSeconds < 1) HousingSweepSeconds = 1; // Points/loyalty boards. The sweep touches every point entry on the shard, and ten of // the ~25 systems keep a row per character ever created, so the default interval is // deliberately slow — these are month-scale standings, not live state. PointsSweepSeconds = Config.Get("Bridge.PointsSweepSeconds", 300); if (PointsSweepSeconds < 1) PointsSweepSeconds = 1; PointsLeaderboardEnabled = Config.Get("Bridge.PointsLeaderboardEnabled", true); // Board size. Bounded below at 1 because the selection indexes the Nth slot directly, // and above at 100 because the frame is emitted per system — a large N multiplied by // ~25 systems is how a "board" turns into a bandwidth problem. PointsTopN = Config.Get("Bridge.PointsTopN", 10); if (PointsTopN < 1) PointsTopN = 1; if (PointsTopN > 100) PointsTopN = 100; // Blank (the default) means "publish whatever the shard itself shows on the loyalty // gump", so a shard that adds a subsystem gets its board without an edit here. PointsSystems = Config.Get("Bridge.PointsSystems", ""); PointsProfileEnabled = Config.Get("Bridge.PointsProfileEnabled", true); // Off by default, and the default is the point: a rank cannot early-exit the way a // points lookup can — it must count every row that beats the player, in every system, // on every profile build. See BridgeProfile.WritePoints. PointsProfileRank = Config.Get("Bridge.PointsProfileRank", false); // Player-vendor market index. Unlike every other sweep, this one does NOT walk its whole // collection per tick: MarketSweepBatch caps how many vendors are inventoried, and a // persistent cursor round-robins through the rest, so the per-tick cost is bounded by // the batch rather than by how many vendors the world holds. MarketEnabled = Config.Get("Bridge.MarketEnabled", true); MarketSweepSeconds = Config.Get("Bridge.MarketSweepSeconds", 60); if (MarketSweepSeconds < 1) MarketSweepSeconds = 1; // Bounded below at 1 (a batch of 0 would advance the cursor nowhere and publish nothing, // silently) and above at 500, past which the batch stops bounding anything on any // realistic shard and the tick is a whole-world pass by another name. MarketSweepBatch = Config.Get("Bridge.MarketSweepBatch", 25); if (MarketSweepBatch < 1) MarketSweepBatch = 1; if (MarketSweepBatch > 500) MarketSweepBatch = 500; // Per-vendor listing cap. BridgeJson.Parse caps INBOUND frames at 1 MB; outbound is // uncapped and the sidecar's read_line will allocate whatever arrives, so the cap here // is what keeps one commodity reseller with 8,000 stacked resources from emitting a // multi-megabyte frame. Over the cap the frame carries "truncated": true and the site // says so. MarketMaxListings = Config.Get("Bridge.MarketMaxListings", 250); if (MarketMaxListings < 1) MarketMaxListings = 1; if (MarketMaxListings > 5000) MarketMaxListings = 5000; // The ruleset frame is not a sweep — it is emitted once per sidecar connect (and on // `[bridge reload`), so it has no interval. PublicConnectAddress is the ONE connection // detail the bridge will publish, and only because an operator typed it here for that // purpose; Server.cfg's Address/Port are never read (see BridgeRuleset's allowlist note). RulesetEnabled = Config.Get("Bridge.RulesetEnabled", true); PublicConnectAddress = Config.Get("Bridge.PublicConnectAddress", ""); RulesetIncludeSchedule = Config.Get("Bridge.RulesetIncludeSchedule", true); LinkUrl = Config.Get("Bridge.LinkUrl", "https://yoursite/link"); TownCrierMaxLines = Config.Get("Bridge.TownCrierMaxLines", 6); TownCrierMaxLineLength = Config.Get("Bridge.TownCrierMaxLineLength", 200); TownCrierMaxActive = Config.Get("Bridge.TownCrierMaxActive", 20); TownCrierMaxDurationSec = Config.Get("Bridge.TownCrierMaxDurationSec", 86400); NewsMaxTitleLength = Config.Get("Bridge.NewsMaxTitleLength", 100); NewsMaxBodyLength = Config.Get("Bridge.NewsMaxBodyLength", 2000); NewsMaxExternal = Config.Get("Bridge.NewsMaxExternal", 20); NewsAnnounceDurationSec = Config.Get("Bridge.NewsAnnounceDurationSec", 300); if (NewsAnnounceDurationSec < 1) NewsAnnounceDurationSec = 1; AdminWriteEnabled = Config.Get("Bridge.AdminWriteEnabled", false); AdminAccessFloor = ParseAccessLevel(Config.Get("Bridge.AdminAccessFloor", "CoOwner"), AccessLevel.CoOwner); AdminBroadcastMaxLength = Config.Get("Bridge.AdminBroadcastMaxLength", 300); AdminReasonMaxLength = Config.Get("Bridge.AdminReasonMaxLength", 400); AdminBanMaxDurationSec = Config.Get("Bridge.AdminBanMaxDurationSec", 31536000); // The event plane. Off until an operator says otherwise - see the field block above for // why this is not AdminWriteEnabled. EventsEnabled = Config.Get("Bridge.EventsEnabled", false); // Thirty days, matching core's own MAX_LEASE_MS. This is the shard's INDEPENDENT // ceiling rather than a mirror of it: the website bounds what it will ask for, and a // shard that trusted the asking would have no bound of its own at the one moment it // matters, which is when the website is wrong. LeaseMaxDurationSec = Config.Get("Bridge.LeaseMaxDurationSec", 2592000); if (LeaseMaxDurationSec < 1) LeaseMaxDurationSec = 1; // How long a finished lease stays listed after its deadline restored it, so teardown // still gets a definite verdict rather than finding nothing and having to guess. LeaseGraceSec = Config.Get("Bridge.LeaseGraceSec", 86400); if (LeaseGraceSec < 0) LeaseGraceSec = 0; ParticipationSweepSeconds = Config.Get("Bridge.ParticipationSweepSeconds", 30); if (ParticipationSweepSeconds < 1) ParticipationSweepSeconds = 1; // What one kill inside the area is worth against one minute of standing in it. Both // halves live on the shard because the score IS the shard's number: core stores an // opaque decimal it never interprets, so a weight core could edit would be a weight // nobody could explain from either side. ParticipationKillWeight = Config.Get("Bridge.ParticipationKillWeight", 5.0); if (ParticipationKillWeight < 0.0) ParticipationKillWeight = 0.0; ParticipationMaxRuns = Config.Get("Bridge.ParticipationMaxRuns", 8); if (ParticipationMaxRuns < 1) ParticipationMaxRuns = 1; ParticipationMaxMembers = Config.Get("Bridge.ParticipationMaxMembers", 2000); if (ParticipationMaxMembers < 1) ParticipationMaxMembers = 1; // A radius, not a rectangle, and bounded: an area big enough to cover a facet makes // "took part" meaningless and the sweep expensive in the same stroke. ParticipationMaxRadius = Config.Get("Bridge.ParticipationMaxRadius", 300); if (ParticipationMaxRadius < 1) ParticipationMaxRadius = 1; ParticipationGraceSec = Config.Get("Bridge.ParticipationGraceSec", 86400); if (ParticipationGraceSec < 0) ParticipationGraceSec = 0; // How many members one snapshot resolves before yielding the Core thread. See // BridgeParticipation: this is what makes the handler DEFER, which is what makes // `bridge.busy` reachable at all. ParticipationSnapshotChunk = Config.Get("Bridge.ParticipationSnapshotChunk", 100); if (ParticipationSnapshotChunk < 1) ParticipationSnapshotChunk = 1; // The world verbs. PEC's published quotas are the defaults, because they are the only // numbers anyone has ever defended in public: 30 creatures, a handful of bosses, five // NPCs of five lines each, a four-hour gate. See EVENTS.md's PEC section. EventsMaxCreatures = Config.Get("Bridge.EventsMaxCreatures", 30); if (EventsMaxCreatures < 1) EventsMaxCreatures = 1; EventsMaxBosses = Config.Get("Bridge.EventsMaxBosses", 4); if (EventsMaxBosses < 1) EventsMaxBosses = 1; EventsMaxNpcs = Config.Get("Bridge.EventsMaxNpcs", 5); if (EventsMaxNpcs < 1) EventsMaxNpcs = 1; EventsMaxDecor = Config.Get("Bridge.EventsMaxDecor", 60); if (EventsMaxDecor < 1) EventsMaxDecor = 1; EventsMaxGateMinutes = Config.Get("Bridge.EventsMaxGateMinutes", 240); if (EventsMaxGateMinutes < 1) EventsMaxGateMinutes = 1; // The whole run, across every verb. The per-verb ceilings above bound one CALL; this // bounds a run that calls a verb in a loop, which is the shape a runaway schedule // actually takes. EventsMaxOwnedPerRun = Config.Get("Bridge.EventsMaxOwnedPerRun", 200); if (EventsMaxOwnedPerRun < 1) EventsMaxOwnedPerRun = 1; EventsMaxSpread = Config.Get("Bridge.EventsMaxSpread", 40); if (EventsMaxSpread < 0) EventsMaxSpread = 0; // "An enhanced regular mob", per EVENTS.md's boss row -- so a ceiling low enough that // the result is still recognisably the creature the author picked. EventsMaxBossMultiplier = Config.Get("Bridge.EventsMaxBossMultiplier", 10.0); if (EventsMaxBossMultiplier < 1.0) EventsMaxBossMultiplier = 1.0; EventsOracleMaxLines = Config.Get("Bridge.EventsOracleMaxLines", 5); if (EventsOracleMaxLines < 1) EventsOracleMaxLines = 1; EventsOracleGreetRange = Config.Get("Bridge.EventsOracleGreetRange", 4); if (EventsOracleGreetRange < 1) EventsOracleGreetRange = 1; EventsOracleSpeechRange = Config.Get("Bridge.EventsOracleSpeechRange", 8); if (EventsOracleSpeechRange < 1) EventsOracleSpeechRange = 1; EventsOracleGreetCooldownSec = Config.Get("Bridge.EventsOracleGreetCooldownSec", 60); if (EventsOracleGreetCooldownSec < 0) EventsOracleGreetCooldownSec = 0; EventsOracleAnswerCooldownSec = Config.Get("Bridge.EventsOracleAnswerCooldownSec", 5); if (EventsOracleAnswerCooldownSec < 0) EventsOracleAnswerCooldownSec = 0; // How often expired gates are collected and dead ownership rows pruned. Gates are a // minutes-scale deadline, so one slow sweep beats a timer per object. EventsSweepSeconds = Config.Get("Bridge.EventsSweepSeconds", 30); if (EventsSweepSeconds < 1) EventsSweepSeconds = 1; // Phase 12b. How many characters one grant may reach, and how many of one item may go // into one hand. Both refuse rather than clamp, on `LeaseMaxDurationSec`'s argument: // the website records what was handed out, and a silent clamp would make its ledger a // description of a grant that did not happen. EventsMaxGrantPerRun = Config.Get("Bridge.EventsMaxGrantPerRun", 200); if (EventsMaxGrantPerRun < 0) EventsMaxGrantPerRun = 0; EventsMaxGrantStack = Config.Get("Bridge.EventsMaxGrantStack", 1000); if (EventsMaxGrantStack < 1) EventsMaxGrantStack = 1; // A save stops the world, so this one is a rate limit rather than a cap. It counts from // the last save by ANYBODY -- ServUO's own autosave included -- because an event save // thirty seconds after the hourly one is the same freeze twice, and this shard is the // only half that can see both. EventsMinSaveIntervalSec = Config.Get("Bridge.EventsMinSaveIntervalSec", 300); if (EventsMinSaveIntervalSec < 0) EventsMinSaveIntervalSec = 0; // Account provisioning. An absent SignupMode defaults to Hybrid; a *present but // unrecognized* value falls back to Game (the safest — no website creation), so a // typo can never accidentally open provisioning. Signup = ParseSignupMode(Config.Get("Bridge.SignupMode", "hybrid"), SignupMode.Game); // Default follows the mode: creation is on unless the shard is game-authority. AccountCreateEnabled = Config.Get("Bridge.AccountCreateEnabled", Signup != SignupMode.Game); RequireIpForCreate = Config.Get("Bridge.RequireIpForCreate", true); AccountNameMaxLength = Config.Get("Bridge.AccountNameMaxLength", 16); AccountPasswordMaxLength = Config.Get("Bridge.AccountPasswordMaxLength", 30); if (AccountNameMaxLength < 1) AccountNameMaxLength = 1; if (AccountPasswordMaxLength < 1) AccountPasswordMaxLength = 1; if (QueueCap < 16) QueueCap = 16; WarnOnSignupMismatch(); } /// /// The bridge governs only the account.create verb; ServUO's in-game first-login /// auto-create is the core Accounts.AutoCreateAccounts setting. A shard whose two halves /// disagree is quietly broken (website-only that still auto-creates in game, or a mode /// that expects in-game creation with it switched off), so surface the contradiction /// loudly rather than silently doing the permissive thing. /// private static void WarnOnSignupMismatch() { var autoCreate = Config.Get("Accounts.AutoCreateAccounts", true); if (Signup == SignupMode.Website && autoCreate) Console.WriteLine( "[Bridge] WARNING: SignupMode=website but Accounts.AutoCreateAccounts=true; " + "an in-game login of any new name still mints an account. Set it false for website-only."); else if (Signup == SignupMode.Game && !autoCreate) Console.WriteLine( "[Bridge] WARNING: SignupMode=game but Accounts.AutoCreateAccounts=false; " + "in-game creation is off and account.create is refused, so no account can be created."); else if (Signup == SignupMode.Hybrid && !autoCreate) Console.WriteLine( "[Bridge] WARNING: SignupMode=hybrid but Accounts.AutoCreateAccounts=false; " + "in-game first-login creation is off. Only website account.create will work."); } /// /// Parses a SignupMode name, case-insensitively, falling back to /// on anything unrecognized so a typo can never open provisioning wider than intended. /// private static SignupMode ParseSignupMode(string value, SignupMode fallback) { SignupMode parsed; if (!String.IsNullOrEmpty(value) && Enum.TryParse(value.Trim(), true, out parsed) && Enum.IsDefined(typeof(SignupMode), parsed)) return parsed; Console.WriteLine("[Bridge] unrecognized SignupMode '{0}', using {1}", value, fallback); return fallback; } /// /// Parses an AccessLevel name from config, case-insensitively, falling back to the given /// default on anything unrecognized so a typo can never open the floor wider than intended. /// private static AccessLevel ParseAccessLevel(string value, AccessLevel fallback) { AccessLevel parsed; if (!String.IsNullOrEmpty(value) && Enum.TryParse(value.Trim(), true, out parsed) && Enum.IsDefined(typeof(AccessLevel), parsed)) return parsed; Console.WriteLine("[Bridge] unrecognized AdminAccessFloor '{0}', using {1}", value, fallback); return fallback; } public static string Describe() { return String.Format( "enabled={0} endpoint={1}:{2} queueCap={3} sweeps(stat={4}s decay={5}s econ={6}s champ={7}s) adminWrite={8}(floor={9}) signup={10}(create={11}) events={12}", Enabled, Host, Port, QueueCap, StatSweepSeconds, DecaySweepSeconds, EconomySweepSeconds, ChampSweepSeconds, AdminWriteEnabled, AdminAccessFloor, Signup, AccountCreateEnabled, EventsEnabled); } } }