Краткосрочная память: определения (definitions)¶
Что такое определения?¶
Итак, вы уже умеете писать простые скрипты. Вы освоили базовые идеи о типах скриптов, командах и тегах.
Если вы шли по руководству и при этом думали над тем, что читаете, то, скорее всего, уже поняли: значение тега можно изменить, поменяв реальное игровое значение, на котором этот тег основан.
Например, значение <player.health> можно поменять командами hurt или heal. Если <player.health> возвращает 20, а вы хотите, чтобы было 10, — просто выполните - hurt 10. Всё элементарно.
А что если вам нужно значение, которое можно прочитать через тег или поменять по желанию, но которое не привязано к какому-то реальному игровому значению?
Вот тут на сцену выходят определения (definitions).
Определения (часто их называют просто 'defs') — это простая форма краткосрочной памяти внутри скрипта Denizen. В математике их назвали бы переменными.
Как пользоваться определениями?¶
Определения создаются и изменяются командой define, а читаются — тегом определения.
У любого определения есть имя и значение. Имя обычно статическое (хотя может быть и динамическим) и пишется прямо в скрипте. Значение — динамическое и существует только в памяти.
Тег определения¶
Тег определения — это особый тег. Технически он записывается как <definition[<name>]> (где name — это имя определения), но в скриптах он используется в сокращённой форме <[<name>]>.
Например, определение с именем target можно прочитать с помощью <[target]>.
Если определение target хранит значение типа EntityTag, то тег <EntityTag.health> можно использовать как <[target].health>.
Если вы помните базовый формат тега, тег начинается с базового тега и иногда принимает входное значение. Например, <element[hi]> — это базовый тег с именем element и входным значением hi. Как бы странно это ни звучало, тег определения точно так же укладывается в этот формат. Наверняка вы спросите: «а где же тут базовый тег?». Ответ: нигде. Базовый тег нулевой длины — это и есть базовый тег определения. То есть базовый тег с именем из нуля букв интерпретируется как сокращение для definition.
Команда Define¶
Команда define — это то, чем вы создаёте или меняете определение.
Стандартный формат команды: - define <name> <value>.
Например:
- define target <player.target>— задаёт определениеtargetравным той сущности, на которую сейчас смотрит игрок.- define count 10— задаёт определениеcountсо значением10.- define goal <player.health.mul[2]>— задаёт определениеgoalравным текущему здоровью игрока, умноженному на два.
Простой пример использования¶
Вот простой task-скрипт, в котором используются команды define и теги определений.
def_sample:
type: task
script:
- define current <player.health>
- define goal <util.random.int[2].to[10]>
- narrate "Your health is <[current]>. Let's heal you to <[current].add[<[goal]>]>!"
- heal <[goal]>
Если вы выполните /ex run def_sample в игре, вам покажут текущее здоровье и случайно выбранное большее значение, а затем подлечат до этого большего значения (или до максимума здоровья).
И зачем вообще нужны эти defs?¶
Если посмотреть на скрипт def_sample выше, вы увидите очень распространённый пример того, где определения очень полезны: случайные значения! Если бы вы попытались использовать тег util.random и в строке с narrate, и в строке с heal, в чате отобразилось бы одно значение, а хила прилетело бы на другое. Это нехорошо! Определение позволяет выбрать случайное значение один раз и затем использовать одно и то же значение несколько раз.
Вдобавок к случайным/сложным значениям, ещё один типичный сценарий использования определений — значения, которые меняются. Например, рассмотрим такой скрипт:
this_needs_defs:
type: world
events:
after player breaks *_ore:
- ratelimit <player> 30s
- if <player.health> < 5:
- narrate "You mined a <context.material.name>! Pretty impressive."
- wait 2s
- narrate "But shouldn't you be healing, not mining shiny things, <player.name>?"
- wait 2s
- narrate "You only have <player.health> health left."
На первый взгляд всё выглядит нормально и при тестировании, скорее всего, будет работать как ожидается. Когда игрок сильно ранен и отвлекается на добычу руды, скрипт будет отправлять надоедливые сообщения в чат с предложением подлечиться — не чаще, чем раз в 30 секунд.
Многие скрипты используют команды wait — особенно те, что построены как диалоги и потому делают паузы между сообщениями. К сожалению, эта пауза привносит потенциальную проблему: что если игрок вылечится (или, например, умрёт) в те 4 секунды между поломкой блока и финальным сообщением? В чате появится что-то вроде You only have 20 health left!, и это довольно нелепо.
Определения помогают спасти эту нелепую задумку скрипта: заранее сохранив значение здоровья, мы гарантируем, что в финальном сообщении будет показано именно то низкое значение, которого мы и ждали.
better_with_defs:
type: world
events:
after player breaks *_ore:
- ratelimit <player> 30s
- define health <player.health>
- if <[health]> < 5:
- narrate "You mined a <context.material.name>! Pretty impressive."
- wait 2s
- narrate "But shouldn't you be healing, not mining shiny things, <player.name>?"
- wait 2s
- narrate "You only have <[health]> health left."
Теперь финальное сообщение гарантированно покажет то самое значение здоровья ниже 5, которое было в момент, когда игрок сломал блок, — как и задумывалось.
Ещё один типичный сценарий использования определений — чистота кода. Иногда строка становится очень длинной из-за сложных тегов, и если положить сложный тег в определение, в саму строку достаточно вставить короткий тег определения, и всё становится чуть аккуратнее.
Время жизни¶
Определения, как и сказано в заголовке страницы, — это краткосрочная память. Что значит это «краткосрочная»?
Формально говоря, определение привязано к очереди, в которой оно было создано, и существует ровно столько, сколько существует эта очередь (подробнее об очередях мы поговорим позже в этом руководстве).
На практике это значит: при каждом запуске скрипта у него собственный набор определений. Если вы задали определение в одном событии или при одном срабатывании события, в следующий раз при срабатывании того же события оно уже не будет доступно.
Вот несколько примеров того, как определения НЕ работают.
cant_go_between_scripts:
type: world
events:
after player breaks iron_ore:
- define health <player.health>
after player breaks stone:
- narrate "You had <[health]> health when you broke iron."
Пример выше не сработает, потому что определение было задано в одной ветке скриптов, а читается в другой.
no_queue_persistence:
type: world
events:
after player breaks iron_ore:
- narrate "You had <[next_health]> health when you broke that last bit of iron."
- define next_health <player.health>
Пример выше тоже не сработает — и в первую очередь потому, что при самом первом ломании железной руды эта команда define вообще ни разу не отработала. Но даже после первого запуска определение всё равно не заработает: определение с прошлого срабатывания события не остаётся доступным при следующем срабатывании того же события.
Чтобы было ясно: это касается не только событий — каждый раз, когда вы runите task-скрипт, у него собственный набор определений (как это изменить, вы узнаете в одном из следующих разделов руководства).
На следующей странице вы узнаете об инструменте для долгосрочной памяти.
Продвинутые изменения определений¶
Если вы хотите изменить уже заданное определение, разумеется, можно просто вызвать ещё одну команду define. Например, такой вариант вполне нормален:
def_sample:
type: task
script:
- define current <player.health>
- define goal 1
- if <player.inventory.contains_item[healing_tool]>:
- define goal 20
- narrate "Your health is <[current]>. Let's heal you to <[current].add[<[goal]>]>!"
- heal <[goal]>
Этот скрипт подлечит игрока на 1 HP, если только у него в инвентаре нет кастомного предмета с именем 'healing_tool' — в этом случае он подлечится до полного здоровья. Такое использование define, перезаписывающее предыдущее define с тем же именем, совершенно легитимно и работает как ожидается.
Но что если вы хотите как-то модифицировать значение — например, прибавить 1 к уже существующему значению в определении? Разумеется, можно просто использовать теги и сделать всю работу через них, например - define mydef <[mydef].add[1]>.
Однако есть более аккуратный способ делать такие типовые изменения значений в определениях. Этот способ называется Data Actions.
Как пользоваться data actions?¶
Data actions писать очень просто. Вот тот же пример, что и выше, но через data action: - define mydef:++. Куда чище!
Доступны такие data actions без аргументов:
++— увеличивает значение на единицу. Например:- define mydef:++--— уменьшает значение на единицу. Например:- define mydef:--!— очищает значение (удаляет определение). Например:- define mydef:!
Есть также data actions, которые принимают дополнительное входное значение. Например, если вы хотите прибавить 3 к определению, можно написать - define mydef:+:3.
Data actions с входным значением:
+— прибавляет входное значение к определению. Например:- define mydef:+:3-— вычитает входное значение из определения. Например:- define mydef:-:3*— умножает определение на входное значение. Например:- define mydef:*:3/— делит определение на входное значение. Например:- define mydef:/:3
Определения, разумеется, могут хранить значения типа ListTag. Когда определение содержит список, для него тоже есть свои data actions, упрощающие работу со списком.
Data actions для ListTag:
->— вставляет в список новое значение. Например:- define mydef:->:new_value<-— удаляет из списка существующее значение. Например:- define mydef:<-:old_value|— объединяет входной список с исходным. Например:- define mydef:|:a|b|c
Чтобы вы точно поняли, что делает каждый из этих вариантов, рекомендуем написать task-скрипт, который выполняет эти примеры изменений над существующим определением, и понаблюдать за результатом.
data_actions_test:
type: task
script:
- define mydef 3
# Change the line below to each of the example data actions and run each
- define mydef:++
- narrate "mydef became <[mydef]>"
Когда вы запустите этот скрипт, в чате сначала появится mydef became 4, что показывает: значение 3 увеличилось и стало 4. Меняя среднюю строку на каждый из примеров, вы увидите, как они влияют на значение.
Чтобы проверить data actions для списков, замените первую строку на - define mydef <list[old_value|some_other_value]>.
Что ещё?¶
Также с помощью команды define (и её частного случая — команды definemap) можно динамически собирать data-карты; подробнее об этом рассказывается в продвинутом разделе.
Другие источники определений¶
Дополнительно учтите: помимо команды define, определения создаются и в ряде других мест — в том числе в циклах, в некоторых командах (например, shoot) — там, где в документации к соответствующей команде это явно указано, — а иногда и во внешних плагинах, которые интегрируются с Denizen.