[#] Информация по stead3
Andrew Lobanov(tavern,1) — All
2017-02-14 18:27:59


Это нарезка с форума, где Пётр описывал особенности нового стека движка Instead.
----

== Объявление объектов в stead3.

obj {
    nam = 'яблоко';
}

Вроде, похоже на stead2? Да, но есть важные отличия.

nam может быть только строкой. Он не может быть функцией. Не может быть булевым значением. Только строка. nam -- однозначный и достаточный идентификатор объекта. Что это значит? После того как вы создали объект, вы всегда можете получить к нему доступ с помощью:

std.ref 'яблоко' или (более кратко) std 'яблоко' или (еще более кратко) _'яблоко'

То-есть, можно написать что-то вроде: _'яблоко'.variable = true

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

apple = obj {
    nam = 'яблоко';
}
apple.variable = true

Или получить временную/постоянную ссылку позже:

apple = _'яблоко'
local a = _'яблоко'

В обработчике вы можете сопоставить объект его имени просто:

use = function(s, w)
    if w/'яблоко' then p [[Я откусил яблоко.]] end
end

Или, конечно, так:

use = function(s, w)
    if w == _'яблоко' then p [[Я откусил яблоко.]] end
end

Итак, nam стал единственным идентификатором. Для отображения объекта в инвентаре используется он-же, или, если вы задали disp -- то disp. disp уже может быть и строкой и функцией (впрочем, как это было и раньше).

При размещении объектов в списках (в комнатах, например) мы используем имя объекта, то -есть:

obj {
    nam = 'яблоко';
}
 
room {
    obj = { 'яблоко' };
}

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

Имена объектов должны быть уникальными. Если вы создадите еще один объект с тем-же именем, вы сразу же получите ошибку.

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

obj {
    nam = 'main';
    obj = {
        disp = 'стол';
        dsc = 'тут стоит стол';
    }
}

Или у нас очень много однотипных объектов, типа фраз в диалоге. Мы же не собираемся каждой фразе давать уникальное имя? Если мы не зададим имя, движок сам создаст числовой индекс для такого объекта, но пользоваться им неудобно. Для таких случаев предусмотрены теги. Тег - это строка, которая начинается с символа # (таким образом в коде игры всегда понятно о чем мы говорим, о тегах или именах). Теги не обязаны быть одинаковыми, но они должны быть уникальными в пределах комнаты (вернее говоря, просто при поиске по тегу мы найдем 1-й такой объект).

obj {
    nam = 'main';
    obj = {
        tag = '#стол';
        -- nam = 'имя стола'; -- мы могли бы дать имя стулу, но зачем?
        dsc = 'тут стоит стол';
    }
}

Или, альтернативная форма записи (когда мы не задаем имя, а только тег):

obj {
    nam = 'main';
    obj = {
        nam = '#стол'; -- на самом деле мы задали тег, 
                              -- который может мыслится как локальное имя
        dsc = 'тут стоит стол';
    }
}

При этом, в качестве отображения в инвентаре используется disp, если его нет, то тег, с убранным первым символом #. Большинство функций STEAD3 умеют работать как с тегом, так с именем, например:

walk '#переход1'

Будет искать объект с тегом #переход1 среди доступных переходов.

Теги очень широко используются в диалогах, вы можете посмотреть это на примере диалога, который приведен выше в этой ветке.

Говоря об объектах, нужно еще сказать, что в room появился новый атрибут -- title. Теперь disp (или tag/nam) используется для показа названия перехода в эту комнату, а title -- становится названием комнаты, когда мы в нее перешли. Если title не задан, то стандартная схема disp/tag/nam.

== Про переменные.

В stead3 вам не нужно определять переменные объекта, которые вы хотите сохранить, через var {}. В stead2 было так:

apple = obj {
    nam = 'яблоко';
    var { eaten = false }; -- сохранится
    _red = true; -- сохранится, так как начинается с символа _
}

В stead3:

obj {
    nam = 'яблоко';
    eaten = false;
}

eaten сохранится, если в процессе игры значение этой переменной менялось.

То-есть, если где-то в коде было _'яблоко'.eaten = true, то такая переменная сохранится.

Если где-то в коде есть _'яблоко'.newvar = 1, то такая переменная тоже сохранится (создание переменных на лету).

UPD: По результатам обсуждения решили, что по умолчанию создание переменных на-лету запрещено. Все переменные должны быть объявлены заранее. Если вы не указали std.nostrict = false в начале игры. :) Либо, для добавления переменных на лету используйте:

apple 'variable' (10) -- создали переменную на-лету

Есть только определенная особенность с таблицами. Дело в том, что таблица будет сохранена в save если к ней в процессе игры был доступ хотя бы на чтение.

obj {
    nam = 'яблоко';
    tabl = { 1, 2, 3}; -- будет сохранена, если к таблице были обращения
}

Это сделано из-за того, что модификации внутри таблицы сложно отследить (без лишнего усложнения кода движка), поэтому таблицы сохраняются целиком. Но что если вы не хотите, чтобы таблица (или переменная) попадала в save файл? Тогда придется написать так:

