Долгосрочная память: флаги (flags)¶
На предыдущей странице вы узнали об определениях и об их использовании в роли краткосрочной памяти. На этой странице речь пойдёт о флагах.
Что такое флаги?¶
Флаги, как и определения, — это одна из форм памяти. Это точки данных, которыми вы свободно управляете и которые можно менять в скрипте, не затрагивая ничего, кроме собственных скриптов.
У флагов есть два ключевых отличия от определений:
Во-первых, флаги — это долгосрочная память, а определения — краткосрочная. Как вы узнали на прошлой странице, определение живёт ровно столько же, сколько живёт очередь. Флаги же хранятся гораздо дольше — при необходимости они могут храниться вечно.
Во-вторых, вы сами выбираете, к какому объекту привязан флаг. Когда вы разберётесь в системе флагов, станет понятно, что определения — это, по сути, частный случай флагов, привязанных исключительно к очередям.
Флаги тоже используют систему Data Actions, как и определения.
В отличие от команды define, команда flag всегда использует синтаксис data actions. То есть в простейшей форме установки значения используется : вместо пробела, как здесь: - flag <object> my_flag:my_value.
Базовое использование флагов¶
Прежде чем углубляться в теорию флагов, давайте попрактикуемся — с помощью команды /ex!
Откройте окно Minecraft и введите такую команду: /ex flag server greetable:World
Этой командой вы задали флаг с именем greetable и значением World на объекте server.
А теперь воспользуемся этим флагом — введите: /ex narrate "Hello, <server.flag[greetable]>!"
Если всё сделано правильно, в чате появится примерно следующее:

Флаги привязываются к объектам¶
Флаги всегда привязаны к какому-то объекту. В примере выше мы использовали объект server. Объект server — это специальный вспомогательный объект, существующий в том числе для того, чтобы флаги могли иметь глобальный статический «якорь». Флаги, привязанные к серверу, хранятся вечно и доступны откуда угодно и когда угодно.
Чаще всего флаги привязывают к игроку. Когда флаг привязан к игроку, это значит, что он есть только у этого конкретного игрока — у других он появится, только если для каждого из них снова выполнить команду flag.
Установить флаг на игрока очень просто — почти так же, как глобальный флаг на сервер, только вместо server нужно передать команде конкретного игрока.
Попробуйте ввести: /ex flag <player> status:learning
А затем: /ex narrate "<player.name> is <player.flag[status]> Denizen!"
Вы должны увидеть примерно такое сообщение:

Если кто-то из других игроков попробует выполнить ту же команду /ex narrate без предварительного /ex flag, появится сообщение об ошибке — ведь флаг status есть только у вас, а у остальных его нет.
Зачем мне это вообще может понадобиться?¶
Примеры выше довольно абстрактные, поэтому давайте используем уже изученные части системы флагов, чтобы сделать небольшой скрипт, который больше похож на что-то реально полезное.
ore_counter:
type: world
events:
after player breaks iron_ore:
- flag player ores_broken:++
- narrate "You have broken <player.flag[ores_broken]> iron ore so far! Keep it up!"
Этот скрипт будет вести текущий счёт того, сколько блоков железной руды добыл игрок. У каждого игрока — свой собственный счётчик (здесь используется data action «инкремент», которая описана на странице об определениях).

С помощью data action «clear», обозначаемой символом !, можно в любой момент удалить флаг. Поработав с примером скрипта выше, попробуйте выполнить /ex flag player ores_broken:!, чтобы удалить свой счётчик железной руды, а затем сломайте ещё один блок железной руды. Если всё прошло как надо, вы увидите, что тег флага подставляет значение 1 (то есть, насколько известно скрипту, вы только что впервые в жизни добыли железную руду).
А есть ли у тебя этот флаг?¶
Если вы хотите узнать, есть ли у объекта нужный флаг, ответ прост: has_flag! Этот тег выглядит так же, как тег flag, но вместо значения флага возвращает простое булево значение: true или false.
Его можно использовать внутри команды if, например: - if <player.has_flag[ores_broken]>: — тогда команды внутри if будут выполняться только если игрок уже сломал хотя бы один блок железной руды в примере скрипта выше.
Это же, по сути, и базовый способ реализовать значения вида «есть/нет» (в отличие от значений, у которых есть какие-то данные — как, например, у ores_broken с привязанным числом).
toggler_task:
type: task
script:
- if <player.has_flag[my_first_toggle]>:
- flag player my_first_toggle:!
- narrate "Disabled your toggle."
- else:
- flag player my_first_toggle
- narrate "Enabled your toggle."
Этот скрипт, запущенный через /ex run toggler_task, переключает флаг my_first_toggle — удаляет его или устанавливает (если значение передавать не нужно, его можно просто не указывать в команде flag), — а тег has_flag используется, чтобы понять, удалять его или устанавливать.
Когда вы освоите другие возможности — например, команды игрока, — с их помощью можно делать простые, но мощные штуки: команду, переключающую флаг при использовании, которая затем привязывается к какому-нибудь world-событию, проверяющему наличие флага, чтобы решить, применять ли особый эффект.
Флаги — это ядро Denizen¶
Флаги — это одна из ключевых возможностей Denizen… а значит, они встроены в самые разные места Denizen, чтобы работать с флагами было удобнее.
Например, если вы хотите, чтобы событие срабатывало только при наличии у игрока определённого флага, можно воспользоваться переключателем события flagged::
optional_ore_counter:
type: world
events:
after player breaks iron_ore flagged:ore_quest:
- flag player ores_broken:++
- narrate "You have broken <player.flag[ores_broken]> iron ore so far! Keep it up!"
Это тот же скрипт ore_counter, что и раньше, но теперь он работает только для игроков, у которых есть переключаемый флаг ore_quest. Так обычный счётчик добытой руды превращается в часть квеста, в котором игроку нужно отправиться и добыть определённое количество железной руды, чтобы его завершить.
Подобных механик в Denizen много: есть переключатель flagged: для игрока, похожие переключатели server_flagged: и location_flagged: и т. д., matchables вида (x)_flagged, разнообразные аргументы команд (например, аргумент to_flagged команды announce), множество удобных тегов вроде <server.online_players_flagged[flag_name]> и так далее. По мере изучения Denizen и знакомства с мета-документацией вы встретитесь со многими из них.
Время жизни¶
Флаги персистентны. Это значит, что события, которые обычно приводят к исчезновению данных — например, перезапуск сервера, — не стирают данные флагов.
Есть всего четыре случая, в которых флаг пропадает:
Явное намеренное удаление в скрипте с помощью data action «clear».
Истечение срока жизни (описано в следующем разделе).
Привязанный объект исчез (например, флаг был на мобе, а моб умер).
И, разумеется: жёсткий крэш сервера или удаление файлов сервера.
Срок жизни флагов¶
Одно из типичных применений флагов — долгосрочные таймеры задержки, например квест или особый эффект, который можно получить раз в день. Вот как это может выглядеть:
once_a_day:
type: task
script:
- if <player.has_flag[my_script_cooldown]>:
- narrate "You can only get this reward once per day. You must wait <player.flag_expiration[my_script_cooldown].from_now.formatted>."
- stop
- flag player my_script_cooldown expire:1d
- narrate "Here's your daily reward!"
- give diamond
Этот скрипт выдаёт игроку алмаз, но не чаще одного раза в сутки, — в качестве кулдауна используется флаг.
В виде task-скрипта это может выглядеть не очень впечатляюще, но дальше в руководстве вы научитесь привязывать его к обработчику клика по NPC — и тогда у вас появится NPC, по клику на которого раз в сутки выдаётся награда. По сути, готовая фича для сервера!

