ru:gamedev:documentation

# Базовая документация

## Общие сведения

Код игр для STEAD пишется на lua (5.1), поэтому, знание этого языка полезно, хотя и не необходимо. Код движка на lua занимает около ~3000 строк и лучшей документацией является изучение его кода.

Главное окно игры содержит информацию о статической и динамической части сцены, активные события и картинку сцены с возможными переходами в другие сцены (в графическом интерпретаторе).

Статическая часть сцены отображается только один раз, при показе сцены, или при повторении команды `look` (в графическом интерпретаторе – клик на названии сцены). Динамическая часть сцены составлена из описаний объектов сцены, она отображается всегда.

Игроку доступны объекты, доступные на любой сцене – инвентарь. Игрок может взаимодействовать с объектами инвентаря и действовать объектами инвентаря на другие объекты сцены или инвентаря.

Следует отметить, что понятие инвентаря является условным. Например, в «инвентаре» могут находиться такие объекты как «открыть», «осмотреть», «использовать» и т.д.

Действиями игрока могут быть:

- осмотр сцены;
- действие на объект сцены;
- действие на объект инвентаря;
- действие объектом инвентаря на объект сцены;
- действие объектом инвентаря на объект инвентаря;
- действие объектом сцены на объект сцены (режим scene_use);
- действие объектом сцены на инвентарь (режим scene_use);
- переход в другую сцену;

Игра представляет из себя каталог, в котором должен находиться скрипт `main.lua`. Другие ресурсы игры (скрипты на `lua`, графика и музыка) должны находиться в рамках этого каталога. Все ссылки на ресурсы делаются относительно текущего каталога – каталога игры.

В начале файла `main.lua` может быть определен заголовок, состоящий из тегов. Теги должны начинаться с символов `–`: комментарий с точки зрения lua. На данный момент существует один тег: `$Name:`, который должен содержать название игры в кодировке UTF-8. Пример использования тега:

    -- $Name: Самая интересная игра!$

Сразу после заголовков вам необходимо указать версию STEAD API, которая требуется игре. На данный момент последняя версия 1.8.0.

    instead_version "1.8.0"

Важно!

Если version отсутствует, то STEAD API будет работать в режиме совместимости (устаревшее API).

Инициализацию игры следует описывать в функции `init`:

    function init()
        me()._know_truth = false
        take(knife);
        take(paper);
    end

Графический интерпретатор ищет доступные игры в каталоге `games`. Unix-версия интерпретатора кроме этого каталога просматривает также игры в каталоге `~/.instead/games`. Windows-версия: `Documents and Settings/USER/Local Settings/Application Data/instead/games`. В Windows- и standalone-Unix-версии игры ищутся в каталоге `./appdata/games, если он существует`.

## 1. Сцена

Сцена – это единица игры, в рамках которой игрок может изучать все объекты сцены и взаимодействовать с ними. В игре должна быть хотя бы одна сцена с именем `main`.

    main = room {
    	nam = 'главная комната',
    	dsc = 'Вы в большой комнате.',
    };

Запись означает создание объекта `main` типа `room`. У каждого объекта игры есть атрибуты и обработчики. Например, атрибут `nam` (имя) является необходимым для любого объекта.

Атрибут `nam` для сцены это то, что будет заголовком сцены при ее отображении. Имя сцены также используется для ее идентификации при переходах.

Атрибут `dsc` это описание статической части сцены, которое выводится один раз при входе в сцену или явном выполнении команды `look`.

Вы можете использовать символ `;` вместо `,` для разделения атрибутов. Например:

    main = room {
    	nam = 'главная комната';
    	dsc = 'Вы в большой комнате.';
    };

Если для вашего творческого замысла необходимо, чтобы описание статической части сцены выводилось каждый раз, вы можете определить для своей игры параметр `forcedsc` (в начале игры).

    game.forcedsc = true;

Или, аналогично, задать атрибут forcedsc для конкретных сцен.

Для длинных описаний удобно использовать запись вида:

    dsc = [[ Очень длинное описание... ]],

При этом переводы строк игнорируются. Если вы хотите, чтобы в выводе описания сцены присутствовали абзацы – используйте символ `^`.

    dsc = [[ Первый абзац. ^^
    Второй Абзац.^^

    Третий абзац.^
    На новой строке.]],

## 2. Объекты

Объекты – это единицы сцены, с которыми взаимодействует игрок.

    tabl = obj {
    	nam = 'стол',
    	dsc = 'В комнате стоит {стол}.',
    	act = 'Гм... Просто стол...',
    };

Имя объекта `nam` используется при попадании его в инвентарь а также в текстовом интерпретаторе для адресации объекта.

`dsc` – описание объекта. Оно будет выведено в динамической части сцены. Фигурными скобками отображается фрагмент текста, который будет являться ссылкой в графическом интерпретаторе.

`act` – это обработчик, который вызывается при действии пользователя (действие на объект сцены). Его задача – возвращение строки текста, которая станет частью событий сцены, или логического значения (см. раздел 5).

ВНИМАНИЕ: в пространстве имен lua уже существуют некоторые объекты (таблицы), например: table, io, string… Будьте внимательны при создании объекта. Например, в приведенном примере используется `tabl`, а не `table`. Лучше не использовать подобные названия, хотя в новых версиях INSTEAD эта проблема во многом решена.

## 3. Добавляем объекты в сцену

Ссылкой на объект называется текстовая строка, содержащая имя объекта при его создании. Например: 'tabl' – ссылка на объект `tabl`.

Для того, чтобы поместить в сцену объекты, нужно определить массив `obj`, состоящий из ссылок на объекты:

    main = room {
    	nam = 'главная комната',
    	dsc = 'Вы в большой комнате.',
    	obj = { 'tabl' },
    };

Теперь, при отображении сцены мы увидим объект «стол» в динамической части.

Вы можете использовать ссылки на объекты без кавычек в том случае, если объект был определен ранее, но использование кавычек всегда безопасней.

## 4. Объекты, связанные с другими объектами

Объекты тоже могут содержать атрибут `obj`. При этом, список будет последовательно разворачиваться. Например, поместим на стол яблоко.

    apple = obj {
    	nam = 'яблоко',
    	dsc = 'На столе лежит {яблоко}.',
    	act = 'Взять что-ли?',
    };

    tabl = obj {
    	nam = 'стол',
    	dsc = 'В комнате стоит {стол}.',
    	act = 'Гм... Просто стол...',
    	obj = { 'apple' },
    };

При этом, в описании сцены мы увидим описание объектов стол и яблоко, так как `apple` – связанный с `tabl` объект.

## 5. Атрибуты и обработчики как функции

Большинство атрибутов и обработчиков могут быть функциями. Так, например:

    nam = function()
    	return 'яблоко';
    end,

Это синоним записи: nam = 'яблоко';

Обработчик должен вернуть строку. Если вам удобнее, для возвращения текста вы можете использовать функции:

- `p («текст»)` – вывод текста и пробела;
- `pn («текст»)` – вывод текста с переводом строки;
- `pr («текст»)` – вывод текста как есть;

Если `p`/`pn`/`pr` вызывается с одним текстовым параметром, то скобки можно опускать.

Используйте `..` или `,` для склейки строк. Например:

    pn "Нет скобкам!";
    pn ("Строка 1".." Строка 2");
    pn ("Строка 1", "Строка 2");

Функции сильно расширяют возможности STEAD, например:

    apple = obj {
    	nam = 'яблоко',
    	dsc = function(s)
    		if not s._seen then
    			p 'На столе {что-то} лежит.';
    		else
    			p 'На столе лежит {яблоко}.';
    		end
    	end,
    	act = function(s)
    		if s._seen then
    			p 'Это яблоко!';
    		else
    			s._seen = true;
    			p 'Гм... Это же яблоко!';
    		end
    	end,
    };

Если атрибут или обработчик оформлен как функция, то обычно первый аргумент функции (`s`) – сам объект.

В данном примере при показе сцены в динамической части сцены будет выведен текст: 'На столе что-то лежит'. При взаимодействии с 'что-то', переменная `_seen` объекта `apple` будет установлена в `true` и мы увидим, что это было яблоко.

Запись `s._seen` означает, что переменная `_seen` размещена в объекте `s` (то-есть `apple`). Подчеркивание означает, что эта переменная попадет в файл сохранения игры.

