From a84f4244cdec21c5284b3ef478e8d2245ded8673 Mon Sep 17 00:00:00 2001 From: roflmuffin Date: Sun, 20 Jul 2025 11:43:38 +0000 Subject: [PATCH] feat: extract baseclass, automatic de-registration --- .../CounterStrikeSharp.API/Core/BasePlugin.cs | 25 +++ .../Modules/Cvars/ConVarOfT.cs | 172 +++++++++--------- .../ConVarTests.cs | 10 +- managed/TestPlugin/TestPlugin.cs | 9 + 4 files changed, 130 insertions(+), 86 deletions(-) diff --git a/managed/CounterStrikeSharp.API/Core/BasePlugin.cs b/managed/CounterStrikeSharp.API/Core/BasePlugin.cs index a4a2b47f..aacc218b 100644 --- a/managed/CounterStrikeSharp.API/Core/BasePlugin.cs +++ b/managed/CounterStrikeSharp.API/Core/BasePlugin.cs @@ -129,6 +129,8 @@ namespace CounterStrikeSharp.API.Core internal readonly Dictionary EntitySingleOutputHooks = new Dictionary(); + internal readonly List ConVars = []; + public readonly List CommandDefinitions = new List(); public readonly List Timers = new List(); @@ -367,6 +369,7 @@ namespace CounterStrikeSharp.API.Core this.RegisterAttributeHandlers(instance); this.RegisterConsoleCommandAttributeHandlers(instance); this.RegisterEntityOutputAttributeHandlers(instance); + this.RegisterConVars(instance); this.RegisterFakeConVars(instance); } @@ -524,6 +527,19 @@ namespace CounterStrikeSharp.API.Core }); } } + + public void RegisterConVars(Type type, object instance = null) + { + var convars = type + .GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) + .Where(prop => prop.FieldType.IsGenericType && + prop.FieldType.GetGenericTypeDefinition() == typeof(ConVar<>)); + + foreach (var prop in convars) + { + ConVars.Add(prop.GetValue(instance) as ConVarBase); // ConVar instance + } + } /// /// Used to bind a fake ConVar to a plugin command. Only required for ConVars that are not public properties of the plugin class. @@ -535,6 +551,10 @@ namespace CounterStrikeSharp.API.Core RegisterFakeConVars(instance.GetType(), instance); } + public void RegisterConVars(object instance) { + RegisterConVars(instance.GetType(), instance); + } + /// /// Hooks an entity output. /// @@ -657,6 +677,11 @@ namespace CounterStrikeSharp.API.Core subscriber.Dispose(); } + foreach (var convar in ConVars) + { + convar.Delete(); + } + foreach (var definition in CommandDefinitions) { CommandManager.RemoveCommand(definition); diff --git a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs index ae16f745..3e38272a 100644 --- a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs +++ b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs @@ -2,14 +2,9 @@ using CounterStrikeSharp.API.Modules.Utils; namespace CounterStrikeSharp.API.Modules.Cvars; -public class ConVar +public class ConVarBase { - public ushort AccessIndex { get; private set; } - - public ConVar(ushort accessIndex) - { - AccessIndex = accessIndex; - } + public ushort AccessIndex { get; protected set; } public string Name => NativeAPI.GetConvarName(AccessIndex); public string Description => NativeAPI.GetConvarHelpText(AccessIndex); @@ -28,6 +23,95 @@ public class ConVar set => NativeAPI.SetConvarFlags(AccessIndex, (ulong)value); } + public string ValueAsString + { + get => NativeAPI.GetConvarValueAsString(AccessIndex); + set => NativeAPI.SetConvarValueAsString(AccessIndex, value); + } + + /// + /// Shorthand for checking the flag. + /// + public bool Public + { + get => Flags.HasFlag(ConVarFlags.FCVAR_NOTIFY); + set + { + if (value) + { + Flags |= ConVarFlags.FCVAR_NOTIFY; + } + else + { + Flags &= ~ConVarFlags.FCVAR_NOTIFY; + } + } + } + + public void Delete() + { + if (AccessIndex == 0) + throw new InvalidOperationException("Cannot delete a ConVar that has not been created or found."); + + NativeAPI.DeleteConvar(AccessIndex); + AccessIndex = 0; + } +} + +public class ConVar : ConVarBase +{ + public ConVar(ushort accessIndex) + { + AccessIndex = accessIndex; + } + + public ConVar(string name, string description, T defaultValue = default(T), ConVarFlags flags = ConVarFlags.FCVAR_NONE, + T? minValue = default, T? maxValue = default) : this(new ConVarCreationOptions + { + Name = name, + DefaultValue = defaultValue, + Description = description, + Flags = flags, + MinValue = minValue, + MaxValue = maxValue + }) + { + } + + public ConVar(ConVarCreationOptions options) + { + var type = typeof(T); + var conVarType = type switch + { + _ when type == typeof(bool) => ConVarType.Bool, + _ when type == typeof(float) => ConVarType.Float32, + _ when type == typeof(double) => ConVarType.Float64, + _ when type == typeof(ushort) => ConVarType.UInt16, + _ when type == typeof(short) => ConVarType.Int16, + _ when type == typeof(uint) => ConVarType.UInt32, + _ when type == typeof(int) => ConVarType.Int32, + _ when type == typeof(long) => ConVarType.Int64, + _ when type == typeof(ulong) => ConVarType.UInt64, + _ when type == typeof(string) => ConVarType.String, + _ when type == typeof(QAngle) => ConVarType.Qangle, + _ when type == typeof(Vector2D) => ConVarType.Vector2, + _ when type == typeof(Vector) => ConVarType.Vector3, + _ when type == typeof(Vector4D) => ConVarType.Vector4, + _ => throw new InvalidOperationException($"Unsupported type: {type}") + }; + + AccessIndex = NativeAPI.CreateConvar(options.Name, (short)conVarType, options.Description, (UInt64)options.Flags, + options.MinValue != null, options.MaxValue != null, + options.DefaultValue, + options.MinValue, + options.MaxValue); + + if (AccessIndex == 0) + { + throw new InvalidOperationException($"Failed to create ConVar '{options.Name}' with type '{type}'."); + } + } + public T Value { get @@ -114,12 +198,6 @@ public class ConVar set => NativeAPI.SetConvarValue(AccessIndex, value); } - public string ValueAsString - { - get => NativeAPI.GetConvarValueAsString(AccessIndex); - set => NativeAPI.SetConvarValueAsString(AccessIndex, value); - } - public static ConVar? Find(string name) { var accessIndex = NativeAPI.GetConvarAccessIndexByName(name); @@ -128,25 +206,6 @@ public class ConVar return new ConVar(accessIndex); } - /// - /// Shorthand for checking the flag. - /// - public bool Public - { - get => Flags.HasFlag(ConVarFlags.FCVAR_NOTIFY); - set - { - if (value) - { - Flags |= ConVarFlags.FCVAR_NOTIFY; - } - else - { - Flags &= ~ConVarFlags.FCVAR_NOTIFY; - } - } - } - public override string ToString() { return $"ConVar [name={Name}, value={Value}, description={Description}, type={Type}, flags={Flags}]"; @@ -161,53 +220,4 @@ public class ConVar public T? MinValue { get; init; } public T? MaxValue { get; init; } } - - public static ConVar? Create(ConVarCreationOptions options) - { - if (string.IsNullOrWhiteSpace(options.Name)) - throw new ArgumentException("ConVar name cannot be null or whitespace.", nameof(options.Name)); - - return Create(options.Name, options.DefaultValue, options.Description, options.Flags, options.MinValue, options.MaxValue); - } - - public static ConVar? Create(string name, T defaultValue, string description = "", ConVarFlags flags = ConVarFlags.FCVAR_NONE, - T? minValue = default, T? maxValue = default) - { - var type = typeof(T); - var conVarType = type switch - { - _ when type == typeof(bool) => ConVarType.Bool, - _ when type == typeof(float) => ConVarType.Float32, - _ when type == typeof(double) => ConVarType.Float64, - _ when type == typeof(ushort) => ConVarType.UInt16, - _ when type == typeof(short) => ConVarType.Int16, - _ when type == typeof(uint) => ConVarType.UInt32, - _ when type == typeof(int) => ConVarType.Int32, - _ when type == typeof(long) => ConVarType.Int64, - _ when type == typeof(ulong) => ConVarType.UInt64, - _ when type == typeof(string) => ConVarType.String, - _ when type == typeof(QAngle) => ConVarType.Qangle, - _ when type == typeof(Vector2D) => ConVarType.Vector2, - _ when type == typeof(Vector) => ConVarType.Vector3, - _ when type == typeof(Vector4D) => ConVarType.Vector4, - _ => throw new InvalidOperationException($"Unsupported type: {type}") - }; - - var accessIndex = NativeAPI.CreateConvar(name, (short)conVarType, description, (UInt64)flags, minValue != null, maxValue != null, - defaultValue, - minValue, - maxValue); - if (accessIndex == 0) return null; - - return new ConVar(accessIndex); - } - - public void Delete() - { - if (AccessIndex == 0) - throw new InvalidOperationException("Cannot delete a ConVar that has not been created or found."); - - NativeAPI.DeleteConvar(AccessIndex); - AccessIndex = 0; - } } \ No newline at end of file diff --git a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs index 0e8a17bd..92522c73 100644 --- a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs +++ b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs @@ -91,7 +91,7 @@ public class ConVarTests { ConVar.Find("test_bool_convar")?.Delete(); - var conVar = ConVar.Create("test_bool_convar", true, "Test boolean ConVar", ConVarFlags.FCVAR_NOTIFY); + var conVar = new ConVar("test_bool_convar", "Test boolean ConVar", true, ConVarFlags.FCVAR_NOTIFY); Assert.NotNull(conVar); Assert.Equal("test_bool_convar", conVar.Name); Assert.Equal(ConVarType.Bool, conVar.Type); @@ -112,7 +112,7 @@ public class ConVarTests { ConVar.Find("test_vector_convar")?.Delete(); - var conVar = ConVar.Create(new() + var conVar = new ConVar(new ConVar.ConVarCreationOptions() { Name = "test_vector_convar", DefaultValue = new Vector(1, 2, 3), @@ -151,7 +151,7 @@ public class ConVarTests { ConVar.Find("test_string_convar")?.Delete(); - var conVar = ConVar.Create("test_string_convar", "default_value", "Test string ConVar", ConVarFlags.FCVAR_NOTIFY); + var conVar = new ConVar("test_string_convar", "Test string ConVar", "default_value", ConVarFlags.FCVAR_NOTIFY); Assert.NotNull(conVar); Assert.Equal("test_string_convar", conVar.Name); Assert.Equal(ConVarType.String, conVar.Type); @@ -172,7 +172,7 @@ public class ConVarTests { ConVar.Find("test_float_convar")?.Delete(); - var conVar = ConVar.Create(new() + var conVar = new ConVar(new ConVar.ConVarCreationOptions() { Name = "test_float_convar", DefaultValue = 1.23f, @@ -209,7 +209,7 @@ public class ConVarTests { ConVar.Find("test_int_convar")?.Delete(); - var conVar = ConVar.Create(new() + var conVar = new ConVar(new ConVar.ConVarCreationOptions() { Name = "test_int_convar", DefaultValue = 42, diff --git a/managed/TestPlugin/TestPlugin.cs b/managed/TestPlugin/TestPlugin.cs index a88bbab7..093a3a2c 100644 --- a/managed/TestPlugin/TestPlugin.cs +++ b/managed/TestPlugin/TestPlugin.cs @@ -65,6 +65,15 @@ namespace TestPlugin private TestInjectedClass _testInjectedClass; + public ConVar MyExampleConvar = new ConVar( + "example_convar", + "An example ConVar for testing purposes", + 42.0f, + ConVarFlags.FCVAR_NONE, + 0.0f, + 100.0f + ); + public SamplePlugin(TestInjectedClass testInjectedClass) { _testInjectedClass = testInjectedClass;