obj {
    nam = 'яблоко';
   {   -- блок переменных, за которыми не следит instead
        tabl = { 1, 2, 3}; -- не будет сохранена
        x = 8; -- не будет сохранена
    }
}

Что с глобальными переменными?

В stead3 теперь отслеживается доступ к неинициализированным переменным. Таким образом, описки в именах переменных, обращение к несуществующей переменной -- сразу дадут ошибку. Присвоение неинициализированных переменных разрешено только если:

значение - это объект
значение - это функция

Всего теперь есть три вызова:

global -- объявление глобальной переменной
const -- объявление константы (стед3 сдедит за тем, чтобы переменная не менялась)
declare -- объявление переменной, за которой не следит stead3

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

Как раньше:

declare {
   a = 1,
   b = 2, 
...
}

Или одиночное объявление:

declare 'myfunc'  (function() pn "Hello!" end)
global 'X' (11)

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

Для чего нужен global и const -- понятно. Но зачем declare?

В stead2 единственным способом записать функцию в save была конструкция code [[ ]]
В stead3 такой конструкции нет. Зато тут можно объявлять функции!

declare 'myfunc' (function() pn "Hello!" end)
global 'X' (myfunc)

X сохранится и в save будет записана ссылка на функцию! Так что, теперь вместо code можно декларировать функции. Но это можно делать только заранее, в глобальном контексте игры, примерно как определяются объекты/комнаты.

declare также полезен в тех случаях, когда вам не нужно, чтобы инстед вообще не следил за вашей структурой данных.

Что с динамическими объектами?

У нас есть new, но прототип теперь другой. В качестве иллюстрации, напишу пример:

declare 'mycat' (function(nam)
    return obj { nam = nam; dsc = 'Тут сидит котик!' }
end)

где то в недрах игры:

act = function(s)
    p [[Я взял еще одного котика.]]
    take(new(mycat, "Барсик"))
end

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

== Как работают ссылки?

Варианты использования:

dsc = "Тут лежит {яблоко}"

Это, как и в 2.4, следующий вариант --- аналог xact:

dsc = "Тут лежит {яблоко} и {груша|сладкая груша}. А еще {#неведомая штука|странный предиет}"

Это ссылка на другой объект. Если ссылка неверная, будет ошибка. Объект должен быть доступен игроку (быть в зоне видимости).

И, наконец, вариант с ссылкой на системный объект. В stead3 есть понятие системных объектов. Такие объекты не уничтожаются и существуют весь цикл игры. Их имена начинаются на '@'. Например, \@timer, \@sound, \@sprite и тд.

Так вот, можно создавать ссылки на системные объекты. При этом не обязательно, такой объект должен находиться в комнате. Например:

dsc = "Нажми на {@button|кнопочку}"

При этом вызовется act объекта button. Системные объекты могут получать параметры. Так, в stead3 уже есть стандартный объект с именем @, который удобно использовать как обработчик xact, например:

xact.walk = walk -- переход
dsc = "Идем {@ walk room2|вперед}

То-есть, берем объект @, у него вызываем act, передаем параметры walk и room (строки в данном случае). Стандартный обработчик _'@'.act действует сл образом -- находит метод, который совпадает с именем 1го параметра и вызывает его. Таким образом объект @ выполняет функции, которые выполнял модуль xact в stead2
При передаче параметров действуют соглашения:

Если это строка вида "something" -- идет как строка.
Если это число 1234... true или false -- идет как число или булево.
Все остальное становится строкой.
Параметры разделяются ,.

== Насчет обработчиков.

При переходах.

Было: exit/left/enter/entered.
Теперь: onexit/exit/onenter/enter.

Смысл примерно такой, onXXX обработчики способны предотвратить цепочку. Теперь onXXX обработчики есть у всего. У act, у use. При этом логика такая: сначала запускается onXXX у объекта game, потом у текущего игрока, потом у комнаты. потом выполняется действие.

Каждый из onXXX может прекратить действие, вернув false. onXXX обработчик для use -- это used, который вызывается у страдательного объекта. Надеюсь, я более-менее объяснил все корректно. :)

Каждое действие над объектом считается. Всегда можно получить число действий над объектом с помощью action(объект, имя действия). А visits и visited для комнат, работают как и в stead2.

disable/enable объекта теперь действует несколько по иному. Они влияют на видимость объекта игроку, но не на возможность найти объект из кода. Грубо говоря: и walk и take итд будут работать с disabled объектом, но игрок этот объект не увидит. Есть специальные функции для определения видимости: seen, disabled(), если это нужно.

Кроме disable/enable можно делать close/open. Это влияет на видимость перехода, видимость фраз в диалоге, а также видимость вложенных объектов в объект. Если объект закрыт -- его содержимое не видно, но сам объект -- виден (в отличие от disabled объекта).

Еще одно отличие в отображении сцены.
Есть dsc -- это описание, которое пропадает после 1го показа.
Есть decor -- описание, которое всегда показывается и служит для связного описания статических декораций с ссылками.
Есть объекты -- которые показаны всегда.

Необходимость decor назрела уже давно. Часто в комнате есть много статических объектов, которые хочется описать связанно. Теперь это можно сделать без создания фиктивного объекта.

Вследствие этого, forcedsc теперь нет.