Как сидеть на двух стульях. Совмещаем 1.26 и Reforged.

Добавлен , опубликован
Раздел:
Разное
Цель этой статьи собрать различные техники и фишки, позволяющие создать мультиверсионную карту полноценно работающую на клиенте с любым актуальным патчем (в частности, 1.26, 1.31, 1.32+ Reforged SD/HD). Показать, как внедрять механики из последних патчей, не теряя совместимости со старым клиентом.
Если у вас есть замечания по оптимальности описанных методов или другие предложения, то обязательно напишите об этом. Будем вместе дополнять статью.

Оглавление

Основы. Узнаём версию клиента

Всё описанное далее актуально для создания мультиверсионных карт в редакторе патча 1.26. Карты, созданные в более новых патчах, не будут работать в старых клиентах.
Для начала, полезно будет узнать текущую версию клиента (1.32, 1.31 или более старая версия). Для этого используются следующие функции:
//True, если версия клиента 1.32+
function Is132OrNewer takes nothing returns boolean
   return GetLocalizedString("REFORGED") != "REFORGED"
endfunction

//True, если версия клиента 1.31+
function Is131OrNewer takes nothing returns boolean
   return GetLocalizedString("WINDOW_MODE_WINDOWED") != "WINDOW_MODE_WINDOWED"
endfunction
Также, при игре на 1.32+ Reforged важно знать текущий графический режим (SD или HD). Для этого можно воспользоваться фактом, что у некоторых аудиофайлов разная длина в этих двух версиях:
function IsHD_Default takes nothing returns boolean
	return GetSoundFileDuration("sound\\buildings\\death\\ancientuprootdeath1.flac")==4631
endfunction
Тем не менее, предпочтительнее использовать вместо стандартного аудиофайла заранее импортированную пару. Это связано с тем, что некоторые игроки ставят модификации на замену аудиофайлов (в частности, очень популярен мод на "Возвращении старой русской озвучки"). И этот мод конфликтует с этим методом. Импортируйте файл_1 с полным путём "Check_SD_HD.mp3" и файл_2 с полным путём "_HD.w3mod\Check_SD_HD.mp3".
//True, если у локального игрока включена HD графика
function IsHD takes nothing returns boolean
	return GetSoundFileDuration("Check_SD_HD.mp3")==144
endfunction
Внимание! При игре по сети возможно, что у разных игроков будет включён различный режим графики. Используйте только локальный код (без создания новых объектов) при проверках на графический режим.
function GetVersion takes nothing returns nothing
	local string s
	if Is132OrNewer()      then
	    set s="1.32+ Reforged"
	elseif Is131OrNewer()  then
	    set s="1.31"
	else
	    set s="1.26-1.30.2"
	endif
	set s="Current version - "+s
	 if IsHD() then
        set s=s+" (HD)"
    else
        set s=s+" (SD)"
    endif
	call DisplayTextToPlayer(GetLocalPlayer(),0,0,s)
endfunction
Карта-пример (12.6 КБ) с определением версии игры и результаты её запуска на разных клиентах:

Импорт HD моделей. Альтернативные модели

Для импорта HD-версий моделей достаточно добавить к полному пути префикс "_HD.w3mod\". Аналогично импортируются текстуры.
При замене стандартных моделей стоит учесть, что у некоторых юнитов в HD режиме меняется путь к модели (поле file в Редакторе Объектов) и масштаб (scale), в случаях, если в эти поля не были внесены изменения. Используя этот факт можно проводить замену моделей только для одного из режимов, используя в другом версию по-умолчанию. К примеру, юнит Мечник (hhes) в SD режиме имеет модель человеческого капитана "units\human\TheCaptain\TheCaptain.mdx", а в HD эльфийского солдата "Units\Human\HighElfSwordsman\HighElfSwordsman.mdx".
Если же в поле масштаба внести изменение, то может получиться так, что юниты со стандартными моделями получаются сильно несоразмерными в SD и HD режимах. Это связно с тем, что несоразмерен масштаб самих моделей.
И изменение поля scale при переходе из SD в HD как раз компенсировало эту несоразмерность. В этих случаях можно задать необходимый размер юнита в коде карты. Это не вызывает рассинхронизацию.
function Rescale takes unit u, real sclSD, real sclHD returns nothing
	local real scl
	if IsHD() then
		set scl=sclHD
	else
		set scl=sclSD
	endif
	call SetUnitScale(u,scl,scl,scl)
