# Architecture How the 2009scape game server is put together. See [`../AGENTS.md`](../AGENTS.md) for build/run instructions and the top-level rules, and [`CONTENT_GUIDE.md`](CONTENT_GUIDE.md) for adding gameplay. ## The two halves: engine vs. content The codebase is deliberately split: - **`Server/src/main/core/`** — the **engine**. Networking, the RS cache reader, the game loop/pulse scheduler, entities (players, NPCs), the world map, storage/auth, and the **content API** (`core.api`). Engine code must not depend on any specific piece of content. - **`Server/src/main/content/`** — the **content**. Quests, NPC behavior, minigames, skills, items, region-specific interactions. Content is built on top of `core.api` and is discovered reflectively at startup. Keeping this boundary clean is the main architectural discipline of the project. ## Startup and plugin discovery Entry point: `core.Server` (`core.Server.main`). During boot the server: 1. Loads configuration from `worldprops/default.conf` (TOML) into `GameWorld.settings`. 2. Loads the RuneScape **cache** (map, item/npc/object definitions) via `core.cache`. 3. Runs **`core.plugin.ClassScanner`**, which uses the **classgraph** library to scan the classpath and reflectively instantiate every class implementing a known `ContentInterface` sub-type (see the table in `AGENTS.md`). This is how content "registers" itself — there is no central manifest to edit. 4. Starts the world / game loop and opens the network listener(s). Because discovery is reflective, **adding a new content class is enough to activate it** — just implement the right interface and put the file under `content/`. `ClassScanner` also builds the registry of player-runnable bot scripts (see [`BOT_SCRIPTING.md`](BOT_SCRIPTING.md)) by picking up classes annotated with `@PlayerCompatible`. ## The game loop / pulse system The world advances in discrete **ticks** (RuneScape's canonical tick is 600 ms). Timed and repeating work is modeled as **`Pulse`** objects submitted to `GameWorld.Pulser`. A `Pulse` overrides `pulse(): Boolean` and returns `true` when it is finished. Movement, combat swings, skill actions, and bot scripts are all implemented as pulses. `TickListener` content runs once per tick globally; `MovementPulse` handles walking a player/NPC to a destination. Coroutines (`kotlinx-coroutines`) back parts of the worker/pulse infrastructure. ## Entities and nodes Everything interactable in the world is a **`Node`** (`core.game.node`): - `Entity` → `Player` and `NPC` (living things with combat, skills, movement). - `Player` also has an AI subclass, `AIPlayer`, used for bots. - `Item` / `GroundItem` — items in containers or on the floor. - `Scenery` — world objects (trees, doors, altars…). Players carry a lot of linked state under `core.game.node.entity.player.link` (quests, diaries, prayers, emotes, teleport manager, hint icons, etc.) and use `Container`s (`inventory`, `equipment`, `bank`) for items. ## The content API (`core.api`) `core.api.ContentAPI.kt` is a large file of **top-level helper functions** that form the intended, stable surface for content code — e.g. `sendMessage`, `animate`, `addItem`, `removeItem`, `getStatLevel`, `rewardXP`, `teleport`, `getVarp`/`setVarp`, `lock`, `sendDialogue`. Content should call these rather than reaching into engine internals. Key interfaces in `core.api` (all extend the `ContentInterface` marker so `ClassScanner` finds them): `InteractionListener`, `InterfaceListener`, `Commands`, `LoginListener`, `LogoutListener`, `TickListener`, `StartupListener`, `ShutdownListener`, `PersistPlayer`, `PersistWorld`, `MapArea`. ## Networking The server speaks the original RS2 build-530 binary protocol. - **TCP** is the primary transport. - An optional **WebSocket** listener (`org.java-websocket`) carries the *same* raw binary protocol inside binary frames, enabling browser/PWA clients. It is off by default; enable via `websocket_enabled`/`websocket_port` in `default.conf` (port defaults to `53594 + world_id`). Plain `ws://` for local testing; `wss://` (with a PKCS12 keystore) for production/PWA. See the README for keystore setup. - Client and server must share `secret_key` or the connection is refused. A separate **management** interface is defined via Protobuf (`Proto/Management.proto`, `src/main/proto/`). ## Data, config, and storage - **Configuration:** `worldprops/default.conf` (TOML), parsed by `core.game.system.config.ServerConfigParser` into `GameWorld.settings` (`core.game.world.GameSettings`). Paths (cache, saves, data, logs, scripts) are held in `core.ServerConstants`. - **Static game data** (npc spawns, shops, drop tables, item configs) lives in JSON files loaded at runtime. Edit these with the **Thanos tool** (needs Java 11), *not* by hand. - **Item/NPC/object/animation IDs** come from the external **`ConstLib`** dependency (`org.rs09.consts.*`, bundled in `Server/libs/`) — reference them as `Items.FEATHER_314`, `NPCs.CHICKEN_...`, etc. - **Persistence:** player saves + account data via MySQL (`mysql-connector-java`) or SQLite (`sqlite-jdbc`). Controlled by `use_auth` / `persist_accounts` in config; when both are false, **no database is needed** (ideal for local content work). Both MUST be `true` in production. ## Testing & static analysis - **JUnit 5** tests in `Server/src/test/kotlin/` (e.g. `APITests`, `ExchangeTests`, skill/region tests). Run with `./run -t` or `sh mvnw test`. - **detekt** (`Server/detekt.yml`) runs in the Maven `verify` phase and in CI (`.gitlab-ci.yml`); keep new Kotlin clean against it. ## Build specifics worth knowing - Maven, JVM target **11** (JDK 11 required — not newer). - `mvn clean` installs the bundled `ConstLib` and `PrimitiveExtensions` jars from `Server/libs/` into the local Maven repo, so a fresh checkout won't compile until clean has run once. - The package build produces a fat jar (`…-with-dependencies.jar`) whose main class is `core.Server`; the `build` script renames it to `builddir/server.jar`.