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

Общие функции.

string GetGlobalGameVar( strVal, strDefault )

Достает значение глобальной переменной с именем strVal. Если таковой нет - возвращает значение strDefault. Значения глобальных переменных сохраняются в сейве игры, собственно, потому они и называются глобальными. Пример иллюстрирует небольшой нюанс, связанный скорее, не с данной функцией, а с механизмом конвертации типов, использованном в данном диалекте Lua:
local prob = GetGlobalGameVar( "Probability", "0" );
prob = prob + 0;
if(random(100)<prob) then 
-- что-то делаем
end
Если убрать строчку с прибавлением к prob нуля, то фрагмент работать не будет - выдаст ошибку на сравнении, типа, "пытаешься ты, дорогой друг, сравнить строку с числом. Нифига делать не буду".

SetMaxCriticalSeverity( nSeverity )

"Устанавливает cheat Severity для всех критических поражений (и наших, и вражеских)". Говоря по простому, чем меньше число nSeverity, тем меньше вероятность того, что очередной выстрел приведет к критикалу. Не следует думать, что после вызова SetMaxCriticalSeverity(0) критикалов не будет вообще. Вероятность словить критикал все равно есть. Хотя небольшая. Рекомендуется использование данной функции в роликах, например, когда бьют морду главному герою. Так:
SetMaxCriticalSeverity(0)
UnitShoot( sniper, GetHero(), HL_ANY )
WaitForUnit(sniper)
SetMaxCriticalSeverity(1000)

WaitForInterface()

Эта функция есть только в руководстве по редактору. В системе ее нет. И не мудрено - для "приостанавления выполнения скрипта до завершения движения камеры" надо пользовать WaitForUI, так:
WaitForUI( CameraMove( T2V, 3000 ) )
а "приостанавления выполнения скрипта до завершения диалога" ее же, так:
WaitForUI( DialogPlay( Dialog ) )

GetCurrentZoneAILevel()

Функция присутствует только в том случае, если загружена "сценарная" зона. Реальный уровень врагов на карте будет равен возвращаемой величине плюс значение поля AILevel из соответствующей строки таблицы DifficultyConstants.

Работа с юнитами.

DividedDeploy( prefix = "" )

Размещает партию по вейпоинтам вида "UnitHero[prefiх]", "Unit1[prefiх]" и т.д. Т.е. вызов DividedDeploy( "_cd" ) приведет к расстановке партии по вейпоинтам с именами "UnitHero_cd", "Unit1_cd", ... "Unit6_cd". Иногда бывает полезно подождать расстановки партии, так:
DividedDeploy("_cd")
WaitForGroupRoute(GetParty())

unit CreateUnit( PersID, PlayerID, name, waypoint, level )

