Compare commits

...

6 Commits
v83 ... v1.0.87

Author SHA1 Message Date
Michael Wilson
1354b4972d docs: add some example plugins (#154) 2023-12-01 13:27:55 +10:00
johnoclockdk
8b5eb7e38d Update README.md (#153) 2023-11-30 17:08:09 +10:00
Roflmuffin
2dd62c44d3 docs: add missing core config doc 2023-11-30 16:07:28 +10:00
Roflmuffin
f811338ce4 feat: add option to disable plugin hot reload, closes #151
Also adds default values to unmanaged core config
2023-11-30 13:49:24 +10:00
Daniel Saewitz
194c340ae7 Improve plugin setup docs (#152) 2023-11-30 09:58:38 +10:00
Roflmuffin
6b0912d3cd hotfix: revert entity enumeration 2023-11-29 23:09:38 +10:00
27 changed files with 398 additions and 36 deletions

View File

@@ -63,6 +63,10 @@ public class HelloWorldPlugin : BasePlugin
public override string ModuleVersion => "0.0.1";
public override string ModuleAuthor => "roflmuffin";
public override string ModuleDescription => "Simple hello world plugin";
public override void Load(bool hotReload)
{
Logger.LogInformation("Plugin loaded successfully!");

View File

@@ -1,5 +1,6 @@
{
"PublicChatTrigger": [ "!" ],
"SilentChatTrigger": [ "/" ],
"FollowCS2ServerGuidelines": true
"FollowCS2ServerGuidelines": true,
"PluginHotReloadEnabled": true
}

View File

@@ -68,19 +68,20 @@ Now build your project using your ide or the `dotnet build` command. You should
### Installing your Plugin
Locate the `plugins` folder in your CS2 dedicated server (`/game/csgo/addons/counterstrikesharp/plugins`) and create a new folder with the exact same name as your output .dll file. In this example it would be `HelloWorldPlugin.dll`, so I will make a new folder called `HelloWorldPlugin`. Inside of this folder, copy and paste all of the `.dll` files _except_ the `CounterStrikeSharp.API.dll` file. Once completed, the folder should look as follows:
Locate the `plugins` folder in your CS2 dedicated server (`/game/csgo/addons/counterstrikesharp/plugins`) and create a new folder with the exact same name as your output .dll file. In this example it would be `HelloWorldPlugin.dll`, so I will make a new folder called `HelloWorldPlugin`. Inside of this folder, copy and paste the: `HelloWorldPlugin.deps.json`, `HelloWorldPlugin.dll`, and `HelloWorldPlugin.pdb` files. Once completed, the folder should look as follows:
```shell
.
└── SamplePlugin
├── McMaster.NETCore.Plugins.dll
├── Microsoft.DotNet.PlatformAbstractions.dll
── Microsoft.Extensions.DependencyModel.dll
├── SamplePlugin.deps.json
├── SamplePlugin.dll
└── SamplePlugin.pdb
└── HelloWorldPlugin
├── HelloWorldPlugin.deps.json
├── HelloWorldPlugin.dll
── HelloWorldPlugin.pdb
```
:::caution
If you have installed external nuget packages for your plugin, you may need to include their respective `.dll`s. For example, if you utilize the `Stateless` C# library, include `stateless.dll` in your `HelloWorld` plugin directory.
:::
:::note
Note that some of these dependencies may change depending on the version of CounterStrikeSharp being used.
:::

View File

@@ -25,6 +25,10 @@ public class HelloWorldPlugin : BasePlugin
public override string ModuleVersion => "0.0.1";
public override string ModuleAuthor => "roflmuffin";
public override string ModuleDescription => "Simple hello world plugin";
public override void Load(bool hotReload)
{
Logger.LogInformation("Plugin loaded successfully!");

View File

@@ -23,4 +23,8 @@ receive a ban.
:::note
Disable this option at your own risk.
:::
:::
## PluginHotReloadEnabled
When enabled, plugins are automatically reloaded when their .dll file is updated.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using Microsoft.Extensions.Logging;
namespace HelloWorld;
[MinimumApiVersion(80)]
public class HelloWorldPlugin : BasePlugin
{
public override string ModuleName => "Example: Hello World";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A simple plugin that says hello world!";
public override void Load(bool hotReload)
{
Logger.LogInformation("Hello World! We are loading!");
}
public override void Unload(bool hotReload)
{
Logger.LogInformation("Hello World! We are unloading!");
}
}

View File

@@ -0,0 +1,2 @@
# Hello World
This is a minimal "Hello World" example that executes code on load & unload.

View File

@@ -0,0 +1,4 @@
# With Commands
This is an example that shows how to register chat & console commands.
All commands that are prefixed with "css_" will automatically be registered as a chat command without the prefix. i.e. `css_ping` can be called with `!ping` or `/ping`.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,63 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
namespace WithCommands;
[MinimumApiVersion(80)]
public class WithCommandsPlugin : BasePlugin
{
public override string ModuleName => "Example: With Commands";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A simple plugin that registers some commands";
public override void Load(bool hotReload)
{
// All commands that are prefixed with "css_" will automatically be registered as a chat command without the prefix.
// i.e. `css_ping` can be called with `!ping` or `/ping`.
// Commands can be registered using the instance `AddCommand` method.
AddCommand("css_ping", "Responds to the caller with \"pong\"", (player, commandInfo) =>
{
// The player is null, then the command has been called by the server console.
if (player == null)
{
commandInfo.ReplyToCommand("pong server");
return;
}
commandInfo.ReplyToCommand("pong");
});
}
// Commands can also be registered using the `Command` attribute.
[ConsoleCommand("css_hello", "Responds to the caller with \"pong\"")]
// The `CommandHelper` attribute can be used to provide additional information about the command.
[CommandHelper(minArgs: 1, usage: "[name]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
[RequiresPermissions("@css/cvar")]
public void OnHelloCommand(CCSPlayerController? player, CommandInfo commandInfo)
{
// The first argument is the command name, in this case "css_hello".
commandInfo.GetArg(0); // css_hello
// The second argument is the first argument passed to the command, in this case "name".
// The `minArgs` helper parameter is used to ensure that the second argument is present.
var name = commandInfo.GetArg(1);
commandInfo.ReplyToCommand($"Hello {name}");
}
// Permissions can be added to commands using the `RequiresPermissions` attribute.
// See the admin documentation for more information on permissions.
[RequiresPermissions("@css/kick")]
[CommandHelper(minArgs: 1, usage: "[id]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSpecialCommand(CCSPlayerController? player, CommandInfo commandInfo)
{
var id = commandInfo.GetArg(1);
Server.ExecuteCommand($"kick {id}");
}
}

View File

@@ -0,0 +1,2 @@
# WithConfig
This example shows how you can implement the `IPluginConfig` interface to allow for semi-automatic config parsing & loading.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
namespace WithConfig;
public class SampleConfig : BasePluginConfig
{
[JsonPropertyName("ChatPrefix")] public string ChatPrefix { get; set; } = "My Cool Plugin";
[JsonPropertyName("ChatInterval")] public float ChatInterval { get; set; } = 60;
}
[MinimumApiVersion(80)]
public class WithConfigPlugin : BasePlugin, IPluginConfig<SampleConfig>
{
public override string ModuleName => "Example: With Config";
public override string ModuleVersion => "1.0.0";
public SampleConfig Config { get; set; }
public void OnConfigParsed(SampleConfig config)
{
// Do manual verification of the config and override any invalid values
if (config.ChatInterval > 60)
{
config.ChatInterval = 60;
}
if (config.ChatPrefix.Length > 25)
{
throw new Exception($"Invalid value has been set to config value 'ChatPrefix': {config.ChatPrefix}");
}
// Once we've validated the config, we can set it to the instance
Config = config;
}
}

View File

@@ -0,0 +1,2 @@
# With Dependency Injection
This example shows how can you implement the `IPluginServiceCollection` interface to register your services in a provided DI container.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,50 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace WithDependencyInjection;
[MinimumApiVersion(80)]
public class WithDependencyInjectionPlugin : BasePlugin
{
public override string ModuleName => "Example: Dependency Injection";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "An example plugin that uses dependency injection.";
private readonly TestInjectedClass _testInjectedClass;
public WithDependencyInjectionPlugin(TestInjectedClass testInjectedClass)
{
_testInjectedClass = testInjectedClass;
}
public override void Load(bool hotReload)
{
_testInjectedClass.SayHello();
}
}
public class WithDependencyInjectionPluginServiceCollection : IPluginServiceCollection<WithDependencyInjectionPlugin>
{
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<TestInjectedClass>();
}
}
public class TestInjectedClass
{
private readonly ILogger<TestInjectedClass> _logger;
public TestInjectedClass(ILogger<TestInjectedClass> logger)
{
_logger = logger;
}
public void SayHello()
{
_logger.LogInformation("Hello World from Test Injected Class");
}
}

View File

@@ -0,0 +1,2 @@
# With Game Event Handlers
This is example shows how you can subscribe to legacy Source 1 game events.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,54 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using Microsoft.Extensions.Logging;
namespace WithGameEventHandlers;
[MinimumApiVersion(80)]
public class WithGameEventHandlersPlugin : BasePlugin
{
public override string ModuleName => "Example: With Game Event Handlers";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A simple plugin that subscribes to game events";
public override void Load(bool hotReload)
{
// Subscriptions can be added via the instance method
RegisterEventHandler<EventPlayerDeath>((@event, info) =>
{
// You can use `info.DontBroadcast` to set the dont broadcast flag on the event (in pre handlers)
// This will prevent the event from being broadcast to other clients.
// In this example we prevent kill-feed messages from being broadcast if it was not a headshot.
if (!@event.Headshot)
{
@event.Attacker.PrintToChat($"Skipping player_death broadcast");
info.DontBroadcast = true;
}
return HookResult.Continue;
}, HookMode.Pre);
}
// Subscriptions can be added via an attribute
[GameEventHandler]
public HookResult OnPlayerBlind(EventPlayerBlind @event, GameEventInfo info)
{
Logger.LogInformation("Player was just blinded for {Duration}", @event.BlindDuration);
return HookResult.Continue;
}
// The event name is inferred from the event type you pass to the first argument.
// e.g. EventRoundStart becomes "round_start"
// Note: You can use the `HookMode` enum to specify the hook mode
// If you do not specify a hook mode, it will default to `HookMode.Post`
[GameEventHandler(HookMode.Pre)]
public HookResult OnEventRoundStartPre(EventRoundStart @event, GameEventInfo info)
{
Logger.LogInformation("Round has started with Timelimit: {Timelimit}", @event.Timelimit);
return HookResult.Continue;
}
}

View File

@@ -43,6 +43,9 @@ namespace CounterStrikeSharp.API.Core
[JsonPropertyName("FollowCS2ServerGuidelines")]
public bool FollowCS2ServerGuidelines { get; set; } = true;
[JsonPropertyName("PluginHotReloadEnabled")]
public bool PluginHotReloadEnabled { get; set; } = true;
}
/// <summary>
@@ -80,6 +83,11 @@ namespace CounterStrikeSharp.API.Core
/// </para>
/// </summary>
public static bool FollowCS2ServerGuidelines => _coreConfig.FollowCS2ServerGuidelines;
/// <summary>
/// When enabled, plugins are automatically reloaded when their .dll file is updated.
/// </summary>
public static bool PluginHotReloadEnabled => _coreConfig.PluginHotReloadEnabled;
}
public partial class CoreConfig : IStartupService

View File

@@ -23,6 +23,10 @@ namespace CounterStrikeSharp.API.Core
{
T Config { get; set; }
/// <summary>
/// Called when the `ConfigManager` has parsed the configuration file for this plugin
/// </summary>
/// <param name="config">Parsed config instance</param>
public void OnConfigParsed(T config);
}
}

View File

@@ -64,23 +64,26 @@ namespace CounterStrikeSharp.API.Core.Plugin
config.IsUnloadable = true;
});
_fileWatcher = new FileSystemWatcher
if (CoreConfig.PluginHotReloadEnabled)
{
Path = Path.GetDirectoryName(path)
};
_fileWatcher.Deleted += async (s, e) =>
{
if (e.FullPath == path)
_fileWatcher = new FileSystemWatcher
{
_logger.LogInformation("Plugin {Name} has been deleted, unloading...", Plugin.ModuleName);
Unload(true);
}
};
Path = Path.GetDirectoryName(path)
};
_fileWatcher.Filter = "*.dll";
_fileWatcher.EnableRaisingEvents = true;
Loader.Reloaded += async (s, e) => await OnReloadedAsync(s, e);
_fileWatcher.Deleted += async (s, e) =>
{
if (e.FullPath == path)
{
_logger.LogInformation("Plugin {Name} has been deleted, unloading...", Plugin.ModuleName);
Unload(true);
}
};
_fileWatcher.Filter = "*.dll";
_fileWatcher.EnableRaisingEvents = true;
Loader.Reloaded += async (s, e) => await OnReloadedAsync(s, e);
}
}
private Task OnReloadedAsync(object sender, PluginReloadedEventArgs eventargs)

