mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-07 00:16:36 -08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d495ac6230 | ||
|
|
f78abf0c81 | ||
|
|
bcacc42d0e | ||
|
|
8235d5e728 | ||
|
|
56739356d5 | ||
|
|
aaba87551d | ||
|
|
a3466dd5d1 | ||
|
|
c8604760f2 | ||
|
|
d50a945317 | ||
|
|
55396e005c | ||
|
|
98b2b01992 | ||
|
|
a537be89e4 | ||
|
|
c07d5d2aa9 | ||
|
|
1cc95555fe | ||
|
|
378c28dfd0 | ||
|
|
c7343c3b7a | ||
|
|
62f6b09f50 |
16
.github/workflows/cmake-single-platform.yml
vendored
16
.github/workflows/cmake-single-platform.yml
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"PublicChatTrigger": [ "!" ],
|
||||
"SilentChatTrigger": [ "/" ],
|
||||
"FollowCS2ServerGuidelines": true,
|
||||
"PluginHotReloadEnabled": true
|
||||
"PluginHotReloadEnabled": true,
|
||||
"ServerLanguage": "en"
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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".
|
||||
5
docfx/examples/WithTranslations.md
Normal file
5
docfx/examples/WithTranslations.md
Normal 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)]
|
||||
@@ -13,5 +13,7 @@ items:
|
||||
href: WithGameEventHandlers.md
|
||||
- name: Database (Dapper)
|
||||
href: WithDatabase.md
|
||||
- name: Translations
|
||||
href: WithTranslations.md
|
||||
- name: Warcraft Plugin
|
||||
href: WarcraftPlugin.md
|
||||
|
||||
@@ -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-primary btn-lg fw-bold my-5">Download <i class="bi bi-arrow-right"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export default {
|
||||
},
|
||||
{
|
||||
icon: "discord",
|
||||
href: "https://discord.gg/X7r3PmuYKq",
|
||||
href: "https://discord.gg/eAZU3guKWU",
|
||||
title: "Discord",
|
||||
},
|
||||
],
|
||||
|
||||
8
examples/WithTranslations/README.md
Normal file
8
examples/WithTranslations/README.md
Normal 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.
|
||||
15
examples/WithTranslations/WithTranslations.csproj
Normal file
15
examples/WithTranslations/WithTranslations.csproj
Normal 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>
|
||||
52
examples/WithTranslations/WithTranslationsPlugin.cs
Normal file
52
examples/WithTranslations/WithTranslationsPlugin.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
examples/WithTranslations/lang/en-GB.json
Normal file
3
examples/WithTranslations/lang/en-GB.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"test.translation": "This is the english (GB) translation"
|
||||
}
|
||||
6
examples/WithTranslations/lang/en.json
Normal file
6
examples/WithTranslations/lang/en.json
Normal 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}"
|
||||
}
|
||||
3
examples/WithTranslations/lang/fr.json
Normal file
3
examples/WithTranslations/lang/fr.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"test.translation": "This is the french translation"
|
||||
}
|
||||
Submodule libraries/hl2sdk-cs2 updated: d7ed476064...1d394d3365
110
managed/CounterStrikeSharp.API.Tests/AdminTests.cs
Normal file
110
managed/CounterStrikeSharp.API.Tests/AdminTests.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace CounterStrikeSharp.API.Tests.Fixtures;
|
||||
|
||||
[CollectionDefinition("Logging collection")]
|
||||
public class LoggingCollection : ICollectionFixture<CoreLoggingFixture>
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"example_command": {
|
||||
"flags": [
|
||||
"@css/custom-permission"
|
||||
],
|
||||
"check_type": "all",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
23
managed/CounterStrikeSharp.API.Tests/Resources/admins.json
Normal file
23
managed/CounterStrikeSharp.API.Tests/Resources/admins.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"test.translation": "This is the english (GB) translation"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"test.translation": "This is the french translation"
|
||||
}
|
||||
52
managed/CounterStrikeSharp.API.Tests/SteamIDTests.cs
Normal file
52
managed/CounterStrikeSharp.API.Tests/SteamIDTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
14
managed/CounterStrikeSharp.API.Tests/TestUtils.cs
Normal file
14
managed/CounterStrikeSharp.API.Tests/TestUtils.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
92
managed/CounterStrikeSharp.API.Tests/TranslationTests.cs
Normal file
92
managed/CounterStrikeSharp.API.Tests/TranslationTests.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
1
managed/CounterStrikeSharp.API.Tests/Usings.cs
Normal file
1
managed/CounterStrikeSharp.API.Tests/Usings.cs
Normal file
@@ -0,0 +1 @@
|
||||
global using Xunit;
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CBasePlayerWeapon
|
||||
{
|
||||
public CBasePlayerWeaponVData? VData => GetVData<CBasePlayerWeaponVData>();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CCSWeaponBase
|
||||
{
|
||||
public new CCSWeaponBaseVData? VData => GetVData<CCSWeaponBaseVData>();
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
@@ -6,6 +6,7 @@ public interface IPluginContext
|
||||
IPlugin Plugin { get; }
|
||||
int PluginId { get; }
|
||||
|
||||
string FilePath { get; }
|
||||
void Load(bool hotReload);
|
||||
void Unload(bool hotReload);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 = "";
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
{
|
||||
|
||||
@@ -26,6 +26,10 @@ 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
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -80,6 +84,14 @@ 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
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
@@ -91,5 +103,6 @@ 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}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -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;
|
||||
@@ -484,7 +494,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 +525,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)
|
||||
{
|
||||
|
||||
@@ -13,4 +13,10 @@
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="lang\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
3
managed/TestPlugin/lang/en-GB.json
Normal file
3
managed/TestPlugin/lang/en-GB.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"testPlugin.maxPlayersAnnouncement": "The server currently has a max player count of {0} players (GB)"
|
||||
}
|
||||
3
managed/TestPlugin/lang/en.json
Normal file
3
managed/TestPlugin/lang/en.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"testPlugin.maxPlayersAnnouncement": "The server currently has a max player count of {0} players",
|
||||
}
|
||||
3
managed/TestPlugin/lang/fr.json
Normal file
3
managed/TestPlugin/lang/fr.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"testPlugin.maxPlayersAnnouncement": "Le serveur compte actuellement un nombre maximum de {0} joueurs."
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -43,6 +43,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;
|
||||
@@ -77,6 +78,7 @@ EntityManager entityManager;
|
||||
ChatManager chatManager;
|
||||
ServerManager serverManager;
|
||||
|
||||
bool gameLoopInitialized = false;
|
||||
GetLegacyGameEventListener_t* GetLegacyGameEventListener = nullptr;
|
||||
std::thread::id gameThreadId;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class IServerPluginHelpers;
|
||||
class IUniformRandomStream;
|
||||
class IEngineTrace;
|
||||
class IEngineSound;
|
||||
class IEngineServiceMgr;
|
||||
class INetworkStringTableContainer;
|
||||
class CGlobalVars;
|
||||
class IFileSystem;
|
||||
@@ -65,6 +66,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;
|
||||
@@ -108,6 +110,7 @@ extern CGameConfig* gameConfig;
|
||||
|
||||
typedef IGameEventListener2 *GetLegacyGameEventListener_t(CPlayerSlot slot);
|
||||
|
||||
extern bool gameLoopInitialized;
|
||||
extern GetLegacyGameEventListener_t* GetLegacyGameEventListener;
|
||||
extern std::thread::id gameThreadId;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace counterstrikesharp {
|
||||
SH_DECL_HOOK3_void(IServerGameDLL, GameFrame, SH_NOATTRIB, 0, bool, bool, bool);
|
||||
SH_DECL_HOOK3_void(INetworkServerService, StartupServer, SH_NOATTRIB, 0,
|
||||
const GameSessionConfiguration_t&, ISource2WorldSession*, const char*);
|
||||
SH_DECL_HOOK3_void(IEngineServiceMgr, RegisterLoopMode, SH_NOATTRIB, 0, const char *, ILoopModeFactory *, void **);
|
||||
SH_DECL_HOOK1(IEngineServiceMgr, FindService, SH_NOATTRIB, 0, IEngineService*, const char*);
|
||||
|
||||
CounterStrikeSharpMMPlugin gPlugin;
|
||||
|
||||
@@ -95,6 +97,8 @@ bool CounterStrikeSharpMMPlugin::Load(PluginId id, ISmmAPI* ismm, char* error, s
|
||||
NETWORKSERVERSERVICE_INTERFACE_VERSION);
|
||||
GET_V_IFACE_ANY(GetEngineFactory, globals::gameEventSystem, IGameEventSystem,
|
||||
GAMEEVENTSYSTEM_INTERFACE_VERSION);
|
||||
GET_V_IFACE_ANY(GetEngineFactory, globals::engineServiceManager, IEngineServiceMgr,
|
||||
ENGINESERVICEMGR_INTERFACE_VERSION);
|
||||
|
||||
auto coreconfig_path = std::string(utils::ConfigsDirectory() + "/core");
|
||||
globals::coreConfig = new CCoreConfig(coreconfig_path);
|
||||
@@ -129,6 +133,8 @@ bool CounterStrikeSharpMMPlugin::Load(PluginId id, ISmmAPI* ismm, char* error, s
|
||||
&CounterStrikeSharpMMPlugin::Hook_GameFrame, true);
|
||||
SH_ADD_HOOK_MEMFUNC(INetworkServerService, StartupServer, globals::networkServerService, this,
|
||||
&CounterStrikeSharpMMPlugin::Hook_StartupServer, true);
|
||||
SH_ADD_HOOK_MEMFUNC(IEngineServiceMgr, RegisterLoopMode, globals::engineServiceManager, this, &CounterStrikeSharpMMPlugin::Hook_RegisterLoopMode, false);
|
||||
SH_ADD_HOOK_MEMFUNC(IEngineServiceMgr, FindService, globals::engineServiceManager, this, &CounterStrikeSharpMMPlugin::Hook_FindService, true);
|
||||
|
||||
if (!globals::dotnetManager.Initialize()) {
|
||||
CSSHARP_CORE_ERROR("Failed to initialize .NET runtime");
|
||||
@@ -214,6 +220,23 @@ void CounterStrikeSharpMMPlugin::OnLevelInit(char const* pMapName, char const* p
|
||||
CSSHARP_CORE_TRACE("name={0},mapname={1}", "LevelInit", pMapName);
|
||||
}
|
||||
|
||||
void CounterStrikeSharpMMPlugin::Hook_RegisterLoopMode(const char *pszLoopModeName, ILoopModeFactory *pLoopModeFactory, void **ppGlobalPointer)
|
||||
{
|
||||
if (strcmp(pszLoopModeName, "game") == 0)
|
||||
{
|
||||
if (!globals::gameLoopInitialized) globals::gameLoopInitialized = true;
|
||||
|
||||
CALL_GLOBAL_LISTENER(OnGameLoopInitialized());
|
||||
}
|
||||
}
|
||||
|
||||
IEngineService* CounterStrikeSharpMMPlugin::Hook_FindService(const char* serviceName)
|
||||
{
|
||||
IEngineService *pService = META_RESULT_ORIG_RET(IEngineService *);
|
||||
|
||||
return pService;
|
||||
}
|
||||
|
||||
void CounterStrikeSharpMMPlugin::OnLevelShutdown() {}
|
||||
|
||||
bool CounterStrikeSharpMMPlugin::Pause(char* error, size_t maxlen) { return true; }
|
||||
|
||||
@@ -49,6 +49,9 @@ public: // hooks
|
||||
const char *);
|
||||
void AddTaskForNextFrame(std::function<void()> &&task);
|
||||
|
||||
void Hook_RegisterLoopMode(const char* pszLoopModeName, ILoopModeFactory *pLoopModeFactory, void **ppGlobalPointer);
|
||||
IEngineService* Hook_FindService(const char* serviceName);
|
||||
|
||||
public:
|
||||
const char *GetAuthor() override;
|
||||
const char *GetName() override;
|
||||
|
||||
Reference in New Issue
Block a user