Skip to content

Интерактивность

AliceCyber edited this page Jan 25, 2022 · 4 revisions

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

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

disp = function()
	p 'яблоко';
end

Пример не очень удачен, так как проще было бы написать disp = 'яблоко', но показывает синтаксис записи функции.

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

 return "яблоко";

При этом ход выполнения кода функции прекращается и она возвращает движку строку. В данном случае "яблоко".

Более привычным способом вывода являются функции:

  • p ("текст") -- вывод текста и пробела;
  • pn ("текст") -- вывод текста с переводом строки;
  • pr ("текст") -- вывод текста "как есть".

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

pn "Нет скобкам!"

Все эти функции дописывают текст в буфер и при возврате из функции возвращают его движку. Таким образом, вы можете постепенно формировать вывод за счет последовательного выполнения p/pn/pr. Имейте в виду, что автору крайне редко необходимо явно форматировать текст, особенно, если это описание объектов, движок сам расставляет необходимые переводы строк и пробелы для разделения информации разного рода и делает это унифицированным способом.

Вы можете использовать '..' или ',' для склейки строк. Тогда '(' и ')' обязательны. Например:

pn ("Строка 1".." Строка 2");
pn ("Строка 1", "Строка 2");

Основное отличие атрибутов от обработчиков событий состоит в том, что обработчики событий могут менять состояние игрового мира, а атрибуты нет. Поэтому, если вы оформляете атрибут (например, 'dsc') в виде функции, помните, что задача атрибута это возврат значения, а не изменение состояния игры! Дело в том, что движок обращается к атрибутам в те моменты времени, которые обычно четко не определены, и не связаны явно с какими-то игровыми процессами!

Важно!

Еще одной особенностью обработчиков является тот факт, что вы не должны ждать каких то событий внутри обработчика. То есть, не должно быть каких-то циклов ожидания, или организации задержек (пауз). Дело в том, что задача обработчика -- изменить игровое состояние и отдать управление INSTEAD, который визуализирует эти изменения и снова перейдет в ожидание действий пользователя. Если вам требуется организовать задержки вывода, вам придется воспользоваться модулем "timer".

Функции практически всегда содержат условия и работу с переменными. Например:

obj {
	nam = 'яблоко';
	seen = false;
	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) -- сам объект. То-есть, в данном примере, 's' это синоним _'яблоко'. Когда вы работаете с самим объектом в функции, удобнее использовать параметр, а не явное обращение к объекту по имени, так как при переименовании объекта вам не придется переписывать вашу игру. Да и запись будет короче.

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

Как видим, синтаксис оператора 'if' довольно очевиден. Для наглядности, несколько примеров.

if <выражение> then <действия> end

if have 'яблоко' then
	p 'У меня есть яблоко!'
end

if <выражение> then <действия> else <действия иначе> end

if have 'яблоко' then
	p 'У меня есть яблоко!'
else
	p 'У меня нет яблока!'
end

if <выражение> then <действия> elseif <выражение 2> then <действия 2> else <иначе> end --  и т.д.

if have 'яблоко' then
	p 'У меня есть яблоко!'
elseif have 'вилка' then
	p 'У меня нет яблока, но есть вилка!'
else
	p 'У меня нет ни яблока, ни вилки!'
end

Выражение в операторе if может содержать логическое "и" (and), "или" (or), "отрицание" (not) и скобки ( ) для задания приоритетов. Запись вида if <переменная> then означает, что переменная не равна false. Равенство описывается как '==', неравенство '~='.

if not have 'яблоко' and not have 'вилка' then
    p 'У меня нет ни яблока, ни вилки!'
end

...
if w ~= apple then
   p 'Это не яблоко.';
end
...

if time() == 10 then
   p '10 й ход настал!'
end

Важно!

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

Переменные объекта

Запись 's.seen' означает, что переменная 'seen' размещена в объекте 's' (то есть 'яблоко'). Помните, мы назвали первый параметр функции 's' (от self), а первый параметр -- это сам текущий объект.

Переменные объекта должны быть определены заранее, если вы собираетесь их модифицировать. Примерно так, как мы поступили с seen. Но переменных может быть много.

