Compare commits

..

23 Commits

Author SHA1 Message Date
Charles_
563a5d7b3a feat: Added RemovePlayerItem() to CBasePlayerPawn. (#200)
Co-authored-by: roflmuffin <shortguy014@gmail.com>
2023-12-16 14:24:08 +10:00
Charlie Thomson
983b673b4c feat: Add OnClientVoice listener (#204)
Co-authored-by: roflmuffin <shortguy014@gmail.com>
2023-12-16 14:18:40 +10:00
Roflmuffin
74fd0e0832 fix: offsets for CBaseEntity derived classes
Also adds test commands to test plugin for future validation
2023-12-14 15:14:34 +10:00
Michael Wilson
44e3f2240c chore: license updates (#199) 2023-12-14 11:03:18 +10:00
BuSheeZy
8af219e7a8 CHANGE: visual adjustments (#198) 2023-12-14 10:44:48 +10:00
Poggu
bff04e7795 Add voice manager (ability to override voice chat / mute players) (#179)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
Co-authored-by: roflmuffin <shortguy014@gmail.com>
2023-12-12 17:46:45 +10:00
Michael Wilson
d495ac6230 chore: bump version 2023-12-12 10:55:55 +10:00
roflmuffin
f78abf0c81 fix: fallback to en language files for invariant mode 2023-12-12 10:51:38 +10:00
Charles_
bcacc42d0e feat: Added support for custom gamedata files. (#194) 2023-12-12 09:44:28 +10:00
HerrMagiic
8235d5e728 Update docs link in README.md (#193) 2023-12-11 23:11:03 +10:00
Michael Wilson
56739356d5 Plugin Translations (#146) 2023-12-11 16:19:50 +10:00
ZoNiCaL
aaba87551d Admin improvements round 2! (this time it's personal) (#143)
Co-authored-by: Roflmuffin <shortguy014@gmail.com>
2023-12-11 13:24:14 +10:00
Roflmuffin
a3466dd5d1 chore: cleanup null reference warnings in virtual funcs 2023-12-11 11:48:55 +10:00
johnoclockdk
c8604760f2 Update docs (#176) 2023-12-11 10:41:10 +10:00
Michael Wilson
d50a945317 feat: defers calls to HookEvent until after game loop mode init (#187) 2023-12-10 22:13:03 +10:00
Michael Wilson
55396e005c fix: discord links (#190) 2023-12-09 14:11:27 +10:00
Roflmuffin
98b2b01992 Merge branch 'FixSteamIdOnWindowsServer' into main 2023-12-08 12:34:52 +10:00
Roflmuffin
a537be89e4 tests: update tests, throw out of range exception <= 0 2023-12-08 12:32:46 +10:00
Roflmuffin
c07d5d2aa9 Merge remote-tracking branch 'origin/main' into FixSteamIdOnWindowsServer 2023-12-08 12:25:50 +10:00
Michael Wilson
1cc95555fe feat: add basic tests project with SteamID tests (#186) 2023-12-08 12:24:35 +10:00
Roflmuffin
378c28dfd0 chore: bump hl2sdk version 2023-12-08 12:22:54 +10:00
TheR00st3r
c7343c3b7a Fix SteamId on Windows Server #182 2023-12-07 21:19:07 +01:00
Michael Wilson
62f6b09f50 Add VData Access (#181) 2023-12-07 22:58:16 +10:00
105 changed files with 3064 additions and 259 deletions

View File

@@ -117,7 +117,21 @@ jobs:
with:
dotnet-version: '7.0.x'
- run: |
- name: Install dependencies
run: dotnet restore managed/CounterStrikeSharp.sln
- name: Run tests
run: dotnet test --logger trx --results-directory "TestResults-${{ env.GITHUB_SHA_SHORT }}" managed/CounterStrikeSharp.API.Tests/CounterStrikeSharp.API.Tests.csproj
- name: Upload dotnet test results
uses: actions/upload-artifact@v3
with:
name: test-results-${{ env.GITHUB_SHA_SHORT }}
path: TestResults-${{ env.GITHUB_SHA_SHORT }}
if: ${{ always() }}
- name: Publish artifacts
run: |
dotnet publish -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API
dotnet pack -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API

View File

@@ -22,6 +22,22 @@ saul/demofile-net, https://github.com/saul/demofile-net/blob/main/LICENSE:
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
neverlosecc/source2gen, https://github.com/neverlosecc/source2gen
source2gen - Source2 games SDK generator
Copyright 2023 neverlosecc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Source2ZE/CS2Fixes:
alliedmodders/sourcemod:
Source-Python-Dev-Team/Source.Python:

View File

@@ -75,6 +75,7 @@ SET(SOURCE_FILES
src/scripting/natives/natives_memory.cpp
src/scripting/natives/natives_schema.cpp
src/scripting/natives/natives_entities.cpp
src/scripting/natives/natives_voice.cpp
src/core/managers/entity_manager.cpp
src/core/managers/entity_manager.h
src/core/managers/chat_manager.cpp
@@ -83,6 +84,8 @@ SET(SOURCE_FILES
src/core/managers/server_manager.h
src/scripting/natives/natives_server.cpp
libraries/nlohmann/json.hpp
src/core/managers/voice_manager.cpp
src/core/managers/voice_manager.h
src/scripting/natives/natives_dynamichooks.cpp
)

View File

@@ -2,7 +2,7 @@
CounterStrikeSharp is a server side modding framework for Counter-Strike: Global Offensive. This project attempts to implement a .NET Core scripting layer on top of a Metamod Source Plugin, allowing developers to create plugins that interact with the game server in a modern language (C#) to facilitate the creation of maintainable and testable code.
[Come and join our Discord](https://discord.gg/X7r3PmuYKq)
[Come and join our Discord](https://discord.gg/eAZU3guKWU)
## History
@@ -14,7 +14,7 @@ Due to the architectural changes of CS2, the plugin is being rebuilt on the grou
Download the latest build from [here](https://github.com/roflmuffin/CounterStrikeSharp/releases). (Download the with runtime version if this is your first time installing).
Detailed installation instructions can be found in the [docs](https://docs.cssharp.dev/guides/getting-started/).
Detailed installation instructions can be found in the [docs](https://docs.cssharp.dev/docs/guides/getting-started.html).
## What works?
@@ -39,7 +39,7 @@ These features are the core of the platform and work pretty well/have a low risk
## Links
- [Join the Discord](https://discord.gg/X7r3PmuYKq): Ask questions, provide suggestions
- [Join the Discord](https://discord.gg/eAZU3guKWU): Ask questions, provide suggestions
- [Read the docs](https://docs.cssharp.dev/): Getting started guide, hello world plugin example
- [Issue tracker](https://github.com/roflmuffin/CounterStrikeSharp/issues): Raise any issues here
- [Builds](https://github.com/roflmuffin/CounterStrikeSharp/actions): Download latest unstable dev snapshot
@@ -128,3 +128,4 @@ Build
```bash
cmake --build . --config Debug
```

View File

@@ -2,5 +2,6 @@
"PublicChatTrigger": [ "!" ],
"SilentChatTrigger": [ "/" ],
"FollowCS2ServerGuidelines": true,
"PluginHotReloadEnabled": true
"PluginHotReloadEnabled": true,
"ServerLanguage": "en"
}

View File

@@ -28,8 +28,8 @@
},
"CCSPlayerController_Respawn": {
"offsets": {
"windows": 241,
"linux": 243
"windows": 242,
"linux": 244
}
},
"CCSPlayerPawn_Respawn": {
@@ -122,14 +122,21 @@
},
"CBasePlayerPawn_CommitSuicide": {
"offsets": {
"windows": 356,
"linux": 356
"windows": 357,
"linux": 357
}
},
"CBasePlayerPawn_RemovePlayerItem": {
"signatures": {
"library": "server",
"linux": "\\x55\\x48\\x89\\x2A\\x41\\x2A\\x49\\x89\\x2A\\x41\\x2A\\x49\\x89\\x2A\\xE8\\x2A\\x2A\\x2A\\x2A\\x49\\x39",
"windows": "\\x48\\x85\\xD2\\x0F\\x84\\x2A\\x2A\\x2A\\x2A\\x48\\x89\\x5C\\x24\\x08\\x57\\x48\\x83\\xEC\\x30\\x48\\x8B\\xDA"
}
},
"CBaseEntity_Teleport": {
"offsets": {
"windows": 148,
"linux": 147
"windows": 149,
"linux": 148
}
},
"CBaseEntity_TakeDamageOld": {

View File

@@ -13,7 +13,7 @@ How to get started installing & using CounterStrikeSharp.
## Installing CounterStrikeSharp
Download the latest release of CounterStrikeSharp from <a href="https://github.com/roflmuffin/CounterStrikeSharp/actions/workflows/cmake-single-platform.yml" target="_blank">GitHub actions build pages</a> (just choose the latest development snapshot). **You may need to be logged into GitHub to gain access to the downloads**.
Download the latest release of CounterStrikeSharp from <a href="https://github.com/roflmuffin/CounterStrikeSharp/releases/latest" target="_blank">GitHub releases pages</a>.
> [!CAUTION]
> If this is your first time installing, you will need to download the `with-runtime` version. This includes a copy of the .NET runtime, which is required to run the plugin.

View File

@@ -32,3 +32,7 @@ receive a ban.
## PluginHotReloadEnabled
When enabled, plugins are automatically reloaded when their .dll file is updated.
## ServerLanguage
Configures the default language to use for server commands & messages. The format for the culture name based on RFC 4646 is `languagecode2-country`/`regioncode2`, where `languagecode2` is the two-letter language code and `country/regioncode2` is the two-letter subculture code. Examples include `ja-JP` for Japanese (Japan) and `en-US` for English (United States). Defaults to "en".

View File

@@ -0,0 +1,5 @@
[!INCLUDE [WithTranslations](../../examples/WithTranslations/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithTranslations" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/WithTranslations/WithTranslationsPlugin.cs)]

View File

@@ -0,0 +1,5 @@
[!INCLUDE [WithVoiceOverrides](../../examples/WithVoiceOverrides/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithVoiceOverrides" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/WithVoiceOverrides/WithVoiceOverridesPlugin.cs)]

View File

@@ -13,5 +13,9 @@ items:
href: WithGameEventHandlers.md
- name: Database (Dapper)
href: WithDatabase.md
- name: Translations
href: WithTranslations.md
- name: Voice Overrides
href: WithVoiceOverrides.md
- name: Warcraft Plugin
href: WarcraftPlugin.md

View File

@@ -14,6 +14,7 @@ description: Write Counter-Strike 2 server plugins in C#.
<span>CounterStrikeSharp is a simpler way to write CS2 server plugins.</span>
<div>
<a href="docs/guides/getting-started.md" class="btn btn-primary btn-lg fw-bold my-5">Get Started <i class="bi bi-arrow-right"></a>
<a href="https://github.com/roflmuffin/CounterStrikeSharp/releases/latest" class="btn btn-secondary btn-lg fw-bold my-5">Download <i class="bi bi-download"></a>
</div>
</div>

View File

@@ -7,7 +7,7 @@ export default {
},
{
icon: "discord",
href: "https://discord.gg/X7r3PmuYKq",
href: "https://discord.gg/eAZU3guKWU",
title: "Discord",
},
],

View File

@@ -0,0 +1,8 @@
# With Translations
This example shows how to use an `IStringLocalizer` and language `json` files to provide localization for your plugins.
## How to use
1. Add the `IStringLocalizer` to your services with dependency injection, or use the `Localizer` provided on the plugin instance.
2. Add a `lang` folder to your plugin, and add a `json` file for each language you want to support. The name of the file should be a locale code, like `en.json` or `fr.json` etc.
3. Ensure that the `lang` folder is shipped with your plugin, see the example `.csproj` file for an example to auto-copy to the output folder.
4. Use the `IStringLocalizer` to localize your strings.

View File

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

View File

@@ -0,0 +1,52 @@
using System.Globalization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Commands;
using Microsoft.Extensions.Logging;
namespace WithTranslations;
[MinimumApiVersion(80)]
public class WithTranslationsPlugin : BasePlugin
{
public override string ModuleName => "Example: With Translations";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A simple plugin that provides translations";
public override void Load(bool hotReload)
{
// A global `Localizer` is provided on the plugin instance.
// You can also use dependency injection to inject `IStringLocalizer` in your own services.
Logger.LogInformation("This message is in the server language: {Message}", Localizer["test.translation"]);
// IStringLocalizer can accept standard string format arguments.
// "This number has 2 decimal places {0:n2}" -> "This number has 2 decimal places 123.55"
Logger.LogInformation(Localizer["test.format", 123.551]);
// This message has colour codes
Server.PrintToChatAll(Localizer["test.colors"]);
// This message has colour codes and formatted values
Server.PrintToChatAll(Localizer["test.colors.withformat", 123.551]);
}
[ConsoleCommand("css_replylanguage", "Test Translations")]
public void OnCommandReplyLanguage(CCSPlayerController? player, CommandInfo command)
{
// Commands are executed in a players provided culture (or fallback to server culture).
// Players can configure their language using the `!lang` or `css_lang` command.
Logger.LogInformation("Current Culture is {Culture}", CultureInfo.CurrentCulture);
command.ReplyToCommand(Localizer["test.translation"]);
if (player != null)
{
// You can also get the players language using the `GetLanguage` extension method.
// This will always return a culture, defaulting to the server culture if the user has not configured it.
var language = player.GetLanguage();
}
}
}

View File

@@ -0,0 +1,3 @@
{
"test.translation": "This is the english (GB) translation"
}

View File

@@ -0,0 +1,6 @@
{
"test.translation": "This is the english translation",
"test.format": "This number has 2 decimal places {0:n2}",
"test.colors": "{orange}This{default} text has {green}green{default} text",
"test.colors.withformat": "{orange}{0:n2}{default}"
}

View File

@@ -0,0 +1,3 @@
{
"test.translation": "This is the french translation"
}

View File

@@ -0,0 +1,2 @@
# With Voice Overrides
Provides examples how to manipulate player voice flags & listening overrides to prevent certain players from hearing others.

View File

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

View File

@@ -0,0 +1,79 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using Microsoft.Extensions.Logging;
namespace WithVoiceOverrides;
[MinimumApiVersion(80)]
public class WithVoiceOverridesPlugin : BasePlugin
{
public override string ModuleName => "Example: With Voice Overrides";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A plugin that manipulates voice flags";
[ConsoleCommand("css_hearall")]
public void OnHearAllCommand(CCSPlayerController? caller, CommandInfo command)
{
if (caller is null) return;
if (caller.VoiceFlags.HasFlag(VoiceFlags.ListenAll))
{
caller.VoiceFlags = VoiceFlags.Normal;
command.ReplyToCommand("Voice set back to default");
}
else
{
caller.VoiceFlags = VoiceFlags.ListenAll;
command.ReplyToCommand("Can hear both teams");
}
}
[ConsoleCommand("css_muteself")]
public void OnMuteSelfCommand(CCSPlayerController? caller, CommandInfo command)
{
if (caller is null) return;
if (caller.VoiceFlags.HasFlag(VoiceFlags.Muted))
{
caller.VoiceFlags = VoiceFlags.Normal;
command.ReplyToCommand("Unmuted yourself");
}
else
{
caller.VoiceFlags = VoiceFlags.Muted;
command.ReplyToCommand("Muted yourself");
}
}
[ConsoleCommand("css_muteothers")]
[CommandHelper(minArgs: 1, usage: "[target]")]
public void OnMuteOthersCommand(CCSPlayerController? caller, CommandInfo command)
{
if (caller is null) return;
var targetResult = command.GetArgTargetResult(1);
foreach (var player in targetResult.Players)
{
if (player == caller) continue;
var existingOverride = caller.GetListenOverride(player);
if (existingOverride == ListenOverride.Mute)
{
caller.SetListenOverride(player, ListenOverride.Default);
command.ReplyToCommand($"Now hearing {player.PlayerName}");
}
else
{
caller.SetListenOverride(player, ListenOverride.Mute);
command.ReplyToCommand($"Muted {player.PlayerName}");
}
}
}
}

View File

@@ -0,0 +1,110 @@
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Tests;
[Collection("Logging collection")]
public class AdminTests
{
public AdminTests()
{
AdminManager.LoadAdminData(TestUtils.GetTestPath("admins.json"));
AdminManager.LoadAdminGroups(TestUtils.GetTestPath("admin_groups.json"));
AdminManager.LoadCommandOverrides(TestUtils.GetTestPath("admin_overrides.json"));
AdminManager.MergeGroupPermsIntoAdmins();
}
[Fact]
public void ShouldReturnValidAdminData()
{
var adminData = AdminManager.GetPlayerAdminData((SteamID)76561197960265731);
Assert.NotNull(adminData);
Assert.Equal("#css/admin", adminData.Groups.Single());
}
[Fact]
public void ShouldUseGroupImmunity()
{
var adminData = AdminManager.GetPlayerAdminData((SteamID)76561197960265731);
Assert.NotNull(adminData);
Assert.Equal(125u, adminData.Immunity); // Group immunity is 125, Admin immunity is 100
AdminManager.SetPlayerImmunity((SteamID)76561197960265731, 150u);
Assert.Equal(150u, adminData.Immunity); // Group immunity is 125, Admin immunity is 100
}
[Fact]
public void ShouldReturnNullAdminData()
{
var adminData = AdminManager.GetPlayerAdminData((SteamID)76561197960265732);
Assert.Null(adminData);
}
[Fact]
public void ShouldReturnValidCommandOverrides()
{
var adminData = AdminManager.GetPlayerAdminData((SteamID)76561197960265731);
Assert.NotNull(adminData);
Assert.True(adminData.CommandOverrides["fake_command"]);
Assert.False(adminData.CommandOverrides["css"]);
}
[Fact]
public void ShouldHandleWildcardDomainFlags()
{
// User has @mycustomplugin/* so should have the admin subflag despite it not being in their group.
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265731, "@mycustomplugin/admin"));
// User has @mycustomplugin/* so should have the admin subflag despite it not existing anywhere else.
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265731, "@mycustomplugin/fake"));
// User has @css/root so should have the slay subflag despite it not being in their group.
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265731, "@css/slay"));
// Flag provided explicitly in the admins.json file
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265731, "@css/custom-flag-2"));
}
[Fact]
public void ShouldAddFlagsAtRuntime()
{
// Existing player
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265731, "@runtime/flag1"));
AdminManager.AddPlayerPermissions((SteamID)76561197960265731, "@runtime/flag1");
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265731, "@runtime/flag1"));
// Non-existent player
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265730, "@runtime/flag1"));
AdminManager.AddPlayerPermissions((SteamID)76561197960265730, "@runtime/flag1");
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265730, "@runtime/flag1"));
AdminManager.ClearPlayerPermissions((SteamID)76561197960265730);
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265730, "@runtime/flag1"));
Assert.Empty(AdminManager.GetPlayerAdminData((SteamID)76561197960265730)!.GetAllFlags());
}
[Fact]
public void ShouldAddPlayerToGroup()
{
AdminManager.AddPlayerToGroup(new SteamID("STEAM_0:1:3"), "#css/root");
var adminData = AdminManager.GetPlayerAdminData(new SteamID("STEAM_0:1:3"));
Assert.NotNull(adminData);
Assert.Equal("#css/root", adminData.Groups.Single());
}
[Fact]
public void ShouldAddPlayerPermissionOverridesAtRuntime()
{
Assert.False(AdminManager.PlayerHasCommandOverride((SteamID)76561197960265731, "runtime_command"));
AdminManager.SetPlayerCommandOverride((SteamID)76561197960265731, "runtime_command", true);
Assert.True(AdminManager.PlayerHasCommandOverride((SteamID)76561197960265731, "runtime_command"));
}
[Fact]
public void ShouldAddCommandPermissionOverridesAtRuntime()
{
Assert.False(AdminManager.CommandIsOverriden("runtime_command"));
AdminManager.AddPermissionOverride("runtime_command", "@runtime/override");
Assert.True(AdminManager.CommandIsOverriden("runtime_command"));
Assert.Equal("@runtime/override", AdminManager.GetPermissionOverrides("runtime_command").Single());
}
}

View File

@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Update="Resources\**\*.*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="lang\en.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="lang\en-GB.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="lang\fr.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,23 @@
using CounterStrikeSharp.API.Core.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Tests.Fixtures;
public class CoreLoggingFixture : IDisposable
{
public CoreLoggingFixture()
{
var serviceProvider = new ServiceCollection()
.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddCoreLogging(TestUtils.GetTestPath(""));
})
.BuildServiceProvider();
}
public void Dispose()
{
// TODO release managed resources here
}
}

View File

@@ -0,0 +1,6 @@
namespace CounterStrikeSharp.API.Tests.Fixtures;
[CollectionDefinition("Logging collection")]
public class LoggingCollection : ICollectionFixture<CoreLoggingFixture>
{
}

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
{
"Erikj": {
"identity": "76561197960265731",
"immunity": 100,
"flags": [
"@css/custom-flag-1",
"@css/custom-flag-2",
"@mycustomplugin/*"
],
"groups": [
"#css/admin"
],
"command_overrides": {
"css_plugins": true,
"css": false,
"fake_command": true
}
},
"Another erikj": {
"identity": "STEAM_0:1:1",
"flags": ["@mycustomplugin/admin"]
}
}

View File

@@ -0,0 +1,3 @@
{
"test.translation": "This is the english (GB) translation"
}

View File

@@ -0,0 +1,6 @@
{
"test.translation": "This is the english translation",
"test.format": "This number has 2 decimal places {0:n2}",
"test.colors": "{orange}This{default} text has {green}green{default} text",
"test.colors.withformat": "{orange}{0:n2}{default}"
}

View File

@@ -0,0 +1,3 @@
{
"test.translation": "This is the french translation"
}

View File

@@ -0,0 +1,52 @@
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
namespace CounterStrikeSharp.API.Tests;
public class SteamIdTests
{
[Theory]
[InlineData(76561197960524373ul, 76561197960524373ul, "STEAM_0:1:129322", "[U:1:258645]", 258645, SteamAccountType.Individual, SteamAccountInstance.Desktop, SteamAccountUniverse.Public, true)]
[InlineData(258645, 76561197960524373ul, "STEAM_0:1:129322", "[U:1:258645]", 258645, SteamAccountType.Individual, SteamAccountInstance.Desktop, SteamAccountUniverse.Public, true)]
[InlineData(76561197960265728ul, 76561197960265728ul, "STEAM_0:0:0", "[U:1:0]", 0, SteamAccountType.Individual, SteamAccountInstance.Desktop, SteamAccountUniverse.Public, false)]
[InlineData(103582791429521412ul, 103582791429521412ul, "STEAM_0:0:13510796734627842", "[g:1:27021593469255684]", 4, SteamAccountType.Clan, SteamAccountInstance.All, SteamAccountUniverse.Public, true)]
public void ValidateSteamId(ulong parseValue, ulong steamId64, string steamId2, string steamId3, int steamId32, SteamAccountType accountType, SteamAccountInstance accountInstance, SteamAccountUniverse accountUniverse, bool valid)
{
var steamId = new SteamID(parseValue);
Assert.Equal(steamId64, steamId.SteamId64);
Assert.Equal(steamId2, steamId.SteamId2);
Assert.Equal(steamId3, steamId.SteamId3);
Assert.Equal(steamId32, steamId.SteamId32);
Assert.Equal(accountType, steamId.AccountType);
Assert.Equal(accountInstance, steamId.AccountInstance);
Assert.Equal(accountUniverse, steamId.AccountUniverse);
Assert.Equal(valid, steamId.IsValid());
}
[Theory]
[InlineData(76561197960524373ul, 76561197960524373ul)]
[InlineData("STEAM_0:1:129322", 76561197960524373ul)]
[InlineData("[U:1:258645]", 76561197960524373ul)]
public void CanCastLongToString(dynamic parseable, ulong longValue)
{
Assert.Equal(longValue, ((SteamID)parseable).SteamId64);
}
[Fact]
public void CanUseValueEquality()
{
var steamId1 = new SteamID(76561197960524373ul);
var steamId2 = new SteamID(76561197960524373ul);
var steamId3 = new SteamID(76561197960265728ul);
Assert.True(steamId1 == steamId2);
Assert.True(steamId1 != steamId3);
}
[Fact]
public void ThrowsOutOfRangeException()
{
Assert.Throws<ArgumentOutOfRangeException>(() => new SteamID(0));
}
}

View File

@@ -0,0 +1,14 @@
using System.Reflection;
namespace CounterStrikeSharp.API.Tests;
public static class TestUtils
{
public static string GetTestPath(string relativePath)
{
var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().Location);
var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath);
var dirPath = Path.GetDirectoryName(codeBasePath);
return Path.Combine(dirPath, "Resources", relativePath);
}
}

View File

@@ -0,0 +1,92 @@
using System.Globalization;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Plugin;
using CounterStrikeSharp.API.Core.Translations;
using Microsoft.Extensions.Localization;
using Moq;
namespace CounterStrikeSharp.API.Tests;
public class TranslationTests
{
private readonly JsonStringLocalizerFactory _factory;
private readonly IStringLocalizer _localizer;
public TranslationTests()
{
var pluginContextMock = new Mock<IPluginContext>();
pluginContextMock.SetupGet(x => x.FilePath).Returns(TestUtils.GetTestPath("test_plugin.dll"));
_factory = new JsonStringLocalizerFactory(pluginContextMock.Object);
_localizer = _factory.Create(this.GetType());
// This is generally derived from the core config, but for the sake of these tests we default to `en`.
var serverCulture = CultureInfo.GetCultureInfo("en");
CultureInfo.DefaultThreadCurrentUICulture = serverCulture;
CultureInfo.DefaultThreadCurrentCulture = serverCulture;
CultureInfo.CurrentUICulture = serverCulture;
CultureInfo.CurrentCulture = serverCulture;
}
[Fact]
public void TranslatesLanguagesCorrectly()
{
using (new WithTemporaryCulture(CultureInfo.GetCultureInfo("en")))
{
Assert.Equal("This is the english translation", _localizer["test.translation"]);
}
using (new WithTemporaryCulture(CultureInfo.InvariantCulture))
{
Assert.Equal("This is the english translation", _localizer["test.translation"]);
}
using (new WithTemporaryCulture(CultureInfo.GetCultureInfo("en-US")))
{
Assert.Equal("This is the english translation", _localizer["test.translation"]);
}
using (new WithTemporaryCulture(CultureInfo.GetCultureInfo("en-GB")))
{
Assert.Equal("This is the english (GB) translation", _localizer["test.translation"]);
}
using (new WithTemporaryCulture(CultureInfo.GetCultureInfo("fr")))
{
Assert.Equal("This is the french translation", _localizer["test.translation"]);
}
using (new WithTemporaryCulture(CultureInfo.GetCultureInfo("fr-FR")))
{
Assert.Equal("This is the french translation", _localizer["test.translation"]);
}
}
[Fact]
public void ShouldFallbackToServerLanguage()
{
using (new WithTemporaryCulture(CultureInfo.GetCultureInfo("de")))
{
Assert.Equal("This is the english translation", _localizer["test.translation"]);
}
}
[Fact]
public void ShouldReturnKeyIfNotFound()
{
Assert.Equal("test.notfound", _localizer["test.notfound"]);
}
[Fact]
public void ShouldHandleFormatStrings()
{
Assert.Equal("This number has 2 decimal places 0.25", _localizer["test.format", 0.251]);
}
[Fact]
public void HandlesColorFormatting()
{
// Sets invisible pre-color if there is a color code in the string.
Assert.Equal(" \x10This\x01 text has \x04green\x01 text", _localizer["test.colors"]);
Assert.Equal($" {'\x10'}1.25\x01", _localizer["test.colors.withformat", 1.25]);
}
}

View File

@@ -0,0 +1 @@
global using Xunit;

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -7,6 +8,7 @@ using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Logging;
using CounterStrikeSharp.API.Core.Plugin;
using CounterStrikeSharp.API.Core.Plugin.Host;
using CounterStrikeSharp.API.Core.Translations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -38,6 +40,7 @@ public static class Bootstrap
services.AddSingleton<IScriptHostConfiguration, ScriptHostConfiguration>();
services.AddScoped<Application>();
services.AddSingleton<IPluginManager, PluginManager>();
services.AddSingleton<IPlayerLanguageManager, PlayerLanguageManager>();
services.AddScoped<IPluginContextQueryHandler, PluginContextQueryHandler>();
services.Scan(i => i.FromCallingAssembly()

View File

@@ -1311,5 +1311,51 @@ namespace CounterStrikeSharp.API.Core
return (bool)ScriptContext.GlobalScriptContext.GetResult(typeof(bool));
}
}
public static void SetClientListening(IntPtr receiver, IntPtr sender, uint listen){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(receiver);
ScriptContext.GlobalScriptContext.Push(sender);
ScriptContext.GlobalScriptContext.Push(listen);
ScriptContext.GlobalScriptContext.SetIdentifier(0xD38BEE77);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static ListenOverride GetClientListening(IntPtr receiver, IntPtr sender){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(receiver);
ScriptContext.GlobalScriptContext.Push(sender);
ScriptContext.GlobalScriptContext.SetIdentifier(0xE95644E3);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (ListenOverride)ScriptContext.GlobalScriptContext.GetResult(typeof(ListenOverride));
}
}
public static void SetClientVoiceFlags(IntPtr client, uint flags){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(client);
ScriptContext.GlobalScriptContext.Push(flags);
ScriptContext.GlobalScriptContext.SetIdentifier(0x48EB2FC8);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static uint GetClientVoiceFlags(IntPtr client){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(client);
ScriptContext.GlobalScriptContext.SetIdentifier(0x9685205C);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (uint)ScriptContext.GlobalScriptContext.GetResult(typeof(uint));
}
}
}
}

View File

@@ -14,13 +14,16 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System.Globalization;
using System.Linq;
using System.Text;
using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Plugin;
using CounterStrikeSharp.API.Core.Plugin.Host;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Logging;
@@ -40,10 +43,11 @@ namespace CounterStrikeSharp.API.Core
private readonly CoreConfig _coreConfig;
private readonly IPluginManager _pluginManager;
private readonly IPluginContextQueryHandler _pluginContextQueryHandler;
private readonly IPlayerLanguageManager _playerLanguageManager;
public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration scriptHostConfiguration,
GameDataProvider gameDataProvider, CoreConfig coreConfig, IPluginManager pluginManager,
IPluginContextQueryHandler pluginContextQueryHandler)
IPluginContextQueryHandler pluginContextQueryHandler, IPlayerLanguageManager playerLanguageManager)
{
Logger = loggerFactory.CreateLogger("Core");
_scriptHostConfiguration = scriptHostConfiguration;
@@ -51,6 +55,7 @@ namespace CounterStrikeSharp.API.Core
_coreConfig = coreConfig;
_pluginManager = pluginManager;
_pluginContextQueryHandler = pluginContextQueryHandler;
_playerLanguageManager = playerLanguageManager;
_instance = this;
}
@@ -61,19 +66,20 @@ namespace CounterStrikeSharp.API.Core
_coreConfig.Load();
_gameDataProvider.Load();
var adminGroupsPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_groups.json");
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
AdminManager.LoadAdminGroups(adminGroupsPath);
var adminPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admins.json");
Logger.LogInformation("Loading Admins from {Path}", adminPath);
AdminManager.LoadAdminData(adminPath);
var adminGroupsPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_groups.json");
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
AdminManager.LoadAdminGroups(adminGroupsPath);
var overridePath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_overrides.json");
Logger.LogInformation("Loading Admin Command Overrides from {Path}", overridePath);
AdminManager.LoadCommandOverrides(overridePath);
AdminManager.MergeGroupPermsIntoAdmins();
AdminManager.AddCommands();
_pluginManager.Load();
@@ -98,7 +104,7 @@ namespace CounterStrikeSharp.API.Core
info.ReplyToCommand(
" CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison and CS2Fixes.\n" +
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison, source2gen and CS2Fixes.\n" +
" See ACKNOWLEDGEMENTS.md for more information.\n" +
" Current API Version: " + currentVersion, true);
return;
@@ -152,7 +158,7 @@ namespace CounterStrikeSharp.API.Core
true);
break;
}
// If our arugment doesn't end in ".dll" - try and construct a path similar to PluginName/PluginName.dll.
// We'll assume we have a full path if we have ".dll".
var path = info.GetArg(2);
@@ -172,7 +178,8 @@ namespace CounterStrikeSharp.API.Core
try
{
_pluginManager.LoadPlugin(path);
} catch (Exception e)
}
catch (Exception e)
{
info.ReplyToCommand($"Could not load plugin \"{path}\")", true);
}
@@ -181,7 +188,7 @@ namespace CounterStrikeSharp.API.Core
{
plugin.Load(false);
}
break;
}
@@ -244,11 +251,44 @@ namespace CounterStrikeSharp.API.Core
}
}
[CommandHelper(usage: "[language code, e.g. \"de\", \"pl\", \"en\"]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnLangCommand(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
SteamID steamId = (SteamID)player.SteamID;
if (command.ArgCount == 1)
{
var language = _playerLanguageManager.GetLanguage(steamId);
command.ReplyToCommand(string.Format("Current language is \"{0}\" ({1})", language.Name, language.NativeName));
return;
}
if (command.ArgCount != 2)
{
return;
}
try
{
var language = command.GetArg(1);
var cultureInfo = CultureInfo.GetCultures(CultureTypes.AllCultures).Single(x => x.Name == language);
_playerLanguageManager.SetLanguage(steamId, cultureInfo);
command.ReplyToCommand($"Language set to {cultureInfo.NativeName}");
}
catch (InvalidOperationException)
{
command.ReplyToCommand("Language not found.");
}
}
private void RegisterPluginCommands()
{
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.",
OnCSSPluginCommand);
CommandUtils.AddStandaloneCommand("css_lang", "Set Counter-Strike Sharp language", OnLangCommand);
}
}
}

