Spell Spells - Multi-Unit Instanceability (MUI)

Tom Jones

N/A
Reaction score
437
Code:
Index:
What’s MUI?
Which unit references are mui?
Variables.
Local Handle Systems (jass).
Conclusion.

What’s MUI?
MUI stands for Multi Unit Instanceable, and simply put this means that two or more units are able to trigger a specific trigger at the same time, performing the triggers actions for each triggering units. To understand MUI, we must also understand how a trigger works. A trigger consist of events, conditions, and actions. When the trigger registers the event, and it’s conditions evaluates true, the triggers actions gets executed. The game engine then creates a new trigger in the old triggers place, containing the same events, conditions, and actions. This means that the “same” trigger can be triggered even if it’s still being executed.

Which unit references are MUI?
References are my word for all Blizzard defined actions that refers to something, including Event Responses, group responses, and Last Something actions. In this tutorial only unit references will be mentioned, which includes:

unitlistdr2.jpg


Event Responses:
All unit event responses are MUI and you can refer to the these responses even after long waits. To prove this I killed two pre placed units with a trigger, and made another trigger register a generic unit death. This trigger displayed the name of the triggerring unit twice, once when the trigger got triggered and once after five seconds. Here is the triggers I used:

eventprovetrigger1bc6.jpg

eventproveds4.jpg


This was the result:

eventproveresultnv2.jpg


I then made the trigger wait for one minute before displaying the name of the triggering unit, and changed triggering unit to dying unit. The result was the same:

eventproveresult2uv3.jpg


After the 60 seconds wait the text was displayed:

eventproveresult3kg0.jpg


We can conclude that Event Responses are MUI, even after long waits.

Group Responses:
Group responses includes Picked unit and Matching unit. Both responses are MUI but their functionality are more limited than event responses. Picked unit can only be used inside a group loop action, and Matching unit can only be used in group pick every units actions that has a condition. To prove that both Picked unit and Matching unit are MUI, I killed two preplaced units, one owned by player red and one owned by player blue, with a trigger. Another trigger registers the death, and picks all units owned by the owner of the triggering unit matching a condition, and displays the name and owner of the picked unit. These are the triggers I used:

groupprovetrigger1cw2.jpg

groupprovetrigger2ym5.jpg


And the result:

groupproveresultfk0.jpg


I could also have choosen to execute the triggers and used another pick every units action, instead of killing a unit and picking all units owned by the owner of the triggering unit . It would give us the same result. We can conclude that Mathcing unit and Picked unit is MUI, however we can’t use them with waits because of Blizzard limitations.

Last Something Actions:
These are actually variables created by Blizzard, and they are not MUI. To prove this I created two units with triggers, and displayed the name of the last created unit twice, once when the trigger gets triggered, and once after 5 seconds. Here’s the triggers I used:

lastprovetrigger1tc6.jpg

lastprovetrigger2ah9.jpg


The result:

lastproveresultfo5.jpg


As you can see, after 5 seconds trigger ones Last Created Unit is a knight, even though the unit created in that trigger was a footman. However it worked when the trigger got triggered, so with very very short waits it’s close to MUI.

Variables:
Variables can be declared in two ways, as locals or globals. When you create a variable using the trigger editors variable editor, you are in fact creating global variables. One of the differences between the two declarations is that globals may be used by all triggers, but locals can only be used in the trigger wherein they got declared. The most crucial difference however, is that locals works just like Event Responses. We’re going to take advantage of this later, but first I’ll prove that global variables aren’t MUI. I created two units with triggers, and assigned them to the same variable. I know that allready now it’s obvius that this isn’t MUI, but just in case here’s the trigger:

variableprovetrigger1md4.jpg

variableprovetrigger1md4.jpg


And the result:

variableproveresultdz2.jpg


Seems familiar? This is the exact same result as the Last Something result. Not only is Last Something actions variables, they are actually global variables. And once again we proven that global variables isn’t MUI... Or is it?