endfunction
Карта-пример (4.15 МБ) с новой импортированной SD и HD моделью; только с изменённой SD моделью; с изменением масштаба юнита в зависимости от режима графики:

Исполнение новых функций через Preloader

Внимание! Описанный ниже метод подходит только для версии 1.32+ Reforged. Он не работает для 1.31.
К версии 1.32+ Reforged в Warcraft было добавлено много новых native-функций. Но при попытке обратиться к ним напрямую в редакторе 1.26 происходит ошибка компиляции (так как они не описаны в текущих common.j и blizzard.j). Обойти эту ошибку можно использовав следующую команду:
call Preloader("CustomPreloader.pld") 
Эта команда выполняет функцию PreloadFiles() из импортированного и предварительно созданного файла предзагрузки "CustomPreloader.pld" (название файла произвольно). Воспользуемся здесь тем свойством этой команды, что при синтаксической ошибке в этой функции она просто игнорируется, а не вызывает вылет игры с ошибкой. Использовать эту команду можно в любой момент игры. Разрешено иметь несколько различных .pld-файлов.
Внутри функции PreloadFiles() нельзя ссылаться на пользовательские функции из карты, а также на глобальные переменные. Разрешено использование локальных переменных.
В качестве примера, используем функцию BlzSetAbilityIcon() для изменения иконки способности Самопожертвование (Adtn) у Светлячка, когда игра запущена в версиях 1.32+ Reforged. С помощью блокнота создаём новый файл с названием и разрешением "CustomPreloader.pld" и вставляем туда следующий текст:
function PreloadFiles takes nothing returns nothing
	call BlzSetAbilityIcon('Adtn',"ReplaceableTextures\\CommandButtons\\BTNSacrifice.blp")
endfunction
Импортируем этот .pld-файл в карту. Вызываем выполнение этой команды через call Preloader("CustomPreloader.pld").
Карта-пример (13 КБ) с вызовом новой native-функции через Preloader:

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

