). Эти циклы предназначены для перебора всех агентов на сцене или партий в игре соответственно. Итератор в процессе прохода по циклу последовательно принимает значения каждого агента или партии, и для использования в теле цикла должен быть сохранен как локальная переменная.
Пример:
(get_player_agent_no, ":player"),(agent_get_team, ":playerteam", ":player"),(try_for_agents, ":agent"), #Цикл перебора всех агентов на сцене (agent_is_alive, ":agent"), #Проверка, жив ли данный агент (agent_is_human, ":agent"), #Если жив, проверка, является ли человеком (не лошадью) (agent_is_non_player, ":agent"), #Если жив и человек, проверка, не является ли игроком (agent_get_team, ":team", ":agent"), # Если жив, человек и не игрок, узнаем его команду (eq, ":team", ":playerteam"), #Проверка, в одной ли команде этот агент с игроком #Последующие операции, производимые с этим агентом, если он в одной команде с игроком(try_end), #Переход к следующему агенту или если больше агентов нет — выход из цикла
Выходы из цикла
Вы, скорее всего, неоднократно столкнетесь с необходимостью перебирать в цикле всех агентов, чтобы найти одного конкретного агента, или же перебирать массивы любых других объектов, чтобы найти какой-то один конкретный. И после того, как искомый найден — нет больше необходимости проходить циклом по оставшимся — вы ведь уже нашли то, что искали. Нужно выходить из цикла. В МС нет никаких специальных операций для того, чтобы прекратить выполнение цикла, но существует несколько простых и эффективных способов сделать это вручную. Какой из них выбрать в данном случае, зависит от типа использующегося цикла try_for_*.
Способ 1: Изменение условия конца цикла.
- Для цикла try_for_range
Вы легко можете прервать цикл try_for_range, изменив в процессе выполнения верхнюю границу так, что она совпадет с нижней. Таким образом, когда цикл попытается выполнить следующую итерацию, он обнаружит, что итератор уже достиг верхней границы (итератор будет больше или равен верхней границе) и сразу же прекратит выполнение. Чтобы иметь возможность воспользоваться этим способом, нужно, чтобы верхняя граница была переменной, объявленной вне цикла, и необходимо убедиться, что при изменении этой переменной не произойдет потери данных, хранящихся в ней — создайте резервную копию этой переменной, чтобы не потерять ее исходное значение. Когда код был выполнен достаточное количество раз, когда нужный объект был найден и т. д., присвойте переменной верхней границы значение, равное значению нижней границы.
Пример:
#Проходим циклом (try_for_agents), по всем агентам (assign, ":end", "itm_glaive"), #Установка верхней границы (try_for_range, ":item", "itm_jousting_lance",":end"), #Перебираем все копья в игре (agent_has_item_equipped, ":agent", ":item"), #Проверяем, есть ли у данного агента данное копье в инвертаре (agent_set_wielded_item, ":agent", ":item"), #Если есть — агент берет его в руки (assign, ":end", "itm_jousting_lance"), #Выход из цикла — приравниваем верхнюю границу к нижней — перестаем перебирать копья, мы ведь уже нашли, какое есть у агента (try_end),#Продолжаем работать с агентами в цикле (try_for_agents),
- Для цикла try_for_range_backwards
Тот же самый способ используется и для цикла try_for_range_backwards. Но здесь условием конца цикла является нижняя граница, поэтому для выхода нужно изменять не верхнюю, а нижнюю границу — она должна быть приравнена к значению верхней границы.
Пример:
#Код, в котором присваивается какое-то значение переменной ":troop" (assign, ":array_begin", 0), # Установка нижней границы (try_for_range_backwards, ":i", ":array_begin", 10), #Цикл от 0 до 10 (party_slot_eq, "p_main_party_backup", ":i", 0), #Проверка, равняется ли i-тый слот данной партии 0 (party_set_slot, "p_main_party_backup", ":i", ":troop"), #Если да, то присваиваем этому слоту значение переменной ":troop" (assign, ":array_begin", 10), # Выход из цикла — приравниваем нижнюю границу к верхней — перестаем перебирать слоты, ведь нужный слот, равный 0, уже найден (try_end),
Способ 2: С помощью проверки условия
- Для циклов try_for_agents и try_for_parents
В циклах try_for_agents и try_for_parents нет возможности менять верхние и нижние границы. Вместо этого для выхода из цикла используется проверка какого-либо условия (как правило, проверка на равенство двух значений), которая располагается в начале тела цикла, чтобы проверять данное условие перед каждой итерацию. Пока условие выполняется, цикл продолжает работать. Когда условие становится неверным, цикл продолжает работать, однако в его теле не происходит уже никаких действий, поскольку условие, стоящее перед всем основным телом, неверно и выполнение кода тела цикла прерывается в самом начале. Таким образом, цикл очень скоро закончит свою работу. Чтобы воспользоваться этим способом, инициализируйте новую переменную перед циклом. Затем, в самом начале цикла поставьте проверку условия, связанного с этой переменной. После того, как нужный код в теле цикла выполнился, значение этой переменной должно измениться так, чтобы условие, стоящее в начале, стало неверным и произошел выход из цикла.
Пример:
#Код, в котором присваивается какое-то значение позиции pos1 (assign, ":break_loop", 0), #Инициализация переменной для выхода из цикла (try_for_agents, ":agent"), #Перебор всех агентов (eq, ":break_loop", 0), #Проверка условия: равна ли переменная 0? (agent_is_alive, ":agent"), #Если условие верно, продолжаем; жив ли данный агент? (agent_is_non_player, ":agent"), #Если жив, проверка, не игрок ли он? (agent_is_human, ":agent"), #Если жив и не игрок, проверка, человек ли он? (agent_get_position, pos0, ":agent"), #Если жив, человек и не игрок, сохраняем его местоположение в pos0 (get_distance_between_positions_in_meters, ":distance_to_target_pos", pos0, pos1), #Сохраняем расстояние между pos0 и pos1 в локальную переменную (lt, ":distance_to_target_pos", 10), #Проверка, находится ли агент менее, чем в 10 метрах от pos1 (assign, ":agent_at_target", ":agent"), #Если да, то сохранить ID этого агента в локальную переменную ":agent_at_target" (assign, ":break_loop", 1), #Выход из цикла — изменяем значение переменной, чтобы при следующем прохождении цикла условие не было выполнено (try_end),#Выход из цикла произойдет, как только будет найден первый живой агент-человек, не являющийся игроком и находящийся менее, чем в 10 метрах от целевой позиции pos1#Дальнейший код, работающий с найденным агентом
ДРУГИЕ ТЕМЫ
Триггеры
Триггеры из module_mission_templates.py (да и из module_triggers.py, если уж на то пошло) всегда записываются в следующем виде:
Первая часть: частота проверки условия выполнения триггера, "интервал вызова", либо в секундах, либо по определенному событию, ка например "ti_on_agent_hit"
Вторая часть: задержка в секундах между проверкой условий из блока условий и выполнением тела триггера. Не работает, если в качестве частоты проверки условия указано событие, как например "ti_on_agent_hit".
Третья часть: задержка «перезарядки» в секундах, то есть время между моментом завершения выполнения триггера и моментом, когда он снова сможет быть активирован. Специальная задержка перезарядки "ti_once" предназначена для одноразовых триггеров, которые могут выполниться лишь однажды, и после одного выполнения не могут больше выполняться вообще.
Четвертая часть: Блок условий, которые всегда проверяются при вызове триггера
Пятая часть: Тело триггера, основной код, который выполняется только в том случае, если выполнены все условия из блока условий и после задержки, указанной во вторй части триггера.
Важно: В отличие от всех остальных чисел в МС, интервалы задержек в триггерах НЕ обязаны быть целыми числами.
На практике это выглядит как-то так:
(10, 2, 60, [ (eq, 1, 1), ], [ (val_add, "$global_variable", 10), ]),
В этом примере интервал вызова составляет 10 секунд, поэтому триггер будет вызываться через каждые 10 секунд. Задержка между проверкой блока условий и выполнением тела равна 2 секундам, поэтому если все условия из блока условий выполнены, то спустя 2 секунды начнется выполнение тела триггера. Интервал перезарядки составляет 60 секунд, значит после каждого выполнения триггера пройдет минута, прежде чем он снова сможет быть активирован.
Здесь блок условий — простая проверка 1==1, которая всегда будет выполняться, поэтому тело триггера будет всегда срабатывать через 2 секунды после проверки условий выполнения триггера. В теле производится простейшее действие: к глобальной переменной прибавляется число 10.
Временные триггеры
Чтобы понять, что интервалы проверки/задержки/перезарядки во временных триггерах могут быть весьма непростыми штуками, рассмотрим следующий пример:
(2, 1, 3, [], [ #некоторый код ]),
Этот триггер, как и любой триггер с интервалом проверки > 0, начнет проверяться с 1 секунды миссии:
Секунда Событие 1 Триггер вызван; условие неверно — ожидание следующего вызова ( 2 секунды)3 Триггер вызван; условие неверно — ожидание следующего вызова ( 2 секунды)5 Триггер вызван; условие верно — включение задержки ( 1 секунда)6 Тело выполнено - ожидание следующего вызова ( 2); включение перезарядки ( 3)11 Триггер вызван; условие неверно — ожидание следующего вызова ( 2 секунды)13 Триггер вызван; условие верно — включение задержки ( 1 секунда)14 Тело выполнено - ожидание следующего вызова ( 2); включение перезарядки ( 3)19 Триггер вызван....
Итак, хотя интервал вызова равен 2 секундам, можно увидеть, что на самом деле вызов триггера не происходит каждые две секунды; вместо этого он происходит в 1, 3, 5, 11, 13, 19 секунды и т. д.
Если триггер должен вызываться точно «по расписанию», нельзя использовать ни задержку, ни перезарядку.
Два триггера с одинаковыми типами или интервалами
Если два триггера имеют одинаковые интервалы вызова (будь то в секундах или по условию ti_*), то порядок, в котором они будут выполнены, определяется текущим шаблоном миссии. Триггер, прописанный в шаблоне первым, первым и сработает, за ним уже выполнится второй с тем же интервалом и т. д. Это означает, что если у вас есть два триггера, срабатывающих по условию ti_on_agent_spawn, первым выполнится тот, который прописан в файле перед другим.
Триггеры в начале миссии
Логично предположить, что триггер ti_before_mission_start срабатывает перед тем, как сформирована сцена и появились агенты. Затем выполняются триггеры ti_on_agent_spawn – перед всеми остальными триггерами. Потом срабатывают триггеры ti_after_mission_start и все триггеры, интервал вызова у которых равен 0, поскольку на этом этапе запускается таймер миссии. Триггеры, срабатывающие по событию, не будут вызваны, пока это ti_* событие не произойдет. Остальные временные триггеры начинают вызываться где-то на первой секунде миссии, после выполнения всех триггеров ti_after_mission_start и с интервалами вызова 0.
Если у вас есть триггеры, которые не обязательно должны срабатывать сразу при запуске миссии, добавление в блок условий чего-нибудь в этом роде сильно поможет снизить нагрузку на процессор при запуске миссии:
(store_mission_timer_a, reg0),(gt, reg0, ),
Ни ti_before_mission_start, ни ti_after_mission_start не требуют использования "ti_once" в качестве перезарядки, поскольку они все равно и так будут выполнены только один раз.
Подводя итог, запуск миссии происходит в следующем порядке:
- Триггеры ti_before_mission_start
- Триггеры ti_on_agent_spawn
- Запуск таймера миссии
- Триггеры ti_after_mission_start и триггеры с интервалом вызова равным 0
- Временные триггеры (с 1 секунды миссии)
- Триггеры по событию
Диалоги
Первая часть любого диалога, с которой он начинается, выглядит примерно так:
"[ ...какой-то код... ]"
Это код, отвечающий за проверку условий при вызове диалога. Единственное и главное его назначение – проверять, выполнено ли данное условие или набор условий и возвращать результат. Каждый диалог при вызове проверяет это условие до тех, пока оно не будет выполнено.
Далее идут последующие части диалога, которые выполняются, только в том случае, если проверка условий пройдена успешно и активна именно их ветвь диалога. Поэтому, если вы хотите, чтобы какое-то одно действие выполнялось всегда, когда, игрок, например, встречает Главнокомандующего во вторник, но совершенно другое – когда они встречаются в среду, диалог может выглядеть как-то так:
#Начало диалога[#См. header_dialogs, чтобы узнать, какие команды могут быть использованы здесь помимо "anyone". Но любой диалог обязательно должен иметь строку "start",anyone, "start",#Начало стартового блока проверки условий[ (eq, "$g_talk_troop", "trp_generalissimo_evilguy"),#Собеседник игрока – главнокомандущий?(try_begin), (eq, "$g_day_of_week", 2),#Сейчас вторник? (assign, "$g_conversation_temp",1), (str_store_string, s17, "@Так начинается разговор по вторникам"),(else_try), (eq, "$g_day_of_week", 3),# Сейчас среда? (assign, "$g_conversation_temp",2), (str_store_string, s17, "@ Так начинается разговор по средам "), (else_try), ,# Если сейчас не вторник и не среда (assign, "$g_conversation_temp",3) (str_store_string, s17, "@ Так начинается разговор во все остальные дни "),(try_end),],#Допустим, проверка пройдена и собеседник игрока – главнокомандующий. Теперь надо вывести нужный текст"s17",#Теперь нужно добавить ответные реплики игрока, пускай даже какие-нибудь простейшие типа «Уйти» для выхода из диалога"player_response_or_responses_here",#Специальный код, если нужен:[],#Конец диалога],
Но вообще создавать строки диалога, зависящие от классов, в одном блоке условий, как это было сделано выше — довольно плохой тон. Так делать не стоит, поскольку больше вероятность сделать какую-нибудь логическую ошибку. Лучше заключать создание каждой строки в отдельный блок условий, вот так:
[anyone,"start", [(eq, "$g_talk_troop", "trp_player_castellan"),], "Что вам угодно, {playername}?", "castellan_talk",[]],
С помощью такого подхода можно сделать несколько вариантов начала разговора, используя несколько блоков условий:
[anyone,"start", [(eq, "$g_talk_troop", "trp_player_castellan"),(eq, "$g_some_game_global", 1),], "Что вам угодно,{playername}?", "castellan_talk",[]],[anyone,"start", [(eq, "$g_talk_troop", "trp_player_castellan"),(eq, "$g_some_game_global", 2),], "А, это снова ты, {playername}! Подлый мерзавец, [b]Строки состояния[/b]Строка состояния — это условное выражение, использующееся в строке для подстановки одной из двух данных строк в зависимости от значения определенного регистра. Если значение регистра не равно 0, подставляется первый вариант; в противном случае — если значение регистра равно 0 — подставляется второй вариант. Вариант может быть пустой строкой. Люди, знакомые с программированием, могут понять, что строке состояния соответствует оператор «?:» в других языках программирования.[b]Использование строк состояния[/b]Строка состояния представляет собой выражение, заключенное в фигурные скобки «{ }» и выглядящее следующим образом: сначала идет название регистра, на основании значения которого и будет определяться выбор варианта, за ним следует символ «?», далее — вариант, при регистре, не равном 0, символ «:», вариант при регистре, равном 0.Простейший пример строки состояния:[code]город{reg0?а:}
Если reg0 не равен 0, вместо данного выражения в строку подставится «а», если равен — подставится «» - пустая строка. Поэтому, если reg0 не равен 0, то данная строка будет «города», а если равен - «город».
Сложные строки состояния
Строки состояния могут вкладываться одна в другую, создавая сложные динамически генерируемые строки. Например:
{reg6?Я:{reg7?Ты{s11}}}
Данная строка примет значение «Я», если reg6 != 0 (значение reg7 в этом случае не важно), “Ты”, если reg6= 0 и reg7 != 0, или же строки s11 (в данном случае содержащей имя лорда), если оба этих регистра равны 0. Это компактное и красивое решение, более эффективное, чем использование напрямую отдельного строкового регистра и громоздкое присваивание ему «Я», «Ты» или имени нужного лорда с помощью, например, try блоков или других подобных конструкций.
Однако сложные строки состояния стоит использовать, только если данная строка встречается в коде только однажды. В случае, если такая строка должна использоваться в коде несколько раз, будет как раз удобнее и лучше один раз создать отдельный строковый регистр и присвоить ему нужное значение с помощью try блока так, как это было описано выше, без использования строк состояния.
Строки пола
Строка пола, или как ее реже называют, строка чередования полов — это условное выражение, использующееся в диалогах для подстановки одной из двух данных строк в зависимости от пола персонажа, с которым происходит диалог (обычно игрока). Если этот персонаж мужского пола, то вместо данного выражения подставляется первый вариант, если женского — то второй. Это является одним из способов динамической генерации текста и используется, как правило, для правильного формирования обращений игровых персонажей к игроку.
Использование строк пола
Строка пола имеет чрезвычайно простую конструкцию: она состоит из двух текстовых строк, одна для мужчины, другая для женщины, разделенных слешем “/” и заключенных в фигурные скобки “{ }”.
«Добрый день, {сударь/сударыня}. Чем могу помочь?»
В случае, если персонаж, к которому обращено эта фраза, мужского пола, она примет вид:
«Добрый день, сударь. Чем могу помочь?»
Ну и если собеседник — женщина:
«Добрый день, сударыня. Чем могу помочь?»
Примечания
Используйте строки пола всегда, когда это возможно
Будьте особенно внимательны, когда пишете диалог для игрока или лорда, у которого может быть другой пол, нежели вы предполагаете. Если с помощью каких-либо сторонних модификаций или чисто случайно пол собеседника окажется не таким, каким вы предполагали при написании диалога, диалог будет несогласован. Поэтому если есть возможность, что собеседником может оказаться как мужчина, так и женщина, используйте строки пола для избежания ошибок.
Строки пола могут использоваться только с говорящим и слушающим персонажами
Обратите внимание, что строка пола относится к тому, к кому обращена реплика, а не к тому, кто ее произносит.
Чтобы слушателем был игрок, запись диалога должна начинаться так:
[anyone
Если слушателем должен быть другой игровой персонаж, то эта запись должна быть такой:
[anyone|plyr
Также помните, что строки пола учитывают пол только тех двух персонажей, которые непосредственно участвуют в диалоге (говорящий и слушающий). Если нужно определить пол какого-то стороннего, третьего персонажа, следует определить класс этого персонажа и воспользоваться функцией troop_get_type, чтобы определить его пол, и в зависимости от полученного результата выполнить какие-то действия.
Регистры
Регистры в основном используются для динамического отображения на экране чисел. Это происходит так: регистру присваивается нужное числовое значение, после чего регистр встраивается в отображаемую строку, используемую, например, в диалогах или сообщениях, выводимых на экран. Таким образом применение регистров является одним из четырех вариантов создания динамически генерируемых строк (наряду со строками состояния, строками пола и строковыми регистрами).
Использование регистров
Регистр, встроенный в строку, представляет собой следующую конструкцию: название регистра, заключенное в фигурные скобки «{ }». Когда строка выводится на экран, данное выражение заменяется числовым значением регистра. Имена регистров строго определены и имеют вид «reg*», где * - число от 0 до 65. Например данная строка содержит два регистра с ID 1 и 2 соответственно, значения которых будут выводиться игроку в данной строке:
Денег у вас в наличии: {reg1}^Общая стоимость товаров: {reg2}
Для изменения значения регистра используется операция (assign),
Примечания
Важно отметить, что регистры – не переменные; они предназначены только для отображения чисел на экране или их хранения. Все вычисления этого выводимого числа необходимо проводить с помощью локальных или глобальных переменных, и после чего только уже в регистр записывать окончательный ответ для вывода на экран.
Хотя для регистров и можно использовать арифметические и другие операции типа (store_add), (val_add), и т. д., следует избегать подобных ситуаций. С регистрами не должно проводиться никаких операций! Используйте для вычислений вместо регистров переменные — это сделает код гораздо более логичным, удобным и понятным.
Единственная функция, которая предназначена для работы напрямую с регистрами – (shuffle_range),
Строковые регистры
Строковые регистры — это специальные регистры, которые используются для хранения строк. Строковые регистры могут включаться в какую-либо строку, например, в часть диалога, в качестве одного из четырех способов динамической генерации текста (другие три способа — с помощью регистров, строк пола и строк состояния).
Использование строковых регистров
Строковый регистр должен быть вставлен в строку, будучи заключенным в фигурные скобки «{ }». Когда текст данной строки выводится на экран (в качестве диалога или сообщения и др.), строковый регистр заменяется строкой, в нем содержащейся. Заметьте, что все пробелы и любые другие символы внутри строкового регистра выводятся точно так, как были записаны внутри.
Вид имен строковых регистров строго задан: имя любого строкового регистра начинается с символа «s», за которым следует число от 0 до 67.
Как ты посмел усомниться в могуществе такого величайшего государства как {s43}?! Умри, {s5}!
Эта строка содержит два строковых регистра с номерами 43 и 5 соответственно. Перед тем, как эта строка будет выведена на экран в виде реплики диалога, выражения строковых регистров будут заменены строками, содержащимися в них, образуя таким образом связный текст. Соответственно, перед тем, как выводить эту строку в диалоге, необходимо присвоить этим строковым регистрам подходящие значения. В данном случае, вероятно, наиболее подходящими операциями для этого будут str_store_string_faction_name для s43 и str_store_string_troop_name для s5.
Управление строковыми регистрами
Управление строковыми регистрами происходит с помощью соответствующих операций, предназначенных специально для этого. Первый параметр в этих операциях — это всегда ID строкового регистра, значение которого должно быть изменено. Второй параметр, если он есть, - ID объекта, из которого будет получена строка, присваиваемая строковому регистру. Подобные операции легко узнать — их названия начинаются с приставки str_.
Флаги
Флаг — это «да-или-нет»-состояние, привязанное к своему числовому значению с помощью операции логического сложения (логического ИЛИ «|»). Название произошло от аналогии с семафором: когда флаг поднят, состояние активно, когда опущен — состояние неактивно. Флаг имеет два условных состояния, в одном из которых может находиться: «1» - активен, «0» - неактивен. Поскольку для хранения одного флага используется один бит информации, получается, что его значение можно проверять простейшей математической операцией, и это позволяет флагу занимать минимальное количество памяти, поэтому такая конструкция, несмотря на простоту, является наиболее удобной и надежной.
В МС все объекты, операции и переменные технически представляют собой просто числа, что позволяет просто добавлять флаги к ID объектов и других подобных конструкций, чтобы хранить о этих объектах дополнительную информацию, за которую эти флаги отвечают. Но только движок игры способен определить, содержит ли данный числовой ID какие-либо флаги и в зависимости от этого совершать какие-то действия.
Использование флагов
Синтаксис использования флагов довольно прост: чтобы добавить к объекту флаг, нужно после названия объекта через символ «|» написать название флага, например anyone|plyr. Таким образом можно добавлять любое количество флагов, просто так же ставя очередной символ «|» и приписывая название очередного флага после него. Каждый приписанный флаг добавляет один бит, содержащий его значение, к ID объекта.
Пример
party_tpl|pt_looters|plyr
Этот пример из module_dialogs.py ассоциирует данный диалог с шаблонной партией pt_looters и применяет к нему флаги party_tpl и plyr. Можно увидеть, что свойства получившегося диалога зависят от примененных флагов, но не зависят от порядка, в котором они прописаны. В итоге данный диалог происходит благодаря флагам party_tpl и pt_looters предназначен для разговора не с отдельным персонажем, как это было бы по умолчанию, а с партией, имеющей шаблон pt_looters, то есть с отрядом грабителей. Флаг plyr означает, что диалог будет обращен не к игроку, как это было бы по умолчанию, а к лидеру партии.