[Top] [Contents] [Index] [ ? ]

Enigma Reference Manual

This manual describes the internals of Enigma version 1.20, in particular how to build new levels using Lua and how to interact with the game engine.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1. Running Enigma

Hopefully, after successfully installing and playing some first levels, you may be interested in some information about how we have configured Enigma, how you can optimize Enigma to your needs, and the purpose of some options and attributes within Enigma.

This first chapter should give you some valuable information about these questions, and provide some basic knowledge you will need to manage level packs, levels or write your own levels, as described in the following chapters.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1 Locating Resources

For reasons of backup, system changes, special configurations, level additions and hopefully your own new levels, you may need to know where Enigma stores the resources and how you can control them.

Enigma maintains several paths for control of load and storage of files. You can list these paths either in the help submenu paths, or by starting Enigma with the switch ‘--log’ (see section Startup Switches) and looking at the console output.

Preferences Path

This is the path to the file that stores your preferences concerning application options. This file is usually located at your HOME directory. For HOME-less Windows users, it is stored in the ‘Application Data\Enigma’ directory. Since it is the third version in the history of Enigma, the file is named ‘.enigmarc.xml’ by default.

We recommend that you backup this file, although it contains only a few data that you can quickly reconfigure.

Since these preferences are quite specific for the operating system and configuration, you will use a different version on each Enigma installation you have.

Mainly for Enigma developers, a switch exists ‘--pref’ (see section Startup Switches) to rename this preferences file. By starting Enigma with a renamed preferences file, a developer can temporarily use a complete separate configuration for testing purposes without the danger of destroying his main configuration. The developer may use it to start Enigma with a plain standard configuration for testing purposes, too.

In all cases, a leading ‘.’ will hide the preferences filename.

User Path

This is the main path to the user’s Enigma data. All updates, user-installed levels and user- written levels, the user’s scores, history and usually the user’s screenshots and level previews, are stored at this path.

A backup of this directory is mandatory!

The standard location is the directory ‘.enigma’ in your HOME directory. For HOME-less Windows users, it is the folder ‘%APPDATA%\Enigma’, what resolves to the subfolder ‘Application Data\Enigma’ on XP/2000 or ‘AppData\Roaming\Enigma’ on Vista/Windows 7 located within your user data folder.

This standard location of the user path is the location of logging and error output files, too.

You can define your own path within the User Options. By doing so, you can store your Enigma user data on a memory stick or on a shared partition, and use them alternatively from two Enigma installations.

User Image Path

This is a second path to the user’s Enigma data, which you can use to access images such as screenshots and thumbnails of levels. Usually this path is identical to the main ‘User Path’.

Just in case you make many screenshots and have limited resources on the main ‘User Path’, you may want to store the images on another path. You can define your own path within the User Options.

System Path

This path gives you the location of all system resources that are distributed with Enigma. Here you will find the levels, libraries, etc. This is a first class address to look for examples if you start writing your own levels.

Resource Paths

This is a list of paths. The program looks up each version-independent resource on all paths in this list, and loads from the first occurrence.

User data precedes system data; this way, updates on your user data path will win. Have a look at this list if you are observing a difference between a source and the runtime behavior. You may have looked at a file that another file had hidden on a preceding path in this list.

l10n Path

This path shows the directory that contains the localization data.

Please note that some resources, like levels, may be zipped. In this case, a resource that you expect to find at ‘dirname/filename’ may be stored in a zipfile named ‘dirname.zip’. The path of the file within the zip can be either ‘dirname/filename’ or ‘./filename’. In case a resource is provided in zipped and unzipped form, the plain file stored in a directory wins, since Enigma assumes it to be an update to the zip.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.2 Startup Switches

Besides starting Enigma by clicking on an installation-provided icon or start menu entry, you can start Enigma from a shell or commandline. This allows you to add a selection of startup switches that are valid for just a single run.

For repeated usage of constant startup switches you can create an desktop icon or start menu entry and append the startup switch to the target string of the Enigma application executable.

The following list explains the supported user options. If an option is listed with a long name preceded by two minus signs, and with a one-character abbreviation preceded by one minus sign, use one of the notations, not both together; for example, ‘--data path’ or ‘-d path’.

--assert

A switch for Enigma developers that forces all debugging assertions, even expensive ones, to be evaluated. The additionally evaluated checks look like ‘ASSERT(noAssert || long_lasting_check(), XLevelRuntime, "remark");’.

--data -d path

A switch for Enigma developers that allows you to add an additional path to the resource paths that precedes the system path (see section Startup Switches). A developer can test an Enigma compilation, without installation, by calling it from the shell with the current working directory on the main directory via ‘src/Enigma -d ./data’.

--help -h

Just lists the available startup switches to the output and terminate.

--lang -l lang

A switch that allows you to override any other language preference. The language is given in the common 2-character sequence as ‘fr’ for French or ‘ru’ for Russian.

--log

This switch turns on logging of internal information to the standard output. Windows users will find an file called ‘Output.log’ in the standard ‘User Path’ folder. An additional file ‘Error.log’ lists severe error messages.

The output will, for example, list the paths described in Locating Resources.

--nograb

A switch for Enigma developers that causes Enigma not to grab the mouse. You can hardly play a level in this mode, but it makes it possible to debug the application in its core parts.

--nomusic

Start Enigma without playing background music.

--nosound

Start Enigma with sound being switched off.

--pref -p filename

The name of an alternative preferences file without the leading dot for hidden filenames. This switch is a pure Enigma developer support feature, as described in Locating Resources.

--pref -p dirpath

The path of an alternative directory that contains the standard named preference file ‘.enigmarc.xml’. If no preference file exists or the directory does not yet exist they are created. On creation of the preference file the user data path is set to the given dirpath per default. This allows to store all Enigma user data in a single directory that can be stored anywhere, e.g. on a USB stick. You always need to call Enigma with the above switch to use this new set up. Remember that a path with spaces needs to be quoted.

--redirect

Redirect the ‘stdout’ and ‘stderr’ to files named ‘Output.log’ and ‘Error.log’ on the standard user path (see section Locating Resources). For Windows this option is always true, but the usage of this option is useful on any operating system if Enigma is started via a desktop icon or a menu button.

--robinson

Disable all connections to the internet. No automatic updates will take place and all user initiated requests that would require an internet connection will fail with an error message.

--showfps

Show the framerate (FPS) during the game.

--version

Just print the version number to the output and terminate.

--window -w

Start Enigma in window mode instead of screen mode.

Enigma interprets all further arguments supplied on the commandline as level file addresses. You can use absolute or relative addresses to level files stored on your computer. Adding url’s to levels stored in the Internet is also possible.

A Unix user may start Enigma with the following command:

enigma --log ~/mylevel.xml http://somewhere.com/netlevel.xml

A Windows user may start Enigma from the command line (please adjust the Enigma installation path):

C:\Programs\Enigma\enigma.exe --log demo_simple.xml

You will find these levels in the levelpack called ‘Startup Levels’, which is only visible by default if you supplied levels on the commandline.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.3 User Options

Ratings update

Please retain this option on the value ‘Never’ until release of Enigma 1.00.

User name

Enter your user name, which Enigma will attach to your scores. Please look at the Enigma home page for user names already in use and choose a new, unique name. You can change your user name at anytime without losing any of your scores.

User path

This textfield allows you to define an arbitrary directory for your Enigma user data as described in Locating Resources.

Deletion of the string resets the path to the default.

Enigma activates the new path when you leave the options menu. Though it stores all files directly to the new path, and will still find files on the old path, you may want to quit Enigma immediately and first copy/merge the old directory to the new location. This copy of your old data is necessary, since with the next start, Enigma will locate user data at the new location exclusively.

User image path

This textfield allows you to define an arbitrary directory for your Enigma user image data as described in Locating Resources.

Deletion of the string resets the path to the default.

Enigma activates the new path when you leave the options menu. Though it has stored all files directly to the new path and files will still be found on the old path, you may want to quit Enigma immediately and first copy/merge the old directory to the new location. This copy of your old data is necessary, since with the next start, Enigma will locate user data at the new location exclusively.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.4 Inventory Console

The lower right window area that usually shows the inventory items and scrolls the texts of activated document items allows the user to reread previous document texts, to enter textual commands and to reissue previous commands.

You can issue a command by usage of the keyboard. Just enter the command string and activate the command by a finishing <return> stroke. The following commands are supported:

help

List all public available commands.

abort

Abort level and return to level selection menu. Same as <Alt X>

April 1st

Just a joke.

cheats

List level developer cheat commands for fast testing.

collision

Developer cheat that disables collisions between stones and marbles or pearls. Once used no score will be recorded if the level is successfully finished.

easy

Restart level in easy difficulty mode.

find search_string

Searches levels in all levelpacks that contain matching string in either the level’s title, author’s name or the file name.

god

Developer cheat that protects the actors assigned to the current player like the activation of an it_umbrella does. Once used no score will be recorded if the level is successfully finished.

hunt

Switch to world record hunting mode. Same as toggling the left most button in the level selection menu to the world icon.

info

Show info about level like the levelpack, position within levelpack, the file location, the title, author, version and the level internal id.

jumpto levelpack,position

Directly start the given level. The levelpack is identified by its title. The position is the number within the levelpack. E.g. jumpto Enigma IV,33.

nohunt

Switch off the world record hunting mode. Same as toggling the left most button in the level selection menu to the marble icon.

regular

Restart level in regular difficulty mode

restart

Restart level in currently selected difficulty mode.

suicide

Kill actors, but continue level if possible. Same as <F3>.

Both, the commands and the displayed document text have a history. You recall the history by usage of the up and down arrows.

Starting with the inventory item display the up arrow shows the previously submitted commands. Just by another <return> you can reissue a command. The history will be resorted with the last command at the position direktly above the inventory. You can edit history commands anytime like you can insert a new command. If you do not finish a command by a <return> the string will still be recorded and presented as the first command above the inventory. The command history is persistent.

The document history can be recalled by usage of the down arrow. All level documents previously displayed can be redisplayed. Additionally the initial level info displayed on the level start can be read again.

Both histories revolve to the item inventory when the up or down keys are used beyond the oldest command or message.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.5 Level Info

For every level, Enigma manages more data than can be displayed in the level menu. You can view them all with the levelinspector. You can call this special menu from within the level menu by right or control clicking on the level icon.

Besides title and author, Enigma provides information concerning a public rating of the level, different score values of interest, details on the level version, the level file location and more. Additionally, the levelinspector allows you to enter personal annotations for a level. You can review any screenshots you made for this level directly from the levelinspector, too.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.5.1 Public Ratings

Most levels are rated within five different categories:

To distinguish the ratings from their everyday-meanings, we use the following abbreviations for the ratings. Each of these categories takes values between 1 (easy) and 5 (difficult), except kno, which can also be 6 (unique mechanism).

Please bear in mind that it’s not simple to retain the following definitions in each of nearly 750 cases, so there might be (will be) deviations from them in single levels.

Intelligence (int)

This rating is intended to measure the creativity, planning and analytic requirements needed to solve the level. Intelligence is a very difficult concept in itself, and thus at first not easy to rate or to grasp. Consequently, a fixed definition of the five rating-degrees not only helps, but is essential in the rating process. So, assume you know everything about the single elements of a level. Then ask yourself these questions:

High values for intelligence are typically puzzles. int-ratings do not accumulate; the most difficult puzzle itself already determines the rating.

Dexterity (dex)

You can solve many levels either by accuracy or by patience. In our context, we do not mean dexterity in the sense of accuracy to avoid impatience, but accuracy to avoid death. So it focuses on the lethal positions in a level, not only death-stones and abysses, but also casualties like pushing a stone accidentally into an unreachable corner.

In contrast to the int-rating, dex might accumulate: A level with many situations, each of dex 3, can add up to dex 4 or even 5. This way, you can achieve dex 5. Rotors in a level also contribute to dex and to the speed-rating, spe. Thus, levels with a high dex-spe-combination are mostly action-driven, whereas a high dex-pat-combination typically is a dangerous maze.

Patience (pat)

Patience is a relatively subjective rating, and refers mostly to “felt time”, how long it felt to complete the level. So two levels with same mechanics can have different pat-values, e.g., if one level has a nicer design or shows the progress of the level in some way, like the number of opened oxyds. It explicitly includes having to restart the level repeatedly; not the time in the lower left corner or the score is crucial, but the complete “felt time” needed to solve the level, starting from the first look at it.

A high number of oxyds can heighten the pat-value and also lower it: If the player has to traverse the level several times to open matching pairs of oxyds, it is definitely pat-heightening. However, if oxyds are arranged to mark the progress of the player, and act as a kind of small reward within the level, they can lower the pat-value. It’s the same with a high number of doors: The arrangement is the critical factor.

High pat-values are typically mazes. In combination with int 3, a high pat-value can indicate a hidden item or a hollow stone. pat-values involve the whole level, so they can’t accumulate.

Knowledge of Enigma (kno)

The kno-rating mostly takes on the function and interactions of single objects in the game, like stones, floors, items, and actors. However, in some cases it also deals with special techniques. The guideline is the “Advanced Tutorial”, which defines kno 3. kno 4 corresponds to standard objects that aren’t shown in the tutorial; kno 5 requires a deeper knowledge of the game internals. Finally, kno 6 indicates special mechanisms, that are seldom encountered or unique. The overall kno-rating of a level equals that of the most difficult object or technique (and thus is non-accumulative):

  1. Moving a single marble on normal floors, normal walls, oxyds, stones that look like oxyds, death-stones, water, an abyss, documents, using the inventory, static gravity, visible gradients.
  2. Pushing stones, simple Sokoban-tricks, bridge-building in water and an abyss, connected puzzle-stones, moving more than one marble, meditations, grates, rotors and tops, hidden gradients, triggers and switches, doors, holes (not made by dynamite), swamp, floppies and st-floppy, keys and locks, coins and slots, cracks, timer-stones.
  3. Different floors can have different fraction and mouseforce, space, ice, inverted floor, some stones sink while others swim, black grates that hold rotors and tops away, dynamite, dynamite-breakable stones, spade, boulders, magic-wand to change boulder-direction, boulders sink into an abyss, sheets of glass, spoon, actors and items may hide under movable stones, small not-killer whiteballs, coloured one-way-streets, actorimpulse-stones (“bumpers”), rotors can fly over an abyss, quake-stones, swords and knights, lasers, static and movable mirrors, item- and coin-transformations by pushing stones over them and by using lasers, umbrellas protect in an abyss, hammer and breakable stones (although not in the tutorial).
  4. Bridge-building in swamps, rubber-bands, rubber-band-stones, scissor-stones, unconnected puzzle-stones, exploding puzzle-stones, turning puzzle-stones (with and without a magic wand), springs (both types, on the floor and hole-kind springs like in “Upstream Journey”), thieves, three-part shogun-stones, invisible stones, hollow stones, chameleon-stones, items hidden under chameleon stones, stones that aren’t what they seem (e.g., fake-death-stones), wormholes, magnets, using F3 for a restart to solve a level, yin-yangs, one-color-, yin-yang- and inverted yin-yang-stones, stones breakable by only one color, killer-balls, swap-stones, brush and paintable stones, changing one-way-streets with a magic wand, changing stones to glass with a magic wand, impulse-stones (movable, static and hollow), black and white bombs, bomb-stones, fire, extinguishers, rotator-stones, yellow anti-swapping stones, mines, flags, seeds, weights, putting objects under one-way-streets and other hollow stones, electric stones, turnstiles, mailing and pipes, rings (single and multiplayer), volcanos, bags, randomizers (as possible effect of a switch), horses (the actors) and horse-passing stones, pins, bananas, cherries can make you invisible, surprise-item.
  5. Cracks, floor-springs, wormholes, etc., are all items, seeds can grow inside stones, the laser is blocked by all items, killer-balls don’t sink in water, “Space Meditation”-kind collisions, holding down the mouse-button, invisibility lets you go through glass, jumping over lasers ...
  6. Spitter-stones, surprise-stones, levels like “Enigris” or “Flood Gates” ...

kno 6 does not necessarily mean that this level is difficult to understand; the unique mechanism or object might also be very intuitive, like in “Flood Gates”.

Speed and speed control (spe)

The spe-value corresponds not only to the maximum speed a level requires (like you need to run away from a rotor), but also the degree of control a player has over his mouse while moving it; excellent examples for this are “Mourning Palace” and the middle part of “Sacrifice”. This involves moving the mouse at a constant velocity for a long time, as well as correctly estimating the speed that’s needed in a certain task, like shattering a sheet of glass.

  1. No time limit.
  2. You shouldn’t stop for too long. For example, something slow might be chasing you.
  3. There is an appropriate time limit or speed control task. This can be a single, not-too-fast rotor in an open area.
  4. Don’t stop! Examples include difficult timing-tasks as well as a single fast rotor or several slower ones.
  5. Hurry Up! Whereas spe 4 is meant to be difficult, but obviously solvable in not too many attempts, spe 5 is everything beyond this.

The spe-rating again is cumulative, since many slow rotors can add up to spe 3 or 4, or a combination of many slow time-switches to be pressed in a certain order can create a horrible task. In contrast to the other categories, for which the average is near 3 (or between 3 and 4 for kno), most levels are definitely spe 1. So, the spe-rating is more a supplement to the three core-ratings int, dex and pat.

Combinations of ratings

Sometimes, it can be interesting to have a single value to measure the difficulty of a level. To calculate such a universal rating, a simple possibility is choosing a linear combination of the 5 single ratings, weighted with appropriate weights. These weights should correspond to the difficulty a single category adds to the universal difficulty. Yet you should also choose these weights carefully to avoid number-theoretic obstructions (e.g., when all weights are even except for the spe-rating, then there will be a visible difference in the distribution of even and odd universal ratings, which can be very misleading). A working, and very interesting linear combination, is the following, which has been applied in the reordering process:

 
universal difficulty  =  7*int + 6*dex + 4*pat + 3*kno + 4*spe - 23

This has a special property, in that it takes relatively broad and continuously distributed values between 1 (all ratings 1) and 100 (all ratings 5, kno 6) and emphasizes the most difficult categories, intelligence and dexterity. However, some very low or very high values cannot appear in this combination, such as 2 or 99. Other combinations lead to full but narrow, or to broad but noncontinuous spectra.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.5.2 Scores

The score columns show your and some comparison values for the difficult and for the easy mode, if the levels supports it.

The world record is the best score that was retransmitted to the Enigma team. The world record holders are listed below.

The PAR value is the “professional average rating” of the level. It is the harmonic average of all scores that Enigma players have retransmitted. However, we take into account only scores from players who have solved a certain number of levels. Opposed to the world record, which will be very difficult to gain, the PAR value is a much more realistic aim for an ambitious player. If you are equal or better than PAR, the levels are marked with a speeding blackball within the level menu.

The author’s value is another reference score. Most authors are not keen on holding the world record of their own levels. However, they will likely know the fastest way to solve the level. If your score is much higher than the author’s score, a simpler solution to solve the level may exist.

The solved number is the number of players who solved this level in the given score version.

The solved percentage is the relation of the number of players who solved this level to the number of players who retransmitted scores. Actually, we take into account only those players who could have solved the level. For example, players who did retransmit scores before the level was written, without updating afterwards, are not taken into account. A low percentage is a hint that a level is not easy to solve.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.5.3 Versions

The version column shows detailed information about the level. Read the chapter Level Basics node see section <version> and see section <modes> for an explanation of the values.

For you as a player, the ‘Score’ version number can be interesting. A level you had solved with a certain score may appear with a red triangle in the level menu in an updated Enigma release of the level. Although the level menu displays the medals showing that you solved the level, it will not display the score values anymore. This is due to an incompatible level update that requires a new solution with different, incomparable score values. The author will increase the score version number in such a case.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.5.4 Private Annotations and Ratings

This textfield allows you to enter an annotation for a level that you can review on later replays. Note that the current textfield is limited (it may not allow you to enter all characters, and needs the mouse cursor to remain within its boundaries). Yet it should work for entering short annotations that may be very useful later.

Enigma stores annotations in your private applications ‘state.xml’ file. It permits one annotation per level, independent of the level version.

You may rate the levels, too. Just click on the ratings button. Values go from 0 to 10 with an additional ‘-’ for abstention. 0 stands for a poor level that you think is not worth playing, 5 for an average level and 10 for the ultimate, best levels. Try to use all values in your ratings.

Enigma stores the ratings with the scores and evaluates them anonymously. Enigma displays the resulting average rating of all users, for your information. Note that different ratings are possible for different score versions of the same level, because levels may improve as a result of suggestions by users. If you do not re-rate a new version of a level, Enigma inherits your rating from a previous version.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.5.5 Screenshots

While playing a level, you can make screenshots by pressing <F10>. You can make several screenshots in sequence for documentation purposes. Enigma will store each with a unique image filename. Using the level inspector, you can view the screenshots directly from within Enigma. Just click on the screenshot button to view the first image.

Because any buttons would disturb the view of a screenshot, all functions are keyboard commands. Press <F1> to get a help screen. <ESC> returns to the level inspector. <Page Up> and <Page Down> will show the previous and next screenshot. If you scroll down behind the last screenshot, the “missing” screenshot file is named. This may be a useful hint as to where to search the other screenshot files on your ‘user image path’ (see section Locating Resources).


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.6 Handicap and PAR

As PAR (see section Scores) describes the difficulty of a level, the handicap ‘hcp’ describes your ability to solve levels in PAR. The handicap is always related to a levelpack or group of levelpacks. You can see your handicap for each levelpack in the level menu, if you select the PAR mode by clicking on the lower left button until the speeding black marble appears. The value is displayed in the upper right corner, with the number of levels you solved in PAR.

The handicap is similar to the golfer’s handicap. A low value is better than a high value. If you solve all levels exactly in PAR, your handicap will be 0. If you are even better than PAR, your handicap will be negative. Players can use this value to compare their overall abilities.

Just for those of you that want to know the details of this score rating system of PAR and handicap, here is some additional information, which others may skip and continue with the next chapter Levelpack Basics.

We request all users to send their scores. All scores are evaluated for world records and counts of level solution rates and numbers.

However, for the PAR calculation, we take into account only scores from users who have solved more than a certain percentage of levels (currently about 10% of the levels). For every level, we calculate the harmonic average of the scores of these ‘professionals’. We take professionals who did not solve a level into account with the 10-fold world record score. The harmonic average calculates as

harm.avg. = N / (sum_[j=1..N] 1/score_j) )

It weights small (short) times with a greater weight than large (long) solution times.

The handicap is a sum of values that describe your scores in relationship to the PAR value of a level. Since it has to take into account that you have no score at all or that no PAR value exists, we apply some exception rules to the addends:

+ 1.0for each unsolved level
+ log10(score/par)for each solved level with existing par if score >= par
+ 0.7as upper limit for each solved level with existing par if score >= par
+ log2(score/par)for each solved level with existing par if score < par
- 3.0as lower limit and as value for levels without par

Note that each score that is better than PAR results in a negative addend and thus reduces your handicap. For a levelpack with 100 levels, the handicap will be in the range of +100 to -300. For levelpacks with more or fewer levels, Enigma will scale the sum by a factor 100/size to result in comparable handicap values. Handicaps are stated with one digit behind the decimal point.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.7 User Sound Sets

(The following information accounts only for Enigma 1.01 and above.) Sound effects are triggered by so-called ‘sound events’. These sound events usually have a name (like ‘dooropen’) and an associated location (the coordinates of the door) which affects the way a sound effect is played. The collection of all sound files, their assignment to sound events, and some additional information how to play them is called a ‘sound set’.

You can use own sound files to create own sound sets for Enigma, and choose among them in the options menu (entry ‘Sound set’). You can distribute these sound sets under your own choice of license and install sound sets from other users. There is no internal limit for the number of installed sound sets.

The sound event is converted into a real sound effect using tables, you can find such tables in the ‘data/sound-defaults.lua’ file and in the empty sample file at ‘reference/soundset.lua’. Each entry in these tables is either a string like ‘enigma/st-coinslot’, which is interpreted as the file ‘soundsets/enigma/st-coinslot.wav’ with some default properties, or a list of sound attributes enclosed in curly braces. Sound events triggered by a sound message are converted the same way. Here is an example of such an entry:

 
dooropen = { file="my_soundset/open-door", volume=0.9, priority=4 },

The meaning of these attributes is as follows:

To design a new sound set, proceed as follows:

  1. Create a new folder containing a copy of the sample file ‘soundset.lua’ and the wav files you want to use.
  2. Move this new folder into Enigma’s "soundsets" folder in your user path. (Possibly you have to create it.) The directory structure should look something like this:
     
    (user path)/soundsets/my_sounds/
                                   /soundset.lua
                                   /high_pitch.wav
                                   /soundfile_13.wav
                                   ...
    
  3. Run Enigma and choose ‘My Soundset’ in the options menu. Since this file’s sound set does not map any sound effect to a wav file, you should hear nothing.
  4. Edit the contents of ‘soundset.lua’ to your liking. You can access the default sound files, e.g.:
     
    ...
    coinsloton = { file="enigma/st-coinslot" },
    ...
    

    When using own sound files, remember to add the subfolder, like in

     
    ...
    coinsloton = { file="my_sounds/soundfile_13" },
    ...
    

    No extension ".wav"! It’s added automatically. Make sure that the extension is in lower case letters.

  5. Replace ‘MY_SOUNDSET’ by a suitable variable name, and ‘My Soundset’ by the name you want to see in the sound options menu. Remember to make it short enough to fit on the button. The three identifiers variable, button name, directory name need not have the same names, but it eases the life of other developers to give them similar names that uniquely determine the sound set.

Remember to choose the sound set in the options menu anew each time you change its name. And always shut down Enigma before changing sound sets, new sounds are not recognized during runtime.

Feel free to zip and distribute the whole directory containing your sounds and the ‘soundset.lua’ file. You can install a downloaded zipped sound set simply by unpacking it and placing it into the ‘soundsets’-subdirectory of your user path. Make sure that the ‘soundset.lua’ is always exactly one subdirectory below ‘soundsets’. Deinstall a user sound set simply by deleting its directory. Renaming the directory does not suffice – you have to rename the ‘soundset.lua’ if you want to hide a sound set from Enigma. This can be advantageous if you use interdependent sound sets (sound sets that share sound files) and want to deactivate just one of them.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2. Levelpack Basics

Knowing the basics of running Enigma, you may wonder how levels are organized in levelpacks and how you can add levels or complete levelpacks to Enigma.

Levelpacks are sorted collections of levels that consist of an index and optional attached level sources. Not all level sources of a levelpack have to be included within the levelpack itself. A levelpack can crossreference levels stored in other levelpacks. If a levelpack has no level sources of its own and consists only of crossreferences, we speak of a crossindex, since just a single index file represents the levelpack.

These definitions suit all versions of Enigma well. However, up to Enigma 0.92, levelpacks needed to be manually edited, and the registration of levelpacks was a little bit cryptic. Thus, we decided to rewrite the complete levelpack system for Enigma 1.0, and tried to make it versatile and easy to use. We did set up the following aims:

Some of these features work seamlessly. You can use them immediately from the levelpack menu. For others, you may need to know where to place files. We will explain these details in the following sections:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.1 Getting Started with Levelpacks

One of the outstanding features of Enigma is its extensibility by new levels. And the community of users usually provides us several new great levels every week.

Adding a new level that you receive as an XML file is very simple. Locate the subdirectory ‘levels/auto’ on your ‘user path’ (see section Locating Resources). Just copy the level file to this folder and restart Enigma. The new level will be part of the ‘Auto’ levelpack, and you can play it like any other level.

Please note that Enigma displays erroneous or incompatible levels with an error icon in the level menu. Of course an attempt to run such a level will result in an error message. Look at the level metadata with the levelinspector (see section Level Info) to identify the required compatibility version, and contact the author via the address in case of level code errors.

A second way to run new levels is to add the address of the level files to the commandline (see section Startup Switches). This way you can play levels that are stored anywhere, and you may even use url addresses of levels stored on the internet. Levels added to the commandline are accessible via the ‘Startup Levels’ levelpack.

If you want to run an old-fashioned Lua level that someone wrote for Enigma 0.92 or earlier, you may try to start it via the commandline. These old levels miss necessary metadata for auto detection. However, commandline-supplied levels are treated as temporary levels available just for a single run of Enigma; reasonable defaults substitute the missing data. The level will probably run, but scoring and copy, paste and linking of such levels is not possible.

Besides single new levels, the community may provide you with complete levelpacks, too. These levelpacks may occur as directories with levels, zip archives or single XML files. You can install all of them simply by copying the files, but we have to distinguish the three formats.

You must copy levelpacks distributed as directories, with level files and an index file in them, to the subdirectory ‘levels’ on your ‘user path’ (see section Locating Resources).

You must copy levelpacks distributed as zip archives to the subdirectory ‘levels’ on your ‘user path’. You do not need to unpack the zip, although it is possible, as described in the section Zip Levelpacks.

You must copy levelpacks that are distributed as a single XML index file to the subdirectory ‘levels/cross’ on your ‘user path’.

All new levelpacks should be accessible via the levelpack menu after restarting Enigma.

That is all you need to know to be able to add new levels and levelpacks for testing and playing. If your main interest lies in writing your own levels, you may want to proceed directly to chapter Level Basics. The rest of this chapter explains how to arrange and sort existing levels in your own levelpacks.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2 Converting 0.92 Levelpacks

With the changes of the levelpack index format, converting old levelpacks is necessary. Although the main work is done automatically just by starting Enigma, a few special cases remain that need manual preparation. Further on, after the autoconversion, some cleanup may be useful.

If you formerly maintained your levelpacks within the Enigma system levels directory, you should now copy your own levelpacks from the old Enigma version to the ‘user path’ subdir ‘levels’ (see section Locating Resources). The ‘user path’ exists on all systems, and since Enigma 1.00 will never write to the system levels directory, it will perform updates and conversions only on the ‘user path’. If you registered your levelpacks on the system levels directory within the ‘index.lua’ file, you need to copy these registration lines to the ‘index_user.lua’ file, which you should store on your ‘user path’.

If you maintained several of your own levelpacks, Enigma 0.92 allowed you to keep them in several subdirectories of the ‘levels’ directory. However, since it also allowed you to keep all level files and different indices in the ‘levels’ directory itself, you will run into trouble with the auto conversion, because Enigma 1.00 allows only one levelpack with attached level files per directory. In this case, we recommend a step-by-step conversion: in every step, provide only one old index for conversion. Enigma will convert this index to a new ‘index.xml’. Move this new index, together with all levels, to a subdirectory and convert the next levelpack.

A last special case occurs if you had an old index stored in ‘levels’ that referenced level files in different subdirectories of ‘levels’. Since Enigma 0.92 did not have a concept of cross-references, and Enigma 1.00 requires that you store all level files attached to a levelpack in a single subdirectory, the conversion algorithm needs to guess the correct subdirectory. It simply takes the subdirectory of the first level. If this does not fit, you may need to clean up your 0.92 levelpack prior to conversion.

Enigma should convert all other standard levelpacks without problems. It only performs the conversion once. As soon as the new ‘index.xml’ exists, only this index is used. Thus, after a careful check, you may remove the old ‘index.txt’. We recommend keeping a backup of the old index until you have completely switched to Enigma 1.00.

If you used a levelpack of your own in the zip format, you will find a subdirectory named with the base name of the zip archive in your user ‘levels’ directory. Enigma stores the converted ‘index.xml’ within this directory. You may want to exchange the old ‘index.txt’ in the zip with the new index. Afterwards you can delete the subdirectory, since Enigma will load the index directly from the zip archive.

After converting your levelpacks, we strongly recommend that you update your own levels to the new XML format, as described in Level Basics.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3 Zip Levelpacks

Besides the classic levelpack format of a subdirectory of ‘levels’ with an ‘index.xml’ and several level files, Enigma 1.00 provides a compatible zip archive format. This zip allows you to reduce resources and to ease distribution of levelpacks.

The compatibility is 100%. If you have a classic subdirectory levelpack, you can simply zip the complete subdirectory and name the zip with the name of the subdirectory, plus the standard ‘.zip’ suffix. Now you can completely remove the subdirectory; Enigma autodetects the levelpack and it is fully playable. Even cross-references into this levelpack will not be broken!

On the other hand, Enigma allows you to expand every zip levelpack to a subdirectory with index and level files. Again, everything runs and no cross-references are broken.

If you keep both, the files contained in the subdirectory precede files in the zip archive. Thus, Enigma stores updates of single files in subdirectories in parallel to existing zip archives.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4 Grouping and Sorting Levelpacks

As the number of levelpacks increased, it became necessary to sort and group the levelpacks in the menu. We tried to provide a useful set of default groups and default assignment of the distributed levelpacks to these groups:

Still, this is just a proposal. You are free to rename the groups, add new groups and change the assignments of the levelpacks. As in other parts of Enigma, you can right or control click on the group and levelpack buttons.

The group configuration menu allows you to rename and reposition a group. You can choose any name that is not a duplicate, that is not enclosed in square brackets and differs from ‘Every Group’. Note that you may not be able to enter as many characters as you are used to. Sorry for this inconvenience.

The levelpack configuration menu allows you to assign a pack to a group. The group list contains two special entries: ‘[Every Group]’ and another name enclosed in square brackets. Selecting the first pseudogroup displays the levelpack in every group. This is the default assignment of the ‘Startup Levels’ group. The second square bracket-enclosed name is the default group of the levelpack itself. It is a hint for you and allows you to reassign a levelpack to the default group even if meanwhile you have deleted the group.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.5 Creating New Levelpacks

To create a new levelpack, you simply select the group to which you want to add the new pack. This is most likely the ‘User’ group. Right or ctrl click on the group and simply click on the ‘New Levelpack’ button. Enigma will call the levelpack configuration menu, which allows you to enter all the important data for the creation of a levelpack.

First you should enter a name for the levelpack. You are limited to characters that can be used for filenames, too. You may use alphanumerical characters A-Z, a-z, 0-9 and space, underscore and hyphen. Note that you may rename the pack later for a better or more suitable display name (see section Modifying and Deleting Levelpacks).

Later, you should decide whether you want a levelpack that can contain level sources or just a crossreference levelpack. The first one is useful for storing your own self-written levels or levels that you download from the internet. You may use the crossreference levelpacks for your favorite collections, where you simply reference existing levels of other levelpacks with your own personal sorting. You set the selected type with the ‘Level types’ button, which uses symbols for references and carbon copies.

The ‘Default Location’ is a number that determines the sorting location within levelpack groups, if you have not resorted the levelpack manually (see section Grouping and Sorting Levelpacks). This default value is relevant only if you distribute your levelpack and want to ensure that the users will find your levelpack at a proper location. The value given after creating a new levelpack should work well in most circumstances.

You may declare yourself as owner or creator of the levelpack. This is just a string for identification purposes.

Finally, when you have completed the configuration, you can create the levelpack by clicking ‘OK’. Enigma will create the levelpack on your ‘userpath’ (see section Locating Resources).

If you decide not to create a new levelpack, just click ‘Undo’. Enigma will not create or change anything in this case.

If you want to set up the new levelpack immediately, you can click directly on ‘Compose Pack’. Enigma will create the levelpack, and you can use the composer to fill it with levels.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.6 Modifying and Deleting Levelpacks

To modify a levelpack, right or ctrl click on its button in the levelpack menu. You will see the metadata for all levelpacks. However, an ‘Edit Metadata’ button will appear only for your own levelpacks, which Enigma stores on your ‘userpath’. Clicking on it allows you to edit the metadata.

Renaming the levelpack is possible, but Enigma will not change the filenames anymore. It will use the new name as the logical levelpack name that shows up in Enigma.

Other attributes that you can modify include the ‘Default Location’ and the ‘Owner’.

Note that changing the levelpack type later is not possible. You must create a new levelpack of the proper type and copy the levels by using Composing Levelpacks.

We do not provide a levelpack deletion function to avoid unintended loss of levelsources. Still, the deletion of a levelpack is as simple as deleting the complete levelpack directory on your ‘userpath’. For crossreference levelpacks, you simply need to delete the index XML file on the ‘levels/cross’ subdirectory of your ‘userpath’.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.7 Composing Levelpacks