As mentioned earlier, local variables works excactly like Event Responses which, in case you forgot it, is 100% MUI. We have the option to assign local variables to our globals variables, but unfurtunately we can’t refer to local variables in the Trigger Editor. What we’re going to do instead, is localizing the global creating a, what I like to call it, global local. This is done with the custom script action, and it looks like this:

globallocalhb0.jpg


Notice the variable name: udg_Unit. When I created the variable in the variable editor, I named it Unit, but the editor automatically puts udg_ in front of all user created variables, which stands for User Defined Global. This is a safety to avoid that users creates malfunctioning variable names, and this is the real name of the variable. This means that if I had entered Unit as the variable name instead of udg_Unit, I would actually have created a local variable and not localized the global variable. It’s very important that you put udg_ in front of all user created variables in a custom script action. To prove that global locals are MUI, I used the same triggers as the last prove, except that I localized the global variable unit, which looks like this:

globallocltrigger1ok8.jpg

globallocaltrigger2rt4.jpg


Here’s the result:

globallocalresulter3.jpg


This proves that global locals are MUI, and by adding a global local to the two triggers we also made them MUI. You can localize the same global in several triggers, and a localized global can be set to any value, without overridding the same localized global in other triggers. The only problem with localized globals is, that because of the Trigger Editors poor conversion form GUI to JASS, global locals can’t be used in the following:

In a If/Then/Else’s condition.
In a group condition.
Inside a group loop.
In a Conditional wait.

Also remember to null the global locals at the end of the trigger to avoid leaks, and note that for some reason beyond my knowlegde, only one variable type can be localized in each trigger, etc. two localized unit variables wouldn't work.

Local Handle Vars (jass):
This is a great tool to make your code MUI.If your unfamiliar with the concept, it simply allows us to store variables and use them later, be it in a function, after waits, at the end of the map, etc.The two most used systems are KaTTaNas Local Handle Vars system, and Vexorians CS system. What I’m going to describe is similar for both systems, so if you learn one system you should also be able to learn the other. I would like to point out, that besides the normal local handle functions, Vexorians system also contains table and set functions to optimize the functionality of the system. I’m going to use KaTTaNas system, because that’s the system I work with. If you haven’t done so by now, get the system from this link. Implementation is fairly easy, all you gotta do is copy the system to the map header, and create a game cache variable. Create a game cache at map initialization, and assign the game cache variable to it. Now we have to change the return of LocalVars() to return our game cache variable:
Code:
function LocalVars takes nothing returns gamecache
    return udg_gc
endfunction

Now that we imported the system, let’s understand how it works. The base of the system is the function H2I, the return bug function:
Code:
function H2I takes handle returns integer
    return h
    return 0
endfunction
The jass parser only checks the last return value in a function, but a function will return the first return value it meets. In this case, it’ll return a handle. Handles are basically references in the memory and each handle has it’s own id code. There are several handle types, also better known as Variable Types. It could be a player, a unit, a timer and so on. There are some exceptions though, which are integers, reals, strings, booleans, and codes which are are not considered handles. The next four functions are used to assign values to a handle:
Code:
function SetHandleHandle takes handle subject, string name, handle value returns nothing
    if value==null then
        call FlushStoredInteger(LocalVars(),I2S(H2I(subject)),name)
    else
        call StoreInteger(LocalVars(), I2S(H2I(subject)), name, H2I(value))
    endif
endfunction

function SetHandleInt takes handle subject, string name, integer value returns nothing
    if value==0 then
        call FlushStoredInteger(LocalVars(),I2S(H2I(subject)),name)
    else
        call StoreInteger(LocalVars(), I2S(H2I(subject)), name, value)
    endif
endfunction

function SetHandleBoolean takes handle subject, string name, boolean value returns nothing
    if value==false then
        call FlushStoredBoolean(LocalVars(),I2S(H2I(subject)),name)
    else
        call StoreBoolean(LocalVars(), I2S(H2I(subject)), name, value)
    endif
endfunction

