Compare commits

...

9 Commits

Author SHA1 Message Date
Nexd
18e9e37a98 CoreConfig implementation on the managed side (#62)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
2023-11-12 12:19:57 +10:00
Roflmuffin
fe236806e1 hotfix: con command hot reload 2023-11-11 10:30:05 +10:00
Nexd
2c4e9bca42 Small adjustments (#56) 2023-11-11 09:50:54 +10:00
Roflmuffin
8f3e0c226b docs: add information about flags and standard flags 2023-11-11 01:42:57 +10:00
Roflmuffin
5f6ccf2839 feat: change color marshalling to ABGR (tested against render color) 2023-11-11 00:05:13 +10:00
Roflmuffin
6c2f56793b hotfix: con command hot reload failing 2023-11-10 23:51:18 +10:00
Roflmuffin
cc7dd5ca96 ci: I have the utmost confidence 2023-11-10 20:16:04 +10:00
Roflmuffin
ebc361b2f8 ci: publish to api.nuget.org 2023-11-10 20:05:52 +10:00
Roflmuffin
c72eff2546 ci: fix nuget source 2023-11-10 20:00:09 +10:00
13 changed files with 316 additions and 96 deletions

View File

@@ -94,5 +94,5 @@ jobs:
- name: Publish NuGet package
run: |
dotnet nuget push managed/CounterStrikeSharp.API/bin/Release/CounterStrikeSharp.API.1.0.${{ env.BUILD_NUMBER }}.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source "github" --skip-duplicate
dotnet nuget push managed/CounterStrikeSharp.API/bin/Release/CounterStrikeSharp.API.1.0.${{ env.BUILD_NUMBER }}.snupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --source "github" --skip-duplicate
dotnet nuget push managed/CounterStrikeSharp.API/bin/Release/CounterStrikeSharp.API.1.0.${{ env.BUILD_NUMBER }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push managed/CounterStrikeSharp.API/bin/Release/CounterStrikeSharp.API.1.0.${{ env.BUILD_NUMBER }}.snupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate

View File

@@ -2,14 +2,26 @@
"Erikj": {
"identity": "76561197960265731",
"flags": [
"@css/reservation",
"@css/generic",
"@css/kick",
"@css/ban"
"@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"
]
},
"Another erikj": {
"identity": "STEAM_0:1:1",
"flags": [
"@anotherscope/foobar"
]
"flags": ["@mycustomplugin/admin"]
}
}
}

View File

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

View File

@@ -15,7 +15,7 @@ Adding an Admin is as simple as creating a new entry in the `configs/admins.json
{
"ZoNiCaL": {
"identity": "76561198808392634",
"flags": ["can_manipulate_players", "admin_messages"]
"flags": ["@css/changemap", "@css/generic"]
}
}
```
@@ -27,7 +27,7 @@ You can also manually assign permissions to players in code with `AddPlayerPermi
Assigning permissions to a Command is as easy as tagging the Command method (function callback) with a `RequiresPermissions` attribute.
```csharp
[RequiresPermissions("can_execute_test_command", "other_permission")]
[RequiresPermissions("@css/slay", "@custom/permission")]
public void OnMyCommand(CCSPlayerController? caller, CommandInfo info)
{
...
@@ -35,3 +35,28 @@ public void OnMyCommand(CCSPlayerController? caller, CommandInfo info)
```
CounterStrikeSharp handles all of the permission checks behind the scenes for you.
### 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:
```shell
@css/reservation # Reserved slot access.
@css/generic # Generic admin.
@css/kick # Kick other players.
@css/ban # Ban other players.
@css/unban # Remove bans.
@css/vip # General vip status.
@css/slay # Slay/harm other players.
@css/changemap # Change the map or major gameplay features.
@css/cvar # Change most cvars.
@css/config # Execute config files.
@css/chat # Special chat privileges.
@css/vote # Start or create votes.
@css/password # Set a password on the server.
@css/rcon # Use RCON commands.
@css/cheats # Change sv_cheats or use cheating commands.
@css/root # Magically enables all flags and ignores immunity values.
```

View File

@@ -0,0 +1,26 @@
---
title: Core Configuration
description: Summary for core configuration values
---
## PublicChatTrigger
List of characters to use for public chat triggers.
## SilentChatTrigger
List of characters to use for silent chat triggers.
## FollowCS2ServerGuidelines
Per [CS2 Server Guidelines](https://blog.counter-strike.net/index.php/server_guidelines/), certain plugin
functionality will trigger all of the game server owner's Game Server Login Tokens
(GSLTs) to get banned when executed on a Counter-Strike 2 game server.
Enabling this option will block plugins from using functionality that is known to cause this.
This option only has any effect on CS2. Note that this does NOT guarantee that you cannot
receive a ban.
:::note
Disable this option at your own risk.
:::

View File

@@ -0,0 +1,118 @@
/*
* 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 System;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
namespace CounterStrikeSharp.API.Core
{
/// <summary>
/// Serializable instance of the CoreConfig
/// </summary>
internal sealed partial class CoreConfigData
{
[JsonPropertyName("PublicChatTrigger")] public string PublicChatTrigger { get; internal set; } = "!";
[JsonPropertyName("SilentChatTrigger")] public string SilentChatTrigger { get; internal set; } = "/";
[JsonPropertyName("FollowCS2ServerGuidelines")] public bool FollowCS2ServerGuidelines { get; internal set; } = true;
}
/// <summary>
/// Configuration related to the Core API.
/// </summary>
public static partial class CoreConfig
{
/// <summary>
/// List of characters to use for public chat triggers.
/// </summary>
public static string PublicChatTrigger => _coreConfig.PublicChatTrigger;
/// <summary>
/// List of characters to use for silent chat triggers.
/// </summary>
public static string SilentChatTrigger => _coreConfig.SilentChatTrigger;
/// <summary>
/// <para>
/// Per <see href="http://blog.counter-strike.net/index.php/server_guidelines/"/>, certain plugin
/// functionality will trigger all of the game server owner's Game Server Login Tokens
/// (GSLTs) to get banned when executed on a Counter-Strike 2 game server.
/// </para>
///
/// <para>
/// Enabling this option will block plugins from using functionality that is known to cause this.
/// Note that this does NOT guarantee that you cannot
/// receive a ban.
/// </para>
///
/// <para>
/// Disable this option at your own risk.
/// </para>
/// </summary>
public static bool FollowCS2ServerGuidelines => _coreConfig.FollowCS2ServerGuidelines;
}
public static partial class CoreConfig
{
private static CoreConfigData _coreConfig = new CoreConfigData();
static CoreConfig()
{
CommandUtils.AddStandaloneCommand("css_core_reload", "Reloads the core configuration file.", ReloadCoreConfigCommand);
}
[RequiresPermissions("@css/config")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadCoreConfigCommand(CCSPlayerController? player, CommandInfo command)
{
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
Load(Path.Combine(rootDir.FullName, "configs", "core.json"));
}
public static void Load(string coreConfigPath)
{
if (!File.Exists(coreConfigPath))
{
Console.WriteLine($"Core configuration could not be found at path '{coreConfigPath}', fallback values will be used.");
return;
}
try
{
var data = JsonSerializer.Deserialize<CoreConfigData>(File.ReadAllText(coreConfigPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (data != null)
{
_coreConfig = data;
}
Console.WriteLine($"Loaded core configuration");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load core configuration: {ex}, fallback values will be used.");
}
}
}
}

View File

@@ -59,6 +59,9 @@ namespace CounterStrikeSharp.API.Core
}
public void InitGlobalContext()
{
Console.WriteLine("Loading CoreConfig from \"configs/core.json\"");
CoreConfig.Load(Path.Combine(rootDir.FullName, "configs", "core.json"));
Console.WriteLine("Loading GameData from \"gamedata/gamedata.json\"");
GameData.Load(Path.Combine(rootDir.FullName, "gamedata", "gamedata.json"));

View File

@@ -1,26 +0,0 @@
/*
* 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 CGlowProperty
{
// m_bGlowing
public ref bool IsGlowing => ref Schema.GetRef<bool>(this.Handle, "CGlowProperty", "m_bGlowing");
}

View File

@@ -20,11 +20,16 @@ public class ColorMarshaler : ICustomMarshal<Color>
{
public Color NativeToManaged(IntPtr pointer)
{
return Color.FromArgb(Marshal.ReadInt32(pointer));
var color = Marshal.ReadInt32(pointer);
var alpha = (byte)((color >> 24) & 0xFF);
var blue = (byte)((color >> 16) & 0xFF);
var green = (byte)((color >> 8) & 0xFF);
var red = (byte)(color & 0xFF);
return Color.FromArgb(alpha, red, green, blue);
}
public void ManagedToNative(IntPtr pointer, Color managedObj)
{
Marshal.WriteInt32(pointer, managedObj.ToArgb());
Marshal.WriteInt32(pointer, (managedObj.A << 24) | (managedObj.B << 16) | (managedObj.G << 8) | managedObj.R);
}
}

View File

@@ -10,8 +10,53 @@ public class Schema
{
private static Dictionary<Tuple<string, string>, short> _schemaOffsets = new();
private static HashSet<string> _cs2BadList = new HashSet<string>()
{
"m_bIsValveDS",
"m_bIsQuestEligible",
// "m_iItemDefinitionIndex", // as of 2023.11.11 this is currently not blocked
"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",
};
public static short GetSchemaOffset(string className, string propertyName)
{
if (CoreConfig.FollowCS2ServerGuidelines && _cs2BadList.Contains(propertyName))
{
throw new Exception($"Cannot set or get '{className}::{propertyName}' with \"FollowCS2ServerGuidelines\" option enabled.");
}
var key = new Tuple<string, string>(className, propertyName);
if (!_schemaOffsets.TryGetValue(key, out var offset))
{
@@ -29,6 +74,11 @@ public class Schema
public static void SetSchemaValue<T>(IntPtr handle, string className, string propertyName, T value)
{
if (CoreConfig.FollowCS2ServerGuidelines && _cs2BadList.Contains(propertyName))
{
throw new Exception($"Cannot set or get '{className}::{propertyName}' with \"FollowCS2ServerGuidelines\" option enabled.");
}
NativeAPI.SetSchemaValueByName<T>(handle, (int)typeof(T).ToDataType(), className, propertyName, value);
}

View File

@@ -155,10 +155,11 @@ 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_HOOK2_void(ConCommandHandle, Dispatch, SH_NOATTRIB, false, const CCommandContext&,
const CCommand&);
void ConCommandInfo::HookChange(CallbackT cb, bool post) {
void ConCommandInfo::HookChange(CallbackT cb, bool post)
{
if (post) {
this->callback_post->AddListener(cb);
} else {
@@ -166,7 +167,8 @@ void ConCommandInfo::HookChange(CallbackT cb, bool post) {
}
}
void ConCommandInfo::UnhookChange(CallbackT cb, bool post) {
void ConCommandInfo::UnhookChange(CallbackT cb, bool post)
{
if (post) {
if (this->callback_post && this->callback_post->GetFunctionCount()) {
callback_post->RemoveListener(cb);
@@ -178,8 +180,7 @@ void ConCommandInfo::UnhookChange(CallbackT cb, bool post) {
}
}
ConCommandManager::ConCommandManager()
: last_command_client(-1) {}
ConCommandManager::ConCommandManager() : last_command_client(-1) {}
ConCommandManager::~ConCommandManager() {}
@@ -187,16 +188,17 @@ void ConCommandManager::OnAllInitialized() {}
void ConCommandManager::OnShutdown() {}
void CommandCallback(const CCommandContext& context, const CCommand& command) {
bool rval = globals::conCommandManager.InternalDispatch(
context.GetPlayerSlot(), &command);
void CommandCallback(const CCommandContext& context, const CCommand& command)
{
bool rval = globals::conCommandManager.InternalDispatch(context.GetPlayerSlot(), &command);
if (rval) {
RETURN_META(MRES_SUPERCEDE);
}
}
void CommandCallback_Post(const CCommandContext& context, const CCommand& command) {
void CommandCallback_Post(const CCommandContext& context, const CCommand& command)
{
bool rval = globals::conCommandManager.InternalDispatch_Post(context.GetPlayerSlot(), &command);
if (rval) {
@@ -204,14 +206,14 @@ void CommandCallback_Post(const CCommandContext& context, const CCommand& comman
}
}
ConCommandInfo* ConCommandManager::AddOrFindCommand(const char* name,
const char* description,
bool server_only,
int flags) {
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);
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;
@@ -234,11 +236,13 @@ ConCommandInfo* ConCommandManager::AddOrFindCommand(const char* 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);
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;
@@ -246,24 +250,22 @@ ConCommandInfo* ConCommandManager::AddOrFindCommand(const char* 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);
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);
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;
} else {
// p_info->callback_pre = globals::callbackManager.CreateCallback(name);
// p_info->callback_post = globals::callbackManager.CreateCallback(name);
// p_info->server_only = server_only;
//
// SH_ADD_HOOK(ConCommandHandle, Dispatch, pointerConCommand->handle,
// SH_STATIC(CommandCallback), false); SH_ADD_HOOK(ConCommandHandle,
// Dispatch, pointerConCommand->handle, SH_STATIC(CommandCallback_Post),
// true);
p_info->callback_pre = globals::callbackManager.CreateCallback(name);
p_info->callback_post = globals::callbackManager.CreateCallback(name);
p_info->server_only = server_only;
}
return p_info;
@@ -272,8 +274,9 @@ ConCommandInfo* ConCommandManager::AddOrFindCommand(const char* name,
return p_info;
}
ConCommandInfo* ConCommandManager::AddCommand(
const char* name, const char* description, bool server_only, int flags, CallbackT callback) {
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;
@@ -284,10 +287,12 @@ ConCommandInfo* ConCommandManager::AddCommand(
return p_info;
}
bool ConCommandManager::RemoveCommand(const char* name, CallbackT callback) {
bool ConCommandManager::RemoveCommand(const char* name, CallbackT callback)
{
auto strName = std::string(strdup(name));
ConCommandInfo* p_info = m_cmd_lookup[strName];
if (!p_info) return false;
if (!p_info)
return false;
if (p_info->callback_pre && p_info->callback_pre->GetFunctionCount()) {
p_info->callback_pre->RemoveListener(callback);
@@ -298,24 +303,16 @@ bool ConCommandManager::RemoveCommand(const char* name, CallbackT 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);
bool success;
auto it = std::remove_if(m_cmd_list.begin(), m_cmd_list.end(),
[p_info](ConCommandInfo* i) { return p_info == i; });
if ((success = it != m_cmd_list.end())) m_cmd_list.erase(it, m_cmd_list.end());
// if (success) {
// m_cmd_lookup[strName] = nullptr;
// }
return success;
}
return true;
}
ConCommandInfo* ConCommandManager::FindCommand(const char* name) {
ConCommandInfo* ConCommandManager::FindCommand(const char* name)
{
ConCommandInfo* p_info = m_cmd_lookup[std::string(name)];
if (p_info == nullptr) {
@@ -327,7 +324,8 @@ ConCommandInfo* ConCommandManager::FindCommand(const char* name) {
}
ConCommandHandle p_cmd = globals::cvars->FindCommand(name);
if (!p_cmd.IsValid()) return nullptr;
if (!p_cmd.IsValid())
return nullptr;
p_info = new ConCommandInfo();
p_info->command = globals::cvars->GetCommand(p_cmd);
@@ -350,12 +348,14 @@ 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) {
bool ConCommandManager::InternalDispatch(CPlayerSlot slot, const CCommand* args)
{
const char* cmd = args->Arg(0);
ConCommandInfo* p_info = m_cmd_lookup[cmd];
if (p_info == nullptr) {
if (slot.Get() == 0 && !globals::engine->IsDedicatedServer()) return false;
if (slot.Get() == 0 && !globals::engine->IsDedicatedServer())
return false;
for (ConCommandInfo* cmdInfo : m_cmd_list) {
if ((cmdInfo != nullptr) && strcasecmp(cmdInfo->command->GetName(), cmd) == 0) {
@@ -384,12 +384,14 @@ bool ConCommandManager::InternalDispatch(CPlayerSlot slot, const CCommand* args)
return result;
}
bool ConCommandManager::InternalDispatch_Post(CPlayerSlot slot, const CCommand* args) {
bool ConCommandManager::InternalDispatch_Post(CPlayerSlot slot, const CCommand* args)
{
const char* cmd = args->Arg(0);
ConCommandInfo* p_info = m_cmd_lookup[cmd];
if (p_info == nullptr) {
if (slot.Get() == 0 && !globals::engine->IsDedicatedServer()) return false;
if (slot.Get() == 0 && !globals::engine->IsDedicatedServer())
return false;
for (ConCommandInfo* cmdInfo : m_cmd_list) {
if ((cmdInfo != nullptr) && strcasecmp(cmdInfo->command->GetName(), cmd) == 0) {
@@ -414,9 +416,9 @@ bool ConCommandManager::InternalDispatch_Post(CPlayerSlot slot, const CCommand*
return result;
}
bool ConCommandManager::DispatchClientCommand(CPlayerSlot slot,
const char* cmd,
const CCommand* args) {
bool ConCommandManager::DispatchClientCommand(CPlayerSlot slot, const char* cmd,
const CCommand* args)
{
ConCommandInfo* p_info = m_cmd_lookup[cmd];
if (p_info == nullptr) {
auto found =
@@ -430,7 +432,8 @@ bool ConCommandManager::DispatchClientCommand(CPlayerSlot slot,
p_info = *found;
}
if (p_info->server_only) return false;
if (p_info->server_only)
return false;
bool result = false;
if (p_info->callback_pre) {
@@ -455,4 +458,4 @@ bool ConCommandManager::DispatchClientCommand(CPlayerSlot slot,
return result;
}
} // namespace counterstrikesharp
} // namespace counterstrikesharp

View File

@@ -0,0 +1,4 @@
OnServerHibernationUpdate: isHibernating:bool
OnGameServerSteamAPIActivated:
OnGameServerSteamAPIDeactivated:
OnHostNameChanged: hostname:string

View File

@@ -37,11 +37,6 @@ static ConCommandInfo* AddCommand(ScriptContext& script_context)
CSSHARP_CORE_TRACE("Adding command {}, {}, {}, {}, {}", name, description, server_only, flags,
(void*)callback);
if (globals::conCommandManager.FindCommand(name)) {
script_context.ThrowNativeError("Failed to add command \"%s\", command already exists.", name);
return nullptr;
}
return globals::conCommandManager.AddCommand(name, description, server_only, flags, callback);
}