Table of Contents

0. General information

This documentation is probably outdated! Please help us to translate actual documentation from Russian language. Spellchecking and other text improvement are welcomed.

http://instead.syscall.ru/wiki/ru/gamedev/documentation

Game code for INSTEAD is written in lua (5.1 or 5.2), therefore knowing this language is useful though not necessary. This knowledge can help to understand the internal work of the engine core if you have such need.

During its evolution INSTEAD abilities have widened and allow to implement games of various genres including arcades and text-parsing ones. It's also possible to launch games written for some other engines. But the original core focused on text-graphic adventure games stays the INSTEAD basis. This is what the present documentation describes. Learning it is necessary even you are going to write a game beyond the basic genre. Let you start learning INSTEAD with writing a simple game!

Origin

Most of people assotiate words “text adventure” with one of two familiar sights. First one contains some text and action choice, eg.:

You see a table in front of you. There is an apple on it. What do you do?

1) Take the apple
2) Step away from the table

More rarely the classical games are meant where the player types his actions literally:

You are in the kithen. There is a table near a window.
> look at the table
There is an apple on the table.

Both practices has some advantages as well as drawbacks.

The first approach resembles the gamebook genre and doesn't suit writing classical quests where a hero explores a virtual world free to move across it and interact with in-world objects.

The second approach gives us a more realistic model but requires more efforts from the writer. It also requires more experienced player. Especially in case of using human language with complex grammar.

INSTEAD project had been created for games of other kind which combines advantages of both approaches, the same time trying to avoid their drawbacks.

In INSTEAD game world is modelled like in the second approach, i.e. there are places (scenes) which the protagonist can visit and objects (including alive characters) he interacts with. The player explores the world and manipulates objects freely. Besides actions on objects are not written directly as menu items but its implementation reminds classical graphic quests of the nineties.

Actually INSTEAD has features that allow to extend devloping beyond common text quests. Games for INSTEAD attracts not only text games fans but also those who this genre is new to.

Before reading this manual it's recommended to play some classical INSTEAD games to clarify what is it talked about. On the other side if you are here probably you've already done it.

There is no point to examine the code of those games now, though. Old games often are written in non-optimal way, using deprecated constructions. The modern INSTEAD version allows to write code more lcaonically, simply, clearly.

How does a classical INSTEAD game looks

The main game window contains information about static and dynamic parts of the scene, active events and the scene picture with possible passages to other scenes (in the graphic interpreter).

Static part of the scene is shown only once when the player enters the scene. It's also shown on look command (click on the scene name in the graphic interpreter).

The dynamic part of a scene is composed of the scene objects descriptions. It is always shown.

Inventory contains objects the player can access in every scene. Interaction is possible between inventory objects and the player or other inventory or scene objects.

One should note that the “inventory” is defined rather vaguely. For example it may contain such objects as “open”, “examine”, “use”, etc.

Possible actions of the player are:

How to write a game

Each game is a directory with a main.lua script. Other game resources (lua scripts, images, music) should be placed in this directory. All references to the resources are given relative to this game directory.

At the beginning of main.lua file a header may be defined consisting of tags. Each tag should start with symbols (comments in Lua). Currently 3 tags exist:

$Name: tag contains the game title in UTF-8 encoding. For example:

-- $Name: The most interesting game!$

Then it's recommended to specify the game version:

-- $Version: 0.5$

And the authorship:

-- $Author: Anonimous fan of text adventures$

If you develop your games in Windows make sure your editor supports UTF-8 encoding without BOM! And use it for writing the code.

Since version 1.2.0 you should define required STEAD API version after the header. It is “1.9.1” currently.

instead_version "1.9.1"

Without this line STEAD API works in compatible(legacy) mode.

INSTEAD has backward compatibility but explicit specifying some API version makes possible to use capabilities of that version such as snapshots, autosaved global variables, changing functions on the fly, autoformatting, modules and so on. There is no point to write new games in the old API though such practice is in evidence in consequence of studying INSTEAD by the example of old games

For that reason do NOT attack game code before this manual read!

After that modules are included usually (modules are explained hereinafter):

require "para" -- cute indents;
require "dash" -- replacing double minus sign with a dash;
require "quotes" -- replacing "" quotation marks with «»;

It's also usually a good idea to define the default handlers game.act, game.use, game.inv which are explained hereinafter too:

game.act = 'Does not work.';
game.use = 'It will not help.';
game.inv = 'Do I really need this?';

Game initialization should be defined at the init function which is called by the engine at the very beginning. It's a good place to set the player initial state or similar things. However you may not need the init function.

function init()
    me()._know_truth = false; -- set player's variable
    take(knife); -- add a knife and paper to player's inventory
    take(paper);
end

The games directory is where the graphic interpreter looks for available games. The Unix version also checks ~/.instead/games. The windows version (>=0.8.7) checks Documents and Settings/USER/Local Settings/Application Data/instead/games. From STEAD 1.2.0 Windows and Unix standalone builds looks at ./appdata/games, if it exists.

In some INSTEAD builds (on Windows or on Linux if the project is built with gtk, and things) it is possible to open a game from any location through the menu item “Choose game” or hitting F4. If there is only one game in the game directory it will be launched by interpreter automatically which is handy if you are going to distribute your game with the engine included. So you just put your game in the directory and run INSTEAD.

During writting the code it's strongly recommended to use indents to mark nesting levels like in code samples given in this manual. It really helps to clarify the code end to decrease mistakes number.

So here is a basic template of your first game:

-- $Name: My first game$
-- $Version: 0.1$
-- $Author: Homo codius$
 
instead_version "1.9.1"
 
require "para" -- for decoration
require "dash"
require "quotes"
 
require "dbg" -- for debugging
 
game.act = 'Um...';
game.use = 'It does not works!';
game.inv = 'Why?';
 
function init() 
-- some initialization code if needed
end

Debugging basics

During debugging (checking your game operates properly) it's handy to run INSTEAD with command-line parameter -debug which cause error messages more informative – call stack will be displayed. Note the parameter can be specified in shortcut properies.

Also it's often neede to load/save the game state. You can utilize the standard mechanism with menu items and hotkeys F2/F3 or you can use quicksaves/-loads (F8/F9).

Debug mode allows you to restart a game hitting Alt-R. Combined with F8/F9 it makes possible to view quickly changes after editing the code.

In debug mode windows version of INSTEAD creates a console window where errors will be output to. What is used on Unix is the console INSTEAD is launched from. Also you can output your own debug messages with the function print(), eg.:

        act = function(s)
                print ("Act is here! "..stead.deref(s));
                ...
        end;

Don't fear this sample, after manual has been read and with your own game on the anvil you will look at this code with greater inspiration most likely.

Also you can engage the debugger module for which purpose add the following line somewhere after specifying instead_version:

require "dbg"

The debugger is available on F7 key.

During debugging it's often useful to examine saved game files which contain state of game variables. To not to search for the files each time it's needed let you create saves subdirectory in the game directory (where main.lua is) and saves will be written there. You can also copy those files to another computer.

Probably (especially if using Unix) you'd like the idea to check your Lua scripts syntax through parsing it with luac compiler (with option -p on command line). On Windows it's possible after installing Lua binaries from http://luabinaries.sourceforge.net/ , then you use luac52.exe.

You can make a syntax check with INSTEAD (at least version 1.9.2 (sic! really?)) just adding -luac parameter:

sdl-instead -debug -luac <path/to/lua/script.lua>

1. Scene

Сцена (or room) is the basic game unit whithin which player can examine each scene objects and interact with them. A room the player is in or a forest region available for observation are examples of a scene.

Any game must contain the scene named main. This is where the game begins from and where the player appears first.

main = room {
	nam = "The main room";
	dsc = [[You are in a large room.]];
}

This creates an object (by the way most of things in INSTEAD are objects) named main which has type room. Each game object has attributes and event handlers. In given example there are two attributes: nam and dsc. Attributes are delimited with, um, a delimeter, here it is a semicolon (see Lua syntax). Attributes usually are a text string, handler-function or a boolean value.

For instanse the obligatory attribute nam defines text displayed in scene title when it's shown. The scene name is also used for identifying it while esteblishing links between scenes. By the way nam attribute is obligatory for any game object. You often can refer an object bu it's name in place of corresponding variable name.

Attribute dsc contains desctription of scene static part which is printed automatically on first enter only. To see this text again you must to look at the scene explicitly.

You can use , in place of ; as attribute delimiter. Eg.:

main = room {
        nam = 'The main room',
        dsc = 'You are in the room your childhood passed in.',
}

INSTEAD hides static description after it's shown on entry. If your intention claims dsc to be shown constantly then define parameter forcedsc in the top of code or among the concrete scene attributes:

game.forcedsc = true;
main = room {
        forcedsc = true;
        nam = 'The main room';
        dsc = [[Guess what.]];
}

But it's not recommended to use this technique because the engine is optimized for classical games which do not use it.

All attributes are strings here. A string can be enclosed with single or double quotation marks:

main = room {
        nam = 'The very same room';
        dsc = "Got it in one!";
}

For long text pieces it's better to use the following notation:

dsc = [[ Very loooong descrition... ]];

Line breaks are ignored here, use the ^ symbol if you need one on the screen:

dsc = [[ First paragraph. ^^
Second one.^^
 
The third.^
New line here:)]];

Actually object name can be separated from how the object is displayed (for scenes it means what will be in the title). If defined, disp attribute will replace nam one when it's about representing the object on the screen.:

main = room {
        nam = 'Start';
        disp = 'My room'; -- what will be in the scene title
        dsc = [[There is an I in my room.]];
}

2. Objects

Objects are the scene units the player interacts with.

tabl = obj {
	nam = 'table',
	dsc = 'There is a {table} in the room.',
	act = 'Hm... Just a table...',
};
tabl = obj {
        nam = 'стол';
        disp = 'угол стола';
        dsc = 'В комнате стоит {стол}.';
        tak = 'Я взялся за угол стола';
        inv = 'Я держусь за угол стола.';
};

