feat: extract baseclass, automatic de-registration

This commit is contained in:
roflmuffin
2025-07-20 11:43:38 +00:00
parent 8d9ea3f330
commit a84f4244cd
4 changed files with 130 additions and 86 deletions

View File

@@ -129,6 +129,8 @@ namespace CounterStrikeSharp.API.Core
internal readonly Dictionary<Delegate, EntityIO.EntityOutputCallback> EntitySingleOutputHooks =
new Dictionary<Delegate, EntityIO.EntityOutputCallback>();
internal readonly List<ConVarBase> ConVars = [];
public readonly List<CommandDefinition> CommandDefinitions = new List<CommandDefinition>();
public readonly List<Timer> Timers = new List<Timer>();
@@ -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
}
}
/// <summary>
/// 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);
}
/// <summary>
/// Hooks an <a href="https://developer.valvesoftware.com/wiki/Inputs_and_Outputs">entity output</a>.
/// </summary>
@@ -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);

View File

@@ -2,14 +2,9 @@ using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Modules.Cvars;
public class ConVar<T>
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<T>
set => NativeAPI.SetConvarFlags(AccessIndex, (ulong)value);
}
public string ValueAsString
{
get => NativeAPI.GetConvarValueAsString(AccessIndex);
set => NativeAPI.SetConvarValueAsString(AccessIndex, value);
}
/// <summary>
/// Shorthand for checking the <see cref="ConVarFlags.FCVAR_NOTIFY"/> flag.
/// </summary>
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<T> : 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<T>
set => NativeAPI.SetConvarValue(AccessIndex, value);
}
public string ValueAsString
{
get => NativeAPI.GetConvarValueAsString(AccessIndex);
set => NativeAPI.SetConvarValueAsString(AccessIndex, value);
}
public static ConVar<T>? Find(string name)
{
var accessIndex = NativeAPI.GetConvarAccessIndexByName(name);
@@ -128,25 +206,6 @@ public class ConVar<T>
return new ConVar<T>(accessIndex);
}
/// <summary>
/// Shorthand for checking the <see cref="ConVarFlags.FCVAR_NOTIFY"/> flag.
/// </summary>
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<T>
public T? MinValue { get; init; }
public T? MaxValue { get; init; }
}
public static ConVar<T>? 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<T>? 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<T>(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;
}
}

View File

@@ -91,7 +91,7 @@ public class ConVarTests
{
ConVar<bool>.Find("test_bool_convar")?.Delete();
var conVar = ConVar<bool>.Create("test_bool_convar", true, "Test boolean ConVar", ConVarFlags.FCVAR_NOTIFY);
var conVar = new ConVar<bool>("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<Vector>.Find("test_vector_convar")?.Delete();
var conVar = ConVar<Vector>.Create(new()
var conVar = new ConVar<Vector>(new ConVar<Vector>.ConVarCreationOptions()
{
Name = "test_vector_convar",
DefaultValue = new Vector(1, 2, 3),
@@ -151,7 +151,7 @@ public class ConVarTests
{
ConVar<string>.Find("test_string_convar")?.Delete();
var conVar = ConVar<string>.Create("test_string_convar", "default_value", "Test string ConVar", ConVarFlags.FCVAR_NOTIFY);
var conVar = new ConVar<string>("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<float>.Find("test_float_convar")?.Delete();
var conVar = ConVar<float>.Create(new()
var conVar = new ConVar<float>(new ConVar<float>.ConVarCreationOptions()
{
Name = "test_float_convar",
DefaultValue = 1.23f,
@@ -209,7 +209,7 @@ public class ConVarTests
{
ConVar<int>.Find("test_int_convar")?.Delete();
var conVar = ConVar<int>.Create(new()
var conVar = new ConVar<int>(new ConVar<int>.ConVarCreationOptions()
{
Name = "test_int_convar",
DefaultValue = 42,

View File

@@ -65,6 +65,15 @@ namespace TestPlugin
private TestInjectedClass _testInjectedClass;
public ConVar<float> MyExampleConvar = new ConVar<float>(
"example_convar",
"An example ConVar for testing purposes",
42.0f,
ConVarFlags.FCVAR_NONE,
0.0f,
100.0f
);
public SamplePlugin(TestInjectedClass testInjectedClass)
{
_testInjectedClass = testInjectedClass;