You can change the levels of a levelpack by using the levelpack composer. You call it by right or ctrl clicking on the levelpack button in the levelpack menu, then clicking on the ‘Compose Pack’ button in the levelpack configuration menu.

The composer looks similar to the levelmenu, but it provides other functionality. Enigma lists all commands in the F1 help menu. First, if you compose your own levelpacks, you may note that the levels are bordered red. This is a warning, since you can modify these levelpacks. System levelpacks (the distributed Enigma levelpacks) will border the levels in gray, since you can use the composer only for copying levels to the clipboard.

The clipboard allows you to select levels in one or several levelpacks and to insert these levels as reference or as copy to your own levelpacks. First, clear the clipboard by ‘Shift delete’. Then select any levelpack you want from within the composer levels. Add them by ‘Shift click’. They will appear in the upper text lines in the composer. Return to the levelpack where you want to add the levels. Select the level behind which you want to add the levels. Use ‘F8’ to insert the levels of the clipboard as references. If you edit a levelpack that can take level copies, you may use ‘F9’ to insert the levels of the clipboard as file copies.

As soon as you modify the levelpack, a small red triangle in the upper left corner signals the modification. Leaving the composer via the ‘OK’ button finalizes all changes. Leaving the composer via the‘Undo’ button reverts all changes.

Besides adding levels, you can delete levels by using the ‘delete’ button. Note that Enigma will delete the level files themselves if you delete a level that is not just a reference. Be careful with all levels that have the document icon on their preview. You can revert deletions with the ‘Undo’ button.

You can resort all levels with the ‘alt left arrow’ and ‘alt right arrow’. The new sorting appears immediately, and you can save it by using the ‘OK’ button.

You can use the ‘F5’ button to update the index from the levels. This is very useful if you edit levels yourself. The levelpack will notice changes in title, revision, easy mode support etc. Enigma updates all levels of the levelpack at once.

By using the Auto levelpack and the composer, you can set up levelpacks of your own levels, as follows: Create a new levelpack, add the level files to the ‘auto’ folder, restart Enigma, add the levels from the ‘auto’ folder to the clipboard, use the composer to insert the levelpack to your levelpack as a copy, and delete the unused level file copies from the ‘auto’ folder.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3. Level Basics

Now that you have played some levels of Enigma, you may have noticed that Enigma is quite a dynamic game with versatile levels. Thus, it is not astonishing that it is impossible to describe such levels with a static approach of a simple object map like Sokoban. Some levels, like mazes, generate their layout and look different each time you play them. Other levels provide a dynamic behavior during the play; i.e., switches may open doors only in certain circumstances. To comply with these demands, we have integrated the powerful lightweight C extension language Lua as of version 5.1.4 into Enigma.

Up to Enigma 0.92, two different level formats did exist. One was a XML-like format, primarily designed for external level editor programs. Because its static object map description part was inconvenient for manual editing, many authors never used it. The second format was plain Lua code that used an interface of Enigma Lua functions to add objects and callback functions. Nearly all authors used this second format, but it had a small drawback: you could store metadata for the level (like the author name, license info, and last but not least, the level name itself) only as unformatted Lua comments, and you had to reinsert it manually into the level-package indices.

With the post-0.92 XMLification of Enigma, we achieved full XML support by integrating Apache Xerces, and were wondering how to get rid of the old level format drawbacks and how to add some compelling new features:

Let us have a first view on complete simple ‘Hello World’ level in the new format:

 
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<el:level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://enigma-game.org/schema/level/1 level.xsd" xmlns:el="http://enigma-game.org/schema/level/1">
  <el:protected >
    <el:info el:type="level">
      <el:identity el:title="Demo Simple" el:id="20060210ral001"/>
      <el:version el:score="1" el:release="1" el:revision="2" el:status="stable"/>
      <el:author  el:name="Ronald Lamprecht"/>
      <el:copyright>Copyright © 2006,2009 Ronald Lamprecht</el:copyright>
      <el:license el:type="GPL v2.0 or above" el:open="true"/>
      <el:compatibility el:enigma="1.10"/>
      <el:modes el:easy="false" el:single="true" el:network="false"/>
      <el:score el:easy="-" el:difficult="-"/>
    </el:info>
    <el:luamain><![CDATA[
ti[" "] = {"fl_lawn_b"}
ti["#"] = {"st_box"}
ti["o"] = {"st_oxyd"}
ti["@"] = {"#ac_marble"}

wo(ti, " ", {
    "####################",
    "#                  #",
    "#  o      @     o  #",
    "#                  #",
    "####################",
})
    ]]></el:luamain>
    <el:i18n/>
  </el:protected>
</el:level>

You may notice that the XML portion contains all the metadata that the level author is accustomed to supplying with a level. The XML part is like a formula that you can copy from a template and fill out.

The Lua code is embedded in the XML. The only limitation to the Lua portion is that it reserves ‘]]>’ for the end mark, and you would have to substitute it with ‘]] >’. No further restrictions.

Since the example above includes all mandatory XML parts, we should achieve our aim to avoid major changes for Lua level authors.

You can find the example above in the ‘Exp’ levelpack grouped in ‘Development’. The source code is located on the system path subdirectory ‘levels/enigma_experimental’ (see section Locating Resources).

If you make your first coding experiments on a copy of this level, either add your copy to the auto folder (see section Getting Started with Levelpacks), or use it as an argument on the command line (see section Startup Switches).

Of course we must look at the details of the format and explain the optional parts:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1 Getting Started with Levels

Most likely you are keen on understanding the basic principles of placing objects in a level. Here is a very simple level description that can also serve as a starting-point for new landscapes. (In fact, this is the first welcome level in levelpack Enigma I, so you can try it out right away.)

 
 1    ti[" "] = {"fl_gravel"}
 2    ti["#"] = {"st_box"}
 3    ti["O"] = {"st_oxyd"}
 4    if wo["IsDifficult"] then
 5        ti["Q"] = {"st_quake", name="quake"}
 6        ti["T"] = {"st_timer", interval=10.0, target="quake"}
 7    else
 8        ti["Q"] = ti[" "]
 9        ti["T"] = ti[" "]
10    end
11    ti["@"] = {"ac_marble_black", 0.0, 0.5}
11
12    wo(ti, " ", {
13	"####################",
14	"#                  #",
15	"#                  #",
16	"#  O            O  #",
17	"#         @        #",
18	"#                  #",
19	"#        QT        #",
20	"#                  #",
21	"#                  #",
22	"#  O            O  #",
23	"#                  #",
24	"#                  #",
25	"####################"})

The resulting level looks like this inside the game:

images/first_level

Let’s now turn to a line-by-line analysis of this program:

 
 1    ti[" "] = {"fl_gravel"}
 2    ti["#"] = {"st_box"}
 3    ti["O"] = {"st_oxyd"}

First we declare some keys for objects we like to use in our level map. We just add each key to our ti tiles repository and assign an object tile description that consists of the object kind name in these simple cases. The two character prefix of the kind name shows us the basic object type like floor, item, stone, actor, etc.

 
 4    if wo["IsDifficult"] then
 5        ti["Q"] = {"st_quake", name="quake"}
 6        ti["T"] = {"st_timer", interval=10.0, target="quake"}
 7    else
 8        ti["Q"] = ti[" "]
 9        ti["T"] = ti[" "]
10    end

The welcome level provides two modes, the regular difficult one and an easy one. As the regular difficult one differs just in two additional stones we add two mode specific tile declarations.

In the difficult mode we assign two stone definitions. Each provides the stone kind and additional attributes. The ‘st_quake’ is the stone that closes oxyd stones when being hit or toggled. We just name it, to be able to reference it later on. The second stone is a timer that should get active every 10 seconds and should send a toggle message to its target, our oxyd closing ‘st_quake’. As we did name this stone we can reference it here as target by its name.

 
11    ti["@"] = {"ac_marble_black", 0.0, 0.5}

Now we just need to declare our actor. It is a black marble that should not be placed at the left upper corner of a grid but in the mid of the left border of a tile grid. Actually we just want to center it within the level. As a one screen sized level has the extension of 20 x 13 we need the offsets given above.

 
12    wo(ti, " ", {
13        "####################",
14        "#                  #",
15        "#                  #",
16        "#  O            O  #",
17        "#         @        #",
18        "#                  #",
19        "#        QT        #",
20        "#                  #",
21        "#                  #",
22        "#  O            O  #",
23        "#                  #",
24        "#                  #",
25        "####################"})

Now we can create the world simply by providing a map. We just need to call ‘wo’, our world handle, provide it our tile resolver, the key of the default floor and a map of tile keys.

You will find all conceptional background information in chapter Enigma Paradigm and more examples and syntax information in chapter Lua API. But first you should take the time to get aware of the XML based level metadata.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2 XML Level structure

Let us start with a complete overview of all existing top XML element nodes. The following level skeleton contains optional elements that are beyond level basics. We include these elements for completeness:

 
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<el:level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://enigma-game.org/schema/level/1 level.xsd http://enigma-game.org/schema/editor editor.xsd" xmlns:el="http://enigma-game.org/schema/level/1" xmlns:ee="http://enigma-game.org/schema/editor">
  <el:protected>
    <el:info el:type="level">
      <!-- required elements omitted -->
    </el:info>
    <el:elements/>
    <el:luamain><![CDATA[
    ]]></el:luamain>
    <ee:editor/>
    <el:i18n/>
  </el:protected>
  <el:public>
    <el:i18n/>
    <el:upgrade/>
  </el:public>
</el:level>

The first line is the XML declaration. It is fixed besides the encoding specification. Enigma supports on all platforms, at least ‘US-ASCII’, ‘UTF-8’, ‘UTF-16’, ‘ISO-8859-1’, ‘windows-1252’. Enter your encoding and make sure that your editor saves the level in this encoding. On some editors, you can start in ASCII mode, copy the level skeleton with a different encoding declaration, like UTF-8, save the level still in ASCII mode and reopen the file. The editor may then detect the XML declaration and switch automatically to the given encoding. Note that unless you enter international strings in the level, you do not have to bother with the encoding at all. You can choose UTF-8 in this case.

Some additional remarks for XML newbies: The XML markup tags are quite similar to HTML. But XML requires a corresponding end tag ‘</element>’ for each start tag ‘<element>’. For elements that have only attributes and no content, you can and should use the alternative empty element notation ‘<element/>’. Note that when we define an element as empty or state that no content is allowed, not a single whitespace, not even a linebreak is allowed between start and end tag. Use the empty element notation to avoid mistakes.

We use a pretty printing format with an indentation of 2. Each element starts on a separate line. Elements with text content have the end tag on the same line. Only elements with subelements have the end tag on a separate line with the same indentation.

This format is not mandatory. You can even insert linebreaks in text contents, within the marks, and even within attribute values. But note: The basic rule is that each linebreak will be substituted by a space during the XML parsing. Take this space into account to avoid mistakes, or simply live with the long lines.

A namespace identifier prefixes all tag names and attribute names. We use ‘el’ as an abbreviation for Enigma levels. All tag names you can manually edit use this prefix.

Finally, a short comment on the XML reserved characters, ‘&’ and ‘<’. These two characters are reserved as tag and entity starting characters. If you need them in text contents or in attribute values, you must substitute them by the entity sequences ‘&amp;’ and ‘&lt;’. Additionally, you must enclose attribute values with either ‘"’ or ‘'’. Of course, you must substitute the enclosing character used in attribute values, too. Use ‘&quot’ and ‘&apos’.

Elements:

/level, required, single occurrence

This is the root node. Only one instance of this node occurs per file. Like the first XML declaration line, this second line is quite fixed. There are two versions. The simple 3-attribute version, as used in the first example, and only level editor programs use the 4-attribute version as above. For manual level editing, just copy the simple version as the second line to your level file.

Attributes:

xmlns:xsi, required, contents fixed

Namespace definition for the schema. The contents are fixed to “http://www.w3.org/2001/XMLSchema-instance”. The attribute tag ‘xsi’ must match the prefix of the next attribute tag, and is standard.

xsi:schemaLocation, required, contents fixed

Location of the schemas used. The contents are the fixed Enigma level namespace, followed by the schema location URL. Level editor programs will add their namespace and their schema location URL, as in the second example above.

xmlns:el, required, contents fixed

Namespace definition for “Enigma level”. We use ‘el’ as the namespace prefix for all level element and attribute tags, as standard. The prefix used can be arbitrary, but must match this attributes tag. The contents of the attribute is fixed to the Enigma level namespace.

xmlns:ee, optional

Only level editor programs use this last namespace definition. For example, we declared ‘ee’ as the namespace prefix for all editor element and attribute tags. The prefix you use can be arbitrary, but must match this attributes tag. The contents of the attribute are the editor’s namespace.

/level/protected, required, single occurrence

The protected node section contains all level data that derive from the author and should not be modified by anyone else.

/level/protected/info, required, single occurrence

The info node section contains all level metadata. It is mandatory and described in detail at section Info metadata.

/level/protected/elements, optional, single occurrence

The elements node section is optional. It contains level description parts that are given in a data-driven manner. Though the driving force is the support for level editor programs, a level author may use any parts of this section he or she likes.

/level/protected/luamain, optional, single occurrence

The luamain node section is the part to insert manually Lua level descriptions. It is described in detail at section LUA code.

/level/protected/editor, optional, single occurrence

The editor node section is an open extension area for level editor programs. They can add any additional information in this section that they need. Enigma simply ignores this node section.

/level/protected/i18n, required, single occurrence

The i18n node section contains English strings, native translations and comments supplied by the author for the translators. This node section is mandatory and described in detail at section Internationalization (i18n).

/level/public, optional, single occurrence

This public node section is an optional extension to the protected part. It contains information that the author has not validated and may even be added after the last author’s review.

/level/public/i18n, optional, single occurrence

This public i18n section contains further translations supplied for the level. They may derive from the author or other sources. The translators will validate these translations, and they continue in use if the translators do not supply corrected versions. See Internationalization (i18n).

/level/public/upgrade, optional, single occurrence

This upgrade node is part of the Update and Upgrade system.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3 Info metadata

The Info node contains all author-supplied metadata for the level. This is the source of these data. All other parts of Enigma, such as level indices, simply contain copies that will be automatically updated to the level’s original data.

Let us look at all supported subnodes of info with typically used attributes:

 
<el:info el:type="level">
  <el:identity el:title="Demo I18N" el:subtitle="Translate or let it be translated" el:id="20060211ral002"/>
  <el:version el:score="1" el:release="1" el:revision="0" el:status="experimental"/>
  <el:author  el:name="Ronald Lamprecht" el:email="ral@users.berlios.de"/>
  <el:copyright>Copyright © 2006 Ronald Lamprecht</el:copyright>
  <el:license el:type="GPL v2.0 or above" el:open="true"/>
  <el:compatibility el:enigma="0.92"/>
  <el:modes el:easy="false" el:single="true" el:network="false"/>
  <el:comments/>
  <el:update el:url="http://…"/>
  <el:upgrade el:url="http://…" el:release="2"/>
  <el:score el:easy="-" el:difficult="-"/>
</el:info>

Attributes:

type, required, values = "level", "library", "multilevel"

You may use the schema for single Enigma levels, libraries that contain level description parts for reuse, and descriptions for multiple levels at once.

level’ are all single level descriptions. It does not matter if you edit them manually or with a level editor program, or which description elements you use.

