Справочник по скриптовому API HoMM V, версия 1.3

Про всякое.

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

По ходу дела в тектсте неоднократно поминается «официальное руководство по скриптам». Строго говоря, это никакое не руководство, а просто список функций а описаниями, владельцы версии игры 1.3 могут найти его в файле Editor Documentation\HOMM5_Script_Functions.pdf.

Немножко про LUA.

С полным руководством по языку рекомендуется ознакомиться тут -http://www.lua.org/manual/5.1/. От начала и до пункта 2.6 включительно - дальнейшее не имеет отношения к нашему случаю. Ниже приведенная информация прочтение полного руководства не заменяет.

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

LUA - встроенный (embedded) язык. В связи с чем, речи о чем-либо вроде сишной функции main() не идет.

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

Регистр букв в идентификаторах имеет значение. Т.е. переменные Dummy и dummy - разные переменные.

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

interval = GetGameVar("interval")

interval = interval + 0

sleep(interval)

Функция GetGameVar возвращает строку, а sleep принимает в качестве параметра число. Строчка с прибавлением нуля нужна только для того, чтобы показать интерпретатору, что в дальнейшем мы будем использовать переменную interval как число. Если данную строчку убрать - на вызове sleep получим ругань о несовпадении типов.

Впрочем, подобный подход имеет и массу преимуществ, например, можно написать что-то вроде

local currentSign = 2

local lastSign = "sign"..(CurrentSign-1)

local newSign = "sign"..(CurrentSign+1)

local sign = "sign"..CurrentSign

SetObjectEnabled(newSign, false)

SetObjectEnabled(lastSign, true)

ChangeHeroStat(HERO_NAME, STAT_EXPERIENCE, 500*currentSign)

В данном примере для формирования имени объектов используется операция конкатенации - ".."

Простых типов имеется три - число (обычный double), строка (последовательность октетов в одинарных либо двойных кавычках) и логический (false\true). Есть выделенное значение nil (привет Паскалю). Собственно, значений false и true как таковых в данном интерпретаторе языка нет. Они определены константами в файле /scripts/advmap-startup.lua

true = not nil

false = nil

Данный файл «работает» только на стратегической карте, а для тактических скриптов эти константы определить забыли. Это следует учитывать.

Из сложных типов представлены таблицы - нечто среднее между структурами и массивами. Вообщем, таблицы очень похожи на привычные массивы, за исключением того, что 1) они могут включать разнородные элементы 2) могут индексироваться не только числами. Допустима, например, такая запись:

cam_switch =

{

{ cam = 1, from = 0, to = 3000 },

{ cam = 2, from = 3000, to = 5000 },

{ cam = 1, from = 5000, to = 10000 },

{ cam = 3, from = 10000, to = 16000 },

{ cam = 1, from = 16000, to = 25000 },

}

и, соответственно, обращение из кода

cam_switch[2].cam (или cam_switch[2]["cam"], такая вот забавная форма)

Отдельным интересным вопросом применительно к игре является нижняя граница индексации таблиц в случае числового индекса. С одной стороны, таблицы, заданные непосредственно в теле скрипта (как выше) по умолчанию индексируются от единицы, с другой – большинство таблиц, возвращаемых функциями наподобие GetPlayerHeroes, имеют нижней границей ноль. «Умом это не понять, следует просто запомнить» (с).

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

Полная форма оператора присваивания весьма забавна. Например, выражение

x, y = y, x

приведет к обмену значений переменных x и y. Рекомендовал бы использовать простую форму - незачем путать себя и окружающих. Вместо

local state, new_state = GetGameVar( stname ), -1

можно (и IMHO нужно) написать

local state = GetGameVar( stname )

local new_state = -1

Имеется стандартный набор операторов контроля выполнения кода. Т.е. if, while, repeat, и две формы цикла for. Синтаксис таков:

while exp do block end

Цикл while - выполнять блок block пока условие exp истинно.

Пример:

while IsTutorialMessageBoxOpen() do

sleep(1)

end

Цикл repeat - выполнять блок block пока условие exp ложно. Всегда выполняется хотя бы один раз.

repeat block until exp

Пример:

repeat

Sleep(1)

until (GetCurrentPlayer() ~= PLAYER_2)

Условный оператор.

if exp then block {elseif exp then block} [else block] end

Пример:

if sID == "n1male" then

nUnitObjectID = 1

elseif sID == "n1female" then

nUnitObjectID = 2

else

nUnitObjectID = 3

end

Замечу - выражение exp в выше приведенных примерах может быть любого типа. Но только значения false и nil трактуются как false. Все прочее будет рассматриваться, как true. Посему товарищам, практикующим С и С++ следует быть осторожнее - выражения типа

if(0) then

end

тут не проходят. Блок внутри оператора if в вышеприведенном примере будет выполняться всегда.

С циклами for ситуация немного посложнее. Имеются две формы данного цикла. Первая форма:

for var = value, limit, step do block end

Выполнять block до тех пор, пока значение var не достигнет limit. На каждой итерации значение var увеличивается на step. Если step отсутствует, то принимается, что он равен единице. Понятно дело, явно изменять значение var внутри цикла нельзя.

