Project — "The Adventure Begins"

dnd-game-v2

A browser-based, D&D 5e-inspired dungeon crawler. Create a character, explore hand-built and procedurally-populated dungeons, and fight turn-based combat in real time over WebSockets — all served by a single BoxLang backend with no frontend build step.

Character creation & sheet

Species, class, background, alignment, standard-array ability scores, starting spells, fighting styles, multiclassing, inventory, and spell management.

Turn-based combat

Grid-based movement and line-of-sight, attacks, spellcasting, rage, smite, breath weapons, short/long rests, and death saves — synced live per encounter over WebSockets.

Map editor

Paint tile-based dungeon/outdoor maps, place spawn points, loot, and transitions between maps and modules. Desktop-only — grid painting doesn't translate to a phone.

Standing party & accounts

A persistent, server-side roster of up to 4 characters managed from a header toolbar; username/password accounts (PBKDF2-hashed) scope characters and maps to their owner.

Tech stack

LayerWhat
BackendBoxLang (.bx classes, .bxm templates) running on CommandBox — no separate compile/build step for backend code.
RealtimeSocketBox (bundled BoxLang WebSocket module).
DatabaseMySQL.
FrontendVue 3 + Vue Router 4, loaded via CDN <script> tags — plain JS components, no bundler/npm build.
StylingHand-written CSS (public/assets/game.css), no framework.

Project layout

models/                  BoxLang service classes (business logic + SQL)
  AuthService.bx          Login/register, password hashing, ownership checks
  CharacterService.bx      Character creation, inventory, sheet data
  CombatService.bx        Facade over the combat engine — delegates to models/combat/
  MapService.bx           Map/tile storage, editor persistence, geometry decode
  combat/                  Combat engine, split into focused services:
    DiceService.bx           Dice notation parsing and rolling
    RulesService.bx          Ability/proficiency math, skills, species traits
    GridService.bx           Pathfinding, line of sight, walkability
    MonsterLibrary.bx        Stat-block parsing, rosters, spawn building
    SpellcastingService.bx   Caster classes, spell slots, spell lookups
    ProgressionService.bx    XP/level-ups, ASI, feats, multiclassing
    CharacterStateService.bx Character load/save, short/long rests
    CombatActionsService.bx  Attack resolution, action economy, conditions
    EnemyAIService.bx        Enemy turns, aggro, legendary actions

public/
  Application.bx           App-level config (session, datasource, cache-busting)
  WebSocket.bx              Combat WebSocket message handler
  index.bxm                 SPA shell (loads Vue + all component scripts)
  api/                       JSON endpoints consumed by the frontend (*.bxm)
  assets/
    app.js                   Router, root layout/nav, shared game state
    components/*.js          Vue components (one per screen)
    game.css                 All styling

db/                       Schema + reference-data dump
runtime/boxlang.json      BoxLang engine config (datasource, caches, logging)
server.json               CommandBox server config (webroot, WebSocket wiring)

Setup

Prerequisites

  • CommandBox (installs BoxLang automatically via server.json's cfengine setting)
  • MySQL reachable at the host/port configured below

1. Database

Load the schema and reference data into a fresh gameserver database:

bashmysql -u root gameserver < db/schema.sql
mysql -u root gameserver < db/seed.sql

2. Configure the datasource (optional)

Defaults assume MySQL on 127.0.0.1:3306 with user root and no password, database gameserver. Override via environment variables before starting the server:

bashexport DB_HOST=127.0.0.1
export DB_PORT=3306
export DB_DATABASE=gameserver
export DB_USER=root
export DB_DRIVER=MySQL   # or MSSQL, Postgres, etc.

3. Start the server

bashbox server start

First run installs the bx-mysql module automatically. The app is then served at the URL CommandBox prints (defaults to a random free port — pin one with box server start port=8080 if you want it fixed).

4. Create an account

Visit the app and register; there's no seeded admin user. The seeded modules (The Arena, Starter Dungeon) have no owner, so they're playable immediately but not editable via the map editor until claimed:

sqlUPDATE adventure_modules SET owner_user_id = <your-user-id> WHERE owner_user_id IS NULL;

If you're restoring an existing populated database rather than a fresh seed, the same applies to any characters rows with no owner.

Development notes

No frontend build step. Edit public/assets/**/*.js or game.css directly and reload — nothing to compile.

Cache-busting: every asset <script>/<link> tag in index.bxm carries a ?v= query string set once at server start (Application.bx). Restarting the server (box server restart) forces browsers to pick up frontend changes instead of serving stale cached JS/CSS — or, without a restart, load any page while logged in with ?resetassets=1 appended to regenerate the token immediately. Ignored for a logged-out visitor, so it can't be used to force cache invalidation for other users.

Formatting: box run-script format (or format:check for CI) formats models/ and root .bx files via boxlang format.

Exposing it externally (e.g. via ngrok) Works out of the box — new WebSocket("/ws") resolves to wss:// automatically over HTTPS, no config changes needed. Just remember sessions are per-origin, so you'll need to log in again on the tunnel's URL even if you're already logged in on localhost.

Testing

All submissions must include corresponding test specs (tests/specs/) covering the change. Tests run via TestBox's HTTP runner (tests/runner.bxm), not a CommandBox CLI module. With the server running:

  • Browser — visit /tests/runner.bxm for TestBox's visual HTML report.
  • CLI/CIcurl -s "http://127.0.0.1:PORT/tests/runner.bxm?reporter=json" for a JSON summary (totalSpecs, totalPass, totalFail, totalError).

Useful query params: reporter (simple default, json, text, dot), bundles (restrict to one spec — needs the fully-qualified dotted path, e.g. ?bundles=tests.specs.unit.CombatServiceSpec), labels (restrict to labeled tests).

Credits

Dungeon/tile art from Kenney's Tiny Dungeon and Roguelike/RPG packs (CC0 1.0).