Меняем путь: команда If

Что такое команда «If»?

Вы узнали, как с помощью событий запускать набор команд при наступлении в игре определённой ситуации. Вы узнали, как с помощью тегов менять поведение команд в зависимости от деталей этой ситуации. Теперь пришло время познакомиться с командой if, которая объединяет обе эти идеи: выбор набора команд для запуска в зависимости от деталей ситуации.

Команда if делает ровно то, что написано на её «этикетке»: говорит «если что-то истинно, выполни вот эти команды. Иначе — не выполняй».

Так как же выглядит команда If?

Не переживайте, команды if пишутся довольно просто и выглядят в скрипте Denizen ровно так же, как всё остальное.

Вот базовый формат:

- if (some condition here):
    - (some commands)
    - (go here)

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

Давайте посмотрим, как это может выглядеть в настоящем скрипте…

magic_healing_bell:
    type: world
    events:
        after player right clicks bell:
        - if <player.health_percentage> < 25:
            - heal
            - actionbar "<&[base]>The bell has healed you!"

../../_images/magic_bell.png

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

Скрипт также показывает игроку сообщение в actionbar, чтобы тот знал, что его вылечили, — с использованием базового цвета текста, заданного в вашем файле config.yml для Denizen.

Условия

В команде if условие можно записать несколькими способами. В простейшем случае условие — это булев тег (который возвращает «true» или «false») — и этого уже достаточно. (Например, - if <player.on_fire>:). Вложенные команды if выполняются, когда тег возвращает true, и не выполняются, когда тег возвращает false. Такие простые булевы команды if можно инвертировать (выполнять вложенные команды, когда тег возвращает «false», и не выполнять, когда возвращает «true») с помощью символа ! (читается как «не»). Например, - if !<player.on_fire>: выполнится только в том случае, если игрок не горит.

Условие может быть и просто каким-то тегом-значением (тегом, который возвращает значение сложнее булева) — в этом случае оно будет сравниваться с каким-то другим значением. Базовый формат команды if, сравнивающей два значения, такой: - if (первое значение) (сравнение) (второе значение): (например, - if <player.name> == mcmonkey4eva:). Одно из значений или оба сразу могут быть тегами. (Технически оба значения могут быть и просто статическим текстом, но тогда if либо всегда будет выполнять вложенные команды, либо никогда их не выполнит — такая команда if довольно бесполезна.).

Доступно несколько разных типов сравнения:

  • == — проверка на равенство (команда - if 3 == 3: выполнит вложенные команды, а - if 3 == 4: — нет) — читается как «если три равно трём, выполни вот эти команды».


  • != — проверка на равенство, но с обратным результатом (т. е. - if 3 != 3: не выполнит вложенные команды, а - if 3 != 4:выполнит) — символ ! читается как «не», так что эту конструкцию можно прочитать как «если три не равно трём, выполни вот эти команды». Разумеется, в реальных скриптах одно из значений или оба будут приходить из тега.


  • > (больше), >= (больше или равно), < (меньше) и <= (меньше или равно) — числовые сравнения. Обратите внимание, что > и <= противоположны, как и < и >=. Небольшое дополнительное замечание: вы наверняка заметили, что символы < и > обычно обозначают тег, но в сравнениях используются в другом смысле — это нормально: в одном аргументе сравнения символы < и > одновременно не встречаются, так что они никогда не будут ошибочно приняты за тег.


  • contains и in — проверки вхождения в список, например: - if one|two contains one: или - if one in one|two:


  • matches — проверки по advanced-matcher, например: - if <player.item_in_hand> matches diamond_sword:

Комбинирование условий: венти-мокко-фрап с двойным сахаром и без сливок

Когда простого сравнения не хватает и нужно добавить ещё пару условий — не переживайте: команда if это позволяет!

Доступны два способа комбинирования:

  • Если вы хотите, чтобы набор команд выполнялся, только если все сравнения одновременно истинны, используйте && (читается как «и» — сам символ представляет собой двойной амперсанд). Например, - if <player.on_fire> && <player.health_percentage> < 25: выполнит вложенные команды, только если игрок горит и у него почти не осталось здоровья.


  • Если вы хотите, чтобы набор команд выполнялся, если хотя бы одно (или больше) из нескольких сравнений истинно, используйте || (читается как «или» — сам символ представляет собой двойной «пайп»). Например, - if <player.on_fire> || <player.health_percentage> < 25: выполнит вложенные команды, если игрок горит (вне зависимости от его здоровья), либо если у игрока мало здоровья (вне зависимости от того, горит он или нет).