Для работы с фреймами в рамках мультиверсионной карты воспользуемся тем свойством функции Preloader(), что при вызове она наследует поток исполнения. И хотя внутри функции PreloadFiles() мы не можем обратиться к глобальным переменным или другим пользовательским функциям, но через функцию GetTriggeringTrigger() разрешено получить триггер, вызвавший исходный поток.
В качестве примера создадим кнопку, выводящую всем игрокам простой текст по нажатию.
Подготовим файл ButtonExample.pld, в котором будет создаваться элемент-кнопка
function PreloadFiles takes nothing returns nothing
	local trigger trig = GetTriggeringTrigger()
	local framehandle mainButton =BlzCreateFrame("ScriptDialogButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI,0), 0,0)
	call BlzFrameSetSize(mainButton, 0.12,0.05)
	call BlzFrameSetAbsPoint(mainButton, FRAMEPOINT_CENTER, 0.3,0.3)
	call BlzFrameSetText(mainButton, "Hello, world!")
	call BlzTriggerRegisterFrameEvent(trig, mainButton, FRAMEEVENT_CONTROL_CLICK)
	set trig=null
	set mainButton=null
endfunction
В коде карты прописываем функцию ButtonTrigger_Init():

globals
	trigger udg_trig
	triggeraction udg_trigact
endglobals

//Функция с действиями по нажатию кнопки
function ButtonTrigger_ClickAction takes nothing returns nothing
	call DisplayTextToPlayer(GetLocalPlayer(),0,0,"Hello, world!")
	call Preloader("ButtonResetFocus.pld")	//Исправление залипания фокуса на кнопке после нажатия
endfunction

//Функция создания кнопки
function ButtonTrigger_CreateButton takes nothing returns nothing
	call TriggerRemoveAction(udg_trig,udg_trigact)	//Уберём с триггера действие по созданию кнопки
	set udg_trigact=null
	call Preloader("ButtonExample.pld")
	call TriggerAddAction(udg_trig,function ButtonTrigger_ClickAction)	//Добавим действие по нажатию кнопки
endfunction

//Создаём и запускаем триггер
function ButtonTrigger_Init takes nothing returns nothing
	set udg_trig=CreateTrigger()	//Создаём триггер. В итоге он будет запускаться при нажатии кнопки.
	set udg_trigact=TriggerAddAction(udg_trig,function ButtonTrigger_CreateButton)	//Понадобиться только для первонального создания кнопки.
	call TriggerExecute(udg_trig)
endfunction
Дополнительно, для исправления фичи (или бага?) с залипанием фокуса на кнопке после нажатия, создадим и импортируем файл ButtonResetFocus.pld:
function PreloadFiles takes nothing returns nothing
	call BlzFrameSetEnable(BlzGetTriggerFrame(), false)
	call BlzFrameSetEnable(BlzGetTriggerFrame(), true)
endfunction
Карта-пример (15 КБ) с действующей кнопкой:

Превью и миникарта

Обычной проблемой карт созданных в редакторе 1.26 является неотображение картинки-превью в Reforged, так как стандартный метод с импортом файла "war3mapPreview.tga" не работает в новом клиенте. Роль превью теперь здесь играет изображение игровой карты, которое можно задать импортом файла с именем и форматом "war3mapMap.dds".
Далее, для того, чтобы сделать обратную замену preview на истинное изображение карты, при инициализации вызывается функция BlzChangeMinimapTerrainTex(). В картах, сделанных в старых редакторах, это можно сделать через загрузку соответствующего .pld-файла.
С помощью блокнота создаём новый файл с названием и разрешением "ChangeMinimap.pld" и вставляем туда следующий текст:
function PreloadFiles takes nothing returns nothing
	call BlzChangeMinimapTerrainTex("war3mapMapTrue.dds")
endfunction
Здесь "war3mapMapTrue.dds" - полный путь к файлу с истинным изображением игровой карты, который необходимо дополнительно импортировать (название файла произвольно). Файл "ChangeMinimap.pld" сохраняем и импортируем в карту. Добавляем "Preloader("ChangeMinimap.pld")" в функцию инициализации:
function Trig_MapInit_Actions takes nothing returns nothing
    call Preloader( "ChangeMinimap.pld" )
endfunction
Карта-пример (69 КБ) с рабочей миникартой и превью в разных версиях клиента:

Анимируем портреты в Reforged HD

Одной из проблем при запуске старых карт в Reforged HD является отсутствие эффекта движения губ во время роликов у портретов юнитов со стандартными HD моделями. Это связано с тем, что в Reforged HD предусмотрена синхронизация движения губ и воспроизведения звуковых дорожек. Отсутствие как самих специальных файлов с набором движения губ, так и привязки их к соответствующим звуковым дорожкам, приводят к тому, что юниты вообще перестают открывать рты, даже если параллельно идёт озвучка.
Оставим за рамками этой статьи описание способа, как самостоятельно создать файл, описывающий правильное движение губ для конкретных аудиодорожек (например, с помощью пакета FaceFX Studio). Но покажем, как можно подключить произвольную (не связанную с аудиодорожкой) анимацию движения губ, используя редактор 1.26.
За основу порядка движения губ возьмём набор анимации Акамы из 5 миссии компании Альянса из The Frothen Throne. Эта анимация длиться довольно долго (41.2 с в русской версии и 34.7 с в английской) и она совместима со всеми основными гуманоидными портретами.
Создаём в блокноте и после импортируем в карту файлы conversation.json (название файла строгое) и LipsStart.pld (название файла произвольно) со следующим содержанием:
{
    "stringTablePath": "war3map.wts",
    "conversation": {
        "All_conversations": [          
	    {
                "conversationOrder": 0,
                "animationLabel": "A05Akama11",
                "animationGroupLabel": "Map-DranaiAkama",
                "animationSetFilepath": "sound\\dialogue\\faceanimation\\humanx05\\facialanimation\\dranaiakama.animset_ingame",
                "animationSetFilepathMapRelative": true
            }
        ]
    }
}
function PreloadFiles takes nothing returns nothing //41.225 - Rus; 34.709 - Eng
local sound soundhandle=CreateSound("sound\\ambient\\doodadeffects\\elevator.flac", false, false, false, 10, 10, "DefaultEAXON")
call SetSoundFacialAnimationLabel(soundhandle, "A05Akama11")
call SetSoundFacialAnimationGroupLabel(soundhandle, "Map-DranaiAkama")
call SetSoundFacialAnimationSetFilepath(soundhandle, "sound\\dialogue\\faceanimation\\humanx05\\facialanimation\\dranaiakama.animset_ingame")
call SetSoundChannel(soundhandle,36)
call SetSoundVolume(soundhandle,0)
call StartSound(soundhandle)
call KillSoundWhenDone(soundhandle)
set soundhandle=null
endfunction
Здесь за основу soundhandle может быть выбран любой звук произвольной длины. Если нужно добавить аудиодорожку с реальной озвучкой персонажа, то её можно запустить параллельно используя другой звуковой канал. Теперь при фоновом запуске звука soundhandle (через функцию Preloader("LipsStart.pld") ) текущий отображаемый портрет начнёт шевелить губами.
call CinematicModeBJ( true, GetPlayersAll() )
call DoTransmissionBasicsXYBJ('hrif', GetPlayerColor(Player(0)),0, 0, null, "Sniper", "Hello, Worker!", 7.0)
call Preloader( "LipsStart.pld" )
call PolledWait( 7.00 )
call DoTransmissionBasicsXYBJ( 'hpea', GetPlayerColor(Player(1)),0, 0, null, "Worker", "Hello, Sniper!", 7.0)
call Preloader( "LipsStart.pld" )
call PolledWait( 7.00 )
call CinematicModeBJ( false, GetPlayersAll() )

Два стула и табуретка. Совместимость с патчем 1.31

Хотя клиент 1.31 пользуются меньшей популярностью чем 1.26 или актуальный Reforged, здесь есть своя ненулевая аудитория. Если вам нужна совместимость с этим патчем, необходимо учесть некоторые особенности.
В 1.31 существует баг, проявляющийся после загрузок сохранённых игр. В этом случае перестают работать почти все раннее добавленные события триггеров типа Specific Unit Event (EVENT_UNIT_SPELL_EFFECT, EVENT_UNIT_USE_ITEM и т.п.) Из подобных остаются работать всего несколько (EVENT_UNIT_DAMAGED, EVENT_UNIT_DEATH).
Если ваша карта не односессионная, то необходимо либо использовать только события типа Generic Unit Event (EVENT_PLAYER_UNIT_SPELL_EFFECT, EVENT_PLAYER_UNIT_USE_ITEM и т.п.). Либо отслеживать событие загрузки игры (EVENT_GAME_LOADED) и добавлять заново потерянные события.
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
0
22
2 года назад
0
Bergi:
На мемхаке просто делаем сейв через сервер 🐍
0
10
2 года назад
0
сейв код это будущее
Это 2008 год
0
32
2 года назад
0
Vladimir TVK, 2001, но вряд ли вы понимаете вообще о чем речь
1
10
2 года назад
1
Vladimir TVK, 2001, но вряд ли вы понимаете вообще о чем речь
Не знаю, шла речь про сейв-коды, которые ты ручками вводишь, или про прелоад. В любом случае, сомнительное будущее)

