jengine-dungeon
Dungeon crawling: level generation, field of view, turn-based combat, items, monster AI.
| Kind | Project package: linked into the builds of the projects that list it |
| Source | packages/jengine-dungeon |
| Version | 0.1.0 |
| Entry point | jengine_dungeon::DungeonPackage |
| Script namespace | Dungeon |
| Listed by | projects/arcade-classics, projects/engine-test-lab, projects/rpg-classics |
Using it
Section titled “Using it”List it in the top-level "packages" array of project.jengine:
{ "packages": [ { "name": "jengine-dungeon", "source": { "type": "path", "path": "../../packages/jengine-dungeon" } } ]}Then rebuild the editor viewport for the project (jengine viewport-build projects/<name>, or the
editor’s Rebuild viewport banner). Native and web builds link it on their own.
A scene that uses its components must be a v2 scene ("version": 2): a v1 scene has no field
for a package component and keeps it as inert custom data.
A script can check that the package is linked with if Dungeon == nil then return end.
Components
Section titled “Components”| Scene key | Rust type | |
|---|---|---|
dungeon | Dungeon | package only: needs a v2 scene |
Script commands
Section titled “Script commands”Commands run in every runtime and return nothing; a package publishes what scripts read onto components (see the notes below). The flat name also works.
| Call | Flat name |
|---|---|
Dungeon.move(e, dx, dy) | dungeon_move |
Dungeon.wait(e) | dungeon_wait |
Dungeon.quaff(e) | dungeon_quaff |
Dungeon.descend(e) | dungeon_descend |
Dungeon.travel(e, wx, wy) | dungeon_travel |
Dungeon.new_game(e, seed) | dungeon_new_game |
Dungeon.set_ai(e, on) | dungeon_set_ai |
Dungeon.ascend(e) | dungeon_ascend |
Dungeon.pickup(e) | dungeon_pickup |
Dungeon.use(e, slot) | dungeon_use |
Dungeon.drop(e, slot) | dungeon_drop |
Dungeon.zap(e, slot, dx, dy) | dungeon_zap |
Dungeon.start(e, seed, class) | dungeon_start |
Dungeon.set_calm(e, on) | dungeon_set_calm |
Dungeon.set_advise(e, on) | dungeon_set_advise |
Scenes that use it
Section titled “Scenes that use it”projects/arcade-classics/assets/scenes/rogue.sceneprojects/engine-test-lab/assets/scenes/75-dungeon.sceneprojects/rpg-classics/assets/scenes/rogue.scene
From packages/jengine-dungeon/README.md.
Dungeon crawling: levels, sight, turns, combat, items and monsters. It’s the reusable core of Rogue and the roguelikes after it.
Used by projects/arcade-classics (Rogue), projects/rpg-classics (DEEP
BARROW, a NetHack-lite) and Engine Test Lab scene 75.
It comes in three layers, and the first two are plain Rust with no world involved, so a game (or a test) can use them without the component:
| Module | What it is |
|---|---|
map | A seeded RNG, the Map / Tile types, and a Rogue-style generator. The level is a 3 x 3 grid of rooms joined by a spanning tree of corridors, plus two extra corridors for loops, with doors and stairs down. Every walkable tile is reachable (tested over 200 seeds). |
fov | Recursive shadowcasting over eight octants. Walls are seen but hide what is behind them. |
game | The turn-based game, described below. |
items | The pack, magic items with randomised looks, identification, shops. |
bot | Travel (click to walk) and the bot that plays by itself. |
game covers:
- Actors with speed: energy per turn, so a bat at 150 acts three times in two.
- Bump-to-attack combat: the to-hit chance is 70% plus 5 per point of attack over defence (clamped to 10–95%), and damage is a dice range.
- Items: healing potions, gold, and weapons and armour that are worn when better than what you have. Picking up the Amulet wins.
- Experience levels, and slow regeneration.
- Levels defined by
LevelDef, with the last one holding a boss and the Amulet. - Monster AI:
- A sleeping monster may not notice you at once.
- An awake one chases using the engine’s A*
(
tilemap::find_path_with), going round other monsters. - It remembers where it last saw you, and wanders when it loses you.
- The bot: it drinks when hurt and rests when it’s safe, fights what’s next to it, goes for monsters and items it sees, explores to the nearest unknown edge, and then takes the stairs.
All of the content (creatures, gear, levels, numbers) lives in Content,
whose default is a two-level dungeon. A scene overrides only what it
changes.
Switched on by content (NetHack-lite)
Section titled “Switched on by content (NetHack-lite)”Everything below is off by default, so a scene that names none of it plays exactly as the arcade’s Rogue always did - down to the random numbers.
- Classes (
classes): stats, stealth, regeneration, gold, a startingkit(by name;"random wand"picks one), and what eachknows(an item, or a whole kind like"wand").Dungeon.start(e, seed, class). - A pack (
pack_size): items are carried, gear is wielded and worn by choice (Dungeon.use), dropped (Dungeon.drop), and found with an enchantment from -1 to +2. - Magic items (
items): potions, scrolls, wands (with charges) and food, each an effect with apowerrange, levels, weight and price. Potions, scrolls and wands wear a look fromlooks- “bubbling potion”, “scroll labelled ZELGO MER”, “oak wand” - shuffled every game, and are identified when their effect is seen, by a scroll of identify, or (in a shop) guessed from their price. Effects: potionheal,extra_heal,strength,speed,detect,sleep,confuse,sick,juice; scrollidentify,teleport,mapping,enchant_weapon,enchant_armor,scare,summon,fire; wandstriking,fire,sleep,dig,teleport(Dungeon.zap(e, slot, dx, dy)). - Hunger (
hunger.start> 0): nutrition falls a point a turn; hungry, weak (a point off attack and damage), fainting, then starving to death. - Monster behaviours (
CreatureDef::behavior):pack(comes in agroupthat wakes together),thief(steals gold, else an item, vanishes and runs; drops it all when killed),sleeper(sleeps until struck),erratic(flits about),peaceful(a shopkeeper: talked to, never fought). Any monster with arangeshoots along a clear line. - Shops (
LevelDef::shop+shopkeeper): a room of priced goods; step on one to see its price,Dungeon.pickupto buy it. - The climb back (
climb): the Amulet does not win by itself - you carry it back up and out of the first level’s stairs (Dungeon.ascend). Levels are kept as you left them, andclimb_monsterscome for you on each one you revisit with the Amulet. - Explore mode (
Dungeon.set_calm): monsters hit half as hard and the hero cannot die - a test’s or a learner’s run. - Causes of death:
DungeonState.causereads “killed by a jackal on dungeon level 3”, “starved to death on dungeon level 7”, “killed by an arrow shot by a kobold archer on …”.
The component: dungeon
Section titled “The component: dungeon”Put it on an entity that also has an engine tilemap. The package
writes the level into that tilemap at runtime, and draws the creatures and
items as atlas-region sprites it spawns and moves.
"tilemap": { "tile_size": [24, 24], "layers": [], "tilesets": [ { "texture_id": "atlases/rogue_tiles.png", "tile_width": 24, "tile_height": 24, "columns": 7, "tile_count": 7, "first_gid": 1, "image_width": 168, "image_height": 24 } ] },"dungeon": { "width": 44, "height": 26, "atlas": "atlases/rogue.png", "step_time": 0.12, "tiles": { "wall": 1, "floor": 2, "corridor": 3, "door": 4, "stairs_down": 5, "stairs_up": 6, "fog": 7 }, "content": { "...": "overrides" } }- The tilemap: the entity’s transform is the map’s top-left corner. The
package keeps two layers:
terrain(what you have explored) andfog(a dimming tile over explored ground that’s out of view). It rewrites them only when a turn passes. - Sprites: creatures show only while in view, and flash red when hit. Items stay where you saw them, dimmed. A step glides rather than jumps.
- Draw order: tilemaps draw in their own pass, under every sprite
whatever the
z_order. A full-screen background sprite would hide the dungeon, so leave the background to the project’sbackground_color.
Script commands
Section titled “Script commands”| Call | Does |
|---|---|
Dungeon.move(e, dx, dy) | step, or attack what’s there (8 directions; a diagonal can’t cut a corner or pass a doorway) |
Dungeon.wait(e) | pass a turn |
Dungeon.quaff(e) | drink a potion |
Dungeon.descend(e) | take the stairs down |
Dungeon.travel(e, wx, wy) | walk to a world position over explored ground, a step at a time, until a monster comes into view |
Dungeon.new_game(e, seed) | a new dungeon |
Dungeon.set_ai(e, on) | let the bot play |
Dungeon.start(e, seed, class) | a new dungeon as class class (0-based) of content.classes |
Dungeon.ascend(e) | take the stairs up (out of the first level, with the Amulet, on a climb) |
Dungeon.pickup(e) | pick up, or buy, what lies here |
Dungeon.use(e, slot) | drink, read, eat, wield, wear, take off the pack item in slot (0-based) |
Dungeon.zap(e, slot, dx, dy) | zap the wand in slot in a direction |
Dungeon.drop(e, slot) | drop the pack item in slot |
Dungeon.set_calm(e, on) | explore mode |
Dungeon.set_advise(e, on) | publish the bot’s next action as DungeonState.advice without letting it play |
What scripts read
Section titled “What scripts read”DungeonState, rewritten every frame (read it from a script with
tick_rate = 60):
| Field | Meaning |
|---|---|
events | { kind, text, x, y, wx, wy, amount }; kind is below, and text is a ready-made message line |
hp, max_hp, level, levels, xl, xp, gold, potions, kills, turn | the player now |
attack, damage, defense, weapon, armor | with gear |
dead, won, busy (walking a Dungeon.travel route), px, py | |
advice | with set_advise or set_ai: { kind, dx, dy, slot }, kind one of move, wait, descend, ascend, pickup, use, zap, drop, none |
With a pack, classes or hunger switched on it adds: pack ({ slot, letter, name, kind, region, worn, unknown }), pack_size, here (what lies at your
feet, with prices), nutrition, hunger ("", Hungry, Weak,
Fainting), fast, confused, helpless (turns left), class, cause,
amulet, max_depth, stairs (down, up or ""), discoveries
(“bubbling potion = potion of healing”), seed, calm, seen (monsters in
view) and str; weapon and armor become the worn items’ names with their
enchantment.
Component fields beside content: class, sprite_size (draw actors and
items this many pixels square), level_tiles (tile ids per level, so the
dungeon changes its look as it goes down), advise, calm.
An event’s kind is one of:
- combat:
hit,miss,kill,hurt,dodge,die,notice - items:
pickup,gold,equip,quaff,read,zap,eat,wield,wear,drop,buy,identify,enchant,teleport,map,amulet - monsters:
steal,shoot(fromfwx, fwytowx, wy); a wand’sboltlikewise, itstextthe effect - the hero:
hungry(amount 1-3),ascend - progress:
levelup,level,descend,win info, for anything else
src/map.rs: 200 seeds are all connected, with reachable stairs and a solid border, and a seed reproduces its level.src/fov.rs: an open room is seen whole, walls hide what’s behind them, the radius limits sight, and sight is symmetric between open tiles.src/game.rs:- start state; walls take no turn; bumping kills and gives XP
- potions; the stairs and the Amulet
- a monster chasing and attacking
- the bot winning at least 8 of 30 dungeons and not all of them
cargo test -p jengine-dungeon balance -- --ignored --nocaptureprints how the bot fares per seed
src/game.rsalso: packs wake together, thieves steal gold or an item and drop it when killed, sleepers sleep through company, archers shoot along a clear line, levels are kept and the climb ends out of the first level, hunger weakens and starves, explore mode cannot die.src/items.rs: looks shuffled per game, a class’s kit, drinking and identify identify, sleeping, a wand of striking and of digging, food, gear worn by choice, a shop.tests/deep_barrow.rs: RPG Classics’ DEEP BARROW content as shipped - the bot goes deep and sometimes dies, and explore-mode runs climb out.tests/rogue_scene.rs: the arcade’s Rogue scene.- The level is drawn into the tilemap layers, and actors are atlas sprites.
- A move command takes a turn.
- The bot reaches level 2, with fog over what it left behind.
- It also covers test-lab scene 75.