Если вам нужно проверить сразу много условий, их можно просто выстроить в цепочку: - if (условие 1) && (условие 2) && (условие 3) && (условие 4):. Здесь только одно ограничение: в цепочке можно использовать либо только && от начала до конца, либо только || от начала до конца. Смешивать их в одной цепочке нельзя… подумайте: - if (одно) && (два) || (три) && (четыре): — это вообще что значит? Это значит, что должны выполниться или «одно и два», или «три и четыре»? Или что должны выполниться «одно, два или три» и при этом «четыре»? Мы, люди, ещё можем попробовать угадать, что имелось в виду, но Denizen — просто программа, мысли он читать не умеет.

Решение этой проблемы было спрятано в самом вопросе: обратите внимание, как я сгруппировал части в двух возможных интерпретациях — части из одной группы я объединил, а остальные перечислил через запятую. В Denizen для этого есть специальный синтаксис: круглые скобки () с пробелами вокруг служат маркерами группировки. Как этим пользоваться?

Вот длинный, но относительно простой пример:

- if ( <player.name> == mcmonkey4eva && <player.health_percentage> > 90 ) || ( <player.on_fire> && <player.health_percentage> < 25 ):
    - narrate "wow mcmonkey specifically is doing pretty well! That or somebody is dying from a fire..."

С помощью группировки, как в примере выше, можно собрать любую комбинацию условий, которая вам может понадобиться. Можно даже вкладывать группы друг в друга, если очень хочется, — но учтите, что слишком навороченные конструкции делают скрипт трудночитаемым, и иногда лучше перестроить его по-другому (хороший вариант мы рассмотрим в следующем подразделе).

Самое частое применение «If»

Польза от команды if в примере с «волшебным колоколом лечения» довольно прямолинейна: она позволила нам сузить условие, при котором выполняется набор команд. Теперь это не просто «когда игрок кликает по колоколу», а «когда игрок кликает по колоколу и у него мало здоровья». В целом, сужение условий, при которых выполняются команды, — это самое частое применение команды if.

В реальных скриптах это чаще всего выглядит чуть-чуть иначе, но смысл тот же:

magic_healing_bell:
    type: world
    events:
        after player right clicks bell:
        - if <player.health_percentage> > 25:
            - stop
        - heal
            - actionbar "<&[base]>The bell has healed you!"

Используя команду stop внутри if, мы не даём скрипту выполнить команду лечения, если у игрока больше 25% здоровья. По сути это тот же исходный скрипт, но записанный «наоборот» (вместо «если у игрока меньше 25% здоровья, лечить его» мы говорим «если у игрока больше 25% здоровья, лечить его не надо»). Это очень удобно, когда перед запуском основных команд нужно проверить несколько условий — их можно записать аккуратным списком (а не упихивать в одну длинную строку через &&).

Развилка на дороге

До сих пор мы объясняли if с точки зрения «выполнить набор команд или не выполнять их» в зависимости от условия. Это отлично подходит, чтобы добавить дополнительные требования к событию, при которых сработает набор команд, — но это не всё, что умеет if! Команда if, как мы её пока понимаем, говорит: «если условие истинно, выполни эти команды, иначе — не выполняй». Расширим это: «если условие истинно, выполни эти команды, иначе выполни другие команды». Теперь наш скрипт может пойти по одной из двух дорожек в зависимости от условия в теге. Сделать это нам поможет команда else.

../../_images/bell_fork.png

Как выглядит команда Else?

Команда else выглядит почти так же, как if.

Вот базовый формат:

- if (some condition here):
    - (commands for when the condition is 'true')
- else:
    - (commands for 'false')

Всё очень просто! Это просто else без входных аргументов, оформленная так же, как if, но с одним обязательным требованием: она должна идти сразу за if. Когда условие команды if оказывается ложным, выполняются команды внутри else.

Давайте используем else в нашем скрипте с волшебным колоколом…

magic_healing_bell:
    type: world
    events:
        after player right clicks bell:
        - if <player.health_percentage> < 25:
            - heal
            - actionbar "<&[base]>The bell has healed you!"
        - else:
            - actionbar "<&[error]>The bell does nothing: you're healthy enough already."

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

Может возникнуть вопрос: «а почему бы не использовать stop так, как было показано раньше, и поставить сообщение-отказ прямо перед stop?» — И так тоже можно! Это прекрасный способ. Так в чём же тогда толк от else? Он особенно полезен, когда после блока if/else есть ещё команды, которые должны выполниться в любом случае. Взгляните на этот скрипт:

magic_healing_bell:
    type: world
    events:
        after player right clicks bell:
        - if <player.health_percentage> > 90:
            - actionbar "<&[error]>The bell does nothing: you're healthy enough already."
            - stop
        - if <player.health_percentage> < 25:
            - actionbar "<&[base]>The bell has saved you!"
        - else:
            - actionbar "<&[base]>The bell has healed you!"
        - heal

Этот скрипт откажется работать для игрока с 90% здоровья и вылечит его при любом здоровье ниже этого. При этом он выдаст разные сообщения в зависимости от того, почти ли игрок мёртв или просто немного ранен. Если бы мы здесь не использовали else, нам пришлось бы писать так:

magic_healing_bell:
    type: world
    events:
        after player right clicks bell:
        - if <player.health_percentage> > 90:
            - actionbar "<&[error]>The bell does nothing: you're healthy enough already."
            - stop
        - if <player.health_percentage> < 25:
            - actionbar "<&[base]>The bell has saved you!"
            - heal
            - stop
        - actionbar "<&[base]>The bell has healed you!"
        - heal

Так гораздо хуже! Пришлось дважды написать команду heal (не говоря уже о лишней команде stop). Если вам кажется, что это «не так уж и плохо», представьте, во что это превратится в настоящем скрипте, где таких команд, выполняемых в любом случае, не одна, а целый список. Вам пришлось бы дублировать весь список — благо, команда else избавляет от этого!

А у вилок разве не три-четыре зубца?

../../_images/trifork.png

Команда else и сама по себе очень удобна, но у неё есть ещё кое-что! Мы начали с одной команды if, которая позволяла выполнить или не выполнить кусок кода. Потом добавили else — и теперь можем выбирать один из двух вариантов кода. Дальше логично расширить это до выбора одного из многих вариантов. Для этого существует else if!

Как написать «Else If»?

Вот базовый формат:

- if (first condition here):
    - (commands for when the first condition is 'true')
- else if (second condition):
    - (second condition 'true' commands)
- else:
    - (commands for when all conditions were 'false')

Как видите, по сути это просто if внутри else. Можно выстроить сколько угодно else if подряд, единственное ограничение — в начале цепочки всегда должен стоять обычный if. Финальный else (без if) необязателен, но если вы его добавляете, он должен идти строго в конце.

Как это выглядит на практике?

magic_healing_bell:
    type: world
    events:
        after player right clicks bell:
        - if <player.health_percentage> > 90:
            - actionbar "<&[error]>The bell does nothing: you're healthy enough already."
        - else if <player.health_percentage> < 25:
            - actionbar "<&[error]>The bell can't save you: you're too far gone."
        - else:
            - actionbar "<&[base]>The bell has healed you!"
            - heal

Эта версия скрипта с волшебным колоколом не лечит, если игрок уже здоров, и не спасает от смерти — он лечит только при средней тяжести ранения. Благодаря такой структуре команда stop нам больше вообще не нужна: heal выполняется лишь в одной из «веток».

Стоп, кто тут говорил про деревья?

../../_images/logic_tree.png

Если у вас есть опыт в проектировании сложной логики — во-первых, зачем вы тогда вообще читаете это руководство? — но, что важнее, вы наверняка встречали термин «ветка» применительно к команде if. Если нет, возможно, вы знакомы с идеей изображать пути выполнения в виде деревьев с множеством веток. Если и это вам не знакомо — не переживайте, сейчас объясню.

Разные пути, по которым может пойти выполнение команды if, иногда называют «ветками» (branches); обычно при таком словоупотреблении общую структуру скрипта называют «деревом» (tree). Когда скрипт выполняется, он стартует от «корня» (это первая команда скрипта), и «отращивает ветки» каждый раз, когда встречается команда if или подобная ей (любой поднабор команд, который выполняется лишь иногда).

Необязательно знать и использовать все эти термины или любые другие из тех, что мы вводим, — но если вы где-то увидите их в чужом коде и не поймёте, что имеется в виду, вспомните про этот подраздел и загляните сюда!

Ещё одна пара часто встречающихся терминов — «pass» и «fail» (дословно «прошёл» и «провалился»): команда if «проходит», когда её условие истинно (и вложенные команды выполняются). «Проваливается» команда if, когда её условие ложно (и вложенные команды не выполняются).

Идём дальше

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