---
name: counterstrikesharp
description: Riferimento operativo per scrivere, compilare e mettere in esercizio plugin server-side per Counter-Strike 2 con CounterStrikeSharp (CS#) su Metamod:Source, in C#/.NET. Copre BasePlugin, game event, console command, listener, timer, config JSON, ConVar, entita' e giocatori (controller vs pawn), armi e inventari, hook di funzioni native, admin framework, installazione del server dedicato e deploy. Include le trappole gia' pagate sul progetto ZeusMod / King of the Olympus (crash noti, API che sembrano funzionare e non funzionano, cvar fantasma di CS2). USA SEMPRE questa skill quando si lavora su plugin CS2 o CounterStrikeSharp, quando si tocca il codice in src/KingOfOlympus, quando si parla di weapon_taser, GiveNamedItem, CCSPlayerController, Metamod, gamedata, deploy sul server dedicato, cvar di gioco o file .cfg di CS2 — anche se CounterStrikeSharp non viene nominato esplicitamente.
---

# CounterStrikeSharp — guida di lavoro

Verificata sulla versione **1.0.371** dell'API (il pacchetto NuGet `CounterStrikeSharp.API`
usato dal progetto), sorgenti del tag `v1.0.371` e documentazione ufficiale
<https://docs.cssharp.dev>. Le firme riportate qui sono state lette dal codice di quella
versione, non ricordate a memoria.

## Come usare questa skill

Il grosso del contenuto sta nei file in `references/`, che vanno letti quando servono
davvero — non tutti insieme. La regola pratica:

| Se devi... | Leggi |
|---|---|
| installare/aggiornare server, Metamod, CS#, compilare e distribuire il plugin | `references/01-installazione-e-deploy.md` |
| scrivere il plugin: eventi, comandi, listener, timer, config, ConVar, admin | `references/02-api-plugin.md` |
| toccare giocatori, pawn, entita', armi, inventari, posizioni, HUD | `references/03-entita-giocatori-armi.md` |
| agganciare funzioni native, schema, user message, entity output, API condivise | `references/04-hook-avanzati.md` |
| capire perche' qualcosa crasha o non fa niente | `references/05-trappole.md` **(leggilo prima di debuggare, non dopo)** |
| lavorare sul plugin King of the Olympus di questo repo | `references/06-progetto-zeusmod.md` |

Se stai per scrivere codice che crea entita', muove armi, rimuove oggetti dall'inventario
o legge lo stato di un giocatore a ogni tick, `05-trappole.md` ti fa risparmiare ore. Quasi
tutte quelle voci sono costate un crash del server o un pomeriggio di diagnosi.

## Il modello mentale in cinque righe

CS2 dedicato carica **Metamod:Source**, che carica **CounterStrikeSharp**, che ospita il
runtime .NET e carica i plugin come `.dll`. Il plugin gira **dentro il processo del
server**: nessuna eccezione .NET protegge dal codice nativo sottostante, quindi un
puntatore sbagliato non lancia un'eccezione, ammazza il server. Non si tocca il client di
nessuno e non c'e' rischio VAC.

Ogni giocatore esiste in due pezzi: il **controller** (identita', punteggio, squadra,
SteamID) e il **pawn** (il corpo nel mondo: vita, posizione, inventario, armi). Quasi ogni
bug di un plugin nasce dal confondere i due o dall'usare un riferimento a un pawn che nel
frattempo e' morto.

## Scheletro minimo di un plugin

```csharp
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using Microsoft.Extensions.Logging;

namespace MioPlugin;

public class MioPlugin : BasePlugin
{
    public override string ModuleName => "Mio Plugin";
    public override string ModuleVersion => "1.0.0";
    public override string ModuleAuthor => "chi lo firma";
    public override string ModuleDescription => "cosa fa";

    public override void Load(bool hotReload)
    {
        Logger.LogInformation("caricato (hotReload={HotReload})", hotReload);
    }

    // I comandi con prefisso css_ diventano automaticamente comandi di chat:
    // css_ciao si chiama in chat con !ciao o /ciao, e in console con css_ciao.
    [ConsoleCommand("css_ciao", "Saluta")]
    public void OnCiao(CCSPlayerController? player, CommandInfo info)
        => info.ReplyToCommand("ciao");

    [GameEventHandler]
    public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
        => HookResult.Continue;
}
```

Il `.csproj` deve avere `EnableDynamicLoading` e `CopyLocalLockFileAssemblies` a `true`:
il plugin viene caricato a runtime e le sue dipendenze devono finire accanto alla dll.

```xml
<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <EnableDynamicLoading>true</EnableDynamicLoading>
  <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
  <PackageReference Include="CounterStrikeSharp.API" Version="1.0.371" />
</ItemGroup>
```

Sul server la dll va in una cartella **che si chiama esattamente come la dll**:
`addons/counterstrikesharp/plugins/MioPlugin/MioPlugin.dll`, insieme al `.deps.json`, al
`.pdb` e alle dipendenze NuGet non incluse in CS#.

## Le cinque regole che evitano il 90% dei guai

**1. Valida ogni handle prima di usarlo.** `IsValid` sul controller non garantisce che il
pawn esista. Il pattern che regge e':

