Esposter

World

The overworld: tile-based maps authored in Tiled Map Editor, walked with grid-engine, populated by NPCs and interactive objects, with random monster encounters in marked areas.

How it works

Tiled pipeline — maps live as .tmx files; pnpm tiled:gen (scripts/tiled/index.ts, built on the parse-tmx package) generates typed artifacts into shared/generated/tiled/: the TilemapKey enum (Home, HomeBuilding1, HomeBuilding2), per-map LayerName enums, object-group names, and typed object-property classes (TeleportObjectProperty, EncounterObjectProperty, NpcId, ItemId, …). Map content changes are therefore compile-time-checked against the code that consumes them.

Movement — the grid-engine Phaser plugin moves the player cell-by-cell; position/direction live in the player store and persist in the save. NPCs move by MovementPattern (idle or clockwise patrol) via useMoveNpcs.

Interactions — pressing Confirm facing an object walks ObjectInteractionEffectMap, keyed by Tiled object group:

flowchart TD
  confirm[Confirm input] --> gio[useGetInteractiveObject\nposition + facing match]
  gio --> npc[NPC: effect list\nmessages / heal / scene fade]
  gio --> sign[Sign: message dialog]
  gio --> chest[Chest: grant item\nopened state → save world.chestMap]
  gio --> door[Door: teleport\nswitchToTilemap + spawn at target id]

NPC dialog and effects come from the NpcMap content map (assets/dungeons/data/npcs.ts); chest opened-state is per-map in save.world[tilemapKey].chestMap; doors read a typed TeleportTarget property and fade-switch tilemaps, spawning the player at the matching door id.

Random encounters — each step on an encounter layer increments stepsSinceLastEncounter, and the encounter chance is steps / MAX_STEPS_BEFORE_NEXT_ENCOUNTER (guaranteed at the cap). The layer's Tiled area property selects an EncounterAreaMap entry, a weighted random pick chooses the monster, createEncounteredMonster spawns it at the entry's level (replaying the normal level-up rolls above the species base, so rare picks arrive stronger), and the scene fade-switches to battle. A settings toggle (isSkipEncounters, dev convenience) bypasses the roll.

Key files

Paths relative to packages/app.

FileRole
scripts/tiled/index.tspnpm tiled:gen codegen entrypoint
shared/generated/tiled/generated enums + typed map properties
app/composables/dungeons/scene/world/tilemap/tilemap asset/metadata creation
app/composables/dungeons/scene/world/interaction/interaction dispatch composables
app/services/dungeons/scene/world/interaction/effect/ObjectInteractionEffectMap.tsobject group → effect
app/composables/dungeons/scene/world/useRandomEncounter.tsencounter roll + battle handoff
app/assets/dungeons/data/npcs.tsNPC content (dialog, effects, movement)
app/assets/dungeons/data/encounterAreas.tsweighted encounter tables + spawn levels
app/services/dungeons/monster/createEncounteredMonster.tslevel-scaled wild monster spawn

Notes

  • encounterAreas precomputes cumulative weights at module load so the weighted pick is a single threshold scan.
  • World state that must survive reloads (chests) lives in the save's per-tilemap WorldData; everything else (NPC positions) resets on scene entry.