View File

@@ -21,12 +21,14 @@ using System.Linq;
using System.Reflection;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Config;
using CounterStrikeSharp.API.Modules.Entities;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
@@ -58,6 +60,8 @@ namespace CounterStrikeSharp.API.Core
public string ModuleDirectory => Path.GetDirectoryName(ModulePath);
public ILogger Logger { get; set; }
public IStringLocalizer Localizer { get; set; }
public virtual void Load(bool hotReload)
{
}
@@ -170,39 +174,53 @@ namespace CounterStrikeSharp.API.Core
{
var caller = (i != -1) ? new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1)) : null;
var command = new CommandInfo(ptr, caller);
using var temporaryCulture = new WithTemporaryCulture(caller.GetLanguage());
var methodInfo = handler?.GetMethodInfo();
if (!AdminManager.CommandIsOverriden(name))
// We do not need to do permission checks on commands executed from the server console.
// The server will always be allowed to execute commands (unless marked as client only like above)
if (caller != null)
{
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null)
var adminData = AdminManager.GetPlayerAdminData(caller!.AuthorizedSteamID);
var permissionsToCheck = new List<BaseRequiresPermissions>();
// If our command is overriden, we dynamically create a new permissions attribute
// based on the data that is stored in admin_overrides.json.
if (AdminManager.CommandIsOverriden(name))
{
foreach (var attr in permissions)
var data = AdminManager.GetCommandOverrideData(name);
if (data != null)
{
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
if (attr != null) permissionsToCheck.Add(attr);
}
}
}
// 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)
// The permissions for this command are not being overriden here, so we
// grab the permissions to check straight from the attribute.
else
{
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null) permissionsToCheck.AddRange(permissions);
}
foreach (var attr in permissionsToCheck)
{
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.");
var responseStr = (attr.GetType() == typeof(RequiresPermissions)) ?
"You are missing the correct permissions" : "You do not have one of the correct permissions";
var flags = attr.Permissions.Except(adminData?.GetAllFlags() ?? new HashSet<string>());
flags = flags.Except(adminData?.Groups ?? new HashSet<string>());
command.ReplyToCommand($"[CSS] {responseStr} ({string.Join(", ", flags)}) to execute this command.");
return;
}
}
@@ -210,15 +228,17 @@ namespace CounterStrikeSharp.API.Core
// Do not execute if we shouldn't be calling this command.
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
if (helperAttribute != null)
if (helperAttribute != null)
{
switch (helperAttribute.WhoCanExcecute)
{
case CommandUsage.CLIENT_AND_SERVER: break; // Allow command through.
case CommandUsage.CLIENT_ONLY:
if (caller == null || !caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by clients."); return; } break;
if (caller == null || !caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by clients."); return; }
break;
case CommandUsage.SERVER_ONLY:
if (caller != null && caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by the server."); return; } break;
if (caller != null && caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by the server."); return; }
break;
default: throw new ArgumentException("Unrecognised CommandUsage value passed in CommandHelperAttribute.");
}
@@ -226,7 +246,12 @@ namespace CounterStrikeSharp.API.Core
// but we'll just ignore that for this check.
if (helperAttribute.MinArgs != 0 && command.ArgCount - 1 < helperAttribute.MinArgs)
{
command.ReplyToCommand($"[CSS] Expected usage: \"!{command.ArgByIndex(0)} {helperAttribute.Usage}\".");
// Remove the "css_" from the beginning of the command name if it's present.
// Most of the time, users will be calling commands from chat.
var commandCalled = command.ArgByIndex(0);
var properCommandName = (commandCalled.StartsWith("css_")) ? commandCalled.Replace("css_", "") : commandCalled;
command.ReplyToCommand($"[CSS] Expected usage: \"!{properCommandName} {helperAttribute.Usage}\".");
return;
}
}

View File

@@ -1,4 +1,4 @@
/*
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,8 @@ using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Logging;
using Microsoft.Extensions.Hosting;
@@ -46,6 +48,9 @@ namespace CounterStrikeSharp.API.Core
[JsonPropertyName("PluginHotReloadEnabled")]
public bool PluginHotReloadEnabled { get; set; } = true;
[JsonPropertyName("ServerLanguage")]
public string ServerLanguage { get; set; } = "en";
}
/// <summary>
@@ -88,6 +93,8 @@ namespace CounterStrikeSharp.API.Core
/// When enabled, plugins are automatically reloaded when their .dll file is updated.
/// </summary>
public static bool PluginHotReloadEnabled => _coreConfig.PluginHotReloadEnabled;
public static string ServerLanguage => _coreConfig.ServerLanguage;
}
public partial class CoreConfig : IStartupService
@@ -138,6 +145,29 @@ namespace CounterStrikeSharp.API.Core
{
_logger.LogWarning(ex, "Failed to load core configuration, fallback values will be used");
}
var serverCulture = CultureInfo.GetCultures(CultureTypes.AllCultures)
.FirstOrDefault(x => x.Name == ServerLanguage);
if (serverCulture == null)
{
try
{
_logger.LogWarning("Server Language \"{ServerLanguage}\" is not supported, falling back to \"en\"",
ServerLanguage);
_coreConfig.ServerLanguage = "en";
serverCulture = new CultureInfo("en");
}
catch (Exception)
{
_logger.LogWarning("Server is running in invariant mode, translations will not be available.");
serverCulture = CultureInfo.InvariantCulture;
}
}
CultureInfo.DefaultThreadCurrentUICulture = serverCulture;
CultureInfo.DefaultThreadCurrentCulture = serverCulture;
CultureInfo.CurrentUICulture = serverCulture;
CultureInfo.CurrentCulture = serverCulture;
_logger.LogInformation("Successfully loaded core configuration");
}

View File

@@ -34,34 +34,58 @@ public class Offsets
public sealed class GameDataProvider : IStartupService
{
private readonly string _gameDataFilePath;
private readonly string _gameDataDirectoryPath;
public Dictionary<string,LoadedGameData> Methods;
private readonly ILogger<GameDataProvider> _logger;
public GameDataProvider(IScriptHostConfiguration scriptHostConfiguration, ILogger<GameDataProvider> logger)
{
_logger = logger;
_gameDataFilePath = Path.Join(scriptHostConfiguration.GameDataPath, "gamedata.json");
_gameDataDirectoryPath = scriptHostConfiguration.GameDataPath;
}
public void Load()
{
try
{
Methods = JsonSerializer.Deserialize<Dictionary<string, LoadedGameData>>(File.ReadAllText(_gameDataFilePath))!;
Methods = new Dictionary<string, LoadedGameData>();
foreach (string filePath in Directory.EnumerateFiles(_gameDataDirectoryPath, "*.json"))
{
string jsonContent = File.ReadAllText(filePath);
Dictionary<string, LoadedGameData> loadedMethods = JsonSerializer.Deserialize<Dictionary<string, LoadedGameData>>(jsonContent)!;
foreach (KeyValuePair<string, LoadedGameData> loadedMethod in loadedMethods)
{
if (Methods.ContainsKey(loadedMethod.Key))
{
_logger.LogWarning("GameData Method \"{Key}\" loaded a duplicate entry from {filePath}.", loadedMethod.Key, filePath);
}
Methods[loadedMethod.Key] = loadedMethod.Value;
}
if (loadedMethods != null)
{
_logger.LogInformation("Successfully loaded {Count} game data entries from {Path}", loadedMethods.Count, filePath);
}
else
{
_logger.LogWarning("Unable to load game data entries from {Path}, game data file is empty", filePath);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load game data");
}
_logger.LogInformation("Successfully loaded {Count} game data entries from {Path}", Methods.Count, _gameDataFilePath);
}
}
public static class GameData
{
internal static GameDataProvider GameDataProvider { get; set; } = null!;
public static string GetSignature(string key)
{
Application.Instance.Logger.LogDebug("Getting signature: {Key}", key);

View File

@@ -16,6 +16,7 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
@@ -54,6 +55,8 @@ namespace CounterStrikeSharp.API.Core
string ModulePath { get; internal set; }
ILogger Logger { get; set; }
IStringLocalizer Localizer { get; set; }
void RegisterAllAttributes(object instance);

View File

@@ -93,6 +93,13 @@ namespace CounterStrikeSharp.API.Core
[ListenerName("OnClientDisconnectPost")]
public delegate void OnClientDisconnectPost(int playerSlot);
/// <summary>
/// Called when a client transmits voice data
/// </summary>
/// <param name="playerSlot">The player slot of the client.</param>
[ListenerName("OnClientVoice")]
public delegate void OnClientVoice(int playerSlot);
/// <summary>
/// Called when a client has been authorized by Steam.
/// </summary>

View File

@@ -1,4 +1,4 @@
using System;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
@@ -26,4 +26,6 @@ public partial class CBaseEntity
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsRotation;
/// </summary>
public QAngle? AbsRotation => CBodyComponent?.SceneNode?.AbsRotation;
public T? GetVData<T>() where T : CEntitySubclassVDataBase => (T)Activator.CreateInstance(typeof(T), Marshal.ReadIntPtr(SubclassID.Handle + 8));
}

View File

@@ -14,4 +14,13 @@ public partial class CBasePlayerPawn
{
VirtualFunction.CreateVoid<IntPtr, bool, bool>(Handle, GameData.GetOffset("CBasePlayerPawn_CommitSuicide"))(Handle, explode, force);
}
/// <summary>
/// Remove Player Item
/// </summary>
/// <param name="weapon"></param>
public void RemovePlayerItem(CBasePlayerWeapon weapon)
{
VirtualFunctions.RemovePlayerItemVirtual(Handle, weapon.Handle);
}
}

View File

@@ -0,0 +1,6 @@
namespace CounterStrikeSharp.API.Core;
public partial class CBasePlayerWeapon
{
public CBasePlayerWeaponVData? VData => GetVData<CBasePlayerWeaponVData>();
}

View File

@@ -202,6 +202,21 @@ public partial class CCSPlayerController
public void ExecuteClientCommand(string command) => NativeAPI.IssueClientCommand(Slot, command);
/// <summary>
/// Overrides who a player can hear in voice chat.
/// </summary>
/// <param name="sender">Player talking in the voice chat</param>
/// <param name="override">Whether the talker should be heard</param>
public void SetListenOverride(CCSPlayerController sender, ListenOverride @override)
{
NativeAPI.SetClientListening(Handle, sender.Handle, (Byte)@override);
}
public ListenOverride GetListenOverride(CCSPlayerController sender)
{
return NativeAPI.GetClientListening(Handle, sender.Handle);
}
public int Slot => (int)Index - 1;
/// <summary>
@@ -234,4 +249,16 @@ public partial class CCSPlayerController
return ipAddress;
}
}
/// <summary>
/// Determines how the player interacts with voice chat.
/// </summary>
public VoiceFlags VoiceFlags
{
get => (VoiceFlags)NativeAPI.GetClientVoiceFlags(Handle);
set
{
NativeAPI.SetClientVoiceFlags(Handle, (Byte)value);
}
}
}

View File

@@ -0,0 +1,6 @@
namespace CounterStrikeSharp.API.Core;
public partial class CCSWeaponBase
{
public new CCSWeaponBaseVData? VData => GetVData<CCSWeaponBaseVData>();
}

View File

@@ -4827,6 +4827,51 @@ public partial class CBasePlayerPawn : CBaseCombatCharacter
}
public partial class CBasePlayerVData : CEntitySubclassVDataBase
{
public CBasePlayerVData (IntPtr pointer) : base(pointer) {}
// m_flHeadDamageMultiplier
public CSkillFloat HeadDamageMultiplier => Schema.GetDeclaredClass<CSkillFloat>(this.Handle, "CBasePlayerVData", "m_flHeadDamageMultiplier");
// m_flChestDamageMultiplier
public CSkillFloat ChestDamageMultiplier => Schema.GetDeclaredClass<CSkillFloat>(this.Handle, "CBasePlayerVData", "m_flChestDamageMultiplier");
// m_flStomachDamageMultiplier
public CSkillFloat StomachDamageMultiplier => Schema.GetDeclaredClass<CSkillFloat>(this.Handle, "CBasePlayerVData", "m_flStomachDamageMultiplier");
// m_flArmDamageMultiplier
public CSkillFloat ArmDamageMultiplier => Schema.GetDeclaredClass<CSkillFloat>(this.Handle, "CBasePlayerVData", "m_flArmDamageMultiplier");
// m_flLegDamageMultiplier
public CSkillFloat LegDamageMultiplier => Schema.GetDeclaredClass<CSkillFloat>(this.Handle, "CBasePlayerVData", "m_flLegDamageMultiplier");
// m_flHoldBreathTime
public ref float HoldBreathTime => ref Schema.GetRef<float>(this.Handle, "CBasePlayerVData", "m_flHoldBreathTime");
// m_flDrowningDamageInterval
public ref float DrowningDamageInterval => ref Schema.GetRef<float>(this.Handle, "CBasePlayerVData", "m_flDrowningDamageInterval");
// m_nDrowningDamageInitial
public ref Int32 DrowningDamageInitial => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerVData", "m_nDrowningDamageInitial");
// m_nDrowningDamageMax
public ref Int32 DrowningDamageMax => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerVData", "m_nDrowningDamageMax");
// m_nWaterSpeed
public ref Int32 WaterSpeed => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerVData", "m_nWaterSpeed");
// m_flUseRange
public ref float UseRange => ref Schema.GetRef<float>(this.Handle, "CBasePlayerVData", "m_flUseRange");
// m_flUseAngleTolerance
public ref float UseAngleTolerance => ref Schema.GetRef<float>(this.Handle, "CBasePlayerVData", "m_flUseAngleTolerance");
// m_flCrouchTime
public ref float CrouchTime => ref Schema.GetRef<float>(this.Handle, "CBasePlayerVData", "m_flCrouchTime");
}
public partial class CBasePlayerWeapon : CEconEntity
{
public CBasePlayerWeapon (IntPtr pointer) : base(pointer) {}
@@ -4857,6 +4902,70 @@ public partial class CBasePlayerWeapon : CEconEntity
}
public partial class CBasePlayerWeaponVData : CEntitySubclassVDataBase
{
public CBasePlayerWeaponVData (IntPtr pointer) : base(pointer) {}
// m_bBuiltRightHanded
public ref bool BuiltRightHanded => ref Schema.GetRef<bool>(this.Handle, "CBasePlayerWeaponVData", "m_bBuiltRightHanded");
// m_bAllowFlipping
public ref bool AllowFlipping => ref Schema.GetRef<bool>(this.Handle, "CBasePlayerWeaponVData", "m_bAllowFlipping");
// m_bIsFullAuto
public ref bool IsFullAuto => ref Schema.GetRef<bool>(this.Handle, "CBasePlayerWeaponVData", "m_bIsFullAuto");
// m_nNumBullets
public ref Int32 NumBullets => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerWeaponVData", "m_nNumBullets");
// m_sMuzzleAttachment
public string MuzzleAttachment
{
get { return Schema.GetUtf8String(this.Handle, "CBasePlayerWeaponVData", "m_sMuzzleAttachment"); }
set { Schema.SetString(this.Handle, "CBasePlayerWeaponVData", "m_sMuzzleAttachment", value); }
}
// m_iFlags
public ref ItemFlagTypes_t Flags => ref Schema.GetRef<ItemFlagTypes_t>(this.Handle, "CBasePlayerWeaponVData", "m_iFlags");
// m_nPrimaryAmmoType
public ref byte PrimaryAmmoType => ref Schema.GetRef<byte>(this.Handle, "CBasePlayerWeaponVData", "m_nPrimaryAmmoType");
// m_nSecondaryAmmoType
public ref byte SecondaryAmmoType => ref Schema.GetRef<byte>(this.Handle, "CBasePlayerWeaponVData", "m_nSecondaryAmmoType");
// m_iMaxClip1
public ref Int32 MaxClip1 => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerWeaponVData", "m_iMaxClip1");
// m_iMaxClip2
public ref Int32 MaxClip2 => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerWeaponVData", "m_iMaxClip2");
// m_iDefaultClip1
public ref Int32 DefaultClip1 => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerWeaponVData", "m_iDefaultClip1");
// m_iDefaultClip2
public ref Int32 DefaultClip2 => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerWeaponVData", "m_iDefaultClip2");
// m_iWeight
public ref Int32 Weight => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerWeaponVData", "m_iWeight");
// m_bAutoSwitchTo
public ref bool AutoSwitchTo => ref Schema.GetRef<bool>(this.Handle, "CBasePlayerWeaponVData", "m_bAutoSwitchTo");
// m_bAutoSwitchFrom
public ref bool AutoSwitchFrom => ref Schema.GetRef<bool>(this.Handle, "CBasePlayerWeaponVData", "m_bAutoSwitchFrom");
// m_iRumbleEffect
public ref RumbleEffect_t RumbleEffect => ref Schema.GetRef<RumbleEffect_t>(this.Handle, "CBasePlayerWeaponVData", "m_iRumbleEffect");
// m_iSlot
public ref Int32 Slot => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerWeaponVData", "m_iSlot");
// m_iPosition
public ref Int32 Position => ref Schema.GetRef<Int32>(this.Handle, "CBasePlayerWeaponVData", "m_iPosition");
}
public partial class CBaseProp : CBaseAnimGraph
{
public CBaseProp (IntPtr pointer) : base(pointer) {}
@@ -8932,6 +9041,269 @@ public partial class CCSWeaponBaseGun : CCSWeaponBase
}
public partial class CCSWeaponBaseVData : CBasePlayerWeaponVData
{
public CCSWeaponBaseVData (IntPtr pointer) : base(pointer) {}
// m_WeaponType
public ref CSWeaponType WeaponType => ref Schema.GetRef<CSWeaponType>(this.Handle, "CCSWeaponBaseVData", "m_WeaponType");
// m_WeaponCategory
public ref CSWeaponCategory WeaponCategory => ref Schema.GetRef<CSWeaponCategory>(this.Handle, "CCSWeaponBaseVData", "m_WeaponCategory");
// m_GearSlot
public ref gear_slot_t GearSlot => ref Schema.GetRef<gear_slot_t>(this.Handle, "CCSWeaponBaseVData", "m_GearSlot");
// m_GearSlotPosition
public ref Int32 GearSlotPosition => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_GearSlotPosition");
// m_DefaultLoadoutSlot
public ref loadout_slot_t DefaultLoadoutSlot => ref Schema.GetRef<loadout_slot_t>(this.Handle, "CCSWeaponBaseVData", "m_DefaultLoadoutSlot");
// m_sWrongTeamMsg
public string WrongTeamMsg
{
get { return Schema.GetUtf8String(this.Handle, "CCSWeaponBaseVData", "m_sWrongTeamMsg"); }
set { Schema.SetString(this.Handle, "CCSWeaponBaseVData", "m_sWrongTeamMsg", value); }
}
// m_nPrice
public ref Int32 Price => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nPrice");
// m_nKillAward
public ref Int32 KillAward => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nKillAward");
// m_nPrimaryReserveAmmoMax
public ref Int32 PrimaryReserveAmmoMax => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nPrimaryReserveAmmoMax");
// m_nSecondaryReserveAmmoMax
public ref Int32 SecondaryReserveAmmoMax => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nSecondaryReserveAmmoMax");
// m_bMeleeWeapon
public ref bool MeleeWeapon => ref Schema.GetRef<bool>(this.Handle, "CCSWeaponBaseVData", "m_bMeleeWeapon");
// m_bHasBurstMode
public ref bool HasBurstMode => ref Schema.GetRef<bool>(this.Handle, "CCSWeaponBaseVData", "m_bHasBurstMode");
// m_bIsRevolver
public ref bool IsRevolver => ref Schema.GetRef<bool>(this.Handle, "CCSWeaponBaseVData", "m_bIsRevolver");
// m_bCannotShootUnderwater
public ref bool CannotShootUnderwater => ref Schema.GetRef<bool>(this.Handle, "CCSWeaponBaseVData", "m_bCannotShootUnderwater");
// m_szName
public string Name
{
get { return Schema.GetUtf8String(this.Handle, "CCSWeaponBaseVData", "m_szName"); }
set { Schema.SetString(this.Handle, "CCSWeaponBaseVData", "m_szName", value); }
}
// m_szAnimExtension
public string AnimExtension
{
get { return Schema.GetUtf8String(this.Handle, "CCSWeaponBaseVData", "m_szAnimExtension"); }
set { Schema.SetString(this.Handle, "CCSWeaponBaseVData", "m_szAnimExtension", value); }
}
// m_eSilencerType
public ref CSWeaponSilencerType SilencerType => ref Schema.GetRef<CSWeaponSilencerType>(this.Handle, "CCSWeaponBaseVData", "m_eSilencerType");
// m_nCrosshairMinDistance
public ref Int32 CrosshairMinDistance => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nCrosshairMinDistance");
// m_nCrosshairDeltaDistance
public ref Int32 CrosshairDeltaDistance => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nCrosshairDeltaDistance");
// m_flCycleTime
public CFiringModeFloat CycleTime => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flCycleTime");
// m_flMaxSpeed
public CFiringModeFloat MaxSpeed => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flMaxSpeed");
// m_flSpread
public CFiringModeFloat Spread => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flSpread");
// m_flInaccuracyCrouch
public CFiringModeFloat InaccuracyCrouch => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyCrouch");
// m_flInaccuracyStand
public CFiringModeFloat InaccuracyStand => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyStand");
// m_flInaccuracyJump
public CFiringModeFloat InaccuracyJump => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyJump");
// m_flInaccuracyLand
public CFiringModeFloat InaccuracyLand => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyLand");
// m_flInaccuracyLadder
public CFiringModeFloat InaccuracyLadder => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyLadder");
// m_flInaccuracyFire
public CFiringModeFloat InaccuracyFire => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyFire");
// m_flInaccuracyMove
public CFiringModeFloat InaccuracyMove => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyMove");
// m_flRecoilAngle
public CFiringModeFloat RecoilAngle => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flRecoilAngle");
// m_flRecoilAngleVariance
public CFiringModeFloat RecoilAngleVariance => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flRecoilAngleVariance");
// m_flRecoilMagnitude
public CFiringModeFloat RecoilMagnitude => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flRecoilMagnitude");
// m_flRecoilMagnitudeVariance
public CFiringModeFloat RecoilMagnitudeVariance => Schema.GetDeclaredClass<CFiringModeFloat>(this.Handle, "CCSWeaponBaseVData", "m_flRecoilMagnitudeVariance");
// m_nTracerFrequency
public CFiringModeInt TracerFrequency => Schema.GetDeclaredClass<CFiringModeInt>(this.Handle, "CCSWeaponBaseVData", "m_nTracerFrequency");
// m_flInaccuracyJumpInitial
public ref float InaccuracyJumpInitial => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyJumpInitial");
// m_flInaccuracyJumpApex
public ref float InaccuracyJumpApex => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyJumpApex");
// m_flInaccuracyReload
public ref float InaccuracyReload => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyReload");
// m_nRecoilSeed
public ref Int32 RecoilSeed => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nRecoilSeed");
// m_nSpreadSeed
public ref Int32 SpreadSeed => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nSpreadSeed");
// m_flTimeToIdleAfterFire
public ref float TimeToIdleAfterFire => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flTimeToIdleAfterFire");
// m_flIdleInterval
public ref float IdleInterval => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flIdleInterval");
// m_flAttackMovespeedFactor
public ref float AttackMovespeedFactor => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flAttackMovespeedFactor");
// m_flHeatPerShot
public ref float HeatPerShot => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flHeatPerShot");
// m_flInaccuracyPitchShift
public ref float InaccuracyPitchShift => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyPitchShift");
// m_flInaccuracyAltSoundThreshold
public ref float InaccuracyAltSoundThreshold => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flInaccuracyAltSoundThreshold");
// m_flBotAudibleRange
public ref float BotAudibleRange => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flBotAudibleRange");
// m_szUseRadioSubtitle
public string UseRadioSubtitle
{
get { return Schema.GetUtf8String(this.Handle, "CCSWeaponBaseVData", "m_szUseRadioSubtitle"); }
set { Schema.SetString(this.Handle, "CCSWeaponBaseVData", "m_szUseRadioSubtitle", value); }
}
// m_bUnzoomsAfterShot
public ref bool UnzoomsAfterShot => ref Schema.GetRef<bool>(this.Handle, "CCSWeaponBaseVData", "m_bUnzoomsAfterShot");
// m_bHideViewModelWhenZoomed
public ref bool HideViewModelWhenZoomed => ref Schema.GetRef<bool>(this.Handle, "CCSWeaponBaseVData", "m_bHideViewModelWhenZoomed");
// m_nZoomLevels
public ref Int32 ZoomLevels => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nZoomLevels");
// m_nZoomFOV1
public ref Int32 ZoomFOV1 => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nZoomFOV1");
// m_nZoomFOV2
public ref Int32 ZoomFOV2 => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nZoomFOV2");
// m_flZoomTime0
public ref float ZoomTime0 => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flZoomTime0");
// m_flZoomTime1
public ref float ZoomTime1 => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flZoomTime1");
// m_flZoomTime2
public ref float ZoomTime2 => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flZoomTime2");
// m_flIronSightPullUpSpeed
public ref float IronSightPullUpSpeed => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flIronSightPullUpSpeed");
// m_flIronSightPutDownSpeed
public ref float IronSightPutDownSpeed => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flIronSightPutDownSpeed");
// m_flIronSightFOV
public ref float IronSightFOV => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flIronSightFOV");
// m_flIronSightPivotForward
public ref float IronSightPivotForward => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flIronSightPivotForward");
// m_flIronSightLooseness
public ref float IronSightLooseness => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flIronSightLooseness");
// m_angPivotAngle
public QAngle PivotAngle => Schema.GetDeclaredClass<QAngle>(this.Handle, "CCSWeaponBaseVData", "m_angPivotAngle");
// m_vecIronSightEyePos
public Vector IronSightEyePos => Schema.GetDeclaredClass<Vector>(this.Handle, "CCSWeaponBaseVData", "m_vecIronSightEyePos");
// m_nDamage
public ref Int32 Damage => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nDamage");
// m_flHeadshotMultiplier
public ref float HeadshotMultiplier => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flHeadshotMultiplier");
// m_flArmorRatio
public ref float ArmorRatio => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flArmorRatio");
// m_flPenetration
public ref float Penetration => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flPenetration");
// m_flRange
public ref float Range => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flRange");
// m_flRangeModifier
public ref float RangeModifier => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flRangeModifier");
// m_flFlinchVelocityModifierLarge
public ref float FlinchVelocityModifierLarge => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flFlinchVelocityModifierLarge");
// m_flFlinchVelocityModifierSmall
public ref float FlinchVelocityModifierSmall => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flFlinchVelocityModifierSmall");
// m_flRecoveryTimeCrouch
public ref float RecoveryTimeCrouch => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flRecoveryTimeCrouch");
// m_flRecoveryTimeStand
public ref float RecoveryTimeStand => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flRecoveryTimeStand");
// m_flRecoveryTimeCrouchFinal
public ref float RecoveryTimeCrouchFinal => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flRecoveryTimeCrouchFinal");
// m_flRecoveryTimeStandFinal
public ref float RecoveryTimeStandFinal => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flRecoveryTimeStandFinal");
// m_nRecoveryTransitionStartBullet
public ref Int32 RecoveryTransitionStartBullet => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nRecoveryTransitionStartBullet");
// m_nRecoveryTransitionEndBullet
public ref Int32 RecoveryTransitionEndBullet => ref Schema.GetRef<Int32>(this.Handle, "CCSWeaponBaseVData", "m_nRecoveryTransitionEndBullet");
// m_flThrowVelocity
public ref float ThrowVelocity => ref Schema.GetRef<float>(this.Handle, "CCSWeaponBaseVData", "m_flThrowVelocity");
// m_vSmokeColor
public Vector SmokeColor => Schema.GetDeclaredClass<Vector>(this.Handle, "CCSWeaponBaseVData", "m_vSmokeColor");
// m_szAnimClass
public string AnimClass
{
get { return Schema.GetUtf8String(this.Handle, "CCSWeaponBaseVData", "m_szAnimClass"); }
set { Schema.SetString(this.Handle, "CCSWeaponBaseVData", "m_szAnimClass", value); }
}
}
public partial class CDamageRecord : NativeObject
{
public CDamageRecord (IntPtr pointer) : base(pointer) {}
@@ -9443,6 +9815,12 @@ public partial class CEntityIOOutput : NativeObject
}
public partial class CEntitySubclassVDataBase : NativeObject
{
public CEntitySubclassVDataBase (IntPtr pointer) : base(pointer) {}
}
public partial class CEnvBeam : CBeam
{
public CEnvBeam (IntPtr pointer) : base(pointer) {}
@@ -10981,6 +11359,45 @@ public partial class CEnvWindShared : NativeObject
}
public partial class CExampleSchemaVData_Monomorphic : NativeObject
{
public CExampleSchemaVData_Monomorphic (IntPtr pointer) : base(pointer) {}
// m_nExample1
public ref Int32 Example1 => ref Schema.GetRef<Int32>(this.Handle, "CExampleSchemaVData_Monomorphic", "m_nExample1");
// m_nExample2
public ref Int32 Example2 => ref Schema.GetRef<Int32>(this.Handle, "CExampleSchemaVData_Monomorphic", "m_nExample2");
}
public partial class CExampleSchemaVData_PolymorphicBase : NativeObject
{
public CExampleSchemaVData_PolymorphicBase (IntPtr pointer) : base(pointer) {}
// m_nBase
public ref Int32 Base => ref Schema.GetRef<Int32>(this.Handle, "CExampleSchemaVData_PolymorphicBase", "m_nBase");
}
public partial class CExampleSchemaVData_PolymorphicDerivedA : CExampleSchemaVData_PolymorphicBase
{
public CExampleSchemaVData_PolymorphicDerivedA (IntPtr pointer) : base(pointer) {}
// m_nDerivedA
public ref Int32 DerivedA => ref Schema.GetRef<Int32>(this.Handle, "CExampleSchemaVData_PolymorphicDerivedA", "m_nDerivedA");
}
public partial class CExampleSchemaVData_PolymorphicDerivedB : CExampleSchemaVData_PolymorphicBase
{
public CExampleSchemaVData_PolymorphicDerivedB (IntPtr pointer) : base(pointer) {}
// m_nDerivedB
public ref Int32 DerivedB => ref Schema.GetRef<Int32>(this.Handle, "CExampleSchemaVData_PolymorphicDerivedB", "m_nDerivedB");
}
public partial class CFilterAttributeInt : CBaseFilter
{
public CFilterAttributeInt (IntPtr pointer) : base(pointer) {}
@@ -11194,6 +11611,24 @@ public partial class CFireSmoke : CBaseFire
}
public partial class CFiringModeFloat : NativeObject
{
public CFiringModeFloat (IntPtr pointer) : base(pointer) {}
// m_flValues
public Span<float> Values => Schema.GetFixedArray<float>(this.Handle, "CFiringModeFloat", "m_flValues", 2);
}
public partial class CFiringModeInt : NativeObject
{
public CFiringModeInt (IntPtr pointer) : base(pointer) {}
// m_nValues
public Span<Int32> Values => Schema.GetFixedArray<Int32>(this.Handle, "CFiringModeInt", "m_nValues", 2);
}
public partial class CFish : CBaseAnimGraph
{
public CFish (IntPtr pointer) : base(pointer) {}
@@ -14982,6 +15417,15 @@ public partial class CMomentaryRotButton : CRotButton
}
public partial class CMoodVData : NativeObject
{
public CMoodVData (IntPtr pointer) : base(pointer) {}
// m_nMoodType
public ref MoodType_t MoodType => ref Schema.GetRef<MoodType_t>(this.Handle, "CMoodVData", "m_nMoodType");
}
public partial class CMotorController : NativeObject
{
public CMotorController (IntPtr pointer) : base(pointer) {}
@@ -15078,6 +15522,70 @@ public partial class CMultiSource : CLogicalEntity
}
public partial class CNavHullPresetVData : NativeObject
{
public CNavHullPresetVData (IntPtr pointer) : base(pointer) {}
// m_vecNavHulls
public NetworkedVector<string> NavHulls => Schema.GetDeclaredClass<NetworkedVector<string>>(this.Handle, "CNavHullPresetVData", "m_vecNavHulls");
}
public partial class CNavHullVData : NativeObject
{
public CNavHullVData (IntPtr pointer) : base(pointer) {}
// m_bAgentEnabled
public ref bool AgentEnabled => ref Schema.GetRef<bool>(this.Handle, "CNavHullVData", "m_bAgentEnabled");
// m_agentRadius
public ref float AgentRadius => ref Schema.GetRef<float>(this.Handle, "CNavHullVData", "m_agentRadius");
// m_agentHeight
public ref float AgentHeight => ref Schema.GetRef<float>(this.Handle, "CNavHullVData", "m_agentHeight");
// m_agentShortHeightEnabled
public ref bool AgentShortHeightEnabled => ref Schema.GetRef<bool>(this.Handle, "CNavHullVData", "m_agentShortHeightEnabled");
// m_agentShortHeight
public ref float AgentShortHeight => ref Schema.GetRef<float>(this.Handle, "CNavHullVData", "m_agentShortHeight");
// m_agentMaxClimb
public ref float AgentMaxClimb => ref Schema.GetRef<float>(this.Handle, "CNavHullVData", "m_agentMaxClimb");
// m_agentMaxSlope
public ref Int32 AgentMaxSlope => ref Schema.GetRef<Int32>(this.Handle, "CNavHullVData", "m_agentMaxSlope");
// m_agentMaxJumpDownDist
public ref float AgentMaxJumpDownDist => ref Schema.GetRef<float>(this.Handle, "CNavHullVData", "m_agentMaxJumpDownDist");
// m_agentMaxJumpHorizDistBase
public ref float AgentMaxJumpHorizDistBase => ref Schema.GetRef<float>(this.Handle, "CNavHullVData", "m_agentMaxJumpHorizDistBase");
// m_agentMaxJumpUpDist
public ref float AgentMaxJumpUpDist => ref Schema.GetRef<float>(this.Handle, "CNavHullVData", "m_agentMaxJumpUpDist");
// m_agentBorderErosion
public ref Int32 AgentBorderErosion => ref Schema.GetRef<Int32>(this.Handle, "CNavHullVData", "m_agentBorderErosion");
}
public partial class CNavLinkAnimgraphVar : NativeObject
{
public CNavLinkAnimgraphVar (IntPtr pointer) : base(pointer) {}
// m_strAnimgraphVar
public string StrAnimgraphVar
{
get { return Schema.GetUtf8String(this.Handle, "CNavLinkAnimgraphVar", "m_strAnimgraphVar"); }
set { Schema.SetString(this.Handle, "CNavLinkAnimgraphVar", "m_strAnimgraphVar", value); }
}
// m_unAlignmentDegrees
public ref UInt32 AlignmentDegrees => ref Schema.GetRef<UInt32>(this.Handle, "CNavLinkAnimgraphVar", "m_unAlignmentDegrees");
}
public partial class CNavLinkAreaEntity : CPointEntity
{
public CNavLinkAreaEntity (IntPtr pointer) : base(pointer) {}
@@ -15135,6 +15643,21 @@ public partial class CNavLinkAreaEntity : CPointEntity
}
public partial class CNavLinkMovementVData : NativeObject
{
public CNavLinkMovementVData (IntPtr pointer) : base(pointer) {}
// m_bIsInterpolated
public ref bool IsInterpolated => ref Schema.GetRef<bool>(this.Handle, "CNavLinkMovementVData", "m_bIsInterpolated");
// m_unRecommendedDistance
public ref UInt32 RecommendedDistance => ref Schema.GetRef<UInt32>(this.Handle, "CNavLinkMovementVData", "m_unRecommendedDistance");
// m_vecAnimgraphVars
public NetworkedVector<CNavLinkAnimgraphVar> AnimgraphVars => Schema.GetDeclaredClass<NetworkedVector<CNavLinkAnimgraphVar>>(this.Handle, "CNavLinkMovementVData", "m_vecAnimgraphVars");
}
public partial class CNavSpaceInfo : CPointEntity
{
public CNavSpaceInfo (IntPtr pointer) : base(pointer) {}
@@ -17816,6 +18339,34 @@ public partial class CPrecipitationBlocker : CBaseModelEntity
}
public partial class CPrecipitationVData : CEntitySubclassVDataBase
{
public CPrecipitationVData (IntPtr pointer) : base(pointer) {}
// m_flInnerDistance
public ref float InnerDistance => ref Schema.GetRef<float>(this.Handle, "CPrecipitationVData", "m_flInnerDistance");
// m_nAttachType
public ref ParticleAttachment_t AttachType => ref Schema.GetRef<ParticleAttachment_t>(this.Handle, "CPrecipitationVData", "m_nAttachType");
// m_bBatchSameVolumeType
public ref bool BatchSameVolumeType => ref Schema.GetRef<bool>(this.Handle, "CPrecipitationVData", "m_bBatchSameVolumeType");
// m_nRTEnvCP
public ref Int32 RTEnvCP => ref Schema.GetRef<Int32>(this.Handle, "CPrecipitationVData", "m_nRTEnvCP");
// m_nRTEnvCPComponent
public ref Int32 RTEnvCPComponent => ref Schema.GetRef<Int32>(this.Handle, "CPrecipitationVData", "m_nRTEnvCPComponent");
// m_szModifier
public string Modifier
{
get { return Schema.GetUtf8String(this.Handle, "CPrecipitationVData", "m_szModifier"); }
set { Schema.SetString(this.Handle, "CPrecipitationVData", "m_szModifier", value); }
}
}
public partial class CPredictedViewModel : CBaseViewModel
{
public CPredictedViewModel (IntPtr pointer) : base(pointer) {}
@@ -18109,6 +18660,15 @@ public partial class CRagdollPropAttached : CRagdollProp
}
public partial class CRangeFloat : NativeObject
{
public CRangeFloat (IntPtr pointer) : base(pointer) {}
// m_pValue
public Span<float> Value => Schema.GetFixedArray<float>(this.Handle, "CRangeFloat", "m_pValue", 2);
}
public partial class CRectLight : CBarnLight
{
public CRectLight (IntPtr pointer) : base(pointer) {}
@@ -18958,6 +19518,24 @@ public partial class CSkeletonInstance : CGameSceneNode
}
public partial class CSkillFloat : NativeObject
{
public CSkillFloat (IntPtr pointer) : base(pointer) {}
// m_pValue
public Span<float> Value => Schema.GetFixedArray<float>(this.Handle, "CSkillFloat", "m_pValue", 4);
}
public partial class CSkillInt : NativeObject
{
public CSkillInt (IntPtr pointer) : base(pointer) {}
// m_pValue
public Span<Int32> Value => Schema.GetFixedArray<Int32>(this.Handle, "CSkillInt", "m_pValue", 4);
}
public partial class CSkyboxReference : CBaseEntity
{
public CSkyboxReference (IntPtr pointer) : base(pointer) {}
@@ -21386,6 +21964,12 @@ public partial class InfoForResourceTypeCTextureBase : NativeObject
}
public partial class InfoForResourceTypeCVDataResource : NativeObject
{
public InfoForResourceTypeCVDataResource (IntPtr pointer) : base(pointer) {}
}
public partial class InfoForResourceTypeIMaterial2 : NativeObject
{
public InfoForResourceTypeIMaterial2 (IntPtr pointer) : base(pointer) {}

View File

@@ -6,6 +6,7 @@ public interface IPluginContext
IPlugin Plugin { get; }
int PluginId { get; }
string FilePath { get; }
void Load(bool hotReload);
void Unload(bool hotReload);
}

View File

@@ -20,8 +20,11 @@ using System.Threading.Tasks;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Logging;
using CounterStrikeSharp.API.Core.Translations;
using McMaster.NETCore.Plugins;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
@@ -43,6 +46,8 @@ namespace CounterStrikeSharp.API.Core.Plugin
private readonly string _path;
private readonly FileSystemWatcher _fileWatcher;
private readonly IServiceProvider _applicationServiceProvider;
public string FilePath => _path;
// TOOD: ServiceCollection
private ILogger _logger = CoreLogging.Factory.CreateLogger<PluginContext>();
@@ -163,8 +168,12 @@ namespace CounterStrikeSharp.API.Core.Plugin
method?.Invoke(pluginServiceCollection, new object[] { serviceCollection });
}
}
serviceCollection.AddSingleton<IPluginContext>(this);
serviceCollection.TryAddSingleton<IStringLocalizerFactory, JsonStringLocalizerFactory>();
serviceCollection.TryAddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
serviceCollection.TryAddTransient(typeof(IStringLocalizer), typeof(StringLocalizer));
serviceCollection.AddSingleton(this);
ServiceProvider = serviceCollection.BuildServiceProvider();
var minimumApiVersion = pluginType.GetCustomAttribute<MinimumApiVersion>()?.Version;
@@ -185,6 +194,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
Plugin.ModulePath = _path;
Plugin.RegisterAllAttributes(Plugin);
Plugin.Localizer = ServiceProvider.GetRequiredService<IStringLocalizer>();
Plugin.Logger = ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(pluginType);
Plugin.InitializeConfig(Plugin, pluginType);

View File

@@ -0,0 +1,11 @@
using System.Globalization;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Core.Translations;
public interface IPlayerLanguageManager
{
void SetLanguage(SteamID steamId, CultureInfo cultureInfo);
CultureInfo GetLanguage(SteamID steamId);
CultureInfo GetDefaultLanguage();
}

View File

@@ -0,0 +1,153 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
namespace CounterStrikeSharp.API.Core.Translations
{
public class JsonResourceManager
{
private static readonly JsonDocumentOptions _jsonDocumentOptions = new()
{
CommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
private ConcurrentDictionary<string, ConcurrentDictionary<string, string>> _resourcesCache = new();
public JsonResourceManager(string resourcesPath)
{
ResourcesPath = resourcesPath;
}
public string ResourcesPath { get; }
public virtual ConcurrentDictionary<string, string> GetResourceSet(string cultureName)
{
TryLoadResourceSet(cultureName);
_resourcesCache.TryGetValue(cultureName, out ConcurrentDictionary<string, string> resources);
return resources;
}
public virtual ConcurrentDictionary<string, string> GetResourceSet(CultureInfo culture, bool tryParents)
{
TryLoadResourceSet(culture);
if (tryParents)
{
var allResources = new ConcurrentDictionary<string, string>();
do
{
TryLoadResourceSet(culture);
if (_resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary<string, string> resources))
{
foreach (var entry in resources)
{
allResources.TryAdd(entry.Key, entry.Value);
}
}
culture = culture.Parent;
} while (culture != CultureInfo.InvariantCulture);
return allResources;
}
else
{
_resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary<string, string> resources);
return resources;
}
}
public string GetFallbackString(string name)
{
GetResourceSet("en");
if (_resourcesCache.ContainsKey("en"))
{
if (_resourcesCache["en"].TryGetValue(name, out string value))
{
return value;
}
}
return null;
}
public virtual string GetString(string name)
{
var culture = CultureInfo.CurrentUICulture;
GetResourceSet(culture, tryParents: true);
if (_resourcesCache.Count == 0)
{
return null;
}
do
{
if (_resourcesCache.ContainsKey(culture.Name))
{
if (_resourcesCache[culture.Name].TryGetValue(name, out string value))
{
return value;
}
}
culture = culture.Parent;
} while (culture != culture.Parent);
return null;
}
public virtual string? GetString(string name, CultureInfo culture)
{
var values = GetResourceSet(culture, tryParents: true);
if (values.Count == 0)
{
return null;
}
return values.TryGetValue(name, out string value)
? value
: null;
}
private void TryLoadResourceSet(string cultureName)
{
if (!_resourcesCache.ContainsKey(cultureName))
{
var file = Path.Combine(ResourcesPath, $"{cultureName}.json");
var resources = LoadJsonResources(file);
_resourcesCache.TryAdd(cultureName,
new ConcurrentDictionary<string, string>(resources.ToDictionary(r => r.Key, r => r.Value)));
}
}
private void TryLoadResourceSet(CultureInfo culture)
{
TryLoadResourceSet(culture.Name);
}
private static IDictionary<string, string> LoadJsonResources(string filePath)
{
var resources = new Dictionary<string, string>();
if (File.Exists(filePath))
{
using var reader = new StreamReader(filePath);
using var document = JsonDocument.Parse(reader.BaseStream, _jsonDocumentOptions);
resources = document.RootElement.EnumerateObject().ToDictionary(e => e.Name, e => e.Value.ToString());
}
return resources;
}
}
}

View File

@@ -0,0 +1,122 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Resources;
using Microsoft.Extensions.Localization;
namespace CounterStrikeSharp.API.Core.Translations;
public class JsonStringLocalizer : IStringLocalizer
{
private readonly JsonResourceManager _resourceManager;
private readonly JsonStringProvider _resourceStringProvider;
public JsonStringLocalizer(string langPath)
{
_resourceManager = new JsonResourceManager(langPath);
_resourceStringProvider = new(_resourceManager);
}
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
{
return GetAllStrings(includeParentCultures, CultureInfo.CurrentUICulture);
}
public LocalizedString this[string name]
{
get
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var value = GetStringSafely(name);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
}
}
public LocalizedString this[string name, params object[] arguments]
{
get
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var format = GetStringSafely(name);
var value = string.Format(format ?? name, arguments);
return new LocalizedString(name, value, resourceNotFound: format == null);
}
}
protected string? GetStringSafely(string name, CultureInfo? culture = null)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
culture = culture ?? CultureInfo.CurrentUICulture;
var result = _resourceManager.GetString(name, culture);
// Fallback to en if running in invariant mode.
if (result == null && culture.Equals(CultureInfo.InvariantCulture))
{
result = _resourceManager.GetFallbackString(name);
}
// Fallback to the default culture (en-US) if the resource is not found for the current culture.
if (result == null && !culture.Equals(CultureInfo.DefaultThreadCurrentUICulture))
{
result = _resourceManager.GetString(name, CultureInfo.DefaultThreadCurrentUICulture!);
}
return result?.ReplaceColorTags();
}
protected virtual IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures, CultureInfo culture)
{
if (culture == null)
{
throw new ArgumentNullException(nameof(culture));
}
var resourceNames = includeParentCultures
? GetResourceNamesFromCultureHierarchy(culture)
: _resourceStringProvider.GetAllResourceStrings(culture, true);
foreach (var name in resourceNames)
{
var value = GetStringSafely(name, culture);
yield return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
}
}
private IEnumerable<string> GetResourceNamesFromCultureHierarchy(CultureInfo startingCulture)
{
var currentCulture = startingCulture;
var resourceNames = new HashSet<string>();
while (currentCulture != currentCulture.Parent)
{
var cultureResourceNames = _resourceStringProvider.GetAllResourceStrings(currentCulture, false);
if (cultureResourceNames != null)
{
foreach (var resourceName in cultureResourceNames)
{
resourceNames.Add(resourceName);
}
}
currentCulture = currentCulture.Parent;
}
return resourceNames;
}
}

View File

@@ -0,0 +1,25 @@
using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Plugin;
using Microsoft.Extensions.Localization;
namespace CounterStrikeSharp.API.Core.Translations;
public class JsonStringLocalizerFactory : IStringLocalizerFactory
{
private readonly IPluginContext _pluginContext;
public JsonStringLocalizerFactory(IPluginContext pluginContext)
{
_pluginContext = pluginContext;
}
public IStringLocalizer Create(Type resourceSource)
{
return new JsonStringLocalizer(Path.Join(Path.GetDirectoryName(_pluginContext.FilePath), "lang"));
}
public IStringLocalizer Create(string baseName, string location)
{
return new JsonStringLocalizer(Path.Join(Path.GetDirectoryName(_pluginContext.FilePath), "lang"));
}
}

View File

@@ -0,0 +1,51 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Resources;
namespace CounterStrikeSharp.API.Core.Translations;
public class JsonStringProvider
{
private readonly ConcurrentDictionary<string, IList<string>> _resourceNamesCache = new();
private readonly JsonResourceManager _jsonResourceManager;
public JsonStringProvider(JsonResourceManager jsonResourceManager)
{
_jsonResourceManager = jsonResourceManager;
}
private string GetResourceCacheKey(CultureInfo culture)
{
return $"Culture={culture.Name}";
}
public IList<string> GetAllResourceStrings(CultureInfo culture, bool throwOnMissing)
{
var cacheKey = GetResourceCacheKey(culture);
return _resourceNamesCache.GetOrAdd(cacheKey, _ =>
{
var resourceSet = _jsonResourceManager.GetResourceSet(culture, tryParents: false);
if (resourceSet == null)
{
if (throwOnMissing)
{
throw new MissingManifestResourceException($"The manifest resource for the culture '{culture.Name}' is missing.");
}
else
{
return null;
}
}
var names = new List<string>();
foreach (var entry in resourceSet)
{
names.Add(entry.Key);
}
return names;
});
}
}

View File

@@ -0,0 +1,17 @@
using System.Globalization;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Core.Translations;
public static class PlayerLanguageExtensions
{
/// <summary>
/// Returns the players configured language, as set using the "css_lang" command.
/// </summary>
public static CultureInfo GetLanguage(this CCSPlayerController? player)
{
if (player == null || !player.IsValid) return PlayerLanguageManager.Instance.GetDefaultLanguage();
return PlayerLanguageManager.Instance.GetLanguage((SteamID)player.SteamID);
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Concurrent;
using System.Globalization;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Core.Translations;
public class PlayerLanguageManager : IPlayerLanguageManager
{
private readonly ConcurrentDictionary<SteamID, CultureInfo> _playerLanguages = new();
public static IPlayerLanguageManager Instance { get; private set; } = null!;
public PlayerLanguageManager()
{
Instance = this;
}
public void SetLanguage(SteamID steamId, CultureInfo cultureInfo)
{
_playerLanguages[steamId] = cultureInfo;
}
public CultureInfo GetLanguage(SteamID steamId)
{
return _playerLanguages.TryGetValue(steamId, out var cultureInfo) ? cultureInfo : GetDefaultLanguage();
}
public CultureInfo GetDefaultLanguage()
{
return CultureInfo.DefaultThreadCurrentUICulture!;
}
}

View File

@@ -0,0 +1,22 @@
using System.Reflection;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Core.Translations;
public static class StringExtensions
{
public static string ReplaceColorTags(this string message)
{
var modifiedValue = message;
foreach (var field in typeof(ChatColors).GetFields())
{
string pattern = $"{{{field.Name}}}";
if (modifiedValue.Contains(pattern, StringComparison.OrdinalIgnoreCase))
{
modifiedValue = modifiedValue.Replace(pattern, field.GetValue(null)?.ToString() ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
}
return modifiedValue.Equals(message) ? message : $" {modifiedValue}";
}
}

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Extensions.Localization;
namespace CounterStrikeSharp.API.Core.Translations;
internal class StringLocalizer : IStringLocalizer
{
private IStringLocalizer _localizer;
public StringLocalizer(IStringLocalizerFactory factory)
{
var type = typeof(StringLocalizer);
var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
_localizer = factory.Create(string.Empty, assemblyName.FullName);
}
public LocalizedString this[string name] => _localizer[name];
public LocalizedString this[string name, params object[] arguments] => _localizer[name, arguments];
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) => _localizer.GetAllStrings(includeParentCultures);
}

View File

@@ -0,0 +1,25 @@
using System.Globalization;
namespace CounterStrikeSharp.API.Core.Translations;
public sealed class WithTemporaryCulture : IDisposable
{
private readonly CultureInfo _originalCulture;
public WithTemporaryCulture(CultureInfo culture)
{
_originalCulture = CultureInfo.CurrentCulture;
SetCulture(culture);
}
private void SetCulture(CultureInfo cultureInfo)
{
CultureInfo.CurrentCulture = cultureInfo;
CultureInfo.CurrentUICulture = cultureInfo;
}
public void Dispose()
{
SetCulture(_originalCulture);
}
}

View File

@@ -25,6 +25,7 @@
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Scrutor" Version="4.2.2" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />

View File

@@ -2,12 +2,14 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Modules.Admin
{
@@ -64,18 +66,18 @@ namespace CounterStrikeSharp.API.Modules.Admin
// 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)
foreach (var (admin, data) in Admins)
{
var groups = adminData.Groups;
var groups = data.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)
AddPlayerPermissions(admin, groupData.Flags.ToArray());
if (groupData.Immunity > data.Immunity)
{
adminData.Immunity = groupData.Immunity;
data.Immunity = groupData.Immunity;
}
}
}
@@ -94,8 +96,20 @@ namespace CounterStrikeSharp.API.Modules.Admin
// 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.AuthorizedSteamID);
return playerData?.Groups.IsSupersetOf(groups) ?? false;
var playerData = GetPlayerAdminData(player.AuthorizedSteamID);
if (playerData == null) return false;
var playerGroups = groups.ToHashSet();
foreach (var domain in playerData.Flags)
{
if (playerData.DomainHasRootFlag(domain.Key))
{
playerGroups.ExceptWith(groups.Where(group => group.Contains(domain.Key + '/')));
}
}
return playerData.Groups.IsSupersetOf(playerGroups);
}
/// <summary>
@@ -104,10 +118,22 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// <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)
public static bool PlayerInGroup(SteamID? steamId, params string[] groups)
{
if (steamId == null) return false;
var playerData = GetPlayerAdminData(steamId);
return playerData?.Groups.IsSupersetOf(groups) ?? false;
if (playerData == null) return false;
var playerGroups = groups.ToHashSet<string>();
foreach (var domain in playerData.Flags)
{
if (playerData.DomainHasRootFlag(domain.Key))
{
playerGroups.ExceptWith(groups.Where(group => group.Contains(domain.Key + '/')));
}
}
return playerData.Groups.IsSupersetOf(playerGroups);
}
/// <summary>
@@ -119,7 +145,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
AddPlayerToGroup((SteamID)player.AuthorizedSteamID, groups);
AddPlayerToGroup(player.AuthorizedSteamID, groups);
}
/// <summary>
@@ -127,8 +153,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// </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)
public static void AddPlayerToGroup(SteamID? steamId, params string[] groups)
{
if (steamId == null) return;
var data = GetPlayerAdminData(steamId);
if (data == null)
{
@@ -144,7 +171,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (Groups.TryGetValue(group, out var groupDef))
{
data.Flags.UnionWith(groupDef.Flags);
AddPlayerPermissions(steamId, groupDef.Flags.ToArray());
groupDef.CommandOverrides.ToList().ForEach(x => data.CommandOverrides[x.Key] = x.Value);
}
}
@@ -161,7 +188,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
RemovePlayerFromGroup((SteamID)player.AuthorizedSteamID, true, groups);
RemovePlayerFromGroup(player.AuthorizedSteamID, true, groups);
}
/// <summary>
@@ -170,8 +197,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// <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)
public static void RemovePlayerFromGroup(SteamID? steamId, bool removeInheritedFlags = true, params string[] groups)
{
if (steamId == null) return;
var data = GetPlayerAdminData(steamId);
if (data == null) return;
@@ -183,7 +211,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (Groups.TryGetValue(group, out var groupDef))
{
data.Flags.ExceptWith(groupDef.Flags);
RemovePlayerPermissions(steamId, groupDef.Flags.ToArray());
}
}
}

View File

@@ -1,18 +1,14 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Utils;
using System.IO;
using System.Linq;
using System.Reflection;
using CounterStrikeSharp.API.Core.Logging;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Modules.Admin
{
public static partial class AdminManager
{
static AdminManager()
public static void AddCommands()
{
CommandUtils.AddStandaloneCommand("css_admins_reload", "Reloads the admin file.", ReloadAdminsCommand);
CommandUtils.AddStandaloneCommand("css_admins_list", "List admins and their flags.", ListAdminsCommand);
@@ -37,6 +33,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
Admins.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
LoadAdminData(Path.Combine(rootDir.FullName, "configs", "admins.json"));
MergeGroupPermsIntoAdmins();
}
[RequiresPermissions(permissions:"@css/generic")]
@@ -45,7 +42,11 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
foreach (var (steamId, data) in Admins)
{
command.ReplyToCommand($"{steamId.SteamId64}, {steamId.SteamId2} - {string.Join(", ", data.Flags)}");
command.ReplyToCommand($"{steamId.SteamId64}, {steamId.SteamId2} - FLAGS: ");
foreach (var domain in data.Flags.Keys)
{
command.ReplyToCommand($" Domain {domain}: {string.Join(", ", data.Flags[domain])}");
}
}
}
@@ -56,6 +57,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
Groups.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
LoadAdminGroups(Path.Combine(rootDir.FullName, "configs", "admin_groups.json"));
MergeGroupPermsIntoAdmins();
}
[RequiresPermissions(permissions: "@css/generic")]

View File

@@ -3,24 +3,110 @@ 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;
using CounterStrikeSharp.API.Core.Logging;
using Microsoft.Extensions.Logging;
using System.Text.Json.Nodes;
using System.Numerics;
using CounterStrikeSharp.API.Modules.Utils;
using System.Diagnostics.Eventing.Reader;
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();
// Flags loaded from file. Do not use this for actual comparisons.
[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();
// Key is the domain of the flag "e.g "css, os, kzsurf"). This should NOT include the @ character.
// Value is a hashmap of the flags inside of the domain (e.g "@css/generic")
public Dictionary<string, HashSet<string>> Flags { get; init; } = new();
public void InitalizeFlags()
{
AddFlags(_flags);
}
/// <summary>
/// Checks to see if a domain has a root flag inside of it.
/// </summary>
/// <param name="domain">Domain to check for.</param>
/// <returns>True if "@{domain}/root" or "@{domain}/*" is present, false if not.</returns>
public bool DomainHasRootFlag(string domain)
{
if (!Flags.ContainsKey(domain)) return false;
if (Flags[domain].Contains("@" + domain + "/root")) return true;
else if (Flags[domain].Contains("@" + domain + "/*")) return true;
else return false;
}
/// <summary>
/// Returns a list of all domains for flags.
/// </summary>
/// <returns></returns>
public string[] GetFlagDomains()
{
return Flags.Keys.ToArray();
}
/// <summary>
/// Returns a HashSet of all flags.
/// </summary>
/// <returns></returns>
public HashSet<string> GetAllFlags()
{
var flags = new HashSet<string>();
foreach (var domainFlags in Flags.Values)
{
flags.UnionWith(domainFlags);
}
return flags;
}
public void AddFlags(HashSet<string> flags)
{
var domains = flags.Where(
flag => flag.StartsWith(PermissionCharacters.UserPermissionChar))
.Distinct()
.Select(domain => domain.Split('/').First()[1..]);
foreach (var domain in domains)
{
if (!Flags.ContainsKey(domain))
{
Flags[domain] = new HashSet<string>();
}
Flags[domain].UnionWith(flags.Where(flag => flag.StartsWith(PermissionCharacters.UserPermissionChar + domain + '/')).ToHashSet());
}
}
public void RemoveFlags(HashSet<string> flags)
{
var domains = flags.Where(
flag => flag.StartsWith(PermissionCharacters.UserPermissionChar))
.Distinct()
.Select(domain => domain.Split('/').First()[1..]);
foreach (var domain in domains)
{
if (!Flags.ContainsKey(domain)) continue;
var domainFlags = flags.Where(flag => flag.StartsWith(PermissionCharacters.UserPermissionChar + domain + '/')).ToHashSet();
Flags[domain].ExceptWith(flags);
if (Flags[domain].Count() == 0) Flags.Remove(domain);
}
}
public bool DomainHasFlags(string domain, string[] flags, bool ignoreRoot = false)
{
if (!Flags.ContainsKey(domain)) return false;
if (DomainHasRootFlag(domain) && !ignoreRoot) return true;
return Flags[domain].IsSupersetOf(flags);
}
}
public static partial class AdminManager
@@ -39,16 +125,28 @@ namespace CounterStrikeSharp.API.Modules.Admin
_logger.LogWarning("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 });
var settings = new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip };
var adminsFromFile = JsonSerializer.Deserialize<Dictionary<string, AdminData>>(File.ReadAllText(adminDataPath), settings);
if (adminsFromFile == null) { throw new FileNotFoundException(); }
foreach (var adminDef in adminsFromFile.Values)
{
adminDef.InitalizeFlags();
Console.WriteLine($"Domains: {adminDef.Flags.Count}");
if (SteamID.TryParse(adminDef.Identity, out var steamId))
{
if (Admins.ContainsKey(steamId!))
{
Admins[steamId!].Flags.UnionWith(adminDef.Flags);
// Merge domains together if we already have pre-existing values.
foreach (var (domain, flags) in adminDef.Flags)
{
if (Admins[steamId!].Flags.ContainsKey(domain))
{
Admins[steamId!].Flags[domain].UnionWith(flags);
}
}
if (adminDef.Immunity > Admins[steamId!].Immunity)
{
Admins[steamId!].Immunity = adminDef.Immunity;
@@ -74,8 +172,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// </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)
public static AdminData? GetPlayerAdminData(SteamID? steamId)
{
if (steamId == null) return null;
return Admins.GetValueOrDefault(steamId);
}
@@ -83,8 +182,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// 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)
public static void RemovePlayerAdminData(SteamID? steamId)
{
if (steamId == null) return;
Admins.Remove(steamId);
}
@@ -102,8 +202,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// 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(player.AuthorizedSteamID);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
return PlayerHasPermissions(player.AuthorizedSteamID, flags);
}
/// <summary>
@@ -112,10 +211,36 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// <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)
public static bool PlayerHasPermissions(SteamID? steamId, params string[] flags)
{
if (steamId == null) return false;
var playerData = GetPlayerAdminData(steamId);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
if (playerData == null) return false;
// Check to see that all of the domains in the flags that we're checking are
// present in our player data.
var localDomains = flags.Where(
flag => flag.StartsWith(PermissionCharacters.UserPermissionChar))
.Distinct()
.Select(domain => domain.Split('/').First()[1..])
.ToHashSet();
var playerFlagDomains = playerData.GetFlagDomains().ToHashSet();
if (!playerFlagDomains.IsSupersetOf(localDomains)) return false;
// Loop through all of the domains and see if we have the required flags
// for every domain.
bool returnValue = true;
foreach (var domain in playerData.Flags)
{
if (!playerData.DomainHasFlags(domain.Key,
flags
.Where(flag => flag.StartsWith(PermissionCharacters.UserPermissionChar + domain.Key + '/'))
.ToArray()))
{
returnValue = false; break;
}
}
return returnValue;
}
#endregion
@@ -136,7 +261,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// 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.AuthorizedSteamID);
var playerData = GetPlayerAdminData(player.AuthorizedSteamID);
return playerData?.CommandOverrides.ContainsKey(command) ?? false;
}
@@ -147,8 +272,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// <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)
public static bool PlayerHasCommandOverride(SteamID? steamId, string command)
{
if (steamId == null) return false;
var playerData = GetPlayerAdminData(steamId);
return playerData?.CommandOverrides.ContainsKey(command) ?? false;
}
@@ -165,7 +291,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// 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.AuthorizedSteamID);
var playerData = GetPlayerAdminData(player.AuthorizedSteamID);
return playerData?.CommandOverrides.GetValueOrDefault(command) ?? false;
}
@@ -175,8 +301,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// <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)
public static bool GetPlayerCommandOverrideState(SteamID? steamId, string command)
{
if (steamId == null) return false;
var playerData = GetPlayerAdminData(steamId);
return playerData?.CommandOverrides.GetValueOrDefault(command) ?? false;
}
@@ -193,7 +320,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// 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.AuthorizedSteamID, command, state);
SetPlayerCommandOverride(player.AuthorizedSteamID, command, state);
}
/// <summary>
@@ -202,8 +329,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// <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)
public static void SetPlayerCommandOverride(SteamID? steamId, string command, bool state)
{
if (steamId == null) return;
var data = GetPlayerAdminData(steamId);
if (data == null)
{
@@ -235,7 +363,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
AddPlayerPermissions((SteamID)player.AuthorizedSteamID, flags);
AddPlayerPermissions(player.AuthorizedSteamID, flags);
}
/// <summary>
@@ -244,27 +372,23 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// </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)
public static void AddPlayerPermissions(SteamID? steamId, params string[] flags)
{
if (steamId == null) return;
var data = GetPlayerAdminData(steamId);
if (data == null)
{
data = new AdminData()
{
Identity = steamId.SteamId64.ToString(),
Flags = new(flags),
Flags = new(),
Groups = new()
};
Admins[steamId] = data;
return;
}
foreach (var flag in flags)
{
data.Flags.Add(flag);
}
Admins[steamId] = data;
Admins[steamId].AddFlags(flags.ToHashSet<string>());
}
/// <summary>
@@ -278,7 +402,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
RemovePlayerPermissions((SteamID)player.AuthorizedSteamID, flags);
RemovePlayerPermissions(player.AuthorizedSteamID, flags);
}
/// <summary>
@@ -287,13 +411,12 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// </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)
public static void RemovePlayerPermissions(SteamID? steamId, params string[] flags)
{
if (steamId == null) return;
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Flags.ExceptWith(flags);
Admins[steamId] = data;
Admins[steamId].RemoveFlags(flags.ToHashSet<string>());
}
/// <summary>
@@ -306,7 +429,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
ClearPlayerPermissions((SteamID)player.AuthorizedSteamID);
ClearPlayerPermissions(player.AuthorizedSteamID);
}
/// <summary>
@@ -314,8 +437,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">Steam ID to remove flags from.</param>
public static void ClearPlayerPermissions(SteamID steamId)
public static void ClearPlayerPermissions(SteamID? steamId)
{
if (steamId == null) return;
var data = GetPlayerAdminData(steamId);
if (data == null) return;
@@ -335,7 +459,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
SetPlayerImmunity((SteamID)player.AuthorizedSteamID, value);
SetPlayerImmunity(player.AuthorizedSteamID, value);
}
/// <summary>
@@ -343,8 +467,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// </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)
public static void SetPlayerImmunity(SteamID? steamId, uint value)
{
if (steamId == null) return;
var data = GetPlayerAdminData(steamId);
if (data == null) return;
@@ -366,10 +491,10 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (target == null) return false;
if (!target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected) return false;
var callerData = GetPlayerAdminData((SteamID)caller.AuthorizedSteamID);
var callerData = GetPlayerAdminData(caller.AuthorizedSteamID);
if (callerData == null) return false;
var targetData = GetPlayerAdminData((SteamID)target.AuthorizedSteamID);
var targetData = GetPlayerAdminData(caller.AuthorizedSteamID);
if (targetData == null) return true;
return callerData.Immunity >= targetData.Immunity;
@@ -381,8 +506,11 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// <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)
public static bool CanPlayerTarget(SteamID? caller, SteamID? target)
{
if (caller == null) return false;
if (target == null) return false;
var callerData = GetPlayerAdminData(caller);
if (callerData == null) return false;

View File

@@ -11,7 +11,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
/// <summary>
/// The permissions for the command.
/// </summary>
public string[] Permissions { get; }
public HashSet<string> Permissions { get; }
/// <summary>
/// The name of the command that is attached to this attribute.
/// </summary>
@@ -23,7 +23,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
public BaseRequiresPermissions(params string[] permissions)
{
Permissions = permissions;
Permissions = permissions.ToHashSet();
Command = "";
}

View File

@@ -24,8 +24,14 @@ namespace CounterStrikeSharp.API.Modules.Admin
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;
if (!AdminManager.PlayerHasPermissions(caller, userPermissions.ToArray()))
{
return false;
}
if (!AdminManager.PlayerInGroup(caller, groupPermissions.ToArray()))
{
return false;
}
return true;
}

View File

@@ -20,12 +20,28 @@ namespace CounterStrikeSharp.API.Modules.Admin
}
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(caller.AuthorizedSteamID);
if (adminData == null) return false;
return (groupPermissions.Intersect(adminData.Groups).Count() + userPermissions.Intersect(adminData.Flags).Count()) > 0;
// Check to see if the caller has a root flag for any of the domains in our permissions.
// If they do, remove all of the user flags and groups that belong to the domain
// from our permission check.
var domains = Permissions.Where(
flag => flag.StartsWith(PermissionCharacters.GroupPermissionChar))
.Distinct()
.Select(domain => domain.Split('/').First()[1..]);
foreach (var domain in domains)
{
if (adminData.DomainHasRootFlag(domain))
{
Permissions.RemoveWhere(flag => flag.Contains(domain + '/'));
}
}
var groupPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.GroupPermissionChar));
var userPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.UserPermissionChar));
return (groupPermissions.Intersect(adminData.Groups).Count() + userPermissions.Intersect(adminData.GetAllFlags()).Count()) > 0;
}
}
}

View File

@@ -29,7 +29,7 @@ namespace CounterStrikeSharp.API.Modules.Commands
public IntPtr Handle { get; }
internal CommandInfo(IntPtr pointer, CCSPlayerController player)
internal CommandInfo(IntPtr pointer, CCSPlayerController? player)
{
Handle = pointer;
CallingPlayer = player;

View File

@@ -8,20 +8,25 @@ namespace CounterStrikeSharp.API.Modules.Entities
const long Base = 76561197960265728;
public ulong SteamId64 { get; set; }
public SteamID(ulong id) => SteamId64 = id;
public SteamID(string id) => SteamId64 = id.StartsWith("[") ? ParseId3(id) : ParseId(id);
public SteamID(ulong id)
{
if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id));
SteamId64 = id >= Base ? id : id + Base;
}
public SteamID(string id) : this(id.StartsWith('[') ? ParseId3(id) : ParseId(id)) { }
public static explicit operator SteamID(ulong u) => new(u);
public static explicit operator SteamID(string s) => new(s);
ulong ParseId(string id)
static ulong ParseId(string id)
{
var parts = id.Split(':');
if (parts.Length != 3 || !ulong.TryParse(parts[2], out var num)) throw new FormatException();
return Base + num * 2 + (parts[1] == "1" ? 1UL : 0);
return Base + (num * 2) + (parts[1] == "1" ? 1UL : 0);
}
ulong ParseId3(string id)
static ulong ParseId3(string id)
{
var parts = id.Replace("[", "").Replace("]", "").Split(':');
if (parts.Length != 3 || !ulong.TryParse(parts[2], out var num)) throw new FormatException();
@@ -39,34 +44,34 @@ namespace CounterStrikeSharp.API.Modules.Entities
get => $"[{EnumUtils.GetEnumMemberAttributeValue(AccountType)}:{(int)AccountUniverse}:{SteamId64 - Base}]";
set => SteamId64 = ParseId3(value);
}
public int SteamId32
{
get => (int)(SteamId64 - Base);
set => SteamId64 = (ulong)value + Base;
}
public int AccountId => (int)((SteamId64 >> 0) & 0xFFFFFFFF);
public int AccountId => (int)(SteamId64 & 0xFFFFFFFF);
public SteamAccountInstance AccountInstance =>
public SteamAccountInstance AccountInstance =>
(SteamAccountInstance)((SteamId64 >> 32) & 0xFFFFF);
public SteamAccountType AccountType =>
public SteamAccountType AccountType =>
(SteamAccountType)((SteamId64 >> 52) & 0xF);
public SteamAccountUniverse AccountUniverse =>
public SteamAccountUniverse AccountUniverse =>
(SteamAccountUniverse)((SteamId64 >> 56) & 0xF);
public bool IsValid()
{
if (AccountUniverse == SteamAccountUniverse.Unspecified
|| AccountType == SteamAccountType.Invalid
if (AccountUniverse == SteamAccountUniverse.Unspecified
|| AccountType == SteamAccountType.Invalid
|| AccountInstance == SteamAccountInstance.Invalid)
return false;
if (AccountType == SteamAccountType.Individual
if (AccountType == SteamAccountType.Individual
&& (AccountId == 0 || AccountInstance != SteamAccountInstance.Desktop))
return false;
if (AccountType == SteamAccountType.Clan
if (AccountType == SteamAccountType.Clan
&& (AccountId == 0 || AccountInstance != SteamAccountInstance.All))
return false;
if (AccountType == SteamAccountType.GameServer && AccountId == 0)
@@ -78,12 +83,12 @@ namespace CounterStrikeSharp.API.Modules.Entities
public Uri ToCommunityUrl()
{
string url = string.Empty;
if (AccountType == SteamAccountType.Individual)
url = "https://steamcommunity.com/profiles/" + SteamId64;
if (AccountType == SteamAccountType.Clan)
url = "https://steamcommunity.com/gid/" + SteamId64;
return new Uri(url);
return AccountType switch
{
SteamAccountType.Individual => new Uri("https://steamcommunity.com/profiles/" + SteamId64),
SteamAccountType.Clan => new Uri("https://steamcommunity.com/gid/" + SteamId64),
_ => new Uri(string.Empty),
};
}
public bool Equals(SteamID? other)
@@ -96,7 +101,7 @@ namespace CounterStrikeSharp.API.Modules.Entities
if (obj?.GetType() != this.GetType()) return false;
return Equals((SteamID)obj);
}
public static bool TryParse(string s, out SteamID? steamId)
{
try

View File

@@ -2,6 +2,9 @@
namespace CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
#pragma warning disable CS8601 // Possible null reference assignment.
#pragma warning disable CS8604 // Possible null reference argument.
public class MemoryFunctionVoid : BaseMemoryFunction
{
public MemoryFunctionVoid(string signature) : base(signature, DataType.DATA_TYPE_VOID, Array.Empty<DataType>())
@@ -276,4 +279,7 @@ public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : BaseM
{
InvokeInternalVoid(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
}
}
}
#pragma warning restore CS8601 // Possible null reference assignment.
#pragma warning restore CS8604 // Possible null reference argument.

View File

@@ -2,6 +2,9 @@
namespace CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
#pragma warning disable CS8601 // Possible null reference assignment.
#pragma warning disable CS8604 // Possible null reference argument.
public class MemoryFunctionWithReturn<TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
@@ -264,4 +267,7 @@ public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
}
}
}
#pragma warning restore CS8601 // Possible null reference assignment.
#pragma warning restore CS8604 // Possible null reference argument.

View File

@@ -19,6 +19,8 @@ using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Core;
#pragma warning disable CS8601 // Possible null reference assignment.
namespace CounterStrikeSharp.API.Modules.Memory
{
public partial class VirtualFunction
@@ -587,4 +589,6 @@ namespace CounterStrikeSharp.API.Modules.Memory
#endregion
}
}
}
#pragma warning restore CS8601 // Possible null reference assignment.

View File

@@ -21,6 +21,8 @@ using CounterStrikeSharp.API.Core;
namespace CounterStrikeSharp.API.Modules.Memory;
#pragma warning disable CS8601 // Possible null reference assignment.
public partial class VirtualFunction
{
private static Dictionary<string, IntPtr> _createdFunctions = new();
@@ -1147,4 +1149,6 @@ public partial class VirtualFunction
};
}
#endregion
}
}
#pragma warning restore CS8601 // Possible null reference assignment.

View File

@@ -76,4 +76,8 @@ public static class VirtualFunctions
public static MemoryFunctionVoid<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouchFunc = new (GameData.GetSignature("CBaseTrigger_EndTouch"));
public static Action<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouch = CBaseTrigger_EndTouchFunc.Invoke;
public static MemoryFunctionVoid<IntPtr, IntPtr> RemovePlayerItemFunc =
new(GameData.GetSignature("CBasePlayerPawn_RemovePlayerItem"));
public static Action<IntPtr, IntPtr> RemovePlayerItemVirtual = RemovePlayerItemFunc.Invoke;
}

View File

@@ -10,5 +10,14 @@ public class CStrongHandle<T> : NativeObject
{
}
public unsafe ulong Value => Unsafe.Read<ulong>((void*)Handle);
}
public class CWeakHandle<T> : NativeObject
{
public CWeakHandle(IntPtr pointer) : base(pointer)
{
}
public unsafe ulong Value => Unsafe.Read<ulong>((void*)Handle);
}

View File

@@ -2,6 +2,8 @@
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace CounterStrikeSharp.API.Modules.Utils;
@@ -17,36 +19,48 @@ public class CommandUtils
var methodInfo = handler?.GetMethodInfo();
if (!AdminManager.CommandIsOverriden(name))
// We do not need to do permission checks on commands executed from the server console.
// The server will always be allowed to execute commands (unless marked as client only like above)
if (caller != null)
{
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null)
var adminData = AdminManager.GetPlayerAdminData(caller!.AuthorizedSteamID);
var permissionsToCheck = new List<BaseRequiresPermissions>();
// If our command is overriden, we dynamically create a new permissions attribute
// based on the data that is stored in admin_overrides.json.
if (AdminManager.CommandIsOverriden(name))
{
foreach (var attr in permissions)
var data = AdminManager.GetCommandOverrideData(name);
if (data != null)
{
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
if (attr != null) permissionsToCheck.Add(attr);
}
}
}
// 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)
// The permissions for this command are not being overriden here, so we
// grab the permissions to check straight from the attribute.
else
{
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null) permissionsToCheck.AddRange(permissions);
}
foreach (var attr in permissionsToCheck)
{
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.");
var responseStr = (attr.GetType() == typeof(RequiresPermissions)) ?
"You are missing the correct permissions" : "You do not have one of the correct permissions";
var flags = attr.Permissions.Except(adminData?.GetAllFlags() ?? new HashSet<string>());
flags = flags.Except(adminData?.Groups ?? new HashSet<string>());
command.ReplyToCommand($"[CSS] {responseStr} ({string.Join(", ", flags)}) to execute this command.");
return;
}
}
@@ -72,7 +86,12 @@ public class CommandUtils
// but we'll just ignore that for this check.
if (helperAttribute.MinArgs != 0 && command.ArgCount - 1 < helperAttribute.MinArgs)
{
command.ReplyToCommand($"[CSS] Expected usage: \"!{command.ArgByIndex(0)} {helperAttribute.Usage}\".");
// Remove the "css_" from the beginning of the command name if it's present.
// Most of the time, users will be calling commands from chat.
var commandCalled = command.ArgByIndex(0);
var properCommandName = (commandCalled.StartsWith("css_")) ? commandCalled.Replace("css_", "") : commandCalled;
command.ReplyToCommand($"[CSS] Expected usage: \"!{properCommandName} {helperAttribute.Usage}\".");
return;
}
}

View File

@@ -75,6 +75,30 @@ namespace CounterStrikeSharp.API
return new Target(pattern).GetTarget(player);
}
public static bool RemoveItemByDesignerName(this CCSPlayerController player, string designerName)
{
CHandle<CBasePlayerWeapon>? item = null;
if (player.PlayerPawn.Value == null || player.PlayerPawn.Value.WeaponServices == null) return false;
foreach(var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
{
if (weapon is not { IsValid: true, Value.IsValid: true })
continue;
if (weapon.Value.DesignerName != designerName)
continue;
item = weapon;
}
if(item != null && item.Value != null)
{
player.PlayerPawn.Value.RemovePlayerItem(item.Value);
return true;
}
return false;
}
public static IEnumerable<T> FindAllEntitiesByDesignerName<T>(string designerName) where T : CEntityInstance
{
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);

View File

@@ -0,0 +1,36 @@
/*
* 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
{
[Flags]
public enum VoiceFlags : Byte
{
Normal = 0,
Muted = (1 << 0),
All = (1 << 1),
ListenAll = (1 << 2),
Team = (1 << 3),
ListenTeam = (1 << 4),
}
public enum ListenOverride
{
Default = 0,
Mute,
Hear
}
}

View File

@@ -28,7 +28,18 @@ internal static partial class Program
"IChoreoServices::ChoreoState_t",
"SpawnPointCoopEnemy::BotDefaultBehavior_t",
"CLogicBranchList::LogicBranchListenerLastState_t",
"SimpleConstraintSoundProfile::SimpleConstraintsSoundProfileKeypoints_t"
"SimpleConstraintSoundProfile::SimpleConstraintsSoundProfileKeypoints_t",
"MoodAnimationLayer_t"
};
private static readonly IReadOnlySet<string> IgnoreClassWildcards = new HashSet<string>
{
"CResourceNameTyped",
"CEntityOutputTemplate",
"CVariantBase",
"HSCRIPT",
"KeyValues3",
"Unknown"
};
public static string SanitiseTypeName(string typeName) => typeName.Replace(":", "");
@@ -159,11 +170,18 @@ internal static partial class Program
// Manually whitelist some classes
visited.Add("CTakeDamageInfo");
visited.Add("CEntitySubclassVDataBase");
visited.Add("CFiringModeFloat");
visited.Add("CFiringModeInt");
visited.Add("CSkillFloat");
visited.Add("CSkillInt");
visited.Add("CRangeFloat");
visited.Add("CNavLinkAnimgraphVar");
var visitedClassNames = new HashSet<string>();
foreach (var (className, schemaClass) in allClasses)
{
if (visited.Contains(className))
if (visited.Contains(className) || className.Contains("VData"))
{
var isPointeeType = pointeeTypes.Contains(className);
@@ -211,10 +229,11 @@ internal static partial class Program
foreach (var field in schemaClass.Fields)
{
if (IgnoreClassWildcards.Any(y => field.Type.Name.Contains(y)))
continue;
// Putting these in the too hard basket for now.
if (field.Name == "m_VoteOptions" || field.Type.Name.Contains("CEntityOutputTemplate") ||
field.Type.Name.Contains("CVariantBase") ||
field.Type.Name == "HSCRIPT" || field.Type.Name == "KeyValues3") continue;
if (field.Name == "m_VoteOptions" || field.Name == "m_aShootSounds") continue;
if (field.Type is { Category: SchemaTypeCategory.Atomic, Atomic: SchemaAtomicCategory.Collection })
{

View File

@@ -26,6 +26,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithDatabaseDapper", "..\ex
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithEntityOutputHooks", "..\examples\WithEntityOutputHooks\WithEntityOutputHooks.csproj", "{31EABE0B-871F-497B-BF36-37FFC6FAD15F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CounterStrikeSharp.API.Tests", "CounterStrikeSharp.API.Tests\CounterStrikeSharp.API.Tests.csproj", "{BBA80E1B-109D-4ABD-9ADF-46EB0FEDFCD3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithTranslations", "..\examples\WithTranslations\WithTranslations.csproj", "{BB44E08E-CCA8-4E22-A132-11B2F69D1890}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithVoiceOverrides", "..\examples\WithVoiceOverrides\WithVoiceOverrides.csproj", "{6FA3107D-42AF-42A0-BF51-2230D13268B5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -80,6 +86,18 @@ Global
{31EABE0B-871F-497B-BF36-37FFC6FAD15F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31EABE0B-871F-497B-BF36-37FFC6FAD15F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31EABE0B-871F-497B-BF36-37FFC6FAD15F}.Release|Any CPU.Build.0 = Release|Any CPU
{BBA80E1B-109D-4ABD-9ADF-46EB0FEDFCD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBA80E1B-109D-4ABD-9ADF-46EB0FEDFCD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBA80E1B-109D-4ABD-9ADF-46EB0FEDFCD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBA80E1B-109D-4ABD-9ADF-46EB0FEDFCD3}.Release|Any CPU.Build.0 = Release|Any CPU
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Release|Any CPU.Build.0 = Release|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
@@ -91,5 +109,7 @@ Global
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{31EABE0B-871F-497B-BF36-37FFC6FAD15F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{BB44E08E-CCA8-4E22-A132-11B2F69D1890} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{6FA3107D-42AF-42A0-BF51-2230D13268B5} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
EndGlobalSection
EndGlobal

View File

@@ -16,6 +16,7 @@
using System;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
@@ -23,6 +24,7 @@ using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Entities;
@@ -31,6 +33,7 @@ using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace TestPlugin
@@ -212,6 +215,13 @@ namespace TestPlugin
return HookResult.Continue;
}, HookMode.Pre);
RegisterEventHandler<EventGrenadeBounce>((@event, info) =>
{
Logger.LogInformation("Player {Player} grenade bounce", @event.Userid.PlayerName);
return HookResult.Continue;
}, HookMode.Pre);
RegisterEventHandler<EventPlayerSpawn>((@event, info) =>
{
if (!@event.Userid.IsValid) return 0;
@@ -455,6 +465,43 @@ namespace TestPlugin
player.PlayerPawn.Value.CommitSuicide(true, true);
}
[CommandHelper(minArgs: 1, usage: "[weaponName]")]
[ConsoleCommand("css_strip", "Removes weapon by name")]
public void OnStripActiveWeapon(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
player.RemoveItemByDesignerName(command.GetArg(1));
}
[ConsoleCommand("css_stripweapons", "Removes player weapons")]
public void OnStripWeapons(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
player.RemoveWeapons();
}
[ConsoleCommand("css_teleportup", "Teleports the player up")]
public void OnTeleport(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
player.PlayerPawn.Value.Teleport(player.PlayerPawn.Value.AbsOrigin.With(z: player.PlayerPawn.Value.AbsOrigin.Z + 100), player.PlayerPawn.Value.AbsRotation, new Vector(IntPtr.Zero));
}
[ConsoleCommand("css_respawn", "Respawns the player")]
public void OnRespawn(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
player.Respawn();
}
[ConsoleCommand("cssharp_attribute", "This is a custom attribute event")]
public void OnCommand(CCSPlayerController? player, CommandInfo command)
@@ -484,7 +531,8 @@ namespace TestPlugin
foreach (var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
{
command.ReplyToCommand(weapon.Value.DesignerName);
var vData = weapon.Value.As<CCSWeaponBase>().VData;
command.ReplyToCommand(string.Format("{0}, {1}, {2}, {3}, {4}, {5}", vData.Name, vData.GearSlot, vData.Price, vData.WeaponCategory, vData.WeaponType, vData.KillAward));
}
}
@@ -514,6 +562,13 @@ namespace TestPlugin
}
}
[ConsoleCommand("css_localetest", "Test Translations")]
public void OnCommandLocaleTest(CCSPlayerController? player, CommandInfo command)
{
Logger.LogInformation("Current Culture is {Culture}", CultureInfo.CurrentCulture);
command.ReplyToCommand(Localizer["testPlugin.maxPlayersAnnouncement", Server.MaxPlayers]);
}
[ConsoleCommand("css_sound", "Play a sound to client")]
public void OnCommandSound(CCSPlayerController? player, CommandInfo command)
{

View File

@@ -13,4 +13,10 @@
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="lang\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,3 @@
{
"testPlugin.maxPlayersAnnouncement": "The server currently has a max player count of {0} players (GB)"
}

View File

@@ -0,0 +1,3 @@
{
"testPlugin.maxPlayersAnnouncement": "The server currently has a max player count of {0} players",
}

View File

@@ -0,0 +1,3 @@
{
"testPlugin.maxPlayersAnnouncement": "Le serveur compte actuellement un nombre maximum de {0} joueurs."
}

View File

@@ -51,6 +51,7 @@ bool CCoreConfig::Init(char* conf_error, int conf_error_size)
SilentChatTrigger = m_json.value("SilentChatTrigger", SilentChatTrigger);
FollowCS2ServerGuidelines = m_json.value("FollowCS2ServerGuidelines", FollowCS2ServerGuidelines);
PluginHotReloadEnabled = m_json.value("PluginHotReloadEnabled", PluginHotReloadEnabled);
ServerLanguage = m_json.value("ServerLanguage", ServerLanguage);
} catch (const std::exception& ex) {
V_snprintf(conf_error, conf_error_size, "Failed to parse CoreConfig file: %s", ex.what());
return false;

View File

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

View File

@@ -1,3 +1,12 @@
// Copyright (C) 2023 neverlosecc
// See end of file for extended copyright information.
/**
* =============================================================================
* Source2Gen
* Copyright (C) 2023 neverlose (https://github.com/neverlosecc/source2gen)
* =============================================================================
**/
#pragma once
#include <vector>
@@ -392,3 +401,18 @@ class CSchemaSystem
return CALL_VIRTUAL(CSchemaSystemTypeScope*, 13, this, m_module_name, nullptr);
}
};
// source2gen - Source2 games SDK generator
// Copyright 2023 neverlosecc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

View File

@@ -45,6 +45,7 @@ public:
virtual void OnStartup() {}
virtual void OnShutdown() {}
virtual void OnAllInitialized() {}
virtual void OnGameLoopInitialized() {}
virtual void OnAllInitialized_Post() {}
virtual void OnLevelChange(const char *mapName) {}
virtual void OnLevelEnd() {}

View File

@@ -20,6 +20,7 @@
#include "interfaces/cs2_interfaces.h"
#include "core/managers/entity_manager.h"
#include "core/managers/server_manager.h"
#include "core/managers/voice_manager.h"
#include <public/game/server/iplayerinfo.h>
#include <public/entity2/entitysystem.h>
@@ -43,6 +44,7 @@ IServerPluginHelpers* helpers = nullptr;
IUniformRandomStream* randomStream = nullptr;
IEngineTrace* engineTrace = nullptr;
IEngineSound* engineSound = nullptr;
IEngineServiceMgr* engineServiceManager = nullptr;
INetworkStringTableContainer* netStringTables = nullptr;
CGlobalVars* globalVars = nullptr;
IFileSystem* fileSystem = nullptr;
@@ -76,7 +78,9 @@ ConCommandManager conCommandManager;
EntityManager entityManager;
ChatManager chatManager;
ServerManager serverManager;
VoiceManager voiceManager;
bool gameLoopInitialized = false;
GetLegacyGameEventListener_t* GetLegacyGameEventListener = nullptr;
std::thread::id gameThreadId;

View File

@@ -20,6 +20,7 @@ class IServerPluginHelpers;
class IUniformRandomStream;
class IEngineTrace;
class IEngineSound;
class IEngineServiceMgr;
class INetworkStringTableContainer;
class CGlobalVars;
class IFileSystem;
@@ -52,6 +53,7 @@ class HookManager;
class EntityManager;
class ChatManager;
class ServerManager;
class VoiceManager;
class CCoreConfig;
class CGameConfig;
@@ -65,6 +67,7 @@ extern IServerPluginHelpers *helpers;
extern IUniformRandomStream *randomStream;
extern IEngineTrace *engineTrace;
extern IEngineSound *engineSound;
extern IEngineServiceMgr *engineServiceManager;
extern INetworkStringTableContainer *netStringTables;
extern CGlobalVars *globalVars;
extern IFileSystem *fileSystem;
@@ -96,6 +99,7 @@ extern TimerSystem timerSystem;
extern ChatCommands chatCommands;
extern ChatManager chatManager;
extern ServerManager serverManager;
extern VoiceManager voiceManager;
extern HookManager hookManager;
extern SourceHook::ISourceHook *source_hook;
@@ -108,6 +112,7 @@ extern CGameConfig* gameConfig;
typedef IGameEventListener2 *GetLegacyGameEventListener_t(CPlayerSlot slot);
extern bool gameLoopInitialized;
extern GetLegacyGameEventListener_t* GetLegacyGameEventListener;
extern std::thread::id gameThreadId;

View File

@@ -44,6 +44,15 @@ EventManager::~EventManager() = default;
void EventManager::OnStartup() {}
void EventManager::OnGameLoopInitialized()
{
while (!m_PendingHooks.empty()) {
const auto& pendingHook = m_PendingHooks.top();
HookEvent(pendingHook.m_Name.c_str(), pendingHook.m_fnCallback, pendingHook.m_bPost);
m_PendingHooks.pop();
}
}
void EventManager::OnAllInitialized()
{
SH_ADD_HOOK(IGameEventManager2, FireEvent, globals::gameEventManager,
@@ -68,6 +77,17 @@ bool EventManager::HookEvent(const char* szName, CallbackT fnCallback, bool bPos
{
EventHook* pHook;
// Plugin load is called before game loop (and thus events file is loaded)
// So we defer hooking until game loop is initialized
if (!globals::gameLoopInitialized) {
const PendingEventHook pendingHook{szName, fnCallback, bPost};
m_PendingHooks.push(pendingHook);
return true;
}
CSSHARP_CORE_TRACE("[EventManager] Hooking event: {0} with callback pointer: {1}", szName,
(void*)fnCallback);
if (!globals::gameEventManager->FindListener(this, szName)) {
globals::gameEventManager->AddListener(this, szName, true);
}

View File

@@ -67,6 +67,12 @@ struct EventOverride {
bool m_bDontBroadcast;
};
struct PendingEventHook {
std::string m_Name;
counterstrikesharp::CallbackT m_fnCallback;
bool m_bPost;
};
namespace counterstrikesharp {
class EventManager : public IGameEventListener2, public GlobalClass
@@ -79,6 +85,7 @@ class EventManager : public IGameEventListener2, public GlobalClass
void OnShutdown() override;
void OnAllInitialized() override;
void OnStartup() override;
void OnGameLoopInitialized() override;
// IGameEventListener2
void FireGameEvent(IGameEvent* pEvent) override;
@@ -94,6 +101,7 @@ class EventManager : public IGameEventListener2, public GlobalClass
std::stack<EventHook *> m_EventStack;
std::stack<IGameEvent *> m_EventCopies;
std::stack<PendingEventHook> m_PendingHooks;
};
} // namespace counterstrikesharp

View File

@@ -31,6 +31,7 @@
#include "core/managers/player_manager.h"
#include "core/managers/con_command_manager.h"
#include "core/managers/voice_manager.h"
#include <public/eiface.h>
#include <public/inetchannelinfo.h>
@@ -45,8 +46,10 @@
SH_DECL_HOOK4_void(IServerGameClients, ClientActive, SH_NOATTRIB, 0, CPlayerSlot, bool, const char*,
uint64);
SH_DECL_HOOK5_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, 0, CPlayerSlot, ENetworkDisconnectionReason,
const char*, uint64, const char*);
SH_DECL_HOOK5_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, 0, CPlayerSlot,
ENetworkDisconnectionReason, const char*, uint64, const char*);
SH_DECL_HOOK1_void(IServerGameClients, ClientVoice, SH_NOATTRIB, 0, CPlayerSlot);
SH_DECL_HOOK4_void(IServerGameClients, ClientPutInServer, SH_NOATTRIB, 0, CPlayerSlot, char const*,
int, uint64);
SH_DECL_HOOK1_void(IServerGameClients, ClientSettingsChanged, SH_NOATTRIB, 0, CPlayerSlot);
@@ -75,6 +78,8 @@ void PlayerManager::OnAllInitialized()
SH_MEMBER(this, &PlayerManager::OnClientDisconnect_Post), true);
SH_ADD_HOOK(IServerGameClients, ClientCommand, globals::serverGameClients,
SH_MEMBER(this, &PlayerManager::OnClientCommand), false);
SH_ADD_HOOK(IServerGameClients, ClientVoice, globals::serverGameClients,
SH_MEMBER(this, &PlayerManager::OnClientVoice), true);
m_on_client_connect_callback = globals::callbackManager.CreateCallback("OnClientConnect");
m_on_client_connected_callback = globals::callbackManager.CreateCallback("OnClientConnected");
@@ -83,6 +88,7 @@ void PlayerManager::OnAllInitialized()
m_on_client_disconnect_callback = globals::callbackManager.CreateCallback("OnClientDisconnect");
m_on_client_disconnect_post_callback =
globals::callbackManager.CreateCallback("OnClientDisconnectPost");
m_on_client_voice_callback = globals::callbackManager.CreateCallback("OnClientVoice");
m_on_client_authorized_callback = globals::callbackManager.CreateCallback("OnClientAuthorized");
}
@@ -100,6 +106,8 @@ void PlayerManager::OnShutdown()
SH_MEMBER(this, &PlayerManager::OnClientDisconnect_Post), true);
SH_REMOVE_HOOK(IServerGameClients, ClientCommand, globals::serverGameClients,
SH_MEMBER(this, &PlayerManager::OnClientCommand), false);
SH_REMOVE_HOOK(IServerGameClients, ClientVoice, globals::serverGameClients,
SH_MEMBER(this, &PlayerManager::OnClientVoice), true);
globals::callbackManager.ReleaseCallback(m_on_client_connect_callback);
globals::callbackManager.ReleaseCallback(m_on_client_connected_callback);
@@ -107,6 +115,7 @@ void PlayerManager::OnShutdown()
globals::callbackManager.ReleaseCallback(m_on_client_disconnect_callback);
globals::callbackManager.ReleaseCallback(m_on_client_disconnect_post_callback);
globals::callbackManager.ReleaseCallback(m_on_client_authorized_callback);
globals::callbackManager.ReleaseCallback(m_on_client_voice_callback);
}
bool PlayerManager::OnClientConnect(CPlayerSlot slot, const char* pszName, uint64 xuid,
@@ -120,8 +129,10 @@ bool PlayerManager::OnClientConnect(CPlayerSlot slot, const char* pszName, uint6
CPlayer* pPlayer = &m_players[client];
if (pPlayer->IsConnected()) {
OnClientDisconnect(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, pszName, xuid, pszNetworkID);
OnClientDisconnect_Post(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, pszName, xuid, pszNetworkID);
OnClientDisconnect(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, pszName,
xuid, pszNetworkID);
OnClientDisconnect_Post(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID,
pszName, xuid, pszNetworkID);
}
pPlayer->Initialize(pszName, pszNetworkID, slot);
@@ -222,8 +233,7 @@ void PlayerManager::OnClientPutInServer(CPlayerSlot slot, char const* pszName, i
m_on_client_put_in_server_callback->Execute();
}
void PlayerManager::OnClientDisconnect(CPlayerSlot slot,
ENetworkDisconnectionReason reason,
void PlayerManager::OnClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason,
const char* pszName, uint64 xuid, const char* pszNetworkID)
{
CSSHARP_CORE_TRACE("[PlayerManager][OnClientDisconnect] - {}, {}, {}", slot.Get(), pszName,
@@ -246,8 +256,7 @@ void PlayerManager::OnClientDisconnect(CPlayerSlot slot,
// globals::entityListener.HandleEntityDeleted(pPlayer->GetBaseEntity(), client);
}
void PlayerManager::OnClientDisconnect_Post(CPlayerSlot slot,
ENetworkDisconnectionReason reason,
void PlayerManager::OnClientDisconnect_Post(CPlayerSlot slot, ENetworkDisconnectionReason reason,
const char* pszName, uint64 xuid,
const char* pszNetworkID) const
{
@@ -269,16 +278,27 @@ void PlayerManager::OnClientDisconnect_Post(CPlayerSlot slot,
m_on_client_disconnect_post_callback->Execute();
}
void PlayerManager::OnClientVoice(CPlayerSlot slot) const
{
CSSHARP_CORE_TRACE("[PlayerManager][OnClientVoice] - {}", slot.Get());
m_on_client_voice_callback->ScriptContext().Reset();
m_on_client_voice_callback->ScriptContext().Push(slot.Get());
m_on_client_voice_callback->Execute();
}
void PlayerManager::OnLevelEnd()
{
CSSHARP_CORE_TRACE("[PlayerManager][OnLevelEnd]");
for (int i = 0; i <= m_max_clients; i++) {
for (int i = 0; i <= MaxClients(); i++) {
if (m_players[i].IsConnected()) {
OnClientDisconnect(m_players[i].m_slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, m_players[i].GetName(), 0,
m_players[i].GetIpAddress());
OnClientDisconnect_Post(m_players[i].m_slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, m_players[i].GetName(), 0,
m_players[i].GetIpAddress());
OnClientDisconnect(m_players[i].m_slot,
ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID,
m_players[i].GetName(), 0, m_players[i].GetIpAddress());
OnClientDisconnect_Post(m_players[i].m_slot,
ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID,
m_players[i].GetName(), 0, m_players[i].GetIpAddress());
}
}
m_player_count = 0;
@@ -286,10 +306,13 @@ void PlayerManager::OnLevelEnd()
void PlayerManager::OnClientCommand(CPlayerSlot slot, const CCommand& args) const
{
CSSHARP_CORE_TRACE("[PlayerManager][OnClientCommand] - {}, {}, {}", slot.Get(), args.Arg(0), (void*)&args);
CSSHARP_CORE_TRACE("[PlayerManager][OnClientCommand] - {}, {}, {}", slot.Get(), args.Arg(0),
(void*)&args);
const char* cmd = args.Arg(0);
globals::voiceManager.OnClientCommand(slot, args);
auto result = globals::conCommandManager.ExecuteCommandCallbacks(
cmd, CCommandContext(CommandTarget_t::CT_NO_TARGET, slot), args, HookMode::Pre);
@@ -302,11 +325,11 @@ int PlayerManager::ListenClient() const { return m_listen_client; }
int PlayerManager::NumPlayers() const { return m_player_count; }
int PlayerManager::MaxClients() const { return m_max_clients; }
int PlayerManager::MaxClients() const { return globals::getGlobalVars()->maxClients; }
CPlayer* PlayerManager::GetPlayerBySlot(int client) const
{
if (client > m_max_clients || client < 0) {
if (client > MaxClients() || client < 0) {
return nullptr;
}
@@ -426,7 +449,6 @@ INetChannelInfo* CPlayer::GetNetInfo() const { return globals::engine->GetPlayer
PlayerManager::PlayerManager()
{
m_max_clients = 64;
m_players = new CPlayer[66];
m_player_count = 0;
m_user_id_lookup = new int[USHRT_MAX + 1];
@@ -441,7 +463,7 @@ void PlayerManager::RunAuthChecks()
m_last_auth_check_time = globals::timerSystem.GetTickedTime();
for (int i = 0; i <= m_max_clients; i++) {
for (int i = 0; i <= MaxClients(); i++) {
if (m_players[i].IsConnected()) {
if (m_players[i].IsAuthorized() || m_players[i].IsFakeClient())
continue;
@@ -532,6 +554,17 @@ float CPlayer::GetLatency() const
return GetNetInfo()->GetLatency(FLOW_INCOMING) + GetNetInfo()->GetLatency(FLOW_OUTGOING);
}
void CPlayer::SetListen(CPlayerSlot slot, ListenOverride listen)
{
m_listenMap[slot.Get()] = listen;
}
void CPlayer::SetVoiceFlags(VoiceFlag_t flags) { m_voiceFlag = flags; }
VoiceFlag_t CPlayer::GetVoiceFlags() { return m_voiceFlag; }
ListenOverride CPlayer::GetListen(CPlayerSlot slot) const { return m_listenMap[slot.Get()]; }
void CPlayer::Connect()
{
if (m_is_in_game) {
@@ -551,6 +584,9 @@ void CPlayer::Disconnect()
m_user_id = -1;
m_is_authorized = false;
m_ip_address.clear();
m_selfMutes->ClearAll();
memset(m_listenMap, 0, sizeof m_listenMap);
m_voiceFlag = 0;
}
QAngle CPlayer::GetAbsAngles() const { return m_info->GetAbsAngles(); }

View File

@@ -45,30 +45,50 @@ namespace counterstrikesharp {
class ScriptCallback;
class CBaseEntityWrapper;
class CPlayer {
enum ListenOverride
{
Listen_Default = 0,
Listen_Mute,
Listen_Hear
};
enum VoiceFlagValue
{
Speak_Normal = 0,
Speak_Muted = 1 << 0,
Speak_All = 1 << 1,
Speak_ListenAll = 1 << 2,
Speak_Team = 1 << 3,
Speak_ListenTeam = 1 << 4,
};
typedef uint8_t VoiceFlag_t;
class CPlayer
{
friend class PlayerManager;
public:
public:
CPlayer();
public:
void Initialize(const char *name, const char *ip, CPlayerSlot slot);
public:
void Initialize(const char* name, const char* ip, CPlayerSlot slot);
void Connect();
void Disconnect();
IPlayerInfo *GetPlayerInfo() const;
IPlayerInfo* GetPlayerInfo() const;
bool WasCountedAsInGame() const;
int GetUserId();
bool IsAuthStringValidated() const;
void Authorize();
public:
const char *GetName() const;
const CSteamID *GetSteamId();
void SetSteamId(const CSteamID *steam_id);
public:
const char* GetName() const;
const CSteamID* GetSteamId();
void SetSteamId(const CSteamID* steam_id);
bool IsConnected() const;
bool IsFakeClient() const;
bool IsAuthorized() const;
void PrintToConsole(const char *message) const;
void PrintToConsole(const char* message) const;
// void PrintToChat(const char *message);
// void PrintToHint(const char *message);
// void PrintToCenter(const char *message);
@@ -76,26 +96,30 @@ public:
Vector GetAbsOrigin() const;
bool IsAlive() const;
bool IsInGame() const;
void Kick(const char *kickReason);
const char *GetWeaponName() const;
void Kick(const char* kickReason);
const char* GetWeaponName() const;
void ChangeTeam(int team) const;
int GetTeam() const;
int GetArmor() const;
int GetFrags() const;
int GetDeaths() const;
const char *GetKeyValue(const char *key) const;
const char* GetKeyValue(const char* key) const;
Vector GetMaxSize() const;
Vector GetMinSize() const;
int GetMaxHealth() const;
const char *GetIpAddress() const;
const char *GetModelName() const;
const char* GetIpAddress() const;
const char* GetModelName() const;
int GetUserId() const;
float GetTimeConnected() const;
float GetLatency() const;
void SetListen(CPlayerSlot slot, ListenOverride listen);
void SetVoiceFlags(VoiceFlag_t flags);
VoiceFlag_t GetVoiceFlags();
ListenOverride GetListen(CPlayerSlot slot) const;
public:
public:
std::string m_name;
IPlayerInfo *m_info = nullptr;
IPlayerInfo* m_info = nullptr;
std::string m_auth_id;
bool m_is_connected = false;
bool m_is_fake_client = false;
@@ -105,72 +129,64 @@ public:
CPlayerSlot m_slot = CPlayerSlot(-1);
const CSteamID* m_steamId;
std::string m_ip_address;
void SetName(const char *name);
INetChannelInfo *GetNetInfo() const;
ListenOverride m_listenMap[66] = {};
VoiceFlag_t m_voiceFlag = 0;
CPlayerBitVec m_selfMutes[64] = {};
void SetName(const char* name);
INetChannelInfo* GetNetInfo() const;
};
class PlayerManager : public GlobalClass {
class PlayerManager : public GlobalClass
{
friend class CPlayer;
public:
public:
PlayerManager();
void OnStartup() override;
void OnAllInitialized() override;
bool OnClientConnect(CPlayerSlot slot,
const char *pszName,
uint64 xuid,
const char *pszNetworkID,
bool unk1,
CBufferString *pRejectReason);
bool OnClientConnect_Post(CPlayerSlot slot,
const char *pszName,
uint64 xuid,
const char *pszNetworkID,
bool unk1,
CBufferString *pRejectReason);
void OnClientPutInServer(CPlayerSlot slot, char const *pszName, int type, uint64 xuid);
void OnClientDisconnect(CPlayerSlot slot,
ENetworkDisconnectionReason reason,
const char *pszName,
uint64 xuid,
const char *pszNetworkID);
void OnClientDisconnect_Post(CPlayerSlot slot,
ENetworkDisconnectionReason reason,
const char *pszName,
uint64 xuid,
const char *pszNetworkID) const;
bool OnClientConnect(CPlayerSlot slot, const char* pszName, uint64 xuid,
const char* pszNetworkID, bool unk1, CBufferString* pRejectReason);
bool OnClientConnect_Post(CPlayerSlot slot, const char* pszName, uint64 xuid,
const char* pszNetworkID, bool unk1, CBufferString* pRejectReason);
void OnClientPutInServer(CPlayerSlot slot, char const* pszName, int type, uint64 xuid);
void OnClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason,
const char* pszName, uint64 xuid, const char* pszNetworkID);
void OnClientDisconnect_Post(CPlayerSlot slot, ENetworkDisconnectionReason reason,
const char* pszName, uint64 xuid, const char* pszNetworkID) const;
void OnClientVoice(CPlayerSlot slot) const;
void OnAuthorized(CPlayer* player) const;
void OnServerActivate(edict_t *pEdictList, int edictCount, int clientMax) const;
void OnServerActivate(edict_t* pEdictList, int edictCount, int clientMax) const;
void OnThink(bool last_tick) const;
void OnShutdown() override;
void OnLevelEnd() override;
void OnClientCommand(CPlayerSlot slot, const CCommand &args) const;
void OnClientCommand(CPlayerSlot slot, const CCommand& args) const;
int ListenClient() const;
void RunAuthChecks();
public:
public:
int NumPlayers() const;
int MaxClients() const;
CPlayer *GetPlayerBySlot(int client) const;
CPlayer *GetClientOfUserId(int user_id) const;
CPlayer* GetPlayerBySlot(int client) const;
CPlayer* GetClientOfUserId(int user_id) const;
private:
void InvalidatePlayer(CPlayer *pPlayer) const;
private:
void InvalidatePlayer(CPlayer* pPlayer) const;
CPlayer *m_players;
CPlayer* m_players;
int m_max_clients = 0;
int m_player_count = 0;
int *m_user_id_lookup;
int* m_user_id_lookup;
int m_listen_client;
bool m_is_listen_server;
float m_last_auth_check_time = 0;
ScriptCallback *m_on_client_connect_callback;
ScriptCallback *m_on_client_put_in_server_callback;
ScriptCallback *m_on_client_connected_callback;
ScriptCallback *m_on_client_disconnect_callback;
ScriptCallback *m_on_client_disconnect_post_callback;
ScriptCallback *m_on_client_authorized_callback;
ScriptCallback* m_on_client_connect_callback;
ScriptCallback* m_on_client_put_in_server_callback;
ScriptCallback* m_on_client_connected_callback;
ScriptCallback* m_on_client_disconnect_callback;
ScriptCallback* m_on_client_disconnect_post_callback;
ScriptCallback* m_on_client_voice_callback;
ScriptCallback* m_on_client_authorized_callback;
};
} // namespace counterstrikesharp
} // namespace counterstrikesharp

View File

@@ -0,0 +1,129 @@
/*
* 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 "core/managers/voice_manager.h"
#include "core/managers/player_manager.h"
#include <public/eiface.h>
#include "scripting/callback_manager.h"
#include <schema.h>
#include <entity2/entitysystem.h>
SH_DECL_HOOK3(IVEngineServer2, SetClientListening, SH_NOATTRIB, 0, bool, CPlayerSlot, CPlayerSlot,
bool);
namespace counterstrikesharp {
VoiceManager::VoiceManager() {}
VoiceManager::~VoiceManager() {}
void VoiceManager::OnAllInitialized()
{
SH_ADD_HOOK(IVEngineServer2, SetClientListening, globals::engine,
SH_MEMBER(this, &VoiceManager::SetClientListening), false);
}
void VoiceManager::OnShutdown()
{
SH_REMOVE_HOOK(IVEngineServer2, SetClientListening, globals::engine,
SH_MEMBER(this, &VoiceManager::SetClientListening), false);
}
bool VoiceManager::SetClientListening(CPlayerSlot iReceiver, CPlayerSlot iSender, bool bListen)
{
auto pReceiver = globals::playerManager.GetPlayerBySlot(iReceiver.Get());
auto pSender = globals::playerManager.GetPlayerBySlot(iSender.Get());
if (pReceiver && pSender)
{
auto listenOverride = pReceiver->GetListen(iSender);
auto senderFlags = pSender->GetVoiceFlags();
auto receiverFlags = pReceiver->GetVoiceFlags();
if (pReceiver->m_selfMutes->Get(iSender.Get()))
{
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, false));
}
if (senderFlags & Speak_Muted)
{
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, false));
}
if (listenOverride == Listen_Mute)
{
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, false));
} else if (listenOverride == Listen_Hear) {
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, true));
}
if ((senderFlags & Speak_All) || (receiverFlags & Speak_ListenAll)) {
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, true));
}
if ((senderFlags & Speak_Team) || (receiverFlags & Speak_ListenTeam))
{
static auto classKey = hash_32_fnv1a_const("CBaseEntity");
static auto memberKey = hash_32_fnv1a_const("m_iTeamNum");
const static auto m_key = schema::GetOffset("CBaseEntity", classKey, "m_iTeamNum", memberKey);
auto receiverController = globals::entitySystem->GetBaseEntity(CEntityIndex(iReceiver.Get() + 1));
auto senderController = globals::entitySystem->GetBaseEntity(CEntityIndex(iSender.Get() + 1));
if (receiverController && senderController)
{
auto receiverTeam = *reinterpret_cast<std::add_pointer_t<unsigned int>>(
(uintptr_t)(receiverController) + m_key.offset);
auto senderTeam = *reinterpret_cast<std::add_pointer_t<unsigned int>>(
(uintptr_t)(senderController) + m_key.offset);
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen,
&IVEngineServer2::SetClientListening,
(iReceiver, iSender, receiverTeam == senderTeam));
}
}
}
RETURN_META_VALUE(MRES_IGNORED, bListen);
}
void VoiceManager::OnClientCommand(CPlayerSlot slot, const CCommand& args)
{
auto pPlayer = globals::playerManager.GetPlayerBySlot(slot.Get());
if (!pPlayer)
return;
if (args.ArgC() > 1 && stricmp(args.Arg(0), "vban") == 0)
{
// clients just refuse to send vban for indexes over 32 and all 4 fields are just the same number, so we only get the first one
//for (int i = 1; (i < args.ArgC()) && (i < 3); i++) {
unsigned int mask = 0;
sscanf(args.Arg(1), "%x", &mask);
pPlayer->m_selfMutes->SetDWord(0, mask);
//}
}
}
} // namespace counterstrikesharp

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/>. *
*/
#pragma once
#include "core/globals.h"
#include "core/global_listener.h"
#include "scripting/script_engine.h"
namespace counterstrikesharp {
class ScriptCallback;
class VoiceManager : public GlobalClass
{
public:
VoiceManager();
~VoiceManager();
void OnAllInitialized() override;
void OnShutdown() override;
bool SetClientListening(CPlayerSlot iReceiver, CPlayerSlot iSender, bool bListen);
void OnClientCommand(CPlayerSlot slot, const CCommand& args);
private:
};
} // namespace counterstrikesharp

Some files were not shown because too many files have changed in this diff Show More