Нда, танцев с бубном довольно много, но решения забавные. Выглядит, правда, не очень удобно.
Я скорее думал о том, что было бы неплохо иметь плагин для редактора, позволяющий "компилировать" карты под ту или иную версию игры. То есть, например, при целевой 1.26а заменять новые "Bliz" функции на аналогичные из мемори хака.
3
8
2 года назад
3
Огромная работа проделана. Фантастическое расследование.
2
14
2 года назад
2
Пишу комментарий в поддержку автора, так как будущее еще не скоро увы наступит, а потому такие костыли будут людям нужны.
6
29
2 года назад
Отредактирован Волчачка
6
  Это годное, что я видел. Статья хорошо написана, что даже поймёт новичок. Автор не только заботиться о читателе, но и об начинающем картодеятеле иже мапмейкере. Также, мне понравилось оформление статьи, где всё нейтрально и просто, и нет злоупотребления форматированием. И ещё – статья затрагивает важный вопрос, касающийся текстуры-изображения предпросмотра для всех версий WarCraft III от классики до Reforged.
 Держи сердечко(like). Даю оценку статье: 10 / 10.
1
37
2 года назад
1
Эх, хорошая статья, но как бы хотелось, чтобы кто-то бы подсказал удачный способ работы с моделями, без использования серьезных 3д-редакторов...
Подредактировал лого
3 комментария удалено
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.