diff --git a/src/Client/vMenu.Enhanced.Configuration/ClientConfig.cs b/src/Client/vMenu.Enhanced.Configuration/ClientConfig.cs
index 1620faa5..4602d4a9 100644
--- a/src/Client/vMenu.Enhanced.Configuration/ClientConfig.cs
+++ b/src/Client/vMenu.Enhanced.Configuration/ClientConfig.cs
@@ -18,12 +18,6 @@ public static class ClientConfig
private static readonly ConfigStore Store = new(Native.GetConvar, ForwardLog);
- public static event Action? Changed
- {
- add => Store.Changed += value;
- remove => Store.Changed -= value;
- }
-
/// Call once, before the menus are built, so the first gate pass reads real values.
public static void Initialize()
{
@@ -31,27 +25,49 @@ public static void Initialize()
ApplyLogLevel();
- // Any convar moving re-reads the level, because Changed does not say which one moved.
- Store.Changed += ApplyLogLevel;
+ Store.Watch([Debugging.LogLevel], ApplyLogLevel);
- // One listener per convar rather than a wildcard filter, which if it matched nothing would
- // look like the module quietly not working.
- foreach (var convar in Store.Tracked)
- {
- NativeFixer.AddConvarChangeListener(convar, OnConvarChanged);
- }
+ Listen(Store.Tracked);
SharedAPI.Commands.RegisterCommand(DumpCommand, false, DebugCommands.Gate(Dump));
}
+ /// Starts watching convars that are not settings, so listeners can be added for them.
+ /// See for why these are not in the catalog.
+ public static void Track(IReadOnlyList convars) => Listen(Store.Track(convars));
+
+ /// Calls whenever any of these settings changes, and nothing else.
+ public static void AddEventListenerFor(IReadOnlyList settings, Action handler) =>
+ Store.Watch(settings, handler);
+
+ /// The same, for convars registered through rather than catalogued settings.
+ public static void AddEventListenerFor(IReadOnlyList convars, Action handler) =>
+ Store.Watch(convars, handler);
+
+ ///
+ /// Calls whenever any setting other than these changes. For a
+ /// subscriber that really does react to almost anything, where naming the settings it reads
+ /// would mean one added later silently never reaching it.
+ ///
+ public static void AddEventListenerExcept(IReadOnlyList settings, Action handler) =>
+ Store.WatchExcept(settings, handler);
+
+ public static void RemoveEventListenerFor(IReadOnlyList settings, Action handler) =>
+ Store.Unwatch(settings, handler);
+
+ public static void RemoveEventListenerFor(IReadOnlyList convars, Action handler) =>
+ Store.Unwatch(convars, handler);
+
+ public static void RemoveEventListenerExcept(Action handler) => Store.UnwatchExcept(handler);
+
/// Prints what this client currently reads for every setting.
public static void Dump()
{
- Log.Debug("[Config] Current values:");
+ Log.Info("[Config] Current values:");
foreach (var line in Store.Describe())
{
- Log.Debug("[Config] " + line);
+ Log.Info("[Config] " + line);
}
}
@@ -79,6 +95,16 @@ public static void Dump()
public static string Value(StringSetting setting) => Store.Value(setting);
+ // One listener per convar rather than a wildcard filter, which if it matched nothing would look
+ // like the module quietly not working.
+ private static void Listen(IReadOnlyList convars)
+ {
+ foreach (var convar in convars)
+ {
+ NativeFixer.AddConvarChangeListener(convar, OnConvarChanged);
+ }
+ }
+
private static void OnConvarChanged(string convar, object? reserved) => Store.NotifyChanged(convar);
private static void ApplyLogLevel() => Log.SetLevel(Store.Value(Debugging.LogLevel));
diff --git a/src/Client/vMenu.Enhanced.Core/Main.cs b/src/Client/vMenu.Enhanced.Core/Main.cs
index 40a0df95..652d8cff 100644
--- a/src/Client/vMenu.Enhanced.Core/Main.cs
+++ b/src/Client/vMenu.Enhanced.Core/Main.cs
@@ -82,7 +82,6 @@ public async void Initialize()
LanguageLoader.Load();
- ClientConfig.Changed += TickRegistry.Reevaluate;
ClientPermissions.PermissionsChanged += TickRegistry.Reevaluate;
VehicleCommands.Initialize();
diff --git a/src/Client/vMenu.Enhanced.MenuFramework/HeaderStyle.cs b/src/Client/vMenu.Enhanced.MenuFramework/HeaderStyle.cs
index 95b59c5b..aad4a575 100644
--- a/src/Client/vMenu.Enhanced.MenuFramework/HeaderStyle.cs
+++ b/src/Client/vMenu.Enhanced.MenuFramework/HeaderStyle.cs
@@ -26,7 +26,13 @@ private static readonly (string Name, int Id)[] Fonts =
/// Call after , before the menus are built.
public static void Initialize()
{
- ClientConfig.Changed += Apply;
+ ClientConfig.AddEventListenerFor(
+ [
+ AppearanceSettings.TitleAlignment,
+ AppearanceSettings.TitleFont,
+ AppearanceSettings.HeaderGlare,
+ ],
+ Apply);
Apply();
}
diff --git a/src/Client/vMenu.Enhanced.MenuFramework/MenuRegistry.cs b/src/Client/vMenu.Enhanced.MenuFramework/MenuRegistry.cs
index 95f95ee5..3c6e00b1 100644
--- a/src/Client/vMenu.Enhanced.MenuFramework/MenuRegistry.cs
+++ b/src/Client/vMenu.Enhanced.MenuFramework/MenuRegistry.cs
@@ -1,10 +1,15 @@
using MenuAPI;
using vMenu.Enhanced.Configuration;
+using vMenu.Enhanced.Data.Configuration;
using vMenu.Enhanced.Logging;
using vMenu.Enhanced.MenuFramework.Localization;
using vMenu.Enhanced.Permissions;
+using DebuggingSettings = vMenu.Enhanced.Data.Configuration.Settings.Debugging;
+using KeyBindingSettings = vMenu.Enhanced.Data.Configuration.Settings.KeyBindings;
+using LocalizationSettings = vMenu.Enhanced.Data.Configuration.Settings.Localization;
+
namespace vMenu.Enhanced.MenuFramework;
/// Builds the menu tree and keeps it in step with permissions, configuration and language.
@@ -12,6 +17,25 @@ namespace vMenu.Enhanced.MenuFramework;
// menu, for one place to unsubscribe and a deterministic order.
public static class MenuRegistry
{
+ ///
+ /// The settings that provably cannot change what any menu shows, so a refresh pass over every
+ /// gate and every label is not worth running for them.
+ ///
+ private static readonly Setting[] Ignored =
+ [
+ DebuggingSettings.LogLevel,
+
+ // Read once by LanguageLoader before the menus are built.
+ // Can't be updated at runtime because the translation files would not be
+ // streamed to the client if changed without a resource restart.
+ LocalizationSettings.Languages,
+
+ // Keybinds get registered with the game at startup, so changing one takes a restart either way.
+ KeyBindingSettings.MenuToggleKey,
+ KeyBindingSettings.NoClipToggleKey,
+ KeyBindingSettings.TeleportKey,
+ ];
+
private static readonly List Hosts = [];
private static readonly Dictionary