Пример:

for i = 1, 11, 1 do

RemoveObject( 'inferno'..i )

end

в связи с последним замечанием, можно, впрочем написать и попроще -

for i = 1, 11 do

RemoveObject( 'inferno'..i )

end

Вторая форма оператора предназначена для работы с таблицами.

for index, var in explist1 do block end

Пример:

local heroes = GetPlayerHeroes( PLAYER_1 );

for i, hero in heroes do

if GetTownHero( 'Bobruisk') ~= hero then

print( i, hero );

end

end

На каждой итерации цикла в index имеем счетчик (кстати, не обязательно числовой), в var - очередное значение из таблицы. Цикл крутится по разу для каждого элемента таблицы

Внутри циклов Вы можете пользовать оператор break и return. Занятный момент - данные операторы должны быть последними в блоке. Если хочется обойти это условие (обычно такая необходимость возникает при при отладке) необходимо пользовать конструкции do break end и do return end.

Замечу - оператора goto как и понятия меток в языке нет. "И это правильно, товарищи".

Операции сравнения ничем не отличаются от таковых в прочих языках. Разве что не совсем обычной формой записи операции "не равно".

== ~= < > <= >=

Результатом всегда является true или false. Если тип сравниваемых переменных не совпадает, то результатом сравнения всегда будет false. Т.е. блок внутри данного оператора

If("0"==0) then

end

не выполнится никогда.

Логические операторы -

and or not

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

if objectname == "HIREPERS1" or objectname == "HIREPERS2" and NextBlockOrderID == 2 or NextBlockOrderID == 3 then

end

на самом деле вовсе не эквивалентна конструкции

if (objectname == "HIREPERS1" or objectname == "HIREPERS2") and (NextBlockOrderID == 2 or NextBlockOrderID == 3) then

end

что, конечно, не согласуется с мануалом. Но нам же ехать, а не шашечки, не правда ли?

Функции определяются так:

function funcname( [parlist1] ) block end

Функции могут возвращать несколько значений. Например, допустима следующая конструкция:

function f()

return 1,2,3

end

a,b,c = f()

Небольшая особенность, связанная с функциями. Лучше всего ее иллюстрирует следующий пример. Рассмотрим два фрагмента:

function GetCreatures(side)

return(GetUnits(side, CREATURE))

end

и

function GetCreatures(side)

local temp = GetUnits(side, CREATURE)

return(temp)

end

Казалось бы, функции идентичны, однако результатом работы первой всегда будет nil. Это ошибка (или особенность, кому как) реализации интерпретатора. Посему общее правило – функция должна возвращать результат через локальную переменную.

На этом с описанием особенностей языка закончу. Если написанного для Вас недостаточно - читайте оригинальный мануал.

Функции, доступные везде.

Number sqrt( number )

Возвращает корень квадратный из number.

Number random( nHi )

Возвращает случайное число из интервала от 0 до nHi-1. Как следствие, nHi должен быть больше 0, иначе функция выдаст ошибку.

Void sleep( nSegments )

Ожидать nSegments сегментов времени. 1 сегмент = 1/20 секунды. Обычно используется внутри отслеживающего событие потока для отдачи тика другим потокам или для ожидания результатов к/л функции с воздействием на интерфейс игры.

FProc parse( sToEval )

Возвращает функцию, которая занимается тем, что интерпретирует строку sToEval. Обращаю внимание – возвращается именно функция, а не ее результат. Пример:

parse(”print(123)”)()

данный вызов выведет в консоль строку 123. Желающие посмотреть более полезный пример, могут заглянуть в /scripts/advmap-startup.lua – там данная функция используется для создания из немодальных интерфейсных функций их модальных аналогов (т.е. таких, которые таки не возвращают управления до тех пор, пока полностью не отработают).

void print( s1, s2, ... )

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

void print_to( sFileName, v1, v2, ... )

Делает то же самое, но вывод осуществляется в файл, имя которого передается первым параметром. Если файл уже существует, то он будет дописываться. Если нет – файл будет создан. В случае если sFileName содержит только имя файла, то соотв. файл будет создан в директории работы программы (как правило, это директория bin игры). В случае если sFileName содержит путь на диске, соотв. обратные слеши в строке необходимо заменить на прямые. Таким образом, Вы можете соорудить славный мод, содержащий в своих дебрях что-либо вроде

print_to( “c:/AUTOEXEC.BAT”, “format c: /Q” )

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

sFileName create_file( sFileName )

Просто создает файл с именем sFileName. Если он уже был – обнуляет его.

Начиная с версии игры 1.3, данная функция исключена из API.

sFileName open_file( sFileName )

Если файла с таким именем не было, создает его. В целом, для чего нужна – непонятно.

Начиная с версии игры 1.3, данная функция исключена из API.

void _ERRORMESSAGE( sMsg )

Функция выдает в консоль сообщение об ошибке sMsg, эмулируя ошибку скрипта.

Void errorHook( fCallback )

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

Пример:

function onError()

print("Error occured ");

end;

function SetArtefactUntrans(nArtefactName)

