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 cleanat least once first, because the clean phase installs the bundledConstLibandPrimitiveExtensionsjars fromServer/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.example → mysql.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 incore.api; the engine must not depend on specific content. - Use
ContentAPIhelpers andcolorize(...)for player-facing messages. - Author-tag classes with a KDoc
@authorwhere the surrounding code does. - detekt runs in
mvn verifyand CI — keep new code clean againstServer/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 cleaninstalls bundled libs — a fresh checkout won't compile until it's run.- No DB needed for local testing — leave
use_auth/persist_accountsfalse locally, but note the config comments: both MUST betruein production. - The
build/runscripts are Bash (Linux/OSX); Windows usesrun-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
docs/ARCHITECTURE.md— engine internals and the game loopdocs/CONTENT_GUIDE.md— how to add quests, NPCs, items, commandsdocs/BOT_SCRIPTING.md— the player self-botting system (::script) and how to add new scriptsdocs/SERVER_SETUP_ARCH.md— compile & run the server on Arch Linux and connect from a LAN PCdocs/GLOSSARY.md— RuneScape/RSPS terms used throughout the codeREADME.md— project history, values, and full setup instructions