mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-05 23:58:24 -08:00
Command Overhaul (#330)
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Logging;
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
using CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -42,6 +44,7 @@ public static class Bootstrap
|
||||
services.AddSingleton<IPluginManager, PluginManager>();
|
||||
services.AddSingleton<IPlayerLanguageManager, PlayerLanguageManager>();
|
||||
services.AddScoped<IPluginContextQueryHandler, PluginContextQueryHandler>();
|
||||
services.AddSingleton<ICommandManager, CommandManager>();
|
||||
|
||||
services.Scan(i => i.FromCallingAssembly()
|
||||
.AddClasses(c => c.AssignableTo<IStartupService>())
|
||||
@@ -50,12 +53,13 @@ public static class Bootstrap
|
||||
})
|
||||
.Build();
|
||||
|
||||
using IServiceScope scope = host.Services.CreateScope();
|
||||
using IServiceScope rootScope = host.Services.CreateScope();
|
||||
|
||||
// TODO: Improve static singleton access
|
||||
GameData.GameDataProvider = scope.ServiceProvider.GetRequiredService<GameDataProvider>();
|
||||
GameData.GameDataProvider = rootScope.ServiceProvider.GetRequiredService<GameDataProvider>();
|
||||
AdminManager.CommandManagerProvider = rootScope.ServiceProvider.GetRequiredService<ICommandManager>();
|
||||
|
||||
var application = scope.ServiceProvider.GetRequiredService<Application>();
|
||||
var application = rootScope.ServiceProvider.GetRequiredService<Application>();
|
||||
application.Start();
|
||||
|
||||
return 1;
|
||||
|
||||
@@ -61,6 +61,12 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:CounterStrikeSharp.API.Modules.Utils.CommandUtils</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:CounterStrikeSharp.API.Core.AnimParamType_t.ANIMPARAM_STRINGTOKEN</Target>
|
||||
@@ -169,6 +175,24 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:CounterStrikeSharp.API.Modules.Memory.VirtualFunctions.CCSPlayerPawn_Respawn</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:CounterStrikeSharp.API.Modules.Memory.VirtualFunctions.CCSPlayerPawn_RespawnFunc</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.Application.#ctor(Microsoft.Extensions.Logging.ILoggerFactory,CounterStrikeSharp.API.Core.Hosting.IScriptHostConfiguration,CounterStrikeSharp.API.Core.GameDataProvider,CounterStrikeSharp.API.Core.CoreConfig,CounterStrikeSharp.API.Core.Plugin.Host.IPluginManager,CounterStrikeSharp.API.Core.Plugin.Host.IPluginContextQueryHandler,CounterStrikeSharp.API.Core.Translations.IPlayerLanguageManager)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.CBaseAnimGraph.get_AnimGraphDirty</Target>
|
||||
@@ -277,6 +301,12 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.CCSPlayerPawn.Respawn</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.CDynamicProp.get_AnimateOnServer</Target>
|
||||
@@ -313,6 +343,12 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.CoreConfig.#ctor(CounterStrikeSharp.API.Core.Hosting.IScriptHostConfiguration,Microsoft.Extensions.Logging.ILogger{CounterStrikeSharp.API.Core.CoreConfig})</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.CPlayer_WeaponServices.get_AllowSwitchToNoWeapon</Target>
|
||||
@@ -391,6 +427,24 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.Plugin.Host.PluginManager.#ctor(CounterStrikeSharp.API.Core.Hosting.IScriptHostConfiguration,Microsoft.Extensions.Logging.ILogger{CounterStrikeSharp.API.Core.Plugin.Host.PluginManager},System.IServiceProvider)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.Plugin.PluginContext.#ctor(System.IServiceProvider,CounterStrikeSharp.API.Core.Hosting.IScriptHostConfiguration,System.String,System.Int32)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>P:CounterStrikeSharp.API.Core.IPlugin.CommandManager</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0010</DiagnosticId>
|
||||
<Target>T:CounterStrikeSharp.API.Core.RenderMultisampleType_t</Target>
|
||||
@@ -985,22 +1039,4 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:CounterStrikeSharp.API.Modules.Memory.VirtualFunctions.CCSPlayerPawn_Respawn</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:CounterStrikeSharp.API.Modules.Memory.VirtualFunctions.CCSPlayerPawn_RespawnFunc</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.CCSPlayerPawn.Respawn</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
</Suppressions>
|
||||
@@ -17,6 +17,7 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
using CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
@@ -26,6 +27,7 @@ using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Menu;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
@@ -34,6 +36,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
private static Application _instance = null!;
|
||||
public ILogger Logger { get; }
|
||||
|
||||
public static Application Instance => _instance!;
|
||||
|
||||
public static string RootDirectory => Instance._scriptHostConfiguration.RootPath;
|
||||
@@ -44,10 +47,12 @@ namespace CounterStrikeSharp.API.Core
|
||||
private readonly IPluginManager _pluginManager;
|
||||
private readonly IPluginContextQueryHandler _pluginContextQueryHandler;
|
||||
private readonly IPlayerLanguageManager _playerLanguageManager;
|
||||
private readonly ICommandManager _commandManager;
|
||||
|
||||
public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration scriptHostConfiguration,
|
||||
GameDataProvider gameDataProvider, CoreConfig coreConfig, IPluginManager pluginManager,
|
||||
IPluginContextQueryHandler pluginContextQueryHandler, IPlayerLanguageManager playerLanguageManager)
|
||||
IPluginContextQueryHandler pluginContextQueryHandler, IPlayerLanguageManager playerLanguageManager,
|
||||
ICommandManager commandManager)
|
||||
{
|
||||
Logger = loggerFactory.CreateLogger("Core");
|
||||
_scriptHostConfiguration = scriptHostConfiguration;
|
||||
@@ -56,6 +61,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
_pluginManager = pluginManager;
|
||||
_pluginContextQueryHandler = pluginContextQueryHandler;
|
||||
_playerLanguageManager = playerLanguageManager;
|
||||
_commandManager = commandManager;
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
@@ -69,7 +75,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
var adminPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admins.json");
|
||||
Logger.LogInformation("Loading Admins from {Path}", adminPath);
|
||||
AdminManager.LoadAdminData(adminPath);
|
||||
|
||||
|
||||
var adminGroupsPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_groups.json");
|
||||
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
|
||||
AdminManager.LoadAdminGroups(adminGroupsPath);
|
||||
@@ -81,24 +87,23 @@ namespace CounterStrikeSharp.API.Core
|
||||
AdminManager.MergeGroupPermsIntoAdmins();
|
||||
AdminManager.AddCommands();
|
||||
|
||||
RegisterPluginCommands();
|
||||
|
||||
_pluginManager.Load();
|
||||
|
||||
for (var i = 1; i <= 9; i++)
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand($"css_{i}", "Command Key Handler", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
|
||||
|
||||
MenuManager.OnKeyPress(player, key);
|
||||
});
|
||||
_commandManager.RegisterCommand(new($"css_{i}", "Command Key Handler",
|
||||
(player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
|
||||
MenuManager.OnKeyPress(player, key);
|
||||
}));
|
||||
}
|
||||
|
||||
RegisterPluginCommands();
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/generic")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private void OnCSSCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
var currentVersion = Api.GetVersion();
|
||||
@@ -112,13 +117,6 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/generic")]
|
||||
[CommandHelper(minArgs: 1,
|
||||
usage: "[option]\n" +
|
||||
" list - List all plugins currently loaded.\n" +
|
||||
" start / load - Loads a plugin not currently loaded.\n" +
|
||||
" stop / unload - Unloads a plugin currently loaded.\n" +
|
||||
" restart / reload - Reloads a plugin currently loaded.",
|
||||
whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private void OnCSSPluginCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
switch (info.GetArg(1))
|
||||
@@ -253,17 +251,19 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHelper(usage: "[language code, e.g. \"de\", \"pl\", \"en\"]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[CommandHelper(usage: "[language code, e.g. \"de\", \"pl\", \"en\"]",
|
||||
whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private void OnLangCommand(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
|
||||
SteamID steamId = (SteamID)player.SteamID;
|
||||
|
||||
if (command.ArgCount == 1)
|
||||
{
|
||||
var language = _playerLanguageManager.GetLanguage(steamId);
|
||||
command.ReplyToCommand(string.Format("Current language is \"{0}\" ({1})", language.Name, language.NativeName));
|
||||
command.ReplyToCommand(string.Format("Current language is \"{0}\" ({1})", language.Name,
|
||||
language.NativeName));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var language = command.GetArg(1);
|
||||
@@ -287,10 +287,21 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
private void RegisterPluginCommands()
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.",
|
||||
OnCSSPluginCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_lang", "Set Counter-Strike Sharp language.", OnLangCommand);
|
||||
_commandManager.RegisterCommand(new("css", "Counter-Strike Sharp options.", OnCSSCommand)
|
||||
{
|
||||
ExecutableBy = CommandUsage.CLIENT_AND_SERVER,
|
||||
});
|
||||
_commandManager.RegisterCommand(new("css_plugins", "Counter-Strike Sharp plugin options.",
|
||||
OnCSSPluginCommand)
|
||||
{
|
||||
ExecutableBy = CommandUsage.CLIENT_AND_SERVER,
|
||||
MinArgs = 1,
|
||||
UsageHint = "[option]\n" +
|
||||
" list - List all plugins currently loaded.\n" +
|
||||
" start / load - Loads a plugin not currently loaded.\n" +
|
||||
" stop / unload - Unloads a plugin currently loaded.\n" +
|
||||
" restart / reload - Reloads a plugin currently loaded.",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ using System.Reflection;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Events;
|
||||
@@ -59,6 +60,8 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
public string ModuleDirectory => Path.GetDirectoryName(ModulePath);
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public ICommandManager CommandManager { get; set; }
|
||||
|
||||
public IStringLocalizer Localizer { get; set; }
|
||||
|
||||
@@ -170,102 +173,13 @@ namespace CounterStrikeSharp.API.Core
|
||||
/// <param name="handler">The callback function to be invoked when the command is executed.</param>
|
||||
public void AddCommand(string name, string description, CommandInfo.CommandCallback handler)
|
||||
{
|
||||
var wrappedHandler = new Action<int, IntPtr>((i, ptr) =>
|
||||
{
|
||||
var caller = (i != -1) ? new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1)) : null;
|
||||
var command = new CommandInfo(ptr, caller);
|
||||
|
||||
using var temporaryCulture = new WithTemporaryCulture(caller.GetLanguage());
|
||||
|
||||
var methodInfo = handler?.GetMethodInfo();
|
||||
|
||||
// We do not need to do permission checks on commands executed from the server console.
|
||||
// The server will always be allowed to execute commands (unless marked as client only like above)
|
||||
if (caller != null)
|
||||
{
|
||||
// Do not execute command if we do not have the correct permissions.
|
||||
var adminData = AdminManager.GetPlayerAdminData(caller!.AuthorizedSteamID);
|
||||
var permissionsToCheck = new List<BaseRequiresPermissions>();
|
||||
|
||||
|
||||
// If our command is overriden, we dynamically create a new permissions attribute
|
||||
// based on the data that is stored in admin_overrides.json.
|
||||
if (AdminManager.CommandIsOverriden(name))
|
||||
{
|
||||
var data = AdminManager.GetCommandOverrideData(name);
|
||||
if (data != null)
|
||||
{
|
||||
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
|
||||
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
|
||||
|
||||
if (attr != null) permissionsToCheck.Add(attr);
|
||||
}
|
||||
}
|
||||
// The permissions for this command are not being overriden here, so we
|
||||
// grab the permissions to check straight from the attribute.
|
||||
else
|
||||
{
|
||||
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
|
||||
if (permissions != null) permissionsToCheck.AddRange(permissions);
|
||||
}
|
||||
|
||||
foreach (var attr in permissionsToCheck)
|
||||
{
|
||||
attr.Command = name;
|
||||
if (!attr.CanExecuteCommand(caller))
|
||||
{
|
||||
var responseStr = (attr.GetType() == typeof(RequiresPermissions)) ?
|
||||
"You are missing the correct permissions" : "You do not have one of the correct permissions";
|
||||
|
||||
var flags = attr.Permissions.Except(adminData?.GetAllFlags() ?? new HashSet<string>());
|
||||
flags = flags.Except(adminData?.Groups ?? new HashSet<string>());
|
||||
command.ReplyToCommand($"[CSS] {responseStr} ({string.Join(", ", flags)}) to execute this command.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not execute if we shouldn't be calling this command.
|
||||
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
|
||||
if (helperAttribute != null)
|
||||
{
|
||||
switch (helperAttribute.WhoCanExcecute)
|
||||
{
|
||||
case CommandUsage.CLIENT_AND_SERVER: break; // Allow command through.
|
||||
case CommandUsage.CLIENT_ONLY:
|
||||
if (caller == null || !caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by clients."); return; }
|
||||
break;
|
||||
case CommandUsage.SERVER_ONLY:
|
||||
if (caller != null && caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by the server."); return; }
|
||||
break;
|
||||
default: throw new ArgumentException("Unrecognised CommandUsage value passed in CommandHelperAttribute.");
|
||||
}
|
||||
|
||||
// Technically the command itself counts as the first argument,
|
||||
// but we'll just ignore that for this check.
|
||||
if (helperAttribute.MinArgs != 0 && command.ArgCount - 1 < helperAttribute.MinArgs)
|
||||
{
|
||||
// Remove the "css_" from the beginning of the command name if it's present.
|
||||
// Most of the time, users will be calling commands from chat.
|
||||
var commandCalled = command.ArgByIndex(0);
|
||||
var properCommandName = (commandCalled.StartsWith("css_")) ? commandCalled.Replace("css_", "") : commandCalled;
|
||||
|
||||
command.ReplyToCommand($"[CSS] Expected usage: \"!{properCommandName} {helperAttribute.Usage}\".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handler?.Invoke(caller, command);
|
||||
});
|
||||
|
||||
var methodInfo = handler?.GetMethodInfo();
|
||||
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
|
||||
|
||||
var subscriber = new CallbackSubscriber(handler, wrappedHandler, () => { RemoveCommand(name, handler); });
|
||||
NativeAPI.AddCommand(name, description, (helperAttribute?.WhoCanExcecute == CommandUsage.SERVER_ONLY),
|
||||
(int)ConCommandFlags.FCVAR_LINKED_CONCOMMAND, subscriber.GetInputArgument());
|
||||
CommandHandlers[handler] = subscriber;
|
||||
var definition = new CommandDefinition(name, description, handler);
|
||||
CommandManager.RegisterCommand(definition);
|
||||
}
|
||||
|
||||
private void AddCommand(CommandDefinition definition)
|
||||
{
|
||||
CommandManager.RegisterCommand(definition);
|
||||
}
|
||||
|
||||
public void AddCommandListener(string? name, CommandInfo.CommandListenerCallback handler, HookMode mode = HookMode.Pre)
|
||||
@@ -462,10 +376,19 @@ namespace CounterStrikeSharp.API.Core
|
||||
foreach (var eventHandler in eventHandlers)
|
||||
{
|
||||
var attributes = eventHandler.GetCustomAttributes<ConsoleCommandAttribute>();
|
||||
var helperAttribute = eventHandler.GetCustomAttribute<CommandHelperAttribute>();
|
||||
foreach (var commandInfo in attributes)
|
||||
{
|
||||
AddCommand(commandInfo.Command, commandInfo.Description,
|
||||
eventHandler.CreateDelegate<CommandInfo.CommandCallback>(instance));
|
||||
var definition = new CommandDefinition()
|
||||
{
|
||||
Name = commandInfo.Command,
|
||||
Description = commandInfo.Description,
|
||||
Callback = eventHandler.CreateDelegate<CommandInfo.CommandCallback>(instance),
|
||||
MinArgs = helperAttribute?.MinArgs,
|
||||
UsageHint = helperAttribute?.Usage,
|
||||
ExecutableBy = helperAttribute?.WhoCanExcecute ?? CommandUsage.CLIENT_AND_SERVER,
|
||||
};
|
||||
AddCommand(definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Commands;
|
||||
|
||||
public class CommandDefinition
|
||||
{
|
||||
public CommandDefinition(string name, string description, CommandInfo.CommandCallback callback)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
Callback = callback;
|
||||
}
|
||||
|
||||
public CommandDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
public string Name { get; init; }
|
||||
public string Description { get; init; }
|
||||
public CommandInfo.CommandCallback Callback { get; init; }
|
||||
|
||||
public CommandUsage ExecutableBy { get; init; } = CommandUsage.CLIENT_AND_SERVER;
|
||||
|
||||
public string? UsageHint { get; init; }
|
||||
|
||||
public int? MinArgs { get; init; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Name: {Name}, Description: {Description}, ExecutableBy: {ExecutableBy}, " +
|
||||
$"UsageHint: {UsageHint}, MinArgs: {MinArgs}";
|
||||
}
|
||||
}
|
||||
158
managed/CounterStrikeSharp.API/Core/Commands/CommandManager.cs
Normal file
158
managed/CounterStrikeSharp.API/Core/Commands/CommandManager.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Commands;
|
||||
|
||||
public class CommandManager : ICommandManager
|
||||
{
|
||||
private readonly Dictionary<string, IList<CommandDefinition>> _commandDefinitions =
|
||||
new(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
private readonly ILogger<CommandManager> _logger;
|
||||
private readonly FunctionReference _internalFunctionReference;
|
||||
|
||||
public CommandManager(ILogger<CommandManager> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_internalFunctionReference = FunctionReference.Create(HandleCommandInternal);
|
||||
}
|
||||
|
||||
public void RegisterCommand(CommandDefinition definition)
|
||||
{
|
||||
bool isRegistered = true;
|
||||
if (!_commandDefinitions.ContainsKey(definition.Name))
|
||||
{
|
||||
_commandDefinitions.Add(definition.Name, new List<CommandDefinition>());
|
||||
isRegistered = false;
|
||||
}
|
||||
|
||||
_commandDefinitions[definition.Name].Add(definition);
|
||||
|
||||
_logger.LogDebug("Registering command {Command}", definition.Name);
|
||||
|
||||
if (!isRegistered)
|
||||
{
|
||||
NativeAPI.AddCommand(definition.Name, definition.Description,
|
||||
definition.ExecutableBy == CommandUsage.SERVER_ONLY,
|
||||
(int)ConCommandFlags.FCVAR_LINKED_CONCOMMAND, _internalFunctionReference);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveCommand(CommandDefinition definition)
|
||||
{
|
||||
_logger.LogDebug("Removing command {Command}", definition.Name);
|
||||
|
||||
if (_commandDefinitions.TryGetValue(definition.Name, out var commandDefinition))
|
||||
{
|
||||
commandDefinition.Remove(definition);
|
||||
}
|
||||
|
||||
if (_commandDefinitions[definition.Name].Count == 0)
|
||||
{
|
||||
NativeAPI.RemoveCommand(definition.Name, _internalFunctionReference);
|
||||
_commandDefinitions.Remove(definition.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCommandInternal(int playerSlot, IntPtr commandInfo)
|
||||
{
|
||||
var caller = (playerSlot != -1) ? Utilities.GetPlayerFromSlot(playerSlot) : null;
|
||||
var info = new CommandInfo(commandInfo, caller);
|
||||
|
||||
var name = info.GetArg(0).ToLower();
|
||||
|
||||
using var temporaryCulture = new WithTemporaryCulture(caller.GetLanguage());
|
||||
|
||||
if (_commandDefinitions.TryGetValue(name, out var handler))
|
||||
{
|
||||
foreach (var command in handler)
|
||||
{
|
||||
var methodInfo = command.Callback?.GetMethodInfo();
|
||||
|
||||
// We do not need to do permission checks on commands executed from the server console.
|
||||
// The server will always be allowed to execute commands (unless marked as client only like above)
|
||||
if (caller != null)
|
||||
{
|
||||
// Do not execute command if we do not have the correct permissions.
|
||||
var adminData = AdminManager.GetPlayerAdminData(caller!.AuthorizedSteamID);
|
||||
var permissionsToCheck = new List<BaseRequiresPermissions>();
|
||||
|
||||
|
||||
// If our command is overriden, we dynamically create a new permissions attribute
|
||||
// based on the data that is stored in admin_overrides.json.
|
||||
if (AdminManager.CommandIsOverriden(name))
|
||||
{
|
||||
var data = AdminManager.GetCommandOverrideData(name);
|
||||
if (data != null)
|
||||
{
|
||||
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
|
||||
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
|
||||
|
||||
if (attr != null) permissionsToCheck.Add(attr);
|
||||
}
|
||||
}
|
||||
// The permissions for this command are not being overriden here, so we
|
||||
// grab the permissions to check straight from the attribute.
|
||||
else
|
||||
{
|
||||
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
|
||||
if (permissions != null) permissionsToCheck.AddRange(permissions);
|
||||
}
|
||||
|
||||
foreach (var attr in permissionsToCheck)
|
||||
{
|
||||
attr.Command = name;
|
||||
if (!attr.CanExecuteCommand(caller))
|
||||
{
|
||||
var responseStr = (attr.GetType() == typeof(RequiresPermissions)) ?
|
||||
"You are missing the correct permissions" : "You do not have one of the correct permissions";
|
||||
|
||||
var flags = attr.Permissions.Except(adminData?.GetAllFlags() ?? new HashSet<string>());
|
||||
flags = flags.Except(adminData?.Groups ?? new HashSet<string>());
|
||||
info.ReplyToCommand($"[CSS] {responseStr} ({string.Join(", ", flags)}) to execute this command.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not execute if we shouldn't be calling this command.
|
||||
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
|
||||
if (helperAttribute != null)
|
||||
{
|
||||
switch (helperAttribute.WhoCanExcecute)
|
||||
{
|
||||
case CommandUsage.CLIENT_AND_SERVER: break; // Allow command through.
|
||||
case CommandUsage.CLIENT_ONLY:
|
||||
if (caller == null || !caller.IsValid) { info.ReplyToCommand("[CSS] This command can only be executed by clients."); return; }
|
||||
break;
|
||||
case CommandUsage.SERVER_ONLY:
|
||||
if (caller != null && caller.IsValid) { info.ReplyToCommand("[CSS] This command can only be executed by the server."); return; }
|
||||
break;
|
||||
default: throw new ArgumentException("Unrecognised CommandUsage value passed in CommandHelperAttribute.");
|
||||
}
|
||||
|
||||
// Technically the command itself counts as the first argument,
|
||||
// but we'll just ignore that for this check.
|
||||
if (helperAttribute.MinArgs != 0 && info.ArgCount - 1 < helperAttribute.MinArgs)
|
||||
{
|
||||
// Remove the "css_" from the beginning of the command name if it's present.
|
||||
// Most of the time, users will be calling commands from chat.
|
||||
var commandCalled = info.ArgByIndex(0);
|
||||
var properCommandName = (commandCalled.StartsWith("css_")) ? commandCalled.Replace("css_", "") : commandCalled;
|
||||
|
||||
info.ReplyToCommand($"[CSS] Expected usage: \"!{properCommandName} {helperAttribute.Usage}\".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
command.Callback?.Invoke(caller, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Commands;
|
||||
|
||||
public interface ICommandManager
|
||||
{
|
||||
void RegisterCommand(CommandDefinition definition);
|
||||
|
||||
void RemoveCommand(CommandDefinition definition);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Decorator for <see cref="ICommandManager"/> that tracks registered commands and removes them when disposed.
|
||||
/// Used for plugins that register commands to ensure they are removed when the plugin is unloaded.
|
||||
/// </summary>
|
||||
public class PluginCommandManagerDecorator : ICommandManager, IDisposable
|
||||
{
|
||||
private readonly ICommandManager _inner;
|
||||
private readonly List<CommandDefinition> _trackedCommands = new();
|
||||
private readonly IPluginContext _pluginContext;
|
||||
private readonly ILogger<PluginCommandManagerDecorator> _logger;
|
||||
|
||||
public PluginCommandManagerDecorator(ICommandManager inner, IPluginContext pluginContext, ILogger<PluginCommandManagerDecorator> logger)
|
||||
{
|
||||
_pluginContext = pluginContext;
|
||||
_logger = logger;
|
||||
_inner = inner;
|
||||
}
|
||||
|
||||
public void RegisterCommand(CommandDefinition definition)
|
||||
{
|
||||
_inner.RegisterCommand(definition);
|
||||
_trackedCommands.Add(definition);
|
||||
_logger.LogDebug("Registered command {Command} from plugin {Plugin}", definition.Name, _pluginContext.Plugin.ModuleName);
|
||||
}
|
||||
|
||||
public void RemoveCommand(CommandDefinition definition)
|
||||
{
|
||||
_inner.RemoveCommand(definition);
|
||||
_trackedCommands.Remove(definition);
|
||||
_logger.LogDebug("Removed command {Command} from plugin {Plugin}", definition.Name, _pluginContext.Plugin.ModuleName);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = _trackedCommands.Count - 1; i >= 0; i--)
|
||||
{
|
||||
RemoveCommand(_trackedCommands[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ using CounterStrikeSharp.API.Modules.Commands;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Logging;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -101,12 +102,15 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
private static CoreConfigData _coreConfig = new CoreConfigData();
|
||||
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly ILogger<CoreConfig> _logger;
|
||||
|
||||
private readonly string _coreConfigPath;
|
||||
private bool _commandsRegistered = false;
|
||||
|
||||
public CoreConfig(IScriptHostConfiguration scriptHostConfiguration, ILogger<CoreConfig> logger)
|
||||
public CoreConfig(IScriptHostConfiguration scriptHostConfiguration, ICommandManager commandManager, ILogger<CoreConfig> logger)
|
||||
{
|
||||
_commandManager = commandManager;
|
||||
_logger = logger;
|
||||
_coreConfigPath = Path.Join(scriptHostConfiguration.ConfigsPath, "core.json");
|
||||
}
|
||||
@@ -120,9 +124,14 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
public void Load()
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand("css_core_reload", "Reloads the core configuration file.",
|
||||
ReloadCoreConfigCommand);
|
||||
|
||||
if (!_commandsRegistered)
|
||||
{
|
||||
_commandManager.RegisterCommand(new CommandDefinition("css_core_reload",
|
||||
"Reloads the core configuration file.",
|
||||
ReloadCoreConfigCommand));
|
||||
_commandsRegistered = true;
|
||||
}
|
||||
|
||||
if (!File.Exists(_coreConfigPath))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* 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
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -57,6 +58,8 @@ namespace CounterStrikeSharp.API.Core
|
||||
ILogger Logger { get; set; }
|
||||
|
||||
IStringLocalizer Localizer { get; set; }
|
||||
|
||||
ICommandManager CommandManager { get; set; }
|
||||
|
||||
void RegisterAllAttributes(object instance);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
@@ -9,12 +11,14 @@ public class PluginManager : IPluginManager
|
||||
{
|
||||
private readonly HashSet<PluginContext> _loadedPluginContexts = new();
|
||||
private readonly IScriptHostConfiguration _scriptHostConfiguration;
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<PluginManager> _logger;
|
||||
|
||||
public PluginManager(IScriptHostConfiguration scriptHostConfiguration, ILogger<PluginManager> logger, IServiceProvider serviceProvider)
|
||||
public PluginManager(IScriptHostConfiguration scriptHostConfiguration, ICommandManager commandManager, ILogger<PluginManager> logger, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_scriptHostConfiguration = scriptHostConfiguration;
|
||||
_commandManager = commandManager;
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
@@ -47,7 +51,7 @@ public class PluginManager : IPluginManager
|
||||
|
||||
public void LoadPlugin(string path)
|
||||
{
|
||||
var plugin = new PluginContext(_serviceProvider, _scriptHostConfiguration, path, _loadedPluginContexts.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
|
||||
var plugin = new PluginContext(_serviceProvider, _commandManager, _scriptHostConfiguration, path, _loadedPluginContexts.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
|
||||
_loadedPluginContexts.Add(plugin);
|
||||
plugin.Load();
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Logging;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
@@ -38,23 +40,27 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
|
||||
private PluginLoader Loader { get; set; }
|
||||
|
||||
private IServiceProvider ServiceProvider { get; set; }
|
||||
private ServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
public int PluginId { get; }
|
||||
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly IScriptHostConfiguration _hostConfiguration;
|
||||
private readonly string _path;
|
||||
private readonly FileSystemWatcher _fileWatcher;
|
||||
private readonly IServiceProvider _applicationServiceProvider;
|
||||
|
||||
public string FilePath => _path;
|
||||
private IServiceScope _serviceScope;
|
||||
|
||||
// TOOD: ServiceCollection
|
||||
private ILogger _logger = CoreLogging.Factory.CreateLogger<PluginContext>();
|
||||
|
||||
public PluginContext(IServiceProvider applicationServiceProvider, IScriptHostConfiguration hostConfiguration, string path, int id)
|
||||
public PluginContext(IServiceProvider applicationServiceProvider, ICommandManager commandManager,
|
||||
IScriptHostConfiguration hostConfiguration,
|
||||
string path, int id)
|
||||
{
|
||||
_applicationServiceProvider = applicationServiceProvider;
|
||||
_commandManager = commandManager;
|
||||
_hostConfiguration = hostConfiguration;
|
||||
_path = path;
|
||||
PluginId = id;
|
||||
@@ -62,11 +68,13 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
Loader = PluginLoader.CreateFromAssemblyFile(path,
|
||||
new[]
|
||||
{
|
||||
typeof(IPlugin), typeof(ILogger), typeof(IServiceCollection), typeof(IPluginServiceCollection<>)
|
||||
typeof(IPlugin), typeof(ILogger), typeof(IServiceCollection), typeof(IPluginServiceCollection<>),
|
||||
typeof(ICommandManager)
|
||||
}, config =>
|
||||
{
|
||||
config.EnableHotReload = true;
|
||||
config.IsUnloadable = true;
|
||||
config.PreferSharedTypes = true;
|
||||
});
|
||||
|
||||
if (CoreConfig.PluginHotReloadEnabled)
|
||||
@@ -168,12 +176,14 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
method?.Invoke(pluginServiceCollection, new object[] { serviceCollection });
|
||||
}
|
||||
}
|
||||
|
||||
serviceCollection.AddScoped<ICommandManager>(c => _commandManager);
|
||||
serviceCollection.DecorateSingleton<ICommandManager, PluginCommandManagerDecorator>();
|
||||
|
||||
serviceCollection.AddSingleton<IPluginContext>(this);
|
||||
serviceCollection.TryAddSingleton<IStringLocalizerFactory, JsonStringLocalizerFactory>();
|
||||
serviceCollection.TryAddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
||||
serviceCollection.TryAddTransient(typeof(IStringLocalizer), typeof(StringLocalizer));
|
||||
|
||||
ServiceProvider = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var minimumApiVersion = pluginType.GetCustomAttribute<MinimumApiVersion>()?.Version;
|
||||
@@ -186,13 +196,18 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
|
||||
_logger.LogInformation("Loading plugin {Name}", pluginType.Assembly.GetName().Name);
|
||||
|
||||
Plugin = ServiceProvider.GetRequiredService(pluginType) as IPlugin;
|
||||
|
||||
_serviceScope = ServiceProvider.CreateScope();
|
||||
|
||||
Plugin = _serviceScope.ServiceProvider.GetRequiredService(pluginType) as IPlugin;
|
||||
|
||||
if (Plugin == null) throw new Exception("Unable to create plugin instance");
|
||||
|
||||
|
||||
State = PluginState.Loading;
|
||||
|
||||
Plugin.ModulePath = _path;
|
||||
Plugin.Logger = _serviceScope.ServiceProvider.GetRequiredService<ILoggerFactory>()
|
||||
.CreateLogger(pluginType);
|
||||
Plugin.CommandManager = _serviceScope.ServiceProvider.GetRequiredService<ICommandManager>();
|
||||
Plugin.RegisterAllAttributes(Plugin);
|
||||
Plugin.Localizer = ServiceProvider.GetRequiredService<IStringLocalizer>();
|
||||
Plugin.Logger = ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(pluginType);
|
||||
@@ -206,6 +221,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Unload(bool hotReload = false)
|
||||
{
|
||||
if (State == PluginState.Unloaded) return;
|
||||
@@ -218,6 +234,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
Plugin.Unload(hotReload);
|
||||
|
||||
Plugin.Dispose();
|
||||
_serviceScope.Dispose();
|
||||
|
||||
_logger.LogInformation("Finished unloading plugin {Name}", cachedName);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
|
||||
|
||||
public static partial class AdminManager
|
||||
{
|
||||
private static Dictionary<string, CommandData> CommandOverrides = new();
|
||||
private static Dictionary<string, CommandData> CommandOverrides = new(StringComparer.InvariantCultureIgnoreCase);
|
||||
public static void LoadCommandOverrides(string overridePath)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -2,20 +2,30 @@ using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Admin
|
||||
{
|
||||
|
||||
public static partial class AdminManager
|
||||
{
|
||||
public static ICommandManager CommandManagerProvider { get; internal set; } = null!;
|
||||
|
||||
public static void AddCommands()
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand("css_admins_reload", "Reloads the admin file.", ReloadAdminsCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_admins_list", "List admins and their flags.", ListAdminsCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_groups_reload", "Reloads the admin groups file.", ReloadAdminGroupsCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_groups_list", "List admin groups and their flags.", ListAdminGroupsCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_overrides_reload", "Reloads the admin command overrides file.", ReloadAdminOverridesCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_overrides_list", "List admin command overrides and their flags.", ListAdminOverridesCommand);
|
||||
CommandManagerProvider.RegisterCommand(new CommandDefinition("css_admins_reload", "Reloads the admin file.",
|
||||
ReloadAdminsCommand));
|
||||
CommandManagerProvider.RegisterCommand(new CommandDefinition("css_admins_list",
|
||||
"List admins and their flags.", ListAdminsCommand));
|
||||
CommandManagerProvider.RegisterCommand(new CommandDefinition("css_groups_reload",
|
||||
"Reloads the admin groups file.", ReloadAdminGroupsCommand));
|
||||
CommandManagerProvider.RegisterCommand(new CommandDefinition("css_groups_list",
|
||||
"List admin groups and their flags.", ListAdminGroupsCommand));
|
||||
CommandManagerProvider.RegisterCommand(new CommandDefinition("css_overrides_reload",
|
||||
"Reloads the admin command overrides file.", ReloadAdminOverridesCommand));
|
||||
CommandManagerProvider.RegisterCommand(new CommandDefinition("css_overrides_list",
|
||||
"List admin command overrides and their flags.", ListAdminOverridesCommand));
|
||||
}
|
||||
|
||||
public static void MergeGroupPermsIntoAdmins()
|
||||
@@ -26,7 +36,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
|
||||
}
|
||||
}
|
||||
|
||||
[RequiresPermissions(permissions:"@css/generic")]
|
||||
[RequiresPermissions(permissions: "@css/generic")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private static void ReloadAdminsCommand(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
@@ -36,7 +46,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
|
||||
MergeGroupPermsIntoAdmins();
|
||||
}
|
||||
|
||||
[RequiresPermissions(permissions:"@css/generic")]
|
||||
[RequiresPermissions(permissions: "@css/generic")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private static void ListAdminsCommand(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
@@ -50,7 +60,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
|
||||
}
|
||||
}
|
||||
|
||||
[RequiresPermissions(permissions:"@css/generic")]
|
||||
[RequiresPermissions(permissions: "@css/generic")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private static void ReloadAdminGroupsCommand(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
@@ -85,8 +95,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
|
||||
{
|
||||
foreach (var (commandName, commandDef) in CommandOverrides)
|
||||
{
|
||||
command.ReplyToCommand($"{commandName} (enabled: {commandDef.Enabled.ToString()}) - {string.Join(", ", commandDef.Flags)}");
|
||||
command.ReplyToCommand(
|
||||
$"{commandName} (enabled: {commandDef.Enabled.ToString()}) - {string.Join(", ", commandDef.Flags)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
|
||||
if (caller?.AuthorizedSteamID == null) return false;
|
||||
var adminData = AdminManager.GetPlayerAdminData(caller.AuthorizedSteamID);
|
||||
if (adminData == null) return false;
|
||||
if (adminData.CommandOverrides.ContainsKey(Command))
|
||||
if (adminData.CommandOverrides.TryGetValue(Command, out var command))
|
||||
{
|
||||
return adminData.CommandOverrides[Command];
|
||||
return command;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
public class CommandUtils
|
||||
{
|
||||
public static void AddStandaloneCommand(string name, string description, CommandInfo.CommandCallback handler)
|
||||
{
|
||||
var wrappedHandler = new Action<int, IntPtr>((i, ptr) =>
|
||||
{
|
||||
var caller = (i != -1) ? new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1)) : null;
|
||||
var command = new CommandInfo(ptr, caller);
|
||||
|
||||
var methodInfo = handler?.GetMethodInfo();
|
||||
|
||||
// We do not need to do permission checks on commands executed from the server console.
|
||||
// The server will always be allowed to execute commands (unless marked as client only like above)
|
||||
if (caller != null)
|
||||
{
|
||||
// Do not execute command if we do not have the correct permissions.
|
||||
var adminData = AdminManager.GetPlayerAdminData(caller!.AuthorizedSteamID);
|
||||
var permissionsToCheck = new List<BaseRequiresPermissions>();
|
||||
|
||||
|
||||
// If our command is overriden, we dynamically create a new permissions attribute
|
||||
// based on the data that is stored in admin_overrides.json.
|
||||
if (AdminManager.CommandIsOverriden(name))
|
||||
{
|
||||
var data = AdminManager.GetCommandOverrideData(name);
|
||||
if (data != null)
|
||||
{
|
||||
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
|
||||
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
|
||||
|
||||
if (attr != null) permissionsToCheck.Add(attr);
|
||||
}
|
||||
}
|
||||
// The permissions for this command are not being overriden here, so we
|
||||
// grab the permissions to check straight from the attribute.
|
||||
else
|
||||
{
|
||||
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
|
||||
if (permissions != null) permissionsToCheck.AddRange(permissions);
|
||||
}
|
||||
|
||||
foreach (var attr in permissionsToCheck)
|
||||
{
|
||||
attr.Command = name;
|
||||
if (!attr.CanExecuteCommand(caller))
|
||||
{
|
||||
var responseStr = (attr.GetType() == typeof(RequiresPermissions)) ?
|
||||
"You are missing the correct permissions" : "You do not have one of the correct permissions";
|
||||
|
||||
var flags = attr.Permissions.Except(adminData?.GetAllFlags() ?? new HashSet<string>());
|
||||
flags = flags.Except(adminData?.Groups ?? new HashSet<string>());
|
||||
command.ReplyToCommand($"[CSS] {responseStr} ({string.Join(", ", flags)}) to execute this command.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not execute if we shouldn't be calling this command.
|
||||
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
|
||||
if (helperAttribute != null)
|
||||
{
|
||||
switch (helperAttribute.WhoCanExcecute)
|
||||
{
|
||||
case CommandUsage.CLIENT_AND_SERVER: break; // Allow command through.
|
||||
case CommandUsage.CLIENT_ONLY:
|
||||
if (caller == null || !caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by clients."); return; }
|
||||
break;
|
||||
case CommandUsage.SERVER_ONLY:
|
||||
if (caller != null && caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by the server."); return; }
|
||||
break;
|
||||
default: throw new ArgumentException("Unrecognised CommandUsage value passed in CommandHelperAttribute.");
|
||||
}
|
||||
|
||||
// Technically the command itself counts as the first argument,
|
||||
// but we'll just ignore that for this check.
|
||||
if (helperAttribute.MinArgs != 0 && command.ArgCount - 1 < helperAttribute.MinArgs)
|
||||
{
|
||||
// Remove the "css_" from the beginning of the command name if it's present.
|
||||
// Most of the time, users will be calling commands from chat.
|
||||
var commandCalled = command.ArgByIndex(0);
|
||||
var properCommandName = (commandCalled.StartsWith("css_")) ? commandCalled.Replace("css_", "") : commandCalled;
|
||||
|
||||
command.ReplyToCommand($"[CSS] Expected usage: \"!{properCommandName} {helperAttribute.Usage}\".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handler?.Invoke(caller, command);
|
||||
});
|
||||
|
||||
var methodInfo = handler?.GetMethodInfo();
|
||||
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
|
||||
|
||||
var subscriber = new BasePlugin.CallbackSubscriber(handler, wrappedHandler, () => { });
|
||||
NativeAPI.AddCommand(name, description, (helperAttribute?.WhoCanExcecute == CommandUsage.SERVER_ONLY),
|
||||
(int)ConCommandFlags.FCVAR_LINKED_CONCOMMAND, subscriber.GetInputArgument());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace CounterStrikeSharp.API;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Decorates a given interface with a decorator class, keeping a local copy for singleton access.
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <typeparam name="TService"></typeparam>
|
||||
/// <typeparam name="TDecoratedService"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection DecorateSingleton<TService, TDecoratedService>(this IServiceCollection services) where TService : class
|
||||
where TDecoratedService : class, TService
|
||||
{
|
||||
TService? localCopy = default(TService?);
|
||||
services.Decorate<TService>((inner, provider) =>
|
||||
{
|
||||
if (localCopy == null)
|
||||
{
|
||||
localCopy = ActivatorUtilities.CreateInstance<TDecoratedService>(provider, inner);
|
||||
}
|
||||
|
||||
return localCopy;
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -8,15 +9,21 @@ namespace TestPlugin;
|
||||
public class TestInjectedClass
|
||||
{
|
||||
private readonly ILogger<TestInjectedClass> _logger;
|
||||
private readonly ICommandManager _commandManager;
|
||||
|
||||
public TestInjectedClass(ILogger<TestInjectedClass> logger)
|
||||
public TestInjectedClass(ILogger<TestInjectedClass> logger, ICommandManager commandManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_commandManager = commandManager;
|
||||
}
|
||||
|
||||
public void Hello()
|
||||
{
|
||||
_logger.LogInformation("Hello World from Test Injected Class");
|
||||
_commandManager.RegisterCommand(new CommandDefinition("cssharp_helloworld", "Hello World!", (player, info) =>
|
||||
{
|
||||
info.ReplyToCommand("Hello!");
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user