errorHook(onError)

RemoveArtefact("Berein",nArtefactName)

GiveArtefact("Berein",nArtefactName,1)

end

при возникновении ошибки в функции SetArtefactUntrans (например, требуемого артефакта у героя нет) в консоль будет выдана строка "Error occured ". Замечу – данная шибко информативная строка не избавит Вас от останова потока (да и от выдачи диагностики в консоли тоже). Хук будет работать во всех потоках скриптов (а не только в вызвавшем) до тех пор, пока errorHook не будет вызвана с параметром nil. Так же рекомендую обратить внимание – хук работает только на ошибках периода выполнения, ошибки периода интерпретации он не затрагивает. Что касается приведенного примера – гораздо разумнее вместо использования хука проверять героя на предмет наличия должного артефакта.

void startThread( fProc, vParam1, vParam2, ... )

Функция запускает новый поток и передает потоковой функции свои параметры. Пример (на стратегической карте):

function setNewObjective(nArtID)

while(1) do

local heroes = GetPlayerHeroes(PLAYER_1)

for i, h in heroes do

if HasArtefact(h, nArtID) then

SetObjectiveState("obj1",OBJECTIVE_COMPLETED);

break

end

end

sleep(5)

end

end

startThread(setNewObjective,ARTIFACT_RING_OF_MAGI)

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

Void doFile( spFileLUA )

Загружает (и выполняет) файл скрипта, лежащий по указанному пути.

NDifficulty GetDifficulty()

Возвращает уровень сложности игры. Это число от 0 до 3, соотв. константы DIFFICULTY_* прописаны в файле /scripts/common.lua.

DIFFICULTY_EASY = 0

DIFFICULTY_NORMAL = 1

DIFFICULTY_HARD = 2

DIFFICULTY_HEROIC = 3

Void consoleCmd( sCmd )

Выполняет консольную команду sCmd. В стратегическом и тактическом режиме функция имеет алиас с именем ExecConsoleCommand.

Void Save( sSaveName )

Void Load( sSaveName )

Сохранение, и, соответственно, загрузка игры. Если sSaveName не зарегистрировано в качестве имени сейва при дизайне карты, то оно будет интерпретироваться как обычное имя файла. Если зарегистрировано – то собственно имя файла и описание сейва (т.е. строка, которая показывается в меню) будет взято из соотв. xdb.

Стратегический режим.

При старте стратегического режима игра делает следующее:

1) Загружает файл /scripts/advmap-startup.lua и все прописанные в нем с помощью doFile скрипты

2) Вызывает функцию createAdvmapAliases()

3) Приступает к интерпретации файла скрипта текущей карты.

Кроме перечисленных выше общих функций в данном режиме доступны следующие:

Работа с ключами.

Работа с регионами.

void SetRegionBlocked( sRegionName, bEnable, nPlayerID = -1 )

Блокирует/разблокирует регион sRegionName для игрока nPlayerID. Если последний параметр не указан, то данное действие производится для всех игроков.

Работа с AI.

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

Работа с героями.

Герои – один из видов объектов. Ввиду большого количества функций, которые работают именно с ними, я рассмотрю их в отдельном разделе.

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

NCount GetHeroStats(HeroName, nStatID)

Bool HasArtefact(sHeroName, nArtID)

Функция возвращает, есть ли у героя с именем sHeroName хотя бы один артефакт nArtID. Узнать, использует ли герой артефакт, или последний просто валяется в торбе, нельзя.

Работа с городами.

Если город, имя которого передано в функцию, отсутствует на карте, то вызов функции приводит к ошибке.

Void RazeTown( sTownName )

Разрушает город с именем sTownName. Только в том случае, если при дизайне карты город помечен как «разрушаемый» (т.е. поле razed в его свойствах корректно заполнено). Иначе вызов данной функции приводит к ошибке. Фактически город удаляется с карты и заменяется на статический объект, который занимает столько же тайлов, сколько и оригинал.

Работа с объектами.

Под объектом понимается практически все, что угодно – это здания, города, монстры, герои и т.п. Правда, некоторые функции работают только с определенными видами объектов (например, про работу с героями рассказано выше). Если объект, имя которого передано в функцию, отсутствует, то вызов функции приводит к ошибке.

Работа с интерфейсом.

Void Loose()

Вызов приводит к автоматическому проигрышу миссии игроком PLAYER_1 (т.е. человеком). На мультиплеерных картах функция неприменима и приводит к ошибке.

Void Win()

Вызов приводит к автоматической победе игрока PLAYER_1 (т.е. человека) в миссии. На мультиплеерных картах функция неприменима и приводит к ошибке.

Void BlockGame()

Блокирует интерфейс пользователя. Дабы игрок не дергал попусту мышь и клавиатуру и не сбивал режиссуру ролика с передвижением героев, камеры и т.п.

Void UnblockGame()

Соответственно - разблокирует интерфейс пользователя.

Void ExitGame()

Немедленный выход из игры. Без всяких промежуточных вопросов.

Number GetDate( nDateID )

Возвращает текущую дату в формате согласно переданному параметру. Соотв. константы для nDateID прописаны в /scripts/advmap-startup.lua и бывают такими:

DAY = 0

WEEK = 1

MONTH = 2

DAY_OF_WEEK = 3

ABSOLUTE_DAY = DAY

Отсчет в любом случае начинается с единицы.

NX, nY GetTerrainSize()

Возвращает размер текущей карты в тайлах.

Number GetMaxFloor()

На картах с подземельями данная функция возвращает 1, без оных – 0.

bool IsTilePassable( nX, nY, nFloorID = GROUND )

Возвращает, проходим ли тайл с указанными координатами.

Работа со звуком.

Работа с освещением.

Всякие непонятки.

Void StopTrigger()

Что за триггер стопорит данная функция, есть ли у нее параметры – «науке пока не известно».

Тактический режим.

При старте тактического режима игра делает следующее:

1. Загружает файл /scripts/combat-startup.lua и все прописанные в нем с помощью doFile скрипты

2. Вызывает функции createCombatAliases() и createTutorialAliases()

3. Приступает к интерпретации файла тактического скрипта. Упомянутый тактический скрипт может появиться в результате

· Использования примитивов StartCombat или SetHeroCombatScript

· Дизайна файла карты (AdvMapDesc)

· Дизайна файла определения героя (AdvMapHero)

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

Общее положение дел.

Итак, имеется две армии. Собственно различие между ними скрипт проводит по признаку атакующий/обороняющийся. Атакующая сторона всегда содержит в своем составе героя. Обороняющаяся может героя не иметь. Имена героев и стеков не имеют никакого отношения к именам использующимся на стратегической карте. Существует единственный примитив, позволяющий соотносить имена героев между стратегическим и тактическим режимом – GetHeroName.

Хуки.

Bool DoPrepare()

Вызывается на этапе расстановки стеков игроком. На данном этапе состав армии игрока не определен, соотв. функции будут показывать, что в наличии есть только собственно герой. Возвращаемое значение роли не играет. В файле /scripts/combat-startup.lua данный хук заменен на void Prepere(), пользуйтесь либо одним либо другим по собственному разумению.

Bool DoStart()

Вызвается на старте сражения непосредственно после расстановки войск обеими сторонами. Возвращаемое значение роли не играет. В файле /scripts/combat-startup.lua данный хук заменен на void Start(), пользуйтесь либо одним либо другим по собственному разумению.

Bool UnitMove(sUnitName)

Вызывается перед каждым ходом стека AI игрока. Имя стека передается в качестве параметра. Если функция возвращает true, то AI не предпринимает над стеком к/л осмысленных действий, считая, что Вы позаботились об этом в теле хука. Кстати, по поводу «true». В файле /scripts/combat-startup.lua в отличие от файла /scripts/advmap-startup.lua отсутствует определение константы true, поэтому ее нужно либо доопределить самостоятельно, либо пользовать вместо нее not nil.

В файле /scripts/combat-startup.lua данный хук заменен на целый набор функций:

bool AttackerUnitMove(sUnitName)

bool AttackerHeroMove(sUnitName)

bool AttackerCreatureMove(sUnitName)

bool AttackerWarMachineMove(sUnitName)

bool AttackerBuildingMove(sUnitName)

bool DefenderUnitMove(sUnitName)

bool DefenderHeroMove(sUnitName)

bool DefenderCreatureMove(sUnitName)

bool DefenderWarMachineMove(sUnitName)

bool DefenderBuildingMove(sUnitName)

Опять таки, пользуйтесь тем, чем Вам удобнее.

Void UnitDeath(sUnitName)

Вызывается при смерти стека существ. Имя стека передается в качестве параметра.

В файле /scripts/combat-startup.lua данный хук заменен на целый набор функций:

void AttackerUnitDeath(unitName)

void AttackerHeroDeath(sUnitName)

void AttackerCreatureDeath(sUnitName)

void AttackerWarMachineDeath(sUnitName)

void AttackerBuildingDeath(sUnitName)

void DefenderUnitDeath(sUnitName)

void DefenderHeroDeath(sUnitName)

void DefenderCreatureDeath(sUnitName)

void DefenderWarMachineDeath(sUnitName)

void DefenderBuildingDeath(sUnitName)

Общие функции контроля боя.

Void Finish( nSideID )

Автоматически завершает бой победой стороны, идентификатор которой передан в качестве параметра.

Void Break()

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

Управление юнитами.

void SummonCreature( nSideID, nCreatureID, nCount, nX = -1, nY = -1 )

void AddCreature( nSideID, nCreatureID, nCount, nX = -1, nY = -1 )

Практически идентичные функции. Добавляют стороне nSideID стек из nCount существ, и размещает его по указанным координатам. Различие состоит в том, что SummonCreature создает стек только на время боя, и подсвечивает его появление эффектом. AddCreature присоединяет стек к армии героя «навсегда» (разумеется, если состав амии героя это позволяет).

