Skip to content

Ground tile maps (experimental)

Experimental

New in v1.4.0 and still settling. The file formats below are what LandingParty (Dawnline) ships with and what the linter and the Tile Map Editor read, but field names and checks may still change. Try it, and report what works and what does not.

A ground tile map is walkable ground for an away team: a ridge, a colony street, a cave system. Each place is an area drawn as ASCII in its own file, a party of crew figures walks it one cell at a time, and a console draws it with gui_tilemap. Nothing on it is a space object.

This is not the same thing as Tile maps, which lay out a sector of space from an ASCII string of terrain decks.

A mission's ground is made of three kinds of file:

File What it holds Read by
<area>.tiles One area: its map, legend, marks and exits tilemap_load
<name>.tileset The tile kinds: which can be walked or seen across, and how each one looks tilemap_tileset_load
media/tileart/<set>/manifest.json An art set: the pictures tilemap_art_use

Props, people and hostiles that stand on the ground are placed from an .amd file (see Placing things on the ground).

An area file

area: ridge
title: Landing Ridge
tileset: mereth
entry: landing                  # a mark name, or "x, y"
legend:
  .: dust
  ,: scrub
  #: rock
  ~: brine
  L: dust @landing              # a tile kind, and a MARK on the cell
  c: path @to_colony            # a mark named to_<area> is an exit there
exits:
  to_colony: colony @to_ridge   # optional - where an exit leads, and to which mark
---
###########
#...,,..c.#
#..L......#
###########

Everything after --- is the map, row 0 first, and x counts columns from the left. A space is nothing: never walked, and drawn black.

Header:

Key Meaning
area: The area's name. Required.
title: What the players see. Defaults to the name.
tileset: Which tileset its kinds come from.
entry: Where somebody beamed down stands: a mark, or x, y. Without it, the walkable cell nearest the middle.
known: no hides the area until a scan reveals it (tilemap_reveal_area).
beam: no means the transporter cannot lock on here.
size: WxH. Fixes the size instead of taking it from the longest row. The editor writes it when you resize.

Legend. Each line is one character, a colon, and a tile kind. Because the key is one character, # and : are fine as keys: a comment is only a # in column 0. A legend line can also put a mark on every cell drawn with that character. For example, pppppp drawn with p: deck @pad makes a six-cell mark called pad.

Marks name places. A scene can belong to one, and a prop can stand on one. When an actor steps onto a mark, the tilemap_entered signal fires.

Exits. A mark called to_<area> is a way out to that area. The exits: block is only needed to choose where the party arrives in the other area. Without it, they stand beside that area's way back.

A tileset file

tileset: mereth
title: Mereth surface
kinds:
  dust:    walk see   look=dirt
  salt:    walk see   look=salt
  brine:        see   look=water     # seen across, never walked
  cliff:        see   look=cliff
  rock:               look=rock      # neither
  wall:    tall       look=wall_metal
  door:    walk       color=#a86

Each word on a kind line is a rule the kind has. A kind that names neither walk nor see blocks both. A misspelled word is an error, never a silent wall.

Word Meaning
walk Can be walked on.
see Can be seen across. Fog of war looks through it. A kind you can see across but not walk, such as water, is what makes a map readable.
tall Stands up. The ground just south of it takes its art set's shade look.
look= Which ground look an art set draws it with. The default is the kind's own name.
cell= A sprite key to draw it with when no art set dresses it.
color= A tint. The editor also uses it as the kind's color.
over= When two kinds meet, the higher over has its fringe drawn on top.

A # that starts a word ends the line, so color=#a86 is a color and # note is a comment.

By convention, tileset: mereth in an area file names mereth.tileset in the same folder. That is how the linter and the editor find it.

A tileset declared in Python still works

tilemap_tileset(name, {kind: {"walk": ..., "see": ..., "look": ...}}) is the same thing as a dict. The game treats both the same. Without a .tileset file, however, the linter and the editor cannot tell what can be walked. Their checks that need it are skipped, and the editor cannot hatch the cells nobody can walk.

Loading it

A mission's own Python (LandingParty's lp_world.py, slightly trimmed):

def lp_setup_tiles(*sets):
    from sbs_utils.procedural.media import media_read_relative_file as read
    from sbs_utils.procedural.tilemap import tilemap_tileset_load
    from sbs_utils.procedural.tilemap_art import tilemap_art_use
    tileset = tilemap_tileset_load(read("surface/mereth.tileset"))
    return tilemap_art_use(*sets, tileset=tileset)     # builtin, then TILE_ART

def lp_load_areas():
    from sbs_utils.procedural.media import media_read_relative_file as read
    from sbs_utils.procedural.tilemap import tilemap_load
    return sum(1 for key in ("ridge", "colony", "flats")
               if tilemap_load(read("surface/%s.tiles" % key)))

Load the tileset before the art, because an art set dresses the kinds of a tileset that already exists. An area or tileset that cannot be read is logged and skipped, not raised: one bad file should not take a mission down. That is why the checks matter.

Art sets

A mission names logical keys only (fig:crew_eva, prop:hatch, ground looks like dirt). An art set says what they look like: a folder media/tileart/<set>/ with PNG sheets and a manifest.json. tilemap_art_use() loads the mission's own builtin set, then whatever the TILE_ART setting names. A set can come from this mission or from a pinned media pack. A later set wins key by key, so a pack that only redraws the people is a valid pack. The manifest format is documented in sbs_utils/procedural/tilemap_art.py.

Placing things on the ground

