Ваш первый world-скрипт

Он запускается сам!

Итак, вы уже узнали, как использовать /ex для запуска одной команды Denizen и как сделать task-скрипт, чтобы запускать серию команд сразу. Это довольно полезные инструменты, но они требуют, чтобы вы сидели и вводили команды вручную. Разве смысл скриптового движка не в том, что он сам срабатывает в нужный момент и делает всякие кастомные штуки на вашем сервере? Было бы абсурдно сидеть на сервере и вводить команды /ex каждый раз, когда игроку что-то нужно!

Знакомьтесь: world-скрипт

Давайте посмотрим на главный способ делать автоматически срабатывающие скрипты — контейнеры world-скриптов!

World-скрипт содержит events (события). Событие — это, по сути, нечто, что происходит в мире. Например, когда игрок ломает блок, на это есть событие. Большинство событий названы довольно понятно и просто — например, упомянутое событие называется player breaks block. Учтите, что все события начинаются со слова on или after, а дальше идёт, как правило, английская фраза, описывающая событие. Ещё примеры имён событий: on player places block, after entity dies, on creeper powered, after lightning strikes(разница между on и after поясняется ниже на этой же странице).

Давайте посмотрим на настоящий world-скрипт!

my_world_script:
    type: world
    events:
        after player breaks block:
        - narrate "Whoa <player.name>, you broke a block!"

Вставьте это в файл скрипта, выполните /ex reload, чтобы загрузить его, а затем сломайте любой блок. Когда вы это сделаете, в чате появится это сообщение.

../../_images/brokeablock.png

Несколько замечаний по этому примеру:

  • Параметр type для таких скриптов теперь — world.

  • Вместо ключа script: теперь у нас events:, внутри которого находится само имя события.

  • Само событие (after player breaks block) имеет отступ ещё на 4 пробела дальше, чем events:, — так событие оказывается частью блока events, а не ключом верхнего уровня скрипта.

  • Игрок, сломавший блок, автоматически становится контекстно привязанным игроком. Поэтому narrate выводится именно ему, и <player.name> вернёт имя именно этого игрока.

Событие будет срабатывать каждый раз, когда в мире случается то, за чем оно закреплено. Каждый раз, когда кто-то из игроков ломает какой-то блок, этот скрипт будет запускаться. Если 5 игроков одновременно сломают по блоку — скрипт запустится 5 раз, по разу на каждого игрока, и каждый раз будет привязан к тому игроку, чьё действие вызвало срабатывание. Если один игрок возьмёт кирку с эффективностью 5 и уничтожит 10 блоков земли меньше чем за секунду — да, событие сработает 10 раз, и каждый раз именно этот игрок будет привязан к нему.

Окей, но какой именно блок сломал игрок?

Итак, вы знаете, что блок был сломан, когда этот скрипт запускается, но, конечно, вам интересно, как узнать, какой именно блок сломали (ломать блок земли и ломать блок алмазной руды — это, разумеется, разные вещи). Есть два способа это сделать.

Теги контекста

Первый способ узнать, какой блок был сломан, — это теги context (теги контекста). Как и следует из названия, теги контекста сообщают подробности о том, в каком контексте — когда, где, почему — запускается скрипт. Внутри события теги контекста содержат всё, что вам нужно знать о произошедшем событии.

<context.material> доступен в событии on player breaks block и возвращает материал. Он возвращает MaterialTag, а тег <MaterialTag.name> даёт имя материала — собирая их вместе, получаем <context.material.name>.

Попробуйте вставить этот тег в строку с narrate, как-то так: - narrate "Whoa <player.name>, you broke a <context.material.name>!", затем перезагрузите скрипт и сломайте пару блоков. По мере того как вы их ломаете, вы будете видеть в чате название типа сломанного блока.

../../_images/brokeastone.png

Заметьте: теги контекста всегда являются базовыми тегами и никогда — под-тегами (хотя, разумеется, при необходимости можно прицепить под-тег в конец тега контекста).

Также имейте в виду, что теги контекста, естественно, существуют только в своём релевантном контексте. У события on entity dies нет тега <context.material>, потому что он не имеет отношения к этому событию. Аналогично, в task-скрипте или команде /ex вообще не будет никаких тегов контекста, потому что они не относятся ни к какому событию.

Более конкретные строки событий

Имена событий позволяют добавлять уточнения — либо в виде входного значения, либо в виде переключателя события (switch).

Событие breaks block задокументировано как: player breaks block или player breaks <material>. Второй вариант даёт нам возможность указать <material> на входе. В данном контексте <> означает «подставьте сюда своё конкретное значение». Попробуйте изменить строку события в вашем скрипте на after player breaks stone:, затем перезагрузите скрипт и попробуйте сломать несколько разных блоков, включая камень. Вы увидите, что narrate сработает только на камне. Если сломать землю или что-то ещё — ничего не произойдёт.

Переключатели событий — это дополнительные опции события помимо базового входного значения. У события breaks block есть несколько переключателей, в том числе описанный как with:<item> to only process the event when the player is breaking the block with a specified item.