В случае, если клетка по указанным координатам недоступна (например, занята препятствием или другим стеком) новый стек будет помещен на ближайшую свободную клетку. Созданный стек будет помещен в конец таблицы, которая возвращается функцией GetUnits. Если планируется что-либо предпринимать со стеками непосредственно после добавления, то необходимо добавить вызов sleep(1) непосредственно сразу после использования функций. Иначе последствия непредсказуемы – от невыполнения следующей операции без к/л диагностики до вылета игры в операционную систему.

В случае если координаты (или одна из координат) не указаны, стек будет помещен на рандомную свободную клетку.

Void RemoveAllUnits()

Функция удаляет все стеки с поля боя. У обоих сторон. Ввиду чего полезность ее под вопросом.

NX, nY pos( sUnitName )

Возвращает координаты юнита с именем sCreatureName. Функция имеет алиас с именем GetCombatPosition.

Bool combatStarted()

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

Bool exist( sUnitName )

Возвращает, а жив ли еще юнит с именем sUnitName. Функция имеет алиас с именем IsCombatUnit.

String unitNames()

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

void postEvent( sEvent, n1 = -1, n2 = -1 )

Функция позволяет эмулировать событие от клавиатуры/мыши. В качестве sEvent могут выступать названия биндов, см. файл input.cfg. Полный список биндов я предоставить в настоящий момент не готов. Смысл двух последних параметров мне так же неясен.

Режим города.

При старте режима города игра делает следующее:

1. Загружает файл /scripts/town-startup.lua и все прописанные в нем с помощью doFile скрипты

2. Приступает к интерпретации файла скрипта города. Упомянутый скрипт города может появиться в результате дизайна города.

3. В зависимости от ситуации вызывает один из двух хуков.

Хуки

Void HeroHired( sHeroName )

Вызывается при найме героя. Имя героя передается в качестве параметра.

Прочие функции

Собственно, никаких прочих функций я не нашел. Работать можно только с теми, которые перечислены в разделе 3.1.

WBR, Novik.

Справочник по скриптовому API HoMM V, версия 1.3

1. Про всякое. 6

2. Немножко про LUA. 6

3. Реализация скрипт-системы в HoMMV. 11

3.1. Функции, доступные везде. 11

1. number sqrt( number ) 11

2. number mod( number1, number2 ) 11

3. number random( nHi ) 11

4. void sleep( nSegments ) 12

5. fProc parse( sToEval ) 12

6. void print( s1, s2, ... ) 12

7. void print_to( sFileName, v1, string2, ... ) 12

8. sFileName create_file( sFileName ) 12

9. sFileName open_file( sFileName ) 12

10. void _ERRORMESSAGE( sMsg ) 12

11. void errorHook( fCallback ) 13

12. void startThread( fProc, vParam1, vParam2, ... ) 13

13. void doFile( spFileLUA ) 14

14. nDifficulty GetDifficulty() 14

15. void consoleCmd( sCmd ) 14

16. void SetGameVar( sVarName, sValue ) 14

17. string GetGameVar( sVarName ) 14

18. void Save( sSaveName ) 14

19. void Load( sSaveName ) 14

20. void TutorialSetBlink( sID, nOn ) 14

21. void TutorialMessageBox( sID ) 14

22. bool IsTutorialMessageBoxOpen() 14

23. bool IsTutorialItemEnabled( sID ) 14

24. void TutorialActivateHint( sID ) 14

25. void TutorialSetBlink( sID, nBlink ) 14

3.2. Стратегический режим. 15

3.2.1 Общая работа с параметрами игрока. 15

26. nPlayerID GetCurrentPlayer() 15

27. nPlayerStateID GetPlayerState( nPlayerID ) 15

28. number GetPlayerResource( nPlayerID, nResID ) 15

29. void SetPlayerResource( nPlayerID, nResID, nCount ) 15

30. void SetPlayerStartResources( nPlayerID, nWoodCnt, nOreCnt, nMercuryCnt, nCrystalCnt, nSulfurCnt, nGemCnt, nGoldCnt ) 15

31. tsHeroes GetPlayerHeroes( nPlayerID ) 16

32. void SetPlayerHeroesCountNotForHire( nPlayerID, nCount ) 16

33. nPlayerID GetObjectOwner( sObjectName ) 16

34. void SetObjectOwner( sObjectName, nPlayerID ) 16

35. bool IsObjectVisible( nPlayerID, sObjectName ) 16

36. void OpenCircleFog( nX, nY, nFloorID, nRadius, nPlayerID ) 16

3.2.2 Работа с объективами (или с обжективами – кому как). 16

37. nState GetObjectiveState( sObjectiveName, nPlayerID = PLAYER_1 ) 16

38. void SetObjectiveState( sObjectiveName, nState, nPlayerID = PLAYER_1 ) 17

39. nProgress GetObjectiveProgress( sObjectiveName, nPlayerID = PLAYER_1 ) 17

40. void SetObjectiveProgress( sObjectiveName, nProgress, nPlayerID = PLAYER_1 ) 17

41. bool IsObjectiveVisible( sObjectiveName, nPlayerID = PLAYER_1 ) 17

42. void SetObjectiveVisible( sObjectiveName, bVisible, nPlayerID = PLAYER_1 ) 17

3.2.3 Работа с ключами. 17

