Splitting a game across files
include "items.zaytun"
include pulls another .zaytun file into the game at the point the line appears. Its declarations land where the include stood, and — unless they are marked local, below — they are visible to the whole game. It's a top-level declaration — it can't go inside a room or item block — and the included file holds ordinary declarations, so there's no header/body split to maintain. A game of any size can be laid out however suits it:
include "rooms/cellar.zaytun"
include "rooms/catacombs.zaytun"
include "items.zaytun"
title: "The Cellar Key"
start: entrance
Two rules worth knowing:
- Paths resolve relative to the file doing the including, the way C resolves
#include "...". A file in rooms/ that includes "shared.zaytun" gets rooms/shared.zaytun, so a subdirectory stays self-contained no matter who pulls it in.
- A file is included at most once. Including it again is quietly skipped, which makes a diamond (two files that both include a common one) work without tripping the duplicate-declaration check.
A cycle is an error. A file that includes itself, or a loop of files that include each other, is always a mistake — there is no game that wants one — so the compiler names the loop instead of silently doing nothing:
zaytunc: ragnarok.zaytun:12: "ragnarok.zaytun" includes itself zaytunc: b.zaytun:1: circular include: a.zaytun -> b.zaytun -> a.zaytunThis is a different question from the one above, and both are answered: have I read this file already? makes a diamond work; am I inside this file right now? makes a cycle an error. Paths are compared after collapsing
.and.., soparts/../parts/x.zaytunandparts/x.zaytuncount as the same file. On Linux, macOS and the BSDs symlinks are resolved too, so a symlinked second name for a file is also recognised as that file; the Windows build normalises the path but does not follow links, and would read such a file twice.
Every name is checked before a byte is emitted — rooms, items, npcs, scenes, enemies, procs, and flags. A misspelled flag is a compile error rather than a silent false:
$ ./zaytunc/zaytunc mygame.zaytun
zaytunc: mygame.zaytun:31: refers to undeclared flag "lantern_lt"
That matters most in a large game, where a typo in one branch means that branch quietly never runs and nothing tells you until a player finds it.
Errors name the file they're in, so a mistake in an included file points at that file:
$ ./zaytunc/zaytunc dawnstone/dawnstone.zaytun
zaytunc: dawnstone/items.zaytun:14: expected "}" but found "on"
Note the spelling: it's include, not #include. In Zaytun a # starts a line comment, so a leading # would make the whole line vanish before the compiler ever saw it.
Keeping a name inside its file
Every name is normally visible to the whole game, which is what you want almost all of the time — a flag set in one room and read in another is the whole point of a flag.
What it costs is short names. An area wants to call its rooms gate, hall, steps, and so does the next area, so you end up prefixing by hand: angband_gate and doriath_gate, archive_step and church_step. Put local in front of a declaration and the name belongs to its file:
# angband.zaytun
local room gate "The Gate of Angband" { ... }
# doriath.zaytun
local room gate "The Gate of Menegroth" { ... }
Both compile, and they are two different rooms. Another file cannot name either of them — that is what local means — so anything two files need to share stays as it was, with no local on it.
local goes on room, item, npc, enemy, scene, proc, flag and stat. Not on a verb: a verb is a word the player types, and they aren't in any file when they type it.
A local item, person or creature still answers to its own name. The player can type TAKE LAMP for item lamp whether or not it is local — two files may each have a lamp and the one in the room is the one you get, exactly as when two items share an alias. Scope is about names in your source, not about what the player is allowed to say.
Two other things worth knowing:
- The name is tagged with the file's name, so two files called
parts.zaytunin different directories cannot both have locals. The compiler says so rather than quietly merging them. That tag is also what a save file keeps, which is worth reading about before renaming a file in a game people have already saved — see A flag that belongs to one file. - Scope is entirely the compiler's business.
localadded no bytecode and did not raise the format number, and neither player has a line of code about it — the players that run the games above were written beforelocalexisted. (A game still needs a player new enough for the other features it uses: the compiler always stamps the current format, whatever is in the game.)