# Библиотека INSTEAD

INSTEAD-кросс-платформенное приложение, которое написано на C. Для удобства встраивания и создания своих вариантов интерпретатора, ядро интерпретатора выделено в отдельную часть.

Эту часть, которая находится в каталоге src/instead/ (c и h файлы) и stead/ (lua файлы) можно назвать библиотекой (несмотря на то, что сборка осуществляется просто путём подключения этих файлов к проекту).

В данном документе приводится информация об этом коде (далее -- библиотека).

# Параметры сборки

Единственная обязательная зависимость, это библиотека Lua. Поддерживаются: Lua5.1, Lua5.2, Lua5.3, Lua5.4, luajit.

Некоторые определения препроцессора используются для условной компиляции:

- \_USE\_SDL - сборка осуществляется вместе с библиотекой sdl;
- \_HAVE\_ICONV - сборка с библиотекой iconv (только для совместимости с играми на URQL);
- STEAD_PATH - путь по умолчанию к файлам .lua (каталог stead/);
- ANDROID - сборка для Android;
- \_WIN32 - сборка для Windows;
- WINRT - сборка для Windows RT;
- SAILFISHOS - сборка для Sailfish OS;
- \_\_APPLE\_\_ - сборка для macOS;
- IOS - сборка для iOS;
- по умолчанию сборка осуществляется для Unix/Linux.

Переменные препроцессора обычно задаются в виде параметров компиляции -D, например: -DSTEAD_PATH=\"./stead\" -DANDROID

# API

Библиотека не поддерживает мультимедиа возможности. Вместо этого, она позволяет загрузить и запустить игру, взаимодействовать с ней путём посылки и приёма текстовых строк (обычно, в UTF-8). А также зарегистрировать расширения функциональности.

_Внимание!_ Библиотека не потоконезависима! Возможно создание одного экземпляра интерпретатора для одного процесса!

## Базовое API

- int instead_set_debug(int mode);

Задать режим отладки (1 или 0). Возвращает старое значение. Режим отладки удобен при разработке игры. Эта функция должна вызываться до instead_init().

- const char *instead_lua_path(const char *path);

Позволяет переопределить путь к stead/ (переопределив STEAD_PATH). Если вызвать с параметром NULL вернёт текущий путь к stead/. Вызывать до instead_extension() и instead_init().

- int instead_extension(struct instead_ext *ext);

Зарегистрировать расширения. О расширениях будет рассказано в дальнейшем. Должна вызываться до instead_init(). Возвращает 0 в случае успеха и -1 -- в случае ошибки.

- int  instead_init(const char *path);

Открыть игру и подготовить её к запуску. В качестве параметра - путь к каталогу с игрой (в распакованном виде, или в виде idf). Возвращает -1 в случае ошибки, 0 -- в случае успеха.

- int instead_load(char **info);

Загрузить игру (запустить). Вызывать только после instead_init(). В качестве необязательного (можно передать NULL) параметра, указатель на указатель, в котором будет записан баннер игры. Обычно, это бесполезная информация (её не показывает даже стандартный плеер). Указатель должен быть освобождён вызовом free(). Возвращает -1 в случае ошибки, 0 -- в случае успеха.

- char *instead_cmd(char *s, int *rc);

Основная функция взаимодействия с игрой: выполнить команду. Вызывать после instead_load(). Команда передаётся в 1-м параметре. Статус возвращается в указатель 2-го параметра (может быть NULL). Возвращает строку, которую нужно освободить free(). Может вернуть NULL в случае ошибки.

Статус команды не 0 если возникла какая-то ошибка функционирования игры (например, подаётся некорректная команда).

Команды будут рассмотрены далее.

- char *instead_file_cmd(char *s, int *rc);

Если библиотека собрана с iconv, то каждый вызов instead_cmd транслирует текст в необходимую кодировку и обратно. Однако, при работе с файловой системой это не нужно. Этот вариант функции не занимается конвертированием и может быть использован для команд save и load. Если вы не используете сборку iconv (устаревшая функция), пользуйтесь instead_cmd() для любых команд.

- const char *instead_err(void);

Движок может сообщать о некоторых ошибках в виде текстовых сообщений. В таком случае, instead_err() возвращает указатель на сообщение (или NULL -- если ошибок не было). Строчку освобождать с помощью free() не нужно.

- void instead_err_msg(const char *s);

Задать сообщение об ошибке (например, из своего расширения). Если передать в качестве параметра NULL, то статус ошибки будет сброшен.

- void instead_done(void);

