• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Mormord's JASS Transpiler

This bundle is marked as pending. It has not been reviewed by a staff member yet.

Mormord's JASS Transpiler

If you are using JASS, I hope you're giving this tool a chance. If you are not using JASS, maybe it's time to try it. I attached a demo map with commented source code to help get started!

This tool intends to bring powerful new features to vJASS. To name a few: automatic function sorting, parameterized events, private variables/functions without scope definitions, referencing unit types from the map with '[Custom Unit: Firemage]', records and foreach loops and so on. The full list with details follows. The tool comes with a command line and a GUI option. I also included a demo map and its source code (with comments, so you can learn to use the tool from it). The objective is to almost fully replace GUI triggers with MJT code in an easy-to-use way.
The Demo map's source code will show the use and code of all features. You can also read the code of the libraries those are also commented on each feature.

Features:


Motivation: When you modularize vJASS projects, you get the option of using libraries. When you library would depend on another library, you have the requires keyword. And when you have two libraries that depend on each other, you have to rethink the whole module structure of your project.
MJT's solution: With MJT, you will not have this problem - organize your code in whichever way you want. MJT will find out in which order it has to write the functions to the output so that they work. Unless you write recursive functions. Don't do that. Other upside that with this, you can omit the "library <name>" part of your file, because it really doesn't matter - it won't clash with some other library that someone else named the same way by accident!
Motivation: What is the worst aspect of event-oriented programming? Global variables. When you accidently trigger an event, the value of global variables might change. How does Warcraft III handle event parameters? With global functions like GetTriggerUnit() and so.
MJT's solution: You can define events and handlers in your code. Events can have parameters and filters (filters are not functions, just variables - OnCast is always filtered on the spell that is cast), handlers can say what filter they use (Wall of Fire looks for the Wall of Fire spell), if any. Handlers without filters can have priorities (so you can break your damage handler into multiple modules depending on where it belongs and still show the final damage number on screen). As an added bonus, MJT events are turned into function calls in transpile-time, which should be more efficient than declaring new triggers.
Motivation: There can be some functions and variables in any module that other modules should not reach. This way you can accidently clashing function and variable names between different modules. vJASS offers the keywords scope and private here, but when we reorder the functions, scopes don't work anymore. Also, you always run the risk of naming your scope the same way as someone else did.
MJT's solution: by prepending the name of the function/variable with ##, the transpiler knows that it has to generate a unique prefix based on the file it processes. No more scopes, scope names, etc.
Note: The "scope" and "private" keywords don't work with MJT.
Motivation: If you ever worked on a library, you probably ran into the issue of needing unique values for hashtable keys or for identification.
MJT's solution: any global integer variable can have the initial value of "unique" and the transpiler will make sure to fill those variables with globally unique values. You can also use "constant integer A = unique 4", of you want to make sure that A+1, A+2 and A+3 are also not used anywhere else for unique values. With this you can reduce the number of hashtables in your project. This feature enables MJT's standard library's MJS_GetUnitReal/MJS_SetUnitReal functions that lets you save any number of custom data to each unit with a key. It is also used in the TriggeredBuff library to identify your buffs (so you can put a trigger on them).
Motivation: In the past years, I used structs 95% of the time in the very same way - store some data and iterate over it. This came with a certain boilerplate code, which was easy to miss and prone to error. Records do not replace structs, you can use structs as well.
MJT's solution: Records are structs with the following characteristics: They only include member variables, not methods. Member variables can have initial values. You can iterate through all elements and you're allowed to delete elements while iterating. You can also add new elements while iterating, but new elements will not be iterated over in that loop. This might look odd, considering how most languages implement foreach loops, but it really helps the user who just want to add a new buff, when their custom buff is expired. Records also come with a number of built-in functions - .new(), .initialize(record_count), .delete(), .random(), .swap(a, b), .copy(src, dst)
Note: Do not store references to records if you ever call the .delete() method as it will reorder the backing arrays of the record to keep the living data more cache-friendly.
Motivation: When you want to reference unit types in your map, Warcraft III offers two solutions - either make a global variable in the GUI and set it's initial value from the palette, or use their four letter code, which looks like this: 'h001'. The code will differ on different maps, so if you developed a spell that uses a custom unit, you're in a bit of trouble.
MJT's solution: Invoking the awesome power of Ladislav Zezula's MPQEditor, MJT can extract the unit information from the map and fill in the blanks. You can use '[Default Unit: Water Elemental (Level 1)]', '[Custom Ability: Deathtouch]' or even '[Default Buff: Slowed]'.
Note: You must save the map with the custom unit/ability first, before MJT can make use of the reference.
Note: MJT will consider items to be units. Write '[Custom Unit: Essence of Strength]' instead of '[Custom Item: Essence of Strength]'
Note: If you base your custom unit on a default unit that has an editor suffix, MJT will treat the editor suffix as empty, unless you change it
Motivation: filling arrays and hashtable in JASS looks long and ugly. You have to repeat "set myArray[...] = ..." part a number of times, where only the ... parts change.
MJT's solution: let's define special blocks where you can fill your data in a table format, which is much more compact and readable.
If you're familiar with C++'s template programming, this is something similar. There was no particular motivation here, I just did it because I could. This lets you generate code out of a template with some parameters. It may have its niche uses, but I would not recommend using it, unless your already familiar with template programming
MJT comes with a number of libraries that I found most useful in my time developing unfinished Warcraft III maps. All libraries are opt-in, nothing is enforced, not even the quote-on-quote standard library, std/system.j. However, all other libraries depend on std/system.j

