Статья рассчитана на новичков в Jass, которые хотели бы научиться создавать триггерные заклинания. Если вы не знакомы с Jass'ом, рекомендую ознакомиться с данным материалом. Начнём с теории. Также не помешает ознакомление с данным материалом.
Из чего состоит триггерное заклинание? - Это, собственно, триггер с событием применения этого заклинания и действия, составляющие суть нашего заклинания. - Отличие триггерного заклинания от обычного (созданного в редакторе объектов) заключается в свободе действий - в редакторе объектов (далее р.о.) вы не сможете сделать заклинание, которое бы одновременно нанесло врагу урон, наложило заклинание исцеления на героя и создало красивый эффект в точке между героем и целью заклинания, для этого и нужны триггеры.
Прежде всего нужно продумать наше заклинание. Какие юниты нужны для его исполнения, может быть, стоит создать dummy (опишу их позже) для наложения заклинаний, придумать описание. Лично я сначала описываю заклинание в редакторе объектов, то есть создаю нестандартную способность, заполняю описание, требования и всё в этом духе, а уже потом приступаю к созданию триггеров и прочего.
Вроде, всё ясно? Придумали сложное заклинание, создали способность (рекомендую делать на основе способности Канал, у которой нет побочных эффектов)? Самое время приступить к триггерам.
Для примера создадим триггерное заклинание, которое бы при выборе цели испускало в неё молнию и отскакивало на других врагов, как Chain Lighting, создавая рядом с поражёнными врагами их клонов, атакующих их, причём клоны брали бы часть здоровья монстра (пример: у монстра с 500 хп станет 400 хп и появится клон с 100 хп), а по происшествию некоторого времени клоны бы исчезали, возвращая их жизнь монстрам, если и первые и вторые живы. Звучит сложно, но зато мы много чему научимся. Далее будет следовать код на Jass2 (стандартный jass, не vJass/cJass). Для комфортного написания кода, очень рекомендую установить JNGP, так как без него печатать код, по моему скромному мнению, невозможно. Чтобы не нарушить читаемость статьи, картинки буду класть под спойлеры. Чтобы не возникало массы вопросов, разжую всё, начиная с установки JNGP. 1. Ставим Jass New Gen Pack 5d (не обязательно) В этой части будет описана загрузка JNGP
1.1 Загружаем JNGP:
1.2 Устанавливаем JNGP. Скачав JNGP, убедитесь что архив вы скачали полностью и вес файла совпадает.
Далее необходимо распаковать архив 7zip, например на диск C:, в папку JNGP.
По завершению распаковки заходим куда надо.
Выделяем папку и нажимаем Копировать. Далее заходим в папку с Warcraft 3, и вставляем туда нашу папочку "jngp".
Ещё не всё! Заходим в папку jngp, и запускаем файл NewGen WE.exe
Он попросит вас (при 1-ом запуске) указать путь к папке с Warcraft 3, что вы и будете должны сделать. Теперь можете создать ярлык этого приложения, т.к. теперь для запуска редактора карт вам следует запускать его.
2. Приступая к делу В этой части мы создадим условия для успешного теста нашей способности. Делаем, как на картинках:
2.1 2.2 2.3 Внимание! Если этого окна нет, сверху нажимаем: Окно -> Новая панель -> Войска. 2.4 2.5 Больше 1 героя нам не требуется. 2.6 2.7 Немного гноллов для теста. 2.8 2.9
3. Способность в Р.О. В этой части мы создадим нашу способность в редакторе объектов. Примечание: На картинках изменялась стандартная способность Канал, в то время, т.к. таких способностей может быть много, рекомендуется создавать нестандартную способность на основе канала и менять её. Делаем как на картинках:
4. Триггеры В этой части мы приступим к триггерам: создадим молнии и добавим звуковой эффект Тут будет уже более подробное описание
Открываем редактор триггеров.
Удаляем триггер инициализации, это приведёт к: - Не будут установлены условия победы - Не будут созданы начальные юниты (ратуша и работники для альянса) - Не будут даны начальные ресурсы - Не будет инициализирован ИИ, ограничены герои
Теперь если вы запустите карту (Ctrl+F9) то увидите что наша способность применяется как надо, но пока что ничего не происходит. Нужно заметить, что если установлен JNGP, перед тестом карты нужно сохранить карту (Ctrl+S), и ничего не меняя запустить, в противном случае карта может не запуститься. Создаём наш триггер:
Превращаем его в текст (все триггеры - обёртка под этот текст):
Видим вот что:
Думаем... По какому событию выполняется заклинание? Это событие - EVENT_UNIT_SPELL_EFFECT, что означает "Юнит приводит способность в действие" (в триггерах). Регистрируем наше событие:
Далее нужно добавить условие, ведь действие должно происходить, только если было применено наше заклинание:
Прежде нужно посмотреть код нашей способности, так называемый Raw-code (ро-код).
Смотрим raw-код, и видим что он состоит из 4-ёх символов, цифр и латинских букв.
Запоминаем и вписываем в код, запомните: raw-код в Jass пишется в одинарных кавычках: 'ANcl'.
Дело сделано, осталось только действие, давайте вспомним, что должно произойти? Герой применил способность на врага, от героя к врагу тянется молния, от врага к другому врагу, и так поражает 4-ёх врагов После этого рядом с врагами появляются их клоны, дерущиеся с ними и обладающие от рождения частью здоровья врага (у врага это здоровье отнимается), а по истечении времени здоровье (сколько его у клона) возвращается, а клон исчезает (если он жив). Ну что же, начнём: Создадим переменную типа Хэш-таблица, она нам очень пригодится.
Далее приписываем в функцию инициализации строчку, инициализирующую нашу хэш-таблицу:
Создадим переменные которые нам понадобятся в функции действий, если понадобятся еще какие-либо, потом допишем
Приступим к действиям
Какой тип молнии нам нужен? Тут нужно провести небольшую демонстрацию
Запускаем, предварительно отключив наш недоделанный триггер.
Смотрим, выглядит красиво Примерно понятно какая молния как называется, лично мне нравится светло-фиолетовая что слева.
Методом исключения я нахожу, что это:
Теперь как мы уже делали это, превращаем триггер в текст и видим:
Пишем этот код в нашем триггере, а триггер Test можно теперь удалить. И не забудьте включить наш триггер обратно.
Теперь, запустив карту, можно наблюдать нашу молнию, появляющуюся при применении заклинания, однако она не исчезает и так и висит. Перепишем код так, чтобы молния удалялась через 0.3 секунды
Ещё, на мой взгляд, не хватает звуковых эффектов, добавим таковой:
Звук есть, добавляем его в код:
Теперь сделаем так, чтобы молния поразила по возможности 4-ёх врагов, и также сохраним молнии в хэш, чтобы их затем удалить:
И функция по таймеру:
Теперь если вы всё правильно сделали, должна выпускаться молния на 4-ёх врагов, со звуком паразита и абсолютно никак на врагов не влияющая, на всякий случай приложу полный код:
Код
function Trig_ChailIllusiry_Conditions takes nothing returns boolean //Наша функция-условие, возвращающая boolean (returns boolean) //Тут проверка на код способности //Вощвращаем: равен ли код применённой способности нашей способности? return GetSpellAbilityId() == 'ANcl' endfunction
//Функция, удаляющая молнию, которую вытаскивает из хэша через ид таймера function Trig_ChainIllusory_RemoveLightning takes nothing returns nothing local timer t = GetExpiredTimer() //Истёкший таймер local integer id = GetHandleId(t) //Ид нашего таймера //Уничтожаем все молнии call DestroyLightning(LoadLightningHandle(udg_Hashtable, id, 0)) call DestroyLightning(LoadLightningHandle(udg_Hashtable, id, 1)) call DestroyLightning(LoadLightningHandle(udg_Hashtable, id, 2)) call DestroyLightning(LoadLightningHandle(udg_Hashtable, id, 3)) set t = null endfunction
function Trig_ChainIllusory_Actions takes nothing returns nothing local lightning l//Переменная типа молния local unit caster = GetTriggerUnit() //Переменная типа юнит, со значением применяющего юнита(кастера) local unit target = GetSpellTargetUnit() //Тоже самое, только хранит значение цели заклинания local integer level = GetUnitAbilityLevel(caster, GetSpellAbilityId()) //Целочисленная переменная, хранящая значения уровня заклинания у кастера local real length = level * 5.0 + 10.0 //Длительность существования двойников - 10 секунд + 5 * уровень заклинания local real life = level * 10.0 + 20.0 //Здоровье двойников в процентах - 20 + 10 * уровень заклинания local group g = CreateGroup() //Переменная типа отряд local group illusions = CreateGroup() //Переменная типа отряд, которая будет хранить наших двойников local integer i = 0 //Целочисленная переменная для цикла local timer t = CreateTimer() //Таймер, понадобится для уничтожения молний и двойников когда придёт время local integer id = GetHandleId(t) //Ид таймера, понадобится для сохранения в хэш с привязкой к таймеру local group targets = CreateGroup() //Переменная типа отряд, будет хранить наших выбранных врагов local unit u local boolean found //Переменная типа boolean, отвечающая за найден ли враг для молнии //Назначаем переменной значение новой молнии, которая будет между кастером и целью заклинания //в кавычках нужно указать тип молнии set l = AddLightning("FORK", false, GetUnitX(caster), GetUnitY(caster), GetUnitX(target), GetUnitY(target)) //Вешаем на таймер нашу молнию через хэш call SaveLightningHandle(udg_Hashtable, id, 0, l) //Запускаем таймер на 0.4 секунд, вызывающий функцию Trig_ChainIllusory_RemoveLightning call TimerStart(t, 0.4, false, function Trig_ChainIllusory_RemoveLightning) //Звуковой эффект: устанавливаем позицию звука на позицию врага и запускаем call SetSoundPosition(gg_snd_ShadowStrikeBirth1, GetUnitX(target), GetUnitY(target), 150.0) call StartSound(gg_snd_ShadowStrikeBirth1) loop set found = false //Пока что следующий враг не найден // Выбрать всех в радиусе 300 от позиции врага в группу g call GroupEnumUnitsInRange(g, GetUnitX(target), GetUnitY(target), 300.0, null) loop //Пусть u - первый из группы set u = FirstOfGroup(g) //Если такого нет, или враг уже найден - конец цикла exitwhen(u == null or found == true) if(IsUnitEnemy(u, GetOwningPlayer(caster)) and GetWidgetLife(u) > 0.405 and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) then if not IsUnitInGroup(u, targets) then set found = true //Враг найден //Этот юнит нам подходит, бъём его молнией и добаляем в группу targets call GroupAddUnit(targets, u) //Молния от предыдущей цели к u set l = AddLightning("FORK", false, GetUnitX(target), GetUnitY(target), GetUnitX(u), GetUnitY(u)) //Предыдущая цель = u set target = u //Сохраняем молнию в хэш через ид таймера на ключ i+1, чтобы не перезаписать другие молнии call SaveLightningHandle(udg_Hashtable, id, i+1, l) //Запускаем таймер на 0.4 секунд, вызывающий функцию Trig_ChainIllusory_RemoveLightning call TimerStart(t, 0.4, false, function Trig_ChainIllusory_RemoveLightning) endif endif //юнита обработали, удаляем и переходим к следующему call GroupRemoveUnit(g, u) endloop set i = i + 1 //Выйти если поражены 3 врага или следующий враг не найден exitwhen(i == 3 or found == false) endloop endfunction
//=========================================================================== function InitTrig_ChainIllusory takes nothing returns nothing //Триггер инициализации, вызовется 1 раз при инициализации карты local boolexpr condition = Condition(function Trig_ChailIllusiry_Conditions) //Создаём переменную типа Условие, и задаём ей значение - //функция Trig_ChailIllusiry_Conditions, которая будучи //условием должна возвращать(returns) boolean, то есть //либо истину(true), либо ложь(false) //в первом случае действия будут выполнены, а //во втором случае, дальше условия триггер не продвинется set udg_Hashtable = InitHashtable() //Инициализируем хэш-таблицу set gg_trg_ChainIllusory = CreateTrigger() //Редактор уже позаботился об объявлении глобального триггера gg_trg_ChainIllusory, //который уже объявлен в коде карты, а мы лишь задаём ему значение - Новый триггер call TriggerRegisterAnyUnitEventBJ(gg_trg_ChainIllusory, EVENT_PLAYER_UNIT_SPELL_EFFECT) //Регистрируем событие //Наш триггер Событие, тут требуется именно PLAYER_UNIT т.к. мы регистрируем событие на всех игроков(всех юнитов) call TriggerAddCondition(gg_trg_ChainIllusory, condition) // call TriggerAddAction(gg_trg_ChainIllusory, function Trig_ChainIllusory_Actions) //Хоть события у триггера и нет, у него уже добавляется действие - //функция Trig_ChainIllusory_Actions, которая расположена выше set condition = null //Обнуляем наш boolexpr, так как он унаследован от handle, а лишние ссылки = нагрузка на память endfunction
5. Триггеры, часть вторая В этой части мы создадим клонов и всё что с ними связано. [spoiler=Спойлер]У нас на руках код из предыдущей части и мы начинаем модифицировать его. Для начала нам нужно создать рядом с каждым врагом его клона. Добавим новую переменную u2 типа unit:
Теперь будем с каждым врагом создавать клона. Создаём клона для первой цели:
И для 3-ёх последующих:
Если вам не ясна строка
Код
call SetUnitVertexColor(u2, 155, 15, 155, 155)
То я объясню: она изменяет отображение модели юнита, причём первый аргумент (u2) - юнит, второй, третий и четвёртый (155, 15, 155) - значения красного, зелёного и синего цветов соответственно, а последний (155) - прозрачность (а точнее, непрозрачность). Цифровые аргументы располагаются в диапазоне от 0 до 255, то есть 0 - это 0% красного/синего/зеленого/непрозрачности (100% прозрачности), а 255 - наоборот, полностью красный/зелёный/синий/непрозрачный. Чтобы это понять, я покажу, как это действует: Если все аргументы, кроме красного и непрозрачности, выдать 0-ми, а первые - 255-ми, мы увидим полностью красного юнита, так как другие цвета пикселей умножены на 0%, и остался только красный:
А в нашем случае:
Код
call SetUnitVertexColor(u2, 155, 15, 155, 155)
Получится вот что:
Но вы можете сделать его серым или ещё каким-то, поэкспериментировав с аргументами (если кто не знает, аргументы - это переменные или значения, передаваемые в функцию между круглых скобок).
Добавлено (15 Марта 2013, 15:00:13) --------------------------------------------- Теперь, если запустить карту, вот что мы увидим:
На первый взгляд всё хорошо, всё сработало, жизни, я вас уверяю, распределились как нужно. Но важно протестировать код со всех сторон, для этого, давайте пересмотрим наш алгоритм: 1. Выбирается цель заклинания 2. К ней идёт молния, и рядом с целью создаётся её клон 3. В цикле 3 раза выбирается группа юнитов вокруг цели, которая подходит в качестве цели, один из критериев отбора - юниты, уже выбранные как цели, и в эту группу наша первая цель не включается. Проверим это допущение в деле, применим молнию на 1-го юнита. Как думаете, что произойдёт? Создастся 1 клон, скажут невнимательные/недогадливые читатели.
А вот и нет, создадутся 2 клона, так как в цикле следующей целью будет выбрана цель заклинания, ведь она подходит по всем параметрам, для исправления этого добавим нашу первую цель в группу выбранных врагов.
Запускаем ещё раз:
Теперь всё хорошо, осталось позаботится о своевременном удалении клонов и возвращении остатков здоровья. Тут нам понадобится воспользоваться хэш-таблицей для сохранения клонов и их целей, а также ещё один таймер. Разумеется можно не городить на 1 таймер 4 клона и 4 цели, а создать 4 таймера на одинаковое время и рпивязать к каждому своего клона и цель, но зачем, если время исчезновения одинаково? Итак, новые переменные: таймер t2 и целочисленная id2.
Сохраняем наше богатство из клонов и их оригиналов в хэш, через таймер t2. Для начала цель заклинания:
А потом в цикле врагов:
Теперь неплохо бы запустить этот таймер и создать соответствующую функцию. Функция удаления иллюзий:
Запускаем таймер:
Теперь можно запустить карту и наблюдать, как всё работает. Кажется, всё? На протяжении всего кода я частично оставлял без внимания одну важную часть. Возможно, вы догадались - это утечки. Пробежимся по коду и обратим на них внимание, но всё по порядку.
В этой функции вообще нет переменных и объектов, поэтому об утечках и речи быть не может.
Тут две переменные, таймер и целочисленная, так как целочисленная не наследуется от типа handle (в моём понимании это ссылка на объект, описывающая как объект, так и его тип) её обнулять не нужно, а таймер мы обнулили (set t = null), но мы забыли его уничтожить, а это очень важно, ведь объект - таймер, так и находится в памяти и нагружает наш варкрафт, для этого изменим нашу функцию: Также необходимо очистить хэш, ведь больше ячейку с идом таймера мы не используем.
Хорошо, идём дальше:
Здесь дело пострашнее, не обнуляются две переменные типа unit, наследуемые от handle, а также не уничтожается таймер. С таймером всё просто, но вот что на счёт юнитов? Почему нужно уничтожать таймер, но не нужно уничтожать юнитов? Об этом позаботится движок игры, юнитов он удаляет сам после смерти (88 секунд после смерти, когда заканчивается разложение, если оно включено). А вот таймер никто, кроме нас, не уничтожит Так сделаем же это! Точно так же, как прежде, освобождаем ячейку хэш-таблицы.
Кстати, два важных момента: - Под словом обнулять я подразумеваю присвоить значение null, что означает 0, то есть отсутствие чего-либо, в данном случае указателя, это необходимо чтобы указатель не занимал память. - При уничтожении объектов, как таймер, важно СНАЧАЛА уничтожить, а уже ПОТОМ обнулять, часто новички обнуляют переменную и пытаются после её удалить, но как можно удалить 0? Идём дальше:
Как много всего, даже не знаю, с чего и начать. Перечислим переменные, наследуемые от handle, и, следовательно, необходимые для обнуления: l, caster, target, g, illusions (ОЙ! Эту переменную мы не использовали, с чистой совестью стираем строчку с её объявлением!), t, t2, target, u, u2 то есть real, integer и boolean обнулять не нужно, это не указатели, а основные типы.
Вот так, но вы можете спросить: "Почему мы не уничтожаем таймеры, используя функцию DestroyTimer()"? Ответ прост, эти таймеры еще работают, мы их запустили, а уничтожатся они в других функциях, когда надобность в них пропадёт, мы же просто обнуляем ссылки на них. Молнию уничтожать нет смысла, т.к. это только последняя созданная молния, и вообще молнии должны уничтожаться через время, что у нас и происходит. Идём дальше:
Так, какой-то boolexpr, это такая штука, так скажем, указатель на функцию-условие, который тоже нужно обнулить т.к. он наследуется от handle, а уничтожать его не нужно, т.к. он используется триггером. Кстати, то же самое можно было написать по-другому, создавая условие прямо в регистрации события:
Теперь и обнулять ничего не нужно, по-моему, так лучше, но я не сделал так в начале, чтобы наглядней показать устройство нашего триггера.[/spoiler]
6. Вместо заключения Ну вот и всё, на выходе должен получится такой код:
Код
function Trig_ChailIllusiry_Conditions takes nothing returns boolean //Наша функция-условие, возвращающая boolean(returns boolean) //Тут проверка на код способности //Вощвращаем: равен ли код применённой способности нашей способности? return GetSpellAbilityId() == 'ANcl' endfunction
//Функция, удаляющая молнию, которую вытаскивает из хэша через ид таймера function Trig_ChainIllusory_RemoveLightning takes nothing returns nothing local timer t = GetExpiredTimer() //Истёкший таймер local integer id = GetHandleId(t) //Ид нашего таймера //Уничтожаем все молнии call DestroyLightning(LoadLightningHandle(udg_Hashtable, id, 0)) call DestroyLightning(LoadLightningHandle(udg_Hashtable, id, 1)) call DestroyLightning(LoadLightningHandle(udg_Hashtable, id, 2)) call DestroyLightning(LoadLightningHandle(udg_Hashtable, id, 3)) call DestroyTimer(t) set t = null endfunction
//Функция, удаляющая клонов function Trig_ChainIllusory_RemoveIllusions takes nothing returns nothing local timer t = GetExpiredTimer() //Истёкший таймер local integer id = GetHandleId(t) //Ид нашего таймера local unit u local unit u2 local integer i = 0 loop set u = LoadUnitHandle(udg_Hashtable, id, i*2) //Враг set u2 = LoadUnitHandle(udg_Hashtable, id, 1 + i*2) //Клон if(u2 != null) then //Если клон существует if(u != null and GetWidgetLife(u) > 0.405 and GetWidgetLife(u2) > 0.405) then //Если есть возможность вернуть здоровье call SetWidgetLife(u, GetWidgetLife(u) + GetWidgetLife(u2)) endif call KillUnit(u2) endif set i = i + 1 exitwhen(i == 4) endloop call DestroyTimer(t) set u = null set u2 = null set t = null endfunction
function Trig_ChainIllusory_Actions takes nothing returns nothing local lightning l//Переменная типа молния local unit caster = GetTriggerUnit() //Переменная типа юнит, со значением применяющего юнита(кастера) local unit target = GetSpellTargetUnit() //Тоже самое, только хранит значение цели заклинания local integer level = GetUnitAbilityLevel(caster, GetSpellAbilityId()) //Целочисленная переменная, хранящая значения уровня заклинания у кастера local real length = level * 5.0 + 10.0 //Длительность существования двойников - 10 секунд + 5 * уровень заклинания local real life = level * 10.0 + 20.0 //Здоровье двойников в процентах - 20 + 10 * уровень заклинания local group g = CreateGroup() //Переменная типа отряд local integer i = 0 //Целочисленная переменная для цикла local timer t = CreateTimer() //Таймер, понадобится для уничтожения молний и двойников когда придёт время local timer t2 = CreateTimer() //Таймер для исчезновения клонов local integer id = GetHandleId(t) //Ид таймера, понадобится для сохранения в хэш с привязкой к таймеру local integer id2 = GetHandleId(t2) //Ид таймера исчезновения клонов local group targets = CreateGroup() //Переменная типа отряд, будет хранить наших выбранных врагов local unit u local unit u2 local boolean found //Переменная типа boolean, отвечающая за найден ли враг для молнии //Назначаем переменной значение новой молнии, которая будет между кастером и целью заклинания //в кавычках нужно указать тип молнии set l = AddLightning("FORK", false, GetUnitX(caster), GetUnitY(caster), GetUnitX(target), GetUnitY(target)) //Присваиваем переменной u2 значение клона, которым владеет кастующий игрок, клон находится в позиции врага set u2 = CreateUnit(GetOwningPlayer(caster), GetUnitTypeId(target), GetUnitX(target), GetUnitY(target), 0.0) //Меняем значения здоровья у клона и врага call SetWidgetLife(u2, GetWidgetLife(target) * life / 100) call SetWidgetLife(target, GetWidgetLife(target) - GetWidgetLife(target) * life / 100) call SetUnitVertexColor(u2, 155, 15, 155, 155) //Под номером 0 будет цель заклинания call SaveUnitHandle(udg_Hashtable, id2, 0, target) //Под номером 1 будет её клон call SaveUnitHandle(udg_Hashtable, id2, 1, u2) //Вешаем на таймер нашу молнию через хэш call SaveLightningHandle(udg_Hashtable, id, 0, l) //Запускаем таймер на 0.4 секунд, вызывающий функцию Trig_ChainIllusory_RemoveLightning call TimerStart(t, 0.4, false, function Trig_ChainIllusory_RemoveLightning) //Звуковой эффект: устанавливаем позицию звука на позицию врага и запускаем call SetSoundPosition(gg_snd_ShadowStrikeBirth1, GetUnitX(target), GetUnitY(target), 150.0) call StartSound(gg_snd_ShadowStrikeBirth1) //Перед циклом добавим нашу цель в группу выбранных целей call GroupAddUnit(targets, target) loop set found = false //Пока что следующий враг не найден // Выбрать всех в радиусе 300 от позиции врага в группу g call GroupEnumUnitsInRange(g, GetUnitX(target), GetUnitY(target), 300.0, null) loop //Пусть u - первый из группы set u = FirstOfGroup(g) //Если такого нет, или враг уже найден - конец цикла exitwhen(u == null or found == true) if(IsUnitEnemy(u, GetOwningPlayer(caster)) and GetWidgetLife(u) > 0.405 and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) then if not IsUnitInGroup(u, targets) then set found = true //Враг найден //Этот юнит нам подходит, бъём его молнией и добаляем в группу targets call GroupAddUnit(targets, u) //Молния от предыдущей цели к u set l = AddLightning("FORK", false, GetUnitX(target), GetUnitY(target), GetUnitX(u), GetUnitY(u)) //Присваиваем переменной u2 значение клона, которым владеет кастующий игрок, клон находится в позиции врага set u2 = CreateUnit(GetOwningPlayer(caster), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), 0.0) //Меняем значения здоровья у клона и врага call SetWidgetLife(u2, GetWidgetLife(u) * life / 100) call SetWidgetLife(u, GetWidgetLife(u) - GetWidgetLife(u) * life / 100) call SetUnitVertexColor(u2, 155, 15, 155, 155) //Под номером 2+i*2 будет враг call SaveUnitHandle(udg_Hashtable, id2, 2+i*2, u) //Под номером 3+i*2 будет его клон call SaveUnitHandle(udg_Hashtable, id2, 3+i*2, u2) //Предыдущая цель = u set target = u //Сохраняем молнию в хэш через ид таймера на ключ i+1, чтобы не перезаписать другие молнии call SaveLightningHandle(udg_Hashtable, id, i+1, l) //Запускаем таймер на 0.4 секунд, вызывающий функцию Trig_ChainIllusory_RemoveLightning call TimerStart(t, 0.4, false, function Trig_ChainIllusory_RemoveLightning) endif endif //юнита обработали, удаляем и переходим к следующему call GroupRemoveUnit(g, u) endloop set i = i + 1 //Выйти если поражены 3 врага или следующий враг не найден exitwhen(i == 3 or found == false) endloop call TimerStart(t2, length, false, function Trig_ChainIllusory_RemoveIllusions) call DestroyGroup(g) //Уничтожаем группу set l = null set caster = null set target = null set u = null set u2 = null set t = null set t2 = null set g = null endfunction
//=========================================================================== function InitTrig_ChainIllusory takes nothing returns nothing //Триггер инициализации, вызовется 1 раз при инициализации карты set udg_Hashtable = InitHashtable() //Инициализируем хэш-таблицу set gg_trg_ChainIllusory = CreateTrigger() //Редактор уже позаботился об объявлении глобального триггера gg_trg_ChainIllusory, //который уже объявлен в коде карты, а мы лишь задаём ему значение - Новый триггер call TriggerRegisterAnyUnitEventBJ(gg_trg_ChainIllusory, EVENT_PLAYER_UNIT_SPELL_EFFECT) //Регистрируем событие //Наш триггер Событие, тут требуется именно PLAYER_UNIT т.к. мы регистрируем событие на всех игроков(всех юнитов) call TriggerAddCondition(gg_trg_ChainIllusory, Condition(function Trig_ChailIllusiry_Conditions)) // call TriggerAddAction(gg_trg_ChainIllusory, function Trig_ChainIllusory_Actions) //Хоть события у триггера и нет, у него уже добавляется действие - //функция Trig_ChainIllusory_Actions, которая расположена выше endfunction
Надеюсь, вам понравилась статья, если что-то плохо объяснил или ошибся, прошу милости писать в комментариях или в личные сообщения, буду регулярно следить за Вашими ответами. Признаюсь, я мало времени уделил handle, и, если вы с ним не знакомы, советую почитать это. Кстати, некоторые опытные jass'еры советуют вместо Save(Unit/Lightning/...)Handle использовать SaveAgentHandle, чего я не сделал, чтобы наглядно показать типы переменных, которые мы вытаскивали из хэша. Также рекоммендуется использовать SetWidgetX/Y вместо GetUnitX/Y, первое вроде как работает быстрее, однако тут на любителя
Статья написана за 4 дня, и если честно, это были 4 ужасных дня, не буду больше писать статьи Да и думаю, вышло ужасно, трудно найти баланс - насколько подробно что-либо описывать. Было бы неплохо, если бы хоть кому-нибудь эта статья открыла свет хотя бы на часть вопросов, посвещённых тематике jass и триггерных заклинаний. Код написан на jass2, а не на vJass/cJass из-за соображений, что новички пользуются первым, да и аудитория вроде как побольше получится. Сильно строго не судите. -------------------by Hexing------------------- ...всем cJass, пацаны... Комментарий от меня (Duosora): В Каталоге статей статья была отключена из-за просроченных картинок. Готов её вернуть, если будут убраны ссылки на сторонние ресурсы и восстановлены картинки.
Не зли других и сам не злись. Мы - гости в этом мире. И если что не так - смирись, Будь поумнее - улыбнись, Ведь в мире всё закономерно. Зло, излучённое тобой, К тебе вернётся непременно.