Создание юнита с идентификатором PersID (брать из таблицы RPGPers) изначально принадлежащего игроку PlayerID с именем name по месту waypoint. Юнит будет иметь уровень level. Несколько замечаний по ходу дела:
  • PlayerID = 0 - это Вы. PlayerID = 1 - как правило, противник. PlayerID = 2 - нейтрал или союзник (если есть на карте). Вообще говоря, who is who определяется картой и состоянием diplomacy на ней. Если игрок с указанным номером на карте отсутствует, функция вернет NIL.
  • name служит для дальнейшего обращения к данному юниту из скрипта, если Вы по каким то причинам не сохранили возвращаемое функцией значение. Т.е. можно получить его еще раз, сказав GetUnit(name).
  • Вейпоинт с именем waypoint должен уже существовать на карте. В противном случае функция вернет NIL. В частности, по этой причине возможно использование данной функции в "нетрадиционных" целях - например, определить, есть ли вейпоинт с данным именем на текущей карте, можно так:
    function isExistWaypoint( wname )
    	local strman = CreateUnit( 692, 0, "StremMan", wname, 2 )
    	if(strman~=nil) then
    		UnitRemove( strman )
    		return(true)
    	end
    	return(false)
    end
    
    Следует заметить, что по каким-то одним разработчикам ведомым причинам юниты, созданные данной функцией появляются на карте частенько в положении "лежа". Поэтому имеет смысл следующий фрагмент:
    enemy = CreateUnit( 692, 1, "enemy" , "w1", 2 ) 
    if(enemy) then
    	UnitSetPose( enemy, POSE_RUN )
    	WaitForUnit(enemy)
    	UnitSetNormalLogic( enemy )
    end
    
    Последний оператор выставляет юниту нормальную логику поведения, дабы он не стоял, как болван.

    UnitShootPrepare( unit, target, location )

    Согласно руководству, вызов данной функции приводит к прицеливанию юнита unit в юнит target. Location определяет, куда производится прицеливание, и может быть одной из HL_ констант (т.е. HL_ANY, HL_BODY,HL_HEAD,HL_RHAND,HL_LHAND,HL_RLEG или HL_LLEG). На самом деле производится не прицеливание, а эмуляция выстрела, т.е. расходуется патрон из обоймы и вызывается обработчик OnShotAtUnit. В случае, если у юнита активно огнестрельное оружие, вреда жертве не причиняется. Если же юнит вооружен холодным оружием (или вообще не вооружен) последует полноценный удар, т.е. в данном случае эта функция эквивалентна UnitShoot( unit, target, location ). Такие же последствия будет иметь применение данной функции к юниту, вооруженному тяжелым оружием, т.е. гранатометом.

    UnitActivateWeapon( unit, bActivate )

    Обнажить или спрятать ствол. Только анимация, на состояние слотов и инвентаря данная функция не воздействует. Холодное оружие доставаться/прятаться не будет.

    UnitDrawWeapon( unit )

    Обнажить ствол. Если в инвентаре есть огнестрельное оружие, то оно будет помещено в активный слот, а содержимое активного слота, соответственно, переместится в инвентарь. "Предметы в слотах не учитываются", как заботливо сообщает нам руководство. В переводе на русский язык это означает, что если оружие лежит не в инвентаре, а в слоте (активном или неактивном), и другого огнестрела у юнита нет, то ничего делаться не будет. Немного пофиксить это можно с помощью функции CreateAndActivateItem( unit, nRPGItemID ), которая создает предмет и помещает его в активный слот. В качестве побочного эффекта она так же выбивает предметы из обоих слотов в инвентарь. Выглядит это примерно так:
    function trueDrawWeapon( unit )
    	CreateAndActivateItem( unit, 123 ) -- это легкая граната
    	WaitForUnit(unit)
    	DestroyItemInHand(unit) -- уничтожаем ее, она свою функцию выполнила
    	UnitDrawWeapon( unit )
    	WaitForUnit(unit)
    end
    
    Если места в инвентаре нет, то будем иметь неприятности. Холодное оружие обнажить нельзя.

    DestroyItemInHand( unit )

    Уничтожить предмет в активном слоте персонажа.Практически единственная возможность отобрать у юнита что-либо. Можно еще пользовать функцию ItemRemove( item ), но чтобы ее использовать надо этот самый итем сначала получить. Что бывает проблематично, если предмет не имеет собственного имени. (Функция item FindItem( itemID ) ищет предмет на карте, она не учитывает инвентарь и слоты юнитов.)

    UnitMoveToWaypoint( unit, waypoint )

    "Персонаж идёт к точке c именем waypoint". Ну, реально, ползет или бежит, а иногда и вообще никуда не двигается :) Последнее скорее всего говорит о том, что
  • Вы отключили AI в консоли. Т.е. сказали game_noai=1. Этого делать нельзя, т.к. сценарные ролики в данном режиме будут подвисать.
  • Юнит в принципе не может пройти в данную точку.
    Иллюстрация работы:
    enemy = CreateUnit( 692, 1, "enemy" , "w1", 2 ) 
    if(enemy) then
    	UnitSetPose( enemy, POSE_RUN )
    	UnitSetWishPose( enemy, POSE_RUN )	
    	WaitForUnit(enemy)
    	UnitSetNormalLogic( enemy )
    	UnitMoveToWaypoint( enemy, "center" )
    	WaitForUnitRoute( enemy )
    end
    
    UnitSetWishPose используется, чтобы указать юниту позу, в которой он будет передвигаться. WaitForUnitRoute - ждем, пока юнит не достигнет точки назначения. Если в принципе не может достичь, или отключен AI, то скрипт на этом месте повиснет. Вообще говоря, WaitForUnitRoute разумно использовать только в роликах.

    Работа с группами юнитов

    int GroupGetSize(group)

    Казалось бы - элементарная функция, возвращает размер группы. Однако, реально данная функция возвращает вовсе не текущий размер группы. При подсчете учитываются убитые, и находящиеся без сознания юниты - поэтому, если надо получить количество способных вести бой, то надо пользовать GroupCanFightSize( group ), или, если хочется получить количество только живых юнитов в группе - писать функцию самому. Так:
    function GroupLiveSize( group )
            local summ = 0
            for I = 0, GroupGetSize( group ) - 1 do
                    local unit = GroupGetUnit( group, I )
                    if UnitIsDead( unit)~=true then
                            summ = summ + 1
                    end
            end
            return summ
    end
    

    Обработчики событий.

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

    OnEnterZone()

    Вызывается после загрузки зоны по истечении одного временного сегмента, т.е. 1/20 секунды. Лично мне непонятно, где это можно использовать. И чем использование данного обработчика отличается от простой последовательности операторов в теле скрипта.

    OnExit()

    Вызывается, когда пользователь нажмет кнопку "Выход". Реакцию надо прописать самому, т.к. иначе с зоны будет не выйти. Стандартная реализация с запросом диалога "Хотите ли Вы покинуть зону" выглядит так:
    function OnExit()
    	ShowLeaveZoneDialog( 16837 )
    end
    
    Можно и без всяких вопросов:
    function OnExit()
    	ExitToChapter()
    end
    

    OnRealExit()

    Вызывается, если в упомянутом выше диалоге пользователь таки нажал кнопку "Да". Реакцию надо прописать самому, т.к. иначе с зоны будет не выйти. Стандартная реализация:
    function OnRealExit()
    	ExitToChapter()
    end
    
    Обработчик можно использовать для простановки целей и задач, у которых указан таг "Completed by script" в состояние "выполнено", и выдачи улик.
    function OnRealExit()
    	ScenarioSetGoalComplete( 279, true )
    	ScenarioGiveClue( "Defend_Informer", true, false)
    	Sleep( 5 )
    	ExitToChapter()
    end
    

    OnShotAtUnit( unit )

    Вызывается "если кто-то выстрелил в персонажа", как написано в руководстве. Вообщем, не так это. И не только "выстрелил", но и "прицелился" (в смысле, функция UnitShootPrepare также вызывает данный обработчик) и не обязательно в персонажа. По началу, я предполагал, что данный обработчик можно использовать для отслеживания состояния diplomacy - типа, напал на нейтрального, он это дело распознал и стал враждебным. Однако, жестоко обломился. Данный обработчик НЕ вызывается при работе холодным оружием и кулаками. Надо сказать, что холодное оружие вообще стоит особняком в игре - его невозможно обнажить, например, на него не реагирует данный обработчик, функция UnitShootPrepare если у юнита в руках нож приводит не к подготовке, а к реальному удару и т.п. Но что-то я увлекся. Проблему пришлось решать, запуская поток, который отслеживает состояние здоровья юнитов нейтрала, и выставляет diplomacy в случае необходимости. Так:
    neitralDiplomacyChanged = false
    
    function changeNeitralDiplomacy() 
    	neitralDiplomacyChanged = true
    	SetDiplomacy( 0, 2, DS_ENEMY )
    	SetDiplomacy( 2, 0, DS_ENEMY )
    	local group = PlayerGetUnits(2)
    	local sz = GroupGetSize(group)
    	for i = 0, sz-1 do
    		local unit = GroupGetUnit(group,i)
    		UnitSetNormalLogic(unit)
    	end
    end
    
    function checkNeitrals()
    	while(neitralDiplomacyChanged~=true) do
    		local group = PlayerGetUnits(2)
    		local sz = GroupGetSize(group)
    		for i = 0, sz-1 do
    			local unit = GroupGetUnit(group,i)
    			if(UnitGetSkill( unit, ST_VP ) ~= UnitGetSkillMaxValue( unit, ST_VP )) then
    				changeNeitralDiplomacy()
    				break
    			end
    		end	
    		Sleep(5)
    		if(sz==0) then
    			break
    		end
    	end	
    end
    
    StartThread(checkNeitrals)
    
    Нарекания на предыдущий кусок - гражданские станут враждебными даже если кто-то из них пострадал в результате действий противника. Ну и разумеется, предполагается, что гражданские - это игрок с номером 2, и этот игрок на карте присутствует.

    OnPlayerLose( bScenarioLose )

    Вызывается, в случае, если главный герой помер или без сознания (bScenarioLose=false) либо сценарий провален по другой причине (bScenarioLose=true). Стандартная реализация обработчика выглядит так:
    function OnPlayerLose( bScenarioLose )
    	if bScenarioLose == 1 then
    		ShowLoseDialog( 20985 )	-- убит юнит, являющийся ключевой уликой
    	else
    		ShowLoseDialog( 20984 ) -- главный герой убит или без сознания
    	end
    end
    
    Соответственно, если убрать вызов ShowLoseDialog(20984), то игру можно будет продолжить и без главного героя.

    OnTalk( HeroUnit, TargetUnit )

    Очередной перл от руководства : "Когда главный герой хочет поговорить с персонажем TargetUnit". Нет, ребята. Никакой не главный герой. А произвольный член партии с юнитом, у которого предварительно было выставлено UnitSetCanTalk( unit, true ) или UnitSetDialog( unit, dialogCode ). Пример:
    function OnTalk( who, target )
    	if(IsEqual( who, GetHero() )) then
    		WaitForUI( DialogPlay("testDlg") )
    		UnitSetCanTalk( target, false )	
    	else
    		UnitSayAck( who, 113 ) -- почекаэм, пока главный ни прийдэ
    	end
    end
    

    Про прочие обработчики как то

  • OnClickUsable( unit, object )
  • OnCloseObject( unit, object )
  • OnOpenObject( unit, object )
  • OnStartTurn( player )
  • OnMineTriggered( victim )
  • OnOpenInventory()
  • OnCloseInventory()
  • OnUnitNeedCommand( unit )
  • OnDialogFinished( code )
  • OnDialogPhrase( code, nPhrase )
  • OnMineDiffusion( object, success )

    сказать мне особо нечего, они достаточно ясно (хоть и излишне кратко) описаны в руководстве. В принципе, есть еще один неописанный обработчик - OnScriptNotify( sID, nParam ). Он используется в движке игры для узкоспециализованной цели - перелистывать страницы папок на экране выбора сценария. Можно ли его приспособить еще к чему-то мне не известно.


    К оглавлению
  • Hosted by uCoz