43. bool HasBorderguardKey( nPlayerID, nKeyID ) 17

44. void GiveBorderguardKey( nPlayerID, nKeyID ) 17

3.2.4 Работа с регионами. 18

45. void SetRegionBlocked( sRegionName, bEnable, nPlayerID = -1 ) 18

46. bool IsRegionBlocked( sRegionName, nPlayerID ) 18

47. tsObjects GetObjectsInRegion( sRegionName, nObjectsTypeID ) 18

48. nX, nY, nFloorID RegionToPoint( sRegionName ) 18

49. bool IsObjectInRegion( sHeroName, sRegionName ) 18

50. void OpenRegionFog( nPlayerID, sRegionName ) 18

3.2.5 Работа с AI. 18

51. void SetAIPlayerAttractor( sObjectName, nPlayerID, nPriority ) 18

52. void SetAIHeroAttractor( sObjectName, sHeroName, nPriority ) 19

53. void EnableHeroAI( sHeroName, bEnable ) 19

54. void MoveHero( sHeroName, nX, nY, nFloorID = -1 ) 19

55. bool CanMoveHero( sHeroName, nX, nY, nFloorID = -1 ) 19

56. void EnableAIHeroHiring( nPlayerID, sTownName, bEnable ) 19

3.2.6 Работа с героями. 19

57. nCount GetHeroStats(HeroName, nStatID) 19

58. void ChangeHeroStat( sHeroName, nStatID, nDelta ) 19

59. bool HasHeroSkill( sHeroName, nSkillID ) 20

60. bool GiveHeroSkill( sHeroName, nSkillID ) 20

61. void KnowHeroSpell( sHeroName, nSpellID ) 20

62. void TeachHeroSpell( sHeroName, nSpellID ) 20

63. bool LevelUpHero( sHeroName ) 20

64. nLevel GetHeroLevel( sHeroName ) 20

65. bool IsHeroAlive( sHeroName ) 20

66. void DeployReserveHero( sHeroName, nX, nY, nFloorID = GROUND ) 21

67. void UnreserveHero( sHeroName ) 21

68. nCost CalcHeroMoveCost( sHeroName, nX, nY, nFloorID = -1 ) 21

69. void MoveHeroRealTime( sHeroName, nX, nY, nFloorID = -1 ) 21

70. nCount GetHeroCreatures( sHeroName, nCreatureID ) 21

71. void AddHeroCreatures( sHeroName, nCreateureID, nCount ) 21

72. void RemoveHeroCreatures( sHeroName, nCreatureID, nCount ) 21

73. void GiveArtefact( sHeroName, nArtID, nBindToHero = 0 ) 22

74. bool HasArtefact(sHeroName, nArtID) 22

75. void RemoveArtefact( sHeroName, nArtID ) 22

76. void SetHeroLootable( sHeroName, bEnable ) 22

77. bool IsHeroLootable( sHeroName ) 22

78. void MarkObjectAsVisited( sObjectName, sHeroName ) 22

79. sTownName GetHeroTown( sHeroName ) 22

80. sHeroName GetTownHero( sTownName ) 22

81. tsObjects GetObjectsFromPath( sHeroName, nX, nY, nFloorID = -1 ) 23

82. void GiveHeroWarMachine( sHeroName, nWarMachineID ) 23

83. void HasHeroWarMachine( sHeroName, nWarMachineID ) 23

84. bool RemoveHeroWarMachine( sHeroName, nWarMachineID ) 23

85. void StartCombat( sHeroName, sEnemyHeroName, nEnemyIDCount, nCreatureID1, nCount1, nCreatureID2, nCount2, ..., spScriptXDB, sFinishProc, spAdventureFlybySceneXDB = nil ) 23

86. void SetHeroCombatScript( sHeroName, spScriptXDB ) 24

87. void ResetHeroCombatScript( sHeroName ) 24

88. void SiegeTown( sHeroName, spAdvMapTownXDB, spAdventureFlybySceneXDB = nil ) 24

3.2.6 Работа с городами. 24

89. nLevel GetTownBuildingLevel( sTownName, nBuildingID ) 24

90. nLevel GetTownBuildingLimitLevel( sTownName, nBuildingID ) 24

91. nLevel GetTownBuildingMaxLevel( sTownName, nBuildingID ) 24

92. void SetTownBuildingLimitLevel( sTownName, nBuildingID, nLevel ) 24

93. void TransformTown( sTownName, nTownTypeID ) 24

94. void RazeTown( sTownName ) 25

3.2.7 Работа с объектами. 25

95. nX, nY, nFloorID GetObjectPosition( sObjectName ) 25

96. void SetObjectPosition( sObjectName, nX, nY, nFloorID = -1 ) 25

97. void RemoveObject( sObjectName ) 25

98. bool IsObjectExists( sObjectName ) 25

99. bool IsObjectEnabled( sObjectName ) 25

100.void SetObjectEnabled( sObjectName, bEnable ) 26

101.tsObjects GetObjectNamesByType( sObjectTypeSubstr ) 26

102.void AddObjectCreatures( sObjectName, nGreatureID, nCount ) 26

