ru:gamedev:documentation

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    instead_version "1.3.1"

Если 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 – замена объекта. Следует отметить, что параметром 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.

Начиная с версии 0.9.1 доступны методы disable_all и enable_all, выключающие и включающие вложенные в объект объекты.

Внимание!!! На данный момент для работы с инвентарем и объектами рекомендуется использовать более высокоуровневые функции: 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.

goto(w) – перейти в сцену w, при этом, если вы не используете p/pn/pr обработчику нужно вернуть возвращаемое значение goto. Например:

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

Внимание!!! После вызова goto выполнение обработчика продолжится до его завершения. change_pl(p) – переключиться на другого игрока (со своим инвентарем и позицией). При этом функция возвращает описание сцены нового игрока и это возвращаемое значение должно быть передано из обработчика (см. goto()).

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

goback() – возвращается из сцены в прошлую.

back() – возвращается из сцены в прошлую. Если это переход из диалога в комнату, то не вызываются: dsc, enter, entered у комнаты. exit/left диалога вызываются. В других случаях аналогична goback.

goin(комната) – перейти в сцену, при этом exit/left текущей комнаты не вызывается;

goout() – вернуться в прошлую сцену, при этом 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 goto('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);

## 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.obj: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() возвращает имя звукового файла, который будет проигран.

Для того, чтобы отменить проигрывание, вы можете использовать stop_music() (с версии 1.0.0).

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» – включить отладчик);
- goto — улучшенный вариант реализации переходов;
- 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, goto.

Объект 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

### goto из обработчика exit

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

    exit = function(s, t)
    	if t == 'dialog' then return; end
    	return goto('dialog');
    end

Начиная с версии 0.9.1 движок сам разрывает рекурсию.

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

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

Динамически создаваемые ссылки могут быть реализованы разным способом. Ниже приводится пример, основанный на использовании объектов 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):

main.lua

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

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

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

Вы можете создать игру с несколькими персонажами и время от времени переключаться между ними (см. switch_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,
        force_dsc = 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 = '_',
    	force_dsc = true,
    	nam = 'Клавиатура',
    	dsc = function(s)
    		return 'Демонстрация: '..tostring(s._txt);
    	end
    };

### Мышь

Начиная с версии 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
    };

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

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

Вы можете использовать функции 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