Files
2026-07-05 18:14:54 -04:00

9.5 KiB

AGENTS.md — 2009scape

Guidance for LLM agents (and humans) working in this repository. Read this first, then consult the deeper docs in docs/ for architecture and content patterns.

What this project is

2009scape is an open-source MMORPG emulation server — a from-scratch remake of RuneScape 2 as it existed around build 530 / January 2009.

The server speaks the original RS2 binary protocol to a matching game client. Players connect over TCP or (optionally) WebSocket. This repo is the game server only — the client and cache tooling live in separate projects.

The single most important rule

All new contributions MUST be written in Kotlin. Only touch the legacy Java when fixing or updating existing Java code. Do not write new features in Java.

Everything else in this file elaborates on how to do that well.

Repository layout (top level)

Path What it is
Server/ The game server Maven project — almost all work happens here
Server/src/main/core/ Engine: networking, cache, game loop, entities, the content API
Server/src/main/content/ Actual game content: quests, NPCs, minigames, skills, regions
Server/src/test/kotlin/ JUnit 5 tests
Server/worldprops/default.conf TOML server configuration (log level, DB, websocket, etc.)
Server/pom.xml Maven build definition
Server/detekt.yml Kotlin static-analysis (detekt) ruleset — runs in the verify phase
build, run Bash helper scripts (build / build+run the server)
run-server.bat Windows run script
Dockerfile, docker-compose.yml Containerized server + MySQL
Proto/, Server/src/main/proto/ Protobuf for the management interface
Tools/ Cache/data editing tools (RSDataSuite, drop-table tool, JSON diff)
docs/ Deeper docs generated for agents (see below)

Build, run, test

Prerequisites: JDK 11 (exactly — newer JDKs are not supported by this codebase), and for JSON data editing, the Thanos tool which needs Java 11.

The Maven wrapper (Server/mvnw) is the source of truth; the root build/run scripts wrap it. From the repo root:

./build -g          # build the server jar into builddir/server.jar
./build -qgc        # clean + build, skipping tests (-q)
./run               # incremental build, then run the server
./run -r            # force a clean rebuild, then run
./run -t            # run tests only, don't start the server
./run -h            # full option list

Directly with Maven (equivalent, from Server/):

sh mvnw clean       # clean (also installs bundled libs from Server/libs/)
sh mvnw package     # build the fat jar (…-with-dependencies.jar)
sh mvnw test        # run the JUnit test suite
sh mvnw verify      # build + run detekt static analysis

If you build manually (not via the provided scripts/IntelliJ), you must run mvn clean at least once first, because the clean phase installs the bundled ConstLib and PrimitiveExtensions jars from Server/libs/ into the local repo.

The server main class is core.Server. It runs headless and listens for client connections. By default (use_auth = false, persist_accounts = false in default.conf) no database is required — great for local content testing.

Docker: copy mysql.env.examplemysql.env, create config/default.conf from Server/worldprops/default.conf, then docker compose up --build.

Tech stack

  • Kotlin 1.8.20 (primary) + legacy Java 11 (~roughly half the files, being phased out)
  • Maven build; JVM target 11
  • kotlinx-coroutines for the pulse/worker system
  • classgraph for reflective plugin discovery (see below)
  • JSON-simple + toml4j for config/data, MySQL connector + SQLite for storage
  • Java-WebSocket for the optional browser transport
  • JUnit 5 for tests, detekt for static analysis

How content is wired in — the plugin/listener model

This is the concept an agent most needs to understand. There is no central registry you edit to add content. Instead, at startup core.plugin.ClassScanner reflectively scans the classpath (via classgraph) and instantiates every class implementing certain marker interfaces. You add content by creating a new class implementing the right interface anywhere under src/main/content/.

The core marker interface is core.api.ContentInterface. The important sub-interfaces:

Interface Override Purpose
InteractionListener defineListeners() React to player↔NPC/object/item/ground interactions (on(...), onUseWith(...), onEquip(...))
InterfaceListener defineInterfaceListeners() Handle interface/widget button clicks
Commands defineCommands() Register ::command chat commands, gated by Privilege
LoginListener / LogoutListener Run logic on player login/logout
TickListener Run logic every game tick
StartupListener / ShutdownListener Server lifecycle hooks
PersistPlayer / PersistWorld Serialize custom player/world data
NPCBehavior Per-NPC-id AI / combat behavior
MapArea / ZoneBuilder Define map zones and their rules
WorldEvent Global scheduled/world events
ActivityPlugin Minigames / activities

Minimal example (Server/src/main/content/global/skill/AttackListener.kt):

class AttackListener : InteractionListener {
    override fun defineListeners() {
        on(IntType.NPC, "attack") { player, npc ->
            player.attack(npc)
            return@on true          // true = interaction handled
        }
    }
}

Almost everything a content author needs is exposed as top-level helper functions in core.api.ContentAPI.kt (e.g. sendMessage, animate, addItem, getStatLevel, teleport, setVarp). Prefer these API functions over reaching directly into engine internals — they are the stable, intended surface for content.

Directory conventions for content

Put new content where similar content already lives:

  • content/region/<kingdom>/ — content tied to a geographic area (Misthalin, Asgarnia, Kandarin, Karamja, Fremennik, Morytania, Desert, Tirannwn, Wilderness, misc). This is by far the largest area (~850 files). Quest logic and area NPCs/dialogue go here.
  • content/minigame/<name>/ — self-contained minigames (Barrows, Castle Wars, Pest Control, MTA, etc.).
  • content/global/skill/<skill>/ — skill training logic shared across the world.
  • content/global/handlers/ — cross-cutting item/interface/scenery handlers.
  • content/data/ — shared data tables and enums (consumables, jewellery, quests list).

Item/NPC/object/animation IDs come from the ConstLib dependency (org.rs09.consts, bundled in Server/libs/). Static game data (spawns, shops, drop tables) lives in JSON data files loaded at runtime — edit those with the Thanos tool (Java 11), not by hand.

Coding standards & conventions

  • Kotlin for anything new. Match the style of the surrounding file.
  • Keep engine (core/) and content (content/) separate. Content depends on the API in core.api; the engine must not depend on specific content.
  • Use ContentAPI helpers and colorize(...) for player-facing messages.
  • Author-tag classes with a KDoc @author where the surrounding code does.
  • detekt runs in mvn verify and CI — keep new code clean against Server/detekt.yml.
  • Write a JUnit test under src/test/kotlin/ when adding non-trivial logic; the suite has good precedent for API, exchange, and skill tests.
  • Authenticity matters. This is a remake targeting the real Jan-2009 game. When implementing behavior, match authentic RuneScape behavior of that era rather than inventing new mechanics.

Contributing / merge requests

  • Upstream is GitLab, not GitHub. Development discussion is on Discord.
  • All merge requests must use the default MR template (.gitlab/issue_templates/Default.md) or they will be rejected.
  • CI is defined in .gitlab-ci.yml.
  • License is AGPL-3.0 and will not be changed — keep all contributions compatible.

Things to be careful about (agent gotchas)

  • Don't switch Java versions. JDK 11 specifically. Higher versions break the build.
  • Don't write new Java. New content must be Kotlin.
  • Don't hand-edit binary/JSON game data — use the provided tools.
  • mvn clean installs bundled libs — a fresh checkout won't compile until it's run.
  • No DB needed for local testing — leave use_auth/persist_accounts false locally, but note the config comments: both MUST be true in production.
  • The build/run scripts are Bash (Linux/OSX); Windows uses run-server.bat.
  • This repo has no pre-existing CLAUDE.md/AGENTS.md; the docs/ folder here was authored to help agents and is not upstream canon — treat the code and README as ground truth if they ever disagree.

Further reading