1. Cookbook

Warning
Please note this section is NOT for children or beginners. Please make sure you are very familiar with the basic aspects of adventuron before reading onwards.

This section contains various recipes for common types of problems that are encountered.

1.1. Vehicles

The following example shows how to create a vehicle (a boat), which can be moved between two location (update the code to allow it to travel to many more locations, possibly using a string to track its location rather than a boolean).

The boat is both an object, and a location. The boat object and the boat location cannot have the same identifier.

If the player enters the boat from the same location as the boat can be in, then the player is sent to the "inside_boat" location. From there rowing will move the location of the boat between two locations (lakeside, and island_beach) - it will create the boat in one of two location, the opposite one of the current location.

This code sample also demostrates a "repair boat" puzzle, but without a pre-requisite. You can repair the boat just by typing "repair boat".

Exiting the boat will take the player to the location containing the boat.

######################################
#  Adventuron                        #
######################################

start_at                 = lakeside

######################################
#  Booleans                          #
######################################

booleans {
   is_boat_fixed       : boolean "false" ;
   is_boat_at_lakeside : boolean "true" ;
}

######################################
#  Locations                         #
######################################

locations {
   lakeside     : location  "You are at the lakeside" ;
   inside_boat  : location  "You are a boat." ;
   island_beach : location  "You are at a beach on an island" ;
}

on_describe {

   : if (is_at "inside_boat") {
      : if (is_boat_fixed == false) {
         : print "There is a hole in the boat." ;
      }
   }
}

######################################
#  On Command                        #
######################################

on_command {

   : match "enter boat;climb boat"  {
      : if (is_at "lakeside" || is_at "island_beach") {
         : goto "inside_boat" ;
         : print "You climb inside the boat" ;
         : press_any_key ;
         : redescribe;

      }
   }

   : match "leave boat;exit _"  {
      : if (is_at "inside_boat") {
         : goto "old_boat" ;
         : print "You climb out of the boat" ;
         : press_any_key ;
         : redescribe;

      }

   }

   : match "repair hole;repair boat"  {
      : if (is_at "inside_boat" && is_boat_fixed == false) {
         : print "You repair the hole in the boat" ;
         : set_true "is_boat_fixed" ;
         : press_any_key ;
         : redescribe;
      }
   }

   : match "row boat"  {
      : if (is_at "inside_boat") {
         : if (is_boat_fixed) {
            : if (is_boat_at_lakeside) {
               : print "You row the boat to the island." ;
               : set_false "is_boat_at_lakeside" ;
               : create "old_boat" target = "island_beach" ;

            }
            : else {
               : print "You row the boat to the lakeside." ;
               : set_true "is_boat_at_lakeside" ;
               : create "old_boat" target = "lakeside" ;
            }
         }
         : else {
            : print "You can't row the boat with the hole in it." ;
         }
      }
   }
}

objects {
   old_boat : object "an old boat" at = "lakeside" ;
}

themes {
   my_theme : theme {
      lister_exits {
         is_list_enter                        = false
         is_list_exit                         = false
      }
   }
}

1.2. Custom get / drop messages

To take or drop an object without the system message being printed (if you want to print a custom message for example), then you can use the get command, but the quiet version of the command, as shown below.

The is_carried check can be used to test that the get was successful. A failed get will always print the failure message (your hands are full, you can’t take that, etc, - messages can be configured in the theme).

start_at = village

locations {
   village  : location "You are in a village." ;
}

objects {
   lamp : object "a lamp" at = "village" ;
}

on_command {
   : match "get lamp"  {
      : if (is_beside "lamp") {
         : get "lamp" quiet = "true" ;
         : if (is_carried "lamp") {
            : print "You pick up the dusty lamp" ;
            : press_any_key ;
            : redescribe;
         }
      }
   }
}

A simpler method exists too. This message will only be displayed if the object is taken.

start_at = village

locations {
   village  : location "You are in a village." ;
}

objects {
   lamp : object "a lamp" at = "village" get_message = "You pick up the dusty lamp";
}

1.3. Block Access To A Location

A barrier can be used to block access to a location. A block can be created in the "barriers" section.

In this example, a lamp is required to enter forest_1.

There is an issue here that the player may drop the lamp in forest_1, then leave forest_1, then permanently block the path, but that can be taken place with in other parts of the code.

Blocks can also have different conditions (block_when_carried, block_when, block_when_not, block_when_exists, block_when_not_exists, block_when_worn, block_when_not_worn).

start_at = village

locations {
   village  : location "You are in the village" ;
   forest_1 : location "You are in a forest" ;
   forest_2 : location "You are in a spooky forest" ;
}
connections {
   from, direction, to = [
      village, east, forest_1,
      forest_1, east, forest_2,
   ]
}

barriers {
   block_forest_path : block {
      location               = forest_1
      block_when_not_carried = lamp
      message                = You need a light source to go into the dark forest.
   }
}
objects {
   lamp : object "a magical glowing lamp" at = "village" ;
}

themes {
   my_theme : theme {
      theme_settings {
         font = clairsys_10
      }
   }
}

1.4. Block Exits From A Location

A location agnostic block is a block that blocks all exits from the current location, no matter what the location is (you can narrow this down with the block condition itself).

By default it has a higher priority than other types of block.

NOTE

This type of block is not compatible with Adventuron 2 PAW / Adventuron 2 DAAD (currently).

start_at        = stream
redescribe = auto_beta
locations {
   stream   : location "You are by the side of a lovely stream.\nType <WEAR CLOAK<12>> to enable block, type <REMOVE CLOAK<12>> to disable block.";
   forest   : location "You are in a grand forest." ;
   old_hut  : location "You are in an old hut." ;
}
connections {
   from, direction, to = [
      stream, north, forest,
      forest, east, old_hut,
   ]
}
barriers {
   block_disorientate : block {
      block_when_worn = cloak
      message         = You are so confused, you can't seem to move anywhere.
   }
}
objects {
   cloak : object "the cloak of disorientation" wearable = "true" at = "inventory" ;
}

1.5. Goto the location of an object

The goto command will let you travel to a location or the location of an object. If the object is non existent, then the goto command does nothing when called.

start_at = village

locations {
   village : location "You are in the village. Type TELEPORT to go to the location of the lamp." ;
   forest  : location "You are in a forest. Type TELEPORT to go to the location of the lamp." ;
}

connections {
   from, direction, to = [
      village, east, forest,
   ]
}

on_command {
   : match "teleport _"  {
      : print "You goto the location of the lamp" ;
      : goto "lamp" ;
      : press_any_key ;
      : redescribe;
   }
}

objects {
   lamp : object "a lamp" at = "forest" ;
}

1.6. Dynamic Text

Printing the contents of a variable is possible using the expression form, which is a contained by parenthesis ( ).

start_at = village

locations {
   village  : location "You are in the village. Type SCORE to see your score." ;
}

integers {
   // Set the default score to zero here
   score : integer "0" ;
}

on_command {
   : match "score _"  {
      // Note that CONTROL + SPACE only works on blank lines in these blocks
      : print (
         "Your score is " +
         score +
         "."
      ) ;
   }
}

You can also use the {} form in regular text to import the value of a string variable.

start_at = village

strings {
   day_of_week : string "Wednesday" ;
}
locations {
   village  : location "You are in the village. It is {day_of_week}." ;
}
booleans {
   is_wednesday : boolean "false" ;
}

You can also use the {boolean ? if_true_string_var : if_false_string_var} form in regular text to import the value of a string variable.

start_at = village

strings {
   wednesday : string "Wednesday" ;
   not_wednesday : string "Not Wednesday" ;
}
locations {
   village  : location "You are in the village. It is {is_wednesday ? wednesday : not_wednesday}.\nType LOOK to refresh the dynamic location description." ;
}
booleans {
   is_wednesday : boolean "false" ;
}
on_tick {
   : if (is_wednesday) {
      : set_false "is_wednesday" ;
   }
   : else {
      : set_true "is_wednesday" ;
   }
}

Here is another variation, but without the requirement for a string variable to reference

start_at = village

locations {
   village  : location "You are in the village. It is {is_wednesday ? `wednesday` : `not wednesday`}.\nType LOOK to refresh the dynamic location description." ;
}
booleans {
   is_wednesday : boolean "false" ;
}
on_tick {
   : if (is_wednesday) {
      : set_false "is_wednesday" ;
   }
   : else {
      : set_true "is_wednesday" ;
   }
}

Here is yet another variation, but in this variation, we only print something if it is a Wednesday, and we use inline text.

start_at = village

locations {
   village  : location "You are in the village. {is_wednesday ? `It is wednesday.`}\nType LOOK to refresh the dynamic location description." ;
}
booleans {
   is_wednesday : boolean "true" ;
}
on_tick {
   : if (is_wednesday) {
      : set_false "is_wednesday" ;
   }
   : else {
      : set_true "is_wednesday" ;
   }
}

This more advanced variation, demonstrates embedded {} blocks.

  • The outer {} only includes the text between the backtick (`) characters if the boolean reference resolves to true.

  • the next {} only includes the {wed}., if it is wednesday (true in this example).

  • the most inner {} refers to the variable 'wed', which contains a value of "Wednesday".

  • Backticks can be escaped using `` in this syntax.

start_at = village

locations {
   village  : location "You are in the village. {is_wednesday ? `It is {is_wednesday ? `{wed}.`}.`}\nType LOOK to refresh the dynamic location description." ;
}


strings{
   wed : string "Wednesday";
}
booleans {
   is_wednesday : boolean "true" ;
}
on_tick {
   : if (is_wednesday) {
      : set_false "is_wednesday" ;
   }
   : else {
      : set_true "is_wednesday" ;
   }
}

This variations shows how the {} form can be used to check for the presence of an object:

start_at = street
template = talp

locations {
   street : location "You are in a busy kitchen. {mouse ? ` A <mouse<12>> is here.\nType <CHASE MOUSE<13>> to update the text.`}" {
      contains {
         mouse : scenery "a little mouse" listed = "false" ;
      }
   }
}

on_command {
   : match "chase mouse"  {
      : destroy "mouse" ;
   }
}

##

How to avoid scanning auto entity / direction scanning (if enabled)

street : location "You came from the <north<->>.";

1.7. Persistant Variables

You can specify the 'survivor' scope on a variable to inform Adventuron to let the variable value survive at the point of an end_game or restart occurring (the default value will be set on first game only and never be reapplied upon restart).

In this snippet, completely unrelated, but we also demonstrate the % operator, which is the modulus (or remainder) operator. game_count % 10 gives us the remainder of an integer division by 10. This number will always be in the ranger of zero to nine.

Note
Make sure you provide a full game_information {} section, with a UNIQUE UUID in order for this to work outside of the editor.
start_at                 = my_location

locations {
   my_location : location "You are in a room. This is your {gane_count_nice} game." ;
}

integers {
   // The variable will survive a game restart (or game over)
   game_count : integer "0" scope="survivor";
}

on_startup {
   : increment "game_count" ;
}

strings {
   // NOTE : This syntax is due to change (slightly) in later revisions of Adventuron.
   gane_count_nice : dynamic_string (
       game_count +
         (game_count % 10 == 1 ? "st" :
          game_count % 10 == 2 ? "nd" :
          game_count % 10 == 3 ? "rd" : "th")
   ) ;
}

game_information {
   game_name        = Survivor 1
   game_shortname   = S1
   written_by       = Adventuron Documentation
   year_of_original = 2020
   year_of_release  = 2020
   uuid             = GENERATE A UUID HERE BEFORE RUNNING THIS EXAMPLE - https://www.uuidgenerator.net/
   short_synopsis   = Solve the adventure
   game_version     = 1.0.0
}
start_at = my_location

locations {
   my_location : location "You are in a room." ;
}

integers {
   survivor : integer "100" scope="survivor";
}

on_describe {
   : print ("Survivor = " + survivor) ;
}

on_command {

   : match "test _"  {
      : increment "survivor" ;
      : print ("Survivor = " + survivor) ;
   }

}

game_information {
   game_name        = Survivor 2
   game_shortname   = S2
   written_by       = Adventuron Documentation
   year_of_original = 2020
   year_of_release  = 2020
   uuid             = GENERATE A UUID HERE BEFORE RUNNING THIS EXAMPLE - https://www.uuidgenerator.net/
   short_synopsis   = Solve the adventure
   game_version     = 1.0.0
}

1.8. Picking Commands One At A Time Without Repetition (aka Cycling)

Sometimes you may wish to add some ambient messages to your game, or perhaps some other logic that occurs in cycles.

The logic of a cycle command is that one command out of the command stored inside the cycle {} block will be executed. When executed, the command will not be executed again until all other commands have been exhausted via repeated runs of the cycle command.

At the start of each round, the iteration order is determined. By default, the order is randomised, but you can set the order using predictable_order = "true|false".

Each cycle command requires a unique key as this will be used to store uniquely serialize the state of the cycle command.

The key can be anything you like but it must be unique in your gamefile per : cycle {} block.

Warning
Please do not use else blocks inside this command as the behaviour if doing this is undefined right now. If statement are fine.

1.8.1. Cycling With Predictable Order

start_at = woods

locations {
   woods : location "You are in the woods.\nType <WAIT<12>> repeatedly to cycle through the print commands." {
      on_tick {
         : cycle key = "woods_events" predictable_order = "true" skip_interval = "0"  {
            : print "You step on some twigs." ;
            : print "You hear the hoot of an owl." ;
            : print "The wind blows through your hair." ;
            : print "In the distance you hear the church bell chime." ;
            : print "You are afraid." ;
         }
      }
   }
}

1.8.2. Cycling With Randomized Order (per round)

By setting predictable_order = "false", Adventuron will jumble the commands so that they happen in a random order each round. Each round the random order will be different, but by default the same command can never be executed twice in a row (if it was randomly at the start of a new round after being at the end of the last round then it will automatically be shuffled by adventuron). This behaviour is can be switched off using allow_back_to_back="true" (false by default).

start_at = woods

locations {
   woods : location "You are in the woods.\nType <WAIT<12>> repeatedly to cycle through the print commands." {
      on_tick {
         : cycle key = "woods_events" predictable_order = "false" skip_interval = "0"  {
            : print "You step on some twigs." ;
            : print "You hear the hoot of an owl." ;
            : print "The wind blows through your hair." ;
            : print "In the distance you hear the church bell chime." ;
            : print "You are afraid." ;
         }
      }
   }
}

1.8.3. Skipping

By using skip="1" (or greater), then the cycle command will only be ignored for 1 or more ticks (depending on the integer provided) after each execution.

start_at = woods

locations {
   woods : location "You are in the woods.\nType <WAIT<12>> repeatedly to cycle through the print commands." {
      on_tick {
         : cycle key = "woods_events" predictable_order = "false" skip_interval = "0" skip="1" {
            : print "You step on some twigs." ;
            : print "You hear the hoot of an owl." ;
            : print "The wind blows through your hair." ;
            : print "In the distance you hear the church bell chime." ;
            : print "You are afraid." ;
         }
      }
   }
}

1.8.4. Max Loops

By using max_loops = 1, then the cycle will stop executing sub-commands after one full set of commands have been executed.

start_at = woods

locations {
   woods : location "You are in the woods.\nType <WAIT<12>> repeatedly to cycle through the print commands." {
      on_tick {
         : cycle key = "woods_events" predictable_order = "false" skip_interval = "0" max_loops = "1" {
            : print "Message 1" ;
            : print "Message 2" ;
            : print "Message 3" ;
            : print "Message 4" ;
            : print "Message 5" ;
         }
      }
   }
}

1.8.5. on_exhausted

'on_exhausted' can be used in conjunction with a non zero value of 'max_loops' to execute a list of commands when all commands have been exhausted.

In the example below, each tick, one of the messages (message 1 - 5) will be executed for the first 5 ticks, then on tick 6 onwards, message 6a 6b and 6c will be printed (all commands in the on_exhausted block will be executed as opposed to commands in the main cycle body that are selected one at a time).

start_at = woods

locations {
   woods : location "You are in the woods.\nType <WAIT<12>> repeatedly to cycle through the print commands." {
      on_tick {
         : cycle key = "woods_events" predictable_order = "false" skip_interval = "0" max_loops = "1" {
            : print "Message 1" ;
            : print "Message 2" ;
            : print "Message 3" ;
            : print "Message 4" ;
            : print "Message 5" ;

            on_exhausted {
               : print "Message 6a";
               : print "Message 6b";
               : print "Message 6c";
            }
         }
      }
   }
}

1.9. Semi Random Messages

If you want to execute one command in a mutually exclusive set at random, use the execute_one_at_random command.

This command will select one command (at random) from its direct descendents to execute and only run that command.

Warning
Please do not use else blocks inside this command as the behaviour if doing this is undefined right now. If statement are fine.
start_at = village

locations {
   village  : location "You are in the village." ;
}

on_tick {
   : execute_one_at_random {
      : print "one" ;
      : print "two" ;
      : print "three" ;
      : print "four" ;
   }
}

1.10. Asking For A String Using A Negative Confirmation Question

start_at                 = my_location
locations {
   my_location : location "Hello {name}. You are in a room." ;
}
strings {
   name : string "";
}
on_startup {
   : ask_string
      question                  = "What is your name?"
      confirm_negative_question = "You entered your name as \"{name}\".\nWould you like to change the name?"
      var                       = "name"
   ;
}

1.11. Asking For Yes / No Answer

Adventuron allows for the setting of a boolean based on asking a yes / no question.

The : ask_bool command must be used in conjunction with a boolean.

In this snippet, some text in the game is conditionally displayed using a dynamic_string too, along with
a ? : operation

BOOLEAN ? value_if_true : value_if_false

NOTE : This feature does not work with Adventuron2PAW or Adventuron2DAAD

start_at                 = my_location

locations {
   my_location : location "You are in a bright kitchen.\n{toast_thought}" ;
}

strings {
   toast_thought : dynamic_string (
      is_liking_toast ?
      "I can't stop thinking about delicious toast." :
      "I can't stop thinking about how disgusting toast is."
   ) ;
}

booleans {
   is_liking_toast : boolean "false" ;
}

on_startup {
   : ask_bool {
      question   = Do you like toast?
      yes_answer = Yes, I like toast very much
      no_answer  = No, toast is not something that appeals to me at all
      var        = is_liking_toast
   }
}

1.12. Adding A Status Bar

A status bar is a screen element that sticks to the top of the screen and does not scroll out of view.

Status bars in Adventuron currently can only (currently) contain two slots, top left, and top right.

To create a status, define one in your theme. Status bars can be independently styled using 'header_bar_pen' and 'header_bar_paper' inside the colors section of the theme section.

Within the status_bar section, you can use the 'dynamic_text' item to reference a string (which will contain some piece of text that may change during the game).

You can also refer to the 'header_text' from the header_layout section, which will use the current location header text for the slot.

There is a third option, not show here, where you can specify static_text, which is text that is literal and will not change.

The existence of a status_bar section in a theme WITHOUT a specific layout will default to a layout with the status bar showing, but without the header. The absense of a status_bar section will default to a layout that prints the header as text, and may scroll away as the screen gets full.

1.12.1. Minimal Status Bar

For a minimal status bar (without styling), simply specify SB in your theme layout. SB must be the first item in the layout.

If SB is specified without any further configuration, then the location header will be listed in the status bar. Generally speaking, you do not want to do this unless you have configured header text for all locations in your game.

Type lots of inputs to see that the header text "Village" never flows off the screen.

start_at = village
locations {
   village  : location "You are in the village" header = "Village" ;
}
integers {
   score : integer "0" ;
}
objects {
   lamp : scenery "a magical glowing lamp" at = "village" ;
}
themes {
   my_theme : theme {
      theme_settings {
         layout = SB G D X O
      }

      screen {
         padding_horz             = 4
         status_bar_padding_horz  = 4
      }

      colors {
         status_bar_pen   = 15
         status_bar_paper = 9
      }
   }
}

1.12.2. Implied Status Bar Without Specifying Layout

If a layout is not specified but the status_bar{} section is provided, then the default (implied) layout will start with SB (status bar).

This is a lazy way to configure the system, and generally speaking it’s better practise to explicitly create a layout string.

Note
If the status_bar{} section is not provided then an inline (non status bar) location header piece of text will be printed (as is usual behaviour).
start_at = village
locations {
   village  : location "You are in the village" header = "Village" ;
}
integers {
   score : integer "0" ;
}

strings {
   score_topright : dynamic_string ( score + "!" );
}

objects {
   lamp : scenery "a magical glowing lamp" at = "village" ;
}
themes {
   my_theme : theme {
      status_bar {
         : header_text;
         : dynamic_text "score_topright" ;
      }
      colors {
         status_bar_pen   = 15
         status_bar_paper = 9
      }
   }
}

In the sample below, type 'jump' at the prompt to increment the score (up to a max of 20). The header will update. The header never scrolls off screen.

start_at = village

locations {
   village  : location "You are in the village" header = "Village" ;
   forest_1 : location "You are in a forest" header = "Forest" ;
   forest_2 : location "You are in a spooky forest" header = "Spooky Forest" ;
}
connections {
   from, direction, to = [
      village, east, forest_1,
      village, west, forest_2,
   ]
}
strings {
   score_topright : dynamic_string ( score + " / 20" ) ;
}

######################################
#  On Command                        #
######################################

on_command {

   : match "jump _"  {
      : if (score < 20) {
         : increment "score" ;
         : print "You do something very impressive" ;
         : press_any_key ;
         : redescribe;
      }
   }
}

integers {
   score : integer "0" ;
}
objects {
   lamp : scenery "a magical glowing lamp" at = "village" ;
}
themes {
   my_theme : theme {
      theme_settings {
         // SB = status bar
         layout                = SB G D X O
         header_capitalization = original
      }
      screen {
         experimental_spacer_pre_text = 5
      }
      status_bar {
         : header_text;
         : dynamic_text "score_topright" ;
      }
      colors {
         // 15 == white (#fff)
         status_bar_pen   = 15
         // 9 = Blue (#00f)
         status_bar_paper = 9
      }
   }
}

Another example:

start_at = village

locations {
   village  : location "You are in the village" header = "Village" ;
   forest_1 : location "You are in a forest" header = "Forest" ;
   forest_2 : location "You are in a spooky forest" header = "Spooky Forest" ;
}
connections {
   from, direction, to = [
      village, east, forest_1,
      village, west, forest_2,
   ]
}
strings {

   topright : dynamic_string ( h() + " (" + turns() + " turns)" );
   topleft x` : dynamic_string (
      /* If turns less than 10 then use "Morning" OTHERWISE ....*/
      turns() < 10 ? "Morning" :
      /* If turns less than 20 then use "Lunchtime" OTHERWISE ....*/
      turns() < 20 ? "Lunchtime" :
      /* Use "Lunchtime" OTHERWISE ....*/
      "Evening"
   ) ;
}

######################################
#  On Command                        #
######################################

on_command {

   : match "jump _"  {
      : if (score < 20) {
         : increment "score" ;
         : print "You do something very impressive" ;
         : press_any_key ;
         : redescribe;
      }
   }
}

integers {
   score : integer "0" ;
}
objects {
   lamp : scenery "a magical glowing lamp" at = "village" ;
}
themes {
   my_theme : theme {
      theme_settings {
         // SB = status bar
         layout = SB G D X O
         header_capitalization = original
      }
      screen {
         experimental_spacer_pre_text = 5
      }
      status_bar {
         : dynamic_text "topleft" ;
         : dynamic_text "topright" ;
      }
      colors {
         // 15 == white (#fff)
         status_bar_pen   = 15
         // 9 = Blue (#00f)
         status_bar_paper = 9
      }
   }
}

Excluding the status bar on mobile.

1.13. Default layouts

Manual Redescribe Mode default layout (without status_bar defined):

H G- D X O

Manual Redescribe Mode default layout (WITH status_bar defined):

SB G- D X O

Auto Redescribe Mode default layout (with or without status_bar defined):

G D O X

1.14. Treasure Hunt Mode

Adventuron Supports a basic 'treasure hunt' mode.

To switch on treasure hunt mode, simply supply a treasure_room, and mark one or more object as a treasure (using treasure="true").

