The advcomp compiler can parse AdvGame JSON source files and can convert those into MEG-4 Adventure Games. These are classic textual games (also known as Interactive Fiction) where the player enters sentences in order to progress in the game. Each sentence is then parsed into a verb and noun(s), and looked up. If a script is found for that verb and noun(s) combination, then it is executed.
NOTE: The advcomp
tool can also generate point'n'click adventure games from the very same JSON files. Although MEG-4 can import
these too, but converts them into textual games, the point'n'click gameplay mode is only available with the advgame
interpreter.
Limitations:
The Adventure Game state is an array of 256 bytes. The first byte stores the current room number, the last 32 bytes are reserved for the inventory, all the others are up to you. You can use these as flags, you can store counters in them, whatever. [Scripts] operate on this 256 bytes of memory, and this state can be saved and loaded in a game save file.
The source file must have an "AdvGame"
JSON structure at the top which is also a magic. Its fields are:
sprites
: string, path to a 256 x 256 pixels sprite sheet PNG, the first 32 rows must be emptymusic
: array of strings, path to Amiga MOD (.mod) or MIDI (.mid) files (up to 8 files)sounds
: array of strings, path to RIFF WAVE (.wav) files (up to 31 elements)setup
: array of numbers, initial game state values (up to 256 elements, each between 0 and 255, first number is starting room)status
: array of numbers, game states to be shown on status (up to 2, see custom
for more)vars
: array of strings, aliases to game state indeces, aka "variables" (only used in the JSON)rooms
: array of strings, aliases to room numbers (only used in the JSON)verbs
: array of strings, aliases to verb1
, verb2
, verb3
etc. (only used in the JSON)nouns
: array of strings, aliases to nouns (only used in the JSON)colors
: array of 5 numbers, intro and room background and text, and prompt colorpos
: number, text position from top in pixelscustom
: array of strings, additional C code to display user interface (first intro code, second room code)logic
: array of strings, common game logic script executed before each and every commandconfig0
: structure, main game configurationconfig1
: structure, alternative game configuration (to support multi-language)Normally sprites are assumed to be splitted: top 128 rows are swapped between rooms, bottom 128 rows are constant (for ui elements,
icons, etc. this latter is loaded from sprites
). If textpos is set, then both on the intro page and in rooms textpos rows (up
to 96 pixels) starting from the sprite sheet's row 32 will be shown on screen. With the custom user interface C code you can display
whatever and howmany sprites you'd like.
There's a main and an alternative configuration to support more languages. Their fields are:
lang
: string, two letter language codetext
: string, intro text (up to 429 bytes)answers
: array of 4 strings, messages (up to 63 bytes each): unknown verb, can't go there, can't use, game savedsave
: array of strings, verbs to save game (up to 15 bytes, 8 synonyms)load
: array of strings, verbs to load gamelist
: array of strings, verbs to list inventorynorth
: array of strings, verbs to navigate to Northwest
: array of strings, verbs to navigate to Westeast
: array of strings, verbs to navigate to Eastsouth
: array of strings, verbs to navigate to Southup
: array of strings, verbs to go upstrairsdown
: array of strings, verbs to go downstairsverb1
: array of strings, verbs for action 1 (key can be one of the "verbs" aliases)verb2
: array of strings, verbs for action 2verb3
: array of strings, verbs for action 3verb32
: array of strings, verbs for action 32nouns
: array of strings, nouns for objects (up to 255 strings)During parsing, words shorter than three UNICODE characters must match exactly, otherwise it is enough if the words starts with that.
This is needed for inflecting languages, not for English (an example could be maybe nouns
being [ "brit" ]
that would match both
if player typed either briton
or british
).
Room number goes from 1 to 254 (room 0 is the intro or reset or no direction for navigation verbs, 255 is the saved game slot). Each room structure's fields are:
image
: string, path to a PNG image, up to 256 x 96 pixelstext0
: array of strings, texts for config0
(up to 32 strings, each 255 bytes)text1
: array of strings, texts for config1
north
: number / string / array of strings, go to North scriptwest
: number / string / array of strings, go to West scripteast
: number / string / array of strings, go to East scriptsouth
: number / string / array of strings, go to South scriptup
: number / string / array of strings, go upstairs scriptdown
: number / string / array of strings, go downstairs scriptlogic
: number / string / array of strings, script to run when player enters this specific roomverbX
: number / string / array of strings, scripts with a single verb (X goes 1 to 32 or a "verbs" alias)verbX Y
: number / string / array of strings, scripts with a verb and a single noun (Y goes 1 to 255, or a "nouns" alias)verbX Y Z
: number / string / array of strings, scripts with a verb and two nouns (Y, Z goes 1 to 255, or a "nouns" alias)Texts can be 255 bytes long, and each room might have 32 texts, can be displayed with the say
/ sayv
/ sayc
instructions.
The scripts in room 1 are special in a way that they can be accessed from every other room too. Therefore it is recommended to put
a "Game over" room here, which has only a jmp 0
reset in its logic, so that there's no further command parsing in this room.
Every JSON value after a navigation or a verb command can be a non-zero number, which is a room number:
"north": 12,
Or it can be a string, which is a "rooms" alias:
"north": "attic",
But if it is an array of strings, then it is a script with instructions:
"north": [ "jmp 12" ],
These alter the game's state, display messages, etc., Instruction arguments are numbers, but in the appropriate places "rooms", "vars", etc. aliases can be used.
Available instructions:
end
: end scriptand
: it's suffix is added to next instruction's condition with a logical ANDor
: it's suffix is added to next instruction's condition with a logical ORbgm X
: play background music (X goes 0 to 7)sfx X
: play sound effect (X goes 0 to 63)jmp X
: goes to room X (X goes from 0 to 254)jmpv (X)
: like jmp
, but reads the room number from variable Xcall X
: call script X in room 1 (X goes 1 to 64)callv (X)
: like call
, but reads the script number from variable Xgive X
: add noun X to inventory (X goes 1 to 255)take X
: remove noun X from inventorysay X
: says text X (X is from 1 to 32)sayv (X) Y
: say text value of variable X + Ysayc X Y
: either say text X (false) or Y (true) depending on conditionadd (X) Y
: increase game state variable X by Y (X and Y goes from 1 to 255)addv (X) (Y)
: increase game state variable X by value of variable Ysub (X) Y
: decrease game state variable X by Y (X and Y goes from 1 to 255)subv (X) (Y)
: decrease game state variable X by value of variable Yset (X) Y
: set variable X to Y (Y can be 0 to 255)rnd (X) Y
: like set
, but puts a random number between 0 and Y into Xmov (X) (Y)
: move value of variable Y into variable XAll of these instructions can be expanded with exactly one of these suffixes:
if (V) = N
: only do if variable V equals Nif (V) != N
: only do if variable V isn't Nif (V) <= N
: only do if variable V less than or equal to Nif (V) < N
: only do if variable V less than Nif (V) > N
: only do if variable V greater than Nhas N
: only do if noun N is in the inventorynot N
: only do if noun N is not in the inventoryA special case when (V)
is 250 or above, these use the navigation drirections: 250 (or north
), 251 (or west
), 252 (or east
),
253 (or south
), 254 (or up
), 255 (or down
). For example to check if the room is passable to the North, that's if north != 0
.
These state indeces are storing the inventory's bitmask, which must be accessed using the has
and not
conditions, so there's no
conflict.
Jumping to room 0 will wait for a keypress, then resets the game and instead of 0, jumps to the given starting room (this needs
an explicit jmp
command as number 0 cannot be used as destination room number).
Normally to connect rooms one would specify a room number (or "rooms" alias), but this allows passing from one room to another unconditionally. For example if you store a "has the key" flag in game state 7, and in room 11 you have a door and you only want to allow to go North to room 12 if the player has that key, then you'll need a scripted navigation.
"11": {
"text0": [ "The door is closed." ],
"north": [ "jmp 12 if (7) = 1", "say 1" ]
}
Instead of a number now we have an array of strings, with a jmp 12 if (7) = 1
instruction in the north
property. This would
only jump to room 12 if state 7 is 1, otherwise it remains in room 11. In this case we can also say "The door is closed" with a
say 1
instruction (if we jumped to room 12 then this won't be executed). You can use a "rooms" alias in place of a room number,
and a "vars" alias instead of a game state index (variable), for example jmp attic if key = 1
. You can also use more complex
conditions with the and
/ or
instructions, if you add them before the instruction they should affect. For example to only
allow passing when it's night and the player has a lamp too, then [ "and if night = 1", "and has lamp", "jmp attic if key = 1" ]
.
Another example, which randomly picks a room between 100 and 109:
[ "rnd (1) 10", /* random between 0 and 9 into game state 1 */
"add (1) 100", /* add 100 so state 1 is now between 100 and 109 */
"jmpv (1)" ] /* jump to the room stored in a game state */
Texts can be choosen depending on variables too, for example if you store the cause of death 0 to 2 in variable 9, then:
"text0": [
"You're dead, because:", /* 1st text */
"you drown", /* 2nd text */
"you starved", /* 3rd text */
"monster eat you" /* 4th text */
],
"logic": [ "say 1", "sayv (9) 2" ]
This will first display the 1st text with say
, then sayv
takes the value from game state 9, adds 2 to it and displays one
of the texts from 2nd to 4th.
The conditional text is also useful, which can display either a false or a true text depending on its suffix. For example:
"text0": [
"You don't have a key.", /* 1st text */
"The chest is now open." /* 2nd text */
],
"open chest": [ "sayc 1 2 has key", "set (10) 1 has key" ]
If the player does not have the key, then sayc
will display the first, otherwise the second text. Then we set a "chest open"
flag with set
, but only if the player has the key. (To specify the command like this, you'll need a "verbs" alias open
and a
"nouns" alias chest
as well. Again, aliases are only used in the JSON and they are independent to the translated words players
can type in the game.)
Example MEG-4 Adventure Games can be found in the meg4_advgame repository.