1. The long-awaited results for Concept Art Contest #11 have finally been released!
    Dismiss Notice
  2. Join Texturing Contest #30 now in a legendary battle of mythological creatures!
    Dismiss Notice
  3. The 20th iteration of the Terraining Contest is upon us! Join and create exquisite Water Structures for it.
    Dismiss Notice
  4. Hivers united and created a bunch of 2v2 melee maps. Vote for the best in our Melee Mapping Contest #4 - Poll!
    Dismiss Notice
  5. Check out the Staff job openings thread.
    Dismiss Notice

Multi-Instancible GUI Spell Making

Discussion in 'Trigger (GUI) Editor Tutorials' started by wyrmlord, May 26, 2007.

  1. wyrmlord

    wyrmlord

    Joined:
    Oct 13, 2005
    Messages:
    252
    Resources:
    5
    Tools:
    1
    Maps:
    1
    Tutorials:
    3
    Resources:
    5
    Warning The first suggestion for local variables is incorrect. While the use of multiple local variables is demonstrated, in fact only ONE local variable can be used in GUI. Until the tutorial is updated, please keep this in mind and either use only one local variable in GUI or consider using JASS for spells.

    I've been looking through the spells section lately, and what I see greatly disappoints me. Most of the spells I've looked at - specifically the GUI ones aren't multi-instancible. What this means is that they cannot be cast by more than one unit at a time and still work properly. There are many reasons to why they don't work, and I will go over some of the problems and why they don't work right. After, I will go over methods on how (with little extra trouble) you can make your spell multi-instancible.

    First example - waits
    Probably one of the most common errors I see in GUI spells involves setting a single global variable to a unit - let's say the caster. The spell then does a bunch of actions and then uses a large wait action. After waiting, the user refers to the specific global variable to do things. Let's say the wait was around 10 seconds. What would happen if another unit cast the spell within 10 seconds of the first unit? Here's an example I'll walk through:
    • bloodL
      • Events
        • Unit - A unit Begins channeling an ability
      • Conditions
        • (Ability being cast) Equal to Bloodrage
      • Actions
        • Set Luster = (Triggering unit)
        • Unit - Add speed to Luster
        • Special Effect - Create a special effect attached to the right,hand of Luster using Abilities\Spells\Other\BreathOfFire\BreathOfFireDamage.mdl
        • Set BLse1 = (Last created special effect)
        • Special Effect - Create a special effect attached to the left,hand of Luster using Abilities\Spells\Other\BreathOfFire\BreathOfFireDamage.mdl
        • Set BLse2 = (Last created special effect)
        • Special Effect - Create a special effect attached to the head of Luster using Abilities\Weapons\NecromancerMissile\NecromancerMissile.mdl
        • Set BLse3 = (Last created special effect)
        • Wait 15.00 seconds
        • Special Effect - Destroy BLse1
        • Special Effect - Destroy BLse2
        • Special Effect - Destroy BLse3
        • Unit - Remove speed from Luster

    This is a spell from a recent map I've been looking at - a real example of someone's work I've seen. Now, in the test map the spell may work find or at least seem to, but if you look closely enough you should be able to easily find the error. The ability is added to a unit, and many effects are created as well. Each of these are set to their own variable, and after 15 seconds are referenced. Well, now what happens if 2 units cast it within 15 seconds of another? I'll tell you what, the effects and the ability from the first round of the spell still remain on the hero - not a good sign!

    Second example - periodic events and multiple triggers
    This one is another one I see all the time. People need to create a spell that does a certain thing - such as knocking back repetitively over and over again. These people use another trigger and a periodic event in order to accomplish this. Sure it works for a single unit, but what if someone wants all the units in a map able to cast the spell? Let me show you an example of this:
    • Flurry
      • Events
        • Unit - A unit Begins casting an ability
      • Conditions
        • (Ability being cast) Equal to Flurry
      • Actions
        • Set F_Archer = (Triggering unit)
        • Trigger - Turn on FlurryReg <gen>
        • Animation - Change F_Archer's animation speed to 200.00% of its original speed
        • Unit - Pause F_Archer
        • For each (Integer A) from 1 to 20, do (Actions)
          • Loop - Actions
            • Wait 0.01 seconds
            • Unit - Create 1 arow effect for (Owner of F_Archer) at (Position of F_Archer) facing (Facing of F_Archer) degrees
            • Unit - Order (Last created unit) to Move To (Center of Region 002 <gen>)
            • Unit - Move F_Archer instantly to (Position of F_Archer), facing ((Facing of F_Archer) + 18.00) degrees
            • Animation - Play F_Archer's attack animation
            • -------- Line removed due to taking up too much space --------
        • Animation - Change F_Archer's animation speed to 100.00% of its original speed
        • Unit - Unpause F_Archer
        • Selection - Select F_Archer for (Owner of F_Archer)
        • Set ArrowEffect[(Integer A)] = (Units of type arow effect)
        • Trigger - Turn off FlurryReg <gen>
        • Wait 0.50 seconds
        • Unit Group - Pick every unit in ArrowEffect[(Integer A)] and do (Unit - Remove (Picked unit) from the game)


    (This trigger is initially disabled)
    • FlurryReg
      • Events
        • Time - Every 0.01 seconds of game time
      • Conditions
      • Actions
        • Region - Center Region 002 <gen> on ((Position of F_Archer) offset by 300.00 towards (Facing of F_Archer) degrees)

    You could argue that this spell isn't coded very well, but that's not the point. As you can see, this spell turns on the disabled spell in the middle of the spell. The disabled spell moves a specific region to a unit's position every .01 seconds. The problem now is that this other trigger will be turned on for some time. If this spell were being cast again, the 'F_Archer' global variable would be changed to a different unit. The first and second trigger would then be referring to the second unit to cast the spell and not the first, this is a big problem!

    Some Solutions
    There is no single answer for fixing these kind of problems. Sometimes just better thinking can find you a better solution. If you're unable to find answers, here are some possible solutions:

    GUI Local Variable Bug
    Someone once found that local variables and global variables could share the same name, and that local variables are looked at before globals. Now, if you don't understand what I just said, here's the same thing in different terms. It means you can declare a local variable with the same name as a global variable, and then each time you refer to the global variable you will really be referencing the local variable. Here's an example of what I'm trying to say:
    • Untitled Trigger 001
      • Events
      • Conditions
      • Actions
        • Custom script: local unit udg_Caster
        • Set Caster = (Triggering unit)
        • Wait 60.00 seconds
        • Unit - Kill Caster

    Now, everything should make sense to you except for the first line. The first line uses some JASS to declare a local variable with the same name as a particular global. This means that we can use the name of the global variable to really refer to the local variable. You're still probably wondering how more than one unit can use this at once and not cause problems I'm sure. If you were to create a global variable 'Caster' and test this code, you'd see that it would work without failing. Once again, you may be a bit confused so I'll explain how local variables work. Local variables are called local variables for a reason, they're only able to be accessed by the function they're in. "Functions, isn't that some kind of confusing JASS thing?" you might say. Let me tell you that GUI is converted to JASS when the map is saved (inefficient JASS, but still JASS nonetheless). All the actions inside a specific trigger are all part of one function for the most part. There are exceptions to this, but I'll review those later. Local variables being specific to each function may not make sense to you, so I'll word it in terms you may understand. Creating a local variable is like creating a completely new variable each time the spell starts. Basically, this makes it so we'll be able to refer to the caster no matter how many units cast it at once.

    Now, let's fix up the first spell to use this method:
    • bloodL
      • Events
        • Unit - A unit Begins channeling an ability
      • Conditions
        • (Ability being cast) Equal to Bloodrage
      • Actions
        • Custom script: local unit udg_Luster
        • Custom script: local effect udg_BLes1
        • Custom script: local effect udg_BLes2
        • Custom script: local effect udg_BLes3
        • Set Luster = (Triggering unit)
        • Unit - Add speed to Luster
        • Special Effect - Create a special effect attached to the right,hand of Luster using Abilities\Spells\Other\BreathOfFire\BreathOfFireDamage.mdl
        • Set BLse1 = (Last created special effect)
        • Special Effect - Create a special effect attached to the left,hand of Luster using Abilities\Spells\Other\BreathOfFire\BreathOfFireDamage.mdl
        • Set BLse2 = (Last created special effect)
        • Special Effect - Create a special effect attached to the head of Luster using Abilities\Weapons\NecromancerMissile\NecromancerMissile.mdl
        • Set BLse3 = (Last created special effect)
        • Wait 15.00 seconds
        • Special Effect - Destroy BLse1
        • Special Effect - Destroy BLse2
        • Special Effect - Destroy BLse3
        • Unit - Remove speed from Luster

    As you can see, we've added a local variable for each variable used after the wait in the spell. This spell is now MUI. Now, there's still some more explaining to do. All the global variables you make in the editor are renamed to "udg_Variable_Name" when the GUI is converted to JASS. So when you want to refer to a global variable in JASS, you need to add "udg_" to the front of the variable name and also replace any spaces with an underscore( the _ character).

    I must mention one problem with using locals in GUI. The problem is that they cannot be used everywhere. For instance, you should not refer to local variables in if/then/else blocks and "pick every ..." blocks. The reason behind this is that if/then/else sometimes creates additional functions (even if they really are unnecessary) for the condition amongst other things. The "pick ever ..." blocks will always create an additional function that is called for each picked object. All of this information I just told you is hidden from you in GUI, which is why I'm warning you.

    Implementing a Stack
    Now, that solves one possible problem, but what if we need to use a separate trigger for periodic events? Local variables can't be used outside of the function they're created in. We'll have to be much cleverer to accomplish this task, but it's still possible. This method may be a bit confusing at first, but I'll try to explain it as best I can. I'll be showing you how you might make a knockback ability with this method.

    This particular method I have in mind uses a "stack" of unique integers. When you need a unit to get knocked back, you "take" one of those integers off the stack and use it in conjunction with global arrays to put in data. The knockback trigger will then go through each index in use and do the proper actions. I realize what I said probably doesn't make sense, which is why I'll show you some triggers right now. Here is the trigger that would go off when the spell is cast:

    • Start
      • Events
        • Unit - A unit Starts the effect of an ability
      • Conditions
      • Actions
        • -------- Will increase the index, granting us a unique number --------
        • Trigger - Run NewIndex <gen> (ignoring conditions)
        • -------- Now we will set our variables --------
        • Set KnockbackUnit[CurrentIndex] = (Target unit of ability being cast)
        • Set KnockbackDistance[CurrentIndex] = 10.00
        • Set KnockbackTime[CurrentIndex] = 10.00
        • -------- Our loop trigger will handle the rest --------


    And here's the "loop" trigger that would handle moving the units:

    • Loop
      • Events
        • Time - Every 0.04 seconds of game time
      • Conditions
      • Actions
        • -------- For each index in use --------
        • For each (Integer A) from 1 to 50, do (Actions)
          • Loop - Actions
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • KnockbackUnit[(Integer A)] Not equal to No unit
              • Then - Actions
                • -------- Decrease the time in each index, when it reaches 0 the unit will stop moving --------
                • Set KnockbackTime[(Integer A)] = (KnockbackTime[(Integer A)] - 0.04)
                • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                  • If - Conditions
                    • KnockbackTime[(Integer A)] Greater than 0.00
                  • Then - Actions
                    • -------- The unit is still being knocked back, move it --------
                    • Unit - Move KnockbackUnit[(Integer A)] instantly to ((Position of KnockbackUnit[(Integer A)]) offset by KnockbackDistance[(Integer A)] towards 0.00 degrees)
                  • Else - Actions
                    • -------- The unit should no longer be knocked back, return the index for future use --------
                    • Set CurrentIndex = (Integer A)
                    • Trigger - Run ReturnIndex <gen> (ignoring conditions)
              • Else - Actions


    Now, I've purposefully left out the other triggers being run so I can first explain what's going on here. The start trigger would go off whenever a unit started the effect of an ability. Next, we run the NewIndex trigger which will return a unique integer for use with this knockback spell. The unique integer is stored by the NewIndex function in the CurrentIndex variable. Now, we have also set up a bunch of global array variables. To be exact, there is one for each type of information we want to store. We have one for the unit being knocked back, one for how far the unit is moved each cycle, and how long the knockback should go on for. Next, we store the target unit, the distance we want, and the time in their respective global arrays with the index being the value in the CurrentIndex global variable (which is a unique) integer for this spell.

    Next is the Loop trigger which handles the actual moving of the units. We're going to loop through all the indexes that are being used. To do this, you must first know that the amount of indexes being used is stored in the KnockbackIndex variable. We run a "For each integer A from 1 to 50" action which will allow us to go through each index in use and perform the necessary actions. 50 is the maximum number of indexes, so this code will run through the loop 50 times. Of course, we will need to check the current index to make sure that the unit stored in KnockbackUnit isn't equal to "no unit" (which would mean that index isn't being used). The rest should be self-explanatory because I've commented the code. For those who don't know, (Integer A) will refer to the current index being "looked at" by the trigger.

    Now I will move on to what's happening in the background. We have a stack of unique integers you might say. If you're one of those people who learns better visually, you're in luck. Here's a visual representation of the stack:

    1 2 3 ... 50


    This stack is basically a container that starts out just holding the numbers 1 through 50. Now, when we request a new index, we "take" the value stored on the top of the stack and the next value will be treated as the top of the stack. This is what the stack would look like if we grabbed the value stored at the "top" of the stack.

    2 3 4 ... 50


    Doing things like this will ensure the same number cannot be taken twice, which grants us 50 unique numbers. Now that's great, but unless we return numbers to the stack we'll have nothing left when we get to 50. That would definitely be a problem, so we'll have another trigger to return indexes back to the stack. Let's say the indexes 1 through 5 were already in use. Here's what our stack would look like:

    6 7 8 ... 50


    Now, let's say the first index was done being used. We'll put it back on the stack to be able to be used in the future by our spell. The stack would look like so if we returned the first index:

    1 6 7 8 ... 50


    Okay, I hope that gave you an understanding of how a stack works. If it didn't, that's too bad because I'm not going to really be covering it any longer. Next we need to implement this into trigger form. We obviously first need to have a stack before it can be used, so let's make an initializing trigger for it:

    • Init
      • Events
        • Map initialization
      • Conditions
      • Actions
        • -------- Fill up the array with 50 unique numbers --------
        • For each (Integer A) from 1 to 50, do (Actions)
          • Loop - Actions
            • Set KnockbackId[(Integer A)] = (Integer A)
            • Set KnockbackUnit[(Integer A)] = No unit


    Now, the KnockbackId will contain each of our unique ids to be used with the spell. We've filled the first 50 indexes of it (we shouldn't need any more indexes) with the numbers 1 - 50. We're also going to set the unit stored in each index used in the KnockbackUnit array to "no unit" which means that the index isn't being used. I'd go into further detail on how this trigger works, only I'm not really sure that there's anything more I could say.

    Moving on, let's see how we're going to get a new index from this stack:

    • NewIndex
      • Events
      • Conditions
      • Actions
        • Set KnockbackIndex = (KnockbackIndex + 1)
        • Set CurrentIndex = KnockbackId[KnockbackIndex]


    KnockbackIndex originally is set to 0, meaning the current amount of used ids from the stack is 0. This variable is equivalent to how many ids are currently in use. First thing this trigger does is increment the KnockbackIndex variable so that it will accurately show how many ids are in use. Next, we set the CurrentIndex variable to the value stored in at the "top" of the stack. Since this isn't really a stack, its size will always remain at 50 which is why we have our KnockbackIndex variable. This variable is really used to tell us which index we need to take the id from when we want a new id. Here's a visual representation first when the stack is full, and second when we're taking a value:

    KnockbackIndex (0) ->
    1
    2
    ...
    50


    KnockbackIndex (1) -> 1
    2
    3
    ...
    5


    KnockbackIndex (1) ->
    2
    3
    4
    ...
    50


    This should give you an idea of what's going on with this trigger. Now we will move onto returning an index back to the stack. I'm hoping you will be able to better understand this example after the previous trigger. Here it is:

    • ReturnIndex
      • Events
      • Conditions
      • Actions
        • Set KnockbackId[KnockbackIndex] = CurrentIndex
        • Set KnockbackIndex = (KnockbackIndex - 1)


    From the previous example, you saw that KnockbackIndex would actually point 1 space above where the top of the stack was. First, why not just place the index being returned right there on top of the stack where it belongs? Good question, let's do so first. After the index is placed one space above the stack, KnockbackIndex is pointing directly at it when it should be pointing one space above it. So, let's fix it by setting KnockbackIndex to KnockbackIndex - 1. We of course need to set KnockbackUnit on the id to be released to "no unit" for safety measures with the Loop trigger.

    Just to clear things up, CurrentIndex is equal to one of the ids that was taken from the stack while KnockbackIndex represents a position on the stack.

    This basically concludes things for now. I might choose to explain how you can make a spell just multi-instancible per player for those who have a hard time understanding the stack idea, but that will have to wait. I hope this has helped you and that you will be able to use these techniques in future projects.

    A member of Clan TDG - Quality mapmaking and playtesting.

    Tired of boring old GUI? Want to learn JASS? Take a look at these tutorials.
     
    Last edited: Aug 16, 2008
  2. Wolverabid

    Wolverabid

    Joined:
    Oct 23, 2006
    Messages:
    8,303
    Resources:
    5
    Tutorials:
    5
    Resources:
    5
    Multi-Instancible GUI Spell Making seems very helpful to me wrymlord: if all spell makers would carefully consider your points you would have a lot fewer messes to clean up and our spell resource section would contain far better spells.

    Try to force a line break into the action Unit - Cause F_Archer to damage circular area after 0.00 seconds of radius 300.00 at (Position of F_Archer), dealing 5.00 damage of attack type Pierce and damage type Normal to see if you can prevent that action from stretching the page.


    Let's see what your fellow Spell Resource Moderators have to add: I'm sure that they can help you further develop it.
     
  3. paskovich

    paskovich

    Joined:
    Jul 12, 2005
    Messages:
    794
    Resources:
    2
    Tutorials:
    2
    Resources:
    2
    This was extremely needed! Very good job!

    I can't add or correct things in this tutorial, cause i stopped using GUI, and i don't mess with this. It's far harder than jass...

    I can't see anything against not approving this, Wolve.
     
  4. Katu

    Katu

    Joined:
    Apr 13, 2007
    Messages:
    249
    Resources:
    1
    Tutorials:
    1
    Resources:
    1
    Good job, I really needed this. +rep
     
  5. wyrmlord

    wyrmlord

    Joined:
    Oct 13, 2005
    Messages:
    252
    Resources:
    5
    Tools:
    1
    Maps:
    1
    Tutorials:
    3
    Resources:
    5
    *Bump*

    Just thought I'd bring this to the attention of the moderators since this thread hasn't gotten any new replies in a few weeks. I'd like to know sooner than later if something needs to be fixed as I won't have any time after tomorrow to fix this tutorial up.
     
  6. PurplePoot

    PurplePoot

    Joined:
    Dec 14, 2005
    Messages:
    11,162
    Resources:
    3
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    3
    You don't show ReturnIndex (the trigger)

    Other than that, no complaints.

    (oh, and nice pseudo-functions :p)
     
  7. atropos007

    atropos007

    Joined:
    Jun 21, 2007
    Messages:
    116
    Resources:
    0
    Resources:
    0
    your triggers leak locations in PERIODIC triggers! baaaaaaad :>
     
  8. wyrmlord

    wyrmlord

    Joined:
    Oct 13, 2005
    Messages:
    252
    Resources:
    5
    Tools:
    1
    Maps:
    1
    Tutorials:
    3
    Resources:
    5
    Actually, it's there right at the bottom. :p I had debated on using JASS functions for this, but figured this way would be easier for GUI users.

    This tutorial is just showing methods used for multi-instancible spells. I don't think it's really necessary to remove every last leak.
     
  9. Wolverabid

    Wolverabid

    Joined:
    Oct 23, 2006
    Messages:
    8,303
    Resources:
    5
    Tutorials:
    5
    Resources:
    5
    Sorry, wyrmlord, I just now noticed that you had bumped this.
    1. Approval status: almost certain.

    2. Please detach your signature from the tutorial.

    3. Would it be possible to create a simple map that includes all of your trigger techniques so that consumers will have more flexibility in their implementation?

    4. It's really up to you and the other spell mods to determine if the leak issues should be addressed. My opinion is that in tutorials, they should. If you guys don't think it's absolutely necessary to plug them up, please make a strong disclaimer in the tut. Thanks.
     
  10. paskovich

    paskovich

    Joined:
    Jul 12, 2005
    Messages:
    794
    Resources:
    2
    Tutorials:
    2
    Resources:
    2
    I think removing all leaks would confuse beginners, as they would ask "what the well does it do with the location? what variable it is? etc.."
    This tutorial perfectly covers the MUI GUI (I always loved this phrase:p) spell making. You could leave a note: "Users should remove leaks. Here is a tutorial about it".
    Totally approvable!


    PS: But Wolve is right, a testmap would be nice!
     
  11. atropos007

    atropos007

    Joined:
    Jun 21, 2007
    Messages:
    116
    Resources:
    0
    Resources:
    0
    Considering you are very sensitive on leaks thing saying about them to every beginner, even total newbies confusing them on their first steps on WE... :/

    also people expect 'perfect' examples in tutorials
     
  12. PurplePoot

    PurplePoot

    Joined:
    Dec 14, 2005
    Messages:
    11,162
    Resources:
    3
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    3
    just do something like this at the bottom:

    Note: This tutorial has leaks, which I intentionally did not remove to not distract attention from the main focus of the tutorial

    You can read more about leaks here.

    (here being a link to some random leak tutorial)
     
  13. Wolverabid

    Wolverabid

    Joined:
    Oct 23, 2006
    Messages:
    8,303
    Resources:
    5
    Tutorials:
    5
    Resources:
    5
    Tutorial Approved

    Wyrmlord: I have detached your signature file from the tutorial.

    Please include a disclaimer regarding the leaks and create a map when you have the chance.

    Consumers of this tutorial should note that the triggers contain (minor) leaks which should be addressed when including them in their maps.

    ~ Thread moved to Trigger (GUI) Editor Tutorials.
     
  14. Kidd|WhiteHeaven|

    Kidd|WhiteHeaven|

    Joined:
    Nov 4, 2006
    Messages:
    266
    Resources:
    1
    Maps:
    1
    Resources:
    1
    Welldone wyrmlord, this tutorial helps me alot, thanks for your tutorial.
    +rep!
     
  15. Razorbrain

    Razorbrain

    Joined:
    Aug 18, 2006
    Messages:
    1,165
    Resources:
    1
    Maps:
    1
    Resources:
    1
    about the locals

    i need to make a Weather Effect local, and im guessing the custom script goes like this
    • Custom script: local weather udg_WeatherEffect

    am i wrong?
     
  16. Undifty

    Undifty

    Joined:
    May 3, 2004
    Messages:
    46
    Resources:
    0
    Resources:
    0
    Awesome :)
    Never really used the "local" function, I've always been messing around with arrays and other stuff :(

    *BUMP* ;)
     
  17. Razorbrain

    Razorbrain

    Joined:
    Aug 18, 2006
    Messages:
    1,165
    Resources:
    1
    Maps:
    1
    Resources:
    1
    i still need help :(
     
  18. Wolverabid

    Wolverabid

    Joined:
    Oct 23, 2006
    Messages:
    8,303
    Resources:
    5
    Tutorials:
    5
    Resources:
    5
    Somehow, you must have missed the sticky thread:

    The Hive Workshop > Warcraft III Tutorials > Trigger (GUI) Editor Tutorials >IMPORTANT NOTICE - Read Before Posting!
     
  19. donut3.5

    donut3.5

    Joined:
    Feb 22, 2006
    Messages:
    3,392
    Resources:
    31
    Models:
    20
    Icons:
    9
    Maps:
    1
    Tutorials:
    1
    Resources:
    31
    Very nice, this is how I used to do MUI, although I still find JASS easier.
    --donut3.5--
     
  20. Doompenguin

    Doompenguin

    Joined:
    Apr 13, 2007
    Messages:
    17
    Resources:
    0
    Resources:
    0
    Really good job, so far i've been focusing on using trigger enhanced spells only for heroes, since some werent possible with the knowledge i had at the time :p Very nice info :D