mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-06 16:06:37 -08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a07dd9d7d4 | ||
|
|
d527038fba | ||
|
|
ca85922270 | ||
|
|
b837479f98 | ||
|
|
1e42f72655 | ||
|
|
1ad1828e30 | ||
|
|
563a5d7b3a | ||
|
|
983b673b4c | ||
|
|
74fd0e0832 | ||
|
|
44e3f2240c | ||
|
|
8af219e7a8 | ||
|
|
bff04e7795 | ||
|
|
d495ac6230 | ||
|
|
f78abf0c81 | ||
|
|
bcacc42d0e | ||
|
|
8235d5e728 | ||
|
|
56739356d5 | ||
|
|
aaba87551d | ||
|
|
a3466dd5d1 | ||
|
|
c8604760f2 |
@@ -22,6 +22,22 @@ saul/demofile-net, https://github.com/saul/demofile-net/blob/main/LICENSE:
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
neverlosecc/source2gen, https://github.com/neverlosecc/source2gen
|
||||
source2gen - Source2 games SDK generator
|
||||
Copyright 2023 neverlosecc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Source2ZE/CS2Fixes:
|
||||
alliedmodders/sourcemod:
|
||||
Source-Python-Dev-Team/Source.Python:
|
||||
|
||||
@@ -75,6 +75,7 @@ SET(SOURCE_FILES
|
||||
src/scripting/natives/natives_memory.cpp
|
||||
src/scripting/natives/natives_schema.cpp
|
||||
src/scripting/natives/natives_entities.cpp
|
||||
src/scripting/natives/natives_voice.cpp
|
||||
src/core/managers/entity_manager.cpp
|
||||
src/core/managers/entity_manager.h
|
||||
src/core/managers/chat_manager.cpp
|
||||
@@ -83,6 +84,8 @@ SET(SOURCE_FILES
|
||||
src/core/managers/server_manager.h
|
||||
src/scripting/natives/natives_server.cpp
|
||||
libraries/nlohmann/json.hpp
|
||||
src/core/managers/voice_manager.cpp
|
||||
src/core/managers/voice_manager.h
|
||||
src/scripting/natives/natives_dynamichooks.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -43,7 +43,7 @@ These features are the core of the platform and work pretty well/have a low risk
|
||||
- [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
|
||||
- [Install Docs](https://docs.cssharp.dev/guides/getting-started/): Installation instructions
|
||||
- [Install Docs](https://docs.cssharp.dev/docs/guides/getting-started.html): Installation instructions
|
||||
- [Example Plugin](managed/TestPlugin/TestPlugin.cs): Test plugin with basic functionality
|
||||
|
||||
## Examples
|
||||
@@ -91,8 +91,8 @@ public class HelloWorldPlugin : BasePlugin
|
||||
|
||||
## Credits
|
||||
|
||||
A lot of code has been borrowed from SourceMod as well as Source.Python, two pioneering source engine plugin frameworks which this project lends a lot of its credit to.
|
||||
I've also used the scripting context & native system that is implemented in FiveM for GTA5. Also shoutout to the [CS2Fixes](https://github.com/Source2ZE/CS2Fixes) project for providing good reverse-engineering information so shortly after CS2 release.
|
||||
A lot of code has been borrowed from [SourceMod](https://github.com/alliedmodders/sourcemod) as well as [Source.Python](https://github.com/Source-Python-Dev-Team/Source.Python), two pioneering source engine plugin frameworks which this project lends a lot of its credit to.
|
||||
I've also used the scripting context & native system that is implemented in [FiveM](https://github.com/citizenfx/fivem) for GTA5. Also shoutout to the [CS2Fixes](https://github.com/Source2ZE/CS2Fixes) project for providing good reverse-engineering information so shortly after CS2 release.
|
||||
|
||||
## How to Build
|
||||
|
||||
@@ -128,3 +128,4 @@ Build
|
||||
```bash
|
||||
cmake --build . --config Debug
|
||||
```
|
||||
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"PublicChatTrigger": [ "!" ],
|
||||
"SilentChatTrigger": [ "/" ],
|
||||
"FollowCS2ServerGuidelines": true,
|
||||
"PluginHotReloadEnabled": true
|
||||
"PluginHotReloadEnabled": true,
|
||||
"ServerLanguage": "en"
|
||||
}
|
||||
@@ -28,8 +28,8 @@
|
||||
},
|
||||
"CCSPlayerController_Respawn": {
|
||||
"offsets": {
|
||||
"windows": 241,
|
||||
"linux": 243
|
||||
"windows": 242,
|
||||
"linux": 244
|
||||
}
|
||||
},
|
||||
"CCSPlayerPawn_Respawn": {
|
||||
@@ -122,14 +122,21 @@
|
||||
},
|
||||
"CBasePlayerPawn_CommitSuicide": {
|
||||
"offsets": {
|
||||
"windows": 356,
|
||||
"linux": 356
|
||||
"windows": 357,
|
||||
"linux": 357
|
||||
}
|
||||
},
|
||||
"CBasePlayerPawn_RemovePlayerItem": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"linux": "\\x55\\x48\\x89\\x2A\\x41\\x2A\\x49\\x89\\x2A\\x41\\x2A\\x49\\x89\\x2A\\xE8\\x2A\\x2A\\x2A\\x2A\\x49\\x39",
|
||||
"windows": "\\x48\\x85\\xD2\\x0F\\x84\\x2A\\x2A\\x2A\\x2A\\x48\\x89\\x5C\\x24\\x08\\x57\\x48\\x83\\xEC\\x30\\x48\\x8B\\xDA"
|
||||
}
|
||||
},
|
||||
"CBaseEntity_Teleport": {
|
||||
"offsets": {
|
||||
"windows": 148,
|
||||
"linux": 147
|
||||
"windows": 149,
|
||||
"linux": 148
|
||||
}
|
||||
},
|
||||
"CBaseEntity_TakeDamageOld": {
|
||||
|
||||
@@ -5,31 +5,52 @@ description: How to get started installing & using CounterStrikeSharp.
|
||||
|
||||
# Getting Started
|
||||
|
||||
How to get started installing & using CounterStrikeSharp.
|
||||
In this guide you will learn how to install CounterStrikeSharp onto your vanilla Counter-Strike 2 server. `CounterStrikeSharp` uses `Metamod:Source` as its main way of communicating with the game server, so both frameworks will need to be installed.
|
||||
|
||||
If you're more of a visual person, here is a <a href="https://www.youtube.com/watch?v=FlsKzStHJuY" target="_blank">Youtube video</a> that covers everything.
|
||||
|
||||
## Prerequisites
|
||||
- <a href="https://www.sourcemm.net/downloads.php/?branch=master" target="_blank">Metamod: Source 2.X Dev Build</a>
|
||||
- <a href="https://github.com/roflmuffin/CounterStrikeSharp/releases" target="_blank">CounterStrikeSharp With Runtime</a>
|
||||
|
||||
## Installing Metamod
|
||||
|
||||
`CounterStrikeSharp` uses `Metamod:Source` as its main way of communicating with the game server. To install it, you can follow the detailed instructions found <a href="https://cs2.poggu.me/metamod/installation/" target="_blank">here</a>.
|
||||
1. Extract Metamod and copy the `/addons/` directory to `/game/csgo/`.
|
||||
2. Inside `/game/csgo/`, locate `gameinfo.gi`.
|
||||
3. Create a new line underneath `Game_LowViolence csgo_lv` and add `Game csgo/addons/metamod`.
|
||||
4. Restart your game server.
|
||||
|
||||
Your `gameinfo.gi` should look like <a href="images/gameinfogi-example.png" target="_blank">this</a>. Type `meta list` in your server console to see if Metamod is loaded.
|
||||
|
||||
## 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**.
|
||||
1. Extract CounterStrikeSharp and copy the `/addons/` directory to `/game/csgo/`.
|
||||
2. Restart your game server.
|
||||
|
||||
Running the command `meta list` in the console should show 1 plugin loaded 🎉
|
||||
|
||||
```shell
|
||||
meta list
|
||||
Listing 1 plugin:
|
||||
[01] CounterStrikeSharp (0.1.0) by Roflmuffin
|
||||
```
|
||||
|
||||
> [!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.
|
||||
> Depending on the os you might also either need to install `libicu` / `icu-libs` / `libicu-dev` using your package manager for .NET to run or setting `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true` in your servers environment variables. You can find more infos about that <a href="https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#enabling-the-invariant-mode" target="_blank">here</a>
|
||||
>
|
||||
> Subsequent upgrades will not require the runtime, unless a version bump of the .NET runtime is required (i.e. from 7.0.x to 8.0.x). We will inform you when this occurs.
|
||||
> For Windows servers, you must have <a href="https://aka.ms/vs/17/release/vc_redist.x64.exe" target="_blank">Visual Studio Redistributables</a> installed otherwise CounterStrikeSharp will not work.
|
||||
|
||||
> [!CAUTION]
|
||||
> For Windows users, you must ensure that you have installed **Visual Studio Redistributables**.
|
||||
> If not, you can download it here: <a href="https://aka.ms/vs/17/release/vc_redist.x64.exe" target="_blank">Download</a>
|
||||
> > This link will download VC Redistributable directly.
|
||||
>
|
||||
> You must install it before starting the server, otherwise CSS will not work!
|
||||
## Upgrading CounterStrikeSharp
|
||||
|
||||
To upgrade CounterStrikeSharp you simply need to download the latest release and copy it to your server, the same as the original installation.
|
||||
|
||||
Extract the `addons` folder to the `/csgo/` directory of the dedicated server. The contents of your addons folder should contain both the `counterstrikesharp` folder and the `metamod` folder as seen below.
|
||||
CounterStrikeSharp is designed in a way where your configuration files will not be overwritten if you do this. As CounterStrikeSharp is already installed, you may download the non `with-runtime` build, but you will need to ensure your .NET runtime is up-to-date yourself.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If this is your first time installing, you **MUST** download the `with-runtime` version. This includes a copy of the .NET runtime, which is required to run the plugin.
|
||||
- Depending on your OS you might also either need to install `libicu` / `icu-libs` / `libicu-dev` using your package manager for .NET to run.
|
||||
- If you get `Unknown Command` when typing `meta list` into your console, double-check the folders are copied over correctly and that your `gameinfo.gi` file is correctly modified.
|
||||
|
||||
Your folder structure should look like this:
|
||||
|
||||
```shell
|
||||
<server_path>/game/csgo/addons > tree -L 2
|
||||
@@ -49,15 +70,3 @@ addons
|
||||
├── metamod.vdf
|
||||
└── metamod_x64.vdf
|
||||
```
|
||||
|
||||
## Start the Server
|
||||
|
||||
Launch your CS2 dedicated server as normal. If everything is working correctly, you should see a message in the console that says `CSSharp: CounterStrikeSharp.API Loaded Successfully.`.
|
||||
|
||||
Running the command `meta list` in the console should show 1 plugin loaded 🎉
|
||||
|
||||
```shell
|
||||
meta list
|
||||
Listing 1 plugin:
|
||||
[01] CounterStrikeSharp (0.1.0) by Roflmuffin
|
||||
```
|
||||
@@ -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)]
|
||||
5
docfx/examples/WithVoiceOverrides.md
Normal file
5
docfx/examples/WithVoiceOverrides.md
Normal file
@@ -0,0 +1,5 @@
|
||||
[!INCLUDE [WithVoiceOverrides](../../examples/WithVoiceOverrides/README.md)]
|
||||
|
||||
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithVoiceOverrides" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
|
||||
|
||||
[!code-csharp[](../../examples/WithVoiceOverrides/WithVoiceOverridesPlugin.cs)]
|
||||
@@ -13,5 +13,9 @@ items:
|
||||
href: WithGameEventHandlers.md
|
||||
- name: Database (Dapper)
|
||||
href: WithDatabase.md
|
||||
- name: Translations
|
||||
href: WithTranslations.md
|
||||
- name: Voice Overrides
|
||||
href: WithVoiceOverrides.md
|
||||
- name: Warcraft Plugin
|
||||
href: WarcraftPlugin.md
|
||||
|
||||
BIN
docfx/images/gameinfogi-example.png
Normal file
BIN
docfx/images/gameinfogi-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 KiB |
@@ -14,6 +14,7 @@ description: Write Counter-Strike 2 server plugins in C#.
|
||||
<span>CounterStrikeSharp is a simpler way to write CS2 server plugins.</span>
|
||||
<div>
|
||||
<a href="docs/guides/getting-started.md" class="btn btn-primary btn-lg fw-bold my-5">Get Started <i class="bi bi-arrow-right"></a>
|
||||
<a href="https://github.com/roflmuffin/CounterStrikeSharp/releases/latest" class="btn btn-secondary btn-lg fw-bold my-5">Download <i class="bi bi-download"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
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"
|
||||
}
|
||||
2
examples/WithVoiceOverrides/README.md
Normal file
2
examples/WithVoiceOverrides/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# With Voice Overrides
|
||||
Provides examples how to manipulate player voice flags & listening overrides to prevent certain players from hearing others.
|
||||
12
examples/WithVoiceOverrides/WithVoiceOverrides.csproj
Normal file
12
examples/WithVoiceOverrides/WithVoiceOverrides.csproj
Normal file
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
79
examples/WithVoiceOverrides/WithVoiceOverridesPlugin.cs
Normal file
79
examples/WithVoiceOverrides/WithVoiceOverridesPlugin.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WithVoiceOverrides;
|
||||
|
||||
[MinimumApiVersion(80)]
|
||||
public class WithVoiceOverridesPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: With Voice Overrides";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
|
||||
public override string ModuleDescription => "A plugin that manipulates voice flags";
|
||||
|
||||
[ConsoleCommand("css_hearall")]
|
||||
public void OnHearAllCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (caller is null) return;
|
||||
|
||||
if (caller.VoiceFlags.HasFlag(VoiceFlags.ListenAll))
|
||||
{
|
||||
caller.VoiceFlags = VoiceFlags.Normal;
|
||||
command.ReplyToCommand("Voice set back to default");
|
||||
}
|
||||
else
|
||||
{
|
||||
caller.VoiceFlags = VoiceFlags.ListenAll;
|
||||
command.ReplyToCommand("Can hear both teams");
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_muteself")]
|
||||
public void OnMuteSelfCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (caller is null) return;
|
||||
|
||||
if (caller.VoiceFlags.HasFlag(VoiceFlags.Muted))
|
||||
{
|
||||
caller.VoiceFlags = VoiceFlags.Normal;
|
||||
command.ReplyToCommand("Unmuted yourself");
|
||||
}
|
||||
else
|
||||
{
|
||||
caller.VoiceFlags = VoiceFlags.Muted;
|
||||
command.ReplyToCommand("Muted yourself");
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_muteothers")]
|
||||
[CommandHelper(minArgs: 1, usage: "[target]")]
|
||||
public void OnMuteOthersCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (caller is null) return;
|
||||
|
||||
var targetResult = command.GetArgTargetResult(1);
|
||||
|
||||
foreach (var player in targetResult.Players)
|
||||
{
|
||||
if (player == caller) continue;
|
||||
|
||||
|
||||
var existingOverride = caller.GetListenOverride(player);
|
||||
if (existingOverride == ListenOverride.Mute)
|
||||
{
|
||||
caller.SetListenOverride(player, ListenOverride.Default);
|
||||
command.ReplyToCommand($"Now hearing {player.PlayerName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
caller.SetListenOverride(player, ListenOverride.Mute);
|
||||
command.ReplyToCommand($"Muted {player.PlayerName}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
<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>
|
||||
@@ -20,9 +21,25 @@
|
||||
<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"
|
||||
}
|
||||
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,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()
|
||||
|
||||
@@ -1311,5 +1311,51 @@ namespace CounterStrikeSharp.API.Core
|
||||
return (bool)ScriptContext.GlobalScriptContext.GetResult(typeof(bool));
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetClientListening(IntPtr receiver, IntPtr sender, uint listen){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(receiver);
|
||||
ScriptContext.GlobalScriptContext.Push(sender);
|
||||
ScriptContext.GlobalScriptContext.Push(listen);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xD38BEE77);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static ListenOverride GetClientListening(IntPtr receiver, IntPtr sender){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(receiver);
|
||||
ScriptContext.GlobalScriptContext.Push(sender);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xE95644E3);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (ListenOverride)ScriptContext.GlobalScriptContext.GetResult(typeof(ListenOverride));
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetClientVoiceFlags(IntPtr client, uint flags){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(client);
|
||||
ScriptContext.GlobalScriptContext.Push(flags);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x48EB2FC8);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static uint GetClientVoiceFlags(IntPtr client){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(client);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x9685205C);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (uint)ScriptContext.GlobalScriptContext.GetResult(typeof(uint));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
using CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Menu;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -40,10 +43,11 @@ namespace CounterStrikeSharp.API.Core
|
||||
private readonly CoreConfig _coreConfig;
|
||||
private readonly IPluginManager _pluginManager;
|
||||
private readonly IPluginContextQueryHandler _pluginContextQueryHandler;
|
||||
private readonly IPlayerLanguageManager _playerLanguageManager;
|
||||
|
||||
public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration scriptHostConfiguration,
|
||||
GameDataProvider gameDataProvider, CoreConfig coreConfig, IPluginManager pluginManager,
|
||||
IPluginContextQueryHandler pluginContextQueryHandler)
|
||||
IPluginContextQueryHandler pluginContextQueryHandler, IPlayerLanguageManager playerLanguageManager)
|
||||
{
|
||||
Logger = loggerFactory.CreateLogger("Core");
|
||||
_scriptHostConfiguration = scriptHostConfiguration;
|
||||
@@ -51,6 +55,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
_coreConfig = coreConfig;
|
||||
_pluginManager = pluginManager;
|
||||
_pluginContextQueryHandler = pluginContextQueryHandler;
|
||||
_playerLanguageManager = playerLanguageManager;
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
@@ -61,19 +66,20 @@ namespace CounterStrikeSharp.API.Core
|
||||
_coreConfig.Load();
|
||||
_gameDataProvider.Load();
|
||||
|
||||
var adminGroupsPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_groups.json");
|
||||
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
|
||||
AdminManager.LoadAdminGroups(adminGroupsPath);
|
||||
|
||||
var adminPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admins.json");
|
||||
Logger.LogInformation("Loading Admins from {Path}", adminPath);
|
||||
AdminManager.LoadAdminData(adminPath);
|
||||
|
||||
var adminGroupsPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_groups.json");
|
||||
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
|
||||
AdminManager.LoadAdminGroups(adminGroupsPath);
|
||||
|
||||
var overridePath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_overrides.json");
|
||||
Logger.LogInformation("Loading Admin Command Overrides from {Path}", overridePath);
|
||||
AdminManager.LoadCommandOverrides(overridePath);
|
||||
|
||||
AdminManager.MergeGroupPermsIntoAdmins();
|
||||
AdminManager.AddCommands();
|
||||
|
||||
_pluginManager.Load();
|
||||
|
||||
@@ -98,7 +104,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
info.ReplyToCommand(
|
||||
" CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
|
||||
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison and CS2Fixes.\n" +
|
||||
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison, source2gen and CS2Fixes.\n" +
|
||||
" See ACKNOWLEDGEMENTS.md for more information.\n" +
|
||||
" Current API Version: " + currentVersion, true);
|
||||
return;
|
||||
@@ -152,7 +158,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
true);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// If our arugment doesn't end in ".dll" - try and construct a path similar to PluginName/PluginName.dll.
|
||||
// We'll assume we have a full path if we have ".dll".
|
||||
var path = info.GetArg(2);
|
||||
@@ -172,7 +178,8 @@ namespace CounterStrikeSharp.API.Core
|
||||
try
|
||||
{
|
||||
_pluginManager.LoadPlugin(path);
|
||||
} catch (Exception e)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
info.ReplyToCommand($"Could not load plugin \"{path}\")", true);
|
||||
}
|
||||
@@ -181,7 +188,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
plugin.Load(false);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -244,11 +251,44 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHelper(usage: "[language code, e.g. \"de\", \"pl\", \"en\"]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private void OnLangCommand(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
SteamID steamId = (SteamID)player.SteamID;
|
||||
|
||||
if (command.ArgCount == 1)
|
||||
{
|
||||
var language = _playerLanguageManager.GetLanguage(steamId);
|
||||
command.ReplyToCommand(string.Format("Current language is \"{0}\" ({1})", language.Name, language.NativeName));
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.ArgCount != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var language = command.GetArg(1);
|
||||
var cultureInfo = CultureInfo.GetCultures(CultureTypes.AllCultures).Single(x => x.Name == language);
|
||||
_playerLanguageManager.SetLanguage(steamId, cultureInfo);
|
||||
command.ReplyToCommand($"Language set to {cultureInfo.NativeName}");
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
command.ReplyToCommand("Language not found.");
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterPluginCommands()
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.",
|
||||
OnCSSPluginCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_lang", "Set Counter-Strike Sharp language", OnLangCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace CounterStrikeSharp.API.Core.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class SchemaMemberAttribute : Attribute
|
||||
{
|
||||
public string ClassName { get; }
|
||||
public string MemberName { get; }
|
||||
|
||||
public SchemaMemberAttribute(string className, string memberName)
|
||||
{
|
||||
ClassName = className;
|
||||
MemberName = memberName;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -93,6 +93,13 @@ namespace CounterStrikeSharp.API.Core
|
||||
[ListenerName("OnClientDisconnectPost")]
|
||||
public delegate void OnClientDisconnectPost(int playerSlot);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a client transmits voice data
|
||||
/// </summary>
|
||||
/// <param name="playerSlot">The player slot of the client.</param>
|
||||
[ListenerName("OnClientVoice")]
|
||||
public delegate void OnClientVoice(int playerSlot);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a client has been authorized by Steam.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,4 +14,13 @@ public partial class CBasePlayerPawn
|
||||
{
|
||||
VirtualFunction.CreateVoid<IntPtr, bool, bool>(Handle, GameData.GetOffset("CBasePlayerPawn_CommitSuicide"))(Handle, explode, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove Player Item
|
||||
/// </summary>
|
||||
/// <param name="weapon"></param>
|
||||
public void RemovePlayerItem(CBasePlayerWeapon weapon)
|
||||
{
|
||||
VirtualFunctions.RemovePlayerItemVirtual(Handle, weapon.Handle);
|
||||
}
|
||||
}
|
||||
@@ -202,6 +202,21 @@ public partial class CCSPlayerController
|
||||
|
||||
public void ExecuteClientCommand(string command) => NativeAPI.IssueClientCommand(Slot, command);
|
||||
|
||||
/// <summary>
|
||||
/// Overrides who a player can hear in voice chat.
|
||||
/// </summary>
|
||||
/// <param name="sender">Player talking in the voice chat</param>
|
||||
/// <param name="override">Whether the talker should be heard</param>
|
||||
public void SetListenOverride(CCSPlayerController sender, ListenOverride @override)
|
||||
{
|
||||
NativeAPI.SetClientListening(Handle, sender.Handle, (Byte)@override);
|
||||
}
|
||||
|
||||
public ListenOverride GetListenOverride(CCSPlayerController sender)
|
||||
{
|
||||
return NativeAPI.GetClientListening(Handle, sender.Handle);
|
||||
}
|
||||
|
||||
public int Slot => (int)Index - 1;
|
||||
|
||||
/// <summary>
|
||||
@@ -234,4 +249,16 @@ public partial class CCSPlayerController
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines how the player interacts with voice chat.
|
||||
/// </summary>
|
||||
public VoiceFlags VoiceFlags
|
||||
{
|
||||
get => (VoiceFlags)NativeAPI.GetClientVoiceFlags(Handle);
|
||||
set
|
||||
{
|
||||
NativeAPI.SetClientVoiceFlags(Handle, (Byte)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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(target.AuthorizedSteamID);
|
||||
if (targetData == null) return true;
|
||||
|
||||
return callerData.Immunity >= targetData.Immunity;
|
||||
@@ -381,12 +506,15 @@ 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;
|
||||
|
||||
var targetData = GetPlayerAdminData(caller);
|
||||
var targetData = GetPlayerAdminData(target);
|
||||
if (targetData == null) return true;
|
||||
|
||||
return callerData.Immunity >= targetData.Immunity;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
@@ -76,4 +76,8 @@ public static class VirtualFunctions
|
||||
|
||||
public static MemoryFunctionVoid<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouchFunc = new (GameData.GetSignature("CBaseTrigger_EndTouch"));
|
||||
public static Action<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouch = CBaseTrigger_EndTouchFunc.Invoke;
|
||||
|
||||
public static MemoryFunctionVoid<IntPtr, IntPtr> RemovePlayerItemFunc =
|
||||
new(GameData.GetSignature("CBasePlayerPawn_RemovePlayerItem"));
|
||||
public static Action<IntPtr, IntPtr> RemovePlayerItemVirtual = RemovePlayerItemFunc.Invoke;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,41 @@ namespace CounterStrikeSharp.API
|
||||
return new Target(pattern).GetTarget(player);
|
||||
}
|
||||
|
||||
public static bool RemoveItemByDesignerName(this CCSPlayerController player, string designerName)
|
||||
{
|
||||
return RemoveItemByDesignerName(player, designerName, false);
|
||||
}
|
||||
|
||||
public static bool RemoveItemByDesignerName(this CCSPlayerController player, string designerName, bool shouldRemoveEntity)
|
||||
{
|
||||
CHandle<CBasePlayerWeapon>? item = null;
|
||||
if (player.PlayerPawn.Value == null || player.PlayerPawn.Value.WeaponServices == null) return false;
|
||||
|
||||
foreach(var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
|
||||
{
|
||||
if (weapon is not { IsValid: true, Value.IsValid: true })
|
||||
continue;
|
||||
if (weapon.Value.DesignerName != designerName)
|
||||
continue;
|
||||
|
||||
item = weapon;
|
||||
}
|
||||
|
||||
if (item != null && item.Value != null)
|
||||
{
|
||||
player.PlayerPawn.Value.RemovePlayerItem(item.Value);
|
||||
|
||||
if (shouldRemoveEntity)
|
||||
{
|
||||
item.Value.Remove();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> FindAllEntitiesByDesignerName<T>(string designerName) where T : CEntityInstance
|
||||
{
|
||||
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
|
||||
|
||||
36
managed/CounterStrikeSharp.API/VoiceFlags.cs
Normal file
36
managed/CounterStrikeSharp.API/VoiceFlags.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
namespace CounterStrikeSharp.API
|
||||
{
|
||||
[Flags]
|
||||
public enum VoiceFlags : Byte
|
||||
{
|
||||
Normal = 0,
|
||||
Muted = (1 << 0),
|
||||
All = (1 << 1),
|
||||
ListenAll = (1 << 2),
|
||||
Team = (1 << 3),
|
||||
ListenTeam = (1 << 4),
|
||||
}
|
||||
|
||||
public enum ListenOverride
|
||||
{
|
||||
Default = 0,
|
||||
Mute,
|
||||
Hear
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,9 @@ internal static partial class Program
|
||||
continue;
|
||||
|
||||
// Putting these in the too hard basket for now.
|
||||
if (field.Name == "m_VoteOptions" || field.Name == "m_aShootSounds") continue;
|
||||
if (field.Name == "m_VoteOptions" || field.Name == "m_aShootSounds" || field.Name == "m_pVecRelationships") continue;
|
||||
if (IgnoreClasses.Contains(field.Type.Name)) continue;
|
||||
if (field.Type.Category == SchemaTypeCategory.Bitfield) continue;
|
||||
|
||||
if (field.Type is { Category: SchemaTypeCategory.Atomic, Atomic: SchemaAtomicCategory.Collection })
|
||||
{
|
||||
@@ -242,9 +244,10 @@ internal static partial class Program
|
||||
|
||||
var handleParams = $"this.Handle, \"{schemaClassName}\", \"{field.Name}\"";
|
||||
|
||||
builder.AppendLine($" // {field.Name}");
|
||||
builder.AppendLine($"\t// {field.Name}");
|
||||
builder.AppendLine($"\t[SchemaMember(\"{schemaClassName}\", \"{field.Name}\")]");
|
||||
|
||||
if (field.Type is { Category: SchemaTypeCategory.FixedArray, CsTypeName: "string" })
|
||||
if (field.Type is { Category: SchemaTypeCategory.FixedArray, CsTypeName: "string" } or { Category: SchemaTypeCategory.Ptr, CsTypeName: "string" })
|
||||
{
|
||||
var getter = $"return Schema.GetString({handleParams});";
|
||||
var setter = $"Schema.SetString({handleParams}, value);";
|
||||
|
||||
@@ -4816,10 +4816,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_eThrowStatus",
|
||||
"name": "m_bThrowAnimating",
|
||||
"type": {
|
||||
"category": 6,
|
||||
"name": "EGrenadeThrowState"
|
||||
"category": 0,
|
||||
"name": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -4849,6 +4849,40 @@
|
||||
"category": 5,
|
||||
"name": "GameTime_t"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_bJustPulledPin",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_nNextHoldTick",
|
||||
"type": {
|
||||
"category": 5,
|
||||
"name": "GameTick_t"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_flNextHoldFrac",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "float32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_hSwitchToWeaponAfterThrow",
|
||||
"type": {
|
||||
"atomic": 1,
|
||||
"category": 4,
|
||||
"inner": {
|
||||
"category": 5,
|
||||
"name": "CCSWeaponBase"
|
||||
},
|
||||
"name": "CHandle< CCSWeaponBase >",
|
||||
"outer": "CHandle"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parent": "CCSWeaponBase"
|
||||
@@ -6312,7 +6346,7 @@
|
||||
"category": 0,
|
||||
"name": "char"
|
||||
},
|
||||
"name": "char[4096]"
|
||||
"name": "char[260]"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -6842,6 +6876,13 @@
|
||||
"name": "int64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_nLastRealCommandNumberExecuted",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "int32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_iIgnoreGlobalChat",
|
||||
"type": {
|
||||
@@ -16738,10 +16779,17 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_bPlayerFireEventIsPrimary",
|
||||
"name": "m_ePlayerFireEvent",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "bool"
|
||||
"category": 6,
|
||||
"name": "PlayerAnimEvent_t"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_ePlayerFireEventAttackType",
|
||||
"type": {
|
||||
"category": 6,
|
||||
"name": "WeaponAttackType_t"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -16799,7 +16847,7 @@
|
||||
"category": 5,
|
||||
"name": "HSequence"
|
||||
},
|
||||
"name": "HSequence[6]"
|
||||
"name": "HSequence[7]"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -16951,10 +16999,17 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_flPostponeFireReadyTime",
|
||||
"name": "m_nPostponeFireReadyTicks",
|
||||
"type": {
|
||||
"category": 5,
|
||||
"name": "GameTime_t"
|
||||
"name": "GameTick_t"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_flPostponeFireReadyFrac",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "float32"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -32791,40 +32846,6 @@
|
||||
],
|
||||
"parent": "CLogicalEntity"
|
||||
},
|
||||
"CLogicEventListener": {
|
||||
"fields": [
|
||||
{
|
||||
"name": "m_strEventName",
|
||||
"type": {
|
||||
"atomic": 0,
|
||||
"category": 4,
|
||||
"name": "CUtlString"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_bIsEnabled",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_nTeam",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "int32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_OnEventFired",
|
||||
"type": {
|
||||
"category": 5,
|
||||
"name": "CEntityIOOutput"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parent": "CLogicalEntity"
|
||||
},
|
||||
"CLogicGameEvent": {
|
||||
"fields": [
|
||||
{
|
||||
@@ -34452,35 +34473,7 @@
|
||||
"parent": "CLogicalEntity"
|
||||
},
|
||||
"CMelee": {
|
||||
"fields": [
|
||||
{
|
||||
"name": "m_flThrowAt",
|
||||
"type": {
|
||||
"category": 5,
|
||||
"name": "GameTime_t"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_hThrower",
|
||||
"type": {
|
||||
"atomic": 1,
|
||||
"category": 4,
|
||||
"inner": {
|
||||
"category": 5,
|
||||
"name": "CBaseEntity"
|
||||
},
|
||||
"name": "CHandle< CBaseEntity >",
|
||||
"outer": "CHandle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_bDidThrowDamage",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "bool"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": [],
|
||||
"parent": "CCSWeaponBase"
|
||||
},
|
||||
"CMeshletDescriptor": {
|
||||
@@ -54566,10 +54559,6 @@
|
||||
"fields": [],
|
||||
"parent": "CBaseTrigger"
|
||||
},
|
||||
"CTriggerHostageReset": {
|
||||
"fields": [],
|
||||
"parent": "CBaseTrigger"
|
||||
},
|
||||
"CTriggerHurt": {
|
||||
"fields": [
|
||||
{
|
||||
@@ -70344,7 +70333,7 @@
|
||||
"category": 0,
|
||||
"name": "char"
|
||||
},
|
||||
"name": "char[4096]"
|
||||
"name": "char[260]"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -76074,6 +76063,20 @@
|
||||
"category": 0,
|
||||
"name": "float32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_flTickInterval",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "float32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m_flTickStartTime",
|
||||
"type": {
|
||||
"category": 0,
|
||||
"name": "float64"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -90386,23 +90389,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"C4LightEffect_t": {
|
||||
"align": 4,
|
||||
"items": [
|
||||
{
|
||||
"name": "eLightEffectNone",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "eLightEffectDropped",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "eLightEffectThirdPersonHeld",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"CAnimationGraphVisualizerPrimitiveType": {
|
||||
"align": 4,
|
||||
"items": [
|
||||
@@ -91374,23 +91360,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"EGrenadeThrowState": {
|
||||
"align": 4,
|
||||
"items": [
|
||||
{
|
||||
"name": "NotThrowing",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "Throwing",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "ThrowComplete",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"EInButtonState": {
|
||||
"align": 4,
|
||||
"items": [
|
||||
@@ -96428,7 +96397,7 @@
|
||||
},
|
||||
{
|
||||
"name": "ALL_CONTEXTS",
|
||||
"value": 4293918720
|
||||
"value": 18446744073708503040
|
||||
},
|
||||
{
|
||||
"name": "ALL_SCENTS",
|
||||
|
||||
@@ -40,7 +40,7 @@ public record SchemaFieldType
|
||||
}
|
||||
|
||||
public bool IsString =>
|
||||
Category == SchemaTypeCategory.FixedArray
|
||||
(Category == SchemaTypeCategory.FixedArray || Category == SchemaTypeCategory.Ptr)
|
||||
&& Inner!.Category == SchemaTypeCategory.Builtin
|
||||
&& Inner.Name == "char";
|
||||
|
||||
@@ -98,7 +98,8 @@ public record SchemaFieldType
|
||||
public string CsTypeName => Category switch
|
||||
{
|
||||
SchemaTypeCategory.Builtin => BuiltinToCsKeyword(Name),
|
||||
SchemaTypeCategory.Ptr => $"{Inner!.CsTypeName}?",
|
||||
SchemaTypeCategory.Ptr => IsString
|
||||
? "string" : $"{Inner!.CsTypeName}?",
|
||||
SchemaTypeCategory.FixedArray => IsString
|
||||
? "string"
|
||||
: $"{Inner!.CsTypeName}[]",
|
||||
|
||||
@@ -28,6 +28,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithEntityOutputHooks", "..
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CounterStrikeSharp.API.Tests", "CounterStrikeSharp.API.Tests\CounterStrikeSharp.API.Tests.csproj", "{BBA80E1B-109D-4ABD-9ADF-46EB0FEDFCD3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithTranslations", "..\examples\WithTranslations\WithTranslations.csproj", "{BB44E08E-CCA8-4E22-A132-11B2F69D1890}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithVoiceOverrides", "..\examples\WithVoiceOverrides\WithVoiceOverrides.csproj", "{6FA3107D-42AF-42A0-BF51-2230D13268B5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -86,6 +90,14 @@ Global
|
||||
{BBA80E1B-109D-4ABD-9ADF-46EB0FEDFCD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BBA80E1B-109D-4ABD-9ADF-46EB0FEDFCD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BBA80E1B-109D-4ABD-9ADF-46EB0FEDFCD3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
@@ -97,5 +109,7 @@ Global
|
||||
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
{31EABE0B-871F-497B-BF36-37FFC6FAD15F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
{BB44E08E-CCA8-4E22-A132-11B2F69D1890} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
{6FA3107D-42AF-42A0-BF51-2230D13268B5} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -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
|
||||
@@ -462,6 +465,43 @@ namespace TestPlugin
|
||||
|
||||
player.PlayerPawn.Value.CommitSuicide(true, true);
|
||||
}
|
||||
|
||||
[CommandHelper(minArgs: 1, usage: "[weaponName]")]
|
||||
[ConsoleCommand("css_strip", "Removes weapon by name")]
|
||||
public void OnStripActiveWeapon(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
|
||||
player.RemoveItemByDesignerName(command.GetArg(1));
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_stripweapons", "Removes player weapons")]
|
||||
public void OnStripWeapons(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
|
||||
player.RemoveWeapons();
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_teleportup", "Teleports the player up")]
|
||||
public void OnTeleport(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
|
||||
player.PlayerPawn.Value.Teleport(player.PlayerPawn.Value.AbsOrigin.With(z: player.PlayerPawn.Value.AbsOrigin.Z + 100), player.PlayerPawn.Value.AbsRotation, new Vector(IntPtr.Zero));
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_respawn", "Respawns the player")]
|
||||
public void OnRespawn(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
|
||||
player.Respawn();
|
||||
}
|
||||
|
||||
[ConsoleCommand("cssharp_attribute", "This is a custom attribute event")]
|
||||
public void OnCommand(CCSPlayerController? player, CommandInfo command)
|
||||
@@ -522,6 +562,13 @@ namespace TestPlugin
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_localetest", "Test Translations")]
|
||||
public void OnCommandLocaleTest(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
Logger.LogInformation("Current Culture is {Culture}", CultureInfo.CurrentCulture);
|
||||
command.ReplyToCommand(Localizer["testPlugin.maxPlayersAnnouncement", Server.MaxPlayers]);
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_sound", "Play a sound to client")]
|
||||
public void OnCommandSound(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
// Copyright (C) 2023 neverlosecc
|
||||
// See end of file for extended copyright information.
|
||||
/**
|
||||
* =============================================================================
|
||||
* Source2Gen
|
||||
* Copyright (C) 2023 neverlose (https://github.com/neverlosecc/source2gen)
|
||||
* =============================================================================
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
@@ -392,3 +401,18 @@ class CSchemaSystem
|
||||
return CALL_VIRTUAL(CSchemaSystemTypeScope*, 13, this, m_module_name, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
// source2gen - Source2 games SDK generator
|
||||
// Copyright 2023 neverlosecc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "interfaces/cs2_interfaces.h"
|
||||
#include "core/managers/entity_manager.h"
|
||||
#include "core/managers/server_manager.h"
|
||||
#include "core/managers/voice_manager.h"
|
||||
#include <public/game/server/iplayerinfo.h>
|
||||
#include <public/entity2/entitysystem.h>
|
||||
|
||||
@@ -77,6 +78,7 @@ ConCommandManager conCommandManager;
|
||||
EntityManager entityManager;
|
||||
ChatManager chatManager;
|
||||
ServerManager serverManager;
|
||||
VoiceManager voiceManager;
|
||||
|
||||
bool gameLoopInitialized = false;
|
||||
GetLegacyGameEventListener_t* GetLegacyGameEventListener = nullptr;
|
||||
|
||||
@@ -53,6 +53,7 @@ class HookManager;
|
||||
class EntityManager;
|
||||
class ChatManager;
|
||||
class ServerManager;
|
||||
class VoiceManager;
|
||||
class CCoreConfig;
|
||||
class CGameConfig;
|
||||
|
||||
@@ -98,6 +99,7 @@ extern TimerSystem timerSystem;
|
||||
extern ChatCommands chatCommands;
|
||||
extern ChatManager chatManager;
|
||||
extern ServerManager serverManager;
|
||||
extern VoiceManager voiceManager;
|
||||
|
||||
extern HookManager hookManager;
|
||||
extern SourceHook::ISourceHook *source_hook;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "core/managers/player_manager.h"
|
||||
#include "core/managers/con_command_manager.h"
|
||||
#include "core/managers/voice_manager.h"
|
||||
|
||||
#include <public/eiface.h>
|
||||
#include <public/inetchannelinfo.h>
|
||||
@@ -45,8 +46,10 @@
|
||||
|
||||
SH_DECL_HOOK4_void(IServerGameClients, ClientActive, SH_NOATTRIB, 0, CPlayerSlot, bool, const char*,
|
||||
uint64);
|
||||
SH_DECL_HOOK5_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, 0, CPlayerSlot, ENetworkDisconnectionReason,
|
||||
const char*, uint64, const char*);
|
||||
SH_DECL_HOOK5_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, 0, CPlayerSlot,
|
||||
ENetworkDisconnectionReason, const char*, uint64, const char*);
|
||||
|
||||
SH_DECL_HOOK1_void(IServerGameClients, ClientVoice, SH_NOATTRIB, 0, CPlayerSlot);
|
||||
SH_DECL_HOOK4_void(IServerGameClients, ClientPutInServer, SH_NOATTRIB, 0, CPlayerSlot, char const*,
|
||||
int, uint64);
|
||||
SH_DECL_HOOK1_void(IServerGameClients, ClientSettingsChanged, SH_NOATTRIB, 0, CPlayerSlot);
|
||||
@@ -75,6 +78,8 @@ void PlayerManager::OnAllInitialized()
|
||||
SH_MEMBER(this, &PlayerManager::OnClientDisconnect_Post), true);
|
||||
SH_ADD_HOOK(IServerGameClients, ClientCommand, globals::serverGameClients,
|
||||
SH_MEMBER(this, &PlayerManager::OnClientCommand), false);
|
||||
SH_ADD_HOOK(IServerGameClients, ClientVoice, globals::serverGameClients,
|
||||
SH_MEMBER(this, &PlayerManager::OnClientVoice), true);
|
||||
|
||||
m_on_client_connect_callback = globals::callbackManager.CreateCallback("OnClientConnect");
|
||||
m_on_client_connected_callback = globals::callbackManager.CreateCallback("OnClientConnected");
|
||||
@@ -83,6 +88,7 @@ void PlayerManager::OnAllInitialized()
|
||||
m_on_client_disconnect_callback = globals::callbackManager.CreateCallback("OnClientDisconnect");
|
||||
m_on_client_disconnect_post_callback =
|
||||
globals::callbackManager.CreateCallback("OnClientDisconnectPost");
|
||||
m_on_client_voice_callback = globals::callbackManager.CreateCallback("OnClientVoice");
|
||||
m_on_client_authorized_callback = globals::callbackManager.CreateCallback("OnClientAuthorized");
|
||||
}
|
||||
|
||||
@@ -100,6 +106,8 @@ void PlayerManager::OnShutdown()
|
||||
SH_MEMBER(this, &PlayerManager::OnClientDisconnect_Post), true);
|
||||
SH_REMOVE_HOOK(IServerGameClients, ClientCommand, globals::serverGameClients,
|
||||
SH_MEMBER(this, &PlayerManager::OnClientCommand), false);
|
||||
SH_REMOVE_HOOK(IServerGameClients, ClientVoice, globals::serverGameClients,
|
||||
SH_MEMBER(this, &PlayerManager::OnClientVoice), true);
|
||||
|
||||
globals::callbackManager.ReleaseCallback(m_on_client_connect_callback);
|
||||
globals::callbackManager.ReleaseCallback(m_on_client_connected_callback);
|
||||
@@ -107,6 +115,7 @@ void PlayerManager::OnShutdown()
|
||||
globals::callbackManager.ReleaseCallback(m_on_client_disconnect_callback);
|
||||
globals::callbackManager.ReleaseCallback(m_on_client_disconnect_post_callback);
|
||||
globals::callbackManager.ReleaseCallback(m_on_client_authorized_callback);
|
||||
globals::callbackManager.ReleaseCallback(m_on_client_voice_callback);
|
||||
}
|
||||
|
||||
bool PlayerManager::OnClientConnect(CPlayerSlot slot, const char* pszName, uint64 xuid,
|
||||
@@ -120,8 +129,10 @@ bool PlayerManager::OnClientConnect(CPlayerSlot slot, const char* pszName, uint6
|
||||
CPlayer* pPlayer = &m_players[client];
|
||||
|
||||
if (pPlayer->IsConnected()) {
|
||||
OnClientDisconnect(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, pszName, xuid, pszNetworkID);
|
||||
OnClientDisconnect_Post(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, pszName, xuid, pszNetworkID);
|
||||
OnClientDisconnect(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, pszName,
|
||||
xuid, pszNetworkID);
|
||||
OnClientDisconnect_Post(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID,
|
||||
pszName, xuid, pszNetworkID);
|
||||
}
|
||||
|
||||
pPlayer->Initialize(pszName, pszNetworkID, slot);
|
||||
@@ -222,8 +233,7 @@ void PlayerManager::OnClientPutInServer(CPlayerSlot slot, char const* pszName, i
|
||||
m_on_client_put_in_server_callback->Execute();
|
||||
}
|
||||
|
||||
void PlayerManager::OnClientDisconnect(CPlayerSlot slot,
|
||||
ENetworkDisconnectionReason reason,
|
||||
void PlayerManager::OnClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason,
|
||||
const char* pszName, uint64 xuid, const char* pszNetworkID)
|
||||
{
|
||||
CSSHARP_CORE_TRACE("[PlayerManager][OnClientDisconnect] - {}, {}, {}", slot.Get(), pszName,
|
||||
@@ -246,8 +256,7 @@ void PlayerManager::OnClientDisconnect(CPlayerSlot slot,
|
||||
// globals::entityListener.HandleEntityDeleted(pPlayer->GetBaseEntity(), client);
|
||||
}
|
||||
|
||||
void PlayerManager::OnClientDisconnect_Post(CPlayerSlot slot,
|
||||
ENetworkDisconnectionReason reason,
|
||||
void PlayerManager::OnClientDisconnect_Post(CPlayerSlot slot, ENetworkDisconnectionReason reason,
|
||||
const char* pszName, uint64 xuid,
|
||||
const char* pszNetworkID) const
|
||||
{
|
||||
@@ -269,16 +278,27 @@ void PlayerManager::OnClientDisconnect_Post(CPlayerSlot slot,
|
||||
m_on_client_disconnect_post_callback->Execute();
|
||||
}
|
||||
|
||||
void PlayerManager::OnClientVoice(CPlayerSlot slot) const
|
||||
{
|
||||
CSSHARP_CORE_TRACE("[PlayerManager][OnClientVoice] - {}", slot.Get());
|
||||
|
||||
m_on_client_voice_callback->ScriptContext().Reset();
|
||||
m_on_client_voice_callback->ScriptContext().Push(slot.Get());
|
||||
m_on_client_voice_callback->Execute();
|
||||
}
|
||||
|
||||
void PlayerManager::OnLevelEnd()
|
||||
{
|
||||
CSSHARP_CORE_TRACE("[PlayerManager][OnLevelEnd]");
|
||||
|
||||
for (int i = 0; i <= m_max_clients; i++) {
|
||||
for (int i = 0; i <= MaxClients(); i++) {
|
||||
if (m_players[i].IsConnected()) {
|
||||
OnClientDisconnect(m_players[i].m_slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, m_players[i].GetName(), 0,
|
||||
m_players[i].GetIpAddress());
|
||||
OnClientDisconnect_Post(m_players[i].m_slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, m_players[i].GetName(), 0,
|
||||
m_players[i].GetIpAddress());
|
||||
OnClientDisconnect(m_players[i].m_slot,
|
||||
ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID,
|
||||
m_players[i].GetName(), 0, m_players[i].GetIpAddress());
|
||||
OnClientDisconnect_Post(m_players[i].m_slot,
|
||||
ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID,
|
||||
m_players[i].GetName(), 0, m_players[i].GetIpAddress());
|
||||
}
|
||||
}
|
||||
m_player_count = 0;
|
||||
@@ -286,10 +306,13 @@ void PlayerManager::OnLevelEnd()
|
||||
|
||||
void PlayerManager::OnClientCommand(CPlayerSlot slot, const CCommand& args) const
|
||||
{
|
||||
CSSHARP_CORE_TRACE("[PlayerManager][OnClientCommand] - {}, {}, {}", slot.Get(), args.Arg(0), (void*)&args);
|
||||
CSSHARP_CORE_TRACE("[PlayerManager][OnClientCommand] - {}, {}, {}", slot.Get(), args.Arg(0),
|
||||
(void*)&args);
|
||||
|
||||
const char* cmd = args.Arg(0);
|
||||
|
||||
globals::voiceManager.OnClientCommand(slot, args);
|
||||
|
||||
auto result = globals::conCommandManager.ExecuteCommandCallbacks(
|
||||
cmd, CCommandContext(CommandTarget_t::CT_NO_TARGET, slot), args, HookMode::Pre);
|
||||
|
||||
@@ -302,11 +325,11 @@ int PlayerManager::ListenClient() const { return m_listen_client; }
|
||||
|
||||
int PlayerManager::NumPlayers() const { return m_player_count; }
|
||||
|
||||
int PlayerManager::MaxClients() const { return m_max_clients; }
|
||||
int PlayerManager::MaxClients() const { return globals::getGlobalVars()->maxClients; }
|
||||
|
||||
CPlayer* PlayerManager::GetPlayerBySlot(int client) const
|
||||
{
|
||||
if (client > m_max_clients || client < 0) {
|
||||
if (client > MaxClients() || client < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -426,7 +449,6 @@ INetChannelInfo* CPlayer::GetNetInfo() const { return globals::engine->GetPlayer
|
||||
|
||||
PlayerManager::PlayerManager()
|
||||
{
|
||||
m_max_clients = 64;
|
||||
m_players = new CPlayer[66];
|
||||
m_player_count = 0;
|
||||
m_user_id_lookup = new int[USHRT_MAX + 1];
|
||||
@@ -441,7 +463,7 @@ void PlayerManager::RunAuthChecks()
|
||||
|
||||
m_last_auth_check_time = globals::timerSystem.GetTickedTime();
|
||||
|
||||
for (int i = 0; i <= m_max_clients; i++) {
|
||||
for (int i = 0; i <= MaxClients(); i++) {
|
||||
if (m_players[i].IsConnected()) {
|
||||
if (m_players[i].IsAuthorized() || m_players[i].IsFakeClient())
|
||||
continue;
|
||||
@@ -532,6 +554,17 @@ float CPlayer::GetLatency() const
|
||||
return GetNetInfo()->GetLatency(FLOW_INCOMING) + GetNetInfo()->GetLatency(FLOW_OUTGOING);
|
||||
}
|
||||
|
||||
void CPlayer::SetListen(CPlayerSlot slot, ListenOverride listen)
|
||||
{
|
||||
m_listenMap[slot.Get()] = listen;
|
||||
}
|
||||
|
||||
void CPlayer::SetVoiceFlags(VoiceFlag_t flags) { m_voiceFlag = flags; }
|
||||
|
||||
VoiceFlag_t CPlayer::GetVoiceFlags() { return m_voiceFlag; }
|
||||
|
||||
ListenOverride CPlayer::GetListen(CPlayerSlot slot) const { return m_listenMap[slot.Get()]; }
|
||||
|
||||
void CPlayer::Connect()
|
||||
{
|
||||
if (m_is_in_game) {
|
||||
@@ -551,6 +584,9 @@ void CPlayer::Disconnect()
|
||||
m_user_id = -1;
|
||||
m_is_authorized = false;
|
||||
m_ip_address.clear();
|
||||
m_selfMutes->ClearAll();
|
||||
memset(m_listenMap, 0, sizeof m_listenMap);
|
||||
m_voiceFlag = 0;
|
||||
}
|
||||
|
||||
QAngle CPlayer::GetAbsAngles() const { return m_info->GetAbsAngles(); }
|
||||
|
||||
@@ -45,30 +45,50 @@ namespace counterstrikesharp {
|
||||
class ScriptCallback;
|
||||
class CBaseEntityWrapper;
|
||||
|
||||
class CPlayer {
|
||||
enum ListenOverride
|
||||
{
|
||||
Listen_Default = 0,
|
||||
Listen_Mute,
|
||||
Listen_Hear
|
||||
};
|
||||
|
||||
enum VoiceFlagValue
|
||||
{
|
||||
Speak_Normal = 0,
|
||||
Speak_Muted = 1 << 0,
|
||||
Speak_All = 1 << 1,
|
||||
Speak_ListenAll = 1 << 2,
|
||||
Speak_Team = 1 << 3,
|
||||
Speak_ListenTeam = 1 << 4,
|
||||
};
|
||||
|
||||
typedef uint8_t VoiceFlag_t;
|
||||
|
||||
class CPlayer
|
||||
{
|
||||
friend class PlayerManager;
|
||||
|
||||
public:
|
||||
public:
|
||||
CPlayer();
|
||||
|
||||
public:
|
||||
void Initialize(const char *name, const char *ip, CPlayerSlot slot);
|
||||
public:
|
||||
void Initialize(const char* name, const char* ip, CPlayerSlot slot);
|
||||
void Connect();
|
||||
void Disconnect();
|
||||
IPlayerInfo *GetPlayerInfo() const;
|
||||
IPlayerInfo* GetPlayerInfo() const;
|
||||
bool WasCountedAsInGame() const;
|
||||
int GetUserId();
|
||||
bool IsAuthStringValidated() const;
|
||||
void Authorize();
|
||||
|
||||
public:
|
||||
const char *GetName() const;
|
||||
const CSteamID *GetSteamId();
|
||||
void SetSteamId(const CSteamID *steam_id);
|
||||
public:
|
||||
const char* GetName() const;
|
||||
const CSteamID* GetSteamId();
|
||||
void SetSteamId(const CSteamID* steam_id);
|
||||
bool IsConnected() const;
|
||||
bool IsFakeClient() const;
|
||||
bool IsAuthorized() const;
|
||||
void PrintToConsole(const char *message) const;
|
||||
void PrintToConsole(const char* message) const;
|
||||
// void PrintToChat(const char *message);
|
||||
// void PrintToHint(const char *message);
|
||||
// void PrintToCenter(const char *message);
|
||||
@@ -76,26 +96,30 @@ public:
|
||||
Vector GetAbsOrigin() const;
|
||||
bool IsAlive() const;
|
||||
bool IsInGame() const;
|
||||
void Kick(const char *kickReason);
|
||||
const char *GetWeaponName() const;
|
||||
void Kick(const char* kickReason);
|
||||
const char* GetWeaponName() const;
|
||||
void ChangeTeam(int team) const;
|
||||
int GetTeam() const;
|
||||
int GetArmor() const;
|
||||
int GetFrags() const;
|
||||
int GetDeaths() const;
|
||||
const char *GetKeyValue(const char *key) const;
|
||||
const char* GetKeyValue(const char* key) const;
|
||||
Vector GetMaxSize() const;
|
||||
Vector GetMinSize() const;
|
||||
int GetMaxHealth() const;
|
||||
const char *GetIpAddress() const;
|
||||
const char *GetModelName() const;
|
||||
const char* GetIpAddress() const;
|
||||
const char* GetModelName() const;
|
||||
int GetUserId() const;
|
||||
float GetTimeConnected() const;
|
||||
float GetLatency() const;
|
||||
void SetListen(CPlayerSlot slot, ListenOverride listen);
|
||||
void SetVoiceFlags(VoiceFlag_t flags);
|
||||
VoiceFlag_t GetVoiceFlags();
|
||||
ListenOverride GetListen(CPlayerSlot slot) const;
|
||||
|
||||
public:
|
||||
public:
|
||||
std::string m_name;
|
||||
IPlayerInfo *m_info = nullptr;
|
||||
IPlayerInfo* m_info = nullptr;
|
||||
std::string m_auth_id;
|
||||
bool m_is_connected = false;
|
||||
bool m_is_fake_client = false;
|
||||
@@ -105,72 +129,64 @@ public:
|
||||
CPlayerSlot m_slot = CPlayerSlot(-1);
|
||||
const CSteamID* m_steamId;
|
||||
std::string m_ip_address;
|
||||
void SetName(const char *name);
|
||||
INetChannelInfo *GetNetInfo() const;
|
||||
ListenOverride m_listenMap[66] = {};
|
||||
VoiceFlag_t m_voiceFlag = 0;
|
||||
CPlayerBitVec m_selfMutes[64] = {};
|
||||
void SetName(const char* name);
|
||||
INetChannelInfo* GetNetInfo() const;
|
||||
};
|
||||
|
||||
class PlayerManager : public GlobalClass {
|
||||
class PlayerManager : public GlobalClass
|
||||
{
|
||||
friend class CPlayer;
|
||||
|
||||
public:
|
||||
public:
|
||||
PlayerManager();
|
||||
void OnStartup() override;
|
||||
void OnAllInitialized() override;
|
||||
bool OnClientConnect(CPlayerSlot slot,
|
||||
const char *pszName,
|
||||
uint64 xuid,
|
||||
const char *pszNetworkID,
|
||||
bool unk1,
|
||||
CBufferString *pRejectReason);
|
||||
bool OnClientConnect_Post(CPlayerSlot slot,
|
||||
const char *pszName,
|
||||
uint64 xuid,
|
||||
const char *pszNetworkID,
|
||||
bool unk1,
|
||||
CBufferString *pRejectReason);
|
||||
void OnClientPutInServer(CPlayerSlot slot, char const *pszName, int type, uint64 xuid);
|
||||
void OnClientDisconnect(CPlayerSlot slot,
|
||||
ENetworkDisconnectionReason reason,
|
||||
const char *pszName,
|
||||
uint64 xuid,
|
||||
const char *pszNetworkID);
|
||||
void OnClientDisconnect_Post(CPlayerSlot slot,
|
||||
ENetworkDisconnectionReason reason,
|
||||
const char *pszName,
|
||||
uint64 xuid,
|
||||
const char *pszNetworkID) const;
|
||||
bool OnClientConnect(CPlayerSlot slot, const char* pszName, uint64 xuid,
|
||||
const char* pszNetworkID, bool unk1, CBufferString* pRejectReason);
|
||||
bool OnClientConnect_Post(CPlayerSlot slot, const char* pszName, uint64 xuid,
|
||||
const char* pszNetworkID, bool unk1, CBufferString* pRejectReason);
|
||||
void OnClientPutInServer(CPlayerSlot slot, char const* pszName, int type, uint64 xuid);
|
||||
void OnClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason,
|
||||
const char* pszName, uint64 xuid, const char* pszNetworkID);
|
||||
void OnClientDisconnect_Post(CPlayerSlot slot, ENetworkDisconnectionReason reason,
|
||||
const char* pszName, uint64 xuid, const char* pszNetworkID) const;
|
||||
void OnClientVoice(CPlayerSlot slot) const;
|
||||
void OnAuthorized(CPlayer* player) const;
|
||||
void OnServerActivate(edict_t *pEdictList, int edictCount, int clientMax) const;
|
||||
void OnServerActivate(edict_t* pEdictList, int edictCount, int clientMax) const;
|
||||
void OnThink(bool last_tick) const;
|
||||
void OnShutdown() override;
|
||||
void OnLevelEnd() override;
|
||||
void OnClientCommand(CPlayerSlot slot, const CCommand &args) const;
|
||||
void OnClientCommand(CPlayerSlot slot, const CCommand& args) const;
|
||||
int ListenClient() const;
|
||||
void RunAuthChecks();
|
||||
|
||||
public:
|
||||
public:
|
||||
int NumPlayers() const;
|
||||
int MaxClients() const;
|
||||
CPlayer *GetPlayerBySlot(int client) const;
|
||||
CPlayer *GetClientOfUserId(int user_id) const;
|
||||
CPlayer* GetPlayerBySlot(int client) const;
|
||||
CPlayer* GetClientOfUserId(int user_id) const;
|
||||
|
||||
private:
|
||||
void InvalidatePlayer(CPlayer *pPlayer) const;
|
||||
private:
|
||||
void InvalidatePlayer(CPlayer* pPlayer) const;
|
||||
|
||||
CPlayer *m_players;
|
||||
CPlayer* m_players;
|
||||
int m_max_clients = 0;
|
||||
int m_player_count = 0;
|
||||
int *m_user_id_lookup;
|
||||
int* m_user_id_lookup;
|
||||
int m_listen_client;
|
||||
bool m_is_listen_server;
|
||||
float m_last_auth_check_time = 0;
|
||||
|
||||
ScriptCallback *m_on_client_connect_callback;
|
||||
ScriptCallback *m_on_client_put_in_server_callback;
|
||||
ScriptCallback *m_on_client_connected_callback;
|
||||
ScriptCallback *m_on_client_disconnect_callback;
|
||||
ScriptCallback *m_on_client_disconnect_post_callback;
|
||||
ScriptCallback *m_on_client_authorized_callback;
|
||||
ScriptCallback* m_on_client_connect_callback;
|
||||
ScriptCallback* m_on_client_put_in_server_callback;
|
||||
ScriptCallback* m_on_client_connected_callback;
|
||||
ScriptCallback* m_on_client_disconnect_callback;
|
||||
ScriptCallback* m_on_client_disconnect_post_callback;
|
||||
ScriptCallback* m_on_client_voice_callback;
|
||||
ScriptCallback* m_on_client_authorized_callback;
|
||||
};
|
||||
|
||||
} // namespace counterstrikesharp
|
||||
} // namespace counterstrikesharp
|
||||
129
src/core/managers/voice_manager.cpp
Normal file
129
src/core/managers/voice_manager.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
#include "core/managers/voice_manager.h"
|
||||
#include "core/managers/player_manager.h"
|
||||
|
||||
#include <public/eiface.h>
|
||||
#include "scripting/callback_manager.h"
|
||||
#include <schema.h>
|
||||
#include <entity2/entitysystem.h>
|
||||
|
||||
SH_DECL_HOOK3(IVEngineServer2, SetClientListening, SH_NOATTRIB, 0, bool, CPlayerSlot, CPlayerSlot,
|
||||
bool);
|
||||
|
||||
namespace counterstrikesharp {
|
||||
|
||||
VoiceManager::VoiceManager() {}
|
||||
|
||||
VoiceManager::~VoiceManager() {}
|
||||
|
||||
void VoiceManager::OnAllInitialized()
|
||||
{
|
||||
SH_ADD_HOOK(IVEngineServer2, SetClientListening, globals::engine,
|
||||
SH_MEMBER(this, &VoiceManager::SetClientListening), false);
|
||||
}
|
||||
|
||||
void VoiceManager::OnShutdown()
|
||||
{
|
||||
SH_REMOVE_HOOK(IVEngineServer2, SetClientListening, globals::engine,
|
||||
SH_MEMBER(this, &VoiceManager::SetClientListening), false);
|
||||
}
|
||||
|
||||
bool VoiceManager::SetClientListening(CPlayerSlot iReceiver, CPlayerSlot iSender, bool bListen)
|
||||
{
|
||||
auto pReceiver = globals::playerManager.GetPlayerBySlot(iReceiver.Get());
|
||||
auto pSender = globals::playerManager.GetPlayerBySlot(iSender.Get());
|
||||
|
||||
if (pReceiver && pSender)
|
||||
{
|
||||
auto listenOverride = pReceiver->GetListen(iSender);
|
||||
auto senderFlags = pSender->GetVoiceFlags();
|
||||
auto receiverFlags = pReceiver->GetVoiceFlags();
|
||||
|
||||
if (pReceiver->m_selfMutes->Get(iSender.Get()))
|
||||
{
|
||||
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
|
||||
(iReceiver, iSender, false));
|
||||
}
|
||||
|
||||
if (senderFlags & Speak_Muted)
|
||||
{
|
||||
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
|
||||
(iReceiver, iSender, false));
|
||||
}
|
||||
|
||||
if (listenOverride == Listen_Mute)
|
||||
{
|
||||
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
|
||||
(iReceiver, iSender, false));
|
||||
} else if (listenOverride == Listen_Hear) {
|
||||
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
|
||||
(iReceiver, iSender, true));
|
||||
}
|
||||
|
||||
if ((senderFlags & Speak_All) || (receiverFlags & Speak_ListenAll)) {
|
||||
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
|
||||
(iReceiver, iSender, true));
|
||||
}
|
||||
|
||||
if ((senderFlags & Speak_Team) || (receiverFlags & Speak_ListenTeam))
|
||||
{
|
||||
static auto classKey = hash_32_fnv1a_const("CBaseEntity");
|
||||
static auto memberKey = hash_32_fnv1a_const("m_iTeamNum");
|
||||
const static auto m_key = schema::GetOffset("CBaseEntity", classKey, "m_iTeamNum", memberKey);
|
||||
|
||||
auto receiverController = globals::entitySystem->GetBaseEntity(CEntityIndex(iReceiver.Get() + 1));
|
||||
auto senderController = globals::entitySystem->GetBaseEntity(CEntityIndex(iSender.Get() + 1));
|
||||
|
||||
if (receiverController && senderController)
|
||||
{
|
||||
auto receiverTeam = *reinterpret_cast<std::add_pointer_t<unsigned int>>(
|
||||
(uintptr_t)(receiverController) + m_key.offset);
|
||||
|
||||
auto senderTeam = *reinterpret_cast<std::add_pointer_t<unsigned int>>(
|
||||
(uintptr_t)(senderController) + m_key.offset);
|
||||
|
||||
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen,
|
||||
&IVEngineServer2::SetClientListening,
|
||||
(iReceiver, iSender, receiverTeam == senderTeam));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_META_VALUE(MRES_IGNORED, bListen);
|
||||
}
|
||||
|
||||
void VoiceManager::OnClientCommand(CPlayerSlot slot, const CCommand& args)
|
||||
{
|
||||
auto pPlayer = globals::playerManager.GetPlayerBySlot(slot.Get());
|
||||
|
||||
if (!pPlayer)
|
||||
return;
|
||||
|
||||
if (args.ArgC() > 1 && stricmp(args.Arg(0), "vban") == 0)
|
||||
{
|
||||
// clients just refuse to send vban for indexes over 32 and all 4 fields are just the same number, so we only get the first one
|
||||
//for (int i = 1; (i < args.ArgC()) && (i < 3); i++) {
|
||||
unsigned int mask = 0;
|
||||
sscanf(args.Arg(1), "%x", &mask);
|
||||
|
||||
pPlayer->m_selfMutes->SetDWord(0, mask);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace counterstrikesharp
|
||||
38
src/core/managers/voice_manager.h
Normal file
38
src/core/managers/voice_manager.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/globals.h"
|
||||
#include "core/global_listener.h"
|
||||
#include "scripting/script_engine.h"
|
||||
|
||||
namespace counterstrikesharp {
|
||||
class ScriptCallback;
|
||||
|
||||
class VoiceManager : public GlobalClass
|
||||
{
|
||||
public:
|
||||
VoiceManager();
|
||||
~VoiceManager();
|
||||
void OnAllInitialized() override;
|
||||
void OnShutdown() override;
|
||||
bool SetClientListening(CPlayerSlot iReceiver, CPlayerSlot iSender, bool bListen);
|
||||
void OnClientCommand(CPlayerSlot slot, const CCommand& args);
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace counterstrikesharp
|
||||
129
src/scripting/natives/natives_voice.cpp
Normal file
129
src/scripting/natives/natives_voice.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
#include "scripting/autonative.h"
|
||||
#include "scripting/script_engine.h"
|
||||
#include "core/managers/player_manager.h"
|
||||
#include <public/entity2/entitysystem.h>
|
||||
|
||||
|
||||
namespace counterstrikesharp {
|
||||
|
||||
void SetClientListening(ScriptContext& scriptContext)
|
||||
{
|
||||
auto receiver = scriptContext.GetArgument<CBaseEntity*>(0);
|
||||
auto sender = scriptContext.GetArgument<CBaseEntity*>(1);
|
||||
auto listen = scriptContext.GetArgument<ListenOverride>(2);
|
||||
|
||||
if (!receiver) {
|
||||
scriptContext.ThrowNativeError("Receiver is a null pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sender) {
|
||||
scriptContext.ThrowNativeError("Sender is a null pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
auto iSenderSlot = sender->GetEntityIndex().Get() - 1;
|
||||
|
||||
if (iSenderSlot < 0 || iSenderSlot >= globals::getGlobalVars()->maxClients)
|
||||
scriptContext.ThrowNativeError("Invalid sender");
|
||||
|
||||
auto pPlayer = globals::playerManager.GetPlayerBySlot(receiver->GetEntityIndex().Get() - 1);
|
||||
|
||||
if (pPlayer == nullptr) {
|
||||
scriptContext.ThrowNativeError("Invalid receiver");
|
||||
return;
|
||||
}
|
||||
|
||||
pPlayer->SetListen(iSenderSlot, listen);
|
||||
}
|
||||
|
||||
ListenOverride GetClientListening(ScriptContext& scriptContext)
|
||||
{
|
||||
auto receiver = scriptContext.GetArgument<CBaseEntity*>(0);
|
||||
auto sender = scriptContext.GetArgument<CBaseEntity*>(1);
|
||||
|
||||
if (!receiver) {
|
||||
scriptContext.ThrowNativeError("Receiver is a null pointer");
|
||||
return Listen_Default;
|
||||
}
|
||||
|
||||
if (!sender) {
|
||||
scriptContext.ThrowNativeError("Sender is a null pointer");
|
||||
return Listen_Default;
|
||||
}
|
||||
|
||||
auto iSenderSlot = sender->GetEntityIndex().Get() - 1;
|
||||
|
||||
if (iSenderSlot < 0 || iSenderSlot >= globals::getGlobalVars()->maxClients)
|
||||
scriptContext.ThrowNativeError("Invalid sender");
|
||||
|
||||
auto pPlayer = globals::playerManager.GetPlayerBySlot(receiver->GetEntityIndex().Get() - 1);
|
||||
|
||||
if (pPlayer == nullptr) {
|
||||
scriptContext.ThrowNativeError("Invalid receiver");
|
||||
return Listen_Default;
|
||||
}
|
||||
|
||||
return pPlayer->GetListen(iSenderSlot);
|
||||
}
|
||||
|
||||
void SetClientVoiceFlags(ScriptContext& scriptContext)
|
||||
{
|
||||
auto client = scriptContext.GetArgument<CBaseEntity*>(0);
|
||||
auto flags = scriptContext.GetArgument<VoiceFlag_t>(1);
|
||||
|
||||
if (!client) {
|
||||
scriptContext.ThrowNativeError("Receiver is a null pointer");
|
||||
return;
|
||||
}
|
||||
auto pPlayer = globals::playerManager.GetPlayerBySlot(client->GetEntityIndex().Get() - 1);
|
||||
|
||||
if (pPlayer == nullptr) {
|
||||
scriptContext.ThrowNativeError("Invalid receiver");
|
||||
return;
|
||||
}
|
||||
|
||||
pPlayer->SetVoiceFlags(flags);
|
||||
}
|
||||
|
||||
VoiceFlag_t GetClientVoiceFlags(ScriptContext& scriptContext)
|
||||
{
|
||||
auto client = scriptContext.GetArgument<CBaseEntity*>(0);
|
||||
|
||||
if (!client) {
|
||||
scriptContext.ThrowNativeError("Receiver is a null pointer");
|
||||
return VoiceFlag_t{};
|
||||
}
|
||||
|
||||
auto pPlayer = globals::playerManager.GetPlayerBySlot(client->GetEntityIndex().Get() - 1);
|
||||
|
||||
if (pPlayer == nullptr) {
|
||||
scriptContext.ThrowNativeError("Invalid receiver");
|
||||
}
|
||||
|
||||
return pPlayer->GetVoiceFlags();
|
||||
}
|
||||
|
||||
REGISTER_NATIVES(voice, {
|
||||
ScriptEngine::RegisterNativeHandler("SET_CLIENT_LISTENING", SetClientListening);
|
||||
ScriptEngine::RegisterNativeHandler("GET_CLIENT_LISTENING", GetClientListening);
|
||||
ScriptEngine::RegisterNativeHandler("SET_CLIENT_VOICE_FLAGS", SetClientVoiceFlags);
|
||||
ScriptEngine::RegisterNativeHandler("GET_CLIENT_VOICE_FLAGS", GetClientVoiceFlags);
|
||||
})
|
||||
} // namespace counterstrikesharp
|
||||
4
src/scripting/natives/natives_voice.yaml
Normal file
4
src/scripting/natives/natives_voice.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
SET_CLIENT_LISTENING: receiver:pointer, sender:pointer, listen:uint -> void
|
||||
GET_CLIENT_LISTENING: receiver:pointer, sender:pointer -> ListenOverride
|
||||
SET_CLIENT_VOICE_FLAGS: client:pointer, flags:uint -> void
|
||||
GET_CLIENT_VOICE_FLAGS: client:pointer -> uint
|
||||
@@ -63,6 +63,8 @@ public class Mapping
|
||||
return "[CastFrom(typeof(ulong))]SteamID";
|
||||
case "HookMode":
|
||||
return "HookMode";
|
||||
case "ListenOverride":
|
||||
return "ListenOverride";
|
||||
case "DataType_t":
|
||||
return "DataType";
|
||||
case "any":
|
||||
|
||||
Reference in New Issue
Block a user