Написание триггеров Триггеры и что это такое. Триггеры служат для внесение в игровой мир разнообразия. Используя их можно существенно расширить возможности сервера и красоту создаваемой зоны. По типу "прикрепления" триггеры делятся на три группы: 1.Триггеры, прикрепляемые к монстрам. 2.К объектам. 3.К комнатам зоны. Сам триггер - это набор команд на специальном языке (DG Scripts), исполняемый при возникновении в игре какого-либо события. Событием может быть смерть моба, появление игрока в определенной комнате, подбирание предмета, называние игроком пароля и т.п. В общем-то, практически любое действие игрока может быть отслежено, и на него может быть сделана специальная реакция. Обычными примерами обычно являются триггеры, делающие следующее: 1. Стражник, зовущий на помощь, когда его атакуют 2. Горожанин, здоровающийся с проходящими мимо 3. Стеклянная колба, разбивающаяся и образующая лужу, когда ее бросают на пол 4. Две половинки ключа, которые можно соединить в целый ключ 5. Комната, при входе в которую игрок падает в яму, и его слегка повреждает 6. Комната, в которой можно повернуть ручку и попасть в другую комнату Написание триггеров, в общем-то, дело несложное, но требующее хотя бы общих представлений о любом языке программирования (в пределах школьного курса хотя бы). Обычно на триггерах осуществляется написание "квестов". Как правило, квест - это несколько загадок и заданий, объединенных какой-либо сюжетной линией. Я рекомендую особо не налегать на написание "украшательных" триггеров типа журчащей водички и т.п.: сделали парочку - и хватит Ж). Подготовка к написанию триггеров Написание триггеров делится на 4 этапа: 1 Этап. Планирование квестов. Тут Вы планируете квесты, вначале в общем, например: Хочу, чтобы у меня был монстр ИМЯ1, который выдает задание забрать вещь, которую давно отнял монстр ИМЯ2. Для этого можно ее либо купить, либо убить этого монстра. Для этого надо найти яд и подмешать монстру ИМЯ2 в еду, после этого переодеться в одежду слуги и отнести ему эту еду, он съест и помрет, игрок возмет предмет и отнесет его, получив награду. После того, как вы согласуете квест в общем (с Мандосом согласуете :)), можно перейти к планированию маленьких его частей, т.е. непосредственно к триггерам. Цель этого этапа: убедиться, что планируемая квестовая линия оригинальна, реализуема и вполне подходяща к игровому миру. В результате получается небольшой файл, в котором описана общая схема прохождения всех квестов для зоны с указанием всех их основных ветвей, а также общее описание легенды, на фоне которой все это происходит. 2 Этап. Разбиение квеста на триггера. И описание всего механизма в подробностях. К примеру: 1 триггер : к мобу ИМЯ1 входит игрок проверить, что квест еще не выдан если выдан - сказать "Поздно - блаблабла" если не выдан - сказать "Если хочешь отличиться - блаблабла" 2 триггер : в комнате с мобом ИМЯ1 кто-то что-то сказал если сказал ДА - закрепить за этим игроком квест, сказав "блаблабла" если сказал НЕТ - сказать "блаблабла" если сказал другое - ничего не делать 3 триггер : мобу ИМЯ1 дают вещь проверить что вещь та, которую он просил если нет - не брать ее, сказав "блаблабла" если да проверить, что квест был выдан игроку, давшему вещь если нет - что делать ? если да взять вещь удалить ее сказать "блаблабла" наградить игрока предметом (номер) 4 триггер : мобу ИМЯ2 дают вещь описание действий. Цель этого этапа: расписать все возможные реакции монстров, вещей и комнат на действия игроков. Создать все необходимые предметы и т.д. и т.п. В общем, целиком и полностью подготовиться к написанию триггеров, так чтобы по ходу их написания не додумывать имена вещей, мобов, их реплики. Все это делается в отдельном текстовом файле. 3 Этап. Написание триггеров и их начальное тестирование. В случае, если Вы сами не в состоянии написать триггера, их может написать за Вас Мандос, по описаниям, созданным на этапе 2. Заранее договоритесь с ним об этом. Собственно далее будет описание лишь этого этапа. 4 Этап. Тестирование и исправление найденных ошибок. Говорит само за себя Ж) Общие правила написания триггеров: 1. Тщательно изучите всю имеющуюся документацию. В этом один из залогов успеха. Лучше всего прочитать все, что найдете, как то: описание скриптов <ссылка>, данное руководство и всевозможные памятки. Критически относитесь к прочитанному и проверяйте изложенные факты путем тестирования. Найдя ошибку в документации, сообщите о ней Мандосу - он поправит то, что надо поправить (и не обязательно документацию). 2. Не извращайтесь. Не пытайтесь придумать оригинальное применение триггерам. Они предназначены для решения определенного круга задач. Если у Вас получается для простой вещи очень непростые и длинные триггера, это значит, что Вы делаете что-то не так; обратитесь к Мандосу - он наставит Вас на правильный путь. 3. Придерживайтесь правил "хорошего программирования": а.Название триггера должно отражать то, что он делает. То есть не следует давать имена типа "триггер1", "триггер2"; гораздо лучше "Привидение(номер 39421) атакует того, у кого в руках нет магического жезла(номер 39421)" b. Внутренности блоков типа if что-то else что-то end надо оформлять с 2-мя пробелами. То есть: if (<условие>) <что-то еще> if (<условие>) <что-то еще> end <что-то еще> end c. Не создавайте бессмысленных идентификаторов, d. Не пишите большие куски одинакового кода (когда этого можно избежать), e. Поясняйте неординарные вещи комментариями. Можно в начале триггера написать все, что он должен делать. 4. Не пытайтесь тестировать триггера (и вообще зоны) персонажами-богами; не все команды будут нормально работать, а что-то, что будет работать, может не работать на игроках. 5. При тестировании выведите персонажа-бога и сделайте ему syslog complete. Тогда он будет видеть сислог с ошибками, возникающими в триггерах. Также неплохо помогает делать switch в монстра, который выполняет триггер. В этом случае Вы можете прочувствовать его действия на своей шкуре Ж). 6. Каждый триггер должен содержать хотя бы одну команду. Не надо делать пустых триггеров - сервер вас не поймет. 7. Одинаковые триггеры, подключенные к одному монстру/предмету/комнате, могут не работать. Точнее, обычно срабатывает только первый из них. Например, если подключить к монстру два Death-триггера, то сработает только первый из них. 8. Не надо использовать триггера, помеченные в редакторе как нестрандартные. Как правило, их использование - альтернативный путь решения тех или иных проблем. Если вам нельзя обойтись без этих триггеров - обратитесь к Мандосу... он вас вразумит Ж). Недокументированные возможности лучше не использовать совсем, или попросить, чтобы их документировали. Это __ОЧЕНЬ__ важно, так как став первопроходцем, Вы рискуете найти такие глюки, что все содрогнутся. 9. Протестируйте ВСЮ зону персонажем женского пола. Все фразы должны быть построены так, чтобы корректно отображаться и для женщин. Грабли При написании триггеров есть всего несколько "тонких мест", однако грабли разложены в этих местах необычайно искусно и ОЧЕНЬ толстым слоем. Поэтому лучше вникните в нижеописанное. Что надо знать обязательно: 1. Команда wait - одна из ключевых команд. Очень важно использовать ее, когда надо, и НЕ использовать, когда не надо. Внимательно анализируйте, когда выполняется триггер (то есть, когда он начинает выполняться). К примеру, триггер, вызывающийся, когда монстру дают предмет, вызывается ДО передачи предмета; следовательно, чтобы уничтожить предмет командой mjunk, надо подождать хотя бы совсем немного (wait 1), чтобы предмет успел попасть к монстру. Напротив, если Вы хотите сделать монстра, который обязательно здоровается с каждым вошедшим, то не используйте в его триггере wait вообще, иначе, когда к нему войдет группа - он поздоровается только с первым из нее. Важно понимать и то, что каждый триггер (скажем, триггер монстра) будет вызван для этого монстра только в том случае, если этот триггер у этого монстра еще не активирован. Предположим, есть монстр, который рассказывает историю, делая между частями паузы по 10 секунд. Начинает он это, когда игрок входит в его комнату. Игрок входит в комнату, монстр произносит первую часть истории и начинает ждать. В это время в комнату с монстром входит другой игрок... триггер не сработает вторично... монстр будет ждать и дальше, расскажет вторую часть истории, потом третью. Если после того, как он закончит, в комнату войдет другой игрок - монстр выполнит триггер еще раз, если не зайдет - триггер не выполнится, даже если во время, пока монстр был "занят" историей, к нему зашло 100 человек. Имейте в виду, что при частом и длительном использовании wait, следует проверять некоторые вещи, например дерется моб или нет с игроком :). Может, пока он тебе задание дает, ты его уже пинаешь вовсю. Есть и еще одна проблема. Триггера не исполняются во время лагов у монстров (лаг наступает, например, если монстра сбили с ног, или он попытался сбить). Точнее, не исполняются некоторые команды. Так, если монстр ждет 5 секунд после того, как игрок войдет в комнату, говорит 1-2 фразы и убивает его, то если этого монстра убить за эти 5 секунд или держать под башем после 4-х секунд, то он не сможет сказать свои реплики и никого не убъет. ВСЕ команды монстров подвержены таким лагам, исключая команды, выполняемые в death-trigger-е. Всегда тестируйте такие тонкие места. 2. Правильное использование переменных - залог победы над любыми трудностями. Об этом будет сказано чуть позже. 3. Не забывайте подключать триггера к нужным комнатам, монстрам, объектам. Для этого в соответствующем поле напишите T <номер триггера>. Например: (подключить два триггера) T 10010 T 10011 Список подключенных к монстру триггеров можно узнать командой stat имямонстра (если вы бессмертный 35го уровня). 4. Удаляя что-то в триггере, проверьте, что это не то, к чему этот триггер прицеплен :). Иначе удаляйте это в самом конце. 5. Иногда бывает нужно как-то сослаться на монстра или предмет. Например, для его уничтожения. Многие при этом пишут команды типа "mjunk хлеб". Это чудовищная ошибка... особенно если учесть, что обычно это бывает не хлеб, а какой-нибудь монстр :). Никогда не обращайтесь к вещам и монстрам по их названию, используйте специальные уникальные идентификаторы. Эти идентификаторы не надо создавать, они есть всегда. Для мобов они выглядят как mob_XXXXXX, где XXXXXX - это vnum этого монстра, для объектов obj_XXXXXX (vnum - это то, что перед именем в редакторах в квадратных скобочках). -------------------------------------------------------------------------------------------- Работа с переменными. Очень длинно и очень важно. Всего переменные бывают 2-х типов. К первому типу относятся переменные DG-scripts, получающиеся, скажем, при команде set <имя переменной> <значение>. Их использование тривиально и не требует специальной подготовки. Они тупые как валенки, но иногда весьма полезны. Как правило, в них имеет смысл хранить временную информацию, пропадающую после окончания триггера. Для других целей их лучше не использовать. Ко второму типу относятся переменные, специально сделанные нами для нашего сервера. Они существенно отличаются от переменных dg-scripts. Основное их отличие - возможность задавать условия, по которым они очищаются... этот, казалось бы, пустячок на самом деле является весьма необходимым дополнением. Далее речь пойдет именно о таких переменных. Используйте команду VARS для проверки состояния этих переменных. И команду sset var_debug 1 для включения дебаг-режима. !! ВНИМАНИЕ !! У вас есть только один шанс создать АБСОЛЮТНО никому не нужную зону - это использовать в ее триггерах переменные с произвольными именами, или раздавать (создавать и прикреплять к мобам/игрокам и т.п.) переменные направо и налево (старайтесь выдавать столь мало переменных, сколь возможно - ведь каждая из них требует обработки и нагружает сервер, особенно это касается "долгоживущих" переменных). Все переменные доступны из любого из триггеров. Пожалуйста, помните это. И не пытайтесь получать доступ к чужим переменным, если только у Вас нет исключительной ситуации (поговорите с Мандосом :)). Одновременно может существовать только одна переменная с заданным именем (нельзя создать две переменных с именем "var_10010") Все переменные в Вашей зоне должны иметь ЖЕСТКО предопределенные имена. Это имена типа var_XXXYY, где XXX - это номер вашей зоны (например 125 или 025), а YY - это произвольное значение, лучше в качестве XXXYY использовать номер триггера, который _создает_ переменную. Если ваша зона имеет номер 125, то вы сможете создать в ней как минимум 100 разных переменных, от var_12500 до var_12599. Если у Вас есть особое желание, то Вы можете расширить этот список и далее, введя дополнительный суффикс через _. Тогда имена переменных будут иметь вид var_12500_ЧТОТОЕЩЕ, например var_12500_numberone, var_12500_numbertwo, и т.п. Но не увлекайтесь чрезмерно - длинные имена переменных лишь пожирают память сервера. НИКОГДА не используйте в именах переменных и других идентификаторов русские буквы и извращенческие символы типа * ? и т.п. Используйте ТОЛЬКО английские буквы, цифры и знак _. В качестве значения переменных лучше тоже использовать лишь эти символы, хотя при реальной необходимости можно использовать и кириллицу (например, если надо запомнить имя игрока). Функции для работы с переменными: create_var - Создать переменную delete_var - Удалить переменную set_var - установить значение переменной get_var - Получить значение переменной set_var_reset - Установить параметры ресета (очищения) переменной get_var_reset - Получить параметры ресета (очищения) переменной is_var_reseted - Очищена ли переменная is_var_exist - Есть ли такая переменная Каждая из переменных может быть прикреплена к монстру, игроку, предмету или комнате. Несмотря на то, что любая переменная доступна из любого триггера, ОЧЕНЬ важно вызывать переменные от тех объектов, в которых они находятся. Так вы можете написать if (%self.get_var("var_27203")%==quest_gived) действия end либо if (%actor.get_var("var_27203")%==quest_gived) действия end Оба куска кода будут работать внешне одинаково, но в них есть отличие. В одном используется self, в другом actor. Весь механизм поиска переменных устроен так, что вначале они ищутся в списке того, от кого вызвана данная функция (это совсем маленький список... сами понимаете, у одного игрока или монстра переменных много быть не может), а потом в общем списке _ВСЕХ_ переменных мада (он очень большой). Следовательно, правильно указывая хранилище, мы ничем не рискуем, но экономим производительность сервера и делаем написанный код более понятным. Обычным применением переменных является использование их для хранения в них информации о том, в каком состоянии находится тот или иной мини-квест (выдан/не выдан, кому выдан, и т.п.). Как правило, такие переменные хранятся до ресета зоны (репопа); схема реализуется примерно так: Предположим, есть квест-монстр, дающий квест принести вещь. Предположим, что выдается один квест в репоп. Номер зоны 123, номера триггеров начиная с 0. Тогда надо: В самом начале квеста (когда игрок соглашается его взять, к примеру) ставится проверка типа следующей: if ( %self.is_var_exist("var_12300")%==0 ) - если переменной еще нет, иначе квест уже взят %self.create_var("var_12300")% - создать переменную %self.set_var_reset("var_12300" "reset_zone" "123")% - удалить переменную при репопе %self.set_var("var_27203" "quest_gived")% - установить значение переменной end В качестве значения можно запомнить и имя игрока, чтобы потом проверить его. %self.set_var("var_27203" "%actor.name%")% - установить значение переменной в имя игрока Там, где надо, можно проверить значение переменной конструкцией типа: if (%self.get_var("var_27203")%==quest_gived) действия end Более подробно смотри зону-пример. !!! Внимание !!! Есть специальный режим для переменных, он включается командой sset var_debug 1, выключается sset var_debug 0 (по умолчанию выключен). В этом режиме у бессмертных в sys-лог попадают данные о работе с переменными. Описание функций работы с переменными. Все параметры, передаваемые в эти функции, отделяются друг от друга пробелами и заключаются в двойные кавычки. ** Создать переменную ** Создает переменную с заданным именем и флагами. Имейте в виду, что имя должно быть уникально, т.е. ЛЮБОЙ повторный вызов с тем именем, которое уже используется, приведет к ошибке. Синтаксис: create_var(name flags) Где: name - имя переменной flags - флаги переменной c VAR_FLG_NODECAY Не удалять перменную из памяти после ресета (как правило, не используется) d VAR_FLG_PERMANENT переменная постоянная (не удаляется при уходе игрока в ренту, перезагрузке сервера и т.п. ПОКА НЕ РАБОТАЕТ. КАК БУДЕТ - СООБЩУ. НЕ РАССЧИТЫВАЙТЕ НА ЕЕ НАЛИЧИЕ. Возвращаемые значения (обычно используются лишь для отладки): 0 - переменная создана 1,2 - неверно заданные параметры 3 - переменная не создана (такая уже есть в локальном списке) 4 - переменная не создана (такая уже есть в глобальном списке) 5 - не создана Пример: %actor.create_var("var_23101" "c")% - создает переменную с именем var_23101 при ресете не удаляется из памяти %actor.create_var("var_111")% - никаких флагов нет ** Установить значение переменной ** set_var(name value) name - имя переменной value - значение переменной (старое значение удаляется и замещается новым) Возвращаемые значения(обычно используются лишь для отладки): not_found - Неременная не найдена или не существует err_no_params - Неверно заданы параметры функции err_params_empty - Неверно заданы параметры функции err_type_unknown - Ошибка в коде. Неверный тип вызова. Пример: %actor.set_var("var_23101" "value11")% ** Получить значение переменной ** get_var(name) name - Имя переменной Возвращаемые значения: значение переменной - если все хорошо not_found - Переменная не найдена или не существует err_no_params - Неверно заданы параметры функции err_params_empty - Неверно заданы параметры функции err_type_unknown - Ошибка в коде. Неверный тип вызова. msg_type_unknown - Неизвестный тип сообщения Примеры: if (%actor.get_var(%var%)%==load) say LOAD end ** УСТАНОВКА ПАРАМЕТРОВ РЕСЕТА ** set_var_reset(name xtype param) Вы можете задавать для одной переменной любое количество условий ее очищения. То есть, Вы можете комбинировать и делать переменную, очищающуюся при репопе или через 10 минут после того, как она установлена (для этого надо вызвать функцию дважды). Функция не работает с переменными с флагом VAR_FLG_PERMANENT. Такие переменные могут быть модифицированы только вызовом set_var. После установки новых параметров (вызова set_var) признак ресета переменной принудительно очищается, т.е. функция isResetted перестает выдавать истину. name - имя переменной Важно комбинировать нужный тип с нужным параметром. xtype - Одно из: "round", - очищать переменную по времени "reset_zone", - очищать переменную при ресете зоны "die_pc", - очищать переменную при смерти игрока "die_mob", - очищать переменную при смерти моба с заданным vnum (любого) "extract_obj", - очищать переменную при разрушении объекта с заданным vnum (любого) "rent_pc", - очищать переменную, если игрок ушел в ренту (не путать с "игрок умер") param (число): round - Кол-во раундов до ресета. Один раунд - это 2.5 секунды. reset_zone - VNUM зоны die_pc - IDNUM чара (%actor.id%) die_mob - VNUM монстра extract_obj - VNUM предмета rent_pc - IDNUM чара (%actor.id%) Возвращаемые значения: not_found - Неременная не найдена или не существует err_no_params - Неверно заданы параметры функции err_params_empty - Неверно заданы параметры функции err_type_unknown - Ошибка в коде. Неверный тип вызова. msg_type_unknown - Неизвестный тип сообщения var_is_permanent - Попытка изменить параметры сброса постоянной переменной Пример: 2: * использовать переменную dg_scripts для записи временного значения set VAR var_10001 * очищать переменную var_10001 после ребута зоны 100 %self.set_var_reset("%VAR%" "reset_zone" "100")% 2: * очищать переменную var_10001 через 25секунд(10 раундов) после вызова этой функции * будьте аккуратны с этой опцией %self.set_var_reset("%VAR%" "round" "10")% 3: * очищать переменную var_10001 когда игрок уходит в ренту * (актуально если переменная содержится не в игроке, а в монстре) if (%actor.vnum==-1) %self.set_var_reset("%VAR%" "rent_pc" "%actor.id%")% end ** ПРОВЕРКА НА РЕСЕТ ПЕРЕМЕННОЙ ** is_var_reseted(name) name - Имя переменной Возвращаемые значения: not_found - Переменная не найдена или не существует err_no_params - Неверно заданы параметры функции err_params_empty - Неверно заданы параметры функции err_type_unknown - Ошибка в коде. Неверный тип вызова. msg_type_unknown - Неизвестный тип сообщения 0 - Переменная еще не поресетилась 1 - Переменная уже поресетилась ** ЧТЕНИЕ ПАРАМЕТРОВ РЕСЕТА ** Неясно зачем вам это надо Ж). get_var_reset(name param) name: имя переменной param: "round", "reset_zone", "die_pc", "die_mob", "extract_obj", "rent_pc", Возвращаемые значения: not_found - Переменная не найдена или не существует err_no_params - Неверно заданы параметры функции err_params_empty - Неверно заданы параметры функции err_type_unknown - Ошибка в коде. Неверный тип вызова. msg_type_unknown - Неизвестный тип сообщения Примеры: %self.get_var_reset("var_001" "die_pc")% - возвращает значение IDNUM чара если при событии die_pc оно совпадет с idnum чара, который умер, то переменная поресетится. eval rounds %self.get_var_reset("var_001" "round")% if (%rounds%!=not_found) say кол-во раундов боя до ресета переменной %rounds%. end ** УДАЛЕНИЕ ПЕРЕМЕННОЙ ** delete_var(name) name: Имя переменной Возвращаемые значения: not_found - Переменная не найдена или не существует err_no_params - Неверно заданы параметры функции err_params_empty - Неверно заданы параметры функции err_type_unknown - Ошибка в коде. Неверный тип вызова. msg_type_unknown - Неизвестный тип сообщения Примеры: %actor.delete_var("var_0001")% // *события* Описание того как обрабатываются события и очищаются переменные (для маньяков) Все переменные с флагом VAR_FLG_PERMANENT игнорируются msg_round наступает перед каждым раундом боя и если targets[VAR_MSG_ROUND] > 0, происходит уменьшение на 1 При достижении нуля происходит ресет переменной (функция reset()) вывоз триггера соответствующего типа удаление переменной из памяти если не установлен флаг VAR_FLG_NODECAY msg_reset_zone Наступает до ресета зоны Если targets[VAR_MSG_RESET_ZONE] == VNUM(zone) то ресет переменной msg_die_pc Наступает перед вызовом death_trigger Если targets[VAR_MSG_DIE_PC] == IDNUM(char) то ресет переменной msg_die_mob Наступает перед вызовом death_trigger Если targets[VAR_MSG_DIE_MOB] == GET_MOB_VNUM(char) то ресет переменной msg_extract_obj Наступает перед удалением предмета из памяти. Если targets[VAR_MSG_EXTRACT_OBJ] == GET_OBJ_VNUM(obj) то ресет переменной (триггер не вызывается если переменная принадлежит удаляемому объекту) msg_rent_pc Наступает перед рентой или авторентой чара Если targets[VAR_MSG_RENT_PC] == IDNUM(char) то ресет переменной ресет перменной: 1. вызывается VAR.reset() 2. вызывается триггер 3. удаляется из памяти ТРИГГЕР ------- Reset_var Описание: Этот триггер вызывается когда происходит ресет переменной принадлежащей объекту к которому прицеплен этот триггер. Аргумент: Не используется Числовой аргумент: процент запуска триггера Возвращаемое значение: не используется Переменные: var имя переменной, которая сбрасывается msg событие - инициатор ресета одно из: "round", "reset_zone", "die_pc", "die_mob", "extract_obj", "rent_pc", флаги MOB - p OBJ - o ROOM - i ---------------------------------------------------------------------- ----------------------------------------------------------------------