diff --git a/Robust.Client/Configuration/ClientNetConfigurationManager.cs b/Robust.Client/Configuration/ClientNetConfigurationManager.cs
index 8722cb1049a..0381e664fe8 100644
--- a/Robust.Client/Configuration/ClientNetConfigurationManager.cs
+++ b/Robust.Client/Configuration/ClientNetConfigurationManager.cs
@@ -7,6 +7,8 @@
using Robust.Shared.Network;
using Robust.Shared.Replays;
using Robust.Shared.Utility;
+using Robust.Shared.Player;
+using Robust.Client.Player;
namespace Robust.Client.Configuration;
@@ -15,6 +17,7 @@ internal sealed class ClientNetConfigurationManager : NetConfigurationManager, I
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IReplayRecordingManager _replay = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
private bool _receivedInitialNwVars = false;
@@ -136,4 +139,51 @@ private void ApplyClientNetVarChange(List<(string name, object value)> networked
///
public override T GetClientCVar(INetChannel channel, string name) => GetCVar(name);
+
+ public override void OnClientCVarChanges(string name, Action onChanged)
+ {
+ if (_player.LocalSession is not { } localSession)
+ {
+ Sawmill.Error("Got null local session for client!");
+ return;
+ }
+
+ OnValueChanged(name, (x) => onChanged(x, localSession), true);
+ }
+
+ ///
+ public override void OnClientCVarChanges(string name, ClientCVarChanged onChanged)
+ {
+ if (_player.LocalSession is not { } localSession)
+ {
+ Sawmill.Error("Got null local session for client!");
+ return;
+ }
+
+ OnValueChanged(name, (T newValue, in CVarChangeInfo info) => onChanged(localSession, newValue, in info), true);
+ }
+
+ ///
+ public override void UnsubClientCVarChanges(string name, Action onChanged)
+ {
+ if (_player.LocalSession is not { } localSession)
+ {
+ Sawmill.Error("Got null local session for client!");
+ return;
+ }
+
+ UnsubValueChanged(name, (x) => onChanged(x, localSession));
+ }
+
+ ///
+ public override void UnsubClientCVarChanges(string name, ClientCVarChanged onChanged)
+ {
+ if (_player.LocalSession is not { } localSession)
+ {
+ Sawmill.Error("Got null local session for client!");
+ return;
+ }
+
+ UnsubValueChanged(name, (T newValue, in CVarChangeInfo info) => onChanged(localSession, newValue, in info));
+ }
}
diff --git a/Robust.Server/Configuration/ServerNetConfigurationManager.cs b/Robust.Server/Configuration/ServerNetConfigurationManager.cs
index 61e52181811..f3a5a2ff18e 100644
--- a/Robust.Server/Configuration/ServerNetConfigurationManager.cs
+++ b/Robust.Server/Configuration/ServerNetConfigurationManager.cs
@@ -1,11 +1,15 @@
+using Robust.Server.Player;
+using Robust.Shared.Collections;
using Robust.Shared.Configuration;
+using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
+using Robust.Shared.Player;
+using Robust.Shared.Replays;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
+using System;
using System.Collections.Generic;
-using Robust.Shared.IoC;
-using Robust.Shared.Replays;
namespace Robust.Server.Configuration;
@@ -13,9 +17,13 @@ internal sealed class ServerNetConfigurationManager : NetConfigurationManager, I
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly Dictionary> _replicatedCVars = new();
+ private readonly Dictionary _replicatedInvokes = new();
+
+
public override void SetupNetworking()
{
base.SetupNetworking();
@@ -26,7 +34,10 @@ public override void SetupNetworking()
public override void Shutdown()
{
base.Shutdown();
+
+
_replicatedCVars.Clear();
+ _replicatedInvokes.Clear();
}
private void PeerConnected(object? sender, NetChannelArgs e)
@@ -106,31 +117,187 @@ protected override void ApplyNetVarChange(
return;
}
- using var _ = Lock.ReadGuard();
+ var cVarChanges = new List();
+ using (Lock.ReadGuard())
+ {
+ foreach (var (name, value) in networkedVars)
+ {
+ if (!_configVars.TryGetValue(name, out var cVar))
+ {
+ Sawmill.Warning($"{msgChannel} tried to replicate an unknown CVar '{name}.'");
+ continue;
+ }
+
+ if (!cVar.Registered)
+ {
+ Sawmill.Warning($"{msgChannel} tried to replicate an unregistered CVar '{name}.'");
+ continue;
+ }
- foreach (var (name, value) in networkedVars)
+ if ((cVar.Flags & CVar.REPLICATED) != 0)
+ {
+ clientCVars.TryGetValue(name, out var oldValue);
+ cVarChanges.Add(new(name, tick, value, oldValue ?? value));
+ clientCVars[name] = value;
+ Sawmill.Debug($"name={name}, val={value}");
+ }
+ else
+ {
+ Sawmill.Warning($"{msgChannel} tried to replicate an un-replicated CVar '{name}.'");
+ }
+ }
+ }
+
+ foreach (var info in cVarChanges)
+ {
+ InvokeClientCvarChange(info, msgChannel);
+ }
+ }
+
+ private void InvokeClientCvarChange(CVarChangeInfo info, INetChannel msgChannel)
+ {
+ if (!_playerManager.TryGetSessionByChannel(msgChannel, out var session))
{
- if (!_configVars.TryGetValue(name, out var cVar))
+ Sawmill.Error($"Got client cvar change for NetChannel {msgChannel.UserId} without session!");
+ return;
+ }
+
+ if (!_replicatedInvokes.TryGetValue(info.Name, out var cVarInvokes))
+ return;
+
+ foreach (var entry in cVarInvokes.ClientChangeInvoke.Entries)
+ {
+ try
+ {
+ entry.Value!.Invoke(info.NewValue, session, in info);
+ }
+ catch (Exception e)
{
- Sawmill.Warning($"{msgChannel} tried to replicate an unknown CVar '{name}.'");
- continue;
+ Sawmill.Error($"Error while running {nameof(ClientValueChangedDelegate)} for replicated CVars callback: {e}");
}
+ }
+ }
+
+ ///
+ public override void OnClientCVarChanges(string name, Action onChanged)
+ {
+ if (!_configVars.TryGetValue(name, out var cVar))
+ {
+ Sawmill.Error($"Tried to subscribe an unknown CVar '{name}.'");
+ return;
+ }
- if (!cVar.Registered)
+ if (!cVar.Flags.HasFlag(CVar.REPLICATED) || !cVar.Flags.HasFlag(CVar.CLIENT))
+ {
+ Sawmill.Error($"Tried to subscribe server to client cvar '{name}' but cvar don't have flags CLIENT | REPLICATED");
+ return;
+ }
+
+ using (Lock.WriteGuard())
+ {
+ if (!_replicatedInvokes.TryGetValue(name, out var cVarInvokes))
+ {
+ cVarInvokes = new ReplicatedCVarInvokes { };
+ cVarInvokes.ClientChangeInvoke.AddInPlace((object value, ICommonSession session, in CVarChangeInfo _) => onChanged((T)value, session), onChanged);
+
+ _replicatedInvokes.Add(name, cVarInvokes);
+ }
+ else
{
- Sawmill.Warning($"{msgChannel} tried to replicate an unregistered CVar '{name}.'");
- continue;
+ cVarInvokes.ClientChangeInvoke.AddInPlace((object value, ICommonSession session, in CVarChangeInfo _) => onChanged((T)value, session), onChanged);
}
+ }
+ }
+
+ ///
+ public override void OnClientCVarChanges(string name, ClientCVarChanged onChanged)
+ {
+ if (!_configVars.TryGetValue(name, out var cVar))
+ {
+ Sawmill.Error($"Tried to subscribe an unknown CVar '{name}.'");
+ return;
+ }
- if ((cVar.Flags & CVar.REPLICATED) != 0)
+ if (!cVar.Flags.HasFlag(CVar.REPLICATED) || !cVar.Flags.HasFlag(CVar.CLIENT))
+ {
+ Sawmill.Error($"Tried to subscribe server to client cvar '{name}' but cvar don't have flags CLIENT | REPLICATED");
+ return;
+ }
+
+ using (Lock.WriteGuard())
+ {
+ if (!_replicatedInvokes.TryGetValue(name, out var cVarInvokes))
{
- clientCVars[name] = value;
- Sawmill.Debug($"name={name}, val={value}");
+ cVarInvokes = new ReplicatedCVarInvokes { };
+ cVarInvokes.ClientChangeInvoke.AddInPlace((object value, ICommonSession session, in CVarChangeInfo info) => onChanged(session, (T)value, info), onChanged);
+
+ _replicatedInvokes.Add(name, cVarInvokes);
}
else
{
- Sawmill.Warning($"{msgChannel} tried to replicate an un-replicated CVar '{name}.'");
+ cVarInvokes.ClientChangeInvoke.AddInPlace((object value, ICommonSession session, in CVarChangeInfo info) => onChanged(session, (T)value , info), onChanged);
}
}
}
+
+ ///
+ public override void UnsubClientCVarChanges(string name, Action onValueChanged)
+ {
+ if (!_configVars.TryGetValue(name, out var cVar))
+ {
+ Sawmill.Error($"Tried to unsubscribe an unknown CVar '{name}.'");
+ return;
+ }
+
+ if (!cVar.Flags.HasFlag(CVar.REPLICATED) || !cVar.Flags.HasFlag(CVar.CLIENT))
+ {
+ Sawmill.Error($"Tried to unsubscribe client cvar '{name}' without flags CLIENT | REPLICATED");
+ return;
+ }
+
+ using (Lock.WriteGuard())
+ {
+ if (!_replicatedInvokes.TryGetValue(name, out var cVarInvokes))
+ {
+ Sawmill.Warning($"Trying to unsubscribe for cvar {name} changes that dont have any subscriptions at all!");
+ return;
+ }
+
+ cVarInvokes.ClientChangeInvoke.RemoveInPlace(onValueChanged);
+ }
+ }
+
+ ///
+ public override void UnsubClientCVarChanges(string name, ClientCVarChanged onChanged)
+ {
+ if (!_configVars.TryGetValue(name, out var cVar))
+ {
+ Sawmill.Error($"Tried to unsubscribe an unknown CVar '{name}.'");
+ return;
+ }
+
+ if (!cVar.Flags.HasFlag(CVar.REPLICATED) || !cVar.Flags.HasFlag(CVar.CLIENT))
+ {
+ Sawmill.Error($"Tried to unsubscribe client cvar '{name}' without flags CLIENT | REPLICATED");
+ return;
+ }
+
+ using (Lock.WriteGuard())
+ {
+ if (!_replicatedInvokes.TryGetValue(name, out var cVarInvokes))
+ {
+ Sawmill.Warning($"Trying to unsubscribe for cvar {name} changes that dont have any subscriptions at all!");
+ return;
+ }
+
+ cVarInvokes.ClientChangeInvoke.RemoveInPlace(onChanged);
+ }
+ }
+
+ private delegate void DisconnectDelegate(ICommonSession session);
+
+ private sealed class ReplicatedCVarInvokes
+ {
+ public InvokeList ClientChangeInvoke = new();
+ }
}
diff --git a/Robust.Shared.IntegrationTests/Configuration/ConfigurationManagerTest.cs b/Robust.Shared.IntegrationTests/Configuration/ConfigurationManagerTest.cs
index ef84ada7194..744b2a42217 100644
--- a/Robust.Shared.IntegrationTests/Configuration/ConfigurationManagerTest.cs
+++ b/Robust.Shared.IntegrationTests/Configuration/ConfigurationManagerTest.cs
@@ -1,6 +1,7 @@
using Moq;
using NUnit.Framework;
using Robust.Server.Configuration;
+using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -142,6 +143,7 @@ private IConfigurationManager MakeCfg()
var collection = new DependencyCollection();
collection.RegisterInstance(new Mock().Object);
collection.RegisterInstance(new Mock().Object);
+ collection.RegisterInstance(new Mock().Object);
collection.Register();
collection.Register();
collection.Register();
diff --git a/Robust.Shared.IntegrationTests/Configuration/NetConfigurationManagerTest.cs b/Robust.Shared.IntegrationTests/Configuration/NetConfigurationManagerTest.cs
new file mode 100644
index 00000000000..5b475f330fa
--- /dev/null
+++ b/Robust.Shared.IntegrationTests/Configuration/NetConfigurationManagerTest.cs
@@ -0,0 +1,239 @@
+using NUnit.Framework;
+using Robust.Shared.Configuration;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Robust.UnitTesting.Shared.Configuration;
+
+[TestFixture]
+[Parallelizable(ParallelScope.All)]
+[TestOf(typeof(NetConfigurationManager))]
+internal sealed class NetConfigurationManagerTest : RobustIntegrationTest
+{
+ [Test]
+ public async Task TestSubscribeUnsubscribe()
+ {
+ using var server = StartServer();
+ using var client = StartClient();
+
+ await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
+
+ var serverNetConfiguration = server.ResolveDependency();
+ var clientNetConfiguration = client.ResolveDependency();
+
+ // CVar def consts
+ const string CVarName = "net.foo_bar";
+ const CVar CVarFlags = CVar.CLIENT | CVar.REPLICATED;
+ const int DefaultValue = 1;
+
+ // setup debug CVar
+ server.Post(() =>
+ {
+ serverNetConfiguration.RegisterCVar(CVarName, DefaultValue, CVarFlags);
+ });
+
+ client.Post(() =>
+ {
+ clientNetConfiguration.RegisterCVar(CVarName, DefaultValue, CVarFlags);
+ });
+
+ await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
+ // connect client
+ Assert.DoesNotThrow(() => client.SetConnectTarget(server));
+ await client.WaitPost(() =>
+ {
+ client.Resolve().ClientConnect(null!, 0, null!);
+ });
+
+ await RunTicks(server, client);
+
+ var session = server.PlayerMan.Sessions.First();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(serverNetConfiguration.GetClientCVar(session.Channel, CVarName), Is.EqualTo(DefaultValue));
+ Assert.That(clientNetConfiguration.GetClientCVar(session.Channel, CVarName), Is.EqualTo(DefaultValue));
+ });
+
+
+ ICommonSession? subscribeSession = default!;
+ var SubscribeValue = 0;
+ var timesRan = 0;
+ void ClientValueChanged(int value, ICommonSession session)
+ {
+ timesRan++;
+ SubscribeValue = value;
+ subscribeSession = session;
+ }
+
+ // actually subscribe
+ server.Post(() =>
+ {
+ serverNetConfiguration.OnClientCVarChanges(CVarName, ClientValueChanged);
+ });
+
+ // set new value in client
+ const int NewValue = 8;
+ Assert.That(NewValue, Is.Not.EqualTo(DefaultValue));
+ client.Post(() =>
+ {
+ clientNetConfiguration.SetCVar(CVarName, NewValue);
+ });
+
+ await RunTicks(server, client);
+
+ // assert handling cvar change and receiving event
+ Assert.Multiple(() =>
+ {
+ Assert.That(clientNetConfiguration.GetClientCVar(session.Channel, CVarName), Is.EqualTo(NewValue));
+ Assert.That(serverNetConfiguration.GetClientCVar(session.Channel, CVarName), Is.EqualTo(NewValue));
+
+ Assert.That(timesRan, Is.EqualTo(1));
+ Assert.That(SubscribeValue, Is.EqualTo(NewValue));
+ Assert.That(subscribeSession, Is.EqualTo(session));
+ });
+
+ // unsubscribe
+ server.Post(() =>
+ {
+ serverNetConfiguration.UnsubClientCVarChanges(CVarName, ClientValueChanged);
+ });
+
+ // set new value in client
+ const int UnsubValue = 16;
+ Assert.That(UnsubValue, Is.Not.EqualTo(NewValue));
+ client.Post(() =>
+ {
+ clientNetConfiguration.SetCVar(CVarName, UnsubValue);
+ });
+
+ await RunTicks(server, client);
+
+ // assert handling cvar change and unsubscribing
+ Assert.Multiple(() =>
+ {
+ Assert.That(clientNetConfiguration.GetClientCVar(session.Channel, CVarName), Is.EqualTo(UnsubValue));
+ Assert.That(serverNetConfiguration.GetClientCVar(session.Channel, CVarName), Is.EqualTo(UnsubValue));
+
+ Assert.That(timesRan, Is.EqualTo(1));
+ Assert.That(SubscribeValue, Is.EqualTo(NewValue));
+ });
+ }
+
+ [Test]
+ public async Task TestSubscribeUnsubscribeMultipleClients()
+ {
+ const int ClientAmount = 4;
+ using var server = StartServer();
+ ClientIntegrationInstance[] clients = new ClientIntegrationInstance[ClientAmount];
+
+ // CVar def consts
+ const string CVarName = "net.foo_bar";
+ const CVar CVarFlags = CVar.CLIENT | CVar.REPLICATED;
+ const int DefaultValue = -1;
+
+ HashSet eventSessions = [];
+ Dictionary clientValues = [];
+ void ClientValueChanged(int value, ICommonSession session)
+ {
+ eventSessions.Add(session);
+ clientValues[session] = value;
+ }
+
+ // setup debug CVar
+ server.Post(() =>
+ {
+ server.Resolve().RegisterCVar(CVarName, DefaultValue, CVarFlags);
+ server.Resolve().OnClientCVarChanges(CVarName, ClientValueChanged);
+
+ });
+
+ // for this collection I need order of adding
+ List clientSessions = new();
+ for (int i = 0; i < ClientAmount; i++)
+ {
+ var client = StartClient();
+ clients[i] = client;
+
+ client.Post(() =>
+ {
+ client.Resolve().RegisterCVar(CVarName, DefaultValue, CVarFlags);
+ });
+
+ await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
+
+ Assert.DoesNotThrow(() => client.SetConnectTarget(server));
+ await client.WaitPost(() =>
+ {
+ client.Resolve().ClientConnect(null!, 0, null!);
+ });
+
+ await RunTicks(server, client);
+
+ clientSessions.Add(server.PlayerMan.Sessions.Except(clientSessions).First());
+ }
+
+ // check that we got correct sessions
+ Assert.That(clientSessions.Count, Is.EqualTo(ClientAmount));
+
+ // server invoke subscribed events of replicated CVar on client connect
+ Assert.Multiple(() =>
+ {
+ Assert.That(eventSessions, Has.Count.EqualTo(ClientAmount));
+ Assert.That(clientValues, Has.Count.EqualTo(ClientAmount));
+
+ Assert.That(clientValues.Values.Distinct().Count(), Is.EqualTo(1));
+ Assert.That(clientValues.Values.Distinct().First(), Is.EqualTo(DefaultValue));
+ });
+
+ eventSessions.Clear();
+ clientValues.Clear();
+
+ // try to change CVar on every client EXCEPT last one
+ for (int i = 0; i < ClientAmount - 1; i++)
+ {
+ var client = clients[i];
+ // set new value in client
+ Assert.That(i, Is.Not.EqualTo(DefaultValue));
+ client.Post(() =>
+ {
+ client.Resolve().SetCVar(CVarName, i);
+ });
+
+ await RunTicks(server, client);
+ }
+
+ Assert.Multiple(() =>
+ {
+ // session events worked correctly (reminder: last one haven't changed it CVar)
+ Assert.That(eventSessions, Has.Count.EqualTo(ClientAmount - 1));
+
+ for (int i = 0; i < ClientAmount - 1; i++)
+ {
+ var currentSession = clientSessions[i];
+
+ int? value = null;
+ Assert.DoesNotThrow(() => value = clientValues[currentSession]);
+
+ // check if session wasn't messed up
+ Assert.That(value, Is.EqualTo(i));
+ }
+
+ var lastSession = clientSessions[ClientAmount - 1];
+ Assert.That(clientValues.ContainsKey(lastSession), Is.False);
+ });
+ }
+
+ private async Task RunTicks(IntegrationInstance server, IntegrationInstance client, int numberOfTicks = 5)
+ {
+ for (int i = 0; i < numberOfTicks; i++)
+ {
+ await server.WaitRunTicks(1);
+ await client.WaitRunTicks(1);
+ }
+ }
+}
+
diff --git a/Robust.Shared.IntegrationTests/GameObjects/EntityState_Tests.cs b/Robust.Shared.IntegrationTests/GameObjects/EntityState_Tests.cs
index e042a1aa59a..3686eb0eab9 100644
--- a/Robust.Shared.IntegrationTests/GameObjects/EntityState_Tests.cs
+++ b/Robust.Shared.IntegrationTests/GameObjects/EntityState_Tests.cs
@@ -3,6 +3,7 @@
using Moq;
using NUnit.Framework;
using Robust.Server.Configuration;
+using Robust.Server.Player;
using Robust.Server.Reflection;
using Robust.Server.Serialization;
using Robust.Shared.Configuration;
@@ -45,6 +46,7 @@ public void ComponentChangedSerialized()
container.Register();
container.Register();
container.RegisterInstance(new Mock().Object);
+ container.RegisterInstance(new Mock().Object);
container.BuildGraph();
var cfg = container.Resolve();
diff --git a/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManagerOrderTest.cs b/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManagerOrderTest.cs
index 8cf31f52881..849cb603542 100644
--- a/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManagerOrderTest.cs
+++ b/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManagerOrderTest.cs
@@ -4,6 +4,7 @@
using Moq;
using NUnit.Framework;
using Robust.Server.Configuration;
+using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
@@ -89,6 +90,7 @@ public void Test()
deps.RegisterInstance(new Mock().Object);
deps.Register();
deps.RegisterInstance(new Mock().Object);
+ deps.RegisterInstance(new Mock().Object);
// WHEN WILL THE SUFFERING END
deps.RegisterInstance(new Mock().Object);
diff --git a/Robust.Shared/Configuration/NetConfigurationManager.cs b/Robust.Shared/Configuration/NetConfigurationManager.cs
index f13c0e71491..60af0942ddc 100644
--- a/Robust.Shared/Configuration/NetConfigurationManager.cs
+++ b/Robust.Shared/Configuration/NetConfigurationManager.cs
@@ -5,11 +5,14 @@
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
+using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Configuration
{
+ public delegate void ClientCVarChanged(ICommonSession session, T newValue, in CVarChangeInfo info);
+
///
/// A networked configuration manager that controls the replication of
/// console variables between client and server.
@@ -58,6 +61,54 @@ public interface INetConfigurationManager : IConfigurationManager
/// Replicated CVar of the client.
T GetClientCVar(INetChannel channel, CVarDef definition) where T : notnull
=> GetClientCVar(channel, definition.Name);
+
+ ///
+ /// Listen for an event for if the config value changes on client changes
+ ///
+ /// CVar type.
+ /// Name of the CVar.
+ /// The delegate to run when the cvar was changed.
+ void OnClientCVarChanges(string name, Action onChanged) where T : notnull;
+
+ ///
+ /// Listen for an event for if the config value changes on client changes
+ ///
+ /// CVar type.
+ /// The CVar.
+ /// The delegate to run when the cvar was changed.
+ void OnClientCVarChanges(CVarDef definition, Action onChanged) where T : notnull
+ => OnClientCVarChanges(definition.Name, onChanged);
+
+ ///
+ void OnClientCVarChanges(string name, ClientCVarChanged onChanged) where T : notnull;
+
+ ///
+ void OnClientCVarChanges(CVarDef definition, ClientCVarChanged onChanged) where T : notnull
+ => OnClientCVarChanges(definition.Name, onChanged);
+
+ ///
+ /// Unsubscribe an event previously registered with .
+ ///
+ /// CVar type.
+ /// Name of the CVar.
+ /// The delegate to run when the cvar was changed.
+ void UnsubClientCVarChanges(string name, Action onChanged) where T : notnull;
+
+ ///
+ /// Unsubscribe an event previously registered with .
+ ///
+ /// CVar type.
+ /// The CVar.
+ /// The delegate to run when the cvar was changed.
+ void UnsubClientCVarChanges(CVarDef definition, Action onChanged) where T : notnull
+ => UnsubClientCVarChanges(definition.Name, onChanged);
+
+ ///
+ void UnsubClientCVarChanges(string name, ClientCVarChanged onChanged) where T : notnull;
+
+ ///
+ void UnsubClientCVarChanges(CVarDef definition, ClientCVarChanged onChanged) where T : notnull
+ => UnsubClientCVarChanges(definition.Name, onChanged);
}
internal interface INetConfigurationManagerInternal : INetConfigurationManager, IConfigurationManagerInternal
@@ -65,6 +116,8 @@ internal interface INetConfigurationManagerInternal : INetConfigurationManager,
}
+ public delegate void ClientValueChangedDelegate(object value, ICommonSession session, in CVarChangeInfo info);
+
///
internal abstract class NetConfigurationManager : ConfigurationManager, INetConfigurationManagerInternal
{
@@ -198,5 +251,11 @@ public void SyncConnectingClient(INetChannel client)
///
public abstract T GetClientCVar(INetChannel channel, string name);
+
+ public abstract void OnClientCVarChanges(string name, Action onChanged) where T : notnull;
+ public abstract void OnClientCVarChanges(string name, ClientCVarChanged onChanged) where T : notnull;
+
+ public abstract void UnsubClientCVarChanges(string name, Action onChanged) where T : notnull;
+ public abstract void UnsubClientCVarChanges(string name, ClientCVarChanged onChanged) where T : notnull;
}
}