function SetHandleReal takes handle subject, string name, real value returns nothing
    if value==0 then
        call FlushStoredReal(LocalVars(), I2S(H2I(subject)), name)
    else
        call StoreReal(LocalVars(), I2S(H2I(subject)), name, value)
    endif
endfunction

function SetHandleString takes handle subject, string name, string value returns nothing
    if value==null then
        call FlushStoredString(LocalVars(), I2S(H2I(subject)), name)
    else
        call StoreString(LocalVars(), I2S(H2I(subject)), name, value)
    endif
endfunction
We could compare this to a local variable in a function, except that instead of having the local in a function, we have it on a handle. This means that a handle can have several values assigned to it, as long as the name of the value doesn’t interfere with allready assigned value names. Furthermore since the same handle is able to be used by several functions, we are also able to use it’s assigned values in other functions. We use SetHandleInt() when we assign integers to handles, SetHandleReal() when we assign reals to a variable, SetHandleString() when we assign strings to a handles, SetHandleBoolean() when we assign booleans to a handle, and SetHandleHandle for assigned handles to a handle.The last set of functions are used to get the assigned values from handles, and as you can see they all return a specific handle type:
Code:
function GetHandleHandle takes handle subject, string name returns handle
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleInt takes handle subject, string name returns integer
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
endfunction
function GetHandleBoolean takes handle subject, string name returns boolean
    return GetStoredBoolean(LocalVars(), I2S(H2I(subject)), name)
endfunction
function GetHandleReal takes handle subject, string name returns real
    return GetStoredReal(LocalVars(), I2S(H2I(subject)), name)
endfunction
function GetHandleString takes handle subject, string name returns string
    return GetStoredString(LocalVars(), I2S(H2I(subject)), name)
endfunction

function GetHandleUnit takes handle subject, string name returns unit
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleTimer takes handle subject, string name returns timer
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleTrigger takes handle subject, string name returns trigger
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleEffect takes handle subject, string name returns effect
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleGroup takes handle subject, string name returns group
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleLightning takes handle subject, string name returns lightning
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleWidget takes handle subject, string name returns widget
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
    return null
endfunction
This is becuase the jass parser won’t allow us to directly return a handle to variable types, but we simply use the return bug to trick a handle return. Even though we trick the parser, it’s still important to assure that you actually are returning a correct handle. Returning a timer to a unit variable will surely cause some unwanted errors. I mentioned earlier that you could compare handle values with local variables, so after we’re done with the handle values we have to null it. We have to do this before destroying the handle and nulling the variable referering to the handle, and this is done with this function:
call FlushHandleLocals(SomeHandle)
After that you can fix leaks, and null variables refering to the handle. Let’s have a look at how to use the system. We’re going to make a function that damages the target of a ability over time.

Code:
function Test_Actions takes nothing returns nothing
    local timer t =  CreateTimer()     timer 

    call SetHandleHandle(t,”u”,GetTriggerUnit())   
    call SetHandleHandle(t,”v”,GetSpellTargetUnit())   
    call TimerStart(t,1,true, function Test_Timer)
    set t = null
endfunction

The first thing I do is creating a timer, and assign it to a variable. I then assign the triggering unit and the targeted unit of the ability to the newly created timer. I then start the timer which will execute this function every second:

Code:
function Test_Timer takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u = GetHandleUnit(t,”u”)
    local unit v = GetHandleUnit(t,”v)
    local real r = GetHandleReal(t,”r”)+1

    if r <= 5 then
        call UnitDamageTarget(u,v,50,false,false,ATTACK_TYPE_NORMAL,etc)
        call SetHandleReal(t,"r",r)
    else
        call PauseTimer(t)
        call FlushHandleLocals(t)
        call DestroyTimer(t)
    endif
    set v = null
    set u = null
    set t = null