In the code show below, dropping the lamp in the treasure room will display a (system) message of "You have found all the treasures. You have won!".

The winning message can be customised using a theme, and the automatic win-game logic can be disabled in the game settings, in case, you want to script a better ending, or have a more complex set of conditions for completing the treasure hunt.

start_at      = treasure_room
treasure_room = treasure_room
locations {
   treasure_room : location "The treasure room";
   tomb          : location "A tomb" ;
}
connections {
   from, direction, to = [
      treasure_room, east, tomb,
   ]
}
objects {
   lamp : object "a lamp" at = "tomb" treasure = "true" ;
}

Treasure hunt mode with two treasures:

start_at      = treasure_room
treasure_room = treasure_room

locations {
   treasure_room : location "The treasure room";
   tomb          : location "A tomb" ;
   stream        : location "A stream" ;
}

connections {
   from, direction, to = [
      treasure_room, east, tomb,
      treasure_room, south, stream,
   ]
}

objects {
   lamp : object "<a lamp<14>>" at = "tomb"   treasure = "true" ;
   ring : object "<a ring<14>>" at = "stream" treasure = "true" ;
}

1.15. Advanced Theming

1.15.1. Changing The Font

Fonts are defined at theme level. The default font is Bamburgh, but you can change fonts here.

start_at = village

locations {
   village  : location "You are in the village" ;
   forest_1 : location "You are in a forest" ;
   forest_2 : location "You are in a spooky forest" ;
}
connections {
   from, direction, to = [
      village, east, forest_1,
      forest_1, east, forest_2,
   ]
}
objects {
   lamp : object "a magical glowing lamp" at = "village" ;
}

themes {
   my_theme : theme {
      theme_settings {
         font = clairsys_10
      }
   }
}

User imported fonts (use the MENU / IMPORT option in the Adventuron Classroom editor) can be referenced via pressing CONTROL (or ALT) + SPACE after "font = ", all user fonts are named userfont_xxxxxxxxxx (where xxxxxxxxx is the filename of the ttf font imported).

1.15.2. Setting border mode on.

If you set 'border_mode_vertical_percent = x' (where x is usually best with a value of 1), then this will reserve a MINIMUM of x percentage of the screen as a vertical border (non mobile mode only).

As the content panel of the display area generally only scales in integer amounts (pixel doubling, tripling, etc), then it will be likely that a lot more of the vertical section of the screen will be lost, in order to fit with this request without blurring the bitmap text.

If in doubt, use a value of 'border_mode_vertical_percent = 1'.

1.15.3. Setting border colour.