```csharp
if (player is not { IsValid: true, PawnIsAlive: true }) return;
var pawn = player.PlayerPawn.Value;
if (pawn is not { IsValid: true }) return;
```

I sotto-oggetti dello schema (`pawn.ItemServices`, `pawn.WeaponServices`) **non hanno**
`IsValid`: si controlla `is not null && Handle != IntPtr.Zero`.

**2. L'identita' stabile di un giocatore e' `player.Slot`.** `Utilities.GetPlayers()`
restituisce wrapper **nuovi a ogni chiamata**, anche per lo stesso giocatore: confrontarli
con `==` o `ReferenceEquals` da' sempre "diverso" e qualunque logica di "e' cambiato?"
impazzisce. Conserva lo `Slot` (un `int`), mai il wrapper.

**3. I dati di un `GameEvent` muoiono con l'handler.** Se ti servono dentro un
`AddTimer`, un `Server.NextFrame` o un `Task`, copiali in variabili locali prima. Leggere
`@event.Qualcosa` dopo l'uscita dall'handler e' un accesso a memoria liberata.

**4. Scrivere su un campo networked non basta: va notificato.** Dopo aver modificato una
proprieta' replicata (posizione, spotted state, move type) serve
`Utilities.SetStateChanged(entity, "CBaseEntity", "m_nomeCampo")`, altrimenti il client
non vede niente e sembra che il codice non funzioni.

**5. Non chiamare mai `Remove()` su un'entita' che ha ancora un proprietario.** Su
un'arma ancora nell'inventario di un giocatore lascia un riferimento morto nelle sue
weapon services e al primo aggiornamento di rete il server muore con
`FATAL ERROR: WriteEnterPVS: GetEntServerClass failed for ent N`. E' un crash nativo:
nessuna eccezione .NET, solo un minidump.

## Registrare le cose

Due strade, equivalenti: gli **attributi** (registrazione automatica, deregistrazione
automatica sul reload) e i metodi di istanza dentro `Load()`. Gli attributi sono piu'
puliti per il codice statico; i metodi servono quando la registrazione dipende dalla
config o va fatta in un servizio.

```csharp
public override void Load(bool hotReload)
{
    RegisterEventHandler<EventRoundStart>(OnRoundStart);            // default HookMode.Post
    RegisterEventHandler<EventPlayerDeath>(OnDeath, HookMode.Pre);  // pre: puoi bloccare
    RegisterListener<Listeners.OnMapStart>(map => Setup(map));
    AddCommand("css_stato", "Stato", (p, i) => i.ReplyToCommand("ok"));
    AddCommandListener("drop", OnDrop);                             // intercetta un comando del gioco
    AddTimer(0.1f, Tick, TimerFlags.REPEAT);
}
```

CS# deregistra da solo handler e listener sul reload, quindi in `Load` si registra sempre
senza controllare `hotReload`. Il flag serve solo per le azioni specifiche del reload a
caldo (es. reinizializzare lo stato di una partita gia' in corso).

## Ritmi e tempi

`Server.TickInterval` vale `0.015625f` (64 tick/s). Un timer a `0.1f` con
`TimerFlags.REPEAT` e' il compromesso usuale per un ciclo di gioco: abbastanza fine per
bonus e HUD, abbastanza rado da non pesare. `Listeners.OnTick` gira **ogni frame**: usalo
solo se ti serve davvero quella granularita'.

Il lavoro che deve avvenire nel frame giusto va incapsulato: `Server.NextFrame(...)` per
il frame successivo, `Server.NextWorldUpdate(...)` per dopo l'aggiornamento del mondo,
`Server.RunOnTick(tick, ...)` per un tick preciso. Tutto cio' che tocca entita' appena
create o appena droppate va fatto **il frame dopo**, non subito.

## Log, chat e diagnostica

`Logger` (un `ILogger` di Microsoft.Extensions.Logging) e' iniettato in `BasePlugin`.
Attenzione: **i log del plugin sono bufferizzati**, quindi non servono per verificare
qualcosa appena fatto. Per un riscontro immediato usa RCON e una risposta in chat o in
console (`Server.PrintToChatAll`, `player.PrintToChat`, `Server.PrintToConsole`).

I comandi si chiamano **dalla chat** con `!nome` o `/nome`, e **dalla console** con il
nome completo `css_nome`. Scrivere `!nome` in console da' "Unknown command": e' una
confusione che costa sempre qualche minuto.

## Quando qualcosa non funziona

L'ordine che paga, in questo ambiente, e' sempre lo stesso:

1. `05-trappole.md` — c'e' una discreta probabilita' che il problema sia gia' scritto li'.
2. Il `console.log` del server. Con `-condebug` non finisce in `game/csgo/` ma in
   `game/csgo/addons/metamod/console.log`, perche' Metamod e' il primo nei search path.
3. RCON, per interrogare lo stato adesso invece che leggere log vecchi.
4. Solo alla fine, aggiungere log al plugin e ricompilare.

E ricorda che **il reload a caldo di CounterStrikeSharp e' inaffidabile**: se il
comportamento non cambia dopo un deploy, probabilmente in memoria c'e' ancora il build
vecchio. Riavvia il server.
