Карты Warcraft, форум о игре, опросы. Технические вопросы, Dota, турниры. Картография, картостроение.

Объявление

Варик warcraft III варкрафт 3 4 всё о warcraft 3 компьютерные, игры, патчи, моды, nocd, прохождение, игр, коды, читы, трейнеры, скачать, игры Warcraft, allstars, 3, iii, варкрафт, вц3, wc3, киберспорт, дота, форум, стратегия, реплеи, с, чемпионатов, цфкскфае, Всё, для, III, wc3, файлы, карты, мапы, программы, софт, Dota, картостроение, картография, скачать, создание карт warcraft 3

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.



Всё о JASS'е

Сообщений 1 страница 5 из 5

1

всё о джассе тут

Код:
w3l.jimdo.com/статьи/картография/всё-о-jass-е/

Перейти к первой части статьи
Перейти ко второй части статьи

Полезные функции
JASS – триггеры в виде кода.
0. Вступление
Этот цикл статей посвящен изучению языка jass (создание текстовых триггеров, так называемых скриптов). Хотя про jass уже написано достаточное число статей, но многие картостроители все равно не желают его изучать. И я их вполне понимаю – для тех, кто не занимается программированием в тех статьях, которые имеются, разобраться довольно сложно. Не хватает некоторых базовых знаний, не понятно, с чего начинать и т.д.
Любой учитель знает, что излагать предмет надо поэтапно и в определенной последовательности. Я выбрал последовательность в том порядке, в котором сам когда-то начал осваивать язык jass. Так что триггерщики, которые до сих пор не решаются приступить к изучению jass, смогут проделать тот же путь.
Эти статьи я написал для одного своего друга триггерщика. И друг вполне оправдал мои ожидания - не прошло и месяца, как он стал jass-ером.

Для удобства, я буду обращаться в статьях к читающему – «Читатель». Надеюсь, что читатель уже неплохо овладел триггерами, знает, что такое переменные, массивы и циклы.

Содержание:
Чачть 1:
0. Вступление
1. Что есть jass и для чего он нужен
2. Локальные переменные
3. Применение локальных переменных
4. Условия, циклы в jass
5. Функции на jass
6. Устройство триггера с точки зрения jass
7. Динамическое создание триггера
8. События с малым периодом
9. Полярные координаты (ликбез)
10. Оптимизация: утечки памяти
Часть 2:
11. RETURN BUG (RB)
12. Тип Handle
13. Система Super Custom Value (SCV) или RB+cache
14. Да здравствует SCV!
15. Послесловие
16. Приложение 1: проблемы Кеша и РБ
17. Приложение 2: JESP стандарт


1. Что есть jass и для чего он нужен
Итак, Читатель, мы приступаем к освоению jass. Прежде всего, я хочу, чтобы ты понимал, что jass не является чем-то сверхъестественным и необычным. Многое, из того, что нам предстоит изучить уже будет знакомо по триггерам. И неудивительно – ведь каждое триггерное действие имеет свой аналог на jass.

Дело в том, что blizzard создали свой миниязык программирования jass. Они хотели упростить работу по программированию сценариев и их правил. Но даже такой упрощенный язык слишком сложен для многих картостроителей. Поэтому был создан редактор Trigger editor – где вместо того, чтобы набирать команды вручную, можно создавать триггеры путем выбора команд из списка. В редакторе мы работаем с триггерами, но когда игра запускает определенный сценарий, она читает не триггеры, а КОД, который создается путем перевода всех триггеров игры на язык jass.

По сути триггеры – это надстройка для языка jass упрощающая ввод команд. Но при любом упрощении происходит потеря функциональности. Т.е. возможности программирования игры триггерами в чистом виде заметно меньше, чем при программировании при помощи jass.

Итак, jass дает картостроителю новые возможности. Но стоят ли они того, чтобы их изучать? Это уже каждый картостроитель решает для себя. Jass может помочь в следующих случаях:
1. Упростить создание сложных триггеров и триггерных систем, которые позволят полностью или почти полностью поменять правила оригинальной игры.
2. Создавать триггерные заклинания, у которых нет аналогов в оригинальной игре.
3. Оптимизировать карту, сделать, чтобы сложные триггеры не тормозили игру.
4. Вставить на карту некоторые команды на jass, которые не имеют аналогов для обычных триггеров.
5. Создавать собственные AI, более гибкие, чем в редакторе AI.

У некоторых бытует мнение, что на jass в игре можно сделать все. Конечно же, это не так. Более того, я не рекомендую писать все триггеры сценария исключительно на jass. Для многих задач редактор триггеров подойдет лучше – ведь это действительно очень удобная штука.
2. Локальные переменные
Локальные переменные – это воистину первый шаг в освоении jass. И это очень важный шаг. Умея работать с локальными переменными, ты сможешь заметно упростить себе решение многих триггерных задач.

Читатель, прежде чем начнем изучение, я советую тебе скачать файл sample locs.w3x, который приложен к данной статье. Скачай, затем открой в редакторе и запусти на исполнение. Суть примера в том, что если стрельнуть заклинанием файербол, над головой жертвы появляется спецэффект «восклицательный знак», который через несколько секунд исчезнет. Действие сделано на триггерах с применение локальных переменных.

С одной стороны можно задать вопрос – а зачем тут вообще нужны триггеры? Этого же эффекта можно достичь в редакторе объектов. Да, можно. Но главное не это. Вместо спецэффекта мы можем навесить и любое другое действие. Например, при ударе файербола, на юните будет появляться череда сменяющих друг друга спецэффектов. Такого в редакторе объектов уже не сделаешь.

А можно ли сделать этот эффект при помощи обычных триггеров? Конечно. Для одного юнита, это легко сделать. Например, запустил юнит файербол, мы:
1. Помещаем цель заклинания в переменную u типа юнит.
2. Ждем время, пока файербол долетит (которое равно расстояние до цели делить на скорость полета).
3. Создаем спецэффект на юните u, который записываем в переменную se типа спецэффект.
4. Через несколько секунд уничтожаем спецэффект se.

Все просто, но... Насколько такой триггер будет универсален? Предположим, несколько юнитов имеют заклинание файербол, и поочередно друг за другом применяют его, так что два триггерных действия (создание спецэффекта) выполняются в короткий промежуток времени. Тогда у нас произойдет триггерный конфликт. Ведь в одни и те же переменные u и se будут писаться параметры для разных файерболов. В итоге у нас могут появиться 2 спецэффекта над одним и тем же юнитом и один из этих спецэффектов останется навсегда. Все это произойдет из-за того, что без переменных вообще обойтись нельзя, а для разных запусков нельзя использовать одни и те же переменные.
Есть способ исправить эту проблему: для каждого запуска файербола помещать значения не в переменные, а в ячейку массива. Для каждого запуска сохранять значения в свои ячейки, каким-то образом отслеживать, что пришел момент создать спецэффект для такого-то юнита из массива или удалить такой-то спецэффект из другого массива. Это не очень удобный и достаточно громоздкий способ. В итоге, простая по сути задача – становится очень тяжелой.

В то же время, в том примере sample locs, эта задача решена очень легко. Чтобы узнать как – рассмотрим, что же такое локальные переменные.

Читатель, ты уже знаком с переменными в редакторе. Ты умеешь создавать их при помощи редактора переменных. Так вот, все переменные, которые создаются в редакторе переменных, будем отныне называть глобальными переменными. Глобальные переменные можно использовать во всех триггерах игры.
Оказывается, что кроме глобальных переменных, существует еще один вид – локальные переменные. Локальные переменные – это переменные, которые работают только внутри определенного триггера. Локальные переменные создаются при запуске триггера и уничтожаются после того, как выполнение триггера закончено. Если триггер запущен на исполнение несколько раз, то при каждом запуске создается свой набор локальных переменных, никак не связанный с другими наборами.

В каждом триггере можно определить набор локальных переменных. Для этого нужно применить команду из jass. В редакторе есть возможность вставить в триггер команду из jass – так называемый Custom Script (в дальнейшем cs). Читатель, давай посмотрим, как это сделано в примере – см триггер «Cast fireball method 1».
В самом начале триггера идут команды

cs:   local unit u
cs:   local effect e

Это объявление того, что при запуске этого триггера будут созданы 2 локальные переменные: u типа юнит и e типа спецэффект. Создавать локальные переменные можно всех тех же типов, что и глобальные и в любом количестве. Можно даже создавать массивы локальных переменных.
Далее идет обычная триггерная команда:

Set unit = (Target unit of ability being cast)

В глобальную переменную unit помещается юнит - цель нашего заклинания.

Дальше идет еще одна jass-команда:

cs:   set u = udg_unit

Что это значит? Дело в том, что в jass есть такое правило: перед глобальными переменными ставится приставка udg. udg_unit - это наша глобальная переменная unit. Что касается локальных переменных, то их имена пишутся непосредственно без всяких приставок. Что же означает наша команда, записанная выше? u – локальная переменная, udg_unit – глобальная, set – это оператор присвоения.
Ответ таков: мы в локальную переменную u поместили то, что было записано в глобальную переменную unit (а в ней у нас был юнит-цель заклинания).
Оставим пока вопрос зачем, просто посмотрим, что будет дальше. А дальше идет команда

Код:
Wait ((Distance between (Position of (Casting unit)) and (Position of (Target unit of ability being cast))) / 1000.00) game-time seconds

- ждать время, равное отношению расстояния между кастонувшим юнитом и юнитом-целью к 1000 (1000 – это скорость снаряда файербола). Т.е. ждать время полета. Далее идет команда на jass:

cs:   set udg_unit = u

Догадаетесь, что она означает? В глобальную переменную unit помещаем то, что записано в локальной переменной.
Далее проделана аналогичная схема с созданием спецэффекта. Созданный спецэффект помещается в глобальную переменную se, затем в локальную переменную e помещается что, что записано в se. Затем ждем период 3 игровых секунды и делаем обратное: записываем в se то, что записано в e. И уничтожаем спецэффект.
Итого, весь триггер напоминает тот, который мы создали бы, чтобы реализовать появление спецэффекта для одного юнита. Разница лишь в нескольких jass вставках. Но без этих вставок триггер НЕ УНИВЕРСАЛЕН, а со вставками – УНИВЕРСАЛЕН. Почему?

Давай вспомним про локальные переменные, которые мы создали. При каждом запуске триггера «Cast fireball method 1» будет создаваться набор из двух локальных переменных u и e. Причем для каждого запуска свой набор – не зависящий от других наборов. Запустим триггер 1000 раз – будет создано 1000 локальных переменных u типа юнит и e типа спецэффект.
В локальную переменную u мы поместили юнит-цель заклинания (сначала в глобальную unit, затем в локальную u). Через несколько секунд мы не можем гарантировать, что значение глобальной переменной unit не изменится. Ведь другой юнит может запустить файербол по другой цели – тогда значение переменной unit будет перезаписано. НО ЗНАЧЕНИЕ ЛОКАЛЬНОЙ ПЕРЕМЕННОЙ ДЛЯ ДАННОГО ЗАПУСКА НЕ ИЗМЕНИТСЯ. Ведь при следующем запуске триггера будет создан новый набор локальных переменных, а старые наборы не будут затронуты.

Итак, при помощи локальных переменных мы можем сохранить юнит-цель для каждого запуска заклинания файербол. А через некоторое время, равное времени полета файербола, мы должны создать на юните спецэффект. Мы делаем нужную паузу и затем помещаем в глобальную переменную unit ссылку на юнит из переменной u. И создаем спецэффект над юнитом из переменной unit.
Таким приемом мы можем гарантировать, что сколько бы файерболов не было выпущено, спецэффект будет создаваться над юнитом-целью и только над ним. Никаких сбоев не будет. Точно такой же прием с удалением спецэффекта через 3 секунды после создания. Все эти три секунды ссылка на спецэффект будет храниться в локальной переменной e. А затем мы перебросим ее значение в глобальную переменную se и удалим спецэффект.

Итак, как показывает пример, локальные переменные очень удобны для реализации УНИВЕРСАЛЬНЫХ отсроченных действий. Это свойство локальных переменных делает их незаменимыми при создании триггерных заклинаний. Причем добавить локальные переменные в триггер, как ты убедился, совсем не сложно.

Этот пример я специально сделал наиболее простым. jass команды, которые в нем используются – создать локальную переменную и присвоить значение переменной. Локальные переменные только для хранения данных. Для конкретных действий мы используем глобальные переменные unit и se. Также мы используем их как посредники – для переброски в них значений из локальных переменных и наоборот. Вообще говоря, в нашем примере можно обойтись и без глобальных переменных – одними локальными. Но проблема в том, что использование локальных переменных не предусмотрено в редакторе. Чтобы использовать эти переменные необходимо записывать команды на jass.

Теперь, Читатель, используй команду Правка->Конвертировать в текст, чтобы перевести весь триггер в «Cast fireball method 1» в jass. Не вдаваясь пока в устройство триггеров, обрати внимание на фрагмент, в который превратились наши триггерные действия. Каждая строчка триггерных команд превратилась в какую-то строчку jass-кода. Что касается строчек из custom script, они не изменились, т.к. они уже были написаны на jass. Теперь посмотри на триггер «Cast fireball method 2». Похоже? Да, это почти то же самое, только во втором примере я уже не использую глобальные переменные.

Обрати внимание на то, что в jass-код можно вставлять комментарии
// любой текст
Если нужно отключить какую-то строчку кода, не обязательно ее стирать. Можно превратить ее в комментарий. Второй пример на самом деле не работает, т.к. я превратил в комментарий строчку, которая отвечает за события триггера.
Еще обрати внимание, что названия спецэффектов в jass-коде записываются немного иначе - вместо одного \, записывается два:
"Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl"

Любой триггер можно конвертировать в jass-код, но обратную процедуру выполнить невозможно. Хотя есть такое действие – отменить последнюю команду редактора: ctrl-z. Если нужно, можно посмотреть, как выглядит код триггера, а затем вернуть триггер обратно.

Наконец, посмотри на третий триггер «Cast fireball method 3». Он делает то же самое, что и первые два. Но этот третий пример является как бы смесью первых двух. Как и во втором примере, здесь не используются глобальные переменные, но все команды, в которых используются локальные – пришлось переписать в виде cs.

Итак, первый шаг в мире jass уже сделан.

3. Применение локальных переменных
Закрепим то, что узнали. Чтобы создать локальную переменную, нужно вставить команду:

local <тип переменной>  <имя переменной>

Типы переменных – это строки. В некоторых случаях они совпадают с названием переменных в Редакторе переменных. Например unit, integer, real, string. Но иногда не совпадают как в случае с effect, который означает спецэффект.
Если вы не знаете, как называется такой-то тип переменных в jass, как это узнать? Можно использовать такой способ: создаете глобальную переменную нужного типа. Затем используете команду редактора Файл->Экспорт кода – сохраняете код сценария в файл. Затем смотрите содержание этого файла при помощи блокнота. Находите пункт
//* Global Variables
- он будет в самом верху.
Там перечислены все глобальные переменные в сценарии и рядом записан их тип.
Можно еще использовать такой способ: сделать какую-то ошибку в jass-коде, после чего игра отключить и подключить триггер с ошибкой (disable/enable). Игра выдаст ошибку и в окне с ошибкой будет виден код сценария. Так что там же можно найти раздел Globals.

Примечания: попробуйте посмотреть таким способом как в jass называются типы «тип юнита», «тип предмета», «способность» или «бафф». И обнаружите, что они преобразуются к... типу integer. Тут нет никакой ошибки. Эти типы переменных существуют только в редакторе триггеров. В jass они представляют собой тип integer. Типы юнитов, способности и т.п. кодируются числами. Хотя, у них имеется и другой способ кодирования – специальными константами ‘hfoo’- означает тип юнита footman. Подобные названия объектов можно узнать переводя триггеры в jass или в редакторе объектов (если поставите галочку «Вид->Показывать названия переменных»

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

Можно создавать массивы локальных переменных при помощи команды

local <тип переменной>  array <имя переменной>

Например, массив юнитов:

local unit array u

Обращение к элементам этого массива такое же как в триггерах:

Set u[1] = …

- записываем в первый элемент массива такое-то значение. И т.п.

При создании переменных, можно сразу же записывать в них какое-то значение.

local integer i =1

создаст переменную i и присвоит ей значение 1.

Предупреждение. Когда мы создаем глобальную числовую переменную, то ее значение автоматически приравнивается к нулю. Но для локальных переменных это не так. При создании локальной переменной ее значение не определено. Попытка их использования до того, как вы поместите в них какое-либо значение приводит к сбою. Пример ошибочного кода:

local integer i
set i = i +1

Чтобы не было ошибки, сначала прировняйте значение переменной i к нулю.

local integer i = 0
set i = i +1

Локальные переменные очень хорошо решают проблему хранения данных при отсроченных действиях, как мы разобрали в прошлом примере. Существует способ решения задач при помощи локальных переменных: способ движения от частного к общему. Алгоритм такой:
1. Создай обычный не универсальный триггер, который решает задачу для одного запуска.
2. Добавь локальные переменные и запиши в них все, что должно сохраниться во время паузы.
3. Помести данные обратно в глобальные переменные и делай нужные действия.

Локальные переменные выступают как хранилища на время пауз в триггере, глобальные переменные нужны для каких-то мгновенных действий. См. пример sample locs. Мы не можем угадать, что будет храниться в переменной unit в какой-то момент времени. Ее значение будет постоянно меняться в зависимости от игровых событий. Мы не можем помещать в эту переменную ДАННЫЕ ДЛЯ СОХРАНЕНИЯ, но можем использовать ее для мгновенных действий. В нашем случае работает только одно триггерное заклинание, но мы могли бы использовать ту же самую переменную unit для сотни точно таких же заклинаний.

Если хотите обойтись одними локальными переменными, то нужно либо весь триггер переводить в jass, либо переводить в cs те строки, где имеются ссылки на эти переменные.

Чтобы посмотреть, как выглядит та или иная команда в jass, можно использовать такой прием: создаем новый пустой триггер, создаем внутри него нужную команду и переводим триггер в текст. Затем этот текст можно будет вставить в cs один в один. Так что нет необходимости запоминать все команды на jass.

Итак, Читатель, ты уже достаточно узнал, чтобы создать свой собственный jass код. Правда, есть определенные тонкости который тебе нужно узнать. Во-первых, если в jass допущена ошибка, то при попытке сохранить карту или запустить ее будут выданы ошибки. При этом триггер тут же отключится и ты не сможешь его включить, пока не исправишь ошибки.
А теперь представь, что на данный триггер ссылается еще один. Что произойдет? Триггер отключился из-за ошибки и все триггерные команды, которые ссылались на него тоже отключатся.
Еще одна ситуация. Допустим, имеется триггер на jass или с cs, в котором идет ссылка на глобальную переменную unit. Затем, мы берем и меняем название глобальной переменной на unit2. Во всех нормальных триггерных действиях название старой переменной на новую произойдет автоматически. Но не в jass-коде! Там все названия останутся старыми. Т.е. нам нужно вручную менять везде udg_unit на udg_unit2, иначе будет выдана ошибка.

Поэтому при создании jass кода надо всегда соблюдать осторожность. Тем более что ошибки в jass не всегда бывают безобидными. Некоторые из них приводят к тому, что редактор вылетает без сохранения карты. Так что когда работаете с jass-кодом – ЧАЩЕ СОХРАНЯЙТЕСЬ!

Итак, Читатель, если есть время и желание, поработай над реализацией какой-нибудь из задач на jass. К примеру:
1. Заклинание разговор: когда применяешь его на юнит, на две секунды над ним появляется фраза плавающего текста «Привет».
2. Заклинание banish (триггерный аналог): на 20 секунд юниту-цели дается способность ethereal (дух).

4. Условия, циклы в jass
Рассмотрим такой пример: имеется фрагмент триггерного действия

For each (Integer i) from 1 to 10, do (Actions)
    Цикл
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            Условие
                i равно 1
            Действие
                Set s = (s + 2)
            Иначе
                Set s = (s + 1)

Цикл по i от 1 до 10 и условие внутри цикла. Во что превратится это действие, когда мы переведем его в jass? Создай в редакторе такой триггер и проверь.
Действие превратится в следующий фрагмент

set udg_i = 1
    loop
        exitwhen udg_i > 10
        if ( Trig_____________________________________001_Func001Func001C() ) then
            set udg_s = ( udg_s + 2 )
        else
            set udg_s = ( udg_s + 1 )
        endif
        set udg_i = udg_i + 1
    endloop

Думаю, что пока не очень понятно, что здесь за что отвечает. Начнем с оператора if. Очевидно, он превратился в строки:

if ( Trig_____________________________________001_Func001Func001C() ) then
            set udg_s = ( udg_s + 2 )
        else
            set udg_s = ( udg_s + 1 )
        endif

Все что ниже первой строки - понятно, но почему вместо нормального условия в первой строке стоит "( Trig_____________________________________001_Func001Func001C() )"? Дело в том, что редактор триггеров довольно глупо переводит условия из триггеров или триггерных действий. После такого перевода часто приходится исправлять и оптимизировать код. В нашем случае, редактор создал специальную функцию с именем Trig_____________________________________001_Func001Func001C() для того, чтобы проверить нужное нам условие, что i=1. Эту функцию ты можешь увидеть вверху триггера:

Код:
function Trig_____________________________________001_Func001Func001C takes nothing returns boolean
    if ( not ( udg_i == 1 ) ) then
        return false
    endif
    return true
endfunction

Пока не будем вдаваться в то, что это за функция и что она делает. Самое главное - эта функция возвращает значение true (истина) если i=1, или ложь, если i не равно 1. Возникает вопрос: что же, при каждом применении оператора if нам придется создавать какую-то функцию? Ничего подобного - можно обойтись и без нее! Стираем эту ненужную функцию, а в строчку вносим изменения:

Код:
if (udg_i == 1) then

И все. остальное оставляем неизменным. У нас получится фрагмент кода:

Код:
    set udg_i = 1
    loop
        exitwhen udg_i > 10
        if (udg_i == 1) then
            set udg_s = ( udg_s + 2 )
        else
            set udg_s = ( udg_s + 1 )
        endif
        set udg_i = udg_i + 1
    endloop

Теперь ты знаешь, что такое оптимизация :).
Но остается открытым вопрос: что же такое мы вставили в условие оператора if. Это просто проверка, равна ли переменная i единице. В jass есть специальные значки для проверки условий равенства или неравенства:
== (два знака равно) переводится как равно
!= переводится как не равно
< меньше
> больше
<= меньше или равно
>= больше или равно.

Т.е. если мы хотим записать условие i[.b] не равно 10, то оно будет выглядеть
[ b ]i!=10
Теперь ты можешь сам разобраться с условным оператором в jass:

Код:
        if (udg_i == 1) then
            set udg_s = ( udg_s + 2 )
        else
            set udg_s = ( udg_s + 1 )
        endif

Переводится как "Если i=1 то делать то-то иначе делать то-то".

Хорошо, с условным оператором разобрались. А как насчет циклов?
К оператору цикла относятся следующие строки:

Код:
    set udg_i = 1
    loop
        exitwhen udg_i > 10
        ...
        set udg_i = udg_i + 1
    endloop

... - это могут быть любые действия, которые происходят внутри цикла
Итак, перед началом цикла переменной i присваивается значение 1 - это начальное значение для нашего цикла.
loop - ключевое слово, означающее начало цикла
endloop - конец цикла
Т.е. действия между loop и endloop будут повторяться. Но сколько раз они должны повторяться? Вообще говоря, 10. Но в jass все циклы устроены более универсально, чем в триггерах. Тут циклы повторяются не ОПРЕДЕЛЕННОЕ ЧИСЛО РАЗ, а ДО ТЕХ ПОР, ПОКА НЕ БУДЕТ ВЫПОЛНЕНО ТАКОЕ-ТО УСЛОВИЕ. За проверку этого условия отвечает строка:
exitwhen <УСЛОВИЕ> - переводится как выйти из цикла, когда выполнено УСЛОВИЕ.
exitwhen udg_i > 10 - переводится как выйти из цикла, когда переменная i станет больше 10.
Мы могли бы к примеру написать условие
exitwhen udg_i == 10- выйти из цикла, когда iстанет равно 10. Тогда в цикле будет выполнено на одно действие меньше.

Итак, вся наша структура

Код:
    set udg_i = 1
    loop
        exitwhen udg_i > 10
        ...
        set udg_i = udg_i + 1
    endloop

имеет следующий смысл. Переменная i приравнивается к 1. На каждом витке цикла проверяется, не стала ли переменная i больше 10. Если не стала, производится какое-то действие и затем переменная i увеличивается на 1. И так до тех пор, пока не будет выполнено условие окончания цикла.

Итоги:
1. Условный оператор при переводе триггера в jass не очень удобен, т.к. его приходится оптимизировать.
2. Оператор цикла в jass более универсальный, т.к. действие производится не фиксированное число раз, а до тех пор, пока не выполнится условие. Кроме того, переменную цикла в триггерах можно увеличивать только на 1, а в jass - ее можно изменять произвольным образом.

Читатель, про циклы можно сказать еще следующее. Если ты попробуешь перевести в текст действие

Код:
For each (Integer A) from 1 to 10, do (Actions)
   ...

(т.е. воспользуешься одним из циклов с Integer A или Integer B, то на выходе получишь:

Код:
    set bj_forLoopAIndex = 1
    set bj_forLoopAIndexEnd = 10
    loop
        exitwhen bj_forLoopAIndex > bj_forLoopAIndexEnd
        ...
        set bj_forLoopAIndex = bj_forLoopAIndex + 1
    endloop

Что такое bj_forLoopAIndex и bj_forLoopAIndexEnd? Оказывается это специальные глобальные переменные, которые используются для проверки условий окончания такого вида цикла. Обычные переменные типа integer. Проверь сам - действие в цикле будет выполнено ровно 10 раз.
Отсюда вывод: хочешь ты того или нет, но редактор всегда вставляет в твой сценарий 4 специальных глобальных переменных:
set bj_forLoopAIndex
set bj_forLoopAIndexEnd
set bj_forLoopBIndex
set bj_forLoopBIndexEnd

В принципе они предназначены для циклов, а на самом деле при помощи jass в них можно записывать все что угодно. Кстати, подобных переменных на самом деле довольно много.
5. Функции в jass
Что такое функция? Функция, это фрагмент кода, в который можно передавать параметры, который может возвращать один параметр и производить определенные действия. Не очень понятно, но вспомни в предыдущем сообщении, как для определения выполняется ли условие i=1 создавалась специальная функция.

Кроме того, ты наверное замечал, что при переводе триггера в jass, в итоге создаются несколько функций. Обычно, функция для действий триггера, для условия, а также функция для присоединения события. Но об этом я еще напишу позже.

Самое главное, что функции можно использовать, чтобы сделать код более удобным и коротким. Синтаксис функции выглядит следующим образом:
function <ИМЯ ФУНКЦИИ> takes <ПЕРЕЧЕНЬ ПАРАМЕТРОВ, которые функция БЕРЕТ> returns <тип параметра,

Код:
который функция ВОЗВРАЩАЕТ>
...
<ПЕРЕЧЕНЬ ДЕЙСТВИЙ ФУНКЦИИ>
...
endfunction

Все это может быть выглядит страшно, но мы разберем на примерах. Самый простой вид функций - та которая ничего не берет и ничего не возвращает. К примеру, создадим функцию с именем property, которая при каждом ее запуске дает игроку1 1000 золотых.

Такая функция будет выглядеть следующим образом:

Код:
function property takes nothing returns nothing
    call AdjustPlayerStateBJ( 1000, Player(0), PLAYER_STATE_RESOURCE_GOLD )
endfunction

Несколько замечаний. Во-первых действие добавления денег взято путем перевода такого действия из триггеров в jass. Я сказал, что деньги даются игроку1, а в коде написано Player(0) - это потому что в jass игроки начинают нумероваться с нуля. Т.е. 0 - номер первого игрока, 1 - второго и т.д. PLAYER_STATE_RESOURCE_GOLD - кодовое слово, которое означает, что прибавляется именно золото, а не скажем лес.
Вместо перечня параметров, которые берутся и вместо типа параметра, который возвращается функцией стоит слово nothing - на английском означает ничего. Т.е. функция ничего не берет и ничего не возвращает. Она просто делает действие - добавляет деньги игроку1.

Для того, чтобы вызвать эту функцию на исполнение, достаточно написать команду

Код:
call property()

() - это скобки, в которых указывается список параметров для функции, но в нашем случае он пуст.
Ты можешь вставить эту команду в триггеры (в виде custom script) или в jass. Когда триггер запущен и очередь дойдет до этой команды, будет запущена функция и выполнены все ее действия. И при каждом запуске игрок1 будет получать 1000 золота.
Конечно, функция состоящая из одного действия не имеет смысла, но действий может быть и больше. Если в триггерах или коде имеются часто повторяющиеся фрагменты, то имеет смысл создать функцию и заменять фрагмент на вызов функции.

Пока что я не рассказал, а куда нужно вставлять текст функции. Это нельзя делать куда попало. Нельзя вставлять функцию внутрь другой функции. Функцию можно вставить в пустое пространство между другими функциями в триггере или в специально отведенное место (второй вариант предпочтительнее, позже расскажу почему).

Путь к этому специальному месту: открой редактор триггеров. Слева в окне найди дерево триггеров (список папок и самих триггеров). Самая высокая позиция этого дерева - иконка карты. Щелкни на нее. Справа откроется окно "Нестандартный код". Вот в него и нужно вставлять функции.

Вставь в это окно текст функции property. Затем сделай триггер с событием Map Initialization и действием: cs call property()
Запусти сценарий и проверь, что функция действительно работает.

Итак, первая и самая простая функция сделана. Но функции очень удобны тем, что они могут принимать определенные параметры, которые влияют на действие функции. К примеру, модернизируем функцию property, чтобы она давала 1000 золота не первому игроку, а игроку, которого мы укажем в параметре. Т.е. в функцию мы будем передавать параметр номер игрока (типа integer). В итоге, функция будет выглядеть так

Код:
function property takes integer n returns nothing
    call AdjustPlayerStateBJ( 1000, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )
endfunction

Смотри, в первой строке вместо takes nothing теперь стоит takes integer n. Это означает, что функция имеет 1 параметр типа integer. Чтобы запустить функцию с параметром, нужно будет вставить строку

Код:
call property(<какое-то число>)

И это самое число будет передано в функцию при запуске и записано в локальную переменную n. Вот такой фокус. Мы можем вводить номер игрока, которому мы хотим дать 1000 золота и этот номер будет передан в функцию. А для того, чтобы дать 1000 золота игроку с этим номером, мы переделали вторю строку:

Код:
call AdjustPlayerStateBJ( 1000, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )

Т.е. дать 1000 золоту игроку с индексом n-1. Я нарочно поставил не n, а n-1, т.к. мы привыкли нумеровать игроков с 1, а в jass нумерация идет с 0.
Итак, если у нас имеется указанная функция, то чтобы дать игроку1 1000 золота, мы можем набрать команду

Код:
call property(1)

Еще несколько слов о параметрах. Во-первых, параметров может быть любое число и они могут быть любого типа. Если параметров более одного, то они идут перечислением через запятую. Например, вот модернизированная функция, в которую мы в качестве параметров передаем не только номер игрока, но и количество золота.

Код:
function property takes integer n, integer gold returns nothing
    call AdjustPlayerStateBJ( gold, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )
endfunction

Но сколько параметров у функции, столько должно передаваться и при ее вызове. Т.е. для вызова нужно использовать строку

Код:
call property(1,1000)

Во-вторых, параметры, как я и говорил, передаются в локальные переменные. Но в любой функции могут быть и другие локальные переменные. Просто нужно объявить их в начале функции

Код:
function property takes integer n, integer gold returns nothing
    local real r
    call AdjustPlayerStateBJ( gold, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )
endfunction

Ну и еще одно замечание. В большинстве языков программирования имеется разделение понятий процедура и функция. Процедура - фактически тоже самое что и функция, но она ничего не возвращает в качестве параметра. Все примеры, рассмотренные нами выше - брали или не брали параметры, но все равно ничего не возвращали. Т.е. грамотнее было бы назвать их процедурами.

И переходим к последнему - самому общему варианту, когда функция что-то возвращает. Раньше мы везде писали returns nothing, но если мы хотим, чтобы функция что-то вернула, нужно указать какой-нибудь тип. Скажем returns integer (возвратить параметр типа integer). Например, если мы хотим создать функцию, которая будет возвращать нам сумму чисел от 1 до n, где n - параметр, передаваемый в функцию. Функция выглядит так:

Код:
function summa takes integer n returns integer
local integer i
local integer s
    set i = 1
    set s = 0
    loop
        exitwhen i > n
        set s = s + i
        set i = i + 1
    endloop
return s
endfunction

Попытайся разобраться с действием этой функции. Внутри есть цикл, который нужен для нахождения суммы 1+2+...+n. Далее есть ключевое слово return - это одновременно команда прекратить выполнение функции, и способ заставить функцию вернуть значение.
return s означает, что функция вернет значение из переменной s, т.е. искомую сумму.

Как же обратиться к такой функции для ее вызова? Функции, возвращающие определенное значение, вызываются по-особому. Их можно использовать в каких-то выражениях или равенствах. К примеру, если у тебя есть глобальная переменная i, ты можешь вызвать функцию summa следующим образом:

Код:
cs set udg_i = summa(10)

И тогда РЕЗУЛЬТАТ ФУНКЦИИ, то что она возвращает - сумма, будет помещен в переменную i. Или можно сделать так:

Код:
cs set udg_i = summa(9+1)+2

Тогда в переменную i будет помещена сумма чисел от 1 до 10 плюс еще 2 единицы.

В этом и состоит смысл функций, с возвращаемым значением.
Примечания:
1. Тип данных, возвращаемых функцией должен совпадать с переменной, куда мы пишем это значение. integer-integer или real-real.
2. Вообще говоря, даже если функция возвращает значение, ее можно запустить методом

Код:
call <Функция> (<параметры>)

Но понятное дело, значение функции, которое оно возвращает, не будет никуда записано.
3. Команда return представляет определенный интерес сама по себе. Если ты проверишь, во что превратится команда skip remaining actions в jass - она превратится в return. Т.е. это команда, которая прерывает исполнение функции.
4. Допускается запуск одной функции из другой. К примеру, в функцию summa можно вставить строчку

Код:
call property(1,1000)

Но может возникнуть ошибка. Обращаться можно только к функции, которая записана выше данной (т.е. создана раньше). Т.е. если функция property будет ниже чем summa - то обращаться к property из summa нельзя.

Кстати, код в специальном месте для триггерных функций расположен ВЫШЕ чем код всех игровых триггеров. Поэтому к функциям записанным здесь можно обращаться из любого триггера.
5. Если внимательно приглядеться, то кроме функций, определенных пользователем (т.е. тобой) существуют еще и встроенные функции. К примеру, глянь команду

Код:
call AdjustPlayerStateBJ( gold, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )

Слово call тебе ни о чем не говорит? :). AdjustPlayerStateBJ - это встроенная функция с тремя параметрами. Список всех таких встроенных функций имеется в MPQ архивах. Так что получается у нас, что все триггеры устроены так, что одни функции ссылаются на другие, те на третьи и т.д. :).

На этом о функциях пока все.

В качестве примера по тому, что мы пока прошли, предлагаю изучить сценарий AR, в котором реализован достаточно простой огнемет на jass с использованием массивов, циклов и функций.
6. Устройство триггера с точки зрения jass
Теперь, когда ты уже изучил функции, остановимся подробнее на устройстве триггера. Я уже говорил, что при переводе триггера в текст, он преобразуется в несколько функций. Но что же такое триггер? Просто несколько jass функций? Не совсем так. Правильнее сказать триггер, все его события, условия, действия СОЗДАЮТСЯ при помощи jass-функций. Функции сами по себе, а триггер как бы объединяет их в единую структуру.

Давай рассмотрим этот процесс. Возьмем какой-нибудь триггер:

Код:
Триггер sample
События
    Every 5.00 seconds of game time
Условия
    ((Triggering unit) is Здание равно Да
    (Ability being cast) равно «Гальванизация»
Действия
    Wait 2.00 game-time seconds
    Play (no unit)'s stand animation

Вообще говоря, бессмысленный триггер, но важно не это. Во что он превратится, когда мы переведем его в jass? В следующий код:

Код:
function Trig_sample_Conditions takes nothing returns boolean
    if ( not ( IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE) == true ) ) then
        return false
    endif
    if ( not ( GetSpellAbilityId() == 'AUan' ) ) then
        return false
    endif
    return true
endfunction

function Trig_sample_Actions takes nothing returns nothing
    call PolledWait( 2 )
    call SetUnitAnimation( null, "stand" )
endfunction

//===========================================================================
function InitTrig_sample takes nothing returns nothing
    set gg_trg_sample = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_sample, 5.00 )
    call TriggerAddCondition( gg_trg_sample, Condition( function Trig_sample_Conditions ) )
    call TriggerAddAction( gg_trg_sample, function Trig_sample_Actions )
endfunction

Первая функция - это то во что превратились условия триггера. Можешь проверить, что эта функция возвращает значение ИСТИНА, если условия исходного триггера будут выполняться, и ложь в противном случае. Вторая функция - действия триггера. Об этом и так можно догадаться, если глянуть на названия триггеров "Trig_sample" - т.е. триггер с названием sample "_Conditions" - условия, "_Actions" - действия.
Что касается третьей функции, то у нее свое особое назначение. Посмотри на название "InitTrig_sample". Приставка Init - напоминает слово Initialization, т.е. это что-то связанное с загрузкой карты. Эта функция запускается при инициализации карты. Ее назначение - собрать наш триггер воедино, объединив события, условия и действия. Сейчас посмотрим на строки:

Код:
set gg_trg_sample = CreateTrigger(  )

Что-то к чему-то прировняли... gg_trg_sample - это разновидность глобальной переменной типа ТРИГГЕР, которая будет отвечать за хранение нашего триггера в памяти компьютера во время игры. Такие переменные автоматически создаются, когда ты создаешь в редакторе новый триггер.
В начале игры все такие переменные пустые. Действие set gg_trg_sample = CreateTrigger( ) приводит к тому, что в игре создается НОВЫЙ ТРИГГЕР - настоящий триггер, который до этого не существовал. В нем пока нет ни условий, ни событий, ни действий. При этом переменная gg_trg_sample будет ссылаться на этот триггер.
Далее идет строка

Код:
call TriggerRegisterTimerEventPeriodic( gg_trg_sample, 5.00 )

Эта команда приводит к тому, что к нашему пустому триггеру добавляется событие "Every 5.00 seconds of game time". Т.е. у нашего триггера уже есть событие. Для добавления любого события есть своя команда, которую ты можешь посмотреть при переводе триггера в текст. Исключение Map Initialization, но это отдельный разговор.
Далее

Код:
call TriggerAddCondition( gg_trg_sample, Condition( function Trig_sample_Conditions ) )

Это специальная команда, которая добавляет нашему триггеру условие. Заметь, в качестве аргументов мы указываем триггер и функцию, в которой записано условие триггера.
Далее

Код:
call TriggerAddAction( gg_trg_sample, function Trig_sample_Actions )

По аналогии с предыдущей командой - эта добавляет в триггер действия.

Когда при загрузке карты будет автоматически выполнена функция InitTrig_sample, только тогда объект триггер будет загружен в память компьютера и начнет работать. Вот такая механика.

Частенько структура триггера может быть сложнее, чем в описанном примере. Например, применение условного оператора (в триггерном виде), действий типа Pick every unit and do приводит к тому, что в триггере создаются новые функции - события и действия. Бывает, что триггер так загромождается ими, что при переводе в jass трудно разобраться, что к чему. Иногда код можно оптимизировать и сделать более простым.

Еще такой интересный вопрос: если мы создаем триггеры при загрузке карты, не можем ли мы воспользоваться теми же командами, чтобы создавать триггер ПРЯМО ВО ВРЕМЯ игры. Ответ положительный - можем. И иногда это бывает очень удобно.

Читатель, если ты прочитал все что было до этого и разобрался в этом, то считай, что ты уже не новичок. Хотя еще не хватает практики собственной работы. И теперь мы сможем приступить к изучению продвинутого jass, который значительно расширяет возможности создания сценариев.
7. Динамическое создание триггера
Читатель, давай рассмотрим пример, который покажет некоторые возможности jass, недоступные в редакторе триггеров.

Вот например, известно, что определить, когда юнит получает повреждения можно лишь при событии Unit takes damage, которое можно создать лишь для конкретного юнита. Жуть как неудобно. А если возникает задача по ходу игры узнать, когда ударили юнит и сколько повреждений нанесли? Попробуем решить эту задачу исходя из того, что мы узнали о jass. Предлагаю скачать пример, который я выслал в данном сообщении и посмотреть его устройство.

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

Если мы можем динамически создавать новые триггеры прямо по ходу игры, почему бы не сделать так, что когда юнит применяет заклинание защиты, мы СОЗДАДИМ триггер, который будет отлавливать, повреждения. Делается это достаточно просто:
делаем триггер Magic Shield, который сработает при произнесении заклинания брони. Для юнита создается триггер с событием unit takes damage где в качестве проверяемого юнита выступает кастер. В качестве действия для нового триггера, нужно указать какую-нибудь функцию. Я использовал функцию "Adv_Trig_Actions", которая записана в специальном месте для пользовательских функций. Эта функция определяет действия, которые произойдут, когда юнит получит повреждения.

Правда имеются с триггером и некоторые сложности. Во-первых, где-то надо хранить информацию о том, что у такого-то юнита имеется такой-то запас брони. Локальные переменные тут не годятся, т.к. информацию мы сохраняем в одном триггере, а действие реализовано в другом. Поэтому в качестве хранилища информации пришлось использовать массивы. При применении заклинания, в массив MS_units заносится кастер, в массив MS_power заносится количество повреждений, которые может поглотить броня, в массив MS_trigs заносится триггер, созданный для отлавливания повреждений кастера. В переменной MS_num – общее число юнитов с данной защитой. Т.е. к примеру, произнес юнит заклинание брони. Мы проверяем. Не ли такого уже в массиве. Если нет – увеличиваем MS_num на 1, а затем заносим данные об этом новом юните в элементы массивов MS_units[MS_num], MS_power[MS_num] , MS_trigs[MS_num] . Если же юнит уже находится внутри массива под номером N, это значит он уже применил ранее заклинание щита. Тогда нам не нужно заново создавать для него триггер, отлавливающий повреждения. Мы просто обновим уровень защиты в переменной MS_power[N] .

В этом смыл действий основного триггера, который происходит во время применения заклинания защиты. А как насчет дополнительного триггера, создаваемого по ходу игры?

Прежде всего, когда мы отловили дополнительным триггером нанесенные повреждения юниту, нужно найти номер этого юнита в массиве. Скажем, его номер N. Далее, мы сопоставляем полученные юнитом повреждения и уровень защиты юнита MS_power[N] . Если защиты больше – мы просто уменьшаем уровень защиты на количество повреждений, а затем восстанавливаем юниту потерянную жизнь. Если же уровень защиты меньше количества повреждений, то мы должны восстановить юниту число повреждений, равное остатку защиты, после чего удалить триггер MS_trigs[N] .
Чтобы удалить данные из N-того элемента массива, мы просто заменяем значение N-того элемента на значения последнего элемента с номером MS_num. После чего уменьшаем MS_num на 1 – ведь элементов стало меньше.

Кстати, при каждом ударе по юниту с защитой, также появляется спецэффект.

Обрати внимание на действие

Код:
call DestroyTrigger(<ссылка на триггер>)

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

Вообще-то пример получился не очень простым. Для работы заклинания требуются 3 массива и еще одна переменная. При каждом запуске или ударе по юниту с броней происходит цикл, в котором проверяется, есть ли такой-то юнит в массиве... Неудобно. Но что делать – как напрямую сопоставить юниту какие-то значения? Вообще-то сопоставить можно. Есть custom value, но для нашего примера его явно недостаточно.

На самом деле есть на jass есть замечательный прием, который позволяет сопоставить любому игровому объекту какие-то значения. Но для того, чтобы узнать как, нам придется углубиться в jass. И тогда мы сможем этот пример значительно улучшить.
8. События с малым периодом
Читатель, предлагаю рассмотреть еще один пример. Напрямую он не связан с jass, но зато расширит твои познания в триггерных заклинаниях.
Иногда в триггерах возникает задача делать действия в течении очень малого периода времени. Проблема в том, что в вар3 действия типа wait работают крайне коряво. Минимальный период для этого действия 0.1 доля секунды. Иногда этого бывает недостаточно. К тому же действие wait ужасно не стыкуется с командами цикла.
Если ты сделаешь цикл такого типа:

Код:
Цикл от 1 до 100
   (какое-нибудь действие)
   wait game time (0.1)
конец цикла

По идее, цикл должен завершиться через 100*0.1=10 секунд, а на самом деле пройдет больше времени. Можешь проверить сам. Поэтому циклы + wait-ы оказываются непригодными для организации действий на малых периодах. А ведь эти действия ой как полезны.
Ну, раз wait-ы нам помочь не могут, остается надеяться на другой метод - использование события Time periodic, которое, к счастью, позволяет генерировать запуск триггера с периодом до 0.01 секунды. К сожалению, это событие не связано с каким-то конкретным объектом. Поэтому, если нам нужно организовать какое-нибудь триггерное заклинание, работающее для множества объектов, придется переходить к массивам (примерно как с триггерной защитой).

Итак, рассмотрим такую задачу: заклинание паладина Благодать срабатывает мгновенно. К юниту-цели не летит никакой снаряд и в настройках редактора объектов невозможно сделать так, чтобы заклинание работало как снаряд. Но мы попробуем сделать это.

Итак, при запуске заклинания-пустышки, должен создаваться юнит-снаряд, который начинает движение к цели. Снаряд будет двигаться не сам по себе - мы это будем делать при помощи триггеров. Причем скорость снаряда у нас будет такой, какую мы захотим сделать, не ограничиваясь пределами в игровых константах.
Идея состоит в том, чтобы при каждом использовании заклинания, мы будем заносить в массивы u и u2 юнит-снаряд и юнит-цель, а массивы u_level - уровень заклинания. Допустим, у нас в данный момент уже имеется n юнитов-снарядов, летящих к своей цели, тогда все данные про новый снаряд мы будем заносить под номером n+1. Т.е. u[n+1] , u2[n+1], u_level[n+1]. Далее, при запуске у каждого снаряда, переменную num увеличиваем на 1 (а когда снаряд долетит - будем уменьшать). Т.е. переменной num будет храниться общее число юнитов-снарядов в любой момент времени.

Далее, у нас будет триггер с событием

Код:
Every 0.05 seconds of game time

Мы не случайно взяли период 0.05. Именно такой период нужен, чтобы организовать плавное движение юнита. Ведь триггер выполнится 20 раз в секунду - как раз такова частота обновления информации человеческого глаза. Меньший период уже не требуется.
Действие этого триггера: делаем цикл от 1 до num по всем юнитам-снарядам. Для каждого юнита-снаряда определяем направление движения (угол между юнитом снарядом и целью), определяем расстояние до цели. Если расстояние до цели больше определенного числа, то производим перемещение юнита-снаряда в сторону юнита цели (при помощи полярных координат).
Если же расстояние до цели стало меньше какого-то значения, то нужно во-первых, уничтожить юнит-снаряд,во вторых, произвести нужные действия с целью (добавить жизнь - своим, отнять жизнь у умертвий).

Можно было сделать так: дать юниту снаряду заклинание Благодать (настоящее) и когда он долетит, заставить применить его на цели, а затем уже удалить снаряд. Но поскольку действия с целью вполне можно совершать при помощи триггеров, я и сделал их триггерно (добавить 200*уровень спела жизней своим или отнять 100*уровень спела жизней врагам).

Высылаю пример и предлагаю посмотреть, как он устроен.

Примечание:
действия по передвижению юнита-снаряда сделаны в виде функции на jass. Вообще говоря, эти действия можно было делать и при помощи обычных триггеров, но при этом возник бы очень неприятный эффект - утечка памяти. Что это такое, чем вызвано и как с этим бороться - напишу в следующий раз.

Примечание2: я не останавливаюсь подробно на примерах и предлагаю исследовать их самостоятельно. Предполагается, что Читатель уже знает и умеет использовать массивы, циклы и т.п. Во всяком случае, рассмотреть их применение можно в статьях по триггерам. Тем не менее, остановимся подробнее на так называемых полярных координатах. Они вызывают вопрос у многих картостроителей.
9. Полярные координаты (ликбез)
Все достаточно просто. Если у вас есть две точки A и B, координаты которых нам известны. Как вычислить координаты третьей точки C, находящейся на заданном расстоянии R от точки A в направлении к точке B? Чтобы было понятнее, нарисуйте себе на бумаге точки A, B, выберите какой-то отрезок R длинна которого меньше AB. Точка C – находится на пересечении отрезка AB и окружности, проведенной из точки A радиуса R. Теперь должно быть понятнее.

Итак, зачем нам может понадобиться искать точку C? Как в примере, рассмотренном выше. Юнит-цель движется из произвольной точки A в точку B. Каждые 0.05 секунды мы должны вычислить следующее положение юнита и переместить его на какое-то расстояние в направлении точки B. Для того чтобы вычислять позицию точки C используются полярные координаты.

Итак, что такое обычные координаты ты знаешь. Они задаются двумя координатами X и Y. Но есть еще один способ записать координаты точки. Нарисуй координатные оси, выбери произвольную точку A. Соедини точку A и начало координат O. Пуская длинна AO=r, а угол, который образует AO с началом координат – равен a. Тогда полярные координаты точки называется пара чисел (r, a). Т.е. полярные координаты задаются расстоянием точки до начала координат и углом. Это просто еще один способ задать координаты точки. Можно через (X,Y) можно через (r, a).

В war3 есть встроенные функции для вычисления полярных координат. Например, можно записать такое действие

Код:
Set p = Point with polar [offset ((Center of (Playable map area)) offset by 256.00 towards 50.00 degrees)]

p – переменная типа точка. После выполнения действия, в точке p будет точка, полученная из точки ЦЕНТР КАРТЫ (Center of (Playable map area)), путем перемещения последней на расстояние 256 под углом 50 градусов. Представили?

Полярные координаты очень удобны, если требуется организовать движение по кругу или движение по произвольной прямой. Например действие цикла

Код:
For each (i) from 1 to 10, do (Actions)
    Цикл Действия
        Set p = ((Center of (Playable map area)) offset by i*100 towards 50.00 degrees)
        <создать юнит в точке p>

Приведет к тому, что на расстоянии 100, 200, 300... -1000 от центра карты под углом 50 будет создано 10 юнитов.
Если же мы сделаем так:

Код:
For each (i) from 1 to 10, do (Actions)
    Цикл Действия
        Set p = ((Center of (Playable map area)) offset by 1000 towards 36*i degrees)
        <создать юнит в точке p>

То будет создано 10 юнитов, расположенных на окружности радиуса 1000. Один будет под углом 36, второй 2*36... последний под углом 10*36=360=0 градусов.

Вот что такое полярные координаты точки.

Впрочем, в игре функции для вычисления полярных координат устроены не самым лучшим образом, но об этом будет сказано ниже.

Пересылаю также сценарий paint, в котором демонстрируется, как можно при помощи полярных координат рисовать отрезки и окружности.
10. Оптимизация: утечки памяти
Читатель, некоторое представления на эту тему ты уже имеешь. Например, известный факт, что если не удалять созданные спецэффекты, то игра через некоторое время начнет сильно тормозить. Поэтому, даже если спецэффект мгновенного действия и через некоторое время уже не виден, его все равно нужно удалять. Почему так происходит? Потому что каждый спецэффект - это игровой объект. Когда мы создаем новый спецэффект, он попадает в память. Если его не удалять, то он останется в памяти до конца игры.
Аналогичная история с юнитами в ТД, Дотах или Аеонах. Умершие юниты должны быть удалены действием Remove Unit, чтобы не занимать место в памяти.

Оказывается, что такая же ситуация наблюдается и с остальными игровыми объектами. Предметы, декорации, регионы, точки, группы юнитов, плавающий текст, модификаторы видимости - все они с тем же успехом способны засорить память. Да, конечно объект типа точка занимает в памяти гораздо меньше места, чем юнит - в точку нужно записать только координаты X и Y, а в юнит - все его параметры. Но, вообще говоря, утечки в памяти склонны к накоплению. И постепенно игра становится все более тормознутой.

Показателен в этом смысле мой сценарий Air War - там где летает вертолет, управляемый стрелками. Передвижение вертолета реализовано примерно тем же методом, который рассматривался в прошлом сообщении, т.е. события с малым периодом. Но когда я выпустил первую версию, я понятия не имел о том, что нужно оптимизировать код и удалить утечки памяти.

Результат не заставил себя ждать. Через 2 минуты работы сценария, начиналось такое торможение, что дальнейшая игра не имела смысла. Я догадывался об утечках памяти, пытался сделать все проще и эффективнее. Удалось увеличить период игры с 2-х до 5 минут. Да и то, при условии, что играет 1 человек, а ведь чем больше - тем хуже. Разгадку утечки помог найти Alexey B.H.

Дело было в том, что для вычисления координат юнита я использовал стандартные операции worldedit с полярными координатами. А сделаны они, как оказалось, очень коряво. Каждый раз, когда ты используешь функцию point with polar offset, в игре создается новый объект типа точка. Обычно, даже не один, а два объекта. А теперь представь, что у нас работает периодический триггер, запускаемый 20 раз в секунду. И при каждом запуске, создаются новые объекты, которые не будут удаляться до конца игры. 40 точек в секунду, все равно что 80 параметров типа real. А если функцию полярных координат применять не один, а несколько раз в периодическом триггере (к примеру, чтобы вычислить положение камеры), да еще и вычислять координаты для 4-ех игроков - то... Получаем торможение игры.

Вариант устранения утечки подсказал Какодемон. К примеру, если мы хотим вычислить полярные координаты точки, полученной из точки p заданной положения юнита u при угле a равном углу между точкой p и какой-то точкой p2. Для этого можно воспользоваться действиями:

Код:
set p = GetUnitLoc(u)
set a = AngleBetweenPoints(p, p2)
call MoveLocation(p, GetLocationX(p) + 50 * CosBJ(a), GetLocationY(p) + 50 * SinBJ(a))

Теперь давай проанализируем. Сначала мы переменной p присваиваем положение юнита u. Следующим действием мы ПЕРЕМЕЩАЕМ точку p в другое место. А что это за место? Вообще, в чем суть команды MoveLocation?
Если бы мы написали

Код:
call MoveLocation(p, 10, 15)

то координаты точки p стали бы (10,15)
Если бы мы написали

Код:
call MoveLocation(p, GetLocationX(p) + 50, GetLocationY(p) + 60)

то точка p сместилась бы на вектор (50,60) относительно текущего положения.
А форма записи

Код:
call MoveLocation(p, GetLocationX(p) + r* CosBJ(a), GetLocationY(p) + r * SinBJ(a))

означает, что точка p сместится в точку, полученную пересечением окружности радиуса r и луча, проведенного из точки p под углом a. Т.е. это и есть по сути полярные координаты.
Если известно положение текущей точки (X,Y) и нужно найти координаты точки, полученной из текущей при смещении в направлении a на расстояние r – то ее координаты будут (X+r*cos(a), Y+r*sin(a)). Это все известные в математике факты.

Но в чем преимущество нового метода по сравнению со стандартной функцией вычисления полярных координат? Преимущество в том, что МЫ НЕ СОЗДАЕМ НОВУЮ ТОЧКУ, чтобы вычислить полярные координаты. Вместо этого мы ПЕРЕМЕЩАЕМ СУЩЕСТВУЮЩУЮ ТОЧКУ.

Есть конечно еще один нюанс. Обрати внимание на команду:

Код:
set p = GetUnitLoc(u)

Присваиваем точке p положение юнита u. Но переменная со ссылкой на объект и сам объект - это разные вещи. До этого действия, переменная p была пустой. Объект точка с положением юнита не существовал. После действия, переменная p начала ссылаться на какую-то точку. Какой вывод? Игра создала объект типа точка и сделала на него сслыку в переменной p. Т.е. даже когда ты используешь элементарную функцию - определит положение юнита, в игре создается объект. И он тоже засоряет память - и это тоже станет заметно в триггерах с малым периодом.
Чтобы окончательно избавиться от утечки, необходимо применить действие для удаления объекта типа точка из памяти. В триггерах ты такой команды не найдешь. В jass эта команда выглядит следующим образом:

Код:
call RemoveLocation(p)

Можешь посмотреть, как это сделано в примере, который я выслал в прошлый раз.

Ну, вроде с утечкой из-за точек разобрались. Но, увы, есть и другие виды утечек. Группы юнитов. Вообще-то есть переменные типа unit group. Это из той же серии. Переменная есть ссылка. А на что ссылается переменная unit group? На некий объект. Делаем выводы: объекты, на которые может ссылаться переменная unit group могут создаваться по ходу игры. И не только могут, но и создаются. И засоряют многострадальную память :).
Каждый раз, когда ты применяешь функцию pick every (unit in unit group) and do actions - ты в качестве unit group указываешь определенную функцию. Например, [units of type] или [units in range matching conditions]. Вот тут и скрывается зло. Именно из-за таких функций создаются объекты, которые будут торчать в памяти (кстати, во втором случае еще и точка может выплыть). И, как ты догадываешься, в триггерах с малым периодом - это означает торможение.
Так что если хочешь создавать, к примеру, огонь, который каждые 0.25 секунд наносит повреждения всем юнитам в такой-то области, нужно уметь бороться с утечками. А борьба будет просиходить по схеме:
1. Создай переменную, например ug типа группа.
2. Перед действием pick every unit нужно занести в переменную ug группу, которую мы будем использовать. Например: set ug = [units in range matching conditions]
3. Во время действия pick every unit в качсестве группы указываем переменную ug.
4. После действия pick every unit вставляем команду:

Код:
call DestroyGroup(udg_ug)

- уничтожить группу из глобальной переменной ug.

Итак, подведем итог: в большинстве сценариев, утечки могут возникать из-за
- юнитов (создаем много и не удаляем)
- спецэффектов
- функция для работы с точками
- функций для работы с группами
Могут быть и другие варианты, но они встречаются реже.
Утечки особенно опасны, для триггеров с малым периодом.

Читатель, если удалить все основные утечки, то для 99,99% сценариев больше ничего не нужно оптимизировать. Но, как оказалось, существуют и другие виды утечек. Например, куда девается локальная переменная после того, как триггер кончил исполнение? На самом деле они продолжают сидеть в памяти. И хотя занимают они очень мало, но на протяжении длинной игры, их может накопиться порядочно. Чтобы этого избежать, имеет смысл обнулять локальные переменные после окончания действия триггера (по крайней мере, переменные объектного типа).

Ты наверное замечал уже это в примере, который я присылал. В конце триггера идут строки типа

Код:
set i = 0
set r = 0
set s = ""

А как обнулять переменные со ссылками на юниты, точки и пр.? Есть способ. Для всех объектных переменных ноль это null.
Т.е. мы пишем

Код:
set p = null
set u = null

и т.д. И переменные обнуляются.

Есть еще одна интересная возможность увеличить ресурсы памяти для длинных сценариев. Для этого нужно в какой-нибудь триггер с событием Map Initialization добавить команду:

Код:
call DoNotSaveReplay()

- Эта команда заставит war3 не писать реплей игры. А значит, снизит ее загрузку.

Примечание. Мой друг картостроитель, прочитал про утечки и начал думать, что же с этим теперь делать? Глянул на свой проект и приуныл. Столько всего оптимизировать... Раньше для него не было проблемы утечек, а теперь пришлось думать, что с этим делать. А я думаю, может быть и не стоит так уж усердствовать? Во всяком случае не все сценарии требуют дотошно оптимизировать и удалять все утечки. В первую очередь, конечно, должны оптимизироваться сценарии для сетевой игры и сценарии, где есть триггеры с малым периодом. Остальные – надо смотреть по обстоятельствам. Если есть торможение, можно попробовать от него избавиться.
Вообщем, jass поможет вам решить проблемы, которые до его изучения не существовали. :)

Покончив с оптимизацией, мы наконец перейдем к одному из самых полезных аспектов jass.
Перейти ко второй части статьи

0

2

Полезные функции

Code

Код:
function DisplayTextForPlayer takes player p, string text returns nothing
  if GetLocalPlayer() == p then
   call DisplayTextToForce(GetPlayersAll(), text)
  endif
endfunction

Функция выводит сообщение для одного игрока. Время "жизни" сообщения определяется автоматически.

Code

Код:
function DisplayTextForPlayerTimed takes player p, string text, real time returns nothing
  if GetLocalPlayer() == p then
   call DisplayTimedTextToForce(GetPlayersAll(), time, text)
  endif
endfunction

Функция выводит сообщение для одного игрока. Время "жизни" сообщения определяется параметром time.

Code

Код:
function CreateOneUnit takes player p, integer id, location loc returns unit
  set bj_lastCreatedUnit = CreateUnitAtLocSaveLast(p, id, loc, bj_UNIT_FACING)
  return bj_lastCreatedUnit
endfunction

Функция вара для создания юнитов крайне неудобна, так как даже создавая 1 юнита она возвращает нам группу. Данная функция создаёт одного юнита в нужной нам точке, не требуя никаких лишних параметров.

Code

Код:
function PlayerColor takes integer i returns string
  if i == 1 then
   return "|CFFFF0000"
  elseif i == 2 then
   return "|CFF0000FF"
  elseif i == 3 then
   return "|CFF18E7BD"
  elseif i == 4 then
   return "|CFF520084"
  elseif i == 5 then
   return "|CFFFFFF00"
  elseif i == 6 then
   return "|CFFFF8A08"
  elseif i == 7 then
   return "|CFF18BE00"
  elseif i == 8 then
   return "|CFFE759AD"
  elseif i == 9 then
   return "|CFF949694"
  elseif i == 10 then
   return "|CFF7BBEF7"
  elseif i == 11 then
   return "|CFF086142"
  elseif i == 12 then
   return "|CFF4A2800"
  elseif i == 13 then
   return "|CFF383838"
  elseif i == 14 then
   return "|CFFFFF799"
  endif
  return ""
endfunction

Набор цветовых кодов вара для каждого игрока. Передаём в функцию номер игрока, она возвращает нам его цвет.

Code

Код:
function SetColorText takes player p, string text returns string
  return PlayerColor(GetConvertedPlayerId(p)) + text + "|r"
endfunction

Данная функция должна использоваться вместе с предыдущей. Она возвращает перекрашеную в цвет указанного игрока надпись.

Code

Код:
function PolarLocation takes location source, real dist, real angle returns location
  local real x = GetLocationX(source) + dist * Cos(angle * bj_DEGTORAD)
  local real y = GetLocationY(source) + dist * Sin(angle * bj_DEGTORAD)
  call MoveLocation(source, x, y)
  set x = 0.00
  set y = 0.00
  return source
endfunction

Об этой функции я уже говорил. Стандартная функция вычисления полярных координат в варе очень глючная (то есть создаёт огромное количество утечек). Данная функция позволяет этого избежать.

Code

Код:
function MoveUnitXYZ takes unit u, real x, real y, real z returns nothing
  local location l = Location(x, y)
  call SetUnitPositionLoc(u, l)
  call RemoveLocation(l)
  call SetUnitFlyHeight(u, z, 30000)
  set l = null
endfunction

Данная функция поможет при создании различных эффектов и т.д. Она премещает указанного юнита по всем трём осям координат. НО перемещаемый юнит обязательно должен иметь тип движения летающий! Иначе он не переместица по оси Z.

0

3

JASS – триггеры в виде кода.
Недавно я подумал, что уже довольно давно не писал статей и решил написать статью на тему триггеры в понимании JASS.

Собственно, вот то, что из этого вышло.

Итак, триггер. Вспомним, из чего он состоит.


    * События.
    * Условия.
    * Действия.

И начнём мы с общего анализа триггера в текстовом виде. Для примера создадим триггер с названием Sample. Добавим в него событие Игрок красный выигрывает (Игрок - Victory). Теперь – условие: Игрока красного контролирует человек (Player Controller Comparison). Ну и в качестве действия выведем на экран строку: «Вы выиграли!!!» (действие Игра – Text Message). Теперь у нас есть триггер. Переведя его в текст, мы получим это:
Code

Код:
function Trig_Sample_Conditions takes nothing returns boolean <br />      if ( not ( GetPlayerController(Player(0)) == MAP_CONTROL_USER ) ) then <br />          return false <br />      endif <br />      return true <br /> endfunction <p> function Trig_Sample_Actions takes nothing returns nothing <br />      call DisplayTextToForce( GetPlayersAll(), "Вы выиграли!!!" ) <br /> endfunction <p> //======================================================== <br /> function InitTrig_Sample takes nothing returns nothing <br />      set gg_trg_Sample = CreateTrigger(  ) <br />      call TriggerRegisterPlayerEventVictory( gg_trg_Sample, Player(0) ) <br />      call TriggerAddCondition( gg_trg_Sample, Condition( function Trig_Sample_Conditions ) ) <br />      call TriggerAddAction( gg_trg_Sample, function Trig_Sample_Actions ) <br /> endfunction

У нас есть три функции. Начать стоит с самой последней (InitTrig_Sample). Как видно, её название состоит из приставки InitTrig_ и названия триггера. InitTrig – инициализация триггера. В этой функции триггер регистрируется в игре. Это происходит в первой троке:
Code

Код:
set gg_trg_Sample = CreateTrigger(  )

gg_trg_Sample – это что то вроде глобальной переменной (ubg_), но используется она только для триггеров. То есть создаётся новый триггер, который записывается в переменную.
Следующая строка – регистрация события. Собственно, чтобы добавить событие, нужно вызвать соответствующую функцию (в нашем случае TriggerRegisterPlayerEventVictory) и передать в неё все параметры (gg_trg_Sample, Player(0)).

*Примечание.

Событие Map initialization – особый случай и о нём мы поговорим позднее.

Далее идёт добавление условия:
Code

Код:
call TriggerAddCondition( gg_trg_Sample, Condition( function Trig_Sample_Conditions ) )

Здесь принцип несколько другой, чем для добавления событий. Функция добавления условий одна (TriggerAddCondition). В неё мы передаём триггер и функцию с условиями. Теперь посмотрим на саму функцию с условиями.
Там всё чрезвычайно запутанно. Но ведь в этом и цель ВЕ – запутать всех и вся =). В конце концов весь этот код можно свести к одной строчке:
Code

Код:
return GetPlayerController(Player(0)) == MAP_CONTROL_USER

*Примечание.
Добавлять условия в функцию InitTrig_*** ВЕ будет только тогда, когда эти условия добавлены именно в блок с условиями триггера. Если же, например, мы использовали действие if / then / else, то функция с условиями создастся, но в функцию инициализации триггера она не добавится. В триггере можно создавать сколько угодно отдельных функций, которые не должны нигде указываться.
---------------------------------------------
С условиями разобрались. Посмотрим теперь на действие. Собственно, здесь всё аналогично условию (за исключением названия функции =). Так что объяснять всё подробно нет смысла.

Вот вкратце об устройстве триггеров. Теперь немного поговорим об оптимизации. Я уже немного говорил об этом (условие), но теперь стоит сказать зачем это вообще надо.
Ну, первый факт, это конечно удобство. Ведь если каждое условие будет создавать новую функцию, в которой будут тонны ненужных операторов… Всё это в конце концов приведёт к хронической головной боли. Далее. Лишняя нагрузка – лишний геморрой. Да и к тому же гораздо проще разобраться в триггере (постороннему человеку) когда он решит поподробней рассмотреть наработку =).
Но вот хуже всего в ВЕ реализованы циклы, которые перебирают всех юнитов в определённой области. Предположим, мы хотим убить всех юнитов на карте. Опущу подробности такого сложного триггера =) и приведу его сразу на JASS:
Code

Код:
function Trig_KillAllUnit_Func001A takes nothing returns nothing <br />      // Здесь действия цикла <br />      call KillUnit( GetEnumUnit() ) <br /> endfunction <p> function Trig_KillAllUnit_Actions takes nothing returns nothing <br />      // Здесь действия функции <br />      call ForGroupBJ( GetUnitsInRectAll(GetPlayableMapRect()), function Trig_KillAllUnit_Func001A ) <br /> endfunction

К сожалению, объединить данную конструкцию в одну функцию нельзя. Но можно сделать её несколь-ко красивее. Например, так:
Code

Код:
function KILL takes nothing returns nothing <br />      call KillUnit(GetEnumUnit()) <br /> endfunction <p> function Trig_KillAllUnit_Actions takes nothing returns nothing <br />      call ForGroupBJ( GetUnitsInRectAll(GetPlayableMapRect()), function KILL ) <br /> endfunction

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

Теперь отдельный разговор о событии Map Initialization. На самом деле, это даже не событие. Оно никак и нигде не регистрируется. Такие триггеры исполняются во время инициализации потоков игры. Соответственно серьёзные ошибки в таких триггерах могут привести к краху этих самых потоков.

И в конце ещё одно примечание.
Код из действия Custom Script остаётся неизменным при переводе с GUI на JASS.

Вот я и рассказал о триггерах. Если дойдут руки до следующей части о триггерах, опишу их создание динамически (что очень часто бывает чрезвычайно полезным). А пока на этом всё. Жду комментов.

0

4

Перейти к первой части статьи
Перейти ко второй части статьи
11. RETURN BUG (RB)
Прежде всего, нужно рассказать немного о том, что собой представляет компьютерная память. Читатель, может быть ты и так это знаешь, но на всякий обрисую картину. Все без исключения игровые объекты - юниты, строки, числа - все это находится в компьютерной памяти. Память представляет собой просто последовательность идущих подряд ячеек, в каждую из которых записано определенное число. При помощи чисел можно закодировать все - и юниты и строки. Разные объекты занимают разное количество ячеек. Ведь чтобы записать объект юнит, нужно записать целую кучу информации о всех его параметрах. Это гораздо больше информации, чем то, которое несет какое-то значение типа integer. Вообще говоря разработчики war3 вряд ли хотели, чтобы картостроители могли работать с памятью, но они допустили при разработке один важный баг, которым научились пользоваться спецы по jass. Баг не позволяет менять содержимое ячейки памяти - но зато он дает возможность для любого игрового объекта найти номер ячейки памяти, в которую он записан, а также по номеру ячейки найти ссылку на объект.

К примеру, рассмотрим функцию

Код:
function FindHandle takes rect reg returns integer
    return reg
    return 0
endfunction

Что она по идее делает? В функцию передается параметр типа reg типа регион. Функция должна возвращать параметр типа integer. Вроде бы все нормально. Но почему-то присутствует два оператора return... Более того, первый из них говорит, что функция должна вернуть отнюдь не целочисленное значение, а наш регион reg.
Фишка в том, что war3 проверяет правильность функций по последнему оператору return. В этом последнем операторе записано:

Код:
return 0

Если бы не было этой записи, war3 выдал бы ошибку - т.к. наблюдается несоответствие типов. Должны вернуть integer, а возвращаем rect. Но у нас два оператора return. war3 смотрит - вроде все нормально, ошибок нет. 0 -ведь целочисленное число, значит его можно вернуть. И невдомек ему бедному, что хитрый jass-ер уже поставил до этого еще один return. И вот, возникает интересная задача - игра должна вернуть регион reg, как целочисленное число. По идее это бессмысленно, но тут и срабатывает return bug. Результатом работы функции будет число, равное адресу ячейки памяти, в которой содержится информация по региону reg.

Это первая часть бага. Есть и другая. Рассмотрим функцию:

Код:
function FindRegion takes integer i returns rect
    return i
    return null
endfunction

Все аналогично. Функция должна вернуть регион, а вместо этого мы передаем ей параметр типа integer. Результатом будет то, что игра вернет регион, записанный по адресу i. Если конечно по этому адресу действительно записан регион. Впрочем, если не записан, это не будет ошибкой. Ошибка может возникнуть, если попробовать обратиться к недоступному участку памяти. Но это произойдет, разве что если ты будешь экспериментировать с памятью war3.

Итак, у нас уже есть две функции, очень хорошо дополняющие одна другую. Создавать такие функции - очень просто. Так что для любого объекта в игре можно найти ячейку номер его ячейки в памяти.

Какое может быть применение у return bug (RB)? Ну например, для наших функций. Зачем нам может понадобиться узнавать номера для регионов? Я этому сходу нашел следующее применение. В редакторе worldedit, к сожалению, невозможно простым и быстрым способом занести созданные регионы в массив. А ведь иногда бывает нужно. Скажем, на твоей карте несколько сотен регионов, в которых должны появиться одни и те же монстры и отправиться в патруль в следующую точку. Проще всего делать это через массивы с циклами. Но заносить вручную сотни регионов в массив - тоже приятного мало.
А я сделал предположение, что регионы, которые мы создаем в редакторе, занимают соседние ячейки памяти. Т.е. если создали мы первый регион - он попал в ячейку X, тогда следующий регион попадет в ячейку X+1 и т.д. Т.е. если мы знаем номер первого региона и остальные регионы создавали в определенном порядке, мы можем при помощи RB найти все остальные регионы и занести их в массив на автомате.
Итак, сначала при помощи функции FindHandle мы находим номер первого региона, затем при помощи функции FindRegion в цикле заносим все остальные регионы в массив. Элегантное решение нудной задачи :). Рекомендую посмотреть пример return bug, в котором реализованы все функции, описанные выше.

Но конечно RB имеет гораздо более ценное применение. Дело в том, что все ячейки памяти имеют сквозную нумерацию. Т.е. благодаря RB мы можем сопоставить всем объектам их номера (точнее номера ячеек, которые они занимают в памяти). И все эти номера будут уникальны. Совпадений не будет. Эту нумерацию можно использовать, чтобы... Впрочем, это тема другой части :).
12. Тип Handle
Читатель, кроме всех тех типов, к которым ты привык в редакторе переменных, существует еще один тип – handle. Это очень специфический тип - что-то типа универсального указателя на объекты. Нас он интересует с позиции его применения. К примеру, если ты сделаешь функцию:

Код:
function <Имя> takes handle h returns <...>

в аргументе указан тип handle. Так вот, в эту функцию в качестве параметра можно передавать ЛЮБОЙ игровой объект. Хочешь, посылай в качестве аргумента юнит, хочешь - регион, хочешь - точку и т.д. В связи с тем, что мы уже описали return bug, мы можем использовать его, чтобы найти ячейку в памяти, на которую ссылается handle:

Код:
function H2I takes handle h returns integer
return h
return 0
endfunction

Это дает нам определенное преимущество. Вот в прошлой статье мы рассмотрели функцию:

Код:
function FindHandle takes rect reg returns integer
    return reg
    return 0
endfunction

Для региона находили номер. А как насчет других объектов? Юнитов, точек и пр.? Для каждого создавать свою функцию? Можно конечно, но используя тип handle мы значительно упростим задачу. Функцию H2I, рассмотренная чуть выше, предназначена как раз для этой цели. H2I(O) - и есть номер ячейки в памяти, которую занимает объект O, каким бы ни был этот объект. Напишешь

Код:
set i = H2I(u)

- в i попадет номер для юнита u. И т.д.
Вот такой универсальный способ находить номер объекта. К сожалению, такого же универсального способа, чтобы по номеру определять что за объект в нем записан - нет.
13. Система Super Custom Value (SCV) или RB+cache
Читатель, мы вплотную подошли к системе SCV. Эта система, которая позволяет сопоставлять ЛЮБОМУ игровому объекту - либо другой объект, либо какое-то значение (или даже массив). Наподобие custom value, но значительно более универсальная. Значение этой системы трудно переоценить. Фактически, она позволяет упростить решение огромного множества задач, избавиться от глобальных переменных и создавать так называемые кешь-переменные прямо во время игры.

С чего тут начать. Пожалуй, с Кеша. Существует такая замечательная вещь, называемая кешь. Программисты называет такие структуры - ассоциативный массив. Кешь в war3 - это особый двумерный массив, в котором в качестве аргументов используются строки. Т.е. вводишь аргументами 2 строки, им сопоставляется значение. Можно сопоставить значение типа integer, типа real, типа string и типа boolean.
Как жаль, что в этот массив нельзя записать ссылку на юниты, предметы, способности и т.п. Стоп, а действительно ли нельзя? Или все таки можно?

Ссылку может и нельзя, но давай вспоминать, что мы узнали про RB. Каждому игровому объекту соответствует уникальный номер, число типа integer. Это число можно найти, и по этому числу можно найти объект. А ведь число типа integer может быть записано в кешь!

(*) Итак, если мы используем кешь не для переброски данных, а для хранения информации, то в качестве хранимой информации кешь способен записать указатели (номера) объектов.

Это первый важный вывод. А теперь подумаем, если мы в кешь можем сохранять объекты, можем ли мы при помощи кешь сопоставить какому-то игровому объекту какое-то значение? Игровой объект имеет свой уникальный номер. Номер есть число, но специальные функции позволяют перевести его в строку.

(**)Договоримся, если мы хотим сопоставить игровому объекту значение в кешь, то в качестве первого аргумента записи будем использовать уникальный номер этого объекта, переведенный в строку.

(***)Что касается второго аргумента кешь, то мы можем использовать его, чтобы дать нашему сопоставлению уникальное имя.

Сопоставь факты, отмеченные выше, и ты поймешь идею SCV.

Рассмотрим функцию вида:

Код:
function set_object_iparam takes handle h, string key, integer val returns nothing
   call StoreInteger(udg_cache, I2S(H2I(h)), key, val)
endfunction

Эта функция предназначена, чтобы сопоставлять любому объекту параметр типа integer. Аргументами выступает ссылка на объект handle h, строка key - имя сопоставления и переменная val типа integer - это число, которое мы сопоставляем объекту.
udg_cache - это переменная типа кешь - специальный кешь-файл создается в самом начале игры.
В функции единственное действие:

Код:
call StoreInteger(udg_cache, I2S(H2I(h)), key, val)

Это обычная команда занести значение в кешь.

Для записи в кешь, нудно передать 2 строки-аргумента. Первая строка:

Код:
I2S(H2I(h))

Разберемся подробнее. Здесь написана функция внутри функции. H2I(h) - мы уже рассмотрели выше. Она вернет номер для объекта, переданного через переменную h. Вторая функция I2S(...) - это обычная варкрафтовская функция перевода числа в строку. Итак, вся конструкция в целом приведет к тому, что первая строка - это переведенный в текст уникальный номер объекта.
Вторая строка key - это строка, которую заполняет сам пользователь, давая имя сопоставлению. Параметр для записи val.

Итак, если у тебя есть юнит u и ему нужно сопоставить число 10, то можно использовать команду:

Код:
call set_object_iparam(u, "int", 10)


имя сопоставления "int".

Отлично! Как делать запись мы выяснили. А можно ли эту запись прочитать обратно? Да! Во-первых, для удобства создадим вторую функцию:

Код:
function get_object_iparam takes handle h, string key returns integer
   return GetStoredInteger(udg_cache, I2S(H2I(h)), key)
endfunction

Она похожа по структуре на предыдущую, только аргументов на один меньше. Это потому, что функция нужна не для записи значения в кеш, а для чтения значения из кеша.

Код:
return GetStoredInteger(udg_cache, I2S(H2I(h)), key)

Т.е. наша функция вернет значение выражения GetStoredInteger(udg_cache, I2S(H2I(h)), key) . А что это за выражение? Стандартная функция для чтения из кеша. В качестве первой строки указывается уникальный номер объекта, переведенный в строку. Вторая строка - определена пользователем.

Итак, если мы хотим узнать, что записано в записи кеша "int" для юнита u, используем команду:

Код:
set i = get_object_iparam(u, "int")

Т.е. можно и записывать значения и читать их. Читатель, не замечаешь чего-то общего между нашими сопоставлениями и custom value? По сути, custom value - это тоже сопоставление, но менее универсальное, т.к. можно сопоставлять юнитам (и только юнитам) одно (и только одно) значение типа integer. А при помощи SCV можно сопоставить что угодно и чему угодно. Поэтому я называл эту систему Super Custom Value (SCV) , а сопоставления-записи - для краткости cv.

А как сопоставить юниту u - другой юнит u2? Очень просто.

Код:
call set_object_iparam(u, "int", H2I(u2))

Мы записали в параметр "int" уникальный номер u2.

Этот номер мы можем прочесть обратно. Проблема лишь в том, как при помощи этого номера получить ссылку обратно на u2. Для этого в SCV есть специальные функции.

Код:
function I2U takes integer i returns unit
return i
return null
endfunction

и

Код:
function get_object_uparam takes handle h, string key returns unit
   return I2U(GetStoredInteger(udg_cache, I2S(H2I(h)), key))
endfunction

Первая функция по уникальному номеру возвращает сам юнит, вторая сделана для простоты - она читает уникальный номер из записи в кеше и при помощи первой функции возвращает ссылку на этот юнит.
Так что, если нужно прочесть какой юнит записан в cv "int" для юнита u, используем команду

Код:
set u2 = get_object_uparam(u, "int")

Вот и все. Остальное все по аналогии. Есть и другие функции для сопоставления чисел real, строк, флагов boolean. Есть функции для нахождения не только юнитов по их номеру, но и других объектов - точек, регионов, спецэффектов и др.

Есть правда еще одна функция

Код:
function flush_object takes handle h returns nothing
   call FlushStoredMission(udg_cache, I2S(H2I(h)))
endfunction

- она позволяет быстро отчистить все записи кеша, относящиеся к какому-то объекту.
Скажем, собираешься ты удалить юнит u. Для того, чтобы cv этого объекта не занимали место в памяти, когда объекта уже нет, пишешь команду:

Код:
call flush_object(u)

Все эти функции в сумме вмещаются на 1-1.5 экрана. Переносить систему из сценария в сценарий - элементарно. Просто копируем код, создаем переменную cache и при событии Map Initizlization создаем кешь-файл.

Читатель, попробуй представить себе все возможные способы применения SCV. Вспомни примеры, которые мы рассмотрели ранее. Может быть есть способ что-то сделать проще, быстрее и надежнее ? ;)

Когда ДимонТ выпустил систему, я разработал по ней небольшой обучающий сценарий, который демонстрирует ее возможности, в том числе создание переменных и массивов cv. Я хочу, чтобы ты подробно изучил этот сценарий.

Освоив SCV ты поднимешься на следующую ступень мастерства.
14. Да здравствует SCV!
Рассмотрим один из наших старых примеров – полет юнита снаряда. Можно ли улучшить его при помощи SCV? Раньше нам приходилось использовать массивы, чтобы сохранить информацию, что такой-то юнит-снаряд летит к такой-то цели и имеет такой-то уровень заклинания. Теперь мы можем сопоставить эти данные непосредственно юниту-снаряду при помощи SCV. Т.е. записать все необходимые данные в cv. А как нам сделать периодический цикл по всем юнитам снарядам, чтобы сдвигать их? О, тут у нас появляются новые интересные возможности. Мы можем для каждого юнита-снаряда создать отдельный триггер с периодом 0.05, отвечающий за его передвижения к цели.
Ну допустим, мы создали триггер с событием Периодическое 0.05. А как прописать, что этот триггер должен работать только для определенного юнита-снаряда? Очень просто, мы сопоставим триггеру (триггер ведь тоже игровой объект!) нужный нам юнит-снаряд. И при запуске триггера сможем определить, что нужно двигать такой-то юнит-снаряд.

В целом система организации движения юнита-снаряда становится довольно простой. На основе этого принципа я сделал несколько геройских заклинаний - предлагаю тебе ознакомиться с ними. К примеру, герой Лорд Хаоса. Заклинания Звездный конус, Групповой файербол и Сфера Хаоса сделаны таким способом. Это открывает широчайшие возможности по созданию триггерных заклинаний любой сложности.

Кстати, огромное достоинство системы SCV, что ее можно легко дополнить. Допустим, нам нужно чтобы объектам можно было сопоставлять триггеры и таймеры. К функциям SCV добавим новые:

Код:
function I2Tm takes integer i returns timer
    return i
    return null
endfunction

function I2Tr takes integer i returns trigger
    return i
    return null
endfunction

и еще две

Код:
function get_object_tmparam takes handle h, string key returns timer
   return I2Tm(GetStoredInteger(udg_cache, I2S(H2I(h)), key))
endfunction

function get_object_trparam takes handle h, string key returns trigger
   return I2Tr(GetStoredInteger(udg_cache, I2S(H2I(h)), key))
endfunction

Вот и все.

15. Послесловие
Читатель, ты уже очень многое узнал о jass. Конечно, в jass полно и других важных наработок. Есть команды, позволяющие выводить текст в любом месте экрана. Есть спец. команды, позволяющие производить общие действия для всех игроков на одном только компьютере (например, играть такой-то звук). Есть наработки Vexorian-а, который по сути сделал такую же систему, как SCV ДимонаТ, но раньше. Есть методы, позволяющие упростить работу с группами юнитов. Есть методы создания специальных объектов, которые определяют, что юзер щелкнул мышкой в такую-то точку (на основании этого построен инвентарь ДимонаТ на 80 слотов). Но всему этому обучать я уже не буду, т.к. во-первых, сам многого не знаю, во-вторых, в случае необходимости ты уже сможешь разобраться самостоятельно. Можешь проверить - статьи по jass, которые раньше казались сложными и непонятными, теперь станут доступными, а материал изложенный в них - будет уже знаком.

Владея приемами работы с jass, самое главное для триггерщика - умение правильно ставить и находить решение задач, умение алгоритмизировать задачу, умение мыслить творчески. Про это я уже писал отдельную статью. Дальше, все уже зависит от твоих мозгов и твоего опыта.

Вот, к примеру, как-то раз попросил меня один картостроитель разобраться с тем, как устроено заклинание passive mana shield известного буржуйского автора. Идея в том, что повреждения должны наноситься мане, а когда мана кончится – жизни юнита. Думаю, ты уже догадаешься, как это сделать. Единственный способ, пригодный для этого методами war3 - отлавливать повреждения, полученные юнитом при помощи триггера с событием unit takes damage. Т.е. для каждого юнита с пассивкой, нужно создать такой триггер - это задача решаемая. При нанесении повреждения юниту, возможны разные случаи:
- маны у юнита больше размера повреждения: в этом случаи мы восстанавливаем юниту жизнь и отнимаем ману.
- маны у юнита меньше размера повреждения: в этом случае из полученного юнитом повреждения мы должны восстановить число жизни, равное количеству маны, а потом обнуляем ману.

Вроде все хорошо. Все, да не все. Оказывается, что событие unit takes damage срабатывает на какую-то долю секунды раньше, чем юниту наносится повреждение. Из-за этого, если жизнь юнита полная, мы пытаемся добавить жизнь и отнять ману. Но жизнь полная и поэтому добавлять к ней что-то бесполезно. Получается, что жизнь юнита все равно страдает. Мелочь, а не красиво.
Можно конечно сделать паузу 0.1 секунды и уже после этой паузы восстанавливать жизнь юнита. Тогда все работает нормально, но все таки видно, как жизнь юнита колеблется. Опять же, некрасиво.

Картостроитель предложил глянуть работу спела у буржуйского автора - там было столько jass кода, в котором было так лень разбираться, что я на это забил. Стал думать. Есть ли возможность отловить момент, когда у юнита отнимутся жизни? В принципе есть. Существует такое событие, которое срабатывает, когда жизнь юнита станет меньше указанного значения. Этим можно воспользоваться! Просто если жизни у юнита почти полные и нельзя использовать метод добавить жизнь сразу. Вместо этого мы СОЗДАДИМ ТРИГГЕР с событием, которое сработает, когда жизнь текущего юнита станет меньше текущего значения. Это произойдет буквально через миг. И именно в этот момент мы добавим жизни юниту обратно. Проверил – все сработало. А триггерный код получился заметно короче, чем у буржуя.
- спел passive magic shield

Удачных тебе наработок, Читающий ;).
К сожалению статья, которуя как я надеюсь Вы только что прочитали содержит некоторые неточности, хотя и являеться до сих пор лучшей русскоязычной вводной статьей по Jass. Поэтому я решил просто взять их и описать в отдельной статье - "Осваиваем JASS - исправления и дополнения", с которой я и рекомендую Вам ознакомиться.
16.Приложение 1: проблемы Кеша и РБ
Читатель, на первых порах тебе важнее понять принцип работы SCV и научиться его применять. Но в реальности применение системы return bug + Кеш порождает определенные проблемы, которые я постараюсь рассмотреть в этом приложении.

1. Глюки нескольких запусков.
При запуске сценария мы создаем в памяти Кеш и делаем в него записи. Когда мы выходим из сценария, записи должны удаляться. Должны то должны, но иногда этого не происходит. И тогда при следующем запуске игры могут остаться записи, относящиеся к несуществующим объектам. Это чревато ужасными глюками. Я сам с этим столкнулся и долго не мог понять, почему глючит код, в котором не может быть никаких ошибок. И только проверив все самым тщательным образом, обнаружил, что записи не стерты.

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

Код:
Create a game cache from SCV.w3v
Clear (Last created game cache)
Create a game cache from SCV.w3v
Set cache = (Last created game cache)

