# Getting started

s2script is a TypeScript plugin runtime for Source 2 games (Counter-Strike 2 first), loaded via Metamod:Source. The live runtime is **CS2 on `linuxsteamrt64` only**.

## Prerequisites

1. A Counter-Strike 2 dedicated server (Linux x86-64 / `linuxsteamrt64`).
2. Stock [Metamod:Source](https://www.sourcemm.net/) with plugin API (PLAPI) 18 support, installed under `game/csgo/addons/metamod/`. No patched or custom Metamod host is required; a compatible official upstream release is fine if you confirm PLAPI for that release and every other native plugin on the server.
3. Custom Panorama HUD also needs [MultiAddonManager](https://github.com/Source2ZE/MultiAddonManager) **v1.6.1 or later**. v1.6 is stale for CS2 builds 25515854+: the addon-list offset does not match, so clients never mount the HUD addon. See [HUD](https://s2script.com/docs/concepts/hud).

## Install the runtime

1. Download the latest `s2script-cs2-linux-*.zip` from the [Download](https://s2script.com/download) page (or GitHub Releases).
2. Extract into `game/csgo/` so `addons/` overlays in place. That creates a SHA-256–verified CS2 **game package** under `addons/s2script/`:

```text
addons/s2script/
  bin/linuxsteamrt64/s2script.so
  bin/linuxsteamrt64/libs2script_core.so
  game-packages.json
  game-packages/cs2/index.js
  game-packages/cs2/gamedata.json
  game-packages/cs2/trusted-functions.json
  gamedata/core/
  gamedata/sdkhooks/
  plugins/
```

`game-packages.json` is selected-package metadata plus artifact hashes. The native shim (`s2script.so`) and core (`libs2script_core.so`) **must ship as a pair** — replace the whole zip rather than mixing versions.

Operator overrides still live in `addons/s2script/gamedata/cs2/custom/` and apply **after** the verified package data (acquire, HUD-click, and damage signature repairs included). Every repair is reported at boot. Shipped game files are no longer loaded from `gamedata/cs2/` or `js/pawn.js`; there is no prelude-by-path fallback.

3. Patch `gameinfo.gi` so Metamod is the first SearchPath:

```text
Game    csgo/addons/metamod
```

4. Ensure `addons/s2script/configs` and `addons/s2script/data` are writable.
5. Restart the server and confirm with `meta list`.

The native s2script shim stays resident during gameplay. `.s2sp` plugins still hot-reload inside s2script; stop and restart the server for native shim or Metamod updates.

Operators can bound native async admission with `S2SCRIPT_ASYNC_LIMITS_JSON` before starting the process — [Async & networking](https://s2script.com/docs/concepts/async#async-limits).

## Base plugins

The release zip includes the SourceMod-parity suite (basecommands, basechat, playercommands, antiflood, adminhelp, basecomm, basebans, reservedslots, basetriggers, funcommands, clientprefs, adminmenu, basevotes, zones).

First adoption of the versioned cookie schema needs matched core and [clientprefs](https://s2script.com/plugins/clientprefs) artifacts installed with the server stopped, then a fresh process. Do not hot-load a newer clientprefs over an older generation with unversioned cookie writes still in flight. clientprefs is the sole cookie-table writer — see [Cookies](https://s2script.com/docs/concepts/cookies).

Opt-in plugins (nominations, rockthevote, nextmap, funvotes) live under `plugins/disabled/` — the loader's non-recursive scan skips that subdirectory. To enable one, move its `.s2sp` up one level into `addons/s2script/plugins/`. Those shipped files are not in `plugins.json`; a registry plugin is (see below), so do not move a managed file by hand.

## Install a plugin without loading it

`s2s install` records registry plugins in `addons/s2script/plugins.json`. To stage one so it stays off across restarts:

```bash
s2s install @edge/rtv --disabled
```

The `.s2sp` lands in `plugins/disabled/` (the loader never scans that folder) and the name is listed under `"disabled"`:

```json
{
	"dependencies": { "@edge/rtv": "^1.2.0" },
	"disabled": ["@edge/rtv"]
}
```

`"disabled"` must name a dependency — the CLI and the runtime reject anything else. A dependency is disabled only when **every** plugin that pulls it in is disabled; one shared with an enabled plugin stays enabled. The startup update still keeps a disabled plugin current there, so it is ready the moment you turn it on.

Enable and disable move the already-installed file without downloading. The loader's file watch then loads or unloads it:

```bash
s2s enable @edge/rtv
s2s disable @edge/rtv
```

The same feature is on the plugin API (`Plugins.install(names, { disabled: true })`, `Plugins.enable` / `Plugins.disable` from [`@s2script/sdk/plugins`](https://s2script.com/docs/api/modules/plugins)) and on the server console / RCON (`sm plugins install <name> --disabled`, `sm plugins enable` / `sm plugins disable`). Those console commands are not available to players, admins included.

`enable` / `disable` are permanent: they move the file and edit `plugins.json`, so they survive restarts. `sm plugins load` / `unload` only change what runs right now and touch nothing on disk.

## Load and unload for this run

`sm plugins load <folder>/<plugin id>` (and `Plugins.load` with the same spec) runs a plugin kept in **any** folder under `plugins/` **for this run only** — `disabled/@edgegamers/jailbreak`, `fun/@edge/rtv`, even nested folders like `events/halloween/@edge/rtv`. Previously `load` could only bring back a plugin unloaded earlier in the same run.

```text
sm plugins load disabled/@edge/rtv
sm plugins load fun/@edge/rtv
sm plugins load events/halloween/@edge/rtv
sm plugins unload @edge/rtv
```

`unload` takes the same `<folder>/<id>` form, or just the id.

- **Folder vs. id.** The plugin id is the last segment, or the last two when they form `@scope/name`. Everything before is the folder.
- **No accidental loads.** Only a folder prefix reaches into a folder. A bare `sm plugins load <id>` never does.
- **You type the id.** s2script maps it to its file by the registry naming rule (`@` and `/` become `_`, e.g. `_edgegamers_jailbreak.s2sp`). If no file has that name (dropped in or renamed by hand), the loader falls back to the archive whose manifest carries that id.
- **Confined to `plugins/`.** The folder must resolve inside it. `..`, absolute paths, symlinks leading out, and `plugins/` itself are refused.

The file stays where it is, so the next restart — or `sm plugins unload` — turns it off again. `sm plugins list` marks it `(running from plugins/<folder>/, this run only)`. `Plugins.list()` entries (`PluginInfo`) include `folder: string | null` (the folder under `plugins/` it was loaded from for this run; `null` for `plugins/` itself). If nothing matches, the console says there is nothing to load as that spec and shows the `<folder>/<plugin id>` form with an example.

Use this for servers that keep plugins around in folders and only switch them on for an event. See the [plugins module](https://s2script.com/docs/api/modules/plugins).

## Next

- Coming from another framework: [SourceMod](https://s2script.com/docs/migrating/sourcemod), [CounterStrikeSharp](https://s2script.com/docs/migrating/counterstrikesharp), [ModSharp](https://s2script.com/docs/migrating/modsharp), [Swiftly](https://s2script.com/docs/migrating/swiftly)
- [Authoring plugins](https://s2script.com/docs/authoring)
- [Plugin lifecycle](https://s2script.com/docs/concepts/lifecycle)
- [HUD (CS2 Panorama)](https://s2script.com/docs/concepts/hud)
- [Custom camera](https://s2script.com/docs/concepts/camera)
- [Browse plugins](https://s2script.com/plugins)
- [Package reference](https://s2script.com/docs/api/packages)