Заметьте, что здесь используются две новые части системы флагов:
Во-первых, в команде flag появился аргумент expire:1d. Он делает ровно то, что вы подумали: флаг хранится всего один день… после чего он считается так, как если бы его не было вовсе. Входное значение — это объект DurationTag, который можно записать как 1d (один день), 5h (пять часов) и т. п. На основе этого значения вычисляется дата/время, к которому флаг должен перестать действовать (путём прибавления длительности к текущей дате/времени), и сохраняется вместе с флагом.
Во-вторых, в команде narrate использован тег flag_expiration. Как и следует из названия, он возвращает время, к которому флаг должен перестать действовать, в виде объекта TimeTag. Затем мы применяем тег TimeTag.from_now, который возвращает DurationTag — промежуток времени между «прямо сейчас» и сохранённым TimeTag (то есть в момент установки флага это будет 1d, через час — 23h и так далее), а после него — тег DurationTag.formatted, который превращает «сырой» объект длительности во что-то более-менее читаемое человеком.
Заметьте, что у сроков жизни флагов можно узнать, наступили ли они, — но на них нельзя «подписаться»: события «флаг истёк» не существует. Если вам интересно, почему так, обратитесь к технической документации по системе флагов.
Учтите, что для очень коротких и простых кулдаунов (короче нескольких часов) чаще лучше использовать команду ratelimit вместо флагов, например: - ratelimit <player> 1h. Команда ratelimit очень проста и удобна, но, что важно, её состояние не переживает перезапуск сервера, и у неё нет встроенной возможности выводить сообщение об оставшемся времени — как в скрипте выше.
К каким ещё объектам можно привязывать флаги?¶
На самом деле, флагами можно пометить множество объектов. Вот основные из них:
Сам сервер — как глобальный флаг, через ключевое слово
server.Любой игрок — через объект
PlayerTag.Любой NPC из Citizens — через объект
NPCTag.Любая сущность Minecraft — через объект
EntityTag.Любой блок в мире — через объект
LocationTag.Любой чанк в мире — через объект
ChunkTag.Любой предмет — но предметы устроены чуть иначе, это будет рассмотрено на странице о кастомных предметах.
Любой мир — через объект
WorldTag.Любая заметная область (заметки рассматриваются в продвинутом разделе) через объект
CuboidTag,EllipsoidTagилиPolygonTag.Любой заметный инвентарь (заметки рассматриваются в продвинутом разделе) через объект
InventoryTag.Большинство других типов объектов тоже. Любой тип, который так или иначе однозначно идентифицируется, обычно поддерживает флаги.
Заметьте, что данные флагов, как правило, загружаются вместе с объектом — то есть серверные флаги загружаются при старте сервера, флаги игрока — при его входе на сервер и т. д.
Если игрок не в сети и при этом используются команды или теги flag, его данные будут автоматически загружены в этот момент.
А вот с сущностями всё иначе: если сущность деспаунилась (то есть находится в незагруженном чанке), надёжно загрузить её обратно нельзя, и работа с её флагами не получится.
Для блоков: если чанк, в котором находится блок, не загружен, его нужно сначала загрузить, прежде чем работать с флагами этого блока. Команда flag сделает это автоматически, а вот теги флагов автоматической загрузки чанка не делают — в этом случае стоит заранее выполнить команду chunkload, чтобы чанк точно был доступен.
Подробности о том, как именно разные типы объектов работают с флагами, можно найти в технической документации по системе флагов.
Имена флагов¶
Многие базовые ASCII-символы (например, ., !, % и т. п., в общем, всё, что не является буквой или цифрой) в именах сущностей (флагов, определений, контейнеров скриптов и т. п.) в Denizen могут иметь особое значение — и, кроме случаев, когда вы намеренно этим пользуетесь, таких символов лучше избегать. Лучше использовать простые текстовые имена, разделяя слова символом подчёркивания _ вместо пробелов.
Продвинутое замечание: карты и подкарты¶
Опытные пользователи, возможно, уже поняли, что флаги — это одна из форм data-карт (map), то есть системы данных «именованный ключ → значение». Таким пользователям могут быть интересны объекты MapTag или использование флагов для построения динамических вложенных структур (карты внутри карт). Система флагов Denizen автоматически поддерживает подкарты (submapping): в качестве разделителя используется символ ., например: - flag server myroot.mysubmap.mykey:myvalue. При этом флаг с именем myroot.mysubmap получит значение MapTag вида [mykey=myvalue], а флаг myroot — вложенное значение MapTag вида [mysubmap=[mykey=myvalue]]. Тег has_flag вернёт true и для has_flag[myroot], и для has_flag[myroot.mysubmap], и для has_flag[myroot.mysubmap.mykey].
Если всё это звучит для вас слишком мудрёно, не переживайте — использовать или понимать это прямо сейчас необязательно. Подробнее об этой системе, о том, как её применять и для чего она полезна, будет рассказано позже, в продвинутом разделе.
Особое замечание: зарезервированные имена флагов¶
Стоит знать, что имена флагов, начинающиеся с двух подчёркиваний (например, __name), считаются зарезервированными. Это связано с тем, что некоторые внутренние механизмы Denizen сами создают флаги и используют именно такое соглашение об именовании, чтобы не конфликтовать с пользовательскими скриптами. В частности, так делают команда zap (создаёт __interact_step) и команда cooldown (создаёт __interact_cooldown).
Ещё больше памяти¶
Опытные пользователи могут решить, что им нужны ещё более кастомные варианты хранения данных. Флагов и определений хватает в подавляющем большинстве случаев, но Denizen предлагает и дополнительные возможности (в основном ради совместимости с уже существующими системами данных), такие как команда YAML и команда SQL.