Objects are represented in the inventory panel as text defined in the obligatory nam attribute until the optional disp one defined which overrides it. nam is also used to address the object in a text interpreter.

Objects with disp set to false are not displayed in the inventory.

The dsc attribute contains an object description. It is shown in the dynamic part of the scene. Text enslosed with curly brackets will be displayed as a link anchor (object 'handle') in the graphic interpreter.

act is a handler called on object activation which is usually done by clicking the object link anchor. act must be a string (displayed on the screen), a boolean value (see section 5) or a function returning one of these.

WARNING: in the Lua namespace some objects (aka tables) already exist, for example table, io, string and so on. Be careful choosing names for your own objects. That's why tabl used instead of table in examples above. In modern INSTEAD versions this problem is almost solved. Anyway you cannot use identifiers matching INSTEAD constructors names such as obj, game, player, list, room, dlg.

3. Adding objects to the scene

A reference to an object is a text string containing the object identifier. For example 'tabl' is a reference to and object created with tabl = obj {…}.

Add references to the obj array to place corresponding objects at the scene:

main = room {
	nam = 'main room',
	dsc = 'You are in a large room.',
	obj = { 'tabl' },
};

Table object referenced in this sample will be shown in the dynamic part.

You can use unquoted identidiers directly instead of references. But take in account you must define corresponding object before the room definition in this case. References have no such limitation. That's why using it is highly recommended.

Separate multiple objects in the array with comma (standard Lua delimiter):

	obj = { 'tabl', 'apple' };

Feel free to use line breaks to make your code more readable.

	obj = { 
                 'tabl', 
                 'apple',
                 'knife', 
        };

You can also use special functions described hereinafter to place objects in scenes.

4. Objects referencing objects

Objects may have obj attribute too. In this case the top level thing prints its description then enumerates its child objects. In turn, when involved, each child shows its description and checks its own children for the samelist will expand sequentially. For example let's place an apple on the table.

main = room {
	nam = 'The Room',
	dsc = 'You step to a semispherical hall.',
	obj = { 'window', 'stone' }
};
window = obj {
	nam = 'window',
	dsc = 'Diffuse daylight come in through a round open in the middle of the ceil.'
};
stone = obj {
	nam = 'stone altar',
	dsc = 'You see a stone altar in the light cone.'
};
bowl = obj {
	nam = 'bowl',
	dsc = 'On the rough surface there is a metal bowl covered with a weird engraving.'
};

Therefore in the scene text we will see the room description, followed by window, stone (room children) and bowl (stone child) description respectively.

Also if you, say, move some object to other room you'll get the nested objects moved with that object. And it makes sense because a reference is what actually moves while obj array with all its elements stays bound to its very own object.

5. Attributes and handlers as functions

Most of attributes and handlers may be functions. For example:

nam = function()
	return 'apple';
end,

which is equivalent to nam = 'apple';

The function must return string. You can use few helpers:

If you pass the only parameter to these functions the parentheses can be omitted. Otherwise .. or , can be used for string concatenation. Lua syntax plus engine internals.

pn "No parentheses";
pn ("No idea".." why splitted");
pn ("There are ", bullet_count, " bullets in the cylinder");

Strings passed to these functions are accumulated in an internal buffer which content is passed back to the engine when handler returns. It means you can build a whole text with sequential calls to p/pn/pr. Note the engine perform general formatting, spaces and line breaks separate corresponding text parts. Pi-functions should be invoked in context of formatting a single attribute value. The currently buffered text can be got with the pget() call. pclr() will clear the buffer.

The principal difference between handlers and attributes is handlers may change the game world state while attributes may not. So if you define an attribute as function remember the attribute purpose is returning value rarher then changing the game state! Moments when INSTEAD invokes attributes are usually not predictable and not bound to any game process.

Another feature of handlers is the fact you must not to wait for any event inside the handler. I.e. no delay loops and other delay mechanisms. Handler purpose is to change the game state and to return control to INSTEAD immediately after that. Interpreter displays changes and goes waiting for user actions. If you need some delay of output you should use the timer or cutscene module.

Handler functions almost always contain conditional statements and dealing with variables, as example:

apple = obj {
	nam = 'red apple',
	dsc = function(s)
		if not s._seen then
			p 'There is {something} on the table.';
		else
			p 'There is an {apple} on the table.';
		end
	end,
	act = function(s)
		if s._seen then
			p 'The same apple, huh...';
		else
			s._seen = true;
			p 'Wow! It\'s an apple!';
		end
	end,
};

The object attribute- and handler function first argument is always the object itself. In the example scene the dynamic part will contain: 'There is something on the table.' When you try to use this “something”, '_seen' variable of the object apple will be set to true and we will see it was an apple. s._seen means that the _seen variable is placed in the s object (which refers to aple). Underscore as first character of a variable name means the variable is saved in a savegame file automatically.

Syntax of the if operator is pretty clear:

if <expression> then <action> end

if have (apple) then
    p 'I have an apple!'
end

if <expression> then <action> else <action if the condition isn't met> end

if have (apple) then
    p 'I have an apple!'
else
    p 'I have no apple!'
end

if <expression1> then <action exp1 ok> elseif <expression2> then <action exp1 fails exp2 ok> else <action exp1 & exp2 fail> end и т.д.

if have (apple) then
    p 'I have an apple!'
elseif have (fork)
    p 'I have not apple but I have a fork instead!'
else
    p 'Damn, no apple, no fork!'
end

An expression in the if operator can contain logical operators “and”, “or”, “not” and also parentheses (, ) for prioritizing. if <variable> then means condition is met if the variable is defined and doesn't equals to false. A == B is met when A equals to B and A ~= B if does not. Refer the Lua documentation to know more about expressions syntax.

if not have (apple) and not have(fork) then
    p 'I have neither apple nor fork!'
end
 
...
if w ~= apple then
   p 'Um, it\'s not an apple.';
end
...
 
if time() == 10 then
   p 'Round 10 has come!'
end

In the situation when a variable has not been defined but is used in a conditional expression, assumption is that the variable equals emptiness (nil). So you can check if a variable exists with a code like this:

if z == nil then
        p "Global variable z does not exist."
end

However the nil variable is treated as false in conditional expressions:

if z == false then
       p "Variable z equals to false."
end -- actually z can be undefined and you will catch a bug probably
if not z then
   if not (z == nil)
       p "Variable z equals to false."
   else
       p "z is undefined! sell you property and go to hunt bugs!"
   end
end

Take that into account while writing and debugging your game because if you have got a misprint in a variable name, the expression will be evaluated (with no error reported) but the game logic will be incorrect.

Saved game files contain delta between initial and current game state, and there are three ways to make variables being stored in those files. First one is prepending variable name with an underscore character. Second and third ones are defining variables in a per object table var or in a top level table global:

global { -- let's define some global variables
    global_var = 1; -- number 
    some_number = 1.2; -- another one
    some_string = 'hllwrld'; -- string
    know_truth = false; -- boolean value
}
main = room {
    var { -- defining room variables
        i = "a";
        z = "b";
    };
    nam = 'My first room.';
    var {
        new_var = 3;
    };
    dsc = function(s)
        p ("i == ", s.i);
        -- "var" object subtable is mapped to the object namespace
        p ("new_var == ", s.new_var);
        -- "global" table elements are mapped to the global namespace
        p ("global_var == ", global_var);
        -- as if "global" and "var" tables are excluded from the objects tree
        -- and table's children become the property of their parents
    end;

You must initialize every element of var and global tables. A system dummy object null is all yours if it is needed to initialize an 'empty' variable which will store some object in the future.

So, variables are saved if both conditions are met: a) variable is defined in the global namespace or in the one of a room, an object, the game, or a player; b) variable has its name prepended with undescore character or is defined in global global table or per object var subtable. Using var and global is more intuitive so it's recommended.

Saved game files can store variable of the following types:

The code statement is another way for defining functions:

	dsc = code [[
		if not self._seen then
			p '{Something} lays on the tabe.';
		else
			p 'There is an {apple} on the table.';
		end
	]],

Take into account that the function text is surrounded with [[ ]] brackets, so if you need multiline string literals inside the statement you must use nested brackets ([=[ ]=], [==[ ]==] and so on, see the Lua docs) and the very same ^ for line breaks.

Invoked code statemet creates some objects automatically. They are self variable which refers the object containing the code, and arg1..arg9 and args[] array which holds all arguments.

The code statement is useful if you need to define a very short function:

    act = code [[ walk(sea) ]];

Or if you are going to redefine functions on the fly, although it usually become an example of bad programming style. code statements are saved if are assigned to stored variables.

    var { 
       act = code [[ walk(sea) ]];
    };
...
    s.act = code [[ walk(ground) ]];

Sometimes you may need auxiliary variables for storing temporary values, eg:

