Table of Contents

Welcome to INSTEAD

The engine for INSTEAD is written in Lua 5.1 and 5.2, therefore knowing this language is useful, although completely unnecessary. In fact, it was the express intention of the creator that new designers should be able to take on interesting projects without any need for engine-level programming.

Throughout its evolution, the engine's capabilities have widened and allowed implementation of games in various genres including arcade games and highly complex text-parsing adventures. It's even possible to launch games written for other engines. However, the original intent was custom text-based adventures, and this remains the primary focus. This is what the present documentation describes, and strengthening these foundation skills is important even if you plan to write a genre-bending game in the future, you clever child. So let's start learning INSTEAD by writing a simple text adventure!

Adventures In Text

Most people associate a text adventure with some text printed on the screen and a few pre-defined courses of action, for example:

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

Some classic games have the player type her actions literally, like this:

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

Both of these implementations have advantages as well as drawbacks. The first approach resembles what's known as the gamebook genre and doesn't suit classic text adventures where a hero could explore a virtual world, free to move and interact with in-world objects. The second approach gives us a more realistic model, but requires more effort from the game developer and her players, making accommodation for human language with complex grammar.

The INSTEAD game engine was intended for games which combine the advantages of both approaches, at the same time attempting to avoid their drawbacks. The game world in INSTEAD is modeled in a similar fashion to the second approach in that there are places (rooms) which the protagonist can visit, and objects (including NPC characters) she can interact with. The player explores the world and can manipulate objects freely. Acting upon objects might remind her of classical graphic quests from the 1990s, yet with a greater depth of features.

Before reading this manual it's recommended you play some INSTEAD games to get an idea how each concept contained herein is implemented. However, it must be noted many old games are written with less than optimal code using deprecated constructions, so studying their code is a treacherous endeavor for the fledgling game developer. Modern structure and syntax enables you to write more laconically, simply, and clearly, so go have fun with some games then return here to pick up best practices.

Basic Anatomy

The game window displays text and images for the room the player is standing in.

The static elements of a room are shown only once, upon entering. Clicking on the room's title will recall them.

The dynamic elements of a room are defined by its objects, including character dialogues. These are always visible to the player, yet can change based on the player's actions.

The player's inventory is represented in a separate panel that contains interactive objects the player can access throughout the game.

Player actions in the game world include:

Where To Begin

A game should have its own primary directory within your system directory, with one main.lua script inside the top level. Other game resources should be placed in this directory as well, and can be organized in sub-directories. This includes Lua scripts, image files, sound files, and savegame files. All references in the code must be made relative to this top level directory.

To save yourself having to search for save files elsewhere, be sure to create a sub-directory named “saves”. Your saved games will automatically be stored here.

At the beginning of the main.lua file a header can be defined with information tags. Each tag should start with – & and end with a closing $ symbol.

The $Name tag contains the game title, encoded in UTF-8 without BOM!, which will be displayed by the interpreter when the player opens the game:

-- $Name: The most interesting game!$

It's also recommended to specify the development version of the game:

-- $Version: 0.5$

As well as the author(s):

-- $Author: Non Anonymous$

If you develop your game in Windows make sure your editor supports UTF-8 encoding without BOM! and use it to write the code.

Since version 1.2.0 you must define the STEAD API version after the header. It's currently version 1.9.1 as of this writing.

instead_version "1.9.1"

Without this line STEAD API will operate in compatibility/legacy mode. The game engine has competent backwards compatibility, but by explicitly specifying a modern API version number it's possible to use features such as snapshots, autosaved global variables, on-the-fly functions, auto-formatting, feature modules, and so on. There's no point writing new games in an old API, although such practice is common. For this reason, it's best NOT to attack game code until you've studied this manual. It will result in a better game.

Modules are included beneath the API declaration:

require "para" --different indents
require "dash" --replaces double hyphen sign with a large hyphen
require "quotes" --replaces quotation marks with chevrons << >>

It's usually a good idea to define default handlers here as well:

-- an impossible attempted action generates 
-- this response by default
game.act = 'Does not work.';
game.use = 'It will not help.';
game.inv = 'Do I really need this?'; 

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

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

The INSTEAD installation directory is where the graphic interpreter looks for available games. The Unix version also checks ~/.instead/games. The Windows version since 0.8.7 checks Documents_and_Settings/USER/Local_Settings/Application_Data/instead/games but from STEAD 1.2.0 on both 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, etc.) it's possible to open a game from any location using the menu option “Select Game” or by hitting F4. If there's only one game in the directory it will be launched by the interpreter automatically, which is handy if you distribute your game with the engine included. Your players can simply launch INSTEAD to start their adventure.

While writing code it's strongly recommended to use indents for nested levels like in the code samples in this manual. It really helps to clarify and decreases mistakes.

Here's a basic template for your first game:

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

Debugging Basics

In the event bugs occur (they will), it's handy to run INSTEAD with the command line parameter -debug which will generate more informative error messages. The -debug parameter can also be specified in the desktop icon/shortcut properties. Right click your INSTEAD desktop icon, select Properties from the dialog menu, then type -debug at the end of the Target: field.

In Windows, debug mode opens a separate console window where errors are output. Unix uses the console where INSTEAD was launched from.

Also you can engage an internal hotkey debugger module (F7) by adding the following line after instead_version in the header of the main.lua file.

require "dbg" 

Debug mode displays the call stack, as well as allows you to quickly restart the game with Alt+R. Combining this hotkey with quicksave/quickload (F8/F9) it's possible to assess changes very efficiently with each edit you write to your code.

In your code you can force output your own custom debug messages using the print() method inside a function placed within an object's act handler:

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

Don't fear the previous example! After reading this manual, and with your own game on the anvil, you'll look at this code with more inspiration.

HINT: It's often useful to examine savegame files during debugging because they contain special stored game state variables.

If you're already an experienced debugger you'd probably like the ability to check your Lua syntax by parsing it with the luac compiler. In Unix just use the -p option at the command line.

On Windows you'll have to install the Lua binaries from here , then run luac52.exe to parse your code.

You can also run a syntax check with your initial program call, as long as you're running version 1.9.2 or higher. Just add -luac to your call.

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

Rooms

A room is a basic game unit in which the player can exist and interact with objects. A room can represent a literal room inside a house or a massive redwood forest.

Every game must contain one variable called main of object type room. This main room is always where the game begins and where the player appears upon starting the game.

HINT: It could also be a custom start menu.

main = room {
    nam = "The Main Room";
    dsc = [[You are in the first room of this game.]];
}

The definition above, signified by the = sign, actually creates an object in the game world, a room simply being a special kind of object. Most things in INSTEAD are objects.

For every object there are two requisite attributes, nam and dsc, which are closed with a semicolon or a comma.

Many more attributes and handlers can come along with these two. They can receive text strings, numbers, booleans, and even functions.

The above example's nam attribute contains a text string that will be displayed at the top of the game window. The nam attribute can be used to target the object from other code. You can often refer to an object by its nam rather than its variable, in this case main, when developing more complex interactions throughout your game.

In fact, nam doesn't have to be output to the screen at all, to be reserved for other uses. The disp attribute, if present, will override it in the graphical interpreter and be displayed at the top of the player's window.

main = room {
    nam = 'Start';
 
    -- displayed in the window
    disp = 'My old room, covered in dust...'; 
    dsc = [[I'd almost forgotten the color of the walls.]];
}

This provides you the developer with multiple names by which you can target an object.

The dsc attribute contains a static description of the object, which gets displayed to the player on her first entrance. To display dsc again she must look at the room explicitly, or if it's your intention for dsc to be shown repeatedly, you can include the forcedsc attribute.

Either put it with your other game definitions at the top of the main.lua file.

game.forcedsc = true;

Or inside the room's attribute list.

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

However, it's not recommended to use this technique because the engine is currently optimized for classic games to which it can't apply.

You can also use stead.need_scene() to force display static dsc in current room wherever you need. Read more in the Auxiliary Methods/Functions section.

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 strings it's better to use double-bracket notation.

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

Line breaks are ignored, so you must use the ^ symbol if you want to display one on the player's screen.

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

Passing Between Rooms

Typical passages look like links at the top of the main story panel in the player's game window. These connections are defined in the way attribute as a list. Rooms are listed by nam reference (or using a variable identifier, as long the room has been defined earlier in the code). This is written the same as an obj list.

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

Two rooms are defined here. It's useful to remember that nam and disp can be written as functions to utilize dynamic room captions, which are generated on the fly, for example in a room whose nam shouldn't be known to the player until he enters. However, there are more appropriate tools for this purpose such as the wroom module discussed later.

When passing between rooms the engine calls the exit handler of the current room and the enter handler of the destination:

    hall = room {
    enter = 'You enter the hall.',
    nam = 'Hall',
    dsc = 'This is one gigantically stupendous hall.',
    way = { 'main' },
    exit = 'You leave the hall.',
};

Like any handler, exit and enter may be defined as functions. The first parameter is the current room. The second parameter is the previous room f for entering, or the destination room t for exiting:

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 for graphical display and/or a boolean for changing a status. In our example the exit handler's function returns false if the player tries to go into the main room from the hall. It means she'll have to find another way, like the Barlog :). The same logic can be applied to the enter and tak handlers.

You can also return both values in a more concise way:

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

Take into account the room pointer here() may not update in sync with the enter call! You can instead use the left and entered handlers which are triggered after the player has passed through. These handlers are recommended if you don't need to forbid passage.

Sometimes you'll want to give a passage a clever title that's distinct from the destination room's nam. There are several ways to implement this, but the easiest is by using the vroom module:

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

Technically the vroom module returns an auxiliary room object with a special enter handler set to bounce the player into the destination room. Yippee!

Sometimes you may need to get a bit more complex by disabling and enabling passages. The engine's concept of passages assumes they are always visible even if blocked, such as a room within a house with a locked door. Therefore it's unnecessary to hide passages.

Instead you can check for a key in the player's inventory, which will be implemented in the enter handler of the destination room. If no key is present, a notification can alert the player and forbid her passing through. You can also create a door as a room object, place it inside another room, and implement unlocking it with a key, to allow the player to pass using the customary way list.

There are situations when you won't want a passage to be obvious to the player, or to make it appear as the result of a special event. For example a clock might be hiding a secret tunnel behind it:

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

So as you can see, an initially hidden passage is created with the vroom() and :disable() methods. You can easily hide objects in lists this fashion, so behindclock can disappear from the room description, but it will still be available to be called later. A useful technique for hidden objects is to initialize them in a disabled state like this. The object isn't processed inside the engine until the :enable() method has returned it.

Semantically, you can also write these enable/disable methods reversed like this:

way = { disable(vroom('In the hours, 'inclock')) };

Then inside the clock object, its act handler invokes the path() method, which references the 'Behind the clock' object vroom aliased just prior, and calls enable() on it. You can also write it this way:

act = function(s)
    enable(path('Behind the clock'))  
    --or, since you don't have to parenthesize solo 
    -- parameters in Lua, you could also write it like this:   
    -- enable(path 'Behind the clock')
    p [[You discover a secret tunnel behind the clock!]];
end;

If the passage we want to toggle on or off is in another room, we can pass it as the second parameter of path().

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

If you'd rather create simple passage variables, you can define them with vroom inside like this:

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 can also toggle a room object itself without employing vroom.

inclock = room {
    nam = 'In the tunnel';
    dsc = [[It's dark here.]];
}:disable(); --simply tacks on a disable 
-- method to the end of the room object
-- the following three methods are also equivalent:
-- }:disable()
-- }; inclock:disable()
-- }; disable(inclock)
 
clock = obj {
    nam = 'clock';
    dsc = [[You see an old {clock}.]];
    act = function(s)
        -- toggles the "inclock" room to true
        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' };
};

Objects

Objects are what the player interacts with. Here are two examples:

tabl = obj { --you can't name it table with an 'e' because 
-- it's a Lua keyword. Be careful with keywords and variables.
    nam = 'table',
    disp = 'Table',
    dsc = 'There\'s a {table} in the room.', -- you can escape 
    -- apostrophes using \ so they 
    -- won't interfere with the string
 
    act = 'Hm... just a table...', -- displays when 
    -- object's hotlink is clicked in the dsc
};
 
salt = obj {  -- this variable can be used 
-- inside the obj list of other objects, 
-- making this object display with them
 
    nam = 'salt shaker'; -- name that can be
    -- used as a reference in some functions
 
    -- displayed in the inventory panel
    disp = 'Salt Shaker'; 
 
    -- displays a highlighted hotlink for anything within 
    -- curly braces, i.e., {salt shaker}
    dsc = 'There\'s a {salt shaker}.'; 
 
    -- displays when player clicks the hotlink above
    tak = 'I took hold of the salt shaker of the table'; 
    -- displays when the player uses the item on another item in her inventory
    inv = 'It's the corner of the table.';
};

Objects are represented in the inventory panel by the nam or the disp attribute, disp taking precedence. Also, disp can be set to false which makes the object invisible in the inventory.

Text enclosed with curly braces { } inside the dsc attribute will be displayed as a hotlink in the graphic interpreter.

The act event handler must be a string, a boolean value, or a function returning one of these two.

WARNING: Some objects (table, io, string, etc…) are reserved in the Lua namespace. Be careful not to use any reserved words when choosing variable identifiers for your objects. In modern INSTEAD versions this problem is almost solved. However, you still will not be able to use variable identifiers that match INSTEAD constructors (obj, game, player, list, room, dlg, etc…)

Adding Objects to Rooms

To include an object to a room or another object add the intended object's variable to the target room/object's obj table. For example salt can be bound to the tabl object this way:

Add references to the obj table to place corresponding objects at the room:

tabl = room {
    nam = 'Table',
    dsc = 'It\'s a large kitchen table with a bowl of {salt}.',
    obj = { 'salt' },
};

You can also use an unquoted variable identifier instead of a quoted reference, but you must define the object before defining the room in order for this to work. References have no such limitation, therefore using references is highly recommended.

Separate multiple objects within a list with commas or semicolons (standard Lua delimiters):

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

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

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

You can also use functions to place objects in rooms, which will come later.

Objects Referencing Objects

Any object may have an obj list. The parent object displays its dsc then carries out the same operation for each of its children inside obj. For example let's place a bowl on the desk.

main = room {
    nam = 'A Kitchen',
    dsc = 'You walk inside.',
    -- These references can be defined after because 
    -- they're housed within quotes
    obj = { 'window', 'desk' },
};
 
window = obj {
    nam = 'Window',
    dsc = [[Diffused daylight pours through a 
            round porthole in the middle of the ceiling.]],
};
 
desk = obj {
    nam = 'Desk',
    dsc = [[There's a desk in the center of the room.]],
    obj = { 'bowl' },
};
 
bowl = obj {
    nam = 'Bowl',
    dsc = [[On the surface there's a metal
        bowl covered in strange engravings.]]
};

First the player will see the room dsc with a large line break, then the window's dsc, the desk's dsc, and finally the bowl's dsc (it's the desk's child object).

Also if you move an object to another room, nested objects will be moved with it. This is part of the referencing mechanism: an obj table stays bound to its parent object. So be diligent about keeping track of nested objects.

Using Objects On Objects

Players often try to use objects in their inventory on other objects. To satisfy this basic urge, the use handler can be invoked for the held object, and used for the targeted object. For example:

knife = obj {
    nam = 'knife',
    dsc = [[There's a {knife} on the chair]],
    inv = [[It's sharp!]],
    tak = 'I took the knife!',
    use = 'You try to use the knife.',
};
chair = obj {
    nam = 'chair',
    dsc = 'There is a {chair} in the room.',
    act = 'Hm... Just a chair...',
    obj = { 'apple', 'knife' },
 
    --you monster!
    used = 'You try to hurt the chair...', 
};

If the player takes the knife and uses it on the chair, he gets the text of knife's use handler along with the chair's used handler. These two can also contain functions. The first parameter s refers to the object itself, and the second parameter w is the target object if you're defining the use handler, the affected object if you're defining the used handler.

knife = obj {
    nam = 'knife',
    dsc = 'There is a knife on the {chair}',
    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 chair.';
        end
    end;
};

In the example above you can only use the knife on the chair. If use returns false then used won't be invoked, and its return value is ignored. Also, if these two handlers aren't defined or they return nothing the default handler game.use is called.

It's your choice which one to implement in any given situation, but it's a good idea to place the handler near the object it affects. For example, a trash bin that allows the player to throw anything inside makes sense to employ the trash bin's 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;
}

Bugs often occur when use and used are implemented by two unintended objects the player decides to play with. For example, if the player has a knife you intend for her to use on an apple, but she decides to cut the trash bin, the knife will vanish. We'll need to rework the knife's handlers to correct this behavior. The nouse handler should be implemented:

..........................
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 will be called by the engine if neither use or used returns something adequate. If nouse isn't called, then the noused handler within the affected object will searched for and called. If none of these result in an adequate return value, then the default handler game.nouse will be deployed.

All of these usage handlers can be functions, each with three arguments (the handler owner object, the actor object, the target object).

NOTE: The nouse handler actually redefines game.use so you must switch to its alternatives such as game.nouse.

It's also possible to use a scene object (placed in scene, not in inventory) on other objects. If you are going to implement this mechanism, you need to set an object or global game.scene_use boolean attribute scene_use, or build a function that returns a boolean value.

stone = obj {
    nam = 'stone1';
    var { pushed = false };
    scene_use = true;
    dsc = [[I see the heavy {stone} on the floor.]];
    use = function(s, w)
        if w == door then
            s.pushed = true
            p [[I pushed stone to the door.]]
        else
            p [[I do not want to do a hard work!]];
        end
    end;
}
-- here, this object in scene can be used on other
-- objects in scene, like the inventory item...

The Player Object

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

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

You can target the current player object with the me() pointer. In most instances me() == pl.

The obj table inside pl represents the player's inventory. Also, if you want the player to have special qualities you can implement them in the var table (such as power, speed, etc.):

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

You can even create multiple players (pl2, pl3, pl4, etc.) and alternate between them using the change_pl() function. It only has one parameter, which targets the player object being switched to. Using this function will change the current game location to whereever the target player is located.

Inventory

A player's inventory exists inside the player object. The easiest way to put something there is to define its tak handler.

apple = obj {
    nam = 'apple',
    dsc = 'There is an {apple} on the table.',
    inv = function(s)
        inv():del(s); --//inv()// is 
        -- a pointer that you can use in the shorthand 
        -- Lua syntax describes before to target the 
        -- inventory of (s) which refers to apple
	return 'I ate the apple.';
    end,
    tak = 'You took the apple.', -- same as 
    -- the act handler, only it puts the 
    -- target object in the inventory panel
};

Examine the inv handler above. After the object has been take, the player can click it in her inventory panel. The apple is then removed from the inventory and the returned action text is displayed:

'I ate the apple.'

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

apple = obj {
    nam = 'apple';
    dsc = 'There is an {apple} on the table.';
    inv = function(s)
        remove(s, me()); -- remove(parent target, consumer) 
	-- me() is the player pointer. The effect of 
        -- consuming an inventory item can be defined
	p 'I ate the apple.'
    end;
    act = function(s)
        take(s) --take() is another method
        p 'You took that apple.';
    end;
};

If an object doesn't have an inv handler then game.inv will be called.

You should also be aware that the term “inventory” is loosely defined in the game engine, so the inventory panel could even house abstract objects such as interactive command buttons and status meters. Go wild.

Multiple Inventory Presets

In fact, you may use multiple characters for switching between different types of inventory. Like this:

instead_version "2.1.0"
pl1 = player {
    nam = '1';
    where = 'main';
}
pl2 = player {
    nam = '2';
    where = 'main';
}
inv_sw = menu {
    nam = function(s)
        return ('Inv:'.. stead.nameof(me()))
    end;
    menu = function(s)
        if me() == pl1 then
            pl2.where = here()
            change_pl(pl2)
        else
            pl1.where = here()
            change_pl(pl1)
        end
    end
}
apple = obj {
    nam = 'apple';
}
knife = obj {
    nam  = 'knife';
}
function init()
    change_pl 'pl1'
    place(inv_sw, pl1)
    place(inv_sw, pl2)
    place(apple, pl1)
    place(knife, pl2)
end
main = room {
    nam = 'room 1';
    dsc = 'room 1';
    way =  { 'r2' }
}
r2 = room {
    nam = 'room 2';
    dsc = 'room 2';
    way = { 'main' }
}

The Game Object

The game itself is represented as the object game of type game. The engine defines it as follows:

game = game {
    codepage = "UTF-8",
    nam = "INSTEAD, version "..stead.version.." '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 = 'p1',
    showlast = true,
    _scripts = {},
};

As we can see, the game object holds the current player pl reference and some settings. At the beginning of your game you can set the text encoding to anything you want, but keep in mind it's better to just switch your editor to UTF-8 without BOM! encoding and work with the game engine. Changing the encoding is useful when running games written for other platforms (like URQL).

game.codepage = "cp1251";

The game object also contains your default handlers for act, inv, use (It's best to always define them!).

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

NOTE: Don't forget that the nouse handler reserves the game.use default handler for its own peculiar needs, so if you implement nouse then you'll need to to include a game.nouse handler as a fallback for use to prevent bugs.

The Timer Object

Since version 1.1.0 of the SDL version, the game engine has incorporated a timer object.

timer:set(ms)  --sets the timer interval in milliseconds
timer:stop()  --stops the timer
timer.callback(s)  --creates a callback for the timer, calling it in a fixed time interval

You can create a function that will return a stead interface command invoked after the callback's 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;
};

Lightweight Objects

Sometimes we need to fill a room with non-functional decorations to make the game world seem more diverse. Lightweight objects may be used for this purpose. For example:

sside = room {
    nam = 'southern side',
    dsc = [[I walk to the southern wall of the building. ]],
    act = function(s, w)
        if w == "porch" then  -- this references
        -- the lightweight objects created 
        -- below in the obj list
            ways():add('stolcorridor');
            p [[There's an entrance with a porch. 
               The sign on the door reads 'Canteen'. 
               I'm not hungry... Should I go in?]];
        elseif w == "people" then
            p "The people leaving look quite content.";
        end
    end;
    obj = { 
           -- this is a lightweight object
           vobj("porch", "There is a small {porch} by the eastern corner."),  
           --another lightweight
	   vobj("people", [[From time to time the porch 
                      door slams letting {people} in and out..]])
     },
};

The method vobj() allows you to create a lightweight version of a static object. It can be interacted with by means of its parent's act handler which assesses the child's name. The vobj() method will call the used handler. The third parameter can call a separate object to interact with it. The vobj() syntax is simple:

vobj(name, description, interactive_object)

NOTE: Lightweight objects often don't have handles to help you with interactions via inventory items, etc., so if you want the player to be able to access them you can define those behaviors within their use handler, via the stead.nameof() method, like so:

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

One useful trick is adding vobj() to the room dynamically with a place() method:

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

But this style is old-fashioned. Instead try using a disable() or enable() method within your static object descriptions, like so:

obj = { vobj("cont_button", "{Continue}"):disable() };
.......................................................
exist('cont_button'):enable();  -- returns the object 
-- if it's present regardless of "able" state, then enables it

A useful permutation of vobj() is vway() which creates a reference leading to a specified room. Here's the syntax:

vway(name, description, destination_room)

And an example implementation in an obj list:

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

If you're creating a game in the storybook genre where the gameplay consists of wandering from one link to another (which is probably a poor decision for your first game!) you should utilize the xact() module. It provides a simpler mechanism for creating references and also has the xwalk feature.

instead_version "1.9.0"
require "xact"
 
main = room {
    nam = 'Begin';
    forcedsc = true;
    dsc = [[ Begin {xwalk(startgame)|adventure}? ]];
}
startgame = room {
    dsc = [[ A long time ago ...]];
}

Using simple methods like add() and del() you can dynamically fill a room with vway() objects:

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

This behavior is due to vobj() and vway() being generic objects with predefined handlers and save methods, which allows on-the-fly creation. Knowing the engine architecture will help you implement object variants like these.

In addition to using lightweight objects for scene decoration, you can also define a static object directly in the obj list without a handle:

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

You can also target such objects via stead.nameof() within the use handler, just like you did with vobj() lightweight objects:

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

FIXME (NEEDS AN EXAMPLE)

You can also use a single object in multiple rooms, such as shotgun shell which gets duplicated as it's dropped into the player's current room each time she fires her shotgun. In this case the shells might merely serve as decoration without being interactive.

Dynamically Spawning Objects

NOTE: It's recommended you study constructors before taking on this supplementary concept.

You can use the new() and delete() methods to create and remove objects dynamically. The new() method can accept a string argument that's written in the form of a constructor, which will of course return an object. Yay!

-- adds a constructor definition to the code
new ("obj { nam = 'test', act = 'test' }");

Better yet:

-- places a new constructor in-world
place(new [[
        function myconstructor()  
        local v = {}
        v.nam = 'test object',
        v.act = 'test feedback',
        return obj(v);
    end;]]);

The constructed object will be saved every time the game is saved.

Something else to have fun with is the fact that new() adds a real object to the world, so you can find its variable identifier using the deref() method then do whatever you want with it, like delete it.

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

Dynamically Creating References

NOTE: This is an advanced topic so there's no need for beginners to study this section in detail.

Dynamically created references can be implemented in various ways. The example below uses vway objects. Notice, it's most convenient to create dynamic references in the enter handler, but you're free to leave them wherever they fall in your code.

To add a reference you can use the add() method.

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

To delete a reference you can use the del() method.

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

The srch() method will check if the reference is present in the target room.

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

If a reference is created in the current room, the previous 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 manage references with enable() and disable().

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

Creating initially disabled vobj and vway references works like this:

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

Enabling them by their index in the obj table or by finding them with srch is similar.

objs()[1]:enable();
-- but better variant is
exist 'Road':enable();

Attributes and Handlers

Most attributes and handlers can be defined as functions. For example the nam attribute could just be defined as a string like so:

nam = 'apple';

Or you could be fancy and define it as a function that returns that string, like so:

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

Keep in mind the nam attribute must be defined as a string, even if you put some extra mojo inside its definition.

Strings passed to any of the p() methods are accumulated in an internal buffer for that specific attribute/handler. The accumulated content is passed back to the engine when the attribute/handler reaches its return. With this you can build a long text out of sequential calls to p()/pn()/pr().

NOTE: The engine performs general formatting with spaces and line breaks to separate corresponding text parts. So, usually you do not need to do manual formatting. Use pxxx-functions to format a single attribute value.

The principal difference between handlers and attributes is that handlers can change the game world's state, while attributes cannot. So, if you define an attribute as a function remember an attribute's only purpose is to return a value. This is because the calling of attributes within the engine as a player roams around freely is often unpredictable and isn't bound to a specific game process, so their application within mechanisms is strongly discouraged.

Something to note about the always mechanism-friendly handler is that it cannot pause the engine to wait for an event to happen. That means you can't employ delay loops or other game delay mechanisms inside the handler itself. They're intended to change the game state and immediately return control to the game engine. The interpreter must deliver the handler's changes quickly so it can continue listening for player input. If you need to delay something use the timer or cutscene modules.

Handler functions almost always contain conditional statements when dealing with variables.

apple = obj {
    nam = 'red apple',
    dsc = function(s)
        if not s._seen then
            p 'There is {something} on the table.';  --first entrance description
        else
            p 'There is an {apple} on the table.';  --alternate description
        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 first parameter in a function (s) always refers to the parent object it self. When the player enters the room from this example her display will read “There's something on the table.” When the player clicks something the _seen variable inside the apple object will be set to true, which plays in nicely to the dsc function above it.

The stored variable _seen has an underscore at the beginning to signify it should be saved in the player's savegame file automatically. Now every time she returns to the room the game knows she's already seen the apple and will display the second description.

When an if statement is built without an operator ==, ⇐, ~=,…, the variable is judged as a boolean value. Therefore, being defined as anything other than false means it's true. So for example:

if s._seen then  --asks "Does s._seen exist and is it not false?"
    ACTION  --if the first statement is true this is performed
else  --deduces that s._seen doesn't exist or was defined as false
    ACTION  --so this is performed instead
end

When a numeric variable hasn't been defined and it's used in a conditional expression like the one above, the engine assumes the variable equals emptiness nil. You can check if a numeric variable exists with a similar code like this:

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

The nil variable is also treated as false in conditional expressions:

if not z then
    p "Variable z is not defined or false."
end 

But:

if z == false then
    p "Variable z is false." -- z == nil will not pass this condition!!!
end 

NOTE: The z variable above could be undefined and so, condition z == false will not work, that might produce a bug in your game logic, so double check!

You can also cascade conditional statements using then as a delimiter, like so:

if not z then 
    if z ~= nil then
        p "Variable z equals false."
    else
        p "z is undefined! Sell your property and go hunt bugs!"
    end
end

Take these behaviors into account when writing and debugging your game. If you have a misprint in a variable name, the expression will be evaluated as “undefined” with no error report. Your game logic will be broken and it will be difficult to find the error later.

On-The-Fly Attribute Generation

Considering the dynamic room dsc in the above example is generated with a function, why not alter the dsc on the fly? Actually this won't work unless you define the dsc in the room's var table like this:

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'}
};

It's highly recommended to NOT do this. Instead, write your attributes themselves as functions, and refrain from changing their values externally. Such programming style is costly, since you're making future modifications difficult by not keeping attribute values inside the objects they describe, and your savegame files will consume much more disk space.

Custom Attribute Lists

Standard attribute lists such as way or obj store objects in a table and manage themselves with a pre-defined set of methods, but lucky you can create lists for your own needs, and they don't have to be defined through var or global. For example:

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

Your available list methods are:

The methods add, del, purge, replace, srch and others can receive objects by nam as well as by a referenced link.

A simple example involves the system method inv() that references the player's inventory list, which can be quickly manipulated with a list method like so:

inv():del('apple'), 

You could even cleverly recreate the take functionality inside the act handler using list methods:

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,
};

The system method objs() returns the list containing all current room objects, or if given a parameter it can target any room. The corresponding method for getting the list of passages for a given room is ways().

Starting in version 0.8 the game object can be used as a parameter for the add() method. Also there's an optional second parameter that targets a specific position in the receiving list. You can now also set a list's values by index using the set() method. For example:

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

Starting in version 0.9.1 the enable_all() and disable_all() methods work with embedded objects (objects within object), and the methods zap() and cat() became available.

Starting in version 1.8.0 the methods disable() and enable() can be used, however…

NOTE: It's highly recommended when manipulating objects and inventory to use higher level methods such as:

place/get/take/drop/remove/seen/have

The Handler's Text Buffer

Sometimes we need to format output from a handler based upon a prescribed set of conditions. In such a situation the p() and pn() methods are quite useful. These two methods add formatted text to the internal buffer of the handler, which gets returned by the handler in one big chunk.

dsc = function(s)
    p "There's a {barrel}." --this gets buffered, with whitespace
    if s._opened then
        p "Its lid is leaning against the wall."  --this gets tacked onto the first one
    end
end;

One way to update the player with the status of some action is via pget() in the return.

knife = obj {
    ...
    use = function(s, w)
-- (w) is another object the player 
-- is using the knife to interact with
    if w == apple then  
        -- so if it's an apple
        -- she says she peeled it
        p 'I peeled the apple'; 
        -- and the apple stays peeled
        apple._peeled = true
        return
    end
    p [[You shouldn't use a knife like that.]]
    return pget(), false;  -- false is to stop call 'used' method
end;

No-Output Handlers

Sometimes we need a handler doing something without producing any output.

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 [[This button hasn't been pressed.]];
        else
            p [[The button has been pressed.]];
        end
    end,
    obj = {'button'}
};

Here the act handler alternates the room description, so there's no need for it to print anything itself. It's a contrived example, and you'll likely never need handlers with no output. As for the above example, you could instead print “Pressed the button.” with the act handler's function. Moreover, the example requires forcedsc mode, which breaks backward compatibility. However, such a handler could at some point be useful, so it's worthwhile to be aware of it.

You could also return only true from an act handler, which would mean an action completed successfully but didn't require any output. If you need some kind of report for the player, just don't return anything, which will display your default game.act text. Problem solved.

Handler Calls Using Lua

Sometimes you need to call a handler manually. You can use the shortform Lua syntax for method calls object:method(parameters) if the handler act, tak, inv, etc., was written as a function within the target object:

-- the function in the act handler of apple is called
apple:act()

For more clarity here is the longform of the Lua syntax:

-- so the shortform above simply erases the (self) parameter 
-- since it's redundant, and replaces the period with a colon
apple.act(apple)

If the object's handler is not a function you can utilize the stead.call() routine to call the handler in the same manner as the interpreter does. The syntax is: stead.call(object, 'name of handler/attribute', arguments…) So, for example, to get nam value of the object apple you can do this:

act = function(s) -- somewhere
    local r = stead.call(apple, 'nam')
    p(r)
end

In fact, there is the stead.nameof() for this case, but this is just an example.

Dynamic Events And Life Simulation

Handlers can be programmed to execute as certain player conditions are met. This technique is often used to run background processes such as life simulation. For this purpose a special event handler exists, called the life handler.

Any object, including rooms, can employ a life handler, which is called every time the game clock advances as long as the object has been added to the worldwide list of living objects (more on this in a moment). Remember, the game clock is the total number of player actions so far in the game.

Dynamic events follow this rough algorithm for each tick of the game clock:

  1. The player does something in the game, which increments the game clock
  2. The life handlers for all living objects are triggered
  3. Relevant object's commence their life-like behaviors

To illustrate a simple living object, first let's give Barsik the cat some animation.

mycat = obj {
    nam = 'Barsik',
    behaviors = {
        [1] = 'Barsik peers out of my coat.',
        [2] = 'Barsik kneads my chest. OWWW!',
        [3] = 'Barsik purrs against me.',
        [4] = 'Barsik yawns.',
        [5] = 'Barsik\'s warmth is welcome.',
        [6] = 'Barsik escapes from my coat.',
    },
    life =  function (s) 
        local r = rnd(5);
        if r > 2 then  
            return;  --provides a 60% chance of no behavior
        end
        r = rnd(#s.behaviors);  --the # symbol gets the last index number
           p(s.behaviors[r]);  --displays a random behavior
    end;
};

Now that Barsik looks like a living creature, let's make it final.

lifeon('mycat');

Okay, like you were told before, this is the part about the worldwide list of living objects. The lifeon() method as seen in the code above, adds Barsik the cat to this list. Don't forget to remove deceased objects from the list with lifeoff(), usually via the exit handler.

If there are a dizzying number of living objects in your game, you may want to assign priority levels. The second parameter of lifeon() takes a positive integer, with 1 being highest priority.

lifeon(object_name, positive_integer);

To initialize a life simulation background process for a particular room, simply include lifeon() in an object's entered handler and lifeoff() in the left handler, then define the process in the life handler.

cellar= room {
    nam = 'in the basement';
    dsc = [[It's dark down here.]];
    entered = function(s)
        lifeon(s);
    end;
    left = function(s)
        lifeoff(s);
    end;
    life = function(s)
        if rnd(10) > 8 then
            p [[I heard something rustling!]];  --scare tactics!
        end
    end;
    way = { 'upstair' };
};

You can discern whether the player has crossed a room's border by employing the player_moved() method inside the life handler.

flash = obj {
    nam = 'flashlight';
    var { on = false };
    life = function(s)
        if player_moved() then  
            -- extinguishes the flashlight on room changes
            s.on = false
            -- let's the player know what happened
            p "My light went out..."
               return
        end
    end;
	...
};

To track continuous events you can use the time() method, or utilize a custom counter variable. To track player location use here() function. Use these in conjunction with the live() method, which checks if an object is still living.

dynamite = obj {
    nam = 'dynamite';
    dsc = 'BOOM!';
    var { timer = 0 };
    used = function(s, w)
        -- checks if the player uses their zippo lighter
        if w == zippo then
            if live(s) then
                --checks redundant
                return "It's already lit."
            else
                -- the player lights it
                p "I ignited the fuse."
                -- the dynamite is added to list of living objects
                lifeon(s)
            end
        end
    end;
    life = function(s)
        -- if it's living/lit the timer starts
        s.timer = s.timer + 1
        if s.timer == 5 then
            -- when it reaches 5 the dynamite dies
            lifeoff(s)
            if here() == where(s) then
                -- checks if player is in the same room
                p [[...yeah I'm dead.]]
            else
                p [[I heard an explosion somewhere.]];
            end
        end
    end;
};

If a life handler returns a string, it displays below the room's static description by default. To make the text appear above, simply return true as it's second value.

life = function(s)
    return "The guardian entered the room.", true;
end;

If you want to prevent all life processing in a particular room, add the nolife module to your game.

instead_version "1.8.2" --needs to be this version or higher
require "hideinv"
require "nolife"
...
guarddlg = dlg {
    nam = 'Guardian';
    hideinv = true;
    nolife = true;
	...
};

WARNING: When putting the walk() method inside a room's life handler you should take into account whether the life handler will affect player location. If so, any results from the handler will will be discarded at the point of player movement, then her new location's life handler will activate. This can get confusing, so it's best to implement walk() carefully.

The life handler is capable of removing all act handler text from the display, in order to let more pressing events dominate the player's focus. Imagine the player is examining a regular window in a boring room…

window = obj {
    rainrain = false;
	...
    act = function(s)
        s.rainrain = true;  --the variable that triggers the goblin
        return "It's probably about to rain. I wonder where my umbrella is..."
    end;
};

Suddenly an evil goblin enters the room and runs right for her. In such a panicky situation, the window's act handler telling her about rain and umbrellas is useless information. The following code removes it and allows the goblin's life handler to take center stage.

goblin = obj {
    ...
    life = function(s)
        if rainrain == true then 
            p("A goblin's running toward me!");
            -- removes all act handlers' text from the display
            ACTION_TEXT = nil;
        end;
    end
};

The ACTION_TEXT variable is unique to life handlers because of their clock-based routine, and allows them to change the output of all act handlers in the room at once. It's usually best to simply clear it with nil. This way the player can focus on the goblin. :O

Methods/Functions

The game engine provides several standardized methods for returning frequently used objects (Methods are also called functions, but this manual tries to reserve the term “function” for your own code definitions, and tries to use the term “method” for anything pre-defined within the engine, simply to make it easier for young developers to discern what's being addressed in this manual).

The following terminology is used throughout this section, so refer to it as you study the methods themselves.

Common Terminology

Methods Returning Lists

Methods Returning Objects

The methods used in the following example are usually used in conditional statements or to find objects for subsequent modification.

exit = function(s)
    -- remember that Lua allows solo parameters to go without parentheses
    if seen 'monster' then
        p 'The monster blocks your way!'  --another solo parameter
        return false
    end
end;
	...
use = function(s, w)
    if w == window and path 'Through the window':disabled() then
        -- checks whether actions on the window and 
        -- its corresponding passage are currently 
        -- disabled in the game
 
        --enables them
        path 'Through the window':enable();
 
        -- dramatizes with descriptive fiction. Yay!
	p 'I broke the window!'
    end
end;
...
act = function(s)
    if have('knife') then
        p 'But I have a knife!';
        return
    end
end;

Two useful system methods are:

The first returns a link to the object passed by reference, the second one returns a reference to the object passed directly. This might seem confusing now, but you'll soon understand the concept of references versus objects. Here's an illustration:

-- if apple is defined, the reference is equal to the variable itself
stead.ref 'apple' == apple

Here's a simple example of stead.deref() in action:

act = function(s)
    -- this takes the object, s,  that was passed to the 
    -- function directly and returns a reference so that 
    -- the print method can concatenate it with its string
    p('You clicked the object '.. stead.deref(s));
end;

Code Statements

A code statement is useful for defining a very short function. To save them you can assign them to a stored variable in the global or var tables. Here's an example of a short code function called in an act event handler:

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

You can also redefine code functions on the fly, although this is usually an example of bad programming.

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

Invoked code statements can create objects automatically. They are also “self” variables that target the object that contains them. An args[] table holds all the code statement's arguments.

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

NOTE: The code statement above is already surrounded with double brackets, so if you need to write a multi-line string literal inside a code statement you must use nested brackets such as [=[ ]=], [==[ ]==], [===[ ]===], etc. The same is needed for delineating line breaks with the ^ symbol. Study the Lua syntax rules for more detail.

Temporary Values

Sometimes you may need to create an auxiliary variable in the var table to store a temporary value, for example:

kitten = obj {
    nam = 'kitten';
    var { state = 1 };  -- state variable
 
    --(s) refers to the entire kitten object, and 
    -- this act handler function is triggered by each click
    act = function(s)
        -- increments the "state" temporary value 
        -- by 1 with each click
        s.state = s.state + 1
        -- iterates until it reaches 3
        if s.state > 3 then
             -- resets it to 1, creating a clickable looping effect
             s.state = 1
	end
        p [[Purrr!]]
    end;
    -- this dsc attribute function's table is related 
    -- to the handler function above through the index
    dsc = function(s)
        local effects = {
            "The {kitten} is purring.",
            "The {kitten} is playing.",
            "The {kitten} is washing up.",
        }; 
        -- Now the index corresponding to the 
        -- click loop in "act" can be printed to the player's screen
        -- s.state is a numerical index for the 
        -- effects table which has 1,2,3 (a Lua index begins at 1)
        p(effects[s.state])
    end;
}

In the dsc function above, a local table is created. The keyword local constrains the table's visibility to within the function itself, meaning nothing outside can see or use it. And local variables are not written in save files. You should define all auxiliary/temporary variables as local.

The example could also be written like this:

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
end;

Or like this with an external function:

function mprint(n, ...)
    -- temporary table for the function arguments
    local a = {...};
    -- prints the nth element of the temp table
    p(a[n])
end;

Which can be called inside the dsc attribute:

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

To simplify it all you could write it like this:

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

Saving Game Variables

Savegame files store the delta (change) between the initial and current game state. They can store these types of variables:

There are three ways to create a stored variable.

  1. Write the variable name with an underscore character at the beginning (which we saw in the previous section with _seen)
  2. Define the variable in a “var” table inside some object
  3. Define the variable in a global table

Using var and global is more intuitive so it's recommended.

Defining the global table is simple:

global { 
    -- a number stored in the savegame file
    gl_var = 1;
    -- another number
    gl_num = 1.2;
    -- a string
    gl_str = 'hello world';
    -- a boolean
    knows_truth = false;
}

Defining a var table inside an object is also simple:

main = room {
    var {
        -- this will be stored in the savegame file
        i = "a";
        z = "b";
    };
    nam = 'My first room.';
    var {
        -- also stored
        new_var = 3;
    };
    dsc = function(s)
        --(s) refers to main, so s.i refers to 
        -- the variable "i" that's housed in 
        -- main's var table
        p ("i == ", s.i);
        -- global elements are mapped to the 
        -- global namespace. It's as if "global"
        -- and "var" tables are excluded from the 
        -- object tree
        p ("gl_var == ", gl_var);
    end;

Every element inside the var and global tables must have an initial value. A system dummy object called null will serve as a default placeholder if you don't define a value. It can be replaced by your game code later.

Auxiliary Methods/Functions

The game engine has a number of high-level methods that may be useful to you. You've already encountered some of them.

-- moves the object from the current 
-- or given room to a given destination
move(what, where_to[, where_from])

If you want to move an object from an arbitrary room you must to know its location. For implementing objects moving in a complicated fashion you could write an advanced function to track the object's location as it moves between distant rooms, or you could locate the object with the where() method each time, like so:

move(mycat, here(), where(mycat))   --mycat travels with me

NOTE: You must handle the moving object with a place() method in order for the where() parameter to work.

The movef() method is similar to move() but the target object is sent to the beginning of the destination obj list.

The twin methods drop()/dropf() and take()/takef() act in the same way except they focus on the player's inventory.

The four methods above can materialize objects in the target obj lists even if the object wasn't present in the source location. This trick is often used for filling the player inventory in the init function.

function init()
     -- this knife doesn't exist anywhere, 
     -- it simply materializes in the player's 
     -- inventory list. Yay!
	take('knife'); 
end;

The following methods all work in a similar fashion to those above.

The methods above can be applied directly to rooms and objects as well.

-- uses the me() pointer to access the 
-- 'pl' object's inventory list
remove(apple, me())

Some methods above have variations suffixed with to which receive an additional parameter, the specific position for the object to be inserted into the target list.

More methods include:

You will often move the player to different rooms. It's important to mention that calling walk() triggers a corresponding exit/enter/left/entered handler, which may occasionally cancel the player's movement. Continue reading to learn how to manage this situation when it arises.

Here's the basic format for walk() followed by an example:

.......................................
act = code [[
    pn "I'm going to the next room..."
        walk(nextroom);
]]

Here's an example of passing a function reference to the walk() method:

mycar = obj {
    nam = 'my car',
    dsc = 'In front of the cabin there is my old Toyota {pickup}.',
    act = function(s)
        walk('inmycar');
    end;
};

NOTE: walk() doesn't interrupt the parent handler's execution, so you should typically place your function's return operator immediately after walk() like this:

act = code [[
    pn "I'm going to the next room..."
    walk(nextroom);
    return
]]

To change players:

change_pl(player)
-- switches the game to the specified player, 
-- who has an independent inventory and location

When changing players the exit/enter/left/entered handlers are not triggered.

If you'd like to simulate offscreen movement for an inactive player without having to change players, you can simply change her location using the where attribute:

jane.where = 'Living Room'

Here are even more auxiliary functions:

In cases where forcedsc is incompatible with your intentions you can use the following method to maintain compatibility:

Text Formatting

You can accomplish simple text formatting with the following methods:

For example:

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

You can define a special text style with these methods:

For example:

main = room {
    nam = 'Intro',
    -- only boldfaces the word "main"
    dsc = 'You are in the room: '..txtb('main')..'.',
};

Since version 1.1.0 you can create unwrapped strings using the txtnb() method.

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

Constructors and Inheritance

WARNING: If you're writing your first game you should make it simple, that is to say the information in this section of the manual is not important. Moreover, 90% of the games written for INSTEAD don't use anything in this section! That's not to say it's not a good section.

The idea of constructors should come to mind once your (second/third/tenth) game has a lot of similar objects. You might say to yourself, “I really would love to simplify this code…” Constructors are a method for automating common pieces of code. In fact the obj, room, and dlg handlers are constructors themselves.

When you write something as an obj a constructor method calls obj as a parameter which passes the table {nam = 'name'}.

apple = obj {
    nam = 'apple';
    dsc = "It's a nice apple.";
};

Say you need a window in your game. Realizing that real windows can be broken, you can write a constructor that will manifest all windows with this realistic behavior, saving yourself from ever having to code it again.

-- first create a variable as a function
window = function (v)
    -- pass itself in as true
    v.window = true
    -- assess whether nam is empty
    if v.nam == nil then
        -- if so, fill it with this generic title
        v.nam = 'window'
    end
    -- assess whether dsc is empty
    if v.dsc == nil then
        -- if so, fill it with this phrase
        v.dsc = 'There is a {window}'
    end
    -- function to define the default act handler 
    -- regardless of whether it's empty
    v.act = function (s)
        -- assess whether the act handler's _broken variable is set to true
        if s._broken then
            -- if so display this phrase when the player clicks the window
            p [[broken window.]]
	else
            p [[dark outside.]]  --otherwise display this
	end
    end;
    -- assess whether the used handler is empty
    if v.used == nil then
        -- function to autofill the used handler
        v.used = function (s, w)
            -- assess whether the second parameter (parent self?) 
            if w == hammer then
                if s._broken then
                    p [[Window already broken.]]
                else
                    p [[I broke the window.]]
                    s._broken = true;
                end
            end
        end;
    end
    return obj (v)
end;

This constructor fills in some of its attributes and handlers itself, but still allows the player's game and your own extraneous code to override some of them. At the end it calls for the manifestation of the object with finalized parameters and returns the custom window object.

win1 = window {
    -- overrides only the dsc attribute, while all others auto-fill
    dsc = "In the eastern wall is {window frame}.";
};

Since windows are usually static objects, you can could put it directly into the obj handler for your room instead.

obj = {
    window {
        dsc = 'In the east wall is a {window frame}.';
    }
};

If you want to use a more conventional constructor syntax, as a function that takes several parameters instead of a table it's perfectly workable. In fact, vroom(), vobj(), and vway() follow this paradigm.

window = function (nam, dsc)
    -- creates an empty table inside function
    local v = {}    
    v.window = true  -- begins to fill the table
    v.nam = 'window'
    -- checks if the given window being passed in already has a dsc
    if dsc == nil then
        v.dsc = 'There is a {window}.'
    end
    v.act = function (s)
        if s._broken then
            p [[broken window.]]
        else
            p [[dark outside.]]
        end
    end
    v.used = function (s, w)
        if w == hammer then
            if s._broken then
                p [[Window already broken.]]
             else
                p [[I broke the window.]]
                s._broken = true;
             end
        end
    end
    -- sends the local window settings out 
    -- into the world as a constructed (pre-filled) object
    return obj(v)
end;

A call to this constructor will look different than the table-based calls.

obj = {window ('Window', 'In the east wall is {box}.')}

In fact, both approaches are useful in different scenarios. If the constructed object might be assigned new or optional attributes later, it's easier to define the constructor as a function of the host table, the same as obj, room, and dlg are. Otherwise, if the object is sure to be hands-off later, you can use define it as a function itself.

You might need to add new variables to your constructor. In the example above we created the stored variable _broken prefixed with an underscore because stored variables can be created on-the-fly. But if you need unstored variables you can use stead.add_var() instead. The syntax is shown below.

stead.add_var(constructor, {variable = value})

Here's the previous example with an unstored variable.

window = function (v)
    -- adds the "broken" with a value to your constructor
    stead.add_var (v, {broken = false})
    v.window = true
    ...
end;

If you need to add a global variable it can be done on-the-fly just like stored variables.

-- no need for parentheses since it's only one parameter
stead.add_var {global_var = 1}

Now that we understand how to build constructors, let's think about the concept of inheritance. It's employed in the examples above, at least it will be since there's no other reason to have constructors. They call another constructor window obj, thereby obtaining all the properties of a regular object. Also, window defines a variable attribute window, to the game, we can understand that we are dealing with a window. For example:

use = function (s, w)
    if w.window then
        p [[I point my flashlight at the window.]]
        return
    end
end;

To illustrate the mechanism, create a class of objects called treasure.

global {score = 0}
treasure = function()
    local v = {}
    v.nam = 'treasure'
    v.treasure = true
    v._points = 100
    v.dsc = function (s)
        p ('There are {', stead.dispof (s), '}.')
    end;
    v.inv = function (s)
        p ('same', stead.dispof (s), '.');
    end;
    v.tak = function (s)
        -- Increase the account
        score = score + s.points;
        p [[With trembling hands I took the treasure.]];
    end
    return obj (v)
end;

Now, based on our treasure constructor we can define any specific kind of treasure for our world. How about gold, diamond and a treasure chest?

gold = function(dsc)
    local v = treasure ();
    v.nam = 'gold';
    v.gold = true;
    v.points = 50;
    v.dsc = dsc;
    return v
end;
diamond = function(dsc)
    local v = treasure ();
    v.nam = 'diamond';
    v.diamond = true;
    v.points = 200;
    v.dsc = dsc;
    return v
end;
chest = function(dsc)
    local v = treasure ();
    v.nam = 'chest';
    v.chest = true
    v.points = 1000;
    v.dsc = dsc;
    return v
end;

To put them in the game, just define a variable with them, and as you should be able to tell the inherited aspects from treasure() fall into diamond(), gold(), and chest(). Cool huh?

diamond1 = diamond("I see a glittering {sparkle in the mud}...");
diamond2 = diamond();  --fills in with the default dsc
gold1 = gold("In the corner, a bit of yellow {shimmers}.");
cave = room {
    nam = 'cave';
    obj = {
        'diamond1',
        'gold1',
        chest ("A heavy steel {chest}!")
    };
}

When writing constructors sometimes you'll want to run a handler or a method from within. Simply use this system method to access it:

stead.call(object, method/handler, parameters) 

You could modify the window constructor you created earlier (example to show syntax, rather than a purposeful modification).

window = function (nam, dsc, customHandler)
    local v = {} --creates an empty table
    v.window = true  --fills the table
    v.nam = 'window'
    v.newHandler = function(s)
        -- custom event handler that returns a string
        p("It looks to be antique glass.")
    end;
    if dsc == nil then
       v.dsc = [[There's a blurry plate glass {window}]]
    end
    v.act = function(s)
        if s._broken then
            p [[It's broken.]]
        end
        -- calls the custom 'newHandler' and places its result in r	
        local r = stead.call(s, 'newHandler')
        if r then
            p(r)
        else
            p [[It's so dark outside.]]
        end
    end;
...
end;

WARNING: The following section is merely to prevent frustrating misunderstandings with the nature of constructors. This is the fact that it's impossible to save objects created on-the-fly without giving them a variable identifier. To do so requires much deeper engine manipulation than should be necessary for the scope of this engine.

For example, you could try to create a custom version of vway(), calling it xway(). When clicked, an xway() object executes xwalk(), like so:

function xway(name, dsc, w)
-- you can't pass the variable handle itself
    return obj {
        nam = name;
        dsc = dsc;
        act = function (s)
            walk (s.where)
        end;
	where = w;
     };
end

When you attempt to implement this in real code it will be impossible to save any object it constructs because there's no variable identifier.

-- creates an handle-less object
place(xway('the cave', '{In the cave}', 'cave'));

The engine then won't be able to save the object. This is because generally speaking, INSTEAD is only able to save objects that have identifiers. Objects that only have descriptors are created by explicitly assigning them in a global context such as (apple = obj {…}) or by creating them within lists such as obj and way where they're indexed and stored. (Also you can use the inadvisable new() system method described below).

Nevertheless, there is a way to store these objects, but it isn't useful for writing games. It's intended for the development of modules and extensions of the engine. If you're an average storytelling game designer, you can safely skip the following explanation. In fact, it's the wish of the creator of INSTEAD that designers never need to deal with the engine on this level.

So, to save a variable in a very unfriendly and non-game-code kind of way you can do this:

function xway (name, dsc, w)
    return obj {
        nam = name;
        dsc = dsc;
        act = function(s)
            walk (s.where)
        end;
        where = w;
        save = function(self, name, h, need)
            -- self == the current object
            -- name == the intended handle of the variable
            -- h == file descriptor for savegame
            -- need == a sign that it is creating an object, 
            -- the save file is to create an object at startup
            local dsc = self.dsc;  --startup
            local w = stead.deref(self.where);
            if need then
                -- in the case of creation, 
                -- we write the string to call the constructor
                h: write (stead.string.format(
                    "% s = xway (%s,%s,%s); \ n", 
                    name,
                    -- Formation of lines
                    stead.tostring (self.nam),
                    stead.tostring (dsc),
                    stead.tostring (w)));
            end
            stead.savemembers (h, self, name, false); 
            -- keep all other variables such as object, 
            -- the state of the on / off, etc. false 
            -- in last position.
            -- this will mean they'll be transferred to 
            -- the save-nested objects as the 'need' parameter
        end
    };
end;

Dialogues

Character dialogues are actually room objects of type dlg which contain a phr object list. There are two types of dialogues: extended and simple. Both share the same behavior, but simple dialogues are deprecated and therefore not recommended for use.

When the player enters a dialogue she will see a list of phrases. When she clicks a phrase it triggers a reaction. By default, clicked phrases become hidden. When all of them have disappeared the dialogue returns the player to the previous room. Often you may want to include permanent phrases like 'End this dialogue.' or 'Ask one more time.' to prevent the dialogue from escaping to the previous room.

Dialogues are entered in the same way as rooms:

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

It's recommended to use walkin() instead of walk() because the current room's exit/left handlers won't be activated. Also, the person we want to talk to is usually in the same room with the player, so we don't want to officially leave.

You can enter a dialogue from another one, implementing hierarchical branches. You can return to the previous branch with a back() call. Extended dialogues implement hierarchical branches much better than simple dialogues.

The engine prevents entering a dialogue with no phrases because it will be exited automatically. Take this into account while debugging your games.

NOTE: It's highly recommended to use the hideinv handler when implementing dialogue into your game. Dialogues will look better if inventory items aren't affecting phrases, and you will prevent unexpected conversation bugs. Add it by putting this at the top of the main.lua file:

require "hideinv"

Then declare it as true inside your dialogues like this:

guarddlg = dlg {
    nam = 'guard';
    hideinv = true;
    phr = {
        {...};
        {...};
    };
}

The most common mistake is incorrectly calling a dialogue from the inv handler without hiding the inventory itself from the dialogue. For example, when building a mobile phone inside your game, you would expect the player to click the phone inside her inventory which would trigger a dialogue. As you know from above, escaping dialogues is usually done through a back() call, but if the inventory isn't hidden and the player clicks the mobile phone in the inventory one more time, we run into the same dialogue again! The back() call will put the player in the previous instance of the dlg “room” instead of the actual room she's supposed to be standing in. Of course you can avoid such situations by doing the following:

phone = obj {
    nam = 'mobile';
    -- when the player clicks on 
    -- her mobile phone in her inventory
    inv = function(s)
        if here() ~= phone_dlg then
            -- this line checks if the current room 
            -- is the actual room she's in, not the dlg "room"
            -- if she's not in the dlg, it makes her walk into the dlg
            walkin(phone_dlg)
            return
        end
        -- this relates the situation to the player :)
        p "I am already holding the mobile."
    end;
};

Extended Syntax

Since version 1.7.0 a more powerful dialogue syntax has been supported, known as extended dialogues. Unlike the earlier format, now phrases are defined in a phrase table:

cookdlg = dlg {
    nam = 'in the kitchen';
    hideinv = true;
    entered = [[I notice the round face of the 
              cook under her white hat. Her eyelids are sagging...]];
    phr = {
        { always = true, 
            "Some of those greens, please. And beans too.", 
            "Mmmmkay."};
        { always = true, 
            "Fried potatoes please.", 
            "Of course."};
        { always = true, 
            "Two bowls of garlic soup.", 
            "My favorite..." };
        { always = true, "Something light, please. I've got an ulcer.", 
            "No soup for you!" };
        { always = true, "Thank you, I don't want anything.", 
            "Get out of the line.", 
            [[back()]] };
    };
};

If the dialogue's dsc attribute isn't defined, it will automatically call the last reply from the player's interactions. The player will see it after clicking the room's caption. Because of this pleasing behavior it's better to put any necessary static description of the dlg into its entered handler, as was done in the above example.

Phrases are written with the following syntax:

phr = {
    [index or tag=TAG],
    [false if disabled],
    [always = true], 
    "Question", 
    "Reply", 
    [[reaction code]] 
};

Each phrase is presented to the player as its “Question” value. The “Reply” value is displayed after it's been clicked. The phrase then becomes disabled (changing its second value to false), and the reaction code is invoked (if present). The reaction code may contain any Lua code.

The game engine provides the following methods to use on phrases:

If called with no arguments, these methods are applied to the current phrase, wherever the code is called from.

To manipulate phrases in a different dialogue, you can use the following syntax:

dialogue:method()

For example, if the guard_dlg exists somewhere outside the current room, you can target it like this:

guard_dlg:pon('show_card')

You will probably find it quite useful at times to disable a phrase initially, then enable it later, like so:

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 = {
        -- this phrase is initially disabled
        { 1, false, always = true, [[I love those!]], [[Everyone does.]] };
        -- once the player finds out there are 
        -- french rolls on the shelf the first phrase is enabled
        { [[What's on the shelf?]], [[French rolls.]], [[pon(1)]] };
        { always = true, "Some greens, please.", "Good."};
        { always = true, "Fried potatoes, please!", "Eat now!"};
        { always = true, "Thanks, but I'm not hungry.", "As you wish.", [[back()]] };
    };
};

If you'll never need to manipulate a certain phrase, you can drop its first field entirely:

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

To manipulate phrases you can target them by index:

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

However for complex dialogues, tags are more suitable:

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

A tag is simply a string label. As explained earlier, you can pass phrases by index or tag to the pon/poff/pseen/punseen methods. You can also assign a tag to an indexed phrase.

NOTE: If multiple phrases have the same tag, the method's action will be applied to all of them.

Also be aware that:

If a phrase contains always=true it won't be automatically hidden after being clicked by the player.

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

If you want to implement reaction code without returning any text after the player clicks the phrase, you can use one of the following examples:

-- put "nil" in the Reply field
{ tag='exit', always=true, "Ok, I'm leaving!", nil, [[back()]] }
-- which will return nil simply omit the Reply field, 
{ tag='exit', always=true, "Ok, I'm leaving!", code = [[back()]] }

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

-- this phrase unhides the next
{ tag='exit', code[[p"I'm leaving."]], code[[p"Please stay."; pon'really']] },
-- this phrase is hidden by default
{ tag='really', false, always=true, "No, I'm really leaving!", function() back() end }

For complex branching dialogues you can group phrases, which essentially removes the need for massively convoluted pon/poff crossovers between numerous dlg objects. Phrase groups are displayed to the player as a new dialogue screen.

Each phrase group must be separated by a phrase with no reaction code. The simplest implementation is using curly braces { } but you can use anything to separate your groups, even a tag which allows you to target switching between branches, like so:

--First Phrase Group
{ 'Tell me about the weather.', 
  'Does I look like I keep up with the weather?', 
  [[psub 'weather']] 
},
{ always=true, [[Goodbye.]], code = [[back()]] },
-- this is the delimiter, essentially serving as 
-- a targetable nametag leading into the following branch
{ tag = 'weather' },
-- Second Phrase Group
{ 'Do you know the current temperature?', 'Maybe... 25 Celsius.' }, 
{ 'What the humidity like?', 'Definitely eighty percent. You can bet on it.' },

Only one group can be visible to the player at a time. Upon entering the sample dialogue above, the player sees two phrases to choose from. The first phrase's code pushes her down to the phrase with the tag weather, which is empty, so it basically falls out of the way and the second phrase group takes over. After both questions in the second group are asked, the player is pushed back to the initial branch, where the only visible phrase left is 'Goodbye.'

Branch switching is implemented with the following methods:

To get the current branch's index use this method:

dialogue_nam:current() 

To get its tag use this:

dialogue_nam:curtag()

You can apply the two methods above to external dialogues as well, like so:

shop_dlg:pstart(1)

A branch's active/inactive status as well as the number of visible phrases remaining. Simply use the following methods, both of which accept the index/tag of the leading phrase in the target branch as their argument. If no argument is supplied, the current group is processed:

-- returns true if the group contains no active phrases
dialogue_nam:empty([t])
-- returns the number of visible phrases, 0 if empty
dialogue_nam:visible([t])

You can get fancy with how branches are displayed to the player by including a Question field in the target, which becomes a static caption for the entire group:

{ 'Tell me about the weather.', code = [[psub'weather']] },
{ always=true, [[Goodbye.]], code = [[back()]] },
{ },
-- plays the role of branch caption
{ tag = 'weather', 'Okay, what are you interested in?'},
{ 'What is the temperature?', '25 degrees Celsius.' },
{ 'What about the humidity?', '80%.' },

This caption question can also be a function, allowing you to invoke code as branches switch.

{ 'Tell me about the weather.', code = [[psub'weather']] },
{ always=true, [[Goodbye.]], code = [[back()]] },
{ },
{ tag = 'weather', function()
    p 'Ok, what exactly?';
    weather_asked = true;
end },
{ 'What\'s the temperature?', '25 degrees Celsius...' },
{ 'Do you know the humidity?', '80%!' },

The caption phrase can additionally contain an empty method, which will be called after all phrases in the branch are hidden:

{ 'Tell me about the weather.', code = [[psub'weather']] },
{ always=true, [[Bye!]], code = [[back()]] },
{ },
{ tag = 'weather', 
  'What do you?', 
   empty = code [[p'Enough talking about the weather!'; 
   pret()]] },
{ 'Temperature?', '25 C!' },
{ 'Humidity?', '80%' },

Despite being included in the above empty method, the default action for empty is pret(). However, if you choose to redefine empty with some other method you must call pret() explicitly when needed from then on.

If you want to see a full branching dialogue.. in Russian, check out the following link to the GitHub repository: Example Dialogue

Deprecated Syntax

WARNING: The following examples utilize deprecated syntax, and are included in this documentation for the sake of potentially helping you sort your way through obsolete code in old games. Some concepts are shared with Extended dialogues, but it's still important to follow the Extended rules for modern development.

Here's a dialogue written in the deprecated syntax:

food = obj {
    nam = 'meal',
    inv = function (s)
        remove('food', inv());
	p "I'm eating.";
    end;
};
gotfood = function(w)
    take 'food';
    -- this is a savegame stored variable
    food._num = w;
    back();
end;
 
povardlg = dlg {
    nam = 'In the kitchen';
    dsc = [[I notice the round face of the cook under 
        a big white hat. She looks really, really tired.]];
    obj = {
        [1] = phr("Some greens, please.", "Enjoy...", [[pon(); gotfood(1);]]),
        [2] = phr("Fried potatoes now!", "Bon appetit, ya filthy animal...", 
           [[pon(); gotfood(2);]]),
        [3] = phr("Two bowls of garlic soup", "That's my favorite too!", 
           [[pon(); gotfood(3);]]),
        [4] = phr("Something light, please. I've got an ulcer...", "Porridge for you then.",
           [[pon(); gotfood(4);]]),
    };
};

The player chooses her meal, she receives it and the nam is stored in food._num, then returns back to the room where she began the dialogue. Reactions can contain any Lua code, but usually it's phrase management logic.

NOTE: The pon/poff/prem/pseen/punseen methods only work with indexes here. Deprecated dialogues CANNOT employ tags.

To pass from one deprecated dialogue to another, you would hide phrases while initializing the dialogue then unhide them later through the conditional _phr variable and pon/poff/prem methods, like so:

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 you, -- the guard grins 
            -- and I was told to only let decent people in."]],
            [[pon(2);]]),
        [2] = _phr("I have the invitation right here!",
            [["I see. Now look at that mirror there. 
            I suppose you may not have one in your home. 
            You've come to listen to Belin. Be-lin! 
            The right hand of... - the guard pauses for a second - 
            get out of here!"]],
            [[pon(3,4)]]),
        [3] = _phr("I'm gonna kick you in your fat face!",
            [["Well, let's see it." - powerful hands push me 
            into the corridor, but I feel lucky to remain in one piece...]],
            [[poff(4)]]),
        [4] = _phr("You boar! I told you I have an invitation!",
            [["Do you?" - the guard's eyes turn red - 
            a hard knock sends me out - it could be worse...]],
            [[poff(3)]]),
    };
    exit = function(s,w)
        s:pon(1);
    end;
};

NOTE: Again, do not write dialogues like this! Extended dialogues are much easier and better.

For style/aesthetic purposes you can define the prefix placed in front of every phrase, which is its numerical index by default:

stead.phrase_prefix = '--'
stead.phrase_prefix = '+'
stead.phrase_prefix = ':)'

Each of these causes all phrases to be prefixed by the given string instead of their index number. This constant (stead.phrase_prefix) is not variable and so it must be defined early in game. Or you can set it in init() function.

NOTE: The string value isn't stored in the player's savegame file, so the game has to define it explicitly for each playthrough. Be aware of this.

Extra Features

Graphics

The game's graphic interpreter (the part that displays things onscreen) will treat a room's pic attribute as a path to an image that should be displayed in the viewer.

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

Of course, pic can also be a function. If the current room has no pic attribute defined, the game.pic attribute is assessed instead. If it's also undefined, no image will be displayed.

From version 0.9.2 you can use animated gif files, and graphics can be embedded everywhere, including within the text itself and inside the inventory panel useing the img() method.

knife = obj {
    -- the nam will be a concatenation of the two
    nam = 'Knife'..img('img/knife.png'),
}

In the current version you're free to use the disp attribute instead of the nam attribute.

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

Starting with version 1.3.0 text flow around images is supported. Use the imgl() and imgr() methods, which insert the image at the left or right border respectively. To add open space around the image use the pad: prefix.

NOTE: The text flow images (imgl and imgr) cannot be a link.

Notice the abbreviated versions below and how they affect more than one side.

-- the image is padded on top 0px, right 16px, bottom 16px, left 4px
imgl('pad:0 16 16 4, picture.png')
 
-- the image is padded on top 0px, right 16px, bottom 0px, left 16px
imgr('pad:0 16, picture.png') 
 
-- the image is padded on all sides with 16px
imgl('pad:16, picture.png')

A clever use of the img() method is to create blank areas and border boxes.

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

Starting with version 1.0.0 the interpreter can compose images from a base image and an overlay.

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

Sound and Music

The interpreter cycles music that's been called with the set_music() method, which is often implemented in the enter handler.

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

The get_music() method returns the name of the current track.

Starting with version 0.7.7 the set_music() function can receive an additional parameter for the number of loops you wish it to play.

You can ask for the current loop position with the get_music_loop() method. If -1 is returned it means the current track is finished looping.

From version 0.9.2 the set_sound() method will play a sound file.

The get_sound() method returns the filename of the sound that's queued to be played.

Starting with version 1.0.0 you can simply call stop_music() to end the track being played.

From version 1.0.0 you can use the is_music() method to check if music is playing or not.

Themes

The SDL version's graphic interpreter has a fancy theme subsystem. It accesses your game's theme directory which must house a theme.ini file. It must also include a default theme. This is always the first to load. All other themes inherit from it and can partially or completely override its parameters.

Themes can be selected by the player in the settings menu, but your game can initially inject its own theme which will override all others by as much as you choose. To do this, place a duplicate of the theme.ini file within the game directory, where main.lua resides. The player can always override your theme if she wants. If she does, the interpreter will issue a warning that she's overruling the godlike developer's creative intentions. ;)

You can include comments with tags inside the theme header. Currently there's only one tag available. Here's a sample theme to show the basic format to follow:

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

The interpreter searches for the player's own themes in her installation's themes directory. The Unix version also checks ~/.instead/themes/ and since version 0.8.7 the Windows version checks Documents and Settings/USER/Local Settings/Application Data/instead/themes

The theme.ini file has a very simple syntax:

<parameter> = <value>; comment

A value can be a string, a number, or a color. Certain colors can be called with a string, such as yellow, green, and violet, but other colors must be written in #RGB format, where R, G, and B are replaced with hexadecimal values.

Parameters

scr.w –game window width in pixels

scr.h –game window height in pixels

scr.col.bg –background color of the entire game window

scr.gfx.bg –directory path to the static background picture, gifs are not enabled

scr.gfx.mode –sets the layout mode for room pictures as float, embedded, or fixed

scr.gfx.x, scr.gfx.y, scr.gfx.w, scr.gfx.h –X/Y coordinates, width, and height of room pictures, adjusted by layout mode

scr.gfx.scalable = [0|1|2|4|5|6]

Added in Version 2.2.0

win.gfx.h –synonymous with scr.gfx.h (for compatibility)

win.x, win.y, win.w, win.h –X/Y coordinates, width, and height of the main text area only, excluding the scrollbar width, and the text will be pushed by a fixed or embedded room picture

win.fnt.name –path to a font file

win.fnt.size –font size for the main text area

win.fnt.height –line height for the main text area, as a float number, 1.0 by default

win.align = left/center/right/justify – alignment of text in the story panel's text area

win.gfx.up, win.gfx.down –paths to the pictures of up/down scroll handles for the main story panel

scr.gfx.pad –sets padding for all scrollbars and menu edges

win.up.x, win.up.y, win.down.x, win.down.y –coordinates for scroll handles, literal position or -1 for invisible

win.col.fg –font color for the main story panel

win.col.link –color of clickable words in the main story panel

win.col.alink –hover-over color for clickable words in the main story panel

win.scroll.mode = [0|1|2|3] – the scrolling area on the scene

inv.x, inv.y, inv.w, inv.h –X/Y coordinates, width, and height of the inventory panel

inv.modehorizontal or vertical inventory stacking mode –in vertical mode only 1 object fits per row, in horizontal multiple objects can fit –you must reposition and resize the panel to make good use of horizontal mode

inv.col.fg –text color for the inventory panel

inv.col.link –color for clickable words inventory panel

inv.col.alink –hover-over color for clickable words in the inventory panel

inv.fnt.name –path to a font file for the inventory panel

inv.fnt.size –font size for the inventory panel

inv.fnt.height –line height for the inventory panel, as a float number, 1.0 by default

inv.gfx.up, inv.gfx.down –paths to the pictures for the inventory panel's up/down scroll handles

inv.up.x, inv.up.y, inv.down.x, inv.down.y –coordinates of scroll handles, literal position or -1 for invisible

menu.col.bg –background color for menus

menu.col.fg –text color for menus

menu.col.link –color for clickable menu items

menu.col.alink –hover-over color for clickable menu items

menu.col.alpha –transparency for menus as an alpha value, from 0-255

menu.col.border –border color for menus

menu.bw –border width for menus

menu.fnt.name –paths to font files for menus

menu.fnt.size –font size for menus

menu.fnt.height –line height for menus, as a float number, 1.0 by default

menu.gfx.button –path to the menu icon picture file

menu.button.x, menu.button.y –coordinates for the button location on the main screen

snd.click –path to a click sound file

include –path of a theme file to serve as filler for missing parameters, no extension or = sign needed

For example, this will build your custom parameters over the Book theme that comes included with Instead:

include book

The following parameters have been available since version 0.8.9 of the engine:

scr.gfx.cursor.x –X coordinate of the center of the cursor

scr.gfx.cursor.y –Y coordinate of the center of the cursor

scr.gfx.cursor.normal –directory path to the cursor's picture file

scr.gfx.cursor.use –path to a picture file for the cursor's use indicator

scr.gfx.use –path to a picture file for the room's use indicator

Extending The Engine With Modules

Starting with version 1.2.0 you can employ feature extension modules via the require method. Modules must be manually added to your main.lua file, usually near the top.

...
require "para"
require "dbg"

These are the currently available modules:

If the engine is higher than version 1.2.0 the 'vars', object, and walk modules are automatically activated.

To illustrate the use of modules in code, let's take a look at the prefs module. A special object variable called counter is part of this engine extension. It can store game variables such as player progress or number of attempts.

require "prefs"
...
prefs.counter = 0  --number of times the player has attempted a room
...
exit = function(s)
    -- the number of exits is incremented
    prefs.counter = prefs.counter + 1
    -- saves the counter inside the prefs object
    prefs:store()
end;
...
enter = function(s)
    -- retrieves the counter and displays it
    return 'You passed the game '..prefs.counter..' times';
end;
 
...
 
act = function(s)
    -- clears the prefs object
    prefs:purge()
    return "Preferences has been cleared"
end;

Real-Time Keyboard Input

WARNING: The following examples utilize deprecated syntax, and are included in this documentation for the sake of potentially helping you sort your way through obsolete code in old games. Please, use module kbd to handle keyboard. Also, there is a module keyboard to make possible the player text input.

Since version 1.1.0 the SDL version has supported real-time keyboard input. This can be activated via the input object.

input.key(s, pressed, key) 

The first argument references the keyboard handler's self. The second argument accesses the engine's press and release events. The third argument receives the name of the specific key being activated.

The input object's key handler can also return an engine interface command, in which case the interpreter won't handle it, which is useful for behind-the-scenes effects. For example:

input.key = function(s, pr, key)
    if not pr or key == "escape" then  --checks if the Escape key was activated
        return  --exits the current typing area??
    elseif key == 'space' then  --checks if the Spacebar was activated
        key = ' '   --outputs an empty space through the key handler
    elseif key == 'return' then  --checks if the Enter key was activated
        key = '^';  --outputs a line break through the key handler
    end
    if key:len() > 1 then   --checks if the 
        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;
};

Real-Time Mouse Input

You may use click module to handle mouse click events. Module will call here().click or game.click method with coordinates (x, y) of click in parameters. 0,0 is the left top corner. You can get even background clicks, just define:

click.bg = true;

So, the method will called in this case with those parameters:

game.click(s, x, y, px, py)

Where x, y is the coordinates of whole window and px,py - coordinates in image (if image was clicked).

If you need to get press and release events, you may define:

click.press = true;

In such case the parameters are:

game.click(s, press, x, y, px, py)

Where press is boolean.

If you need to get button number, please, define:

click.button = true

And get the button number as the 3d parameter.

Here is the example of the module usage:

-- $Name: Tets module -- click$
-- $Version: 0.1$
-- $Author: instead$
 
instead_version "1.8.0"
 
-- use module
require "click"
 
-- define game.click
game.click = function(s, x, y) -- this is global method,
-- will called if not here().click defined
    p ("You are clicked at: ", x, ", ", y); 
end
 
main = room {   nam = 'Forest',
    forcedsc = true,
    pic = 'house.png',
    dsc = [[ You are seeing a house. Door is open. ]],
    -- define own click method in this scene
    click = function(s, x, y)
        -- is this the door area?
        if x > 80 and x < 200 and y > 225 and y < 325 then
            walk('house');
        else
            return 'This is not the door.';
        end;
    end,
};
 
house = room {  nam = 'Дом',
    forcedsc = true,
    pic = 'door.png',
    dsc = [[ You are inside the house.]],
};

Neat Ideas/Examples

In-Game Walkman

To implement a personal music device for the player to control you must create it as an “alive” object. First create a tracklist in the tracks handler.

--might be an attribute... not sure
tracks = {"mus/astro2.mod", "mus/aws_chas.xm", "mus/dmageofd.xm", "mus/doomsday.s3m"}  

Then create a living, breathing Walkman object to entertain your players. ;)

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 even use get_music_loop() and get_music() to remember the last track and let the player restore it.

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;
...

Since version 0.8.5 the save_music() and restore_music() methods have been permanently included in the game library.

Teleportation Hack

If your player lost their horse or they need something else to appear next to them, one way to materialize that object is to implement the lifeon() method as a teleporter, controlled by the object's life handler. This will materialize the object at the player's current location, technically just bringing it to life again. But you get the idea.

horse = obj {
	nam = 'horse',
	dsc = 'A {horse} is 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;

Custom Menus Within Items

Menus are a great feature to add to your world. You can add them to an object by incorporating the following menu constructor into your code. It's activated by the player with a single mouse click. If it has no return string, the state of game will not change. For example, here's a simple implementation of a pocket housed inside the inventory.

pocket = menu {
    var { state = false };
    nam = function(s)
        if s.state then
            return txtu('pocket');
        end
        return 'pocket';
    end;
    gen = function(s)  
      -- this is our custom function -- pocket generator
        if s.state then
            s:enable_all();
        else
            s:disable_all();
        end
    end;
    menu = function(s)
        s.state = not s.state
	s:gen(); -- generate pocket
    end;
};
knife = obj {
	nam = 'knife',
	inv = 'This is knife',
};
function init()
    inv():add(pocket);
    place(knife, pocket);
    pocket:gen();
end;
main = room {
    nam = 'test',
};

Another cool implementation of menus is to place an unclickable text readout of the player's status within the inventory panel.

global {
	life = 10;
	power = 10;
}
status = stat { -- create status object
    nam = 'my status';
    disp = function(s)
        p ('Life: ', life, 'Power: ', power)
    end;
};
function init()
    take 'status';
end;

Preparing Your Game For Release

Splitting Your Game Into Files

Split your game into multiple files to make it easier to manage. You can use the dofile() method to insert your separated source code files into the game. You must use dofile() in a global context, so load all of them while parsing main.lua at the opening of the game.

...somewhere inside main.lua...
dofile "episode1.lua"
dofile "npc.lau"
dofile "start.lua"

Dynamically including files gives you the ability to redefine objects and rooms as the game progresses. Nifty! Just use the gamefile() method.

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

You can also clear the game stack of old files, leaving you with a brand new game without the player needing to load anything herself. This is quite useful for multi-part stories where locations need to be revisited in a brand new light. Just put true as a second parameter for the gamefile() method.

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

Encoding Your Game's Source

Since version 0.9.3, if you want to hide your source code you can encode it from the command line as follows:

sdl-instead -encode <lua file> [encoded file]

Then to load your encoded file from Lua use:

doencfile() 

It's necessary to keep main.lua as a plain text file for this, so the recommended scheme is:

game is a encoded game.lua ):
main.lua
-- $Name: My closed source game!$
doencfile("game");

WARNING: Do NOT use the luac compiler for encoding, as it produces platform-dependent code! For regular game compilation it's quite useful for hunting down bugs, but not for encoding.

Packaging Your Game

Since version 1.4.0 you're able to package all your game's resources including graphics, sounds, themes, etc., into one fancy .idf file. Just put your resource directories all into a single directory called data and from the command line run:

instead -idf <path_to_your_parent_data_directory>

A file called data.idf will be created. Put this into your game's top level directory along with the code files and remove the original resource files.

You can also pack your entire game into one .idf file like so:

instead -idf <path_to_game_directory>

A game stored in .idf format can be run like any other game within INSTEAD, or straight from the command line with:

instead game.idf

Appendix

STEAD Interface Commands

Pre-Defined Methods

Pre-Defined Objects

Credits

Translation from Russian by Vopros

English Localization and Technical Editing by Ryan Joseph