View File

@@ -78,12 +78,10 @@ namespace CounterStrikeSharp.API
public static IEnumerable<T> FindAllEntitiesByDesignerName<T>(string designerName) where T : CEntityInstance
{
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
for (; pEntity != null && pEntity.EntityHandle.IsValid; pEntity = pEntity.Next)
for (; pEntity != null && pEntity.Handle != IntPtr.Zero; pEntity = pEntity.Next)
{
var value = pEntity.EntityInstance.EntityHandle.Value;
if (value == null) continue;
yield return value.As<T>();
if (!pEntity.DesignerName.Contains(designerName)) continue;
yield return new PointerTo<T>(pEntity.Handle).Value;
}
}
@@ -92,10 +90,7 @@ namespace CounterStrikeSharp.API
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
for (; pEntity != null && pEntity.Handle != IntPtr.Zero; pEntity = pEntity.Next)
{
var value = pEntity.EntityInstance.EntityHandle.Value;
if (value == null) continue;
yield return value;
yield return new PointerTo<CEntityInstance>(pEntity.Handle).Value;
}
}

View File

@@ -12,6 +12,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WarcraftPlugin", "..\exampl
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithConfig", "..\examples\WithConfig\WithConfig.csproj", "{2846604A-5B9F-4D80-9476-657C09CFDD5C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloWorld", "..\examples\HelloWorld\HelloWorld.csproj", "{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithDependencyInjection", "..\examples\WithDependencyInjection\WithDependencyInjection.csproj", "{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithCommands", "..\examples\WithCommands\WithCommands.csproj", "{EA2F596E-2236-4999-B476-B1FDA287674A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithGameEventHandlers", "..\examples\WithGameEventHandlers\WithGameEventHandlers.csproj", "{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -38,9 +48,34 @@ Global
{DAE388A8-94A4-4C24-9450-E34677EEA2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAE388A8-94A4-4C24-9450-E34677EEA2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAE388A8-94A4-4C24-9450-E34677EEA2CF}.Release|Any CPU.Build.0 = Release|Any CPU
{2846604A-5B9F-4D80-9476-657C09CFDD5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2846604A-5B9F-4D80-9476-657C09CFDD5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2846604A-5B9F-4D80-9476-657C09CFDD5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2846604A-5B9F-4D80-9476-657C09CFDD5C}.Release|Any CPU.Build.0 = Release|Any CPU
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}.Release|Any CPU.Build.0 = Release|Any CPU
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}.Release|Any CPU.Build.0 = Release|Any CPU
{EA2F596E-2236-4999-B476-B1FDA287674A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA2F596E-2236-4999-B476-B1FDA287674A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA2F596E-2236-4999-B476-B1FDA287674A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA2F596E-2236-4999-B476-B1FDA287674A}.Release|Any CPU.Build.0 = Release|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{DAE388A8-94A4-4C24-9450-E34677EEA2CF} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{2846604A-5B9F-4D80-9476-657C09CFDD5C} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{EA2F596E-2236-4999-B476-B1FDA287674A} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
EndGlobalSection
EndGlobal

View File

@@ -47,9 +47,10 @@ bool CCoreConfig::Init(char* conf_error, int conf_error_size)
m_json = json::parse(ifs);
try {
PublicChatTrigger = m_json["PublicChatTrigger"];
SilentChatTrigger = m_json["SilentChatTrigger"];
FollowCS2ServerGuidelines = m_json["FollowCS2ServerGuidelines"];
PublicChatTrigger = m_json.value("PublicChatTrigger", PublicChatTrigger);
SilentChatTrigger = m_json.value("SilentChatTrigger", SilentChatTrigger);
FollowCS2ServerGuidelines = m_json.value("FollowCS2ServerGuidelines", FollowCS2ServerGuidelines);
PluginHotReloadEnabled = m_json.value("PluginHotReloadEnabled", PluginHotReloadEnabled);
} catch (const std::exception& ex) {
V_snprintf(conf_error, conf_error_size, "Failed to parse CoreConfig file: %s", ex.what());
return false;

View File

@@ -28,6 +28,7 @@ class CCoreConfig
std::vector<std::string> PublicChatTrigger = { std::string("!") };
std::vector<std::string> SilentChatTrigger = { std::string("/") };
bool FollowCS2ServerGuidelines = true;
bool PluginHotReloadEnabled = true;
using json = nlohmann::json;
CCoreConfig(const std::string& path);