kitten = obj {
        nam = 'kitten';
        var { state = 1 };
        act = function(s)
                s.state = s.state + 1
                if s.state > 3 then
                        s.state = 1
                end
                p [[Purrr!]]
        end;
        dsc = function(s)
                local dsc = {
                        "The {kitten} is purring.",
                        "The {kitten} is playing.",
                        "The {kitten} is washing up.",
                };
                p(dsc[s.state])
        end;
end

In the dsc handler the dsc array is defined. local limits array visibility with the function boundaries. You should define all auxiliary variables as local. Если вам нужны вспомогательные переменные в функциях, всегда пишите перед их определением local. Of course, part of the previous example can be rewritten as follows:

dsc = function(s)
        if s.state == 1 then
                p "The {kitten} is purring."
        elseif s.state == 2 then
                p "The {kitten} is playing."
        else
                p "The {kitten} is washing up.",
        end
function mprint(n, ...)
        local a = {...}; -- temporary array for the function arguments
        p(a[n]) -- print the nth element
end
....
        dsc = function(s)
                mprint(s.state, {
                        "The {kitten} is purring.",
                        "The {kitten} is playing.",
                        "The {kitten} is washing up.",
                });
        end;

Simplify it:

        dsc = function(s)
                p(({
                        "The {kitten} is purring.",
                        "The {kitten} is playing.",
                        "The {kitten} is washing up.",
                })[s.state]);
        end;

Sometimes we need a handler doing something without any output, eg.:

button = obj {
	nam = "button";
        var {
                on = false;
        };
	dsc = "There is a big red {button} on the wall.";
	act = function (s)
         	s.on = true
                return true
        end;
}
 
r12 = room {
	nam = 'room';
        forcedsc = true;
	dsc = function (s)
                if not button.on then
                        p [[I am in a room.]];
                else
                        p [[I am in a room with the button pressed.]];
                end
        end,
	obj = {'button'}
}

Here the act handler alterates the room description and there is no need for it print anything itself. Frankly speaking it's some sort of a contrived example. You likely will not have to use handlers without output. As for above example, why not to print something like “I pressed the button” in act code? Moreover we had to enable forcedsc mode. However such need can occur.

You can return true from a handler, which will mean an action completed successfully but doesn't require any additional description.

If you need to accent the fact no action has been taken, do not return anything. The default text from game.act will be displayed. It usually contains a message about undoable action, like here:

game.act = 'Um... I can\'t do this...';

Take into account that dynamic scene description above is generated with a function. Why not to alter dsc value on the fly? Actually it will not work until you defined dsc in the room var block:

button = obj {
	nam = "button";
	dsc = "There is a big red {button} on the room wall.";
	act = function (s)
         	here().dsc = [[Everything changed in the room!!!]];
	        pn [[The room transformed after I pressed the button. 
		The book-case disappeared along with the table and the chest, and a strange 
		looking device took its place.]];
        end,
}
 
r12 = room {
	nam = 'room';
        var {
	        dsc = [[I am in the room.]];
        };
	obj = {'button'}
}

Although such programming style is not recommended. First, you tangle the code with desctription texts spread around it instead of containig them inside the object they describe. Second, saved game files will consume much more disk space. It's highly recommended to use functions for alterable attributes and reactions instead of changing latter ones externally.

Sometimes they need to call a handler manually. Lua syntax of method call (object:method(parameters)) is used if the handler implemented as function:

apple:act() -- ''act'' handler of ''apple'' object is called

The above code is unrolled into:

apple.act(apple) -- the same object and handler, the object is referred in first parameter

If the handler is not a function you can utilize stead.call() routine to call the handler in the same manner as the interpreter does. (Will be described later).

6. Inventory

The easiest way to create a takeable object is to define its tak handler:

apple = obj {
	nam = 'apple',
	dsc = 'There is an {apple} on the table.',
	inv = function(s)
		inv():del(s);
		return 'I ate the apple.';
	end,
	tak = 'You took the apple.',
};

Turn attention to the inv handler, which is called when the player uses (clicks) the object in the inventory. So the apple is moved from the scene to the inventory when the act handler is triggered, and is removed from the inventory with action text displayed ('I ate the apple.') when the inv is.

It's also possible to implement taking action in the act handler:

apple = obj {
        nam = 'apple';
        dsc = 'There is an {apple} on the table.';
        inv = function(s)
                remove(s, me()); -- remove the apple from the inventory
                p 'I ate the apple.'
        end;
        act = function(s)
                take(s) -- the fruit is moved from the scene to the inventory
                p 'You took the apple.';
        end
};

If an object has not inv handler, the game.inv will be called instead.

7. Passing between the scenes

Classical INSTEAD passages look like links above the scene description. Such passages are defined in the dedicated scene attribute way. It's a list containing rooms as references or object links (in fashion similar to obj list):

room2 = room {
	nam = 'hall',
	dsc = 'You are in a huge hall.',
	way = { 'main' },
};

main = room {
	nam = 'main room',
	dsc = 'You are in a large room.',
	obj = { 'tabl' },
	way = { 'room2' },
};

Here two rooms are defined you can pass between. As was said, nam (or disp) may be written as function to utilize dynamic scene captions, which are generated on the fly. For example a room which name is not known to player until he visit it. By the way there are more appropriate tools for such purposes, say wroom module, which is discussed later.

When passing between scenes the engine calls the exit handler of the current scene and the enter of the destination one:

hall = room {
	enter = 'You enter the hall.',
	nam = 'hall',
	dsc = 'You are in a huge hall.',
	way = { 'main' },
	exit = 'You leave the hall.',
};

Like any hanler exit and enter may be functions. Then the first parameter is the scene containing the handler and the second parameter is the room where the player is heading (for exit) or which he is leaving (for enter):

hall = room {
	enter = function(s, f)
		if f == main then
			p 'You came from the room.';
		end
	end,
	nam = 'hall',
	dsc = 'You are in a huge hall.',
	way = { 'main' },
	exit = function(s, t)
		if t == main then
			p 'I don\'t wanna go back!'
                        return false
		end
	end,
};

As we see, the handlers can return two values: a string or a status. In our example the exit function returns false if the player tries to go to the main room from the hall. false means that the player will not pass. Like the Barlog. The same logic works for enter and tak.

You can return a staus in other way if you want:

        return "I don't wanna go back.", false -- action text followed by comma separated status value

If you prefer p/pn/pr instead, just return status itself:

Take into account the scene pointer (here()) may be still not updated while enter is called! You can use left and entered handlers instead which are triggered after the passing ends. These handlers are recommended for use every time you don't need to forbid passing.

Sometimes there is a need to have passage name different from the destionation room name. There are several ways to implement it, and here is the first one, vroom:

hall = room {
	nam = 'hall';
	dsc = 'You are in the hall';
	way = { vroom('To the first room', 'main') };
        -- vroom ('passage name', destination_room)
};
 
main = room {
	nam = 'main room';
	dsc = 'You are in the small room.';
	obj = { 'tabl' };
	way = { vroom('To the hall', 'hall') };
};

Actually vroom function returns an auxiliary room object with programmed enter handler which bounces the player into the destination room.

First vroom obtains the second parameter as a reference because main room is not defined at that point. At the second call we drop the quotes because the hall already exists, but leaving it will make the code style more consistent.

Module wroom may be used if the vroom functionality is insufficient.

Sometimes you may need to disable and enable passing. Supposedly not often. The original conception of passages assumes they are always visible even if blocked like in a scene with a house with locked entrance.

It's not needed to hide the entrance passage. A check for key presence in the inventory placed in the enter handler of the passage destination will be enough. If no key, it will display a notification and cancel passing. If you desire to make the entrance door a scene object, place it in the room and implement unlocking it with a key, but let the player get inside through the customary passages list.

Yes, there are situations when the passage is not obvious or it appears as a result of some event. Eg. we examined a clock and found a secret tunnel behind it:

clock = obj {
        nam = 'clock';
        dsc = [[You see an old {clock}.]];
        act = function(s)
                path('Behind the clock'):enable()
                p [[You discover a secret tunnel behind the clock!]];
        end;
}
hall = room {
	nam = 'hall';
	dsc = 'You are in a huge hall.';
        obj = { 'clock' };
	way = { vroom('Behind the clock', 'behindclock'):disable() };
};

Here a disabled passage is created with vroom :disable() method. It is commonly found in all objects and turns them into disabled state. It means the object is not processed with the engine until has enabled with :enable() method. Both method returns the object in its new state.

You may also write it as follows:

way = { disable(vroom('В часы', 'inclock')) }; -- enable() is its counterpart

Returning to the clock scene example, act handler invokes the path() function, which finds the passage named 'Behind the clock' in way of the current room and calls its enable() method. An alternative notation also may be used:

        act = function(s)
                enable(path('Behind the clock')) 
                -- Lua syntax allows to drop parentheses
                -- if the only parameter is passed to a function
                -- so it will be correct (and less complex) to write code as
                -- enable( path 'Behind the clock' )
                p [[You discover a secret tunnel behind the clock!]];
        end;

If the passage we want to toggle is in an other room, we can pass it as the second parameter of path():

        path('Behind the clock', room312):enable();

If you don't like to refer vroom passages by name, you may use variables:

path_clock = vroom('Behind the clock', 'behindclock');
 
clock = obj {
        nam = 'clock';
        dsc = [[You see an old {clock}.]];
        act = function(s)
                path_clock:enable()
                p [[You discover a secret tunnel behind the clock!]];
        end;
}
hall = room {
	nam = 'hall';
	dsc = 'You are in a huge hall';
        obj = { 'clock' };
	way = { path_clock:disable() };
};

You may toggle rooms itself if don't use vroom:

inclock = room {
        nam = 'In the tunnel';
        dsc = [[It's dark here.]];
}:disable();
-- three lines below are equivalent
-- }:disable()
-- }; inclock:disable()
-- }; disable(inclock)
 
clock = obj {
        nam = 'clock';
        dsc = [[You see an old {clock}.]];
        act = function(s)
                inclock:enable()
                p [[You discover a secret tunnel behind the clock!]];
        end;
}
hall = room {
	nam = 'hall';
	dsc = 'You are in a huge hall';
        obj = { 'clock' };
	way = { 'inclock' };
};

8. Using an object on an object

The player may use an inventory object on other objects. In this case use handler is invoked for the object in the inventory and used for the other one.

For example:

knife = obj {
	nam = 'knife',
	dsc = 'There is a {knife} on the table',
	inv = 'It\'s sharp!',
	tak = 'I took the knife!',
	use = 'You try to use the knife.',
};

tabl = obj {
	nam = 'table',
	dsc = 'There is a {table} in the room.',
	act = 'Hm... Just a table...',
	obj = { 'apple', 'knife' },
	used = 'You try to do something with the table...',
};

If the player takes the knife and uses it on the table, he gets the text of knife use handler and table used one. These handlers may be a function. The first parameter refers the object itself and the second parameter is the action subject for use and the actioning object for used handler.

If use returns false then used is not invoked (event if it is defined). The used returning value is ignored.

Example:

knife = obj {
	nam = 'knife',
	dsc = 'There is a knife on the {table}',
	inv = 'Sharp!',
	tak = 'I took the knife!',
	use = function(s, w)
		if w ~= tabl then
			p 'I don\'t want to cut this.'
                        return false
		else
			p 'You incise your initials on the table.';
		end
};

