CounterStrikeSharp Documentation
repository·main·Indexed 20 days ago
https://github.com/roflmuffin/counterstrikesharpA .NET 8 scripting layer for Counter-Strike 2 that runs on top of a Metamod Source Plugin, enabling developers to create high-performance server-side plugins using C#. The framework provides APIs for console and chat commands, game event handlers, dependency injection via IPluginServiceCollection, semi-automatic configuration parsing with IPluginConfig, and localization using IStringLocalizer. It supports shared APIs, entity output hooks, and integration with Dapper and SQLite for data persistence.
What's inside CounterStrikeSharp
- You can use the Shared Types (Capabilities) pattern to allow one plugin to expose a library or contract (e.g., a balance contract) that other plugins can consume. This enables a plugin to expose a specific capability for a player or another plugin, which can then be accessed via an exposed API by any other plugin in the environment.
Access and use GameEvent parameters
mainSubclasses of
GameEventprovide strongly typed properties corresponding to the event definition (e.g.,longfor time limits,CCSPlayerControllerfor user IDs).Important Lifecycle Warning:
GameEventinstances and their properties are transient. They cease to exist once the event listener function returns. Do not attempt to access event properties inside asynchronous tasks, timers, or functions likeServer.NextFrame(). Instead, capture the required values into local variables before the listener function completes.Implement IPluginConfig for semi-automatic configuration parsing
mainTo enable semi-automatic configuration parsing and loading in CounterStrikeSharp, implement theIPluginConfiginterface within your plugin. This allows the framework to handle the loading and mapping of configuration data for your plugin automatically.How to implement inter-plugin communication using Capabilities
mainInter-plugin communication in CounterStrikeSharp is achieved through a Shared Plugin API (Capabilities). This pattern allows one plugin to expose functionality (an API) that other plugins can consume without direct project references.
To implement this, you follow a three-part pattern:
- Contract Library: A shared assembly containing only interfaces (no business logic).
- Capability Declaration: A static variable in your plugin class that identifies the API via a unique string name.
- Registration/Usage: The provider plugin registers an implementation, and the consumer plugin retrieves it using
.Get().
There are two types of capabilities:
PlayerCapability<T>: Provides functionality scoped to a specific player (e.g., a player's balance).PluginCapability<T>: Provides generic functionality scoped to the plugin instance (e.g., a global service).
// 1. The Contract (in a shared library) public interface IBalanceHandler { decimal Balance { get; } public decimal Add(decimal amount); public decimal Subtract(decimal amount); } // 2. The Declaration (in your plugin class) public static PlayerCapability<IBalanceHandler> BalanceCapability { get; } = new("myplugin:balance"); // 3. The Registration (by the provider plugin) Capabilities.RegisterPlayerCapability(BalanceCapability, player => new BalanceHandler(player)); // 4. The Usage (by the consumer plugin) var balance = BalanceCapability.Get(player); if (balance != null) { balance.Add(500); }Implement Immunity Checks in Plugins
mainCounterStrikeSharp does not automatically enforce immunity checks for all actions. Plugin developers must manually implement immunity logic when a player targets another player.
Use the following
AdminManagermethods to handle immunity:AdminManager.CanPlayerTarget: Check if a player is allowed to target another player based on their immunity levels.AdminManager.SetPlayerImmunity: Programmatically set a player's immunity value.
How dependency injection works in CounterStrikeSharp
mainCounterStrikeSharp utilizes a standard
IServiceCollectionto support dependency injection (DI) within plugins.To register your own services (scoped or singleton), you must implement the
IPluginServiceCollection<T>interface, whereTis your plugin class.Lifecycle and Discovery:
- CounterStrikeSharp scans your assembly for implementations of
IPluginandIPluginServiceCollection<T>. - It configures the service provider using your
ConfigureServicesimplementation. - It requests a singleton instance of your plugin class.
- Any dependencies declared in your plugin's constructor are automatically injected at instantiation time, which occurs before the
Loadmethod is called.
public class TestPluginServiceCollection : IPluginServiceCollection<TestPlugin> { public void ConfigureServices(IServiceCollection serviceCollection) { serviceCollection.AddScoped<ExampleInjectedClass>(); } }- CounterStrikeSharp scans your assembly for implementations of
Understand the difference between Controllers and Pawns
mainIn CS2, player data is split into two distinct entities:
- Player Controller (
CCSPlayerController): Represents the player on the server. Use this to access persistent data like SteamIDs. - Player Pawn (
CCSPlayerPawn): Represents the player's physical character in the game world. Use this to modify gameplay attributes like health.
Every controller has a
PlayerPawnproperty (which is aCHandle), and every pawn has aControllerproperty (also aCHandle). To access the underlying object from a handle, you must use the.Valueproperty.- Player Controller (
How hot reloading works in CounterStrikeSharp
mainCounterStrikeSharp supports automatic hot reloading. If you replace the
.dllfile in your plugin folder while the server is running, the framework will automatically trigger a reload.During a hot reload:
- The framework calls the
Unloadfunction (if implemented). - The framework calls the
Loadfunction with thehotReloadparameter set totrue.
Note on Event Handlers: The framework automatically deregisters event handlers and listeners during unload. You can safely re-register them in your
Loadmethod without manually checking thehotReloadflag, though you may use the flag to perform specific logic unique to a reload scenario.- The framework calls the
Understand Dependency Resolution Order
mainWhen the NuGet resolver is enabled, CounterStrikeSharp searches for assemblies in the following order:
- Plugins directory: In-place assemblies located within the plugin folders.
shared/folder: Existing shared assemblies mechanism (manual).- NuGet cache: The auto-resolver (if enabled).
Register chat and console commands
mainYou can register commands that function both as console commands and chat commands.
To ensure a command is automatically registered as a chat command (accessible via
!or/prefixes), prefix the command name withcss_. For example, a command namedcss_pingwill be automatically available in chat as!pingor/ping.This example demonstrates the pattern for registering these types of commands within CounterStrikeSharp.
Implement localization with IStringLocalizer and JSON files
mainTo provide localized strings in your Counter-Strike Sharp plugin, follow these steps:
- Setup Language Files: Create a
langfolder within your plugin directory. Add a.jsonfile for every language you wish to support, naming the files after their locale codes (e.g.,en.json,fr.json). - Configure Project Output: Ensure the
langfolder is included in your plugin's output directory. You can automate this in your.csprojfile. - Access the Localizer: You can obtain localization capabilities in two ways:
- Use Dependency Injection to add
IStringLocalizerto your services. - Use the
Localizerproperty provided directly on the plugin instance.
- Use Dependency Injection to add
- Localize Strings: Use the
IStringLocalizerinstance to retrieve the appropriate string based on the current locale.
- Setup Language Files: Create a
Upgrade CounterStrikeSharp
mainTo upgrade CounterStrikeSharp, download the latest release and copy the files to your server using the same method as the original installation.
CounterStrikeSharp is designed to prevent your configuration files from being overwritten during an upgrade.
Note on builds: If CounterStrikeSharp is already installed, you may use the non
with-runtimebuild, but you are responsible for ensuring your server's .NET runtime is up-to-date.