diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index f971a0c1a..2ce35da86 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -2,6 +2,7 @@ on: push: branches: - main + workflow_dispatch: permissions: contents: read diff --git a/docfx/docs/features/game-events.md b/docfx/docs/features/game-events.md index 3c330a3f7..5faab3797 100644 --- a/docfx/docs/features/game-events.md +++ b/docfx/docs/features/game-events.md @@ -54,6 +54,9 @@ The specific subclass of `GameEvent` will provide strongly typed parameters from These event properties are mutable so you can update them as normal and they will update in the event instance. +> [!CAUTION] +> `GameEvent` instances and their properties will cease to exist after the event listener function is called, which means that you will encounter errors when accessing properties in timers and functions like `Server.NextFrame()`. You should store the value of properties in variables before calling functions like `Server.NextFrame()` so you can read the data safely. + ## Preventing Broadcast You can modify a game event so that it does not get broadcast to clients by modifying the `bool info.DontBroadcast` property. e.g. diff --git a/docfx/docs/reference/referencing-players.md b/docfx/docs/guides/referencing-players.md similarity index 100% rename from docfx/docs/reference/referencing-players.md rename to docfx/docs/guides/referencing-players.md diff --git a/docfx/docs/guides/toc.yml b/docfx/docs/guides/toc.yml index 477af3f63..903a358f0 100644 --- a/docfx/docs/guides/toc.yml +++ b/docfx/docs/guides/toc.yml @@ -6,3 +6,6 @@ - name: Dependency Injection href: dependency-injection.md + +- name: Referencing Players + href: referencing-players.md \ No newline at end of file diff --git a/docfx/docs/reference/toc.yml b/docfx/docs/reference/toc.yml index b4de3a76c..62b630950 100644 --- a/docfx/docs/reference/toc.yml +++ b/docfx/docs/reference/toc.yml @@ -1,5 +1,2 @@ - name: Core Configuration - href: core-configuration.md - -- name: Referencing Players - href: referencing-players.md + href: core-configuration.md \ No newline at end of file diff --git a/managed/CounterStrikeSharp.API/Core/Model/CBaseEntity.cs b/managed/CounterStrikeSharp.API/Core/Model/CBaseEntity.cs index 64cd31a14..d42d09874 100644 --- a/managed/CounterStrikeSharp.API/Core/Model/CBaseEntity.cs +++ b/managed/CounterStrikeSharp.API/Core/Model/CBaseEntity.cs @@ -19,8 +19,14 @@ public void Teleport(Vector? position = null, QAngle? angles = null, Vector? vel nint _position = position?.Handle ?? 0; nint _angles = angles?.Handle ?? 0; nint _velocity = velocity?.Handle ?? 0; - - VirtualFunction.CreateVoid(Handle, GameData.GetOffset("CBaseEntity_Teleport"))(Handle, _position, _angles, _velocity); + nint _handle = Handle; + + if (this is CCSPlayerController player && player.PlayerPawn.Value is CCSPlayerPawn playerPawn) + { + _handle = playerPawn.Handle; + } + + VirtualFunction.CreateVoid(_handle, GameData.GetOffset("CBaseEntity_Teleport"))(_handle, _position, _angles, _velocity); } /// Entity is not valid diff --git a/managed/CounterStrikeSharp.API/Core/Model/CTakeDamageInfo.cs b/managed/CounterStrikeSharp.API/Core/Model/CTakeDamageInfo.cs new file mode 100644 index 000000000..b616e3c96 --- /dev/null +++ b/managed/CounterStrikeSharp.API/Core/Model/CTakeDamageInfo.cs @@ -0,0 +1,32 @@ +using System.Runtime.InteropServices; + +namespace CounterStrikeSharp.API.Core; + +public partial class CTakeDamageInfo +{ + /// + /// Retrieves the hitgroup + /// + /// + /// Returns a enumeration representing the player's current hit group, + /// or if the hit group cannot be determined. + /// + public HitGroup_t GetHitGroup() + { + IntPtr v4 = Marshal.ReadIntPtr(Handle, 0x78); + + if (v4 == nint.Zero) + { + return HitGroup_t.HITGROUP_INVALID; + } + + IntPtr v1 = Marshal.ReadIntPtr(v4, 16); + + if (v1 == nint.Zero) + { + return HitGroup_t.HITGROUP_GENERIC; + } + + return (HitGroup_t)Marshal.ReadInt32(v1, 56); + } +} diff --git a/managed/CounterStrikeSharp.API/Modules/Extensions/PluginConfigExtensions.cs b/managed/CounterStrikeSharp.API/Modules/Extensions/PluginConfigExtensions.cs new file mode 100644 index 000000000..cdd666912 --- /dev/null +++ b/managed/CounterStrikeSharp.API/Modules/Extensions/PluginConfigExtensions.cs @@ -0,0 +1,81 @@ +using System.Text.Json; +using System.Reflection; + +namespace CounterStrikeSharp.API.Modules.Extensions; + +public static class PluginConfigExtensions +{ + private static readonly JsonSerializerOptions _jsonSerializerOptions = new() + { + WriteIndented = true + }; + + public static JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions; + + /// + /// Gets the configuration file path + /// + /// Type of the plugin configuration. + /// Current configuration instance + public static string GetConfigPath(this T _) where T : BasePluginConfig, new() + { + string assemblyName = typeof(T).Assembly.GetName().Name ?? string.Empty; + return Path.Combine(Server.GameDirectory, "csgo", "addons", "counterstrikesharp", "configs", "plugins", assemblyName, $"{assemblyName}.json"); + } + + /// + /// Updates the configuration file + /// + /// Type of the plugin configuration. + /// Current configuration instance + public static void Update(this T config) where T : BasePluginConfig, new() + { + var configPath = config.GetConfigPath(); + + try + { + using var stream = new FileStream(configPath, FileMode.Create, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(stream); + writer.Write(JsonSerializer.Serialize(config, JsonSerializerOptions)); + } + catch (Exception ex) + { + throw new Exception($"Failed to update configuration file at '{configPath}'.", ex); + } + } + + /// + /// Reloads the configuration file and updates current configuration instance. + /// + /// Type of the plugin configuration. + /// Current configuration instance + public static void Reload(this T config) where T : BasePluginConfig, new() + { + var configPath = config.GetConfigPath(); + + try + { + if (!File.Exists(configPath)) + { + throw new FileNotFoundException($"Configuration file '{configPath} not found."); + } + + var configContent = File.ReadAllText(configPath); + + var newConfig = JsonSerializer.Deserialize(configContent) + ?? throw new JsonException($"Deserialization failed for configuration file '{configPath}'."); + + foreach (var property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (property.CanWrite) + { + property.SetValue(config, property.GetValue(newConfig)); + } + } + } + catch (Exception ex) + { + throw new Exception($"Failed to reload configuration file at '{configPath}'.", ex); + } + } +}