Compare commits

...

17 Commits
v48 ... v1.0.62

Author SHA1 Message Date
zonical
3ab5893f22 Remove required from flags in AdminData (#106) 2023-11-21 10:34:11 +10:00
Nexd
50ce09a7b3 feat: new virtual functions with wrapper methods (#87) 2023-11-20 08:52:07 +10:00
Roflmuffin
9c7944e6f1 Merge branch 'main' of github.com:roflmuffin/CounterStrikeSharp 2023-11-19 18:49:48 +10:00
Roflmuffin
bc71aa7739 feat: add FireEventToClient native 2023-11-19 18:49:36 +10:00
Snowy
16a1efc0cb docs: use nuget instead (#89) 2023-11-19 18:40:43 +10:00
Roflmuffin
8ae85cedf4 docs: add Admin Framework docs category 2023-11-19 14:23:30 +10:00
zonical
8e2234ff25 Admin Manager improvements (#74) 2023-11-19 14:18:40 +10:00
Roflmuffin
04e7ed682a fix: add command listener pre handlers 2023-11-19 09:51:20 +10:00
Roflmuffin
15e1260146 feat: update schema from 17.11.23 update 2023-11-19 09:44:15 +10:00
Roflmuffin
517607d962 fix: chat command config prefixes 2023-11-18 15:40:02 +10:00
Boink
0f72631eb0 Update CommitSuicide offset for 17-11-2023 update (#98) 2023-11-18 09:43:06 +10:00
Nexd
75fcf21fb7 feat: managed coreconfig implementation (#79) 2023-11-17 17:50:59 +10:00
Nexd
0ddf6bcdfa fix: new signature for CBaseModelEntity_SetModel (#84) 2023-11-15 15:18:03 +10:00
Roflmuffin
98661cd069 fix: public and silent triggers (finally) 2023-11-14 22:29:59 +10:00
Roflmuffin
86a5699b40 feat: re-add global command listener 2023-11-14 21:15:06 +10:00
Roflmuffin
414710d05c hotfix: wrap vfunc creation in try catch to prevent all vfuncs from erroring 2023-11-13 21:40:33 +10:00
Michael Wilson
b09c2b62c8 Improved Command Handling (#76) 2023-11-13 20:59:46 +10:00
60 changed files with 2242 additions and 842 deletions

View File

@@ -29,6 +29,8 @@ SET(SOURCE_FILES
src/core/utils.h
src/core/globals.h
src/core/globals.cpp
src/core/coreconfig.h
src/core/coreconfig.cpp
src/core/gameconfig.h
src/core/gameconfig.cpp
src/core/log.h
@@ -75,8 +77,6 @@ SET(SOURCE_FILES
src/core/managers/entity_manager.h
src/core/managers/chat_manager.cpp
src/core/managers/chat_manager.h
src/core/managers/client_command_manager.cpp
src/core/managers/client_command_manager.h
src/core/managers/server_manager.cpp
src/core/managers/server_manager.h
src/scripting/natives/natives_server.cpp
@@ -86,7 +86,7 @@ SET(SOURCE_FILES
if (LINUX)
# memoverride.cpp is not usable on CMake Windows, cuz CMake default link libraries (seems) always link ucrt.lib
set(SOURCE_FILES
set(SOURCE_FILES
${SOURCE_FILES}
libraries/hl2sdk-cs2/public/tier0/memoverride.cpp
)

View File

@@ -0,0 +1,23 @@
{
"#css/admin": {
"flags": [
"@css/reservation",
"@css/generic",
"@css/kick",
"@css/ban",
"@css/unban",
"@css/vip",
"@css/slay",
"@css/changemap",
"@css/cvar",
"@css/config",
"@css/chat",
"@css/vote",
"@css/password",
"@css/rcon",
"@css/cheats",
"@css/root"
],
"immunity": 100
}
}

View File

@@ -0,0 +1,9 @@
{
"example_command": {
"flags": [
"@css/custom-permission"
],
"check_type": "all",
"enabled": true
}
}

View File

@@ -1,24 +1,18 @@
{
"Erikj": {
"identity": "76561197960265731",
"immunity": 100,
"flags": [
"@css/reservation",
"@css/generic",
"@css/kick",
"@css/ban",
"@css/unban",
"@css/vip",
"@css/slay",
"@css/changemap",
"@css/cvar",
"@css/config",
"@css/chat",
"@css/vote",
"@css/password",
"@css/rcon",
"@css/cheats",
"@css/root"
]
"@css/custom-flag-1",
"@css/custom-flag-2"
],
"groups": [
"#css/admin"
],
"command_overrides": {
"css_plugins": true,
"css": false
}
},
"Another erikj": {
"identity": "STEAM_0:1:1",

View File

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

View File

@@ -50,14 +50,60 @@
"CBaseModelEntity_SetModel": {
"signatures": {
"library": "server",
"windows": "\\x48\\x89\\x5C\\x24\\x2A\\x48\\x89\\x7C\\x24\\x2A\\x55\\x48\\x8B\\xEC\\x48\\x83\\xEC\\x50\\x48\\x8B\\xF9",
"linux": "\\x55\\x48\\x89\\xF2\\x48\\x89\\xE5\\x41\\x54\\x49\\x89\\xFC\\x48\\x8D\\x7D\\xE0\\x48\\x83\\xEC\\x18\\x48\\x8D\\x05\\xE5\\xD1\\xBF\\x00"
"windows": "\\x48\\x89\\x5C\\x24\\x10\\x48\\x89\\x7C\\x24\\x20\\x55\\x48\\x8B\\xEC\\x48\\x83\\xEC\\x50",
"linux": "\\x55\\x48\\x89\\xF2\\x48\\x89\\xE5\\x41\\x54\\x49\\x89\\xFC\\x48\\x8D\\x7D\\xE0\\x48\\x83\\xEC\\x18\\x48\\x8D\\x05\\x25"
}
},
"CCSPlayer_ItemServices_DropActivePlayerWeapon": {
"offsets": {
"windows": 20,
"linux": 19
}
},
"CCSPlayer_ItemServices_RemoveWeapons": {
"offsets": {
"windows": 21,
"linux": 20
}
},
"CGameSceneNode_GetSkeletonInstance": {
"offsets": {
"windows": 8,
"linux": 8
}
},
"CCSGameRules_TerminateRound": {
"signatures": {
"library": "server",
"windows": "\\x48\\x8B\\xC4\\x4C\\x89\\x48\\x20\\x55\\x56",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x49\\x89\\xFD\\x41\\x54\\x53\\x48\\x81\\xEC\\xE8\\x01\\x00\\x00"
}
},
"UTIL_CreateEntityByName": {
"signatures": {
"library": "server",
"windows": "\\x48\\x83\\xEC\\x48\\xC6\\x44\\x24\\x30\\x00\\x4C\\x8B\\xC1",
"linux": "\\x48\\x8D\\x05\\x49\\xB5\\xBC\\x00\\x55\\x48\\x89\\xFA\\x41\\x89\\xF0\\x48"
}
},
"CBaseEntity_DispatchSpawn": {
"signatures": {
"library": "server",
"windows": "\\x48\\x89\\x5C\\x24\\x10\\x57\\x48\\x83\\xEC\\x30\\x48\\x8B\\xDA\\x48\\x8B\\xF9\\x48\\x85\\xC9",
"linux": "\\x48\\x85\\xFF\\x74\\x4B\\x55\\x48\\x89\\xE5\\x41\\x56\\x41\\x55\\x49\\x89\\xFD"
}
},
"LegacyGameEventListener": {
"signatures": {
"library": "server",
"windows": "\\x48\\x8B\\x15\\x2A\\x2A\\x2A\\x2A\\x48\\x85\\xD2\\x74\\x2A\\x85\\xC9\\x74",
"linux": "\\x48\\x8B\\x05\\x2A\\x2A\\x2A\\x2A\\x48\\x85\\xC0\\x74\\x2A\\x83\\xFF\\x3F\\x76\\x2A\\x31\\xC0"
}
},
"CBasePlayerPawn_CommitSuicide": {
"offsets": {
"windows": 355,
"linux": 355
"windows": 356,
"linux": 356
}
},
"CBaseEntity_Teleport": {

View File

@@ -36,6 +36,10 @@ export default defineConfig({
label: 'Features',
autogenerate: { directory: 'features' },
},
{
label: 'Admin Framework',
autogenerate: { directory: 'admin-framework' },
},
{
label: 'Reference',
autogenerate: { directory: 'reference' },

View File

@@ -0,0 +1,47 @@
---
title: Admin Command Attributes
description: A guide on using the Admin Command Attributes in plugins.
---
## Assigning permissions to a Command
Assigning permissions to a Command is as easy as tagging the Command method (function callback) with either a `RequiresPermissions` or `RequiresPermissionsOr` attribute. The difference between the two attributes is that `RequiresPermissionsOr` needs only one permission to be present on the caller to be passed, while `RequiresPermissions` needs the caller to have all permissions listed. CounterStrikeSharp handles all of the permission checks behind the scenes for you.
```csharp
[RequiresPermissions("@css/slay", "@custom/permission")]
public void OnMyCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```
```csharp
[RequiresPermissionsOr("@css/ban", "@custom/permission-2")]
public void OnMyOtherCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```
You can even stack the attributes on top of each other, and all of them will be checked.
```csharp
// Requires (@css/cvar AND @custom/permission-1) AND either (@custom/permission-1 OR @custom/permission-2).
[RequiresPermissions("@css/cvar", "@custom/permission-1")]
[RequiresPermissionsOr("@css/ban", "@custom/permission-2")]
public void OnMyComplexCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```
You can also check for groups using the same attributes.
```csharp
[RequiresPermissions("#css/simple-admin")]
public void OnMyGroupCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```

View File

@@ -1,6 +1,6 @@
---
title: Admin Framework
description: A guide on using the Admin Framework in plugins.
title: Defining Admins
description: A guide on how to define admins for CounterStrikeSharp.
---
## Admin Framework
@@ -9,7 +9,7 @@ CounterStrikeSharp has a basic framework which allows plugin developers to assig
## Adding Admins
Adding an Admin is as simple as creating a new entry in the `configs/admins.json` file. The important things you need to declare are the SteamID identifier and the permissions they have. CounterStrikeSharp will do all the heavy-lifting to decipher your SteamID. If you're familar with SourceMod, permission definitions are slightly different as they're defined by an array of strings instead of a string of characters.
Adding an Admin is as simple as creating a new entry in the `configs/admins.json` file. The important things you need to declare are the SteamID identifier and the permissions they have. CounterStrikeSharp will do all the heavy-lifting to decipher your SteamID. If you're familiar with SourceMod, permission definitions are slightly different as they're defined by an array of strings instead of a string of characters.
```json
{
@@ -20,27 +20,17 @@ Adding an Admin is as simple as creating a new entry in the `configs/admins.json
}
```
You can also manually assign permissions to players in code with `AddPlayerPermissions` and `RemovePlayerPermissions`. These changes are not saved to `configs/admins.json`.
You can also manually assign permissions to players in code with `AdminManager.AddPlayerPermissions` and `AdminManager.RemovePlayerPermissions`. These changes are not saved to `configs/admins.json`.
## Assigning permissions to a Command
Assigning permissions to a Command is as easy as tagging the Command method (function callback) with a `RequiresPermissions` attribute.
```csharp
[RequiresPermissions("@css/slay", "@custom/permission")]
public void OnMyCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```
CounterStrikeSharp handles all of the permission checks behind the scenes for you.
:::note
All user permissions MUST start with an at-symbol @ character, otherwise CounterStrikeSharp will not recognize the permission.
:::
### Standard Permissions
Because the flag system is just a list of strings associated with a user, there is no real list of letter based flags like there was previously in something like SourceMod. This means as a plugin author you can declare your own flags, scoped with an `@` symbol, like `@roflmuffin/guns`, which might be the permission to allow spawning of guns in a given command.
However there is a somewhat standardised list of flags that it is advised you use if you are adding functionality that aligns with their purpose, and these are based on the original SourceMod flags:
However there is a somewhat standardized list of flags that it is advised you use if you are adding functionality that aligns with their purpose, and these are based on the original SourceMod flags:
```shell
@css/reservation # Reserved slot access.
@@ -60,3 +50,7 @@ However there is a somewhat standardised list of flags that it is advised you us
@css/cheats # Change sv_cheats or use cheating commands.
@css/root # Magically enables all flags and ignores immunity values.
```
:::note
CounterStrikeSharp does not implement traditional admin command such as `!slay`, `!kick`, and `!ban`. It is up to individual plugins to implement these commands.
:::

View File

@@ -0,0 +1,53 @@
---
title: Defining Command Overrides
description: A guide on how to define command overrides for CounterStrikeSharp.
---
## Defining Admin and Group specific overrides
Command permissions can be overriden so specific admins or groups can execute the command, regardless of any permissions they may or may not have. You can define command overrides by adding the `command_overrides` key to each admin in `configs/admins.json` or group in `configs/admin_groups.json`. Command overrides can either be set to `true`, meaning the admin/group can execute the command, or `false`, meaning the admin/group cannot execute the command at all.
```json
{
"ZoNiCaL": {
"identity": "76561198808392634",
"flags": ["@css/changemap", "@css/generic"],
"immunity": 100,
"command_overrides": {
"example_command": true
}
}
}
```
```json
"#css/simple-admin": {
"flags": [
"@css/generic",
"@css/reservation",
"@css/ban",
"@css/slay",
],
"command_overrides": {
"example_command_2": false
}
}
```
You can set a command override for a player in code using `AdminManager.SetPlayerCommandOverride`.
## Replacing Command permissions
Command permissions can be entirely replaced. These are defined in `configs/admin_overrides.json`. The important things you need to declare are what commands are being changed and what their new flags are. Command overrides can be set to be enabled or disabled, and you can toggle them in code with `AdminManager.SetCommandOverrideState`. You can also specify whether the command override requires the caller to have all of the new permissions (similar to a `RequiresPermissions` attribute check) or only one or more permissions (similar to a `RequirePermissionsOr` attribute check). You cannot stack permission checks.
```json
"css": {
"flags": [
"@css/custom-permission"
],
"check_type": "all",
"enabled": true
}
```
You can check if a command has been overriden in code using `AdminManager.CommandIsOverriden`, and you can manipulate the command override permissions using `AdminManager.AddPermissionOverride` and `AdminManager.RemovePermissionOverride`.

View File

@@ -0,0 +1,25 @@
---
title: Defining Admin Groups
description: A guide on how to define admin groups for CounterStrikeSharp.
---
## Adding Groups
Groups can be created to group a series of permissions together under one tag. They are defined in `configs/admin_groups.json`. The important things you need to declare is the name of the group and the permissions they have.
```json
"#css/simple-admin": {
"flags": [
"@css/generic",
"@css/reservation",
"@css/ban",
"@css/slay",
]
}
```
:::note
All group names MUST start with a hashtag # character, otherwise CounterStrikeSharp will recognize the group.
:::
Admins can be assigned to multiple groups and they will inherit their flags. You can manually assign groups to players in code with `AdminManager.AddPlayerToGroup` and `AdminManager.RemovePlayerFromGroup`.

View File

@@ -0,0 +1,36 @@
---
title: Defining Admin Immunity
description: A guide on how to define immunity for admins for CounterStrikeSharp.
---
## Player Immunity
Admins can be assigned an immunity value, similar to SourceMod. If an admin or player with a lower immunity value targets another admin or player with a larger immunity value, then the command will fail. You can define an immunity value by adding the `immunity` key to each admin in `configs/admins.json`.
```json
{
"ZoNiCaL": {
"identity": "76561198808392634",
"flags": ["@css/changemap", "@css/generic"],
"immunity": 100
}
}
```
You can even assign an immunity value to groups. If an admin has a lower immunity value, the group's value will be used instead.
```json
"#css/simple-admin": {
"flags": [
"@css/generic",
"@css/reservation",
"@css/ban",
"@css/slay",
],
"immunity": 100
}
```
:::note
CounterStrikeSharp does not automatically handle immunity checking. It is up to individual plugins to handle immunity checks as they can implement different ways of targeting players. This can be done in code with `AdminManager.CanPlayerTarget`. You can also set a player's immunity in code with `AdminManager.SetPlayerImmunity`.
:::

View File

@@ -34,6 +34,13 @@ Use your IDE (Visual Studio/Rider) to add a reference to the `CounterStrikeSharp
</Project>
```
:::tip
Instead of manually adding a reference to `CounterStrikeSharp.Api.dll`, you can install the NuGet package `CounterStrikeSharp.Api` using the following:
```shell
dotnet add package CounterStrikeSharp.API
```
:::
### Creating a Plugin File
Rename the default class file that came with your new project (by default it should be `Class1.cs`) to something more accurate, like `HelloWorldPlugin.cs`. Inside this file, we will insert the stub hello world plugin. Be sure to change the name and namespace so it matches your project name.

View File

@@ -30,7 +30,7 @@ namespace CounterStrikeSharp.API.Core
}
}
public static IntPtr AddCommand(string name, string description, bool serveronly, int flags, InputArgument callback){
public static void AddCommand(string name, string description, bool serveronly, int flags, InputArgument callback){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(name);
@@ -41,7 +41,6 @@ namespace CounterStrikeSharp.API.Core
ScriptContext.GlobalScriptContext.SetIdentifier(0x807C6B9C);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr));
}
}
@@ -553,6 +552,17 @@ namespace CounterStrikeSharp.API.Core
}
}
public static void FireEventToClient(IntPtr gameevent, int clientindex){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(gameevent);
ScriptContext.GlobalScriptContext.Push(clientindex);
ScriptContext.GlobalScriptContext.SetIdentifier(0x40B7C06C);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static string GetEventName(IntPtr gameevent){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();

View File

@@ -162,6 +162,42 @@ namespace CounterStrikeSharp.API.Core
var command = new CommandInfo(ptr, caller);
var methodInfo = handler?.GetMethodInfo();
if (!AdminManager.CommandIsOverriden(name))
{
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null)
{
foreach (var attr in permissions)
{
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
}
// If this command has it's permissions overriden, we will do an AND check for all permissions.
else
{
// I don't know if this is the most sane implementation of this, can be edited in code review.
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));
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
// Do not execute if we shouldn't be calling this command.
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
if (helperAttribute != null)
@@ -185,14 +221,6 @@ namespace CounterStrikeSharp.API.Core
}
}
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttribute<RequiresPermissions>()?.RequiredPermissions;
if (permissions != null && !AdminManager.PlayerHasPermissions(caller, permissions))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
handler?.Invoke(caller, command);
});
@@ -209,14 +237,10 @@ namespace CounterStrikeSharp.API.Core
{
var wrappedHandler = new Func<int, IntPtr, HookResult>((i, ptr) =>
{
if (i == -1)
{
return HookResult.Continue;
}
var caller = (i != -1) ? new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1)) : null;
var entity = new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1));
var command = new CommandInfo(ptr, entity);
return handler.Invoke(entity.IsValid ? entity : null, command);
var command = new CommandInfo(ptr, caller);
return handler.Invoke(caller, command);
});
var subscriber = new CallbackSubscriber(handler, wrappedHandler, () => { RemoveCommandListener(name, handler, mode); });

View File

@@ -23,6 +23,7 @@ using System.Text.Json.Serialization;
using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using System.Collections.Generic;
namespace CounterStrikeSharp.API.Core
{
@@ -31,9 +32,9 @@ namespace CounterStrikeSharp.API.Core
/// </summary>
internal sealed partial class CoreConfigData
{
[JsonPropertyName("PublicChatTrigger")] public string PublicChatTrigger { get; set; } = "!";
[JsonPropertyName("PublicChatTrigger")] public IEnumerable<string> PublicChatTrigger { get; set; } = new HashSet<string>() { "!" };
[JsonPropertyName("SilentChatTrigger")] public string SilentChatTrigger { get; set; } = "/";
[JsonPropertyName("SilentChatTrigger")] public IEnumerable<string> SilentChatTrigger { get; set; } = new HashSet<string>() { "/" };
[JsonPropertyName("FollowCS2ServerGuidelines")] public bool FollowCS2ServerGuidelines { get; set; } = true;
}
@@ -46,12 +47,12 @@ namespace CounterStrikeSharp.API.Core
/// <summary>
/// List of characters to use for public chat triggers.
/// </summary>
public static string PublicChatTrigger => _coreConfig.PublicChatTrigger;
public static IEnumerable<string> PublicChatTrigger => _coreConfig.PublicChatTrigger;
/// <summary>
/// List of characters to use for silent chat triggers.
/// </summary>
public static string SilentChatTrigger => _coreConfig.SilentChatTrigger;
public static IEnumerable<string> SilentChatTrigger => _coreConfig.SilentChatTrigger;
/// <summary>
/// <para>

View File

@@ -65,8 +65,14 @@ namespace CounterStrikeSharp.API.Core
Console.WriteLine("Loading GameData from \"gamedata/gamedata.json\"");
GameData.Load(Path.Combine(rootDir.FullName, "gamedata", "gamedata.json"));
Console.WriteLine("Loading Admin Groups from \"configs/admin_groups.json\"");
AdminManager.LoadAdminGroups(Path.Combine(rootDir.FullName, "configs", "admin_groups.json"));
Console.WriteLine("Loading Admins from \"configs/admins.json\"");
AdminManager.Load(Path.Combine(rootDir.FullName, "configs", "admins.json"));
AdminManager.LoadAdminData(Path.Combine(rootDir.FullName, "configs", "admins.json"));
Console.WriteLine("Loading Admin Command Overrides from \"configs/admin_overrides.json\"");
AdminManager.LoadCommandOverrides(Path.Combine(rootDir.FullName, "configs", "admin_overrides.json"));
AdminManager.MergeGroupPermsIntoAdmins();
for (var i = 1; i <= 9; i++)
{
@@ -188,7 +194,7 @@ namespace CounterStrikeSharp.API.Core
return plugin;
}
[RequiresPermissions("can_execute_css_commands")]
[RequiresPermissions("@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSCommand(CCSPlayerController? caller, CommandInfo info)
{
@@ -201,7 +207,7 @@ namespace CounterStrikeSharp.API.Core
return;
}
[RequiresPermissions("can_execute_css_commands")]
[RequiresPermissions("@css/generic")]
[CommandHelper(minArgs: 1,
usage: "[option]\n" +
" list - List all plugins currently loaded.\n" +

View File

@@ -12,6 +12,11 @@ public partial class CBaseEntity
Handle, position.Handle, angles.Handle, velocity.Handle);
}
public void DispatchSpawn()
{
VirtualFunctions.CBaseEntity_DispatchSpawn(Handle, IntPtr.Zero);
}
/// <summary>
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsOrigin;
/// </summary>

View File

@@ -0,0 +1,31 @@
/*
* 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 <https://www.gnu.org/licenses/>. *
*/
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CCSGameRules
{
/// <summary>
/// Terminate the round with the given delay and reason.
/// </summary>
public void TerminateRound(float delay, RoundEndReason roundEndReason)
{
VirtualFunctions.TerminateRound(Handle, roundEndReason, delay);
}
}

View File

@@ -26,7 +26,7 @@ public partial class CCSPlayerController
public void PrintToConsole(string message)
{
NativeAPI.PrintToConsole((int)EntityIndex.Value.Value, message);
NativeAPI.PrintToConsole((int)EntityIndex.Value.Value, $"{message}\n\0");
}
public void PrintToChat(string message)
@@ -47,9 +47,37 @@ public partial class CCSPlayerController
Duration = 5,
Userid = this
};
@event.FireEvent(false);
@event.FireEventToClient(this);
}
/// <summary>
/// Drops the active player weapon on the ground.
/// </summary>
public void DropActiveWeapon()
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
if (PlayerPawn.Value.ItemServices == null) return;
if (PlayerPawn.Value.WeaponServices == null) return;
if (!PlayerPawn.Value.WeaponServices.ActiveWeapon.IsValid) return;
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
CCSPlayer_WeaponServices weponServices = new CCSPlayer_WeaponServices(PlayerPawn.Value.WeaponServices.Handle);
itemServices.DropActivePlayerWeapon(weponServices.ActiveWeapon.Value);
}
/// <summary>
/// Removes every weapon from the player.
/// </summary>
public void RemoveWeapons()
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
if (PlayerPawn.Value.ItemServices == null) return;
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
itemServices.RemoveWeapons();
}
public bool IsBot => ((PlayerFlags)Flags).HasFlag(PlayerFlags.FL_FAKECLIENT);

View File

@@ -0,0 +1,38 @@
/*
* 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 <https://www.gnu.org/licenses/>. *
*/
using CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CCSPlayer_ItemServices
{
/// <summary>
/// Drops the active player weapon on the ground.
/// </summary>
public void DropActivePlayerWeapon(CBasePlayerWeapon activeWeapon)
{
VirtualFunction.CreateVoid<nint, nint>(Handle, GameData.GetOffset("CCSPlayer_ItemServices_DropActivePlayerWeapon"))(Handle, activeWeapon.Handle);
}
/// <summary>
/// Removes every weapon from the player.
/// </summary>
public void RemoveWeapons()
{
VirtualFunction.CreateVoid<nint>(Handle, GameData.GetOffset("CCSPlayer_ItemServices_RemoveWeapons"))(Handle);
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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 <https://www.gnu.org/licenses/>. *
*/
using CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CGameSceneNode
{
/// <summary>
/// Gets the <see cref="CSkeletonInstance"/> instance from the node.
/// </summary>
public CSkeletonInstance GetSkeletonInstance()
{
return new CSkeletonInstance(VirtualFunction.Create<nint, nint>(Handle, GameData.GetOffset("CGameSceneNode_GetSkeletonInstance"))(Handle));
}
}

View File

@@ -355,6 +355,13 @@ public enum BrushSolidities_e : uint
BRUSHSOLID_ALWAYS = 0x2,
}
public enum C4LightEffect_t : uint
{
eLightEffectNone = 0x0,
eLightEffectDropped = 0x1,
eLightEffectThirdPersonHeld = 0x2,
}
public enum CAnimationGraphVisualizerPrimitiveType : uint
{
ANIMATIONGRAPHVISUALIZERPRIMITIVETYPE_Text = 0x0,
@@ -2773,6 +2780,7 @@ public enum TakeDamageFlags_t : uint
DFLAG_RADIUS_DMG = 0x400,
DMG_LASTDFLAG = 0x400,
DFLAG_IGNORE_ARMOR = 0x800,
DFLAG_SUPPRESS_UTILREMOVE = 0x1000,
}
public enum TextureRepetitionMode_t : uint
@@ -3927,6 +3935,9 @@ public partial class CBaseCSGrenadeProjectile : CBaseGrenade
{
public CBaseCSGrenadeProjectile (IntPtr pointer) : base(pointer) {}
// m_vInitialPosition
public Vector InitialPosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CBaseCSGrenadeProjectile", "m_vInitialPosition");
// m_vInitialVelocity
public Vector InitialVelocity => Schema.GetDeclaredClass<Vector>(this.Handle, "CBaseCSGrenadeProjectile", "m_vInitialVelocity");
@@ -3972,6 +3983,12 @@ public partial class CBaseCSGrenadeProjectile : CBaseGrenade
// m_nTicksAtZeroVelocity
public ref Int32 TicksAtZeroVelocity => ref Schema.GetRef<Int32>(this.Handle, "CBaseCSGrenadeProjectile", "m_nTicksAtZeroVelocity");
// m_bHasEverHitPlayer
public ref bool HasEverHitPlayer => ref Schema.GetRef<bool>(this.Handle, "CBaseCSGrenadeProjectile", "m_bHasEverHitPlayer");
// m_bClearFromPlayers
public ref bool ClearFromPlayers => ref Schema.GetRef<bool>(this.Handle, "CBaseCSGrenadeProjectile", "m_bClearFromPlayers");
}
public partial class CBaseDMStart : CPointEntity
@@ -7099,8 +7116,8 @@ public partial class CCSGameRules : CTeamplayRules
// m_nShorthandedBonusLastEvalRound
public ref Int32 ShorthandedBonusLastEvalRound => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_nShorthandedBonusLastEvalRound");
// m_bMatchAbortedDueToPlayerBan
public ref bool MatchAbortedDueToPlayerBan => ref Schema.GetRef<bool>(this.Handle, "CCSGameRules", "m_bMatchAbortedDueToPlayerBan");
// m_nMatchAbortedEarlyReason
public ref Int32 MatchAbortedEarlyReason => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_nMatchAbortedEarlyReason");
// m_bHasTriggeredRoundStartMusic
public ref bool HasTriggeredRoundStartMusic => ref Schema.GetRef<bool>(this.Handle, "CCSGameRules", "m_bHasTriggeredRoundStartMusic");
@@ -7515,6 +7532,12 @@ public partial class CCSPlayer_MovementServices : CPlayer_MovementServices_Human
// m_flStamina
public ref float Stamina => ref Schema.GetRef<float>(this.Handle, "CCSPlayer_MovementServices", "m_flStamina");
// m_flHeightAtJumpStart
public ref float HeightAtJumpStart => ref Schema.GetRef<float>(this.Handle, "CCSPlayer_MovementServices", "m_flHeightAtJumpStart");
// m_flMaxJumpHeightThisJump
public ref float MaxJumpHeightThisJump => ref Schema.GetRef<float>(this.Handle, "CCSPlayer_MovementServices", "m_flMaxJumpHeightThisJump");
}
public partial class CCSPlayer_PingServices : CPlayerPawnComponent
@@ -7792,6 +7815,9 @@ public partial class CCSPlayerController : CBasePlayerController
// m_uiAbandonRecordedReason
public ref UInt32 UiAbandonRecordedReason => ref Schema.GetRef<UInt32>(this.Handle, "CCSPlayerController", "m_uiAbandonRecordedReason");
// m_bCannotBeKicked
public ref bool CannotBeKicked => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerController", "m_bCannotBeKicked");
// m_bEverFullyConnected
public ref bool EverFullyConnected => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerController", "m_bEverFullyConnected");
@@ -7909,9 +7935,15 @@ public partial class CCSPlayerController : CBasePlayerController
// m_bGaveTeamDamageWarningThisRound
public ref bool GaveTeamDamageWarningThisRound => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerController", "m_bGaveTeamDamageWarningThisRound");
// m_dblLastReceivedPacketPlatFloatTime
public ref double DblLastReceivedPacketPlatFloatTime => ref Schema.GetRef<double>(this.Handle, "CCSPlayerController", "m_dblLastReceivedPacketPlatFloatTime");
// m_LastTeamDamageWarningTime
public ref float LastTeamDamageWarningTime => ref Schema.GetRef<float>(this.Handle, "CCSPlayerController", "m_LastTeamDamageWarningTime");
// m_LastTimePlayerWasDisconnectedForPawnsRemove
public ref float LastTimePlayerWasDisconnectedForPawnsRemove => ref Schema.GetRef<float>(this.Handle, "CCSPlayerController", "m_LastTimePlayerWasDisconnectedForPawnsRemove");
}
public partial class CCSPlayerController_ActionTrackingServices : CPlayerControllerComponent
@@ -8046,6 +8078,9 @@ public partial class CCSPlayerPawn : CCSPlayerPawnBase
set { Schema.SetString(this.Handle, "CCSPlayerPawn", "m_szLastPlaceName", value); }
}
// m_bInHostageResetZone
public ref bool InHostageResetZone => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerPawn", "m_bInHostageResetZone");
// m_bInBuyZone
public ref bool InBuyZone => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerPawn", "m_bInBuyZone");
@@ -12825,6 +12860,9 @@ public partial class CHostage : CHostageExpresserShim
// m_vecSpawnGroundPos
public Vector SpawnGroundPos => Schema.GetDeclaredClass<Vector>(this.Handle, "CHostage", "m_vecSpawnGroundPos");
// m_vecHostageResetPosition
public Vector HostageResetPosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CHostage", "m_vecHostageResetPosition");
}
public partial class CHostageAlias_info_hostage_spawn : CHostage
@@ -14153,6 +14191,28 @@ public partial class CLogicDistanceCheck : CLogicalEntity
}
public partial class CLogicEventListener : CLogicalEntity
{
public CLogicEventListener (IntPtr pointer) : base(pointer) {}
// m_strEventName
public string StrEventName
{
get { return Schema.GetUtf8String(this.Handle, "CLogicEventListener", "m_strEventName"); }
set { Schema.SetString(this.Handle, "CLogicEventListener", "m_strEventName", value); }
}
// m_bIsEnabled
public ref bool IsEnabled => ref Schema.GetRef<bool>(this.Handle, "CLogicEventListener", "m_bIsEnabled");
// m_nTeam
public ref Int32 Team => ref Schema.GetRef<Int32>(this.Handle, "CLogicEventListener", "m_nTeam");
// m_OnEventFired
public CEntityIOOutput OnEventFired => Schema.GetDeclaredClass<CEntityIOOutput>(this.Handle, "CLogicEventListener", "m_OnEventFired");
}
public partial class CLogicGameEvent : CLogicalEntity
{
public CLogicGameEvent (IntPtr pointer) : base(pointer) {}
@@ -20138,6 +20198,12 @@ public partial class CTriggerGravity : CBaseTrigger
}
public partial class CTriggerHostageReset : CBaseTrigger
{
public CTriggerHostageReset (IntPtr pointer) : base(pointer) {}
}
public partial class CTriggerHurt : CBaseTrigger
{
public CTriggerHurt (IntPtr pointer) : base(pointer) {}

View File

@@ -0,0 +1,176 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Linq;
namespace CounterStrikeSharp.API.Modules.Admin
{
public partial class CommandData
{
[JsonPropertyName("flags")] public required HashSet<string> Flags { get; init; }
[JsonPropertyName("enabled")] public bool Enabled { get; set; } = true;
[JsonPropertyName("check_type")] public required string CheckType { get; init; }
}
public static partial class AdminManager
{
private static Dictionary<string, CommandData> CommandOverrides = new();
public static void LoadCommandOverrides(string overridePath)
{
try
{
if (!File.Exists(overridePath))
{
Console.WriteLine("Admin command overrides file not found. Skipping admin command overrides load.");
return;
}
var overridesFromFile = JsonSerializer.Deserialize<Dictionary<string, CommandData>>
(File.ReadAllText(overridePath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (overridesFromFile == null) { throw new FileNotFoundException(); }
foreach (var (command, overrideDef) in overridesFromFile)
{
if (CommandOverrides.ContainsKey(command))
{
CommandOverrides[command].Flags.UnionWith(overrideDef.Flags);
}
else
{
CommandOverrides.Add(command, overrideDef);
}
}
Console.WriteLine($"Loaded {CommandOverrides.Count} admin command overrides.");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load admin command overrides: {ex}");
}
}
/// <summary>
/// Checks to see if a command has overriden permissions.
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <returns>True if the command has overriden permissions, false if not.</returns>
public static bool CommandIsOverriden(string commandName)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null) return false;
return overrideDef.Enabled && overrideDef?.Flags.Count() > 0;
}
/// <summary>
/// Grabs the data for a command override that was loaded from "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <returns>CommandData class if found, null if not.</returns>
public static CommandData? GetCommandOverrideData(string commandName)
{
return CommandOverrides.GetValueOrDefault(commandName);
}
/// <summary>
/// Grabs the new, overriden flags for a command.
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <returns>If the command is valid, a valid array of flags.</returns>
public static string[] GetPermissionOverrides(string commandName)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
return overrideDef?.Flags.ToArray() ?? new string[] { };
}
/// <summary>
/// Adds a new permission to a command override.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <param name="permissions">Permissions to add to the command override.</param>
public static void AddPermissionOverride(string commandName, params string[] permissions)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null)
{
overrideDef = new CommandData()
{
Flags = new(permissions),
Enabled = true,
CheckType = "all"
};
CommandOverrides[commandName] = overrideDef;
return;
}
foreach (var flag in permissions)
{
overrideDef.Flags.Add(flag);
}
CommandOverrides[commandName] = overrideDef;
}
/// <summary>
/// Removes a permission from a command override.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <param name="permissions">Permissions to remove from the command override.</param>
public static void RemovePermissionOverride(string commandName, params string[] permissions)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null) return;
overrideDef.Flags.ExceptWith(permissions);
CommandOverrides[commandName] = overrideDef;
}
/// <summary>
/// Clears all permissions from a command override.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <param name="disable">Whether to disable the command override after clearing.</param>
public static void ClearPermissionOverride(string commandName, bool disable = true)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null) return;
overrideDef.Flags?.Clear();
overrideDef.Enabled = !disable;
CommandOverrides[commandName] = overrideDef;
}
/// <summary>
/// Deletes a command override.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
public static void DeleteCommandOverride(string commandName)
{
if (!CommandOverrides.ContainsKey(commandName)) return;
CommandOverrides.Remove(commandName);
}
/// <summary>
/// Sets a command override to be enabled or disabled.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <param name="state">New state of the command override.</param>
public static void SetCommandOverideState(string commandName, bool state)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null) return;
overrideDef.Flags?.Clear();
overrideDef.Enabled = state;
CommandOverrides[commandName] = overrideDef;
}
}
}

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Admin
{
public partial class AdminData
{
[JsonPropertyName("groups")] public HashSet<string> Groups { get; init; } = new();
}
public partial class AdminGroupData
{
[JsonPropertyName("flags")] public required HashSet<string> Flags { get; init; }
[JsonPropertyName("immunity")] public uint Immunity { get; set; } = 0;
[JsonPropertyName("command_overrides")] public Dictionary<string, bool> CommandOverrides { get; init; } = new();
}
public static partial class AdminManager
{
private static Dictionary<string, AdminGroupData> Groups = new();
public static void LoadAdminGroups(string adminGroupsPath)
{
try
{
if (!File.Exists(adminGroupsPath))
{
Console.WriteLine("Admin groups file not found. Skipping admin groups load.");
return;
}
var groupsFromFile = JsonSerializer.Deserialize<Dictionary<string, AdminGroupData>>(File.ReadAllText(adminGroupsPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (groupsFromFile == null) { throw new FileNotFoundException(); }
foreach (var (key, groupDef) in groupsFromFile)
{
if (Groups.ContainsKey(key))
{
Groups[key].Flags.UnionWith(groupDef.Flags);
if (groupDef.Immunity > Groups[key].Immunity)
{
Groups[key].Immunity = groupDef.Immunity;
}
}
else
{
Groups.Add(key, groupDef);
}
}
Console.WriteLine($"Loaded {Groups.Count} admin groups.");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load admin groups: {ex}");
}
// Loop over each of the admins. If one of our admins is in a group,
// add the flags from the group to their admin definition and change
// the admin's immunity if it's higher.
foreach (var adminData in Admins.Values)
{
var groups = adminData.Groups;
foreach (var group in groups)
{
// roflmuffin is probably smart enough to condense this function down ;)
if (Groups.TryGetValue(group, out var groupData))
{
adminData.Flags.UnionWith(groupData.Flags);
if (groupData.Immunity > adminData.Immunity)
{
adminData.Immunity = groupData.Immunity;
}
}
}
}
}
/// <summary>
/// Checks to see if the player is part of an admin group.
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="groups">Groups to check for.</param>
/// <returns>True if a player is part of all of the groups provided, false if not.</returns>
public static bool PlayerInGroup(CCSPlayerController? player, params string[] groups)
{
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of groups.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
return playerData?.Groups.IsSupersetOf(groups) ?? false;
}
/// <summary>
/// Checks to see if the player is part of an admin group.
/// </summary>
/// <param name="steamId">SteamID of the player.</param>
/// <param name="groups">Groups to check for.</param>
/// <returns>True if a player is part of all of the groups provided, false if not.</returns>
public static bool PlayerInGroup(SteamID steamId, params string[] groups)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.Groups.IsSupersetOf(groups) ?? false;
}
/// <summary>
/// Adds a player to a group.
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="groups">Groups to add the player to.</param>
public static void AddPlayerToGroup(CCSPlayerController? player, params string[] groups)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
AddPlayerToGroup((SteamID)player.SteamID, groups);
}
/// <summary>
/// Adds a player to a group.
/// </summary>
/// <param name="steamId">SteamID of the player.</param>
/// <param name="groups">Groups to add the player to.</param>
public static void AddPlayerToGroup(SteamID steamId, params string[] groups)
{
var data = GetPlayerAdminData(steamId);
if (data == null)
{
data = new AdminData()
{
Identity = steamId.SteamId64.ToString(),
Flags = new(),
Groups = new(groups)
};
}
foreach (var group in groups)
{
if (Groups.TryGetValue(group, out var groupDef))
{
data.Flags.UnionWith(groupDef.Flags);
groupDef.CommandOverrides.ToList().ForEach(x => data.CommandOverrides[x.Key] = x.Value);
}
}
Admins[steamId] = data;
}
/// <summary>
/// Removes a player from a group.
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="removeInheritedFlags">If true, all of the flags that the player inherited from being in the group will be removed.</param>
/// <param name="groups"></param>
public static void RemovePlayerFromGroup(CCSPlayerController? player, bool removeInheritedFlags = true, params string[] groups)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
RemovePlayerFromGroup((SteamID)player.SteamID, true, groups);
}
/// <summary>
/// Removes a player from a group.
/// </summary>
/// <param name="steamId">SteamID of the player.</param>
/// <param name="removeInheritedFlags">If true, all of the flags that the player inherited from being in the group will be removed.</param>
/// <param name="groups"></param>
public static void RemovePlayerFromGroup(SteamID steamId, bool removeInheritedFlags = true, params string[] groups)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Groups.ExceptWith(groups);
if (removeInheritedFlags)
{
foreach (var group in groups)
{
if (Groups.TryGetValue(group, out var groupDef))
{
data.Flags.ExceptWith(groupDef.Flags);
}
}
}
Admins[steamId] = data;
}
}
}

View File

@@ -1,44 +1,43 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.Json;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Utils;
using System.IO;
using System.Linq;
using System.Reflection;
namespace CounterStrikeSharp.API.Modules.Admin
{
public partial class AdminData
{
[JsonPropertyName("identity")] public required string Identity { get; init; }
[JsonPropertyName("flags")] public required HashSet<string> Flags { get; init; }
}
public static class AdminManager
public static partial class AdminManager
{
private static readonly Dictionary<SteamID, AdminData> Admins = new();
static AdminManager()
{
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);
}
[RequiresPermissions("can_reload_admins")]
public static void MergeGroupPermsIntoAdmins()
{
foreach (var (steamID, adminDef) in Admins)
{
AddPlayerToGroup(steamID, adminDef.Groups.ToArray());
}
}
[RequiresPermissions(permissions:"@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadAdminsCommand(CCSPlayerController? player, CommandInfo command)
{
Admins.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
Load(Path.Combine(rootDir.FullName, "configs", "admins.json"));
LoadAdminData(Path.Combine(rootDir.FullName, "configs", "admins.json"));
}
[RequiresPermissions("can_reload_admins")]
[RequiresPermissions(permissions:"@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ListAdminsCommand(CCSPlayerController? player, CommandInfo command)
{
@@ -48,166 +47,42 @@ namespace CounterStrikeSharp.API.Modules.Admin
}
}
public static void Load(string adminDataPath)
[RequiresPermissions(permissions:"@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadAdminGroupsCommand(CCSPlayerController? player, CommandInfo command)
{
try
{
if (!File.Exists(adminDataPath))
{
Console.WriteLine("Admin data file not found. Skipping admin data load.");
return;
}
var adminsFromFile = JsonSerializer.Deserialize<Dictionary<string, AdminData>>(File.ReadAllText(adminDataPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (adminsFromFile == null) { throw new FileNotFoundException(); }
foreach (var adminDef in adminsFromFile.Values)
{
if (SteamID.TryParse(adminDef.Identity, out var steamId))
{
if (Admins.ContainsKey(steamId!))
{
Admins[steamId!].Flags.UnionWith(adminDef.Flags);
}
else
{
Admins.Add(steamId!, adminDef);
}
}
}
Groups.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
LoadAdminGroups(Path.Combine(rootDir.FullName, "configs", "admin_groups.json"));
}
Console.WriteLine($"Loaded admin data with {Admins.Count} admins.");
}
catch (Exception ex)
[RequiresPermissions(permissions: "@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ListAdminGroupsCommand(CCSPlayerController? player, CommandInfo command)
{
foreach (var (groupName, groupDef) in Groups)
{
Console.WriteLine($"Failed to load admin data: {ex}");
command.ReplyToCommand($"{groupName} - {string.Join(", ", groupDef.Flags)}");
}
}
/// <summary>
/// Grabs the admin data for a player that was loaded from "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID object of the player.</param>
/// <returns>AdminData class if data found, null if not.</returns>
public static AdminData? GetPlayerAdminData(SteamID steamId)
[RequiresPermissions(permissions: "@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadAdminOverridesCommand(CCSPlayerController? player, CommandInfo command)
{
return Admins.GetValueOrDefault(steamId);
CommandOverrides.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
LoadCommandOverrides(Path.Combine(rootDir.FullName, "configs", "admin_overrides.json"));
}
/// <summary>
/// Checks to see if a player has access to a certain set of permission flags.
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="flags">Flags to look for in the players permission flags.</param>
/// <returns>True if flags are present, false if not.</returns>
public static bool PlayerHasPermissions(CCSPlayerController? player, params string[] flags)
[RequiresPermissions(permissions: "@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ListAdminOverridesCommand(CCSPlayerController? player, CommandInfo command)
{
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
/// <summary>
/// Checks to see if a player has access to a certain set of permission flags.
/// </summary>
/// <param name="steamId">Steam ID object.</param>
/// <param name="flags">Flags to look for in the players permission flags.</param>
/// <returns>True if flags are present, false if not.</returns>
public static bool PlayerHasPermissions(SteamID steamId, params string[] flags)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
/// <summary>
/// Temporarily adds a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to add a flag to.</param>
/// <param name="flags">Flags to add for the player.</param>
public static void AddPlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
AddPlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
/// Temporarily adds a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID to add a flag to.</param>
/// <param name="flags">Flags to add for the player.</param>
public static void AddPlayerPermissions(SteamID steamId, params string[] flags)
{
var data = GetPlayerAdminData(steamId);
if (data == null)
foreach (var (commandName, commandDef) in CommandOverrides)
{
data = new AdminData()
{
Identity = steamId.SteamId64.ToString(),
Flags = new(flags)
};
Admins[steamId] = data;
return;
command.ReplyToCommand($"{commandName} (enabled: {commandDef.Enabled.ToString()}) - {string.Join(", ", commandDef.Flags)}");
}
foreach (var flag in flags)
{
data.Flags.Add(flag);
}
}
/// <summary>
/// Temporarily removes a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to remove flags from.</param>
/// <param name="flags">Flags to remove from the player.</param>
public static void RemovePlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
RemovePlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
/// Temporarily removes a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">Steam ID to remove flags from.</param>
/// <param name="flags">Flags to remove from the player.</param>
public static void RemovePlayerPermissions(SteamID steamId, params string[] flags)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Flags.ExceptWith(flags);
}
/// <summary>
/// Removes a players admin data. This is not saved to "configs/admins.json"
/// </summary>
/// <param name="player">Player controller to remove admin data from.</param>
public static void RemovePlayerAdminData(CCSPlayerController? player)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
RemovePlayerAdminData((SteamID)player.SteamID);
}
/// <summary>
/// Removes a players admin data. This is not saved to "configs/admins.json"
/// </summary>
/// <param name="steamId">Steam ID remove admin data from.</param>
public static void RemovePlayerAdminData(SteamID steamId)
{
Admins.Remove(steamId);
}
}
}

View File

@@ -0,0 +1,391 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Text.Json;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Commands;
using System.Reflection;
using System.Numerics;
using System.Linq;
namespace CounterStrikeSharp.API.Modules.Admin
{
public partial class AdminData
{
[JsonPropertyName("identity")] public required string Identity { get; init; }
[JsonPropertyName("flags")] public HashSet<string> Flags { get; init; } = new();
[JsonPropertyName("immunity")] public uint Immunity { get; set; } = 0;
[JsonPropertyName("command_overrides")] public Dictionary<string, bool> CommandOverrides { get; init; } = new();
}
public static partial class AdminManager
{
private static Dictionary<SteamID, AdminData> Admins = new();
public static void LoadAdminData(string adminDataPath)
{
try
{
if (!File.Exists(adminDataPath))
{
Console.WriteLine("Admin data file not found. Skipping admin data load.");
return;
}
var adminsFromFile = JsonSerializer.Deserialize<Dictionary<string, AdminData>>(File.ReadAllText(adminDataPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (adminsFromFile == null) { throw new FileNotFoundException(); }
foreach (var adminDef in adminsFromFile.Values)
{
if (SteamID.TryParse(adminDef.Identity, out var steamId))
{
if (Admins.ContainsKey(steamId!))
{
Admins[steamId!].Flags.UnionWith(adminDef.Flags);
if (adminDef.Immunity > Admins[steamId!].Immunity)
{
Admins[steamId!].Immunity = adminDef.Immunity;
}
}
else
{
Admins.Add(steamId!, adminDef);
}
}
}
Console.WriteLine($"Loaded admin data with {Admins.Count} admins.");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load admin data: {ex}");
}
}
/// <summary>
/// Grabs the admin data for a player that was loaded from "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID object of the player.</param>
/// <returns>AdminData class if data found, null if not.</returns>
public static AdminData? GetPlayerAdminData(SteamID steamId)
{
return Admins.GetValueOrDefault(steamId);
}
/// <summary>
/// Removes a players admin data. This is not saved to "configs/admins.json"
/// </summary>
/// <param name="steamId">Steam ID remove admin data from.</param>
public static void RemovePlayerAdminData(SteamID steamId)
{
Admins.Remove(steamId);
}
#region Command Permission Checks
/// <summary>
/// Checks to see if a player has access to a certain set of permission flags.
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="flags">Flags to look for in the players permission flags.</param>
/// <returns>True if flags are present, false if not.</returns>
public static bool PlayerHasPermissions(CCSPlayerController? player, params string[] flags)
{
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
/// <summary>
/// Checks to see if a player has access to a certain set of permission flags.
/// </summary>
/// <param name="steamId">Steam ID object.</param>
/// <param name="flags">Flags to look for in the players permission flags.</param>
/// <returns>True if flags are present, false if not.</returns>
public static bool PlayerHasPermissions(SteamID steamId, params string[] flags)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
#endregion
// This is placed here instead of in AdminCommandOverrides.cs as this all relates to admins that are
// defined within the "configs/admins.json" file.
#region Admin Specific Command Overrides
/// <summary>
/// Checks to see if a player has a command override. This does NOT return the actual
/// state of the override.
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="command">Name of the command to check for.</param>
/// <returns>True if override exists, false if not.</returns>
public static bool PlayerHasCommandOverride(CCSPlayerController? player, string command)
{
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
return playerData?.CommandOverrides.ContainsKey(command) ?? false;
}
/// <summary>
/// Checks to see if a player has a command override. This does NOT return the actual
/// state of the override.
/// </summary>
/// <param name="steamId">Steam ID object.</param>
/// <param name="command">Name of the command to check for.</param>
/// <returns>True if override exists, false if not.</returns>
public static bool PlayerHasCommandOverride(SteamID steamId, string command)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.CommandOverrides.ContainsKey(command) ?? false;
}
/// <summary>
/// Gets the value of a command override state.
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="command">Name of the command to check for.</param>
/// <returns>True if override is active, false if not.</returns>
public static bool GetPlayerCommandOverrideState(CCSPlayerController? player, string command)
{
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
return playerData?.CommandOverrides.GetValueOrDefault(command) ?? false;
}
/// <summary>
/// Gets the value of a command override state.
/// </summary>
/// <param name="steamId">Steam ID object.</param>
/// <param name="command">Name of the command to check for.</param>
/// <returns>True if override is active, false if not.</returns>
public static bool GetPlayerCommandOverrideState(SteamID steamId, string command)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.CommandOverrides.GetValueOrDefault(command) ?? false;
}
/// <summary>
/// Sets a player command override. This is not saved to "configs/admins.json".
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="command">Name of the command to check for.</param>
/// <param name="state">New state of the command override.</param>
public static void SetPlayerCommandOverride(CCSPlayerController? player, string command, bool state)
{
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of permissions.
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
SetPlayerCommandOverride((SteamID)player.SteamID, command, state);
}
/// <summary>
/// Sets a player command override. This is not saved to "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID to add a flag to.</param>
/// <param name="command">Name of the command to check for.</param>
/// <param name="state">New state of the command override.</param>
public static void SetPlayerCommandOverride(SteamID steamId, string command, bool state)
{
var data = GetPlayerAdminData(steamId);
if (data == null)
{
data = new AdminData()
{
Identity = steamId.SteamId64.ToString(),
Flags = new(),
Groups = new(),
CommandOverrides = new() { { command, state } }
};
Admins[steamId] = data;
return;
}
data.CommandOverrides[command] = state;
Admins[steamId] = data;
}
#endregion
#region Manipulating Permissions
/// <summary>
/// Temporarily adds a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to add a flag to.</param>
/// <param name="flags">Flags to add for the player.</param>
public static void AddPlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
AddPlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
/// Temporarily adds a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID to add a flag to.</param>
/// <param name="flags">Flags to add for the player.</param>
public static void AddPlayerPermissions(SteamID steamId, params string[] flags)
{
var data = GetPlayerAdminData(steamId);
if (data == null)
{
data = new AdminData()
{
Identity = steamId.SteamId64.ToString(),
Flags = new(flags),
Groups = new()
};
Admins[steamId] = data;
return;
}
foreach (var flag in flags)
{
data.Flags.Add(flag);
}
Admins[steamId] = data;
}
/// <summary>
/// Temporarily removes a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to remove flags from.</param>
/// <param name="flags">Flags to remove from the player.</param>
public static void RemovePlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
RemovePlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
/// Temporarily removes a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">Steam ID to remove flags from.</param>
/// <param name="flags">Flags to remove from the player.</param>
public static void RemovePlayerPermissions(SteamID steamId, params string[] flags)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Flags.ExceptWith(flags);
Admins[steamId] = data;
}
/// <summary>
/// Temporarily removes all permission flags from a player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to remove flags from.</param>
public static void ClearPlayerPermissions(CCSPlayerController? player)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
ClearPlayerPermissions((SteamID)player.SteamID);
}
/// <summary>
/// Temporarily removes all permission flags from a player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">Steam ID to remove flags from.</param>
public static void ClearPlayerPermissions(SteamID steamId)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Flags.Clear();
Admins[steamId] = data;
}
#endregion
#region Player Immunity
/// <summary>
/// Sets the immunity value for a player.
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="value">New immunity value.</param>
public static void SetPlayerImmunity(CCSPlayerController? player, uint value)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
SetPlayerImmunity((SteamID)player.SteamID, value);
}
/// <summary>
/// Sets the immunity value for a player.
/// </summary>
/// <param name="steamId">Steam ID of the player.</param>
/// <param name="value">New immunity value.</param>
public static void SetPlayerImmunity(SteamID steamId, uint value)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Immunity = value;
Admins[steamId] = data;
}
/// <summary>
/// Checks to see if a player can target another player based on their immunity value.
/// </summary>
/// <param name="caller">Caller of the command.</param>
/// <param name="target">Target of the command.</param>
/// <returns></returns>
public static bool CanPlayerTarget(CCSPlayerController? caller, CCSPlayerController? target)
{
// The server console should be able to target everyone.
if (caller == null) return true;
if (target == null) return false;
if (!target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected) return false;
var callerData = GetPlayerAdminData((SteamID)caller.SteamID);
if (callerData == null) return false;
var targetData = GetPlayerAdminData((SteamID)target.SteamID);
if (targetData == null) return true;
return callerData.Immunity >= targetData.Immunity;
}
/// <summary>
/// Checks to see if a player can target another player based on their immunity value.
/// </summary>
/// <param name="caller">Caller of the command.</param>
/// <param name="target">Target of the command.</param>
/// <returns></returns>
public static bool CanPlayerTarget(SteamID caller, SteamID target)
{
var callerData = GetPlayerAdminData(caller);
if (callerData == null) return false;
var targetData = GetPlayerAdminData(caller);
if (targetData == null) return true;
return callerData.Immunity >= targetData.Immunity;
}
#endregion
}
}

View File

@@ -0,0 +1,44 @@
using CounterStrikeSharp.API.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Admin
{
public class BaseRequiresPermissions : Attribute
{
/// <summary>
/// The permissions for the command.
/// </summary>
public string[] Permissions { get; }
/// <summary>
/// The name of the command that is attached to this attribute.
/// </summary>
public string Command { get; set; }
/// <summary>
/// Whether this attribute should be used for permission checks.
/// </summary>
public bool Enabled { get; set; }
public BaseRequiresPermissions(params string[] permissions)
{
Permissions = permissions;
Command = "";
}
public virtual bool CanExecuteCommand(CCSPlayerController? caller)
{
// If we have a command in the "command_overrides" section in "configs/admins.json",
// we skip the checks below and just return the defined value.
var adminData = AdminManager.GetPlayerAdminData((SteamID)caller.SteamID);
if (adminData == null) return false;
if (adminData.CommandOverrides.ContainsKey(Command))
{
return adminData.CommandOverrides[Command];
}
return true;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CounterStrikeSharp.API.Modules.Admin
{
public class PermissionCharacters
{
// Example: "#css/admin"
public const char GroupPermissionChar = '#';
// Example: "@css/manipulate_players"
public const char UserPermissionChar = '@';
}
}

View File

@@ -1,15 +1,33 @@
using System;
using CounterStrikeSharp.API.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Admin
{
[AttributeUsage(AttributeTargets.Method)]
public class RequiresPermissions : Attribute
{
public string[] RequiredPermissions { get; }
public RequiresPermissions(params string[] permissions)
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequiresPermissions : BaseRequiresPermissions
{
public RequiresPermissions(params string[] permissions) : base(permissions) { }
public override bool CanExecuteCommand(CCSPlayerController? caller)
{
RequiredPermissions = permissions;
if (caller == null) return true;
if (AdminManager.PlayerHasCommandOverride(caller, Command))
{
return AdminManager.GetPlayerCommandOverrideState(caller, Command);
}
if (!base.CanExecuteCommand(caller)) return false;
var groupPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.GroupPermissionChar));
var userPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.UserPermissionChar));
if (!AdminManager.PlayerInGroup(caller, groupPermissions.ToArray())) return false;
if (!AdminManager.PlayerHasPermissions(caller, userPermissions.ToArray())) return false;
return true;
}
}
}

View File

@@ -0,0 +1,31 @@
using CounterStrikeSharp.API.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Admin
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequiresPermissionsOr : BaseRequiresPermissions
{
public RequiresPermissionsOr(params string[] permissions) : base(permissions) { }
public override bool CanExecuteCommand(CCSPlayerController? caller)
{
if (caller == null) return true;
if (AdminManager.PlayerHasCommandOverride(caller, Command))
{
return AdminManager.GetPlayerCommandOverrideState(caller, Command);
}
if (!base.CanExecuteCommand(caller)) return false;
var groupPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.GroupPermissionChar));
var userPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.UserPermissionChar));
var adminData = AdminManager.GetPlayerAdminData((SteamID)caller.SteamID);
if (adminData == null) return false;
return (groupPermissions.Intersect(adminData.Groups).Count() + userPermissions.Intersect(adminData.Flags).Count()) > 0;
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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 <https://www.gnu.org/licenses/>. *
*/
namespace CounterStrikeSharp.API.Modules.Entities.Constants
{
public enum RoundEndReason : uint
{
Unknown = 0x0u,
TargetBombed = 0x1u,
TerroristsEscaped = 0x4u,
CTsPreventEscape = 0x5u,
EscapingTerroristsNeutralized = 0x6u,
BombDefused = 0x7u,
CTsWin = 0x8u,
TerroristsWin = 0x9u,
RoundDraw = 0xAu,
AllHostageRescued = 0xBu,
TargetSaved = 0xCu,
HostagesNotRescued = 0xDu,
TerroristsNotEscaped = 0xEu,
GameCommencing = 0x10u,
TerroristsSurrender = 0x11u, // this also triggers match cancelled
CTsSurrender = 0x12u, // this also triggers match cancelled
TerroristsPlanned = 0x13u,
CTsReachedHostage = 0x14u,
SurvivalWin = 0x15u,
SurvivalDraw = 0x16u
}
}

View File

@@ -123,6 +123,7 @@ namespace CounterStrikeSharp.API.Modules.Events
protected void SetEntityIndex(string name, int value) => NativeAPI.SetEventEntityIndex(Handle, name, value);
public void FireEvent(bool dontBroadcast) => NativeAPI.FireEvent(Handle, dontBroadcast);
// public void FireEventToClient(int clientId, bool dontBroadcast) => NativeAPI.FireEventToClient(Handle, clientId);
public void FireEventToClient(CCSPlayerController player) => NativeAPI.FireEventToClient(Handle, (int)player.EntityIndex.Value.Value);
}
}

View File

@@ -31,9 +31,15 @@ public partial class VirtualFunction
{
if (!_createdFunctions.TryGetValue(signature, out var function))
{
function = NativeAPI.CreateVirtualFunctionBySignature(IntPtr.Zero, Addresses.ServerPath, signature,
argumentTypes.Count(), (int)returnType, arguments);
_createdFunctions[signature] = function;
try
{
function = NativeAPI.CreateVirtualFunctionBySignature(IntPtr.Zero, Addresses.ServerPath, signature,
argumentTypes.Count(), (int)returnType, arguments);
_createdFunctions[signature] = function;
}
catch (Exception)
{
}
}
return function;

View File

@@ -1,5 +1,6 @@
using System;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Modules.Memory;
@@ -27,4 +28,10 @@ public static class VirtualFunctions
// void(*CBaseModelEntity_SetModel)(CBaseModelEntity*, const char*);
public static Action<IntPtr, string> SetModel = VirtualFunction.CreateVoid<IntPtr, string>(GameData.GetSignature("CBaseModelEntity_SetModel"));
public static Action<IntPtr, RoundEndReason, float> TerminateRound = VirtualFunction.CreateVoid<nint, RoundEndReason, float>(GameData.GetSignature("CCSGameRules_TerminateRound"));
public static Func<string, int, IntPtr> UTIL_CreateEntityByName = VirtualFunction.Create<string, int, IntPtr>(GameData.GetSignature("UTIL_CreateEntityByName"));
public static Action<IntPtr, IntPtr> CBaseEntity_DispatchSpawn = VirtualFunction.CreateVoid<IntPtr, IntPtr>(GameData.GetSignature("CBaseEntity_DispatchSpawn"));
}

View File

@@ -16,6 +16,42 @@ public class CommandUtils
var command = new CommandInfo(ptr, caller);
var methodInfo = handler?.GetMethodInfo();
if (!AdminManager.CommandIsOverriden(name))
{
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null)
{
foreach (var attr in permissions)
{
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
}
// If this command has it's permissions overriden, we will do an AND check for all permissions.
else
{
// I don't know if this is the most sane implementation of this, can be edited in code review.
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));
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
// Do not execute if we shouldn't be calling this command.
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
if (helperAttribute != null)
@@ -41,14 +77,6 @@ public class CommandUtils
}
}
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttribute<RequiresPermissions>()?.RequiredPermissions;
if (permissions != null && !AdminManager.PlayerHasPermissions(caller, permissions))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
handler?.Invoke(caller, command);
});

View File

@@ -15,6 +15,7 @@
*/
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using System;
using System.Collections.Generic;
@@ -43,6 +44,11 @@ namespace CounterStrikeSharp.API
return (T)Activator.CreateInstance(typeof(T), NativeAPI.GetEntityFromIndex(index))!;
}
public static T? CreateEntityByName<T>(string name) where T : CBaseEntity
{
return (T?)Activator.CreateInstance(typeof(T), VirtualFunctions.UTIL_CreateEntityByName(name, -1));
}
public static CCSPlayerController GetPlayerFromIndex(int index)
{
return Utilities.GetEntityFromIndex<CCSPlayerController>(index);

View File

@@ -4855,6 +4855,14 @@
},
"CBaseCSGrenadeProjectile": {
"fields": [
{
"name": "m_vInitialPosition",
"type": {
"atomic": 0,
"category": 4,
"name": "Vector"
}
},
{
"name": "m_vInitialVelocity",
"type": {
@@ -4970,6 +4978,20 @@
"category": 0,
"name": "int32"
}
},
{
"name": "m_bHasEverHitPlayer",
"type": {
"category": 0,
"name": "bool"
}
},
{
"name": "m_bClearFromPlayers",
"type": {
"category": 0,
"name": "bool"
}
}
],
"parent": "CBaseGrenade"
@@ -13245,10 +13267,10 @@
}
},
{
"name": "m_bMatchAbortedDueToPlayerBan",
"name": "m_nMatchAbortedEarlyReason",
"type": {
"category": 0,
"name": "bool"
"name": "int32"
}
},
{
@@ -13828,6 +13850,13 @@
"name": "uint32"
}
},
{
"name": "m_bCannotBeKicked",
"type": {
"category": 0,
"name": "bool"
}
},
{
"name": "m_bEverFullyConnected",
"type": {
@@ -14125,12 +14154,26 @@
"name": "bool"
}
},
{
"name": "m_dblLastReceivedPacketPlatFloatTime",
"type": {
"category": 0,
"name": "float64"
}
},
{
"name": "m_LastTeamDamageWarningTime",
"type": {
"category": 5,
"name": "GameTime_t"
}
},
{
"name": "m_LastTimePlayerWasDisconnectedForPawnsRemove",
"type": {
"category": 5,
"name": "GameTime_t"
}
}
],
"parent": "CBasePlayerController"
@@ -14439,6 +14482,13 @@
"name": "char[18]"
}
},
{
"name": "m_bInHostageResetZone",
"type": {
"category": 0,
"name": "bool"
}
},
{
"name": "m_bInBuyZone",
"type": {
@@ -16244,6 +16294,20 @@
"category": 0,
"name": "float32"
}
},
{
"name": "m_flHeightAtJumpStart",
"type": {
"category": 0,
"name": "float32"
}
},
{
"name": "m_flMaxJumpHeightThisJump",
"type": {
"category": 0,
"name": "float32"
}
}
],
"parent": "CPlayer_MovementServices_Humanoid"
@@ -30081,6 +30145,14 @@
"category": 4,
"name": "Vector"
}
},
{
"name": "m_vecHostageResetPosition",
"type": {
"atomic": 0,
"category": 4,
"name": "Vector"
}
}
],
"parent": "CHostageExpresserShim"
@@ -32719,6 +32791,40 @@
],
"parent": "CLogicalEntity"
},
"CLogicEventListener": {
"fields": [
{
"name": "m_strEventName",
"type": {
"atomic": 0,
"category": 4,
"name": "CUtlString"
}
},
{
"name": "m_bIsEnabled",
"type": {
"category": 0,
"name": "bool"
}
},
{
"name": "m_nTeam",
"type": {
"category": 0,
"name": "int32"
}
},
{
"name": "m_OnEventFired",
"type": {
"category": 5,
"name": "CEntityIOOutput"
}
}
],
"parent": "CLogicalEntity"
},
"CLogicGameEvent": {
"fields": [
{
@@ -54460,6 +54566,10 @@
"fields": [],
"parent": "CBaseTrigger"
},
"CTriggerHostageReset": {
"fields": [],
"parent": "CBaseTrigger"
},
"CTriggerHurt": {
"fields": [
{
@@ -90276,6 +90386,23 @@
}
]
},
"C4LightEffect_t": {
"align": 4,
"items": [
{
"name": "eLightEffectNone",
"value": 0
},
{
"name": "eLightEffectDropped",
"value": 1
},
{
"name": "eLightEffectThirdPersonHeld",
"value": 2
}
]
},
"CAnimationGraphVisualizerPrimitiveType": {
"align": 4,
"items": [
@@ -96794,6 +96921,10 @@
{
"name": "DFLAG_IGNORE_ARMOR",
"value": 2048
},
{
"name": "DFLAG_SUPPRESS_UTILREMOVE",
"value": 4096
}
]
},

View File

@@ -354,7 +354,7 @@ namespace TestPlugin
[ConsoleCommand("cssharp_attribute", "This is a custom attribute event")]
public void OnCommand(CCSPlayerController? player, CommandInfo command)
{
Log("cssharp_attribute called!");
command.ReplyToCommand("cssharp_attribute called", true);
}
[ConsoleCommand("css_changelevel", "Changes map")]

79
src/core/coreconfig.cpp Normal file
View File

@@ -0,0 +1,79 @@
/*
* 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 <https://www.gnu.org/licenses/>. *
*/
#include <fstream>
#include "core/log.h"
#include "core/coreconfig.h"
namespace counterstrikesharp {
CCoreConfig::CCoreConfig(const std::string& path) { m_sPath = path; }
CCoreConfig::~CCoreConfig() = default;
bool CCoreConfig::Init(char* conf_error, int conf_error_size)
{
std::ifstream ifs(m_sPath);
if (!ifs) {
V_snprintf(conf_error, conf_error_size, "CoreConfig file not found.");
return false;
}
m_json = json::parse(ifs);
try {
PublicChatTrigger = m_json["PublicChatTrigger"];
SilentChatTrigger = m_json["SilentChatTrigger"];
FollowCS2ServerGuidelines = m_json["FollowCS2ServerGuidelines"];
} catch (const std::exception& ex) {
V_snprintf(conf_error, conf_error_size, "Failed to parse CoreConfig file: %s", ex.what());
return false;
}
return true;
}
const std::string CCoreConfig::GetPath() const
{
return m_sPath;
}
bool CCoreConfig::IsTriggerInternal(std::vector<std::string> triggers, const std::string& message, std::string& prefix) const
{
for (std::string& trigger : triggers)
{
if (message.rfind(trigger, 0) == 0)
{
prefix = trigger;
CSSHARP_CORE_TRACE("Trigger found, prefix is {}", prefix);
return true;
}
}
return false;
}
bool CCoreConfig::IsSilentChatTrigger(const std::string& message, std::string& prefix) const
{
return IsTriggerInternal(SilentChatTrigger, message, prefix);
}
bool CCoreConfig::IsPublicChatTrigger(const std::string& message, std::string& prefix) const
{
return IsTriggerInternal(PublicChatTrigger, message, prefix);
}
} // namespace counterstrikesharp

50
src/core/coreconfig.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* 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 <https://www.gnu.org/licenses/>. *
*/
#pragma once
#include "core/globals.h"
#include <string>
#include <nlohmann/json.hpp>
namespace counterstrikesharp {
class CCoreConfig
{
public:
std::vector<std::string> PublicChatTrigger = { std::string("!") };
std::vector<std::string> SilentChatTrigger = { std::string("/") };
bool FollowCS2ServerGuidelines = true;
using json = nlohmann::json;
CCoreConfig(const std::string& path);
~CCoreConfig();
bool Init(char* conf_error, int conf_error_size);
const std::string GetPath() const;
bool IsSilentChatTrigger(const std::string& message, std::string& prefix) const;
bool IsPublicChatTrigger(const std::string& message, std::string& prefix) const;
private:
bool IsTriggerInternal(std::vector<std::string> triggers, const std::string& message, std::string& prefix) const;
private:
std::string m_sPath;
json m_json;
};
} // namespace counterstrikesharp

View File

@@ -23,6 +23,8 @@
#include "const.h"
#include "utils/virtual.h"
#include <string>
#include <vector>
#include <stdint.h>
#include <type_traits>
@@ -54,6 +56,45 @@ inline uint64_t hash_64_fnv1a_const(const char *str, const uint64_t value = val_
}
namespace schema {
static std::vector<std::string> CS2BadList = {
"m_bIsValveDS",
"m_bIsQuestEligible",
"m_iItemDefinitionIndex", // in unmanaged this cannot be set.
"m_iEntityLevel",
"m_iItemIDHigh",
"m_iItemIDLow",
"m_iAccountID",
"m_iEntityQuality",
"m_bInitialized",
"m_szCustomName",
"m_iAttributeDefinitionIndex",
"m_iRawValue32",
"m_iRawInitialValue32",
"m_flValue", // MNetworkAlias "m_iRawValue32"
"m_flInitialValue", // MNetworkAlias "m_iRawInitialValue32"
"m_bSetBonus",
"m_nRefundableCurrency",
"m_OriginalOwnerXuidLow",
"m_OriginalOwnerXuidHigh",
"m_nFallbackPaintKit",
"m_nFallbackSeed",
"m_flFallbackWear",
"m_nFallbackStatTrak",
"m_iCompetitiveWins",
"m_iCompetitiveRanking",
"m_iCompetitiveRankType",
"m_iCompetitiveRankingPredicted_Win",
"m_iCompetitiveRankingPredicted_Loss",
"m_iCompetitiveRankingPredicted_Tie",
"m_nActiveCoinRank",
"m_nMusicID",
};
int16_t FindChainOffset(const char *className);
SchemaKey GetOffset(const char *className, uint32_t classKey, const char *memberName, uint32_t memberKey);
} // namespace schema

View File

@@ -19,53 +19,52 @@
#include "memory_module.h"
#include "interfaces/cs2_interfaces.h"
#include "core/managers/entity_manager.h"
#include "core/managers/client_command_manager.h"
#include "core/managers/server_manager.h"
#include <public/game/server/iplayerinfo.h>
#include <public/entity2/entitysystem.h>
namespace counterstrikesharp {
namespace modules {
CModule *engine = nullptr;
CModule *tier0 = nullptr;
CModule *server = nullptr;
CModule *schemasystem = nullptr;
CModule *vscript = nullptr;
} // namespace modules
CModule* engine = nullptr;
CModule* tier0 = nullptr;
CModule* server = nullptr;
CModule* schemasystem = nullptr;
CModule* vscript = nullptr;
} // namespace modules
namespace globals {
IVEngineServer *engine = nullptr;
IGameEventManager2 *gameEventManager = nullptr;
IGameEventSystem *gameEventSystem = nullptr;
IPlayerInfoManager *playerinfoManager = nullptr;
IBotManager *botManager = nullptr;
IServerPluginHelpers *helpers = nullptr;
IUniformRandomStream *randomStream = nullptr;
IEngineTrace *engineTrace = nullptr;
IEngineSound *engineSound = nullptr;
INetworkStringTableContainer *netStringTables = nullptr;
CGlobalVars *globalVars = nullptr;
IFileSystem *fileSystem = nullptr;
IServerGameDLL *serverGameDll = nullptr;
IServerGameClients *serverGameClients = nullptr;
INetworkServerService *networkServerService = nullptr;
IServerTools *serverTools = nullptr;
IPhysics *physics = nullptr;
IPhysicsCollision *physicsCollision = nullptr;
IPhysicsSurfaceProps *physicsSurfaceProps = nullptr;
IMDLCache *modelCache = nullptr;
IVoiceServer *voiceServer = nullptr;
IVEngineServer* engine = nullptr;
IGameEventManager2* gameEventManager = nullptr;
IGameEventSystem* gameEventSystem = nullptr;
IPlayerInfoManager* playerinfoManager = nullptr;
IBotManager* botManager = nullptr;
IServerPluginHelpers* helpers = nullptr;
IUniformRandomStream* randomStream = nullptr;
IEngineTrace* engineTrace = nullptr;
IEngineSound* engineSound = nullptr;
INetworkStringTableContainer* netStringTables = nullptr;
CGlobalVars* globalVars = nullptr;
IFileSystem* fileSystem = nullptr;
IServerGameDLL* serverGameDll = nullptr;
IServerGameClients* serverGameClients = nullptr;
INetworkServerService* networkServerService = nullptr;
IServerTools* serverTools = nullptr;
IPhysics* physics = nullptr;
IPhysicsCollision* physicsCollision = nullptr;
IPhysicsSurfaceProps* physicsSurfaceProps = nullptr;
IMDLCache* modelCache = nullptr;
IVoiceServer* voiceServer = nullptr;
CDotNetManager dotnetManager;
ICvar *cvars = nullptr;
ISource2Server *server = nullptr;
CGlobalEntityList *globalEntityList = nullptr;
CounterStrikeSharpMMPlugin *mmPlugin = nullptr;
ICvar* cvars = nullptr;
ISource2Server* server = nullptr;
CGlobalEntityList* globalEntityList = nullptr;
CounterStrikeSharpMMPlugin* mmPlugin = nullptr;
SourceHook::Impl::CSourceHookImpl source_hook_impl;
SourceHook::ISourceHook *source_hook = &source_hook_impl;
ISmmAPI *ismm = nullptr;
SourceHook::ISourceHook* source_hook = &source_hook_impl;
ISmmAPI* ismm = nullptr;
CGameEntitySystem* entitySystem = nullptr;
CCoreConfig* coreConfig = nullptr;
CGameConfig* gameConfig = nullptr;
// Custom Managers
@@ -76,10 +75,12 @@ TimerSystem timerSystem;
ConCommandManager conCommandManager;
EntityManager entityManager;
ChatManager chatManager;
ClientCommandManager clientCommandManager;
ServerManager serverManager;
void Initialize() {
GetLegacyGameEventListener_t* GetLegacyGameEventListener = nullptr;
void Initialize()
{
modules::engine = new modules::CModule(ROOTBIN, "engine2");
modules::tier0 = new modules::CModule(ROOTBIN, "tier0");
modules::server = new modules::CModule(GAMEBIN, "server");
@@ -90,14 +91,18 @@ void Initialize() {
entitySystem = interfaces::pGameResourceServiceServer->GetGameEntitySystem();
GetLegacyGameEventListener = reinterpret_cast<GetLegacyGameEventListener_t*>(modules::server->FindSignature(
globals::gameConfig->GetSignature("LegacyGameEventListener")));
if (int offset = -1; (offset = gameConfig->GetOffset("GameEventManager")) != -1) {
gameEventManager = (IGameEventManager2*)(CALL_VIRTUAL(uintptr_t, offset, server) - 8);
}
}
int source_hook_pluginid = 0;
CGlobalVars *getGlobalVars() {
INetworkGameServer *server = networkServerService->GetIGameServer();
CGlobalVars* getGlobalVars()
{
INetworkGameServer* server = networkServerService->GetIGameServer();
if (!server) {
return nullptr;
@@ -106,5 +111,5 @@ CGlobalVars *getGlobalVars() {
return networkServerService->GetIGameServer()->GetGlobals();
}
} // namespace globals
} // namespace counterstrikesharp
} // namespace globals
} // namespace counterstrikesharp

View File

@@ -33,6 +33,7 @@ class ICvar;
class IGameEventSystem;
class CounterStrikeSharpMMPlugin;
class CGameEntitySystem;
class IGameEventListener2;
namespace counterstrikesharp {
class EntityListener;
@@ -48,8 +49,8 @@ class ChatCommands;
class HookManager;
class EntityManager;
class ChatManager;
class ClientCommandManager;
class ServerManager;
class CCoreConfig;
class CGameConfig;
namespace globals {
@@ -92,7 +93,6 @@ extern EntityManager entityManager;
extern TimerSystem timerSystem;
extern ChatCommands chatCommands;
extern ChatManager chatManager;
extern ClientCommandManager clientCommandManager;
extern ServerManager serverManager;
extern HookManager hookManager;
@@ -101,8 +101,13 @@ extern int source_hook_pluginid;
extern IGameEventSystem *gameEventSystem;
extern CounterStrikeSharpMMPlugin *mmPlugin;
extern ISmmAPI *ismm;
extern CCoreConfig* coreConfig;
extern CGameConfig* gameConfig;
typedef IGameEventListener2 *GetLegacyGameEventListener_t(CPlayerSlot slot);
extern GetLegacyGameEventListener_t* GetLegacyGameEventListener;
void Initialize();
// Should only be called within the active game loop (i e map should be loaded
// and active) otherwise that'll be nullptr!

View File

@@ -24,6 +24,7 @@
#include <public/eiface.h>
#include "core/memory.h"
#include "core/log.h"
#include "core/coreconfig.h"
#include "core/gameconfig.h"
#include <funchook.h>
@@ -39,8 +40,7 @@ ChatManager::~ChatManager() {}
void ChatManager::OnAllInitialized()
{
m_pHostSay = reinterpret_cast<HostSay>(
modules::server->FindSignature(globals::gameConfig->GetSignature("Host_Say"))
);
modules::server->FindSignature(globals::gameConfig->GetSignature("Host_Say")));
if (m_pHostSay == nullptr) {
CSSHARP_CORE_ERROR("Failed to find signature for \'Host_Say\'");
@@ -57,71 +57,72 @@ void ChatManager::OnShutdown() {}
void DetourHostSay(CBaseEntity* pController, CCommand& args, bool teamonly, int unk1,
const char* unk2)
{
CCommand newArgs;
newArgs.Tokenize(args.Arg(1));
if (*args[1] == '/' || *args[1] == '!') {
globals::chatManager.OnSayCommandPost(pController, newArgs);
return;
}
m_pHostSay(pController, args, teamonly, unk1, unk2);
if (pController) {
auto pEvent = globals::gameEventManager->CreateEvent("player_chat", true);
if (pEvent) {
pEvent->SetBool("teamonly", teamonly);
pEvent->SetInt("userid", pController->GetEntityIndex().Get());
pEvent->SetInt("userid", pController->GetEntityIndex().Get() - 1);
pEvent->SetString("text", args[1]);
globals::gameEventManager->FireEvent(pEvent, true);
}
}
std::string prefix;
bool bSilent = globals::coreConfig->IsSilentChatTrigger(args[1], prefix);
bool bCommand = globals::coreConfig->IsPublicChatTrigger(args[1], prefix) || bSilent;
if (!bSilent) {
m_pHostSay(pController, args, teamonly, unk1, unk2);
}
if (bCommand)
{
char *pszMessage = (char *)(args.ArgS() + prefix.length() + 1);
// Trailing slashes are only removed if Host_Say has been called.
if (bSilent)
pszMessage[V_strlen(pszMessage) - 1] = 0;
CCommand args;
args.Tokenize(pszMessage);
auto prefixedPhrase = std::string("css_") + args.Arg(0);
auto bValidWithPrefix = globals::conCommandManager.IsValidValveCommand(prefixedPhrase.c_str());
if (bValidWithPrefix) {
// Re-tokenize with a `css_` prefix if we have found that its a valid command.
args.Tokenize(("css_" + std::string(pszMessage)).c_str());
}
globals::chatManager.OnSayCommandPost(pController, args);
}
}
bool ChatManager::OnSayCommandPre(CBaseEntity* pController, CCommand& command) {
return false;
}
bool ChatManager::OnSayCommandPre(CBaseEntity* pController, CCommand& command) { return false; }
bool ChatManager::OnSayCommandPost(CBaseEntity* pController, CCommand& command)
void ChatManager::OnSayCommandPost(CBaseEntity* pController, CCommand& command)
{
const char* args = command.ArgS();
auto commandStr = command.Arg(0);
return InternalDispatch(pController, commandStr + 1, command);
return InternalDispatch(pController, commandStr, command);
}
bool ChatManager::InternalDispatch(CBaseEntity* pPlayerController, const char* szTriggerPhase,
void ChatManager::InternalDispatch(CBaseEntity* pPlayerController, const char* szTriggerPhase,
CCommand& fullCommand)
{
auto ppArgV = new const char*[fullCommand.ArgC()];
ppArgV[0] = strdup(szTriggerPhase);
for (int i = 1; i < fullCommand.ArgC(); i++) {
ppArgV[i] = fullCommand.Arg(i);
}
auto prefixedPhrase = std::string("css_") + szTriggerPhase;
auto command = globals::conCommandManager.FindCommand(prefixedPhrase.c_str());
if (command) {
ppArgV[0] = prefixedPhrase.c_str();
}
CCommand commandCopy(fullCommand.ArgC(), ppArgV);
if (pPlayerController == nullptr) {
auto result = globals::conCommandManager.InternalDispatch(CPlayerSlot(-1), &commandCopy);
delete[] ppArgV;
return result;
globals::conCommandManager.ExecuteCommandCallbacks(
fullCommand.Arg(0), CCommandContext(CommandTarget_t::CT_NO_TARGET, CPlayerSlot(-1)),
fullCommand, HookMode::Pre);
return;
}
auto index = pPlayerController->GetEntityIndex().Get();
auto slot = CPlayerSlot(index - 1);
auto result = globals::conCommandManager.InternalDispatch(slot, &commandCopy);
delete[] ppArgV;
return result;
globals::conCommandManager.ExecuteCommandCallbacks(
fullCommand.Arg(0), CCommandContext(CommandTarget_t::CT_NO_TARGET, slot), fullCommand,
HookMode::Pre);
}
} // namespace counterstrikesharp

View File

@@ -53,10 +53,9 @@ class ChatManager : public GlobalClass
void OnShutdown() override;
bool OnSayCommandPre(CBaseEntity* pController, CCommand& args);
bool OnSayCommandPost(CBaseEntity* pController, CCommand& args);
void OnSayCommandPost(CBaseEntity* pController, CCommand& args);
private:
bool InternalDispatch(CBaseEntity* pPlayerController, const char* szTriggerPhrase,
void InternalDispatch(CBaseEntity* pPlayerController, const char* szTriggerPhrase,
CCommand& pFullCommand);
std::vector<ChatCommandInfo*> m_cmd_list;

View File

@@ -1,147 +0,0 @@
#include "core/managers/client_command_manager.h"
#include <public/eiface.h>
#include <algorithm>
#include "scripting/callback_manager.h"
#include "core/log.h"
namespace counterstrikesharp {
ClientCommandManager::ClientCommandManager() {}
ClientCommandManager::~ClientCommandManager() {}
void ClientCommandManager::OnAllInitialized()
{
m_global_cmd.callback_pre = globals::callbackManager.CreateCallback("OnClientCommandGlobalPre");
m_global_cmd.callback_post =
globals::callbackManager.CreateCallback("OnClientCommandGlobalPost");
}
void ClientCommandManager::OnShutdown() {}
bool ClientCommandManager::DispatchClientCommand(CPlayerSlot slot, const char* cmd,
const CCommand* args)
{
CSSHARP_CORE_TRACE("Dispatch client command {}", cmd);
auto* p_info = m_cmd_lookup[cmd];
bool result = false;
if (m_global_cmd.callback_pre->GetFunctionCount() > 0) {
m_global_cmd.callback_pre->ScriptContext().Reset();
m_global_cmd.callback_pre->ScriptContext().Push(slot.Get());
m_global_cmd.callback_pre->ScriptContext().Push(args);
for (auto fnMethodToCall : m_global_cmd.callback_pre->GetFunctions()) {
if (!fnMethodToCall)
continue;
fnMethodToCall(&m_global_cmd.callback_pre->ScriptContextStruct());
auto hookResult = m_global_cmd.callback_pre->ScriptContext().GetResult<HookResult>();
CSSHARP_CORE_TRACE("Received hook result from command callback {}:{}", cmd, hookResult);
if (hookResult >= HookResult::Stop) {
return true;
} else if (hookResult >= HookResult::Handled) {
result = true;
}
}
}
if (p_info && p_info->callback_pre) {
p_info->callback_pre->ScriptContext().Reset();
p_info->callback_pre->ScriptContext().Push(slot.Get());
p_info->callback_pre->ScriptContext().Push(args);
for (auto fnMethodToCall : p_info->callback_pre->GetFunctions()) {
if (!fnMethodToCall)
continue;
fnMethodToCall(&p_info->callback_pre->ScriptContextStruct());
auto hookResult = p_info->callback_pre->ScriptContext().GetResult<HookResult>();
CSSHARP_CORE_TRACE("Received hook result from command callback {}:{}", cmd, hookResult);
if (hookResult >= HookResult::Stop) {
return true;
} else if (hookResult >= HookResult::Handled) {
result = true;
}
}
}
if (m_global_cmd.callback_post->GetFunctionCount() > 0) {
m_global_cmd.callback_post->ScriptContext().Reset();
m_global_cmd.callback_post->ScriptContext().Push(slot.Get());
m_global_cmd.callback_post->ScriptContext().Push(args);
m_global_cmd.callback_post->Execute();
}
if (result && p_info && p_info->callback_post) {
p_info->callback_post->ScriptContext().Reset();
p_info->callback_post->ScriptContext().Push(slot.Get());
p_info->callback_post->ScriptContext().Push(args);
p_info->callback_post->Execute();
}
return result;
}
void ClientCommandManager::AddCommandListener(const char* cmd, CallbackT callback, bool bPost)
{
// Handle global command listeners that listen for every ClientCommand.
if (cmd == nullptr) {
if (bPost) {
m_global_cmd.callback_post->AddListener(callback);
return;
}
m_global_cmd.callback_pre->AddListener(callback);
return;
}
auto* p_info = m_cmd_lookup[std::string(cmd)];
if (!p_info) {
p_info = new ClientCommandInfo();
p_info->command = cmd;
p_info->callback_pre = globals::callbackManager.CreateCallback(cmd);
p_info->callback_post = globals::callbackManager.CreateCallback(cmd);
m_cmd_list.push_back(p_info);
m_cmd_lookup[cmd] = p_info;
}
if (bPost) {
p_info->callback_post->AddListener(callback);
} else {
p_info->callback_pre->AddListener(callback);
}
}
void ClientCommandManager::RemoveCommandListener(const char* cmd, CallbackT callback, bool bPost)
{
if (cmd == nullptr) {
if (bPost) {
m_global_cmd.callback_post->RemoveListener(callback);
return;
}
m_global_cmd.callback_pre->RemoveListener(callback);
return;
}
auto* p_info = m_cmd_lookup[std::string(cmd)];
if (!p_info) {
return;
}
if (bPost) {
p_info->callback_post->RemoveListener(callback);
} else {
p_info->callback_pre->RemoveListener(callback);
}
}
} // namespace counterstrikesharp

View File

@@ -1,45 +0,0 @@
#pragma once
#include <map>
#include <vector>
#include "core/globals.h"
#include "core/global_listener.h"
#include "scripting/script_engine.h"
#include <string>
#include "playerslot.h"
namespace counterstrikesharp {
class ScriptCallback;
class ClientCommandInfo {
friend class ClientCommandManager;
public:
ClientCommandInfo() {}
private:
std::string command;
ScriptCallback* callback_pre;
ScriptCallback* callback_post;
};
class ClientCommandManager : public GlobalClass {
public:
ClientCommandManager();
~ClientCommandManager();
void OnAllInitialized() override;
void OnShutdown() override;
bool DispatchClientCommand(CPlayerSlot slot, const char* cmd, const CCommand* args);
void AddCommandListener(const char* cmd, CallbackT callback, bool bPost);
void RemoveCommandListener(const char* cmd, CallbackT callback, bool bPost);
private:
std::vector<ClientCommandInfo*> m_cmd_list;
std::map<std::string, ClientCommandInfo*> m_cmd_lookup;
ClientCommandInfo m_global_cmd;
};
} // namespace counterstrikesharp

View File

@@ -157,307 +157,248 @@ CON_COMMAND(dump_schema, "dump schema symbols")
output << std::setw(2) << j << std::endl;
}
SH_DECL_HOOK2_void(ConCommandHandle, Dispatch, SH_NOATTRIB, false, const CCommandContext&,
const CCommand&);
SH_DECL_HOOK3_void(ICvar, DispatchConCommand, SH_NOATTRIB, 0, ConCommandHandle,
const CCommandContext&, const CCommand&);
void ConCommandInfo::HookChange(CallbackT cb, bool post)
ConCommandInfo::ConCommandInfo()
{
if (post) {
this->callback_post->AddListener(cb);
} else {
this->callback_pre->AddListener(cb);
}
callback_pre = globals::callbackManager.CreateCallback("");
callback_post = globals::callbackManager.CreateCallback("");
}
ConCommandInfo::~ConCommandInfo()
{
globals::callbackManager.ReleaseCallback(callback_pre);
globals::callbackManager.ReleaseCallback(callback_post);
}
ConCommandInfo::ConCommandInfo(bool bNoCallbacks) {
}
void ConCommandInfo::UnhookChange(CallbackT cb, bool post)
{
if (post) {
if (this->callback_post && this->callback_post->GetFunctionCount()) {
callback_post->RemoveListener(cb);
}
} else {
if (this->callback_pre && this->callback_pre->GetFunctionCount()) {
callback_pre->RemoveListener(cb);
}
}
}
ConCommandManager::ConCommandManager() : last_command_client(-1) {}
ConCommandManager::ConCommandManager() {}
ConCommandManager::~ConCommandManager() {}
void ConCommandManager::OnAllInitialized() {}
void ConCommandManager::OnAllInitialized()
{
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
&ConCommandManager::Hook_DispatchConCommand, false);
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
&ConCommandManager::Hook_DispatchConCommand_Post, true);
void ConCommandManager::OnShutdown() {}
m_global_cmd.callback_pre = globals::callbackManager.CreateCallback("OnClientCommandGlobalPre");
m_global_cmd.callback_post =
globals::callbackManager.CreateCallback("OnClientCommandGlobalPost");
}
void ConCommandManager::OnShutdown()
{
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
&ConCommandManager::Hook_DispatchConCommand, false);
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
&ConCommandManager::Hook_DispatchConCommand_Post, true);
globals::callbackManager.ReleaseCallback(m_global_cmd.callback_pre);
globals::callbackManager.ReleaseCallback(m_global_cmd.callback_post);
}
void CommandCallback(const CCommandContext& context, const CCommand& command)
{
bool rval = globals::conCommandManager.InternalDispatch(context.GetPlayerSlot(), &command);
if (rval) {
RETURN_META(MRES_SUPERCEDE);
}
// This is handled by the global hook
RETURN_META(MRES_SUPERCEDE);
}
void CommandCallback_Post(const CCommandContext& context, const CCommand& command)
void ConCommandManager::AddCommandListener(const char* name, CallbackT callback, HookMode mode)
{
bool rval = globals::conCommandManager.InternalDispatch_Post(context.GetPlayerSlot(), &command);
if (rval) {
RETURN_META(MRES_SUPERCEDE);
}
}
ConCommandInfo* ConCommandManager::AddOrFindCommand(const char* name, const char* description,
bool server_only, int flags)
{
ConCommandInfo* p_info = m_cmd_lookup[std::string(name)];
if (!p_info) {
CSSHARP_CORE_TRACE("[ConCommandManager] Could not find command in existing lookup {}",
name);
// auto found = std::find_if(m_cmd_list.begin(), m_cmd_list.end(),
// [&](ConCommandInfo* info) {
// return V_strcasecmp(info->command->GetName(), name) == 0;
// });
// if (found != m_cmd_list.end()) {
// return *found;
// }
p_info = new ConCommandInfo();
ConCommandHandle existingCommand = globals::cvars->FindCommand(name);
ConCommandRefAbstract pointerConCommand;
p_info->p_cmd = pointerConCommand;
if (!existingCommand.IsValid()) {
if (!description) {
description = "";
}
CSSHARP_CORE_TRACE("[ConCommandManager] Creating new command {}", name);
char* new_name = strdup(name);
char* new_desc = strdup(description);
CSSHARP_CORE_TRACE("[ConCommandManager] Creating new command {}, {}, {}, {}, {}",
(void*)&pointerConCommand, new_name, (void*)CommandCallback,
new_desc, flags);
auto conCommand =
new ConCommand(&pointerConCommand, new_name, CommandCallback, new_desc, flags);
CSSHARP_CORE_TRACE("[ConCommandManager] Creating callbacks for command {}", name);
p_info->command = conCommand;
p_info->callback_pre = globals::callbackManager.CreateCallback(name);
p_info->callback_post = globals::callbackManager.CreateCallback(name);
p_info->server_only = server_only;
CSSHARP_CORE_TRACE(
"[ConCommandManager] Adding hooks for command callback for command {}", name);
SH_ADD_HOOK(ConCommandHandle, Dispatch, &pointerConCommand.handle,
SH_STATIC(CommandCallback), false);
SH_ADD_HOOK(ConCommandHandle, Dispatch, &pointerConCommand.handle,
SH_STATIC(CommandCallback_Post), true);
CSSHARP_CORE_TRACE("[ConCommandManager] Adding command to internal lookup {}", name);
m_cmd_list.push_back(p_info);
m_cmd_lookup[name] = p_info;
if (name == nullptr) {
if (mode == HookMode::Pre) {
m_global_cmd.callback_pre->AddListener(callback);
} else {
p_info->callback_pre = globals::callbackManager.CreateCallback(name);
p_info->callback_post = globals::callbackManager.CreateCallback(name);
p_info->server_only = server_only;
m_global_cmd.callback_post->AddListener(callback);
}
return p_info;
return;
}
return p_info;
}
auto strName = std::string(name);
ConCommandInfo* pInfo = m_cmd_lookup[strName];
ConCommandInfo* ConCommandManager::AddCommand(const char* name, const char* description,
bool server_only, int flags, CallbackT callback)
{
ConCommandInfo* p_info = AddOrFindCommand(name, description, server_only, flags);
if (!p_info || !p_info->callback_pre) {
return nullptr;
if (!pInfo) {
pInfo = new ConCommandInfo();
m_cmd_lookup[strName] = pInfo;
ConCommandHandle hExistingCommand = globals::cvars->FindCommand(name);
if (hExistingCommand.IsValid()) {
pInfo->command = globals::cvars->GetCommand(hExistingCommand);
}
}
p_info->callback_pre->AddListener(callback);
return p_info;
if (mode == HookMode::Pre) {
pInfo->callback_pre->AddListener(callback);
} else {
pInfo->callback_post->AddListener(callback);
}
}
bool ConCommandManager::RemoveCommand(const char* name, CallbackT callback)
void ConCommandManager::RemoveCommandListener(const char* name, CallbackT callback, HookMode mode)
{
auto strName = std::string(strdup(name));
ConCommandInfo* p_info = m_cmd_lookup[strName];
if (!p_info)
if (name == nullptr) {
if (mode == HookMode::Pre) {
m_global_cmd.callback_pre->RemoveListener(callback);
} else {
m_global_cmd.callback_post->RemoveListener(callback);
}
return;
}
auto strName = std::string(name);
ConCommandInfo* pInfo = m_cmd_lookup[strName];
if (!pInfo) {
return;
}
if (mode == HookMode::Pre) {
pInfo->callback_pre->RemoveListener(callback);
} else {
pInfo->callback_post->RemoveListener(callback);
}
}
bool ConCommandManager::AddValveCommand(const char* name, const char* description, bool server_only,
int flags)
{
ConCommandHandle hExistingCommand = globals::cvars->FindCommand(name);
if (hExistingCommand.IsValid())
return false;
if (p_info->callback_pre && p_info->callback_pre->GetFunctionCount()) {
p_info->callback_pre->RemoveListener(callback);
ConCommandRefAbstract conCommandRefAbstract;
auto conCommand =
new ConCommand(&conCommandRefAbstract, strdup(name), CommandCallback, strdup(description), flags);
ConCommandInfo* pInfo = m_cmd_lookup[std::string(name)];
if (!pInfo) {
pInfo = new ConCommandInfo();
m_cmd_lookup[std::string(name)] = pInfo;
}
if (p_info->callback_post && p_info->callback_post->GetFunctionCount()) {
p_info->callback_post->RemoveListener(callback);
}
if (!p_info->callback_pre || p_info->callback_pre->GetFunctionCount() == 0) {
// It does not look like this actually removes the con command.
// You can still find with `find` command.
globals::cvars->UnregisterConCommand(p_info->p_cmd.handle);
}
pInfo->p_cmd = conCommandRefAbstract;
pInfo->command = conCommand;
pInfo->server_only = server_only;
return true;
}
ConCommandInfo* ConCommandManager::FindCommand(const char* name)
bool ConCommandManager::RemoveValveCommand(const char* name)
{
ConCommandInfo* p_info = m_cmd_lookup[std::string(name)];
auto hFoundCommand = globals::cvars->FindCommand(name);
if (p_info == nullptr) {
auto found = std::find_if(m_cmd_list.begin(), m_cmd_list.end(), [&](ConCommandInfo* info) {
return V_strcasecmp(info->command->GetName(), name) == 0;
});
if (found != m_cmd_list.end()) {
return *found;
}
ConCommandHandle p_cmd = globals::cvars->FindCommand(name);
if (!p_cmd.IsValid())
return nullptr;
p_info = new ConCommandInfo();
p_info->command = globals::cvars->GetCommand(p_cmd);
p_info->p_cmd = *p_info->command->GetRef();
p_info->callback_pre = globals::callbackManager.CreateCallback(name);
p_info->callback_post = globals::callbackManager.CreateCallback(name);
p_info->server_only = false;
m_cmd_list.push_back(p_info);
m_cmd_lookup[name] = p_info;
return p_info;
if (!hFoundCommand.IsValid()) {
return false;
}
return p_info;
globals::cvars->UnregisterConCommand(hFoundCommand);
auto pInfo = m_cmd_lookup[std::string(name)];
if (!pInfo) {
return true;
}
pInfo->command = nullptr;
return true;
}
int ConCommandManager::GetCommandClient() { return last_command_client; }
void ConCommandManager::SetCommandClient(int client) { last_command_client = client + 1; }
bool ConCommandManager::InternalDispatch(CPlayerSlot slot, const CCommand* args)
HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CCommandContext& ctx,
const CCommand& args, HookMode mode)
{
const char* cmd = args->Arg(0);
CSSHARP_CORE_TRACE("[ConCommandManager::ExecuteCommandCallbacks][{}]: {}",
mode == Pre ? "Pre" : "Post", name);
ConCommandInfo* pInfo = m_cmd_lookup[std::string(name)];
ConCommandInfo* p_info = m_cmd_lookup[cmd];
if (p_info == nullptr) {
if (slot.Get() == 0 && !globals::engine->IsDedicatedServer())
return false;
HookResult result = HookResult::Continue;
for (ConCommandInfo* cmdInfo : m_cmd_list) {
if ((cmdInfo != nullptr) && strcasecmp(cmdInfo->command->GetName(), cmd) == 0) {
p_info = cmdInfo;
auto globalCallback = mode == HookMode::Pre ? m_global_cmd.callback_pre : m_global_cmd.callback_post;
if (globalCallback->GetFunctionCount() > 0) {
globalCallback->ScriptContext().Reset();
globalCallback->ScriptContext().Push(ctx.GetPlayerSlot().Get());
globalCallback->ScriptContext().Push(&args);
for (auto fnMethodToCall : globalCallback->GetFunctions()) {
if (!fnMethodToCall)
continue;
fnMethodToCall(&globalCallback->ScriptContextStruct());
auto hookResult = globalCallback->ScriptContext().GetResult<HookResult>();
if (hookResult >= HookResult::Stop) {
if (mode == HookMode::Pre) {
return HookResult::Stop;
}
result = hookResult;
break;
}
if (hookResult >= HookResult::Handled) {
result = hookResult;
}
}
}
if (!p_info) {
return false;
if (!pInfo) {
return result;
}
int realClient = slot.Get();
auto pCallback = mode == HookMode::Pre ? pInfo->callback_pre : pInfo->callback_post;
bool result = false;
if (p_info->callback_pre) {
p_info->callback_pre->ScriptContext().Reset();
p_info->callback_pre->ScriptContext().SetArgument(0, realClient);
p_info->callback_pre->ScriptContext().SetArgument(1, args);
p_info->callback_pre->Execute(false);
pCallback->Reset();
pCallback->ScriptContext().Push(ctx.GetPlayerSlot().Get());
pCallback->ScriptContext().Push(&args);
result = p_info->callback_pre->ScriptContext().GetResult<bool>();
for (auto fnMethodToCall : pCallback->GetFunctions()) {
if (!fnMethodToCall)
continue;
fnMethodToCall(&pCallback->ScriptContextStruct());
auto thisResult = pCallback->ScriptContext().GetResult<HookResult>();
if (thisResult >= HookResult::Handled) {
return thisResult;
} else if (thisResult > result) {
result = thisResult;
}
}
return result;
}
bool ConCommandManager::InternalDispatch_Post(CPlayerSlot slot, const CCommand* args)
void ConCommandManager::Hook_DispatchConCommand(ConCommandHandle cmd, const CCommandContext& ctx,
const CCommand& args)
{
const char* cmd = args->Arg(0);
const char* name = args.Arg(0);
ConCommandInfo* p_info = m_cmd_lookup[cmd];
if (p_info == nullptr) {
if (slot.Get() == 0 && !globals::engine->IsDedicatedServer())
return false;
CSSHARP_CORE_TRACE("[ConCommandManager::Hook_DispatchConCommand]: {}", name);
for (ConCommandInfo* cmdInfo : m_cmd_list) {
if ((cmdInfo != nullptr) && strcasecmp(cmdInfo->command->GetName(), cmd) == 0) {
p_info = cmdInfo;
continue;
}
}
auto result = ExecuteCommandCallbacks(name, ctx, args, HookMode::Pre);
if (result >= HookResult::Handled) {
RETURN_META(MRES_SUPERCEDE);
}
int realClient = slot.Get();
bool result = false;
if (p_info->callback_post) {
p_info->callback_post->ScriptContext().Reset();
p_info->callback_post->ScriptContext().SetArgument(0, realClient);
p_info->callback_post->ScriptContext().SetArgument(1, args);
p_info->callback_post->Execute(false);
result = p_info->callback_post->ScriptContext().GetResult<bool>();
}
return result;
}
bool ConCommandManager::DispatchClientCommand(CPlayerSlot slot, const char* cmd,
const CCommand* args)
void ConCommandManager::Hook_DispatchConCommand_Post(ConCommandHandle cmd,
const CCommandContext& ctx,
const CCommand& args)
{
ConCommandInfo* p_info = m_cmd_lookup[cmd];
if (p_info == nullptr) {
auto found =
std::find_if(m_cmd_list.begin(), m_cmd_list.end(), [&](const ConCommandInfo* info) {
return V_strcasecmp(info->command->GetName(), cmd) == 0;
});
if (found == m_cmd_list.end()) {
return false;
}
const char* name = args.Arg(0);
p_info = *found;
auto result = ExecuteCommandCallbacks(name, ctx, args, HookMode::Post);
if (result >= HookResult::Handled) {
RETURN_META(MRES_SUPERCEDE);
}
if (p_info->server_only)
return false;
bool result = false;
if (p_info->callback_pre) {
p_info->callback_pre->ScriptContext().Reset();
p_info->callback_pre->ScriptContext().Push(slot.Get());
p_info->callback_pre->ScriptContext().Push(args);
p_info->callback_pre->Execute();
result = true;
}
if (result) {
if (p_info->callback_post) {
p_info->callback_post->ScriptContext().Reset();
p_info->callback_post->ScriptContext().Push(slot.Get());
p_info->callback_post->ScriptContext().Push(args);
p_info->callback_post->Execute();
result = true;
}
}
return result;
}
bool ConCommandManager::IsValidValveCommand(const char* name) {
ConCommandHandle pCmd = globals::cvars->FindCommand(name);
return pCmd.IsValid();
}
} // namespace counterstrikesharp

View File

@@ -40,6 +40,16 @@
#include <string>
#include "playerslot.h"
struct CaseInsensitiveComparator {
bool operator()(const std::string& lhs, const std::string& rhs) const {
return std::lexicographical_compare(
lhs.begin(), lhs.end(),
rhs.begin(), rhs.end(),
[](char a, char b) { return std::tolower(a) < std::tolower(b); }
);
}
};
namespace counterstrikesharp {
class ScriptCallback;
@@ -47,7 +57,9 @@ class ConCommandInfo {
friend class ConCommandManager;
public:
ConCommandInfo() {}
ConCommandInfo();
ConCommandInfo(bool bNoCallbacks);
~ConCommandInfo();
public:
void HookChange(CallbackT callback, bool post);
@@ -64,39 +76,27 @@ private:
class ConCommandManager : public GlobalClass {
friend class ConCommandInfo;
friend void CommandCallback(const CCommand& command);
friend void CommandCallback_Post(const CCommand& command);
public:
ConCommandManager();
~ConCommandManager();
void OnAllInitialized() override;
void OnShutdown() override;
ConCommandInfo* AddOrFindCommand(const char* name,
const char* description,
bool server_only,
int flags);
bool DispatchClientCommand(CPlayerSlot slot, const char* cmd, const CCommand* args);
bool InternalDispatch(CPlayerSlot slot, const CCommand* args);
int GetCommandClient();
bool InternalDispatch_Post(CPlayerSlot slot, const CCommand* args);
public:
ConCommandInfo* AddCommand(
const char* name, const char* description, bool server_only, int flags, CallbackT callback);
bool RemoveCommand(const char* name, CallbackT callback);
ConCommandInfo* FindCommand(const char* name);
void AddCommandListener(const char* name, CallbackT callback, HookMode mode);
void RemoveCommandListener(const char* name, CallbackT callback, HookMode mode);
bool IsValidValveCommand(const char* name);
bool AddValveCommand(const char* name, const char* description, bool server_only, int flags);
bool RemoveValveCommand(const char* name);
void Hook_DispatchConCommand(ConCommandHandle cmd, const CCommandContext& ctx, const CCommand& args);
void Hook_DispatchConCommand_Post(ConCommandHandle cmd, const CCommandContext& ctx, const CCommand& args);
HookResult ExecuteCommandCallbacks(const char* name, const CCommandContext& ctx,
const CCommand& args, HookMode mode);
private:
void SetCommandClient(int client);
private:
int last_command_client;
std::vector<ConCommandInfo*> m_cmd_list;
std::map<std::string, ConCommandInfo*> m_cmd_lookup;
std::map<std::string, ConCommandInfo*, CaseInsensitiveComparator> m_cmd_lookup;
ConCommandInfo m_global_cmd = ConCommandInfo(true);
};
} // namespace counterstrikesharp

View File

@@ -30,7 +30,7 @@
*/
#include "core/managers/player_manager.h"
#include "core/managers/client_command_manager.h"
#include "core/managers/con_command_manager.h"
#include <public/eiface.h>
#include <public/inetchannelinfo.h>
@@ -287,8 +287,10 @@ void PlayerManager::OnClientCommand(CPlayerSlot slot, const CCommand& args) cons
const char* cmd = args.Arg(0);
bool response = globals::clientCommandManager.DispatchClientCommand(slot, cmd, &args);
if (response) {
auto result = globals::conCommandManager.ExecuteCommandCallbacks(
cmd, CCommandContext(CommandTarget_t::CT_NO_TARGET, slot), args, HookMode::Pre);
if (result >= HookResult::Handled) {
RETURN_META(MRES_SUPERCEDE);
}
}

View File

@@ -20,7 +20,9 @@ inline std::string GameDirectory() {
return gameDirectory;
}
inline std::string PluginDirectory() { return GameDirectory() + "/addons/counterstrikesharp"; }
inline std::string GetRootDirectory() { return GameDirectory() + "/addons/counterstrikesharp"; }
inline std::string PluginsDirectory() { return GameDirectory() + "/addons/counterstrikesharp/plugins"; }
inline std::string ConfigsDirectory() { return GameDirectory() + "/addons/counterstrikesharp/configs"; }
inline std::string GamedataDirectory() { return GameDirectory() + "/addons/counterstrikesharp/gamedata"; }
} // namespace utils

View File

@@ -18,6 +18,7 @@
#include "core/global_listener.h"
#include "core/log.h"
#include "core/coreconfig.h"
#include "core/gameconfig.h"
#include "core/timer_system.h"
#include "core/utils.h"
@@ -83,6 +84,17 @@ bool CounterStrikeSharpMMPlugin::Load(PluginId id, ISmmAPI* ismm, char* error, s
GET_V_IFACE_ANY(GetEngineFactory, globals::gameEventSystem, IGameEventSystem,
GAMEEVENTSYSTEM_INTERFACE_VERSION);
auto coreconfig_path = std::string(utils::ConfigsDirectory() + "/core.json");
globals::coreConfig = new CCoreConfig(coreconfig_path);
char coreconfig_error[255] = "";
if (!globals::coreConfig->Init(coreconfig_error, sizeof(coreconfig_error))) {
CSSHARP_CORE_ERROR("Could not read \'{}\'. Error: {}", coreconfig_path, coreconfig_error);
return false;
}
CSSHARP_CORE_INFO("CoreConfig loaded.");
auto gamedata_path = std::string(utils::GamedataDirectory() + "/gamedata.json");
globals::gameConfig = new CGameConfig(gamedata_path);
char conf_error[255] = "";

View File

@@ -96,7 +96,7 @@ void* get_export(void* h, const char* name)
// Using the nethost library, discover the location of hostfxr and get exports
bool load_hostfxr()
{
std::string base_dir = counterstrikesharp::utils::PluginDirectory();
std::string base_dir = counterstrikesharp::utils::GetRootDirectory();
namespace css = counterstrikesharp;
#if _WIN32
std::wstring buffer =
@@ -164,7 +164,7 @@ CDotNetManager::~CDotNetManager() {}
bool CDotNetManager::Initialize()
{
const std::string base_dir = counterstrikesharp::utils::PluginDirectory();
const std::string base_dir = counterstrikesharp::utils::GetRootDirectory();
CSSHARP_CORE_INFO("Loading .NET runtime...");
@@ -184,7 +184,7 @@ bool CDotNetManager::Initialize()
std::string((base_dir + "/api/CounterStrikeSharp.API.runtimeconfig.json").c_str());
CSSHARP_CORE_INFO("Loading CSS API, Runtime Config: {}", wide_str);
#endif
const auto load_assembly_and_get_function_pointer = get_dotnet_load_assembly(wide_str.c_str());
if (load_assembly_and_get_function_pointer == nullptr) {
CSSHARP_CORE_ERROR("Failed to load CSS API.");

View File

@@ -16,7 +16,6 @@
#include <eiface.h>
#include "core/managers/client_command_manager.h"
#include "scripting/autonative.h"
#include "scripting/callback_manager.h"
#include "core/managers/con_command_manager.h"
@@ -26,7 +25,7 @@
namespace counterstrikesharp {
static ConCommandInfo* AddCommand(ScriptContext& script_context)
static void AddCommand(ScriptContext& script_context)
{
auto name = script_context.GetArgument<const char*>(0);
auto description = script_context.GetArgument<const char*>(1);
@@ -37,7 +36,8 @@ static ConCommandInfo* AddCommand(ScriptContext& script_context)
CSSHARP_CORE_TRACE("Adding command {}, {}, {}, {}, {}", name, description, server_only, flags,
(void*)callback);
return globals::conCommandManager.AddCommand(name, description, server_only, flags, callback);
globals::conCommandManager.AddValveCommand(name, description, server_only, flags);
globals::conCommandManager.AddCommandListener(name, callback, HookMode::Pre);
}
static void RemoveCommand(ScriptContext& script_context)
@@ -45,7 +45,8 @@ static void RemoveCommand(ScriptContext& script_context)
auto name = script_context.GetArgument<const char*>(0);
auto callback = script_context.GetArgument<CallbackT>(1);
globals::conCommandManager.RemoveCommand(name, callback);
globals::conCommandManager.RemoveCommandListener(name, callback, HookMode::Pre);
globals::conCommandManager.RemoveValveCommand(name);
}
static void AddCommandListener(ScriptContext& script_context)
@@ -54,7 +55,7 @@ static void AddCommandListener(ScriptContext& script_context)
auto callback = script_context.GetArgument<CallbackT>(1);
auto post = script_context.GetArgument<bool>(2);
globals::clientCommandManager.AddCommandListener(name, callback, post);
globals::conCommandManager.AddCommandListener(name, callback, post ? HookMode::Post : HookMode::Pre);
}
static void RemoveCommandListener(ScriptContext& script_context)
@@ -63,7 +64,7 @@ static void RemoveCommandListener(ScriptContext& script_context)
auto callback = script_context.GetArgument<CallbackT>(1);
auto post = script_context.GetArgument<bool>(2);
globals::clientCommandManager.RemoveCommandListener(name, callback, post);
globals::conCommandManager.RemoveCommandListener(name, callback, post ? HookMode::Post : HookMode::Pre);
}
static int CommandGetArgCount(ScriptContext& script_context)

View File

@@ -1,4 +1,4 @@
ADD_COMMAND: name:string,description:string,serverOnly:bool,flags:int,callback:callback -> pointer
ADD_COMMAND: name:string,description:string,serverOnly:bool,flags:int,callback:callback -> void
REMOVE_COMMAND: name:string,callback:callback -> void
ADD_COMMAND_LISTENER: cmd:string, callback:callback, post:bool -> void
REMOVE_COMMAND_LISTENER: cmd:string, callback:callback, post:bool -> void

View File

@@ -18,6 +18,7 @@
#include "core/managers/event_manager.h"
#include "scripting/autonative.h"
#include "igameevents.h"
namespace counterstrikesharp {
@@ -61,24 +62,21 @@ static void FireEvent(ScriptContext &script_context) {
managed_game_events.erase(std::remove(managed_game_events.begin(), managed_game_events.end(), game_event), managed_game_events.end());
}
// static void FireEventToClient(ScriptContext& script_context) {
// auto game_event = script_context.GetArgument<IGameEvent*>(0);
// int client = script_context.GetArgument<int>(1);
// if (!game_event) {
// script_context.ThrowNativeError("Invalid game event");
// }
//
// auto base_entity = reinterpret_cast<CBaseEntityWrapper*>(ExcBaseEntityFromIndex(client));
// if (!base_entity)
// {
// script_context.ThrowNativeError("Invalid client index");
// }
//
// auto iclient = base_entity->GetIClient();
// IGameEventListener2* game_client = (IGameEventListener2*)((intptr_t)iclient - sizeof(void*));
//
// game_client->FireGameEvent(game_event);
// }
static void FireEventToClient(ScriptContext& script_context) {
auto game_event = script_context.GetArgument<IGameEvent*>(0);
int entityIndex = script_context.GetArgument<int>(1);
if (!game_event) {
script_context.ThrowNativeError("Invalid game event");
}
IGameEventListener2* pListener = globals::GetLegacyGameEventListener(CPlayerSlot(entityIndex - 1));
if (!pListener) {
script_context.ThrowNativeError("Could not get player event listener");
}
pListener->FireGameEvent(game_event);
}
static const char *GetEventName(ScriptContext &script_context) {
IGameEvent *game_event = script_context.GetArgument<IGameEvent *>(0);
@@ -266,7 +264,7 @@ REGISTER_NATIVES(events, {
ScriptEngine::RegisterNativeHandler("UNHOOK_EVENT", UnhookEvent);
ScriptEngine::RegisterNativeHandler("CREATE_EVENT", CreateEvent);
ScriptEngine::RegisterNativeHandler("FIRE_EVENT", FireEvent);
// ScriptEngine::RegisterNativeHandler("FIRE_EVENT_TO_CLIENT", FireEventToClient);
ScriptEngine::RegisterNativeHandler("FIRE_EVENT_TO_CLIENT", FireEventToClient);
ScriptEngine::RegisterNativeHandler("GET_EVENT_NAME", GetEventName);
ScriptEngine::RegisterNativeHandler("GET_EVENT_BOOL", GetEventBool);

View File

@@ -2,7 +2,7 @@ HOOK_EVENT: name:string, callback:func, isPost:bool -> void
UNHOOK_EVENT: name:string, callback:func, isPost:bool -> void
CREATE_EVENT: name:string, force:bool -> pointer
FIRE_EVENT: gameEvent:pointer, dontBroadcast:bool -> void
# FIRE_EVENT_TO_CLIENT: gameEvent:pointer, clientIndex:int -> void
FIRE_EVENT_TO_CLIENT: gameEvent:pointer, clientIndex:int -> void
GET_EVENT_NAME: gameEvent:pointer -> string
GET_EVENT_BOOL: gameEvent:pointer, name:string -> bool
GET_EVENT_INT: gameEvent:pointer, name:string -> int

View File

@@ -23,6 +23,7 @@
#include "core/log.h"
#include "schema.h"
#include "core/function.h"
#include "core/coreconfig.h"
namespace counterstrikesharp {
@@ -122,6 +123,12 @@ void SetSchemaValueByName(ScriptContext& script_context)
auto dataType = script_context.GetArgument<DataType_t>(1);
auto className = script_context.GetArgument<const char*>(2);
auto memberName = script_context.GetArgument<const char*>(3);
if (globals::coreConfig->FollowCS2ServerGuidelines && std::find(schema::CS2BadList.begin(), schema::CS2BadList.end(), memberName) != schema::CS2BadList.end()) {
CSSHARP_CORE_ERROR("Cannot set '{}::{}' with \"FollowCS2ServerGuidelines\" option enabled.", className, memberName);
return;
}
auto classKey = hash_32_fnv1a_const(className);
auto memberKey = hash_32_fnv1a_const(memberName);

View File

@@ -40,6 +40,11 @@ enum HookResult {
Stop = 4,
};
enum HookMode {
Pre = 0,
Post = 1,
};
inline uint32_t hash_string(const char *string) {
unsigned long result = 5381;