endfunction
In this funciton I start by getting the expired timer, which is the one we started in the Test_Actions function. Then I get the units we assigned to the timer, and finally I get a real. You may notice that get the assigned real before actually assigning it. This is to show, that if the assigned value doesn’t exist, the GetHandle functions will return 0 for reals and integers,”” for strings, false for booleans, and null for handles. The real keeps track of the elapsed time of the timer, that’s why I add one to to the real, after I get it from the handle. If the real is greater than 5, which is the max duration the unit may take damage, the handle values are nulled and the handle is destroyed afterwards. The script looks like this

Code:
function Test_Timer takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u = GetHandleUnit(t,”u”)
    local unit v = GetHandleUnit(t,”v)
    local real r = GetHandleReal(t,”r”)+1

    if r <= 5 then
        call UnitDamageTarget(u,v,50,false,false,ATTACK_TYPE_NORMAL,etc)
        call SetHandleReal(t,"r",r)
    else
        call PauseTimer(t)
        call FlushHandleLocals(t)
        call DestroyTimer(t)
    endif
    set v = null
    set u = null
    set t = null
endfunction

function Test_Actions takes nothing returns nothing
    local timer t =  CreateTimer()    

    call SetHandleHandle(t,”u”,GetTriggerUnit())   
    call SetHandleHandle(t,”v”,GetSpellTargetUnit())   
    call TimerStart(t,1,true, function Test_Timer)
    set t = null
endfunction

function InitTrig_Test takes nothing returns nothing
blabla
endfunction

Try getting the above to work, starting from scratch. If you encounter problems, here’s some pointers:
Did you create a game cache?
Did you assign a game cache to the game cache variable?
Did you change the return value of the LocalVars() function?
Are you using the assigning and getting a value using the correct name?
Did you create the timer?

Conclusion:
I hope I might have clarified the meaning of MUI, and helped you with the issues that lead you to read this tutorial. I would also like to point out, that making efficient MUI is best done with JASS. If you wish to learn jass, I’ll suggest the tutorials by Daelin or emijl3r.
 
I

IKilledKEnny

Guest
Good tutorial, you get +REP for me. However I was hoping you could add a little JASS section, to give few tips how using locals in Triggers, but I guess it's too much to ask.

Thanks for making it, cleared few things!!
 

D.K.

New Member
Reaction score
11
Nice Tutorial! +rep. This finished my map because i was trying to fix the bug after wait.
 

ReVolver

Mega Super Ultra Cool Member
Reaction score
608
I just can't imagine why a lot of users have problems doing this to their maps. GJ
 

InfectedWithDrew

I used to go here a lot.
Reaction score
95
This is not only an incredibly helpful and well done tutorial, but it also taught me a lot about what MUI is. Thanks!
 

Exide

I am amazingly focused right now!
Reaction score
448
Good tutorial. :)
 

SFilip

Gone but not forgotten
Reaction score
633
Quite informative, finally something I can link to when a "what is MUI" question pops up.
You should probably mention however that you can only declare one global local of a type from some reason...last time I tried it was impossible to have two local-global units for instance.
Also it would be an excellent idea to explain systems like local handle variables and CSCache here. A lot of people out there don't know how to create a MUI spell when they use more than one trigger for it.

And I think that MUI in fact stands just for Multi-instanceable, but I'm not sure, nor does it matter after all...
 

Tom Jones

N/A
Reaction score
437
SFilip said:
You should probably mention however that you can only declare one global local of a type from some reason...last time I tried it was impossible to have two local-global units for instance.
It didn't get copy pasted, il'll fix it right way. I was also thinking about explaining the GUI local handle system, but I after some hours of testing I found the system non efficient.
 

SFilip

Gone but not forgotten
Reaction score
633
What's preventing you from explaining the Jass system instead?
 

Tom Jones

N/A
Reaction score
437
1. Daelin made a very troughout explanation of the subject in his advanced jass tutorial.
2. How many jass users do we have on th. 20-30 against the rest who uses gui?
3. I'm lazy :p
 

SFilip

Gone but not forgotten
Reaction score
633
May I ask what is it that you did exactly?
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top