library’ are level description parts that may be included in levels. Library Files consist simply of Lua code in the luamain node. Libraries may make use of nearly all nodes besides the ‘/level/protected/info/score’ and ‘/level/*/i18n’, which both must be provided, but will not be evaluated. Libraries are included in levels via the dependency node-element. See <compatibility>.

multilevel’ are descriptions for multiple levels at once. The main purpose is to support foreign game level formats, like the Sokoban level format, which usually describes a whole set of level maps in a single file (see section Multilevel Files).

quantity, optional

The number of levels contained in a multilevel file (see section Multilevel Files).

Contents - Elements:

identity, required

The title, subtitle and the main level identification string. See <identity>.

version, required

All aspects of the level <version>.

author, required

All information provided about the author him- or herself. See <author>.

copyright, required

The <copyright> message for the level.

license, required

Information about the <license> conditions.

compatibility, required

All information about <compatibility> to Enigma releases, dependencies from libraries, external data and the editor program that generated the level.

modes, required

The <modes> that the level supports, such as difficulty, network and control.

comments, optional

Optional comments, such as credits, dedication and code comments. See <comments>.

update, optional

Update and Upgrade

upgrade, optional

Update and Upgrade

score, required

The author’s own <score> of this level.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.1 <identity>

The ‘identity’ element is required, since it provides the information for human and system identification of the level.

 
<el:identity el:title="Demo I18N" el:subtitle="Translate or let it be translated" el:id="20060211ral002"/>

Attributes:

title, required

The English title of the level. The string can contain arbitrary characters that are displayable by Enigma’s font and XML conformant. Just in case of Multilevel Files a trailing hash sign has a special meaning. Anyway please make sure that the title is not too long, since Enigma will use it on the level selection menu. Translations of the title can be provided in the Internationalization (i18n) sections.

subtitle, optional

An optional English subtitle. Used for title parts that are too long for the main title, or for a short first hint. Enigma displays the subtitle on the level info page and on the start of the level. Translations of the subtitle can be provided in the Internationalization (i18n) sections.

id, required

This is the central system identification string of the level that remains valid for all time, independent of upcoming release updates. The id string should not contain spaces, braces and wildcard characters, that means no character out of ‘*? ()[]{}’. Enigma’s main demand on the id is that it is unique among all levels created by all authors around the world and that it does not end on a closing square bracket.

Since you can edit levels with any text editor or different special Enigma level editors, there is no control about the uniqueness. Thus, we have to provide a simple convention to avoid any possible id clashes:

YYYYMMDDuserNNN

Where ‘YYYY’,‘MM’,‘DD’ is the date of the creation of the first experimental version, ‘user’ stands for a user-specific name and ‘NNN’ for a random number. For example, my level called ‘Houdini’ has the id ‘20060816ral719’. Of course all levels created on the same day have to differ in the random number part. The id is an Enigma level system id, and is never exposed to users.

For backward compatibility, legacy levels keep their former filename as the new level id, and do not fit in the name schema given above. Still, that does not harm since the only requirement is the uniqueness.

Contents:

The element itself is empty - no content is allowed.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.2 <version>

This element provides the versioning information for the system.

 
<el:version el:score="1" el:release="1" el:revision="0" el:status="experimental"/>

Attributes:

score, required

The score version is given as a positive integer number. New levels start with score version “1”. New level versions need to increase the score version number if the level modifications cause different solutions with incomparable score values. Of course, level authors should be very restrictive with such modifications.

During the development of a level, you should use the attribute ‘status’ to mark a level as not released. When the author changes the ‘status’ to ‘released’, he has to check scoring compatibility and increase the score version if necessary.

This attribute is the logical equivalence to the Enigma 0.92 ‘index.txt’ attribute ‘revision’.

release, required

The technical release version is given as a positive integer number. New levels start with release version “1”. You must increase the release version number if the level modifications cause either technical incompatibilities with previous Enigma releases, or the scoring version has been increased.

The primary cause for technical incompatibilities should be the compensation of Enigma engine changes. Since such compensations will not run on the old Enigma version, the level versions must be distinguished by a different release number.

In both cases, technical and scoring incompatibilities, the level file name must be changed, too. This is necessary since different Enigma versions may be installed on some systems at the same time. They have the need for both level versions at the same time. Internet servers providing Enigma levels need to offer the different level release at the same time, too.

To enable people to assign different level release files to a level itself, we strongly recommend the name convention for levels AuthoridentifierLevelnumber_Releasenumber.Suffix, where the levelnumber is at least 2 digits; for example, ‘ral01_2.xml

revision, required

The revision number is a simple, ever-increasing version number. Every published version of the level should have a new revision number. The revision number is independent from the scoring and release version number.

If Enigma finds two levelfiles in its data search paths with identical filenames, id, score and release version, it will load the one with the higher revision number. This feature guarantees that an older level revision stored on the user’s home level directory cannot supersede a new revision of a level distributed with a new Enigma release. Online updates will check the level revision numbers, too.

Although the revision evaluates to a number, the attribute can take a second string format as the literal keyword ‘$Revision: 1.23 $’. This Subversion format allows level authors to let their Subversion repository automatically insert the level revision number. They must simply set ‘svn propset svn:keywords "Revision" level.xml’ at their repository for every level file. Since the Subversion revision number is ever-increasing, it fulfills our criteria. Note that Enigma does not require that revision numbers be consecutive.

status, required, values = “released”, “stable”, “test”, “experimental”

This attribute describes the quality of the level during development. Enigma uses the status to protect the score database from being spoiled by unplanned solution scores. It will record scores only for levels marked as ‘released’.

As a level author, if you start to change a released level, you should first change the status back to ‘experimental’. Then make your changes and test the level. When you are definitively sure that you did not introduce any spoilers, you can release the level again with a new revision and perhaps a new release or score version number.

Contents:

The element itself is empty - no content is allowed.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.3 <author>

The information about the author him/herself. Enigma requires the author element itself, but all attributes are optional to allow an author to be anonymous. Please remember that level administrators and translators may need to contact you as the author. So please provide a way for them to contact you.

The author element node may look like:

 
<el:author  el:name="Ronald Lamprecht" el:email="ral@users.berlios.de" el:homepage="http://myhomepage.domain"/>

Attributes:

name, optional, default = “anonymous”

The author’s name as it will be displayed on the level info page and on the start of the level. The name defaults to ‘anonymous’.

email, optional

The author’s email address or a newsgroup or forum he monitors. In general, this is a hint as to how to communicate with him or her. The value will simply be displayed as a string on the level info page.

homepage, optional

An address for the author or where the author publishes additional Enigma levels. The value will simply be displayed as a string on the level info page.

Contents:

The element itself is empty; no content is allowed.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.4 <copyright>

The standardized location for the author’s copyright message:

 
<el:copyright>Copyright © 2006 Ronald Lamprecht</el:copyright>

Attributes:

none

Contents:

The author’s copyright notice.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.5 <license>

Of course, every author is free to choose the license conditions for his/her levels. However, the author must state the conditions. Thus, this node element and its attributes are required:

 
<el:license el:type="GPL v2.0 or above" el:open="true"/>

Attributes:

type, required

A short license identifier of the license type, with an optional link address to the license text or the string ‘special’, if the author supplies his/her own license as the content of this element.

open, required

A boolean statement, whether the chosen license fulfills the criteria of the Open Source Initiative (OSI). Please note that a value of ‘false’ may prevent your level from being distributed with Enigma.

Contents:

You may add a complete license text as the contents of this element. Please use the type attribute to identify the level.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.6 <compatibility>

 
<el:compatibility el:enigma="0.92" el:engine="enigma">
  <el:dependency el:path="lib/natmaze" el:id="lib/natmaze" el:release="1" el:preload="true" el:url="http://anywhere.xxx/mypage/natmaze.xml"/>
  <el:externaldata el:path="./extfile" el:url="http://anywhere.xxx/mypage/extdata.xml"/>
  <el:editor el:name="none" el:version=""/>
</el:compatibility>

Attributes:

enigma, required

The minimal Enigma release number required for compatibility.

engine, optional, values = “enigma”, “oxyd1”, “per.oxyd”, “oxyd.extra”, “oxyd.magnum”; default = “enigma”

The required engine compatibility mode that influences the behavior of various objects. This attribute is evaluated only for levels. Libraries ignore this attribute.

Contents - Elements:

The compatibility element itself contains only subelements as content.

dependency, optional, multiple occurrence

You can use this element to specify any Enigma-Lua library this level depends on. You can specify several libraries by multiple occurrence of this element. If you configure a library to be preloaded, the engine will load it before it loads or executes any level Lua code. The load sequence of several libraries conforms strictly to the sequence of their dependencies elements.

Attributes:

path, required

The resource path of the library without its suffix or any release extension. Enigma stores most libraries in the ‘lib’ subdirectory of its ‘levels’ directory, in most cases the resource path will be like the one in the example above: ‘lib/ant’. This is the valid path for the library file that may be either ‘levels/lib/ant.xml’ or ‘levels/lib/ant.lua’ or ‘levels/lib/ant_1.xml’.

However, libraries can also be totally level pack-specific. In this case, you may use a relative resource path, such as ‘./mylib’ and store the library in the level pack directory itself.

id, required

The version independent id of the library, as specified in the library metadata. Enigma will check it on load of the library to avoid problems, and may use it with the release number to detect relocated libraries.

release, required

Although different release versions of libraries must have different filenames, we require to specify the library version. Enigma will check it on load of the library to avoid problems, and may use it with the release number to detect relocated libraries.

preload, required

A boolean statement that specifies whether the library should be preloaded. If the library is not preloaded, you can still load it via Lua code statements. Yet even those libraries must be declared since Enigma will checked them on conformance. You should always preload your libraries if you make use of the ‘elements’ section.

url, optional

This optional attribute allows you to specify a backup address for the library. This will be useful for using new libraries that are not yet distributed with the system.

For the development and test phase of new libraries themselves, a developer can hand out test levels with an empty ‘library’ resource path attribute. The test levels will load the newest library version as published at the given url.

Contents:

none.

externaldata, optional, multiple occurrence

You can use this element to specify any external text data file this level depends on. You can specify several files by multiple occurrences of this element. Files declared can be read via the Lua interface.

This feature should support levels that simulate foreign games like Sokoban within Enigma. Due to copyrights and license conditions, the inclusion of such data within a level or even the distribution with Enigma may not be possible. However, distributing or downloading the data in the original unmodified format may be legal.

Attributes:

path, required

The resource path of the external data file without its suffix ‘.txt’. The path has to be either of the format "./name" for an external data file that is locally stored in the same folder as the level file, or will be saved at this position when it gets downloaded. Or the path can be of the format "externaldata/name" for shared external data files, that are referenced by multiple level files stored at different folders. The external data file will be locally stored or gets saved in the folder "levels/externaldata". In any case the local name of the external data file will have the suffix ‘.txt’ to mark it readable but not executable for the local operating system.

url, optional

This optional attribute allows you to specify an internet download address for the external data file. On first access a missing external data file will be downloaded and a copy will be stored locally for further access.

Contents:

none.

editor, optional, single occurrence

Special level editor programs use this element to store information about themselves.

Attributes:

name, required

The name of the level editor.

version, required

A version number of the editor, given as a string. .

Contents:

none

Contents:

none


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.7 <modes>

The modes element allows the author to declare the supported and the default modes of his level. Enigma’s engine checks that the level is used in supported modes.

 
<el:modes el:easy="false" el:single="true" el:network="false" el:control="force" el:scoreunit="duration" el:scoretarget="time"/>

Attributes:

easy, required, values = “true”, “false”

If a level provides a second easy-difficulty mode, set this attribute to ‘true’. If only a one difficulty mode is supported, set this attribute to ‘false’.

single, required, values = “true”, “false”

If a level provides a single player game as it is standard, set this attribute to ‘true’. Set this attribute to ‘false’ only if the level is a 2-player-network game.

network, required, values = “true”, “false”

If a level provides a 2-player-network game, set this attribute to ‘true’. If not, set this attribute to ‘false’.

control, optional, values = “force”, “balance”, “key”, “other”; default = “force”

This attribute defines the standard control mode of the level. You can play a level by using the mouse to generate forces on the marbles, since it is the standard and was the only way up to Enigma 0.92. Or you can play a level using the mouse, or other input devices to balance the level-world with the marbles. Or you may use the keyboard with its cursor keys to move the actor like in classic Sokoban games.

Although the user has always the last choice to define the input method he/she currently wants to use, the author must define the standard control-mode that the scoring system uses. Enigma will save and evaluate only scores achieved in the defined control mode for high score lists.

scoreunit, optional, values = “duration”, “number”; default = “duration”

This attribute defines the evaluation and display mode of score values. By the default ‘duration’, the score is interpreted as level solution time and displayed in a MM:SS format. The ‘number’ mode displays scores as plain numbers and lower numbers will be evaluated as better scores. This mode is appropriate for counting pushes and moves.

scoretarget, optional, values = “time”, “pushes”, “moves”, *; default = “time”

The score target triggers the measuring of score values. ‘time’ will take the solution time, ‘pushes’ counts the pushes of stones, ‘moves’ counts the moves of the actor. Any other value will call a Lua function for score values. The target is used as a short title for the score in user interface displays.

Contents:

none


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.8 <comments>

The optional comments node allows the author to add a few comments and to determine how they should be processed. Please note that internationalization support will not translate comments.

 
<el:comments>
    <el:credits el:showinfo="true" el:showstart="false">Thanks to the author of my favorite libs</el:credits>
    <el:dedication el:showinfo="true" el:showstart="false">To a honorable or a beloved person</el:dedication>
    <el:code>some important general notes</el:code>
</el:comments>

Attributes: none

Contents - Elements:

The comments element itself contains only subelements as content.

credits, optional, single occurrence

The place to honor people who helped to make your level run.

Attributes:

showinfo, optional, default = “false”

A value of ‘true’ will display the message on the level info page

showstart, optional, default = “false”

A value of ‘true’ will display the message on startup of the level. Please use this feature only in rare cases.

Contents:

The credits message itself. It may be broken into several lines. Whitespace will be collapsed before display.

dedication, optional, single occurrence

The place to dedicate the level to a honorable or a beloved person. Please use this place instead of adding document-items within the level.

Attributes:

showinfo, optional, default = “false”

A value of ‘true’ will display the message on the level info page

showstart, optional, default = “false”

A value of ‘true’ will display the message on startup of the level. Please use this feature only in rare cases.

Contents:

The dedication message itself. It may be broken into several lines. Whitespace will be collapsed before display.

code, optional, single occurrence

Attributes:

none.

Contents:

The main code comment, which may be an explanation of the <version> status or a to-do list. It may be broken into several lines. This comment will not be processed.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.9 <score>

In this node, the author should provide his own scoring values as hints and a challenge for other players. All values are related to the control mode defined in <modes>.

 
<el:score el:easy="01:07" el:difficult="-"/>

Attributes:

easy, required

The solution time for the easy mode. The format is either MM:SS, where MM stands for the minutes, and SS for the seconds, or - if the author did not yet solve the level him/herself. For levels with a score unit mode ‘number’, the value would be the number of marble moves or pushes.

difficult, required

The solution time for the difficult mode. The format is either MM:SS, where MM stands for the minutes, and SS for the seconds, or - if the author did not yet solve the level him/herself. For levels with a score unit mode ‘number’, the value would be the number of marble moves or pushes.

Contents:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.4 LUA code

This element takes any Lua code as a single chunk with nearly no limitations:

 
    <el:luamain><![CDATA[
levelw = 20
levelh = 13

create_world( levelw, levelh)
draw_border("st-wood")
fill_floor("fl-leavesb", 0,0,levelw,levelh)

oxyd( 4,4)
oxyd( 14,4)

document(5,10,"hint1")
document(10,10,"hint2")
document(10,5,"Heureka!")
set_actor("ac-blackball", 4, 11)
    ]]></el:luamain>

Attributes:

none

Contents:

This element takes the main Lua code as its contents.

All other possible libraries that are declared as dependencies, and Lua chunks supplied by XML elements are preloaded as described in <compatibility>. Generally there is no more need to use Lua functions like ‘Require’ to load libraries. Just in case you need to control the point of execution were the library must be loaded, you can declare the library with the attribute ‘el:preload="false"’. You should use the new function @ref{enigma.LoadLib} to load the library.

The Lua code that is enclosed in a XML CDATA section. This limits the Lua code not to use the reserved end marker ‘]]>’. Any occurrence must be substituted by ‘]] >’.

On the other hand, the XML format extends the Lua capabilities to the use of encodings. You may use Lua strings and comments with Umlauts, but Lua identifiers are still limited to pure US-ASCII. The benefit is that you can use Umlauts and other non-ASCII characters within it-document hints.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5 Internationalization (i18n)

The internationalization of levels is a driving force behind the level format changes. As you may have noticed, there are two ‘i18n’ elements, one in the author’s protected section and one in the public. Let us review how to use them for internationalization of the three documents of our ‘demo_i18n.xml’ level:

 
  <el:protected >
    <!-- elements omitted -->
    <el:i18n>
      <el:string el:key="title">
        <el:english el:translate="false"/>
      </el:string>
      <el:string el:key="subtitle">
        <el:english el:translate="true"/>
        <el:translation el:lang="de">Übersetzten oder übersetzten lassen</el:translation>
      </el:string>
      <el:string el:key="hint1">
        <el:english el:comment="Let 'right' be ambiguous: correct and opposite of left - if not possible choose correct">Read the right document</el:english>
        <el:translation el:lang="de">Lies das rechte Dokument</el:translation>
      </el:string>
      <el:string el:key="hint2">
        <el:english el:comment="the correct one and not the right positioned one">The right one, not the right one!</el:english>
        <el:translation el:lang="de">Das rechte, nicht das rechte</el:translation>
      </el:string>
      <el:string el:key="Heureka!">
        <el:english el:translate="false">Heureka!</el:english>
      </el:string>
    </el:i18n>
  </el:protected>
  <el:public>
    <el:i18n>
      <el:string el:key="hint1">
        <el:translation el:lang="fr">Lisez la document de droite</el:translation>
      </el:string>
    </el:i18n>
  </el:public>

Two of the documents use key words to reference a string. The last one uses the English string itself as the key. There are two additional reserved keys, ‘title’ and ‘subtitle’.

For each string we like to translate or have translated, we define a ‘string’ subelement of the protected section and add a ‘english’ subelement to the ‘string’ element itself. The ‘string’ element just takes a single mandatory attribute, the key of the string. The ‘english’ element has a single mandatory attribute ‘translate’ that defaults to ‘true’, stating the author’s decision whether the string should be translated. If the author does not want a string to be translated, he can and must simply add no ‘string’ element for this string at all. Thus, the elements for the strings with the keys ‘title’ and ‘Heureka!’ are optional and quite unusual.

title’ and ‘subtitle’ display the English text in the <identity> element. All other strings referenced by keys need to add the English text as the content of the ‘english’ element. ‘hint1’ and ‘hint2’ are examples.

Because we chose quite ambiguous English texts, it is very likely that translators who do not play the game but just translate the text, may deliver a wrong translation. To avoid mistakes, a level author may add a ‘comment’ attribute to the ‘english’ element. The translator receives this comment with the English string as we will see later.

If the author is not native English-speaking, he should add his own ‘translation’ subelement to the ‘string’ element. The ‘translation’ element has a single mandatory attribute ‘lang’ that takes the 2-character language abbreviation. The contents of the element is the translation itself.

All translations added in the protected section take precedence over any translator’s translation and will work directly after addition without waiting for a translator’s translation.

Last but not least, we have an ‘i18n’ element in the public section. This element takes translation suggestions. The author may add them him/herself for other languages he/she knows. They may be added by others on the way to the user, or even by the user himself.

Translations in this section will work immediately after addition without waiting for a translator’s translation. However, available translations, provided by translators, will precede them.

The format is identical to the protected section, with the exception that no ‘english’ element may be provided. The ‘key’ attribute in the ‘string’ element must match exactly the ‘key’ attribute in the corresponding ‘string’ element in the protected section. One subtle difference exists, due to technical and practical reasons. ‘key’ attributes in the public section need to be XML identifiers; thus, you cannot provide public translations for strings that use the English phrase as the key. Choose a keyword and provide the English string in the public ‘i18n’ section to avoid these troubles.

The ‘string’ element in protected section and in the public section must be unique concerning the attribute ‘key’ within the section. This means you should add translations for all known languages for a string in ‘string’ element in the protected and in the public section. The sequence does not matter.

Let us review what the translator receives for each string. Let us start with ‘hint2’ for the German translator:

 
#  level: "Demo Internationalization"
#  author: "Ronald Lamprecht" email "ral@users.berlios.de"
#  comment: "the correct one and not the right positioned one"
#  use: "Das rechte, nicht das rechte"
#: po/level_i18n.cc:17
msgid "The right one, not the right one!"
msgstr ""

msgid’ is the English string. ‘msgstr’ takes the German translation. But the translator does not need to translate since the author provided the German translation in the ‘# use:’ line

As another example, ‘hint1’ for the French translator:

 
#  level: "Demo Internationalization"
#  author: "Ronald Lamprecht" email "ral@users.berlios.de"
#  comment: "Let 'right' be ambiguous: correct and opposite of left - if not possible choose correct"
#  check: "Lisez la document de droite"
#: po/level_i18n.cc:14
msgid "Read the right document"
msgstr "Lisez le document de droite"

Here the author gives the public translation in the ‘# check:’ line. Since it contains at least one mistake, the translator will correct it, as shown in the ‘msgstr’ string.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.6 Usage

After all the theory, let’s look at how to deal with the XML levelformat in practice. Of course, you will not assemble all XML metadata from scratch for every new level you write. You should use templates. You can start with any existing level, for example, the ‘demo_i18n.xml’ supplied with this documentation. Add your personal data to your template and store it as the basis for all new levels you write.

Some level authors are very familiar with the Lua file format since their favorite editor supports Lua files with syntax coloring. The XML file name and the XML elements will cause their editor to use XML syntax coloring. Nevertheless, these authors are used to supplying metadata in the header of their Lua levels as non-standardized Lua comments; we decided to support a similar Lua-compatible XML format. We call it “Lua commented XML” since it simply comments out all XML lines with the Lua comment ‘--xml-- ’. For example:

 
--xml-- <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
--xml-- <el:level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://enigma-game.org/schema/level/1 level.xsd" xmlns:el="http://enigma-game.org/schema/level/1">
--xml--   <el:protected >
--xml--     <el:info el:type="level">
--xml--       <el:identity el:title="Demo Simple" el:id="20060210ral001"/>
--xml--       <el:version el:score="1" el:release="1" el:revision="0" el:status="stable"/>
--xml--       <el:author el:name="Ronald Lamprecht"/>
--xml--       <el:copyright>Copyright © 2006 Ronald Lamprecht</el:copyright>
--xml--       <el:license el:type="GPL2" el:open="true">GPL v2.0 or above</el:license>
--xml--       <el:compatibility el:enigma="0.92"/>
--xml--       <el:modes el:easy="false" el:single="true" el:network="false"/>
--xml--       <el:score el:easy="-" el:difficult="-"/>
--xml--     </el:info>
--xml--     <el:luamain><![CDATA[
levelw = 20
levelh = 13

create_world( levelw, levelh)
draw_border("st-wood")
fill_floor("fl-leavesb", 0,0,levelw,levelh)

oxyd( 4,4)
oxyd( 14,4)

set_actor("ac-blackball", 4, 11)
--xml--     ]]></el:luamain>
--xml--     <el:i18n/>
--xml--   </el:protected>
--xml-- </el:level>

Please note that each XML metadata line must start exactly with ‘--xml-- ’, 8 characters, including the space at the end! An additional limitation of the Lua-commented XML format arises from Lua’s capability of handling character encodings. You need to limit yourself to ‘UTF-8’ or, of course ‘US-ASCII’ to successfully use the Lua-commented XML format. Please remember, that although the XML part is Lua-commented, it must still be evaluated and thus must be valid.

Every level stored in this Lua-commented XML format as a file with extension ‘.lua’ can be used locally for command line use as well as in any level package that is stored on the Enigma user’s home directory. However, Lua-commented XML levels cannot be stored on Internet servers or be updated online. Thus, this format is good for level development, but you should convert the levels to the pure XML format for distribution. Please note that Enigma looks for XML levels first, and uses Lua levels only if it can’t find an XML level.

Another use of Lua-commented XML levels is the format backward compatibility to Enigma 0.92. If levels do not use new Enigma features, you can include your levels in Enigma 0.92 level packages in this format.

Since you may need to convert levels several times between the XML and the Lua format, we do provide tools for conversion: ‘xml2lua’ and ‘lua2xml’. Both are very simple Lua 5 scripts that you can execute as ‘lua xml2lua demo_simple.xml > demo_simple.lua’ with a properly installed Lua 5 version. On Unix systems, you can mark the scripts as executable and simply call ‘xml2lua demo_simple.xml > demo_simple.lua’.

Of course you can add the conversion algorithms as simple macros for your favorite editor. Please publish any editor macros you write.

As you fiddle with the XML metadata, you may produce syntactical errors, of course. You can validate your level by trying to start it with Enigma. XML errors are output as Lua errors are. If the error messages are too long to read, you may want to start Enigma from the command line with the option ‘--log’ and read the messages printed to the command line or written to the file ‘stdout.txt’ on the current working directory for Windows systems.

Of course, you can use any external XML validation tool, too. You just need to copy the schema file ‘level.xsd’ on the same directory as the level itself. Possible validation tools are the Xerces-C sample application ‘DOMPrint.exe -n -s -f -v=always level.xml’ or validating editors, such as Exchanger XML Lite. Such editors will provide you with selections of all possible elements and attributes at each position.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.7 Update and Upgrade

Enigma is able to load new level versions since we provide all necessary attributes in the <version> element.

If Enigma loads a new level version, which differs just in the ‘revision’, we speak of an ‘update’. You can perform updates automatically and replace old versions with the updates, since the author guarantees them to be compatible in scoring and dependencies. The author should provide a download address for automatic updates in the protected info element:

 
<el:update el:url="http://myLevelServer.org/path/level_1.xml"/>

Attributes:

url, required

A long-term valid, complete address for update downloads of this level in the same score and release version.

If the author of a level introduces incompatibilities into the level, he increases the release version of the level and stores it with a new filename. We call the download of such a new level version an ‘upgrade’.

To publish the availability of an upgrade release, the author should update the previous release with a final revision that simply adds an upgrade element that announces the new release:

 
<el:upgrade el:url="http://myLevelServer.org/path/level_2.xml" el:release="2"/>

Attributes:

url, required

A long-term valid, complete address for upgrade downloads of this level. A path to the new file.

release, required

The release version of the upgrade.

Since the author cannot update all distributed levels himself to announce the availability of the new release, we added another upgrade element in the public section. Level administrators can use this element for the same purpose, with the same syntax, without modifying the author’s protected section.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.8 Library Files

Libraries are collections of Lua functions for reuse in many levels. To use a library, you must declare it as a dependency, as described in <compatibility>. Preloading the library is all you have to do to use the library. Otherwise, you can use the function @ref{enigma.LoadLib} to load the library at a certain point of execution.

Enigma provides several very useful Libraries. You will find them on the system path in the subdirectory ‘levels/lib’. Most of them are documented in-line. You will find a separate documentation file ‘doc/ant_lua.txt’ for ‘ant’.

In this section, we will concentrate on the aspects of writing and maintaining libraries:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.8.1 Writing a Library

Library files are nearly identical to level files. The main difference is the attribute ‘el:type’ in the ‘info’ element, which you should set to ‘library’. You must provide all other elements and attributes as you must for levels. Of course no scoring related attributes will ever be evaluated and you should set them to default.

Libraries may depend on others, so you must provide an id and a release number. Several releases of a library can coexist and you can update and upgrade them if you provide the necessary information. Of course, libraries may contain document strings that can be localized if you provide the ‘i18n’ elements.

The ‘el:luamain’ element takes the complete Lua code as it does for levels. Let’s look at the essential XML parts of a library:

 
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<el:level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://enigma-game.org/schema/level/1 level.xsd" xmlns:el="http://enigma-game.org/schema/level/1">
  <el:protected >
    <el:info el:type="library">
      <el:identity el:title="" el:id="lib/ant"/>
      <el:version el:score="1" el:release="1" el:revision="0" el:status="released"/>
      <el:author  el:name="Petr Machata"/>
      <el:copyright>Copyright © 2002-2003 Petr Machata</el:copyright>
      <el:license el:type="GPL v2.0 or above" el:open="true"/>
      <el:compatibility el:enigma="0.92">
        <el:dependency el:path="lib/natmaze" el:id="lib/natmaze" el:release="1" el:preload="false">
      </el:compatibility>
      <el:modes el:easy="false" el:single="false" el:network="false"/>
      <el:score el:easy="-" el:difficult="-"/>
    </el:info>
    <el:luamain><![CDATA[
    …
    ]]></el:luamain>
    <el:i18n/>
  </el:protected>
</el:level>

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.8.2 Maintaining a Library

Libraries may exist in different releases and revisions. Library versions that differ simply in the revision, denote compatible versions. Library versions that introduce incompatibilities must differ in the release number. However, since existing levels may depend on the legacy behavior of the older release, you must maintain both library release versions and distribute them with Enigma at the same time.

To coexist, these different library releases must follow a strict naming scheme. Every library has a base name. In the example above it is ‘lib/ant’. The filename of a given release is the basename with the addition of an underscore and the release number plus the suffix ‘xml’. Thus, you must store release ‘lib/ant’ as ‘lib/ant_2.xml’.

If you look at the lib directory, you may wonder that Enigma stores most library files without release number addition to the basename. This is due to 0.92 Lua level format compatibility support. You can store one, and of course only one, release of each library without release number addition to the basename. Enigma will load this version from pure Lua levels that do not provide any information of the required library release.

If a library file with a complete filename is not present, the default library file without release number addition will be loaded for XML load requests, too. Yet the future belongs to the new naming scheme, and every new library should follow it from the beginning.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.9 Multilevel Files

Another concept of code reusage besides Library Files are multilevel files. The code contained in a single file generates several levels, called sublevels, that appear as autonomous levels in a levelpack. Of course this concept is much less flexible than the library concept as other level files can not reuse the code. But you can write a multilevel if you wrote a lot of specific code for a complex level that provides more than just two variants, which would be otherwise presented as ‘difficult’ and ‘easy<modes>.

But the main reason for multilevel files is the support of foreign game level formats like Sokoban, which describe a whole set of levels in a single file. Enigma imports these original files with just a few lines of code. It would be inefficient, even though being possible, to write an Enigma level stub for every level imported from a single foreign file.

But multilevel files have some restrictions. They use a single set of XML level metadata. Thus these metadata must fit to all levels. The <version> will be identical as it reflects either the code version of the native level or the version of the imported foreign file. But the other data like <author>, <compatibility> and <modes> have to match, too. If they do not, you can not use a multilevel file.

Just the values for ‘title’ and ‘id’ will and have to differ for all levels described by a single multilevel file. There exists special support for multilevels to handle these attributes.

Let us look at all attributes and features of multilevels that differ from standard level files.

First you have to declare in the Info metadata element the type as "multilevel" and to provide the quantity of generated levels. The sublevels will be numbered from 1 to the given quantity.

In the element <identity> you have to provide just one unique level id value. Enigma will automatically append the string "[1]" for the first sublevel, "[2]" for the second and so on. Thus every sublevel has an unique id.

Additionally you should provide a base title for the levels in this metadata <identity> element. If your title ends with a hash ‘#’ sign Enigma will autogenerate titles for the sublevels by appending the sublevel number to the given common title string.

For individual sublevel titles the LUA code has to provide the titles. The title in the <identity> element may not end on a hash sign and will only be used as a default title base in case the LUA code fails to provide a sublevel title. Prior execution of the Lua code the global attribute SublevelNumber gets initialized. The Lua part has either way to load the appropriate sublevel based on this number. Now it has additionally the task to set the second special multilevel global attribute SublevelTitle.

<compatibility> externaldata


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4. Enigma Paradigm

Now that you have learned about the formal declarative XML part of a level you should be eager to understand the basic principles of the participants of an Enigma level world. In this chapter we explain all the fundamental concepts and the terms used in the following chapters that describe the level author’s view of a level.

Please note that we describe the features of the new API of Enigma 1.10. The API of the earlier releases does not provide all the features and differs in several aspects.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1 The World’s Structure

We speak of a level as the opus as a whole that describes the initial composition of a gaming world and its dynamic behaviour during the game play. Let us look at the participating objects in details.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1.1 World’s Shape and Coordinates

Having played a few levels you will have noticed that every screen shows quadratic tiles, 20 ones in the horizontal and 13 ones in the vertical direction. Even if it is difficult for a player to map together all rooms and screens of a large level, every level world has the shape of a rectangle in whole. Nevertheless some parts may never be visible to the player due to walls of stones or oceans of water.

On the creation of a world the level author has to give its size in measure of tiles. The given width and height of the world are fixed and cannot be changed later on. A common size is 20x13 for a Onescreener. But there are no limits. You can even build levels smaller than a screen. Note that for larger levels you have to take into account that one tile row or column is usually shared between two screens on scrolling. Thus a level of 2x2 screens has a size of 39x25 tiles, a 3x4 screen level has 58x49 tiles,...

Looking at the edges of all the tiles we get a grid that spans our world. We define the upper left corner of our world as the position {0, 0}. The first coordinate is the horizontal offset to the right, the second coordinate the vertical offset to the bottom. For a Onescreener level the tile in the lower right corner is located at position {19, 12}, whereas the corner itself is at the position {20, 13} (Note that this point is actually not part of the level anymore).

A position of an actor like the black marble needs to be given by two floating numbers as coordinates like {1.5, 2.5} for an actor positioned in the center of the tile that is one column right and two rows down of the upper left corner tile.

But most objects like stones can only be placed on the fixed integral grid positions. Even if you try to put a stone on {1.5, 2.5} it will be put on the grid position {1, 2}. Thus we speak of a grid position if just the integral part is taken into account. You may note that a tile is positioned according to its upper left corner. Actually the upper and the left edge are part of a tile, whereas the right and lower edge belong to the neighbour tiles.

Finally let us look more precisely on the tile itself. On one grid position you may place a floor, an item, a stone and even several actors. The combination of all objects on one grid position is called a tile. It is a common technique to declare these object combinations once in so called tile definitions. As many grid positions share the same combination of objects these tiles can be reused very efficiently.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1.2 Object Layers

On every grid position you may set a floor, an item and a stone. But just one of each. If you set a second stone the first one will be replaced. Floor, item and stone have a unique physical arrangement with the floor always being below an item and a stone always being on top of the others. Thus we speak of three object layers - the floor layer, the item layer and the stone layer.

The floor layer has a unique prerequisite. Every grid position needs to be covered by a floor. You can define a default tile which contains a default floor that gets automatically set on every grid where you set no other floor. Even if you kill a floor, that means removing a floor without setting a replacement floor, a default floor will be set for you.

The floors provide two elementary features to the game: friction and adhesion. The friction slows down actors and the adhesion enables you to accelerate actors with your mouse. A floor may additionally cause a directed flat force that gives the user the feeling of a slope. And last but not least a floor may burn. A whole set of attributes let you control the details of the fire behaviour.

The item layer is shared between items that an actor can pick up and items that are static. The first category are items like keys, banana, etc. Static items are bombs, landmines, triggers, hollows and items that will only be set by the system itself like laserbeams, fire animations, ash, etc. As only one item can be positioned of every grid position a marble can not drop an item on such a static item. This is the technical reason that you can not intercept a laser beam by dropping an item. But as an level author you are free to add any item you like to the initial grid tile.

The stone layer is straight forward. The level author can choose a stone out of the repository per grid. Of course most grid positions should be kept free for the actors to move around. Even if most levels have a stone wall at the border of the world that visually limits the area this is not mandatory. Without a stone wall the marbles will be bounced at the physically boundary of the world.

The actors live in another layer that is not grid based. The actors can be placed at any position. Actors that pass a stone will be displayed below the stone.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1.3 World as an Object

Friction, Brittleness, Modes and Co., Scrollmodes


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1.4 Unpositioned Objects

You should be missing at least one object, that can neither be assigned to a single position nor to one of the above layers: rubberbands! In fact there are many Other Objects besides floors, items, stones and actors that are unpositioned. Besides visible rubberbands and wires useful gadgets, that help in plug and play composition of levels, can be added to the world.

All these other objects are full objects concerning the following chapters. But you need to use the world’s add method to add them and you need to use Object Reference or Object Naming to access them later on, as no position driven access does exist.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1.5 Player and Inventory

Enigma is conceptionally a game for 2 players. But nevertheless it can be played by one user on a single computer by toggling the control between two virtual players. We do call these virtual player’s YIN and YANG, as the first player controls in most cases a black marble, whereas the second controls usually a white marble.

Each virtual player has its own inventory of up to 13 items. The leftmost item of the inventory is called ‘revealed’, as an item activation by a mouse click does activate this item and on actor hits this item may cause special actions.

The players inventories do exist outside of the rectangular world. Thus any item being part of a player’s inventory will report an invalid, out of world position, that evaluates on an ‘exists()’ request to ‘false’. You can add items directly to inventories by the advanced world method add.

Even though the actors are assigned to players they are quite independent objects that live in one of the Object Layers. Their relationship to players is as follows:

Each virtual player can own and control one or several actors of any kind. That means player YIN is not limited to a black ac_marble, but may as well control a white ac_pearl, an ac_horse or any other set of one or several arbitrary actors.

Ownership and control of actors are two distinct aspects. Ownership of an actor means that every item picked up by an actor ends up in the player’s inventory and items of the player’s inventory can act on all owned actors. The control of an actor by a player does just affect the movement of the actor by the users force input. An actor may be controlled by a player without parallel ownership. Another actor may be owned by a player without being controlled by it, thus being a passive actor that depends on being pushed by others. An actor may even be controlled by both players, but it can just be owned by one player or none.

The assignment of actors to players is solely configured by Actor Attributes.

When a single user plays Enigma he starts with the control over the player Yin. By usage of yinyang objects he can toggle the control to the player Yang and back again. Items it_yinyang are added automatically for network levels when played by a single user. They allow an arbitrary switch between the players. The stones st_yinyang do limit the user’s ability to switch between the player control.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1.6 Owned Objects

Besides objects owned by a player and being part of his inventory, objects can be temporary part of another object. The most obvious case is an item contained in an it_bag. Other samples are two st_shogun stones pushed onto the same grid position or for a short fraction of time a stone swapping the position with an st_swap or an st_pull.

In any case the owned object will report the same position as the owner itself. Even in case of some items contained in a bag that is itself part of another bag all items will report the same position as the outmost bag. If this bag is part of an player’s inventory all items report an invalid position.

You can not directly enforce ownership by setting two objects to the same position as this standard operation will kill and replace the old object by the new one. Where possible, like in the case of an bag, you can add objects to a container object by usage of the world advanced method add.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.2 Object Description

Knowing where to place objects it is time to know how to select an object type, how to specify the details of the object and how to reference it later on.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.2.1 Object Kind

Up to now we have spoken about object kinds of floor ‘fl’, item ‘it’, stone ‘st’ and actor ‘ac’. All these kinds are called abstract. You can check if a given object is of such a kind, but you can not instantiate an abstract kind.

To create an object you need to give a specific kind name like ‘st_switch’. You will find all object kinds described in the chapters starting with Floor Objects. All these kind names with at least one underscore can be instantiated.

Most kinds provide subkinds like ‘st_switch_black’ and ‘st_switch_white’. In case of the switches you get a color independent switch if you do not append a suffix. In other cases like ‘st_chess’ the super kind will result in a default ‘st_chess_black’ as no colorless chess stone exists.

If you request an object for its kind it will always return the most specific kind. This means that a fresh generated ‘st_chess’ returns the kind ‘st_chess_black’, whereas an ‘st_switch’ reports its name unchanged.

Objects can change their kind by level code statements or by user actions. You may set a color on a switch or a marble may cause a color change on a chess stone by hitting it with a revealed wand. The object will report the new kind on subsequent requests.

A few special object kinds do exist only for setting a new object. They are usually named with a suffix ‘_new’. These objects will never report their initial kind name but change to a standard kind immediately.

If you are not interested in the specific subkind you can check an object for conformity to any super kind. E.g. any switch stone of whatever color will return true if checked for ‘st_switch’.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.2.2 Object Reference

Having set objects to the various layers a level author sometimes has the need of referencing them later on. On callbacks the engine provides references to sender objects. But the author can request any grid object anytime by its position.

With an object reference, that is of a special Lua type ‘object’, you can request the objects on its current state and attributes, modify the object, send messages or perform any supported methods of the object.

Objects can be grouped for efficient handling of common operations on all affected objects. E.g. if you can send a message to a group of objects all objects will receive the message in turn. The sequence of several objects in a group is constant and guaranteed to be observed in processing common operations.

As objects can seize to exist you have to be aware that the references are volatile, too. You can check every object reference for existence. But in many cases the validity of the reference is unimportant as Enigma 1.10 is very tolerant on invalid object references access. The operations will simply be ignored and requests will return default values.

As a general thumb rule you should request and keep object references just for the time of a local call. As long as your level code is processed in sequence without the running world simulation giving the player a chance to kill objects by marble actions, objects should seize to exist just due to your own direct statements.

To gain access to an object later on a subsequent call you can address it via two methods. First you can address it via its position. But as many objects are movable the position is not constant. Therefore you can address an object by name. See section Object Naming.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.2.3 Object Naming

For addressing objects on a long term valid basis every object can individually be tagged by a name. Assigning a name to an object is as simple as setting the attribute ‘name’ with a unique string on this object. Of course you can request an objects name by reading the attribute ‘name’.

The name is a string that should be made up of characters ‘a..z’, ‘A..Z’, numbers ‘0..9’ and the underscore ‘_’. Other special characters are only allowed as far as they are explained in the following text.

It is up to you to ensure unique names. Reuse of an already assigned name will unname the prior object and assign the name to the new object. To simplify the naming of larger groups of similar objects you can add the hash sign ‘#’ as the last character to a name, e.g. ‘mydoor#’. This causes Enigma to add a unique random number to the given string. Thus an auto named object will never unname another prior auto named object. But if you delete an auto named object that has been named e.g. ‘mydoor#103284’ the number and the same name may be assigned to another that is created later on.

All named objects are registered by the named object repository. The API provides a variable ‘no’ that allows you to retrieve any named object, e.g. ‘no["mylaser_a"]’. You get an Object Reference or ‘nil’, if no object is registered by the given name.

As you can auto name groups of objects you are allowed to use the wildcard characters ‘?’ and ‘*’. The question mark replaces a single arbitrary character, the asterisk any number of arbitrary characters. E.g. ‘no["mydoor#*"]’ retrieves all auto named ‘mydoor’ objects in a single object group.

Many object attributes like ‘target’, ‘destination’ need object references to other objects. Besides a volatile Object Reference you always can provide a name string as a long term valid object reference. If the attribute allows several objects to be given you can either give a group of object references, a table of object names or a object name with wildcards. Thus the string ‘"mydoor#*"’ is a valid target.

Often switches are located near by their target object. As a major shortcut you can reference the nearest object out of a group by prefixing its name with an ‘@’ character.

 
ti["F"] = {"st_floppy", target="@door#*"}
ti["D"] = {"st_blocker", name="door#"}

With this tile declaration you can describe arbitrary number of floppy switches and nearby blocker doors in a world map all by the same two tile key characters. Every floppy switch will target the nearest blocker door. If two targets are given within the same distance the one located in the south will win. If the targets are additionally horizontally aligned the one located in east will win. In the rare case of objects located on the same position stones will precede items, floors and actors. The chosen target or destination depends just on the location of these objects and their type, but nothing else. Thus you can rely on a stable selection mechanism. Nearest Object Clustering may help you in case of unexpected selected equidistant targets.

Auto naming and nearest object features help you to reduce the number of needed tile declarations. Resolvers like res.autotile and res.composer are another feature for reducing the need of tile declarations.

Another unique feature of object names is their late on access evaluation. This allows you to reference an object prior to its existence. E.g. if you want to set two vortices each declaring the other one as its destination, object names are the favorite solution:

 
wo[{3,4}]  = {"it_vortex", name="vortex1", destination="vortex2"}
wo[{15,9}] = {"it_vortex", name="vortex2", destination="vortex1"}

In general you will need to use object name references within any tile declarations as none of the referenced objects will yet exist at the point of tile declarations.

Objects do change over time. Doors do open, a chess may be recolored, a blocker stone may shrink to a blocker item. This means that the kind of the objects will change. But in many cases this means that the volatile object reference will brake, too. For the sake of the level authors the identity of the object will be transferred even if the reference gets invalid. And like the user attributes the name is part of the object identity. Thus if you name an st_blocker and it shrinks to an it_blocker you will retrieve this item if you ask the name object repository for the named object.

When an object like a door is completely killed, e.g. by an it_seed, it can no longer be targeted by active objects like switches. A still existing reference to a no longer existing object does not cause problems on Messages. But what about the nearest object references? To avoid problems due to killed objects the standard nearest object reference with just one ‘@’ as prefix are finalized on Level Initialization. This means that they get substituted by the unique name of the nearest of all existing objects at a point of time when all objects have been created, but before the user takes action and can incidentally kill a candidate.

But sometimes you may like a dynamic nearest object target or destination. One that is evaluated when it gets accessed. By prefixing a name with ‘@@’ the reference will not get finalized on initialization but remains dynamic.

 
ti["c"] = {"it_coin_s", "magic#"}
ti["v"] = {"it_vortex", destination="@@magic#*"}

Setting three magic coins and one vortex in your map will teleport the marble to the grid of that coin that is nearest to the vortex at the moment of teleportation.

To avoid unexpected problems with invalid object references a few critical objects are internally autonamed if the level author does not provide a name. But these unique names should never interfere with the user assigned object names.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.2.4 Object Attributes

One of the key concepts for the versatility of Enigma is possibility to fine tune objects by means of attributes. The level author is not limited to a fixed set of preconfigured objects as given by the object kind.

An attribute is a name, a string, with an assigned value. E.g. ‘obj["inverse"]=true’ sets a single object attribute to a boolean value and ‘{"it_magnet", range=6.5}’ describes a magnet item with an initial set floating point attribute.

The scope of values is manifold. Most Lua types and a bunch of Enigma specific types can be assigned:

If we speak of a bool value we do it in the sense of Lua 5, that means with the possible values ‘true’ and ‘false’.

Many enumerated values like orientations and colors are covered by the integer numbers.

Of special interest is the value ‘nil’. Just a few attributes make direct use of the value ‘nil’, e.g. "color" on some objects. If you set an attribute to value ‘nil’ you do actually reset its value to the default value. E.g. if you set the attribute "orientation" of st_boulder to ‘nil’ it will be set to its default, which is actually ‘NORTH’, an enumerated orientation value. A subsequent read of the attribute will return this value. Just those attributes that allow a nil value will ever return ‘nil’ on a read access. As a direct consequence these attributes always default to ‘nil’.

The authors of Lua did decide to prohibit the usage of ‘nil’ as a value in Lua tables. As we make heavy usage of anonymous tables as object declarations, you would not be able to set such attributes to ‘nil’. You would need to set such attributes explicitly. As a workaround we added a custom value ‘DEFAULT’ that can be used anywhere to set attributes - even within Lua tables.

 
mySwitch["color"] = nil
mySwitch["color"] = DEFAULT
wo[{3,6}] = {"ac_marble_black", player=DEFAULT}

Note that ‘DEFAULT’ is not equal to ‘nil’. They are different values concerning Lua. They just result both in attributes reset to their default. If you request a nil valued attribute you will always receive the Lua value ‘nil’. ‘DEFAULT’ will never be returned by the engine.

A group is an ordered set of Object References. As all contained objects must exist this value is seldom used for attributes in object declarations. But it is very useful for postprocessing of objects and for usage within Callback Functions.

The most complex attribute value type are the tokens. Their purpose is the specification of one or many objects. As Enigma provides several means to do that this value type combines and mix all possibilities. A tokens value may be a string, representing an object name, an object reference, a group or a table with any of these basic types in any sequence and number. E.g. the following right sides are all valid tokens for the attribute ‘target’:

 
obj1["target"] = "mydoor"
obj2["target"] = myobject
obj3["target"] = grp(ojb1, obj2, obj3)
obj4["target"] = {"mydoor", myobject}
obj5["target"] = {grp(ojb1, obj2, obj3), "mydoor", myobject, "anotherdoor"}

This versatility is useful to set tokens attributes independent of the given object reference types.

The chapter Common Attributes and Messages and its followers describe the existing object attributes in detail.

Besides these predefined attributes the level author can store own information on objects for later retrieval. Any name starting with an underscore ‘_’ can be used for level specific purposes. This prefix has been chosen as the resulting names are still valid Lua names. Common usage patterns are switches or triggers with callback functions. These functions provide the sender, the switch or trigger, as an argument. If you attach the same function to number of senders you can store the necessary context information within the sender.

The internal engine uses object attributes as well. Such inaccessible attributes are named with a leading dollar sign ‘$’. They may appear in the documentation for C++ developers information. Level authors should ignore these attributes.

In some cases you may observe a different behaviour on setting an attribute within the object definition and setting the same attribute while the object is already on the grid. E.g. a door ‘{"st_door_h", state = OPEN}’ is opened from the very beginning. Whereas ‘mydoor["state"] = OPEN’ on a closed door will start opening the door. This takes a short time until the door is really open. You find more details on these as aspects in the section The Lifecycle of a Level.

If you ever look into the C++ code you may wonder about the implementation of attributes. They are not all directly stored in a map. Some of them are hold in object instance variables, other do not exist at all. Objects attributes are an abstract concept that unifies several internal features within a common simple API for level description code. Within the C++ engine subtle reasons like performance optimization forces a much more complex handling.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.3 Methods of Interaction

Having looked at the description of the initial object composition of a level world we still need to understand how to configure the dynamic behaviour of a level.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.3.1 Messages

You can generate an initially open door by setting its attributes. But how can a switch stone open a door when it is hit by a marble? It simply sends a message ‘open’ to the door. Another switch may send a message ‘on’ to a laser or ‘ignite’ to an it_dynamite. On explosion the dynamite will in turn send automatically ‘ignite’ messages to the neighbour grid positions.

Messages are a simple universal function or from the receiver object and the Lua level authors point of view a "method". It takes two arguments - the message name, a string, and an optional value. E.g.

 
mydoor:message("open")
myboulder:message("orientate", NORTH)

mydoor:open()
myboulder:orientate(NORTH)

The last two examples are a common abbreviation of the first two ones.

Messages may return a value. But most messages just return ‘nil’.

You can send any message to any object. Not supported messages are silently ignored. This is the reason that an exploding dynamite can send ‘ignite’ messages to its neighbours without knowing if the objects can be ignited at all. Further on the dynamite has not to bother with the recipients of the messages. Due to messages the sender and the receiver objects are totally decoupled concerning the code base. Thus the level author just needs one method that allows sending arbitrary messages to arbitrary objects.

You should not send a message during initialization of the level. You configure the switch to send an ‘open’ message to the door by Target - Action. Within a Lua Callback Function you may send messages during runtime to any object.

All messages are listed and described in Common Messages and the subsequent chapters.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.3.2 Target - Action

The "target action paradigm" is a classical object oriented method that allows you to easily plug together objects. One object is triggered by a function call or by an event like an actor hitting a stone, crossing over or applying an item. You simply plug this object to another target object and tell it to send an action message. Every time the first object is triggered it will send the message to its target.

You configure such a target action by setting the attributes ‘target’ and ‘action’ on the first object. E.g. a for a switch stone that should open a door named ‘mydoor’ you can write:

 
{st_switch, target="mydoor", action="open"}

Objects like the switch can be triggered on and off. Each time they will perform the action. If you would like the door to open and close in turn to the switch you need another action than ‘open’. The universal message for changing targets in their alternate states is ‘toggle’.

 
{st_switch, target="mydoor", action="toggle"}
{st_switch, target="mydoor"}

Now the door will toggle in sync with the switch between its open and closed state. The message toggle can be used quite independent of the target object. In fact it is the default action message. As a default you may omit the action in this case as it is demonstrated by the second example.

But keep in mind that toggling just changes the state of the target. If you start with a switch in off state and an open door, the door will close when the switch in turned on. They will not sync. If you configure two switches both targeting the same door, you will have no clear relationship between the switch states and the door.

As you remember messages can take a value. Action messages are no exception. Every object sends its actions with a value, usually a bool value. A switch sends a value ‘true’ if it just switched on, and a value ‘false’ if it just switched off. The appropriate message for the door would be the universal message ‘signal’:

 
{st_switch, target="mydoor", action="signal"}

Now the door will open when the switch is turned on and close if the switch is turned off.

The message signal takes an integer value of ‘0’ or ‘1’. Indeed the action value does not match. But in this as in many other cases the messages and values are designed in a way that they are automatically converted to the appropriated type. This compatibility is the basis for a seamless plugging of objects.

In many cases authors face the task of triggering two or more objects by a single object. ‘target’ and ‘action’ are both able to take multiple values. ‘target’ is of type tokens, as described in Object Attributes, whereas ‘action’ can be a string or a table of strings.

 
{st_switch, target={grp(ojb1, obj2, obj3), "mydoor", myobject, "anotherdoor"},
            action={"toggle",              "open",   "turn",   "close"}}

All objects described by a token receive the related message in the action table. If not enough messages are listed the default action ‘toggle’ will be sent.

Usually actions are performed at once. That is very important as the sequence of actions if often essential. Consider an st_box being pushed from one it_trigger to a neighboring one, or just an ac_marble moving from the first trigger to the neighboring one. In both cases it is important that the first trigger is released prior the second one to be pressed. If this sequence gets mixed up both triggers could be pressed by a single object for a moment what could cause major shortcuts in a level. Thus all actions are performed in the logical sequence and in a stable, repeatable sequence without any random.

Action may be simple or sometimes be very complex world rearrangements. But in any case you should never everkill’ the sender object. Killing the sender can cause application crashes! Be aware that even chained actions are not allowed to kill any prior sender object. Thus an it_trigger that toggles an st_switch which in turn kills the first trigger is as critical as the trigger killing itself. We do generally discourage you to kill any object within its own action, as there is no dissolving animation and the WYSIWYG user paradigm is violated, too. But if there is urgent need for reasons of the level gaming logic you can perform the action in a secure, delayed mode. Just add the attribute safeaction with value ‘true’ to the self killing sender object. The action will no longer be performed at once, but with a minimum delay in a manner that will never cause crashes. But be aware that even a minimum delay, which is still within the same timeslice, may disturb the sequence of actions. This can cause unexpected logical results on the other hand.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.3.3 Callback Function

The most powerful extension to the Target - Action paradigm that you can think of are callback functions. Instead of a target object as receiver of an action message you can supply an own Lua function that is called whenever the action is triggered.

 
{"st_switch", target="my_magic", action="callback"}
{"st_switch", target="my_magic"}

The ‘target’ is the name of the function as a string. You may set the ‘action’ to the string ‘"callback"’ for purpose of clarification, but it is not necessary as you see in the second example. The engine identifies the target to be of type of a Lua function and thus the action needs to be a callback. But you should note and remember that it is up to you to ensure that all object names and callback functions names are unique.

Let us look at the syntax of such a callback function

 
function my_magic(value, sender)
    if value == true then
        wo[sender + {1,0}] = {"it_coin_s"}
    end
end

The function is called with two arguments. The first one is a value. The type and contents depends on the issuing object, but in most cases it is a boolean value. You will find the value described in the objects description. The second argument is the reference of the calling object.

In the example we check if the st_switch did just toggle to ON. If this is given we take the switch, which is the sender, as a position and set a new it_coin to the grid east of it - a small bank automate that supplies money.

The Advanced Lua Examples will show examples of real powerful callback functions with a line by line comment.

Further usage and aspects of callbacks in the level’s lifecycle are given in the section Callbacks and Load Balancing.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.3.4 Object State

A key concept for the ability to plug together objects like switches and doors are the very simple state machines of these objects. Most objects are described by simple machines with just 2 states like ‘ON’,‘OFF’ or ‘OPEN’, ‘CLOSED’. These objects can be plugged together by just few common messages. Further on these simple state machines are suited to the gamers who do not want to read manuals but want to explore the objects by playing with just a few tests.

Even though states are usually named by appropriate uppercase names like above, the states are integer numbers starting with ‘0’ usually related to the default state. But some objects use another mapping due to historic reasons. E.g. states that are orientation related use the state ‘3’ representing ‘NORTH’ usually as the default and number the orientations clockwise down to ‘0’ representing ‘WEST’.

In most cases it is sufficient to perform a state independent common action like toggle. Even two stated objects can be easily synchronized by the standard action signal. But sometimes you may want to perform very state specific actions. Let us look how this can be done.

E.g. let us take an st_fourswitch, that has four states, and two st_laser which should be switched on and off. Both lasers should emit their beams while the fourswitch is in 3 of its states. But one of them should be off just while the fourswitch is in the ‘EAST’ state and the other should be off just while the fourswitch is in the ‘WEST’ state. This can be done by usage of state dependent target and actions:

 
{st_fourswitch, target_3="laser#2", action_3="on",
                target_2="laser#1", action_2="off",
                target_1="laser#1", action_1="on",
                target_0="laser#2", action_0="off"}

Adding a number as suffix to ‘target_’ and ‘action_’ gives you special target and action attributes that will take precedence over the general ‘target’ and ‘action’ attributes if the state value equals the suffix number. An alternative declaration would be:

 
{st_fourswitch, target={"laser#1", "laser#2"},
              action_3={"nop",     "on"},
              action_2={"off",     "nop"},
              action_1={"on",      "nop"},
              action_0={"nop",     "off"}}

Here we do address both lasers in all states. But one of them receives a nop message that stands for "no operation". In fact this message will never be send. It is just a dummy message that we have need of for syntax reasons in the case above.

Another example are two it_trigger that switch a laser. An object pressing the first trigger should switch the laser on, an object pressing the second trigger should switch it off. But a trigger is two stated and performs one action on being pressed and another on being released. Thus we want to block the actions on trigger release events:

 
{it_trigger, name="on_trigger",  target="laser#1", action_1="on", action_0="nop"}
{it_trigger, name="off_trigger", target="laser#1", action_1="off", action_0="nop"}

The blocking of ‘action_0’ is essential and can not be omitted, as otherwise the default action would be performed. This would be a ‘toggle’ message that would switch the laser.

As this useful default mechanism can sometimes be annoying you can switch off the default message by setting the nopaction attribute to true.

 
{it_trigger, name="on_trigger",  target="laser#1", action_1="on", nopaction=true}
{it_trigger, name="off_trigger", target="laser#1", action_1="off", nopaction=true}

When an objects leaves a trigger the state ‘0’ action will be performed. As neither ‘action_0’ nor ‘action’ is specified the default action will be performed, which is now ‘nop’.

If you ever look into the C++ code you may note that many objects do have much more complex state machines than you expect from the level authors and gamers view. This is due to running animations, timers, etc.. The C++ objects map their complex internal state set to the much simpler external state set. This is the main reason that some features that level authors request can not be provided in the Lua API.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4 The Lifecycle of a Level

Snapshot Levelloading, Initialization, Runtime Callbacks, Ending Conditions - the mystery of Oxyds and Meditation


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.1 Library Preloading


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.2 Snapshot Principle

Most levels contain objects that take influence on each other. A switch might toggle a door by Target - Action, marbles may press a trigger, or a laser might activate a laserswitch or transform a hammer into a sword. Of course it is essential to know how to set up such objects to get the desired start configuration without the objects changing unexpected on level initialization.

The snapshot principle is a simple thumb rule that you can rely on in describing the level as a snapshot of object at a given point of time. Every object has just to be configured as it should be at the given time. All interactions that would take place in a running game do not take place while setting objects during initialization.

E.g. if a switch toggles a door and the switch should be initially on and the door should be initially open you describe the object with exactly these attributes:

 
{"st_switch", target="mydoor", state=ON}
{"st_door", name="mydoor", state=OPEN}

A laser that is initially on that illuminates a laserswitch needs an initially active laserswitch. But of course no attribute exists that would allow you to set a laserswitch active. The snapshot principle includes the rule that all internal states are updated without external actions. This means that the laserswitch will show up active without causing an action on its target.

 
{"st_laser", state=ON}
{"st_laserswitch", target="mydoor"}

What about objects that transform on laser light. The snapshot principle keeps the object from transforming during initialization. A hammer that is set in an initially existing laser beam will not transform to a sword. It remains as a hammer that will transform on any subsequent new laser light during the game.

Of course it cannot be allowed to describe impossible initial level states. Objects like dynamite do explode immediately on a laser beam hit. Thus a dynamite item in an initial laser beam is a fault that causes an exception. The snapshot principle forces you in this case to set an explosion item instead of the dynamite.

Some objects do process internal state transformations that cannot be configured by attributes. But some of these states may be of interest on describing a snapshot of a level. Where possible a special object subkind exists with a suffix of ‘_new’. These objects can be used in the initial level description to set objects in special initial states. E.g. it_blocker provides such a special subkind. Note that these objects will never report their initial subkind on a kind request as they come into existence as a standard object.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.3 Level Initialization

Knowing what has been preloaded and knowing exactly which objects we have to set in which state, it is time to have a look on how your level code is processed. The main issue is to guarantee that all parts referenced have been set up properly in advance.

Prior execution of the first line of your code the world exists just as an abstract handle, but not as grid array able to accept objects. Thus the first lines of code should set up all Global Attributes deviating from their defaults. Even though many attributes can be set or changes later on and even on runtime, there are some attributes like ProvideExtralifes that take only effect if being set prior world creation, or others like MaxOxydColor that must be set prior their first usage. Our recommendation is to collect all global attribute settings at the very beginning of the level.

The second section of your level code should be the declaration of tiles. Being just declarations these code lines do not set objects to the world. They just depend on global attributes and may reference each other. Listing them all together in a code section makes it easy to maintain the overview and to avoid dependency conflicts.

If you use Resolvers you should put their declarations in the next code section as they may refer to tiles and global attributes and need to be set up prior the world creation.

The central statement of every level is the World Creation. It sets up the world to its size and sets initial objects according to the given tile declarations to the grid array. While you are free to add and change any of these objects later on the size of the world is fixed and can not be changed.

Thus subsequent code lines should add other objects, draw additional maps of objects and finalize some objects. The most common statement for such a finalization is the shuffleOxyd method. It needs to know all st_oxyd to be able to color and shuffle them. Another finalization may be a custom rendering of a maze, that extracts the maze shape out of the level map (see section res.maze).

This post world creation code may well have the need of loops or even some local used functions that need to be integrated to precede. Please keep such functions near by their usage and within the same code section.

Another set of functions that you may want to add are Callbacks and Load Balancing. We recommend to append these functions as the last section as they are not called within the level initialization itself.

But there is one very special exception. The postinit() callback is called after the level initialization code has been processed and all subsequent engine internal initialization has been finished. If this function is present in a level it gets executed directly before the first mouse movement event gets processed. Thus you can rely within this function that all objects are set up in their final state. If you have need of such a postinit callback you should put it after all the level intialization code and in front of other callback functions that will be executed on subsequent events.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.4 Object Transformation

During runtime some Enigma objects do transform into other successor objects, like an st_blocker/it_blocker, an st_brake/it_brake, an it_rubberband/ot_rubberband, an it_hammer/it_sword,...

Even though the successor object may have other attributes, some attributes and especially any user attributes should be maintained. In fact the objects name, its target and action attributes and all attributes starting with an underscore ‘_’, the user attributes, are transferred to the successor object. Thus you can rely on the successor to message the same target and you can it access it via its old name.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.5 Named Positions

Many stones are movable and if the user can not push them, most may still be swapped. Items may be picked up by actors or be killed in a burning fire. Thus in most cases it is preferable to mark anchors or shapes in the floor. On every grid position a floor object is guaranteed and they are much more stable than other objects. But nevertheless a user may push an st_box, an st_puzzle or other floor building stone on an fl_water or fl_abyss. Furthermore a user may drop and ignite an it_bomb that destructs the floor leaving a new fl_abyss. In all these cases you may loose a named anchor or an essential part of a named grid area accessible as an object group.

Thus for every named floor that gets killed its position is stored in a repository under its name. You just need to retrieve the named positions instead of the named objects if you want to get all affected floor positions.

 
ti["~"] = {"fl_water", "water#"}
...
function sweet()
    wo[po["water#*"]] = {"it_cherry"}
end

Note that a request for a named position will include all positions of matching named objects as well as those named positions derived from killed floors.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.6 Callbacks and Load Balancing

The most flexible feature for a level author to establish an unique behaviour of his level are Callback Functions.

Target - Action kill warning ot_timer

global and res.*.function


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.7 Level Restart


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.8 Ending Conditions

Two essential questions remain to be answered. When and under which conditions is a level successfully finished. How do I declare a level as being a meditation level in opposite to a standard oxyd pair opening level.

In fact there is no ’meditation’ flag, neither within the XML Level Info metadata nor as one of the Global Attributes. That means there is no formal distinction between both level ’types’. But there are two different types of ending conditions. Both are checked permanently and whichever is fulfilled first wins. Thus Enigma allows you to write true hybrid levels that provide st_oxyd as well as it_meditation allowing the user to solve the level by two totally different means.

The main way to end the game is to fulfill the oxyd pair opening condition:

The game is over when the user succeeds in opening all regular colored st_oxyds by pairs.

This implies that there is at least one pair of regular colored oxyd stones and that all oxyd colors appear in even number of instances. Whereas you will always add at least one pair of oxyds for a standard level, you may simply loose track of the number of instances. Therefore the engine will permanently check at runtime that every oxyd color appears in an even number of instances. Any violation causes an error. In case you will add or delete oxyd stones you need to disable this checking by setting the global attribute AllowSingleOxyds to true. Now it is your reponsibility as an author to ensure that the level remains solvable by adding or removing pairs only.

The second way to end the game is to fulfill the meditation condition:

All ac_pearls must reside for at least one second within the uneven area of an it_meditation, all it_meditation marked as essential must be occupied and the number of ac_pearls must be equal to the number of occupied it_meditations.

This implies again that there exists at least one ac_pearl and that no two pearls can reside within the same it_meditation. There must be at least the same number of it_meditation as of ac_pearl, but there may be more it_meditations as long as they are not marked as being essential. A surplus of it_meditation can easily occur due to explosions of it_dynamite.

A level that should be solved by fulfillment of the meditation condition can contain st_oxyd and by setting AllowSingleOxyds to true you can add odd numbers of oxyd stones of a color. On the other hand you can add ac_pearl to a level that should be solved by fulfillment of the oxyd pair condition. But you must carefully check that the user can not rest the pearls in given it_meditation or can create it_meditation by it_dynamite. Marking more it_meditation as essential than existing ac_pearls can avoid shortcuts.

A level that is set up to allow the user to fulfill both conditions is called a hybrid level. Of course it is a difficult task to provide equivalent solutions for both approaches.

Independent of the condition type all actors marked as essential need to be alive at the moment the condition is fulfilled. Mainly in existing legacy levels but may in some very carefully designed future levels the author may allow the user to sacrify an actor to fulfill the condition by setting the global attribute SurviveFinish to false. In this case a mable may shatter while the condition is fulfilled. Of course the essential actor may not shatter in advance, because as soon as the shattering is over the essential actor will cause a level restart, if it can not be resurrected.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5. Lua API

Knowing the basic principles of an Enigma level’s world you now just need the language glue to write your first level. Enigma levels are written in the language Lua as of version 5.1.4. This powerful language gives you the ability to write most complex, dynamical levels, while being nearly transparent on writing basic standard levels. Indeed there is no reason to dig into this language at the very beginning.

With the second Lua API version, as of Enigma 1.10, we designed an optimized way of describing levels in a very short and readable manner. Thus we would like to introduce you to this API by giving several examples from a basic level to most thrilling dynamic real Enigma levels. You should be able to start your first experiments just after reading the first example with its explanations.

For your convenience we do color the Lua code part. Predefined Lua variables and functions are colored in green. Enigma internal string constants as object kinds, attribute or message names are colored in blue. Level specific variable names and value constants are colored in magenta.

After the examples and a short overview the details of the language specific API part are given, as you can expect it for a reference manual. Please note that additional Advanced Features are described in a separate chapter.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.1 Basic Lua Examples

Let us look at two basic onescreener levels, that make use of all basic techniques. While the first level is a little bit artificial, as it is designed for demo purposes only, the second one is a quite dynamic real level out of the Enigma levelpacks.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.1.1 Basic Example

Let us view the source code. We did add a line count in the first two columns for reference purpose within this section. These line count numbers are not part of the source code itself!

 
 1    <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
 2    <el:level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://enigma-game.org/schema/level/1 level.xsd" xmlns:el="http://enigma-game.org/schema/level/1">
 3     <el:protected>
 4       <el:info el:type="level">
 5         <el:identity el:title="Basic Level" el:subtitle="" el:id="20080721ral513"/>
 6         <el:version el:score="1" el:release="1" el:revision="$Revision: 1.23 $" el:status="experimental"/>
 7         <el:author el:name="Ronald Lamprecht" el:email="ral@users.berlios.de"/>
 8         <el:copyright>Copyright © 2008 Ronald Lamprecht</el:copyright>
 9         <el:license el:type="GPL v2.0 or above" el:open="true"/>
10         <el:compatibility el:enigma="1.10"/>
11         <el:modes el:easy="true" el:single="true" el:network="false"/>
12         <el:score el:easy="-" el:difficult="-"/>
13       </el:info>
14       <el:luamain><![CDATA[
15
16    wo["ConserveLevel"] = true
17
18    ti[" "] = {"fl_samba"}
19    ti["."] = {"fl_abyss"}
20    ti["~"] = {"fl_water"}
21    ti["#"] = {"st_granite"}
22    ti["X"] = {"st_oxyd"}
23
24    ti["L"] = {"st_laser", orientation=EAST, state=ON}
25    ti["M"] = {"st_lightpassenger", interval=0.04}
26
27    ti["P"] = {"st_polarswitch", name="polar"}
28    ti["T"] = {"it_trigger", target="polar"}
29
30    ti["^"] = {"st_boulder", "boulder", orientation=NORTH}
31    ti["F"] = {"st_fourswitch", target="boulder", action="orientate"}
32
33    ti["D"] = {"st_door_d", "door", faces="ew"}
34    ti["B"] = {"it_blocker", "wall#"}
35    ti["S"] = {"st_switch", target={"door", "wall#*"}}
36
37    ti["v"] = {"it_vortex", "left", destination="right"}
38    ti["V"] = {"it_vortex", "right", destination="left"}
39
40    ti["O"] = {"st_turnstile", flavor="red"}
41    ti["E"] = {"st_turnstilearm", orientation=EAST}
42    ti["N"] = ti["."] .. {"st_turnstilearm_n"}
43
44    ti["+"] = {"fl_samba", checkerboard=0} .. ti({"fl_wood", checkerboard=1})
45
46    ti["1"] = {"#ac_marble"}
47
48    if wo["IsDifficult"] then
49        ti["="] = ti["~"]
50    else
51        ti["="] = ti["~"] .. {"it_strip_ew"}
52    end
53
54    w, h = wo(ti, " ", {
55        "####################",
56        "#      ....++++++~ #",
57        "L   PM ..N.++~~~~OE#",
58        "#######  T~++++++. #",
59        "#     ^   ~++++++# #",
60        "#         =++++++X X",
61        "#         ~++++++# #",
62        "#~~~~~~~~~~~~~+++X X",
63        "#    ~   B   ~+++###",
64        "F    ~   B   ~+++++#",
65        "# 1  ~   B   #+++++#",
66        "S   v~V  B   D+++++#",
67        "####################"
68    })
69
70    wo:shuffleOxyd()
71
72     ]]></el:luamain>
73        <el:i18n>
74          <el:string el:key="title">
75            <el:english el:translate="false"/>
76          </el:string>
77        </el:i18n>
78      </el:protected>
79    </el:level>

The resulting level looks like this in the game

images/ralD006_1

Let us now analyse the code line by line.

Lines 1 to 14 are the XML metadata of the level as described in Level Basics. The only line worth mentioning is

 
10         <el:compatibility el:enigma="1.10"/>

You need to declare the level to be compatible to Enigma 1.10 or higher for the new API 2 as described in this reference manual. A value less than 1.10 indicates compatibility to a previous Enigma release that did use the old API 1, which should not be mixed up with the new API 2.

The Lua part starts with line 15:

 
16    wo["ConserveLevel"] = true

Like most levels it starts with setting Global Attributes. The handle of our world is ‘wo’. This object reference is preset (see section World as an Object). Concerning Lua it is an ‘userdata’, but most of its usage syntax is identical to that of Lua tables. Thus we access an attribute by providing the desired attribute name in square brackets. As we give a literal attribute name, we have to put it in double quotes ‘"’. In total this line requests the world to resurrect a killed actor as long as there are enough extra lifes to conserve the running level (see section ConserveLevel). In fact ‘true’ is the default value. So we could have left this line out. But remember it is a demo level.

The second part of a level are the tile definitions as explained in World’s Shape and Coordinates. Let us start with the most simple ones:

 
18    ti[" "] = {"fl_samba"}
19    ti["."] = {"fl_abyss"}
20    ti["~"] = {"fl_water"}
21    ti["#"] = {"st_granite"}
22    ti["X"] = {"st_oxyd"}

Again we use a handle ‘ti’ which is a preset object reference for the tile definition repository. Like the world it is a Lua ‘userdata’. And we can access it like the world by giving the desired index in square brackets. These indices are free to your choice. They have to be of a common character length if they are referenced in the world map below. For a small level one character keys are sufficient. You can use any ASCII character that Lua is aware of. That means upper and lower case characters ‘A-Z,a-z’, the numbers and special characters besides backslash ‘\’ and double quote ‘"’.

The assigned object definition are given as Lua anonymous tables, the curly braces, containing in the most simple case just the desired Object Kind. As it is again a literal string, it has to be quoted. Without any further specification the objects are taken in their default configuration as described in Floor Objects and following chapters.

 
24    ti["L"] = {"st_laser", orientation=EAST, state=ON}
25    ti["M"] = {"st_lightpassenger", interval=0.04}

These two lines define objects with custom configuration. The st_laser should send its beam to the east and should start being switched on. The st_lightpassenger should move a little bit faster than usually. Both times we just have to add comma separated additional attributes. The attribute names are not quoted as they are followed by an equal ‘=’ sign.

 
27    ti["P"] = {"st_polarswitch", name="polar"}
28    ti["T"] = {"it_trigger", target="polar"}

An st_polarswitch named for reference usage (see section Object Naming). The it_trigger sets up a Target - Action, the target being our polarswitch. The action attribute is omitted. It defaults to the message ‘toggle’. Thus any actor or stone on top of the trigger makes the polarswitch transparent, but switches it back to opacity when leaving the trigger.

 
30    ti["^"] = {"st_boulder", "boulder", orientation=NORTH}
31    ti["F"] = {"st_fourswitch", target="boulder", action="orientate"}

Another pair of objects that are coupled by Target - Action. The st_boulder starts trying to move to north. This time we name the object just by giving the name as the second comma separated string. We omitted the attribute identifier ‘name =’. This is a shortcut for this most common attribute which requires the name to be given as the second value directly after the objects kind.

The st_fourswitch references the boulder as its target. We need to give the action as well, as we want to make use of a special action that directly steers the boulder according to the fourswitch orientation.

 
33    ti["D"] = {"st_door_d", "door", faces="ew"}
34    ti["B"] = {"it_blocker", "wall#"}
35    ti["S"] = {"st_switch", target={"door", "wall#*"}}

And another even more complex Target - Action. We want a single st_switch to toggle a st_door as well as set of it_blockers at the same time. The gaming idea is that neither with switch on nor with switch off the marble can pass both obstacles. The gamer needs to steer the boulder through the blocker wall to pass these obstacles.

The setup of the door is simple. We just need to name it to be able to reference it later on. We want to use several blocker objects and we need to name each for reference purposes. We do this by appending a hash sign ‘#’ to its name as described in Object Naming. Every blocker gets a unique name. The switch needs to list all these objects as its targets. This is done by an embedded anonymous table given by the curly braces and comma separated values. The first one is our door’s name, the second one is a wildcarded string that describes all our blocker objects. The asterisk stands for any suffix that may have been added behind the hash in the process of autonaming of our blockers.

 
37    ti["v"] = {"it_vortex", "left", destination="right"}
38    ti["V"] = {"it_vortex", "right", destination="left"}

We want to use two it_vortex that are connected to each other allowing the marble to warp into both directions. We set up both vortices with a unique name and add the attribute ‘destination’ referencing the other vortex’ name.

Note that it is no problem to reference the right vortex in line 37 while it is named later on in line 38. We are still just defining tiles and not creating any objects at all.

 
40    ti["O"] = {"st_turnstile", flavor="red"}
41    ti["E"] = {"st_turnstilearm", orientation=EAST}
42    ti["N"] = ti["."] .. {"st_turnstilearm_n"}

Another object group is an st_turnstile cluster with one arm disconnected. The first two definitions are straight forward. But in line 42 we precede the arm’s definition by another tile reference. It is the abyss tile defined in line 19. By concatenation, the two dots .., of a tile and an object definition we can define a new tile that is composed of both objects. In this case we define a turnstile arm on top of an abyss floor.

You may be wondering why we did not define floors for the other stone and item tiles. We make use of the tile definition in line 18 that we will declare later as the default floor for our level. Thus any tile declaration that does not provide its own floor will set this default floor.

 
44    ti["+"] = {"fl_samba", checkerboard=0} .. ti({"fl_wood", checkerboard=1})

Just for fun we want to provide a checkerboard floor on the right side of our level. This can be done by usage of the checkerboard attribute. Again we concatenate two object definitions for a single tile. Both are floors. That means for each grid position we try to set both floor types, but just one meets the checkerboard condition and will be set.

Please notice that we did convert one the floor object definitions to a tile definition by the function call ‘ti()’. This is necessary as Lua does not know how to concatenate two anonymous tables. One argument of the concatenation has to be a tile.

 
46    ti["1"] = {"#ac_marble"}

Finally we do need our marble. Unlike other objects it can be positioned anywhere within a grid. The most common position is the center of the grid. This is simply done by preceding the actor’s kind by a hash sign ‘#’.

 
48    if wo["IsDifficult"] then
49        ti["="] = ti["~"]
50    else
51        ti["="] = ti["~"] .. {"it_strip_ew"}
52    end

We encourage every level author to provide an easy mode for the levels. This is an example how to define mode dependent tiles. Like in line 16 we access a world attribute. But this time it is a read access of IsDifficult. In easy mode we want an it_strip on top of the water floor that allows the marble to pass and press the trigger. In difficult mode there should be no passage. Thus the special tile is identical to the water tile defined in line 20.

 
54    w, h = wo(ti, " ", {
55        "####################",
56        "#      ....++++++~ #",
57        "L   PM ..N.++~~~~OE#",
58        "#######  T~++++++. #",
59        "#     ^   ~++++++# #",
60        "#         =++++++X X",
61        "#         ~++++++# #",
62        "#~~~~~~~~~~~~~+++X X",
63        "#    ~   B   ~+++###",
64        "F    ~   B   ~+++++#",
65        "# 1  ~   B   #+++++#",
66        "S   v~V  B   D+++++#",
67        "####################"
68    })

After all tiles have been defined we can create our world simply by a map that uses our tile keys. The first argument is our handle ‘ti’, that defines how the keys should be resolved. The second argument is the key of our default floor. The third argument is the map as a table of strings, one for every line.

The world initialization returns the width and height of our world which are calculated by the map’s size.

 
70    wo:shuffleOxyd()

After the world is created and all objects are set, we can do some final postprocessing before the level starts to run. The most common task is the shuffling of the oxyds, which is just a method call of shuffleOxyd to our mighty world object.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.1.2 Colored Turnstiles

As this level is part of the Enigma levelpacks we recommend that you play the level first to get familiar with the used objects and their behaviour.

Now let us look at the essential Lua source code part of the level to understand how such an idea can be realized with the new API

 
ti[" "] = {"fl_sahara"}
ti["#"] = {"st_purplegray"}
ti["@"] = {"#ac_marble_black", "marble_black"}

ti["N"] = {"st_turnstilearm_n"}
ti["S"] = {"st_turnstilearm_s"}
ti["E"] = {"st_turnstilearm_e"}
ti["W"] = {"st_turnstilearm_w"}
ti["R"] = {"st_turnstile", action = {"open", "close"}, target = {"red#*", "green#*"}}
ti["G"] = {"st_turnstile", action = {"close", "open"}, target = {"red#*", "green#*"},
                           flavor = "green"}
ti["r"] = {"it_blocker", "red#"} .. ti({"fl_red"})
ti["g"] = {"it_blocker", "green#"} .. ti({"fl_lawn"})

ti["O"] = {"st_oxyd", flavor = "d", oxydcolor = OXYD_GREEN}
ti["o"] = {"st_oxyd", flavor = "d", oxydcolor = OXYD_RED}

w, h = wo(ti, " ", {
 -- 01234567890123456789
   "#O#####O############",
   "#   r N g N rO##O#O#",
   "#WRE#WGE# R ####g#r#",
   "#   r N r S  r N   #",
   "#g#g#WG #g##r# REr##",
   "# # N S r    g S  gO",
   "#@g RE#g#gWGE###g###",
   "# # S   g    r N  ro",
   "#r#r#WGE#r##g#WGEg##",
   "# N r S g N  r     #",
   "#WGE# RE# RE####r#g#",
   "#   g S r S go##o#o#",
   "#o#####o############"
})

wo:shuffleOxyd()

There are just four tile definitions that do all the dynamic actions. Let us look first at the blocker item definitions:

 
ti["r"] = {"it_blocker", "red#"} .. ti({"fl-red"})
ti["g"] = {"it_blocker", "green#"} .. ti({"fl-leaves"})

All blockers on red floors are autonamed with a name being composed of the prefix ‘red#’ and a unique random number being added by the engine as explained in Object Naming. This allows us to address all these blockers later on.

 
ti["R"] = {"st_turnstile", action = {"open", "close"}, target = {"red#*", "green#*"}}
ti["G"] = {"st_turnstile", action = {"close", "open"}, target = {"red#*", "green#*"},
                           flavor = "green"}

Whenever the marble hits and turns an st_turnstile it performs its actions on the targets. Here the author makes clever usage of multitargets and multiactions as described in Target - Action. On every turn of a red turnstile all objects named ‘red#*’, that are all our blockers on a red floor, will be sent a message ‘open’, whereas all blocks on a green floor, the second target group, receives the second action message ‘close’. It is essential to choose the ‘open’, ‘close’ messages instead of ‘toggle’, as more than one red turnstile may be turned in sequence, but just the first red turn should "toggle" all blockers. The next toggling should occur on the first green turn following thereafter.

Hope you got the basic idea of the new API. You may well start with you first level experiments. But you should return and read the following chapters with overview and advanced examples to write even more fancy levels.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2 API 2 Overview

Having analysed a first level it is time get an overview of the API 2 capabilities. Let us take a task driven approach by listing the different possibilities and use cases by example.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.1 Types Overview

But first we need to introduce you to the special Enigma value types besides the standard Lua types ‘nil’, ‘boolean’, ‘string’, ‘function’ and ‘table’:

Types:
position:  See section Position

A position within the world that can be described by an x and y coordinate.

positions: preset variable: po;   See section Positions Repository

The singleton type of the repository of all named positions.

object:  See section Object

An Enigma object like a stone, item, floor, other. Any object is a position, too.

group:  See section Group

A list of objects.

namedobjects: preset variable: no;   See section NamedObjects

The singleton type of the repository of all named objects.

default: preset variable: DEFAULT;   See section Object Attributes

The singleton type of default values that can be used instead of Lua’s ‘nil’ in anonymous table tile definitions.

tile:  See section Tile and Object Declaration

A description of one or several objects for a common grid position (floor, item, stone, actor)

tiles: preset variable: ti;   See section Tiles Repository

The singleton type of the repository of all tile instances.

world: preset variable: wo;   See section World

The singleton type of the world that contains all objects.

position list:  See section Named Positions

A list of positions.

Please note the four handles ‘po’, ‘no’, ‘ti’ and ‘wo’. You have noticed two of them in the previous section Basic Lua Examples. These are four variables, that are preset prior the level code gets executed.

API 2 uses mainly two character names for frequently used variables and functions to shorten the level code and to make it better readable. Authors should try to use either single characters or names that are three characters or longer for private variable names.

For the rest of this section let us assume that ‘obj’ is an object reference of a stone, item or floor, which means that is of type ‘object’. And let ‘pos’ be a valid variable of type ‘position’.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.2 Position Tasks

For reference details see section Position.

Creating Positions:
 
pos = po(7, 3)         -- using function "po()" to generate a position object
pos = po({7, 3})        -- using a table position constant as argument
pos = obj              -- every object is a valid position
pos = po(12.3, 3.7)    -- a position within a grid (for an actor)

Absolute positions are created by the function ‘po()’. But the most common way should be the reinterpretation of an object as a position. This lets you set other objects relatively to given ones.

Position Constants:
 
{7,3}     -- a valid position for all arguments and operations (see Caveats)

Anonymous tables with just two number values can be used in many cases directly as a position constant. In case of errors, e.g. when operators are not well defined like addition of two constants, which results in an attempt of adding two Lua tables, use the function ‘po()’ to convert the constant.

Coordinate Access:
 
x, y = pos.x, pos.y
x, y = pos["x"], pos["y"]
x, y = pos:xy()
x, y = obj.x, obj.y
x, y = obj:xy()

The x and y coordinate of a position or object can be read accessed like any object attribute. A position or object method call by ‘xy()’ returns both coordinate values at once. You can not set a position value by coordinate write access. Objects need to be set to a new world position. New positions can be calculated by position arithmetic.

Position Calculation:
 
pos = obj + {2,7}
dpos = obj1 - obj2
dpos2 = 2 * dpos
dpos3 = dpos / 2

Positions can be added or subtracted to get distance vectors. You can multiply and divide them with any number.

Center positions for set actors
 
pos_centered1 = pos + {0.5, 0.5}
pos_centered2 = #pos
pos_centered3 = #obj

Especially for positioning of actors you sometimes need the position of the center of a grid. Of course you can get it by addition of a constant position. But the ‘#’ operator applied on a position or an actor does the same in a simpler way.

Round a position to a grid
 
grid_pos = pos:grid()
grid_pos = ((pos1 - pos2)/2):grid()

A result of a position calculation needs sometimes to be rounded to integer grid coordinates. This is done by the ‘grid()’ method.

Position comparison
 
pos_centered1 == pos_centered2
pos_centered1 ~= pos_centered2    -- Lua's inequality operator

Position can be easily compared to equality.

Position existence
 
pos:exists()

true’ if a position is a valid position of the world. All positions outside of the world return ‘false’.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.3 Attribute Tasks

For reference details see section Object.

Single Attribute Setting:
 
obj["destination"] = po(7,3)
wo["Brittleness"] = 7

Object attributes as well as global world attributes can be set like Lua table values. They can take values of special Enigma types like position, object or group.

Multiple Attribute Setting:
 
obj:set({target=mydoor, action="open"})

You can set multiple attributes on any object at once with the objects ‘set()’ method. The argument is an anonymous Lua table with the attribute names as keys and assigned values of your choice.

Requesting Attributes:
 
value = obj["attr_name"]
value = wo["Brittleness"]
if wo["IsDifficult"] then ... end

Attributes of objects and the world can be read like Lua table key values.

Reset Attributes:
 
obj["length"] = nil       -- the default length, e.g. ‘1obj["color"]  = nil       -- delete color attribute - no color
obj["length"] = DEFAULT   -- the default length, e.g. ‘1

Any object attribute can be reset to its default value, which is the attribute’s "delete" operation, by assigning it the Lua ‘nil’ or the Enigma ‘DEFAULT’ value.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.4 Object Tasks

For reference details see section Object.

Creating Objects:
 
wo[pos]  = {"st_chess", color=WHITE, name="Atrax"}   -- on grid pos
wo[#pos] = {"ac_bug"}              -- actor centered on grid pos
wo[pos]  = {"#ac_bug"}             -- actor centered on grid pos
wo[pos]  = {"ac_bug", 0.3, 0.7}    -- actor with offsets to pos
wo[my_floor] = {"it_magicwand"}    -- set a wand on top of a given floor object
wo[pos]  = ti["x"]                 -- tile based object definition

Besides map based object creation, that you saw in the previous basic examples, you can create new objects on any world position directly. The world takes a position, that may well be an object, as key argument. The new object is described either by an anonymous Lua table, containing the kind string as first value and additional attributes as key value pairs appended, or by a tile object.

Object Naming:
 
no["Atrax"] = obj
wo[pos] = {"st_chess", name="Atrax"}
wo[pos] = {"st_chess", "Atrax", color=WHITE}

As explained in Object Naming, the names are the only longtime valid object references. You can explicitly name an object by assigning it at the named object repository ‘no’ to the name as the key. But most times you just supply the objects name as an object attribute. If you supply the name attribute as the second value in the anonymous table you can omit the key ‘name =’ part as a common abbreviation.

Object Autonaming:
 
wo[pos] = {"st_chess", name="Atrax#"}

As explained in Object Naming you can append a hash sign ‘#’ to a name and use the resulting string for arbitrary number of similar objects. This is especially useful for building groups.

Requesting Objects:
 
obj = no["Atrax"]       -- named object retrieval from repository
obj = it(pos)
obj = it(x,y)
obj = st(pos)
obj = wo:it(pos)
my_item = it(my_floor)  -- get the item that is on top of the given floor

The most common way is naming objects and the requesting the ‘no’ repository for the object reference. If you know the position of the desired object you can use one of the functions or world methods ‘fl’, ‘it’, ‘st’ that take a position, an object as position, or just the two coordinates as arguments. Especially requesting one type of objects that is positioned at the same grid as another object, the stone on top of a floor, etc. can be very useful.

Killing Objects:
 
wo[pos] = {"it_nil"}
obj:kill()

You remove an object by setting another replacement object at the same position in the same layer. If you do not want to set a new object you can use the placebo objects ‘fl_nil’, ‘it_nil’, ‘st_nil’. Another way is to call the ‘kill()’ method of an object or send it a ‘kill’ message. You can only remove objects that are set on the grid. Neither actors nor owned objects like items in a players inventory can be killed - they will simply ignore the attempt.

Comparing Objects
 
obj1 == obj2
obj1 ~= obj2

Objects can be directly compared on equality or inequality. It is an identity comparison that acknowledges that you have two references of the same object.

Existence of an object
 
obj:exists()
-obj                -- unary minus operator on object
if -obj then ...

Object references may get invalid due to objects being killed. In most cases this creates no problem as requests to invalid objects will simply be ignored. But if the level logic depends on the existence of an object you can call the ‘exists()’ method or simply precede the reference by the unary minus ‘-’ operator. Both ways return a simple bool value stating if the object reference is still valid.

Messages:
 
my_boulder:message("orientate", WEST)
my_boulder:orientate(EAST)
my_door:open()

Messages are a main feature of Enigma. You can send them directly to any object by the ‘message()’ method or by using any message directly as a method call itself.

Object Classification:
 
obj:is("st_chess")
obj:is("st")
obj:is("st_chess_black")

You create objects by giving an Object Kind. Later on you can check a given object for conformity to a given class or kind. Even though you can not create abstract kind objects like ‘st’, you can check this way if an object is a stone. Checking for special subkinds may even evaluate the current state or other attributes of an object to report its current classification.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.5 Group Tasks

For reference details see section Group.

Creating Groups:
 
group = no["Atrax#*"]           -- a group of all matching objects
			        --   wildcards "*","?" allowed
group = grp(obj1, obj2, obj3)
group = grp({obj1, obj2, obj3})  -- a group of objects set up in a table

Requesting objects from the named object repository will result in a group of objects if you make proper usage of wildcards. Appending an asterisk ‘*’ to the autonaming hash will retrieve all objects that have been set with this name suffix. But you can create a group by the ‘grp’ function, too. Simply add the desired object reference as arguments, either single or as a table.

Group Usage:
 
floor_group["friction"] = 3.2      -- set attribute on all floors in the group
door_group:message("open")
door_group:open()
stone_group:kill()
wo[floor_group] = {"it_coin_m"}   -- add some money on all floor positions

wo[pos] = {"st_switch", target=door_group, action="open"}
wo[pos] = {"st_switch", target="door#*", action="close"}

Many object operations can be applied to groups in the same manner. The operations will be applied to all members of the group. You set attributes, send messages or call any method.

The world object takes a group as key, too. You can set objects of a given definition to many positions at once.

Another usage of groups is the application as an attribute value. E.g. you can define multiple targets by supplying a group.

Group Operations:
 
doors_lasers = doorgrp + lasergrp       -- join of two groups
lasergrp     = doors_lasers - doorgrp   -- difference of two groups
common_doors = doorgrp1 * doorgrp2      -- intersection of two groups

Groups offer some standard operations known from handling with sets.

Group Members:
 
count = #mygroup        -- number of objects in the group
obj   = mygroup[5]     -- 5th object of the group
obj   = mygroup[-1]    -- last object of the group
for i = 1, #mygroup do obj = mygroup[i] ... end
for obj in mygroup do ... end

You can access the members of a group by numbered indices. The size of a group is reported by the standard Lua hash ‘#’ operator. If you need to iterate over the objects of a group you can write easily Lua for loops. You can either iterate with a counter or directly iterate the content objects.

Shuffled Group:
 
shuffled_group = sorted_group:shuffle()
shuffled_group = no["Atrax#*"]:shuffle()

Every group returns a shuffled group with the same members when receiving the message "shuffle".

Sorted Group:
 
sorted_group = group:sort("linear", po(2, 1))
sorted_group = group:sort("linear")
sorted_group = group:sort("circular")
sorted_group = group:sort()

Sort group objects in linear order according to given direction vector, or direction determined by the first two objects. Or order objects circular around their center. If no argument is given order objects lexically.

Subset Group:
 
sub_group = group:sub(2)   -- first two objects
sub_group = group:sub(-2)  -- last two objects
sub_group = group:sub(2, 4) -- objects from 2 to 4
sub_group = group:sub(2, -2) -- two objects starting with 2

Build subgroup with given indices and numbers.

Nearest Object:
 
object = group:nearest(reference)

Search object in group that is nearest to the given reference object.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.6 Tiles and World Tasks

For reference details see section Tile and Object Declaration, Tiles Repository and World.

Tiles:
 
ti["_"] = {"fl_sahara"}
ti["__"] = {"fl_sahara"}
ti[".."] = {"fl_sand"}
ti["##"] = {"st_blocker"}
ti["switch_template"] = {"st_switch"}
ti[".."] = {"fl_abyss"}   -- redefinition causes error to avoid common mistakes
ti[".w"] = ti[".."] .. {"it_magicwand"}
ti[" w"] = {"fl_abyss"} .. ti({"it_magicwand"})

The tiles repository ‘ti’ is like a table, but specialized on storage of tile definitions. You can use any string as key. You can store the same definition twice at different keys. But you are not allowed to redefine an already set key. This is pure protection of common error situations. A definition stored in the repository can be used in other definitions that follow. Referencing a tiles repository entry at a given key like ‘ti[".."]’ results in a tile value. Such tile values can be concatenated by the ‘..’ operator with other tile values and anonymous tables containing object definitions. The last example is a concatenation of two prior not declared object definitions. You can not concatenate two anonymous tables. Lua forbids that. By converting any of the two tables by the ‘ti()’ to a tile value the concatenation gets valid.

World Initialization
 
  width, height = wo(ti, "__", { -- second arg: default tile key that
  "##__......",                  --   defines the base, too - this example
  "##..__.w__",                  --   is 2 chars per tile/grid
  "##.. w__.."
  })

The world is initialized by the ‘wo()’ call that is explained in details at World Creation. In the simple form you supply the ‘ti’ handle as the first argument. The second argument is the key of the default tile definition that defines the default floor to be set if a tile does not contain another floor object. At the same time this key defines by its length the standard key length as used in the following map, too. The third argument is the map given as an anonymous table of strings. The worlds size is given by the maximum line length and the number of lines. These values are returned by the call.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.7 Named Positions Tasks

For reference details see section PositionList.

Named Position Usage:
 
obj["name"] = "anchor1"
obj:kill()
pos = po["anchor1"]
po["anchor2"] = pos

The position of any named object can be directly retrieved. The position is still accessible under the name, when the object gets killed. You can additionally name own positions. Note that the position of an existing object precedes a position stored under the same name.

Creating Position Lists:
 
polist = po["deepwater#*"]
polist = po(grp)

Requesting positions will result in a list of positions if you make proper usage of wildcards. A given object group can be converted into a position list, too.

Position List Usage:
 
wo[polist] = ti["x"]
grp = fl(polist)

You can use a list of positions to set tiles or to retrieve a group of floors, items, stones.

Position List Operations:
 
wo[polist .. po["beach#*"]] = {"it_banana"}

Two position lists can be appended. Single positions can be appended to a given position list, too.

Position List Members:
 
for i = 1, #polist do
    wo[polist[i]] = {"it_cherry"}
end

Single positions within the list can be accessed by index. The whole list can be traversed by a simple for loop. The hash length operator reports the number of contained positions.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.3 Advanced Lua Examples

Now it is time to reveal the real power of the new API. Let us look again at two real levels. Investigate the levels first by playing and then join in the line by line commentary of the source code to understand how to implement your own level ideas.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.3.1 Color Maze

Let us view the Lua source code part. We did add a line count in the first two columns for reference purpose within this section. These line count numbers are not part of the source code itself!

 
01    wo["ConserveLevel"] = false
02    wo["FollowGrid"] = false
03    wo["FollowMethod"] = FOLLOW_SCROLL
04
05    ti[" "] = {"fl_fake_abyss"} .. ti({"st_lightglass"})
06
07    ti["!"] = {"fl_blueslab", "blue#", _color="blue"}
08    ti["@"] = {"fl_pinkbumps", "orange#", _color="orange"}
09    ti["#"] = {"fl_redslab", "red#", _color="red"}
10    ti["$"] = {"fl_lawn_b", "green#", _color="green"}
11
12    ti["b"] = ti["!"] .. {"st_door", flavor="d", faces="ns", state=OPEN}
13    ti["B"] = ti["!"] .. {"st_door", flavor="d", faces="ew", state=OPEN}
14    ti["o"] = ti["@"] .. {"st_door", flavor="d", faces="ns", state=OPEN}
15    ti["O"] = ti["@"] .. {"st_door", flavor="d", faces="ew", state=OPEN}
16    ti["r"] = ti["#"] .. {"st_door", flavor="d", faces="ns", state=OPEN}
17    ti["R"] = ti["#"] .. {"st_door", flavor="d", faces="ew", state=OPEN}
18    ti["g"] = ti["$"] .. {"st_door", flavor="d", faces="ns", state=OPEN}
19    ti["G"] = ti["$"] .. {"st_door", flavor="d", faces="ew", state=OPEN}
20
21    ti["d"] = {"it_document", text="text1"}
22    ti["5"] = ti["b"] .. ti["d"]
23    ti["6"] = ti["O"] .. ti["d"]
24    ti["7"] = ti["r"] .. ti["d"]
25    ti["8"] = ti["G"] .. ti["d"]
26
27    ti["x"] = {"it_sensor", invisible=true, target="gates"}
28    ti["*"] = ti["x"] .. {"#ac_marble_black", "me"}
29
30    ti["?"] = {"st_oxyd_a"}
31
32    wo(ti, " ", {
33    --      |         1    1   |2    2
34    --      |1   5    0    5   |0    5
35           "                           ",
36           " xO@OxR#RxO@OxB!BxR#RxB!Bx ", --01
37           " b   r   g   g   b   g   r ",
38           " !   #   $   $   !   $   # ",
39           " b   r   g   g   b   g   r ",
40           " xR#RxB!BxO@OxG$GxO@OxO@Ox ", --05
41           " g   g   r   g   g   b   b ",
42           " $   $   #   $   $   !   ! ",
43           " g   g   r   g   g   b   b ",
44           " xR#RxO@OxG$GxR#RxG$GxR#Rx ",
45           " g   b   b   o       b   r ", --10
46           " $   !   !   @       !   # ",
47           " g   b   5   o   ?   b   r ", --
48           " xO@OxO@6*8$Gx   xG$GxR#Rx ",
49           " r   b   7   b   ?   o   o ",
50           " #   !   #   !       @   @ ", --15
51           " r   b   r   b       o   o ",
52           " xG$GxB!BxR#RxO@OxR#RxG$Gx ",
53           " g   o   o   g   g   o   b ",
54           " $   @   @   $   $   @   ! ",
55           " g   o   o   g   g   o   b ", --20
56           " xB!BxO@OxR#RxR#RxO@OxB!Bx ",
57           " o   r   g   g   b   b   g ",
58           " @   #   $   $   !   !   $ ",
59           " o   r   g   g   b   b   g ", --
60           " xR#RxB!BxB!BxR#RxO@OxR#Rx ", --25
61           "                           "} --
62    --      |         1    1   |2    2
63    --      |1   5    0    5   |0    5
64    )
65
66    last = it(no["me"])   -- the last visited sensor
67    move = 0              -- the count of link moves
68    sequence = {}         -- the sequence of the 4 colors that the user did choose
69
70    function gates(value, sender)
71        if last ~= sender then
72            local middle = last + (sender - last)/2
73            local color = fl(middle)["_color"]
74            if color == nil then return end  -- someone cheated, avoid throwing an exception
75            st(no[color.."#*"]):close()
76            sequence[move%4] = color
77            if move >= 3 then
78                st(no[sequence[(move+1)%4].."#*"]):open()
79            end
80            move = move + 1
81            last = sender
82        end
83    end

Let us concentrate on new aspects not discussed in the previous Basic Lua Examples.

 
01    wo["ConserveLevel"] = false
02    wo["FollowGrid"] = false
03    wo["FollowMethod"] = FOLLOW_SCROLL

This level must forbid the user to resurrect a marble at the start position. At the same time the user should see the area around the marble as complete as possible. Thus the scroll mode needs to be set, too. All this is done by setting special Global Attributes.

 
05    ti[" "] = {"fl_fake_abyss"} .. ti({"st_lightglass"})
32    wo(ti, " ", {

The inaccessible areas are filled with a transparent glass on top of a black floor as defined in line 5. The world initialization uses this tile definition as the default tile. This is o.k. as it contains a floor definition. Additional objects like the glass stone will never be set on default usage.

 
07    ti["!"] = {"fl_blueslab", "blue#", _color="blue"}
08    ti["@"] = {"fl_pinkbumps", "orange#", _color="orange"}
09    ti["#"] = {"fl_redslab", "red#", _color="red"}
10    ti["$"] = {"fl_lawn_b", "green#", _color="green"}

Every floor object is autonamed for later group access purposes. Additionally every floor object sets a user attribute prefixed in its name by an underscore ‘_’. This attribute stores a string that we need later on in the callback function.

 
12    ti["b"] = ti["!"] .. {"st_door", flavor="d", faces="ns", state=OPEN}
13    ti["B"] = ti["!"] .. {"st_door", flavor="d", faces="ew", state=OPEN}

The doors are set without being named, as we will target them by their position.

 
27    ti["x"] = {"it_sensor", invisible=true, target="gates"}

The actors moves are detected by invisible it_sensors that are positioned on any intersection. The target is the Callback Functiongates’. The action can be omitted as the function name is a unique target.

 
66    last = it(no["me"])   -- the last visited sensor

A Lua variable that stores the last sensor visited by the marble. This is initially the sensor beneath the start position of the marble. We do get the marble by name, but do store the sensor item beneath it, that is an unnamed object.

 
67    move = 0              -- the count of link moves
68    sequence = {}         -- the sequence of the 4 colors that the user did choose

These are the essential variables for our algorithm. The user is free in selecting the sequence of the colored floors. We do initialize the sequence by an anonymous table that will be filled with the color names. An additional move counter will give us the current index into this table.

 
70    function gates(value, sender)
71        if last ~= sender then

The callback function provides the sender, the it_sensor, that caused the action. It is the current sensor. As the marble can return to the last sensor, we have to check that it is a new sensor before taking any actions. A simple object comparison suffices.

 
72            local middle = last + (sender - last)/2
73            local color = fl(middle)["_color"]

We need to know the color of the floor strip that the marble did pass. We do calculate the position of the middle of this floor strip by position calculation. We simply take the middle position between the last and the current intersection. Once we have the middle position we can get the floor object and retrieve the private user attribute with the color description.

 
74            if color == nil then return end  -- someone cheated, avoid throwing an exception

In regular play we are guaranteed to get a color value. But just in case a gamer cheats he may have moved irregular without visiting neighboring sensors. Just avoid errors. The gamer can not score anyway.

 
75            st(no[color.."#*"]):close()

Knowing the color we want to close all doors on same colored floor strips. We did autoname the floors by matching name prefixes. Thus we can retrieve all floors of a given color by concatenating the color string with the suffix ‘#*’ and requesting the named object repository. As we are interested in the doors we do request the stone above every floor. We provide a group of floors and get a group of stones. Not every floor has a door on top. That does not matter as only existing objects are added to the resulting stone group. Knowing the stones we just send them all a ‘close’ message.

 
76            sequence[move%4] = color

We need to remember the sequence of colors. We just store the color name in the table at the index given by the move count modulo 4. Yes we could limit this statement to the first four moves. But who cares? The modulo operation is simpler than a conditional expression.

 
77            if move >= 3 then
78                st(no[sequence[(move+1)%4].."#*"]):open()
79            end

On the first 3 moves we just do close doors and remember the color sequence. But starting with the 4th move we need to open the next color in sequence. We do retrieve the color string of the next color from the sequence table by a simple modulo calculation. Having the color name we do the same trick as in line 75. But this time we do send the ‘open’ messages to all affected doors.

 
80            move = move + 1
81            last = sender

Finally we just have to increase the move count and to remember the current sender as the last visited sensor.

That is all to code a quite complex dynamic level idea like "Color Maze".


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.3.2 Weirdly Wired

You should have restarted this level several times to notice the design changes of the floor pattern and the border panel stones besides the dynamic wiring of the stones.

Let us view the Lua source code part. We did add a line count in the first two columns for reference purpose within this section. These line count number are not part of the source code itself!

 
01      <el:compatibility el:enigma="1.10">
02         <el:dependency el:path="lib/libmath" el:id="lib/libmath" el:release="1" el:preload="true"/>
03      </el:compatibility>
...
04    ti[" "] = {"fl_sahara", friction = 3.5, adhesion = 4.0}
05    ti["a"] = {"fl_ivory", friction = 3.5, adhesion = 4.0}
06    ti["b"] = {"fl_bright", friction = 3.5, adhesion = 4.0}
07    ti["c"] = {"fl_platinum", friction = 3.5, adhesion = 4.0}
08    ti["_"] = {"fl_water"}
09    ti["@"] = {"#ac_marble_black"}
10    ti["w"] = {"st_flat_movable", "wood#"}
11    ti["t"] = {"it_trigger", "trigger#"}
12    ti["d"] = {"st_blocker", "door#"}
13    ti["o"] = {"st_oxyd", oxydcolor = OXYD_YELLOW, flavor = "a"}
14    ti["O"] = {"st_oxyd", oxydcolor = OXYD_WHITE, flavor = "a"}
15    ti["1"] = {"st_panel", cluster = 1}
16    ti["2"] = {"st_panel", cluster = 2}
17    ti["S"] = {"st_switch", target = "easy_mode_call"}
18
19    floors  = {ti[" "], ti["a"], ti["b"], ti["c"]}
20    polynom = lib.math.random_vector(10, 4)
21
22    function myresolver(key, x, y)
23      if key == " " then
24        return floors[lib.math.cubic_polynomial(polynom, x, y) % (#floors) + 1]
25      elseif    (key == "#")
26            or ((key == "_") and (random(4) == 1))
27            or ((key == "S") and wo["IsDifficult"]) then
28        return ti[""..random(2)]
29      else
30        return ti[key]
31      end
32    end
33
34    w, h = wo(myresolver, " ", {
35     -- 01234567890123456789
36       "####################___________________",
37       "#                  #_____###o###_______",
38       "#   w   w t   t    #_____#d   d#_______",
39       "#     w   w t   t  #___### ### ###_____",
40       "#  w     t         #___#d d#_#d d#_____",
41       "#                  ##### ###_### ###___",
42       "S    w   w t @ t        d#___#_#d d#___",
43       "#                  #######_####### #___",
44       "#  w     t         #_______O  d# # o___",
45       "#     w   w t   t  #_______### ### #___",
46       "#   w   w t   t    #_________#d   d#___",
47       "#                  #_________###O###___",
48       "####################___________________"
49    })
50
51    door_p = lib.math.permutation(12)
52    wire_p = lib.math.permutation(12)
53    woods = no["wood#*"]
54    triggers = no["trigger#*"]
55    doors = no["door#*"]
56
57    for j = 1, 12 do
58      triggers[j].target = doors[door_p[j]]
59    end
60
61    for j = 1, 9 do
62      wo:add({"ot_wire",
63              anchor1 = woods[wire_p[j + 3]],
64              anchor2 = woods[wire_p[j%3 + 1]]})
65      wo:add({"ot_wire", name = "obsolete_wire#",
66              anchor1 = woods[wire_p[j + 3]],
67              anchor2 = woods[wire_p[j%9 + 4]]})
68    end
69
70    function easy_mode_call(is_on, sender)
71      if is_on then
72        no["obsolete_wire#*"]:kill()
73      else
74        for j = 1, 9 do
75          wo:add({"ot_wire", name = "obsolete_wire#",
76             	  anchor1 = woods[wire_p[j + 3]],
77                anchor2 = woods[wire_p[j%9 + 4]]})
78        end
79      end
80    end

How is this versatility in design and action achieved as the last lines 69 to 79 obviously deal just with the easy mode diffs? Let us analyse the lines that do the real work.

 
01       <el:compatibility el:enigma="1.10">
02         <el:dependency el:path="lib/libmath" el:id="lib/libmath" el:release="1" el:preload="true"/>
03       </el:compatibility>

We make use of some functions of the libmath library. Thus we need to preload it, besides declaration of compatibility to Enigma 1.10.

 
04    ti[" "] = {"fl_sahara", friction = 3.5, adhesion = 4.0}
05    ti["a"] = {"fl_ivory", friction = 3.5, adhesion = 4.0}
06    ti["b"] = {"fl_bright", friction = 3.5, adhesion = 4.0}
07    ti["c"] = {"fl_platinum", friction = 3.5, adhesion = 4.0}

Four floor types that the dynamic floor is composed of. They all are unified in the ‘friction’ and ‘adhesion’ to provide smooth movement on the stylish floor.

 
10    ti["w"] = {"st_flat_movable", "wood#"}
11    ti["t"] = {"it_trigger", "trigger#"}
12    ti["d"] = {"st_blocker", "door#"}

The movable stone that will be wired, the target triggers and the doors to be opened. All are autonamed for group retrieval from the named object repository.

 
13    ti["o"] = {"st_oxyd", oxydcolor = OXYD_YELLOW, flavor = "a"}
14    ti["O"] = {"st_oxyd", oxydcolor = OXYD_WHITE, flavor = "a"}

A minor design aspect: selecting two unique colors for the st_oxyds.

 
15    ti["1"] = {"st_panel", cluster = 1}
16    ti["2"] = {"st_panel", cluster = 2}

The base of the prominent all time different looking st_panel border design. Two tiles with panel stones assigned to two different clusters. The engine will automatically join all neighboring stones of the same cluster to big unified blocks. Now we just need to assign these tiles to the different grid positions.

 
17    ti["S"] = {"st_switch", target = "easy_mode_call"}

The left border switch that will just be used in easy mode. It is blocked in line 27 to not appear in the regular mode. The target is the callback function of lines 71 to 81.

 
19    floors  = {ti[" "], ti["a"], ti["b"], ti["c"]}
20    polynom = lib.math.random_vector(10, 4)

Preparations for the floor design. The four floor tiles are stored in a table for number based index access. Ten random numbers in the range 1 to 4 are stored in a table, which we will use as polynom coefficients later on.

 
22    function myresolver(key, x, y)
34    w, h = wo(myresolver, " ", {

Up to now we did look up the keys used in the map from our tiles repository ‘ti’ that was the first argument of the world initialization call. But now we use a Custom Resolver. The function starting in line 22 is called on every tile to be resolved. It has the task of delivering the appropriate tile.

 
23      if key == " " then
24        return floors[lib.math.cubic_polynomial(polynom, x, y) % (#floors) + 1]

These two lines generate the always changing floor design. For every map key ‘ ’ we calculate the cubic polynomial that is randomized due to the coefficients. The resulting number is limited to the number of our four floors. This number is taken as the index into our ‘floors’ table and the resulting tile definition is returned.

 
25      elseif    (key == "#")
26            or ((key == "_") and (random(4) == 1))
27            or ((key == "S") and wo["IsDifficult"]) then
28        return ti[""..random(2)]

And now we cluster the border panels. First we need to decide where to put panels at all. The positions marked ‘#’ in the map are for sure. Additionally we choose randomly every 4th ‘_’ position to be a panel instead of being a water floor. Finally we replace just in difficult mode the switch marked as ‘S’ by a panel stone. Now we need to assign to this grid position one of the two panel cluster tiles. We simply generate a random number out of 1 and 2. But we do need a string as the tiles key. We force Lua to convert the number to string by concatenating an empty string ‘""’ with the random number. Choosing the right panel variants to build up closed clusters is done by the engine.

 
29      else
30        return ti[key]

Finally for all other keys that need no special treatment we just take the tile definition as stored in the tiles repository.

 
34    w, h = wo(myresolver, " ", {
35     -- 01234567890123456789
36       "####################___________________",
37       "#                  #_____###o###_______",
38       "#   w   w t   t    #_____#d   d#_______",
39       "#     w   w t   t  #___### ### ###_____",
...

The map uses the keys as interpreted by the custom resolver. Thus all mandatory panel stones are marked by ‘#’ and all may be water by ‘_’. All spaces ‘ ’ do not stand for the sahara floor definition in the tiles repository, but are floor positions for our design floor set up in the custom resolver. Note that even the ‘w’ marked tiles will set a design floor, as the default floor is ‘ ’, too.

 
51    door_p = lib.math.permutation(12)
52    wire_p = lib.math.permutation(12)

Now let us shuffle the trigger/door assignment and the wire distribution. We do this by permuting 12 index numbers to be used for door and wire access.

 
53    woods = no["wood#*"]
54    triggers = no["trigger#*"]
55    doors = no["door#*"]

Get the groups of movable stones, triggers and doors. It is essential to do this once and to store the resulting groups as we want to index the group members. Repeated access to the named object repository does not guarantee a stable sorting of the result groups. Thus we operate on the stable once retrieved and stored groups.

 
57    for j = 1, 12 do
58      triggers[j].target = doors[door_p[j]]
59    end

A random assignment of the triggers to the doors. Every triggers gets a random indexed member of the door group as target. Note the alternative attribute member access on the trigger. Instead of embracing the attributes name in square brackets and quoting the string constant as ‘["target"]’ the author did prefer to write ‘.target’. That is a legal Lua alternative statement as long as the attribute’s name is a legal Lua name (see Caveats).

 
61    for j = 1, 9 do
62      wo:add({"ot_wire",
63              anchor1 = woods[wire_p[j + 3]],
64              anchor2 = woods[wire_p[j%3 + 1]]})

Finally we need to add the ot_wire between our movable stones. This can not be done within the map. We need to use the World method ‘wo:add()’, which takes the two connected stones as two anchor attributes. We select the first 3 stones of our wood group as stones to be connected with 3 other stones of the indices 4 to 12. Thus we take in every loop as the first anchor one of the stones 4 to 12 and connect it to one of the first 3 stones by a simple modulo operation. The first three stones now have three wires and are finished. The last 9 stones have just one wire.

 
65      wo:add({"ot_wire", name = "obsolete_wire#",
66              anchor1 = woods[wire_p[j + 3]],
67              anchor2 = woods[wire_p[j%9 + 4]]})
68    end

Now we wire these remaining 9 stones in sequence, in a closed circle. That gives each stone 2 additional wires. We do this by connecting each of the stones 4 to 11 with its successor and finally connecting stone 12 to stone 4, what is done by the modulo operation. This completes the level for the regular mode. As preparation for the easy mode we do autoname these additional wires.

 
71    function easy_mode_call(is_on, sender)
72      if is_on then
73        no["obsolete_wire#*"]:kill()

Just for the easy mode we added a switch to remove and recreate the additional wires. As we named these obsolete wires we can simply kill all of them in a single call by applying the ‘kill()’ method to the group of these wires.

 
73      else
74        for j = 1, 9 do
75          wo:add({"ot_wire", name = "obsolete_wire#",
76             	  anchor1 = woods[wire_p[j + 3]],
77                anchor2 = woods[wire_p[j%9 + 4]]})
78        end
79      end

When the user switches off again, the wires should be recreated. That is done by the same code as lines 65 to 68. Note that is essential that we stored and kept the used wire permutation in the variable ‘wire_p’.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.4 Introduction to Datatypes

Before describing the datatypes in detail let us look at the used common concepts and conventions.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.4.1 Syntax and Conventions

In the following subchapters we will describe the datatypes, their operators and methods, and global functions in detail. We need some syntax and conventions for an efficient description.

The following short names, and those derived by appending a number, do always represent a value of the corresponding type:

On syntax descriptions of datatype operators or methods we need to list allowed argument types. Often several types are possible and you are allowed to choose any of a list. In these cases we enlist the types enclosed by ‘<’ and ‘>’ and separated by ‘|’. These characters are not part of the operator or method itself and should thus not be typed into the level code. Note that we keep square braces ‘[’, ‘]’ and curly braces ‘{’, ‘}’ as literal Lua symbols. When these braces appear in the syntax you need to type them in the code. E.g. the following syntax rule:

 
result = pos + <pos | obj | cpos | polist>

allows you to write any of the following lines in your level

 
result = pos + pos
result = pos + obj
result = pos + cpos
result = pos + polist

But a syntax rule like

 
x = pos["x"]

requires the Lua square brackets to be coded literally. Of course you are still free to name your position variable and the resulting value variable whatever you like.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.4.2 Value and Reference

Another most important aspect of Lua data types is the difference between values and references. Values are numbers, booleans like ‘true’ and ‘false’, strings and ‘nil’. The only reference data type is the Lua table.

Values are always constant. They can never be modified. You assign a value to a variable. On calculations you may assign another value to the same variable. But the original value does never get modified. That should be obvious if you think of values like ‘true’ or numbers like ‘7’. But it is even true for strings like "hello". When you append two strings you get a new string. But the components themselves do not change. All "string modifying" methods do return a new string with the resulting value. Thus a variable containing the value of the original string still contains the unmodified value.

Tables are of a totally opposite nature. They represent data containers. In Lua you are just handling references to these containers. When you add or change a value within the container the table reference remains unmodified but the table contents changes. Thus two variable containing both references to the same table will afterwards both reference the same modified table.

We will denote for every new data type the character of being a value or a reference. If you are unfamiliar to the resulting effects you may want to read the appendix about Caveats.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.4.3 Polymorphism and Overloading

You may have noticed that in many operations you can use an Object as a Position. This is due to the fact that objects support most of the position features as well. Objects are no positions, but they do speak the same language. This feature is called ‘polymorphism’ and helps you to simplify your code significantly. In the following subchapters you should carefully look at the syntax to understand which types of arguments do fit in seamlessly.

The number of usable operators is limited by Lua. Thus an addition of two data by the ‘+’ operator causes different actions depending on the involved data themselves. An addition of two positions results in a vectorial addition. But the addition of two groups results in a join of the groups. This reuse of a single operator is called ‘overloading’.

Overloading combined with polymorphism can cause situations that are ambiguous by design. E.g. we decided to allow the addition of a position with an object resulting in the vectorial addition of the object’s position to the first one. At the same time we want to be able to join an object with an existing group by usage of the ‘+’ operator. But what should be the result of an addition of two objects? The vectorial addition of their positions, or the join of both objects in a new group? Either makes sense and would be useful. In this case we decided for the first possibility as the minus operation as the vectorial difference between two objects is a very important feature. Anyway you can always force the operation of your choice to be applied by transforming one object either into a position or a group. Please read carefully the given syntax rules for a clear understanding of the results.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.4.4 Pseudo Datatypes

Even though we introduce just the ten additional fundamental datatypes as described in the following subchapters, the API does additionally differ the same datatype according to its usage. E.g. a standard Lua number is used to describe the state of an object. Just in rare occurrences the state will reflect a real number like the state of ot_counter. For most objects the state will just be one of a given set of allowed values, which happen to be described by numbers by the API. Thus we speak of a pseudo datatype in the case of state values.

The API provides Common Constants for all pseudo datatypes, all written in upper case letters only. You should use exclusively these constants and never their equivalent basic number or other type values. The usage of the constants makes the level code readable and upward compatible if we ever should have the need of changing the assigned values or transforming the pseudo datatype to another datatype.

There is one abstract datatype that needs to be mentioned, as it uses two different pseudo datatypes at the same time. This special case is the datatype used to describe the ‘direction’ or ‘orientation’. Both are essentially the same. But we speak of ‘orientation’ if we are just interested in enlisting the main directions by number values for the purpose of identification. The constant values are given as orientations.

Sometimes you need values for calculation of position offsets. In this case we speak of a ‘direction’ and use Position values as offset vectors. The most common values are given as constants as listed in subchapter direction offsets. Note that our ‘direction’ values have no need of being normalized to the length of 1.

A given ‘orientation’ can be transformed into a ‘direction’ value by the conversion table ORI2DIR.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5 Position

A position is a Lua userdata type introduced by Enigma to handle world positions as introduced in World’s Shape and Coordinates. A position is a value and thus constant. Once a position is created it can not be modified anymore. But you can calculate with positions by usage of operators. When you add two position values you receive the resulting position as a new value.

In contrast to Objects positions have an unlimited lifetime and will never cease to exist. Thus you can store position values in global variables and keep them as long as you need. The values are constant and will not change even if the objects from which they have been derived have meanwhile moved to another grid or even been killed.

Position values are not limited to valid world coordinates. Both coordinates can take every positive or negative number and zero, too. Thus you can calculate with positions and use them to express offsets between two other positions.

Positions are created by the Positions Repository singleton handle, that allows you to convert coordinates, objects, position constants into positions. The handle allows you to retrieve existing named positions, too. Furtheron positions are implicitly created as return values of many operations.

For task driven samples see section Position Tasks.

Let us look at the supported operators:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5.1 Position Addition and Subtraction

Syntax:

result = pos <+|-> <pos | obj | cpos | polist>

result = <pos | obj | cpos | polist> <+|-> pos

Details:

When a position is added to, or subtracted from another position or data convertible to a position the result is the position value representing the vectorial addition or difference of both arguments.

If a position is added to, or subtracted from a position list a new list is created with the positions representing the sum or difference of the position with every member of the supplied position list.

Syntax Samples:
 
newpos = po(3, 4) + {1, 2}              -- = po(4, 6)
newpos = myobject - po(1, 5)
newpolist = po(2, 3) + NEIGHBORS_4      -- po(1, 3) .. po(2, 4) .. po(3, 3) .. po(2, 2)
newpolist = po["myfloor#*"] - po(3, 0)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5.2 Position Multiplication and Division

Syntax:

result = pos <*|/> number

result = number * pos

Details:

A scalar multiplication or division of a position vector. A position value with both coordinate values multiplicated or divided by the given number is returned.

Syntax Samples:
 
newpos = 3 * po(3, 4)   -- = po(9, 12)
newpos = po(2, 3) / 2   -- = po(1, 1.5)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5.3 Position Sign

Syntax:

result = -pos

Details:

An unary scalar multiplication of a position vector with ‘-1’. A new position value with both coordinate values multiplicated by ‘-1’ is returned.

Syntax Samples:
 
newpos = -po(3, 4)   -- = po(-3, -4)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5.4 Position Center

Syntax:

result = #pos

Details:

A rounding of a position vector to the center of the grid. A new position value with coordinates of the center of the containing grid position is returned.

Syntax Samples:
 
newpos = #po(3, 4)   -- = po(3.5, 4.5)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5.5 Position Comparison

Equality and Inequality.

Syntax:

result = pos1 <==|~=> pos2

Details:

A comparison of two position values. Two position values are equal if both coordinates are equal. Otherwise they are unequal. If you want to know whether two positions point to the same grid, you may want to round both position prior comparison. You can round either to the center or to the grid by usage of the position operator ‘#’ or the method ‘grid()’.

Syntax Samples:
 
bool = po(3, 4) == po({3, 4})  -- = true
bool = po(3, 4) == po(4, 3)    -- = false
bool = po(3, 4) ~= po(4, 3)    -- = true

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5.6 Position Concatenation

Syntax:

result = pos1 .. <pos2 | polist>

result = <pos1 | polist> .. pos2

Details:

Concatenates two positions or a position with an existing PositionList to a new PositionList containing all positions in the given order. Note that this operation is associative, that means it does not matter if you use braces in multiple concatenations or not.

Syntax Samples:
 
newpolist = po(3, 4) .. po(4, 4)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5.7 Position Coordinate Access

Syntax:

result = pos["x"]

result = pos["y"]

result1, result2 = pos:xy()

Details:

The single coordinates of a position can be read anytime. You can retrieve single coordinates by Lua square bracket index access. Of course you can use the Lua alternative dot index access syntax, too (see examples). If you want to evaluate both coordinates, you can make use of the method ‘xy()’, that returns both numbers at once in a Lua multiple assignment.

Syntax Samples:
 
number = po(3, 4)["x"]            -- = 3
number = po(3, 4).x               -- = 3
number = po(3, 4)["y"]            -- = 4
number = po(3, 4).y               -- = 4
number1, number2 = po(3, 4):xy()  -- = 3, 4

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5.8 Position Grid Rounding

Syntax:

result = pos:grid()

Details:

Returns a new position value that points to the upper left corner of the grid that contains the position itself.

Syntax Samples:
 
newpos = po(3.2, 4.7):grid()    -- = 3, 4
newpos = po(-2.4, -5.0):grid()  -- = -3, -5

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.5.9 Position Existence

Syntax:

result = pos:exists()

Details:

Checks if the position is part of the world and returns ‘true’ if it is contained. Otherwise ‘false’ is returned.

Note that the Objects ‘exists’ method reports the existence of the object. Evaluation of ‘po(obj):exists()’ may result in ‘false’ for existing objects. E.g. this result occurs for Item Objects currently being part of a player’s inventory. The item exists, but is not part of the world. But items contained in a bag placed in the world will report the same position as the bag.

Syntax Samples:
 
boolean = po(3.2, 4.7):exists()

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6 Object

This datatype handles all world participating objects as described in Object Layers. When you request an object you get a reference to this object and not the object itself. You can modify the object, but the object can be modified by user actions, too. Deleting your reference by assigning another value to your variable does not delete the object itself.

On the other hand an object can cease to exist due to user actions while you still have a reference assigned to a Lua variable. Of course this reference gets invalid when the referenced object is deleted. But such an invalid reference, which we call a ‘NULL’ reference, is no longer fatal with the new API. Any write accesses on such references are simply ignored. Thus you can send messages to object references independently of their validity. Just on read accesses you may want to prior check the existence of an object as you would get ‘nil’ values on access of ‘NULL’ references.

Objects take attributes that you access by Lua index methods. Additional to the object specific attributes you are free to store your own custom attributes on any object. Custom attributes are any indices starting with an underscore ‘_’ as prefix to their name.

The real world objects are created by assigning a tile declaration to a World position. You retrieve a corresponding object reference either by the NamedObjects repository, by Functions or other methods that return single object references.

Objects provide most methods of a Position and can in most cases be directly used as a position without explicit conversion. Just special methods like existence differ on both datatypes. Of course all objects placed in the world are limited to positions within the world. But be aware that portable Item Objects can well be part of a player’s inventory and thus report a position outside of the world. Actors will always report positions rounded to the containing grid. This feature is legacy. As Lua code is anyway inappropriate to handle actor movements we maintained this rounding feature.

Objects support standard set operators of Groups, too, as long as one operand is a group and the other an object.

For task driven samples see section Object Tasks and Attribute Tasks.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.1 Object Attribute Access

Syntax:

result = obj["attributename"]

obj["attributename"] = value

obj:set({attributename1=value1, attributename2=value2,...})

Details:

Read or write object attributes as described in the following chapters or custom attributes. The ‘set’ method allows you to set multiple attributes at once. Attribute writes are ignored if the object reference is invalid. Attribute reads require a valid object reference. Otherwise they return ‘nil’.

Syntax Samples:
 
value = obj["color"]
value = obj.color
obj["color"] = BLACK
obj.color = BLACK
obj:set({target=mydoor, action="open"})

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.2 Object Messaging

Syntax:

result = obj:message("msg", value)

result = obj:msg(value)

Details:

Send a message with a given value or ‘nil’ to the object. Every message can be sent directly as a method with the given message name. When the object reference is invalid the message is simply ignored.

Syntax Samples:
 
value = obj:message("open")
value = obj:open()
value = obj:message("signal", 1)
value = obj:signal(1)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.3 Object Comparison

Syntax:

result = obj1 <==|~=> obj2

Details:

A comparison of two object values. Two object values are equal if both reference the same, still existing world object. Otherwise they are unequal.

Syntax Samples:
 
bool = obj1 == obj1  -- = true
bool = obj1 == obj2  -- = false, if two different objects
bool = obj1 ~= obj2  -- = true, if two different objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.4 Object Existence

Syntax:

result = -obj

result = obj:exists()

Details:

Checks whether an object reference is still valid. Returns true if the object still exists, otherwise false is returned for ‘NULL’ object references.

Syntax Samples:
 
bool = -obj
bool = obj:exists()

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.5 Object Kill

Syntax:

obj:kill()

Details:

Kills the object at once. Note that you should never kill a sender object within a callback action. If you have need of killing the sender then add the attribute safeaction as explained in Target - Action.

Syntax Samples:
 
obj:kill()

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.6 Object Kind Check

Syntax:

result = obj:is("kind")

result = obj:kind()

Details:

These methods allow you to check or retrieve the Object Kind.

Syntax Samples:
 
bool = obj:is("st_chess")
string = obj:kind()

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.7 Object Coordinate Access

Syntax:

result = obj["x"]

result = obj["y"]

result1, result2 = obj:xy()

Details:

The single position coordinates of an object can be read anytime. You can retrieve single coordinates by Lua square bracket index access. Of course you can use the Lua alternative dot index access syntax, too (see examples). If you want to evaluate both coordinates, you can make use of the method ‘xy()’, that returns both numbers at once in a Lua multiple assignment. Anyway the coordinates of an object are read only. You can not reposition an object by changing its coordinates.

Syntax Samples:
 
number = obj["x"]
number = obj.x
number = obj["y"]
number = obj.y
number1, number2 = obj:xy()

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.8 Object Addition and Subtraction

Syntax:

result = obj <+|-> <pos | obj | cpos | polist>

result = <pos | obj | cpos | polist> <+|-> obj

Details:

When an object is added to or subtracted from another position or data convertible to a position the result is the position value representing the vectorial addition or difference of both positions.

If an object is added to or subtracted from a position list a new list is created with the positions representing the sum or difference of the position with every member of the supplied position list.

Syntax Samples:
 
newpos = obj + {1, 2}
newpos = myobject - obj
newpolist = obj + NEIGHBORS_4
newpolist = po["myfloor#*"] - obj

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.9 Object Center

Syntax:

result = #obj

Details:

A rounding of an objects position vector to the center of the grid. A new position value with coordinates of the center of the containing grid position is returned.

Syntax Samples:
 
newpos = #obj   -- e.g. po(3.5, 4.5)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.10 Object Join

Syntax:

result = obj + group

result = group + obj

Details:

A new set containing the objects of the group plus the single object is returned. The object sequence is maintained. If the object is already member of the group the new group will contain just one object reference, namely the first one in sequence.

Syntax Samples:
 
newgroup = obj1 + grp(obj2, obj3, obj1)   -- = grp(obj1, obj2, obj3)
newgroup = grp(obj2, obj3) + obj1         -- = grp(obj2, obj3, obj1)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.11 Object Intersection

Syntax:

result = obj * group

result = group * obj

Details:

A new set containing just the object itself, in case it is part of the group, too, or an empty group returned.

Syntax Samples:
 
newgroup = obj1 * grp(obj1, obj2)  -- = grp(obj1)
newgroup = grp(obj2) * obj1         -- = grp()

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.12 Object Difference

Syntax:

result = obj - group

result = group - obj

Details:

In the first case a new set containing just the object itself, if it is not part of the group, too, or an empty group returned. In the second case the new group contains all members of the old group without the object. The sequence of objects remains untouched.

Syntax Samples:
 
newgroup = obj1 - grp(obj2, obj1)  -- = grp()
newgroup = grp(obj1, obj2) - obj1  -- = grp(obj2)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.6.13 Object Sound

Syntax:

result = obj:sound("name", volume)

Details:

Play a sound of given name at the position of the object. The volume defaults to ‘1’.

Syntax Samples:
 
obj:sound("quake")
obj:sound("quake", 2)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7 Group

A group is a sorted set of Objects. Every object can be contained just in one instance in the group. In contrast to a Lua table the group is a constant value datatype. Once you retrieved a group it can not be modified. But you can calculate and apply operators like join, intersection and difference on groups and generate thereby other group values as results.

Even though groups as values are long living you should be careful in keeping a group longer than a callback evaluation. Groups contain object references and objects can be killed. Thus a prior retrieved group may contain invalid object references on a subsequent callback evaluation. This may be uncritical if you just send a message to a group, but in other cases you may need to clean the group from invalid members (see below).

You create a group by listing the contained objects as arguments to the ‘grp()Functions, by retrieving objects from the NamedObjects repository or as result of other methods and calculations.

As groups are constant the sequence of contained objects is stable. All operations that generate resulting new groups maintain this sequence as far as possible. E.g. a join of two groups takes the objects of the first group in the given sequence and appends the additional objects of the second group in the sequence they appear in the second group.

All group operations that return new groups clean their results from any meanwhile invalid ‘NULL’ object references. You can use this feature to clean a group by the function ‘grp()’.

Any message sent to a group will be forwarded to all its members in sequence. An attribute write access to a group results in series of attribute writes on every group member in sequence, too.

But of course you can iterate over a group and access every member by index access. Several special methods for common group evaluations like shuffling, subgroup, sort, etc. are supported, too.

For task driven samples see section Group Tasks.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.1 Group Messaging

Syntax:

result = group:message("msg", value)

result = group:msg(value)

Details:

Send a message with a given value or ‘nil’ to all objects in the group. Every message can be sent directly as a method with the given message name. When an object reference is invalid the message is simply ignored. The result is the return value of the message to the last object in the group, or ‘nil’ for an empty group.

You are even allowed to send a ‘kill()’ message to all objects in a group. The objects will be killed, but the group remains filled with invalid ‘NULL’ object references.

Syntax Samples:
 
value = group:message("open")
value = group:open()
value = group:message("signal", 1)
value = group:signal(1)
value = group:kill()

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.2 Group Attribute Write

Syntax:

group["attributename"] = value

group:set({attributename1=value1, attributename2=value2,...})

Details:

Set attributes as described in the following chapters or custom attributes on all objects of the group. The ‘set’ method allows you to set multiple attributes at once. Attribute writes are ignored if an object reference is invalid. Attribute read on groups in not allowed - the group index read is overloaded as a group member access.

Syntax Samples:
 
group["color"] = BLACK
group.color = BLACK
group:set({target=mydoor, action="open"})

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.3 Group Comparison

Syntax:

result = group1 <==|~=> group2

Details:

A comparison of two groups. Two groups are equal if both contain the same set of objects independent of their sequence within both groups. Otherwise they are unequal.

Syntax Samples:
 
bool = grp(obj1, obj2) == grp(obj2, obj1)  -- = true
bool = grp(obj1, obj2) == grp(obj1, obj3)  -- = false, if different object contents
bool = grp(obj1) ~= grp(obj2, obj1)        -- = true, if different object contents

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.4 Group Length

Syntax:

result = #group

Details:

Number of objects contained in the group. Invalid ‘NULL’ object references are counted, too.

Syntax Samples:
 
number = #grp(obj1, obj2)         -- = 2
for i = 1, #group do obj = group[i] ... end

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.5 Group Member Access

Syntax:

result = group[index]

result = group[obj]

Details:

An index like read access with an index number between 1 and #group returns the object in the related group sequence position. Negative indices between -#group and -1 give the same objects. Thus you can always access the last object at index -1. All other index positions return an invalid ‘NULL’ object - not ‘nil’ as tables do! Thus you are always able to send messages to returned object references.

An index like read access with an object as index returns the sequence position number, the number index, of the object if it is contained in the group or ‘nil’ if it is not contained.

Syntax Samples:
 
object = grp(obj1, obj2)[2]     -- = obj2
object = grp(obj1, obj2)[-1]    -- = obj2
object = grp(obj1, obj2)[0]     -- = NULL object
for i = 1, #group do obj = group[i] ... end
number = grp(obj1, obj2)[obj2]  -- = 2
number = grp(obj1, obj2)[obj3]  -- = nil

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.6 Group Loop

Syntax:

for obj in group do ... end

Details:

Looping over all objects contained in a group. The loop occurs in the sequence of the contained objects and includes all objects, even invalid ‘NULL’ object references.

Syntax Samples:
 
for obj in group do obj:toggle() end

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.7 Group Join

Syntax:

result = group + <obj|group>

result = <obj|group> + group

Details:

A new set containing any object of both arguments just once, is returned. The object sequence is maintained. If an object is member of both arguments the new group will contain just one object reference, namely the first one in sequence.

Syntax Samples:
 
newgroup = obj1 + grp(obj2, obj3, obj1)   -- = grp(obj1, obj2, obj3)
newgroup = grp(obj2, obj3) + grp(obj1, obj3)   -- = grp(obj2, obj3, obj1)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.8 Group Intersection

Syntax:

result = <obj|group> * group

result = group * <obj|group>

Details:

A new set containing just those objects that are contained in both arguments. The objects are returned in the same sequence as they appear in the first argument.

Syntax Samples:
 
newgroup = obj1 * grp(obj2, obj1)  -- = grp(obj1)
newgroup = grp(obj1, obj2) * grp(obj2, obj1, obj3)  -- = grp(obj1, obj2)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.9 Group Difference

Syntax:

result = <obj|group> - group

result = group - <obj|group>

Details:

A new set containing just those objects of the first argument, that are not contained in the second argument is returned. The sequence of the objects in the new group is the same as in the first argument.

Syntax Samples:
 
newgroup = obj1 - grp(obj2, obj1)  -- = grp()
newgroup = grp(obj1, obj2, obj3) - grp(obj2, obj4)  -- = grp(obj1, obj3)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.10 Group Shuffle

Syntax:

result = group:shuffle()

Details:

Returns a new group with the same objects in another random sequence. Note that all invalid ‘NULL’ object references are removed in the resulting group by this method call.

Syntax Samples:
 
newgroup = grp(obj1, obj2)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.11 Group Sorting

Syntax:

result = group:sort("circular")

result = group:sort("linear" <, direction>)

result = group:sort()

Details:

Returns a new group with the same objects in another sorted sequence. Note that all invalid ‘NULL’ object reference are removed in the resulting group by this method call.

With a string argument "circular" the objects are arranged around their center by their angle. The distance from this center has no influence.

With a string argument "linear" the objects are arranged in a linear fashion. You either supply a Position as sorting direction vector, or the first two objects of the group will define the sorting direction as default.

If no sorting argument is supplied the objects will be sorted by their name in lexical order.

Syntax Samples:
 
newgroup = grp(obj1, obj2, obj3):sort("linear", po(2,1))
newgroup = grp(obj1, obj2, obj3):sort("circular")
newgroup = grp(obj1, obj2, obj3):sort()

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.12 Group Subset

Syntax:

result = group:sub(number)

result = group:sub(start, end)

result = group:sub(start, -number)

Details:

Returns a new group with a subset of the contained objects. The sequence of objects in the new group is identical to the sequence in the originating group. Note that all invalid ‘NULL’ object reference are removed in the resulting group by this method call after determination of the sub set candidate objects.

With a number as argument you determine the number of requested objects. A positive number returns the amount of objects starting with the first object of the group. Whereas a negative number returns objects from the tail of the group. In this case the absolute value determines the number of selected objects counted backwards from the last one in sequence.

With two positive numbers as arguments you define the first and the last sequence index of the requested subset.

With two numbers, but the second one being negative, you define the first object and the with the absolute value of the second number you define the amount of objects you want in your subset.

Syntax Samples:
 
newgroup = grp(obj1, obj2, obj3, obj4):sub(2)     -- = grp(obj1, obj2)
newgroup = grp(obj1, obj2, obj3, obj4):sub(-2)    -- = grp(obj3, obj4)
newgroup = grp(obj1, obj2, obj3, obj4):sub(2, 4)  -- = grp(obj2, obj3, obj4)
newgroup = grp(obj1, obj2, obj3, obj4):sub(2, -2) -- = grp(obj2, obj3)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.7.13 Group Nearest Object

Syntax:

result = group:nearest(obj)

Details:

Returns the object contained in the group that is nearest to the given reference object. The distances are calculated exactly without rounding actor positions. In case two objects have the same distance from the reference object one is chosen by chance as the result.

Syntax Samples:
 
newobject = grp(obj1, obj2, obj3):nearest(obj4)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.8 NamedObjects

The datatype NamedObjects is used by just one object, the singleton repository of named objects. Whenever you name an object, see section Object Naming, this repository registers its name and allows you to retrieve the object lateron by providing its name.

Being a singleton you can not create new NamedObjects. The singleton is stored at the global variable ‘no’ on level load.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.8.1 NamedObjects Repository Request

Syntax:

result = no["name"]

Details:

Request of one or several objects from the repository. If no wildcard characters are used in the name an Object value is returned. It is either the unique object with the given name, or an invalid ‘NULL’ object if no object exists with the given name.

If the requested name contains a wildcard character, either an asterisk ‘*’ or a question mark ‘?’, a Group containing all objects with matching names is returned. An asterisk matches zero, one or several arbitrary characters. A question mark matches a single arbitrary character. Both wildcard characters can be used anywhere in the string and in arbitrary multiplicity. Anyway the result is always returned as a Group. The group may contain multiple objects, just a single object, or even no objects at all when no existing object matches your name pattern.

Syntax Samples:
 
obj = no["mydoor"]       -- exact name match
group = no["mydoors#*"]  -- any suffix
group = no["mydoor?"]    -- just one char suffix
group = no["mydoors?#*"] -- matches e.g. "mydoorsA#123435", "mydoorsB#1213"

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.8.2 NamedObjects Object Naming

Syntax:

no["name"] = obj

Details:

Index write accesses to the singleton allows you to name or rename objects. Note that you can name or rename objects by Object attribute writes, too. The object’s name is stored as attribute "name". Both ways of naming an object are totally equivalent.

Syntax Samples:
 
no["myobject"] = obj

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.9 PositionList

A PositionList is a sorted set, known as list, of Positions. Like a Group this container is a constant datatype, that can not be modified. But you can easily compose new position lists by concatenation of existing lists and single positions.

An important difference to a group is the ability of a position list to store a position multiple times at different locations in its sequence. Thus a position list is suited to describe paths, even paths that are closed or cross itself.

As Positions are values that never become invalid, position lists once created will never change or get invalid. They are true values themselves. Thus they are the containers of choice for longterm storage.

You can easily create a position list by PositionList Conversion, which is featured by the Positions Repository singleton and allows you to transform a group into a position list by the simple expression ‘po(group)’. On the other hand you can retrieve all Objects of a basic kind located along a position list path by Functions like ‘st(polist)’, ‘it(polist)’ and ‘fl(polist)’.

As a unique floor object is guaranteed to be on every grid position, you can convert a given list of unique grid positions to a group of floors without loosing any information. Now you can apply all group methods on the floors, like shuffling, sorting, subgrouping, etc.. Finally you can convert the resulting group back to a persistent position list. Of course the conversions preserve the sequence of members.

Some additional position specific operators are supplied to allow simple translations and stretchings of position lists.

Note that in contrast to a Group this datatype can not directly be stored in an Object attribute. But you can always store a Group of floors in an attribute. In case floors can be destroyed you may need to name these floors as discussed in Named Positions.

For task driven samples see section Named Positions Tasks.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.9.1 PositionList Comparison

Syntax:

result = polist1 <==|~=> polist2

Details:

A comparison of two position lists. Two position lists are equal if both contain the same positions in the identical sequence. Otherwise they are unequal.

Syntax Samples:
 
bool = (po(2,3).. po(5,7)) == (po(2,3) .. po(5,7))  -- = true
bool = (po(2,3).. po(5,7)) == (po(4,0) .. po(5,7))  -- = false, different positions
bool = (po(2,3).. po(5,7)) == (po(5,7) .. po(2,3))  -- = false, different sequence

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.9.2 PositionList Length

Syntax:

result = #polist

Details:

Number of positions contained in the list.

Syntax Samples:
 
number = #(po(2,3) .. po(5,7)) -- = 2
for i = 1, #polist do pos = polist[i] ... end

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.9.3 PositionList Member Access

Syntax:

result = group[index]

Details:

An index like read access with an index number between 1 and #polist returns the position in the related position list position. Negative indices between -#polist and -1 give the same positions. Thus you can always access the last position at index -1. All other indices return a Lua ‘nil’ value like tables.

Syntax Samples:
 
pos = (po(2,3) .. po(5,7))[2]     -- = po(5,7)
pos = (po(2,3) .. po(5,7))[-1]    -- = po(5,7)
pos = (po(2,3) .. po(5,7))[0]     -- = nil
for i = 1, #polist do pos = polist[i] ... end

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.9.4 PositionList Concatenation

Syntax:

result = polist1 .. <pos | polist2>

result = <pos | polist1> .. polist2

Details:

Concatenates two position lists or a position with a position list to a new PositionList containing all positions in the given order. Note that this operation is associative, that means it does not matter if you use braces in multiple concatenations or not.

Syntax Samples:
 
newpolist = po(po(2,3), po(5,7)) .. po(4, 4) -- = (2,3),(5,7),(4,4)
Caveats:

Note that due to the value nature of position lists the concatenation creates a new value. This is an expensive operation. When you are collecting potentially large number of positions in a loop, you should not concat each new candidate to an existing position list. Avoid the creation of numerous position list values and collect the positions in a standard Lua table. Convert this table at the end to a position list (See section PositionList Conversion).

 
result = {}
for x = 1, 200 do
    table.insert(result, po(x, 17))
end
return po(result)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.9.5 PositionList Translation

Syntax:

result = polist <+|-> <pos | obj | cpos>

result = <pos | obj | cpos> <+|-> polist

Details:

If a position or data convertible to a position is added to or subtracted from a position list a new list is created with the positions representing the sum or difference of the position with every member of the position list. In total the position list is shifted by the position as a vector.

Syntax Samples:
 
newpolist = po(2, 3) + NEIGHBORS_4      -- po(1, 3) .. po(2, 4) .. po(3, 3) .. po(2, 2)
newpolist = po["myfloor#*"] - po(3, 0)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.9.6 PositionList Stretching

Syntax:

result = polist * number

result = number * polist

Details:

A scalar multiplication or division of all positions in a position list. All position values are multiplicated or divided by the given number in both coordinate values. In total the position list is stretched by a scalar factor.

Syntax Samples:
 
newpolist = 2 * NEIGHBORS_4              -- = po(9, 12)
newpolist = (po(2,4) .. po(6,7)) * 1/2   -- = (1, 2), (3, 3.5)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.10 Positions Repository

The Positions datatype is just used by a single instance, the singleton repository of named positions. Besides the management of named positions it provides useful conversions of other datatypes to position based types.

Being a singleton you can not create a new Positions Repository. The singleton that is available on level load is stored at the global variable ‘po’.

The position repository is an extension of the NamedObjects repository. Whenever you name an object, See section Object Naming, this repository registers its name and allows you to retrieve lateron the current object’s position by providing its name. But even when a floor object gets killed, its position entry remains stored as Named Positions. Of course you can name positions yourself, but positions of existing named objects will always override plane named positions on name clashes.

For task driven samples see section Named Positions Tasks.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.10.1 Positions Repository Request

Syntax:

result = po["name"]

Details:

Request of one or several positions from the repository. If no wildcard characters are used in the name the Position value of the unique object with the given name, if existent, is returned. It no object exists with the name, the last position stored with the same name is returned. If no position exists the value ‘nil’ is returned.

If the requested name contains a wildcard character, either an asterisk ‘*’ or a question mark ‘?’, a PositionList containing all positions with matching names is returned. An asterisk matches zero, one or several arbitrary characters. A question mark matches a single arbitrary character. Both wildcard characters can be used anywhere in the string and in arbitrary multiplicity. Anyway the result is always returned as a PositionList. The list may contain multiple positions, just a single position, or even no position at all when no existing position matches your name pattern.

Syntax Samples:
 
pos = po["mydoor"]        -- exact name match
polist = po["mydoors#*"]  -- any suffix
polist = po["mydoor?"]    -- just one char suffix
polist = po["mydoors?#*"] -- matches e.g. "mydoorsA#123435", "mydoorsB#1213"

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.10.2 Positions Repository Storage

Syntax:

po["name"] = obj

Details:

Index write accesses to the singleton allows you to name or rename positions. Note that can not assign a new position to a name that currently references an existing Object. Such write access are silently ignored.

Syntax Samples:
 
po["mypos"] = pos

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.10.3 Position Conversion

Syntax:

result = po(<obj | pos | {x, y} | x, y >)

Details:

Converts its argument to a new position value.

Syntax Samples:
 
pos = po(pos2)
pos = po(obj)
pos = po({2, 4})
pos = po(3, 7)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.10.4 PositionList Conversion

Syntax:

result = po(group | {pos1, pos2, pos3})

Details:

Converts the given group or table of positions to a new PositionList value, that contains the positions of all valid group Objects or table members in the same sequence.

Syntax Samples:
 
polist = po(group)
polist = po({po(3, 7), po(2, 6)})
polist = po({})  -- an empty position list

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.11 Tile and Object Declaration

A tile is the description of one or several objects that should be positioned on the same grid position. A single object can be set up by a straight object declaration, an anonymous Lua table with entries for the object kind and all attributes. The object declaration comes in three minor variations:

 
{"st_chess", name="jumper", color=WHITE}
{"st_chess_white", "jumper", _myattr=5}
{"ac_marble", 0.2, 0.6, name="blacky"}

The first entry, the one stored at table position ‘1’, has always to be the kind name of a supported Enigma object. In the first example all other table entries are key value pairs with the key being the attribute name. The second example uses the shortcut of specifying the name attribute value as second table entry, that will be stored at table position ‘2’. It has to be a string value. The third variation, that is useful for actor declarations, stores the grid offsets in x and y directions in the table positions ‘2’ and ‘3’. Of course you can not use the name attribute shortcut in the same declaration.

These table driven object declarations are always sufficient if you just want to set a single object at once. But tiles do often take an item or a stone besides a floor. So we need an Enigma data type being able to handle these multiple declarations. This is the ‘tile’ data type. It can take just one object declaration or an arbitrary list of declarations. You convert a table object declaration into a tile by the Tiles Repository handle. Once you have a tile you can concat other tiles or table object declarations to set up new tiles.

Enigma guarantees that the objects will be set to the world in the sequence of declarations in the tile.

Even though in most cases you use object declarations and tiles to set objects you may need in some advanced usage cases to supply such a datatype in situations where you want to add nothing at all or even want to kill a possibly existing object. In these cases you can supply one of the pseudo object kind names "fl_nil", "it_nil", "st_nil" or "nil". While the first three pseudo kinds will kill existing objects on the given layer, the last pseudo kind will just do nothing. It is equivalent, but more expressive than an empty Lua table being used as an object declaration:

 
ti["D"] = cond(wo["IsDifficult"], {"st_death"}, {"nil"})
ti["S"] = {"st_surprise", selection={"st_box", "st_nil"}}
function customresolver(key, x, y)
    if key == "a" then
        return {"nil"}
    elseif key == "b" then
        return {}
    else
        return ti[key]
    end
end

The first sample uses the pseudo to supply a valid third arguement to the cond function that causes no syntax error when being passed to the world in easy mode.

The second sample uses the pseudo to kill the st_surprise even when no substitution stone is being set.

The last example of a Custom Resolver provides a solution for avoiding the change of the world on a given key in the world map. Usually you will always set at least a floor object. But if you draw a map during runtime there is no longer the need of setting inital floors. In cases where this can not be handled by proper usage of default keys the pseudo kind "nil" is your friend.

For task driven samples see section Tiles and World Tasks.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.11.1 Tile concat

Syntax:

result = tile .. <tile | odecl>

result = <tile | odecl> .. tile

Details:

Compose a new tile by concatenation of a tile with another tile or a table object declaration. In a concatenated chain of tiles and object declarations one of the first two evaluated arguments needs to be a tile as two Lua tables do not know how to concat.

Note that Lua does evaluate the ‘..’ operator from right to left! Thus you need either use proper braces or you need to guarantee that at least one of the two rightmost tokens is a tile.

Syntax Samples:
 
newtile = ti{"st_chess"} .. {"fl_sahara"}
newtile = ti{"st_chess"} .. {"fl_sahara"} .. {"it_cherry"}   -- Lua error due to right to left evaluation
newtile = (ti{"st_chess"} .. {"fl_sahara"}) .. {"it_cherry"} -- evaluation order fixed
newtile = ti{"st_chess"} .. {"fl_sahara"} .. ti{"it_cherry"} -- converted one of the two critical declarations

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.12 Tiles Repository

The Tiles datatype is just used by a single instance, the singleton repository of Tile and Object Declarations. Besides the management of tiles it provides useful conversions of table based object declarations to tiles.

Being a singleton you can not create a new Tiles repository. The singleton is stored at the global variable ‘ti’ on level load.

The repository stores tiles for given string keys. The key strings can be of any length. Due to Lua limitations they need to be composed of printable 7-bit ASCII characters.

You can assign a tile to every key just once. A reassign causes an error. On one hand this allows internal implementation optimization, but on the other hand an unforeseen key reassignment is the most common level coding error that needs to be reported.

For task driven samples see section Tiles and World Tasks.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.12.1 Tiles Storage

Syntax:

ti["key"] = <tile|odecl>

Details:

Index write accesses to the singleton allows you to assign a tile or an table based object declaration, that is autoconverted to a tile, to a given key. The key must be a unique string. Unique in the sense that you can not reassign a new tile to key to which previously another tile has been assigned.

Syntax Samples:
 
ti["#"] = tile
ti["$"] = {"st_chess"}
ti["$"] = {"st_switch"}   -- error of key reassignment
ti["anykey"] = {"st_chess"}

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.12.2 Tiles Request

Syntax:

result = ti["key"]

Details:

Request of the tile that has been assigned to the given key. If no tile has yet been stored for the key a Lua ‘nil’ value is returned. Note that this tiles repository does not use wildcard characters as the named objects and positions repositories do. The asterisk ‘*’ and question mark ‘?’ are just keys as any other characters.

Syntax Samples:
 
tile = ti["#"]

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.12.3 Tile Conversion

Syntax:

result = ti(odecl)

Details:

Converts a table based object declaration to a new tile value.

Syntax Samples:
 
tile = ti({"st_chess"})
tile = ti{"st_chess"}   -- Lua syntax equivalence

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13 World

The World datatype is just used by a single instance, another singleton object. A reference to this singleton is stored at the Lua global variable ‘wo’ on level load. Being a singleton you can not instantiate another World object.

But even though the singleton ‘wo’ already exists on load of a level the world is still undefined in all aspects. From the first line of Lua code you can access the Global Attributes. But the world gets really set up with the World Creation. After this call the world has a well defined size and is filled with an initial set of objects that you can access and modify from this moment on.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.1 World Creation

Once all parameters have been set and all tiles have been declared it is time to create the level world with all its objects. This is done by the following constructor that appears in three variations.

Syntax:

width, height = wo(topresolver, defaultkey, map)

width, height = wo(topresolver, libmap)

width, height = wo(topresolver, defaultkey, width, height)

topresolver = ti | resolver | localresolver

Every tile in the world is given by a key that needs to be resolved to its declaration. This can be done either by the Tiles Repositoryti’, or by given library Resolvers or by a local Custom Resolver function. This argument takes the top resolver that is requested first.

defaultkey

A string that defines the key that should be taken as default. It is taken if no other key is given and it is added to a tile if a floor object is missing. The character length of this key defines the key size within the map

map

A table of strings. Each string describes a row of tiles by its tile keys. If a map is given, the world size is determined from the longest string and the number of rows.

libmap

A map of the library libmap

width

As an argument that is given instead of a map it describes the width of the desired world.

height

As an argument that is given instead of a map it describes the height of the desired world.

Syntax Samples:
 
w, h = wo(ti, "  ", 20, 13)
w, h = wo(resolver, " ", {
       "                    ",
       ...
       "                    "})
w, h = wo(ti, mylibmap)
Details:

This world constructor may just be called once. Every subsequent call causes an error. This call sets the size of the world to fixed values that are reported by its two return values. The world size can later on be retrieved by the world attributes Width and Height, too.

A mapless world is filled with default tiles. Rows in a given map that are shorter than others are filled with default tiles, too. Any tile that does not define a floor object will add the floor object of the default tile.

Every key is resolved to its tile declaration via the given resolver chain. The top resolver is given to this call as a parameter. If it is ‘ti’ the chain consists just of one element and the tile declaration stored in the tiles repository at the given key is taken. Otherwise the resolvers will be evaluated as explained in Resolver Chaining.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.2 World Tile Set

Syntax:

wo[<object | position | table | group | polist>] = tile_declarations

Details:

Index write accesses to an index that can be interpreted as a grid position or a list of grid positions allows you to set one or several new objects to the given positions according to the supplied tile declaration.

Syntax Samples:
 
wo[no["myobjectname"]] = {"st_chess"}
wo[po(3, 4)] = ti["x"]
wo[{2, 5}] = ti["x"] .. ti["y"]
wo[no["floorgroup#*"]] = {"it_burnable_oil"}
wo[no["myobjectname"] + NEIGHBORS_4] = ti["x"]

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.3 Global Attribute Set

Syntax:

wo["attritbutename"] = value

Details:

Write accesses to string type indices allows you to change Global Attributes. Just existing attributes with write accessibility may be changed. Note that some attributes must be set prior World Creation to take proper affect.

Syntax Samples:
 
wo["ConserveLevel"] = true

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.4 Global Attribute Get

Syntax:

var = wo["attritbutename"]

Details:

Read accesses to string type indices allows you to retrieve Global Attributes. Just existing attributes with read accessibility can be read. Note that some attributes report proper values just after World Creation.

Syntax Samples:
 
var = wo["IsDifficult"]

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.5 add

Add Other Objects to the world or a portable item to an inventory or other container object.

Syntax:

wo:add(tile_declarations)

wo:add(target, tile_declarations)

tile_declarations

One or many other object declarations given as tiles or anonymous tables.

target

YIN’, ‘YANG’ or valid Object Reference

Syntax Samples:
 
wo:add({"ot_rubberband", anchor1="a1", anchor2="w", length=2, strength=80, threshold=0})
wo:add(ti["r"] .. {"ot_wire", anchor1="w1", anchor2="w2"})
wo:add(YIN, {"it_magicwand"})
wo:add(no["mybag"], {"it_magicwand"} .. ti["h"] .. ti["c"])
Details:

Just Other Objects can be directly added to the world. Just portable Item Objects can be added to the player’s inventories ‘YIN’ and ‘YANG’ and to it_bags. No other targets do currently add objects by this method.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.6 drawBorder

Draw a border around a rectangle out of given tiles.

Syntax:

wo:drawBorder(upperleft_edge, lowerright_edge, <tile | key, resolver>)

wo:drawBorder(upperleft_edge, width, height, <tile | key, resolver>)

upperleft_edge

Upper left anchor position of the rectangle.

lowerright_edge

Lower right end position of the rectangle.

width

Width of the rectangle.

height

Height of the rectangle.

tile

A tile or an object declaration.

key

A key string to be resolved via the given resolver.

resolver

A resolver to be used for resolving the key to a valid tile.

Syntax Samples:
 
wo:drawBorder(po(0, 0), wo["Width"], wo["Height"], ti["#"])
wo:drawBorder(no["myRectUL"], no["myRectLR"], {"st_grate1"})
wo:drawBorder(no["myRectUL"], no["myRectLR"], {"fl_water"} .. ti["X"])
wo:drawBorder(no["myRectUL"], no["myRectLR"], "x", myresolver)
Details:

The rectangle as four one grid thick lines is drawn with the given tile. That means on every position of the rectangle itself an instance of every object of the tile declaration is set. The rectangle may degenerate to a single line.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.7 drawMap

Even if the world is initialized by a map on creation of the world (see section World Creation), it is sometime useful to be able to draw smaller submaps either as part of the initialization or as dynamic level changes within Callback Function. Of course the main purpose of ‘drawMap’ is the drawing of repeating patterns.

Syntax:

wo:drawMap(resolver, anchor, ignore, map, [readdir])

wo:drawMap(resolver, anchor, libmap-map, [readdir])

subresolver

Resolver to which unresolved requests should be forwarded. May be ‘ti’ as the final resolver of the resolver chain.

anchor

The anchor position where the upper left tile of the map should be drawn.

ignore

A tile key string that should be ignored. This key string is mandatory, even if it not used within the map.

map

A table of strings. Each string describes a row of tiles by its tile keys.

libmap-map

If the map used is created via libmap, the ‘ignore’-string can be omitted. The map’s default key will then be ignored instead.

readdir

An optional argument to modify the direction of the map relative to the world. This argument can be any of the constants described in Rotating and Mirroring Maps.

Syntax Samples:
 
wo:drawMap(ti, po(5, 7), "-", {"abcabc"})
wo:drawMap(ti, anchor_object, "--", {"--##--##","##--##"})
wo:drawMap(ti, {12, 5}, " ", {"122  221"}, MAP_ROT_CW)
Details:

The syntax is similar to the world creation call. But there are two essential differences you need to be aware of. First the map is drawn in the already existing world. Thus we need to define the position. This is done via the anchor position, which can be an already existing object, too.

The second difference is in the definition of a tile key string for tiles in the map that should be ignored. Remember that the world initialization requested a default tile key string. This default is still valid. But with the given ignore key string we can draw arbitrary shaped patterns by filling unused grids in the map with this key.

The length of the ignore key defines the map key length. It is strongly recommended to use the same key length as in the world map.

The rows of the supplied map are drawn from the anchor position. The rows may be of different length and may start with ignore tile keys. The anchor must be the position composed of the smallest x and smallest y coordinate within the pattern.

You can use drawMap anywhere after the world creation. You are even allowed to use it within the world creation in a resolver.

Full Example:
 
01    ti[" "] = {"fl_plank"}
02    ti["X"] = {"st_oxyd"}
03    ti["B"] = {"st_passage_black", flavor="frame"}
04    ti["W"] = {"st_passage_white", flavor="frame"}
05    ti["y"] = {"it_yinyang"}
06    ti["1"] = {"#ac_marble_black"}
07    ti["2"] = {"#ac_marble_white"}
08
09    function myresolver(key, x, y)
10        if key == "w" then
11            wo:drawMap(ti, po(x-1, y-1), "-", {"-W-",
12                                               "WXW",
13                                               "-W-"})
14            return ti({})
15        elseif key == "b" then
16            wo:drawMap(ti, po(x-1, y-1), "-", {"-B",
17                                               "BXB",
18                                               "-B"})
19            return ti({})
20        else
21            return ti[key]
22        end
23    end
24
25    w, h = wo(myresolver, " ", {
26      "                    ",
27      "  b         b       ",
28      "       w       w    ",
29      "                    ",
30      "                    ",
31      "   w                ",
32      "         12      b  ",
33      "              w     ",
34      "         w          ",
35      "      b             ",
36      "   w           b    ",
37      "         b          ",
38      "                    "
39    })
40    wo:shuffleOxyd()

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.8 drawRect

Fill a rectangle with a given tile.

Syntax:

wo:drawRect(upperleft_edge, lowerright_edge, <tile | key, resolver>)

wo:drawRect(upperleft_edge, width, height, <tile | key, resolver>)

upperleft_edge

Upper left anchor position of the rectangle.

lowerright_edge

Lower right end position of the rectangle.

width

Width of the rectangle.

height

Height of the rectangle.

tile

A tile or an object declaration.

key

A key string to be resolved via the given resolver.

resolver

A resolver to be used for resolving the key to a valid tile.

Syntax Samples:
 
wo:drawRect(po(0, 0), wo["Width"], wo["Height"], ti[" "])
wo:drawRect(no["myRectUL"], no["myRectLR"], {"fl_water"})
wo:drawRect(no["myRectUL"], no["myRectLR"], {"fl_water"} .. ti["#"])
wo:drawRect(no["myRectUL"], no["myRectLR"], "x", myresolver)
Details:

The complete rectangle is filled with the given tile. That means on every position of the rectangle and its interior an instance of every object of the tile declaration is set. The rectangle may degenerate to a single line.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.9 world floor

Retrieves the floor objects for the given position or positions.

Syntax:

result = wo:fl(<pos| {x, y}|x, y| obj | group| polist>)

Details:

This world method is identical to the global function fl.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.10 world item

Retrieves the item objects for the given position or positions.

Syntax:

result = wo:it(<pos| {x, y}|x, y| obj | group| polist>)

Details:

This world method is identical to the global function it.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.11 shuffleOxyd

Shuffling the color distribution of the st_oxyd makes every level, that is no meditation, a bit generic. On every level start the level looks a little bit different and the user has to solve a modified level. This provides long term amusement. Thus the call of this method is part of most levels.

Many levels just call this method without any arguments. This results in a shuffling of all st_oxyd that are not excluded by a ‘noshuffle’ attribute.

But sometimes levels need to influence the shuffling, either for ensuring that the level remains solvable, or simply to ensure fairness. Imagine a level that has two st_oxyds in every corner. If by chance a user gets a distribution where he has in each corner a pair of same colored oxyds, the level might be trivial. Another level may have a passage that the marble can pass just a few times. With 5 or more oxyds on each side of the passage you need to ensure that the marble never needs to pass the passage more often than possible. Both situations can be handled by providing proper rules as arguments to this method.

Syntax:

wo:shuffleOxyd(rules)

rules = rule, rule,...

No rule or as many as you like, all separated by a comma.

rule = {group1, group2, maxrule, minrule, circularrule, linearrule, log}

Each rule is a table with a subset of the listed entries. The group1 entry is mandatory. All other entries are optional and can be added in any combination.

group1 = group | objectreference | objectspecifier

A description of oxyd objects that are part of the first rule group. Either a group or a single object reference or a string specifier that resolves to a single or via wildcard to several oxyd objects are legal descriptors.

group2 = group | objectreference | objectspecifier

A description of oxyd objects that are part of the second rule group. Either a group or a single object reference or a string specifier that resolves to a single or via wildcard to several oxyd objects are legal descriptors.

maxrule = max = number

The maximum number of oxyd pairs.

minrule = min = number

The minimum number of oxyd pairs.

circularrule = circular = true

Avoid any pair of neighboring oxyds in group1. Avoid an oxyd pair of first and last oxyd in group1, too.

linearrule = linear = true

Avoid any pair of neighboring oxyds in group1.

log = log =    "solution" |"count" |"all"

Log additional information to the log stream for debugging purposes and security checks by the level author.

Syntax Samples:
 
wo:shuffleOxyd()
wo:shuffleOxyd({no["borderoxyds#*"]:sort("circular"), circular=true})
wo:shuffleOxyd({"leftoxyds#*","rightoxyds#*", min=3}, {"islandoxyds#*", max=0})
Details:

Any call of ‘wo:shuffleOxyd()’ must occur after all st_oxyd have been set. That means that it must follow the standard world initialization (see section World Creation). As a side effect shuffleOxyd will assign colors to all ‘OXYD_AUTO’ colored st_oxyd.

Once called the given shuffling rules remain valid. Any further reshuffling must be done by messages ‘closeall’ and ‘shuffle’ to one arbitrary st_oxyd instance. No addition of an st_oxyd or subsequent ‘wo:shuffleOxyd()’ calls are possible without disturbing and deleting the given rules.

Rule based shuffling is limited to a maximum of one pair of each standard oxyd color plus any combination of additional special fake, quake or bold oxyds summing up to a maximum of 32 oxyds. If more than 32 oxyds or 2 or more pairs of a single standard color are set, all oxyds will be shuffled by random ignoring any provided rules.

There are basically two different types of rules. Those with one group and those with two groups of oxyds (Note that group is the general API expression for a set of oxyds and not a mathematical group). For a single group the rules apply to the oxyd instances within this group. For two groups the rules apply to oxyd pairs with one oxyd in the first group and the other in the second group.

E.g. ‘{"islandoxyds#*", max=0}’ requests that there is no pair within this group of oxyds. Whereas ‘{"leftoxyds#*","rightoxyds#*", min=3}’ requests that there are 3 different oxyd pairs, each with one oxyd out of the leftoxyd group and the second out of the rightoxyd group.

Linear and circular rules can only be applied to a single group. They are shortcuts for the most common rules that are applied to oxyds arranged on a line or a circle. In both cases they avoid pairs of neighboring oxyds. They are equivalent to ‘n-1’ res. ‘n’ rules with all possible neighboring oxyd pairs as two groups and a rule of ‘max=0’.

Note that you can apply several rules at once to given groups. E.g. you can apply a minrule and a maxrule within one rule!

The shuffling process consists always of two stages. The most important first stage generates a valid oxyd pair distribution. That means that we settle which pairs will have the same color. But the color itself is assigned in an independent second stage. As for the examination of given rules just the pair distribution is relevant, we do just count and log these different distributions ignoring the colors.

With 16 oxyds of 8 different colors and no restricting rules you have 2027025 (15 * 13 * 11 * 9 * 7 * 5 * 3) different valid distributions. Keep in mind that useful rules should always keep hundreds or thousands of different valid distributions for a level.

For debugging purposes you can add a log parameter to one of the rules (it does not matter to which one). If you request the log of ‘solution’ the pair distribution will be printed to the log stream.

In case of ‘count’ the number of different oxyd distributions will be counted and logged. It is recommended to check the count on complex rules to ensure that enough distributions remain for a varying game. But be careful applying count on trivial rules. With 16 oxyds there may be as many as 2027025 distributions and it may take a standard PC up to 30 seconds to count them - add a factor of 17*19 for 20 oxyds!

Be very, very cautious in usage of logging ‘all’. This call tries to print all solutions. It takes ages if there are too many solutions. First check the count before trying to log the solutions.

Full Example:
 
01    wo["ConserveLevel"] = false
02
03    ti["~"] = {"fl_water"}
04    ti[" "] = {"fl_plank"}
05    ti["c"] = {"it_crack_l", brittleness=0}
06    ti["^"] = {"st_oneway_n"}
07    ti["1"] = {"ac_marble_black", 0, 0.5}
08
09    ti["x"] = {"st_oxyd", "island#"}
10    ti["y"] = {"st_oxyd", "left#"}
11    ti["z"] = {"st_oxyd", "right#"}
12
13    w, h = wo(ti, " ", {
14      "~~x  x  x  x  x  x~~",
15      "~~                ~~",
16      "~~~~^~~~~~~~~~~^~~~~",
17      "y       ~~~~       z",
18      "~       cccc       ~",
19      "y       ~~~~       z",
20      "~       cccc       ~",
21      "y       ~~~~       z",
22      "~       cccc       ~",
23      "y       ~~~~       z",
24      "~~~~c~~~~~~~~~~c~~~~",
25      "~~                ~~",
26      "~~        1       ~~"
27    })
28
29    wo:shuffleOxyd({"island#*", min=3, linear=true}, {"left#*","right#*", max=2, min=2})

This level uses 14 oxyds. The 6 oxyds in the upper row are on an island that can not be left once the marble entered it through one of the oneways. Thus we need 3 pairs of oxyds on this island, which are enforced by the min rule. To avoid trivial neighboring pairs on the island, we do add a linear rule, too. The marble can pass just three times between the left and right islands. This allows a first look on the color oxyds with one pass and opening one pair on each of the two following passes. Thus we limit the number of pairs by a max rule to 2. To avoid trivial oxyd pair distributions, like two pairs on the left and two pairs on the right side, we do add a min rule that enforces that two shared pairs of oxyds do exist.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.13.12 world stone

Retrieves the stone objects for the given position or positions.

Syntax:

result = wo:st(<pos| {x, y}|x, y| obj | group| polist>)

Details:

This world method is identical to the global function st.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14 Functions

Besides all the features strongly related to a value as context and thus implemented as operators or methods of these datatypes, a few other tasks remain. These are either context free or take at least in one variation just a standard Lua datatype, that does not supply a context. Thus these tasks are implemented as simple functions.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.1 assert_bool

The function ‘assert_bool’ throws an error if a given condition doesn’t hold.

Syntax:

assert_bool(condition, message, level)

condition

A boolean expression or anything else. If it is false or nil, an error will be thrown.

message

A string, holding the error message. If message is nil or empty, an "anonymous assertion" will be thrown, but it’s always better to provide a meaningful error message.

level

level specifies the error position in the same way as does Lua’s ‘error’-function. Default is 1.

Syntax Samples:
 
assert_bool(no["mystone"]:exists(), "Stone 'mystone' has disappeared.")
Details:

Assertions help you to detect coding errors. They are heavily used on argument checks of library functions and resolver implementations. As the assertions should not lead to performance penalties during runtime they are usually just evaluated when the level’s ‘status’ is declared in the XML header element <version> with a value of either "test" or "experimental".

For "stable" and "released" levels standard assert statements are simply skipped on compilation like Lua comments. But you can enforce assert statements to be executed even in these modes by the following pattern of assignment and additional braces:

 
dummy = (assert_bool)(no["mystone"]:exists(), "Stone 'mystone' has disappeared.")

Similar to cond, all side effects within the evaluation of ‘message’ and ‘level’ will appear.

See Lua’s manual for more detailed information about the ‘error’-function.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.2 assert_type

The function ‘assert_type’ throws an error if the first argument is not of one of the specified types.

Syntax:

assert_type(var, vardescription, level, type1, type2, ...)

var

Any kind of variable.

vardescription

If ‘var’ is not of one of the types ‘type1’, ‘type2’ ..., then an error message will be thrown which includes the actual type of ‘var’ and the desired types. ‘vardescription’ is a string which holds additional information for the error message. It should be a lower-case not-too-short description of ‘var’ (a name, as it is), additional details should be added in brackets.

level

level specifies the error position in the same way as does Lua’s ‘error’-function. Can’t be omitted, use ‘1’ if in doubt.

type1, type2, ...

A sequence of strings. If ‘var’ is none of these types, the error will be thrown. See details below for type descriptors.

Syntax Samples:
 
assert_type(arg1, "mygreatfunction first argument (level width)", 1, "nil", "positive integer", "position")
Details:

Assertions help you to detect coding errors. They are heavily used on argument checks of library functions and resolver implementations. As the assertions should not lead to performance penalties during runtime they are usually just evaluated when the level’s ‘status’ is declared in the XML header element <version> with a value of either "test" or "experimental".

For "stable" and "released" levels standard assert statements are simply skipped on compilation like Lua comments. But you can enforce assert statements to be executed even in these modes by the following pattern of assignment and additional braces:

 
dummy = (assert_type)(arg1, "myfunction first argument", 1, "integer")

Possible types are all Lua types (like "nil", "number", "boolean", "string", "table", "function") except "userdata", all Enigma-own user types ("object", "position", "tile", "tiles", "group", "world", "polist", "unknown"), and types defined inside metatables ("map" from libmap), see etype. In addition, the following type descriptors are recognized:

"integer"

Any integer number (..., -2, -1, 0, 1, 2, ...)

"positive"

Any number which is positive and not zero.

"non-negative"

Any number which is not negative, i.e. which is positive or zero.

"natural"

Any non-negative integer number (0, 1, 2, ...).

"positive integer"

Any positive integer number (1, 2, 3, ...).

"non-empty string"

Any string other than the empty string "".

"any table"

If ‘var’ is a table, the ‘_type’-attribute of its metatable will be used as its etype. In particular, it won’t be accepted as a "table" anymore, if this ‘_type’-attribute exists. For example,

 
assert_type(mytable, "large table", 1, "table")

will throw an assertion when ‘mytable’ is a "map", although, technically, a "map" always is a "table". You can use "any table" as type to allow for any table, regardless of its metatable.

"valid object"

Any valid object.

Similar to cond, all side effects within the evaluation of ‘vardescription’, ‘level’ and any type descriptor will apply.

See Lua’s manual for more detailed information about the ‘error’-function.

Full Example:
 
function paint_lawn(pos)
    assert_type(pos, "paint_lawn first argument", 2, "position", "object", "polist", "group", "table")
    if etype(pos) == "object" then
        assert_bool(-pos, "paint_lawn: Object not existing.", 2)
    end
    wo[pos] = ti["lawn"]
end
paint_lawn(no["mystone"])
paint_lawn("myotherstone")

If ‘mystone’ doesn’t exist, no["mystone"] will still be of etype "object", an invalid object. Hence assert_type will not trigger, but assert_bool will.

If ‘mystone’ exists, the second ‘paint_lawn’ will throw an error via ‘assert_type’, as pos now is a "string". The error message will be:

 
Wrong type for paint_lawn first argument, is string, must be one of position,
object, polist, group, table.

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.3 cond

cond’ is a conditional assignment, a substitution for the ternary ‘?:’ operator of C-like languages. Note however, that it is not an equivalent substitution but just a workaround with some subtle side effects.

Syntax:

cond(condition, iftrue, iffalse)

condition

A boolean expression.

iftrue

The expression to be returned if ‘condition’ is true.

iffalse

The expression to be returned if ‘condition’ is false.

Syntax Samples:
 
ti["x"] = cond(wo["IsDifficult"], {"st_death"}, ti["#"])
ti["D"] = cond(wo["IsDifficult"], {"st_death"}, {"nil"})
Details:

cond’ always evaluates both expressions ‘iftrue’ and ‘iffalse’, regardless of ‘condition’. Hence,

 
mytable = {1,2,3,4,5,6,7,8,9,0}
removed_element = cond(i < 5, table.remove(mytable, i), table.remove(mytable, 5))

will always remove two elements. With ‘i=2’ the ‘2’ will be returned but ‘mytable’ will result in ‘{1,3,4,5,7,8,9,0}’, and with ‘i=6’ you get the ‘5’ but mytable will be ‘{1,2,3,4,7,8,9,0}’.

Another Enigma example that will cause errors is:

 
w,h = cond(wo["IsDifficult"], wo(ti, " ", map1), wo(ti, " ", map2))

Both, the second and the third argument will be evaluated. Thus two contradicting attempts to create a new world will be made causing the second one to fail. Use the following statement instead:

 
w,h = wo(ti, " ", cond(wo["IsDifficult"], map1, map2))

However, in most cases ‘cond’ is used anyway with static expressions for ‘iftrue’ and ‘iffalse’ (e.g. strings or variables) and no side effects will occur.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.4 etype

The function ‘etype()’ returns the advanced type of its argument.

Syntax:

etype(var)

var

Any kind of variable.

Syntax Samples:
 
argtype = etype(value)
Details:

Lua types are "nil", "number", "boolean", "string", "table", "function", "userdata", and "thread". You can use Lua’s ‘type’-function to query the type of any variable. However, Enigma defines more types through various means, and these types can be queried via ‘etype’. ‘etype’ will return its argument’s Lua type as usual, with the following two exceptions:

"userdata"

Instead of "userdata", Enigma’s special types will be returned. These special types are "object", "position", "tile", "tiles", "group", "world", "polist" and "default". If an unknown userdata is encountered, "unknown" will be returned.

"table"

If var is a table, its metatable will be queried. If there is an entry ‘_type’, this entry will be used as etype. Most important examples of this kind are libmap-maps, Resolvers and res.maze with its mazes and cells. So ‘etype’ will return "map", "resolver", "maze" and "cell", too. You may access the etype-system through ‘_type’ whenever you use metatables on your own.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.5 fl

The function ‘fl()’ retrieves the floor objects for the given position or positions.

Syntax:

result = fl(<pos| {x, y}|x, y| obj | group| polist>)

Details:

If the argument describes a single position, the floor object at this position is returned. When this single position is outside of the world an invalid ‘NULLObject reference is returned.

If the argument is either a Group or PositionList all floor objects of the related positions are retrieved and added in the same sequence to a new result group. Invalid positions will be skipped without adding an object to the group.

In any case you can send messages to the result value.

Syntax Samples:
 
floor = fl(po(3, 5))
floor = fl({3, 5})
floor = fl(3, 5)
floor = fl{3, 5}     -- by Lua syntax equivalence
floor = fl(mystone)
group = fl(no["door#*"])
group = fl(po(3, 5)..po(4, 2))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.6 grp

The function ‘grp()’ builds a Group out of its argument Objects.

Syntax:

grp(<{obj1,obj2,...}| obj1,obj2,... |group>)

Details:

Returns a new Group that is build up by the objects listed by the arguments. The contents objects must be either listed in a Lua table, given as multiple single object arguments or an existing group. In all cases the sequence of objects is maintained in the returned new group, but all invalid ‘NULL’ objects are omitted. In case one object is listed multiple times just the first instance will occur in the group and subsequent instances will be omitted.

Syntax Samples:
 
newgroup = grp(obj1, obj2, obj3)
newgroup = grp({obj1,obj2})
newgroup = grp{obj1,obj2}   -- Lua syntax equivalence
newgroup = grp{}            -- empty group
newgroup = grp(group)       -- a copy of group cleaned of invalid ‘NULL’ objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.7 it

The function ‘it()’ retrieves the item objects for the given position or positions.

Syntax:

result = it(<pos| {x, y}|x, y| obj | group| polist>)

Details:

If the argument describes a single position, the item object at this position is returned. When no item is located at the given single position or this position is outside of the world an invalid ‘NULLObject reference is returned.

If the argument is either a Group or PositionList all item objects of the related positions are retrieved and added in the same sequence to a new result group. Invalid positions or positions without items will be skipped without adding an object to the group.

In any case you can send messages to the result value.

Syntax Samples:
 
item = it(po(3, 5))
item = it({3, 5})
item = it(3, 5)
item = it{3, 5}     -- by Lua syntax equivalence
item = it(mystone)
group = it(no["door#*"])
group = it(po(3, 5)..po(4, 2))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.8 ORI2DIR

The table ‘ORI2DIR[]’ converts orientation values to direction values.

Syntax:

result = ORI2DIR[orientation]

Details:

The table has stored the proper directions values at the index positions of the related orientations.

Syntax Samples:
 
direction = ORI2DIR[NORTH]      -- N  = po(0, -1)
direction = ORI2DIR[SOUTHEAST]  -- SE = po(1,  1)
direction = ORI2DIR[NODIR]      --      po(0,  0)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.9 random

The function ‘random()’ is a syntax compatible replacement for the standard Lua function ‘math.random()’. Both names refer to the same Enigma random implementation.

Syntax:

result = random(<|n|l,u>)

Details:

When called without arguments, math.random returns a pseudo-random real number in the range [0,1). When called with a number n, math.random returns a pseudo-random integer in the range [1,n]. When called with two arguments, l and u, math.random returns a pseudo-random integer in the range [l,u].

The only difference from the Lua implementation is the random generator itself. Enigma uses an own implementation that guarantees the same pseudo-random number sequence on any operating system and any processor for a given seed. This feature will be important for future Enigma versions and thus the randomseed can not be modified by the level itself.

Syntax Samples:
 
float = random()          -- e.g. 0.402834
integer = random(20)      -- e.g. 13
integer = random(5, 10)   -- e.g. 5

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.10 st

The function ‘st()’ retrieves the stone objects for the given position or positions.

Syntax:

result = st(<pos| {x, y}|x, y| obj | group| polist>)

Details:

If the argument describes a single position, the stone object at this position is returned. When no stone is located at the given single position or this position is outside of the world an invalid ‘NULLObject reference is returned.

If the argument is either a Group or PositionList all stone objects of the related positions are retrieved and added in the same sequence to a new result group. Invalid positions or positions without stone will be skipped without adding an object to the group.

In any case you can send messages to the result value.

Syntax Samples:
 
stone = st(po(3, 5))
stone = st({3, 5})
stone = st(3, 5)
stone = st{3, 5}     -- by Lua syntax equivalence
stone = st(myfloor)
group = st(no["cherry#*"])
group = st(po(3, 5)..po(4, 2))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.14.11 usertype

The function ‘usertype()’ returns type info for Enigma data types.

Syntax:

usertype(var)

var

Any kind of variable.

Syntax Samples:
 
argtype = usertype(value)
Details:

Just for Lua type "userdata" Enigma’s special type info will be returned. These special types are "object", "position", "tile", "tiles", "group", "world", "polist" and "default". If another data type is encountered, "unknown" will be returned.

The function etype provides a more general type evaluation for arbitrary data types and is partially based on this function.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6. Common Attributes and Messages

Some attributes, messages and constants are common to many objects or even supported by all objects. We describe them here in detail. The following chapters will just reference them or even skip them when they are generally supported and used in the default manner.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1 Common Attributes


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.1 name

The attribute of Object Naming that allows you to name any object for reference purposes. It is up to you to ensure the uniqueness of the names. But the engine supports you by autonumbering names ending on a ‘#’ sign (see section Object Naming). If you reuse an already used name the first object will be unnamed and all name references will point to the new named object. If you have need of naming an object you should do it with the object creation as a few objects have need of names and will otherwise be named by the engine with unique names.

Note that this attribute is not listed in the individual object descriptions.

Type:   string
Values:   {a-zA-Z0-9_}+

A sequence of characters of the given characters plus special characters as mentioned in the text above.

Default:   nil

Some objects will be autonamed if no name is defined.

Access:   read/write
Support:   by all objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.2 state

The central attribute of any object that describes the current state of an object in its standard life cycle. This Object State is described by a simple number. Most dynamic objects have just 2 states. Others may have more. The available states are listed with each object. This universal attribute allows common messages like toggle, signal, on, off, open, close.

Type:   number
Values:   dependent on the individual object

Please use the given upper case constants.

Default:   0
Access:   read/sometimes write

While it is common to set the state attribute on object creation, it is preferable to change the object state later on by messages.

Support:   by all objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.3 target

All active objects react on being triggered by performing an action on their targets. This attribute is part of the Target - Action paradigm that guarantees plugability of objects. You can either set a general ‘target’ attribute for an object, or you can set state dependent attributes ‘target_0’, ‘target_1’,... (see section Object State). They all have the same syntax:

Type:   string, object, group, tokens   See section Object Description

Single targets may be declared by their object name or their reference. Multiple targets can be declared by usage of groups and tokens.

Values:   See section Object Attributes
 
target = "myDoor"
target = myObject
target = {"myDoor", myObject}
target = {grp(obj1, obj2), "myDoor", myObject}
Default:   nil
Access:   read/write
Support:   by all objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.4 action

All active objects react on being triggered by performing an action on their targets. This attribute is part of the Target - Action paradigm that guarantees plugability of objects. You can either set a general ‘action’ attribute for an object, or you can set state dependent attributes ‘action_0’, ‘action_1’,... (see section Object State). They all have the same syntax:

Type:   string, tokens of strings   See section Target - Action

A single action may be declared by its message string. Multiple actions that match multiple targets can be declared by tokens of strings.

Values:   See section Object Attributes
 
action = "open"
action = {"open", "turn", "toggle"}
Default:   nil
Access:   read/write
Support:   by all objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.5 nopaction

A very special addition to the Target - Action paradigm that allows in case of state specific actions to deny the sending of default messages (see section Object State).

Type:   bool
Values:   true, false
Default:   false
Access:   read/write
Support:   by all objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.6 safeaction

A very special addition to the Target - Action paradigm that allows to kill the sender within the execution of the action code.

Type:   bool
Values:   true, false
Default:   false
Access:   read/write
Support:   by all objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.7 inverse

An attribute that requests an inversion of the action value. It is supported by all objects with boolean action values.

Note that this attribute is not listed in the individual object description if the object has boolean action values.

Type:   bool
Values:   true, false
Default:   false
Access:   read/write
Support:   by most objects

All objects with boolean action values will support this attribute. Additionally some objects with other invertible action value types like orientations will support the inversion of their attributes as stated in the individual object descriptions.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.8 destination

An attribute that describes one or several destinations. It is used by objects like it_vortex and it_wormhole to describe their teleporting destination and by ac_horse to describe its traveling path.

Note that this attribute is only supported if it is listed in the individual description.

Type:   tokens or a single position

Just a single position for a first destination is allowed. Use tokens to define multiple destination.

Values:   See section Object Attributes
 
destination = po(3.0, 4.7)
destination = "myFloor"
destination = myObject
destination = {"vortex2","vortex3","vortex4"}
po["dest1"] = po(3,4)
po["dest2"] = po(7,8)
destination = {"dest1","dest2","myFloor"}

Note that objects like ‘it_wormhole’ that have just a single destination do take the first token object. Note that in contrast to target tokens a destination tokens argument can take named positions, too. Referencing floors that may be destructed by bombs, cracks, floor building stones, etc. are save destinations, too.

Default:   nil
Access:   read/write
Support:   by teleporting objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.9 friction

An attribute that describes the decelerating friction force on actors that are on the floor. The friction force increases with the speed of the actor and is decelerating for positive friction values. But friction can be set to negative values as well what generates an accelerating force that is very difficult to control for the player.

Besides all floors some floor covering items like it_strip, it_meditation may provide friction values as well to deviate from the floor friction.

Type:   number
Values:   any floating point number
Default:   nil
Access:   read/write
Support:   by all floor, and floor covering item objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.10 adhesion

An attribute that describes the adhesion that allows an actor to accelerate on a floor. Greater adhesion leads to more accelerating force at the same given mouse speed. Adhesion can be set to negative values as well what generates an accelerating force in the inverse direction of the mouse movement which is a little bit difficult to control for the player.

Besides all floors some floor covering items like it_strip, it_meditation may provide adhesion values as well to deviate from the floor adhesion.

Type:   number
Values:   any floating point number
Default:   nil
Access:   read/write
Support:   by all floors, and floor covering item objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.11 checkerboard

An attribute that defines if a given object declaration should only be applied on ’even’ or ’uneven’ grid positions. Setting this attribute to ‘0’ assures that this object will only be set on grid positions with an even sum of x + y grid coordinates, where as a value of ‘1’ assures that the sum must be uneven. This way you can easily provide two different object declarations for a tile to generate an arbitrarily shaped map of checkerboard floors, items or stones.

Type:   number
Values:   0, 1
Default:   nil
Access:   read/write
Support:   by all floor, stone and item objects
 
ti["c"] = ti({"fl_sahara", checkerboard=0}) .. {"fl_tigris", checkerboard=1}

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.12 rubbers

An attribute that gives you access to the ot_rubberbands that are currently connected to this object.

Note that this attribute is read only. You can use the rubberband references to kill or reconnect single rubberbands. But to add new rubberbands you need to use the world’s ‘add’ method.

Type:   group of ot_rubberband objects
Default:   nil
Access:   read only
Support:   by actor and stone objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.13 wires

An attribute that gives you access to the ot_wires that are currently connected to this stone object.

Note that this attribute is read only. You can use the wire references to kill or reconnect single wires. But to add new wires you need to use the world’s ‘add’ method.

Type:   group of ot_wire objects
Default:   nil
Access:   read only
Support:   by all stone objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1.14 fellows

An attribute that gives you access to the group of all objects that are currently connected either by an ot_rubberband or an ot_wire.

Note that this attribute is read only. It is just for evaluation of the current level state. But to add new rubberbands or wires you need to use the world’s ‘add’ method.

Type:   group of objects
Default:   nil
Access:   read only
Support:   by actor and stone objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2 Common Messages


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.1 toggle

This is the default message that is always taken if no other message is provided. It toggles the Object State quite independent of the very nature of the state. Two-stated objects like switches will toggle their state form ‘ON’ to ‘OFF’ or from ‘OFF’ to ‘ON’. Door like objects will toggle their state from ‘OPEN’ to ‘CLOSED’ or from ‘CLOSED’ to ‘OPEN’. Other objects like st_fourswitch will turn into the next orientation. Generally the object will toggle to its next state.

Value:   -
Returns:   -
Support:   by nearly all objects which use the ‘state’ attribute

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.2 nop

A dummy message that just does nothing: no operation. You may need it in cases of state dependent actions to block an otherwise sent default ‘toggle’ message (see section Object State).

Value:   -
Returns:   -
Support:   by all objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.3 signal

A signal message tries to set the object to the state 0 (‘OFF’, ‘CLOSED’) or state 1 (‘ON’, ‘OPEN’) according to its value. This message allows you to keep the states of an action source and a receiving object in sync. Note that values like ‘true’, ‘false’ and orientation values like ‘WEST’ to ‘NORTH’ are converted to 0 and 1. This allows you to use ‘signal’ as action message on most objects.

Value:   0, 1
Returns:   -
Support:   by nearly all objects which use the ‘state’ attribute

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.4 on

This message is just supported by objects that can be switched on and off. Just objects in state ‘OFF’ will be switched on. An object in state ‘ON’ remains unchanged in its state.

Value:   -
Returns:   -
Support:   by objects that can be switched on and off

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.5 off

This message is just supported by objects that can be switched on and off. Just objects in state ‘ON’ will be switched off. An object in state ‘OFF’ remains unchanged in its state.

Value:   -
Returns:   -
Support:   by objects that can be switched on and off

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.6 open

This message is just supported by door like objects that can be opened and closed. Just objects in state ‘CLOSED’ will be opened. An object in state ‘OPEN’ remains unchanged in its state.

Value:   -
Returns:   -
Support:   by door like objects that can be opened and closed

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.7 close

This message is just supported by door like objects that can be opened and closed. Just objects in state ‘OPEN’ will be closed. An object in state ‘CLOSED’ remains unchanged in its state.

Value:   -
Returns:   -
Support:   by door like objects that can be opened and closed

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.8 ignite

Sets fire to a floor if it is burnable (see section Fire Spreading) or start an explosion or shattering of items like it_dynamite, it_bomb or stones like st_ice, st_dispenser and many more.

Value:   -
Returns:   -
Support:   by all Floor Objects and explosive or shattering items and stones

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.9 heat

Sets fire to a floor if it is burnable (see section Fire Spreading) or causes a Heat-Transformation on a floor or other objects like st_ice that starts melting. Some objects do react on a heat message like on an ignite message by explosions or shattering.

Value:   -
Returns:   -
Support:   by all Floor Objects and some other objects.

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.10 setfire

Sets fire to a floor if it is burnable (see section Fire Spreading).

Value:   -
Returns:   -
Support:   by all Floor Objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.11 kill

This message causes the recipient to cease to exist. You are just allowed to kill objects that are directly part of the world. Objects owned by players, being content part of a bag or otherwise owned by another object will refuse this message on behalf of their owner.

Value:   -
Returns:   -
Support:   by all world owned objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.12 sound

This message causes the recipient to play the sound given by the first string argument value. It is played at the position of the object. The volume can be defined by a second number argument and defaults to 1.0.

Value:   string [, number]
Returns:   -
Support:   by all objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2.13 disconnect

This message causes the recipient to disconnect from all fellows by cutting all wires and rubbers that are connected to it.

Value:   -
Returns:   -
Support:   by all objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3 Common Constants

Constants for Pseudo Datatypes.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.1 state values

Number constants for object attribute state. Please choose the constant that is appropriate for an object and do avoid using numbers.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.2 color values

Number constants for attribute color. Please choose the constant that is appropriate for an object and do avoid using numbers. Note that objects do support a color attribute only, if it is related to a functionality. The color of st_oxyd uses another enumeration scheme due to legacy reasons, see section oxyd colors.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.3 actor controllers

Number constants for actor attribute controllers. Please choose the constant that is appropriate for an object and do avoid using numbers.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.4 oxyd colors

Number constants for st_oxyd attribute ‘oxydcolor’. Please choose the constant that is appropriate for an object and do avoid using numbers.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.5 orientations

Number constants for attributes ‘orientation’ or ‘slope’. Please choose the constant that is appropriate for an object and do avoid using numbers.

Just for st_mirror another alternative orientation notation exists:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.6 direction offsets

Position constants as vectorial direction offsets. Please choose the constant that is appropriate for a calculation and do avoid using the trivial positions instead.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.7 position lists

Position list constants. Please choose the constant that is appropriate for a calculation and do avoid using the trivial position lists instead.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.8 essentialness

Number constants for attribute essential. Please choose the constant that is appropriate for an object and do avoid using numbers.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.9 meditation types

Number constants for it_meditation attribute state. Please choose the constant that is appropriate for an object and do avoid using numbers.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.10 glasses abilities

Number constants for it_glasses attribute state and global variable ExtralifeGlasses. Please use sums of the constants that are appropriate for your glasses and do avoid using numbers.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.11 coinslot acceptance

Number constants for st_coinslot attributes ‘interval_*’. Please choose the constant that is appropriate for the coinslot and do avoid using numbers.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.12 rubberband length

Number constant for it_rubberband, st_rubberband and ot_rubberband attribute ‘length’. Please choose the constant that is appropriate for the rubberband length and do avoid using numbers.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.13 subsoil kind

Number constants for the global attribute SubSoil. Please choose the constant that is appropriate for the SubSoil and do avoid using numbers.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.14 screen scrolling

Constants for screen scrolling global attributes FollowMethod and FollowAction. Please choose the constants that are appropriate for screen scrolling and do avoid using numbers or position values.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3.15 map read directions

Constants for map read directions (see drawMap) and map transformations (see Rotating and Mirroring Maps and Map Transformation). They are not numbers, to allow advanced operations on them when libmap is loaded, see Map Transformation Composition and Powers.

Because map transformations are not numbers, you may not safe them as attributes of objects. Instead, you have to use ‘index’ and ‘MAP_ALL’, see Map Transformation Index and MAP_ALL.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4 Global Attributes


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.1 ActorimpulseStrength

A global scalar default factor for the actorimpulse stone bumping force. This global value is only used if no object specific value is set.

Type:   number
Values:   float number
Default:   +200.0
Access:   read/write
Support:   st_actorimpulse

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.2 AllowSingleOxyds

A global variable that enables the existence of single unmatched st_oxyd stones. Setting this variable to true allows you to set uneven numbers of oxyd stones for fixed colors. If a level deletes or adds oxyd stones during runtime a true value avoids possible problems due to temporary uneven oxyd stone numbers. The default false value causes an error message on detection of uneven oxyd numbers.

Type:   bool
Values:   true, false
Default:   false
Access:   read/write
Support:   st_oxyd

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.3 AllowSuicide

A global variable that enables the user to commit suicide with all owned actors by pressing ‘F3’. As this legacy feature can cause unexpected shortcuts, you can deny this freedom. A single actor suicide as committed by activation of an it_spoon is not affected by this global variable.

Type:   bool
Values:   true, false
Default:   true
Access:   read/write
Support:   no object

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.4 AutoRespawn

A global variable that determines, if in absence of an it_flag drop given respawn position, the last recorded secure actor position should be taken instead of the starting position, in case the actor dies and is resurrected.

The recorded auto respawn position will be appropriate for common actor deaths like sinking in fl_water, falling into fl_abyss, hitting of st_death, slipping from an it_strip, being hit by an it_laserbeam. Even a jump into one these lethal destinations resurrects the actor onto the position from where the actor started its jump. There is no danger of being resurrected on floors without adhesion (floor).

The usage of it_flag in combination with AutoRespawn is fully supported. On drop of a flag, the flag position remains the valid respawn position until a flag is picked up. After a pickup of the flag, what delete the flag defined position the AutoRespawn position gets active until a flag is dropped again.

Deaths caused by actor collisions, it_crack and floor shattering explosions can lead to leathal AutoRespawn positions. Either avoid setting AutoRespawn to true in such cases or provide an it_flag in such cases.

Shortcuts introduced by AutoRespawn are unlikely, but not generally impossible.

Type:   bool
Values:   true, false
Default:   false
Access:   read/write
Support:   ac_marble, ac_pearl

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.5 ConserveLevel

A global variable that determines if a dead actor will be resurrected in case of it_extralifes in the gamer’s inventory.

If true, dead actors attached to a player will be resurrected as long as extralifes are available. If a player has no living actor to control or is missing the actor’s essential constraints, the player is dead. The level may still continue if a second player is alive. If the gamer has an it_yinyang in his inventory in single user mode, the control switches to the second player. If all players are dead, a new level game is started.

If the conserve mode is false, no actors will be resurrected. As soon as the player is dead and the control cannot switch to another player, all dead actors are resurrected by using extralifes, and the level is restarted without finishing the level game.

Use false if the level can either be not solved in case of resurrected actors or would provide a major shortcut. In all other cases, mode true with proper usage of actors essential constraints will be preferable.

Type:   bool
Values:   true, false
Default:   true
Access:   read/write
Support:   no objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.6 CrackSpreading

A global default value denoting the probability that a brittle floor plate, that is a floor with an it_crack on top, causes direct neighboring floors to either be marked with an invisible crack or to toggle such invisible cracks to a small visible cracks. A value of 1.0 ensures that the crack spreads to the direct neighbors, a value of 0.0 prohibits the spreading to neighbor grids. Note that even if crack spreading is prohibited, existing cracks on neighbor grids may continue disintegrating due to their Fragility. For more details see it_crack.

Type:   number
Values:   float number between 0.0 and 1.0
Default:   0.5
Access:   read/write
Support:   it_crack

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.7 CreatingPreview

A global read only variable that indicates if the current level load is just for creating a preview thumbnail of the level or a real game play. If ‘true’, you can, e.g., change the start-position of the main actor to display another part of the level in the preview, or hide objects from it. When changing the initial position, it might be advantageous to also set the Display Follow Strategy to permanent smooth scrolling:

 
if wo["CreatingPreview"] then
    wo["FollowGrid"] = false
    wo["FollowMethod"] = FOLLOW_SCROLL
end
Type:   bool
Values:   true, false
Default:   false
Access:   read only
Support:   object independent

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.8 ElectricStrength

A global scalar default factor for electrical forces. Positive numbers result in attracting forces for opposite charges and repelling forces for equal signed charges. Where as negative numbers result in repelling forces for opposite charges and attracting forces for equal signed charges. This global value is always multiplied by the charge load of the affected actors. The actor’s charge load usually results from hits of st_charge.

Type:   number
Values:   float number
Default:   15.0

Positive number are attracting, negative numbers are repelling.

Access:   read/write
Support:   Actor Objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.9 ExtralifeGlasses

A global variable that defines the it_glasses type that is generated on the laser light conversion of an it_extralife

Type:   number
Values:   positive integer number

A sum out of the constants ‘SPOT_DEATH’, ‘SPOT_HOLLOW’, ‘SPOT_ACTORIMPULSE’, ‘SPOT_SENSOR’, ‘SPOT_LIGHTPASSENGER’, ‘SPOT_TRAP

Default:   SPOT_DEATH + SPOT_HOLLOW + SPOT_LIGHTPASSENGER
Access:   read/write
Support:   it_extralife

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.10 FallenPuzzle

A global object replacement kind for st_puzzles fallen into fl_water or fl_abyss. Besides any floor kind you can use any key of a tile declaration preceeded by an equal sign '='. By this second method you can use attributed floor kinds with special values for friction and adhesion.

Additionally you can set this global ‘FallenPuzzle’ attribute to "it_pipe" or "it_stip". In both cases fallen puzzle objects will be replaced by items of the given class with the identical connections as the fallen puzzle stones. Just in case that an item of the given class is already present at one of the affected grid positions the connections of these will be adjusted in a special way. An existing "it_pipe" remains unchanged, but an "it_stip" will add the connections of the fallen puzzle to its own connections.

Type:   string
Values:   any floor object kind, "it_strip", "it_pipe" or "=key", with key being a valid tile key
Default:   "fl_gray"
Access:   read/write
Support:   st_puzzle

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.11 FollowAction

A global variable that describes the action of the display on relocation. This attribute is introduced for future extensions, but is currently just partially supported. Just the values listed below are used. Please use this attribute just as explained in Display Follow Strategy.

Type:   number or position
Values:   0, FOLLOW_FULLSCREEN, HALFSCREEN

The distance of display readjustment. Positions are used to supply different values for x and y. The value ‘{19, 12}’ is a standard full screen move. The value ‘{9.5, 6}’ is a standard half screen move. A value ‘0’ is a minimal smooth move or the default value for grid based moves.

Default:   {19, 12}

Actually the default is mode based (see section Display Follow Strategy).

Access:   read/write
Support:   no objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.12 FollowGrid

A global variable that determines if the display is fixed in its static positions to grids or if it can be scrolled to any pixel position (see section Display Follow Strategy).

Type:   bool
Values:   true, false
Default:   true
Access:   read/write
Support:   no objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.13 FollowMethod

A global variable that describes the method how the display moves, either not at all, by pixelwise scrolling, or by flipping to another screen or region (see section Display Follow Strategy).

Type:   number
Values:   FOLLOW_NO, FOLLOW_SCROLL, FOLLOW_FLIP
Default:   FOLLOW_FLIP
Access:   read/write
Support:   no objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.14 FollowThreshold

A global variable that describes the threshold at which a crossing active marble triggers the display to relocate. It is given as the distance to the screen boundary (see section Display Follow Strategy).

Type:   number or position
Values:   0 or positive number, or a pair of two positive numbers

The distance from the screen boundary at which the displays readjusts. Positions are used to supply different values for x and y. All values need to be less than half of the screen size.

Default:   0.5
Access:   read/write
Support:   no objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.15 Fragility

A global default value denoting the probability that a brittle floor plate, that is a floor with an it_crack on top, continues to disintegrate on events like an actor entering, passing a neighboring it_crack, nearby explosions or fire heating. A value of 1.0 ensures that the crack continues to disintegrates on these events, a value of 0.0 prohibits visible cracks to get larger. This default is superseded by fragility (floor) and it_crack specific values.

Type:   number
Values:   float number between 0.0 and 1.0
Default:   1.0
Access:   read/write
Support:   it_crack and Floor Objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.16 FrictionStrength

A global scalar default factor for floor friction values. This global value is always multiplied by the floor specific friction on calculation of the friction force applied to actors.

Type:   number
Values:   float number
Default:   1.0
Access:   read/write
Support:   all floors

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.17 GlobalForce

A global constant force that is added to every actor on every grid. The force is given as a vector constant by a value of type Position.

Type:   Position
Values:   pair of float numbers
Default:   po(0.0, 0.0)
Access:   read/write
Support:   all floors

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.18 Height

A global read only variable reports the height of the world in grid units. This is set by the initial world constructor call (see section World Creation).

Type:   number
Values:   positive integer number
Default:   ?
Access:   read only
Support:   object independent

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.19 InfiniteReincarnation

A global variable that switches off the it_extralife consumption on resurrection of a dead actor. Usually the number of extralifes is counted, the items are explicitly set or an st_dispenser for extralifes is located at strategic positions. The main purpose of this flag is the support of easy modes for very lethal levels, that should give the user the opportunity to exercise difficult patterns. Usage of this flag for the regular difficult mode is deprecated.

Type:   bool
Values:   true, false
Default:   false
Access:   read/write
Support:   object independent

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.20 IsDifficult

A global read only variable that defines the current difficulty mode selected by the user. All differences of easy and difficult mode within the level should be coded solely in dependence of this flag. If a level that supports an easy mode the author needs to declare it in the XML header in the element <modes>.

Type:   bool
Values:   true, false
Default:   true
Access:   read only
Support:   object independent

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.21 MagnetRange

A global default distance up to which magnets apply forces to actors. This global value is only used if no object specific value is set.

Type:   number
Values:   positive float number or zero
Default:   10.0
Access:   read/write
Support:   it_magnet

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.22 MagnetStrength

A global scalar default factor for magnet forces. Positive numbers are attracting forces where as negative numbers are repelling forces. This global value is only used if no object specific value is set.

Type:   number
Values:   float number
Default:   30.0

Positive number are attracting, negative numbers are repelling.

Access:   read/write
Support:   it_magnet

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.23 MaxOxydColor

A global variable that limits the number of colors assigned to autocolored st_oxyd. Be careful with increasing this value beyond its default.

Type:   number
Values:   OXYD_BLUE, ... OXYD_BROWN
Default:   OXYD_BLACK
Access:   read/write
Support:   st_oxyd

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.24 MeditationStrength

A global scalar default factor for it_meditation slope forces. Positive numbers are downhill forces that let actors roll into dents and hollows and roll down from hills and bumps.

Type:   number
Values:   float number
Default:   1.0
Access:   read/write
Support:   it_meditation

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.25 ProvideExtralifes

A global variable that causes two it_extralifes to be added to both player inventories on start of a new level. Set it to ‘false’ if a gamer could misuse these items. It is important to set this attribute before the world is created (see section World Creation).

Type:   bool
Values:   true, false
Default:   true
Access:   read/write
Support:   object independent

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.26 RubberViolationStrength

A global scalar default factor for the ot_rubberband force that is applied to actors if the length of the rubberband exceeds the given min or max limits. This can happen due to extraordinary events like actor warping, actor resurrection, moving anchor stones or simply new rubberbands that are created with off limit length.

Type:   number
Values:   positive float number
Default:   50.0
Access:   read/write
Support:   ot_rubberband

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.27 ShowMoves

A global variable that enables or disables the display of the stone push counter besides the level time. It is mainly used in Sokoban like levels.

Type:   bool
Values:   true, false
Default:   false
Access:   read/write
Support:   object independent

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.28 SlopeStrength

A global scalar default factor for fl_slope floor gradient forces. This global value is used if no slope object specific strength factor is supplied.

Type:   number
Values:   float number
Default:   25.0
Access:   read/write
Support:   fl_slope

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.29 SublevelNumber

A global variable that is only used by Multilevel Files. It contains the number of the sublevel that should be loaded and played. The number is preset to a value between 1 and the ‘quantity’ given in the Info metadata.

Type:   int
Values:   positive integer numbers
Default:   1
Access:   read only
Support:   no objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.30 SublevelTitle

A global variable that is only used by Multilevel Files. If the sublevel provides an individual title this variable must be set on level load to the title string. An empty string value causes an auto generated title based on the title given in the <identity> element and the SublevelNumber.

Type:   string
Values:   any string
Default:   ""
Access:   read/write
Support:   no objects

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.31 SubSoil

A global variable that defines the subsoil which replaces a floor on its physical destruction. it_bombs and ot_cannonballs can cause it_explosion_debris which in turn dissolves the floor to the base subsoil. The special value SUBSOIL_AUTO determines the subsoil based on the surrounding floors. Any fl_water on a direct neighboring floor causes the floor to be replaced by water, too. Otherwise the default fl_abyss will be used as replacement.

Type:   int
Values:   SUBSOIL_ABYSS, SUBSOIL_WATER, SUBSOIL_AUTO
Default:   SUBSOIL_ABYSS
Access:   read/write
Support:   it_explosion

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.32 SurviveFinish

A global variable that defines if the essential actors have to survive the finish of the game (see section Ending Conditions). With this attribute set to ‘false’ a gamer can sacrifice an essential actor to finish the level in the same step in some subtle cases.

Type:   bool
Values:   true, false
Default:   true
Access:   read/write
Support:   object independent

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.33 SwampSinkTime

A global default for the time it takes a static actor to sink in fl_swamp. Fast moving actors will need slightly more time than static actors.

Type:   number or nil
Values:   positive float number or zero or nil

Sink time in seconds or ‘nil’ for an infinite time aka not sinking. Time values smaller than approximately 0.7 ms will be rounded down to 0 ms.

Default:   1.75
Access:   read/write
Support:   fl_swamp

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.34 WaterSinkTime

A global default for the time it takes an actor to sink in fl_water.

Type:   number or nil
Values:   positive float number or zero or nil

Sink time in seconds or ‘nil’ for an infinite time aka not sinking. Time values smaller than approximately 0.7 ms will be rounded down to 0 ms.

Default:   0.0
Access:   read/write
Support:   fl_water

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.35 Width

A global read only variable reports the width of the world in grid units. This is set by the initial world constructor call (see section World Creation).

Type:   number
Values:   positive integer number
Default:   ?
Access:   read only
Support:   object independent

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.36 WormholeRange

A global default distance up to which wormholes apply forces to actors. This global value is only used if no object specific value is set.

Type:   number
Values:   positive float number or zero
Default:   10.0
Access:   read/write
Support:   it_wormhole

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.4.37 WormholeStrength

A global scalar default factor for wormhole forces. Positive numbers are attracting forces where as negative numbers are repelling forces. This global value is only used if no object specific value is set.

Type:   number
Values:   float number
Default:   30.0

Positive number are attracting, negative numbers are repelling.

Access:   read/write
Support:   it_wormhole

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7. Floor Objects


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.1 Floor Overview

images/fl_abyssfl_abyssimages/fl_adhesionlessfl_adhesionlessimages/fl_aquamarinefl_aquamarine
images/fl_bastfl_bastimages/fl_bluegrayfl_bluegrayimages/fl_bluegreenfl_bluegreen
images/fl_blueslabfl_blueslabimages/fl_brickfl_brickimages/fl_bridge_bw_closedfl_bridge_bw
images/fl_bridge_gc_closedfl_bridge_gcimages/fl_brightfl_brightimages/fl_concretefl_concrete
images/fl_darkgrayfl_darkgrayimages/fl_darkfl_darkimages/fl_dunesfl_dunes
images/fl_fake_triggerfl_fake_triggerimages/fl_gravelfl_gravelimages/fl_grayfl_gray
images/fl_hayfl_hayimages/fl_himalayafl_himalayaimages/fl_icefl_ice
images/fl_inverse_grayfl_inverse_grayimages/fl_inverse_whitefl_inverse_whiteimages/fl_ivoryfl_ivory
images/fl_lawnfl_lawnimages/fl_marblefl_marbleimages/fl_metalfl_metal
images/fl_mortarfl_mortarimages/fl_pinkbumpsfl_pinkbumpsimages/fl_plankfl_plank
images/fl_platinumfl_platinumimages/fl_redfl_redimages/fl_redslabfl_redslab
images/fl_rockfl_rockimages/fl_roughfl_roughimages/fl_saharafl_sahara
images/fl_sambafl_sambaimages/fl_sandfl_sandimages/fl_scales_brickfl_scales_brick
images/fl_scales_bridge_bw_closedfl_scales_bridgewoodimages/fl_scales_concretefl_scales_concreteimages/fl_scales_darkgrayfl_scales_darkgray
images/fl_scales_grayfl_scales_grayimages/fl_scales_platinumfl_scales_platinumimages/fl_slope_ffl_slope
images/fl_slopefl_slope_psimages/fl_slope_cfl_slope_iseimages/fl_slope_bfl_slope_ose
images/fl_spacefl_spaceimages/fl_stonefl_stoneimages/fl_swampfl_swamp
images/fl_thieffl_thiefimages/fl_thief_capturefl_thiefimages/fl_thief_drunkenfl_thief
images/fl_tigrisfl_tigrisimages/fl_waterfl_waterimages/fl_whitefl_white
images/fl_woodfl_woodimages/fl_wovenfl_wovenimages/fl_yinyang_yangfl_yinyang_yang
images/fl_yinyang_yinfl_yinyang_yin

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2 Floor Attributes


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.1 adhesion (floor)

The responsiveness of an actor on mouse movements. It is a scalar factor to the accelerating force applied to an actor. Note that the actor itself has another scalar adhesion (actor) factor. The adhesion may take negative values, too. Such values correspond to inverse forces.

Type:   number
Values:   float number
Default:   0.0
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.2 burnable

Determines whether the floor starts to burn when a fire is nearby and the item on the floor allows it to burn. See section Fire Spreading for details.

Type:   boolean
Values:   false, true
Default:   false
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.3 eternal

Determines whether a fire keeps on burning unlimited, until an external cause stops it. See section Fire Spreading for details.

Type:   boolean
Values:   false, true
Default:   false
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.4 faces (floor)

Describes if a floor is framed.

Currently a floor is either not framed at all or framed on all sides. The frame attribute can not be directly accessed and modified on most floors. Append the string "_framed" to a given floor name to yield the all side framed variant.

Type:   string or nil
Values:   nil, "nesw"
Default:   nil
Access:   none - with a few exceptions

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.5 fastfire

When set to true, fire will spread faster than usual, with the same speed as if it_burnable[_oil] would be on it. See section Flood Spreading for details.

Type:   boolean
Values:   false, true
Default:   false
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.6 floodable

Determines whether a floor can be flooded by fl_water from a neighboring floor. See section Flood Spreading for details.

Type:   boolean
Values:   false, true
Default:   false
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.7 force_x

A constant floor specific force into east direction applied additionally to all other forces to all actors on the floor.

Type:   number
Values:   float number
Default:   0.0
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.8 force_y

A constant floor specific force into south direction applied additionally to all other forces to all actors on the floor.

Type:   number
Values:   float number
Default:   0.0
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.9 fragility (floor)

A value denoting the probability that a brittle floor plate, that is a floor with an it_crack on top, continues to disintegrate on events like an actor entering, passing a neighboring it_crack, nearby explosions or fire heating. A value of 1.0 ensures that the crack continues to disintegrate on these events, a value of 0.0 prohibits visible cracks to get larger. This value is defaulted by Fragility and superseded by it_crack specific values.

Type:   number
Values:   float numbers between 0.0 and 1.0
Default:   1.0
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.10 freeze_check (floor)

If true, allows for Freeze Checking on this floor tile. Note that Freeze Checking only works with those (movable) stones that have additionally set freeze_check = true on their own, see freeze_check (stone).

Type:   boolean
Values:   false, true
Default:   false
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.11 friction (floor)

see section friction.

Type:   number
Values:   float number
Default:   0.0
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.12 ignitable

When true, and the item on the floor allows it to burn, it will start burning on nearby explosions. See section Fire Spreading for details.

Type:   boolean
Values:   false, true
Default:   false
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.13 indestructible

Most floors can dissolve to fl_abyss or fl_water on destruction by explosions, fire, ot_cannonball hits, it_crack, it_trap, etc. But a few like fl_abyss, fl_water and fl_swamp are indestructible by all these events.

Type:   boolean
Values:   false, true
Default:   false
Access:   none

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.14 initfire

When set to true, the floor will start to burn after level initialization. See section Fire Spreading for details.

Type:   boolean
Values:   false, true
Default:   false
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.15 noash

Determines whether it_burnable_ash is put onto the floor after a fire stops. Ash stops a floor from re-igniting, so noash = true will allow a floor to start burning a second time, unless the item on it or its burnable-attribute deny fire. See section Fire Spreading for details.

fl_abyss is the only floor with noash = true by default, because ash falls into the abyss. Note however, that fl_abyss isn’t burnable by default.

Type:   boolean
Values:   false, true
Default:   false
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.16 push_directions

A hint for the resolver res.puzzle for shuffling of a puzzle. A floor marked with this attribute guarantees that an adjacent puzzle row or column can be push rotated into the listed directions by the user.

Type:   string or nil
Values:   nil, "nesw"
Default:   nil
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.17 secure

Ensures that a nearby fire eventually ignites this floor. See section Fire Spreading for details.

Type:   boolean
Values:   false, true
Default:   false
Access:   read/write

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.2.18 texture

Many floors do provide several textures - graphical image variations that are without any influence on the physical engine. Most floors do select a random texture in their default variant. This generates areas without unintended optical effects. But on some floors you can select the textures by means of variant kind names to create your own graphical floor layout.

Type:   integer number or string
Values:   integer number or string
Default:   1
Access:   currently none - just by variants kind string on some floors

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3 Standard Floors


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.1 fl_adhesionless

Orange velvet that offers no adhesion but causes friction.

Attributes:
adhesion,   values: number;   default: 0.0   See section adhesion
friction,   values: number;   default: 2.5   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_adhesionless

fl_adhesionless


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.2 fl_aquamarine

Aquamarine floor with low friction.

Attributes:
adhesion,   values: number;   default: 1.0   See section adhesion
friction,   values: number;   default: 0.4   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_aquamarine

fl_aquamarine

images/fl_aquamarine_framed

fl_aquamarine_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.3 fl_bast

Brown woven bast with black background.

When burnt, fl_bast will become fl_abyss.

Attributes:
adhesion,   values: number;   default: 2.5   See section adhesion
friction,   values: number;   default: 1.5   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_bast

fl_bast

images/fl_bast_framed

fl_bast_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.4 fl_bluegray

Light blue gray floor, visually indistinguishable from fl_thief.

Attributes:
adhesion,   values: number;   default: 1.5   See section adhesion
friction,   values: number;   default: 4.5   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_bluegray

fl_bluegray

images/fl_bluegray_framed

fl_bluegray_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.5 fl_bluegreen

Dark blue green floor.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 6.0   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_bluegreen

fl_bluegreen

images/fl_bluegreen_framed

fl_bluegreen_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.6 fl_blueslab

Dark blue slab like floor, that is marbled with black. A matching floor is fl_redslab.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 7.0   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_blueslab

fl_blueslab

images/fl_blueslab_framed

fl_blueslab_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.7 fl_brick

Floor composed of small red bricks.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 3.5   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_brick

fl_brick

images/fl_brick_framed

fl_brick_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.8 fl_bright

Nearly white floor without any yinyang related features. A matching floor is fl_dark.

Attributes:
adhesion,   values: number;   default: 1.5   See section adhesion
friction,   values: number;   default: 3.0   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_bright

fl_bright

images/fl_bright_framed

fl_bright_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.9 fl_concrete

Granular gray concrete.

Attributes:
adhesion,   values: number;   default: 1.3   See section adhesion
friction,   values: number;   default: 4.5   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_concrete

fl_concrete

images/fl_concrete_framed

fl_concrete_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.10 fl_dark

Nearly black floor without any yinyang related features. A matching floor is fl_bright.

Attributes:
adhesion,   values: number;   default: 1.5   See section adhesion
friction,   values: number;   default: 3.0   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_dark

fl_dark

images/fl_dark_framed

fl_dark_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.11 fl_darkgray

A medium to dark gray floor.

Attributes:
adhesion,   values: number;   default: 1.6   See section adhesion
friction,   values: number;   default: 3.0   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_darkgray

fl_darkgray

images/fl_darkgray_framed

fl_darkgray_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.12 fl_dunes

Brown granular dunes.

Attributes:
adhesion,   values: number;   default: 1.0   See section adhesion
friction,   values: number;   default: 1.3   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_dunes

fl_dunes

images/fl_dunes_framed

fl_dunes_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.13 fl_gravel

Dark gray, granular floor.

Attributes:
adhesion,   values: number;   default: 1.5   See section adhesion
friction,   values: number;   default: 3.2   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_gravel

fl_gravel

images/fl_gravel_framed

fl_gravel_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.14 fl_gray

Gray with some brown spots.

Attributes:
adhesion,   values: number;   default: 3.0   See section adhesion
friction,   values: number;   default: 5.0   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_gray

fl_gray

images/fl_gray_framed

fl_gray_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.15 fl_himalaya

Blue purple marbled floor.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 5.0   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_himalaya

fl_himalaya

images/fl_himalaya_framed

fl_himalaya_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.16 fl_ivory

Warm ivory or pearl white floor.

Attributes:
adhesion,   values: number;   default: 1.6   See section adhesion
friction,   values: number;   default: 2.2   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_ivory

fl_ivory

images/fl_ivory_framed

fl_ivory_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.17 fl_lawn

Grass floor. Light and dark grass and different textures of both exist.

When burnt, fl_lawn becomes fl_dunes.

Attributes:
adhesion,   values: number;   default: 1.5   See section adhesion
friction,   values: number;   default: 4.0   See section friction
texture,   values: "a","b","c1","c2","c3","c4","d1","d2","d3","d4","e1","e2","e3","e4" ;   default: "a";   access: none   See section texture
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_lawnfl_lawn_a: texture = ‘"a"images/fl_lawn_cfl_lawn_b: texture = ‘"b"
images/fl_lawn_efl_lawn_c1: texture = ‘"c1"images/fl_lawn_ffl_lawn_c3: texture = ‘"c3"
images/fl_lawn_e2fl_lawn_c2: texture = ‘"c2"images/fl_lawn_f2fl_lawn_c4: texture = ‘"c4"
images/fl_lawn_gfl_lawn_d1: texture = ‘"d1"images/fl_lawn_hfl_lawn_d3: texture = ‘"d3"
images/fl_lawn_g2fl_lawn_d2: texture = ‘"d2"images/fl_lawn_h2fl_lawn_d4: texture = ‘"d4"
images/fl_lawn_ifl_lawn_e1: texture = ‘"e1"images/fl_lawn_jfl_lawn_e3: texture = ‘"e3"
images/fl_lawn_i2fl_lawn_e2: texture = ‘"e2"images/fl_lawn_j2fl_lawn_e4: texture = ‘"e4"
images/fl_lawnfl_lawn: texture = ‘"a"

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.18 fl_marble

Light red brown marbled floor.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 6.4   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_marble

fl_marble

images/fl_marble_framed

fl_marble_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.19 fl_metal

Grey metal floor with rivets. Several different textures exists with horizontal or vertical aligned joists and various rivets.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 3.0   See section friction
texture,   values: integer number, 1 <= n <= 7 ;   default: random;   access: none   See section texture
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_metalfl_metal: texture = ‘randomimages/fl_metal_framedfl_metal_framed: texture = ‘random
images/fl_metalfl_metal_1: texture = 1images/fl_metal_framedfl_metal_1_framed: texture = 1
images/fl_metal_2fl_metal_2: texture = 2images/constructionfl_metal_2_framed: texture = 2
images/fl_metal_3fl_metal_3: texture = 3images/constructionfl_metal_3_framed: texture = 3
images/fl_metal_4fl_metal_4: texture = 4images/constructionfl_metal_4_framed: texture = 4
images/fl_metal_5fl_metal_5: texture = 5images/constructionfl_metal_5_framed: texture = 5
images/fl_metal_6fl_metal_6: texture = 6images/constructionfl_metal_6_framed: texture = 6
images/fl_metal_7fl_metal_7: texture = 7images/constructionfl_metal_7_framed: texture = 7

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.20 fl_mortar

Very rough brilliant white floor.

Attributes:
adhesion,   values: number;   default: 1.8   See section adhesion
friction,   values: number;   default: 7.2   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_mortar

fl_mortar

images/fl_mortar_framed

fl_mortar_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.21 fl_pinkbumps

Pink floor with white bumps.

Attributes:
adhesion,   values: number;   default: 1.2   See section adhesion
friction,   values: number;   default: 5.0   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_pinkbumps

fl_pinkbumps

images/fl_pinkbumps_framed

fl_pinkbumps_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.22 fl_plank

Floor build up by small diagonal orange brown planks.

When burnt, fl_plank becomes fl_abyss.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 5.5   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_plank

fl_plank

images/fl_plank_framed

fl_plank_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.23 fl_platinum

Warm light gray floor.

Attributes:
adhesion,   values: number;   default: 1.6   See section adhesion
friction,   values: number;   default: 3.0   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_platinum

fl_platinum

images/fl_platinum_framed

fl_platinum_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.24 fl_red

Warm red, very slippy floor.

Attributes:
adhesion,   values: number;   default: 1.0   See section adhesion
friction,   values: number;   default: 0.9   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_red

fl_red

images/fl_red_framed

fl_red_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.25 fl_redslab

Dark red slab floor, that is marbled with black. A matching floor is fl_blueslab.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 7.0   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_redslab

fl_redslab

images/fl_redslab_framed

fl_redslab_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.26 fl_rough

Rough warm white floor.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 7.0   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_rough

fl_rough

images/fl_rough_framed

fl_rough_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.27 fl_sahara

Bright yellow sandstone floor.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 6.4   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_sahara

fl_sahara

images/fl_sahara_framed

fl_sahara_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.28 fl_samba

Gray bleached wooden floor. Textures with horizontal and vertical aligned plank exist.

When burnt, fl_samba becomes fl_abyss.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 6.0   See section friction
texture,   values: "h", "v" ;   default: random;   access: none   See section texture
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_samba

fl_samba: texture = ‘random

images/fl_samba

fl_samba_h: texture = "h"

images/fl_samba_2

fl_samba_v: texture = "v"

images/fl_samba_framed

fl_samba_framed: texture = ‘random

images/fl_samba_framed

fl_samba_h_framed: texture = "h"

images/construction

fl_samba_v_framed: texture = "v"


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.29 fl_sand

Granular orange gray sand floor.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 6.0   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_sand

fl_sand

images/fl_sand_framed

fl_sand_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.30 fl_space

Black space with a few sparkling stars. This floor has neither a friction nor does it provide an adhesion. All actors do drift for infinity on this floor until they reach another floor.

Attributes:
adhesion,   values: number;   default: 0.0   See section adhesion
friction,   values: number;   default: 0.0   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_space

fl_space


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.31 fl_stone

Granular brown gray floor.

Attributes:
adhesion,   values: number;   default: 1.0   See section adhesion
friction,   values: number;   default: 1.4   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_stone

fl_stone

images/fl_stone_framed

fl_stone_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.32 fl_tigris

Red sandstone floor.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 6.0   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_tigris

fl_tigris

images/fl_tigris_framed

fl_tigris_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.33 fl_white

Pure white floor without any yinyang related features.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 4.0   See section friction
burnable,   values: boolean;   default: false   See section burnable
Variants:
images/fl_white

fl_white

images/fl_white_framed

fl_white_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.3.34 fl_woven

Diagonal woven white floor with black shadows.

Attributes:
adhesion,   values: number;   default: 3.0   See section adhesion
friction,   values: number;   default: 6.5   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_woven

fl_woven

images/fl_woven_framed

fl_woven_framed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.4 Special Floors


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.4.1 fl_abyss

Pure black abyss into which all rolling balls do fall and shatter. An abyss chasm can be crossed by jumping. Actors protected by an activated it_umbrella can pass and even move on an abyss as it provides useful ‘adhesion’ and ‘friction’. Another comparable floor barrier is fl_water.

Many floors disintegrate on fire or on a dissolving it_crack into an abyss. The abyss itself is indestructible. It does neither burn nor crack.

Some stones fall into abyss, too. st_box will build floors on which all actors can move and pass the former abyss filled grid.

By default, fl_abyss can’t burn. However, when it burns (e.g. by it_burnable_oil), it does not create it_burnable_ash in the end by default (i.e., noash is true by default). In particular, when burnable is set to true, an abyss may reignite arbitrarily often.

Attributes:
adhesion,   values: number;   default: 1.0   See section adhesion
friction,   values: number;   default: 2.0   See section friction
indestructible,   values: bool ;   default: true;   access: none   See section indestructible
burnable,   values: boolean;   default: false   See section burnable
noash,   values: boolean;   default: true   See section noash
Variants:
images/fl_abyss

fl_abyss


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.4.2 fl_bridge

A bridge is a variable floor that can be passed when being CLOSED but being impassable for marbles like fl_abyss when being opened. Floor bridges come in several flavors. They can be grey or wooden brown, they can pop up in the center or emerge from one side. The flavor string is a combination of a color character and an emerging direction character. Note that not all combinations do exist.

Bridges can be opened and closed via messages and state set operations like st_door.

But a unique feature of bridges is to close on a solid stone being pushed onto them. Thus stones do not fall into the visible abyss of an opened bridge, but can be pushed over them. But as soon as the stone leaves the bridge it opens again. Thus a user will notice the closing and opening bridge on stone pushes.

To be more precise after a stone leaving a bridge, it switches into the state that it would have had if the stone would never have been on top of it. That means that if a stone gets pushed onto an open bridge and the bridge receives a "close" or "toggle" message while the stone is being on top the bridge remains closed when the stone is pushed away.

A bridge being set adjusts its state according to the Snapshot Principle. It checks for solid stones being on top of it and auto closes accordingly.

Only the brown (wooden) bridges are burnable by default. Open bridges are never burnable, regardless of the burnable-attribute.

Attributes:
state,   values: CLOSED, OPEN;   default: OPEN   See section state

The state as visible and responsible for the actor ability to pass.

flavor,   values: "gc", "bw", "bs", "be", "bn";   default: "gc"

The type of the bridge.

adhesion,   values: number;   default: 1.0   See section adhesion
friction,   values: number;   default: 5.0   See section friction
burnable,   values: boolean;   default: depends on flavor   See section burnable

True if flavor is "b*", false otherwise.

Messages:
open   See section open

Opens a closed bridge that is not covered by a solid stone. Otherwise the explicit open request will be registered and executed as soon as the stone is pushed away.

close   See section close

Closes an open bridge. If the bridge is already closed by a covering solid stone, the request will be registered and the bridge will not open when the stone is pushed away.

signal   See section signal

A signal of value 1 sends an open message, a signal of value 0 sends a close message.

toggle   See section toggle

A toggle causes a change of the state if no stone is on top, or the registered explicit state if a solid stone currently covers the bridge.

Variants:
images/fl_bridge_gc_closed

fl_bridge flavor = "gc"

images/fl_bridge_gc_closed

fl_bridge_gc flavor = "gc"

images/fl_bridge_bw_closed

fl_bridge_bw flavor = "bw"

images/fl_bridge_bw_closed

fl_bridge_bs flavor = "bs"

images/fl_bridge_bw_closed

fl_bridge_be flavor = "be"

images/fl_bridge_bw_closed

fl_bridge_bn flavor = "bn"


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.4.3 fl_fake

A floor that mimics other floors, but does not act like them.

In contrast to fl_abyss the fake abyss is just a standard floor that lets pass any actor without any harm.

A fake trigger looks similar to an it_trigger but is just a standard floor without any special features.

Attributes:
adhesion,   values: number;   default: 2.0   See section adhesion
friction,   values: number;   default: 3.0   See section friction
flavor,   values: "abyss", "trigger";   default: "abyss";   access: none
burnable,   values: boolean;   default: depends on flavor   See section burnable

True if flavor is "trigger", false otherwise.

Variants:
images/fl_abyss

fl_fake_abyss: flavor = "abyss"

images/fl_fake_trigger

fl_fake_trigger: flavor = "trigger"


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.4.4 fl_hay

A hay floor is in most aspects a standard floor, that exists in a framed variation, too.

The framed variant is the successor of a fallen st_box_hay. It may be flooded by a fl_water stream without the attribute ‘floodable’ being set to ‘true’ as the flood passes below this floor. The framed wood acts like a bridge over a flood stream and the water will continue to spread to neighboring grids, while the framed wood is still passable for actors. In fact a framed wood can even operate as a flood source, but we do not recommend this usage. see section Flood Spreading.

Attributes:
state,   values: IDLE, FLOODING;   default: IDLE   See section state
interval   values: positive number;   default: 0.2

Number of seconds .

adhesion,   values: number;   default: 1.5   See section adhesion
friction,   values: number;   default: 5.0   See section friction
burnable,   values: boolean;   default: true   See section burnable
Variants:
images/fl_hay

fl_hay

images/fl_hay_framed

fl_hay_framed: faces = "nesw"


[ < ] [ > ]   [ << ] [ Up ] [ >> ]