obj {
	nam = 'яблоко';
	seen = false;
	eaten = false;
	color = 'красный';
	weight = 10;
	...
};

Все переменные объекта, при их изменении, попадают в файл сохранения игры.

Если вы не хотите, чтобы переменная попала в файл сохранения, вы можете объявить такие переменные в специальном блоке:

obj {
	nam = 'яблоко';
	{
	   t = 1; -- эта переменная не попадет в сохранения
	   x = false; -- и эта тоже
	}
};

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

obj {
	nam = 'яблоко';
	{
	   text = { "раз", "два", "три" }; -- никогда не попадет в файл сохранения
	}
	...
};

Вы можете обращаться к переменным объекта через s -- если это сам объект. или по переменной - ссылке, например:

apple = obj {
    color = 'красный';
}
...
-- где-то в другом месте
    apple.color = 'зеленый'

Или по имени:

obj {
    nam = 'яблоко';
    color = 'красный';
}
...
-- где-то в другом месте
    _'яблоко'.color = 'зеленый'

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

apple 'xxx' (10) -- создали переменную xxx у объекта apple по ссылке
(_'яблоко') 'xxx' (10) -- то же самое, но по имени объекта

Локальные переменные

Кроме переменных объекта вы можете использовать локальные и глобальные переменные.

Локальные переменные создаются с помощью служебного слова local:

act = function(s)
    local w = _'лампочка'
    w.light = true
    p [[Я нажал на кнопку и лампочка загорелась.]]
end

В данном примере, переменная w существует только в теле функции-обработчика act. Мы создали временную ссылку-переменную w, которая ссылается на объект 'лампочка', чтобы изменить свойство-переменную light у этого объекта.

Конечно, мы могли написать и:

_'лампочка'.light = true

Но представьте себе, если нам нужно произвести несколько действий с объектом, в таких случаях проще воспользоваться временной переменной.

Локальные переменные никогда не попадают в файл-сохранение и играют роль временных вспомогательных переменных.

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

Еще один пример использования локальных переменных:

