This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Build in release mode (optimized)
cargo build --release
# Run the server (requires DATABASE_PASSWORD environment variable)
DATABASE_PASSWORD=ragnarok cargo run --package server --bin server
# Run with visual debugger (requires visual_debugger feature)
cargo run --package server --bin server --features visual_debugger# Run all tests
cargo test --release
# Run integration tests (requires running PostgreSQL database)
cargo test --features integration_tests
# Run unit tests only
cargo test --features unit_testsThis is a Ragnarok Online MMORPG server implementation written in Rust that combines the traditional Login/Char/Map server architecture into a single unified server. The architecture is built around message-passing and event-driven design patterns.
This project focus exclusively on "pre-re" (or "pre renewal") version of the game.
- Message Passing: Uses message passing instead of shared memory with locks to avoid deadlocks and ensure thread safety
- Event-Driven: State changes occur through queued events processed by dedicated loops
- Service Layer: Business logic is separated into service layers
- Repository Pattern: Data access is abstracted through repository interfaces
- Main game loop runs at fixed intervals (40ms tick rate)
- Events are queued for future ticks and processed sequentially
- State updates trigger persistence events and client notifications
- Game Loop Thread: Processes game events and state updates
- Map Instance Threads: Each map instance runs its own event loop for scalability
- Persistence Thread: Handles all database write operations
- Client Notification Thread: Sends packets to connected clients
server/: Core server implementationserver/src/server/boot/: Server initialization (map loading, script compilation, etc.)server/src/server/service/: Business logic layer (character, battle, inventory, etc.)server/src/server/repository/: Data access layer with PostgreSQL integrationserver/src/server/request_handler/: Packet handling controllersserver/src/server/script/: Integration with rAthena script virtual machineserver/src/server/state/: Game state management (characters, maps, mobs)server/src/server/mod.rs: Implementation of server threadsserver/src/server/game_loop.rs: Implementation of the main game loop, latency of operation within the game loop should be low (<20ms) or server will lag. There is only one loop for the whole server. it handles action made by playerserver/src/server/map_instance_loop.rs: Implementation of map instance loop, responsible to handle action on a specific map, like interaction with MOB or NPC. There is one loop per map instance thread.server/src/server/persistence.rs: Implementation of an event loop for all database interaction which can be defered.server/src/server/request_handler/mod.rs: A function calling packet parser then by downcasting reference to the packet implementation route the request to the right function to handle it.server/src/server/model/events/game_event.rs: Contains enumeration of game event, that are handled by the game loop. It is used for message passing between request handler thread and game loop.server/src/server/model/events/client_notification.rs: Enumeration for sending packet to the clientserver/src/server/state/server.rs: Access to server state, access should only be done from game loop, state can be accessed for mutability in an unsafe way, thus it is mandatory to access it only from main game loop/src/repository/: data access layer, this is the implementation of database interaction usingsqlx, we write mainly raw SQL with prepared statement./src/tests/: unit and integration test of the serverlib/: crate for specific logic implementationlib/configuration: Structure for configuration the server. This is where configuration entry should be addedlib/models: Structures shared accross crateslib/packets: Structures of all packets exchanged between client and serverlib/skills: Structures containing implementation of all class skills
config.json: Main server configuration (copy fromconfig.template.json)- PostgreSQL database for persistent data (accounts, characters, etc.)
- All services have an instance of
client_notification_sender: SyncSender<Notification>,in their structure Notificationis an enum implemented atserver/src/server/model/events/client_notification.rsNotificationinner structures contains raw packet data Vec which come from any packets structurerawfieldserver/src/server/mod.rscontains implementation ofclient_notification_threadwhich route and send packet to the right socket(s)
Only read files and directory listed in Major modules section, if file or directory path does not start with ones listed in Major modules section, ignore them
Never read files/directory belows because they are too big
-
lib/packets -
lib/skills -
when reading documentation never read for "re" (or "renewal") version of the game
This section contains guidance for common implementation tasks
- Add a new condition in
server/src/server/request_handler/mod.rsto downcast_ref the packet to handle, the prompt MUST contains the packet structure - Add a new function to handle this packet following this pattern:
pub fn handle_MY_PACKET_DESCRIPTION(server: &Server, context: Request) - Add a new GameEvent in
server/src/server/model/events/game_event.rs. If there is more than 1 argument to provide in the event, a structure containing those arguments should be created aswell - Arguments of the events are generally, the
char_idof typeu32and otherPacketstructure fields except:packet_id,raw,*_raw - Implement the handling of this GameEvent in the game loop
server/src/server/game_loop.rs - Inside
server/src/server/game_loop.rsa mutable reference of character can be obtain by calling:let character = server_state_mut.characters_mut().get_mut(&game_event_arguments.char_id).unwrap(); - Implement the business logic in a service present in
server/src/server/service/a service function usually have following signature:pub fn use_item(&self, server_ref: &Server, runtime: &Runtime, character: &mut Character, game_event_arguments: MyGameEventArguments) - In addition to business logic the service can also send packet to the client using:
self.client_notification_sender.send(Notification::Char(CharNotification::new(character.char_id, packet_to_send.raw))) .unwrap_or_else(|_| error!("Failed to send notification packet_to_send to client")); - Implement unit test for the newly added service function
- Create the enum in
lib/models/src/enums/(add to existing file or create new one) - Use derive macro:
#[derive(WithMaskValueU64)](or U32, U16, U8 depending on size needed) - First variant must have explicit
#[mask_value = 1]attribute - Subsequent variants auto-double (2, 4, 8, 16...)
- Use
#[mask_value = X]to override specific values - Use
#[mask_all]for a variant combining all flags - If creating a new file, add
pub mod filename;tolib/models/src/enums/mod.rs
Example:
#[derive(WithMaskValueU64, Debug, Copy, Clone, PartialEq, Eq)]
pub enum MyFlags {
#[mask_value = 1]
FirstFlag,
SecondFlag, // = 2
ThirdFlag, // = 4
#[mask_all]
All,
}Traits provided: from_flag(value), try_from_flag(value), as_flag()
Important: Never use hex values directly for flags. Always use enum variants with .as_flag() and bitwise OR:
// Good
let mode = MobMode::CanMove.as_flag() | MobMode::CanAttack.as_flag();
// Bad - never do this
let mode = 0x81;