Выгрузить игру. Этой функцией заканчивается работа игры.

## Ввод и вывод игры

instead_cmd() используется для выполнения команды, которая представляет собой просто текст. Строго говоря, формат команд и её реакция определяются полностью Lua частью (stead/ файлы). На данный момент библиотека поддерживает игры на stead2 и stead3 API которые имеют общие черты, но также имеют существенные отличия.

instead_cmd() вызывает метод iface:cmd(команда) в запущенной Lua машине.

И в stead2 и в stead3 команда выглядит так:

<команда> [аргумент1,аргумент2,...]

Команды:

- look - осмотреться;
- way - получить список переходов;
- inv - получить инвентарь;
- go <переход> - переход;
- act <объект>[,<аргумент1>,<аргумент2>...] - действие на предмет;
- <объект>[,<аргумент1>,<аргумент2>...] - действие на предмет;
- use <предмет> - использование предмета инвентаря;
- use <предмет1>,<предмет2>[,параметры] - использование предмета на предмет;
- save <путь> - сохранить игру;
- load <путь> - загрузить игру.

_Внимание!_ Загрузка игр stead2 возможна только на "чистую" виртуальную машину Lua. Таким образом, для корректной загрузки нужно переинициализировать игру и первой командой выполнить load <файл сохранения>.

В качестве параметров команд используются имена или идентификаторы объектов.

Получить идентификаторы можно из текста вывода. Дело в том, что на каждый активный объект в тексте движком вызывается Lua метод iface:xref. Вы можете переопределить его для своих нужд.

```
function iface:xref(str, o, ...)
-- TODO
-- возвращает текст, который будет внедрён в вывод команды
end
```

Параметры метода: строка, объект игрового мира, аргументы...

Описание реализации iface:xref выходит за рамки данного руководства, но вы можете ознакомится с реализациями:

- стандартная реализация для графического интерпретатора: stead/stead3/ext/gui.lua;
- стандартная реализация для графического интерпретатора для stead2: stead/stead2/ext/gui.lua;
- примитивная реализация для tiny instead (консольная версия): src/tiny/tiny3.lua.

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

Также, интерпретатор знает, какую команду он выполнял: way, inv или look и, тем самым, знает тип объекта: переход, инвентарь или осмотр сцены. Таким образом, он может выполнить нужную команду.

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

- примитивная реализация в 100 строк: src/tiny;
- консольный интерпретатор: https://github.com/instead-hub/instead-cli.

_Внимание!_: если в iface:xref передаются аргументы (...), вы должны их сохранить и использовать как часть идентификатора объекта!

Обратите внимание на этот момент в стандартной реализации (stead/stead3/ext/gui.lua).

Также обратите внимание на приём, который там используется: использование дополнительного словаря. В xref строится специальный словарь идентификаторов (dict), который хранит их вместе с аргументами. Вы можете повторить такой приём в своём плеере, чтобы в выводе всегда присутствовали виртуальные идентификаторы без параметров, но это не обязательно если вы не показываете их игроку. Главное -- сохраняйте идентификатор вместе с аргументами!

## Парсерные игры

Для парсерных игр на stead3 существует отдельная команда:

- @metaparser "команда"

Команда заключается в кавычки. Пример тривиальной реализации парсера: src/tiny/metaparser.c

# Расширения

В базовом режиме игра выдаёт строки текста. Если интерпретатор должен поддерживать, например, проигрывание звуковых файлов, необходимо писать расширение.

Расширение описывается структурой:

```
struct instead_ext {
	struct list_node list;
	int (*init)(void);
	int (*done)(void);
	int (*err)(void);
	int (*cmd)(void);
};
```

И регистрируется с помощью instead_extension().

Структура instead_ext содержит указатели на методы:

- init() - инициализация расширения;
- done() - деинициализация расширения;
- cmd() - вызывается после каждой команды;
- err() - вызывается во время ошибок (работа игры останавливается).

## init()

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

- int instead_api_register(const luaL_Reg *api);

Регистрация lua функций в пространстве созданной виртуальной машины Lua, если это требуется.

- int instead_loadfile(const char *name);

Загрузить и выполнить Lua-скрипт в пространстве созданной виртуальной машины Lua. Скрипты-расширения принято хранить в каталогах ext/ (см stead/stead3/ext и sead/stead2/ext).

Если вам необходимо повторить некоторые мультимедиа-функции графического интерпретатора, вам необходимо повторить расширения, которые в нём реализованы.

Например:

```
static int _restart(lua_State *L)
{
	need_restart = !lua_isboolean(L, 1) || lua_toboolean(L, 1);
	return 0;
}
static const luaL_Reg tiny_funcs[] = {
	{ "instead_restart", _restart },
	{ NULL, NULL }
};

static int tiny_init(void)
{
	int rc;
	char path[1024];
	instead_api_register(tiny_funcs);
	snprintf(path, sizeof(path), "%s/tiny.lua", instead_lua_path(NULL));
	rc = instead_loadfile(path);
	if (rc)
		return rc;
	return 0;
}
static struct instead_ext ext = {
	.init = tiny_init,
};

...
	if (instead_extension(&ext)) {
		fprintf(stderr, "Can't register tiny extension\n");
		exit(1);
	}

```


## cmd()

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

Для вызова Lua из C кода вы можете воспользоваться функцией:

- int  instead_function(char *s, struct instead_args *args);

Первый параметр: текстовое представление метода на Lua, который может содержать двоеточие или точку (вызов метода Lua).

Например:

```
instead_function("instead.get_sound", NULL);

```

Второй параметр задаёт аргументы или NULL. Пример аргументов:

```
struct instead_args args[] = {
	{ .val = "nil", .type = INSTEAD_NIL },
	{ .val = "-1", .type = INSTEAD_NUM },
	{ .val = NULL }
};

```

Как видно из примера, аргументы -- это пары: значение - тип. Допустимые типы:

- INSTEAD_NIL - nil;
- INSTEAD_NUM - целое число;
- INSTEAD_STR - строка;
- INSTEAD_BOOL - булево ("true" или "false").

В случае ошибки возвращается -1, в случае успеха -- 0.

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

- extern char *instead_retval(int n); -- получить строку (освободить через free())
- int instead_iretval(int n); -- получить целое
- int instead_bretval(int n); -- получить булево

После того, как вы проанализировали возвращаемые значения, нужно вызвать:

- void instead_clear(void); -- сбросить стек Lua

_Внимание!_: функцию instead_clear() после вызова instead_function() необходимо вызывать всегда!


## Пример реализации расширения для проигрывания музыки

Ниже, для наглядности, приводятся упрощённые фрагменты расширения для проигрывания музыки. Расширение регистрирует функции на Lua в (stead/stead3/ext/sound.lua).

Полное расширение см. src/instead_sound.c. Кроме игры музыки оно поддерживает расширенную работу со звуком.

```
static void game_music_player(void)
{
	int	loop;
	char		*mus;

	int cf_out = 0;
	int cf_in = 0;

	instead_function("instead.get_music", NULL);
	mus = instead_retval(0);
	loop = instead_iretval(1);
	instead_clear();

	instead_function("instead.get_music_fading", NULL);
	cf_out = instead_iretval(0);
	cf_in = instead_iretval(1);
	instead_clear();

	// в mus - трек
	// cf_out,cf_in -- параметры затухания
	// mus -- путь к файлу
	// loop -- число проигрываний
	// TODO реализовать плеер

	free(mus);
}

static int sound_init(void)
{
	int rc;
	char path[PATH_MAX];

	snprintf(path, sizeof(path), "%s/%s", instead_stead_path(), "/ext/sound.lua");

	rc = instead_loadfile(dirpath(path));
	if (rc)
		return rc;
	// TODO - инициализация звуковой подсистемы
	return 0;
}

static int sound_cmd(void)
{
	game_music_player();
	return 0;
}

static int sound_done(void)
{
	// TODO: деинициализация звуковой подсистемы
	return 0;
}

static struct instead_ext ext = {
	.init = sound_init,
	.done = sound_done,
	.cmd = sound_cmd,
};

int instead_sound_init(void)
{
	return instead_extension(&ext);
}
```

# Lua часть (stead/)

Большая часть логики заключена в Lua файлах. Если вы хотите изучить как работает графический интерпретатор, смотрите ext/gui.lua. Функции instead.xxxxx - это те функции, которые вызывает графический интерпретатор с помощью instead_function().

- instead.get_title, instead.get_inv, instead.get_ways, instead.get_picture и др.

Вы можете заметить, что такие функции как: instead.get_ways или instead.get_inv являются врапперами над командами way и inv. А instead.get_picture, например, получает картинку сцены.

Это пример того, как расширение само устанавливает тот тип интерфейса с интерпретатором, который удобен для конкретной реализации. Всё есть Lua, поэтому вам решать - как именно вы будете взаимодействовать с интерпретатором для реализации конкретных расширений.