ZZT Notes
Here are some notes and code snippets for ZZT which may be useful to you, or future me, if I do anything further with the engine.
Objects and ZZT-OOP
Counters
Set the player's health to a specific value
ZZT infamously only provides addition and subtraction to a handful of predefined counters. The Health counter can be directly set to a specific value by calling the endgame command, and then immediately giving the desired amount:
@setHealth #end :touch #endgame #give health 100 #end
Endgame normally sets the player's health to zero and triggers a Game Over state, but if you immediately give the player health afterwards, ZZT will not catch that the game should have ended, and will continue on as if nothing happened.
Note that this may not be reliable if you are running several commands prior to this in succession, because ZZT enforces a sort of maximum number of ZZT-OOP operations per tick on every object. If in doubt, add an /i just before the #endgame.
Uses of the Time counter
The Time counter is normally used on boards where a time limit has been defined. It's always available to mess around with in ZZT-OOP, even when a time limit is not specified on a given board, and it is always set to zero upon entering a board or upon taking built-in damage. Its value will be preserved when restoring from a save file.
The counter can be monitored with an object like this:
@timeCheck #cycle 1 :a #take time 1 timereset #give time 1 /i#a :timereset #give time 1 timereset label tripped. /i #all:timereset #a
This will trigger whenever the player enters the board, similar to SuperZZT's built-in :enter label, or whenever they take damage from built-in entities, such as colliding with a bullet or creature. Caveat: ZZT-OOP #give / #take Health commands do not have this side effect.
Counter overflow
The six counters in ZZT are 16-bit signed integers. The #give and #take commands guard against going under zero, or above 32767. However, built-in items (that is, the in-world collectibles) do not. The behavior of each counter varies when overflowed, but the most dangerous overflow situation to keep in mind is that Health, when negative, becomes zero and causes the game to end.
Display an external text file
An undocumented menu option (!-FILENAME;) will display the contents of a specified text file. These files may include additional menu links, but the text will not be executed as ZZT-OOP.
@readDoc #end :touch What do you want to read? !-HELLO.TXT;Read HELLO.TXT! !x;Never mind. :x Okay. #end
If the file doesn't exist, nothing happens. Paths may be specified as well. For example, you could display Z:\AUTOEXEC.BAT when running within DOSBox.
There is a syntax for jumping to anchor links and calling other help topics. For an example, check the *.HLP files that come with ZZT 2.0.
This may be useful for conserving limited memory in a large world file, or to allow blocks of text to be viewed on multiple boards without duplicating the text everywhere.
Be warned, though, that depending on the environment being used, not everyone seems to be able to use this feature.
Send objects to labels using an external file
On March 22nd 2019, Discord of ZZT user Mahou Shoujo demonstrated that it's possible to send messages from external files using this syntax:
$External file !a:touch;#SEND a:touch !A:touch;#SEND A:touch !test;#SEND test
The top option will do nothing. The middle option with the upper-case "A" will send "touch" to objects named "@a". The bottom option will send the calling object to the label ":test" if it is present.
Per Mahou Shoujo, this only works with external files called by objects, and not from built-in help files (such as the one invoked with the H key).
Using objects with tweaked X and Y steps to access additional coordinates
ZZT stats have a couple of internal variables, named X Step and Y Step by the community, that are used to determine the facing or walking direction of an entity. For example, a Transporter pointing to the east would have an X Step of 1 and a Y Step of 0. These numbers are not exposed in the built-in editor, but you can set them with an external editor like KevEdit, anywhere in the range of -128 to 127.
Objects use these variables as part of their #walk / :thud movement system, so when you issue a command like #walk n, it's setting X Step to 0 and Y Step to -1. If X Step or Y Step are set beyond -1 to 1, then the object will skip over entire terrain cells while moving. Be warned: this can allow objects to go out of the boundaries of the screen, which typically results in a runtime error.
These out-of-spec numbers will be preserved when used in reference to flow. For example, when using #walk cw flow on an object moving with an X Step of 1 and a Y Step of 1:
'X=1 Y=1 /i #walk cw flow 'X=-1 Y=1 /i #walk cw flow 'X=-1 Y=-1 /i #walk cw flow 'X=1 Y=-1
If an object with tweaked step values is placed against an obstacle so that it doesn't move, then you can use opp flow, cw flow, and ccw flow to access up to three additional coordinates with the "#if blocked [dir]", "#shoot [dir]" or "#put [dir]" commands:
X Step = 1 Y Step = 1 ..... .1N3. .W@E. .2SX. ..... @ - Our object X - Something solid to prevent the object from moving away N - #if blocked n somelabel S - #if blocked s somelabel E - #if blocked e somelabel W - #if blocked w somelabel 1 - #if blocked opp flow somelabel 2 - #if blocked cw flow somelabel 3 - #if blocked ccw flow somelabel
Here's a less compact example:
X Step = 0 Y Step = 2 ....... ...1... ...N... .2W@E3. ...S... ...X... ....... @ - Our object X - Something solid to prevent the object from moving away N - #if blocked n somelabel S - #if blocked s somelabel E - #if blocked e somelabel W - #if blocked w somelabel 1 - #if blocked opp flow somelabel 2 - #if blocked cw flow somelabel 3 - #if blocked ccw flow somelabel
Here is an example using #put opp flow to conjure a built-in enemy seemingly out of nowhere when the player passes the object:
X Step = -7 Y Step = 0 ...........#....... ........N..#....... .X.....W@E.#...1... ........S..#....... ...........#....... @ - Our object X - Something solid to prevent the object from moving away # - Some incidental walls 1 - "opp flow" -------------------- @someobj #cycle 1 :a /i#if not alligned a #put opp flow lion #end --------------------
Again, tweaked X and Y steps can crash ZZT, and not always consistently so. Take care to safely box in these objects with obstacles that will prevent them from flying off the board, and keep your eyes peeled for unintended side effects, like accidentally #putting objects out-of-bounds because the opp flow coordinate was occupied by a pushable entity.
One more caveat: if you prop an object with tweaked stats against an obstacle like the above examples, be aware that the built-in :thud label is going to trigger constantly. Even if no :thud label is present, ZZT is still going to hunt for one, from the top of your script down to the very bottom. Performance may degrade with many such objects, particularly when running ZZT in DOSBox at 3000 cycles. Here are some workarounds, each with their own potential issues: #lock the objects; place a dummy ":thud" label at the top of the script that just points to an #end command; tell the user to increase their DOSBox cycles.
Entities that are visible in the dark
Dark boards in ZZT mask the entire screen in a grey fog, showing only the player, passages and torches. The player can light torches to view a small radius of the surrounding area. Abuse of these entities can allow you to fill in negative space and create animations that otherwise wouldn't be visible.
Also known as the Kangaroo effect, the namesake of this wiki.
The following entities are visible on dark boards, and can be used to display a limited set of patterns or solid colors on an otherwise grey screen:
- Torches. The safest thing to use.
- Passages, both with stats and statless. This can cause undesired warping to other boards, particularly the title screen with statless passages.
- Player Clones, both with stats and statless.
- Kryptonite Text -- Entity ID 54, which shows up as blinking white text. This is an undefined entity and its presence on a board is capable of crashing ZZT!
Torches and passages cannot be pushed by anything, but player clones and Kryptonite text may be moved by non-player entities such as objects and boulders. Player clones carry some potential side effects, particularly with board edge linking if those clones are up against an edge. (TODO expand on this.) These entities in general can be set as the "under" terrain for entities with stats, so that when the upper object moves, the entity underneath shows through the fog mask.
Attribution
- Kryptonite Text, presented and coined by Ellypses, 27 Oct 2018, Discord of ZZT
Linking Boards Together
Board Edges
ZZT boards are 60x25, but the internal playfield incorporates a perimeter of "Edge" terrain which is used for determining when the player is attempting to travel to another screen. Edge cells were designed for internal engine use, but it is possible to place them within the visible playfield using STK, an external editor, or even in-game using a bug in ZZT-OOP:
@makeEdgeCell #end :touch #put opp seek
For some reason, putting no 'kind' identifier after the direction causes an edge cell to be made. (TODO attribution.)
If the player touches an edge cell within the visible playfield, it will attempt to transport the player to the linked board in the direction that it was touched. If there is no link, or if the edge on the other side is blocked, then the cell will behave like an obstacle.
Passages
Passages transport the player to a destination board which is chosen in the editor. When a player is warped by a passage, ZZT will search for a passage on the destination board with matching foreground and background colors. If a match is found, the player is positioned on top of that passage. If no match is found, then the player remains wherever they are currently located on the destination board. When looking for a matching passage, ZZT starts at the bottom-right, and goes bottom-to-top, right-to-left. Only the first match is used.
Passages made with the internal editor cannot link to the title screen. However, passages made with ZZT-OOP in-game can only link to the title.
Back-To-Back Passage Routing
While passage links normally can't be changed in-game, it is possible to use an intermediate linking board with an array of passages to route the player to different destinations. Additionally, on this linking board, one can use ZZT-OOP to modify which egress passage the player is routed through. Using the title screen as a linking board gives one the ability to place and destroy dynamic passages in-game with ZZT-OOP at will.
The unfinished engine Introspect has a working example of this.
Attribution:
- Pepper Bolette by Ellypses uses a static linking board to warp the player to an inventory screen, and back to most boards in the game.
Bugs
Objects and ZZT-OOP Bugs
#PUT is not able to place entities on the bottom row
Due to an off-by-one error, No object is able to #put terrain or creatures on the last row of a board.
An object binding itself causes a crash
Touching this object twice will result in a runtime error:
@obj #end :touch #bind obj
TODO I think there is an encyclopedia board that covers this in more detail.
Passage Bugs
Passages are important for structuring and organizing ZZT games. Even just one passage breaking could make a game unwinnable. Here are some known issues to keep in mind:
Stepping from one passage onto another deletes the first one
This is probably the most well-known issue. When there are two passages side-by-side and the player has entered from one, stepping on the other turns the first into an empty tile.
Mitigation: Ensure that passages that point to different boards are not adjacent. In practice, most passages like this point to the same place, and are just doubled-up for presentation reasons.
Passage moves when "Re-enter when zapped" enabled
If the player entered a board from a passage, and "Re-enter when zapped" is enabled for this board, and the player runs into a built-in entity that can cause harm, there is a small chance that the passage will be shifted one cell when the player reappears on it and moves. The shifted passage will send the player to the title screen1. This typically happens if the player was damaged while running, and then immediately runs off of the passage.
Mitigation: Don't use "Re-enter When Zapped" on boards where the player can enter from a passage and take built-in damage (bullets, lions, etc).
Attr: Discord of ZZT (I think)
1 The passage technically could lead to a different board, if a stat happens to exist in the new position, but when this bug is encountered in most games, it points to the title screen.
Color corruption when stepping off passage onto creature
If the player enters a board from a passage, then immediately steps on a built-in creature taking damage, the passage color can change to white-on-grey. The passage destination will not be changed, but since passages attempt to position the player on the destination board by passage color, this can be enough to break a game.
Mitigation: Unfortunately, there is no easy workaround or pattern for this. These traits will increase the risk of it happening:
- Built-ins (including bullets) are near the passage
- Board is entered from multiple passages
- Passages can only be stepped off of from one direction
One example of a board known to exhibit this issue to multiple players is this one from Best of ZZT.
Attr: Worlds of ZZT
Transporter Bugs
Moving Transporters
A bug can cause Transporters to shift forward if an object that is before it in the stat list dies. My understanding is that ZZT briefly misinterprets the Transporter's X-Step and Y-Step values as walking directions for an object.
You can see a live example of this in the Transporter board in Best of ZZT 1. Touching the æ symbol at 10,12 will cause the north-facing Transporter at 4,20 to shift up by one step. Touching other objects will eventually cause the transporter to move enough that the player can exit the puzzle container through the gap that the Transporter originally rested in.
As a quick workaround, you can try erasing and re-plotting the Transporter and/or objects to move it around in the stat list. If you have a delicate Transporter puzzle, consider not having objects which #die on the same board. The issue is also mitigated by always having Transporters blocked in their facing direction (which is to say, no one-way "gate" usage of Transporters).
Note: A couple of folks have informed me that other entities can also shift around in the same way, such as blinkwalls when the wall part is not active.
Debugging Tips
ZZT has no debugger, doesn't analyze scripts for errors before running them, and its ZZT-OOP syntax error messages don't identify which object specifically encountered a problem. It can be very difficult to track down an issue on a busy board. Here are some tips that may help in difficult situations.
Load Save files in KevEdit
KevEdit can read saved game files. Internally, .ZZT and .SAV files are the same file format. The file picker dialog doesn't see .SAV files, but you can rename .SAV to .ZZT to make them appear. You can also run KevEdit from the command line with the save file as the first argument, like "C:\>kevedit saved.sav". Alternately, for the SDL version, you can drag the SAV file over the KevEdit window to load it.
While viewing the SAV file, you can check the state of counters and flags (W), if an object has halted (Program Instruction is -1 upon processing #end), what the state of an object's zapped labels are, if it's #bind'd to another object, and you can also check the stat list (Ctrl+S) for any messed-up stats.
Slow down DOSBox to see the order in which objects are being processed
When an entity in ZZT moves or changes its character glyph, the graphics associated with that entity are updated immediately. There's no double-buffering, it just writes to the display buffer. You can use this to your advantage in DOSBox by dropping the cycles so low that you can observe what each object is doing on an individual basis, and in what order. You can use the #char command to set a unique glyph representing each label that an object can be sent to, so that you can follow the general #send path that the object is following in slow motion.
If you're test-running your ZZT game from KevEdit, DOSBox will likey be in "auto" mode where the cycle speed is shown as a percentage instead of a plain number. KevEdit sometimes has issues recovering when the speed is lowered to 1%. When I do this, as a workaround, I usually open a second DOSBox instance just for running my project from ZZT without KevEdit involved. As you save the project in KevEdit, you will need to refresh the disk cache on the other DOSBox instance by using the 'rescan' command.
If All Else Fails
ZZT has some subtle bugs that only manifest under very specific circumstances. If your board doesn't depend on objects being in a specific stat order, try deleting and re-plotting the object or entity that is having problems. This will place it at the end of the stat list, and maybe the bug will go away. You can also try cutting and pasting the entire board in KevEdit, which will order all stats from left to right, top to bottom.