ZXGate Visual Novel Engine for ZX Spectrum
With "Steins;Gate Space Octet" port
I don't own the Steins;Gate Space Octet game script. It is copyright to the original authors and right holders. If you do not own a copy of this game, it is illegal for you to download and play this port. The port is provided only as an example of usage of the engine. It is fine for you to use the ZXGate engine itself, though, as it is completely original content.
- ZX Spectrum 128K
- TR-DOS 5.01 or later with Beta Disk Interface
- AY-3-8910 or YM2149F sound generator
- Color monitor
- 640K Floppy Drive
How to play
- Enter TR-DOS from menu or using the command
RANDOMIZE USR 15616in BASIC
- If game won't autostart, input
A>prompt and hit Enter
How to use ZXGate Engine
- Place any necessary binary files into
binfolder (e.g. the Vortex Tracker music player resides there as
- Place your graphics in
- Place your compiled Vortex Tracker music files into
- Write the script and place it into
- Write the system script and save it as
- Write the main script and save it as
STRT_SCPT db "SGBOOT C",#00to match your main menu script file name (the engine loads it after a Game Over).
make.shto build the game.
- The resulting disk images will be in
The system script is a script containing commands available inside the whole game. It is embedded into the engine during build and is not overwritten during the game process. It's a good idea to place i.e. Save and Load commands here.
The main script is a script embedded into the engine during build, that is executed when the engine is started. It's a good place for explanation on how to play your game, and a directive to load the first file of your novel. It will be replaced in memory once the next script is loaded and there is no way to go back to it afterwards.
Text script representation
The text scripts are compiled into binary during build time using
script2zx.py. If you need to eliminate the EOF mark at the end of script (e.g. compiling a System Script — having a EOF mark in it renders the game unplayable!), use the
--no-eof flag. Then specify all your text files as one command line, otherwise the flag names won't be matched correctly and the game won't work. Example:
./script2zx.py myscene1.txt myscene2a.txt myscene2b.txt myscene3.txt.
You can write any text inside a text script. That text will be displayed line-by-line to the player while they are playing your game. However there are some special cases.
If a line begins with
;, then it is considered a comment. It will not be included in the game, so this is handy to quickly explain about what is happening in that place of the script.
If a line begins with
@, it is considered a command. The following commands are supported: (all file name arguments must be supplied without extension)
@stop— stops the background music from playing
@redraw file=filename— draws a picture on the screen
@clear— clears the screen
@wait time=N— wait N seconds. If N > 1000, considered to be in milliseconds.
@play file=filename— play music loaded from the specified file
@goto file=filename— load the script from the specified file name and start executing it
@gameover— game over, return to the starting script
@ifflag name="my flag" value="2"— condition to check a flag's value. There can't be more than 245 different flags, and any flag's value cannot exceed 255. If you need to check multiple at once, combine them like that:
@ifflag name="first_flag" value="0" name="another_flag" value="2". Should the flags match the specified values, the script continues. Otherwise the script will continue from the matching
@iftemp name="my temp flag"— same as
@ifflag, but checks for a temp flag. Temp flags don't have a value, they can be only set or reset.
@ifsys name="my sys flag"— same as
@iftemp, but checks for a systemwide flag.
@flag name="my flag" value="10"— sets a value for a flag.
@tf name="my temp flag"— sets a temp flag.
@sf name="my sys flag"— sets a systemwide flag.
@load file=filename— loads game progress from a save file with the specified file name
@save file=filename— saves game progress into a save file with the specified file name
@input— gives the user a command line to input a command.
If the line starts with a
*, it is considered a user-command. This means, when the user is given input via
@input, if they enter that line, the script will continue executing right below it.
Consider the following script:
@play file=whatislove @input *help You lost it. @gameover *whatislove @ifflag name="whatislove" value="0" Baby don't hurt me. @flag name="whatislove" value="1" @else @ifflag name="whatislove" value="1" Don't hurt me. @flag name="whatislove" value="2" @else No more. @gameover @endif @endif @input
When executed, it will start playing music from file named "WHATISLOVE.C" on the floppy, and give user a command prompt.
If the user types "help", the line "You lost it." will be displayed and the game is considered over.
However, if the user types "whatislove", the text "Baby don't hurt me." is presented, and then, again, a command prompt.
If the user types "whatislove" again, the first
@ifflag won't match, so execution continues from the first
@else. The second
@ifflag will match, so the line "Don't hurt me" is presented.
If the user types "whatislove" once more, then neither of the
@ifflag's match, and the line "No more." is presented, after which, the game is over.
Since recently, you can also write scripts inline in the assembly code, using the definitions in
The above example would look like so in an inline ASM representation:
FLAG_WHATISLOVE equ #01 MY_SCRIPT: defb SCMD_BGM,"WHATISLOV",0 ; mind the TRDOS file name length! defb SCMD_INPUT defb SCMD_PTRLIST defb "help",0 defb SCMD_PTREND defb "You lost it.",0 defb SCMD_GAMEOVER defb SCMD_PTRLIST defb "whatislove",0 defb SCMD_PTREND defb SCMD_GETFLAG,FLAG_WHATISLOVE,#00,#00 defb "Baby don't hurt me.",0 defb SCMD_SETFLAG,FLAG_WHATISLOVE,#01 defb SCMD_ELSE defb SCMD_GETFLAG,FLAG_WHATISLOVE,#01,#00 defb "Don't hurt me.",0 defb SCMD_SETFLAG,FLAG_WHATISLOVE,#02 defb SCMD_ELSE defb "No more.",0 defb SCMD_GAMEOVER defb SCMD_ENDIF defb SCMD_ENDIF defb SCMD_INPUT
So, as you can see, it's a very monstrous construction, and is good mostly for writing small scripts only.
Binary script representation
For reference only! Always refer to
main.asm comments and
engine/commandset.asm code. Or better yet, write text-scripts and build them into compiled binary scripts using
script2zx.py, or write inline ASM-scripts :-)
; ------------ Conditionals ; D1 aa bb aa ... 00 Check GAMEFLAGS (aa is offset, bb is value, logic AND: read as "if aa is bb AND aa2 is bb2 AND ...") ; D0 aa bb Set GAMEFLAGS (aa is offset, bb is value) ; D2 aa Set TEMPFLAGS (logic OR, every bit is one flag) ; D3 aa Check TEMPFLAGS (logic AND) ; D4 aa Set SYSFLAG (logic OR, every bit is one flag) ; D5 aa Check SYSFLAG ; F2 Else (if Check not matched, script continues from there) ; F3 End Check ; ------------ Multi-media ; E0 xx xx xx ... 00 Load Script File ; E1 xx xx xx ... 00 Load Music File ; E2 xx xx xx ... 00 Load Image File ; E3 xx xx xx ... 00 Reserved ; F4 Stop Music ; ------------ Gameplay ; F0 xx xx xx ... 00 Save to file ; F1 xx xx xx ... 00 Load from file ; E4 Game Over ; F5 xx xx xx ... 00 Reserved ; E5 xx Wait xx seconds ; F6 Clear screen ; F7 EOF (used for pointer parsing) ; FF User Input Pointer ; FD xx xx xx ... 00 ; xx xx xx ... 00 ; xx xx xx ... 00 FE Pointer List ; xx xx xx xx ... 00 Print string
Here is how the above example would look like when compiled into binary (markup added for easier reading):
E1 57 48 41 54 49 53 4C 4F 43 00 FF FD 68 65 6C 70 00 FE 59 6F 75 20 6C 6F 73 74 20 69 74 2E 00 E4 FD 77 68 61 74 69 73 6C 6F 76 65 00 FE D1 01 00 00 42 61 62 79 20 64 6F 6E 27 74 20 68 75 72 74 20 6D 65 2E 00 D0 01 01 F2 D1 01 01 00 44 6F 6E 27 74 20 68 75 72 74 20 6D 65 2E 00 D0 01 02 F2 4E 6F 20 6D 6F 72 65 2E 00 E4 F3 F3 FF F7
State of the game
- Bad endings work
- Good ending works
- Save/load works
- Easter eggs work
look badgedoesn't work
State of the engine
Builds and runs as long as the build environment matches:
- FISH shell
- zx7 compressor
- SjASMPlus v1.07 RC8
- Python 2.7
- Linux or OSX
The engine uses standard TR-DOS procedures to load and write files, and the lower screen ROM procedure to output the text. So in theory it's possible to get
4 Out of Screen when displaying a long text or typing a long command, but it isn't the engine's problem :-)
The music player and music files are loaded in the Page 0 starting #C000. The music is played using an interrupt service routine (to make code much easier).
There are custom characters defined for text output while the engine is running:
- α — binary #90, text #83#BF
- β — binary #91, text #83#C0
- Enter key symbol — binary #92
- Floppy disk — binary #93
- ♫ — binary #94, text #81#F4
- Book — binary #95
- Picture frame — binary #96
- Joystick — binary #97
- Clock — binary #98
- ★ — binary #99, text #81#99
- Flag — binary #9A
Todos of the game
- Fix "look badge" (implement in
- Support wildcard command matching (using the EOF mark?)
English translation: Fuwanovel ZX Spectrum port, music re-arrangement in Vortex Tracker: Akasaka
Greetz and Thanks
- Nakata Keigo