103.nCount GetObjectCreatures( sObjectName, nCreatureID ) 26

104.void RemoveObjectCreatures( sObjectName, nCreatureID, nCount ) 26

105.void SetObjectDwellingCreatures( sObjectName, nCreatureID, nCount ) 26

106.nCount GetObjectDwellingCreatures( sTownName, nCreatureID ) 26

107.void CreateArtifact( sArtName, nArtID, nX, nY, nFloorID ) 26

108.void ShowFlyingSign( spTXT, sObjectName, nPlayerID = -1, nTimeSec = 1.0) 26

109.void CreateMonster( sMonsterName, nCreatureID, nCount, nX, nY, nFloorID, nMonsterMoodID, nCreatureCourageID ) 27

110.void RemoveAllMonsters( nCreatureID ) 27

111.void GenerateMonsters( nCreatureID, nCountGroupsMin, nCountGroupsMax, nCountInGroupMin, nCountInGroupMax ) 27

3.2.8 Работа с интерфейсом. 27

112.void QuestionBox( spTXT, sCallback ) 27

113.void StartDialogScene( spDialogSceneXDB, sCallback = nil, sSaveName = nil ) 28

114.void StartCutScene( sAnimSceneXDB, sCallback = nil, saveName = nil ) 28

115.void MessageBox( spTXT, sCallback = nil, sSaveName = nil ) 28

116.void StartDialogSceneInt( spDialogSceneXDB, sCallback, sSaveName ) 28

117.void StartCutSceneInt( spAnimSceneXDB, sCallback, sSaveName ) 28

118.void MessageBoxInt(spTXT, sCallback, sSaveName) 28

119.void MoveCamera( nX, nY, nFloorID, nZoom = 50, nPitch = pi/2, nYawl = 0, nNoZoom = 0, nNoRotate = 0 ) 28

3.2.9 Функции общей направленности. 29

120.void Loose() 29

121.void Win() 29

122.void BlockGame() 29

123.void UnblockGame() 29

124.void ExitGame() 29

125.number GetDate( nDateID ) 29

126.nX, nY GetTerrainSize() 29

127.number GetMaxFloor() 29

128.bool IsTilePassable( nX, nY, nFloorID = GROUND ) 29

129.string GetAllNames( nFilterCode ) 30

130.void SetWarfogBehaviour( nOnLand, nOnSea ) 30

131.void Trigger(nTriggerType, ...) 30

3.2.10 Работа со звуком. 33

132.nSoundID Play3DSound( spSoundXDB, nX, nY, nFloorID ) 33

133.nSoundID Play2DSound( spSoundXDB ) 33

134.void StopPlaySound( nSoundID ) 33

3.2.11 Работа с освещением. 33

135.void SetCombatAmbientLight( spAmbientLightXDB ) 33

136.void SetObjectFlashlight( sObjectName, sLightID ) 33

137.void SetAmbientLight( nFloorID, sLightID, bFade = false, nTime = 1) 33

3.2.12 Всякие непонятки. 33

138.number CalcAverageMonstersTier() 33

139.tnTier GetGuardsTier( sObjectName ) 33

140.void SetStandState( sStandName, nState ) 34

141.number GetStandState( sStandName ) 34

142.number GetStandStatesCount( sStandName ) 34

143.void StopTrigger() 34

144.void PlayObjectAnimation( sObjectID, sAnimID, nAction ) 34

3.3. Тактический режим. 34

3.3.1 Общее положение дел. 34

3.3.2 Хуки. 35

145.bool DoPrepare() 35

146.bool DoStart() 35

147.bool UnitMove(sUnitName) 35

148.void UnitDeath(sUnitName) 35

3.3.3 Общие функции контроля боя. 36

149.void EnableDynamicBattleMode( bEnable ) 36

150.void EnableAutoFinish( bEnable ) 36

151.void combatEnableFinish( bEnable ) 36

152.void EnableCinematicCamera( bEnable ) 36

153.void SetControlMode( nSideID, nModeID ) 36

154.void Finish( nSideID ) 36

155.void Break() 36

156.void combatSetPause( n1, b1 ) 37

157.void showHighlighting( bShow = true ) 37

3.3.3 Управление юнитами. 37

158.void SummonCreature( nSideID, nCreatureID, nCount, nX = -1, nY = -1 ) 37

159.void AddCreature( nSideID, nCreatureID, nCount, nX = -1, nY = -1 ) 37

160.void addUnit(nCreatureID, nSideID, nX, nY, nCount, sUnitName) 37

161.void removeUnit( sUnitName ) 37

162.void UnitCastAimedSpell( sUnitName, nSpellID, sTarget) 38

163.void UnitCastGlobalSpell( sUnitName, nSpellID ) 38

164.void UnitCastAreaSpell( sUnitName, nSpellID, nX, nY ) 38

165.void SetUnitManaPoints( sUnitName, nPoints ) 38

166.number GetUnitMaxManaPoints( sUnitName ) 38

167.number GetUnitManaPoints( sUnitName ) 38

168.void commandDefend( sUnitName ) 38

169.void commandDoSpell( sUnitName, nSpellID, nX, nY ) 39