Props, people and hostiles live in .amd sections (## [Props](props), ## [People](people), ## [Hostiles](hostiles)) and name their spot in one of two ways:

### [Survey drone](drone)
---
Area: ridge
Mark: drone              # a mark in the area file
Sprite: prop:drone_wreck
---

### [Glassback](gb_1)
---
Area: caves
At: 18, 3                # or a cell, x then y
Patrol: 18 3; 25 4; 18 5
Sprite: fig:glassback
---

A mark name goes in Mark:, never in At:. At: reads coordinates only, so a word there reads as nothing and the prop is never placed.

A prop with nothing to it (no Scene:, Item:, Opens with:, Needs: and no description) is scenery: bunks, console banks, barrels, whatever furnishes a room. It is drawn and Blocks: like any prop, but the Look list and the list of things further off leave it out, it never gets a badge, and a click on it walks toward it instead of using it. Give a prop one line of description and it becomes something to look at.

### [Bunk](gnaw_bunk)
---
Area: gnaw
At: 8, 5
Sprite: prop:bunk
Blocks: yes
---

A ship's deck, drawn for you

Any hull with an interior plan (the one Engineering shows) can be boarded on a tile map nobody has to draw. boarding_deckplan lays the plan out as a deck:

  • each plan cell becomes 3 x 3 tiles;
  • each room gets the floor and furniture of its kind. A galley gets tables and stools, a cargo hold gets crates, and a system room gets one piece of kit per node: a reactor per warp node, a turret per beam node, a power cell per impulse node;
  • bulkheads separate the rooms, and every room gets a doorway, so the whole deck can be walked. A plan that comes in pieces, such as a starbase's modules, is joined by gangways;
  • every room's tiles carry a mark named after the room (room:impulse, room:crew-quarters, room:hallway). Boarding arrives at entry, which is the airlock if the ship has one, otherwise the middle of the hallway.
from sbs_utils.procedural.boarding_deckplan import (boarding_deck_plan,
                                                    boarding_deck_tileset,
                                                    boarding_deck_build)
boarding_deck_tileset()                       # the "deck" tileset: kinds named for looks
tilemap_art_use("station", tileset="deck")    # the Cosmos-Tiles station pack draws them
area = boarding_deck_build(boarding_deck_plan(ship), "boarded_deck", title="Enemy cruiser")

boarding_deck_plan(ship) reads a live ship's layout and hull map. For a .grid file, use boarding_deck_plan_ascii(text).

For a live ship, one call does all of it:

deck = boarding_deck_for(enemy_id, title="Kralien cruiser")   # built once, then reused
boarding_invite(player_ship, [], title="Boarding", area=deck)

The deck then follows its ship. boarding_deck_watch checks it every second, and boarding_deck_sync does one check on demand:

  • a damaged node's kit goes dark, with rubble beside it that can be walked over. A system also throws sparks, and any other room catches fire;
  • a repaired node comes back;
  • each damage-control team stands where Engineering has it, walks when it moves, lies down at 0 HP, and leaves when it is gone. Teams are drawn as the ship's own race (RACE_CREWS, read from the hull key: kralien_cruiser has Kralien crews, a TSN ship has humans).

Every doorway has a door, drawn front-on or side-on to match its wall. Doors never block: boarding_deck_animate slides them open for anyone beside them and flips the frames of the sparks and fire. boarding_deck_watch starts it; for a deck with no ship behind it, call boarding_deck_animate(area) yourself.

The furniture is scenery, and the same plan always gives the same deck. To furnish a kind of room your own way, use boarding_deck_kit("cargo", furniture=["prop:barrel"]). To start from the generated deck and edit it by hand, boarding_deck_text(layout, key) gives it as a .tiles file.

The deck's looks come from an art set. Without the station pack (or another set that draws the same looks) the deck still works, but nothing is drawn. A mission that boards ships therefore pins artemis-sbs.Cosmos-Tiles.station.<tag>.zip.

Checking your maps

sbs lint checks area files, tileset files, and every placement in the mission's .amd. The same findings appear as squiggles in VS Code while you type (Artemis AMD extension).

Code Level What it catches
tiles-unknown-char error A map character the legend does not have. The whole area will not load, and every such character is named with its column.
tiles-unknown-kind error A legend kind the tileset does not declare. It draws nothing and cannot be walked.
tiles-syntax / tileset-syntax error A file the parser cannot read, such as one with no --- or a misspelled rule.
tiles-unknown-tileset warning tileset: X with no X.tileset in the mission.
tiles-legend-duplicate warning The same character in the legend twice. The later line wins.
tiles-mark-unplaced warning A mark in the legend that is never drawn on the map.
tiles-entry error/warning entry: names nothing, or puts the party on ground it cannot move from.
tiles-exit warning An exit to an area that does not exist, an arrival mark that is not there, or an exit on ground nobody can walk onto.
tiles-unknown-area / tiles-unknown-mark warning A placement's Area: or Mark: names nothing. It is never placed.
tiles-at-not-a-cell warning A mark name written in At:.
tiles-off-map warning An At: or Patrol: cell outside the area.
tiles-unwalkable warning Someone who walks is placed on, or patrols through, ground that cannot be walked. A prop may stand anywhere.

Editing visually

The Tile Map Editor in VS Code paints .tiles files and shows them with the mission's own art, as the game draws them. It also shows the props, people and hostiles the .amd places on the area, and you can drag them (and patrol points) into place. A .tileset file opens in the Tileset Editor, a table of its kinds with each look's picture. See Tile Map Editor.