The Standard Library (std/system.j)
The standard library is responsible for the most common tasks. It defines the actual triggers for map initialization, casting abilities and a few others and turn them into MJT events for easier handling.
It also gives you functions to properly pause/unpause units (because if a unit is hit three stunning spells, it should not recover when the first one expires), to remove units (because removing units with RemoveUnit can cause crashes if you accidently reference it), setting custom unit data with key (similar to SetUnitUserData, but with more types), which can be used for flagging that a unit was affected by a certain spell, or just remembering, who summoned that elemental. There are some functions for (x, y) coordinates that were only available for locations in blizzard.j. You can also get the (colored) name of a player without the #12345 suffix. Credit goes to TriggerHappy, whose module I got the color codes from.
I also included a singular repeating timer that should help you keep your things organized - no more desyncs due to multiple timers firing at the same time and each client chose a different one to run first.
The standard library comes with the entry point of InitializeSystem which should be called in a map initialization trigger.

TriggeredBuff lib
TriggeredBuff lets you control buffs with JASS code. You can decide if it should be visible/invisible, what should happen on each tick, and what should happen when it expires.

TimedEffect lib
Sometimes you just want to show an effect for some time and remove it. You also get an event when the time is up. Did I mention that I don't like "Wait X seconds" triggers? Yeah, you can use this to replace that too.

Dummy lib
An easy way to work with dummy units. The dummy unit itself is present in the MJS_Start.w3x map which you can copy it from.

Multiboard and FloatText libs
These are just simple wrappers to the corresponding functionality in common.j to make life easier.

ProjSystem lib
This is a very simple projectile system. It lets you target positions and units and also modify the path of the projectile as you see fit. Downside to other projectile systems that you have to do the math yourself if you want something other than direct shooting.

QuickCast lib
Now, this is a stretch, I know, but I liked casting spells without clicking in LoL (and the library is opt-in anyways). The QuickCast library does exactly that. Unfortunately a spell is either quick cast or not, you cannot switch between the options until Blizzard decides to put it in their engine. Also, you can't use the minimap for targeting, which is somewhat annoying, but this is the best I could come up with. Maybe someone else will have something better.
Correction: Warcraft III does support quick casting spells, but it is somewhat obscure, so I didn't know about it before making this library. This library can still be useful if you use mouse movement for your spells, like the shape of Wall of Fire or the jumps of Bouncing Flame in the demo map.
Motivation: What's a common reason against using third party libraries like this? In their effort to work for every use-case, they add a lot of code that is not needed for your project. This makes your project bigger and slower than it needs to be.
MJT's solution: records do not generate their methods that are not being used. MJT also has a flag (-RUF: Remove unused functions, always on if you run MJT_GUI) that removes all unused functions. This strips most of the code from the libraries that you were not using, making the result that much lighter. MJT does not strip variables though. The standard library will also check if you subscribed to its events before making the corresponding triggers.
I gave a long though about inlining certain functions, so calling PolarX(x, angle, offset) is free, but in the end I decided that the work and the slowdown in transpilation time does not worth the benefit.
You can use normal JASS libraries with your MJT project. Don't put these files in the project folder, just state in one of your files that "requires MyExternalJASSLibrary" and MJT will make sure that you can call functions from that library as well.
If you're using the commandline MJT, you can also set a -RUF+MyFunction to make sure that MyFunction is not optimized out and you can call it from the GUI. The normal -RUF is equivalent to -RUF+InitializeSystem.
Motivation: Have you spent hours upon hours on figuring out why a certain trigger/function is not running or stopping midway? It can be a result of a poorly written loop that never exits or just an uninitialized variable.
MJT's solution: In debug mode, MJT will add code to your project that helps identify these problems. It will tell exactly which function crashed and it's entire stacktrace. You can also make use of the stacktrace yourself, if you're interested what line of calls happened before you got to your OnDie handler. You can define debug blocks between #debug and #enddebug that only gets compiled to the debug version.
Note: Debug mode is a lot less performant, because it does a lot of bookkeeping. Don't be surprised!
Note: If you manage to crash or desync Warcraft III, MJT's debug mode won't be able to help you. Sorry.
Motivation: I've worked a lot with triggered spells with multiple levels, where the editor's autofill option for tooltips doesn't quite cut it. Maybe you need Level * 2 somewhere or the radius of your AoE increases from 150 to 200 over 3 levels. Every time you make a balance change, you must correct all the tooltips by hand and an error is just waiting to happen. It's not uncommon that numbers in tooltips do not match actual values in Warcraft 3 custom maps.
MJT's solution: You can give a tooltip template and tooltip data as comment in your code for your ability. MJT will merge tooltip template and data to create leveled tooltips four your map to a special file. You can copy these tooltips into your map from that file. Storing the tooltip near your ability code also makes you notice differences faster.

Choose one of two heroes to fight the endless horde of enemies. Spells are quick-cast and controlled by mouse movement

How to build it?
1.) Open MJT_GUI.exe - that's the GUI for the transpiler.
2.) A window pops up Create/Open - click Open and select demo.mjp
3.) A new window pops up, on the left side there will be buttons, on the right, some empty space for the transpiler output.
4.) On the left, the first button is Map: ... It probably shows up with red text. That's because the .w3x file is in a different place in your computer than on mine.
5.) Click the Map button and select the MJS_Demo.w3x file
6.) The second button is libraries. If you click it, you can select which libraries are being used by the project. The map uses all provided libraries as it tries to demo all of them. The libraries themselves are located in the MJT's folder (std and libs subdirectory)
7.) The next button is save. Click it now, so you don't have to repeat step 4 every time you build the project.
8.) The next two buttons are Build and Build Debug. Click Build (not Build Debug) to build the project. Pay attention to the output on the right side.
9.) Ideally one the last lines in the output will say something like this: //! import "...."
10.) Open MJS_Demo.w3x in Warcraft III's Editor
11.) Open the Trigger Editor
12.) There is a code trigger in MJT Import/Import - copy the line from step 9 in it.
13.) Save the map and test it.
14.) Enjoy! :)

What is Build Debug?
At step 8, you can click Build Debug instead of Build ot generate a lot of additional info that helps you debug if your spells are not working correctly. While debug mode can't catch warcraft crashes, it will catch problems that stop the code from running - infinite loops, uninitialized variables, etc. MJS_Demo comes with a buggy spell that only shows up if you build in debug mode - it's called "Problematic Spell". It's so bad, that it freezes Warcraft III, so use it at your own peril. At the same time debug mode will point you to the function that causes the problem and its stack trace (which function called it, and what function was calling that function, etc.). You'll notice, that private function names start with "p18__" or similar. You use the transpiler output to find out which file generated that function (suprise, it'll be src\debug_example\problematic_spell.j) Can you find and fix the bug?

How does the MJT Tooltip generation work?
Each spell in the source code has a
// TOOLTIP
// ...
// TOOLTIP_DATA
// ...
// ENDTOOLTIP
section that defines the tooltip for the spell.
Tooltip data is a table format where you can define columns with <keys> that is replaced in the original tooltip text to provide the final tooltip text by level.
After step 8, you can open tooltips.txt next to demo.mjp where you can copy the tooltip texts from to your map.

Notes:
1.) Check Initialization/Map Init trigger - it calls InitializeSystem(). This is the entry point of the MJT project that will take care of everything else.
2.) There is also an MJS_Start.w3x that contains everything that is required by the libraries (dummy units, initialization trigger, template abilities), but nothing else. You can start your map here, or simply copy these things to your map from here.

Requires .NET 4.7.2 (should be part of Windows) and .NET 6.0.0 (Downloadable from Microsoft: https://dotnet.microsoft.com/en-us/download/dotnet/6.0 )
Contents

Mormord's JASS Transpiler (Binary)

Top