- Joined
- Oct 13, 2005
- Messages
- 233
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:
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:
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:
Now, let's fix up the first spell to use this method:
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:
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:
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.
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:
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:
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:
Moving on, let's see how we're going to get a new index from this stack:
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:
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.
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
-
Events
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 --------
-
Loop - Actions
- 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)
-
Events
-
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)
-
Events
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, 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
-
Events
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 --------
-
Events
-
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)
-
If - Conditions
- Else - Actions
-
If - Conditions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
Loop - Actions
-
Events
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
-
Loop - Actions
-
Events
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 (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)
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: