Прежде всего, нужно рассказать немного о том, что собой представляет компьютерная память. Читатель, может быть ты и так это знаешь, но на всякий обрисую картину. Все без исключения игровые объекты - юниты, строки, числа - все это находится в компьютерной памяти. Память представляет собой просто последовательность идущих подряд ячеек, в каждую из которых записано определенное число. При помощи чисел можно закодировать все - и юниты и строки. Разные объекты занимают разное количество ячеек. Ведь чтобы записать объект юнит, нужно записать целую кучу информации о всех его параметрах. Это гораздо больше информации, чем то, которое несет какое-то значение типа 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. И т.д.
Вот такой универсальный способ находить номер объекта. К сожалению, такого же универсального способа, чтобы по номеру определять что за объект в нем записан - нет.