This documentation is probably outdated! Please help us to translate actual documentation from Russian language. Spellchecking and other text improvement are welcomed.
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!
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.
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:
scene_use
mode);scene_use
mode);
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
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>
Сцена (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.]]; }
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
.
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.
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.
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:
code
statements;
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).
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.
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' }; };
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.
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
.
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
.
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:
add(object)
– add an object to the listcat(b, [pos])
– insert content of b
list to the current list into position pos
;zap()
– clear the list;del(object)
– remove an object from the list (if it's not disabled);purge(object)
– removes even disabled object;srch(object)
– searches for the given object in the list. If found, returns the found item and its index; replace(old, new)
– replace the old object with the new one; enable
– enables the object if found;disable
– disables the object, if found;enable_all
– enables all objects in the list;disable_all
– disables all objects in the list.
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.
STEAD provides several functions returninig some frequently used objects. In the functions description following convetions are in place:
what
means an object (including rooms) passed as link, reference or name;where
means an object (rooms too) passed as link or reference;room
is an object of type room
, as link or reference;object
is type obj
object;passage
is a passage contained with way
list, as name, link or reference.Functions returning lists are:
inv()
returns the inventory objects list;objs([where])
returns the objects list of the current scene or of object passed as the optional parameter;ways([room])
returns the passages list for the current room or for one passed as the optional parameter;Function returning lists are usually not used, instead ones from the next section are.
As for the funtions returing objects here they are:
me()
returns the current player object;here()
returns the current scene;where(object)
returns the room or the object containing the referred one placed there with put/move/drop/replace
and similar functions;from([room])
returns the room the player has moved to the current room or to the one passed as the optional parameter;seen(what[, where])
returns the object of the current scene or of the thing passed as the optional parameter, if the object is present there and is not diabled;have(what)
returns the given object if it's present in the inventory and is not disabled;exist(what[, where])
is similar to seen(what[, where])
but finds even disabled objects;live(what)
returns the object if it belongs to alive ones (see hereinafter);path(passage[, room])
returns the passage from the way
list of the current scene or one passed as the optional parameter, even if it is disabled.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
STEAD has a number of high-level functions, that may come useful when writing games. You have already met some of them hereinbefore.
move(what, where_to[, where_from])
moved the object from the current (or optionally given) scene to the one passed in the second parameter.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(what[, where])
put the object from the inventory to the current or the optionally given scene:drop (knife);
The twin function dropf
acts in the same way except of placing the object in the beginning of the destination obj
list.
place(what[, where])
/ placef(…)
place the object to the ending/beginning of the current (or optionally given) scene;put(what[, where])
/ putf(…)
are the obsolete names of place/placef;replace(what, what_with[, where])
replaces the first object with the second one in the current (or optionally given) scene;remove(what[, where])
removes the enabled object from corresponding location;purge (what[, where_from])
is similar to remove but disabled objects may be removed too;take(what[, where_from])
/ takef(…)
removes the object from the current or optionally given scene / given object and adds it to the ending/beginning of the inventory. Actually take
adds the object even if it's not present in the source as well as drop
does. This trick is often used for initial inventory filling in the init
function.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.
lifeon(object[, priority])
adds the object to the dynamic (“alive”) objects list (explained hereinafter), the optional parameter priority
must be a number, value 1 means the highest priority;lifeoff(object)
removes the object from the alive list;taken(object)
returns true
if the object has been taken (with tak
handler or take()
function), false
otherwise;rnd(m)
returns a random integer number in the range from 1
to m
;walk(where_to)
moves the player to the specified scene: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>
change_pl(player)
switches the game to the specified player (which has independent inventory and location). Handlers exit/enter/left/entered
are not triggered. To emulate the just choosen player has moved itself “offscreen” you can change his location in his where
attribute:john.where = 'kitchen'
Or invoke walk()
explicitly just after change_pl()
call.
walkback([where_to])
moves the player to the previous scene or to the given one, from
property will not be changed;back([where_to])
similar to walkback
with a remarks. While going back from a dialog to a room the room handlers dsc
, enter
, entered
are not called; dialog exit
, left
handlers are called;walkin(where_to)
moves the player to the given room, the current room handlers exit
, left
are not called;walkout()
moves the player to the previous scene, its enter
and entered
handlers are not called;time()
returns current “game time” measured in player actions;cat(…)
returns concatenation of it's arguments. Returns nil
if the first argument is nil
;par(…)
returns concatenation of it's arguments separated with content of the first parameter;disable
/enable
/disable_all
/enable_all
are similar to objects same-named methods;visited([room])
return how many times the current or the given room has been visited, or nil
if it never has;visits([room])
similar to the previous one but returns zero instead of nil
;player_moved()
return true
if the player has moved this game tick, usually used in life
handlers (explained hereiafter);stead.need_scene()
will make the engine show the scene static part next game tick (if you don't want use forcedsc
magic). Another way to do this is walking to the same room;stead.nameof(object)
returns object name (nam
attribute);stead.dispof(object)
returns object disp
text, nam
value if it's empty;disabled(object)
returns true
if the object is disabled;stead.call(link, attribute_handler_string_name, parameters…)
calls the handler or returns the attribute value (explained hereinafter);instead_gamepath()
returns the full path of the game directory;instead_savepath()
returns the full path of the saved game files directory;
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 }
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:
pon(t..)
– unhides dialog phrases referred as their indexes or tags t… poff(t…)
– hides the sameprem(t…)
– removes (blocks) the same (removing means the phrase can't be re-enabled with pon(t…)
)pseen(t…)
– returns true
, if all given phrases (by index/tag) are visiblepunseen(t…)
– returns true
if they all are hiddenWhen 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
– call with return: after all subbranch phrases become invisible or pret()
is inboked, the player returns to the caller branch;pjump
– unconditional jump;pstart
– unconditional jump with clearing psub
call stack.
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:
dialog:empty([t])
;dialog:visible([t])
;
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
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
).
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.
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.
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'), }
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)
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')..'.', }
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); ]] ...
Starting from version 1.2.0 you can use modules via “require” function call. At the moment the following modules are available:
require “dbg”
to enable debugger);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', };
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
You can do “walk” from the “enter” and “exit” handlers.
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();
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.
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
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.
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
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 };
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.
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
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 };
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.
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);
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
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: