Open-world MMORPG template built with Bonfire (Flutter) + Dart Frog + WebSocket, with a server-authoritative architecture: the server owns the world state and every player's position; clients predict, interpolate and reconcile.
A shared, no-rooms world (Ragnarok Online vibe): maps with portals, NPC enemies, auth with characters, and persistent character position — re-enter and you're exactly where you left off.
- 🕹️ Server-authoritative movement — inputs (
MoveEvent) are validated and integrated on the server game loop; deltas are broadcast per map only when something changes (MapStateTracker). - 🧠 Client-side prediction + reconciliation — the local player moves instantly;
lastInputIdechoes let the client correct itself only when the server is truly ahead, avoiding "rubber-banding". - 🎯 Buttery remote players — render buffer interpolates on the server timeline (clock-synced via
TimeSync), with an adaptive render delay (measured from the state arrival age) + short extrapolation when the buffer runs dry. No more stutter/jumps on real networks. - 🗜️ Compact binary protocol — msgpack envelope (
BEvent), serialized once per broadcast and sent raw to every player in the map (single pass, no per-client inflation). - 🔐 Auth + characters — sign up/in by e-mail (SHA-256 + per-user salt), JWT issued/validated by the server, REST + WebSocket sharing the same data. Characters have nickname + skin (
boy/girl); joining with a JWT spawns you at the character's saved position. - 🗺️ Tiled worlds served by the server — the client loads maps over HTTP (
/maps/...) and crosses portals/gateways between maps (florest ↔ desert) with seamlessJoinMaptransitions. - ⚔️ Enemy NPCs — server-driven movement/vision, rendered on clients as remote entities (collision blocks the local player).
- 🎨 Ragnarok-inspired UI — login/character-select screens in landscape with ornate panels, in-game status HUD (HP/SP/EXP) and a character Status dialog (base stats + derived substats); game is locked to landscape.
![]() Login |
![]() Character select |
![]() Gameplay |
![]() Character status |
Monorepo — every Dart package lives here and is wired with path: dependencies:
| Path | What it is |
|---|---|
game_server/ |
Dart Frog server: REST auth API + the authoritative game loop (GameServer, maps, players, NPCs) |
game_client/ |
Flutter client (Bonfire 4.0): login → character select → world |
shared_events/ |
Protocol models shared by server & client (ComponentStateModel, MoveEvent, GameStateModel, JoinMapEvent...) with hand-written toMap/fromMap |
packages/bonfire_server/ |
Headless game engine (components, maps, movement, collision) |
packages/bonfire_socket_shared/ |
Transport envelope (BEvent), event serializer (msgpack/JSON), TimeSync clock sync |
packages/bonfire_socket_client/ |
WebSocket client (binary frames, dispatch imediato — no reorder queue) |
packages/bonfire_socket_server/ |
WebSocket server layer for Dart Frog |
Dockerfile-server |
Multi-stage self-build image for game_server (builds inside the container) |
.github/workflows/ci.yml |
CI: dart analyze/tests on every Dart package + flutter analyze on the client |
Requires the Dart Frog CLI (one-time):
dart pub global activate dart_frog_cliThen, from the monorepo root:
cd game_server
dart pub get
dart_frog dev # REST + WebSocket on http://localhost:8080The server also serves the Tiled map files (
/maps/map1/florest.tmj, ...) fromgame_server/public/.
Docker (self-build — needs no Dart Frog CLI locally, builds inside the container):
docker build -f Dockerfile-server -t bonfire-server .
docker run -p 8080:8080 bonfire-server
⚠️ Data (users/characters) lives in an in-memory datasource — a server restart wipes it. A real database is on the roadmap.
cd game_client
flutter pub get
flutter run # app is locked to landscapeThe client points at http://127.0.0.1:8080 by default (game_client/lib/util/enviroment.dart, LocalInviroment). To hit a deployed server, switch to ServerInviroment in game_client/lib/bootstrap_injector.dart.
- Sign up (e-mail + password) or log in — or enter anonymously ("entrar sem conta").
- Create a character: pick a nickname and a skin (
boy/girl). - Enter the world: your character spawns at its saved spot. Move with the joystick/keyboard and explore; other players and enemy NPCs appear around you in real time.
- Walk through the golden portals to travel between maps.
Client
- WebSocket transport (binary msgpack, immediate dispatch)
- Local player with prediction & reconciliation (
lastInputId) - Remote players/enemies: smooth interpolation on the server timeline (adaptive render delay + extrapolation, TimeSync)
- Tiled map loading from the server + portal navigation
- Login / sign-up screens (e-mail + password)
- Character select screen — Ragnarok-inspired, landscape-first
- Enemy NPC rendering (server-driven movement)
- Bonfire 4.0 (collision API,
bonfire_blocvendored until a 4.x release) - Game locked to landscape
- In-game status HUD (portrait, HP/SP/EXP bars, level badge) fed by the server state
- Character Status dialog — Ragnarok base stats (STR/AGI/VIT/INT/DEX/LUK) + derived substats, with server-validated point allocation
Server
- Game loop + per-map delta broadcast (
MapStateTracker, only on change) - Server-authoritative movement (input validation, 20–33 Hz position integration)
- Tiled map loading, collision, portals/gateways (florest ↔ desert)
- NPCs: movement + vision
- Auth: sign up/in, password hashing (salt), JWT issue/validate (REST and WebSocket join)
- Characters: create/list, skins
- Position persistence (map change / disconnect / 5s safety timer)
- Player attributes (authoritative): HP/SP synced through the state delta; stamina drains while moving, regenerates while idle
- Ragnarok base stats + classic economy — 0 points at creation, ⌊lv/5⌋+3 per level-up, progressive cost (⌊(x−1)/10⌋+2), cap 99
- XP & leveling: every 100 XP → level up (remainder kept); MaxHP/MaxSP derived from level + VIT/INT; level/XP/stats/HP persist on the character
- Melee combat hooking into life (
takeDamage/healare already wired on the server) - Killable enemies → the first in-game XP source (
addXpis already wired end-to-end: this unlocks real leveling and status points) - Attack feedback, death & respawn
- Status bars above enemies and other players (the state already carries
lifeand attributes)
| Client | Server |
|---|---|
| Ranged attacks, skills & MATK, separate Mana (MP) pool, ASPD | Ranged/skill validation, MATK/ASPD resolution |
| Inventory & equipment | Drops, inventory & equipment |
| Quests, chat, party, friends | Quests, chat, party, friends |
| — | Real database (currently in-memory) |
Historical design docs live at the repo root:
PHASE1_MOVEMENT_FIXES.md— first movement/interpolation iterationPHASE2_CLIENT_PREDICTION.md— client-side prediction & reconciliation designMOVIMENTO_FLUIDO_IMPROVEMENTS.md— smoothing/fluidity pass
| Client | Version |
|---|---|
| bonfire | ^4.0.0-beta.13 |
| flutter_bloc / equatable / get_it / http | ^8.1.3 / ^2.0.5 / ^8.0.3 / ^1.2.2 |
| bonfire_socket_client | local (monorepo) |
| Server | Version |
|---|---|
| dart_frog | ^1.0.0 |
| dart_frog_auth | ^1.2.0 |
| logger | ^2.0.2+1 |
| bonfire_server / bonfire_socket_server | local (monorepo) |
PRs are welcome — CI (dart analyze + tests on packages, flutter analyze --no-fatal-infos on the client) must stay green. This is an active project: feel free to open an issue to discuss a feature before a big PR.