In the above example you can use the knife only on the table.

If these handlers are not defined or return nothing the default hanler game.use is called.

It's up to you to choose use or used but it's a good idea to place the code near the object it assotiates to. Thus, if we create a trash bin object and allow the player to throw anything there, it makes sense to define the trash bin used handler.

trash = obj {
        nam = 'trash';
        dsc = [[I see a {trash bin}.]];
        act = 'It is not for me.';
        used = function(s, w)
                remove(w, me())
                p [[I do not need it anymore.]];
        end
}

Problems occur when both use and used are present. Let the player has a knife which displays “I don't want slash it” if used on anything except of an apple. Use the knife on the trash bin, and after “I don't want” message the knife will vanish in its deeps (as programmed in used). Of course we can patch knife use handler:

p "I don't want to slash it."
return false -- the chain is broken, ''used'' is not called

But it's not very handily. In this situation nouse module may be used:

...
require "nouse"
 
...
knife = obj {
	nam = 'knife',
	use = function(s, w)
                if w ~= apple then -- if the apple is not an action subject
                        return
                end
                if w.knife then
                        return "It's already skinned."
                end
                w.knife = true
	        p 'I skinned the apple.'
	end;
        nouse = [[I don't want slash it.]];
};

The nouse handler is called if neither use nor used returns something adequate. If nouse lines up with them, the noused handler of the action subject is tried to be called. The last stand of the engine is the game wide default handler game.nouse.

Any of these handler can be a function with three arguments: the handler owner, the actor and the subject.

The “nouse” module redefines game.use handler so you must switch to its alternatives such as game.nouse.

Actually it's not only possible to use an inventory item on a scene object but also scent objects on ecah other (and even inventory item on another one). If you are going to invoke this mechanism, set the object or game boolean scene_use. Or a function, returning boolean value.

9. Player object

In STEAD the player is represented by the object pl of type player. In the engine it's created this way:

pl = player {
	nam = "Incognito",
	where = 'main',
	obj = { }
};

The obj attribute represents the player's inventory. Usually it's not needed to redefine the player type, but if you want to add some variables associated with the player, you can do it:

pl = player {
        nam = "James";
        where = 'main';
        var { power = 100 };
        obj = { 'apple' }; -- let's give him an apple
};

STEAD allows you to create multiple players and to switch between them by means of the change_pl() function, which receives the only parameter referring player to switch to. The function changes the current location to one where the new current player resides at.

You can get the current player object with the me() function. In most cases me() == pl.

10. The object “game”

The game also has its representation, the object game of type game. The engine defines it as follows:

game = game {
        codepage = "UTF-8",
	nam = "INSTEAD -- Simple Text Adventure interpreter v"..
                       stead.version.." '2013 by Peter Kosyh",
	dsc = [[
Commands:^
    look(or just enter), act <on what> (or just what), use <what> [on what], 
    go <where>,^
    back, inv, way, obj, quit, save <fname>, load <fname>.]],
	pl = 'pl',
	showlast = true,
        _scripts = {},
};

As we can see, the object keeps the current player ('pl') reference and some settings. At the beginning of your game you can set the text encoding:

game.codepage = "cp1251"; 

But it will be better to swithc your editor to UTF-8 encoding and use it. The redefinig is justified in case of running (by means of corresponding module) games written for other platforms (like URQL) in different encoding.

The object game may contain the default handlers act, inv, use. They will be invoked if no other handlers are found in response to the user's action. You can put such code at the game beginning:

game.act = 'You can\'t.';
game.inv = 'Hmm... Odd thing...';
game.use = 'Won\'t work...';

It's recommended to always define these handlers. Don't forget the “nouse” module reserves the game.use handler for its needs so you have to deal with game.nouse.

11. Attribute lists

Attribute lists (such as way or obj) store objects and allow to manage themselves with a set of methods. You may create lists for your own needs. Lists haven't to be defined through var or global:

treasures = list { 'gold', 'silver' };

List methods are:

It must be noted that methods add, del, purge, replace, srch and others can receive objects by name (nam attribute) as well as by link.

The typical example of list manipulation is inv():del('apple'), where the system function inv() returns the list representing the player inventory.

You can implement tak handler through the act one:

knife = obj {
	nam = 'knife',
	dsc = 'There is a {knife} on the table.',
	inv = 'It\'s sharp!',
	act = function(s)
		objs():del(s);
		inv():add(s);
	end,
};

Respectively, objs() returns the list containing the current room objects. Or any room which identifier the method call is prefixed with. Corresponding method for getting passages list is ways().

UPDATING IN PROGRESS

Starting from version 0.8 the object itself may be a parameter of “add”. Also from this version an optional second parameter is added — position in list. From 0.8 you also can modify the list by the index with the “set” method. For example:

objs():set('knife',1);

You've seen the above example with the eaten apple. It used inv():del('aple');

“inv()” is a function, which returns the inventory list. “del” after “:” is a method, that deletes an element of the inventory.

Similarly, “tak” may be implemented this way:

knife = obj {
	nam = 'knife',
	dsc = 'There is a {knife} on the table,
	inv = 'Sharp!',
	act = function(s)
		objs():del(s);
		inv():add(s);
	end,
};

Apart from adding and deleting objects from lists you may switch them on and off with “enable()” and “disable()” methods. E.g. “knife:disable()”. This way the object “knife” will disappear from the scene description, but may be switched on later with “knife:enable()”.

“enable_all()” and “disable_all()” methods works (from 0.9.1) with embedded objects (objects in object).

From version 0.9.1 methods “zap” and “cat” can be used. zap() – delete all elements. cat(b, [pos]) – add all elements of list b to current list at position [pos].

From version 1.8.0 methods “disabe” and “enable” can be used to disable/enable selected object in list (usually by name);

Attention!!! Currently, it is recommended to use higher lever functions like: put/get/take/drop/remove/seen/have and so on, to work with objects and inventory.

12. Functions, that return objects

STEAD provides several functions returninig some frequently used objects. In the functions description following convetions are in place:

Functions returning lists are:

Function returning lists are usually not used, instead ones from the next section are.

As for the funtions returing objects here they are:

These functions are usually used in conditions or for finding objects for subsequent midification. For example you can write this:

exit = function(s)
        if seen 'monster' then
                -- remember Lua allows not to parenthesize a single parameter
                p 'The monster blocks your way!'
                return false
        end
end
...
use = function(s, w)
        if w == window and path 'Through the window':disabled() then 
                -- action on the window and the corresponding passage is disabled
                path 'Through the window':enable();
                p 'I broke the window!'
        end
end
...
...
act = function(s)
	if have('knife') then
		p 'But I have a knife!';
                return
	end
end
...

You can also rewrite the last sample like here:

...
	if have 'knife' then
...
	if have (knife) then
...

Another two functions are stead.ref(reference) and stead.deref(object). The first one returns the link to the object passed by reference, the second one returns the reference to the given object:

...
stead.ref 'apple' == apple -- with apple defined, this equality explains what stead.ref() does
...
act = function(s)
        p('You clicked the object ', stead.deref(s));
end

13. Some auxiliary functions.

STEAD has a number of high-level functions, that may come useful when writing games. You have already met some of them hereinbefore.

move('mycat','inmycar');

If you want to move an object from an arbitrary scene you must to know its location. For implementing objects moving in a complicated fashion you may write your own method which will track the object location and move it across the scenes. Or you can locate an object with the where function each time:

move(mycat, here(), where(mycat)); -- my cat travels with me;

Keep in mind you must place the cat (mean the object) with put or place function previously for where could work.

There is the movef function similar to move but the object is placed in the beginning of destination obj list.

drop (knife);

The twin function dropf acts in the same way except of placing the object in the beginning of the destination obj list.

take('knife');

These functions are applied to lists as well as to rooms and objects. I.e. remove(apple, inv()) works similar to remove(apple, me()); Some of above-described functions have variations postfixed with to: placeto, putto, taketo, dropto. They receive the additional parameter, the object position it will be inserted into the list at. You also may specify the position right in the list definition:

obj = { [1] = 'apple', [1000] = 'floor' };

But it's not recommended due to its complexity so it's better to use nested objects for controlling descriptions sequence.

act = code [[
        pn "I'm going to the next room..."
        walk (nextroom);
]]
mycar = obj {
	nam = 'my car',
	dsc = 'In front of the cabin there is my old Toyota {pickup}.',
	act = function(s)
		walk('inmycar');
	end
};

<WRAP center round important>
Take into account that ''walk'' call does not interrupt the handler execution. So you usually should place the return operator immediately after ''walk'' if it's not the last instruction in the handler, or even in this case (for safety):
<code lua>
act = code [[
        pn "I'm going to the next room..."
        walk (nextroom);
        return
]]

Keep in mind that calling walk will trigger corresponding handlers exit/enter/left/entered which may cancel movement. </WRAP>

john.where = 'kitchen'

Or invoke walk() explicitly just after change_pl() call.

14. Dialogs

Dialogs are dlg type scenes with phrase objects. Currently there are two ways to define dialogs: extended and simple. Both share the same behavior. The simple dialogs are deprecated and are not recommended for use.

Player entered a dialog see a list of phrases (numbered by default), selecting which triggers some game reaction. By default once chosen phrases become hidden. When all of them have desappeared, the dialog returns player to the previous scene. Often there are concstantly visible phrases like 'End dialog' of 'Ask one more time' which prevent the dialog from closing.

Dialogs are entered in the same way as scenes:

cook = obj {
	nam = 'cook';
	dsc = 'I see a {cook}.';
	act = function()
		return walk 'cookdlg'
	end;
};

It is recommended to use walkin instead of walk, because current scene exit/left is not called in this case (while the person we want to talk to is usually in the same room with the player):

cook = obj {
	nam = 'cook';
	dsc = 'I see a {cook}.';
	act = function()
		return walkin 'cookdlg'
	end;
};

You can enter a dialog from another one, implementing hierarchical dialogs. You can return to the previous level of the tree with back() call. By the way, extended dialogs implement hierarchical pattern more easy.

You can redefine the phrase prefix (index by default):

stead.phrase_prefix = '--';

This code causes phrases are prefixed by dashes instead of numbers. Keep in mind the stead.phrase_prefix value in not stored in saved game file, so you must redefine it manually in the start function!

The engine blocks entering dialogs with no phrases (because it can be exited in the natural way). Take it into account while debugging your games.

It is highly recommended to use the hideinv module with setting dialog hideinv property to true. Dialogs will look much fine, and you will insure the game from bugs and unpredictable reactions caused by using inventory inside dialogs (which is usually not supposed by author):

instead_version "1.8.2"
require "hideinv"
...
guarddlg = dlg {
        nam = 'guard';
        -- inventory is usually not needed in dialogs
        hideinv = true;
        ...
}

The most common mistake is incorrect calling a dialog from the inv handler. Let's look at a mobile phone, activating which leads the player to a phone dialog. The returning is usually done by back() call, but if the inventory is not hidden and the player clicks the mobile phone one more time – we run into the same dialog one much time! And the back() will return us to the previous instance of the dialog instead of the initial room scene. Of course you can avoid such situations with midifying your code:

phone = obj {
        nam = 'mobile';
        inv = function(s)
                if here() ~= phone_dlg then
                        walkin(phone_dlg)
                        return
                end
                p "I am already holding the mobile."
        end
}

Extended dialogs

Since STEAD 1.7.0 the new more simple and powerful dialogs syntax is supported.

Phrases are defined in the dialog phr attribute:

cookdlg = dlg {
	nam = 'in the kitchen';
        hideinv = true;
	entered = [[I see a fat face of a lady-cook wearing a white hat. She looks tired...]];
	phr = {
	    { always = true, '“Those green, please... Yeah, and beans too!”', 
                             '“Enjoy!”'};
	    { always = true, '“Fried potato with lard, please!”', 
                             '“Bon appetit!”'};
	    { always = true, '“Two helpings of garlic sooup!!!”', 
                             '“Good choice!”' };
	    { always = true, 'Something light, please, I've got an ulcer...', 
                             '“Oatmeal!”' };
            { always = true, 'Thank you, I don't want anything', 
                             'As you wish.', [[ back() ]] };
	};
};

If dialog dsc is not defined, it is formed in such a way that the description always contains the last dialog reply, and the player will see it after clicking the scene caption. So it is better to put the dialog introduction into the entered handler, like in the above example. It is not recommended to redefine extended dialogs dsc.

Each phrase is of the form:

{ [INDEX or tag=TAG,][false if disabled,][always = true],
         "Question", "Reply", [[ reaction code, optional ]] },

Each phrase is represented by its question value. When the phrase is selected (by clicking it), the reply value is displayed, the phrase become disabled, and reaction code (Lua code string) is invoked (if present). When all phrases become disabled, the dialog branch returns.

Reaction may contain any Lua code, but usually there is phrases related code there.

STEAD provides following phrases functions:

When called with no arguments, functions are applied to the current phrase (from which the code has been called).

To manupulate phrases in external dialog (which is not the current scene) you can use the syntax dialog:method(), e.g.:

guard_dlg:pon('show_card')

You can define the phrase disabled initially, and enable it later:

cookdlg = dlg {
	nam = 'in the kitchen';
        hideinv = true;
	entered = [[I see a fat face of a lady-cook wearing a white hat. She looks tired...]];
	phr = {
            -- disabled phrase
            { 1, false, always = true,  
                -- you may use line breaks for clarity
                [[Give me french rolls!]], 
                [[Of course...]] }; 
            -- get to know about rolls, enable the phrase
            { [[And what is there, on the shelf?]], 
              [[There are french rolls there.]], 
              [[ pon(1) ]] }; 
	    { always = true, '“Those green, please... Yeah, and beans too!”', 
                             '“Enjoy!”'};
	    { always = true, '“Fried potato with lard, please!”', 
                             '“Bon appetit!”'};
            { always = true, 'Thank you, I don't want anything', 
                             'As you wish.', [[ back() ]] };
	};
};

So you can identify phrases by index:

{ 2, "Question?", "Reply!" };

For more complex dialogs tags are more suitable:

{ tag = 'exit', "Ok, I'm leaving!", code [[ back() ]] };

If you do not need to manipulate the phrase, just drop the first field:

{ "Question?", "Reply!" };

Tag is text label of a phrase. As was said, you may pass phrases to pon/poff/pseen/punseen by index as well as by tag. If multiple phrases have the same tag, action applied to all of them. pseen returns true if at least one phrase with such tag is visible, punseen – if no visible phrase has the tag.

You may assign a tag to indexed phrases as well.

If the phrase definition contains always = true means the phrase will not be hidden automatically after its activation:

{ tag = 'exit', always = true, "Ok, I'm leaving!", code [[ back() ]] }

If you implement the whole reaction in reaction code and you don't need any text in the reply, you can use one of the following ways:

{ tag = 'exit', always = true, "Ok, I'm leaving!", nil, [[ back() ]] },
{ tag = 'exit', always = true, "Ok, I'm leaving!", code = [[ back() ]] }

In both cases the reply field will be nil.

You can also define questions and replies as functions or code:

{ tag = 'exit', code [[ p "Ok, I'm leaving!" ]], 
                code [[ p "Won't you stay?"; pon 'really' ]] },
{ tag = 'really', false, 
  always = true, 
  "I'm surely leaving!", 
  function() back() end } -- this phrase is hidden by defaylt, the previous one unhides it

You can group phrases into branches, implementing hierarchical dialogs without need for massive invoking pon/poff crossing between numerous dlg.

Phrases group is a set of consecutive phrases, which are displayed as an independed dialog screen. Sets are separated with a phrase which have no reaction (the simplest separator is {}):

{ 'Tell me few words about the weather.', 
    'Okay, what are you interested in?', [[ psub 'weather' ]] },
{ always=true, [[Bye!]], code = [[ back() ]] },    
{ tag = 'weather' },
{ 'What is the temperature?', '25 degrees Celsius!' },
{ 'What about the humidity?', '80 percents.' },

Only one group is visible at a time. In the above example there are two groups. Entered the dialog, the player see two phrases to choose: 'Tell me…' and 'Bye!'. First phrase leads him into the subbranch tagged 'weather', where he can ask two questions (about temperature and humidity). After both questions are asked, the player is returned to the previous, initial branch, where the only 'Bye!' phrase remains visible (because no other phrase have always=true).

In the example the groups separator is { tag = 'погода' }, but you can move the tag into one of the group phrase:

{ 'Tell me few words about the weather.', 
    'Okay, what are you interested in?', [[ psub 'weather' ]] },
{ always=true, [[Bye!]], code = [[ back() ]] },    
{ },
{ tag = 'weather', 'What is the temperature?', '25 degrees Celsius!' },
{ 'What about the humidity?', '80 percents.' },

Branch is changed with the following functionx:

psub/pstart/pjump argument may be index or tag. You can apply these functions to external dialogs as well aspon/poff: shopdlg:pstart(1)

You can get the current branch index by calling dialog:current() and its tag by dialog:curtag().

The branch status can be checked with functions:

Both ones receive the index or tag of the phrase determining the group. :empty() returns true if the group contains no active phrases. :visible() returns visible phrases number (0 if empty). If no argument supplied, the current group is processed.

While using psub/pstart/pjump, you may use the first phrase in the called branch as a caption for the group:

{ 'Tell me few words about the weather.', code = [[ psub 'weather' ]] },
{ always=true, [[Bye!]], code = [[ back() ]] },    
{ },
{ tag = 'weather', 'Okay, what are you interested in?'},
{ 'What is the temperature?', '25 degrees Celsius!' },
{ 'What about the humidity?', '80 percents.' },

The phrase tagged 'weather' contains no reaction and plays role of branch caption. When entered with psub 'weather' , the branch displays 'Okay, what are you interested in?'.

The question may be a function, so you can invoke code on branch changing:

{ 'Tell me about weather.', code = [[ psub 'weather' ]] },
{ always=true, [[Bye!]], code = [[ back() ]] },    
{ },
{ tag = 'weather', function() 
          p 'Ok, what exactly?'; 
          weather_asked = true; 
end },
{ 'Temperature?', '25 Celsius!' },
{ 'Humidity?', '80 percents!' },

Also the caption phrase can contain the empty method, which is called after all other phrases are hidden:

{ 'Weather?', code = [[ psub 'weather' ]] },
{ always=true, [[Bye!]], code = [[ back() ]] },    
{ },
{ tag = 'weather', 'What?',
    empty = code [[ p 'Enough talking bout weather!'; pret() ]] },
{ 'Temperature?', '25 C!' },
{ 'Humidity?', '80%' },

Default action for empty is pret(). If you redefine empty, you must call the pret() explicitly if it's needed.

If you want, here is a complete branchy dialog.. in Russian. http://raw2.github.com/instead-hub/instead/master/doc/examples/dialog/main.lua

Simple dialogs

This section describes deprecated syntax, but maybe it will help you to understand the previous section, because some conceptions are common for both kinds of dialogs.

An example dialog in old syntax:

povardlg = dlg {
	nam = 'in the kitchen';
	dsc = [[I see a fat face of a lady-cook wearing a white hat. She looks tired...]];
	obj = {
	[1] = phr('“Those green, please... Yeah, and beans too!”', '“Enjoy!”'),
	[2] = phr('“Fried potato with lard, please!”', '“Bon appetit!”'),
	[3] = phr('“Two helpings of garlic sooup!!!”', '“Good choice!”'),
	[4] = phr('“Something light, please, I've got an ulcer...', '“Oatmeal!”'),
	};
};

phr creates a phrase, which contains a question, a replay and a reaction (not present in this example). A phrase is switched off after the player selects it. Dialog terminates when all phrases are inactive. Reaction is a Lua code string, which is executed after its phrase goes inactive:

food = obj {
	nam = 'meal',
	inv = function (s)
		iremove('food', inv());
		p 'I am eating.';
	end
};
 
gotfood = function(w)
	take 'food';
	food._num = w;
	back();
end
 
povardlg = dlg {
	nam = 'in the kitchen';
	dsc = [[I see a fat face of a lady-cook wearing a white hat. She looks tired...]];
	obj = {
	[1] = phr('“Those green, please... Yeah, and beans too!”', 
                  '“Enjoy!”', [[pon(); gotfood(1);]]),
	[2] = phr('“Fried potato with lard, please!”',
                  '“Bon appetit!”', [[pon(); gotfood(2);]]),
	[3] = phr('“Two helpings of garlic sooup!!!”',
                  '“Good choice!”', [[pon();gotfood(3);]]),
	[4] = phr('“Something light, please, I've got an ulcer...',
                  '“Oatmeal!”', [[pon(); gotfood(4);]]),
	};
};

Here the player chooses his meal, get a helping (kind of meal is stored in food._num) and returns back to the scene which has called the dialog.

Reaction can contain any Lua code, but (similar to extended dialogs) usually it is phrases management logic. pon/poff/prem/pseen/punseen work with indexes only (because simple dialogs don't use tags).

It's possible to pass from a dialog to another one, implementing hierarchical dialogs.

Also you can hide some particular phrases while initializing dialog and unhide them on some conditions:

facectrl = dlg {
	nam = 'face control';
	dsc = 'I see the unpleasant and unfriendly face of the fat security guy.';
	obj = {
		[1] = phr('I\'ve come to listen to the lecture of Belin...', 
		    [[— I don\'t know who you are, — the guard grins —
                    but I was told to let only decent people in here.]],
		    [[pon(2);]]),
		[2] = _phr('I have the invitation!', 
		    [[— I do not damn care! Look at yourself! Used a mirror recently?!
                    You\'ve come to listen to Belin himself! Be-lin! The right hand of... -
                    the guard paused for a second in respect - So... Get out of here!]],
                    [[pon(3,4)]]),
		[3] = _phr('I\'m gonna kick you fat face!', 
                    [[— Well, that\'s enough... - Powerful hands push me out to the corridor...
                    I feel lucky to stay in one piece...]],
		    [[poff(4)]]),
		[4] = _phr('You boar! I\'ve told you I have the invitation!',
		    [[— Whaaat? - The guard\'s eyes are going red... The powerful kick
                    sendsme to the corridor... It could be worse...]],
		    [[poff(3)]]),
	};
	exit = function(s,w)
		s:pon(1);
	end;
};

_phr creates a hidden phrase which can be unhidden later. The example shows usage of dialog methods pon, poff, prem (see exit).

15. Lightweight objects

Sometimes we need to fill a scene with decorations which has a limited functionality to make the game brighter. Lightweight objects may be used for that purpose. For example:

sside = room {
	nam = 'southern side',
	dsc = [[I am near the southern wall of an institute building. ]],
	act = fuUTF-8codenction(s, w)
		if w == "porch" then
			ways():add('stolcorridor');
			p "I walked to the porch. The sign on the door read 'Canteen'. Hm... should I get in?";
		elseif w == "people" then
			p 'The ones going out look happier...';
		end
	end,
	obj = { vobj("porch", "There is a small {porch} by the eastern corner."),
		vobj("people", "From time to time the porch door slams letting {people} in and out..")},
};

vobj allows to create a lightweight version of a static object. It can be interacted with by means of the scene act handler which analyzes the object name. vobj also calls the used method, the third parameter contains an object acting on the virtual object..

vobj usually has not handle, so you can identify the passive object through stead.nameof:

use = function(s, w)
        if stead.nameof(w) == "humans" then
                p "You should not disturb humans."
                return
        end
end;

vobj syntax is simple: vobj(name, description). vobj can be added to the scene dinamically (see also the vway exmple hereinafter):

put(vobj("cont_button", "{Continue}"));

Though this style seems to look old fashioned, so you should rather use disable/enable functionality with static desctiption.

...
obj = { vobj("cont_button", "{Continue}"):disable() };
...
exist 'cont_button':enable();

There is a modification of vobj object, vway. It creates a reference leading to the specified scene. vway syntax: vway(name, description, destination_scene):

	obj = { vway("next", "Press {here} to pass to the next room.", 'nextroom') }

Actually if you are writting something like a gamebook, where the gameplay consists of wandering from a link to another one, then (let alone it's a bad choise for your first game) you should utilize the xact module, which implements more simple mechanism of creating references.

You can dynamically fill the scene with vway objects, similar to vobj. Methods add and del are at your sevice too:

	objs(home):add(vway("next", "{Next}.", 'next_room'));
-- some code here
	home.obj:del("next");

It should to be understood that vobj and vway are just generic objects with predefined handlers and save functions (which allow on-the-fly creation). Knowing the engine architecture you can implement your own object variants with required properties.

In addition to lightweight objects there is another way of describing decorations. You may define a static object directly in the obj array, without a handle:

hall = room {
       nam = 'living room';
       dsc = [[I am in a spacious living room.]];
       obj = {
               obj {
                       nam = 'table';
                       dsc = [[There is a {table} in the middle.]];
                       act = [[It is mahogany.]];
               };
       };
}

In the use handler you can identify such objects as well as vobj:

use = function(s, w)
        if stead.nameof(w) == 'table' then
                p [[I don't want to spoil such thing of beauty.]]
                return
        end
end

It's up to you to use or not to use this pattern, there is much speculation that placing objects in standalone variables makes the code more clear.

You also can use a single object in multiple scenes. As an example, you can create an object “shotgun shell” and throw it onto the scebe each time player shoots. In this case shells serve as decorations only, they cannot be taken or someway interacted with.

16. Dynamic events

You can define handlers that would be executed each time the game timer is incremented by 1. It is usually used for implementing background processes like “life simulation”. Game step has rougly the following algorythm: - the player clicks the link; - act, use, inv handlers, overlooking the scene (when clicking scene title) or passing to another scene; - dynamical events; - scene state output (static part if needed, dynamic part always).

For example, let us animate Barsik the cat:

mycat = obj {
	nam = 'Barsik',
	lf = {
		[1] = 'Barsik is moving in my bosom.',
		[2] = 'Barsik peers out of my bosom.',
		[3] = 'Barsik purrs in my bosom.',
		[4] = 'Barsik shivers in my bosom.',
		[5] = 'I feel Barsik's warmth in my bosom.',
		[6] = 'Barsik leans out of my bosom and looks around.',
	},
	life = function(s)
		local r = rnd(6);
		if r > 2 then
			return;
		end
		r = rnd(6);
		return s.lf[r];
	end,
....

profdlg2 = dlg {
	nam = 'Belin',
	dsc = 'Belin is pale. He absently looks at the shotgun.',
	obj = {
		[1] = phr('“I came for my cat.”',
	'I snatch Barsik from Belin's hand and put in my bosom.',
		[[inv():add('mycat'); lifeon('mycat')]]),
....

Any object, including scenes, may have their life handler, which is called every time the game time advances, if the object or the scene have been added to the list of living objects with lifeon. Don't forget to remofe living objects from the list with lifeoff, when you no longer need them. You can do this, for example, in the exit handler or in some other way.

If there is a lot of “living” objects in your game, you can assign priorities to them. Pass it as the second parameter to lifeon (unsigned integer number, 1 is the highest priority).

If you need a background process in a room, start it in entered and stop in left:

podval = room {
        nam  = 'in the basement';
        dsc = [[It is dark here!]];
        entered = function(s)
                lifeon(s);
        end;
        left = function(s)
                lifeoff(s);
        end;
        life = function(s)
                if rnd(10) > 8 then
                        p [[I hear some rustles!]]; 
                        -- to scare the player from time to time
                end
        end;
        way =  { 'upstair' };
}

You can find that player has crossed the room border by checking player_moved:

flash = obj {
        nam  = 'flashlight';
        var { on = false };
        life = function(s)
                if player_moved() then -- extinguish the light on room chacnges
                        s.on = false
                        p "I switched off the light."
                        return
                end
        end;
...
}

To track continuous events use time() or an auxiliary counter-variable.here() to get the player location. live() to check if the object is “living”.

dynamite = obj {
        nam  = 'dynamite';
        var { 
                timer = 0; 
        };                
        used = function(s, w)
                if w == fire then
                        if live(s) then
                                return "Alreay ignited!"
                        end
                        p "I ignited the fuze."
                        lifeon(s)
                end
        end;
        life = function(s)
                s.timer = s.timer + 1
                if s.timer == 5 then
                        lifeoff(s)
                        if here() == where(s) then
                                p [[Dynamite exploded near me!]]
                        else
                                p [[I heard the dynamite exploded.]];
                        end
                end
        end;
...
}

If life handler returns a text, it's printed after the scene description. To make the text appear before the description, return true as the second value:

    p 'The guardian entered the room.'
    return true

or

    return 'The guardian entered the room.', true

If you want to block all life processing in a particular room, use the nolife module:

instead_version "1.8.2"
require "hideinv"
require "nolife"
 
guarddlg = dlg {
        nam = 'Guardian';
        hideinv = true;
        nolife = true;
...
}

Calling walk from life handler, you should take into account the following: if the handler affects player location, all departure scene life handlers output is discarded (coz it belongs to not current scene); only result of handlers activated after the movement is printed.

For example, life of a scene the cliff return a message that the player is scared while hanging on a rope; life of the rope returns a message that the rope broke and the player fell down and did walk to a new location the sea. In this case the sea becomes the current scene and ouput of the cliff handler is suppressed.

The life handler can also manipulate the player action text in the current game tick. Imagine the situation: the player examines a window (“I glanced out of the window. Depressing landscape.”); life handler of goblin object notifies, that the door flew open and a goblin bounced into the room. In such situation the information about landscape seems little uncalled. The following code hides the action text::

        p [[A wicked goblin bounced into the room!]];
        ACTION_TEXT = nil 
        -- the reaction text has been set to nothing instead of
        -- "I glanced out of the window. Depressing landscape."

ACTION_TEXT is a text variable, writable for the life handler. It usually makes sense to clear or leave it alone.

17. Graphics

Graphic interpreter analyzes the scene “pic” attribute and treats it as a path to the picture. For example:

home = room {
	pic = 'gfx/home.png',
	nam = 'at home',
	dsc = 'I am at home',
};

Of couce, “pic” may be a function. This enhaces the developer's capabilities. If the current scene has no “pic” attribute defined, the “game.pic” attribute is taken. If “game.pic” isn't defined, no picture is displayed.

From version 0.9.2 you can use animated gif files.

From version 0.9.2 graphics can be embedded everywhere in text or inventory with img function. For example:

knife = obj {
	nam = 'Knife'..img('img/knife.png'),
}

From version 1.3.0 text flow is supported. Using functions imgl/imgr, picture can be inserted at left/right. border. Those pictures can not be links.

For padding, you can use 'pad:'. For example:

imgl 'pad:16,picture.png' -- padding 16px;
imgl 'pad:0 16 16 4,picture.png' -- padding: top 0, right 16, bottom 16, left 4
imgl 'pad:0 16,picture.png' -- padding: top 0, right 16, bottom 0, left 16

You can use pseudo-images for blank areas and boxes:

dsc = img 'blank:32x32'..[[Line with blank image.]];
dsc = img 'box:32x32,red,128'..[[Line with red semi-transparent square.]];

In current version you can use disp attribute:

knife = obj {
	nam = 'Knife';
        disp = 'Knife'..img('img/knife.png'),
}

18. Sound

The interpreter cycles the current music defined by the function ”set_music(music file name)”.

For example:

street = room {
	pic = 'gfx/street.png',
	enter = function()
		set_music('mus/rain.ogg');
	end,
	nam = 'on the street',
	dsc = 'It is raining outside.',
};

From version 1.0.0 the interpreter can compose picture from base image and overlays:

pic = 'gfx/mycat.png;gfx/milk.png@120,25;gfx/fish.png@32,32'

get_music() returns the current track name.

From version 0.7.7 the set_music() function can get an additional parameter — the number of playbacks. You can get the current counter with “get_music_loop”. -1 means that the playback of the current track is over.

From version 0.9.2 the set_sound() function lets to play sound file. get_sound() returns sound filename, that will be played.

To stop music use stop_music() function (from version 1.0.0).

Use is_music() to check if music is playing. (from version 1.0.0)

19. Formatting and decorating output

You can do simple text formatting with functions:

For example:

main = room {
	nam = 'Intro',
	dsc = txtc('Welcome!'),
}

You can define text style with functions:

For example:

main = room {
	nam = 'Intro',
	dsc = 'You are in the room: '..txtb('main')..'.',
}

Since the version 1.1.0 you can create unwrapped strings by using txtnb();

For example:

main = room {
	nam = 'Intro',
	dsc = 'You are in the room '..txtb('main')..'.',
}

Formatting

Decorating

20. Constructors and inheritance

21. Advices

Split game into files

You can use “dofile” to include source code fragments. You must use “dofile” in global context, to load all game fragments while parsing main.lua.

-- main.lua
dofile "episode1.lua"
dofile "npc.lau"
dofile "start.lua"

For dynamic including (with possibility to redefine current objects or/and rooms) you can use “gamefile”:

...
act = code [[ gamefile ("episode2.lua"); ]]
...

You can also load new file and forget stack of previous loaded fragments, runnig new file like new game.

...
act = code [[ gamefile ("episode3.lua", true); ]]
...

Modules

Starting from version 1.2.0 you can use modules via “require” function call. At the moment the following modules are available:

Modules can be used like this:

--$Name: My game!$
instead_version "1.2.0"
require "para"
require "dbg"
...

If version is >= 1.2.0 then the following modules are used automatically: vars, object, walk.

“prefs” object (included into “prefs” module) can store game preferences, e.g. player progress or attempt count…

  require "prefs"
...
    prefs.counter = 0
...
    exit = function(s)
        prefs.counter = prefs.counter + 1
        prefs:store()
    end
...
    enter = function(s)
        return 'You passed the game '..prefs.counter..' times';
    end
...
    act = function(s)
        prefs:purge()
        return "Preferences has been cleared"
    end

“xact” module allows to make references to objects from other objects, reactions and life methods. These references have the form {object:string}, e.g.:

...
    act = [[ I noticed a {myknife:knife} under the table.]]
...

“object” part of the reference can be object variable or object name.

This module also defines “xact” and “xdsc” objects.

“xact” is the simple reaction object. For example:

main = room {
    forcedsc = true;
    dsc = [[Author's comment: I was writing this game for a very {note1:long} time.]];
    obj = {
        xact('note1', [[More than 10 years.]]);
    }
}

A reaction can contain a code:

        xact('note1', code [[p "More than 10 years."]]);

“xdsc” allows to insert multiple description to the object list:

main = room {
    forcedsc = true;
    dsc = [[ I'm in the room. ]];
    xdsc = [[ I see an {anapple:apple} and a {aknife:knife}. ]];
    other = [[ There are also {achain:chain} and {atool:handsaw} here.]];
    obj = {
        xdsc(), -- 'xdsc method by default'
        xdsc('other'),
        'apple', 'knife', 'chain', 'tool',
    }
}

You may use xroom:

main = xroom {
    forcedsc = true;
    dsc = [[ I'm in the room. ]];
    xdsc = [[ I see an {anapple:apple} and a {aknife:knife}. ]];
    obj = {
        'apple', 'knife', 'chain', 'tool',
    }
}

“input” module allows to implement simple text entry fields. “click” module helps to handle mouse clicks on scene pictures.

“para” module adds indentation to paragraphs.

“format: module formats the output. By default all settings are disabled:

format.para = false -- adds indentation to paragraphs;
format.dash = false -- changes double - on dash;
format.quotes = false -- changes quotes on << >>;
format.filter = nil -- user formatting function;

You may use modules para/dash/quotes to enable specific feature.

You can do menus in the inventory area, using menu constructor. Menu handler will be called after single mouse click. If handler have no return string the state of game will no change. For example, here is pocket realisation:

pocket = menu {
	State = false,
	nam = function(s)
		if s.State then
			return txtu('pocket');
		end
		return 'pocket';
	end,
	gen = function(s)
		if s.State then
			s:enable_all();
		else
			s:disable_all();
		end 
	end,
	menu = function(s)
		if s.State then
			s.State = false;
		else
			s.State = true;
		end 
		s:gen();
	end,
};

knife = obj {
	nam = 'knife',
	inv = 'This is knife',
};

function init()
    inv():add(pocket);
    put(knife, pocket);
    pocket:gen();
end

main = room {
	nam = 'test',
};

Player status

Below is an implementation of player status as a text in the inventory, which cannot be picked.

global {
    life = 10;
    power = 10;
}

status = stat {
	nam = function(s)
		p ('Life: ', life, 'Power: ', power)
	end
};
function init()
    inv():add('status');
end

“walk” from the “exit” and “enter” handlers

You can do “walk” from the “enter” and “exit” handlers.

Dynamically created references.

Dynamically created references can be implemented in various ways. The example below uses “vway” objects. To add a reference one can write:

objs(home):add(vway('Road', 'I noticed a {road} going into the forest...', 'forest'));

To delete a reference one can use “del” method.

objs(home):del('Road');

The “srch” method can check if the reference is present in the scene.

if not objs(home):srch('Road') then
	objs(home):add(vway('Road', 'I noticed a {road} going into the forest...', 'forest'));
end

It's convenient to create dynamic references either in the “enter” handler, or in the arbitrary place in the game code, where they are required. If the reference is created in the current scene, the example can be simplified:

if not seen('Road') then
	objs():add(vway('Road', 'I noticed a {road} going into the forest...', 'forest'));
end

Or you can just enable and disable references with “enable()” and “disable()”, for example:

	seen('Road', home):disable();
        exist('Road', home):enable();

Creating disabled “vobj” and “vway”:

	obj = {vway('Road', 'I noticed a {road} going into the forest...', 'forest'):disable()},

And then enabling them by their index in the “obj” array or by looking them with srch or seen/exist:

	objs()[1]:enable();

Encoding game sources (from version 0.9.3)

If you want hide a game source code, you can encode it with command: “sdl-instead -encode <lua file> [encoded file]” and load encode file from lua with “doencfile”. It's neccessary to keep main.lua as plain text file. So, the recommended scheme is (game is a encoded game.lua ):

main.lua

-- $Name: My closed source game!$
doencfile("game");

WARNING about using luac compiler: Do not use lua compiler luac, it produces platform-dependent code! But game compilation is useful to find errors in the game code.

Packing resources in .idf file (from version 1.4.0)

You can pack all game's resources (graphics, sounds, theme) in one .idf file. Put all resources in 'data' directory and run:

instead -idf <path to data>

The file data.idf will be created in the current directory. Put it in game's dir and remove resource files.

You may pack whole game in .idf:

instead -idf <path to game>

Game in .idf format can be run like any other game (as it was directory) or directly from command line:

instead game.idf

Switching between players

You can create a game with several characters and switch between them from time to time (see “change_pl”). But you can also use the same trick to switch between different types of inventory.

Using the first parameter of a handler

Code example.

stone = obj {
	nam = 'stone',
	dsc = 'There is a {stone} at the edge.',
	act = function()
		objs():del('stone');
		return 'I pushed the stone, it fell and flew down...';
	end

The “act” handler could look simpler:

	act = function(s)
		objs():del(s);
		return 'I pushed the stone, it fell and flew down...';
	end

Timer

Since the version 1.1. 'instead' has a timer object. (Only for sdl version.)

Timer controls through the timer object.

Timer function can return a stead interface command that have to be invoked after the callback execution. For example:

timer.callback = function(s)
	main._time = main._time + 1;  
	return "look";
end
timer:set(100);
main = room {
	_time = 1,
	forcedsc = true,
	nam = 'Timer',
	dsc = function(s)
	return 'Example: '..tostring(s._time);
	end
};

Music player

You can use “set_music” to play sounds setting the second parameter — the cycle counter how many times to play the sound file.

You can write your own music player, creating it from a live object, e.g:

-- plays tracks in random order, starting from 2-nd
tracks = {"mus/astro2.mod", "mus/aws_chas.xm", "mus/dmageofd.xm", "mus/doomsday.s3m"}
mplayer = obj {
	nam = 'media player',
	life = function(s)
		local n = get_music();
		local v = get_music_loop();
		if not n or not v then
			set_music(tracks[2], 1);
		elseif v == -1 then
			local n = get_music();
			while get_music() == n do
				n = tracks[rnd(4)]
			end
			set_music(n, 1);
		end
	end,
};
lifeon('mplayer');

You can use “get_music_loop” and “get_music” functions to remember the last melody and ren restore it, e.g:

function save_music(s)
	s._oldMusic = get_music();
	s._oldMusicLoop = get_music_loop();
end

function restore_music(s)
	set_music(s._oldMusic, s._oldMusicLoop);
end

-- ....
enter = function(s)
	save_music(s);
end,
exit = function(s)
	restore_music(s);
end,
-- ....

From version 0.8.5 functions “save_music” and “restore_music” are already present in the library.

Living objects

If your hero needs a friend, one of the ways is the “life” method of that character, that would always bring the object to the player's location:

horse = obj {
	nam = 'horse',
	dsc = 'A {horse} is standing next to me.',
	life = function(s)
		if not seen('horse') then
			move('horse', here(), s.__where);
			s.__where = pl.where;
		end
	end,
};
function init()
    lifeon('horse');
end

Keyboard

Since version 1.1.0 instead supports keyboard input (works with SDL version only). This can be done using input object.

input.key(s, pressed, key) – keyboard handler; pressed – press or release event; key – symbolic name of the key;

Handler can return a stead interface command. In this case the interpreter doesn't handle a key. For example:

input.key = function(s, pr, key)
	if not pr or key == "escape"then 
		return
	elseif key == 'space' then 
		key = ' '
	elseif key == 'return' then
		key = '^';
	end
	if key:len() > 1 then return end 
	main._txt = main._txt:gsub('_$','');
	main._txt = main._txt..key..'_';
	return "look";
end

main = room {
	_txt = '_',
	forcedsc = true,
	nam = 'Keyboard',
	dsc = function(s)
		return 'Example: '..tostring(s._txt);
	end 
};

Mouse

Since version 1.1.5 instead supports mouse click handling (works with SDL version only). This can be done using input object.

input.click(s, pressed, mb, x, y, px, py) – mouse click handler; pressed – press or release event. mb – mouse button index (1 is left button), x and y – mouse cursor coordinates relative to upper left corner of the window. px and py parameters exist if a picture have been clicked, they contain mouse cursor coordinates relative to upper left corner of this picture.

Handler can return a stead interface command. In this case the interpreter doesn't handle a key. For example:

input.click = function(s, press, mb, x, y, px, py)
	if press and px then
		click.x = px;
		click.y = py;
		click:enable();
		return "look"
	end
end

click = obj {
	nam = 'click',
	x = 0,
	y = 0,
	dsc = function(s)
		return "You clicked a picture at "..s.x..','..s.y..'.';
	end
}:disable();

main = room {
	nam = 'test',
	pic ='picture.png',
	dsc = 'Example.',
	obj = { 'click' },
};

Here is an example of a code layer that implements calling click method in the current room once the picture is clicked:

input.click = function(s, press, mb, x, y, px, py)
	if press and px then
		return "click "..px..','..py;
	end
end

game.action = function(s, cmd, x, y)
	if cmd == 'click' then
		return call(here(), 'click', x, y);
	end
end
----------------------------------------------------------------------
main = room {
	nam = 'test',
	pic ='picture.png',
	dsc = 'Example.',
	click = function(s, x, y)
		return "You clicked a picture at "..x..','..y..'.';
	end
};

Attention!!! From version 1.2.0 it is recommended to use module click.

Call main menu

Dynamic object creation

You can use new and delete functions to create and remove dynamic objects. An example follows.

new ("obj { nam = 'test', act = 'test' }")
put(new [[obj {nam = 'test' } ]]);
put(new('myconstructor()');
n = new('myconstructor()');
delete(n)

new treats its string argument as an object constructor. The constructor must return an object. Thus, the string argument usually contains a constructor function call. For example:

function myconstructor()
	local v = {}
	v.nam = 'test object',
	v.act = 'test feedback',
	return obj(v);
end

The object created will be saved every time the game is saved. new() returns a real object; to get its name you can use deref function:

o_name = deref(new('myconstructor()'));
delete(o_name);

Complex output from event handlers

Sometimes the we need to form event handler output from several parts depending on some conditions. In this case p() and pn() functions can be useful. These functions add text to the internal buffer of the handler. The content of this buffer is returned from the handler.

dsc = function(s)
	p "There is a {barrel} standing on the floor."
	if s._opened then
		p "The barrel lid lies nearby."
	end
end

pn() function adds line feed to the text and outputs the result to the buffer. p() does almost the same thing but adds a space instead of line feed.

There is a function pr() in versions 1.1.6 and later, that does not add anything at end of output.

To clear the buffer you can use pclr(). To return the status of the action along with the text, use pget() or just return.

use = function(s, w)
	if w == apple then
		p 'I peeled the apple';
		apple._peeled = true
		return
	end
	p 'You cannot use it this way!'
	return false; -- or return pget(), false
end

22. Themes for sdl-instead

Graphic interpreter supports theme mechanism. A theme is a directory with the “theme.ini” file inside.

The theme reqiured at the least is “default”. This theme is always the first to load. All other themes inherit from it and can partially or completely override its parameters. Themes are chosen by the user through the settings menu, but a game may contain its own theme. In the latter case the game directory contains its “theme.ini” file. However, the user may override custom game theme. If he does, the interpreter warns him that it disagrees with the game author's creative design.

“theme.ini” has a very simple syntax:

<parameter> = <value>

or

; comment

Pussible types of values are: string, color, number.

Colors are set in the #rgb form, where r g and b are color components in hexadecimal. Some colours are recognized by their names, e.g.: yellow, green, violet.

Possible parameters are:

scr.w = game area width in pixels (number)

scr.h = game area height in pixels (number)

scr.col.bg = background color

scr.gfx.bg = path to the background image (string)

scr.gfx.cursor.x = x coordinate of the cursor center (number) (version >= 0.8.9)

scr.gfx.cursor.y = y coordinate of the cursor center (number) (version >= 0.8.9)

scr.gfx.cursor.normal = path to the cursor picture file (string) (version >= 0.8.9)

scr.gfx.cursor.use = path to the cursor picture of the “use” indicator (string) (version >= 0.8.9)

scr.gfx.use = path to the cursor picture of the “use” indicator (string) (version < 0.8.9)

scr.gfx.pad = padding for scrollbars and menu edges (number)

scr.gfx.x, scr.gfx.y, scr.gfx.w, scr.gfx.h = coordinates, width and height of the picture window — the area to display the scene picture. Interpreted depending on the layout mode (numbers)

win.gfx.h - synonymous to scr.gfx.h (for compatibility)

scr.gfx.mode = layout mode (string “fixed”, “embedded” or “float”). Sets the mode for the picture. If “embedded”, the picture is part of the main window, scr.gfx.x, scr.gfx.y and scr.gfx.w are ignored. If “float”, the picture is placed in the coordinates (scr.gfx.x, scr.gfx.y) and downscaled to scr.gfx.w by scr.gfx.h if larger. If “fixed”, the picture is part of the main window as in “embedded”, but stays above the text and is not scrolled with it.

win.x, win.y, win.w, win.h = coordinates, width and height of the main wiindow. — the area with the scene description (numbers)

win.fnt.name = path to the font file (string)

win.fnt.size = font size for the main window (number)

win.fnt.height = line height as float number (1.0 by default)

win.gfx.up, win.gfx.down = paths to the pictures of up/down scrollers for the main window (string)

win.up.x, win.up.y, win.down.x, win.down.y = coordinates of scrollers (position or -1)

win.col.fg = font color for the main window (color)

win.col.link = link color for the main window (color)

win.col.alink = active link color for the main window (color)

inv.x, inv.y, inv.w, inv.h = coordinates, width and height of the inventory window (numbers)

inv.mode = inventory mode string (“horizontal” or “vertical”). In the horizontal mode several objects may fit in the same line, in the vertical — only 1 per line. (string)

inv.col.fg = inventory text color (color)

inv.col.link = inventory link color (color)

inv.col.alink = inventory active link color (color)

inv.fnt.name = path to the inventory font file (string)

inv.fnt.size = inventory font size (number)

inv.fnt.height = line height as float number (1.0 by default)

inv.gfx.up, inv.gfx.down = paths to the pictures of inventory up/down scrollers (string)

inv.up.x, inv.up.y, inv.down.x, inv.down.y = coordinates of scrollers (position or -1)

menu.col.bg = menu background (color)

menu.col.fg = menu text color (color)

menu.col.link = menu link color (color)

menu.col.alink = menu active link color (color)

menu.col.alpha = menu transparency 0-255 (number)

menu.col.border = menu border color (color)

menu.bw = menu border width (number)

menu.fnt.name = paths to menu font file (string)

menu.fnt.size = menu font size (number)

menu.fnt.height = line height as float number (1.0 by default)

menu.gfx.button = path to the menu icon (string)

menu.button.x, menu.button.y = menu button coordinates (number)

snd.click = path to the click sound file (string)

include = theme name (the last component in the directory path) (string)

The theme header may include comments with tags. Right now there is only one tag: “$Name:”, it contains an UTF-8 line with the theme name. E.g.:

; $Name:New theme$
; modified "book" theme
include = book
scr.gfx.h = 500

The interpreter searches for themes in the “themes” directory. Unix version also checks ~/.instead/themes/ directory. Windows version (>=0.8.7) checks “Documents and Settings/USER/Local Settings/Application Data/instead/themes”

TODO Full list of objects and methods.

Translation: vopros@pochta.ru dofile(dofile …

It's convenient to create dynamic references either in the “enter” handler, or in the arbitrary place in the game code, where they are required. If the reference is created in the current scene, the example can be simplified: