From 35e3a31b4c3af5668f0f0e2d3045b328ffb26b57 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Wed, 16 Sep 2026 00:42:12 -0700 Subject: [PATCH] fix(feature-flags): define stock flag defaults in code so JSONs exist on first boot (#2653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Summary `default-flags.json` was never shipped — `Configuration/` is gitignored — so the predefined-flag loader has been dead since #2328. A fresh shard boots with 0 flags and writes no JSON until an admin changes something, so `[FeatureList` is empty on first run. Stock flags are now defined in code. `Initialize()` runs `Load()` first, then `LoadDefaultFlags()` seeds any of the 14 stock keys the save is missing, reading each default from the static it syncs (`ServerFeatureFlags` / `ContentFeatureFlags`) rather than a duplicated boolean — so `speedhack_detection` stays off and `insurance` honors `Insurance.Configure` (`insurance.enable`). Existing entries are never overwritten, so admin state survives upgrades and saves predating a flag pick it up. `Save()` runs only when something was seeded, so all five JSON files exist from first boot. Verified: `dotnet build Projects/UOContent/UOContent.csproj -c Release`, 0 warnings 0 errors. --- .../FeatureFlags/FeatureFlagManager.cs | 54 ++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/Projects/UOContent/Engines/FeatureFlags/FeatureFlagManager.cs b/Projects/UOContent/Engines/FeatureFlags/FeatureFlagManager.cs index e599aeeb6..587ffa91b 100644 --- a/Projects/UOContent/Engines/FeatureFlags/FeatureFlagManager.cs +++ b/Projects/UOContent/Engines/FeatureFlags/FeatureFlagManager.cs @@ -40,9 +40,8 @@ public static class FeatureFlagManager Directory.CreateDirectory(savePath); } - // Load predefined flags from JSON, then overlay runtime state - LoadDefaultFlags(); Load(); + LoadDefaultFlags(); _initialized = true; logger.Information( @@ -751,14 +750,55 @@ public static class FeatureFlagManager private static void LoadDefaultFlags() { - var defaultFlagsPath = Path.Combine(Core.BaseDirectory, "Configuration", "FeatureFlags", "default-flags.json"); - var defaultFlags = JsonConfig.Deserialize>(defaultFlagsPath); - if (defaultFlags != null) + (string Key, string Category, string Description, bool Enabled)[] defaults = + [ + ("player_trading", "Economy", "Allow secure trades between players", ServerFeatureFlags.PlayerTrading), + ("pvp_combat", "Combat", "Allow player vs player combat", ServerFeatureFlags.PvPCombat), + ("bank_access", "Economy", "Allow players to access their bank boxes", ServerFeatureFlags.BankAccess), + ("speedhack_detection", "System", "Enable speedhack detection", ServerFeatureFlags.SpeedhackDetection), + ("insurance", "Economy", "Enable item insurance", ServerFeatureFlags.InsuranceEnabled), + ("vendor_purchase", "Economy", "Allow purchasing from NPC vendors", ContentFeatureFlags.VendorPurchase), + ("vendor_sell", "Economy", "Allow selling to NPC vendors", ContentFeatureFlags.VendorSell), + ("player_vendors", "Economy", "Allow player vendor interactions", ContentFeatureFlags.PlayerVendors), + ("house_placement", "Housing", "Allow new house placements", ContentFeatureFlags.HousePlacement), + ("boat_placement", "Housing", "Allow new boat placements", ContentFeatureFlags.BoatPlacement), + ("bulk_orders", "Crafting", "Allow bulk order deeds", ContentFeatureFlags.BulkOrders), + ("passive_detect_hidden", "System", "Enable passive detect hidden", ContentFeatureFlags.PassiveDetectHidden), + ("young_player_system", "System", "Enable the young player system", ContentFeatureFlags.YoungPlayerSystem), + ("bitmap_pathfinding_cache", "Performance", "Enable the bitmap pathfinding cache", ContentFeatureFlags.BitmapPathfindingCache), + ]; + + var now = Core.Now; + var added = 0; + + for (var i = 0; i < defaults.Length; i++) { - foreach (var flag in defaultFlags) + var (key, category, description, enabled) = defaults[i]; + if (_flags.ContainsKey(key)) { - _flags.TryAdd(flag.Key, flag); + continue; } + + _flags.Add( + key, + new FeatureFlag + { + Key = key, + Description = description, + Category = category, + DefaultEnabled = enabled, + Enabled = enabled, + LastModified = now, + LastModifiedBy = "System" + } + ); + added++; + } + + if (added > 0) + { + Save(); + logger.Information("Seeded {Count} default feature flag(s)", added); } }