Начиная с версии 1.2.0 вы можете определять переменные следующим образом:

    global {
        global_var = 1;
    }
    main = room {
        var {
            i = "a";
            z = "b";
        };
        nam = 'Моя первая комната';
        var {
            new_var = 3;
        };
        dsc = function(s)
            p ("i == ", s.i);
            p ("new_var == ", s.new_var);
            p ("global_var == ", global_var);
        end;

Переменные записываются в файл сохранения, если они размещены в одном из перечисленных типов объектов: комната, объект, игра, игрок, глобальное пространство, при этом начинаются с символа `_` или определены с помощью `var` и `global`.

Начиная с версии 1.2.0 вы можете определять функции следующим образом:

    	dsc = code [[
    		if not self._seen then
    			p 'На столе {что-то} лежит.';
    		else
    			p 'На столе лежит {яблоко}.';
    		end
    	]],

При этом в `self` записан текущий объект, `arg1 … arg9` и массив `args[]` – параметры.

В файл сохранения могут быть записаны:

- строки;
- булевы величины;
- числовые величины;
- ссылки на объекты;
- конструкции `code`;

Иногда может понадобиться обработчик, который совершал бы некоторое действие, но не выводил никакого описания. Например:

    button = obj {
    	nam = "кнопка",
    	dsc = "На стене комнаты видна большая красная {кнопка}.",
    	act = function (s)
             	here()._dynamic_dsc = [[После того как я нажал на кнопку, комната преобразилась.
    		  Книжный шкаф куда-то исчез вместе со столом и комодом, а на его месте
    		  появился странного вида аппарат.]];
    	        return true;
            end,
    }
    r12 = room {
    	nam = 'комната',
    	_dynamic_dsc = 'Я нахожусь в комнате.',
    	dsc = function (s) return s._dynamic_dsc end,
    	obj = {'button'}
    }

В данном случае обработчик `act` нужен для того, чтобы поменять описание комнаты, и не нужно, чтобы чтобы он выводил результат действия. Для отключения результата можно вернуть из обработчика значение `true` – это будет означать, что действие успешно выполнено, но не требует дополнительного описания.

Если необходимо показать, что действие невыполнимо, ничего не возвращайте. При этом будет отображено описание по умолчанию, заданное с помощью обработчика `game.act`. Обычно описание по умолчанию содержит описание невыполнимых действий.

Обратите внимание, что для создания динамического описания сцены в рассмотренном выше примере использовалось новая переменная `_dynamic_dsc`. Это сделано для того, чтобы изменённое описание попало в файл сохранения игры. Поскольку имя `dsc` не начинается с подчёркивания или заглавной буквы, по умолчанию оно не попадёт в файл сохранения.

Данный пример мог бы выглядеть более разумно:

    button = obj {
    	nam = "кнопка";
    	dsc = "На стене комнаты видна большая красная {кнопка}.";
    	act = function (s)
             	here().dsc = [[Теперь комната выглядит совсем по-другому!!!]];
    	        pn [[После того как я нажал на кнопку, комната преобразилась.
    		  Книжный шкаф куда-то исчез вместе со столом и комодом, а на его месте
    		  появился странного вида аппарат.]];
            end,
    }

    r12 = room {
    	nam = 'комната';
            forcedsc = true;
            var {
    	        dsc = 'Я нахожусь в комнате.';
            };
    	obj = {'button'}
    }

## 6. Инвентарь

Простейший вариант сделать объект, который можно брать – определить обработчик `tak`.

Например:

    apple = obj {
    	nam = 'яблоко',
    	dsc = 'На столе лежит {яблоко}.',
    	inv = function(s)
    		inv():del(s);
    		return 'Я съел яблоко.';
    	end,
    	tak = 'Вы взяли яблоко.',
    };

При этом, при действии игрока на объект «яблоко» – яблоко будет убрано из сцены и добавлено в инвентарь. При действии игрока на инвентарь – вызывается обработчик `inv`.

В нашем примере, при действии игроком на яблоко в инвентаре – яблоко будет съедено.

## 7. Переходы между сценами

Для перехода между сценами используется атрибут сцены – список `way`.

    room2 = room {
    	nam = 'зал',
    	dsc = 'Вы в огромном зале.',
    	way = { 'main' },
    };

    main = room {
    	nam = 'главная комната',
    	dsc = 'Вы в большой комнате.',
    	obj = { 'tabl' },
    	way = { 'room2' },
    };

При этом, вы сможете переходить между сценами `main` и `room2`. Как вы помните, `nam` может быть функцией, и вы можете генерировать имена сцен на лету, например, если вы хотите, чтобы игрок не знал название сцены, пока не попал на нее.

При переходе между сценами движок вызывает обработчик `exit` из текущей сцены и `enter` в той сцены, куда идет игрок. Например:

    room2 = room {
    	enter = 'Вы заходите в зал.',
    	nam = 'зал',
    	dsc = 'Вы в огромном зале.',
    	way = { 'main' },
    	exit = 'Вы выходите из зала.',
    };

`exit` и `enter` могут быть функциями. Тогда первый параметр это (как всегда) сам объект, а второй это комната куда игрок хочет идти (для `exit`) или из которой уходит (для `enter`). Например:

    room2 = room {
    	enter = function(s, f)
    		if f == main then
    			return 'Вы пришли из комнаты.';
    		end
    	end,
    	nam = 'зал',
    	dsc = 'Вы в огромном зале.',
    	way = { 'main' },
    	exit = function(s, t)
    		if t == main then
    			return 'Я не хочу назад!', false
    		end
    	end,
    };

Как видим, обработчики могут возвращать два значения: строку и статус. В нашем примере функция `exit` вернет `false`, если игрок попытается уйти из зала в комнату `main`. `false` означает, что переход не будет выполнен. Такая же логика работает и для `enter`. Кроме того, она работает и для обработчика `tak`.

Если вы используете функции `p`/`pn`/`pr`, то просто возвращайте статус операции с помощью `return`:

    room2 = room {
    	enter = function(s, f)
    		if f == main then
    			p 'Вы пришли из комнаты.';
    		end
    	end,
    	nam = 'зал',
    	dsc = 'Вы в огромном зале.',
    	way = { 'main' },
    	exit = function(s, t)
    		if t == main then
    			p 'Я не хочу назад!'
                            return false
    		end
    	end,
    };

Следует отметить, что при вызове обработчика enter текущая сцена может быть еще не изменена!!! Начиная с версии 1.2.0 добавлены обработчики left и entered, которые вызываются после того, как переход произошел. Эти обработчики рекомендованы к использованию всегда, когда нет необходимости запрещать переход.

## 8. Действие объектов друг на друга

Игрок может действовать объектом инвентаря на другие объекты. При этом вызывается обработчик `use` у объекта, которым действуют, и `used` – на который действуют.

Например:

    knife = obj {
    	nam = 'нож',
    	dsc = 'На столе лежит {нож}',
    	inv = 'Острый!',
    	tak = 'Я взял нож!',
    	use = 'Вы пытаетесь использовать нож.',
    };

    tabl = obj {
    	nam = 'стол',
    	dsc = 'В комнате стоит {стол}.',
    	act = 'Гм... Просто стол...',
    	obj = { 'apple', 'knife' },
    	used = 'Вы пытаетесь сделать что-то со столом...',
    };

Если игрок возьмет нож и использует его на стол – то он увидит текст обработчиков `use` и `used`. `use` и `used` могут быть функциями. Тогда первый параметр это сам объект, а второй – объект на который направлено действие в случае `use` и объект, которым действие осуществляется в случае `used`.

`use` может вернуть статус `false`, в этом случае обработчик `used` не вызовется (если он вообще был). Статус обработчика `used` игнорируется.

Пример:

    knife = obj {
    	nam = 'нож',
    	dsc = 'На столе лежит {нож}',
    	inv = 'Острый!',
    	tak = 'Я взял нож!',
    	use = function(s, w)
    		if w ~= tabl then
    			p 'Не хочу это резать.'
                            return false
    		else
    			p 'Вы вырезаете на столе свои инициалы.';
    		end
    	end
    };

В примере выше нож можно использовать только на стол.

## 9. Объект "игрок"

Игрок в STEAD представлен объектом `pl`. Тип объекта – `player`. В движке объект создается следующим образом:

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

Атрибут `obj` представляет собой инвентарь игрока.

## 10. Объект ''game''

Игра также представлена объектом `game` с типом `game`. В движке он определяется следующим образом:

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

Как видим, объект хранит в себе указатель на текущего игрока (`pl`) и некоторые параметры. Например, вы можете указать в начале своей игры кодировку текста следующим образом:

    game.codepage="UTF-8";

Поддержка произвольных кодировок изначально присутствует в UNIX версии интерпретатора, в windows версии – начиная с 0.7.7.

Кроме того, объект `game` может содержать обработчики по умолчанию `act`, `inv`, `use`, которые будут вызваны, если в результате действий пользователя не будут найдены никакие другие обработчики. Например, вы можете написать в начале игры:

    game.act = 'Не получается.';
    game.inv = 'Гм.. Странная штука..';
    game.use = 'Не сработает...';

## 11. Атрибуты-списки

Атрибуты-списки (такие как `way` или `obj`) позволяют работать с собой, таким образом позволяя реализовать динамически определяемые переходы между сценами, живые объекты и т.д.

Методы списков: `add`, `del`, `look`, `srch`, `purge`, `replace`. Из них наиболее часто используемые: `add` и `del`.

- `add` - добавляет в список.
- `del` – удаляет из него.
- `purge` – удаляет даже выключенный объект.
- `srch` – выполняет поиск объекта.
- `replace` – замена объекта.

Следует отметить, что параметром `add`, `del`, `purge`, `replace` и `srch` может быть не только сам объект или идентификатор объекта, но и имя объекта.

Начиная с версии 0.8 параметром `add` может быть сам объект. Кроме того, с этой версии добавляется необязательный второй параметр – позиция в списке. Начиная с версии 0.8 вы можете также выполнять модификацию списка по индексу с помощью метода `set`. Например:

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

Выше, вы уже видели пример со съеденным яблоком, там использовалась конструкция `inv():del('apple')`;

`inv()` – это функция, которая возвращает список инвентаря. `del` после `:` – метод, удаляющий элемент инвентаря.

Аналогично, собственная реализация `tak` может быть такой:

    knife = obj {
    	nam = 'нож',
    	dsc = 'На столе лежит {нож}',
    	inv = 'Острый!',
    	act = function(s)
    		objs():del(s);
    		inv():add(s);
    	end,
    };

Кроме добавления, удаления объектов из списков вы можете использовать выключение/включение объектов с помощью методов `enable()` и `disable()`. Например: `knife:disable()`. При этом объект `knife` пропадает из описания сцены, но в последствии может быть опять быть включен, с помощью `knife:enable()`.

Начиная с версии 0.9.1 доступны методы `zap()` и `cat()`. `zap()` – обнуляет список. `cat(b, [pos])` – добавляет в список содержимое `b` на позицию `pos`.

Начиная с версии 1.8.0 поддерживаются методы списков `enable` и `disable`, работающие аналогично одноименным методам объектов.

Внимание!!! На данный момент для работы с инвентарем и объектами рекомендуется использовать более высокоуровневые функции: `put`/`get`/`take`/`drop`/`remove`/`seen`/`have` и др.

## 12. Функции, которые возвращают объекты

В STEAD определены некоторые функции, которые возвращают наиболее часто используемые объекты. Например:

- `inv()` возвращает список инвентаря;
- `objs()` возвращает список объектов текущей сцены; (начиная с 0.8.5 – необязательный параметр – сцена, для которой возвращается список);
- `ways()` возвращает список возможных переходов из текущей сцены; (начиная с 0.8.5 – необязательный параметр – сцена, для которой возвращается список);
- `me()` возвращает объект-игрок;
- `here()` возвращает объект текущую сцену; (начиная с 0.8.5 – еще одна функция `where(obj)` – возвращает сцену на которой находится объект, если он был помещен туда с помощью `put/move/drop`)
- `from()` возвращает объект прошлой сцены;
- `seen(obj, [scene])` возвращает объект, если он присутствует и не отключен на сцене, есть второй необязательный параметр – сцена;
- `have(obj, [scene])` возвращает объект, если он есть в инвентаре и не отключен, есть второй необязательный параметр – сцена;
- `exist(obj, [scene])` возвращает объект, если он присутствует на сцене, есть второй необязательный параметр – сцена;
- `live(obj)` возвращает объект, если он присутствует среди живых объектов;
- `path(объект,[комната])` – найти элемент в `way`, даже если он `disabled`;

Комбинируя эти функции с методами `add`, `del` можно динамически менять сцену, например:

    ways():add('nextroom'); -- добавить переход на новую сцену;

    objs():add('chair'); -- добавить объект в текущую сцену;

Еще одна функция, которая получает объект по ссылке: `ref()`.

Например, мы можем добавить объект в локацию `home` следующим образом:

    ref('home').obj:add('chair');

Впрочем, следующая более простая запись тоже является корректной:

    home.obj:add('chair');

Или, для версии >=0.8.5:

    objs('home'):add('chair');

или, наконец:

    put('chair', 'home');

или даже:

    put(chair, home);

Начиная с 0.8.5 – `deref(o)`, возвращает ссылку-строку для объекта;

## 13. Некоторые вспомогательные функции.

В STEAD определены некоторые высокоуровневые функции, которые могут оказаться полезными при написании игры.

- `have()` – проверяет, есть ли объект в инвентаре. По объекту, его ссылке или по атрибуту `nam` объекта. Например:

    ...
    act = function(s)
    	if have('knife') then
    		return 'Но у меня же есть нож!';
    	end
    end
    ...

Следующие варианты тоже будут работать:

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

В следующих примерах вы также можете использовать все варианты адресации объектов.

- `move(o, w)` – переносит объект из текущей сцены в другую:

    move('mycat','inmycar');

Если вы хотите перенести объект из произвольной сцены, вам придется удалить его из старой сцены с помощью метода `del`. Для создания сложно перемещающихся объектов, вам придется написать свой метод, который будет сохранять текущую позицию объекта в самом объекте и делать удаление объекта из старой сцены.

Вы можете указать исходную позицию (комнату) объекта в качестве третьего параметра `move`.

    move('mycat','inmycar', 'forest');

Начиная с версии 0.8 присутствует также функция `movef`, аналогичная `move`, но добавляющая объект в начало списка.

- `seen(o)` – если объект присутствует в текущей сцене:

    if seen('mycat') then
    	move('mycat','inmycar');
    end

Начиная с 0.8.6 – необязательный второй параметр – сцена.

- `drop(o)` – положить объект из инвентаря на сцену:

    drop('knife');

Начиная с версии 0.8 присутствует также функция `dropf`, аналогичная `drop`, но добавляющая объект в начало списка. Начиная с версии 0.8.5 второй необязательный параметр – комната, куда помещается предмет. Кроме того, для версий >=0.8.5 доступны похожие функции `put`/`putf`, которые не удаляют предмет из инвентаря.

Начиная с 0.8.9 – присутствует функция `remove(o, [from])`, удаляет объект из текущей сцены или сцены `from`.

- `take(o)` – взять объект.

    	take('knife');

Начиная с версии 0.8.5 второй необязательный параметр – комната, с которой берется предмет.

- `taken(o)` – если объект взят – вернет `true` (взят с помощью `tak` или `take()`);
- `rnd(m)` – случайное значение от `1` до `m`.
- `walk(w)` – перейти в сцену `w`, при этом, если вы не используете `p`/`pn`/`pr` обработчику нужно вернуть возвращаемое значение `walk`. Например:

    act = code [[
            pn "Я иду в следующую комнату..."
            walk (nextroom);
    ]]
    ...
    act = code [[
            return cat('Я иду в следующую комнату', walk (nextroom));
    ]]

Внимание!!!

После вызова `walk` выполнение обработчика продолжится до его завершения.

- `change_pl(p)` – переключиться на другого игрока (со своим инвентарем и позицией). При этом функция переносит действие в сцену нового игрока без вызова exit/left/enter/entered. Для указания сцены, на которой находится игрок вы можете использовать атрибут where или явно вызывать функции `walk()` после change_pl().

    mycar = obj {
    	nam = 'моя машина',
    	dsc = 'Перед хижиной стоит мой старенький {пикап} Toyota.',
    	act = function(s)
    		return walk('inmycar');
    	end
    };

- `walkback()` – возвращается из сцены в прошлую.
- `back()` – возвращается из сцены в прошлую. Если это переход из диалога в комнату, то не вызываются: `dsc`, `enter`, `entered` у комнаты. `exit`/`left` диалога вызываются. В других случаях аналогична `goback`.
- `walkin(комната)` – перейти в сцену, при этом `exit`/`left` текущей комнаты не вызывается;
- `walkout()` – вернуться в прошлую сцену, при этом `enter`/`entered` этой сцены не вызовется;
- `time()` – возвращает текущее время игры. Время игры считается в активных действиях.
- `cat(…)` – возвращает строку – склейку строк-аргументов. Если первый аргумент `nil`, то функция возвращает `nil`.
- `par(…)` – возвращает строку – склейку строк-аргументов, разбитых строкой-первым параметром.
- `disable`/`enable`/`disable_all`/`enable_all` – аналог одноименных методов у объекта;
- `visited([комната])` – счетчик посещений комнаты или `nil`;
- `path(объект,[комната])` – найти элемент в `way`, даже если он `disabled`;
- `nameof(объект)` – вернуть имя объекта (`nam` атрибут);
- `purge (объект, [откуда])` – см. `remove`, удаляет даже выключенные объекты;
- `replace(объект, на объект, [где])` – замена одного объекта другим;
- `disabled(объект)` – возвращает `true`, если объект отключен;

## 14. Диалоги

Диалоги – это сцены, содержащие объекты – фразы. Например, простейший диалог может выглядеть следующим образом:

    povardlg = dlg {
    	nam = 'на кухне',
    	dsc = 'Передо мной полное лицо женщины - повара в белом колпаке и усталым взглядом...',
    	obj = {
    	[1] = phr('Мне вот-этих зелененьких... Ага -- и бобов!', 'На здоровье!'),
    	[2] = phr('Картошку с салом, пожалуйста!', 'Приятного аппетита!'),
    	[3] = phr('Две порции чесночного супа!!!', 'Прекрасный выбор!'),
    	[4] = phr('Мне что-нибудь легонькое, у меня язва...', 'Овсянка!'),
    	},
    };

`phr` – создание фразы. Фраза содержит вопрос, ответ и реакцию (реакция в данном примере отсутствует). Когда игрок выбирает одну из фраз, фраза отключается. Когда все фразы отключатся диалог заканчивается. Реакция – это строка кода на lua, который выполнится после отключения фразы. Например:

    food = obj {
    	nam = 'еда',
    	inv = function (s)
    		inv():del('food');
    		return 'Я ем.';
    	end
    };

    gotfood = function(w)
    	inv():add('food');
    	food._num = w;
    	back();
    end

    povardlg = dlg {
    	nam = 'на кухне',
    	dsc = 'Передо мной полное лицо женщины - повара в белом колпаке и усталым взглядом...',
    	obj = {
    	[1] = phr('Мне вот-этих зелененьких... Ага -- и бобов!', 'На здоровье!', [[pon(); gotfood(1);]]),
    	[2] = phr('Картошку с салом, пожалуйста!', 'Приятного аппетита!', [[pon(); gotfood(2);]]),
    	[3] = phr('Две порции чесночного супа!!!', 'Прекрасный выбор!', [[pon();gotfood(3);]]),
    	[4] = phr('Мне что-нибудь легонькое, у меня язва...', 'Овсянка!', [[pon(); gotfood(4);]]),
    	},
    };

В данном примере, игрок выбирает еду. Получает ее (запомнив выбор в переменной food._num) и возвращается обратно (в ту сцену откуда попал в диалог).

В реакции может быть любой lua код, но в STEAD определены наиболее часто используемые функции:

- `pon(n..)` – включить фразы диалога с номерами n… (в нашем примере – чтобы игрок мог повторно взять еду того-же вида).
- `poff(n…)` – выключить фразы диалога с номерами n…
- `prem(n…)` – удалить (заблокировать) фразы диалога с номерами n… (удаление означает невозможность включения фраз. `pon(n..)` не приведет к включению фраз).
- `pseen(n..)` – вернет `true`, если все заданные фразы диалога видимы.
- `punseen(n..)` – вернет `true`, если все заданные фразы диалога невидимы.

Если параметр `n` не указан, действие относится к текущей фразе.

Переход в диалог осуществляется как переход на сцену:

    povar = obj {
    	nam = 'повар',
    	dsc = 'Я вижу {повара}.',
    	act = function()
    		return walk('povardlg');
    	end,
    };

Вы можете переходить из одного диалога в другой диалог – организовывая иерархические диалоги.

Также, вы можете прятать некоторые фразы при инициализации диалога и показывать их при некоторых условиях.

    facectrl = dlg {
    	nam = 'фэйсконтроль',
    	dsc = 'Я вижу перед собой неприятное лицо полного охранника.',
    	obj = {
    		[1] = phr('Я пришел послушать лекцию Белина...',
    		'-- Я не знаю кто вы -- ухмыляется охранник -- но мне велели пускать сюда только приличных людей.',
    		[[pon(2);]]),
    		[2] = _phr('У меня есть приглашение!',
    		'-- А мне плевать! Посмотри на себя в зеркало!!! Ты пришел слушать самого Белина -- правую руку самого... -- охранник почтительно помолчал -- Так что пошел вон..', [[pon(3,4)]]),
    		[3] = _phr('Сейчас я дам тебе по роже!', '-- Ну все... Мощные руки выталкивают меня в коридор...',
    			[[poff(4)]]),
    		[4] = _phr('Ты, кабан! Я же тебе сказал -- у меня есть приглашение!',
    			'-- Чтоооооо? Глаза охранника наливаются кровью... Мощный пинок отправляет меня в коридор...',
    			[[poff(3)]]),
    	},
    	exit = function(s,w)
    		s:pon(1);
    	end,
    };

`_phr` – создает выключенную фразу, которую можно включить. Данный пример показывает также возможность использования методов `pon`, `poff`, `prem` для диалога (см. `exit`).

Вы можете включать/выключать/удалять/проверять фразы не только текущего, но и произвольного диалога, с помощью методов объекта диалог `pon`/`poff`/`prem`/`pseen`/`punseen`. Например: `shopman:pon(5);`

### Новые диалоги

Начиная с версии INSTEAD 1.7.0 поддерживается новый более простой и мощный синтаксис диалогов. Фразы определяются в аттрибуте phr диалога, например:

    povardlg = dlg {
    	nam = 'на кухне',
    	entered = 'Передо мной полное лицо женщины - повара в белом колпаке и усталым взглядом...',
    	phr = {
    	    { always = true, 'Мне вот-этих зелененьких... Ага -- и бобов!', 'На здоровье!'},
    	    { always = true, 'Картошку с салом, пожалуйста!', 'Приятного аппетита!'},
    	    { always = true, 'Две порции чесночного супа!!!', 'Прекрасный выбор!' },
    	    { always = true, 'Мне что-нибудь легонькое, у меня язва...', 'Овсянка!' },
    	},
    };

Имейте в виду, что если в диалоге не определен атрибут dsc, то он формируется движком таким образом, чтобы отражать последнюю реакцию диалога, то-есть, если игрок нажмет на заголовок сцены он увидит последний ответ на свою реплику еще раз. Если вы рассчитываете на такое поведение диалога, то первоначальную реакцию диалога удобнее всего вписать в entered, как в примере выше.

Каждая фраза имеет вид:

    { [номер или tag=тэг,][false если выключена,][always = true],
             "Вопрос", "Ответ", [[ необязательный код ]] },

Вы можете использовать номер, вместо записи [1] = …, например:

    { 2, "Вопрос?", "Ответ!" }

Для сложных диалогов более удобными являются теги, например:

    { tag = 'exit', "Ну ладно, я пошел!", code [[ back() ]] }

Как видно, тег – это текстовая метка фразы. Вы можете делать pon/poff/pseen/punseen как с пронумерованными фразами, так и с фразами, имеющими тег. В случае, если один и тот-же тег стоит у нескольких фраз, то действие применяется на все фразы с одинаковым тегом. Для функции pseen, видимость тега означает наличие хотя бы одной фразы с таким тегом, для функции punseen – отсутствие включенных фраз с заданным тегом.

Вы можете присваивать тег и пронумерованной фразе, если это требуется.

Присутствие в фразе always = true означает, что фраза не будет автоматически выключена при ее срабатывании:

    { tag = 'exit', always = true, "Ну ладно, я пошел!", code [[ back() ]] }

Если необходимо опустить ответ фразы и всю реакцию описать в параметре «необязательный код», то следующие варианты записи являются допустимыми:

    { tag = 'exit', always = true, "Ну ладно, я пошел!", nil, [[ back() ]] },
    { tag = 'exit', always = true, "Ну ладно, я пошел!", code = [[ back() ]] }

Вы также можете задавать вопрос и ответ в виде функций или code.

    { tag = 'exit', code [[ p "Ну ладно, я пошел!" ]], code [[ p "Может, останешься?"; pon 'really' ]] },
    { tag = 'really', false,
      always = true,
      "Я точно пошел!",
      function() back() end } -- эта фраза выключена и включается предыдущей

Используя новый вариант диалогов, вы можете группировать фразы в ветви, тем самым организуя иерархические диалоги без необходимости использования pon/poff и перехода между несколькими dlg. Группа фраз это набор фраз, отделенная от другой группы фразой, у которой нет реакции (пример такой простейшей фразы это пара скобок {}). Например:

    { 'Расскажи что-нибудь о погоде?',
        'Хорошо, что тебя интересует?', [[ psub 'погода' ]] },
    { always=true, [[Пока!]], code = [[ back() ]] },
    { tag = 'погода' },
    { 'Какая температура?', '25 градусов!' },
    { 'Какая влажность?', '80%' },

В диалоге показывается только текущая группа фраз. В примере выше мы видим две группы. При входе в диалог, игрок увидит выбор из двух фраз: «Расскажи что-нибудь…» и «Пока!». Выбрав первую фразу, он попадет в подветку с тегом 'погода', в которой увидит два вопроса (о температуре и влажности). Когда он задаст оба вопроса, то переместится снова на первую ветку, где останется активной только одна фраза: «Пока!».

В данном примере группы разделены фразой: { tag = 'погода' }, но также точно разделителем могла стать пустая фраза без тега:

    { 'Расскажи что-нибудь о погоде?',
        'Хорошо, что тебя интересует?', [[ psub 'погода' ]] },
    { always="true", [[Пока!]], code = [[ back() ]] },
    { },
    { tag = 'погода', 'Какая температура?', '25 градусов!' },
    { 'Какая влажность?', '80%' },

Переход на ветку осуществляется с помощью команд:

- psub – переход с возвратом. Если все ответы ветки были исчерпаны или явно, с помощью pret();
- pjump - безусловный переход.
- pstart - безусловный переход с обнулением истории переходов по psub.

В качестве аргумента psub/pstart/pjump может быть указан номер или тег. Вы можете использовать эти функции и извне диалога, аналогично pon/poff и т.д, с помощью звписи: диалог:метод(), например: shopdlg:pstart(1)

Для того, чтобы узнать текущую подветку, используйте методы диалога диалог:current() и диалог:curtag(). Первый всегда возвращает номер, а второй – тег.

Проверку состояния ветки можно осуществить с помощью функций:

- диалог:empty();
- диалог:visible();

Обе функции могут получать параметр – номер или тег фразы, с которой начинается анализ группы. :empty() возвращает true, в случае, если в группе нет активных фраз. :visible() возвращает число видимых фраз (0 – если группа пуста). В случае, если параметр не указан, анализируется текущая группа.

В случае перехода по psub/pstart/pjump, первая фраза, на который выполняется переход, может служить заголовком группы фраз.

Например:

    { 'Расскажи что-нибудь о погоде?', code = [[ psub 'погода' ]] },
    { always=true, [[Пока!]], code = [[ back() ]] },
    { },
    { tag = 'погода', "Хорошо, что тебя интересует?" },
    { 'Какая температура?', '25 градусов!' },
    { 'Какая влажность?', '80%' },

фраза с тегом 'погода' не содержит в себе реакцию, и выполняет роль заголовка ветки. При переходе на ветку 'погода' с помощью psub будет выведен текст «Хорошо, что тебя интересует?».

Как вы знаете, вопрос может быть функцией, тем самым позволяя выполнять код при переходе между ветками:

    { 'Расскажи что-нибудь о погоде?', code = [[ psub 'погода' ]] },
    { always=true, [[Пока!]], code = [[ back() ]] },
    { },
    { tag = 'погода', function() p "Хорошо, что тебя интересует?"; weather_asked = true; end },
    { 'Какая температура?', '25 градусов!' },
    { 'Какая влажность?', '80%' },

Кроме того, заголовок группы может содержать метод empty, который вызывается в ситуации, когда все вопросы данной группы исчерпаны:

    { 'Расскажи что-нибудь о погоде?', code = [[ psub 'погода' ]] },
    { always=true, [[Пока!]], code = [[ back() ]] },
    { },
    { tag = 'погода', "Хорошо, что тебя интересует?",
        empty = code [[ p "Хватит о погоде..."; pret() ]] },
    { 'Какая температура?', '25 градусов!' },
    { 'Какая влажность?', '80%' },

empty вызывается в ситуации, когда в ветке не остается фраз. Если empty не определена, то действие по-умолчанию это возврат по pret(). Если вы переопределяете empty, вам придется вызвать pret() явно, если это требуется.

Законченный пример реализации диалога, вы можете посмотреть здесь: http://instead.googlecode.com/svn/trunk/doc/examples/dialog/main.lua

## 15. Облегченные объекты

Иногда, сцену нужно наполнить декорациями, которые обладают ограниченной функциональностью, но делают игру разнообразней. Для этого можно использовать облегченный объект. Например:

    sside = room {
    	nam = 'южная сторона',
    	dsc = [[Я нахожусь у южной стены здания института. ]],
    	act = function(s, w)
    		if w == "подъезд" then
    			ways():add('stolcorridor');
    			p "Я подошел к подъезду. На двери подъезда надпись -- 'Столовая'. Хм -- зайти внутрь?";
    		elseif w == "люди" then
    			p 'Те, кто выходят, выглядят более довольными...';
    		end
    	end,
    	obj = { vobj("подъезд", "У восточного угла находится небольшой {подъезд}."),
    		vobj("люди", "Время от времени дверь подъезда хлопает впуская и выпуская {людей}.")},
    };

Как видим, `vobj` позволяет сделать легкую версию статического объекта, с которым тем не менее можно взаимодействовать (за счет определения обработчика `act` в сцене и анализа имени объекта). `vobj` также вызывает метод `used`, при этом в качестве третьего параметра передается объект, воздействующий на виртуальный объект.

Синтаксис `vobj`: `vobj(имя, описатель)`;

Существует модификация объекта `vobj` – `vway`. `vway` реализует ссылку. Синтаксис `vway`: `vway(имя, описатель, сцена назначения);` например:

    	obj = { vway("дальше", "Нажмите {здесь}.", 'nextroom') }

Вы можете динамически заполнять сцену объектами `vobj` или `vway` с помощью методов `add` и `del`. Например:

    	objs(home):add(vway("next", "{Дальше}.", 'next_room'));
    	-- здесь какой-нибудь код
    	home.obj:del("next");

Определена также упрощенная сцена `vroom`. Синтаксис: `vroom(имя перехода, сцена назначения)`. Например:

    	home.way:add(vroom("идти на запад", 'mountains');

## 16. Динамические события

Вы можете определять обработчики, которые выполняются каждый раз, когда время игры увеличивается на 1. Например:

    mycat = obj {
    	nam = 'Барсик',
    	lf = {
    		[1] = 'Барсик шевелится у меня за пазухой.',
    		[2] = 'Барсик выглядывает из-за пазухи.',
    		[3] = 'Барсик мурлычит у меня за пазухой.',
    		[4] = 'Барсик дрожит у меня за пазухой.',
    		[5] = 'Я чувствую тепло Барсика у себя за пазухой.',
    		[6] = 'Барсик высовывает голову из-за пазухи и осматривает местность.',
    	},
    	life = function(s)
    		local r = rnd(6);
    		if r > 2 then
    			return;
    		end
    		r = rnd(6);
    		return s.lf[r];
    	end,
    ....

    profdlg2 = dlg {
    	nam = 'Белин',
    	dsc = 'Белин бледен. Он смотрит на дробовик рассеянным взглядом.',
    	obj = {
    		[1] = phr('Я пришел за своим котом.',
    	'Я выхватываю Барсика из руки Белина и засовываю себе за пазуху.',
    		[[inv():add('mycat'); lifeon('mycat')]]),
    ....

Любой объект или сцена могут иметь свой обработчик `life`, который вызывается каждый раз при смене текущего времени игры, если объект или сцена были добавлены в список живых объектов с помощью `lifeon`. Не забывайте удалять живые объекты из списка с помощью `lifeoff`, когда они больше не нужны. Это можно сделать, например, в обработчике `exit`, или любым другим способом.

`life` метод может возвращать текст события, который печатается после описания сцены.

Начиная с версии 0.9.1 вы можете вернуть из обработчика `life` второй код возврата, важность. (`true` или `false`). Например:

    return 'В комнату вошел охранник.', true

Или:

        p 'В комнату вошел охранник.'
        return true

При этом текст события будет выведен до описания объектов.

## 17. Графика и музыка

Графический интерпретатор анализирует атрибут сцены `pic`, и воспринимает его как путь к картинке, например:

    home = room {
    	pic = 'gfx/home.png',
    	nam = 'дома',
    	dsc = 'Я у себя дома',
    };

Конечно, `pic` может быть функцией, расширяя возможности разработчика. Если в текущей сцене не определен атрибут `pic`, то берется атрибут `game.pic`. Если не определен и он, то картинка не отображается.

Начиная с версии 0.9.2 вы можете использовать в качестве картинок анимированные gif файлы.

Начиная с версии 0.9.2 вы можете встраивать графические изображения в текст или в инвентарь с помощью функции `img`. Например:

    knife = obj {
    	nam = 'Нож'..img('img/knife.png'),
    }

А начиная с 1.3.0 поддерживается обтекание картинок текстом. Если картинка вставляется с помощью функции `imgl`/`imgr`, она будет расположена у левого/правого края. Такие картинки не могут быть ссылками.

Для задания отступов вокруг изображения используйте `pad`, например:

    imgl 'pad:16,picture.png' -- отступы по 16 от каждого края
    imgl 'pad:0 16 16 4,picture.png' -- отступы: вверху 0, справа 16, внизу 16, слева 4
    imgl 'pad:0 16,picture.png' -- отступы: вверху 0, справа 16, внизу 0, слева 16

Вы можете использовать псевдо-файлы для изображений прямоугольников и пустых областей:

    dsc = img 'blank:32x32'..[[Строка с пустым изображением.]];
    dsc = img 'box:32x32,red,128'..[[Строка красным полупрозрачным квадратом.]];

В современной версии INSTEAD вы можете использовать атрибут `disp`:

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

Начиная с версии 1.0.0 интерпретатор может обрабатывать составные картинки, например:

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

Интерпретатор проигрывает в цикле текущую музыку, которая задается с помощью функции: `set_music(имя музыкального файла)`.

Например:

    street = room {
    	pic = 'gfx/street.png',
    	enter = function()
    		set_music('mus/rain.ogg');
    	end,
    	nam = 'на улице',
    	dsc = 'На улице идет дождь.',
    };

`get_music()` возвращает текущее имя трека.

Начиная с версии 0.7.7 в функцию `set_music()` можно передавать второй параметр – количество проигрываний. Получить текущий счетчик можно с помощью `get_music_loop`. 0 – означает вечный цикл. 1..n – количество проигрываний. -1 – проигрывание текущего трека закончено.

Начиная с версии 0.9.2 `set_sound()` позволяет проиграть звуковой файл. `get_sound()` возвращает имя звукового файла, который будет проигран.

`set_sound('snd', 'channel', 'loop')`, где:

- snd – путь и\или имя звукового файла;
- channel – номер канала 0..7;
- loop – количество проигрываний 1..n, 0 – зацикливание.

Для того, чтобы отменить проигрывание, вы можете использовать `stop_music()` (с версии 1.0.0). Так же для остановки проигрывания звука можно использовать `stop_sound()` (с версии 1.4.0). Для остановки в определенном канале `stop_sound('channel')`.

`is_music()` позволяет узнать, проигрывается ли музыка. (с версии 1.0.0)

## 18. Полезные советы

### Разбиение на файлы

Для разбиения текста игры на файлы вы можете использовать `dofile`. Вы должны использовать `dofile` в глобальном контексте таким образом, чтобы во время загрузки `main.lua` загрузились и все остальные фрагменты игры, например.

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

Для динамической подгрузки частей игры (с возможностью переопределения существующих объектов), вы можете воспользоваться `gamefile`:

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

`gamefile` позволяет загрузить новый файл и забыть стек предыдущих загрузок, запустив этот новый файл как самостоятельную игру.

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

### Модули

Подробнее о модулях

Начиная с версии 1.2.0 появилась возможность использования модулей с помощью `require`. На данный момент существуют следующие модули:

- `dbg` — модуль отладки (`require «dbg»` – включить отладчик);
- `walk` — улучшенный вариант реализации переходов;
- `xact` — множественные ссылки;
- `input` — клавиатурный ввод;
- `click` — модуль перехвата кликов мыши по картинке сцены;
- `vars` — модуль определения переменных;
- `prefs` — модуль настроек;
- `snapshots` — модуль поддержки снапшотов;
- `format` — модуль оформления вывода;
- `object` — модуль улучшенных объектов;
- `theme` — управление темой;

Использование модуля выглядит так:

    --$Name: Моя игра!$
    instead_version "1.2.0"
    require "para"
    require "dbg"
    ...

Следующие модули подключаются автоматически, если вы задали version >= 1.2.0: `vars`, `object`, `walk`.

Объект `prefs` (находится в модуле `prefs`) служит для сохранения настроек игры, например, его можно использовать для реализации системы достижений или счетчика количества прохождений…

      require "prefs"
    ...
        prefs.counter = 0
    ...
        exit = function(s)
            prefs.counter = prefs.counter + 1
            prefs:store()
        end
    ...
        enter = function(s)
            return 'Вы прошли игру '..prefs.counter..' раз(а)';
        end
    ...
        act = function(s)
            prefs:purge()
            return "Настройки обнулены"
        end

Модуль `xact` позволяет делать ссылки на объекты из других объектов, реакций и `life` методов в форме {объект|строка} следующим образом:

    ...
        act = [[ Под столом я заметил {knife|нож}.]]
    ...

При этом, объект может быть объектом или именем объекта.

Примечание: до версии 1.2.2 использовался следующий формат ссылок: {объект:строка}.

В этом модуле определены также такие объекты как `xact` и `xdsc`.

`xact` – объект - простейшая реакция. Например:

    main = room {
        forcedsc = true;
        dsc = [[От автора. Эту игру я писал очень  {note1|долго}.]];
        obj = {
            xact('note1', [[Больше 10 лет.]]);
        }
    }

Реакция может содержать код:

            xact('note1', code [[p "Больше 10 лет."]]);

`xdsc` позволяет вставить в список объектов множественное описание:

    main = room {
        forcedsc = true;
        dsc = [[Я в комнате.]];
        xdsc = [[ Я вижу {apple|яблоко} и {knife|нож}. ]];
        other = [[ Еще здесь лежат {chain|цепь} и {tool|пила}.]];
        obj = {
            xdsc(), -- 'xdsc method by default'
            xdsc('other'),
            'apple', 'knife', 'chain', 'tool',
        }
    }

Вы можете также использовать комнату `xroom`:

    main = xroom {
        forcedsc = true;
        dsc = [[Я в комнате.]];
        xdsc = [[ Я вижу {apple|яблоко} и {knife|нож}. ]];
        obj = {
            'apple', 'knife', 'chain', 'tool',
        }
    }

Модуль `input` позволяет реализовывать простые поля ввода, а `click` отслеживает щелчки по картинке сцены.

Модуль `format` выполняет форматирование вывода. По умолчанию все настройки выключены :

    format.para = false -- отступы в начале абзаца;
    format.dash = false -- замена двойного - на тире;
    format.quotes = false -- замена " " на << >>;
    format.filter = nil -- пользовательская функция замены;

Вы можете пользоваться модулями `para`, `dash`, `quotes` для включения отдельных настроек.

### Форматирование

Вы можете делать простое форматирование текста с помощью функций:

- `txtc()` - разместить по центру;
- `txtr()` - разместить справа;
- `txtl()` - разместить слева;
- `txttop()` - сверху строки;
- `txtbottom()` - снизу строки;
- `txtmiddle()` - середина строки (по умолчанию);

Например:

    main = room {
    	nam = 'Intro',
    	dsc = txtc('Добро пожаловать!'),
    }

Вы также можете менять оформление текста с помощью комбинаций функций:

- `txtb()` - жирный;
- `txtem()` - курсив;
- `txtu()` - подчеркнутый;
- `txtst()` - перечеркнутый;

Например:

    main = room {
    	nam = 'Intro',
    	dsc = 'Вы находитесь в комнате '..txtb('main')..'.',
    }

Начиная с версии 1.1.0 вы также можете создавать неразрываемые строки с помощью: `txtnb()`.

### Меню

Вы можете делать меню в области инвентаря, определяя объекты с типом `menu`. При этом, обработчик меню будет вызван после одного клика мыши. Если обработчик не возвращает текст, то состояние игры не изменяется. Например, реализация кармана:

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

    knife = obj {
    	nam = 'нож',
    	inv = 'Это нож',
    };

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

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

### Статус игрока

Ниже представлена реализация статуса игрока в виде текста, который появляется в инвентаре, но не может быть выбран.

    global {
        life = 10;
        power = 10;
    }

    status = stat {
    	nam = function(s)
    		p ('Жизнь: ', life, 'Сила: ', power)
    	end
    };
    function init()
        inv():add('status');
    end

### walk из обработчиков exit и enter

Вы можете делать `walk` из обработчиков `enter` и `exit`.

### Динамически создаваемые ссылки.

Динамически создаваемые ссылки могут быть реализованы разным способом. Ниже приводится пример, основанный на использовании объектов `vway`. Для добавления ссылки можно использовать запись:

    objs(home):add(vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'));

Для удаления ссылки можно использовать метод `del`.

    objs(home):del('Дорога');

Для определения наличия ссылки в сцене – метод `srch`.

    if not objs(home):srch('Дорога') then
    	objs(home):add(vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'));
    end

Динамические ссылки удобно создавать в обработчике `enter`, или по мере необходимости в любом месте кода игры. Если ссылки создаются в текущей сцене, то последний пример можно упростить:

    if not seen('Дорога') then
    	objs():add(vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'));
    end

Кроме того, вы можете просто включать и выключать ссылки с помощью `enable()`, `disable()`, например:

    	seen('Дорога', home):disable();
            exsist('Дорога', home):enable();

Вы можете создавать выключенные `vobj` и `vway` следующим образом:

    	obj = {vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'):disable()},

И затем включать их по индексу в массиве `obj` или иным способом (`seen`/`srch`/`exist`):

    	objs()[1]:enable();

### Кодирование исходного кода игры (начиная с версии 0.9.3)

Если вы не хотите показывать исходный код своих игр, вы можете закодировать исходный код с помощью `sdl-instead -encode <путь к файлу> [выходной путь]` и использовать его с помощью lua функции `doencfile`. При этом главный файл `main.lua` необходимо оставлять текстовым. Таким образом схема выглядит следующим образом (`game` – закодированный `game.lua`):

    -- $Name: Моя закрытая игра!$
    doencfile("game");

Не используйте компиляцию игр с помощью luac, так как luac создает платформозависимый код! Однако, компиляция игр может быть использована для поиска ошибок в коде.

### Запаковка ресурсов (начиная с версии 1.4.0)

Вы можете упаковать ресурсы игры (графику, музыку, темы) в файл .idf, для этого поместите все ресурсы в каталог data и запустите INSTEAD:

`instead -idf <путь к data>`

При этом в текущем каталоге должен будет создастся файл `data.idf`. Поместите его в каталог с игрой. Теперь ресурсы игры в виде отдельных файлов можно удалить.

Вы можете запаковать в формат `.idf` всю игру:

`instead -idf <путь к игре>`

Игры в формате `idf` можно запускать как обычные игры `instead` (как если бы это были каталоги) а также из командной строки:

`instead game.idf`

### Переключение между игроками

Вы можете создать игру с несколькими персонажами и время от времени переключаться между ними (см. `change_pl`). Но вы можете также использовать этот трюк для того, чтобы иметь возможность переключаться между разными типами инвентаря.

### Использование первого параметра обработчика

Пример кода.

    stone = obj {
    	nam = 'камень',
    	dsc = 'На краю лежит {камень}.',
    	act = function()
    		objs():del('stone');
    		return 'Я толкнул камень, он сорвался и улетел вниз...';
    	end

Обработчик act мог бы выглядеть проще:

    	act = function(s)
    		objs():del(s);
    		return 'Я толкнул камень, он сорвался и улетел вниз...';
    	end

### Использование set_music

Вы можете использовать `set_music` для проигрывания звуков, задавая второй параметр – счетчик циклов проигрывания звукового файла.

Вы можете написать для игры свой проигрыватель музыки, создав его на основе живого объекта, например:

    -- играет треки в случайном порядке, начиная со 2-го
    tracks = {"mus/astro2.mod", "mus/aws_chas.xm", "mus/dmageofd.xm", "mus/doomsday.s3m"}
    mplayer = obj {
    	nam = 'плеер',
    	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');

Вы можете использовать функции `get_music_loop` и `get_music`, для того, чтобы запоминать прошлую мелодию, и потом восстанавливать ее, например:

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

Начиная с версии 0.8.5 функции save_music и restore_music уже присутствуют в библиотеке.

### Живые объекты

Если вашему герою нужен друг, одним из способов может стать метод `life` этого персонажа, который всегда переносит объект в локацию игрока:

    horse = obj {
    	nam = 'лошадь',
    	dsc = 'Рядом со мной стоит {лошадь}.',
    	life = function(s)
    		if not seen('horse') then
    			move('horse', here(), s.__where);
    			s.__where = pl.where;
    		end
    	end,
    };
    function init()
        lifeon('horse');
    end

### Таймер

Начиная с версии 1.1.0 в instead появлилась возможность использовать таймер. (Только в графической версии интерпретатора.)

Таймер программируется с помощью объекта `timer`.

- `timer:set(ms)` – задать интервал таймера в ms
- `timer:stop()` – остановить таймер
- `timer.callback(s)` – функция-обработчик таймера, которая вызывается через заданный диапазон времени

Функция таймера может возвратить команду интерфейса stead, которую нужно выполнить после того, как движок выполнит обработчик. Например:

    timer.callback = function(s)
        main._time = main._time + 1;
        return "look";
    end
    timer:set(100);
    main = room {
        _time = 1,
        forcedsc = true,
        nam = 'Таймер',
        dsc = function(s)
    	return 'Демонстрация: '..tostring(s._time);
        end
    };

### Клавиатура

Начиная с версии 1.1.0 в instead появилась возможность анализировать ввод с клавиатуры (только в графической версии интерпретатора). Для этого используется объект `input`.

`input.key(s, pressed, key)` – обработчик клавиатуры; `pressed` – нажатие или отжатие. `key` – символьное имя клавиши;

Обработчик может вернуть команду интерфейса `stead`, в этом случае клавиша не будет обработана интерпретатором. Например:

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

    main = room {
    	_txt = '_',
    	forcedsc = true,
    	nam = 'Клавиатура',
    	dsc = function(s)
    		return 'Демонстрация: '..tostring(s._txt);
    	end
    };

### Мышь

Внимание!!! Начиная с 1.2.0 рекомендуется использовать модуль click.

Начиная с версии 1.1.5 в instead появилась возможность анализировать события мыши. (Только в графической версии интерпретатора.) Для этого используется объект `input`.

`input.click(s, pressed, mb, x, y, px, py)` – обработчик клика мыши; `pressed` – нажатие или отжатие. `mb` – номер кнопки (1 - левая), `x` и `y` – координаты клика относительно левого верхнего угла. `px` и `py` присутствуют, если клик произошел в области картинки сцены и содержит координаты клика относительно левого верхнего угла картинки.

Обработчик может вернуть команду интерфейса stead, в этом случае клик не будет обработан интерпретатором.

Например:

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

    click = obj {
    	nam = 'клик',
    	x = 0,
    	y = 0,
    	dsc = function(s)
    		return "Вы кликнули по картинке в позиции: "..s.x..','..s.y..'.';
    	end
    }:disable();

    main = room {
    	nam = 'test',
    	pic ='picture.png',
    	dsc = 'Демонстрация.',
    	obj = { 'click' },
    };

Пример прослойки, которая реализует вызов метода `click` в текущей комнате при клике на картинку:

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

    game.action = function(s, cmd, x, y)
    	if cmd == 'click' then
    		return call(here(), 'click', x, y);
    	end
    end
    ----------------------------------------------------------------------
    main = room {
    	nam = 'test',
    	pic ='picture.png',
    	dsc = 'Демонстрация.',
    	click = function(s, x, y)
    		return "Вы кликнули по картинке в позиции: "..x..','..y..'.';
    	end
    };

### Динамическое создание объектов

Вы можете использовать функции `new` и `delete` для создания и удаления динамических объектов. Примеры:

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

`new` воспринимает строку-аргумент как конструктор объекта. Результатом выполнения конструктора должен быть объект. Таким образом в аргументе обычно задан вызов функции-конструктора. Например:

    function myconstructor()
    	local v = {}
    	v.nam = 'тестовый объект',
    	v.act = 'Тестовая реакция',
    	return obj(v);
    end

Созданный объект будет попадать в файл сохранения. `new()` возвращает реальный объект; чтобы получить его имя, если это нужно, используйте функцию `deref`:

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

### Сложный вывод из обработчиков

Иногда вывод обработчика может формироваться сложным образом, в зависимости от условий. В таких случаях удобно пользоваться функциями `p()` и `pn()`. Эти функции добавляют текст в буфер, связанный с обработчиком, который будет возвращен из обработчика.

    dsc = function(s)
    	p "На полу стоит {бочка}."
    	if s._opened then
    		p "Крышка от бочки лежит рядом."
    	end
    end

Функция `pn()` выполняет вывод текста в буфер, дополняя его переводом строки. Функция `p()` дополняет вывод пробелом.

Начиная с версии 1.1.6 существует функция `pr()`, которая не выполняет дополнение вывода.

Для очистки буфера, используйте `pclr()`. Если вам нужно вернуть статус действия, используйте `pget()`, или просто используйте `return`.

    use = function(s, w)
    	if w == apple then
    		p 'Гм... Я почистил яблоко.';
    		apple._peeled = true
    		return
    	end
    	p 'Это нельзя использовать так!'
    	return false; -- или return pget(), false
    end

### Отладка

Для того, чтобы во время ошибки увидеть стек вызовов функций lua, вы можете запустить `sdl-instead` с параметром `-debug`. При этом в windows версии интерпретатора будет создана консоль отладки.

Вы можете отлаживать свою игру вообще без instead. Например, вы можете создать следующий файл `game.lua`:

    dofile("/usr/share/games/stead/stead.lua"); -- путь к stead.lua
    dofile("main.lua"); -- ваша игра
    game:ini();
    iface:shell();

И запустите игру в lua: lua game.lua. При этом игра будет работать в примитивном `shell` окружении. Полезные команды: `ls`, `go`, `act`, `use`…

Для включения простого отладчика, после `version` вначале файла напишите:

    require "dbg"

Отладчик вызывается по `F7`.

## 19. Темы для sdl-instead

Графический интерпретатор поддерживает механизм тем. Тема представляет из себя каталог, с файлом `theme.ini` внутри.

Тема, которая является минимально необходимой – это тема `default`. Эта тема всегда загружается первой. Все остальные темы наследуются от нее и могут частично или полностью заменять ее параметры. Выбор темы осуществляется пользователем через меню настроек, однако конкретная игра может содержать собственную тему и таким образом влиять на свой внешний вид. В этом случае в каталоге с игрой должен находиться свой файл `theme.ini`. Тем не-менее пользователь свободен отключить данный механизм, при этом интерпретатор будет предупреждать о нарушении творческого замысла автора игры.

Синтаксис `theme.ini` очень прост.

    <параметр> = <значение>

или

    ; комментарий

Значения могут быть следующих типов: строка, цвет, число.

Цвет задается в форме #rgb, где r g и b компоненты цвета в шестнадцатеричном виде. Кроме того некоторые основные цвета распознаются по своим именам. Например: yellowgreen, или violet.

Параметры могут принимать значения:

- `scr.w` = ширина игрового пространства в пикселях (число)
- `scr.h` = высота игрового пространства в пикселях (число)
- `scr.col.bg` = цвет фона
- `scr.gfx.bg` = путь к картинке фонового изображения (строка)
- `scr.gfx.cursor.x` = x координата центра курсора (число) (версия >= 0.8.9)
- `scr.gfx.cursor.y` = y координата центра курсора (число) (версия >= 0.8.9)
- `scr.gfx.cursor.normal` = путь к картинке-курсору (строка) (версия >= 0.8.9)
- `scr.gfx.cursor.use` = путь к картинке-курсору режима использования (строка) (версия >= 0.8.9)
- `scr.gfx.use` = путь к картинке-индикатору режима использования (строка) (версия < 0.8.9)
- `scr.gfx.pad` = размер отступов к скролл-барам и краям меню (число)
- `scr.gfx.x`, `scr.gfx.y`, `scr.gfx.w`, `scr.gfx.h` = координаты, ширина и высота окна изображений. Области в которой располагается картинка сцены. Интерпретация зависит от режима расположения (числа)
- `win.gfx.h` - синоним `scr.gfx.h` (для совместимости)
- `scr.gfx.mode` = режим расположения (строка `fixed`, `embedded` или `float`). Задает режим изображения. `embedded` – картинка является частью содержимого главного окна, параметры `scr.gfx.x`, `scr.gfx.y`, `scr.gfx.w` игнорируются. `float` – картинка расположена по указанным координатам (scr.gfx.x, scr.gfx.y) и масштабируется к размеру `scr.gfx.w` x `scr.gfx.h` если превышает его. `fixed` – картинка является частью сцены как в режиме embedded, но не скроллируется вместе с текстом а расположена непосредственно над ним.
- `win.x`, `win.y`, `win.w`, `win.h` = координаты, ширина и высота главного окна. Области в которой располагается описание сцены (числа)
- `win.fnt.name` = путь к файлу-шрифту (строка)
- `win.fnt.size` = размер шрифта главного окна (размер)
- `win.fnt.height` = междустрочный интервал как число с плавающей запятой (1.0 по умолчанию)
- `win.gfx.up`, `win.gfx.down` = пути к файлам-изображениям скорллеров вверх/вниз для главного окна (строка)
- `win.up.x`, `win.up.y`, `win.down.x`, `win.down.y` = координаты скроллеров (координата или -1)
- `win.col.fg` = цвет текста главного окна (цвет)
- `win.col.link` = цвет ссылок главного окна (цвет)
- `win.col.alink` = цвет активных ссылок главного окна (цвет)
- `inv.x`, `inv.y`, `inv.w`, `inv.h` = координаты, высота и ширина области инвентаря. (числа)
- `inv.mode` = строка режима инвентаря (`horizontal` или `vertical`). В горизонтальном режиме инвентаря в одной строке могут быть несколько предметов. В вертикальном режиме, в каждой строке инвентаря содержится только один предмет. (число)
- `inv.col.fg` = цвет текста инвентаря (цвет)
- `inv.col.link` = цвет ссылок инвентаря (цвет)
- `inv.col.alink` = цвет активных ссылок инвентаря (цвет)
- `inv.fnt.name` = путь к файлу-шрифту инвентаря (строка)
- `inv.fnt.size` = размер шрифта инвентаря (размер)
- `inv.fnt.height` = междустрочный интервал как число с плавающей запятой (1.0 по умолчанию)
- `inv.gfx.up`, `inv.gfx.down` = пути к файлам-изображениям скорллеров вверх/вниз для инвентаря (строка)
- `inv.up.x`, `inv.up.y`, `inv.down.x`, `inv.down.y` = координаты скроллеров (координата или -1)
- `menu.col.bg` = фон меню (цвет)
- `menu.col.fg` = цвет текста меню (цвет)
- `menu.col.link` = цвет ссылок меню (цвет)
- `menu.col.alink` = цвет активных ссылок меню (цвет)
- `menu.col.alpha` = прозрачность меню 0-255 (число)
- `menu.col.border` = цвет бордюра меню (цвет)
- `menu.bw` = толщина бордюра меню (число)
- `menu.fnt.name` = путь к файлу-шрифту меню (строка)
- `menu.fnt.size` = размер шрифта меню (размер)
- `menu.fnt.height` = междустрочный интервал как число с плавающей запятой (1.0 по умолчанию)
- `menu.gfx.button` = путь к файлу изображению значка меню (строка)
- `menu.button.x`, `menu.button.y` = координаты кнопки меню (числа)
- `snd.click` = путь к звуковому файлу щелчка (строка)
- `include` = имя темы (последний компонент в пути каталога) (строка)

Кроме того, заголовок темы может включать в себя комментарии с тегами. На данный момент существует только один тег: $Name:, содержащий UTF-8 строку с именем темы. Например:

    ; $Name:Новая тема$
    ; модификация темы book
    include = book
    scr.gfx.h = 500

Интерпретатор выполняет поиск тем в каталоге `themes`. Unix версия кроме этого каталога, просматривает также каталог `~/.instead/themes/` Windows версия (>=0.8.7): `Documents and Settings/USER/Local Settings/Application Data/instead/themes`