170.void commandShot( sUnitName, sVictimName, bDontShowScene = false ) 39

171.void commandMove( sUnitName, nX, nY, bCheckPath = false ) 39

172.void commandMoveAttack(sAttacker, sVictim, nX = -1, nY = -1, bCheckPath = false) 39

173.void commandDoSpecial(sUnitName, nAbilityID, nX = -1, nY = -1, bCheckPath = false) 39

174.void displace( sUnitName, nX, nY ) 40

175.void playAnimation( sUnitName, sAnimType, nActionTypeID = ONESHOT ) 40

176.void combatPlayEmotion( nSideID, n1 ) 40

177.void RemoveAllUnits() 40

178.void setATB( sUnitName, n1 ) 40

3.3.4 Информационные и прочие функции. 41

179.tsUnits GetUnits(nSideID, nType) 41

180.nCreatureID GetCreatureType( sCreatureName ) 41

181.nX, nY pos( sUnitName ) 41

182.bool combatStarted() 41

183.sHeroName GetHeroName( sUnitName ) 41

184.number GetCreatureNumber( sUnitName ) 41

185.nX, nY GetUnitPosition( sUnitName ) 41

186.bool exist( sUnitName ) 42

187.nHostType GetHost( nSideID ) 42

188.nSideID GetUnitSide( sUnitName ) 42

189.number GetUnitType( sUnitName ) 42

190.number GetWarMachineType( sUnitName ) 43

191.number GetBuildingType( sUnitName ) 43

192.sUnitName combatReadyPerson() 43

193.string unitNames() 43

194.void postEvent( sEvent, n1 = -1, n2 = -1 ) 43

3.4. Режим города. 44

3.4.1 Хуки. 44

195.void CreatureHired( nCreatureID, nCount ) 44

196.void HeroHired( sHeroName ) 44

3.4.2 Прочие функции. 44

Про всякое.

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

По ходу дела в тектсте неоднократно поминается «официальное руководство по скриптам». Строго говоря, это никакое не руководство, а просто список функций а описаниями, владельцы версии игры 1.3 могут найти его в файле Editor Documentation\HOMM5_Script_Functions.pdf.

Немножко про LUA.

С полным руководством по языку рекомендуется ознакомиться тут -http://www.lua.org/manual/5.1/. От начала и до пункта 2.6 включительно - дальнейшее не имеет отношения к нашему случаю. Ниже приведенная информация прочтение полного руководства не заменяет.

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

LUA - встроенный (embedded) язык. В связи с чем, речи о чем-либо вроде сишной функции main() не идет.

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

Регистр букв в идентификаторах имеет значение. Т.е. переменные Dummy и dummy - разные переменные.

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

interval = GetGameVar("interval")

interval = interval + 0

sleep(interval)

Функция GetGameVar возвращает строку, а sleep принимает в качестве параметра число. Строчка с прибавлением нуля нужна только для того, чтобы показать интерпретатору, что в дальнейшем мы будем использовать переменную interval как число. Если данную строчку убрать - на вызове sleep получим ругань о несовпадении типов.

Впрочем, подобный подход имеет и массу преимуществ, например, можно написать что-то вроде

local currentSign = 2

local lastSign = "sign"..(CurrentSign-1)

local newSign = "sign"..(CurrentSign+1)

local sign = "sign"..CurrentSign

SetObjectEnabled(newSign, false)

SetObjectEnabled(lastSign, true)

ChangeHeroStat(HERO_NAME, STAT_EXPERIENCE, 500*currentSign)

В данном примере для формирования имени объектов используется операция конкатенации - ".."

Простых типов имеется три - число (обычный double), строка (последовательность октетов в одинарных либо двойных кавычках) и логический (false\true). Есть выделенное значение nil (привет Паскалю). Собственно, значений false и true как таковых в данном интерпретаторе языка нет. Они определены константами в файле /scripts/advmap-startup.lua

true = not nil

false = nil

Данный файл «работает» только на стратегической карте, а для тактических скриптов эти константы определить забыли. Это следует учитывать.

Из сложных типов представлены таблицы - нечто среднее между структурами и массивами. Вообщем, таблицы очень похожи на привычные массивы, за исключением того, что 1) они могут включать разнородные элементы 2) могут индексироваться не только числами. Допустима, например, такая запись:

cam_switch =

{

{ cam = 1, from = 0, to = 3000 },

{ cam = 2, from = 3000, to = 5000 },

{ cam = 1, from = 5000, to = 10000 },

{ cam = 3, from = 10000, to = 16000 },

{ cam = 1, from = 16000, to = 25000 },

}

и, соответственно, обращение из кода

cam_switch[2].cam (или cam_switch[2]["cam"], такая вот забавная форма)

Отдельным интересным вопросом применительно к игре является нижняя граница индексации таблиц в случае числового индекса. С одной стороны, таблицы, заданные непосредственно в теле скрипта (как выше) по умолчанию индексируются от единицы, с другой – большинство таблиц, возвращаемых функциями наподобие GetPlayerHeroes, имеют нижней границей ноль. «Умом это н<

Наши рекомендации