Т.е. очищаю Кеш и инициализирую заново. После этого можно быть уверенным, что Кеш будет очищен.

2. Скорость скриптов.

Существует доказательство того, что 1 обращение к Кешу (чтение/запись) раз в 10 медленнее, чем обращение к переменным.

Для некоторых любителей оптимизации это стало поводом отказаться от Кеша и разрабатывать альтернативные системы при помощи масствов. В то же время, я не советую следовать их примеру. Да, я не исключаю, что кому-то из jass-еров удастся разработать более быстрые системы, но вряд ли получится сделать их более простыми, универсальными и доступными, чем SCV.

Практика показывает, что даже для такой карты как Дота применение Кеша вполне приемлемо. Если качество SCV вас устраивает, то нет смысла тратить массу времени и сил на разработку собственных систем.

Кстати говоря, скорость большинства внутренних функций war3 еще меньше, чем обращение к Кешу. Поэтому, если ваш скрипт работает медленно – 99% что проблема в алгоритме, а не Кеше.

Впрочем, кое-какие выводы для себя можно для себя сделать.
1. Кеш лучше всего использовать в случаях, когда в этом есть реальная необходимость. Например, когда по ходу игры нужно одному объекту сопоставить другой объект. Если же у вас имеются данные, которые хранятся в течение всей игры, их лучше хранить в массивах.
2. Старайтесь минимизировать число параметров, которые требуется передавать через Кеш в триггерах с малыми периодами.
3. Если внутри одной и той же функции нужно несколько раз обратиться к одной и той же записи – поместите эту запись в переменную и обращайтесь к переменной.

3. Баг РБ.
Не очень давно был обнаружен баг внутри РБ (баг в баге). Суть в следующем: в результате определенных действий, игровой объект (чаще всего, юнит) может исчезнуть из игры. А записи в Кеше останутся. Например, если уже был запущен какой-то триггер, делающий отсроченный действия с этим объектом. Но еще до того, как отсроченное действие будет выполнено, в игре могут появиться новые объекты. И существует шанс, что игра запишет один из них именно в ту ячейку памяти, в которой раньше был записан старый объект.
Что получится? Игра должна была совершить определенное действие со старыми объектом, а произведет с новым. Потому что указатель на старый и новый юнит совпадают.

Подобная проблема вполне может возникнуть в АОСах. Допустим у вас есть триггерное заклинание, которое при помощи таймер, РБ и Кеша периодически наносит повреждения юниту-цели. Но вот, юнита-цель убивает какой-нибудь артиллерист. Юнит не просто умирает, а взрывается. Т.е. сразу после смерти он исчезает из игры. Но в АОСах постоянно создаются все новы юниты. И вот, создается новый юнит в той же ячейке, что и старый. И тогда мы получим, что периодические повреждения будут наноситься какому-то случайному юниту. Т.е. получили баг.

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

Есть еще один прием. Дело в том, что новый юнит может занять место старого только при условии, что на старый юнит нет ссылок из переменных (неважно, глобальных или локальных). Можно использовать такой прием. Триггер, который сработает при смерти юнита:

Код:
local unit u = GetDyingUnit()
call PolledWait(60.0)
call flush_object(u)
set u = null

Т.е. любой умерший юнит попадает в локальную переменную на 60 секунд. После чего записи из Кеша отчищаются. Для большинства отстроченных действий 60 секунд вполне хватит, чтобы обнаружить, что юнит-цель уже мертв.

Кстати, можем получить и другую сторону этого бага. Даже если ничего не будет записано в пустую ячейку удаленного юнита, отсроченные действия могут не предусмотреть наличие юнита-пустышки там, где раньше был живой юнит.

Реальный пример. Я долго не мог понять, почему глючит моя триггерная аура. Технология ауры заключалась в том, что создавался периодический триггер. Ему сопоставлялась группа юнитов, на которых действует аура. В триггере нужно периодически перебирать юнитов из группы. За это отвечал такой блок:

Код:
    set k2 = CountUnitsInGroup(gr)
    set k = 1
    loop
        exitwhen k>k2
        set u = FirstOfGroup(gr)
        ...
        call GroupRemoveUnit(gr,u)
        set k = k + 1
    endloop

Но если в группу gr зачешется уже убранный из игры юнит – скрипт прекратит выполняться, когда дойдет до строчки set u = FirstOfGroup(gr). Точнее, он вылетит, когда, первым в группе окажется пустой юнит. При этом вы никак не сможете избавиться от этого глючного юнита. Его уже невозможно удалить из группы. Пришлось немного помучаться, пока я не нашел противоядие к этому багу:

Код:
    set gr2 = CreateGroup()
    call GroupAddGroup( gr, gr2 )
    set k2 = CountUnitsInGroup(gr2)
    set k = 1
    loop
        exitwhen k>k2
        set u = FirstOfGroup(gr2)
        ...
        call GroupRemoveUnit(gr2,u)
        set k = k + 1
    endloop

Т.е. мы делаем еще одну группу. В эту группу помещаем все юниты из первой группы (не надо бояться – глючный юнит невозможно добавить в новую группу, также как невозможно удалить его из старой группы). Далее перебираем не первую, а вторую группу.
17. Приложение 2: JESP стандарт
Зарубежные картостроители (буржуи) разработали специальный стандарт написания триггерных заклинаний, который упрощает работу с ними. Упрощает, впрочем, не автору, а тем, кто захочет данное заклинание импортировать или поменять его параметры. Не думаю, что стандарт стоит применять в чистом виде. Но некоторые принципы JESP можно позаимствовать, т.к. с точки зрения программиста они разумны.

Разберем пример.

Код:
//===========================================================================
//            Ion cannon
//            удар по местности с массивными повреждениями
//===========================================================================

//SpellData==================================================================
function Ion_cannon_ability takes nothing returns integer
    return 'A04Y'//способность ионной пушки
endfunction
function Ion_cannon_seunit takes nothing returns integer
    return 'h01B'//юнит-спецэффект
endfunction

//SpellValues================================================================
function Ion_cannon_range takes nothing returns real
    return 200.0//радиус действия
endfunction
function Ion_cannon_damage takes nothing returns real
    return 100.0//урон за цикл 
endfunction
function Ion_cannon_cilnum takes nothing returns integer
    return 5//количество циклов
endfunction

//Cond/Filter funcs==========================================================
function Trig_GEN_Ion_cannon_Conditions takes nothing returns boolean
    return ( GetSpellAbilityId() == Ion_cannon_ability() )
endfunction

//actions==================================================================//уничтожение деревьев
function Trig_GEN_Ion_cannon_ddestr takes nothing returns nothing
    call KillDestructable( GetEnumDestructable() )
endfunction

//пушка
function Trig_GEN_Ion_cannon_Actions takes nothing returns nothing
    local unit u = GetSpellAbilityUnit()
    local unit u2
    local integer i
    local location p = GetSpellTargetLoc()

    call PolledWait(1)

    call CreateNUnitsAtLoc( 1, Ion_cannon_seunit(), GetOwningPlayer(u), p, bj_UNIT_FACING )
    set u2 = GetLastCreatedUnit()

//половинные повреждения
call UnitDamagePointLoc( u2, 0, Ion_cannon_range()/2, p, Ion_cannon_damage()/2, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE )


//полные повреждения
    set i = 1
    loop
        exitwhen i > Ion_cannon_cilnum()
        call PolledWait(0.1)
        call UnitDamagePointLoc( u2, 0, Ion_cannon_range(), p, Ion_cannon_damage(), ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE )
        set i = i + 1
    endloop

    call PolledWait(0.5)
    call EnumDestructablesInCircleBJ( Ion_cannon_range(), p, function Trig_GEN_Ion_cannon_ddestr )
    call RemoveUnit(u2)
    call RemoveLocation(p)
set p = null
set u = null
set u2 = null
set i = 0
endfunction

//===========================================================================
function InitTrig_GNR_Ion_cannon takes nothing returns nothing
    set gg_trg_GNR_Ion_cannon = CreateTrigger(  )
    call DisableTrigger( gg_trg_GNR_Ion_cannon )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_GNR_Ion_cannon, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_GNR_Ion_cannon, Condition( function Trig_GEN_Ion_cannon_Conditions ) )
    call TriggerAddAction( gg_trg_GNR_Ion_cannon, function Trig_GEN_Ion_cannon_Actions )
endfunction

Это триггерное заклинание «Ионная пушка». В выбранной точке создается невыделяемый юнит- спецэффект в виде светового луча. Юниты в области луча получают повреждения (100 повреждений каждые 0.1 секунды – не более 5 раз). Луч также уничтожает деревья в области попадания.

Теперь обратите внимание на то, как это заклинание оформлено.
1. В верхней части имеется шапка с комментариями: название заклинание, его действие и пр.

2. Ниже шапки располагается раздел «SpellData», в котором есть список всех используемых в заклинании объектов:

Код:
function Ion_cannon_ability takes nothing returns integer
    return 'A04Y'//способность ионной пушки
endfunction
function Ion_cannon_seunit takes nothing returns integer
    return 'h01B'//юнит-спецэффект
endfunction

Т.е. для работы заклинания требуется 2 объекта:
а) юнит-спецэффект
б) способность-пустышка

Во всей остальной части триггера больше нету ссылок на константы 'A04Y' или 'h01B'. Вместо них применяются функции Ion_cannon_ability() или Ion_cannon_seunit(). Например, строку

Код:
    call CreateNUnitsAtLoc( 1, 'h01B', GetOwningPlayer(u), p, bj_UNIT_FACING )

мы заменяем на код:

Код:
    call CreateNUnitsAtLoc( 1, Ion_cannon_seunit(), GetOwningPlayer(u), p, bj_UNIT_FACING )

Этот прием поможет заметно ускорить импорт карты. Ведь при импорте вы уже будете знать все необходимые объекты. При импорте объектов сменятся идентификаторы. Но вместо того, чтобы выискивать их по всему триггеру, достаточно будет поменять их в одном месте в верхней части триггера.

3. Еще ниже располагается блок «SpellValues». Здесь находится список констант, имеющих значение для настройки и изменения базовых параметров заклинания. Например, для заклинания ионной пушки я счел нужным вынести в константы:
- радиус действия заклинания
- количество повреждений, наносимых заклинанием за 1 цикл
- количество циклов
Все эти параметры использованы внутри заклинания. Вместо того, чтобы писать:

Код:
        call UnitDamagePointLoc( u2, 0, 200.0, p, 100.0, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE )

мы пишем:

Код:
        call UnitDamagePointLoc( u2, 0, Ion_cannon_range(), p, Ion_cannon_damage(), ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE )

Благодаря этому, любой человек сможет быстро поменять параметры заклинания.
Примечание: заклинание «Ионная пушка» – одноуровневое. Для многоуровневых заклинаний, можно усложнить функции. К примеру:

Код:
function Ion_cannon_damage takes integer level returns real
    return 50.0 +50*I2R(level)//урон за цикл 
endfunction

Т.е. в функцию мы будем передавать уровень заклинания. И от уровня будут зависеть повреждения за цикл:
1 ур – 50
2 ур – 100
3ур - 150 и т.д.

4. Следующий блок называется «Cond/Filter funcs»
Сюда мы помещаем все функции-фильтры, в которых происходит проверка каких-то условий. В нашем случае имеется единственная подобная функция, в которой проверяется, какая абила была применена.

5. Следующий блок называется «Actions»
Сюда мы помещаем все функции-действия. В нашем случае, имеется функция с основными действиями и еще одна функция с действием убить дерево.

6. В самом низу находится функция инициализации триггера.

Замечание 1: обращаю внимание, что все функции, используемые в триггере начинаются с названия «Ion_cannon». Это имеет смысл. В игре может быть всего один триггер с именем «Ion_cannon». Поэтому делая такую приставку в начале каждой триггерной функции, мы можем гарантировать, что подобное название не будет повторено в других триггерах.
Замечание 2: в стандарте JESP бывает еще один блок «CacheValues» (сразу после шапки) где указывается названия Кеш-переменных, использованных в триггере (SCV или ее аналоги).

Вот такая структура триггера. Достаточно удобная – рекомендую пользоваться.

Итак, для того, чтобы оптимизировать готовое заклинание, нужно сделать следующее:
1. Создать базовые блоки
- шапка
- «CacheValues»
- «SpellData»
- «SpellValues»
- «Cond/Filter funcs»
- «Actions»
2. Заполнить шапку.
3. Выделить все использованные объекты. Создать для них функции-константы. Заменить ссылки на эти объекты, ссылками на функции-константы.
4. Выделить ключевые параметры заклинания, которые имеет смысл менять балансерам. Создать для этих параметров функции-константы. Заменить параметры на функции-константы.
5. Разнести игровые функции по разделам.

Эта схема позволит упростить импорт и изучение заклинания. В том числе и вам самим, т.к. через некоторое время сам забываешь, как работало твое заклинание.

0

5

Я или слепой, или хз де, но что-то я не вижу ссылки на закачку карты-примера. sample locs.w3x

0