From 818c694c845b9a73b9972c5ac607e7bbb427579d Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Mon, 14 Jul 2025 06:47:45 +0000 Subject: [PATCH 1/9] feat: add Convar --- CMakeLists.txt | 1 + managed/CounterStrikeSharp.API/Core/API.cs | 88 +++++ .../Modules/Cvars/ConVarOfT.cs | 128 +++++++ .../ConVarTests.cs | 88 +++++ .../GlobalUsings.cs | 3 +- src/scripting/natives/natives_convars.cpp | 325 ++++++++++++++++++ src/scripting/natives/natives_convars.yaml | 8 + tooling/CodeGen.Natives/Mapping.cs | 4 + 8 files changed, 644 insertions(+), 1 deletion(-) create mode 100644 managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs create mode 100644 managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs create mode 100644 src/scripting/natives/natives_convars.cpp create mode 100644 src/scripting/natives/natives_convars.yaml diff --git a/CMakeLists.txt b/CMakeLists.txt index 57904f724..d60e14321 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ set(SOURCE_FILES src/core/managers/con_command_manager.cpp src/core/managers/con_command_manager.h src/scripting/natives/natives_commands.cpp + src/scripting/natives/natives_convars.cpp src/core/memory_module.h src/core/memory_module.cpp src/core/cs2_sdk/interfaces/cgameresourceserviceserver.h diff --git a/managed/CounterStrikeSharp.API/Core/API.cs b/managed/CounterStrikeSharp.API/Core/API.cs index c1de5c22e..3ff603df9 100644 --- a/managed/CounterStrikeSharp.API/Core/API.cs +++ b/managed/CounterStrikeSharp.API/Core/API.cs @@ -218,6 +218,94 @@ public static void ReplicateConvar(int clientslot, string convarname, string con } } + public static void SetConvarFlags(ushort convar, ulong flags){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.Push(flags); + ScriptContext.GlobalScriptContext.SetIdentifier(0xB2BDCCBF); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + } + } + + public static ulong GetConvarFlags(ushort convar){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.SetIdentifier(0x94829E2B); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (ulong)ScriptContext.GlobalScriptContext.GetResult(typeof(ulong)); + } + } + + public static short GetConvarType(ushort convar){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.SetIdentifier(0xB6E0E54C); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (short)ScriptContext.GlobalScriptContext.GetResult(typeof(short)); + } + } + + public static string GetConvarName(ushort convar){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.SetIdentifier(0xB6F0E2F3); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (string)ScriptContext.GlobalScriptContext.GetResult(typeof(string)); + } + } + + public static string GetConvarHelpText(ushort convar){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.SetIdentifier(0x341D1F67); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (string)ScriptContext.GlobalScriptContext.GetResult(typeof(string)); + } + } + + public static ushort GetConvarAccessIndexByName(string name){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(name); + ScriptContext.GlobalScriptContext.SetIdentifier(0x6288420D); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (ushort)ScriptContext.GlobalScriptContext.GetResult(typeof(ushort)); + } + } + + public static T GetConvarValue(ushort convar){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.SetIdentifier(0x935B2E9F); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (T)ScriptContext.GlobalScriptContext.GetResult(typeof(T)); + } + } + + public static void SetConvarValue(ushort convar, T value){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.Push(value); + ScriptContext.GlobalScriptContext.SetIdentifier(0xB3DDAA0B); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + } + } + public static string GetStringFromSymbolLarge(IntPtr pointer){ lock (ScriptContext.GlobalScriptContext.Lock) { ScriptContext.GlobalScriptContext.Reset(); diff --git a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs new file mode 100644 index 000000000..fb454e884 --- /dev/null +++ b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs @@ -0,0 +1,128 @@ +using System; +using System.Runtime.CompilerServices; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; + +namespace CounterStrikeSharp.API.Modules.Cvars; + +public class ConVar +{ + public ushort AccessIndex { get; private set; } + + public ConVar(ushort accessIndex) + { + AccessIndex = accessIndex; + } + + public string Name => NativeAPI.GetConvarName(AccessIndex); + public string Description => NativeAPI.GetConvarHelpText(AccessIndex); + + /// + /// The underlying data type of the ConVar. + /// + public ConVarType Type => (ConVarType)NativeAPI.GetConvarType(AccessIndex); + + /// + /// The ConVar flags as defined by . + /// + public ConVarFlags Flags => (ConVarFlags)NativeAPI.GetConvarFlags(AccessIndex); + + public T Value + { + get + { + var type = typeof(T); + switch (Type) + { + case ConVarType.Bool: + if (type != typeof(bool)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Float32: + if (type != typeof(float)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Float64: + if (type != typeof(double)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.UInt16: + if (type != typeof(ushort)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Int16: + if (type != typeof(short)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.UInt32: + if (type != typeof(uint)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Int32: + if (type != typeof(int)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Int64: + if (type != typeof(long)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.UInt64: + if (type != typeof(ulong)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.String: + if (type != typeof(string)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Qangle: + if (type != typeof(QAngle)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Vector2: + if (type != typeof(Vector2D)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Vector3: + if (type != typeof(Vector)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Vector4: + if (type != typeof(Vector4D)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + case ConVarType.Color: + if (type != typeof(Vector)) + throw new InvalidOperationException( + $"ConVar is a {Type} but you are trying to get a {type} value."); + break; + default: + throw new InvalidOperationException($"Unknown ConVar type: {Type}"); + } + + return NativeAPI.GetConvarValue(AccessIndex); + } + set => NativeAPI.SetConvarValue(AccessIndex, value); + } + + public static ConVar? Find(string name) + { + var accessIndex = NativeAPI.GetConvarAccessIndexByName(name); + if (accessIndex == 0) return null; + + return new ConVar(accessIndex); + } +} \ No newline at end of file diff --git a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs new file mode 100644 index 000000000..d4c91aada --- /dev/null +++ b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs @@ -0,0 +1,88 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Utils; +using Xunit; + +namespace NativeTestsPlugin; + +public class ConVarTests +{ + [Fact] + public async Task BoolConVar() + { + Server.ExecuteCommand("sv_cheats 1"); + await WaitOneFrame(); + + var boolConVar = ConVar.Find("sv_cheats"); + Assert.NotNull(boolConVar); + Assert.Equal("sv_cheats", boolConVar.Name); + Assert.Equal(ConVarType.Bool, boolConVar.Type); + Assert.Equal(ConVarFlags.FCVAR_NOTIFY | ConVarFlags.FCVAR_REPLICATED | ConVarFlags.FCVAR_RELEASE, boolConVar.Flags); + Assert.True(boolConVar.Value); + + boolConVar.Value = false; + Assert.False(boolConVar.Value); + } + + [Fact] + public async Task IntConVar() + { + Server.ExecuteCommand("mp_td_dmgtokick 300"); + await WaitOneFrame(); + + var intConVar = ConVar.Find("mp_td_dmgtokick"); + Assert.NotNull(intConVar); + Assert.Equal("mp_td_dmgtokick", intConVar.Name); + Assert.Equal(ConVarType.Int32, intConVar.Type); + Assert.Equal(300, intConVar.Value); + + intConVar.Value = 500; + Assert.Equal(500, intConVar.Value); + } + + [Fact] + public async Task FloatConVar() + { + Server.ExecuteCommand("inferno_damage 40.0"); + await WaitOneFrame(); + + var floatConVar = ConVar.Find("inferno_damage"); + Assert.NotNull(floatConVar); + Assert.Equal(ConVarType.Float32, floatConVar.Type); + Assert.Equal(40.0, floatConVar.Value); + + floatConVar.Value = 50.0f; + Assert.Equal(50.0f, floatConVar.Value); + } + + [Fact] + public async Task VectorConVar() + { + Server.ExecuteCommand("fog_color -1 -1 -1"); + await WaitOneFrame(); + + var vectorConVar = ConVar.Find("fog_color"); + Assert.NotNull(vectorConVar); + Assert.Equal(-1, vectorConVar.Value.X); + Assert.Equal(-1, vectorConVar.Value.Y); + Assert.Equal(-1, vectorConVar.Value.Z); + + vectorConVar.Value = new Vector(0, 0, 0); + Assert.Equal(0, vectorConVar.Value.X); + Assert.Equal(0, vectorConVar.Value.Y); + Assert.Equal(0, vectorConVar.Value.Z); + } + + [Fact] + public async Task StringConVar() + { + Server.ExecuteCommand("mp_backup_round_file backup"); + await WaitOneFrame(); + + var stringConVar = ConVar.Find("mp_backup_round_file"); + Assert.NotNull(stringConVar); + Assert.Equal("backup", stringConVar.Value); + + stringConVar.Value = "new_backup"; + } +} \ No newline at end of file diff --git a/managed/CounterStrikeSharp.Tests.Native/GlobalUsings.cs b/managed/CounterStrikeSharp.Tests.Native/GlobalUsings.cs index f9b8ff4e0..378294e74 100644 --- a/managed/CounterStrikeSharp.Tests.Native/GlobalUsings.cs +++ b/managed/CounterStrikeSharp.Tests.Native/GlobalUsings.cs @@ -1,2 +1,3 @@ global using static TestUtils; -global using System; \ No newline at end of file +global using System; +global using System.Threading.Tasks; \ No newline at end of file diff --git a/src/scripting/natives/natives_convars.cpp b/src/scripting/natives/natives_convars.cpp new file mode 100644 index 000000000..9852032b8 --- /dev/null +++ b/src/scripting/natives/natives_convars.cpp @@ -0,0 +1,325 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . * + */ + +#define private public +#define protected public + +#include "core/log.h" +#include "scripting/autonative.h" +#include "scripting/script_engine.h" + +#include +#include +#undef private +#undef protected + +namespace counterstrikesharp { + +static void SetConvarFlags(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto ref = ConVarRefAbstract(convarAccessIndex); + + if (!ref.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + auto flags = script_context.GetArgument(1); + ref.GetConVarData()->m_nFlags = flags; +} + +static void GetConvarFlags(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto ref = ConVarRefAbstract(convarAccessIndex); + + if (!ref.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + script_context.SetResult(ref.GetConVarData()->m_nFlags); +} + +static void GetConvarType(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto ref = ConVarRefAbstract(convarAccessIndex); + + if (!ref.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + script_context.SetResult(ref.GetConVarData()->GetType()); +} + +static void GetConvarName(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto ref = ConVarRefAbstract(convarAccessIndex); + + if (!ref.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + script_context.SetResult(ref.GetConVarData()->GetName()); +} + +static void GetConvarHelpText(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto ref = ConVarRefAbstract(convarAccessIndex); + + if (!ref.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + if (!ref.GetConVarData()->HasHelpText()) + { + return; + } + + script_context.SetResult(ref.GetConVarData()->GetHelpText()); +} + +static void GetConvarAccessIndexByName(ScriptContext& script_context) +{ + auto convarName = script_context.GetArgument(0); + ConVarRef ref(convarName); + + if (!ref.IsValidRef()) + { + return; + } + + script_context.SetResult(ref.GetAccessIndex()); +} + +static void GetConvarValue(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto cvar = ConVarRefAbstract(convarAccessIndex); + CSplitScreenSlot server(0); + + if (!cvar.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + if (!cvar.IsConVarDataValid()) return; + + switch (cvar.GetType()) + { + case EConVarType_Int16: + { + script_context.SetResult(cvar.GetAs(server)); + break; + } + case EConVarType_UInt16: + { + script_context.SetResult(cvar.GetAs(server)); + break; + } + case EConVarType_UInt32: + { + script_context.SetResult(cvar.GetAs(server)); + break; + } + case EConVarType_Int32: + { + script_context.SetResult(cvar.GetAs(server)); + break; + } + case EConVarType_UInt64: + { + script_context.SetResult(cvar.GetAs(server)); + break; + } + case EConVarType_Int64: + { + script_context.SetResult(cvar.GetAs(server)); + break; + } + case EConVarType_Bool: + { + script_context.SetResult(cvar.GetAs(server)); + break; + } + case EConVarType_Float32: + { + script_context.SetResult((float)cvar.GetAs(server)); + break; + } + case EConVarType_Float64: + { + script_context.SetResult((double)cvar.GetAs(server)); + break; + } + case EConVarType_String: + { + script_context.SetResult(cvar.GetString(server).String()); + break; + } + case EConVarType_Color: + { + script_context.SetResult(&(cvar.GetConVarData()->ValueOrDefault(server)->m_clrValue)); + break; + } + case EConVarType_Vector2: + { + script_context.SetResult(&(cvar.GetConVarData()->ValueOrDefault(server)->m_vec2Value)); + break; + } + case EConVarType_Vector3: + { + script_context.SetResult(&(cvar.GetConVarData()->ValueOrDefault(server)->m_vec3Value)); + break; + } + case EConVarType_Vector4: + { + script_context.SetResult(&(cvar.GetConVarData()->ValueOrDefault(server)->m_vec4Value)); + break; + } + case EConVarType_Qangle: + { + script_context.SetResult(&(cvar.GetConVarData()->ValueOrDefault(server)->m_angValue)); + break; + } + default: + { + script_context.SetResult(nullptr); + } + } +} +static void SetConvarValue(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto cvar = ConVarRefAbstract(convarAccessIndex); + CSplitScreenSlot server(0); + + if (!cvar.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + if (!cvar.IsConVarDataValid()) return; + + switch (cvar.GetType()) + { + case EConVarType_Int16: + { + cvar.SetAs(script_context.GetArgument(1), server); + break; + } + case EConVarType_UInt16: + { + cvar.SetAs(script_context.GetArgument(1), server); + break; + } + case EConVarType_UInt32: + { + cvar.SetAs(script_context.GetArgument(1), server); + break; + } + case EConVarType_Int32: + { + cvar.SetAs(script_context.GetArgument(1), server); + break; + } + case EConVarType_UInt64: + { + cvar.SetAs(script_context.GetArgument(1), server); + break; + } + case EConVarType_Int64: + { + cvar.SetAs(script_context.GetArgument(1), server); + break; + } + case EConVarType_Bool: + { + cvar.SetAs(script_context.GetArgument(1), server); + break; + } + case EConVarType_Float32: + { + cvar.SetAs(script_context.GetArgument(1), server); + break; + } + case EConVarType_Float64: + { + cvar.SetAs(script_context.GetArgument(1), server); + break; + } + case EConVarType_String: + { + cvar.SetString(script_context.GetArgument(1), server); + break; + } + case EConVarType_Color: + { + cvar.SetAs(*script_context.GetArgument(1), server); + break; + } + case EConVarType_Vector2: + { + cvar.SetAs(*script_context.GetArgument(1), server); + break; + } + case EConVarType_Vector3: + { + cvar.SetAs(*script_context.GetArgument(1), server); + break; + } + case EConVarType_Vector4: + { + cvar.SetAs(*script_context.GetArgument(1), server); + break; + } + case EConVarType_Qangle: + { + cvar.SetAs(*script_context.GetArgument(1), server); + break; + } + default: + { + script_context.ThrowNativeError("Unsupported convar type: %d", cvar.GetType()); + } + } +} + +REGISTER_NATIVES(convars, { + ScriptEngine::RegisterNativeHandler("SET_CONVAR_FLAGS", SetConvarFlags); + ScriptEngine::RegisterNativeHandler("GET_CONVAR_FLAGS", GetConvarFlags); + ScriptEngine::RegisterNativeHandler("GET_CONVAR_TYPE", GetConvarType); + ScriptEngine::RegisterNativeHandler("GET_CONVAR_NAME", GetConvarName); + ScriptEngine::RegisterNativeHandler("GET_CONVAR_HELP_TEXT", GetConvarHelpText); + ScriptEngine::RegisterNativeHandler("GET_CONVAR_ACCESS_INDEX_BY_NAME", GetConvarAccessIndexByName); + ScriptEngine::RegisterNativeHandler("GET_CONVAR_VALUE", GetConvarValue); + ScriptEngine::RegisterNativeHandler("SET_CONVAR_VALUE", SetConvarValue); +}) +} // namespace counterstrikesharp diff --git a/src/scripting/natives/natives_convars.yaml b/src/scripting/natives/natives_convars.yaml new file mode 100644 index 000000000..35fab5c5b --- /dev/null +++ b/src/scripting/natives/natives_convars.yaml @@ -0,0 +1,8 @@ +SET_CONVAR_FLAGS: convar:uint16,flags:uint64 -> void +GET_CONVAR_FLAGS: convar:uint16 -> uint64 +GET_CONVAR_TYPE: convar:uint16 -> int16 +GET_CONVAR_NAME: convar:uint16 -> string +GET_CONVAR_HELP_TEXT: convar:uint16 -> string +GET_CONVAR_ACCESS_INDEX_BY_NAME: name:string -> uint16 +GET_CONVAR_VALUE: convar:uint16 -> any +SET_CONVAR_VALUE: convar:uint16, value:any -> void \ No newline at end of file diff --git a/tooling/CodeGen.Natives/Mapping.cs b/tooling/CodeGen.Natives/Mapping.cs index e12e2b26c..b5275a6bd 100644 --- a/tooling/CodeGen.Natives/Mapping.cs +++ b/tooling/CodeGen.Natives/Mapping.cs @@ -40,6 +40,10 @@ public static string GetCSharpType(string type) return "int"; case "uint": return "uint"; + case "int16": + return "short"; + case "uint16": + return "ushort"; case "bool": return "bool"; case "pointer": From bb8f1e20b6a6ed2176801703ec097c84952842ff Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Mon, 14 Jul 2025 08:12:34 +0000 Subject: [PATCH 2/9] feat: add delete & creation --- managed/CounterStrikeSharp.API/Core/API.cs | 32 +++- .../Modules/Cvars/ConVarOfT.cs | 59 +++++++ .../ConVarTests.cs | 147 ++++++++++++++++ src/scripting/natives/natives_convars.cpp | 158 +++++++++++++++++- src/scripting/natives/natives_convars.yaml | 4 +- 5 files changed, 396 insertions(+), 4 deletions(-) diff --git a/managed/CounterStrikeSharp.API/Core/API.cs b/managed/CounterStrikeSharp.API/Core/API.cs index 3ff603df9..92f3b7321 100644 --- a/managed/CounterStrikeSharp.API/Core/API.cs +++ b/managed/CounterStrikeSharp.API/Core/API.cs @@ -295,7 +295,7 @@ public static T GetConvarValue(ushort convar){ } } - public static void SetConvarValue(ushort convar, T value){ + public static object SetConvarValue(ushort convar, T value){ lock (ScriptContext.GlobalScriptContext.Lock) { ScriptContext.GlobalScriptContext.Reset(); ScriptContext.GlobalScriptContext.Push(convar); @@ -303,6 +303,36 @@ public static void SetConvarValue(ushort convar, T value){ ScriptContext.GlobalScriptContext.SetIdentifier(0xB3DDAA0B); ScriptContext.GlobalScriptContext.Invoke(); ScriptContext.GlobalScriptContext.CheckErrors(); + return (object)ScriptContext.GlobalScriptContext.GetResult(typeof(object)); + } + } + + public static ushort CreateConvar(string name, short type, string helptext, ulong flags, bool hasmin, bool hasmax, T defaultvalue, T minvalue, T maxvalue){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(name); + ScriptContext.GlobalScriptContext.Push(type); + ScriptContext.GlobalScriptContext.Push(helptext); + ScriptContext.GlobalScriptContext.Push(flags); + ScriptContext.GlobalScriptContext.Push(hasmin); + ScriptContext.GlobalScriptContext.Push(hasmax); + ScriptContext.GlobalScriptContext.Push(defaultvalue); + ScriptContext.GlobalScriptContext.Push(minvalue); + ScriptContext.GlobalScriptContext.Push(maxvalue); + ScriptContext.GlobalScriptContext.SetIdentifier(0xF22079B9); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (ushort)ScriptContext.GlobalScriptContext.GetResult(typeof(ushort)); + } + } + + public static void DeleteConvar(ushort convar){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.SetIdentifier(0xFC28F444); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); } } diff --git a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs index fb454e884..906df8bcd 100644 --- a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs +++ b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs @@ -125,4 +125,63 @@ public T Value return new ConVar(accessIndex); } + + public sealed record ConVarCreationOptions + { + public required string Name { get; init; } + public required T DefaultValue { get; init; } + public string Description { get; init; } = string.Empty; + public ConVarFlags Flags { get; init; } = ConVarFlags.FCVAR_NONE; + 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 d4c91aada..570cbb9f1 100644 --- a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs +++ b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs @@ -85,4 +85,151 @@ public async Task StringConVar() stringConVar.Value = "new_backup"; } + + [Fact] + public void CreateBoolConVar() + { + ConVar.Find("test_bool_convar")?.Delete(); + + var conVar = ConVar.Create("test_bool_convar", true, "Test boolean ConVar", ConVarFlags.FCVAR_NOTIFY); + Assert.NotNull(conVar); + Assert.Equal("test_bool_convar", conVar.Name); + Assert.Equal(ConVarType.Bool, conVar.Type); + Assert.Equal("Test boolean ConVar", conVar.Description); + Assert.Equal( + ConVarFlags.FCVAR_NOTIFY | ConVarFlags.FCVAR_GAMEDLL | ConVarFlags.FCVAR_RELEASE | ConVarFlags.FCVAR_CLIENT_CAN_EXECUTE, + conVar.Flags); + Assert.True(conVar.Value); + + conVar.Delete(); + + var found = ConVar.Find("test_bool_convar"); + Assert.Null(found); + } + + [Fact] + public void CreateVectorConVar() + { + ConVar.Find("test_vector_convar")?.Delete(); + + var conVar = ConVar.Create(new() + { + Name = "test_vector_convar", + DefaultValue = new Vector(1, 2, 3), + Description = "Test vector ConVar", + Flags = ConVarFlags.FCVAR_NOTIFY, + MinValue = new Vector(0, 0, 0), + MaxValue = new Vector(100, 100, 100) + }); + + Assert.NotNull(conVar); + Assert.Equal("test_vector_convar", conVar.Name); + Assert.Equal(ConVarType.Vector3, conVar.Type); + Assert.Equal("Test vector ConVar", conVar.Description); + Assert.Equal( + ConVarFlags.FCVAR_NOTIFY | ConVarFlags.FCVAR_GAMEDLL | ConVarFlags.FCVAR_RELEASE | ConVarFlags.FCVAR_CLIENT_CAN_EXECUTE, + conVar.Flags); + Assert.Equal(1, conVar.Value.X); + Assert.Equal(2, conVar.Value.Y); + Assert.Equal(3, conVar.Value.Z); + + conVar.Value = new Vector(500, 500, 500); + + // Test min/max constraints + Assert.Equal(100, conVar.Value.X); + Assert.Equal(100, conVar.Value.Y); + Assert.Equal(100, conVar.Value.Z); + + conVar.Delete(); + + var found = ConVar.Find("test_vector_convar"); + Assert.Null(found); + } + + [Fact] + public void CreateStringConVar() + { + ConVar.Find("test_string_convar")?.Delete(); + + var conVar = ConVar.Create("test_string_convar", "default_value", "Test string ConVar", ConVarFlags.FCVAR_NOTIFY); + Assert.NotNull(conVar); + Assert.Equal("test_string_convar", conVar.Name); + Assert.Equal(ConVarType.String, conVar.Type); + Assert.Equal("Test string ConVar", conVar.Description); + Assert.Equal( + ConVarFlags.FCVAR_NOTIFY | ConVarFlags.FCVAR_GAMEDLL | ConVarFlags.FCVAR_RELEASE | ConVarFlags.FCVAR_CLIENT_CAN_EXECUTE, + conVar.Flags); + Assert.Equal("default_value", conVar.Value); + + conVar.Delete(); + + var found = ConVar.Find("test_string_convar"); + Assert.Null(found); + } + + [Fact] + public void CreateFloatConVar() + { + ConVar.Find("test_float_convar")?.Delete(); + + var conVar = ConVar.Create(new() + { + Name = "test_float_convar", + DefaultValue = 1.23f, + Description = "Test float ConVar", + Flags = ConVarFlags.FCVAR_NOTIFY, + MinValue = 0f, + MaxValue = 25f + }); + Assert.NotNull(conVar); + Assert.Equal("test_float_convar", conVar.Name); + Assert.Equal(ConVarType.Float32, conVar.Type); + Assert.Equal("Test float ConVar", conVar.Description); + Assert.Equal( + ConVarFlags.FCVAR_NOTIFY | ConVarFlags.FCVAR_GAMEDLL | ConVarFlags.FCVAR_RELEASE | ConVarFlags.FCVAR_CLIENT_CAN_EXECUTE, + conVar.Flags); + Assert.Equal(1.23f, conVar.Value); + + // Test min/max constraints + conVar.Value = 50.0f; + Assert.Equal(25.0f, conVar.Value); + + conVar.Delete(); + + var found = ConVar.Find("test_float_convar"); + Assert.Null(found); + } + + [Fact] + public void CreateIntConVar() + { + ConVar.Find("test_int_convar")?.Delete(); + + var conVar = ConVar.Create(new() + { + Name = "test_int_convar", + DefaultValue = 42, + Description = "Test int ConVar", + Flags = ConVarFlags.FCVAR_NOTIFY, + MinValue = 0, + MaxValue = 100 + }); + Assert.NotNull(conVar); + Assert.Equal("test_int_convar", conVar.Name); + Assert.Equal(ConVarType.Int32, conVar.Type); + Assert.Equal("Test int ConVar", conVar.Description); + Assert.Equal( + ConVarFlags.FCVAR_NOTIFY | ConVarFlags.FCVAR_GAMEDLL | ConVarFlags.FCVAR_RELEASE | ConVarFlags.FCVAR_CLIENT_CAN_EXECUTE, + conVar.Flags); + Assert.Equal(42, conVar.Value); + + // Test min/max constraints + conVar.Value = 150; + Assert.Equal(100, conVar.Value); + + conVar.Delete(); + + var found = ConVar.Find("test_int_convar"); + Assert.Null(found); + } } \ No newline at end of file diff --git a/src/scripting/natives/natives_convars.cpp b/src/scripting/natives/natives_convars.cpp index 9852032b8..2a7557b69 100644 --- a/src/scripting/natives/natives_convars.cpp +++ b/src/scripting/natives/natives_convars.cpp @@ -111,6 +111,7 @@ static void GetConvarAccessIndexByName(ScriptContext& script_context) if (!ref.IsValidRef()) { + script_context.SetResult(0); return; } @@ -129,7 +130,11 @@ static void GetConvarValue(ScriptContext& script_context) return; } - if (!cvar.IsConVarDataValid()) return; + if (!cvar.IsConVarDataValid()) + { + script_context.ThrowNativeError("Convar data is not valid for access index %d.", convarAccessIndex); + return; + } switch (cvar.GetType()) { @@ -226,7 +231,11 @@ static void SetConvarValue(ScriptContext& script_context) return; } - if (!cvar.IsConVarDataValid()) return; + if (!cvar.IsConVarDataValid()) + { + script_context.ThrowNativeError("Convar data is not valid for access index %d.", convarAccessIndex); + return; + } switch (cvar.GetType()) { @@ -312,6 +321,149 @@ static void SetConvarValue(ScriptContext& script_context) } } +#define CREATE_CVAR(type) \ + auto createdConVar = new CConVar(name, flags, helpText, script_context.GetArgument(6), hasMin, \ + script_context.GetArgument(7), hasMax, script_context.GetArgument(8)); \ + createdConVarPtr = (void*)createdConVar; \ + createdConVarAccessIndex = createdConVar->GetAccessIndex(); + +#define CREATE_CVAR_PTR(type) \ + auto createdConVar = new CConVar(name, flags, helpText, *script_context.GetArgument(6), hasMin, \ + *script_context.GetArgument(7), hasMax, *script_context.GetArgument(8)); \ + createdConVarPtr = (void*)createdConVar; \ + createdConVarAccessIndex = createdConVar->GetAccessIndex(); + +static void CreateConVar(ScriptContext& script_context) +{ + auto name = script_context.GetArgument(0); + auto type = script_context.GetArgument(1); + auto helpText = script_context.GetArgument(2); + auto flags = script_context.GetArgument(3); + auto hasMin = script_context.GetArgument(4); + auto hasMax = script_context.GetArgument(5); + + // default, min, max is 6,7,8 + + ConVarRefAbstract cvar(name); + if (cvar.IsValidRef()) + { + script_context.ThrowNativeError("Convar with name '%s' already exists.", name); + return; + } + + uint16 createdConVarAccessIndex = 0; + void* createdConVarPtr = nullptr; + + switch (type) + { + case EConVarType_Int16: + { + CREATE_CVAR(int16); + break; + } + case EConVarType_UInt16: + { + CREATE_CVAR(uint16); + break; + } + case EConVarType_UInt32: + { + CREATE_CVAR(uint32); + break; + } + case EConVarType_Int32: + { + CREATE_CVAR(int32); + break; + } + case EConVarType_UInt64: + { + CREATE_CVAR(uint64); + break; + } + case EConVarType_Int64: + { + CREATE_CVAR(int64); + break; + } + case EConVarType_Bool: + { + CREATE_CVAR(bool); + break; + } + case EConVarType_Float32: + { + CREATE_CVAR(float32); + break; + } + case EConVarType_Float64: + { + CREATE_CVAR(float64); + break; + } + case EConVarType_String: + { + auto createdConVar = + new CConVar(name, flags, helpText, script_context.GetArgument(6), hasMin, + script_context.GetArgument(7), hasMax, script_context.GetArgument(8)); + createdConVarAccessIndex = createdConVar->GetAccessIndex(); + break; + } + case EConVarType_Color: + { + CREATE_CVAR_PTR(Color); + break; + } + case EConVarType_Vector2: + { + CREATE_CVAR_PTR(Vector2D); + break; + } + case EConVarType_Vector3: + { + CREATE_CVAR_PTR(Vector); + break; + } + case EConVarType_Vector4: + { + CREATE_CVAR_PTR(Vector4D); + break; + } + case EConVarType_Qangle: + { + CREATE_CVAR_PTR(QAngle); + break; + } + default: + { + script_context.ThrowNativeError("Unsupported convar type: %d", type); + return; + } + } + + script_context.SetResult(createdConVarAccessIndex); +} + +static void DeleteConVar(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto ref = ConVarRefAbstract(convarAccessIndex); + + if (!ref.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + if (ref.GetConVarData() == nullptr) + { + script_context.ThrowNativeError("Convar data is null."); + return; + } + + ref.GetConVarData()->Invalidate(); +} + REGISTER_NATIVES(convars, { ScriptEngine::RegisterNativeHandler("SET_CONVAR_FLAGS", SetConvarFlags); ScriptEngine::RegisterNativeHandler("GET_CONVAR_FLAGS", GetConvarFlags); @@ -321,5 +473,7 @@ REGISTER_NATIVES(convars, { ScriptEngine::RegisterNativeHandler("GET_CONVAR_ACCESS_INDEX_BY_NAME", GetConvarAccessIndexByName); ScriptEngine::RegisterNativeHandler("GET_CONVAR_VALUE", GetConvarValue); ScriptEngine::RegisterNativeHandler("SET_CONVAR_VALUE", SetConvarValue); + ScriptEngine::RegisterNativeHandler("CREATE_CONVAR", CreateConVar); + ScriptEngine::RegisterNativeHandler("DELETE_CONVAR", DeleteConVar); }) } // namespace counterstrikesharp diff --git a/src/scripting/natives/natives_convars.yaml b/src/scripting/natives/natives_convars.yaml index 35fab5c5b..c8b20277d 100644 --- a/src/scripting/natives/natives_convars.yaml +++ b/src/scripting/natives/natives_convars.yaml @@ -5,4 +5,6 @@ GET_CONVAR_NAME: convar:uint16 -> string GET_CONVAR_HELP_TEXT: convar:uint16 -> string GET_CONVAR_ACCESS_INDEX_BY_NAME: name:string -> uint16 GET_CONVAR_VALUE: convar:uint16 -> any -SET_CONVAR_VALUE: convar:uint16, value:any -> void \ No newline at end of file +SET_CONVAR_VALUE: convar:uint16, value:any -> void\ +CREATE_CONVAR: name:string, type:int16, helpText:string, flags:uint64, hasMin:bool, hasMax:bool, defaultValue:any, minValue:any, maxValue:any -> uint16 +DELETE_CONVAR: convar:uint16 -> void \ No newline at end of file From 498ad0697ff8fc10d24e4362baa8d2296a466ae1 Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Mon, 14 Jul 2025 08:15:25 +0000 Subject: [PATCH 3/9] chore: add obsolete, update test plugin --- .../Modules/Cvars/ConVar.cs | 3 +- managed/TestPlugin/TestPlugin.cs | 39 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVar.cs b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVar.cs index 26e7b36c4..16cc16e1d 100644 --- a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVar.cs +++ b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVar.cs @@ -4,6 +4,7 @@ namespace CounterStrikeSharp.API.Modules.Cvars; +[Obsolete("Use ConVar instead for type-safe access to ConVars.")] public class ConVar { public IntPtr Handle { get; } @@ -127,7 +128,7 @@ public string StringValue throw new InvalidOperationException( $"ConVar is a {Type} but you are trying to get a string value."); } - + NativeAPI.SetConvarStringValue(Handle, value); } } diff --git a/managed/TestPlugin/TestPlugin.cs b/managed/TestPlugin/TestPlugin.cs index 655c02b70..a88bbab76 100644 --- a/managed/TestPlugin/TestPlugin.cs +++ b/managed/TestPlugin/TestPlugin.cs @@ -124,16 +124,16 @@ private void SetupConvars() { RegisterListener(name => { - ConVar.Find("sv_cheats")?.SetValue(true); + ConVar.Find("sv_cheats")!.Value = true; - var numericCvar = ConVar.Find("mp_warmuptime"); - Logger.LogInformation("mp_warmuptime = {Value}", numericCvar?.GetPrimitiveValue()); + var numericCvar = ConVar.Find("mp_warmuptime"); + Logger.LogInformation("mp_warmuptime = {Value}", numericCvar?.Value); - var stringCvar = ConVar.Find("sv_skyname"); - Logger.LogInformation("sv_skyname = {Value}", stringCvar?.StringValue); + var stringCvar = ConVar.Find("sv_skyname"); + Logger.LogInformation("sv_skyname = {Value}", stringCvar?.Value); - var fogCvar = ConVar.Find("fog_color"); - Logger.LogInformation("fog_color = {Value}", fogCvar?.GetNativeValue()); + var fogCvar = ConVar.Find("fog_color"); + Logger.LogInformation("fog_color = {Value}", fogCvar?.Value); }); } @@ -205,10 +205,7 @@ private void SetupGameEvents() if (pawn == null) return HookResult.Continue; - Server.NextFrame(() => - { - player?.PrintToChat(activeWeapon?.DesignerName ?? "No Active Weapon"); - }); + Server.NextFrame(() => { player?.PrintToChat(activeWeapon?.DesignerName ?? "No Active Weapon"); }); // Set player to random colour pawn.Render = Color.FromArgb(Random.Shared.Next(0, 255), @@ -265,10 +262,7 @@ private void SetupListeners() }); // Hook global listeners defined by CounterStrikeSharp - RegisterListener(mapName => - { - Logger.LogInformation("Map {Map} has started!", mapName); - }); + RegisterListener(mapName => { Logger.LogInformation("Map {Map} has started!", mapName); }); RegisterListener(() => { Logger.LogInformation($"Map has ended."); }); RegisterListener((playerSlot, name, ip) => { @@ -357,21 +351,24 @@ private void SetupEntityOutputHooks() { HookEntityOutput("weapon_knife", "OnPlayerPickup", (output, _, activator, caller, _, delay) => { - Logger.LogInformation("weapon_knife called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", output.Description.Name, activator.DesignerName, caller.DesignerName, delay); + Logger.LogInformation("weapon_knife called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", + output.Description.Name, activator.DesignerName, caller.DesignerName, delay); return HookResult.Continue; }); HookEntityOutput("*", "*", (output, _, activator, caller, _, delay) => { - Logger.LogInformation("All EntityOutput ({name}, {activator}, {caller}, {delay})", output.Description.Name, activator.DesignerName, caller.DesignerName, delay); + Logger.LogInformation("All EntityOutput ({name}, {activator}, {caller}, {delay})", output.Description.Name, + activator.DesignerName, caller.DesignerName, delay); return HookResult.Continue; }); HookEntityOutput("*", "OnStartTouch", (_, name, activator, caller, _, delay) => { - Logger.LogInformation("OnStartTouch: ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay); + Logger.LogInformation("OnStartTouch: ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, + caller.DesignerName, delay); return HookResult.Continue; }); } @@ -609,9 +606,11 @@ private HookResult GenericEventHandler(T @event, GameEventInfo info) where T } [EntityOutputHook("*", "OnPlayerPickup")] - public HookResult OnPickup(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) + public HookResult OnPickup(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, + float delay) { - Logger.LogInformation("[EntityOutputHook Attribute] Called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay); + Logger.LogInformation("[EntityOutputHook Attribute] Called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", name, + activator.DesignerName, caller.DesignerName, delay); return HookResult.Continue; } From 003f255ec9c49dc98bd536c5265957b481d47933 Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Mon, 14 Jul 2025 08:25:51 +0000 Subject: [PATCH 4/9] chore: remove color --- src/scripting/natives/natives_convars.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/scripting/natives/natives_convars.cpp b/src/scripting/natives/natives_convars.cpp index 2a7557b69..cc29e0124 100644 --- a/src/scripting/natives/natives_convars.cpp +++ b/src/scripting/natives/natives_convars.cpp @@ -289,11 +289,6 @@ static void SetConvarValue(ScriptContext& script_context) cvar.SetString(script_context.GetArgument(1), server); break; } - case EConVarType_Color: - { - cvar.SetAs(*script_context.GetArgument(1), server); - break; - } case EConVarType_Vector2: { cvar.SetAs(*script_context.GetArgument(1), server); @@ -409,11 +404,6 @@ static void CreateConVar(ScriptContext& script_context) createdConVarAccessIndex = createdConVar->GetAccessIndex(); break; } - case EConVarType_Color: - { - CREATE_CVAR_PTR(Color); - break; - } case EConVarType_Vector2: { CREATE_CVAR_PTR(Vector2D); From 288f41fc98fb24f76023082c8799f7d631e73aae Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Mon, 14 Jul 2025 08:59:36 +0000 Subject: [PATCH 5/9] feat: add string manipulation --- managed/CounterStrikeSharp.API/Core/API.cs | 22 ++++++++ .../Modules/Cvars/ConVarOfT.cs | 44 ++++++++++++---- .../ConVarTests.cs | 4 ++ src/scripting/natives/natives_convars.cpp | 51 +++++++++++++++++++ src/scripting/natives/natives_convars.yaml | 2 + 5 files changed, 114 insertions(+), 9 deletions(-) diff --git a/managed/CounterStrikeSharp.API/Core/API.cs b/managed/CounterStrikeSharp.API/Core/API.cs index 92f3b7321..4236afc8f 100644 --- a/managed/CounterStrikeSharp.API/Core/API.cs +++ b/managed/CounterStrikeSharp.API/Core/API.cs @@ -295,6 +295,28 @@ public static T GetConvarValue(ushort convar){ } } + public static string GetConvarValueAsString(ushort convar){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.SetIdentifier(0x5CC184F8); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (string)ScriptContext.GlobalScriptContext.GetResult(typeof(string)); + } + } + + public static void SetConvarValueAsString(ushort convar, string value){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(convar); + ScriptContext.GlobalScriptContext.Push(value); + ScriptContext.GlobalScriptContext.SetIdentifier(0x5EF52D6C); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + } + } + public static object SetConvarValue(ushort convar, T value){ lock (ScriptContext.GlobalScriptContext.Lock) { ScriptContext.GlobalScriptContext.Reset(); diff --git a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs index 906df8bcd..ae16f745e 100644 --- a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs +++ b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs @@ -1,6 +1,3 @@ -using System; -using System.Runtime.CompilerServices; -using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; namespace CounterStrikeSharp.API.Modules.Cvars; @@ -25,7 +22,11 @@ public ConVar(ushort accessIndex) /// /// The ConVar flags as defined by . /// - public ConVarFlags Flags => (ConVarFlags)NativeAPI.GetConvarFlags(AccessIndex); + public ConVarFlags Flags + { + get => (ConVarFlags)NativeAPI.GetConvarFlags(AccessIndex); + set => NativeAPI.SetConvarFlags(AccessIndex, (ulong)value); + } public T Value { @@ -104,11 +105,6 @@ public T Value throw new InvalidOperationException( $"ConVar is a {Type} but you are trying to get a {type} value."); break; - case ConVarType.Color: - if (type != typeof(Vector)) - throw new InvalidOperationException( - $"ConVar is a {Type} but you are trying to get a {type} value."); - break; default: throw new InvalidOperationException($"Unknown ConVar type: {Type}"); } @@ -118,6 +114,12 @@ public T Value 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); @@ -126,6 +128,30 @@ public T Value 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}]"; + } + public sealed record ConVarCreationOptions { public required string Name { get; init; } diff --git a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs index 570cbb9f1..d57438a66 100644 --- a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs +++ b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs @@ -193,6 +193,10 @@ public void CreateFloatConVar() // Test min/max constraints conVar.Value = 50.0f; Assert.Equal(25.0f, conVar.Value); + Assert.Equal("25.0", conVar.ValueAsString); + + conVar.ValueAsString = "10.5"; + Assert.Equal(10.5f, conVar.Value); conVar.Delete(); diff --git a/src/scripting/natives/natives_convars.cpp b/src/scripting/natives/natives_convars.cpp index cc29e0124..77d7f7d74 100644 --- a/src/scripting/natives/natives_convars.cpp +++ b/src/scripting/natives/natives_convars.cpp @@ -118,6 +118,55 @@ static void GetConvarAccessIndexByName(ScriptContext& script_context) script_context.SetResult(ref.GetAccessIndex()); } +static void GetConvarValueAsString(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto ref = ConVarRefAbstract(convarAccessIndex); + CSplitScreenSlot server(0); + + if (!ref.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + if (!ref.IsConVarDataValid()) + { + script_context.ThrowNativeError("Convar data is not valid for access index %d.", convarAccessIndex); + return; + } + + CBufferString buf; + ref.GetValueAsString(buf, server); + script_context.SetResult(buf.Get()); +} + +static void SetConvarValueAsString(ScriptContext& script_context) +{ + auto convarAccessIndex = script_context.GetArgument(0); + auto ref = ConVarRefAbstract(convarAccessIndex); + CSplitScreenSlot server(0); + + if (!ref.IsValidRef()) + { + script_context.ThrowNativeError("Invalid convar access index."); + return; + } + + if (!ref.IsConVarDataValid()) + { + script_context.ThrowNativeError("Convar data is not valid for access index %d.", convarAccessIndex); + return; + } + + auto value = script_context.GetArgument(1); + if (!ref.SetString(value, server)) + { + script_context.ThrowNativeError("Failed to set value for convar %s.", ref.GetName()); + return; + } +} + static void GetConvarValue(ScriptContext& script_context) { auto convarAccessIndex = script_context.GetArgument(0); @@ -462,6 +511,8 @@ REGISTER_NATIVES(convars, { ScriptEngine::RegisterNativeHandler("GET_CONVAR_HELP_TEXT", GetConvarHelpText); ScriptEngine::RegisterNativeHandler("GET_CONVAR_ACCESS_INDEX_BY_NAME", GetConvarAccessIndexByName); ScriptEngine::RegisterNativeHandler("GET_CONVAR_VALUE", GetConvarValue); + ScriptEngine::RegisterNativeHandler("GET_CONVAR_VALUE_AS_STRING", GetConvarValueAsString); + ScriptEngine::RegisterNativeHandler("SET_CONVAR_VALUE_AS_STRING", SetConvarValueAsString); ScriptEngine::RegisterNativeHandler("SET_CONVAR_VALUE", SetConvarValue); ScriptEngine::RegisterNativeHandler("CREATE_CONVAR", CreateConVar); ScriptEngine::RegisterNativeHandler("DELETE_CONVAR", DeleteConVar); diff --git a/src/scripting/natives/natives_convars.yaml b/src/scripting/natives/natives_convars.yaml index c8b20277d..918084f72 100644 --- a/src/scripting/natives/natives_convars.yaml +++ b/src/scripting/natives/natives_convars.yaml @@ -5,6 +5,8 @@ GET_CONVAR_NAME: convar:uint16 -> string GET_CONVAR_HELP_TEXT: convar:uint16 -> string GET_CONVAR_ACCESS_INDEX_BY_NAME: name:string -> uint16 GET_CONVAR_VALUE: convar:uint16 -> any +GET_CONVAR_VALUE_AS_STRING: convar:uint16 -> string +SET_CONVAR_VALUE_AS_STRING: convar:uint16, value:string -> void SET_CONVAR_VALUE: convar:uint16, value:any -> void\ CREATE_CONVAR: name:string, type:int16, helpText:string, flags:uint64, hasMin:bool, hasMax:bool, defaultValue:any, minValue:any, maxValue:any -> uint16 DELETE_CONVAR: convar:uint16 -> void \ No newline at end of file From 8f4fcc70a6fb878dcd6177416e3c91dea2d690ea Mon Sep 17 00:00:00 2001 From: roflmuffin Date: Mon, 14 Jul 2025 18:58:26 +1000 Subject: [PATCH 6/9] fix: update define --- src/scripting/natives/natives_convars.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/scripting/natives/natives_convars.cpp b/src/scripting/natives/natives_convars.cpp index 77d7f7d74..a8d8c2115 100644 --- a/src/scripting/natives/natives_convars.cpp +++ b/src/scripting/natives/natives_convars.cpp @@ -14,9 +14,7 @@ * along with CounterStrikeSharp. If not, see . * */ -#define private public -#define protected public - +#define private public #include "core/log.h" #include "scripting/autonative.h" #include "scripting/script_engine.h" @@ -24,7 +22,6 @@ #include #include #undef private -#undef protected namespace counterstrikesharp { From 8d9ea3f3305a0cee4d773cb5b2d09f17c95c3299 Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Mon, 14 Jul 2025 09:01:01 +0000 Subject: [PATCH 7/9] fix: tests --- managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs index d57438a66..0e8a17bdc 100644 --- a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs +++ b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs @@ -193,7 +193,7 @@ public void CreateFloatConVar() // Test min/max constraints conVar.Value = 50.0f; Assert.Equal(25.0f, conVar.Value); - Assert.Equal("25.0", conVar.ValueAsString); + Assert.Equal("25.000000", conVar.ValueAsString); conVar.ValueAsString = "10.5"; Assert.Equal(10.5f, conVar.Value); From a84f4244cdec21c5284b3ef478e8d2245ded8673 Mon Sep 17 00:00:00 2001 From: roflmuffin Date: Sun, 20 Jul 2025 11:43:38 +0000 Subject: [PATCH 8/9] 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 a4a2b47f0..aacc218b2 100644 --- a/managed/CounterStrikeSharp.API/Core/BasePlugin.cs +++ b/managed/CounterStrikeSharp.API/Core/BasePlugin.cs @@ -129,6 +129,8 @@ public void Dispose() 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 @@ public void RegisterAllAttributes(object instance) this.RegisterAttributeHandlers(instance); this.RegisterConsoleCommandAttributeHandlers(instance); this.RegisterEntityOutputAttributeHandlers(instance); + this.RegisterConVars(instance); this.RegisterFakeConVars(instance); } @@ -524,6 +527,19 @@ public void RegisterFakeConVars(Type type, object instance = null) }); } } + + 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 @@ public void RegisterFakeConVars(object instance) RegisterFakeConVars(instance.GetType(), instance); } + public void RegisterConVars(object instance) { + RegisterConVars(instance.GetType(), instance); + } + /// /// Hooks an entity output. /// @@ -657,6 +677,11 @@ protected virtual void Dispose(bool disposing) 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 ae16f745e..3e38272a5 100644 --- a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs +++ b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs @@ -2,14 +2,9 @@ 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 ConVarFlags Flags 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 T Value 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 string ValueAsString 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 sealed record ConVarCreationOptions 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 0e8a17bdc..92522c73c 100644 --- a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs +++ b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs @@ -91,7 +91,7 @@ public void CreateBoolConVar() { 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 void CreateVectorConVar() { 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 void CreateStringConVar() { 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 void CreateFloatConVar() { 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 void CreateIntConVar() { 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 a88bbab76..093a3a2c8 100644 --- a/managed/TestPlugin/TestPlugin.cs +++ b/managed/TestPlugin/TestPlugin.cs @@ -65,6 +65,15 @@ public void OnConfigParsed(SampleConfig config) 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; From 149787d1391dc21da0522cd7f8fbf6919d9254d6 Mon Sep 17 00:00:00 2001 From: roflmuffin Date: Sun, 20 Jul 2025 11:45:19 +0000 Subject: [PATCH 9/9] chore: extract ConVarCreationOptions --- .../Modules/Cvars/ConVarOfT.cs | 22 +++++++++---------- .../ConVarTests.cs | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs index 3e38272a5..8af1e274f 100644 --- a/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs +++ b/managed/CounterStrikeSharp.API/Modules/Cvars/ConVarOfT.cs @@ -66,7 +66,7 @@ public ConVar(ushort 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 + T? minValue = default, T? maxValue = default) : this(new ConVarCreationOptions { Name = name, DefaultValue = defaultValue, @@ -78,7 +78,7 @@ public ConVar(ushort accessIndex) { } - public ConVar(ConVarCreationOptions options) + public ConVar(ConVarCreationOptions options) { var type = typeof(T); var conVarType = type switch @@ -210,14 +210,14 @@ public override string ToString() { return $"ConVar [name={Name}, value={Value}, description={Description}, type={Type}, flags={Flags}]"; } +} - public sealed record ConVarCreationOptions - { - public required string Name { get; init; } - public required T DefaultValue { get; init; } - public string Description { get; init; } = string.Empty; - public ConVarFlags Flags { get; init; } = ConVarFlags.FCVAR_NONE; - public T? MinValue { get; init; } - public T? MaxValue { get; init; } - } +public sealed record ConVarCreationOptions +{ + public required string Name { get; init; } + public required T DefaultValue { get; init; } + public string Description { get; init; } = string.Empty; + public ConVarFlags Flags { get; init; } = ConVarFlags.FCVAR_NONE; + public T? MinValue { get; init; } + public T? MaxValue { get; init; } } \ No newline at end of file diff --git a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs index 92522c73c..48b359933 100644 --- a/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs +++ b/managed/CounterStrikeSharp.Tests.Native/ConVarTests.cs @@ -112,7 +112,7 @@ public void CreateVectorConVar() { ConVar.Find("test_vector_convar")?.Delete(); - var conVar = new ConVar(new ConVar.ConVarCreationOptions() + var conVar = new ConVar(new ConVarCreationOptions() { Name = "test_vector_convar", DefaultValue = new Vector(1, 2, 3), @@ -172,7 +172,7 @@ public void CreateFloatConVar() { ConVar.Find("test_float_convar")?.Delete(); - var conVar = new ConVar(new ConVar.ConVarCreationOptions() + var conVar = new ConVar(new ConVarCreationOptions() { Name = "test_float_convar", DefaultValue = 1.23f, @@ -209,7 +209,7 @@ public void CreateIntConVar() { ConVar.Find("test_int_convar")?.Delete(); - var conVar = new ConVar(new ConVar.ConVarCreationOptions() + var conVar = new ConVar(new ConVarCreationOptions() { Name = "test_int_convar", DefaultValue = 42,