To set the border colour, just set border = x in the colors{} section inside a theme (where x = #000 - #fff, or a palette entry colour 0 - 15 - which corresponds to the zx spectrum palette by default).

Note
I’m using the spelling of "colour" in my description (because I’m British), but the name of the color fields in Adventuron is in American English (as is the coding convention). I’m sure Webster though he was making things easy.

1.15.4. Theme Inheritance

As border colour is tied up to the theme, then there would normally be a lot of copying and pasting of themes to be identical except for the border colour change.

This is simplified in Adventuron with theme inheritance. Simple write extends = theme_id (where theme is the name of another theme), and all the settings of that theme will be applied, before the settings of the current theme are applied.

You can extend to any depth you like, but do not specify circular inheritance (theme a extends theme b which extends theme a which extends theme b, etc).

1.15.5. Changing The Border Colour

To change the border colour, simply call the name of the theme that has the border colour (via : set_theme "theme_id";).

This feature, and theme inheritance was used in the final public-facing version of Excalibur.

Upon entering a location, in on_describe{} a theme would be set for the current location, which allowed the border colour to match the artwork and the tone of the current location.

start_at    = room
start_theme = base

locations {
   room : location "You are in a room.\n^n^   Type 'GREEN'  to set the border green.\n   Type 'YELLOW' to set the border yellow.^m^\n   (non-mobile clients only).\n" ;
}

on_command {
   : match "green _"  {
      : set_theme "green";
      : print "OK" ;
   }
   : match "yellow _"  {
      : set_theme "yellow";
      : print "OK" ;
   }
   : match "grey _"  {
      : set_theme "base";
      : print "OK" ;
   }
}

themes {
   base : theme {
      theme_settings {
         font    = clairsys_10
         columns = 50
      }
      screen {
         content_width                = 400
         border_mode_vertical_percent = 1
         paragraph_spacing_multiplier = 1
      }
      colors {
         pen   = 0
         paper = 7
      }
   }
   green : theme {
      extends = base
      colors {
         border  = 4
      }
   }
   yellow : theme {
      extends = base
      colors {
         border  = 6
      }
   }
}

1.15.6. Making sure that images are scaled correctly

Use the width = xxxx attribute inside the screen block inside a theme to make sure that the horizontal width of the play area is scaled by multiples of this figure.

Note
Mobile clients do not use this setting and always use 100% of horizontal width (high dpi means that artifacts are barely visible on mobile).
start_at = village

locations {
   village : location "You are in the village." ;
   forest  : location "You are in a forest." ;
}

connections {
   from, direction, to = [
      village, east, forest,
   ]
}

######################################
#  Themes                            #
######################################

themes {
   my_theme : theme {
      screen {
         // Setting the width to 256 will snap the screen scaling factor in integer increments of 256 pixels (meaning graphic scaling can be crisp for precise pixel art)
         content_width = 256
      }
   }
}

1.16. Music

Note
THIS IS VERY EXPERIMENTAL AND MAY NOT WORK WELL OR AT ALL

Playing music is experimentally implemented in Adventuron.

MP3s can be referenced via absolute url (starting with http:// or https:// or relative urls).

If using relative urls, then to test your game, you must compile the game and use it from your local file system, or compile it and host it on a website. The relative urls
will not work using the classroom website as the assets will not be hosted there.

If there is a desktop version of Adventuron Classroom produces, this will not be an issue.

start_at = river

locations {
   river : location "You are in the river.\n" ;
}

on_tick {
   : if (is_just_entered () ) {
      : if (is_at "river") {
         : play_music sound="song_river";
      }
      : else {
         : stop_music;
      }
   }
}

assets {
   sounds {
      // NOTE :: Make sure that only reference content you are
      //         licensed to use.

      // ALSO :: You can use relative paths here, but you can only
      //         test in compiled versions of your code (not the
      //         web editor)
      song_river : sound_sample "https://somedomainid.com/sounds/river.mp3" ;
   }
}

1.17. Darkness

Darkness is a special state where a location cannot be described.

It’s not generally a very friendly state to leave the player in as it stops object lists and exit lists from being displayed (by default) but some people might want to add this to their game.

Darkness requires:

  1. A boolean that describes if the current state is dark or not

  2. Setting up a reference to the darkness boolean in the settings{} block.

  3. (Optionally) Customizing the darkness message, and possibly the darkness text colour.

In this example we use a dynamic boolean to dynamically calculate if it is dark.

Before a location is described, the darkness expression is evaluated and if it is dark, then the darkness message is displayed in place of the regular location text.

start_at = village

game_settings {
   dark_expression -> ( is_at "dark_zone" && is_present "lamp" == false )
}

locations {
   village  : location "You are in the village" ;
   forest_1 : location "You are in a forest" ;
   forest_2 : location "You are in a spooky forest" ;
}

// Zones are ways to define a group of locations.
// In this example we just choose the locations
// we will mark as dark.

zones {
   dark_zone : zone {
      locations = [ forest_1, forest_2 ]
   }
}

connections {
   from, direction, to = [
      village, east, forest_1,
      forest_1, east, forest_2,
   ]
}
objects {
   lamp : object "a magical glowing lamp" at = "village" ;
}

// Setting the dark message is optional here
themes {
   my_theme : theme {
      system_messages {
         it_is_dark = Darkness surrounds you !
      }
   }
}

1.18. Advanced Darkness

This more advanced demonstration of darkness uses dynamic booleans to simplify in_game logic.

Dynamic booleans are used to simplify the logic in the on_command block.

Light lamp is matched before the darkness verb blocking.

start_at                 = start_room

locations {
   start_room : location "You are in a lovely treasure room." {

      on_command {
         : match "test _"  {
            : print "Should be blocked in the dark!" ;
            : done ;
         }
      }

   }
}

objects {
   // The crown is in the same location as the player at the start, but GET is blocked
   // so cannot be taken even if the player knows it's there.
   crown : object "a gold crown" at = "start_room" ;

   // The lamp description is dynamic, based on the state of the lamp
   lamp  : object "a lamp{is_lamp_lit ? ` (lit)`}" at = "inventory" ;

}

booleans {

   is_lamp_lit : boolean "false" ;

   is_dark     : dynamic_boolean  ( is_at "start_room" && is_lamp_lit == false );

   // Everything except [movement, quit, save, load,
   // inventory, turns, look] is banned in the dark.
   is_inaccessible_darkness_verb : dynamic_boolean (
      (
         /* is movement command matches any system compass direction verb + all aliases */
         is_movement_command () ||
         verb_is "quit"         ||
         verb_is "save"         ||
         verb_is "load"         ||
         verb_is "inventory"    ||
         verb_is "turns"        ||
         verb_is "look"
      ) == false
   ) ;
}



on_pre_command {

   // This comes before the darkness action block
   // so we can perform this action in the dark.

   : match "light lamp"  {
      : if (is_carried "lamp" && is_lamp_lit == false) {
         : set_true "is_lamp_lit" ;
         : print "You light the lamp" ;
         : press_any_key ;
         : redescribe;
      }
   }

   // Blocks everything except movement, quit, save, load, inventory, turns, look
   : if (is_dark && is_inaccessible_darkness_verb) {
      : print "You fumble in the dark." ;
      : done ;
   }
}


game_settings {
   // Tells Adventuron when to override the location description with
   // the darkness message (in the theme system messages), plus will remove direction list
   // and object list from the location layout (when is_dark resolves to true)
   dark_expression = is_dark
}

themes {
   my_theme : theme {
      colors {
         // Set the darkness pen colour to dark blue.
         dark_pen = 9
      }
      system_messages {
         it_is_dark = Darkness surrounds you.
      }
   }
}

Another Example

This example demonstrates how to override the status bar with a dark message, but only when it is dark.

start_at = barn
template = talp

game_settings { dark_expression = "is_dark" }

booleans {
   is_dark : dynamic_boolean ( is_at "dark_zone" && is_carried "lamp" == false ) ;
}

strings {
   header_text : dynamic_string ( is_dark ? "IN THE DARK" :  h() ) ;
}

locations {
   barn  : location "You are in a barn."  header = "BARN" ;
   field : location "You are in a field." header = "FIELD" ;
}

connections {
   from, direction, to = [
      barn, east, field,
   ]
}

objects {
   lamp : object "a lamp" at = "barn" ;
}

zones {
   dark_zone : zone {
      locations  = [ field ]
   }
}

themes {
   my_theme : theme {
      status_bar {
         : dynamic_text "header_text" ;
         : fixed_text "MY GAME NAME HERE" ;
      }
      colors {
         status_bar_paper = 1
         status_bar_pen   = 14
      }
   }
}

1.19. Showing a location when dark

Adventuron by default will disable graphics when in dark mode. In some games, you may want to show an image when it is dark.

To show a graphic when it is dark, you should use a dynamic layout (as this will not remove the G element when it is dark).

In addition the 'priority_location_graphic' field in game_settings {} will override the location graphic when retuning a valid graphic id (otherwise it will use the default location id for the current location).

start_at = my_location

locations {
   my_location : location "You are in a room. Type TOGGLE to toggle darkness." graphic = "white";
   my_location_2 : location "You are in another room. Type TOGGLE to toggle darkness.";

}

connections {
   from, direction, to = [
      my_location, east, my_location_2,
   ]
}


on_command {
   : match "toggle _"  {
      : toggle "is_dark" ;
      : redescribe;
   }
}

booleans {
   is_dark : boolean;
}

game_settings {
   dark_expression = is_dark
   priority_location_graphic -> (
      is_dark && is_at "my_location" ? "blue" : ""
   )
}

themes {
   my_theme : theme {
      theme_settings {
         layout_dynamic -> ("H G D O X LOCK")
      }
   }
}

assets {
   graphics {
      blue : base64_png "iVBORw0KGgoAAAANSUhEUgAAAFAAAAAYCAIAAADieO37AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAA9SURBVFhH7c8xAQAgDMTAL8ZYEV9RLFWR5pbMqfs6m5zpGg7TOUznMJ3DdA7TOUznMJ3DdA7TOUznMFvyAR7NAYMt5/6PAAAAAElFTkSuQmCC";
      white : base64_png "iVBORw0KGgoAAAANSUhEUgAAAFAAAAAYCAIAAADieO37AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAySURBVFhH7c8BAQAwDMOg+zfd+2DBAW/HFNYV1hXWFdYV1hXWFdYV1hXWFdYV1h0Lbx+/OmrLngOJxAAAAABJRU5ErkJggg==";
   }
}

1.20. Checking if the player or an object is in a zone

The following snippet demonstrates how to check if the player (or an item / object) is inside a zone.

start_at = lakeside

locations {
   lakeside       : location "You are by the side of a lake.";
   forest         : location "You are in a forest." ;
   treetop        : location "You are in a treetop." ;
}

connections {
   from, direction, to = [
      lakeside,       east,  forest,
      forest,         up,    treetop,
   ]
}

objects {
   lamp : object "a lamp" at = "forest"  ;
}

zones {
   forest_zone : zone { locations  = [ treetop, forest ] }
   treetop_zone : zone { locations  = [ treetop ] }
}

on_tick {

   : if ( is_at "forest_zone" ) {
      : append "The player is in the foresty zone. " ;
   }

   : if ( is_within { outer = "forest_zone" inner = "lamp" } ) {
      : append "The lamp is within the foresty zone. " ;
   }

   : if ( is_within {  outer = "treetop_zone" inner = "lamp" } ) {
      : append "The lamp is within the treetop zone. " ;
   }

}

Using advanced commands like is_within, can make your source code complex to read.

Using dynamic booleans can make the source of your game much easier to read.

This version is functionally identical to the snippet shown above.

start_at = lakeside

locations {
   lakeside       : location "You are by the side of a lake.";
   forest         : location "You are in a forest." ;
   treetop        : location "You are in a treetop." ;
}

zones {
   forest_zone  : zone { locations  = [ treetop, forest ] }
   treetop_zone : zone { locations  = [ treetop ] }
}

connections {
   from, direction, to = [
      lakeside,       east,  forest,
      forest,         up,    treetop,
   ]
}

objects {
   lamp : object "a lamp" at = "forest"  ;
}

booleans {
   is_player_in_forest_zone : dynamic_boolean ( is_at "forest_zone" ) ;
   is_lamp_in_forest_zone   : dynamic_boolean ( is_within { outer = "forest_zone"  inner = "lamp" } ) ;
   is_lamp_in_treetop_zone  : dynamic_boolean ( is_within { outer = "treetop_zone" inner = "lamp" } ) ;
}

on_tick {
   : if ( is_player_in_forest_zone ) { : append "The player is in the foresty zone. " ;   }
   : if ( is_lamp_in_forest_zone )   { : append "The lamp is within the foresty zone. " ; }
   : if ( is_lamp_in_treetop_zone )  { : append "The lamp is within the treetop zone. " ; }
}

1.21. Auto Redescribe Mode

Adventuron by default does not redescribe the current location text when a command is entered by the player. The screen refreshes when the player changes location, types LOOK, or when the game auther scripts a REDESCRIBE command.

Adventuron can be set to change this behaviour such that the screen will automatically refresh if the object list or exit list changes as a result of an action without having to issue a REDESCRIBE command.

The system handlers for get and drop are defaulted to being silent (as getting and dropping success will be conveyed by the updated object list). You can override this if you wish in the on_command block by matching "get _" and "drop _" then doing whatever you like.

Auto Redescribe Mode also changes the default layout to something more appropriate for object centric gameplay. The layout can be manually restored to whatever you like in the theme. Auto Redescribe Mode just affects the default.

If something is printed, then Adventuron will automatically insert a press_any_key command before refreshing the screen.

There are a number of gotchas to this approach, the first of which is that an auto-refresh is currently not a full refresh. It won’t call the on_describe() method.

There are a number of ways to mitigate this issue, and more will be forthcoming.

1.21.1. Basic Demonstration

##############################################
##
## Auto Redescribe Mode will automatically
## clear down the screen when the visible objects
## or visible exits change in the current location
##
## In auto_beta Redescribe Mode, auto redescribes do not
## generate a call to on_describe() when
## redescribing. on_describe() is only called when
## a : redescribe; command is issued or when the
## player changes location.
##
## It is anticipated that full 'auto' mode will be added
## with this behaviour corrected shortly.
##
##############################################

start_at   = village
redescribe = auto_beta

locations {
   village  : location "You are in the village.";
   forest   : location "You are in a forest"  ;
}

objects {
   lamp  : object "a lamp"  at = "village" ;
   pen   : object "a pen"   at = "village" ;
   sword : object "a sword" at = "village" ;
   rock  : object "a rock"  at = "village" ;
}

connections {
   from, direction, to = [
      forest, east, village,
   ]
}

barriers {
   block_forest : block {
      location               = forest
      message                = The path is blocked by rubble
      block_when_carried     = sword
   }
}

1.21.2. Advanced Demonstration

##############################################
##
## Auto Redescribe Mode will automatically
## clear down the screen when the visible objects
## or visible exits change in the current location
##
## In Auto Redescribe Mode, auto redescribes do not
## generate a call to on_describe() when
## redescribing. on_describe() is only called when
## a : redescribe; command is issued or when the
## player changes location.
##
##############################################

start_at   = village
redescribe = auto_beta

locations {
   village  : location "You are in the village.";
   forest   : location "You are in a forest"  ;
}

objects {
   lamp  : object "a lamp"  at = "village" ;
   pen   : object "a pen"   at = "village" ;
   sword : object "a sword" at = "village" ;
   rock  : object "a rock"  at = "village" ;
}

on_startup {

   : print "^nc^<-------------------------------------\nAuto Redescribe Mode\n-------------------------------------<15>>^m^" ;

   : print "<* Type UNLOCK to unlock the exit to the forest without printing anything (instant automatic refresh).<10>>" ;
   : print "<* Type UNLOCKP (after restarting) to unlock the exit to the forest whilst printing a message (a press any key will automatically be placed after the final print message).<11>>" ;
   : print "<* GET/DROP an object to auto refresh the screen.<12>>" ;
   : print "<* GET ALL or DROP ALL to observe one screen refresh only.<13>>" ;
   : print "<* When not unlocked the forest manually (seperate restart) getting the sword will unlock the forest, dropping the sword will lock the forest (exit list automatically updates).<14>>" ;
   : print "<* Auto redescribes do not trigger on_describe() as the describe happens after the on_tick() event therefore it would be dangerous to execute out of sequence or to run another describe without a tick.<15>>" ;

   : press_any_key ;

}

connections {
   from, direction, to = [
      forest, east, village,
   ]
}

barriers {
   block_forest : block {
      location               = forest
      message                = The path is blocked by rubble
      block_when_not         = is_forest_unblocked
   }
}

booleans {
   is_forest_unblocked : dynamic_boolean ( is_carried "sword" || is_unlocked ) ;
   is_unlocked         : boolean ;
}

on_command {
   : match "unlock _"  {
      // NOTICE :: We do not need to call redescribe here ...
      // If there is no printing, then a describe is automatically
      // called (but on_describe() will not be called in this instance)
      : set_true "is_unlocked" ;
   }

   : match "unlockp _"  {
      // NOTICE :: We do not need to call redescribe here ...
      : set_true "is_unlocked" ;
      : print "You unlock access to the forest" ;
   }
}

on_describe {
   // Playing some beeps to demonstrate when on_describe is called
   // If we printed on_describe here, then it would
   : print "on-describe()" ;
}

on_tick {
   : if (is_at "village" && is_first_entered()) {
      : print "NOTE : There is a tick counting bug on GET ALL and DROP ALL at the moment, this will be fixed shortly." ;
   }
   : beep millis = "80"  pitch = "0" ;
}

1.22. Linking External Assets

Importing graphics via the import menu is not scalable to a large number of larger graphics and so an alternative mechanism exists for using images (and other types of media) with Adventuron.

Adventuron cac referencing resources (such as graphics) from external files via either a relative path, or a web url.

e.g.

graphics {
  // The png file will be read from the same path as the game html file
   blah : png "mypng.png";

  // This file should be referencable from anywhere (but only if playing
  // online).
  // Absolute paths can be convenient in development, but for packaging,
  // relative paths are better.

   blah2 : jpeg "https://somedomain.com/somepath/myjpeg.jpg";
}
Warning
The web based editor currently can’t render resources supplied with a relative path (using an explicit url is fine) so when debugging it will look like there are no graphics. To see your graphics in your game, you will need to compile your html to the folder containing your assets.

1.22.1. Pre-caching linked images

Adventuron by default does not pre-cache images for your game. This may result in "pop-in" when Adventuron displays a graphic.

Adventuron supports asynchronous pre-caching of all assets, and also granular pre-caching of assets.

The 'precache_all' option will preload all images in the following order:

  1. Preload the image for location 1 (if it is a linked media image).

  2. Preload the images for locations neighbouring location 1, via the connection table (if they are linked media). Blocks are not taken into consideration.

  3. Preload the images for remaining locations (if they are linked media).

  4. Preload other images used by the game.

Note
Preloading sound effects is not yet supported.
game_settings {
   precache_strategy = precache_all
}

1.23. Changing Default Sound Effect (Jingles)

By default, Adventuron plays no sound effects, which is generally acceptable for modern "Interactive Fiction" style games.

Adventuron also supports contextual sound effects that can be enabled in a custom theme.

Note
Using the 'two' theme, or extending the 'two' theme will enable these sound effects by default.

1.23.1. Enabling the sound effects

The sound effects are off by default and must be enabled (per category) in the theme_settings{} section of a theme. By default, all sound effects are switched off, so it is the responsibility of the author to switch them on if required.

Even if the sound effects are enabled at the theme (or source code) level, then the player must still give consent for sound effects whent he game starts. Adventuron will automatically ask for permissions it requires (to play sound or music) as long as it hasn’t asked the same permission before.

Players can type SOUND OFF and SOUND ON during the game to change their preference during gameplay (NOTE : No UI exists for toggling sound in the current version of Adventuron but this is planned for a future release0>

start_theme = my_theme
themes {
   my_theme : theme {
      theme_settings {
         losegame_jingle = on
         success_jingle  = on
         wingame_jingle  = on
         failure_jingle  = on
      }
   }
}

1.23.2. Customizing the sound effects

Subroutine Name Details

success_jingle

Plays when the : success command is executed (user should map this).

failure_jingle

Plays when the : failure command is executed or when the player attempts to perform a bad command (such as moving in an invalid compass direction).

lose_game_jingle

Plays when the : lose_game (or : end_game) command is executed.

win_game_jingle

Plays when the : win_game command is executed.

1.23.3. Full source example (default jingles)

start_at    = my_location
start_theme = my_theme

themes {
   my_theme : theme {
      theme_settings {
         losegame_jingle = on
         success_jingle  = on
         wingame_jingle  = on
         failure_jingle  = on
      }
   }
}

locations {
   my_location : location "You are in a room with no exits.\n^n^Type WINGAME to play wingame jingle.\nType LOSEGAME to play losegame jingle.\nType FAILURE (or type bad direction) to play failure jingle.\nType SUCCESS to play success jingle.^m^" ;
}

on_command {

   : match "wingame _"  {
      : win_game ;
   }

   : match "losegame _"  {
      : lose_game;
   }

   : match "success _"  {
      : success ;
   }

   : match "failure _"  {
      : failure ;
   }

}

1.23.4. Full source example (customized jingles)

Here is an example where the author may wish to customize the jingles to play particular mp3 sounds (note that you can customize using beep commands too).

start_at    = my_location
start_theme = my_theme

themes {
   my_theme : theme {
      theme_settings {
         losegame_jingle = on
         success_jingle  = on
         wingame_jingle  = on
         failure_jingle  = on
      }
   }
}

locations {
   my_location : location "You are in a room with no exits.\n^n^Type WINGAME to play wingame jingle.\nType LOSEGAME to play losegame jingle.\nType FAILURE (or type bad direction) to play failure jingle.\nType SUCCESS to play success jingle.^m^" ;
}

on_command {

   : match "wingame _"  {
      : win_game ;
   }

   : match "losegame _"  {
      : lose_game;
   }

   : match "success _"  {
      : success ;
   }

   : match "failure _"  {
      : failure ;
   }

}

subroutines {

   success_jingle : subroutine {
      : play_sound "incidental_bell";
   }
   failure_jingle : subroutine {
      : play_sound "incidental_boop";
   }
   lose_game_jingle : subroutine {
      : play_sound "incidental_wawawaaa";
   }
   win_game_jingle : subroutine {
      : play_sound "incidental_tadaa";
   }

}

assets {
   sounds {
      // Note that sound sample assets refer to assets
      // that must be available at the parallel level to the
      // compiled html file (will not be playable in the web
      // version of the Adventuron Editor -- need to compile
      // to test.)
      incidental_bell     : sound_sample "bell.mp3" ;
      incidental_boop     : sound_sample "boop.mp3" ;
      incidental_wawawaaa : sound_sample "wawawaa.mp3" ;
      incidental_tadaa    : sound_sample "tadaa.mp3" ;
   }
}

1.24. Disabling Dynamic Connection Scanning

By default, Adventuron will scan the on_command {} table for additional dynamic directions which may be available to a game.

The following source code shows a game where the field connects (bidirectionally) eastwards from the field to the forest location. A : match record has also been added that intercepts the "west" verb, and will go to the lake if the player types "west" from the field.

Adventuron detects this, and will present "west" as a direction.

start_at                 = field

locations {
   field   : location "You are in a field." ;
   forest  : location "You are in a forest." ;
   lake    : location "You are next to a lake." ;
}

connections {
   from, direction, to = [
      field, east, forest,
   ]
}

on_command {
   : match "west _"  {
      : if (is_at "field") {
         : goto "lake" ;
         : redescribe;
      }
   }
}

If an author wishes to disable connections from being scanned in the on_command {} table then, use the 'exit_list_calculation = basic' option, as shown below:

start_at                 = field

game_settings {
   # basic = only use connections table and do not use on_command table.
   exit_list_calculation = basic
}

locations {
   field   : location "You are in a field." ;
   forest  : location "You are in a forest." ;
   lake    : location "You are next to a lake." ;
}

connections {
   from, direction, to = [
      field, east, forest,
   ]
}

on_command {
   : match "west _"  {
      : if (is_at "field") {
         : goto "lake" ;
         : redescribe;
      }
   }
}

1.25. Finding An Object

Note
This snippet only checks for direct ownership, and will not snap up to the location id.

Use the < parent_of "entity_id" > function to access the id of the direct parent (or holder) of an object.

start_at = village

locations {
   village  : location "You are in the village.";
}

objects {
   sword : object "a sword"  ;
   lamp  : object "a lamp"  at = "inventory" ;
   spoon : object "a spoon" at = "village"  ;
   fork  : object "a fork"  at = "bag";
   bag   : object "a bag"   at = "inventory" container_type="bag";
}

on_tick {
   : if (parent_of "lamp" == "inventory") {
      : print "The lamp is in your inventory." ;
   }
   : if (parent_of "spoon" == current_location()) {
      : print "The spoon is in the same location as you." ;
   }
   : print ( "Location of Lamp  : " + parent_of "lamp"  ) ;
   : print ( "Location of Sword : " + parent_of "sword" ) ;
   : print ( "Location of Spoon : " + parent_of "spoon" ) ;
   : print ( "Location of Bag   : " + parent_of "bag"   ) ;
   : print ( "Location of Fork  : " + parent_of "fork"  ) ;

}

1.26. Pocket When Inventory Space Is Short

The following example demonstrates that Adventuron will recover when a pocket command is executed when the player has no more inventory capacity. The gifted object is simply placed on the floor in the location where the player is next. This is very important as some in-game events cannot be repeated, and the object needs somewhere to be.

start_at = village

locations {
   village  : location "You are in the village.\nType <SWORD<12>> to attempt to place a sword in your pocket / inventory (inventory limit is 1 in this game) then it will move the player to the forest." ;

   forest : location "You are in a forest.\nType <SWORD<12>> to attempt to place a sword in your pocket / inventory (inventory limit is 1 in this game)." ;

}

objects {
   lamp  : object "a lamp"  at = "inventory" ;
   pen   : object "a pen"   at = "village" ;
   sword : object "a sword"  ;
   rock  : object "a rock"  at = "village" ;
}

integers {
   inventory_limit : integer "1" ;
}

connections {
   from, direction, to = [
      village, east, forest,
   ]
}

on_tick {
   : inventory;
}

on_command {
   : match "sword _"  {
      : print "Attempting to create and put a sword in your pocket, then moving to the forest." ;
      : pocket "sword" ;
      : goto "forest" ;
      : press_any_key ;
      : redescribe;
   }
}

game_settings {
   inventory_items_limit_var = inventory_limit
}

1.27. Eliminating Parser Delay

Adventuron comes pre-configured with a small amount of parser delay. To switch off this delay, use the following snippet.

start_at = village

locations {
   village  : location "You are in the village.";
}

themes {
   my_theme : theme {
      theme_settings {
         parser_delay  = 0
      }
   }
}

1.28. Implementing A Safe

Adventuron naturally tries to match inputs that the player enters against its vocabulary. There is one exception, and that is in the case of a verb and a noun entered.

In the case that one word is entered, or two words are entered (language independent), then Adventuron records the original verb and the original noun.

These will store the exact inputs the player entered. In English, the verb comes before the noun. In other languages, vice versa. The parser is aware of which logic to apply depending on the language it is dealing with.

So, we can access the original verb with original "verb" and the original noun with original "noun1". These are functions that return string values, empty if there was not a value provided.

When dealing with safe combinations, we want to check that the player either entered a number in the verb or noun position.

To do this we simply check that we are in the correct context (in the same location as a safe), then we check to see if either the verb or noun are numeric using the is_int() function (is integer?).

If the original verb contains an integer, then match it in the verb position using match. Match the correct combination first. After matching the good combination, we use a : done; command to stop handling items in the current event block (on_command{}).

We use a similar pattern for matching the combination in the noun position, except that we also check for "dial" as the verb.

start_at  = office
locations {
   office : location "You are in an office" ;
}
objects {
   safe : scenery "a safe" at = "office" msg="The safe has a keypad.\nType DIAL XXXX (where XXXX is a number to try to open the safe).";
}

strings {
   safe_combo : string;
}

on_startup {
   : set_string var = "safe_combo"  (
      random (9) + "" + random (9) + "" + random (9) + "" + random (9)
   ) ;
}

on_describe {
   // Usually you would not print the combo, it would be part of the combo.
   : print ("The safe combo this time is : " + safe_combo) ;
}

on_command {
   : if (is_present "safe") {
      : if (is_int (original "verb")) {
         : if (safe_combo == original "verb") {
            : print "You enter the correct safe combination" ;
            : done ; // Stops it from matching anything else
         }
         : print "Wrong Combo" ;
      }
      : match "dial _"  {
         : if (is_int(original "noun1")) {
            : if (safe_combo == original "noun1") {
               : print "You enter the correct safe combination" ;
               : done ; // Stops it from matching anything else
            }
            : print "Wrong Combo" ;
         }
         : else {
            : print "Please type DIAL XXXX (where XXXX is a number)." ;
         }
      }
   }
}

1.29. Treasure Hunt Customization

By specifying "treasure_room = xxxx" at the top of an Adventuron game file, the author signals to adventuron that they want to set up a 'typical' treasure hunt mode.

Setting up the treasure hunt mode will change the default layout (for any themes) but it will also set up a win_game condition (automatically).

The win game condition is essentailly prepended to the on_tick block and looks like this:

## Adventuron AUTOMATICALLY adds this : if block if the author activates treasure hunt mode :
on_tick {
   : if (treasure_deposited() == treasure_total()) {
      : print << your_theme / system_messages / all_treasures_found_win_game >> ;
      : win_game;
   }
}

If you want a bespoke end game routing (perhaps a clear screen is required, some music, perhaps a graphic or animation), then you have to use the following two blocks of code:

Snippet One - Will remove the default win game condition from the treasure hunt mode.

game_settings {
   treasure_hunt_mode = bespoke
}

Snippet Two - Place your own win-game condition in the on_tick block (tests to see if the treasure item count is the same as the treasure item in the treasure room count):

: if (treasure_deposited() == treasure_total()) {
   : print "THIS IS MY CUSTOM END GAME HANDLER";
   : win_game;
}

Full example of a bespoke treasure hunt mode …​.

Note
The 'two' theme is a theme that is built into Adventuron, and provides a minimal textual set of system response messages, as well as formats the screen similarly to the game 'two', released in 2019.
start_at      = tomb
start_theme   = two
treasure_room = treasure_room

locations {
   treasure_room : location "Treasure Room";
   tomb          : location "Tomb";
}

connections {
   from, direction, to = [
      treasure_room, east, tomb,
   ]
}

objects {
   lamp : object "a lamp" at = "tomb" treasure = "true";
}

game_settings {
   treasure_hunt_mode = bespoke
}

on_tick {
   : if (treasure_deposited() == treasure_total()) {
      : clear_screen;
      // NOTE : Win game jingle does not play when bespoke
      //        Treasure Hunt Mode Is Used (play your own jingle).
      : print "THIS IS MY CUSTOM END GAME HANDLER";
      : win_game;
   }
}

1.29.1. Coding a treasure hunt endgame

A treasure hunt endgame is some action that must be performed by the player after placing all the treasure in the treasure room.

Coding a treasure hunt endgame is very specific to the endgame you have in mind.

The first condition is that you want to have all your treasures in the treasure room. You can use the "treasure_deposited() == treasure_total()" as shown previously to check for this.

The first time that all the treasure is placed, best to signal some sort of reward (such as a : success; command plus a message that tells the player that the endgame has started).

This should take the form of a boolean variable that is set the first time that all the treasure is placed. You may want to play the success message multiple times when the treasure is placed, in which case the second boolean is not required.

Rather than say any more, study (and adapt) the following code.

start_at      = beach
start_theme   = two
treasure_room = styx
redescribe    = auto_beta

game_settings {
   treasure_hunt_mode = bespoke
}

locations {
   beach         : location "Beach";
   tomb          : location "Tomb";
   styx          : location "River Styx" ;
}

connections {
   from, direction, to = [
      beach, east, tomb,
      tomb, down, styx,
   ]
}

objects {
   coin_1   : object "a <red coin<10>>" at = "tomb" treasure = "true";
   coin_2   : object "a <blue coin<13>>" at = "beach" treasure = "true";
   ferryman : scenery "the ferryman" msg = "TWO COINS" at = "styx";
}

vocabulary {
   : verb / aliases = [examine, talk]
}

booleans {
   has_found_all_treasures_before : boolean;
   has_found_all_treasures : dynamic_boolean (
      treasure_deposited() == treasure_total()
   ) ;
}

on_command {
   : match "pay ferryman; pay man"  {
      : if (is_at "styx") {
         : if (has_found_all_treasures) {
            : print "YOU WIN THE GAME!" ;
            : win_game ;
         }
         : else {
            : print "NEED TWO COINS" ;
         }
      }
   }
}

on_describe {
   : if (is_at "styx") {
      : if (has_found_all_treasures) {
         : print "\"PAY FERRYMAN\"" ;
      }
      : else {
         : print "DROP TWO COINS HERE." ;
      }
   }
}

on_tick {
   : if (is_at "styx" && has_found_all_treasures) {
      // We use the second boolean to only  show this message once
      // We use manual redescribe to force on_describe{} message to show
      : if (has_found_all_treasures_before == false) {
         : success ;
         : print "ALL COINS FOUND." ;
         : set_true "has_found_all_treasures_before" ;
         : press_any_key ;
         : redescribe;
      }
   }
}

1.30. Checking Permissions

Adventuron requires active consent for sound, blips, and music. Graphic permission is implied, but can be revoked by the player.

You may work some logic into your game to play sounds, or music effects but one problem is that the beep command is synchronous (the game must wait for the beep to play).

If yoy are going to play a lot of beep sound effects, it’s better to manually check if sound permission is on. Players can grant sound permission by typing SOUND ON, and revoke it by typing SOUND OFF. If you have sound in your game, the game will ask you when the game first starts if you want sound on or off, but it won’t ask a second time. The player needs to actively type SOUND ON to enable sound.

Anyway, the way to test for various permissions is to use the sysvar_bool() function, demonstrated below:

: if (sysvar_bool "sysvar_sound_enabled") {
   // Sound is enabled
}
: else {
   // Sound is not enabled
   // Player can type SOUND ON to switch on sound, or SOUND OFF at runtime
}

Here are a table of permission categories (which can be used as parameters to the sysvar_bool "" function):

Permission Id Details

sysvar_features_sound

True if the current game has any kind of sound (blip, ambient sound, incidental sound, or music).

sysvar_features_music

True if the current game has music.

sysvar_features_blip

True if the current game has blips.

sysvar_has_asked_blip

True if the current game has already asked for blip permission.

sysvar_has_asked_music

True if the current game has already asked for music permission.

sysvar_has_asked_sound

True if the current game has already asked for sound permission.

sysvar_blip_enabled

True if blip is enabled, false otherwise.

sysvar_sound_enabled

True if sound is enabled, false otherwise.

sysvar_music_enabled

True if music is enabled, false otherwise. If sysvar_sound_enabled is false, then music will not play.

sysvar_sfx_enabled

True if sound effects are enabled, false otherwise. If sysvar_sound_enabled is false, then sound effects will not play.

sysvar_ambient_enabled

True if ambient sound is enabled, false otherwise. If sysvar_sound_enabled is false, then ambient sound will not play.

sysvar_graphics_enabled

True if graphics are enabled, false otherwise.

If you want to simplify the use of the sysvar_bool() funtion, consider setting up a dynamic_boolean:

Snippet shown below (not full adventuron source file):

booleans {
   is_sound_enabled : dynamic_boolean ( sysvar_bool "sysvar_sound_enabled" ) ;
}

on_command {
      : match "play piano" {
         : if (is_present "piano") {
            : if (is_sound_enabled) {
               // Play beeps here
            }
            : else {
               : print "You play a few notes on the piano";
            }
         }
   }
}

1.31. Containers

Containers are not yet supported by Adventuron (preliminary support is in the engine).

You can check if an object is inside a location as follows:

start_at = lake

locations {
   forest : location "You are in a forest." ;
   lake   : location "You are by a lake" ;
}

objects {
   lamp  : object "a lamp" at = "forest" ;
}

connections {

   from, direction, to = [
      forest, south, lake,
   ]

}

on_tick {

   : if (is_within_direct { outer = "forest" inner = "lamp" } ) {
      : print "The lamp is in the forest (directly)." ;
   }
   : else {
      : print "The lamp is NOT in the forest (directly)." ;
   }

}

1.32. List Capitalization

The formatting of the object list, exit list, and inventory list can be overridden within the theme settings.

Note
If you only declare one theme, this will become the default theme, no matter what the identifier of the theme is.
start_at = village

locations {
   village  : location "You are in the village" ;
   forest_1 : location "You are in a forest" ;
   forest_2 : location "You are in a spooky forest" ;
}
connections {
   from, direction, to = [
      village, east, forest_1,
      forest_1, east, forest_2,
   ]
}
objects {
   lamp : object "a magical glowing lamp" at = "village" ;
}

themes {
   my_theme : theme {
      lister_objects {
         item_capitalization = original
      }
   }
}

1.33. Masking Commands

The on_command {} event handler typically contains code that will do something specific for your game.

If something cannot be matched in the on_command {} block then a default handler for the command will be executed.

If the player types something random then the engine will display the appropriate system message for the input, but there are also generic handlers for the common verbs such as get, drop, wear, remove, go east, go north, etc.

These default handlers are executed when the player input doesn’t match something in the on_command {} block, and they are not executed if something is executed in the on_command {} block.

Note
Commands such as done, if, match, else, else_if are not considered a match, but the CONTENT of an if statement is considered a match. That is, if you test for a particular condition inside your match, and the expression is evaluated to be negative, Adventuron has done nothing except the test, therefore Adventuron will still run the default handler for the entered user command (assuming nothing else matches in the on_command {} block).

99% of the time, this is correct behaviour, but sometimes you may want to do something in addition to the system event handler.

In these cases, you need to hide your handler from Adventuron so it doesn’t know you’ve executed your own code.

Anything inside the : mask {} command will hide the user handler from the system. This means that the system handler will be executed in addition to the code inisde your mask{} block.

As always, this concept is best demonstrated.

In the code below, try running it as is, then remove the : mask{} block (comment it out) and observe the behaviour difference.

start_at = field
locations {
   field      : location "You are in a field." ;
   meadow     : location "You are in a meadow." ;
   river_bank : location "You are at a river bank." ;
}
connections {
   from, direction, to = [
      field,  east, meadow,
      meadow, east, river_bank,
   ]
}

integers {
   num_times_try_go_east : integer "0" ;
}

on_command {
   : match "e _"  {
      // Anything inside a mask will not override a
      // system handler. In this case, we wish to
      // increment a counter everytime we try to
      // head east, but we don't want to block the
      // system movement handler.
      : mask {
         : increment "num_times_try_go_east" ;
      }
   }
}

on_tick {
   : print "You have tried to move east {num_times_try_go_east} time(s)." ;
}

1.34. How to see which locations are adjacent to the current location

The try_move() function will return a string containing the id of the location in the direction of the verb that the player entered. If there is no accessible location in that direction (no connection, or an active block), then a blank string will be returned.

To check to see that there is a valid location:

: if (try_move() != "") {
   // Do something
}

To check to see there is no location or a blocked location:

: if (try_move() == "") {
   // Do something
}

A larger example is here !

start_at = meadow
locations {
   field      : location "You are in a field." ;
   meadow     : location "You are in a meadow." ;
   river_bank : location "You are at a river bank." ;
}
connections {
   from, direction, to = [
      field,  east, meadow,
      meadow, east, river_bank,
   ]
}

on_command {
   : match "e _"  {
      : if (try_move() != "") {
         // Note :: This overrides the system movement handler (see the mask section for more information on this)
         : print "You can go east, but you choose not to go.";
      }
   }
}

A masked example (uses mask + try move):

start_at = field
locations {
   field      : location "You are in a field." ;
   meadow     : location "You are in a meadow." ;
   river_bank : location "You are at a river bank." ;
}
connections {
   from, direction, to = [
      field,  east, meadow,
      meadow, east, river_bank,
   ]
}

integers {
   num_times_gone_east : integer "0" ;
}

on_command {
   : match "e _"  {
      // Anything inside a mask will not override a
      // system handler. In this case, we wish to
      // increment a counter everytime we successfully
      // head east, but we don't want to block the
      // system movement handler.
      : mask {
         // Try move contains the id of the location
         // correspondin to the direction verb in
         // the logial sentence
         // If the direction leads nowhere or it
         // is blocked, then a blank string is returned.
         : if (try_move() != "") {
            : increment "num_times_gone_east" ;
         }
      }
   }
}

on_tick {
   : print "You have successfully gone east {num_times_gone_east} time(s)." ;
}

1.35. Door Interrogation

The 'is_barrier_active' and 'is_locked' boolean functions can be used to test the state of a door or a barrier.

See the example below.

start_at = lakeside

locations {
   lakeside       : location "You are by the side of a lake.";
   outside_castle : location "You are outside the castle. There is a door." ;
   castle         : location "You are inside the castle." ;
}

connections {
   from, direction, to = [
      lakeside,       north, outside_castle,
      outside_castle, north, castle,
   ]
}

objects  { key : object  "a key" at="lakeside"; }

barriers {
   my_door : door {
      from = outside_castle
      to   = castle
      key  = key
   }
}

on_tick {
   : if (is_at "outside_castle") {
      : if (is_blocking "my_door") {
         : print "The door is closed." ;
      }
      : else {
         : print "The door is open." ;
      }
      : if (is_locked "my_door") {
         : print "The door is locked." ;
      }
      : else {
         : print "The door is unlocked." ;
      }
   }
}

1.36. Copying The Value Of A Variable To Another Variable

To copy the value of a variable into another variable use set_integer (for numbers), set_string (for text) and set_boolean (for booleans).

## int_b = int_a
: set_integer var = "int_b"    (int_a) ;
## string_b = string_a
: set_string  var = "string_b" (string_a) ;
## bool_b = bool_a
: set_boolean var = "bool_b"   (bool_a) ;

A full demo of all three types of copy is included below (type COPY at the command to initial the copy.

The location text is formatted to debug the value of all the variables.

start_at = my_location

locations {
   my_location : location "^n^int_a is {int_a}\nint_b is {int_b}\nbool_a is {bool_a}\nbool_b is {bool_b}\nstring_a is {string_a}\nstring_b is {string_b}^m^\nType <COPY<12>> to copy A variables to B variables.";
}

integers {
   int_a    : integer "5" ;
   int_b    : integer "0" ;
}
booleans {
   bool_a   : boolean "true" ;
   bool_b   : boolean "false" ;
}
strings {
   string_a : string "toast";
   string_b : string "eggs";
}

on_command {
   : match "copy _"  {
      : set_integer var = "int_b"    (int_a)    ;
      : set_string  var = "string_b" (string_a) ;
      : set_boolean var = "bool_b"   (bool_a)   ;
      : print "Copied all A variables into B variables." ;
      : press_any_key ;
      : redescribe;
   }
}

(Advanced)

It’s also possible to include expressions on the right hand side:

# If we are running on a mobile client, then "copy the value of a and add 1 into b", else "copy the value of a into b".
: set_integer var = "int_b"    (is_mobile() ? int_a + 1 : int_a) ;

1.37. Disambiguation

Adventuron has a basic (experimental) system-level method of disambiguating the first subject, where you wish to create adventures with multiple objects/scenery with the same noun, or possibly even adjective/noun.

System level disambiguation is already in place for GET/DROP/WEAR/REMOVE/EXAMINE, but needs to be added for user verbs, as different verbs will assume objects are in a certain place. For example, the DROP command only relates to object the player is holding, and GET only relates to objects that the player is beside but not holding.

: disambiguate_s1 "carried";

If successful will place exactly one object_id into the subject 1 slot of the logical sentence - s1().

If nothing is found, then 'unknown' will be returned by s1() . 'unknown' is a reserved object id and authors are not permitted to use 'unknown' as their own object id.

Disambiguation Category Details

carried

Assumes s1() refers to an item (worn or unworn) that the player is carrying.

beside

Assumes s1() refers to an item (worn or unworn) that the player is beside (inside the location that the player is in but not carried).

present

Assumes s1() refers to an item (worn or unworn) that the player is carrying or is beside (in the same location).

worn

Assumes s1() refers to an item (worn or unworn) that the player is carrying and is worn.

unworn

Assumes s1() refers to an item (worn or unworn) that the player is carrying and is not worn and is wearable.

universal

Assumes s1() refers to an item(worn or unworn), even if it does not exist, is not in the current location, and if it is unknown (will scan across the entire model).

start_at = my_location

locations {
   my_location : location "You are in a room." ;
}

objects {
   magenta_coin    : object "a <magenta coin<11>>" start_at = "inventory" ;
   red_coin        : object "a <red coin<10>>"     start_at = "inventory" ;
   green_coin      : object "a <green coin<12>>"   start_at = "inventory" ;
   vending_machine : scenery "a vending maching ({coins_in_vending_machine} / 3)"   start_at = "my_location" ;
   candy           : object "a candy bar" ;
}

on_describe {
   : if (carried ()  > 0) {
      : inventory;
   }
}

integers {
   coins_in_vending_machine : integer;
}

on_command {
   : match "insert coin; use coin" {


      : if (is_present "vending_machine") {

         //
         // disambiguate_s1 "carried"
         //
         // Will check that the adjective noun in the first slot matches exactly one object (or entity)
         // If not, it will present the player with a list of matches (if there are 2 or more), otherwise
         // it will set 'unknown' (if zero matches).

         : disambiguate_s1 "carried";

         // At this point, s1() will return either 'unknown' or the entity id of a single subject.

         : if (s1() == "unknown") {
            : print "No coin to insert." ;
            : done;
         }

         : if (subject1_is "red_coin" || subject1_is "magenta_coin" || subject1_is "green_coin") {
            // In this example, we don't care which coin is used first, middle or last, but at this point
            // we could isolate each coin and handle them differently if we wanted to.
            // When using s1() == "object_id", watch out for typos.
            : print "Ker-ching." ;
            : increment "coins_in_vending_machine" ;
            : destroy;
            : if (coins_in_vending_machine == 3) {
               : create "candy" ;
               : beep millis = "100"  pitch = "0" ;
               : beep millis = "100"  pitch = "2" ;
               : beep millis = "100"  pitch = "4" ;
               : beep millis = "100"  pitch = "6" ;
               : beep millis = "100"  pitch = "8" ;
            }
            : else {
               : beep millis = "100"  pitch = "0" ;
               : beep millis = "300"  pitch = "6" ;
            }
            : redescribe;
         }
      }
   }
}

1.37.1. Lifecycle of s1() / s2() string functions

Note
This section also applies to s2(), but s2 is the second known subject
Note
To be known a subject usually has to be accessible whilst sharing a location with the player.

Without disambiguation, s1() refers to either a known subject that corresponds to the subject words (usually verb and noun).

The subject words may match no known subject (entity, object, or topic), or one known subject, or more than one.

  • If the subject words map to zero known subjects, then s1() returns "unknown".

  • If the subject words map to one known subjects, then s1() returns the id of the known object.

  • If the subject words map to more than one known subjects, then s1() returns "ambiguous".

For information on disambiguation, read the disambiguation section.

Post disambiguation, s1() refers to the filtered subjects that corresponds to the subject words (usually verb and noun).

After calling disambiguation, calling s1() will refer to the disambiguated list, even if that list contains zero entries (returns an "unknown" string value).

Note
Disambiguation will automatically exclude the disambiguated value for the OTHER subject, disambiguate_s2 will exclude the unique disambiguated s1 value (if it exists), and vice versa.

1.38. Traits

Traits are tags that you can apply to objects and locations in Adventuron.

You create a trait (a tag) in the traits {} section. All user-generated traits must end in _t.

You can assign traits to objects in the long form of object defining (where the properties of the object are between the { and } brackets.

The s1_has_trait "objectid|locationid" function will return true if an object has a trait.

start_at = my_location

locations {
   my_location : location "You are in a room.\nType SMELL OBJECTNAME to see if the object is spicy or not." ;
}

traits {
   spicy_t : trait;
}

objects {

   curry : object "some curry" {
      traits = [ spicy_t ]
      at = my_location
   }

   pepper : object "some pepper" {
      traits = [ spicy_t ]
      at = my_location
   }

   carrot : object "a carrot" {
      traits = [ ]
      at = my_location
   }

   bread : object "some bread" {
      traits = [ ]
      at = my_location
   }

}

on_command {
   : match "smell _"  {

      // See the disambiguation section to see how this works ...
      : disambiguate_s1 "present";

      : if (s1() != "unknown") {

         // By using traits, we don't have to hardcode objects ids in our if
         // statement - we can just check that they have the trait we require.

          // NOTE:: Adventuron will always require a call to disambiguate_s1 before calling s1_has_trait...

         : if (s1_has_trait "spicy_t") {
            : print "It has a <spicy<10>> smell" ;
         }
         : else {
            : print "It does not smell spicy." ;
         }

      }
      : else {
         : print "You don't see anything like that to smell." ;
      }
   }
}

You can add traits using the following function:

   : add_trait subject="banana" trait="soft_t";

You can remove traits using the following function:

   : remove_trait subject="banana" trait="soft_t";

You can toggle traits using the following function:

   : toggle_trait subject="banana" trait="soft_t";

You can check for a trait on a given object using the following function:

   : if ( has_trait {subject="banana" trait="soft_t"} ) {
      // Do something
   }

(Advanced) Parameters to all this functions can be provided in the form of expressions:

   : if ( has_trait {subject -> ("ban" + "ana")  trait -> ("soft" + "_t")} ) {
      // Do something
   }
Yielding weapons (using Traits)
start_at = my_location
redescribe = auto_beta

locations {
   my_location : location "You are in a room.\n{wield_status}" ;
}

objects {

   sword : object "a sword" at = "inventory" {
      traits = [t_weapon]
   }

   knife : object "a knife" at = "inventory" {
      traits = [t_weapon]
   }


   // Not a weapon - open to debate perhaps???

   spoon : object "a spoon" at = "inventory";

}

traits {
   t_weapon : trait;
}

strings {
   wielded_weapon : string "" ;
   wield_status : dynamic_string (
      wielded_weapon == "" ? "" : ("You are wielding "+ definite(d(wielded_weapon)) + ".")
   ) ;
}

on_command {
   : match "drop _"  {
      : disambiguate_s1 "carried";
      // Unwield if held
      : if (s1() == wielded_weapon) {
         : set_string var = "wielded_weapon"  text = "" ;
      }
      : drop ;
   }
   : match "unwield _"  {
      : set_string var = "wielded_weapon" text = "" ;
   }

   : match "wield _"  {
      : disambiguate_s1 "carried" ;

      : if (s1() == "unknown") {
         : print "You don't have it." ;

      }
      : else {
         : if (s1_has_trait "t_weapon" == false) {
            : print "Not a weapon" ;
         }
         : else {
            : set_string var = "wielded_weapon" (
               s1()
            );
         }
      }
   }
}

1.38.1. Example Cleaning Dirty Things (using Traits)

start_at   = kitchen
redescribe = auto_beta

locations {
   kitchen : location "You are in a kitchen." ;
}

traits {
   t_polished : trait;
}

strings {
   pan_desc : dynamic_string (
      has_trait{ subject ="pan" trait = "t_polished" } ?
      "a shiny pan" :
      "a dirty pan"
   ) ;
}

objects {
   pan : object "{pan_desc}" at = "kitchen" ;
}

on_command {
   : match "shine pan;clean pan;polish pan;dust pan;wipe pan"  {
      : if (is_present "pan") {
         : print "You shine the pan ...." ;
         : add_trait subject = "pan" trait = "t_polished";
      }
   }
}

1.38.2. System Traits

Adventuron has a number of system traits. You can use these traits without the t_ prefix.

Object Type Details

listed

All objects are listed in the game object list by default. To set an object to be non listed, set listed="false" in your object or scenery or character defintion.

conspicuous

(alias for listed)

known

Objects are made known by sharing a location with them or holding them.

has_existed

If an object has existed during the game sometime.

taken

If an object has been held by the player sometime.

exists

If an object currently exists.

wearable

If an object is currently wearable.

worn

If an object is currently worn.

treasure

If an object is currently a treasure object (for use in treasure hunt mode).

immovable

If an object is defined to be 'scenery' then the immovable trait is set (no trait for scenery).

character

If an object is a character, then this trait is set.

1.39. Context Menus

A later version of Adventuron will support flexible context menus per object. It will be able to determine which actions are appropriate to certain objects, and the author will be able to control if all options are available from the start of the game, or if the player has to type nouns first before they appear as an option.

In the current version of Adventuron a limited version of this system is available, by right clicking with the mouse, or long pressing with touch (a finger) on a listed object, a context menu will appear, in which it is possible to select GET, DROP, or EXAMINE verbs. GET and DROP are only available where it makes sense in the context (you can’t drop an object you are not holding, you can’t get an object you are already holding or a scenery object).

There will be a very rich API for controlling context menus in a later release, but right now, you can enable these context menus in the theme settings as shown below:

theme_settings {
   experimental_contextual_menus = true
}

A more full example is shown below:

Note
The 'two' theme is a theme that is built into Adventuron, and provides a minimal textual set of system response messages, as well as formats the screen similarly to the game 'two', released in 2019.
start_at     = room
start_theme  = my_theme
redescribe   = auto_beta

locations {
   room : location "Room" ;
}

objects {
   yellow_horn  : object "a <yellow horn<14>>"  at = "room" msg="Look at the sky.";
   magenta_horn : object "a <magenta horn<11>>" at = "room" msg="Deep Purple.";
   cyan_horn    : object "a <cyan horn<13>>"    at = "room" msg="Misty.";
}

themes {
   my_theme : theme {
      extends = two
      theme_settings {
         experimental_contextual_menus = true
      }
   }
}

1.39.1. Auto Beta Mode 2

Sometimes you may want to maintain the command log, in that case, then use the auto beta mode 2.

Technically, the auto redescribe is not a redescribe at all, it simply binds the current state of the game to the layout of your theme.

e.g. When the room state changes, it does not execute an on_describe() event (same as auto beta mode 1).

This mode does not silence the get and drop commands by default (as in auto beta mode 1) and it doesn’t insert a press any key if you print something (like auto beta 1).

Note
The 'two' theme is a theme that is built into Adventuron, and provides a minimal textual set of system response messages, as well as formats the screen similarly to the game 'two', released in 2019.
start_at    = start_location
start_theme = my_theme

// Auto Beta 2 - Another experimental
//             auto redescribe test mode
//               Will redescribe and maintain
//               the entire command log.
//
//             NOTE : Subject to change...
redescribe               = auto_beta_2

strings {
   locdesc : dynamic_string (
      (is_beside "spoon" ? "You are in a spoony place" : "You are in a place") + ", and you have taken " +
      turns() + " turns."
   ) ;
}

locations {
   start_location : location "{locdesc}";
}

objects {
   lamp  : object "a lamp"  at = "start_location" ;
   spoon : object "a spoon" at = "start_location" ;
}

on_describe {
   : print "Hello" ;
}

themes {
   my_theme : theme {
      extends = two
      theme_settings {
         layout  = SB G D X O SEP "adv_line_red" LOCK
         columns = 64
      }
      lister_objects {
         show_when_empty         = true
      }
      status_bar {
         : fixed_text "DEMO PROGRAM" ;
         : fixed_text "ADVENTURON" ;
      }
      system_messages {
         object_list_header   = YOU SEE:\s
         object_list_empty    = Nothing
         object_list_end_text = .
      }
   }
}

assets {

   graphics {

      blue_image  : base64_png "iVBORw0KGgoAAAANSUhEUgAAAQAAAABQAQMAAADMRTlmAAAAA1BMVEU/SMwiP9ThAAAAGUlEQVRIx+3BMQEAAADCIPunNsReYAAAEBwKUAABS3aq6QAAAABJRU5ErkJggg==";
      green_image : base64_png "iVBORw0KGgoAAAANSUhEUgAAAQAAAABQAQMAAADMRTlmAAAAA1BMVEUisUweBz0WAAAAGUlEQVRIx+3BMQEAAADCIPunNsReYAAAEBwKUAABS3aq6QAAAABJRU5ErkJggg==";
      red_image   : base64_png "iVBORw0KGgoAAAANSUhEUgAAAQAAAABQAQMAAADMRTlmAAAAA1BMVEXtHCTcNMelAAAAGUlEQVRIx+3BMQEAAADCIPunNsReYAAAEBwKUAABS3aq6QAAAABJRU5ErkJggg==";

      start_location : dynamic_graphic (

         /* random(2) retuns value between 0 and 2 */
         /* 33% chance here to select red*/
         random (2) < 1 ? "red_image"  :

         /* random(2) retuns value between 0 and 1       */
         /* 50% chance here between two remaining values */
         /* (50% multiplied by 66% = 33%                 */
         random (1) < 1 ? "blue_image" : "green_image"
      ) ;
   }

}

1.40. Multiple Choice (Beta)

Adventuron has a method of presenting multiple choices.

Multiple choices are displayed using the " : choose " command, and will display options for each choice that has been added via the " : add_choice " command. Rather than setting the value of a variable, the choose command allows Adventuron to conditionall run sub commands if the choice is selected. The commands to run are contained between { } brackets.

This can be useful for an hybrid "on rails" / parser type game.

All choices are flushed every time the player selects ': choose'.

Important
This method of presenting choices is only intended for hybrid parser / choice based games. Games are only saveable when the command prompt (not a choice prompt) is displayed and the player types SAVE currently. Autosave does not (currently) work inside of : choose;
Important
Adventuron also has a gamebook mode (choices only, no parser) that is documented seperately.
start_at = start_location

locations {
   start_location : location "Type 'choice demo' to see multiple choice demo." ;
}

on_command {

   : match "choice demo"  {

      // Adds a choice

      : add_choice "Fight Wizard" {
         : print "Fight The Wizard Routine.";
      }


      // Adds a choice

      : add_choice "Run Away" {
         : print "You run away.";
         : lose_game;
      }

      // Adds a choice

      : add_choice "Use Something" {
         : print "You look in your inventory....";
      }

      // If no choices have been added, then choose will do nothing.

      : choose "What do you want to do?";

   }

}

1.40.1. Colour cycling for multiple choice options

Later versions of Adventuron will improve theming for multiple choice options. By default, multiple choice options are coloured the same as regular story text, but you can add a colour cycler for multiple choice options (always resets for each list of choices displayed):

start_at                 = start_location
start_theme              = my_theme

locations {
   start_location : location "Type 'choice demo' to see multiple choice demo." ;
}

on_command {

   : match "choice demo"  {

      : add_choice "Fight Wizard" {
         : print "Fight The Wizard Routine.";
      }

      : add_choice "Run Away" {
         : print "You run away.";
         : lose_game;
      }

      : add_choice "Use Something" {
         : print "You look in your inventory....";
      }

      : choose "What do you want to do?";

   }

}

themes {

   my_theme : theme {
      colors {
         multiple_choice_pen = [ "#f00","#0f0", "#00f"]
      }
   }

}

Another example of gamebook mode (gamebook mode is subject to change syntax) .

start_at  = 001_hallway
pages {
   001_hallway "You are in the hallway." {
      : add choice "Turn to page 2 to go to kitchen." ;
      : add choice "Turn to page 3 to go to library." ;
      : add choice "Turn to page 4 to leave the house";
   }
   002_kitchen  "You are in kitchen." {
      : add choice "Turn to page 1 to go to hallway." ;
      : add choice "Turn to page 3 to go to library." ;
   }
   003_library "You are in library." {
      : add choice "Turn to page 1 to go to hallway." ;
      : add choice "Turn to page 2 to go to kitchen." ;
   }
   004_finish "You are outside the house." ;
}

themes {
   my_theme : theme {
      lister_choices {
         gamebook_choice_list_style = list
      }
   }
}

Another gamebook example:

start_at  = 001_cellar
game_type = gamebook

booleans {
   has_spider : boolean;
}

pages {
   001_cellar : page "You are in a dark cellar. Stairs lead up." {
      : if (has_spider == false) {
         : add_choice "Examine barrel"  {
            : clear_screen;
            : print_graphic "spid" ;
            : print "You find a spider. You pick it up and take it with you." ;
            : set_true "has_spider" ;
            : press_any_key ;
         }

      }
      : add_choice "Go upstairs"  goto = "002_hallway" ;
   }
   002_hallway : page "You are in a hallway." ; // no choices = default end of game
}

assets {
   graphics {
      spid : base64_png "iVBORw0KGgoAAAANSUhEUgAAAFAAAAAUCAIAAACVui2AAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHMSURBVFhHzY9LcuRACER9M299b219qKHIbIT4qeSecPiFohsygYKPTQ4HpZ5Q49Oufc1VmCdoK5RG9itrfL+PS4Lr09y4ZinMK7zbVeqME6o/o+wf5gbdUu1YMQIDLigVRi+sBoEHBW8xTzHXl62X72DptTgrAsRAZ3X1W5RDIYKsGLCewmYFKXQDloAYoqcU79GZ9WNMHNAF5u8R5thkCzxZEUrxROc8g50vspL5/PoOHw1HOVzodNC1MNrh0YhSD7flj3VX5ne7BQRfcwsqSSEppShkPRw2fGzo0V3O+eEtuAF6L7JyobMf6eGqRx9H9OBF+c1PZwV0em80loilHm64/di2R/miUbp1yzAoWJIalK7gBn/S/KFrhu/9J7au1coFUsHHhh3gg80P9beU73pyQVS6EaIblBylaHsjsEsQlJ+WL7ISsE3Kp43SvYi5QhTAPFFaYXv7RTB8qOnwmyAWkHaUBado0ZqkeDEzWMBuyEFm5+aMrkkoOUpRoI6/UNT1CGINrmfnYAE3PzrbL4B9DBMReChq2cUOaSDX37JzzDs3e7Ceh4a1eAlkBazuxvp95k28q1sT5qqTkApauUAM8S8wL1O6x3H8A9UZhxxdLZKdAAAAAElFTkSuQmCC";
   }
}

1.41. Dynamic Graphics

Adventuron utilizes graphics by referencing them via their graphic id. Graphics that share the same id as a location, or automatically associated with that location, as their location graphic.

Location graphic ids can also be updated during a game manually via the

: set_graphic = "GRAPHIC_ID"  target = "LOCATION_ID" ;

But, there is also a more powerful way of manipulating graphics at runtime via use of a dynamic_graphic.

The example shown below will select a red, green, or blue image at random for a single location.

start_at     = start_location

locations {
   start_location : location "You are in a room (keep typing LOOK to see image randomize)." ;
}

assets {
   graphics {
      blue_image  : base64_png "iVBORw0KGgoAAAANSUhEUgAAAQAAAABQAQMAAADMRTlmAAAAA1BMVEU/SMwiP9ThAAAAGUlEQVRIx+3BMQEAAADCIPunNsReYAAAEBwKUAABS3aq6QAAAABJRU5ErkJggg==";
      green_image : base64_png "iVBORw0KGgoAAAANSUhEUgAAAQAAAABQAQMAAADMRTlmAAAAA1BMVEUisUweBz0WAAAAGUlEQVRIx+3BMQEAAADCIPunNsReYAAAEBwKUAABS3aq6QAAAABJRU5ErkJggg==";
      red_image   : base64_png "iVBORw0KGgoAAAANSUhEUgAAAQAAAABQAQMAAADMRTlmAAAAA1BMVEXtHCTcNMelAAAAGUlEQVRIx+3BMQEAAADCIPunNsReYAAAEBwKUAABS3aq6QAAAABJRU5ErkJggg==";

      start_location : dynamic_graphic (

         /* random(2) retuns value between 0 and 2 */
         /* 33% chance here to select red*/
         random (2) < 1 ? "red_image"  :

         /* random(2) retuns value between 0 and 1       */
         /* 50% chance here between two remaining values */
         /* (50% multiplied by 66% = 33%                 */
         random (1) < 1 ? "blue_image" : "green_image"
      ) ;
   }
}

The script notation from dynamic_integer, dynamic_string, and dynamic_boolean is featured here.

random(2) produces a number at random between 0 and 2 (3 distinct values).

The following notation is read as follows:

CONDITION ? if_true : if_false

These conditions can be chained together as follows

CONDITION1 ? if_true_1 :

CONDITION2 ? if_true_2 :

CONDITION3 ? if_true_3 :

CONDITION4 ? if_true_4 : if_false

In brief, it means if CONDITION1 returns a true value, then select if_true_1, otherwise if CONDITION2 returns a true value, then select if_true_2, otherwise if CONDITION3 returns a true value, then select if_true_3, otherwise if CONDITION4 returns a true value, then select if_true_4, otherwise select if_false.

As many conditions as you need can be chained toghether in this way, and you can think of it as a primitive if statement.

A requirement in adventuron is that expressions are not permitted to change the game state, so unfortunately, we can’t utilize commands here to set up a random value in a variable unfortunately, but chaining together ? : statements is a good way to achieve case() functionality.

1.42. ADVANCED TOPIC - Understanding Turns and Ticks (Beginners do not read)

Note
This is VERY advanced, and is only provided to better understand the Adventuron game lifecycle better. Beginners - DO NOT READ THIS SECTION - it’s generally not needed.

A turn represents the number of inputs received by the player.

A tick is the number of times that the on_tick {} event handler block has occured in the game (excluding the startup tick).
The first tick is tick 0, and the first turn is turn 1.

Tick 0 runs before first input has been entered by the game (upon presenting the first location). Tick 1 is the first tick that can follow turn 1 (even though it’s technically the second tick). There is no turn 0.

#############################################
# The first tick is TICK 0 (after the first
# room is described). The first tick is enabled
# by defauly but this tick may be configurable in
# a later version of Adventuron.
# -----------------------------------------
# Every submitted command increments turns()
# But some system commands (such as SAVE)
# do not increment the tick counter.
# -----------------------------------------
#
# In the below sample code, the commands ONE, TWO, FOUR
# all generate ticks, INVENTORY and THREE do not generate
# ticks. THREE purposefully stops a tick with the : stop_ticks;
# command.
#
#############################################

start_at                 = my_location

locations {
   my_location : location "You are in a room.\nType the following commands one at a time at the command line (inventory will not generate a tick):\n^n^(1) ONE \n(2) TWO\n(3) THREE\n(4) INVENTORY\n^m^(5) FOUR" ;
}

on_command {
   // Match all words
   : match "_ _"  {
      // Do not match "inventory" (let the system handle it)
      : if (verb_is "inventory" == false) {
         // If the input is "three" then skip downstream ticks
         // (this allows custom responses without the game world pproceeding)
         : if (verb_is "three") {
            : stop_tick;
         }
         // Print how many ticks and turns have elapsed so far
         : print ( "Ticks : " + ticks() +  " Turns : " + turns() ) ;
      }
   }
}

game_settings {
   // This is set to false by default (inventory generates a tick by default, but we can disable that here)
   // NOTE : This is an experimental setting, and may change in a future release.
   experimental_inventory_stops_tick = true
}

on_tick {
   : print ("TICK #" + ticks() ) ;
}

Expected results:

tick

1.42.1. More on Ticks & Turns

Here is another demonstration of turns() and ticks().

  • The turns() function will return a number that is incremented just before on_command() is executed (can think of turns as commands processed so far).

  • The ticks() function will return a number that is incremented just before on_tick() is executed (can think of ticks as ticks processed so far).

turns() and ticks() can be very much out of synch, and this is normal, as some commands are timeless.

Items in GET ALL | DROP ALL | etc. get their own turn followed by their own tick.

A general rule of thumb is only to use the ticks() function in the on_ticks{} block and only use turns() {} in the on_command block.

start_at = village

locations {
   village  : location "You are in the village.";
}

objects {
   lamp  : object "a lamp"  at = "village" ;
   spoon : object "a spoon" at = "village"  ;
}

on_startup {
   : print ("- on_startup (ticks so far:"+ticks() + ", turns so far:" +turns()+")" ) ;
   : press_any_key ;
}
on_command {
: if (true) {
      // Masking makes sure that a command handler doesn't override a system handler.
      : mask {
         : print ("--------------------" ) ;
         : print ("- on_command (ticks so far:"+ticks() + ", turns so far:" +turns()+")" ) ;

      }
   }
}

on_tick {
   : print ("- on_tick (ticks so far:"+ticks() + ", turns so far:" +turns()+")" ) ;
}

on_describe {
   : print ("- on_describe (ticks so far:"+ticks() + ", turns so far:" +turns()+")" ) ;
}

themes {
   my_theme : theme {
      theme_settings {
         clear_screen_enable = false
      }
   }
}

GET ALL (turn, tick, turn, tick)

tick2

GET LAMP (turn, tick)

tick3

GET LAMP, GET SPOON (turn, tick, turn, tick)

tick4

1.43. Gamebook Mode

This is a sample of the experimental gamebook mode of Adventuron (subject to change).

An on_describe {} handler in gamebook mode must do ONE of the following:

  1. Present at least one choice (contribute an add_choice {} block) …​

  2. Change the location to a different location.

  3. Execute a : lose_game ; or win_game ; command.

In this mode, there is no command entry (no parser), and Adventuron will present automatically call a ": choose ;" command at the end of the on_describe block.

At the end of a gamebook tick, the current page will be automatically redescribed.

If you print something in a gamebook tick, then please add a press_any_key afterward, or else the redescribe will overwrite its output.

After each turn a redescribe of the current location will occur automatically, and an autosave will occur just before the redescribe.

It’s still possible in this mode to ask for keyboard input via ask_string, ask_bool & ask_int, but logical sentence processing is switched off.

It is also possible to embed add_choice within existing choices, as required. At least one choice will ALWAYS be required in a location, or else a win_game, or lose_game command should be supplied. All code should be in the on_describe (per location) and the on_pre_describe block (at the top level of the source code). Andthing printed in the pre-describe section will be printed before the location is described.

In gamebook mode, it’s currently not supported to navigate via the connection table. Connections between locations are made manually via the add_choice commands.

Generally speaking inventory management and object management features must be coded into this mode (currently). Some object management and general purpose (use object with xxx) functionality may be added in the future to this mode.

NOTE : This mode does not have a way of quitting, saving or loading at present (auto save automatically works though - i.e. revisiting the game will resume where you left off).

This mode will be expanded upon in later revisions of Adventuron.

It is strongly advised not to use hyperlinks in your story text in this mode, as the behaviour is currently undefined.

start_at  = page_001
game_type = gamebook

on_startup {
   : print "This is a sample of the <GAMEBOOK<14>> mode of <ADVENTURON<#r>>.\nIn this mode, there is no command entry (no parser), and Adventuron will present automatically call a \": choose ;\" command at the end of the on_describe block.\nEvery page that does not win or lose the game must present at least one choice, or Adventuron will report an error.\nAfter each turn a redescribe of the current location will occur automatically, and an autosae will occur just before the redescribe.\nThis mode will be expanded upon in later revisions of Adventuron.\nIt is strongly advised not to use hyperlinks in your story text in this mode, as the behaviour is currently undefined." ;
   : press_any_key ;
}

objects {
   key : object "<a key<12>>" start_at = "page_003" ;
}

locations {

   page_001 : location "You are in front of three large doors.\n One door is marked <CERTAIN DEATH<10>>, one is marked <SOMETHING USEFUL<11>>, and the final door is marked <FREEDOM<12>>." {
      on_describe {
         : add_choice "Enter the door marked <CERTAIN DEATH<10>>."  {
            : goto "page_002" ;
         }

         : add_choice "Enter the door marked <SOMETHING USEFUL<11>>"  {
            : goto "page_003" ;
         }

         : add_choice "Enter the door marked <FREEDOM<12>>."  {
            : if (is_carried "key") {
               : print "Using the <KEY<12>>, you unlock and open the door." ;
               : press_any_key ;
               : goto "page_005" ;
            }
            : else {
               : goto "page_004" ;
            }
         }
      }
   }

   page_002 : location "You fell down a bottomless pit.\nTHIS IS THE END OF YOUR JOURNEY." {
      on_describe {
         : failure ;
         : lose_game;
      }
   }

   page_003 : location "You are in a dark cellar." {
      on_describe {
         : if (is_beside "key") {
            : add_choice "Get The Key"  {
               : get "key";
            }
         }
         : add_choice "Go Back." goto = "back";
      }
   }


   page_004 : location "The door to <FREEDOM<12>> is locked [[require a key]]." {
      on_describe {
         : goto "back" ;
         : press_any_key ;
      }
   }


   page_005 : location "You are free, you live happily ever after." {
      on_describe {
         : success ;
         : win_game ;
      }
   }

}

1.43.1. Auto taking objects in gamebook mode

start_at  = 001_room
game_type = gamebook

locations {
   001_room : location "You are in room 1." {
      on_describe {
         : add_choice "Turn to page 2 to go to room 2.";
         : add_choice "Turn to page 3 to go to room 3.";
      }
   }
   002_room : location "You are in room 2." {
      on_describe {
         : add_choice "Turn to page 1 to go to room 1.";
         : add_choice "Turn to page 3 to go to room 3.";
      }
   }
   003_room : location "You are in room 3." {
         on_describe {
         : add_choice "Turn to page 1 to go to room 1.";
         : add_choice "Turn to page 2 to go to room 2.";
      }
   }
}

objects {
   lamp  : object "a lamp"  at = "001_room" ;
   spoon : object "a spoon" at = "002_room" ;
   knife : object "a knife" at = "003_room" ;
}

on_describe {

   : if (is_beside "lamp") {
      : get "lamp" ;
   }
   : if (is_beside "spoon") {
      : get "spoon" ;
   }
   : if (is_beside "knife") {
      : get "knife" ;
   }

}

1.44. Going Back To The Previous Location

Adventuron doesn’t currently have an built-in way of going back to the previous location.

This code pattern will record the players route through the game and allow the player to go back to the last location (that is not the current location) that was described.

start_at = lakeside

locations {
   lakeside     : location "You are by a lakeside." ;
   forest       : location "You are in a forest." ;
   outside_cave : location "You are outside a cave." ;
}

connections {
   from, direction, to = [
      lakeside, north, forest,
      forest,   north, outside_cave,
   ]
}

strings {
   pending_last_location : string;
   came_from_location    : string;
}

on_describe {
   : gosub "maintain_last_location" ;
   : if (came_from_location != "" ) {
      : print ("Type BACK to go back to location with id : " + came_from_location) ;
   }
}

on_command {
   : match "back _"  {
      : if (came_from_location != "") {
         : goto (came_from_location) ;
         : print ("You go back to " + came_from_location) ;
         : press_any_key ;
         : redescribe;
      }
   }
}

subroutines {
   maintain_last_location : subroutine {
      : if (pending_last_location != current_location()) {
         : set_string var="came_from_location" (pending_last_location) ;
      }
      : set_string var="pending_last_location" (current_location()) ;
   }
}

1.45. Time

Printing the time is easy by simply manipulating integers representing hours and minutes.

Time can be formatted with dynamic strings.

start_at = my_location

locations {
   my_location : location "Type <SHOW TIME<12>> to show the time (moves by one minute each game turn)." ;
}

strings {

   hhmm : dynamic_string (
      (hour < 10 ? "0" : "") +
      hour + ":" +
      (minute < 10 ? "0" : "") +
      minute
   ) ;

}

integers {
   hour   : integer "8" ;
   minute : integer "0" ;
}

on_tick {
   : increment "minute" ;
   : if (minute > 59) {
      : set_integer var = "minute"  value = "0" ;
      : increment "hour" ;
   }
   : if (hour > 23) {
      : set_integer var = "hour"  value = "0" ;
   }
}

on_command {
   : match "show time"  {
      : print "The time is {hhmm}." ;
   }
}

1.46. Control of flow tests

Attached is a source code snippet which demonstrates Adventuron’s control of flow in the on_command {} block.

Run the 10 test by typing the following commands one at a time. You should see "001-PASS-FINISH" …​ "007-PASS-FINISH" printed out each time, and no text containing the word "FAIL".

If you see no text, it is also an error, it should ALWAYS end in text ending in PASS-FINISH.

start_at = test_location

locations {
   test_location "Please type TEST 1, TEST 2, TEST 3 .. TEST 16." ;
}

booleans {
   a; b; c; d;
}

on_command {

   : match "test 1"  {
      : set_true  "a" ;
      : set_true  "b" ;
      : set_true  "c" ;
      : set_false "d" ;

      : if (a) {
         : print "001-PASS-1" ;
      }
      : else_if (b) {
         : print "001-FAIL-1" ;
      }
      : else {
         : print "001-FAIL-2" ;
      }

      : print "001-PASS-2" ;

      : if (a == true && b == true) {
         : print "001-PASS-3" ;
         : print "001-PASS-FINISH (3 TIMES)" ;

         : done ;
      }
      : else {
         : print "001-FAIL-3" ;
         : done ;
      }
      : print "001-FAIL-4" ;
      : done;
   }

   : match "test 2"  {
      : set_true "a" ;
      : set_true "b" ;
      : set_true "c" ;
      : set_false "d" ;

      : if (d || a) {
         : print "002-PASS-1" ;
      }
      : else_if (b) {
         : print "002-FAIL-1" ;
      }
      : else {
         : print "002-FAIL-2" ;
      }

      : print "002-PASS-2" ;

      : if (a == true && b == false) {
         : print "002-FAIL-3" ;
         : done ;
      }
      : else {
         : print "002-PASS-3" ;
         : print "002-PASS-FINISH (3 TIMES)" ;
         : return;
      }
      : print "002-FAIL-4" ;
      : done;
   }

   : match "test 3"  {
      : set_true "a" ;
      : set_true "b" ;
      : set_true "c" ;
      : set_false "d" ;

      : if (a) {
         : print "003-PASS-1" ;
      }
      : else_if (b) {
         : print "003-FAIL-1" ;
      }
      : else {
         : print "003-FAIL-2" ;
      }

      : print "003-PASS-2" ;

      : if (a == true && b == true) {
         : print "003-PASS-3" ;

      }
      : else {
         : print "003-FAIL-3" ;
         : done ;
      }
      : print "003-PASS-4" ;
   }

   : match "test 3"  {
      : print "003-PASS-FINISH (4 TIMES)" ;
   }


   : match "test 4" {
      : set_false "a"  ;
      : set_false "b"  ;
      : set_false "c"  ;
      : set_false "d"  ;

      : if (a) {
         : print "004-FAIL-1" ;
      }
      : else {

      : print "004-PASS-1" ;
         : if (b) {
            : print "004-FAIL-2" ;
         }
         : else_if (c) {
            : print "004-FAIL-3" ;
         }
         : else_if (d) {
            : print "004-FAIL-4" ;
         }
         : else {
            : print "004-PASS-2" ;
         }
         : print "004-PASS-3" ;
      }
      : print "004-PASS-4" ;
      : print "004-PASS-FINISH (4 TIMES)" ;
   }

   : match "test 5" {
      : set_false "a"  ;
      : set_false "b"  ;
      : set_false "c"  ;
      : set_false "d"  ;

      : if (a) {
         : print "005-FAIL-1" ;
      }
      : else {
         : print "005-PASS-1" ;
         : gosub "test5_a";
         : print "005-PASS-4" ;
      }
      : print "005-PASS-5" ;
      : print "005-PASS-FINISH (5 TIMES)" ;
   }

   : match "test 6" {
      : set_false "a"  ;
      : set_false "b"  ;
      : set_false "c"  ;
      : set_false "d"  ;

      : if (a) {
         : print "006-FAIL-1" ;
      }
      : else {

         : print "006-PASS-1" ;
         : gosub "test6_a";
         : print "006-PASS-3" ;
      }
      : print "006-PASS-4" ;
      : print "006-PASS-FINISH (4 TIMES)" ;
   }

   : match "test 7" {
      : set_false "a"  ;
      : set_false "b"  ;
      : set_false "c"  ;
      : set_false "d"  ;

      : if (a) {
         : print "007-FAIL-1" ;
      }
      : else {

         : print "007-PASS-1" ;
         : gosub "test7_a";
         : print "007-FAIL-6" ;
      }
      : print "007-FAIL-7" ;
   }

   : match "test 8" {
      : set_true "a"  ;
      : set_true "b"  ;
      : set_true "c"  ;
      : set_true "d"  ;
      : print "PASS-001" ;

      : if (a) {
         : if (b) {
            : if (c) {
               : print "PASS-002";
               : gosub "test8_a" ;
            }
            : else {
               : print "FAIL-000";
            }
         }
         : else {
            : print "FAIL-001" ;
         }
      }
      : else {
         : print "FAIL-002" ;
      }

      : print "PASS-004" ;
      : print "FINISHED (4 passed)" ;
   }

   : match "test 9"  {
      : set_true "a"  ;
      : set_true "b"  ;
      : if (a && b) {
         : print "PASS-001";
      }
      : print "PASS-002";
      : print "FINISHED (2 passed)" ;
   }

   : match "test 10"  {
      : set_true "a"  ;
      : set_false "b"  ;
      : if (a && b) {
         : print "FAIL-001";
      }
      : print "PASS-001";
      : print "FINISHED (1 passed)" ;
   }


   : match "test 11"  {
      : set_true "a"  ;
      : set_false "b"  ;
      : if (a && b) {
         : print "FAIL-001";
      }
      : else {
         : print "PASS-001";
      }

      : print "PASS-002";
      : print "FINISHED (2 passed)" ;
   }

   : match "test 12"  {
      : set_true "a"  ;
      : set_false "b"  ;
      : if (a && b) {
         : print "FAIL-001";
      }
      : else_if (a == true) {
         : print "PASS-001";
      }

      : print "PASS-002";
      : print "FINISHED (2 passed)" ;
   }

   : match "test 13"  {
      : set_true "a"  ;
      : set_false "b"  ;
      : if (a && b) {
         : print "FAIL-001";
      }
      : else_if (b) {
         : print "FAIL-002";
      }
      : else {
         : print "PASS-001";
      }

      : print "PASS-002";
      : print "FINISHED (2 passed)" ;
   }

   : match "test 14"  {
      : set_true "a"  ;
      : set_false "b"  ;
      : if (a && b) {
         : print "FAIL-001";
      }
      : else_if (a == false) {
         : print "FAIL-002";
      }

      : print "PASS-001";
      : print "FINISHED (1 passed)" ;
   }


   // Testing this ... https://github.com/ainslec/adventuron-issue-tracker/issues/154
   : match "test 15" {

      : set_false "a";
      : set_true  "b";
      : set_false "c";

      : if (a || b) {
         : print "PASS-001";
         : print "FINISHED (1 passed)" ;
         : if (c) { : print "FAIL-001"; }
      }
   }

   : match "test 16" {
      : add_choice "Select This"  {
         : print "PASS-001";
         : print "FINISHED (1 passed)" ;
         : done;
         : print "FAIL-001";
      }
      : choose ;
      : print "FAIL-002";
   }


   : match "test -"  {
      : print "Please type TEST 1, TEST 2, TEST 3, ... TEST 16." ;

   }

}

subroutines {

   test5_a : subroutine {
      : if (b) {
         : print "005-FAIL-2" ;
      }
      : else_if (c) {
         : print "005-FAIL-3" ;
      }
      : else_if (d) {
         : print "005-FAIL-4" ;
      }
      : else {
         : print "005-PASS-2" ;
      }

      : print "005-PASS-3" ;
   }

   test6_a : subroutine {
      : if (b) {
         : print "006-FAIL-2" ;
      }
      : else_if (c) {
         : print "006-FAIL-3" ;
      }
      : else_if (d) {
         : print "006-FAIL-4" ;
      }
      : else {
         : print "006-PASS-2" ;
         : return;
      }
   }

   test7_a : subroutine {
      : if (b) {
         : print "007-FAIL-2" ;
      }
      : else_if (c) {
         : print "007-FAIL-3" ;
      }
      : else_if (d) {
         : print "007-FAIL-4" ;
      }
      : else {
         : print "007-PASS-2" ;
         : print "007-PASS-FINISH (2 TIMES)" ;
         : done;
      }

      : print "007-FAIL-5" ;
   }

   test8_a : subroutine {

      : if (a) {
         : if (b) {

            : print "PASS-003";
         }
         : else {
            : print "FAIL-005";
         }
      }
      : else {
         : print "FAIL-006";
      }
   }

}

1.47. TWO Theme

The TWO theme is baked into Adventuron. All you have to do is extend the theme by creating a theme and extending it.

my_theme : theme {
   extends = two
}

Here is the source code for the two theme.

two : theme {

   theme_settings {
      font                           = daad
      layout                         = SB O X SEP "adv_line_red" LOCK
      layout_mobile                  = SB O X SEP "adv_line_red" LOCK
      columns                        = 48
      columns_mobile                 = 24
      keyboard_click                 = on
      failure_jingle                 = on
      success_jingle                 = on
      losegame_jingle                = on
      capitalization                 = upper
      header_capitalization          = original
      shader                         = scanlines
      default_delay                  = 0
      hide_status_bar_on_clearscreen = true
      hide_status_bar_on_endgame     = true
   }

   screen {
      padding_horz                 = 4
      padding_horz_mobile          = 4
      status_bar_padding_top       = 3
      status_bar_padding_horz      = 4
      paragraph_spacing_multiplier = 1
      border_mode_vertical_percent = 5
   }

   status_bar {
      : location_text  ;
      : treasure_score ;
   }

   lister_inventory {
      list_type = single_line_no_article
   }

   system_messages {
      object_list_header           = ""
      exit_list_header_concise     = ""
      all_treasures_found_win_game = "YOU WIN!"
      i_cant_do_that               = "DOESN'T WORK."
      unknown_noun                 = "NOT UNDERSTOOD."
      unknown_verb                 = "NOT UNDERSTOOD."
      you_cant_go_that_direction   = "CAN'T GO."
      inventory_list_header        = "CARRYING: "
      inventory_list_empty         = "<NOTHING<10>>"
      you_see_nothing_special      = "NOTHING SPECIAL."
      on_get                       = "TAKEN."
      on_drop                      = "DROPPED."
      on_wear                      = "WORN."
      on_remove                    = "REMOVED."
      you_are_already_carrying     = "ALREADY HAVE."
      dont_have_one_of_those       = "DON'T HAVE."
      ask_quit                     = "QUIT?"
      post_quit                    = "OK"
      cant_take                    = "CAN'T TAKE."
      prompt                       = "? "
      cant_see_one_of_those        = "CAN'T SEE."
      not_present                  = "NOT HERE."
      nothing_to_get               = "NOT HERE."
      restore_from_autosave        = "ROLLBACK?"
      you_already_wear             = "ALREADY WEARING."
      must_remove_first            = "REMOVE FIRST."
      it_is_dark                   = "IT'S DARK."
      worn_suffix                  = " *"
      cannot_carry_any_more        = "HANDS FULL."
   }
}

1.48. Sudden Death and 'Rollback'

Note
This feature is not 8-bit compatible.

(Disabled by default)

Adventuron can be configured to be able to rollback the game one turn upon executing a LOSE_GAME command. This is useful where a game wants to introduce sudden death (or forewarned death) as a mechanic, with the sudden death being grossly unfair.

Sudden death is a great way to make the player feel some tension in the game, but of course, should be used appropriately in games targetting younger players (sudden death can also be just a generic game over message such as "the dog steals your wand, your adventure is over", and this type of message could be accompanied by the ': lose_game' command.

Anyway, to configure rollback, use the following code.

Rollback will ask a question if the player wants to rollback when the lose_game command is executed, and it will reload the state from the turn before the turn where the death tick took place.

Warning
This approach is only appropriate where death (lose_game) is not inevitable within one move. For example, if death is inevitable from the moment you do something, and then it takes 3 or 4 turns to actually lose_game, then rollback will only roll back to a moment where lose_game cannot be avoided.
start_at = lake

game_settings {
   rollback_enabled = true
}

locations {
   forest : location "You are in a forest." ;
   cave   : location "You are in front of a cave." ;
   lake   : location "You are in by the side of a lake." ;
}

connections {
   from, direction, to = [
      lake, north, forest,
      forest, north, cave,
   ]
}

objects {
   bush   : scenery "a bush" msg = "You find nothing else." at = "lake" ;
   helmet : object  "a bicycle helmet" wearable = "true" ;
}

on_command {
   : if_examine "bush"  {
      : print "YOU FIND SOMETHING!" ;
      : create "helmet" ;
      : press_any_key ;
      : redescribe;
   }
}

on_tick {
   : if (is_just_entered () && is_at "cave") {
      : if (is_worn "helmet") {
         : print "ROCKS SLIDE ....." ;
         : press_any_key ;
         : print "YOUR HELMET PROTECTS ....." ;
         : press_any_key ;
         : print "YOU WIN!" ;
         : win_game ;
      }
      : else {
         : print "ROCKS SLIDE ....." ;
         : press_any_key ;
         : print "NOTHING PROTECTS YOUR HEAD ....." ;
         : press_any_key ;
         : print "YOU LOSE!" ;
         : lose_game ;
      }
   }
}

Adventuron supports rolling back to not the previous turn, but rather, the previous turn in the previous location . This still doesn’t work for certain games where certain deaths can follow the player around for a certain number of moves. New rollback plans will be added (such as those that depend on virtual autosave tripwires) in future version of adventuron.

: lose_game rollback_plan = "last_location";

1.49. Rewinding

Note
This feature is not 8-bit compatible.

(Disabled by default)

Adventuron can optionally allow the player to undo commands. The amount of moves that can be rewound is dependent upon the client, but typically it is a buffer of 30 moves.

The rewind buffer is the same buffer used by rollback, but just like rollback, rewinding is enabled by default.

Rewinding and rollback can be independently switched on and off, and they do not affect each other. In both cases, internally, Adventuron will use the move buffer.

To rewind the game one move either:

  • type UNDO

  • type REWIND
    or

  • press CONTROL + SHIFT + LEFT ARROW (rewind) or CONTROL + SHIFT + RIGHT ARROW (redo)

start_at = lake

game_settings {
   rewind_enabled   = true
   rollback_enabled = true
}

locations {
   forest : location "You are in a forest." ;
   cave   : location "You are in front of a cave." ;
   lake   : location "You are in by the side of a lake." ;
}

connections {
   from, direction, to = [
      lake, north, forest,
      forest, north, cave,
   ]
}

objects {
   bush   : scenery "a bush" msg = "You find nothing else." at = "lake" ;
   helmet : object  "a bicycle helmet" wearable = "true" ;
}

on_command {
   : if_examine "bush"  {
      : print "YOU FIND SOMETHING!" ;
      : create "helmet" ;
      : press_any_key ;
      : redescribe;
   }
}

on_tick {
   : if (is_just_entered () && is_at "cave") {
      : if (is_worn "helmet") {
         : print "ROCKS SLIDE ....." ;
         : press_any_key ;
         : print "YOUR HELMET PROTECTS ....." ;
         : press_any_key ;
         : print "YOU WIN!" ;
         : win_game ;
      }
      : else {
         : print "ROCKS SLIDE ....." ;
         : press_any_key ;
         : print "NOTHING PROTECTS YOUR HEAD ....." ;
         : press_any_key ;
         : print "YOU LOSE!" ;
         : lose_game ;
      }
   }
}

1.50. Room Escape Game

Below is a small room escape game written in Adventuron.

start_at = cell

locations {
   cell : location "You are in your cell. You see a door, a bed and bland wallpaper adorns the walls." ;
}

objects {
   wallpaper : object "a strip of wallpaper" ;
   pen       : object "a pen"                ;
   key       : object "a small key"          ;
}

booleans {
   is_key_in_keyhole   : boolean "true"  ;
   is_key_on_paper     : boolean "false" ;
   is_paper_under_door : boolean "false" ;
}

vocabulary {
   : noun / aliases = [wallpaper, paper]
}

on_command {

   : match "search bed; examine bed"  {
      : if (has_not_created "pen") {
         : print "You find something" ;
         : create "pen" ;
         : press_any_key ;
         : redescribe;
      }
   }

   : match "examine wallpaper"  {
      : if (has_not_created "wallpaper") {
         : create "wallpaper" ;
         : print "A piece of the wallpaper falls away" ;
         : press_any_key ;
         : redescribe;
      }
      : else {
         : print "You think you should leave the rest of the wallpaper in place." ;
      }
   }

   : match "examine door"  {
      : print "A solid looking oak door with a keyhole." ;
      : if (is_paper_under_door) {
         : print "The wallpaper is peeking out from under the door." ;
      }
   }

   : match "examine keyhole"  {
      : if (is_key_in_keyhole) {
         : print "There appears to be a key in the keyhole on the other side of the door." ;
      }
      : else {
         : print "The key is no longer in the keyhole.\nYou can't see anything else of interest due to the darkness." ;
      }
   }


   : match "slide wallpaper;insert wallpaper;place wallpaper"  {
      : if (is_carried "wallpaper") {
         : if (noun2_is "door") {
            : if (is_paper_under_door) {
               : print "You can't slide it under the door any more." ;
            }
            : else {
               : print "You slide the wallpaper under the door." ;
               : set_true "is_paper_under_door" ;
               : destroy "wallpaper" ;
            }
         }
         : else {
            : print "Where?" ;
         }
      }
      : else {
         : print "You don't have it." ;
      }
   }

   : match "poke keyhole; poke key; insert pen; put pen"  {
      : if (is_key_in_keyhole) {
         : if (is_carried "pen" && (noun2_is "pen" || noun2_is "keyhole" )) {
            : set_false "is_key_in_keyhole" ;
            : if (is_paper_under_door) {
               : print "The key falls onto the paper." ;
               : set_true "is_key_on_paper" ;
            }
            : else {
               : print "The key falls onto the floor behind the door and bounces away." ;
               : print "More planning is perhaps required." ;
               : print "GAME OVER" ;
               : end_game ;
            }
         }
         : else {
            : print "Your finger is too big." ;
         }
      }
      : else {
         : print "The key has already fallen" ;
      }
   }

   : match "pull paper; get paper"  {
      : if (is_paper_under_door) {
         : pocket "wallpaper" ;
         : if (is_key_on_paper) {
            : print "You pull the paper back from underneath the door. You also take the key that is resting upon it." ;
            : pocket "key" ;
         }
         : else {
            : print "You pick up the wallpaper." ;
            : pocket "wallpaper" ;
         }
         : set_false "is_paper_under_door" ;
         : set_false "is_key_on_paper" ;
         : press_any_key ;
         : redescribe;
      }
   }

   : match "unlock door; open door"  {
      : if (is_carried "key") {
         : print "Using the small key, you unlock the door, open it, and continue onward to your next adventure." ;
         : press_any_key ;
         : end_game ;
      }
      : else {
         : print "The door is locked" ;
      }
   }
}

1.51. The difference between :done and :return

: done will always end the current event handler immediately, no more instructions will be executed.

: return (in a subroutine) will exit the current subroutine, but resume execution the line after the line that called the : gosub.

This will print "One".

start_at = my_location

locations {
   my_location : location "" ;
}

on_tick {
   : gosub "my_subroutine" ;
   : print "Two" ;
}

subroutines {
   my_subroutine : subroutine {
      : print "One" ;
      : done ;
   }
}

This will print "One", "Two" (on different lines)

start_at = my_location

locations {
   my_location : location "" ;
}

on_tick {
   : gosub "my_subroutine" ;
   : print "Two" ;
}

subroutines {
   my_subroutine : subroutine {
      : print "One" ;
      : return ;
      : print "Error" ;
   }
}

1.52. Phone Ringing

Playing beeps with pauses between them can make for some interesting effects.

Note
There is no interrupting this phone ring.
start_at = my_location

locations {
   my_location : location "You are in a room." ;
}

integers {
   tmp : integer "0" ;
}

on_describe {
   : gosub "phone_ring" ;
}


booleans {
   // sysvar_bool allows access to boolean system variables
   is_sound_enabled : dynamic_boolean ( sysvar_bool "sysvar_sound_enabled" ) ;
}

subroutines {
   phone_ring : subroutine {
      : print "The phone is ringing ..." ;

      // Without checking for sound being enabled
      // then the pauses will occur on clients without
      // sound playback
      : if (is_sound_enabled) {
         : gosub "phone_ring_solo" ;
         : pause "50" ;
         : gosub "phone_ring_solo" ;
         : pause "1500" ;
         : gosub "phone_ring_solo" ;
         : pause "50" ;
         : gosub "phone_ring_solo" ;
         : pause "1500" ;
         : gosub "phone_ring_solo" ;
         : pause "50" ;
         : gosub "phone_ring_solo" ;
      }

   }

   phone_ring_solo : subroutine {
      : set_integer var = "tmp"  value = "0" ;
      : while (tmp < 20) {
         : if ((tmp % 2) == 0) {
            : beep millis = "20" pitch = "3" ;
         }
         : else {
            : beep millis = "20" pitch = "7" ;
         }
         : increment "tmp" ;
      }
   }
}

1.53. Using Google Fonts (Modern Fonts)

Google hosts hundreds of fonts, and if you want to use them in your game, then you should go to the site.

Once in the site, use the browser, and the search tools to find the font you want, then click "Select This Style".

There is an icon in the top right of the screen that if clicked, will reveal a tab called "embed". Inside this tab, you will see a URL for your font.

The URL will have the format (the url shown below is just an example):

https://fonts.googleapis.com/css2?family=Montserrat&display=swap

In your web browser, in a new tab, go to the URL that Google fonts gave you.

You will see a piece of text like this:

/* cyrillic-ext */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/montserrat/v15/JTUSjIg1_i6t8kCHKm459WRhyzbi.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/montserrat/v15/JTUSjIg1_i6t8kCHKm459W1hyzbi.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/montserrat/v15/JTUSjIg1_i6t8kCHKm459WZhyzbi.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/montserrat/v15/JTUSjIg1_i6t8kCHKm459Wdhyzbi.woff2) format('woff2');
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/montserrat/v15/JTUSjIg1_i6t8kCHKm459Wlhyw.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

From here, select ONE OF the urls contained in the page (copy and paste), e.g.

Note
Do not select two fonts in this step, only one url per font entry in Adventuron.
https://fonts.gstatic.com/s/montserrat/v15/JTUPjIg1_i6t8kCHKm459WxZYgzz_PZw.woff2

Use this font, and the font name, together with the template below to use the font in your own games.

Note
In future, there will be a MUCH easier method of applying this procedure.
Note
Modern fonts tend to look better with a light theme (as shown below), but feel free to experiment.
start_at = lab

locations {
   lab      : location "You are in a lab." header = "Lab" ;
   corridor : location "You are in the corridor." header = "Corridor" ;
}

connections {
   from, direction, to = [
      corridor, east, lab
   ]
}

themes {
   my_theme : theme {
      theme_settings {
         font                   = userfont_montserrat
         columns                = 72
         textbox_capitalization = upper
         header_capitalization  = original
         layout                 = SB G D X O
      }
      colors {
         paper            = #eee
         pen              = #444
         border           = #226
         status_bar_paper = #339
         status_bar_pen   = #eee
      }

      status_bar {
         : header_text;
         : fixed_text "House of Secrets" ;

      }

      screen {
         content_width                = 512
         border_mode_vertical_percent = 2
         padding_horz                 = 1
         snap_mode                    = free
         paragraph_spacing_multiplier = 0.5
         status_bar_padding_top       = 1
         status_bar_padding_bottom    = 1
      }
   }
}

assets {
   fonts {

      userfont_montserrat : ttf {
         // Extracted from https://fonts.googleapis.com/css2?family=Montserrat&display=swap
         filepath     = "https://fonts.gstatic.com/s/montserrat/v15/JTUSjIg1_i6t8kCHKm459Wlhyw.woff2"
         snap_vert    = "false"
         // This is the balance between average vertical pixels and average horz pixels for
         // use in mapping the font's sizing to the 'columns' property in the theme settings.
         horz_pixels  = "8"
         vert_pixels  = "15"
      }
   }
}

1.54. Advanced Graphic Compositing

EXPERIMENTAL - SUBJECT TO CHANGE

Currently can only try this here: https://adventuron.io/beta

In the course of your adventure, you may wish to display dynamic graphics that respond to the state of the game.

As of beta 63, Adventuron permits authors build up images, conditionally, layer by layer, utilising the transparency chanenel in a similar way to old cartoon compositing also similar to the "layers' feature of art editors.

Adventuron beta 63 contains an EXPERIMENTAL conditional compositing feature.

Note
There are limitations to the current compositing renderer which means that compositing should only be performed using static PNG files (not animated PNG or GIF files).
Note
The dimensions of the main image and the overlays (overlay images) should match up so that the perfectly align.
Note
The overlay command also supports x and y co-ordinates, so technically you can programatically build a scene from tiles (never tested).
Note
There exists no current way to overlay anything other than images right now. Using the user font directly is not supported.
Note
on_render{} code (local to image, and shared across all images) is only executed when an image is rendered. Typically an image is only rendered when displayed the first time, or via update graphic. See an example of how to update the graphic after each turn transparently.
TODO

Provide better sample code

NOTE : Overlayed images are not (currently) composited, only the top level image are composited. Placeholder graphics are also not composited.

start_at = hills

game_settings {
   ## Recommended to use this setting to avoid flicker
   experimental_anim   = fade_in
}

booleans {
   is_circle_here : boolean;
   is_square_here : boolean;
}

locations {
   hills  : location "You are standing by some hills.\n (type WAIT a few times)." {
      on_tick {

         : set_boolean var = "is_circle_here"  (
            chance(50)
         );

         : set_boolean var = "is_square_here"  (
            chance(50)
         );

         : if (is_circle_here) {
            : print "The circle is here." ;
         }
         : if (is_square_here) {
            : print "A square is here." ;
         }
         : if (is_circle_here == false && is_square_here == false) {
            : print "The shapes are not here !" ;
         }
      }
   }
}

themes {
   my_theme : theme {
      theme_settings { layout = "H G LOCK D X O" }
   }
}

on_render {
   // Draw TWO squares so we demonstrate the x,y offset
   : if (is_square_here) {
      : overlay "square" ;
      // Offset the overlay (x = 0, y = 0, means plot from top left)
      : overlay "square" x = "8" y = "8";
   }

}

on_post_tick {
    : update_graphic ;
}

assets {
   graphics {
      hills : base64_png "iVBORw0KGgoAAAANSUhEUgAAAQAAAABQAQMAAADMRTlmAAAABlBMVEUAougisUwFNhwiAAAAiklEQVRIx+2QsQ2DQBAEHzlw6BJcikszpVHKl0BIgH4BLSchoREBIrsJ/0Z/u1eSJEnu8NNK5flHGw3nnUxPwltm4ARmIkE7DTcEIHwVjPH0OnzXFem04++znATVw845jlQERMcrQSj0jsDC4AgsVBdiYfQGFiZXZqH5zBawpyxgCllgUkghhaeEBaRmyX658w60AAAAAElFTkSuQmCC" {
         : if (is_circle_here) {
            : overlay "circle" ;
         }
      }
      square : base64_png "iVBORw0KGgoAAAANSUhEUgAAAQAAAABQAQMAAADMRTlmAAAABlBMVEUAAAD/AAQckkk7AAAAAXRSTlMAQObYZgAAAC1JREFUSMdjGAWjYFgDxv9Q8IBsBQ1QRsKoglEFowpGFYwqoLkCygvzUTAAAACIpEkwudsRUgAAAABJRU5ErkJggg==";
      circle : base64_png "iVBORw0KGgoAAAANSUhEUgAAAQAAAABQCAMAAADBVVsXAAAAgVBMVEUAAAD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AD/2AApCodgAAAAKnRSTlMA+wS3LRqAavfm30INCNWqnl9UJnEgFNvEvrONZEt48+58OK9YUITPlaNJYHIUAAAC00lEQVR42u3aaXPbIBAG4EXovg/Lui/fef//D6zdNJ02naRJLDLg4fkuJCRgV7CkaZqmaZqmaZqmaZq2GqMIejtNmtj38cz34yZJ7T4oDHpsRX5qK4a3sao95QU9ImM38hk/+U1qO4fALEt6VpZmcHDstPHx08zH3WONhW2fTLjyWjs3I3pbZOZ26+FqSvotPQbLaRiA+HIo6WPKwyUGwBrHIuUNCQPcti/pc8q+dQGWDKSy0PEBxrPwa1dnnAG+E5KiwsUD/PGeqbwdfcBblHwFhuMB9WDc28xQA56jXlDYxUAd0BqCGoh3pJTwAlQ5rSWvgItK86CIweyI1hPZDLE6GeJxwtmkdZlnTDmpYQOkEa0tSoENqWDz60HVaXhdPVhGYmQMPckuYAI/0wYsILmFHk4kzgme5NEwRW2QOEaNlGRmwi1IpMKFSRJL0JFYJ+xJXhZzLRLLcpnEuyQO9iTaHg5JK0FGomVISFoxTBLNREzSYohItAiMpAXQP9S8iR4BX1N9yxpQkbT23xEFZM6EHDyRaE8ybwpYzN2SWFupM0HisEksG5wktoPgnwHLhdwnBE9oSaRW5kT4xpqFToIFs8wrwM3AcCBRcjD5j8s3YDmJkTOZQ+BvJ7CeROgZOlLBCHQGrc3ogIXUkDHUBa2rqMEyUsXOg7sxaD3GxoUndwLwtzAF4oDWEsRAKvmJyGuDD3CT1mBywJc//L0WLTPAA7pXwIF5iUhB224GYie8q84uBuZO2ZrR7eIBbnKM6CuiY+IC3qJs92+MnAOYkuyzvdhmyQSA5+oVyL1WjjWuzt3Roo+xjt0ZV/VY0mOwHO7iyt+Px8KgtxnFcdz7uHL5I1RK/yEK7GbCDat4amd5UJQvPbTKIsgzO+UVw83U2IGSy/7/GOah4x7e4/HuYKo/7d8VmoNjp21T+d5Lr/2qaVPbGUzFsj1N0zRN0zRNe0A/APg0RzVgW4cEAAAAAElFTkSuQmCC";
   }
}

1.54.1. Tile Overlays

Below is an example of how to create a Basic tile overlay system. This system will be greatly simplified in a later release of Adventuron.

The tile based system uses the collections {} mechanism to load a collection with the tile overlays to be rendered (after every tick), then Adventuron (in the on_render {}) block iterates over the visible tiles, and lays out the co-ordinates based on the user requirements.

Note
Currently there is no mechanism for adding 'hotlinks' to tiles, but this will be added in a future release. Game should never depend on tiles or hotlinks, and games should be playable without graphics if that is the user preference.
start_at = river

collections {
   visible_tiles : list;
}

locations {
   river : location "You are next to a river." ;
   forest : location "You are in the forest." ;
}

connections {
   from, direction, to = [
      river, east, forest,
   ]
}

objects {
   bennie : object  "Bennie" at = "river" ;
   suzie  : scenery "Suzie"  at = "river" ;
   mark   : scenery "Mark"  at = "forest" ;
}

on_pre_describe {

   // This is only required if some temporary information must
   // be prepared for use in on_render {}

   // For on_render {} blocks with simple expressions, then not required.

   : gosub "setup_graphic_state" ;
}

on_post_tick {

   // No need to render after we enter location for the first time
   // as already rendered the location before executing the on_post_tick{} block
   : if (is_just_entered ()  == false) {
      : gosub "setup_graphic_state" ;
      : update_graphic ;
   }
}

subroutines {

   setup_graphic_state : subroutine {

      // Clear down the visible tiles
      : clear "visible_tiles";

      // Adds tiles in order depending on if the tile SHOULD be displayed
      : if (is_beside "bennie") {
         : collection_push { collection = "visible_tiles" item -> ("bennie") }
      }

      // Adds tiles in order depending on if the tile SHOULD be displayed
      : if (is_beside "suzie") {
         : collection_push { collection = "visible_tiles" item -> ("suzie") }
      }

      // Adds tiles in order depending on if the tile SHOULD be displayed
      : if (is_beside "mark") {
         : collection_push { collection = "visible_tiles" item -> ("mark") }
      }

   }

}

on_render {

   // Iterate over the visible tiles collection !

   : iterate "visible_tiles" {

      // In this example, our tiles are 36 pixels across, and we use a 4 pixel margin
      // between tiles, so therefore we multiply the index by 40 (36 + 4), we also add
      // a 4 pixel margin to the left.

      // For the y axis, we lock at 4 pixels offset.

      // NOTE : This approach works when there are a limited amount of tiles you want to draw, but
      //        you'll need to add in a wrapping formula if there are too many tiles.
      //        (not documented here).

      : overlay {
         graphic -> (item())
         x       -> ( 4 + (index() * 40) )
         y       -> ( 4 )
      }
   }

}

assets {

   graphics {

      bennie : base64_png "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkAgMAAACcbnALAAAADFBMVEUAAAD///+IABX/rskxW5mNAAAAwElEQVQY02XOMQrCMBQG4GerEALSOnR3LDmERGj2FgxdBK/gATp0FE8ROpV3CvEOvYdLN6UmL01BfNPH/z94D3QYkOBn9SMOUet1ZxEI4ySYQFLOuptB1+459ka0Voxjh+jEUSBSC5EFGpLL8iDTk+KnAGoPn2ny10at6xdpsM81i85eMoNZp0wuOpJoL0gt0n8ala4a0lbC6kpKbOvuqjIedP2wKlL7zAWcdkUyvZ1UtQEAn6k0aF1aUFvMmQ7zBdU/TfddRk0nAAAAAElFTkSuQmCC";
      forest : base64_png "iVBORw0KGgoAAAANSUhEUgAAAUAAAABgAgMAAAARJUO7AAAACVBMVEUAAAAisUy5elffMhtoAAADSElEQVRYw+3XW6rbMBAG4JkgQfomig19zEMfuowpnAWIEu8nS69P5/Irlk9uuC2lNkTI9sxneWRshfbtqW2aN9nQyzwRDRuCQ+ZKLNuB45kyhrjNxmfi5xLe636zjkTyiFO9Q3Q7g4UGt6f6YVgqiF8te46eELNKlUoX51crLZjWJniSAGnSEFmJzG8WVZoLlBWw0DhJ1nGhMrmfM1ZXEcNGBMyMAhL9gZyVfsKzHUI1RHeDE+WTxVtBtJV++lhBFgez54HnCpDKAqw9aEzhZmzOc+SWFZAFxwGS6NnhLC5gQENMvAAsHpF1P6ozaT0MzNbBc47witlMAHXk4c3pBvqNlChhyOIDTWtgacBJ9PaKP1B2jjsQDxUci5YAecI8sTJLsLQgi9wGc8yT9lMHKsXufCeAuJZ4HK7BSlSNdchh8G93QbwFOI6mZl6Ha7B2IAP0NmkgR17SBhEApQUHOwUw6zC1w4Gkpmz1Q9DjLEiu7nhYgKDyDZD1lKZLDCJZJwFsb1bug+jb90A7C3DEc3gXZDLCo1IHIjW/DBaA7ngtAQ43wXIDrMjcBGSADBApqFDuQaYAkfUwmHow9SAEtdfB7OBZI5CfAaKH1HUwEKwx2IMY47Jefga0rz7eEC2Ii7ZFZDCg8dGI+R8BWkT32GCh0a2UAOKMNOBwBZYWxMDGDhS8Y/BkMpg1cMBeFdTBQFTO7jt1IMZqyQDFe8kMJoB4YQ8Au1kGswrmHizmAKy6g60ATAswAcRXmBUOUJZ/GFIPlh70+6qWjbVat9rj8IU/BpFu4wJoYweYAtRsLAa4AxmMnsaDDJAp1q3lAZA7MF2DieXqnRWfNSxeMSkAvelHWOkcoNq5rWNpQK/qspnOjZfxLncmxT0tQcRUv2iW92YS67Zv2OLW4ODoOySeiRhrEr52p3Gat1gZH+LR+uJzokdQTDsc4HH+fVVwvBB9067uzWDMx9Hn35s8+tPLHnNx8GTBF+TZ6YP2HLx4KX7YUDFfx/vgaRX89OvIZzulFX8ZPOhP2wMWGRuCQ0S/DkboxUZ3fA08rYOyLTg3/w542MHtwNPfAi876Ok7+DB4oB3cwT8Enj4Gjzu4Cp528H8Ej78BpKfAn1/m1JAhT3GdAAAAAElFTkSuQmCC";
      mark   : base64_png "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkAgMAAACcbnALAAAADFBMVEUAAAD////tHCT/8gBwU4pvAAAAs0lEQVQY013OQQrCMBQE0OmnhRIEW/AEroIrwW2xXfQCBXuf4EmCK8kpeoNepdBtICY/iaCzegyzGMw5GBBTBB03m3S9uYV1WGwX5ZztXVK3JtndRe1rt1tWv8GAdTdGvVkSgGBpL2KpXykvgiEDFr1qUkECzxqsGuevLiILZJJC/qUJFCUvILCEhgwinuVXAvmpjBLG6CaoHFE8msGraqupbLkryrY5RVXTyB18V4TdnPMBv79AfyttvmEAAAAASUVORK5CYII=";
      river  : base64_png "iVBORw0KGgoAAAANSUhEUgAAAUAAAABgBAMAAACeZbYbAAAAD1BMVEUAAAAAougisUy5eld/f38YF2GhAAAEgElEQVRo3uzYYW7bMAyGYY3QAdYbEB92EG/w/c+0OXH8WhYZO06y9kcJtKMEm3oiKs7S8h3f8abQHF7eHPWkr5g0Jfa8cHgD0HwiHhKOw6cAr71FeKd2Hf4/EOeEs3xphJ8DnPfQw3osXcfxEND0eqA5TWY1SaI4J3EH6PYsUP2UX5jFJC2rTZlRPO+z+Rpo5VmgueQRsEhzWuc5RizUaeXNRX4WyBmZfnRBKthUa4CKi9NvUytSeQZIQ2akmGQJgHDTUEHE8GigB+htfV/NLnll5DmQfjDy1wKp5k0+cKG49QBQ85Q5S9bDQEAxkN8DXbc9IPfdEnP5JRm7R/hIZEAPgXYbAFQJgBze/myQaE5qByStQwxU9AgE6AA9AdpMi4C0wxogEiICUkoxsAC0EgKlBc4/tqleMmDT1yEAsqwS4LKcB0BTcxt3Uz0DkqGNgM7z4DiwRp+RjqOI6g8AawdsX6IS4PRTWcypZIWgw+yg1i/Dd4CMALIqshhY9oDGzTOQgafA2gLHDmjlANA6YPCI570G0JgDGLSSYQf0/DPEAZYEqO5WNUNKnAPWe8DSATmidegaTDP9HjA7gvvAnoaWZ3b7DF43mBnFhfaBjDmrW6CFQFZw5q+J+LrPM/oYEEcOLAeAxgrWAn3eM4b+9YDFOiDXcPhOAtkBhseBIN4PJB4H2h6QEawHgCUF8tjTHSAKbB3JmXsSaCHQjwD9DHDIgIi2PupqC5QXcQsyCikB2suAzZIAF73xKri6NXh4PtulTgN1D6jbL0WfO54BSfwEkLsCWbRT7RZqB0h1nQYaKcwQCERcmgF9A7R3An3LNOjQWoZve+wRsI7/4vfmC/KQcCBH0DqyJyzPwDMgxczXNCwft+A/2Z0nFLPIVKusgb+utbHQ2BxIDqIFhsJ9oC4YgHwrwUKHc6DWQNIfC/Anf8VrpJ4BIQVAVDOQjgNrgL5u8D0gx/F6EDZAWy6JgLbKxYw2H0M8KqnqE2yOQnwQTAYihsvxjYAiHyBfmvKn2eIA2MLYQIBJeOy1+KseQHKb9mbwAGj5UwYgPY7DjgNFHr5HeJvP8bcdO0pSGITBAEzKBZIbMJyA2SPs/Q+1L+v8U2IaClOLyv9ixtb4CVgrv9UPiYq4QLxU7dCgwDLT9wcAqo6oHKA9x3q/vBpAcHBQAeE+CyTxhtD87Knib/ABiEvLvk3WrXMXUGsUcMvW3/ScsNmm2qDyZxhpAJZHAZ+xUYRFiZM1MPw3jM4AOkA0LPURVeUtJ9x47YEJXWpgcIDOHBerADDkuBvThKnN6HMOKOPAH5yzxd1bJcjDIBBpB8bqEXXUF6ReIF0KxLrtBooK9wNxsP6il3GgP4RwnQCmQSBdDAzlAiDPBJQbgPEEkORZFrAdKE/DC4indB97hpFpgGKED4FlATHDVr4IGEeAYoYXsAVIYmcKoPQBY5gByBMASY6ygD5QDsO3A+nNgXI7UN4dyBawvAZIswPFzfRAvhDor2X6AKDcCpTJgdQC5AW0+0hTFtDsQ21AHgX+Aa7uAZMdfWmAAAAAAElFTkSuQmCC";
      suzie  : base64_png "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAD1BMVEUAAAD/////fyc/SMx/f38iyouEAAAA3klEQVQoz3XSAQ6CMAwF0G5cYHU7QC0XUDjBEu5/Jv/aEnXor8zspWWQQHwJFZqS/pGSLTJRzipYThJSEfyyapBKxqUE9i5MeEASNHYZXZtmqFMWUt1v9/oY84Mkr9u21hvzjgkZRMudPXUTJZDQ7nAwKxkRPbHtdq0kJ7XeeRQ/yGgZM72hvujorR38TTwT0o4+E+IUJ1o+bm+P2kHvLpts9vQ1yCdxKIicoi0ODDpfvH7Q4gZxchuhkxgaCULNFMU8tuyUStwxoYywpIS+Mqg44Z8JgwXE5ff3xZe8AKyCJNZFs1MxAAAAAElFTkSuQmCC";
   }

}

1.55. Adventuron Plugins (Web Based Clients Only)

Note
None of this is tested or working yet - it should really be in here yet!

Adventuron will have an plugin structure in which it is possible to run external JavaScript code via a command inside your game:

: experimental_javascript "function_name" asynch="true|false";

Javascript Code:

class AdventuronPlugin {

    exec(functionName, functionParameterArray) {
       switch(functionName) {
          case "debug": {
            window.console.log(functionParameterArray);
            break;
          }
          case "console": {
            window.console.log("HELLO WORLD!!!!");
            break;
          }
          default: { break; }
       }
    }

    execAsync(functionName, functionParameterArray, callback) {
       switch(functionName) {

          case "debug": {
            window.console.log(functionParameterArray);
            callback.success();
            break;
          }

          case "wait": {
             setTimeout(function() {
                console.log("Forces Adventuron to wait for 5 seconds then calls success callback.");
                callback.success();
             },5000);
             break;
          }

          case "console": {
            window.console.log("HELLO WORLD!!!!");
            callback.success();
            break;
          }

          default: { callback.fail("Invalid function name"); }
       }
    }
}

1.56. Disabling "GET ALL", "TAKE ALL", etc

game_settings {
   enable_standard_all_behaviour = false
}

1.57. Accessing System Messages + Metadata

Adventuron allows authors to interrogate the system messages and metadata. This can be useful if an author wants to re-use the system messages on an ad-hoc basis in the game, but does not want to have to copy and paste messages. By referencing the system messages, you can be sure as the system_messages {} section is edited, that all references are updated automatically.

Adventuron doesn’t provide commands or aliases for all of its system messages, for performance reasons, and to reduce code size, therefore all system message categories have a lookup number, which must be used by the author.

Messages numbers between 0 and 8999 represent system messages (based on current user theme or default theme).
Messages numbers 9000 onwards represent information provided in the game_information {} section.

all_treasures_found_win_game                   = 1000
already_in_container                           = 1010
ask_new_game                                   = 1020
ask_quit                                       = 1030
be_more_specific                               = 1040
cannot_carry_any_more                          = 1050
cant_see_one_of_those                          = 1060
cant_take                                      = 1070
dont_have_one_of_those                         = 1080
door_bang_into_door                            = 1090
door_cant_close_closed_door                    = 1100
door_cant_lock_already_locked_door             = 1110
door_cant_lock_door_with_it                    = 1120
door_cant_lock_non_lockable_door               = 1130
door_cant_lock_open_door                       = 1140
door_cant_open_locked_door                     = 1150
door_cant_open_open_door                       = 1160
door_cant_unlock_already_unlocked_door         = 1170
door_cant_unlock_door_with_it                  = 1180
door_cant_unlock_non_lockable_door             = 1190
door_close                                     = 1200
door_door_slams_shut                           = 1210
door_lock                                      = 1220
door_lock_door_with_what                       = 1230
door_no_door_here                              = 1240
door_open                                      = 1250
door_unlock                                    = 1260
door_unlock_door_with_what                     = 1270
exit_list_additional_exits_are_located_verbose = 1280
exit_list_end_text                             = 1290
exit_list_end_text_verbose                     = 1300
exit_list_from_here_you_can_go_verbose         = 1310
exit_list_header_concise                       = 1320
exit_list_last_sep_verbose                     = 1330
exit_list_sep_verbose                          = 1340
exit_list_default_towards_unknown              = 1341
exit_list_there_are_no_obvious_exits           = 1350
exit_list_to_the_verbose                       = 1360
exit_list_you_can_also_go_verbose              = 1370
gamebook_question                              = 1380
i_cant_do_that                                 = 1390
inventory_list_bullet_point                    = 1400
inventory_list_empty                           = 1410
inventory_list_end_text                        = 1420
inventory_list_final_separator                 = 1430
inventory_list_header                          = 1440
inventory_list_header_verbose                  = 1450
inventory_list_separator                       = 1460
it_is_dark                                     = 1470
must_remove_first                              = 1480
not_carried                                    = 1490
not_present                                    = 1500
nothing_here                                   = 1510
nothing_to_get                                 = 1520
object_list_bullet_point                       = 1530
object_list_empty                              = 1540
object_list_end_text                           = 1550
object_list_final_separator                    = 1560
object_list_header                             = 1570
object_list_header_after_initial               = 1571 **
object_list_header_mobile_after_initial        = 1572 **
object_list_header_verbose                     = 1580
object_list_header_verbose_after_initial       = 1585
object_list_separator                          = 1590
ok                                             = 1600
on_drop                                        = 1610
on_get                                         = 1620
on_put                                         = 1630
on_put_non_container                           = 1640
on_put_non_surface                             = 1650
on_remove                                      = 1660
on_wear                                        = 1670
post_quit                                      = 1680
prompt_prefix                                  = 1690
prompt                                         = 1700
question_prompt_char                           = 1710
there_is_nothing_you_can                       = 1720
treasure_suffix                                = 1730
unknown_noun                                   = 1740
unknown_verb                                   = 1750
noun_without_verb                              = 1755
worn_suffix                                    = 1760
you_already_wear                               = 1770
you_are_already_carrying                       = 1780
you_are_not_holding                            = 1790
you_cant_go_that_direction                     = 1800
you_cant_wear                                  = 1810
you_dont_wear                                  = 1820
you_see_nothing_special                        = 1830
you_see_nothing_special_2                      = 1835
you_cant_wear_anything_el                      = 1840
ask_undo                                       = 1850
invalid_choice                                 = 1860
verb_noun_only                                 = 1870
tutorial_message_prefix                        = 1880
transcript_start_message                       = 1890
experimental_towards                           = 1900
no_help_available                              = 1910
browser_user_agent                             = 6000 (empty if not running in browser)
browser_platform                               = 6010 (empty if not running in browser)
browser_protocol                               = 6020 (empty if not running in browser)
browser_origin                                 = 6030 (empty if not running in browser)
browser_language                               = 6040 (empty if not running in browser)
browser_href                                   = 6050 (empty if not running in browser)
browser_host                                   = 6060 (empty if not running in browser)
browser_body_client_width                      = 6500
browser_body_client_height                     = 6501
browser_window_screen_width                    = 6502
browser_window_screen_height                   = 6503
browser_window_screen_outer_width              = 6504
browser_window_screen_outer_height             = 6505
browser_window_screen_inner_width              = 6506
browser_window_screen_inner_height             = 6507
browser_device_pixel_ratio                     = 6508
current_theme                                  = 7000
last_item_created                              = 7010
ramsave_name (user data)                       = 7500 (empty string if no ramsave)
adventuron_fullname_version                    = 8000
gameinfo_game_name                             = 9000
gameinfo_game_shortname                        = 9010
gameinfo_written_by                            = 9020
gameinfo_year_of_original                      = 9030
gameinfo_year_of_release                       = 9040
gameinfo_uuid                                  = 9050
gameinfo_short_synopsis                        = 9060
gameinfo_game_version                          = 9070
gameinfo_compatibility_version                 = 9075
gameinfo_license_full                          = 9080
gameinfo_ported_by                             = 9090
gameinfo_translated_by                         = 9100
gameinfo_additional_credits                    = 9110
gameinfo_copyright_message                     = 9120
gameinfo_preview_build                         = 9130
gameinfo_icon_128                              = 9140
gameinfo_icon_512                              = 9150
gameinfo_translated_fron_language              = 9160
gameinfo_notes                                 = 9170
gameinfo_may_be_scary                          = 9180
gameinfo_long_synopsis                         = 9190

To access the messages by number, use the sys(n) command, as shown below:

   // Will print the game name (as described in the game information section)
   : print ( sys(9000) ) ;

Optionally, use dynamic strings, to make your code more readable.

strings {
   game_name : dynamic_string (sys(9000)) ;
}


on_tick {
   // Will print the game name (as described in the game information section)
   : print "{game_name}";
}

1.58. Checkpoints

You may wish to introduce checkpoints in your game, at which point, the game automatically saves.

If you want to protect players from a point of no return, checkpoints (or no points of no return) are a good idea.

The ramload, and ramsave commands can be used to implement checkpoints. Dedicated checkpoint commands will follow in a later release.

Note
ramload and ramsave don’t actually store their games in RAM, they are additional save slots stored on local media, and will survive reboots.
start_at = lake

locations {
   lake     : location "You are by the side of a lake." ;
   forest   : location "You are in a forest." ;
   cave     : location "You are in front of a cave." ;
}

objects {
   lamp : object "a lamp" at = "lake" ;
   coin : object "a coin" at = "cave" ;
}

connections {
   from, direction, to = [
      lake, north, forest,
      forest, north, cave,
   ]
}

booleans {
   // sys(7500) returns the description of the ramsave.
   // If there is no ramsave, an empty string is returned.
   // We convert this into a boolean here !
   is_ramsave_exists : dynamic_boolean ( sys(7500) != "" ) ;
   tmp               : boolean;
}

on_startup {
   // Check if there is a ramsave, and if there is, let the player
   // select if they want to load it
   : gosub "checkpoint_load" ;
}

on_post_tick {
   // On post-tick will always run, even if you have a 'done' statement
   // in any other event handling block of code.

   : gosub "checkpoint_save";
}

subroutines {

   checkpoint_load : subroutine {
      : if (is_ramsave_exists) {
         : ask_bool question="Load from checkpoint?" {
            var = tmp
         }
         : if (tmp) {
            : print "Press ENTER to load from checkpoint ..." ;
            : press_any_key ;
            : ramload;
         }
      }
   }

   checkpoint_save : subroutine {
      // Add as many triggers for your checkpointing as you like.
      // Ramsaves will save state as of the previous tick in the game
      : if (is_just_entered() && is_at "forest" && is_carried "lamp" == false) {
         : ramsave deferred="true"; // Deferral tells Adventuron to save at the END of this tick, otherwise it would use the snapshot from the previous tick.
         : print "Saving checkpoint ..." ;
      }
   }
}

1.59. Assigning Integer Properties To Objects (Stats)

If you want your game to be based on rules, then it helps to be able to set multiple flags per location, object, or even the player.

The following snippet shows how to associate statistics with elements inside your game.

start_at = lake

locations {
   lake   : location "You are by the side of a lake." ;
   forest : location "You are in a forest." ;
}

connections {
   from, direction, to = [
      lake, north, forest,
   ]
}

stats {
   stat_elevation : stat ;
   stat_cost      : stat ;
   stat_luck      : stat ;
}

objects {
   lamp : object "a lamp" at = "lake" ;
   coin : object "a coin" at = "lake" ;
}

integers {

   // If a stat is not found, then Adventuron will return the following 'magic number'
   invalid_value              : integer "-1000001";

   current_subjects_cost      : dynamic_integer ( get_stat { target -> (s1()) stat = "stat_cost" } ) ;

   // 'current_location' is an alias for the current location of the player
   // The current location id will be substituted
   current_location_elevation : dynamic_integer (get_stat { target = "current_location" stat = "stat_elevation" }) ;

   // 'player' is an alias for the player character in the game.
   player_luck : dynamic_integer (get_stat { target = "player" stat = "stat_luck" }) ;

}

on_startup {
   : gosub "setup_stats" ;
   : print ("The player's <LUCK<12>> is " + player_luck) ;
   : press_any_key ;
}

on_describe {
   : print ( "The current location has an elevation of " + current_location_elevation ) ;
}

on_command {
   : match "examine _"  {
      : if (s1() != "" && is_present "*") {
         : if (current_subjects_cost != invalid_value) {
            : print ( "The "+ original ("noun1") +"'s cost is " + current_subjects_cost  ) ;
         }
         : else {
            : print "Could not tell cost of object." ;
         }
      }
   }
}

subroutines {
   setup_stats : subroutine {
      : set_stat stat = "stat_elevation" target = "lake"   value = "0";
      : set_stat stat = "stat_elevation" target = "forest" value = "2";
      : set_stat stat = "stat_cost"      target = "coin"   value = "18";
      : set_stat stat = "stat_cost"      target = "lamp"   value = "25";
      : set_stat {
         stat = "stat_luck"
         target = "player"
         value -> (
            random {
               min -> (0)
               max -> (9)
            }
         )
      }
   }
}

Here is a second example:

start_at = my_location

locations {
   my_location : location "You are in a room." ;
}

objects {
   lamp : object "a lamp" at = "inventory"   ;
   spoon : object "a spoon" at = "inventory" ;
}

stats {
   stat_magic : stat;
}

on_command {
   : match "test _"  {
      : print ( "Lamp magic : " + get_stat { stat -> ("stat_magic") target -> ("lamp") } ) ;
      : print ( "Spoon magic : " + get_stat { stat -> ("stat_magic") target -> ("spoon") } ) ;
   }
}


on_startup {
   : set_stat { stat   = "stat_magic" target = "lamp" value -> (1 + 2) }
   : set_stat { stat   = "stat_magic" target = "spoon" value -> (1 + 5) }
}

In this third example, we can see how to set statistics inline (inside the object definition).

Note
The order in which the entity on_startup {} blocks is undefined, so could be ran in any order.

The top-level on_startup {} always runs after the per-object on_startup {} blocks.

start_at = my_location

locations {
   my_location : location "You are in a room." ;
}

stats {
   stat_magic : stat;
}

objects {
   lamp : object "a lamp" at = "inventory"   {
      on_startup {
         // Do not need to specify target
         : set_stat { stat = "stat_magic" value = "3" }
      }
   }
   spoon : object "a spoon" at = "inventory" {
      on_startup {
         // Do not need to specify target
         : set_stat { stat  = "stat_magic" value = "6" }
      }
   }
}

on_startup {
   : print ( "Lamp magic : "  + get_stat { stat -> ("stat_magic") target -> ("lamp") } ) ;
   : print ( "Spoon magic : " + get_stat { stat -> ("stat_magic") target -> ("spoon") } ) ;
   : press_any_key;
}

1.60. Auto Switch Object1 With Object2 if nothing matches then trying again

This isn’t recommended, but you can flip noun1/subject1 with noun2/subject2 if nothing matches in the on_command{} block with the below chunk of code.

Note that has_command_been_handled() will return true if some action occured (executing the conditional part of a while loop or if statement is not an action).

Note that masked actions (actions inside a mask block) do not flag an action as handled.

If Adventuron reaches the bottom of the on_command{} block without a command being handled, then it will execute a system default action on the input (move in a direction, get something, drop something, wear something, remove something, save, load, verb not recognised, noun not recognised, etc).

In the code below the following sentences will be handled the same way:

  • Talk to troll about sparrow

  • Discuss sparrow with troll

start_at = my_location

locations {
   my_location : location "You are in a room. You see a sparrow and a troll." ;
}

strings {
   tmp : string "";
}

on_command {
   : gosub "parse_then_flip" ;
}

subroutines {

   parse_then_flip : subroutine {
      : gosub "handle_commands" ;

      : if (original "noun1" != "" && original "noun2" != "" && has_command_been_handled() == false ) {
         : mask {

            : set_string var = "tmp"  (
               original "noun1"
            ) ;

            : set_noun1 ( original "noun2" ) ;
            : set_noun2 (tmp) ;
         }
         : gosub "handle_commands";
      }
   }

   handle_commands : subroutine {
      : match "talk troll" {
         : if (noun2_is "sparrow") {
            : print "Jammy" ;
         }
      }
   }

}

vocabulary {
   : verb / aliases = [talk, discuss]
}

1.61. Do All

start_at = my_location

game_settings {
   enable_standard_all_behaviour = false
}

locations {
   my_location : location "You are in a room." ;
}

objects {
   coal      : object "a lump of coal" at = "my_location" ;
   lamp      : object "a lamp" at = "my_location" ;
   red_key   : object "a red key" at = "my_location" ;
   green_key : object "a green key" at = "my_location" ;
}

integers {
   interation_count : integer "0" ;
}

on_command {

   : if (true) {
      : mask {
         : increment "interation_count" ;
         : print ("Iteration count : " + interation_count + ", input : " + sentence_raw() + ",s1: " + s1());

      }
   }

   : match "get all"  {
      : print "Processing do-all (get all)" ;
      : do_all "current_location_objects";
   }

}

1.62. Destroying everything the player is holding

This snippet demonstrates how to iterate over the contents of a location (or the inventory in this specific case), then perform actions on each item().

Also notice that we use the long form of : destroy {} here. Most commands with a single parameter can be converted to the long form of the command, by removing the inline value, and using {} braces instead of the ;.

Once inside the braces, you can press control and space to find the long form attributes of the command, which in this case is 'entity.

Most attributes can take an expression as their parameter by changing the 'key = value' type syntax to 'key → (some_expression).

start_at = my_location

locations {
   my_location : location "You are in a room." ;
}

objects {
   lamp : object "a lamp" at = "inventory" ;
   spoon : object "a spoon" at = "inventory" ;
}

on_command {
   : match "test _"  {
      : gosub "destroy_all_in_inventory" ;
   }
}

collections {
   tmp_list : list;
}

subroutines {
   destroy_all_in_inventory : subroutine {
      : look_inside {
         of               = inventory
         extract_the      = id
         store_results_in = tmp_list
      }
      : iterate "tmp_list" {
         : print ( "Destroying : " + item()  ) ;
         : destroy entity -> (item()) ;
      }
   }
}

1.63. Selecting a random item from a collection of items

Imagine you want to select a random item from a list of items, then you can use the one_of "" string function.

Placing a random item at the start location
start_at = my_location

locations {
   my_location : location "You are in a room.";
}

objects {
   apple : object "an apple" ;
   orange : object "an orange" ;
   pear : object "a pair" ;
}

// Executed at the start of the game
// See the main (non-cookbook) document for all of the system values for one_of, or you can select over your collection itself
on_startup {
   : create {
      target = "current_location"
      entity -> (one_of "_entities")
   }
}

1.64. Local variables

Adventuron has experimental support for local scope variables. Local scope variables must be declared as if they are global, but the scope can be set as local using the 'local' command.

See the example for more information.

start_at = my_location

locations {
   my_location : location "Type <TEST<2>> twice to test local variables.";
}

strings {
   myvar : string "Initial Value" ;
}

on_command {
   : match "test _"  {

      // Will print the global value of a
      : print (myvar);

      // Copies 'myvar' onto a local var stack
      // All updates to 'myvar' are on the local version of 'myvar'.
      // The local version will be destroyed when the containing
      // outer bracked '}' is encountered'
      : local "myvar";

      // We update the local version of 'myvar' here'
      : set_string var = "myvar"  value = "Updated Value" ;
      // We print the local version of 'myvar' here.'
      : print (myvar);

      // The local version of 'myvar' is de-allocated here.'
   }
}

1.65. iterate()

Beta 70 of adventuron adds the ": iterate "key" {}" command.

: iterate "_locations" {
   // Loops over ids of every location
}

: iterate "_entities" {
   // Loops over ids of every object
}

: iterate "_entities" filter -> (is_exists(item()) {
   // Loops over ids of every object that exists
}

: iterate "_ether" {
   // Loops over ids of every object that does not exists
}

: iterate "_inventory" {
   // Loops over ids of every object held by the player (directly)
}

: iterate "_inventory_deep" {
   // Loops over ids of every object held by the player (directly or indirectly)
}

: iterate "_inventory_notworn" {
   // Loops over ids of every object held by the player that is not worn
}

: iterate "_inventory_notworn_wearable" {
   // Loops over ids of every object held by the player that is not worn but wearable
}

: iterate "_inventory_worn" {
   // Loops over ids of every object held and worn by the player
}

: iterate "_exits" {
   // Loops over the long description of the current player location exits
}

: iterate "_exits_visible" {
   // Loops over the long description of the visible exits of the current player location
}

: iterate "_beside" {
   // Loops over ids of every object directly in the current location
}

: iterate "_beside_listed" {
   // Loops over ids of every listed object directly in the current location
}

: iterate "_beside_notlisted" {
   // Loops over ids of every non-listed object directly in the current location
}

: iterate "_traits" {
   // Loops over ids of user defined trait (not system traits)
}

: iterate "_stats" {
   // Loops over ids of user defined stats (not system stats).
}

: iterate "_collections" {
   // Loops over ids of user defined collections.
}

: iterate "_graphics" {
   // Loops over ids of graphics.
}

: iterate "_themes" {
   // Loops over ids of themes.
}

: iterate "_strings" {
   // Loops over ids of string variables (dynamic and non dynamic).
}

: iterate "_integers" {
   // Loops over ids of integer variables (dynamic and non dynamic).
}

: iterate "_booleans" {
   // Loops over ids of boolean variables (dynamic and non dynamic).
}

: iterate "_sentence" {
   // Loops over raw parsed sentence elements.
}

: iterate "_zones_in" {
   // Loops over zones the player is currently in.
}

: iterate "<<specific trait id>>" {
   // Loops over ids of all objects with this specific trait set.
}

: iterate "<<specific collection id>>" {
   // Loops over all elements of this particular collection.
}

: iterate "<<specific location or page id>>" {
   // Loops over all objects(directly) contained in the page or location.
}

: iterate "<<specific zone id>>" {
   // Loops over all locations specifically registered against the zone
}

: iterate "<<specific zone id>>" {
   // Loops over all locations specifically registered against the zone
}

1.66. Traits & Boolean Expressions

Traits can be used as arguments for the following boolean functions:

  • is_carried "trait_id"

  • is_holding "trait_id"

  • is_beside "trait_id"

  • is_present "trait_id"

  • is_exists "trait_id"

  • is_worn "trait_id"

start_at = lakeside

game_settings {
   dark_expression = is_dark
}

locations {
   lakeside : location "You are by the side of a lake." ;
   forest   : location "You are in a forest." ;
   dark_1   : location "You are in the forest (1)." ;
   dark_2   : location "You are in the forest (2)." ;
   dark_3   : location "You are in the forest (3)." ;
}

connections {
   from, direction, to = [
      lakeside, north, forest,
      forest,   east,  dark_1,
      dark_1,   north, dark_2,
      forest,   west,  dark_3,
   ]
}

booleans {
   is_dark : dynamic_boolean (
      is_at      "dark_zone" &&
      is_carried "t_lightsource" == false
   ) ;
}

on_describe {
   : if (is_dark) {
      : print "IT IS DARK!" ;
   }
}

traits {
   t_lightsource : trait;
}

zones {
   dark_zone : zone  {
      locations = [ dark_1, dark_2, dark_3 ]
   }
}

objects {
   lamp  : object "a lamp shining brightly" traits = [t_lightsource]  at="forest";
   spoon : object "a spoon" at="inventory" ;
}

1.67. Buying and selling (Advanced users only)

This example uses traits and stats to attach buyable status and price to objects. Buyable objects cannot be taken, only purchased, and will not be purchasable unless you have the correct amount of money.

This implementation does not have selling yet.

start_at = square

locations {
   square : location "You are in the town square.\nType '<dollar<12>>' to add a dollar to your money stash." ;
   shop   : location "You are in a shop." ;
}

connections {
   from, direction, to = [
      square, east, shop,
   ]
}

booleans {
   is_forsale : dynamic_boolean ( is_beside (s1()) && s1_has_trait "t_forsale" ) ;
}

integers {
   money : integer "0" ;
   // Must disambiguate s1 and confirm item has t_forsale trait before calling this
   s1_cost : dynamic_integer (get_stat { stat = "stat_price" target -> (s1()) }) ;
}

strings {
   money_message : dynamic_string (
      "$" +  (money / 100) + "." +
      ((money % 100) < 10 ? "0" : "") + (money % 100) +
      " cents."
   ) ;
}

traits {
   t_forsale : trait;
}

stats {
   stat_price : stat;
}

objects {
   spoon : object "a spoon"  at = "square" msg = "A silver spoon.";
   lamp  : object "a lamp"   at = "shop" msg = "A dusty lamp.";
   sword : object "a sword"  at = "shop" msg = "A sharp sword.";
}

on_startup {
   : gosub "setup_shop" ;
}

subroutines {

   // Setup the for-sale items in the shop.
   setup_shop : subroutine {
      : add_trait subject = "lamp"  trait = "t_forsale" ; : set_stat target  = "lamp"  stat  = "stat_price" value = "150" ;
      : add_trait subject = "sword" trait = "t_forsale" ; : set_stat target  = "sword" stat  = "stat_price" value = "50"  ;
   }

}

on_command {

   // Just useful in the demo to add money as required.
   : match "dollar _"  {

      : print "You now have added a dollar to your money stash." ;

      : set_integer var = "money" (
         money + 100
      ) ;

   }

   // Must go before all other gets.
   : match "get *"  {
      : disambiguate_s1 "beside";
      : if (s1_has_trait "t_forsale") {
         : print "You must purchase this item." ;
      }
   }

   // Must go before all other examines.
   // Appends price onto items that have the 't_forsale' trait
   : match "examine *"  {
      : disambiguate_s1 "present";
      : if (is_forsale ) {
         : examine;
         : print ("The price tag is " +
            "$" +  (s1_cost / 100) + "." +
            ((s1_cost % 100) < 10 ? "0" : "") + (s1_cost % 100) + " cents."
         ) ;
      }
   }

   // Shop buy routine.
   : match "buy *"  {
      : disambiguate_s1 "beside";
      : if ( is_forsale ) {
         : if (money >= s1_cost) {
            : print ("You purchase " + definite(d(s1())) + ".") ;
            : set_integer var = "money"  ( money - s1_cost ) ;
            : remove_trait { subject -> (s1()) trait = "t_forsale" }
            : pocket ;
         }
         : else {
            : print "You don't have enough money." ;
         }
      }
   }

   // Shop sell routine.
   : match "sell *"  {
      : disambiguate_s1 "carried";
      : if ( s1_cost > 0 && is_at "shop") {
         : print ("You sell " + definite(d(s1())) + ".") ;
         : set_integer var = "money" (
            money + s1_cost
         ) ;
         : add_trait { subject -> (s1()) trait = "t_forsale" }
         : drop quiet = "true" ;
      }
      : else {
         : print "You can't sell that." ;
      }
   }
}

themes {
   my_theme : theme {
      theme_settings {
         layout = SB G D X O
      }
      status_bar {
         : fixed_text   "MONEY DEMO" ;
         : dynamic_text "money_message" ;
      }
      colors {
         status_bar_paper = 1
         status_bar_pen = 14
      }
   }
}

1.68. Multi Protagonist

Adventuron has basic support for multi protagonists in games.

start_at    = my_location
protagonist = bill
locations {
   my_location : location "You are in a room." ;
}

objects {
   fred : character "fred" at = "my_location" listed = "true";
   bill : character "bill" at = "my_location" listed = "true";
}

on_command {
   : match "switch _"  {
      : if (protagonist() == "fred") {
         : become "bill";
         : print "You are now Bill" ;

      }
      : else {
         : become "fred";
         : print "You are now Fred" ;
      }
      : press_any_key ;
      : redescribe;
   }
}

themes {
   my_theme : theme {
      lister_objects {
         include_characters = true
      }
   }
}

1.69. Location + Direction Functions

// 1 = north, 2 = northeast, 3 = east, 4 = southeast
// 5 = south, 6 = southwest, 7 = west, 8 = northwest
// 9 = up, 10 = down, 11 = enter, 12 = exit

: if (direction_between{from -> ("loc1") to -> ("loc2") } == 1) {
   : print "If the direct betwene loc1 and loc2 is north then pring this message.";
}

// Move the player in the direction relating to the current sentence verb
// Do nothing if the location is not a direction

: if (is_movement_command()) {
   : print "Printed if the player typed a movement command. (such as N, NORTH, E, UP, DOWN, ENTER, etc.)";
}


// Move the player in the direction relating to the current sentence verb
// Do nothing if the location is not a direction

: if (is_movement_command()) {
   : print "Moves the player in the direction of the movement command ...";
   : move;
   : press_any_key;
   : redescribe;
}


// Moves in a numeric direction
// NOTE: Does not override any blocks, and if direction is invalid for current
// location, then does nothing

: move (1); // Moves the player north (does not redescribe)
: move (2+7); // Moves the player up (does not redescrive)


// Try an move in a direction.\nWill return the location id of the location we will land on.
// If the move is not valid, it will return a blank string (by default).
// Note : This does NOT actually perform any move or change anything in the game state.
// It simply tests to see if a direction is a valid direction using the game navigation table
// + inbuilt blocking mechanism.
// It does NOT currently utilize and blocks that may occur in on_command {} overrides for directions.

: if (try_move() == "maze_centre") {
   : print "If we can move in the direction represented by the current sentence verb, then moving in that direction would take us to location 'maze_centre'.";
}


// A variation using a different start location and direction
// (Directions in this command are textual).
: if (try_move{ from -> ("maze_edge") direction -> ("north") } == "maze_centre") {
   : print "If we can try to move north from 'maze edge', and that path is not blocked via something in barriers, then moving in that direction would take us to location 'maze_centre'.";
}

// The same command, but ignore blocks
: if (try_move{ from -> ("maze_edge") direction -> ("north") block_level = "0" } == "maze_centre") {
   : print ".... ";
}

: if (is_exit_listed(1)) {
   : print "This message prints if north is a listed exit at this location.";
}

: if (is_can_go(1)) {
   : print "This message prints if north is a direction the player can move in (after applying blocks).";
}

: if (is_connected(1)) {
   : print "This message prints if there is a location north of the current location, whether or not it is blocked or not.";
}

########################
## Conversions
########################

: print (dirnum_to_dirtext(1)); // prints 'north' (lower case)
: print (dirtext_to_dirnum("north"));    // prints '1'
: print (dirtext_to_dirnum("n"));        // prints '1'
: print (dirtext_to_dirnum("junktext")); // prints '-1'

// -------------------------------------------------------------------------------
// Assuming location has exits north, south and east (east is not listed)
// The following snippet would print (iteration order in same order as direction list):
// -------------------------------------------------------------------------------

// Will print north!!!  folloed by south!!!
: iterate "_exits_visible" { : print (item() + "!!!" );  }

// Will print north!!!  followed by south!!! followed by east!!!
: iterate "_exits_all"     { : print (item() + "!!!" );  }

1.70. Dynamic Location Descriptions

Sample source below

start_at = my_location
template = talp

locations {
   my_location : location "You are in a room.{rock_desc}" ;
}

strings {
   rock_desc : dynamic_string ( is_present "rock" ? " A big rock is here." : "" );
}

objects {
   rock : scenery "a big rock" listed = "false" at = "my_location" ;
}

on_command {
   : match "destroy rock"  {
      : destroy "rock" ;
   }
}

1.71. Event Handling Psuedocode

Note
This is a work in progress. Incomplete documentation here.
On Startup Event Handling Priority
// Note this is psuedo-code

procedure onStartup() {

   // Note: all handlers have access to the current parsed sentence

   didSomething = false;

   // Execute the on_startup for each object
   // Order of iteration is not defined (could be any order)
   for each gameObject {
      didSomething = execute gameObject.on_startup;
      if (didSomething) {
         break out of for loop;
      }
   }

   if (didSomething IS false) {
      didSomething = execute main.on_startup;
   }

}
On Describe Event Handling Priority
// Note this is psuedo-code for approximately how
// adventuron runs handlers in relations to a game location redescribe
// events

// IMPORTANT  - Only executed when describing room for first time after
//              entering, and on manual redescribes

// WARNING    - Not executed when 'auto' redescribing a location.

procedure onDescribe() {

   // Note: all handlers have access to the current parsed sentence

   boolean didSomething = execute currentLocation.on_pre_describe;

   if (didSomething IS false) {
      didSomething = execute main.on_pre_describe;
   }

   if (didSomething IS false) {
      didSomething = execute currentlocation.on_describe;
   }

   if (didSomething IS false) {
      didSomething = execute main.on_describe;
   }

}
On Tick Event Handling Priority
// Note this is psuedo-code for approximately how
// adventuron runs handlers in relations to a game tick (unit of time passing)

procedure onTick() {

   // Note: all handlers have access to the current parsed sentence

   boolean didSomething = execute main.on_pre_tick;

   if (didSomething IS false) {
      didSomething = execute currentlocation.on_tick;
   }

   if (didSomething IS false) {
      execute main.on_tick;
   }

   // NOTE: on_post_tick ALWAYS executes.

   execute main.on_post_tick;

}
On Command Event Handling Priority
// Note this is psuedo-code for approximately how
// adventuron runs handlers in relations to a user
// input.

procedure onCommand() {

   // Note: all handlers have access to the current parsed sentence

   boolean didSomething = execute main.on_pre_command;

   if (didSomething IS false) {
      didSomething = execute currentlocation.on_command;
   }

   if (didSomething IS false) {
      didSomething = execute main.on_command;
   }

   if (didSomething IS false) {
      didSomething = execute main.on_command_2;
   }

   if (didSomething IS false AND system_handlers_enabled) {
      didSomething = execute system.default_command_handlers; // Not user configuration
   }

   if (didSomething IS false) {
      print "You can't"; // System message lookup here
   }
}

1.72. The Parser

The following describes how the English parser works in Adventuron, other parsers (such as Spanish) may work slightly differently.

To be completed …​.

1.73. How Nouns Are Registered

Nouns are registered in Adventuron using a variety of methods.

1) Extracting nouns from object ids.

(by default, and configurable) Adventuron looks at object IDs and finds a noun from the rightmost numeric "chunk" of the ID.

For example;

objects {
   lamp : object "this doesn't matter at all for noun registration";
}
  1. This will register the 'lamp' noun.

objects {
   red_lamp : object "just a narrative field, not used for vocab";
}
  1. This will also register the 'lamp' noun, also it will register 'red' as an adjective.

objects {
   red_lamp_2 : object "narrative field again";
}

This will register 'red' as an the adjective again, 'lamp' as the noun, and the number will be ignored. The trailing number is a workaround to be able to declare multiple instances of an object with the same adjective/noun pair.

objects {
   anythingyoulike : object "anything you like" noun = "lamp";
}

The above declaration will register the object with the defined noun, ignoring scanning the id for the noun.

All nouns registered using these methods will be registered without direct association with synonym nouns. It just registers a noun if it does not already exist as a noun.

2) Looking for explicitly registered nouns in vocabulary {} table.

This will register a noun:

: noun aliases = [rope] ;

This will register a noun group:

: noun aliases = [rope, twine, vines] ;

3) Via match records

The following code, anywhere in the gamefile will register 'wibble' as a verb, and 'wobble' as a noun. It does not associate the wobble noun with any other noun. If wobble was declared in the vocabulary section with a synonym, then then any noun synonym of wobble would be matched by the match statement.

// Registers wibble as a verb (prior to game execution in the pre-scan phase)
// Registers wobble as a noun (prior to game execution in the pre-scan phase)
: match "wibble wobble" {
   // Something
}

4) via noun1_is "" and noun2_is ""

If anywhere in your code, you use noun1_is or noun2_is, then prior to game execution, the arguments for these boolean functions will be registered as nouns. These functions only register a single noun. If the referenced noun has some registered synonyms (via the vocab section), then any of those synonyms will return true for these functions.

   // Registers wobble as a noun (prior to game execution in the pre-scan phase)
   : if (noun1_is "wobble") {
   }

1.73.1. Text Replacement

Text replacement has nothing at all to do with nouns.

It is simply a pre-processor for the logical sentence entered by the player.

vocabulary {

   // If Adventuron sees "bucket of fish"
   // somewhere in the sentence, replace with "fish"
   // instead, before parsing the sentence.

   // NOTE: This does not register a noun, it's just a macro for
   //       text replacement.

   // NOTE 2: This will likely be moved into a parser {} block
   //         in an upcoming beta as has nothing to do with vocab really.

   : experimental_replace {
      text = bucket of fish
      with = bucket
   }
}
PLAYER TYPES      : take bucket of fish
ADVENTURON REPLACES : "bucket of fish" with "fish"
ADVENTURON PARSES : take fish

NOTE : This is a lossy transformation, the "bucket of" is completely invisible to the parser.

1.73.2. experimental_matching_text_sequences (objects / scenery / characters)

There exists a secondary way of defining substitution patterns, which is to declare multiple text sequences inside the object itself.

If you use this approach, then you have to declare a noun.

It works just the same way as "experimental_replace". It performs a raw sentence mutation. It doesn’t directly make a phrase understood as an object. It simply provides text substitution.

troll          : scenery "an enormous troll"  at = "outside_cave" msg="Hungry."{
   noun  = "troll"
   experimental_matching_text_sequences = ["enormous troll", "big troll"]
}

1.73.3. Problems with Text Substitution

The big problem that still remains in Adventuron is that the standard matching functions are verb and noun centric, and that the substitution macros can only translate to a raw noun, which may not be unambiguous enough to be coersed into a s1() (object subject) reference.

A solution to this is coming, this is just documentation of how the existing noun + text substitution features work, and why you may observe strange behaviour.

1.73.4. Per-Theme Colouring

Adventuron now supports per-theme accent colours.

Pattern Example Description

<YOURTEXT>

<BEDROOM>

Colours in the text to the 'default_alt1_pen' colour defined in the current theme.

<YOURTEXT<_dir>>

<EAST<_dir>>

Colours in the text to the 'exit_list_item_pen' colour defined in the current theme.

start_at   = bedroom
redescribe = auto_beta

locations {
    bedroom : location "This is the <BEDROOM>. A door leads <EAST<_dir>>[east] to the <HALLWAY>." ;
    hallway : location "This is the <HALLWAY>." ;
}
connections {
   from, direction, to = [
      bedroom, east, hallway,
   ]
}

booleans {
   is_dark_theme : boolean "true";
}

on_command {

   : match "toggle _"  {
      : if (is_dark_theme) {
         : set_theme "bright_theme";
      }
      : else {
         : set_theme "dark_theme";
      }

      : toggle "is_dark_theme" ;
      : redescribe;
   }

}


themes {
   dark_theme : theme {
      theme_settings {
         font = bamburgh
      }
      colors {
         paper = 0
         exit_list_item_pen      = 14
         default_alt1_pen        = 11
      }
   }

   bright_theme : theme {
      theme_settings {
         font = daad
         shader = scanlines
      }
      screen {
         border_mode_vertical_percent = 1
      }
      colors {
         border = 1
         paper                   = 15
         exit_list_item_pen      = 2
         default_alt1_pen        = 1
      }
   }

}

1.73.5. Advanced text substitution

To be documented …​…​

// THIS WILL ADD A DIRECTION LINK FOR ENTER

my_location: location  "From here you can <enter<_dir>> the room2.";

// THIS WILL ADD A DIRECTION LINK FOR EXIT

my_location: location  "From here you can <exit<_dir>> the room2.";

// THIS WILL ADD A DIRECTION LINK FOR ENTER WITH CUSTOM TEXT DESCRIPTION

my_location: location  "From here you can <go inside<_dir>>[enter] the room2.";

// THIS WILL PRINT SOUTH WITHOUT ANY DIRECTION LINKING

my_location: location  "I came from the <south<->>, but exits are north and east. I can also <go inside<_dir>>[enter] the board.";
{objectid ? `Text if  object is located in the player location`}
{init:objectid ? `Text if the object id is in the current player location and has never been taken before`}
      theme_settings {
         text_decorations= [ directions, directions_subtract, entities , entities_subtract, entities_no_repeat ]
      }
   directions = colour in directions in the text
   directions_subtract = remove directions in the main description text from the direction list
   entities = colour in (and link) objects, scenery, and characters, based on their object description (checks that all coloured in objects are in scope), not just brute forcing nouns / adjectives or anything like that)
   entities_subtract = remove mentioned entities from the object list
   entities_no_repeat = don't link entities more than once if mentioned multiple times in the text
start_at   = bedroom
redescribe = auto_beta

objects {
   calendar : object  "a calendar" at = "bedroom" ;
   plate    : object  "a plate" at = "bedroom" ;
   spoon    : object  "a spoon" at = "bedroom" ;
   radiator : scenery "a radiator" at = "bedroom";
}

locations {
    bedroom : location "You are in your bedroom.\nA picture hangs above your bed.{init:calendar ? ` On the nightstand you see a calendar.`} The kitchen is to the east.\nYou are so <TIRED>." {
      contains {
         picture    : scenery "a picture" msg = "You examine the picture above your bed. It's a picture your your wife and yourself on your wedding day. It's dated February 14th.";
         bed        : scenery "your bed" msg = "This is where your <SLEEP>.";
         nightstand : scenery "the nightstand" msg = "There are two nightstands. Both are empty." listed = "false" ;
      }
   }
   kitchen : location "You are in the kitchen." ;
   study   : location "You are in a study." ;
}

connections {
   from, direction, to = [
      bedroom, east, kitchen
      bedroom, west, study,
   ]
}

themes {
   my_theme : theme {
      theme_settings {
         text_decorations= [ directions, directions_subtract, entities , entities_subtract, entities_no_repeat ]
      }
      lister_exits {
         exit_list_style                      = verbose
         exit_list_capitalization             = lower
         experimental_enable_direction_arrows = false
      }
      colors {
         exit_list_item_pen      = 14
         entity_pen              = 12
         entity_immovable_pen    = 13
         link_manual_pen         = 10
         inventory_item_pen      = 11
         default_alt1_pen        = 11
      }
   }
}

1.73.6. Snippets

start_at = banquet_hall

locations {
   banquet_hall "You are in the banquet hall.";
}

on_command {
   : if_examine "table"  {
      : if (has_not_created "key" || !has_trait { subject = "key" trait = "_taken" }) {
         : print "On the table you see a key.";
         : create "key";
      }
      : else {
         : print "There is nothing else on the table.";
      }
   }
}

objects {
   table : object  "a table" at = "banquet_hall" ;
   key   : object  "a key";
}

1.74. A custom inventory list

This inventory routine will list objects verbosely, and seperate wearable and non wearable objects.

It makes heavy use of iterators.

start_at = my_location

locations {
   my_location : location "You are in a room.";
}

objects {

   wallet : object "a wallet" at = "inventory" ;
   pen : object "a pen" at = "inventory" ;
   coins : object "some coins" at = "inventory" ;
   hat : object "a hat" at = "inventory" initially_worn = "true" ;
   coat : object "your coat" at = "inventory" initially_worn = "true" ;

}

on_command {

   : match "inventory _"  {

      : append "You are carring ";

      : if (count "_inventory_notworn" > 0) {

         : iterate "_inventory_notworn"  {
            : if (index() > 0) {
               : if (is_final_iteration()) {
                  : append " and ";
               }
               : else {
                  : append ", ";
               }
            }
            : append (d(item()));
         }
         : print ".";
      }
      : else {
         : print "You are holding nothing.";
      }

      : append "You are wearing ";
      : if (count "_inventory_worn" > 0) {
         : iterate "_inventory_worn"  {
            : if (index() > 0) {
               : if (is_final_iteration()) {
                  : append " and ";
               }
               : else {
                  : append ", ";
               }
            }
            : append (d(item()));
         }
         : print ".";
      }
      : else {
         : print "You are wearing nothing (oh-dear!).";
      }
   }

}

1.75. A custom exit list routine

This exist list routine will list exits, and colour code and add hyperlinks to exits.

This is very similar to the existing "concise" exit list implementation, but this code could be used and tweaked if you have very special exit list requirements.

start_at = my_location

locations {
   my_location : location "You are in a room.";
   my_location_2 : location "You are in a room.";
   my_location_3 : location "You are in a room.";
   my_location_4 : location "You are in a room.";

}

connections {
   from, direction, to = [
      my_location, enter, my_location_2,
      my_location, leave, my_location_4,
      my_location, northeast, my_location_3,
   ]
}

strings {
   exits_string : string ;
}

on_pre_describe {

   : if (count "_exits" == 0) {
      : set_string var = "exits_string"  value = "Exits: None." ;
   }
   : else {
      : set_string var = "exits_string" value = "Exits: ";

      : iterate "_exits" {
         : if (index() > 0) {
            : if (is_final_iteration()) {
               : set_string var = "exits_string" value -> (exits_string + " and ");
            }
            : else {
               : set_string var = "exits_string" value -> (exits_string + ", ");
            }
         }
         : set_string var = "exits_string" value -> (exits_string + "<" + upper(item()) + "<14>>[" + item()+ "]");
      }
      : set_string var = "exits_string" value -> (exits_string + ".");
   }
}


themes {
   my_theme : theme {
      theme_settings {
         layout = H G D P* "exits_string" O
      }
   }
}

1.76. Manual Doors

A better implementation of doors will be coming to Adventuron soon, but in the meantime you can implement doors manually using bidirectional paths, and floating unlisted objects representing doors.

start_at = hallway

locations {

   cupboard : location "You are in a cupboard.";
   hallway : location "You are in a hallway. A cupboard lies to the east.";
   cellar : location "You are in a cellar.";

}

booleans {

   is_door_locked : boolean "true";
   is_door_open : boolean "false";

}


on_pre_describe {

   : if (is_at "hallway" || is_at "cupboard") {
      : create "cupboard_door";
   }

}

barriers {

   barrier_cupboard_door : block_path {

      from          = hallway
      to            = cupboard
      message       = The door to the cupboard is closed.
      bidirectional = true
      block_when_not = is_door_open
   }

}


connections {

   from, direction, to = [
      hallway, east, cupboard,
      hallway, down, cellar,
   ]

}

objects {

   cupboard_key : object "the key to the cupboard" at = "inventory" ;
   cupboard_door : object "The door is {is_door_open ? `open` : `closed`}." listed = "false" ;
}


on_command {

   : match "examine door"  {
      : if (is_present "cupboard_door") {
         : print (d("cupboard_door"));
      }
   }

   : match "open door"  {
      : if (is_present "cupboard_door") {
         : if (is_door_open) {
            : print "It's already open.";

         }
         : else {
            : if (is_door_locked) {
               : if (is_present "cupboard_key") {
                  : set_false "is_door_locked" ;
                  : set_true "is_door_open" ;
                  : print "You unlock the door with your key, then open the door.";

                  : press_any_key ;
                  : redescribe;
               }
               : else {
                  : print "You can't open the door as it's locked, and you don't have the right key.";
               }



            }
            : else {
               : print "You open the door";
               : set_true "is_door_open" ;
            }
         }
      }
      : else {
         : print "You don't see a door.";
      }
   }

   : match "close door"  {
      : if (is_present "cupboard_door") {
         : if (!is_door_open) {
            : print "It's already closed.";

         }
         : else {
            : print "You close the door";
            : set_false "is_door_open" ;
            : press_any_key ;
            : redescribe;

         }
      }
      : else {
         : print "You don't see a door.";
      }
   }

   : match "lock door"  {
      : if (is_present "cupboard_door" == false) {
         : print "Nothing to lock.";
      }
      : else {
         : if (is_door_locked) {
            : print "The door is already locked.";
         }
         : else {
            : if (is_carried "cupboard_key") {
               : if (is_door_open) {
                  : print "You close the door and lock it.";
                  : set_false "is_door_open" ;

               }
               : else {
                  : print "You lock the door.";
               }
               : set_true "is_door_locked" ;
               : press_any_key ;
               : redescribe;


            }
            : else {
               : print "You don't have the right key.";
            }
         }

      }

   }


   : match "unlock door"  {
      : if (is_present "cupboard_door" == false) {
         : print "Nothing to unlock.";
      }
      : else {
         : if (is_door_locked) {
            : if (is_present "cupboard_key") {
               : set_false "is_door_locked" ;
               : set_true "is_door_open" ;
               : print "You unlock the door, and also open it.";
               : press_any_key ;
               : redescribe;

            }
            : else {
               : print "You don't have anything to unlock the door with.";
            }
         }
         : else {
            : print "Door is not locked.";
         }
      }
   }
}

1.77. End of Tutorial

Click here to return to the Adventuron User Guide.