Чтобы это применить, можно, например, написать: after player breaks stone with:diamond_pickaxe: — тогда скрипт сработает только если игрок ломает камень именно алмазной киркой; если попробовать железной киркой или чем-то другим — скрипт просто не запустится.

Строки событий статичны

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

Тем не менее, в них доступна небольшая доля динамики. Например, рассмотрим использованный выше переключатель with:diamond_pickaxe. А что если нужно, чтобы подходила любая кирка? Можно использовать with:*_pickaxe. Символ * означает «что угодно в этом месте». Аналогично, если нужны только алмазная или железная, можно написать with:diamond_pickaxe|iron_pickaxe. Символ | означает «подходит любой из вариантов». То же самое можно применять и к входной части строки события. Например, если вы хотите реагировать на то, что игроки ломают деревянные брёвна, но не хотите заводить отдельное событие на каждый вид бревна, можно просто написать after player breaks *_log:.

Ладно, но хватит ломать мои блоки, пожалуйста

Итак, мы можем запустить скрипт, когда происходит событие. Можно ещё проверить подробности этого события в момент срабатывания. Но как повлиять на само событие? Что, мы просто обязаны сделать - narrate "*shakes fist* stop breaking my blocks!"?

Встречайте — команда determine. Команда determine — это то, чем вы определяете исход события. Если её не использовать, событие просто отрабатывает так, как отрабатывало бы обычно. Если использовать — можно поменять любой аспект события (в разумных пределах: речь идёт только о модификации самого события — если вы хотите, чтобы в ответ на событие в мире произошло что-то побочное, используйте подходящую для этого команду).

Самая распространённая и поддерживаемая практически всеми событиями «детерминация» — - determine cancelled. Она отменяет событие — то есть событие либо не произойдёт вовсе, либо сразу же откатится назад. В случае события breaks block это приведёт к тому, что блок вообще не сломается (наблюдающие со стороны игроки не увидят вообще ничего, а вот игрок, который попытался сломать блок, увидит, как блок на мгновение исчезнет и тут же вернётся на место — это особенность клиентского предсказания, и, к сожалению, без клиентского мода этого не избежать).

Так что давайте попробуем! После строки narrate в нашем тестовом скрипте добавьте determine. Если вы применили все предложенные к этому моменту изменения, ваш скрипт должен выглядеть так:

my_world_script:
    type: world
    events:
        after player breaks stone with:diamond_pickaxe:
        - narrate "Whoa <player.name>, you broke a <context.material.name>!"
        - determine cancelled

Стойте… это не сработало. Что пошло не так?

У нас бывают события «до» и «после»

Здесь-то и появляется разница между after и on. after означает, что скрипт запускается после того, как событие произошло и уже завершилось. on означает, что скрипт запускается до того, как событие произойдёт, и это событие ещё можно изменить. По сути, при after событие — это прошлое, а при onбудущее. Использовать determine можно только внутри on, а не after, — и причина предельно проста: прошлое изменить нельзя!

Поэтому замените after в нашем примере скрипта на on.

my_world_script:
    type: world
    events:
        on player breaks stone with:diamond_pickaxe:
        - narrate "Whoa <player.name>, you broke a <context.material.name>!"
        - determine cancelled

Смысл этого скрипта в том, что если вы сломаете камень алмазной киркой — действие будет остановлено. Если сломать другой блок или использовать не алмазную кирку — всё будет работать как обычно.

../../_images/breakingthemstones.gif

Заметьте: по умолчанию команда determine считается завершающей команду скрипта. Если вы хотите использовать determine и при этом продолжить выполнение команд дальше по скрипту (например, чтобы применить несколько разных детерминаций и поменять несколько аспектов события), нужно указать аргумент passively, как здесь: - determine passively cancelled.

Меняем этот мир

Если вы хотите изменить событие, а не просто отменить его целиком, у большинства событий есть свои варианты детерминации. У события breaks block есть опция детерминации nothing — чтобы при разрушении блока в режиме выживания не выпадало никаких предметов — а также опция указать предмет или список предметов, чтобы при разрушении блока выпадали именно они.

Например, чтобы этим воспользоваться, можно взять тег <MaterialTag.item>, который возвращает предмет для любого материала, и, объединив его с доступным тегом контекста <context.material>, получить такую строку: - determine <context.material.item>. Вуаля — у вас бесплатная «шёлковая рука»! Каждый блок теперь выпадает в своей точной исходной форме — например, если вы добываете блок алмазной руды, вы получите именно блок алмазной руды, а не алмазы (учтите, что для этого придётся вернуть в строке события block вместо stone, либо указать diamond_ore или что-то в этом духе).

Целая куча событий

Дополнительно стоит знать: один world-скрипт может содержать несколько событий. Это может выглядеть, например, так:

my_world_script:
    type: world
    events:
        after player breaks block:
        - narrate "Whoa <player.name>, you broke a <context.material.name>!"
        on player breaks stone with:diamond_pickaxe:
        - determine cancelled
        after player places stone:
        - narrate "Why are you putting that stone there? Better not break it with a diamond pickaxe!"