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 (docs/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 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; } 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; } // ---- account provisioning (docs/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); 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 (docs/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; 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; 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); 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); // 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})", Enabled, Host, Port, QueueCap, StatSweepSeconds, DecaySweepSeconds, EconomySweepSeconds, ChampSweepSeconds, AdminWriteEnabled, AdminAccessFloor, Signup, AccountCreateEnabled); } } }