mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-08 17:07:20 -08:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36a97bfffd | ||
|
|
178f7472c6 | ||
|
|
cba5144bbf | ||
|
|
0de951cb6f | ||
|
|
c3d44a87bc | ||
|
|
bd3c0c76e3 | ||
|
|
656c0e3a84 | ||
|
|
40c842149c | ||
|
|
64d1c0a9f4 | ||
|
|
a6de51c444 | ||
|
|
2535ac0575 | ||
|
|
bc61323315 | ||
|
|
241817b7f2 | ||
|
|
fbcdce34fc | ||
|
|
daf0d25f36 | ||
|
|
12485be29f |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -14,12 +14,12 @@
|
||||
[submodule "libraries/dyncall"]
|
||||
path = libraries/dyncall
|
||||
url = https://github.com/LWJGL-CI/dyncall
|
||||
[submodule "libraries/GameTracking-CS2"]
|
||||
path = libraries/GameTracking-CS2
|
||||
url = https://github.com/SteamDatabase/GameTracking-CS2
|
||||
[submodule "libraries/DynoHook"]
|
||||
path = libraries/DynoHook
|
||||
url = https://github.com/qubka/DynoHook
|
||||
[submodule "libraries/asmjit"]
|
||||
path = libraries/asmjit
|
||||
url = https://github.com/asmjit/asmjit
|
||||
[submodule "libraries/Protobufs"]
|
||||
path = libraries/Protobufs
|
||||
url = https://github.com/SteamDatabase/Protobufs
|
||||
|
||||
@@ -90,6 +90,8 @@ SET(SOURCE_FILES
|
||||
src/core/managers/voice_manager.cpp
|
||||
src/core/managers/voice_manager.h
|
||||
src/scripting/natives/natives_dynamichooks.cpp
|
||||
src/core/game_system.h
|
||||
src/core/game_system.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -101,8 +103,8 @@ if (LINUX)
|
||||
)
|
||||
endif()
|
||||
|
||||
set(PROTO_DIRS -I${CMAKE_CURRENT_SOURCE_DIR}/libraries/GameTracking-CS2/Protobufs)
|
||||
file(GLOB PROTOS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/GameTracking-CS2/Protobufs/*.proto")
|
||||
set(PROTO_DIRS -I${CMAKE_CURRENT_SOURCE_DIR}/libraries/Protobufs/csgo)
|
||||
file(GLOB PROTOS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/Protobufs/csgo/*.proto")
|
||||
|
||||
## Generate protobuf source & headers
|
||||
if (LINUX)
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"SilentChatTrigger": [ "/" ],
|
||||
"FollowCS2ServerGuidelines": true,
|
||||
"PluginHotReloadEnabled": true,
|
||||
"PluginAutoLoadEnabled": true,
|
||||
"ServerLanguage": "en"
|
||||
}
|
||||
@@ -206,5 +206,18 @@
|
||||
"windows": "\\x4C\\x89\\x4C\\x24\\x20\\x53\\x55\\x57\\x41\\x54\\x41\\x56\\x48\\x81\\xEC",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x41\\x54\\x49\\x89\\xD4\\x53\\x48\\x89\\xF3\\x48\\x83\\xEC\\x58"
|
||||
}
|
||||
},
|
||||
"IGameSystem_InitAllSystems_pFirst": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x8B\\x1D\\x2A\\x2A\\x2A\\x2A\\x48\\x85\\xDB\\x0F\\x84\\x2A\\x2A\\x2A\\x2A\\xBE\\x2A\\x2A\\x2A\\x2A\\x0F\\x1F\\x00\\x48\\x8B\\x7B\\x10",
|
||||
"linux": "\\x4C\\x8B\\x35\\x2A\\x2A\\x2A\\x2A\\x4D\\x85\\xF6\\x75\\x2D\\xE9\\x2A\\x2A\\x2A\\x2A\\x0F\\x1F\\x40\\x00\\x48\\x8B\\x05"
|
||||
}
|
||||
},
|
||||
"CEntityResourceManifest_AddResource": {
|
||||
"offsets": {
|
||||
"windows": 2,
|
||||
"linux": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
configs/addons/counterstrikesharp/shared/README.md
Normal file
1
configs/addons/counterstrikesharp/shared/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This folder should contain any shared APIs, in the same DLL structure as the plugins folder (MySharedApi/MySharedApi.dll)
|
||||
67
docfx/docs/features/shared-plugin-api.md
Normal file
67
docfx/docs/features/shared-plugin-api.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Shared Plugin API (Capabilities)
|
||||
description: How to add inter-plugin communication to CounterStrikeSharp plugins.
|
||||
---
|
||||
|
||||
# Shared Plugin API
|
||||
|
||||
How to expose and use shared plugin APIs between multiple plugins.
|
||||
|
||||
## Creating a Contract Library
|
||||
|
||||
Inter-plugin communication requires a contract/shared library that simply exposes the shape of the API, using simple interfaces. e.g.
|
||||
|
||||
```csharp
|
||||
public interface IBalanceHandler
|
||||
{
|
||||
decimal Balance { get; }
|
||||
|
||||
// These are just here to show that you can have methods on your shared types.
|
||||
// You could also add a Setter to the Balance property.
|
||||
public decimal Add(decimal amount);
|
||||
public decimal Subtract(decimal amount);
|
||||
}
|
||||
```
|
||||
|
||||
This library ideally should not contain any business logic, and simply define the schema for callers.
|
||||
|
||||
This library should be placed in the `shared` subfolder, in the same folder layout as the plugins folder. So if a contract DLL is named `MySharedApi.dll` its file path should be: `shared/MySharedApi/MySharedApi.dll`.
|
||||
|
||||
## Creating a Capability
|
||||
|
||||
A capability can be declared with a simple static variable in your plugin class. A `PlayerCapability` is a player specific capability, and a `PluginCapability` is generic functionality that a plugin can expose.
|
||||
|
||||
```csharp
|
||||
public static PlayerCapability<IBalanceHandler> BalanceCapability { get; } = new("myplugin:balance");
|
||||
|
||||
public static PluginCapability<IBalanceService> BalanceServiceCapability { get; } = new("myplugin:balance_service");
|
||||
```
|
||||
|
||||
For every plugin that wishes to use this new "Balance API", they must ensure they create a capability using the shared API interface (`IBalanceHandler`), as well as use the same name (`myplugin:balance`).
|
||||
|
||||
## Registering a Capability
|
||||
|
||||
If you are the plugin that is expected to provide the basis of the API (i.e. you are providing a currency/balance plugin which does nothing except store users balances), then you will need to provide the implementation that other callers will use. This is done through the use of static members on the `Capabilities` class:
|
||||
|
||||
```csharp
|
||||
// Player capabilities are given the calling player context
|
||||
Capabilities.RegisterPlayerCapability(BalanceCapability, player => new BalanceHandler(player));
|
||||
|
||||
// Plugin capabilities can simply return an instance of the interface
|
||||
Capabilities.RegisterPluginCapability(BalanceServiceCapability, () => new BalanceService());
|
||||
```
|
||||
|
||||
### Using Capabilities
|
||||
|
||||
To utilise a capability, simply call the `.Get()` method provided on the static capability you declared earlier, i.e.
|
||||
|
||||
```csharp
|
||||
var balance = BalanceCapability.Get(player);
|
||||
var balanceService = BalanceServiceCapability.Get();
|
||||
|
||||
if (balance == null) return;
|
||||
|
||||
balance.Add(500);
|
||||
```
|
||||
|
||||
This value _MUST_ be checked for null, as if there are no plugins providing implementations for a given capability, this method will return null, and you must handle this flow in your plugin.
|
||||
@@ -9,3 +9,6 @@
|
||||
|
||||
- name: Global Listeners
|
||||
href: global-listeners.md
|
||||
|
||||
- name: Shared Plugin API
|
||||
href: shared-plugin-api.md
|
||||
|
||||
@@ -33,6 +33,10 @@ receive a ban.
|
||||
|
||||
When enabled, plugins are automatically reloaded when their .dll file is updated.
|
||||
|
||||
## PluginAutoLoadEnabled
|
||||
|
||||
When enabled, plugins are automatically loaded from the plugins directory on server start.
|
||||
|
||||
## 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".
|
||||
11
docfx/examples/WithSharedTypes.md
Normal file
11
docfx/examples/WithSharedTypes.md
Normal file
@@ -0,0 +1,11 @@
|
||||
[!INCLUDE [WithSharedTypes](../../examples/WithSharedTypes/README.md)]
|
||||
|
||||
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithSharedTypes" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
|
||||
|
||||
[!code-csharp[](../../examples/WithSharedTypes/WithSharedTypesPlugin.cs)]
|
||||
|
||||
[!INCLUDE [WithSharedTypesConsumer](../../examples/WithSharedTypesConsumer/README.md)]
|
||||
|
||||
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithSharedTypesConsumer" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
|
||||
|
||||
[!code-csharp[](../../examples/WithSharedTypesConsumer/WithSharedTypesConsumerPlugin.cs)]
|
||||
@@ -15,6 +15,8 @@ items:
|
||||
href: WithGameEventHandlers.md
|
||||
- name: Database (Dapper)
|
||||
href: WithDatabase.md
|
||||
- name: Shared Plugin Types
|
||||
href: WithSharedTypes.md
|
||||
- name: Translations
|
||||
href: WithTranslations.md
|
||||
- name: Voice Overrides
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace MySharedTypes.Contracts;
|
||||
|
||||
public interface IBalanceHandler
|
||||
{
|
||||
decimal Balance { get; }
|
||||
|
||||
// These are just here to show that you can have methods on your shared types.
|
||||
// You could also add a Setter to the Balance property.
|
||||
public decimal Add(decimal amount);
|
||||
public decimal Subtract(decimal amount);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace MySharedTypes.Contracts;
|
||||
|
||||
public interface IBalanceService
|
||||
{
|
||||
public void ClearAllBalances();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
33
examples/WithSharedTypes/BalanceHandler.cs
Normal file
33
examples/WithSharedTypes/BalanceHandler.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using MySharedTypes.Contracts;
|
||||
|
||||
namespace WithSharedTypes;
|
||||
|
||||
public class BalanceHandler : IBalanceHandler
|
||||
{
|
||||
private readonly CCSPlayerController _player;
|
||||
|
||||
// This could be a database, a file, or a dictionary like this.
|
||||
internal static readonly Dictionary<CCSPlayerController, decimal> Balances = new();
|
||||
|
||||
public BalanceHandler(CCSPlayerController player)
|
||||
{
|
||||
_player = player;
|
||||
}
|
||||
|
||||
public decimal Balance
|
||||
{
|
||||
get => Balances.TryGetValue(_player, out var balance) ? balance : 0;
|
||||
set => Balances[_player] = value;
|
||||
}
|
||||
|
||||
public decimal Add(decimal amount)
|
||||
{
|
||||
return Balance += amount;
|
||||
}
|
||||
|
||||
public decimal Subtract(decimal amount)
|
||||
{
|
||||
return Balance -= amount;
|
||||
}
|
||||
}
|
||||
11
examples/WithSharedTypes/BalanceService.cs
Normal file
11
examples/WithSharedTypes/BalanceService.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using MySharedTypes.Contracts;
|
||||
|
||||
namespace WithSharedTypes;
|
||||
|
||||
public class BalanceService : IBalanceService
|
||||
{
|
||||
public void ClearAllBalances()
|
||||
{
|
||||
BalanceHandler.Balances.Clear();
|
||||
}
|
||||
}
|
||||
5
examples/WithSharedTypes/README.md
Normal file
5
examples/WithSharedTypes/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# With Shared Types (Capabilities)
|
||||
|
||||
An example plugin that exposes a balance contract library, to use as a shared library between multiple plugins.
|
||||
|
||||
This allows one plugin to expose a capability for a player or plugin, and other plugins to use the exposed API.
|
||||
13
examples/WithSharedTypes/WithSharedTypes.csproj
Normal file
13
examples/WithSharedTypes/WithSharedTypes.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<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" />
|
||||
<ProjectReference Include="..\MySharedTypes.Contracts\MySharedTypes.Contracts\MySharedTypes.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
57
examples/WithSharedTypes/WithSharedTypesPlugin.cs
Normal file
57
examples/WithSharedTypes/WithSharedTypesPlugin.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using MySharedTypes.Contracts;
|
||||
|
||||
namespace WithSharedTypes;
|
||||
|
||||
[MinimumApiVersion(184)]
|
||||
public class WithSharedTypesPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: Shared Types";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
|
||||
public override string ModuleDescription => "A simple plugin that shares types between multiple plugins";
|
||||
|
||||
// Declares a player capability, that stores some sort of functionality for a player.
|
||||
// In this case, it's a balance handler, which is used to store a player's balance.
|
||||
// Note that we use the same name for the capability as the one in the other plugin.
|
||||
// IBalanceHandler is defined in MySharedTypes.Contracts, which is a shared library and placed in the `shared/` subfolder.
|
||||
public static PlayerCapability<IBalanceHandler> BalanceCapability { get; } = new("myplugin:balance");
|
||||
|
||||
// Declares a player capability of a primitive type, in this case, a decimal.
|
||||
public static PlayerCapability<Decimal> BalanceCapabilityDecimal { get; } = new("myplugin:balance_decimal");
|
||||
|
||||
// Plugin capabilities are similar to player capabilities, but they are not tied to a player, and are just generic APIs
|
||||
// that are exposed by a plugin. In this case, we expose a balance service, which is used to clear all balances.
|
||||
public static PluginCapability<IBalanceService> BalanceServiceCapability { get; } = new("myplugin:balance_service");
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
// Register the capability implementations here. Note that plugins don't need to register an implementation if it is already implemented in another plugin.
|
||||
Capabilities.RegisterPlayerCapability(BalanceCapability, player => new BalanceHandler(player));
|
||||
Capabilities.RegisterPluginCapability(BalanceServiceCapability, () => new BalanceService());
|
||||
Capabilities.RegisterPlayerCapability(BalanceCapabilityDecimal, (player) => new BalanceHandler(player).Balance);
|
||||
|
||||
AddCommand("css_balance", "Gets your current balance", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
player.PrintToChat($"Your balance is {BalanceCapability.Get(player)?.Balance}");
|
||||
});
|
||||
|
||||
AddCommand("css_give", "Gives you money", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
var balance = BalanceCapability.Get(player);
|
||||
if (balance == null) return;
|
||||
|
||||
balance.Add(100);
|
||||
player.PrintToChat($"Your balance is now {balance.Balance}");
|
||||
});
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
}
|
||||
}
|
||||
2
examples/WithSharedTypesConsumer/README.md
Normal file
2
examples/WithSharedTypesConsumer/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# With Shared Types (Consumer Plugin)
|
||||
Uses the decimal balance shared library.
|
||||
@@ -0,0 +1,13 @@
|
||||
<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" />
|
||||
<ProjectReference Include="..\MySharedTypes.Contracts\MySharedTypes.Contracts\MySharedTypes.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,56 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
using MySharedTypes.Contracts;
|
||||
|
||||
namespace WithSharedTypesConsumer;
|
||||
|
||||
[MinimumApiVersion(184)]
|
||||
public class WithSharedTypesConsumerPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: Shared Types (Consumer)";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
|
||||
public override string ModuleDescription => "A simple plugin that utilises the balance api from another plugin";
|
||||
|
||||
public static PlayerCapability<IBalanceHandler> BalanceCapability { get; } = new("myplugin:balance");
|
||||
public static PlayerCapability<Decimal> BalanceCapabilityDecimal { get; } = new("myplugin:balance_decimal");
|
||||
|
||||
public static PluginCapability<IBalanceService> BalanceServiceCapability { get; } = new("myplugin:balance_service");
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
AddCommand("css_subtract", "Subtracts 50 from your balance", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
var balance = BalanceCapability.Get(player);
|
||||
if (balance == null) return;
|
||||
balance.Subtract(50);
|
||||
player.PrintToChat($"Your balance is now {balance.Balance}");
|
||||
});
|
||||
|
||||
AddCommand("css_clearbalances", "Clears all balances", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
var service = BalanceServiceCapability.Get();
|
||||
if (service == null) return;
|
||||
|
||||
service.ClearAllBalances();
|
||||
|
||||
var balance = BalanceCapability.Get(player);
|
||||
if (balance == null) return;
|
||||
player.PrintToChat($"Your balance is now {balance.Balance}");
|
||||
});
|
||||
|
||||
AddCommand("css_decimalbalance", "Gets your current balance", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
player.PrintToChat($"Your balance is {BalanceCapabilityDecimal.Get(player)}");
|
||||
});
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
}
|
||||
}
|
||||
Submodule libraries/GameTracking-CS2 deleted from 3556d2a923
1
libraries/Protobufs
Submodule
1
libraries/Protobufs
Submodule
Submodule libraries/Protobufs added at 686a0628e6
3747
libraries/moodycamel/concurrentqueue.h
Normal file
3747
libraries/moodycamel/concurrentqueue.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -51,6 +51,7 @@ include_directories(
|
||||
libraries/tl
|
||||
libraries/funchook/include
|
||||
libraries/DynoHook/src
|
||||
libraries/moodycamel
|
||||
libraries
|
||||
)
|
||||
|
||||
|
||||
@@ -427,6 +427,18 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.FunctionReference.Create(System.Delegate)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.FunctionReference.Get(System.Int32)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.NativeAPI.QueueTaskForNextFrame(System.IntPtr)</Target>
|
||||
@@ -451,6 +463,24 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.Translations.JsonStringLocalizerFactory.#ctor(CounterStrikeSharp.API.Core.Plugin.IPluginContext)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.IPlugin.OnAllPluginsLoaded(System.Boolean)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>P:CounterStrikeSharp.API.Core.Hosting.IScriptHostConfiguration.SharedPath</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>P:CounterStrikeSharp.API.Core.IPlugin.CommandManager</Target>
|
||||
|
||||
@@ -749,6 +749,16 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void FreeEvent(IntPtr gameevent){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(gameevent);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x7E8B60C2);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static void FireEvent(IntPtr gameevent, bool dontbroadcast){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
|
||||
@@ -130,11 +130,11 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat(" [#{0}:{1}]: \"{2}\" ({3})", plugin.PluginId,
|
||||
plugin.State.ToString().ToUpper(), plugin.Plugin.ModuleName,
|
||||
plugin.Plugin.ModuleVersion);
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin.ModuleAuthor))
|
||||
plugin.State.ToString().ToUpper(), plugin.Plugin?.ModuleName ?? "Unknown",
|
||||
plugin.Plugin?.ModuleVersion ?? "Unknown");
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleAuthor))
|
||||
sb.AppendFormat(" by {0}", plugin.Plugin.ModuleAuthor);
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin.ModuleDescription))
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleDescription))
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.Append(" ");
|
||||
@@ -175,6 +175,8 @@ namespace CounterStrikeSharp.API.Core
|
||||
try
|
||||
{
|
||||
_pluginManager.LoadPlugin(path);
|
||||
plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
plugin.Plugin.OnAllPluginsLoaded(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -185,6 +187,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
else
|
||||
{
|
||||
plugin.Load(false);
|
||||
plugin.Plugin.OnAllPluginsLoaded(false);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -233,6 +236,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
plugin.Unload(true);
|
||||
plugin.Load(true);
|
||||
plugin.Plugin.OnAllPluginsLoaded(true);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,10 @@ namespace CounterStrikeSharp.API.Core
|
||||
public virtual void Unload(bool hotReload)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
}
|
||||
|
||||
public class CallbackSubscriber : IDisposable
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace CounterStrikeSharp.API.Core.Capabilities;
|
||||
|
||||
public static class Capabilities
|
||||
{
|
||||
public static void RegisterPluginCapability<T>(PluginCapability<T> capability, Func<T> supplier)
|
||||
{
|
||||
if (!PluginCapability<T>.Providers.ContainsKey(capability.Name))
|
||||
{
|
||||
PluginCapability<T>.Providers.Add(capability.Name, new());
|
||||
}
|
||||
|
||||
PluginCapability<T>.Providers[capability.Name].Add(supplier);
|
||||
}
|
||||
|
||||
public static void RegisterPlayerCapability<T>(PlayerCapability<T> capability,
|
||||
Func<CCSPlayerController, T> supplier)
|
||||
{
|
||||
if (!PlayerCapability<T>.Providers.ContainsKey(capability.Name))
|
||||
{
|
||||
PlayerCapability<T>.Providers.Add(capability.Name, new());
|
||||
}
|
||||
|
||||
PlayerCapability<T>.Providers[capability.Name].Add(supplier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Capabilities;
|
||||
|
||||
public sealed class PlayerCapability<T>
|
||||
{
|
||||
public string Name { get; }
|
||||
internal static readonly Dictionary<string, List<Func<CCSPlayerController, T>>> Providers = new();
|
||||
|
||||
public PlayerCapability(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public T? Get(CCSPlayerController entity)
|
||||
{
|
||||
foreach (var provider in Providers[Name])
|
||||
{
|
||||
return provider(entity);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Capabilities;
|
||||
|
||||
public sealed class PluginCapability<T>
|
||||
{
|
||||
public string Name { get; }
|
||||
internal static readonly Dictionary<string, List<Func<T>>> Providers = new();
|
||||
|
||||
public PluginCapability(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public T? Get()
|
||||
{
|
||||
foreach (var provider in Providers[Name])
|
||||
{
|
||||
return provider();
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,11 @@ public class CommandManager : ICommandManager
|
||||
|
||||
foreach (var attr in permissionsToCheck)
|
||||
{
|
||||
if (attr.Permissions.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
attr.Command = name;
|
||||
if (!attr.CanExecuteCommand(caller))
|
||||
{
|
||||
|
||||
@@ -50,6 +50,9 @@ namespace CounterStrikeSharp.API.Core
|
||||
[JsonPropertyName("PluginHotReloadEnabled")]
|
||||
public bool PluginHotReloadEnabled { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("PluginAutoLoadEnabled")]
|
||||
public bool PluginAutoLoadEnabled { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("ServerLanguage")]
|
||||
public string ServerLanguage { get; set; } = "en";
|
||||
}
|
||||
@@ -95,7 +98,13 @@ namespace CounterStrikeSharp.API.Core
|
||||
/// </summary>
|
||||
public static bool PluginHotReloadEnabled => _coreConfig.PluginHotReloadEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// When enabled, plugins are automatically loaded from the plugins directory on server start.
|
||||
/// </summary>
|
||||
public static bool PluginAutoLoadEnabled => _coreConfig.PluginAutoLoadEnabled;
|
||||
|
||||
public static string ServerLanguage => _coreConfig.ServerLanguage;
|
||||
|
||||
}
|
||||
|
||||
public partial class CoreConfig : IStartupService
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
@@ -29,140 +28,149 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
/// <summary>Delegate will be removed after the first invocation.</summary>
|
||||
SingleUse,
|
||||
/// <summary>Delegate will remain in memory for the lifetime of the application.</summary>
|
||||
|
||||
/// <summary>Delegate will remain in memory for the lifetime of the application (or until <see cref="FunctionReference.Remove"/> is called).</summary>
|
||||
Permanent
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a reference to a function that can be called from native code.
|
||||
/// </summary>
|
||||
public class FunctionReference
|
||||
{
|
||||
private readonly Delegate m_method;
|
||||
|
||||
public FunctionLifetime Lifetime { get; set; } = FunctionLifetime.Permanent;
|
||||
|
||||
public unsafe delegate void CallbackDelegate(fxScriptContext* context);
|
||||
private CallbackDelegate s_callback;
|
||||
|
||||
private FunctionReference(Delegate method)
|
||||
private static readonly ConcurrentDictionary<int, FunctionReference> IdToFunctionReferencesMap = new();
|
||||
private static readonly ConcurrentDictionary<Delegate, FunctionReference> TargetMethodToFunctionReferencesMap = new();
|
||||
|
||||
private static readonly object ReferenceCounterLock = new();
|
||||
private static int _referenceCounter;
|
||||
|
||||
private readonly Delegate _targetMethod;
|
||||
private readonly CallbackDelegate _nativeCallback;
|
||||
|
||||
private readonly TaskCompletionSource _taskCompletionSource = new();
|
||||
|
||||
private FunctionReference(Delegate method, FunctionLifetime lifetime)
|
||||
{
|
||||
m_method = method;
|
||||
|
||||
unsafe
|
||||
{
|
||||
var dg = new CallbackDelegate((fxScriptContext* context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var scriptContext = new ScriptContext(context);
|
||||
|
||||
if (method.Method.GetParameters().FirstOrDefault()?.ParameterType == typeof(ScriptContext))
|
||||
{
|
||||
var returnO = m_method.DynamicInvoke(scriptContext);
|
||||
if (returnO != null)
|
||||
{
|
||||
scriptContext.SetResult(returnO, context);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var paramsList = method.Method.GetParameters().Select((x, i) =>
|
||||
{
|
||||
var param = method.Method.GetParameters()[i];
|
||||
object obj = null;
|
||||
if (typeof(NativeObject).IsAssignableFrom(param.ParameterType))
|
||||
{
|
||||
obj = Activator.CreateInstance(param.ParameterType,
|
||||
new[] { scriptContext.GetArgument(typeof(IntPtr), i) });
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = scriptContext.GetArgument(param.ParameterType, i);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}).ToArray();
|
||||
|
||||
var returnObj = m_method.DynamicInvoke(paramsList);
|
||||
|
||||
if (returnObj != null)
|
||||
{
|
||||
scriptContext.SetResult(returnObj, context);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Application.Instance.Logger.LogError(e, "Error invoking callback");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Lifetime == FunctionLifetime.SingleUse)
|
||||
{
|
||||
Remove(Identifier);
|
||||
if (references.ContainsKey(m_method))
|
||||
references.Remove(m_method);
|
||||
}
|
||||
}
|
||||
});
|
||||
s_callback = dg;
|
||||
}
|
||||
|
||||
Lifetime = lifetime;
|
||||
_targetMethod = method;
|
||||
_nativeCallback = CreateWrappedCallback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="FunctionLifetime"/>
|
||||
/// </summary>
|
||||
public FunctionLifetime Lifetime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// For <see cref="FunctionLifetime.SingleUse"/> function references, this task will complete when
|
||||
/// the function has finished invoking.
|
||||
/// </summary>
|
||||
public Task CompletionTask => _taskCompletionSource.Task;
|
||||
|
||||
public int Identifier { get; private set; }
|
||||
|
||||
public static FunctionReference Create(Delegate method)
|
||||
private unsafe CallbackDelegate CreateWrappedCallback()
|
||||
{
|
||||
if (references.ContainsKey(method))
|
||||
return context =>
|
||||
{
|
||||
return references[method];
|
||||
try
|
||||
{
|
||||
var scriptContext = new ScriptContext(context);
|
||||
|
||||
// Allow for manual handling of the script context
|
||||
if (_targetMethod.Method.GetParameters().FirstOrDefault()?.ParameterType == typeof(ScriptContext))
|
||||
{
|
||||
var returnValue = _targetMethod.DynamicInvoke(scriptContext);
|
||||
if (returnValue != null)
|
||||
{
|
||||
scriptContext.SetResult(returnValue, context);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var parameterList = _targetMethod.Method.GetParameters().Select((_, i) =>
|
||||
{
|
||||
var parameter = _targetMethod.Method.GetParameters()[i];
|
||||
return scriptContext.GetArgument(parameter.ParameterType, i);
|
||||
}).ToArray();
|
||||
|
||||
var returnObj = _targetMethod.DynamicInvoke(parameterList);
|
||||
|
||||
if (returnObj != null)
|
||||
{
|
||||
scriptContext.SetResult(returnObj, context);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Application.Instance.Logger.LogError(e, "Error invoking callback");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Lifetime == FunctionLifetime.SingleUse)
|
||||
{
|
||||
RemoveSelf();
|
||||
}
|
||||
|
||||
_taskCompletionSource.TrySetResult();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static FunctionReference Create(Delegate method, FunctionLifetime lifetime = FunctionLifetime.Permanent)
|
||||
{
|
||||
// We always want to create a new reference if the lifetime is single use.
|
||||
if (lifetime == FunctionLifetime.Permanent && TargetMethodToFunctionReferencesMap.TryGetValue(method, out var existingReference))
|
||||
{
|
||||
return existingReference;
|
||||
}
|
||||
|
||||
var reference = new FunctionReference(method);
|
||||
var reference = new FunctionReference(method, lifetime);
|
||||
var referenceId = Register(reference);
|
||||
|
||||
reference.Identifier = referenceId;
|
||||
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
private static Dictionary<int, FunctionReference> ms_references = new Dictionary<int, FunctionReference>();
|
||||
private static int ms_referenceId;
|
||||
|
||||
private static Dictionary<Delegate, FunctionReference> references =
|
||||
new Dictionary<Delegate, FunctionReference>();
|
||||
|
||||
private static int Register(FunctionReference reference)
|
||||
{
|
||||
var thisRefId = ms_referenceId;
|
||||
ms_references[thisRefId] = reference;
|
||||
references[reference.m_method] = reference;
|
||||
|
||||
unchecked { ms_referenceId++; }
|
||||
|
||||
return thisRefId;
|
||||
}
|
||||
|
||||
public static FunctionReference Get(int reference)
|
||||
{
|
||||
if (ms_references.ContainsKey(reference))
|
||||
lock (ReferenceCounterLock)
|
||||
{
|
||||
return ms_references[reference];
|
||||
}
|
||||
var thisRefId = _referenceCounter;
|
||||
IdToFunctionReferencesMap[thisRefId] = reference;
|
||||
TargetMethodToFunctionReferencesMap[reference._targetMethod] = reference;
|
||||
|
||||
return null;
|
||||
unchecked
|
||||
{
|
||||
_referenceCounter++;
|
||||
}
|
||||
|
||||
return thisRefId;
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr GetFunctionPointer()
|
||||
|
||||
public IntPtr GetFunctionPointer() => Marshal.GetFunctionPointerForDelegate(_nativeCallback);
|
||||
|
||||
private void RemoveSelf()
|
||||
{
|
||||
IntPtr cb = Marshal.GetFunctionPointerForDelegate(s_callback);
|
||||
return cb;
|
||||
Remove(Identifier);
|
||||
}
|
||||
|
||||
public static void Remove(int reference)
|
||||
{
|
||||
if (ms_references.TryGetValue(reference, out var funcRef))
|
||||
if (IdToFunctionReferencesMap.TryGetValue(reference, out var functionReference))
|
||||
{
|
||||
ms_references.Remove(reference);
|
||||
if (TargetMethodToFunctionReferencesMap.ContainsKey(functionReference._targetMethod))
|
||||
{
|
||||
TargetMethodToFunctionReferencesMap.Remove(functionReference._targetMethod, out _);
|
||||
}
|
||||
|
||||
IdToFunctionReferencesMap.Remove(reference, out _);
|
||||
|
||||
Application.Instance.Logger.LogDebug("Removing function/callback reference: {Reference}", reference);
|
||||
}
|
||||
|
||||
@@ -7119,6 +7119,15 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
[EventName("warmup_end")]
|
||||
public class EventWarmupEnd : GameEvent
|
||||
{
|
||||
public EventWarmupEnd(IntPtr pointer) : base(pointer){}
|
||||
public EventWarmupEnd(bool force) : base("warmup_end", force){}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[EventName("weapon_fire")]
|
||||
public class EventWeaponFire : GameEvent
|
||||
{
|
||||
|
||||
@@ -18,6 +18,12 @@ public interface IScriptHostConfiguration
|
||||
/// </summary>
|
||||
string PluginPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute path to the directory that contains CounterStrikeSharp plugin shared APIS.
|
||||
/// e.g. /game/csgo/addons/counterstrikesharp/shared
|
||||
/// </summary>
|
||||
string SharedPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute path to the directory that contains CounterStrikeSharp configs.
|
||||
/// e.g. /game/csgo/addons/counterstrikesharp/configs
|
||||
|
||||
@@ -7,12 +7,14 @@ internal sealed class ScriptHostConfiguration : IScriptHostConfiguration
|
||||
{
|
||||
public string RootPath { get; }
|
||||
public string PluginPath { get; }
|
||||
public string SharedPath { get; }
|
||||
public string ConfigsPath { get; }
|
||||
public string GameDataPath { get; }
|
||||
|
||||
public ScriptHostConfiguration(IHostEnvironment hostEnvironment)
|
||||
{
|
||||
RootPath = Path.Join(new[] { hostEnvironment.ContentRootPath });
|
||||
SharedPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "shared" });
|
||||
PluginPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "plugins" });
|
||||
ConfigsPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "configs" });
|
||||
GameDataPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "gamedata" });
|
||||
|
||||
@@ -53,6 +53,14 @@ namespace CounterStrikeSharp.API.Core
|
||||
/// </summary>
|
||||
void Unload(bool hotReload);
|
||||
|
||||
/// <summary>
|
||||
/// Will be called by CounterStrikeSharp after all plugins have been loaded.
|
||||
/// This will also be called for convenience after a reload or a late l oad, so that you don't have to handle
|
||||
/// re-wiring everything.
|
||||
/// </summary>
|
||||
/// <param name="hotReload"></param>
|
||||
void OnAllPluginsLoaded(bool hotReload);
|
||||
|
||||
string ModulePath { get; internal set; }
|
||||
|
||||
ILogger Logger { get; set; }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
@@ -154,5 +155,13 @@ namespace CounterStrikeSharp.API.Core
|
||||
/// <param name="simulating"><see langword="true"/> if simulating, <see langword="false"/> otherwise</param>
|
||||
[ListenerName("OnServerPreWorldUpdate")]
|
||||
public delegate void OnServerPreWorldUpdate(bool simulating);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server precaching resources (only when initial startup / changing map).
|
||||
/// </summary>
|
||||
/// <param name="manifest">Resource Manifest</param>
|
||||
[ListenerName("OnServerPrecacheResources")]
|
||||
public delegate void OnServerPrecacheResources(ResourceManifest manifest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,6 @@ public partial class CCSGameRules
|
||||
/// </summary>
|
||||
public void TerminateRound(float delay, RoundEndReason roundEndReason)
|
||||
{
|
||||
VirtualFunctions.TerminateRound(Handle, roundEndReason, delay);
|
||||
VirtualFunctions.TerminateRound(Handle, roundEndReason, delay, 0, 0);
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,7 @@ public partial class CCSPlayerController
|
||||
Userid = this
|
||||
};
|
||||
@event.FireEventToClient(this);
|
||||
@event.Free();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -14,8 +18,11 @@ public class PluginManager : IPluginManager
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<PluginManager> _logger;
|
||||
private readonly Dictionary<string, Assembly> _sharedAssemblies = new();
|
||||
private bool _loadedSharedLibs = false;
|
||||
|
||||
public PluginManager(IScriptHostConfiguration scriptHostConfiguration, ICommandManager commandManager, ILogger<PluginManager> logger, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory)
|
||||
public PluginManager(IScriptHostConfiguration scriptHostConfiguration, ICommandManager commandManager,
|
||||
ILogger<PluginManager> logger, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_scriptHostConfiguration = scriptHostConfiguration;
|
||||
_commandManager = commandManager;
|
||||
@@ -23,6 +30,36 @@ public class PluginManager : IPluginManager
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
private void LoadLibrary(string path)
|
||||
{
|
||||
var loader = PluginLoader.CreateFromAssemblyFile(path, new[] { typeof(IPlugin), typeof(PluginCapability<>), typeof(PlayerCapability<>) },
|
||||
config => { config.PreferSharedTypes = true; });
|
||||
var assembly = loader.LoadDefaultAssembly();
|
||||
|
||||
_sharedAssemblies[assembly.GetName().FullName] = assembly;
|
||||
}
|
||||
|
||||
private void LoadSharedLibraries()
|
||||
{
|
||||
var sharedDirectory = Directory.GetDirectories(_scriptHostConfiguration.SharedPath);
|
||||
var sharedAssemblyPaths = sharedDirectory
|
||||
.Select(dir => Path.Combine(dir, Path.GetFileName(dir) + ".dll"))
|
||||
.Where(File.Exists)
|
||||
.ToArray();
|
||||
|
||||
foreach (var sharedAssemblyPath in sharedAssemblyPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadLibrary(sharedAssemblyPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to load shared assembly from {Path}", sharedAssemblyPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
var pluginDirectories = Directory.GetDirectories(_scriptHostConfiguration.PluginPath);
|
||||
@@ -31,15 +68,46 @@ public class PluginManager : IPluginManager
|
||||
.Where(File.Exists)
|
||||
.ToArray();
|
||||
|
||||
foreach (var path in pluginAssemblyPaths)
|
||||
AssemblyLoadContext.Default.Resolving += (context, name) =>
|
||||
{
|
||||
if (!_loadedSharedLibs)
|
||||
{
|
||||
LoadSharedLibraries();
|
||||
_loadedSharedLibs = true;
|
||||
}
|
||||
|
||||
if (!_sharedAssemblies.TryGetValue(name.FullName, out var assembly))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return assembly;
|
||||
};
|
||||
|
||||
if (CoreConfig.PluginAutoLoadEnabled)
|
||||
{
|
||||
foreach (var path in pluginAssemblyPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadPlugin(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to load plugin from {Path}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var plugin in _loadedPluginContexts)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadPlugin(path);
|
||||
plugin.Plugin?.OnAllPluginsLoaded(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to load plugin from {Path}", path);
|
||||
_logger.LogError(e, "OnAllPluginsLoaded failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +119,8 @@ public class PluginManager : IPluginManager
|
||||
|
||||
public void LoadPlugin(string path)
|
||||
{
|
||||
var plugin = new PluginContext(_serviceProvider, _commandManager, _scriptHostConfiguration, path, _loadedPluginContexts.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
|
||||
var plugin = new PluginContext(_serviceProvider, _commandManager, _scriptHostConfiguration, path,
|
||||
_loadedPluginContexts.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
|
||||
_loadedPluginContexts.Add(plugin);
|
||||
plugin.Load();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Logging;
|
||||
@@ -64,7 +65,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
_hostConfiguration = hostConfiguration;
|
||||
_path = path;
|
||||
PluginId = id;
|
||||
|
||||
|
||||
Loader = PluginLoader.CreateFromAssemblyFile(path,
|
||||
new[]
|
||||
{
|
||||
@@ -76,7 +77,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
config.IsUnloadable = true;
|
||||
config.PreferSharedTypes = true;
|
||||
});
|
||||
|
||||
|
||||
if (CoreConfig.PluginHotReloadEnabled)
|
||||
{
|
||||
_fileWatcher = new FileSystemWatcher
|
||||
@@ -110,6 +111,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
Loader = eventargs.Loader;
|
||||
Unload(hotReload: true);
|
||||
Load(hotReload: true);
|
||||
Plugin.OnAllPluginsLoaded(hotReload: true);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -118,12 +120,12 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
public void Load(bool hotReload = false)
|
||||
{
|
||||
if (State == PluginState.Loaded) return;
|
||||
|
||||
|
||||
using (Loader.EnterContextualReflection())
|
||||
{
|
||||
var defaultAssembly = Loader.LoadDefaultAssembly();
|
||||
|
||||
Type pluginType = defaultAssembly.GetTypes()
|
||||
Type pluginType = defaultAssembly.GetExportedTypes()
|
||||
.FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
|
||||
|
||||
if (pluginType == null) throw new Exception("Unable to find plugin in assembly");
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<!-- <GenerateCompatibilitySuppressionFile>true</GenerateCompatibilitySuppressionFile> -->
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApiCompatValidateAssemblies>true</ApiCompatValidateAssemblies>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Global using directives
|
||||
|
||||
global using System;
|
||||
global using System.Linq;
|
||||
global using System.IO;
|
||||
global using System.Collections.Generic;
|
||||
global using CounterStrikeSharp.API.Core;
|
||||
@@ -132,7 +132,6 @@ namespace CounterStrikeSharp.API.Modules.Admin
|
||||
foreach (var adminDef in adminsFromFile.Values)
|
||||
{
|
||||
adminDef.InitalizeFlags();
|
||||
Console.WriteLine($"Domains: {adminDef.Flags.Count}");
|
||||
|
||||
if (SteamID.TryParse(adminDef.Identity, out var steamId))
|
||||
{
|
||||
|
||||
@@ -32,12 +32,16 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
|
||||
public class GameEvent : NativeObject
|
||||
{
|
||||
// Used to track freeable state for manually created events.
|
||||
private bool _freeable = false;
|
||||
|
||||
public GameEvent(IntPtr pointer) : base(pointer)
|
||||
{
|
||||
}
|
||||
|
||||
public GameEvent(string name, bool force) : this(NativeAPI.CreateEvent(name, force))
|
||||
{
|
||||
_freeable = true;
|
||||
}
|
||||
|
||||
public string EventName => NativeAPI.GetEventName(Handle);
|
||||
@@ -121,8 +125,28 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
|
||||
protected void SetEntityIndex(string name, int value) => NativeAPI.SetEventEntityIndex(Handle, name, value);
|
||||
|
||||
public void FireEvent(bool dontBroadcast) => NativeAPI.FireEvent(Handle, dontBroadcast);
|
||||
|
||||
public void FireEvent(bool dontBroadcast)
|
||||
{
|
||||
NativeAPI.FireEvent(Handle, dontBroadcast);
|
||||
_freeable = false;
|
||||
}
|
||||
|
||||
public void FireEventToClient(CCSPlayerController player) => NativeAPI.FireEventToClient(Handle, (int)player.Index);
|
||||
|
||||
/// <summary>
|
||||
/// Used to manually free the event.
|
||||
/// <remarks>If <see cref="FireEvent"/> is called, Free will be called automatically.</remarks>
|
||||
/// </summary>
|
||||
public void Free()
|
||||
{
|
||||
if (!_freeable)
|
||||
{
|
||||
throw new InvalidOperationException("Event is not able to be freed.");
|
||||
}
|
||||
|
||||
NativeAPI.FreeEvent(Handle);
|
||||
|
||||
_freeable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public class DynamicHook : NativeObject
|
||||
return NativeAPI.DynamicHookGetParam<T>(Handle, (int)typeof(T).ToValidDataType(), index);
|
||||
}
|
||||
|
||||
public T GetReturn<T>(int index)
|
||||
public T GetReturn<T>()
|
||||
{
|
||||
return NativeAPI.DynamicHookGetReturn<T>(Handle, (int)typeof(T).ToValidDataType());
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@ public static class VirtualFunctions
|
||||
|
||||
public static Action<IntPtr, string> SetModel = SetModelFunc.Invoke;
|
||||
|
||||
public static MemoryFunctionVoid<nint, RoundEndReason, float> TerminateRoundFunc =
|
||||
public static MemoryFunctionVoid<nint, RoundEndReason, float, nint, byte> TerminateRoundFunc =
|
||||
new(GameData.GetSignature("CCSGameRules_TerminateRound"));
|
||||
|
||||
public static Action<IntPtr, RoundEndReason, float> TerminateRound = TerminateRoundFunc.Invoke;
|
||||
public static Action<IntPtr, RoundEndReason, float, nint, byte> TerminateRound = TerminateRoundFunc.Invoke;
|
||||
|
||||
public static MemoryFunctionWithReturn<string, int, IntPtr> UTIL_CreateEntityByNameFunc =
|
||||
new(GameData.GetSignature("UTIL_CreateEntityByName"));
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Utils
|
||||
{
|
||||
public class ResourceManifest : NativeObject
|
||||
{
|
||||
private Action<nint, string> _AddResource;
|
||||
public ResourceManifest(nint pointer) : base(pointer)
|
||||
{
|
||||
_AddResource = VirtualFunction.CreateVoid<nint, string>(Handle, GameData.GetOffset("CEntityResourceManifest_AddResource"));
|
||||
}
|
||||
|
||||
public void AddResource(string resourcePath) => _AddResource(Handle, resourcePath);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
@@ -41,15 +42,35 @@ namespace CounterStrikeSharp.API
|
||||
public static double EngineTime => NativeAPI.GetEngineTime();
|
||||
public static void PrecacheModel(string name) => NativeAPI.PrecacheModel(name);
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="NextFrame"/>
|
||||
/// Returns Task that completes once the synchronous task has been completed.
|
||||
/// </summary>
|
||||
public static Task NextFrameAsync(Action task)
|
||||
{
|
||||
var functionReference = FunctionReference.Create(task, FunctionLifetime.SingleUse);
|
||||
NativeAPI.QueueTaskForNextFrame(functionReference);
|
||||
return functionReference.CompletionTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a task to be executed on the next game frame.
|
||||
/// <remarks>Does not execute if the server is hibernating.</remarks>
|
||||
/// </summary>
|
||||
public static void NextFrame(Action task)
|
||||
{
|
||||
var functionReference = FunctionReference.Create(task);
|
||||
functionReference.Lifetime = FunctionLifetime.SingleUse;
|
||||
NativeAPI.QueueTaskForNextFrame(functionReference);
|
||||
NextFrameAsync(task);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="NextWorldUpdate"/>
|
||||
/// Returns Task that completes once the synchronous task has been completed.
|
||||
/// </summary>
|
||||
public static Task NextWorldUpdateAsync(Action task)
|
||||
{
|
||||
var functionReference = FunctionReference.Create(task, FunctionLifetime.SingleUse);
|
||||
NativeAPI.QueueTaskForNextWorldUpdate(functionReference);
|
||||
return functionReference.CompletionTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -59,9 +80,7 @@ namespace CounterStrikeSharp.API
|
||||
/// <param name="task"></param>
|
||||
public static void NextWorldUpdate(Action task)
|
||||
{
|
||||
var functionReference = FunctionReference.Create(task);
|
||||
functionReference.Lifetime = FunctionLifetime.SingleUse;
|
||||
NativeAPI.QueueTaskForNextWorldUpdate(functionReference);
|
||||
NextWorldUpdateAsync(task);
|
||||
}
|
||||
|
||||
public static void PrintToChatAll(string message)
|
||||
|
||||
@@ -34,6 +34,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithVoiceOverrides", "..\ex
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithFakeConvars", "..\examples\WithFakeConvars\WithFakeConvars.csproj", "{1309954E-FAF7-47A5-9FF9-C7263B33E4E3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithSharedTypes", "..\examples\WithSharedTypes\WithSharedTypes.csproj", "{4E5289B5-E81D-421C-B340-B98B6FFE09D1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MySharedTypes.Contracts", "..\examples\MySharedTypes.Contracts\MySharedTypes.Contracts\MySharedTypes.Contracts.csproj", "{A37676EA-CF2F-424D-85A1-C359D07A679D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithSharedTypesConsumer", "..\examples\WithSharedTypesConsumer\WithSharedTypesConsumer.csproj", "{76AD7BB0-A096-4336-83E2-B32CAE4E9933}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -100,10 +106,22 @@ Global
|
||||
{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
|
||||
{4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A37676EA-CF2F-424D-85A1-C359D07A679D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A37676EA-CF2F-424D-85A1-C359D07A679D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A37676EA-CF2F-424D-85A1-C359D07A679D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A37676EA-CF2F-424D-85A1-C359D07A679D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{76AD7BB0-A096-4336-83E2-B32CAE4E9933}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{76AD7BB0-A096-4336-83E2-B32CAE4E9933}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{76AD7BB0-A096-4336-83E2-B32CAE4E9933}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{76AD7BB0-A096-4336-83E2-B32CAE4E9933}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
@@ -117,6 +135,9 @@ Global
|
||||
{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}
|
||||
{4E5289B5-E81D-421C-B340-B98B6FFE09D1} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
{A37676EA-CF2F-424D-85A1-C359D07A679D} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
{76AD7BB0-A096-4336-83E2-B32CAE4E9933} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -35,6 +35,7 @@ using CounterStrikeSharp.API.Modules.Menu;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static CounterStrikeSharp.API.Core.Listeners;
|
||||
|
||||
namespace TestPlugin
|
||||
{
|
||||
@@ -166,6 +167,19 @@ namespace TestPlugin
|
||||
|
||||
return HookResult.Continue;
|
||||
}), HookMode.Pre);
|
||||
|
||||
// Precache resources
|
||||
RegisterListener<Listeners.OnServerPrecacheResources>((manifest) =>
|
||||
{
|
||||
manifest.AddResource("path/to/model");
|
||||
manifest.AddResource("path/to/material");
|
||||
manifest.AddResource("path/to/particle");
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
Logger.LogInformation("All plugins loaded!");
|
||||
}
|
||||
|
||||
private void SetupConvars()
|
||||
|
||||
@@ -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);
|
||||
PluginAutoLoadEnabled = m_json.value("PluginAutoLoadEnabled", PluginAutoLoadEnabled);
|
||||
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());
|
||||
|
||||
@@ -29,6 +29,7 @@ class CCoreConfig
|
||||
std::vector<std::string> SilentChatTrigger = { std::string("/") };
|
||||
bool FollowCS2ServerGuidelines = true;
|
||||
bool PluginHotReloadEnabled = true;
|
||||
bool PluginAutoLoadEnabled = true;
|
||||
std::string ServerLanguage = "en";
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
66
src/core/game_system.cpp
Normal file
66
src/core/game_system.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* =============================================================================
|
||||
* CS2Fixes
|
||||
* Copyright (C) 2023-2024 Source2ZE
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program 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
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/log.h"
|
||||
#include "core/globals.h"
|
||||
#include "core/gameconfig.h"
|
||||
#include "core/game_system.h"
|
||||
|
||||
#include "core/managers/server_manager.h"
|
||||
|
||||
CBaseGameSystemFactory** CBaseGameSystemFactory::sm_pFirst = nullptr;
|
||||
|
||||
CGameSystem g_GameSystem;
|
||||
IGameSystemFactory* CGameSystem::sm_Factory = nullptr;
|
||||
|
||||
// This mess is needed to get the pointer to sm_pFirst so we can insert game systems
|
||||
bool InitGameSystems()
|
||||
{
|
||||
// This signature directly points to the instruction referencing sm_pFirst, and the opcode is 3
|
||||
// bytes so we skip those
|
||||
uint8* ptr = (uint8*)counterstrikesharp::globals::gameConfig->ResolveSignature("IGameSystem_InitAllSystems_pFirst") + 3;
|
||||
|
||||
if (!ptr) {
|
||||
CSSHARP_CORE_ERROR("Failed to InitGameSystems, see warnings above.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Grab the offset as 4 bytes
|
||||
uint32 offset = *(uint32*)ptr;
|
||||
|
||||
// Go to the next instruction, which is the starting point of the relative jump
|
||||
ptr += 4;
|
||||
|
||||
// Now grab our pointer
|
||||
CBaseGameSystemFactory::sm_pFirst = (CBaseGameSystemFactory**)(ptr + offset);
|
||||
|
||||
// And insert the game system(s)
|
||||
CGameSystem::sm_Factory = new CGameSystemStaticFactory<CGameSystem>("CSSharp_GameSystem", &g_GameSystem);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GS_EVENT_MEMBER(CGameSystem, BuildGameSessionManifest)
|
||||
{
|
||||
IEntityResourceManifest* pResourceManifest = msg->m_pResourceManifest;
|
||||
|
||||
CSSHARP_CORE_INFO("CGameSystem::BuildGameSessionManifest");
|
||||
|
||||
counterstrikesharp::globals::serverManager.OnPrecacheResources(pResourceManifest);
|
||||
}
|
||||
47
src/core/game_system.h
Normal file
47
src/core/game_system.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* =============================================================================
|
||||
* CS2Fixes
|
||||
* Copyright (C) 2023-2024 Source2ZE
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program 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
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/log.h"
|
||||
#include "entitysystem.h"
|
||||
#include "igamesystemfactory.h"
|
||||
|
||||
bool InitGameSystems();
|
||||
|
||||
class CGameSystem : public CBaseGameSystem
|
||||
{
|
||||
public:
|
||||
GS_EVENT(BuildGameSessionManifest);
|
||||
|
||||
void Shutdown() override
|
||||
{
|
||||
CSSHARP_CORE_INFO("CGameSystem::Shutdown");
|
||||
delete sm_Factory;
|
||||
}
|
||||
|
||||
void SetGameSystemGlobalPtrs(void* pValue) override
|
||||
{
|
||||
if (sm_Factory)
|
||||
sm_Factory->SetGlobalPtr(pValue);
|
||||
}
|
||||
|
||||
bool DoesGameSystemReallocate() override { return sm_Factory->ShouldAutoAdd(); }
|
||||
|
||||
static IGameSystemFactory* sm_Factory;
|
||||
};
|
||||
@@ -19,6 +19,9 @@
|
||||
#include "core/log.h"
|
||||
#include "scripting/callback_manager.h"
|
||||
|
||||
#include "core/game_system.h"
|
||||
#include <concurrentqueue.h>
|
||||
|
||||
SH_DECL_HOOK1_void(ISource2Server, ServerHibernationUpdate, SH_NOATTRIB, 0, bool);
|
||||
SH_DECL_HOOK0_void(ISource2Server, GameServerSteamAPIActivated, SH_NOATTRIB, 0);
|
||||
SH_DECL_HOOK0_void(ISource2Server, GameServerSteamAPIDeactivated, SH_NOATTRIB, 0);
|
||||
@@ -56,6 +59,8 @@ void ServerManager::OnAllInitialized() {
|
||||
on_server_pre_fatal_shutdown = globals::callbackManager.CreateCallback("OnPreFatalShutdown");
|
||||
on_server_update_when_not_in_game = globals::callbackManager.CreateCallback("OnUpdateWhenNotInGame");
|
||||
on_server_pre_world_update = globals::callbackManager.CreateCallback("OnServerPreWorldUpdate");
|
||||
|
||||
on_server_precache_resources = globals::callbackManager.CreateCallback("OnServerPrecacheResources");
|
||||
}
|
||||
|
||||
void ServerManager::OnShutdown() {
|
||||
@@ -81,6 +86,8 @@ void ServerManager::OnShutdown() {
|
||||
globals::callbackManager.ReleaseCallback(on_server_pre_fatal_shutdown);
|
||||
globals::callbackManager.ReleaseCallback(on_server_update_when_not_in_game);
|
||||
globals::callbackManager.ReleaseCallback(on_server_pre_world_update);
|
||||
|
||||
globals::callbackManager.ReleaseCallback(on_server_precache_resources);
|
||||
}
|
||||
|
||||
void* ServerManager::GetEconItemSystem()
|
||||
@@ -170,17 +177,17 @@ void ServerManager::UpdateWhenNotInGame(float flFrameTime)
|
||||
|
||||
void ServerManager::PreWorldUpdate(bool bSimulating)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_nextWorldUpdateTasksLock);
|
||||
std::vector<std::function<void()>> out_list(1024);
|
||||
|
||||
if (!m_nextWorldUpdateTasks.empty()) {
|
||||
CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} at time {1}", m_nextWorldUpdateTasks.size(),
|
||||
globals::getGlobalVars()->curtime);
|
||||
auto size = m_nextWorldUpdateTasks.try_dequeue_bulk(out_list.begin(), 1024);
|
||||
|
||||
for (size_t i = 0; i < m_nextWorldUpdateTasks.size(); i++) {
|
||||
m_nextWorldUpdateTasks[i]();
|
||||
if (size > 0) {
|
||||
CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} at time {1}", size,
|
||||
globals::getGlobalVars()->curtime);
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
out_list[i]();
|
||||
}
|
||||
|
||||
m_nextWorldUpdateTasks.clear();
|
||||
}
|
||||
|
||||
auto callback = globals::serverManager.on_server_pre_world_update;
|
||||
@@ -194,7 +201,18 @@ void ServerManager::PreWorldUpdate(bool bSimulating)
|
||||
|
||||
void ServerManager::AddTaskForNextWorldUpdate(std::function<void()>&& task)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_nextWorldUpdateTasksLock);
|
||||
m_nextWorldUpdateTasks.push_back(std::forward<decltype(task)>(task));
|
||||
m_nextWorldUpdateTasks.enqueue(std::forward<decltype(task)>(task));
|
||||
}
|
||||
|
||||
void ServerManager::OnPrecacheResources(IEntityResourceManifest* pResourceManifest)
|
||||
{
|
||||
CSSHARP_CORE_TRACE("Precache resources");
|
||||
auto callback = globals::serverManager.on_server_precache_resources;
|
||||
if (callback && callback->GetFunctionCount()) {
|
||||
callback->ScriptContext().Reset();
|
||||
callback->ScriptContext().Push(pResourceManifest);
|
||||
callback->Execute();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace counterstrikesharp
|
||||
@@ -19,6 +19,9 @@
|
||||
#include "core/globals.h"
|
||||
#include "core/global_listener.h"
|
||||
#include "scripting/script_engine.h"
|
||||
#include <concurrentqueue.h>
|
||||
|
||||
#include "core/game_system.h"
|
||||
|
||||
namespace counterstrikesharp {
|
||||
class ScriptCallback;
|
||||
@@ -31,7 +34,8 @@ public:
|
||||
void OnShutdown() override;
|
||||
void* GetEconItemSystem();
|
||||
bool IsPaused();
|
||||
void AddTaskForNextWorldUpdate(std::function<void()> &&task);
|
||||
void AddTaskForNextWorldUpdate(std::function<void()>&& task);
|
||||
void OnPrecacheResources(IEntityResourceManifest* pResourceManifest);
|
||||
|
||||
private:
|
||||
void ServerHibernationUpdate(bool bHibernating);
|
||||
@@ -51,8 +55,9 @@ private:
|
||||
ScriptCallback *on_server_update_when_not_in_game;
|
||||
ScriptCallback *on_server_pre_world_update;
|
||||
|
||||
std::vector<std::function<void()>> m_nextWorldUpdateTasks;
|
||||
std::mutex m_nextWorldUpdateTasksLock;
|
||||
ScriptCallback *on_server_precache_resources;
|
||||
|
||||
moodycamel::ConcurrentQueue<std::function<void()>> m_nextWorldUpdateTasks;
|
||||
};
|
||||
|
||||
} // namespace counterstrikesharp
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "core/log.h"
|
||||
#include "core/coreconfig.h"
|
||||
#include "core/gameconfig.h"
|
||||
#include "core/game_system.h"
|
||||
#include "core/timer_system.h"
|
||||
#include "core/utils.h"
|
||||
#include "core/managers/entity_manager.h"
|
||||
@@ -144,6 +145,12 @@ bool CounterStrikeSharpMMPlugin::Load(PluginId id, ISmmAPI* ismm, char* error, s
|
||||
CSSHARP_CORE_ERROR("Failed to initialize .NET runtime");
|
||||
}
|
||||
|
||||
if (!InitGameSystems()) {
|
||||
CSSHARP_CORE_ERROR("Failed to initialize GameSystem!");
|
||||
return false;
|
||||
}
|
||||
CSSHARP_CORE_INFO("Initialized GameSystem.");
|
||||
|
||||
CSSHARP_CORE_INFO("Hooks added.");
|
||||
|
||||
// Used by Metamod Console Commands
|
||||
@@ -186,9 +193,7 @@ void CounterStrikeSharpMMPlugin::AllPluginsLoaded()
|
||||
|
||||
void CounterStrikeSharpMMPlugin::AddTaskForNextFrame(std::function<void()>&& task)
|
||||
{
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_nextTasksLock);
|
||||
m_nextTasks.push_back(std::forward<decltype(task)>(task));
|
||||
m_nextTasks.try_enqueue(std::forward<decltype(task)>(task));
|
||||
}
|
||||
|
||||
void CounterStrikeSharpMMPlugin::Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick)
|
||||
@@ -201,19 +206,18 @@ void CounterStrikeSharpMMPlugin::Hook_GameFrame(bool simulating, bool bFirstTick
|
||||
*/
|
||||
globals::timerSystem.OnGameFrame(simulating);
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_nextTasksLock);
|
||||
std::vector<std::function<void()>> out_list(1024);
|
||||
|
||||
if (m_nextTasks.empty())
|
||||
return;
|
||||
auto size = m_nextTasks.try_dequeue_bulk(out_list.begin(), 1024);
|
||||
|
||||
CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} on tick number {1}", m_nextTasks.size(),
|
||||
globals::getGlobalVars()->tickcount);
|
||||
if (size > 0) {
|
||||
CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} on tick number {1}", size,
|
||||
globals::getGlobalVars()->tickcount);
|
||||
|
||||
for (size_t i = 0; i < m_nextTasks.size(); i++) {
|
||||
m_nextTasks[i]();
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
out_list[i]();
|
||||
}
|
||||
}
|
||||
|
||||
m_nextTasks.clear();
|
||||
}
|
||||
|
||||
// Potentially might not work
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <sh_vector.h>
|
||||
#include <vector>
|
||||
#include "entitysystem.h"
|
||||
#include "concurrentqueue.h"
|
||||
|
||||
namespace counterstrikesharp {
|
||||
class ScriptCallback;
|
||||
@@ -63,8 +64,7 @@ public:
|
||||
const char *GetLogTag() override;
|
||||
|
||||
private:
|
||||
std::vector<std::function<void()>> m_nextTasks;
|
||||
std::mutex m_nextTasksLock;
|
||||
moodycamel::ConcurrentQueue<std::function<void()>> m_nextTasks;
|
||||
};
|
||||
|
||||
static ScriptCallback *on_activate_callback;
|
||||
|
||||
@@ -4,7 +4,7 @@ rm -rf temp/ generated/
|
||||
mkdir -p temp generated
|
||||
mkdir -p generated
|
||||
|
||||
cp ../../libraries/GameTracking-CS2/Protobufs/*.proto temp/
|
||||
cp ../../libraries/Protobufs/csgo/*.proto temp/
|
||||
cp -r google/ temp/
|
||||
|
||||
for file in temp/*.proto; do
|
||||
|
||||
@@ -63,7 +63,7 @@ static void FireEvent(ScriptContext &script_context) {
|
||||
}
|
||||
|
||||
|
||||
static void FireEventToClient(ScriptContext& script_context) {
|
||||
static void FireEventToClient(ScriptContext& script_context) {
|
||||
auto game_event = script_context.GetArgument<IGameEvent*>(0);
|
||||
int entityIndex = script_context.GetArgument<int>(1);
|
||||
if (!game_event) {
|
||||
@@ -76,7 +76,17 @@ static void FireEvent(ScriptContext &script_context) {
|
||||
}
|
||||
|
||||
pListener->FireGameEvent(game_event);
|
||||
}
|
||||
}
|
||||
|
||||
static void FreeEvent(ScriptContext& script_context) {
|
||||
auto game_event = script_context.GetArgument<IGameEvent*>(0);
|
||||
if (!game_event) {
|
||||
script_context.ThrowNativeError("Invalid game event");
|
||||
}
|
||||
|
||||
globals::gameEventManager->FreeEvent(game_event);
|
||||
managed_game_events.erase(std::remove(managed_game_events.begin(), managed_game_events.end(), game_event), managed_game_events.end());
|
||||
}
|
||||
|
||||
static const char *GetEventName(ScriptContext &script_context) {
|
||||
IGameEvent *game_event = script_context.GetArgument<IGameEvent *>(0);
|
||||
@@ -263,6 +273,7 @@ REGISTER_NATIVES(events, {
|
||||
ScriptEngine::RegisterNativeHandler("HOOK_EVENT", HookEvent);
|
||||
ScriptEngine::RegisterNativeHandler("UNHOOK_EVENT", UnhookEvent);
|
||||
ScriptEngine::RegisterNativeHandler("CREATE_EVENT", CreateEvent);
|
||||
ScriptEngine::RegisterNativeHandler("FREE_EVENT", FreeEvent);
|
||||
ScriptEngine::RegisterNativeHandler("FIRE_EVENT", FireEvent);
|
||||
ScriptEngine::RegisterNativeHandler("FIRE_EVENT_TO_CLIENT", FireEventToClient);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
HOOK_EVENT: name:string, callback:func, isPost:bool -> void
|
||||
UNHOOK_EVENT: name:string, callback:func, isPost:bool -> void
|
||||
CREATE_EVENT: name:string, force:bool -> pointer
|
||||
FREE_EVENT: gameEvent:pointer -> void
|
||||
FIRE_EVENT: gameEvent:pointer, dontBroadcast:bool -> void
|
||||
FIRE_EVENT_TO_CLIENT: gameEvent:pointer, clientIndex:int -> void
|
||||
GET_EVENT_NAME: gameEvent:pointer -> string
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace CodeGen.Natives
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Generators.GenerateNatives();
|
||||
Generators.GenerateGameEvents();
|
||||
Generators.GenerateGameEvents().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,16 +30,25 @@ public partial class Generators
|
||||
public string? Comment { get; set; }
|
||||
}
|
||||
|
||||
private static List<GameEvent> GetGameEvents()
|
||||
private static HttpClient _httpClient = new HttpClient();
|
||||
private static string BaseUrl = "https://raw.githubusercontent.com/SteamDatabase/GameTracking-CS2/master/";
|
||||
|
||||
private static List<string> GameEventFiles = new List<string>()
|
||||
{
|
||||
"game/core/pak01_dir/resource/core.gameevents",
|
||||
"game/csgo/pak01_dir/resource/game.gameevents",
|
||||
"game/csgo/pak01_dir/resource/mod.gameevents"
|
||||
};
|
||||
|
||||
private static async Task<List<GameEvent>> GetGameEvents()
|
||||
{
|
||||
// temporary, not committing resource files directly to git for now
|
||||
var pathToSearch = @"/home/michael/Steam/cs2-ds/game/csgo/events/resource";
|
||||
if (!Directory.Exists(pathToSearch)) Environment.Exit(0);
|
||||
var allGameEvents = new Dictionary<string, GameEvent>();
|
||||
|
||||
foreach (string file in Directory.EnumerateFiles(pathToSearch, "*.gameevents", SearchOption.AllDirectories).OrderBy(Path.GetFileName))
|
||||
foreach (string url in GameEventFiles)
|
||||
// foreach (string file in Directory.EnumerateFiles(pathToSearch, "*.gameevents", SearchOption.AllDirectories).OrderBy(Path.GetFileName))
|
||||
{
|
||||
var deserialized = VdfConvert.Deserialize(File.ReadAllText(file));
|
||||
var file = await _httpClient.GetStringAsync($"{BaseUrl}/{url}");
|
||||
var deserialized = VdfConvert.Deserialize(file);
|
||||
|
||||
var properties =
|
||||
deserialized.Value.Where(x => x.Type == VTokenType.Property);
|
||||
@@ -79,12 +88,9 @@ public partial class Generators
|
||||
return allGameEvents.Values.ToList();
|
||||
}
|
||||
|
||||
public static void GenerateGameEvents()
|
||||
public static async Task GenerateGameEvents()
|
||||
{
|
||||
var pathToSearch = @"/home/michael/Steam/cs2-ds/game/csgo/events/resource";
|
||||
if (!Directory.Exists(pathToSearch)) return;
|
||||
|
||||
var allGameEvents = GetGameEvents();
|
||||
var allGameEvents = await GetGameEvents();
|
||||
|
||||
var gameEventsString = string.Join("\n", allGameEvents.OrderBy(x => x.NamePascalCase).Select(gameEvent =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user