obj {
	nam = 'котенок';
	state = 1;
	act = function(s)
		s.state = s.state + 1
		if s.state > 3 then
			s.state = 1
		end
		p [[Муррр!]]
	end;
	dsc = function(s)
		local dsc = {
			"{Котенок} мурлычет.",
			"{Котенок} играет.",
			"{Котенок} облизывается.",
		};
		p(dsc[s.state])
	end;
end

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

dsc = function(s)
    if s.state == 1 then
        p "{Котенок} мурлычет."
    elseif s.state == 2 then
        p "{Котенок} играет."
    else
        p "{Котенок} облизывается.",
    end
end

Глобальные переменные

Вы также можете создать глобальную переменную:

global { -- определение глобальных переменных
    global_var = 1; -- число
    some_number = 1.2; -- число
    some_string = 'строка';
    know_truth = false; -- булево значение
    array = {1, 2, 3, 4}; -- массив
}

Еще одна форма записи, удобная для одиночных определений:

global 'global_var' (1)

Глобальные переменные всегда попадают в файл-сохранение.

Кроме глобальных переменных вы можете задавать константы. Синтаксис аналогичен глобальным переменным:

const {
	A = 1;
	B = 2;
}
const 'Aflag' (false)

Движок будет контролировать неизменность констант. Константы не попадают в файл-сохранение.

Иногда вам нужно работать с переменной, которая не определена как local (и видима во всех ваших lua файлах игры), но не должна попадать в файл сохранения. Для таких переменных вы можете использовать декларации:

declare {
	A = 1;
	B = 2;
}
declare 'Z' (false)

Декларации не попадают в файл сохранения. Одно из важных свойств деклараций состоит в том, что вы можете декларировать функции, например:

declare 'test' (function()
	p "Hello world!"
end)

global 'f' (test)

В таком случае, вы можете присваивать значение функции 'test' другим переменным и состояние этих переменных может быть сохранено в файле сохранения. То-есть, декларированную функцию можно использовать как значение переменной!

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

declare 'dprint' (dprint)

Тем самым делая такие недекларированные функции -- декларированными.

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

Вспомогательные функции

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

function mprint(n, ...)
	local a = {...}; -- временный массив с аргументами к функции
	p(a[n]) -- выведем n-й элемент массива
end
...
dsc = function(s)
	mprint(s.state, {
		"{Котенок} мурлычет.",
		"{Котенок} играет.",
		"{Котенок} облизывается." });
end;

Пока не обращайте внимания на данный пример, если он кажется вам сложным.

Возвращаемые значения обработчиков

Если необходимо показать, что действие не выполнено (обработчик не сделал ничего полезного), возвращайте значение false. Например:

act = function(s)
	if broken_leg then
		return false
	end
	p [[Я ударил ногой по мячу.]]
end

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

game.act = 'Гм... Не получается...';

Итак, если вы не задали обработчик act или вернули из него false -- считается, что реакции нет и движок выполнит аналогичный обработчик у объекта 'game'.

Обычно, нет никакого смысла возвращать false из act, но существуют другие обработчики, о которых будет рассказано дальше, для которых описанное поведение точно такое же.

На самом деле, кроме 'game.act' и 'act' -- атрибута объекта существует обработчик 'onact' у объекта game, который может прервать выполнение обработчика 'act'.

Перед тем как вызвать обработчик 'act' у объекта, вызывается onact у game. Если обработчик вернет false, выполнение 'act' обрывается. 'onact' удобно использовать, для контроля событий в комнате или игре, например:

-- вызываем onact комнат, если они есть
-- для действий на любой объект

game.onact = function(s, ...)
	local r, v = std.call(here(), 'onact', ...)
	if v == false then -- если false, обрубаем цепочку
		return r, v
	end
	return
end

room {
	nam = 'shop';
	disp = 'Магазин';
	onact = function(s, w)
		p [[В магазине нельзя воровать!]]
		p ([[Даже, если это всего-лишь ]], w, '.')
		return false
	end;
	obj = { 'мороженное', 'хлеб' };
}

В данном примере, при попытке "потрогать" любой предмет, будет выведено сообщение о запрете данного действия.

Все, что описано выше на примере 'act' действует и для других обработчиков: tak, inv, use, а также при переходах, о чем будет рассказано далее.

Иногда возникает необходимость вызвать функцию - обработчик вручную. Для этого используется синтаксис вызова метода объекта. 'Объект:метод(параметры)'. Например:

apple:act() -- вызовем обработчик 'act' у объекта 'apple' (если он
определен как функция!).  _'яблоко':act() -- то же самое, но по
имени, а не по переменной-ссылке

Такой метод работает только в том случае, если вызываемый метод оформлен как функция. Вы можете воспользоваться 'std.call()' для вызова обработчика тем способом, каким это делает сам INSTEAD. (Будет описано в дальнейшем).

Специальные объекты

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

  1. Системные объекты @;
  2. Подстановки $.

Системные объекты, это объекты, чье имя начинается с символа '@' или '$'. Такие объекты обычно создаются в модулях. Они не уничтожаются при смерти игрового мира (например, при подгрузке gamefile, при загрузке игры из сохранения, и так далее). Примеры объектов: @timer, @prefs, @snd.

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

Объект '@'

Обычно, вам не нужно работать с такими объектами, но в качестве примера рассмотрим реализацию 'ссылок'.

Пусть мы хотим сделать ссылку, при нажатии на которую мы перейдем в другую комнату. Конечно, мы могли бы добавить объект в сцену, но стоит ли это делать в таком простом случае?

Как нам может помочь системный объект?

obj {
	nam = '@walk';
	act = function(s, w)
		walk(w, false, false)
	end;
}
room {
	nam = 'main';
	title = 'Начало';
	decor = [[Начать {@walk старт|приключение}]];
}

При нажатии на ссылку "приключение" будет вызван метод act объекта '@walk' с параметром "старт".

На самом деле, в стандартной библиотеке stdlib уже есть объект, с именем '@', который позволяет делать свои обработчики ссылок следующим образом:

xact.walk = walk

room {
	nam = 'main';
	title = 'Начало';
	decor = [[Начать {@ walk старт|приключение}]];
}

Обратите внимание, на пробел после @. Данная запись делает следующее:

  • берет объект '@' (такой объект создан библиотекой stdlib);
  • берет его act;
  • вызывает act с параметрами walk и старт;
  • act объекта '@' смотрит в массив xact;
  • walk определяет метод, который будет вызван из массива xact;
  • старт -- параметр этого метода.

Другой пример:

xact.myprint = function(w)
	p (w)
end

room {
	nam = 'main';
	title = 'Начало';
	decor = [[Нажми {@ myprint "hello world"|на кнопку}]];
}

Подстановки

Объекты, чье имя начинается на символ '$' тоже считаются системными объектами, но работают они по-другому.

Если в выводе текста встречается "ссылка" вида:

{$my a b c|текст}

То происходит следующее:

  1. Берется объект $my;
  2. Берется act объекта $my;
  3. Вызывается act: _'$my':(a, b, c, текст);
  4. Возвращаемая строка заменяет собой всю конструкцию {...}.

Таким образом, объекты играют роль подстановки.

Зачем это нужно? Представьте себе, что вы разработали модуль, который превращает записи формул из текстового вида в графические. Вы пишете объект $math который в своем act методе превращает текст в графическое изображение (спрайт) и возвращает его в текстовый поток. Тогда пользоваться таким модулем крайне просто, например:

	{$math|(2+3*x)/y^2}

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

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

  • Игрок нажимает на ссылку;
  • Реакция 'act', 'use'', 'inv', 'tak', осмотр сцены (клик по названию сцены) или переход в другую сцену;
  • Динамические события;
  • Вывод нового состояния сцены.

Например, сделаем Барсика живым:

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

И вот момент в игре, когда Барсик попадает к нам за пазуху!

take 'Барсик' -- добавить в инвентарь
lifeon 'Барсик' -- оживить Барсика!

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

Если в вашей игре много "живых" объектов, вы можете задавать им явную позицию в списке, при добавлении. Для этого, воспользуйтесь вторым числовым параметром (целое неотрицательное число) 'lifeon', чем меньше число, тем выше приоритет. 1 -- самый высокий. Или вы можете использовать атрибут pri у объекта. Правда, этот атрибут будет влиять на приоритет объекта в любом списке.

Если вам нужен фоновый процесс в какой-то комнате, запускайте его в 'enter' и удаляйте в 'exit', например:

room {
        nam  = 'В подвале';
        dsc = [[Тут темно!]];
        enter = function(s)
                lifeon(s);
        end;
        exit = function(s)
                lifeoff(s);
        end;
        life = function(s)
                if rnd(10) > 8 then
                        p [[Я слышу какие-то шорохи!]];
                        -- изредка пугать игрока шорохами
                end
        end;
        way =  { 'Дом' };
}

Если вам нужно определить, был ли переход игрока из одной сцены в другую, воспользуйтесь 'player_moved()'.

obj {
	nam  = 'фонарик';
	on = false;
	life = function(s)
		if player_moved() then -- гасить фонарик при переходах
			s.on = false
			p "Я выключил фонарик."
			return
		end;
	end;
...
}

Для отслеживания протекающих во времени событий, используйте 'time()' или вспомогательную переменную-счетчик. Для определения местоположения игрока -- 'here()'. Для определения факта, что объект "живой" -- 'live()'.

obj {
	nam  = 'динамит';
	timer = 0;
	used = function(s, w)
		if w^'спичка' then -- спичка?
			if live(s) then
				return "Уже горит!"
			end
			p "Я поджег динамит."
			lifeon(s)
			return
		end
		return false -- если не спичка
	end;
	life = function(s)
		s.timer = s.timer + 1
		if s.timer == 5 then
			lifeoff(s)
			if here() == where(s) then
				p [[Динамит взорвался рядом со мной!]]
			else
				p [[Я услышал, как взорвался динамит.]];
			end
		end
	end;
...
}

Если 'life' обработчик возвращает текст события, он печатается после описания сцены.

Вы можете вернуть из обработчика 'life' второй код возврата, ('true' или 'false'). Если вы вернете true -- то это будет признаком важного события, которое выведется до описания объектов сцены, например:

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

Или:

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

Если вы вернете false, то цепочка life методов прервется на вас. Это удобно делать при выполнении walk из метода life, например:

life = function()
	walk 'theend'
	return false -- это последний life
end

Если вы хотите блокировать 'life' обработчики в какой-то из комнат, воспользуйтесь модулем 'nolife'. Например:

require "noinv"
require "nolife"

dlg {
        nam = 'Охранник';
        noinv = true;
        nolife = true;
...
}

Отдельно стоит рассмотреть вопрос перехода игрока из 'life' обработчика. Если вы собираетесь использовать функции 'walk' внутри 'life', то вам следует учитывать следующее поведение.

Если 'life' переносит игрока в новую локацию, то обычно предполагается что вы:

  1. Очищаете вывод реакций: game:reaction(false);
  2. Очищаете вывод живых методов на данный момент: game:events(false, false)
  3. Делаете walk.
  4. Останавливаете цепочку life вызовов с помощью return false;

Некоторые моменты требуют пояснений.

game:reaction() -- позволяет взять/изменить вывод реакции пользователя, если задать его в false это означает сбросить реакцию.

game:events() -- позволяет взять/изменить вывод life методов. В качестве параметров принимаются приоритетные и не приоритетные сообщения, задав false, false мы отменили весь вывод предыдущих life методов.

В стандартной библиотеке уже есть функция life_walk(), которая делает описанные действия. Вам остается только вернуть false.

Методы объектов

У всех объектов STEAD3 существуют методы, которые используются при реализации стандартной библиотеке и, обычно, не используются автором игры напрямую. Однако, иногда полезно знать состав этих методов, хотя бы для того, чтобы не называть свои переменные и методы именами уже существующих методов. Ниже представлен список методов с кратким описанием.

Объект (obj)

  • :with({...}) - задание списка obj;
  • :new(...) - конструктор;
  • :actions(тип, [значение]) - задать/прочитать число событий объекта заданного типа;
  • :inroom([{}]) - в какой комнате (комнатах) находится объект;
  • :where([{}]) - в каком объекте (объектах) находится объект;
  • :purge() - удалить объект из всех списков;
  • :remove() - удалить объект из всех объектов/комнат/инвентаря;
  • :close()/:open() - закрыть/открыть;
  • :disable()/:enable() - выключить/включить;
  • :closed() -- вернет true, если закрыт;
  • :disabled() -- вернет true, если выключен;
  • :empty() -- вернет true, если пуст;
  • :save(fp, n) -- функция сохранения;
  • :display() -- функция отображения в сцене;
  • :visible() -- вернет true если считается видимым;
  • :srch(w) -- поиск видимого объекта;
  • :lookup(w) -- поиск любого объекта;
  • :for_each(fn, ...) -- итератор по объектам;
  • :lifeon()/:lifeoff() -- добавить/удалить из списка живых;
  • :live() -- вернет true, если в списке живых.

Комната (room)

Кроме методов obj, добавлены следующие методы:

  • :from() -- откуда пришли в комнату;
  • :visited() -- была ли комната посещена ранее?;
  • :visits() -- число визитов (0 -- если не было);
  • :scene() -- отображение сцены (не объектов);
  • :display() -- отображение объектов сцены;

Диалоги (dlg)

Кроме методов room, добавлены следующие методы:

  • :push(фраза) - перейти к фразе с запоминанием ее в стеке;
  • :reset(фраза) -- то же самое, но со сбросом стека;
  • :pop([фраза]) -- возврат по стеку;
  • :select([фраза]) -- выбор текущей фразы;
  • :ph_display() -- отображение выбранной фразы;

Игровой мир (объект game)

Кроме методов obj, добавлены следующие методы:

  • :time([v]) -- установить/взять число игровых тактов;
  • :lifeon([v])/:lifeoff([v]) -- добавить/удалить объект из списка живых, или включить/выключить живой список глобально (если не задан аргумент);
  • :live([v]) -- проверить активность живого объекта;
  • :set_pl(pl) -- переключить игрока;
  • :life() -- итерация живых объектов;
  • :step() -- такт игры;
  • :lastdisp([v]) -- установить/взять последний вывод;
  • :display(state) -- отобразить вывод;
  • :lastreact([v]) -- установить/взять последнюю реакцию;
  • :reaction([v]) -- установить/взять текущую реакцию;
  • :events(pre, bg) -- установить/взять события живых объектов;
  • :cmd(cmd) -- выполнение команды INSTEAD;

Игрок (player)

Кроме методов obj, добавлены следующие методы:

  • :moved() -- игрок сделал перемещение в текущем такте игры;
  • :need_scene([v]) -- нужна отрисовка сцены в данном такте;
  • :inspect(w) -- найти объект (видимый) в текущей сцене или себе самом;
  • :have(w) -- поиск в инвентаре;
  • :useit(w) -- использовать предмет;
  • :useon(w, ww) -- использовать предмет на предмет;
  • :call(m, ...) -- вызов метода игрока;
  • :action(w) -- действие на предмет (act);
  • :inventory() -- вернуть инвентарь (список, по умолчанию это obj);
  • :take(w) -- взять объект;
  • :walk/walkin/walkout -- переходы;
  • :go(w) -- команда идти (проверяет доступность переходов);

Список атрибутов и обработчиков

С каждым объектом связан набор атрибутов и обработчиков. Чаще всего атрибуты и обработчики могут быть заданы в виде текстовых строк или функций. Но некоторые атрибуты задаются особым образом. Например, список объектов obj задаётся в виде списка {}, а noinv -- булево значение.

Основное отличие атрибута от обработчика состоит в том, что обработчик -- это реакция на игровое событие. Атрибуты же используются движком в основном при генерации описания сцены и инвентаря.

Если обработчик или атрибут задан в виде функции, то первый параметр функции (s) -- это сам объект. Атрибуты - функции не должны менять состояние мира. Это можно делать только в обработчиках.

Ниже в справочных целях представлен список атрибутов и обработчиков.

Объекты и комнаты (obj и room)

  • nam - атрибут, обязательное имя объекта;
  • tag - атрибут, тег объекта;
  • ini - обработчик, вызывается для объекта/комнаты при конструировании игрового мира, может быть только функцией;
  • dsc - атрибут, вызывается для вывода описания;
  • disp - атрибут, информация об объекте в инвентаре или комнаты в списке переходов;
  • title - атрибут комнаты, название комнаты выводимое при нахождении внутри этой комнаты;
  • decor - атрибут комнаты, вызывается для вывода описания декораций в сцене;
  • nolife - атрибут комнаты, не вызывать обработчики живых объектов;
  • noinv - атрибут комнаты, не показывать инвентарь;
  • obj - атрибут, список вложенных объектов;
  • way - атрибут комнаты, список с переходами в другие комнаты;
  • life - обработчик, вызывается для "живых" (фоновых) объектов;
  • act - обработчик объекта, вызывается при действии на предмет сцены;
  • tak - обработчик взятия предмета со сцены (если не задан act);
  • inv - обработчик объекта, вызывается при действии на предмет инвентаря;
  • use(s, на что) - обработчик объекта, вызывается при использовании предмета инвентаря на предмет сцены или инвентаря;
  • used(s, что) - обработчик объекта, вызывается перед use при использовании предмета (страдательная форма);
  • onenter(s, откуда) - обработчик комнаты, вызывается при заходе в комнату игрока, может запретить переход;
  • enter(s, откуда) - обработчик комнаты, вызывается после успешного входа в комнату;
  • onexit(s, куда) - обработчик комнаты, вызывается при выходе из комнаты, может запретить переход;
  • exit(s, куда) - обработчик комнаты, вызывается после успешного выхода из комнаты.

Игровой мир (game)

  • use(s, что, на что) - обработчик, действие по умолчанию для использования предмета;
  • act(s, что) - обработчик, действие по умолчанию при применении предмета сцены;
  • inv(s, что) - обработчик, действие по умолчанию при применении предмета инвентаря;
  • on{use,act,tak,inv,walk} - обработчик, реакция перед вызовом соответствующих обработчиков, может запрещать цепочку;
  • after{use,act,tak,inv,walk} - обработчик, реакция после действий игрока.

Атрибуты-списки

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

room {
	nam = 'холодильник';
	frost = std.list { 'мороженное' };
}

Хотя, обычно, это не требуется. Ниже перечислены методы объектов типа 'список'. Вы можете вызывать их для любых списков, хотя обычно это будут way и obj, например:

ways():disable() -- отключить все переходы
  • disable() - отключает все объекты списка;
  • enable() - включает все объекты списка;
  • close() - закрыть все объекты списка;
  • open() - открыть все объекты списка;
  • add(объект|имя, [позиция]) - добавить объект;
  • for_each(функция, аргументы) - вызвать для каждого объекта функцию с аргументами;
  • lookup(имя/тег или объект) - поиск объекта в списке. Возвращает объект и индекс;
  • srch(имя/тег или объект) - поиск видимого объекта в списке;
  • empty() - вернет true, если список пуст;
  • zap() - очистить список;
  • replace(что, на что) - заменить объект в списке;
  • cat(список, [позиция]) - добавить содержимое списка в текущий список по позиции;
  • del(имя/объект) - удалить объект из списка.

Существуют функции, возвращающие объекты-списки:

  • inv([игрок]) - вернуть инвентарь игрока;
  • objs([комната]) - вернуть объекты комнаты;
  • ways([комната]) - вернуть переходы комнаты.

Конечно, вы можете обращаться к спискам и напрямую:

	pl.obj:add 'нож'

Объекты в списках хранятся в том порядке, в котором вы их добавите. Однако, если у объекта присутствует числовой атрибут pri, то он играет роль приоритета в списке. Если pri не задан, значением приоритета считается 0. Таким образом, если вы хотите, чтобы какой-то объект был первым в списке, давайте ему приоритет pri < 0. Если в конце списка -- > 0.

obj {
	pri = -100;
	nam = 'штука';
	disp = 'Очень важный предмет инвентаря';
	inv = [[Осторожней с этим предметом.]];
}

Другие функции стандартной библиотеки

В INSTEAD в модуле stdlib, который всегда подключается автоматически, определены функции, которые предлагаются автору как основной рабочий инструмент по работе с миром игры. Рассмотрим их в этой главе.

При описании функции в большинстве функций под параметром 'w' понимается объект или комната, заданная именем, тегом или по переменной-ссылке. [ wh ] - означает необязательный параметр.

  • include(файл) - включить файл в игру;

      include "lib" -- включит файл lib.lua из текущего каталога с игрой;
    
  • loadmod(модуль) - подключить модуль игры;

      loadmod "module" -- включит модуль module.lua из текущего каталога;
    
  • rnd(m) - случайное целочисленное значение от '1' до 'm';

  • rnd(a, b) - случайное целочисленное значение от 'a' до 'b', где 'a' и 'b' целые >= 0;

  • rnd_seed(что) - задать зерно генератора случайных чисел;

  • p(...) - вывод строки в буфер обработчика/атрибута (с пробелом в конце);

  • pr(...) - вывод строки в буфер обработчика/атрибута "как есть";

  • pn(...) - вывод строки в буфер обработчика/атрибута (с переводом строки в конце);

  • pf(fmt, ...) - вывод форматной строки в буфер обработчика/атрибута;

      local text = 'hello';
      pf("Строка: %q Число: %d\n", text, 10);
    
  • pfn(...)(...)... "строка" - формирование простого обработчика; Данная функция упрощает создание простых обработчиков:

      act = pfn(walk, 'ванная') "Я решил зайти в ванную.";
      act = pfn(enable, '#переход') "Я заметил отверстие в стене!";
    
  • obj {} - создание объекта;

  • stat {} - создание статуса;

  • room {} - создание комнаты;

  • menu {} - создание меню;

  • dlg {} - создание диалога;

  • me() - возвращает текущего игрока;

  • here() - возвращает текущую сцену;

  • from([w]) - возвращает комнату из которой осуществлен переход в текущую сцену;

  • new(конструктор, аргументы) - создание нового динамического объекта (будет описано далее);

  • delete(w) - удаление динамического объекта;

  • gamefile(файл, [сбросить состояние?]) - подгрузить динамически файл с игрой;

      gamefile("part2.lua", true) -- сбросить состояние игры (удалить
      объекты и переменные), подгрузить  part2.lua и начать с main комнаты.
    
  • player {} - создать игрока;

  • dprint(...) - отладочный вывод;

  • visits([w]) - число визитов в данную комнату (или 0, если визитов не было);

  • visited([w]) - число визитов в комнату или false, если визитов не было;

      if not visited() then
      	p [[Я тут первый раз.]]
      end
    
  • walk(w, [булевое exit], [булевое enter], [булевое менять from]) - переход в сцену;

      walk('конец', false, false) -- безусловный переход (игнорировать
      onexit/onenter/exit/enter);
    
  • walkin(w) - переход в под-сцену (без вызова exit/onexit текущей комнаты);

  • walkout([w], [dofrom]) - возврат из под-сцены (без вызова enter/onenter);

  • walkback([w]) - синоним walkout([w], false);

  • _(w) - получение объекта;

  • for_all(fn, ....) - выполнить функцию для всех аргументов;

      for_all(enable, 'окно', 'дверь');
    
  • seen(w, [где]) - поиск видимого объекта;

  • lookup(w, [где]) - поиск объекта;

  • ways([где]) - получить список переходов;

  • objs([где]) - получить список объектов;

  • search(w) - поиск доступного игроку объекта;

  • have(w) - поиск предмета в инвентаре;

  • inroom(w) - возврат комнаты/комнат, в которой находится объект;

  • where(w, [таблица]) - возврат объекта/объектов, в котором находится объект;

      local list = {}
      local w = where('яблоко', list)
      -- если яблоко находится в более, чем одном месте, то
      -- list будет содержать массив этих мест.
      -- Если вам достаточно одного местоположения, то:
      where 'яблоко' -- будет достаточно
    
  • closed(w) - true если объект закрыт;

  • disabled(w) - true если объект выключен;

  • enable(w) - включить объект;

  • disable(w) - выключить объект;

  • open(w) - открыть объект;

  • close(w) - закрыть объект;

  • actions(w, строка, [значение]) - возвращает (или устанавливает) число действий типа t для объекта w.

      if actions(w, 'tak') > 0 then -- предмет w был взят хотя бы 1 раз;
      if actions(w) == 1 then -- act у предмета w был вызван 1 раз;
    
  • pop(тег) - возврат в прошлую ветвь диалога;

  • push(тег) - переход в следующую ветвь диалога

  • empty([w]) - пуста ли ветвь диалога? (или объект)

  • lifeon(w) - добавить объект в список живых;

  • lifeoff(w) - убрать объект из списка живых;

  • live(w) - объект жив?;

  • change_pl(w) - смена игрока;

  • player_moved([pl]) - текущий игрок перемещался в этом такте?;

  • inv([pl]) - получить список-инвентарь;

  • remove(w, [wh]) - удалить объект из объекта или комнаты; Удаляет объект из списков obj и way (оставляя во всех остальных, например, game.lifes);

  • purge(w) - уничтожить объект (из всех списков); Удаляет объект из всех списков, в которых он присутствует;

  • replace(w, ww, [wh]) - заменить один объект на другой;

  • place(w, [wh]) - поместить объект в объект/комнату (удалив его из старого объекта/комнаты);

  • put(w, [wh]) - поместить объект без удаления из старого местоположения;

  • take(w) - забрать объект;

  • drop(w, [wh]) - выбросить объект;

  • path {} - создать переход;

  • time() - число ходов от начала игры.

Важно!

На самом деле, многие из этих функций также умеют работать не только с комнатами и объектами, но и со списками. То есть 'remove(apple, inv())' сработает также как и 'remove(apple, me())''; Впрочем, remove(apple) тоже сработает и удалит объект из тех мест, где он присутствует.

Рассмотрим несколько примеров.

act = function()
	pn "Я иду в следующую комнату..."
	walk (nextroom);
end

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

Важно!

После вызова 'walk' выполнение обработчика продолжится до его завершения. Поэтому обычно, после 'walk' всегда следует 'return', если только это не последняя строка функции, хотя и в этом случае безопасно поставить 'return'.

act = function()
        pn "Я иду в следующую комнату..."
        walk (nextroom);
        return
end

Не забывайте также, что при вызове 'walk' вызовутся обработчики 'onexit/onenter/exit/enter'' и если они запрещают переход, то он не произойдет.