- Joined
- Jan 26, 2019
- Messages
- 90
YDWE: Boost and Lua Preprocessors
Boost and Lua preprocessors are extremely useful advantages of the YDWE map editor
They make it easier to write Jass code, automate various processes and much more.
The more you know about how they work and your own imagination, the more applications you can find for these two tools.
First of all, I want to mention that these tools were never intended for ordinary users, they were discovered and studied by me personally.
In this regard, you should be aware that using them may not be easy, especially if we are talking about the Lua preprocessor.
For this, it is advisable to understand Jass very well and have at least average skills in Lua.
In the case of the Boost preprocessor, everything is much simpler, even a person who understands only the GUI can use the most basic of its capabilities.
So, let's get started.
Preprocessors process your code at the moment the map is saved, allowing you to change the final result.
You can say that you are giving someone instructions on how your map code should change after saving.
Example:
You write some text for work and make notes in it "Delete all next text"
Or "Insert a link to the site here"
After that, your personal assistant checks the text and carries out your instructions.
The final result will look like this:
This is the job of the preprocessor.
That is, preprocessors can only work until the moment the map is saved, during the game launch (Run Time) it will not work,
since at this moment the preprocessor commands no longer exist, they have already been replaced by your instructions.
[About Boost preprocessor]
It is important to know that it processes code before the Lua preprocessor, which allows you to effectively combine them with each other.
Boost preprocessor commands, or "directives", always begin with the "#" sign.
The first and simplest directive is: #define
It declares a specific macro that can then be used.
First comes the directive itself, then the keyword, then the value.
For example, if we use somewhere in the code: #define _MyTest "Hello World!"
Then after that, anywhere in the code, the keyword _MyTest after saving the map will be replaced with "Hello World!"
Example:
Before saving the map:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, _MyTest)
After:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "Hello World!")
The most obvious ways to use macros are to optimize your code and make it look nicer.
For example, you can use macros for object ids:
#define _AbilId_Invul 'Avul'
Or for proper management of hash table keys:
#define _Hash_BattleRhapsody 0
#define _Hash_Gloria 1
#define _Hash_RageTrigger 2
This way we get rid of a useless function. "StringHash" and we get:
1) Optimization compared to "StringHash"
2) Full control over keys and impossibility of key "collision".
3) More pleasant appearance of the key, even in comparison with "StringHash"
Besides this, there is another very pleasant advantage.
If you place a custom object in a macro, like this: #define _AbilId_FireRage 'A001'
So if you delete an object/change its id, you won't have to update anything in the code, you'll only need to replace the value in the macro _AbilId_FireRage to the new.
Of course, you can use global variables for such things, but why create unnecessary global variables when you can avoid it and reduce the code size.
I recommend that you always start the name of a macro with an underscore "_"
This is so that you can always clearly identify what is a macro in your code, so you never get confused.
You can also use #define as a function, because you can pass arguments to a macro.
For example, here's how a pseudo-function "print" is made in YDWE PK
#define print(s) call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, s)
We take the string "s" from the macro and pass it to the actual function.
Note that this means we don't even have to write "call" before the function call, because "call" is already included in the macro.
Or here's a more complex example.
#define SetUnitInvulnerable_true(unit) UnitAddAbility(unit, 'Avul')
#define SetUnitInvulnerable_false(unit) UnitRemoveAbility(unit, 'Avul')
#define SetUnitInvulnerable(unit, boolean) SetUnitInvulnerable_ ## boolean(unit)
The point is that we replace the standard function "SetUnitInvulnerable" with adding/removing the ability 'Avul' depending on what boolean argument was used in the function "SetUnitInvulnerable"
One macro is replaced by another and in the end one of the two real functions "UnitAddAbility" or "UnitRemoveAbility" will be used
Why is this done?
In wc3 1.26 there is no function to check the invulnerability of a unit, but you can check for the presence of an ability that gives invulnerability.
If you did not know this before, then you will have to search and replace all "SetUnitInvulnerable" with analogs "UnitAddAbility" "UnitRemoveAbility" manually, which can take some time.
But with the help of the Boost preprocessor, you can solve this problem automatically.
You can also leave the macro without value: #define _TestMode
This is necessary so that it can be determined whether a specific macro exists or not.
Now let's talk about conditional directives.
#if comparison of define values and simple values
#ifdef check what to do if macro exists
#ifndef check what to do if macro does not exist
#else same as else in JASS
#elif - same as elseif in JASS
#endif - endblock, same as endif in JASS
One example of use is to enable test mode for your map.
Example:
#ifdef _TestMode
call PK_TestCommandsActivate()
#endif
When I have a macro in my map _TestMode
When saving the map, the PK_TestCommandsActivate() function will be inserted into the code, which activates the command test.
But if I delete or comment out the macro, the function will not be inserted into the final map code.
There is also a #undef directive, which allows you to delete the macro starting from this point.
One of the most useful directives is #include, it allows you to insert code into the map from external files.
Example: #include "C:\Users\user\Desktop\MyJassCode.j"
This allows you to work with the code in any external editors, such as Visual Studio Code, and create any convenient code sorting for yourself.
In addition, this method protects the code from critical errors, because if you write code in the World Editor and a critical error occurs during saving, you will lose all progress until the last save.
You can also use any directives inside the imported code, including #include to create dependencies and import code one by one from different files.
And that's not all, the full functionality can be found on the Internet by the keywords "Boost preprocessor"
[Now about Lua preprocessor]
There's not really much to say here, because it follows the rules of regular Lua, but there are some quirks.
Lua preprocessor code should be inside <? ?> blocks, where "<?" is the start block and "?>" is the end block.
Example:
The following code will open the YDWE PK discord server in your browser when you save the map. (without space)
<?
os.execute('explorer "https:// discord.gg/sVkB27JwG8"')
?>
If you want to insert a value from Lua code into the final Jass code, you need to add the "=" sign like this:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "<?="Test"?>")
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, I2S('<?="Test"?>'))
As you can see, the Lua preprocessor works even inside Jass quotes, unlike Boost preprocessor directives.
Of course you can use Lua functions.
<?
function LuaTestFunc()
return math.random(5, 10)
end
?>
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, I2S(<?=LuaTestFunc()?>))
Another feature of the Lua preprocessor is that you can create objects not through the object editor but using Lua code.
Example:
<?
local slk = require 'slk'
local o = slk.item.afac:new('it01')
o.Name = 'Golden Ring'
o.ubertip = ''
o.abilList = nil
o.description = nil
o.tip = nil
?>
This code will create an item with id 'it01' based on item 'afac'
After that you can change its parameters, the parameter names can be found in the object editor by pressing Ctrl + D.
Note that if you create a unit, the first character in its new Id is responsible for whether the unit will be a hero or not. A capital character means that it is a hero.
Another example:
<?local Misc_DefenseArmor = tonumber(require('slk').misc.Misc.DefenseArmor)?>
This is how we get the value of the game constant "DefenseArmor"
Unfortunately, I haven't studied these methods enough yet, so for now I only know how to get the value of the constants, but I can't change them.
And I also know that you can only change those objects that you created using Lua code. (unit/item/destructable/doodad/ability/upgrade)
In addition, you can't change the parameters of an object created using Lua code manually in the object editor, because they will be replaced by those specified in the Lua code.
What are the possible uses for this?
Personally, I have been able to implement the following cases so far:
1) Instant replacement of object id if necessary
2) Changing an item's stat automatically changes its actual stat and description.
3) Automatically changes the language in the map (object description/trigger text/etc) after saving, based on my choice. (video above)
4) Custom encryption of strings and other values
5) Ignoring some limitations of wc3 editor
For example, It is known that you can hide the ability icon by setting the buttonpos coordinates: 0,-11
But this can lead to a critical error in some cases, but there is a better method: -2147483648,-2147483648
which does not lead to critical errors.
But you cannot manually set these coordinates in the editor, even by holding down Shift.
However, by creating an object through the preprocessor, you can set these coordinates.
Препроцессоры Boost и Lua являются крайне полезными преимуществами редактора карт YDWE
Они позволяют облегчить написание Jass кода, автоматизировать различные процессы и многое другое.
Чем больше у вас знаний об их работе и собственной фантазии, тем больше применений вы сможете найти этим двум инструментам.
Для начала хочу упомянуть что данные инструменты никогда не были предназначены для простых пользователей, они были обнаружены и изучены лично мной.
В связи с этим вы должны осознавать что использовать их может быть не просто, особенно если речь о препроцессоре Lua.
Для него желательно очень хорошо понимать Jass и иметь хотя бы средние навыки в Lua.
В Случае с препроцессором Boost все значительно проще, самые базовые из его возможностей сможет использовать даже человек который понимает только GUI.
Итак, начнем.
Препроцессоры обрабатывают ваш код в момент сохранения карты, позволяя изменить конечный результат.
Можно сказать что вы отдаете кому-то указания о том, как должен измениться ваш код карты после сохранения.
Пример:
Вы пишете какой-либо текст по работе и делаете в нем пометки "Удали весь текст ниже"
Или "Вставь здесь ссылку на сайт"
После этого, текст проверяет ваш личный помощник и выполняет ваши указания.
Конечный результат будет выглядеть так:
Это и есть работа препроцессора.
То есть, препроцессоры могут работать только до момента сохранения карты, во время запуска игры (Run Time) он не будет работать,
так как в этот момент, команд препроцессора больше не существует, они уже были заменены на ваши указания.
[Сначала поговорим о препроцессоре Boost]
Важно знать что он обрабатывает код раньше чем препроцессор Lua, что позволяет вам эффективно комбинировать их друг с другом.
Команды, или же "директивы" препроцессора Boost всегда начинаются со знака "#"
Первая и самая простая директива это: #define
Она объявляет определенный макрос, который потом можно будет использовать.
Сначала идет сама директива, потом ключевое слово, потом значение.
Например если мы используем где-либо в коде: #define _MyTest "Hello World!"
То после этого в любом месте кода, ключевое слово _MyTest после сохранении карты будет заменено на "Hello World!"
Пример:
До сохранения карты:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, _MyTest)
После:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "Hello World!")
Самые очевидные методы использования макросов, это оптимизация кода и придание ему более приятного вида.
Например можно использовать макросы для id объектов:
#define _AbilId_Invul 'Avul'
Или для правильного менеджмента ключей хеш-таблицы:
#define _Hash_BattleRhapsody 0
#define _Hash_Gloria 1
#define _Hash_RageTrigger 2
Таким образом мы отказываемся от бесполезной функции "StringHash" и получаем:
1) Оптимизацию по сравнению с "StringHash"
2) Полный контроль над ключами и невозможность "коллизии" ключей.
3) Более приятный внешний вид ключа, даже по сравнению с "StringHash"
Кроме этого, есть еще одно очень приятное преимущество.
Если вы поместите нестандартный объект в макрос, например: #define _AbilId_FireRage 'A001'
То в случае если вы удалите объект/измените его id, вам не придется ничего обновлять в коде, вам нужно будет только заменить значение в макросе _AbilId_FireRage на новое.
Конечно, для подобных вещей можно использовать глобальные переменные, но зачем создавать лишние глобальные переменные, когда можно этого не делать и уменьшить размер кода.
Я рекомендую вам всегда начинать имя макроса со знака нижнего подчеркивания "_"
Это нужно для того чтобы вы всегда могли точно определить что в вашем коде является макросом, таким образом вы никогда не запутаетесь.
Вы так же можете использовать #define как функцию, потому что в макрос можно передавать аргументы.
Например, вот как сделана псевдо-функция "print" в YDWE PK
#define print(s) call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, s)
Мы берем строку "s" из макроса и передаем её в настоящую функцию.
Обратите внимание что благодаря этому даже не нужно писать "call" перед вызовом функции, потому что "call" уже включен в макрос.
Или вот более сложный пример.
#define SetUnitInvulnerable_true(unit) UnitAddAbility(unit, 'Avul')
#define SetUnitInvulnerable_false(unit) UnitRemoveAbility(unit, 'Avul')
#define SetUnitInvulnerable(unit, boolean) SetUnitInvulnerable_ ## boolean(unit)
Суть в том что мы заменяем стандартную функцию "SetUnitInvulnerable" на добавление/отнятие способности 'Avul' в зависимости от того какой boolean аргумент был использован в функции "SetUnitInvulnerable"
Один макрос заменяется на другой и в конце концов будет использована одна из двух настоящих функций "UnitAddAbility" или "UnitRemoveAbility"
Для чего это сделано?
В wc3 1.26 нет функции для проверки неуязвимости юнита, но вы можете проверить наличие способности которая дает неуязвимость.
Если раньше вы этого не знали то вам придется искать и заменять все "SetUnitInvulnerable" на аналоги "UnitAddAbility" "UnitRemoveAbility" вручную, что может отнять некоторое время.
Но при помощи препроцессора Boost вы можете решить эту проблему автоматически.
Вы так же можете оставить макрос без значения: #define _TestMode
Это нужно для того чтобы можно было определить, существует конкретный макрос или нет.
Теперь поговорим об условных директивах.
#if сравнение значений define и простых значений
#ifdef проверка что делать если макрос существует
#ifndef проверка что делать если макроса не существует
#else тоже самое что else в JASS
#elif - тоже самое что elseif в JASS
#endif - завершающий блок, тоже самое что endif в JASS
Один из примеров использования, это включение тестового режима для своей карты.
Пример:
#ifdef _TestMode
call PK_TestCommandsActivate()
#endif
Когда у меня в карте существует макрос _TestMode
При сохранении карты, в код будет вставлена функция PK_TestCommandsActivate() которая активирует тест команды.
Но если я удалю или закоментирую макрос, функция не будет вставлена в финальный код карты.
Так же существует директива #undef которая позволяет удалить макрос начиная с этого места.
Одна из полезнейших директив это #include, она позволяет вам вставлять код в карту из внешних файлов.
Пример: #include "C:\Users\user\Desktop\MyJassCode.j"
Это позволяет работать с кодом в любых внешних редакторах, таких как Visual Studio Code, и создавать для себя любую удобную сортировку кода.
Кроме того, такой метод защищает код от критических ошибок, потому что если вы пишете код в World Editor и во время сохранения произошел крит, вы потеряете весь прогресс до последнего сохранения.
Вы так же можете использовать любые директивы внутри импортированного кода, в том числе #include чтобы создавать зависимости и импортировать код поочередно из разных файлов.
И это еще не всё, полный функционал можно найти в интернете по ключевым словам "Boost preprocessor"
[Теперь поговорим про препроцессор Lua]
На самом деле здесь особо не о чем говорить, потому что он полностью подчиняется правилам обычного Lua, однако здесь есть небольшие особенности.
Код препроцессора Lua должен находится внутри блоков <? ?> где "<?" это начальный блок и "?>" это завершающий блок.
Пример:
Следующий код при сохранении карты откроет дискорд сервер YDWE PK в вашем браузере (без пробела)
<?
os.execute('explorer "https:// discord.gg/sVkB27JwG8"')
?>
Если же вы хотите вставить значение из Lua кода в конечный Jass код, вам нужно добавить знак "=" следующим образом:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "<?="Test"?>")
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, I2S('<?="Test"?>'))
Как вы можете заметить, препроцессор Lua работает даже внутри Jass кавычек, в отличие от директив препроцессора Boost.
Разумеется вы можете использовать Lua функции.
<?
function LuaTestFunc()
return math.random(5, 10)
end
?>
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, I2S(<?=LuaTestFunc()?>))
Еще одна особенность препроцессора Lua, с его помощью вы можете создавать объекты не через редактор объектов а при помощи Lua кода.
Пример:
<?
local slk = require 'slk'
local o = slk.item.afac:new('it01')
o.Name = 'Golden Ring'
o.ubertip = ''
o.abilList = nil
o.description = nil
o.tip = nil
?>
Этот код создаст предмет с id 'it01' на основе предмета 'afac'
После чего вы можете изменять ему параметры, имена параметров можно найти в редакторе объектов, нажав Ctrl + D.
Обратите внимание что если вы создаете юнита, то первый символ в его новом Id отвечает за то, будет юнит героем или нет. Заглавный символ означает что это герой.
Еще один пример:
<?local Misc_DefenseArmor = tonumber(require('slk').misc.Misc.DefenseArmor)?>
Таким образом мы получаем значение игровой константы "DefenseArmor"
К сожалению, я еще не достаточно изучил эти методы, поэтому пока знаю только как получить значение констант, но не могу их изменять.
А еще я знаю что изменять можно только те объекты которые вы создали при помощи Lua кода. (unit/item/destructable/doodad/ability/upgrade)
Кроме того, у созданного при помощи Lua кода, объекта, нельзя изменить параметры в редакторе объектов вручную, потому что они будут заменяться на те которые указаны в Lua коде.
Какие у этого есть способы применения?
Лично я на данный момент смог реализовать следующие кейсы:
1) Моментальная замена id объекта при необходимости
2) Изменения стата предмета автоматически меняет его настоящий стат и описание
3) Автоматическое изменения языка в карте (описание объектов/текст триггеров/прочее) после сохранения, исходя из моего выбора. (видео выше)
4) Собственное шифрование строк и прочих значений
5) Игнорирование некоторых ограничений редактора wc3
Например, способность "Engineering Upgrade" 'ANeg' позволяет использовать только геройские способности, хотя если создать объект при помощи кода, оказывается что обычные способности могут быть добавлены и полностью работают.
Они позволяют облегчить написание Jass кода, автоматизировать различные процессы и многое другое.
Чем больше у вас знаний об их работе и собственной фантазии, тем больше применений вы сможете найти этим двум инструментам.
Для начала хочу упомянуть что данные инструменты никогда не были предназначены для простых пользователей, они были обнаружены и изучены лично мной.
В связи с этим вы должны осознавать что использовать их может быть не просто, особенно если речь о препроцессоре Lua.
Для него желательно очень хорошо понимать Jass и иметь хотя бы средние навыки в Lua.
В Случае с препроцессором Boost все значительно проще, самые базовые из его возможностей сможет использовать даже человек который понимает только GUI.
Итак, начнем.
Препроцессоры обрабатывают ваш код в момент сохранения карты, позволяя изменить конечный результат.
Можно сказать что вы отдаете кому-то указания о том, как должен измениться ваш код карты после сохранения.
Пример:
Вы пишете какой-либо текст по работе и делаете в нем пометки "Удали весь текст ниже"
Или "Вставь здесь ссылку на сайт"
После этого, текст проверяет ваш личный помощник и выполняет ваши указания.
Конечный результат будет выглядеть так:
Это и есть работа препроцессора.
То есть, препроцессоры могут работать только до момента сохранения карты, во время запуска игры (Run Time) он не будет работать,
так как в этот момент, команд препроцессора больше не существует, они уже были заменены на ваши указания.
[Сначала поговорим о препроцессоре Boost]
Важно знать что он обрабатывает код раньше чем препроцессор Lua, что позволяет вам эффективно комбинировать их друг с другом.
Команды, или же "директивы" препроцессора Boost всегда начинаются со знака "#"
Первая и самая простая директива это: #define
Она объявляет определенный макрос, который потом можно будет использовать.
Сначала идет сама директива, потом ключевое слово, потом значение.
Например если мы используем где-либо в коде: #define _MyTest "Hello World!"
То после этого в любом месте кода, ключевое слово _MyTest после сохранении карты будет заменено на "Hello World!"
Пример:
До сохранения карты:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, _MyTest)
После:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "Hello World!")
Самые очевидные методы использования макросов, это оптимизация кода и придание ему более приятного вида.
Например можно использовать макросы для id объектов:
#define _AbilId_Invul 'Avul'
Или для правильного менеджмента ключей хеш-таблицы:
#define _Hash_BattleRhapsody 0
#define _Hash_Gloria 1
#define _Hash_RageTrigger 2
Таким образом мы отказываемся от бесполезной функции "StringHash" и получаем:
1) Оптимизацию по сравнению с "StringHash"
2) Полный контроль над ключами и невозможность "коллизии" ключей.
3) Более приятный внешний вид ключа, даже по сравнению с "StringHash"
Кроме этого, есть еще одно очень приятное преимущество.
Если вы поместите нестандартный объект в макрос, например: #define _AbilId_FireRage 'A001'
То в случае если вы удалите объект/измените его id, вам не придется ничего обновлять в коде, вам нужно будет только заменить значение в макросе _AbilId_FireRage на новое.
Конечно, для подобных вещей можно использовать глобальные переменные, но зачем создавать лишние глобальные переменные, когда можно этого не делать и уменьшить размер кода.
Я рекомендую вам всегда начинать имя макроса со знака нижнего подчеркивания "_"
Это нужно для того чтобы вы всегда могли точно определить что в вашем коде является макросом, таким образом вы никогда не запутаетесь.
Вы так же можете использовать #define как функцию, потому что в макрос можно передавать аргументы.
Например, вот как сделана псевдо-функция "print" в YDWE PK
#define print(s) call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, s)
Мы берем строку "s" из макроса и передаем её в настоящую функцию.
Обратите внимание что благодаря этому даже не нужно писать "call" перед вызовом функции, потому что "call" уже включен в макрос.
Или вот более сложный пример.
#define SetUnitInvulnerable_true(unit) UnitAddAbility(unit, 'Avul')
#define SetUnitInvulnerable_false(unit) UnitRemoveAbility(unit, 'Avul')
#define SetUnitInvulnerable(unit, boolean) SetUnitInvulnerable_ ## boolean(unit)
Суть в том что мы заменяем стандартную функцию "SetUnitInvulnerable" на добавление/отнятие способности 'Avul' в зависимости от того какой boolean аргумент был использован в функции "SetUnitInvulnerable"
Один макрос заменяется на другой и в конце концов будет использована одна из двух настоящих функций "UnitAddAbility" или "UnitRemoveAbility"
Для чего это сделано?
В wc3 1.26 нет функции для проверки неуязвимости юнита, но вы можете проверить наличие способности которая дает неуязвимость.
Если раньше вы этого не знали то вам придется искать и заменять все "SetUnitInvulnerable" на аналоги "UnitAddAbility" "UnitRemoveAbility" вручную, что может отнять некоторое время.
Но при помощи препроцессора Boost вы можете решить эту проблему автоматически.
Вы так же можете оставить макрос без значения: #define _TestMode
Это нужно для того чтобы можно было определить, существует конкретный макрос или нет.
Теперь поговорим об условных директивах.
#if сравнение значений define и простых значений
#ifdef проверка что делать если макрос существует
#ifndef проверка что делать если макроса не существует
#else тоже самое что else в JASS
#elif - тоже самое что elseif в JASS
#endif - завершающий блок, тоже самое что endif в JASS
Один из примеров использования, это включение тестового режима для своей карты.
Пример:
#ifdef _TestMode
call PK_TestCommandsActivate()
#endif
Когда у меня в карте существует макрос _TestMode
При сохранении карты, в код будет вставлена функция PK_TestCommandsActivate() которая активирует тест команды.
Но если я удалю или закоментирую макрос, функция не будет вставлена в финальный код карты.
Так же существует директива #undef которая позволяет удалить макрос начиная с этого места.
Одна из полезнейших директив это #include, она позволяет вам вставлять код в карту из внешних файлов.
Пример: #include "C:\Users\user\Desktop\MyJassCode.j"
Это позволяет работать с кодом в любых внешних редакторах, таких как Visual Studio Code, и создавать для себя любую удобную сортировку кода.
Кроме того, такой метод защищает код от критических ошибок, потому что если вы пишете код в World Editor и во время сохранения произошел крит, вы потеряете весь прогресс до последнего сохранения.
Вы так же можете использовать любые директивы внутри импортированного кода, в том числе #include чтобы создавать зависимости и импортировать код поочередно из разных файлов.
И это еще не всё, полный функционал можно найти в интернете по ключевым словам "Boost preprocessor"
[Теперь поговорим про препроцессор Lua]
На самом деле здесь особо не о чем говорить, потому что он полностью подчиняется правилам обычного Lua, однако здесь есть небольшие особенности.
Код препроцессора Lua должен находится внутри блоков <? ?> где "<?" это начальный блок и "?>" это завершающий блок.
Пример:
Следующий код при сохранении карты откроет дискорд сервер YDWE PK в вашем браузере (без пробела)
<?
os.execute('explorer "https:// discord.gg/sVkB27JwG8"')
?>
Если же вы хотите вставить значение из Lua кода в конечный Jass код, вам нужно добавить знак "=" следующим образом:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "<?="Test"?>")
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, I2S('<?="Test"?>'))
Как вы можете заметить, препроцессор Lua работает даже внутри Jass кавычек, в отличие от директив препроцессора Boost.
Разумеется вы можете использовать Lua функции.
<?
function LuaTestFunc()
return math.random(5, 10)
end
?>
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, I2S(<?=LuaTestFunc()?>))
Еще одна особенность препроцессора Lua, с его помощью вы можете создавать объекты не через редактор объектов а при помощи Lua кода.
Пример:
<?
local slk = require 'slk'
local o = slk.item.afac:new('it01')
o.Name = 'Golden Ring'
o.ubertip = ''
o.abilList = nil
o.description = nil
o.tip = nil
?>
Этот код создаст предмет с id 'it01' на основе предмета 'afac'
После чего вы можете изменять ему параметры, имена параметров можно найти в редакторе объектов, нажав Ctrl + D.
Обратите внимание что если вы создаете юнита, то первый символ в его новом Id отвечает за то, будет юнит героем или нет. Заглавный символ означает что это герой.
Еще один пример:
<?local Misc_DefenseArmor = tonumber(require('slk').misc.Misc.DefenseArmor)?>
Таким образом мы получаем значение игровой константы "DefenseArmor"
К сожалению, я еще не достаточно изучил эти методы, поэтому пока знаю только как получить значение констант, но не могу их изменять.
А еще я знаю что изменять можно только те объекты которые вы создали при помощи Lua кода. (unit/item/destructable/doodad/ability/upgrade)
Кроме того, у созданного при помощи Lua кода, объекта, нельзя изменить параметры в редакторе объектов вручную, потому что они будут заменяться на те которые указаны в Lua коде.
Какие у этого есть способы применения?
Лично я на данный момент смог реализовать следующие кейсы:
1) Моментальная замена id объекта при необходимости
2) Изменения стата предмета автоматически меняет его настоящий стат и описание
3) Автоматическое изменения языка в карте (описание объектов/текст триггеров/прочее) после сохранения, исходя из моего выбора. (видео выше)
4) Собственное шифрование строк и прочих значений
5) Игнорирование некоторых ограничений редактора wc3
Например, способность "Engineering Upgrade" 'ANeg' позволяет использовать только геройские способности, хотя если создать объект при помощи кода, оказывается что обычные способности могут быть добавлены и полностью работают.
Boost and Lua preprocessors are extremely useful advantages of the YDWE map editor
They make it easier to write Jass code, automate various processes and much more.
The more you know about how they work and your own imagination, the more applications you can find for these two tools.
First of all, I want to mention that these tools were never intended for ordinary users, they were discovered and studied by me personally.
In this regard, you should be aware that using them may not be easy, especially if we are talking about the Lua preprocessor.
For this, it is advisable to understand Jass very well and have at least average skills in Lua.
In the case of the Boost preprocessor, everything is much simpler, even a person who understands only the GUI can use the most basic of its capabilities.
So, let's get started.
Preprocessors process your code at the moment the map is saved, allowing you to change the final result.
You can say that you are giving someone instructions on how your map code should change after saving.
Example:
You write some text for work and make notes in it "Delete all next text"
Or "Insert a link to the site here"
After that, your personal assistant checks the text and carries out your instructions.
The final result will look like this:
This is the job of the preprocessor.
That is, preprocessors can only work until the moment the map is saved, during the game launch (Run Time) it will not work,
since at this moment the preprocessor commands no longer exist, they have already been replaced by your instructions.
[About Boost preprocessor]
It is important to know that it processes code before the Lua preprocessor, which allows you to effectively combine them with each other.
Boost preprocessor commands, or "directives", always begin with the "#" sign.
The first and simplest directive is: #define
It declares a specific macro that can then be used.
First comes the directive itself, then the keyword, then the value.
For example, if we use somewhere in the code: #define _MyTest "Hello World!"
Then after that, anywhere in the code, the keyword _MyTest after saving the map will be replaced with "Hello World!"
Example:
Before saving the map:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, _MyTest)
After:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "Hello World!")
The most obvious ways to use macros are to optimize your code and make it look nicer.
For example, you can use macros for object ids:
#define _AbilId_Invul 'Avul'
Or for proper management of hash table keys:
#define _Hash_BattleRhapsody 0
#define _Hash_Gloria 1
#define _Hash_RageTrigger 2
This way we get rid of a useless function. "StringHash" and we get:
1) Optimization compared to "StringHash"
2) Full control over keys and impossibility of key "collision".
3) More pleasant appearance of the key, even in comparison with "StringHash"
Besides this, there is another very pleasant advantage.
If you place a custom object in a macro, like this: #define _AbilId_FireRage 'A001'
So if you delete an object/change its id, you won't have to update anything in the code, you'll only need to replace the value in the macro _AbilId_FireRage to the new.
Of course, you can use global variables for such things, but why create unnecessary global variables when you can avoid it and reduce the code size.
I recommend that you always start the name of a macro with an underscore "_"
This is so that you can always clearly identify what is a macro in your code, so you never get confused.
You can also use #define as a function, because you can pass arguments to a macro.
For example, here's how a pseudo-function "print" is made in YDWE PK
#define print(s) call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, s)
We take the string "s" from the macro and pass it to the actual function.
Note that this means we don't even have to write "call" before the function call, because "call" is already included in the macro.
Or here's a more complex example.
#define SetUnitInvulnerable_true(unit) UnitAddAbility(unit, 'Avul')
#define SetUnitInvulnerable_false(unit) UnitRemoveAbility(unit, 'Avul')
#define SetUnitInvulnerable(unit, boolean) SetUnitInvulnerable_ ## boolean(unit)
The point is that we replace the standard function "SetUnitInvulnerable" with adding/removing the ability 'Avul' depending on what boolean argument was used in the function "SetUnitInvulnerable"
One macro is replaced by another and in the end one of the two real functions "UnitAddAbility" or "UnitRemoveAbility" will be used
Why is this done?
In wc3 1.26 there is no function to check the invulnerability of a unit, but you can check for the presence of an ability that gives invulnerability.
If you did not know this before, then you will have to search and replace all "SetUnitInvulnerable" with analogs "UnitAddAbility" "UnitRemoveAbility" manually, which can take some time.
But with the help of the Boost preprocessor, you can solve this problem automatically.
You can also leave the macro without value: #define _TestMode
This is necessary so that it can be determined whether a specific macro exists or not.
Now let's talk about conditional directives.
#if comparison of define values and simple values
#ifdef check what to do if macro exists
#ifndef check what to do if macro does not exist
#else same as else in JASS
#elif - same as elseif in JASS
#endif - endblock, same as endif in JASS
One example of use is to enable test mode for your map.
Example:
#ifdef _TestMode
call PK_TestCommandsActivate()
#endif
When I have a macro in my map _TestMode
When saving the map, the PK_TestCommandsActivate() function will be inserted into the code, which activates the command test.
But if I delete or comment out the macro, the function will not be inserted into the final map code.
There is also a #undef directive, which allows you to delete the macro starting from this point.
One of the most useful directives is #include, it allows you to insert code into the map from external files.
Example: #include "C:\Users\user\Desktop\MyJassCode.j"
This allows you to work with the code in any external editors, such as Visual Studio Code, and create any convenient code sorting for yourself.
In addition, this method protects the code from critical errors, because if you write code in the World Editor and a critical error occurs during saving, you will lose all progress until the last save.
You can also use any directives inside the imported code, including #include to create dependencies and import code one by one from different files.
And that's not all, the full functionality can be found on the Internet by the keywords "Boost preprocessor"
[Now about Lua preprocessor]
There's not really much to say here, because it follows the rules of regular Lua, but there are some quirks.
Lua preprocessor code should be inside <? ?> blocks, where "<?" is the start block and "?>" is the end block.
Example:
The following code will open the YDWE PK discord server in your browser when you save the map. (without space)
<?
os.execute('explorer "https:// discord.gg/sVkB27JwG8"')
?>
If you want to insert a value from Lua code into the final Jass code, you need to add the "=" sign like this:
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "<?="Test"?>")
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, I2S('<?="Test"?>'))
As you can see, the Lua preprocessor works even inside Jass quotes, unlike Boost preprocessor directives.
Of course you can use Lua functions.
<?
function LuaTestFunc()
return math.random(5, 10)
end
?>
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, I2S(<?=LuaTestFunc()?>))
Another feature of the Lua preprocessor is that you can create objects not through the object editor but using Lua code.
Example:
<?
local slk = require 'slk'
local o = slk.item.afac:new('it01')
o.Name = 'Golden Ring'
o.ubertip = ''
o.abilList = nil
o.description = nil
o.tip = nil
?>
This code will create an item with id 'it01' based on item 'afac'
After that you can change its parameters, the parameter names can be found in the object editor by pressing Ctrl + D.
Note that if you create a unit, the first character in its new Id is responsible for whether the unit will be a hero or not. A capital character means that it is a hero.
Another example:
<?local Misc_DefenseArmor = tonumber(require('slk').misc.Misc.DefenseArmor)?>
This is how we get the value of the game constant "DefenseArmor"
Unfortunately, I haven't studied these methods enough yet, so for now I only know how to get the value of the constants, but I can't change them.
And I also know that you can only change those objects that you created using Lua code. (unit/item/destructable/doodad/ability/upgrade)
In addition, you can't change the parameters of an object created using Lua code manually in the object editor, because they will be replaced by those specified in the Lua code.
What are the possible uses for this?
Personally, I have been able to implement the following cases so far:
1) Instant replacement of object id if necessary
2) Changing an item's stat automatically changes its actual stat and description.
3) Automatically changes the language in the map (object description/trigger text/etc) after saving, based on my choice. (video above)
4) Custom encryption of strings and other values
5) Ignoring some limitations of wc3 editor
For example, It is known that you can hide the ability icon by setting the buttonpos coordinates: 0,-11
But this can lead to a critical error in some cases, but there is a better method: -2147483648,-2147483648
which does not lead to critical errors.
But you cannot manually set these coordinates in the editor, even by holding down Shift.
However, by creating an object through the preprocessor, you can set these coordinates.
Last edited: