• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[General] Localize an array in GUI

Status
Not open for further replies.
Level 6
Joined
Jul 23, 2018
Messages
243
Hello everyone,
Like the title said, I want to localize an array variable in order to avoid too many variables because it's getting messed up. Thank you.
 

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,456
Hello everyone,
Like the title said, I want to localize an array variable in order to avoid too many variables because it's getting messed up. Thank you.
You'll only be able to use/reference these local variables in the trigger that creates them:
  • Actions
    • Custom script: local integer array MyArray
    • Custom script: set MyArray[0] = 100
    • Custom script: set MyArray[1] = 200
    • Custom script: set MyArray[2] = 300
If you aren't already, I recommend creating reusable Global variables like so:
  • Actions
    • Set VariableSet TempInteger[0] = 100
    • Set VariableSet TempInteger[1] = 500
    • Set VariableSet TempReal[0] = 155.55
    • Set VariableSet TempReal[1] = 399.25
    • Set VariableSet TempPoint[0] = (Position of (Triggering unit))
    • Set VariableSet TempPoint[1] = (Target point of ability being cast)
    • Set VariableSet TempPoint[2] = (Center of (Playable map area))
These "Temp" variables can be re-used throughout your triggers without issue as long as you handle them properly.

Hashtables are also fairly clean since they don't require as many variables.
 
Level 6
Joined
Jul 23, 2018
Messages
243
You'll only be able to use/reference these local variables in the trigger that creates them:
  • Actions
    • Custom script: local integer array MyArray
    • Custom script: set MyArray[0] = 100
    • Custom script: set MyArray[1] = 200
    • Custom script: set MyArray[2] = 300
If you aren't already, I recommend creating reusable Global variables like so:
  • Actions
    • Set VariableSet TempInteger[0] = 100
    • Set VariableSet TempInteger[1] = 500
    • Set VariableSet TempReal[0] = 155.55
    • Set VariableSet TempReal[1] = 399.25
    • Set VariableSet TempPoint[0] = (Position of (Triggering unit))
    • Set VariableSet TempPoint[1] = (Target point of ability being cast)
    • Set VariableSet TempPoint[2] = (Center of (Playable map area))
These "Temp" variables can be re-used throughout your triggers without issue as long as you handle them properly.

Hashtables are also fairly clean since they don't require as many variables.

So by reusing Global variable, I can refer them from another trigger as long as it does not create any leak?
 

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,456
Of course, that's the entire point of a GLOBAL variable, it's accessed globally throughout your triggers.

The main leaks that you need to handle properly are for Points, Groups, and Special Effects.

Integers, Reals, Booleans, and Strings will never leak.
 
Level 6
Joined
Jul 23, 2018
Messages
243
Of course, that's the entire point of a GLOBAL variable, it's accessed globally throughout your triggers.

The main leaks that you need to handle properly are for Points, Groups, and Special Effects.

Integers, Reals, Booleans, and Strings will never leak.

So with 2 variables I can make 1 global in an unit event and make it local in periodic event?
  • Unit Cast
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
    • Actions
      • Set Caster = (Triggering unit)
  • Loop
    • Events
      • Time - Every 2.00 seconds of game time
    • Conditions
    • Actions
      • Custom script: local unit Caster_Local
      • Set Caster_Local = Caster
 
Level 6
Joined
Jul 23, 2018
Messages
243
Assume that I want to make a charge ability for knight. So I need a trigger for setting up variables, points, caster, and distance. and another for moving units within a time-frame.
  • Charge
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Animate Dead
    • Actions
      • Set Caster = (Triggering unit)
      • Set TargetPoint = (Target point of ability being cast)
      • Unit - Pause Caster
      • Unit - Turn collision for Caster Off
      • Trigger - Turn on Charge Loop <gen>
  • Charge Loop
    • Events
      • Time - Every 0.05 seconds of game time
    • Conditions
    • Actions
      • Set CasterPoint = (Position of Caster)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Distance between CasterPoint and TargetPoint) Greater than or equal to 40.00
          • (Caster is alive) Equal to True
        • Then - Actions
          • Set Angle = (Angle from CasterPoint to TargetPoint)
          • Set MoveToPoint = (CasterPoint offset by 50.00 towards Angle degrees)
          • Unit - Move Caster instantly to MoveToPoint
          • Custom script: call RemoveLocation(udg_CasterPoint)
          • Custom script: call RemoveLocation(udg_TargetPoint)
        • Else - Actions
          • -------- Caster is either dead or you've reached the destination, turns trigger off --------
          • Unit - Unpause Caster
          • Unit - Turn collision for Caster On
          • Custom script: set udg_Caster = null
          • Custom script: call RemoveLocation(udg_CasterPoint)
          • Custom script: call RemoveLocation(udg_TargetPoint)
          • Trigger - Turn off (This trigger)

But something's wrong, it's not MUI, which may cause leaks when units are casting at the same time.
To avoid that I localize the variables, but at the same time I don't want to have too much variables either. So I make one variable being an array one to be the target point, caster point and move to point.
Edit: a bit messsy, trying to fix. Fixing done
 
Last edited:

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,456
Making the variables Local won't work for a trigger like this. Local variables will only work in the trigger that created them, but you're working with 2 triggers here.

You need to use one of these: Unit Indexing, Dynamic Indexing, Hashtables, or some other method of making it MUI.

Here's an example of an MUI Charge using a Unit Indexer (GUI Unit Indexer 1.4.0.0). I reuse some Temp variables to help keep things minimal:
  • Cast Charge
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
    • Actions
      • Set VariableSet TempPoint[0] = (Position of (Triggering unit))
      • Set VariableSet CV = (Custom value of (Triggering unit))
      • Set VariableSet Charge_Point[CV] = (Target point of ability being cast)
      • Set VariableSet Charge_Angle[CV] = (Angle from TempPoint[0] to Charge_Point[CV])
      • Custom script: call RemoveLocation (udg_TempPoint[0])
      • Unit Group - Add (Triggering unit) to Charge_Group
      • Trigger - Turn on Charge Loop <gen>
  • Charge Loop
    • Events
      • Time - Every 0.03 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in Charge_Group and do (Actions)
        • Loop - Actions
          • Set VariableSet CV = (Custom value of (Picked unit))
          • Set VariableSet TempPoint[0] = (Position of (Picked unit))
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) is alive) Equal to True
              • (Distance between TempPoint[0] and Charge_Point[CV]) Greater than or equal to 40.00
            • Then - Actions
              • Set VariableSet TempPoint[1] = (TempPoint[0] offset by 40.00 towards Charge_Angle[CV] degrees.)
              • Unit - Move (Picked unit) instantly to TempPoint[1]
              • Custom script: call RemoveLocation (udg_TempPoint[1])
            • Else - Actions
              • Unit Group - Remove (Picked unit) from Charge_Group.
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Number of units in Charge_Group) Equal to 0
                • Then - Actions
                  • Trigger - Turn off (This trigger)
                • Else - Actions
          • Custom script: call RemoveLocation (udg_TempPoint[0])
The only new variables are Charge_Point, Charge_Angle, and Charge_Group. The other variables can be used in your other triggers as well if handled appropriately.
 
Last edited:
Level 6
Joined
Jul 23, 2018
Messages
243
I have these triggers which are from this thread of mine but only one was bothered to answer. Anyway, I decided to use local udg_ but like the title said, localizing array variables to save variables. After that, the only thing I struggle with is removing location because I'm using a wait function. Then I realized I don't need the unit indexer when I'm localizing a variable and destroy and null it after that. I remember that you have to destroy the object first then nullify it afterward?
 
Last edited:

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
Like the title said, I want to localize an array variable in order to avoid too many variables because it's getting messed up. Thank you.
Localize is not the correct word for this.

I am not sure why you want to do this. Thanks to the folder structure and folder item declared variables feature you can now group global variables together where they are used. This makes managing them much easier, however using them will still be difficult due to other short comings of GUI.

In case you did not know what a local variable was I suggest reading the Wikipedia page. In case of JASS and Lua the local scope is the function it is declared in.
You'll only be able to use/reference these local variables in the trigger that creates them:
A single trigger can produce many functions and the scope of a local variable is the function it is declared in. For group and player loops are an example where GUI creates a separate function for the actions and so will not be able to use the local variables declared in the main action block.
These "Temp" variables can be re-used throughout your triggers without issue as long as you handle them properly.
In this case "handle them properly" means that actions run while the variables hold useful data does not cause other triggers to run that use those variables and so overwrite the useful data. This is why people often use separate arrays.

I would suggest separate temp variables instead of an array. Only use arrays if you stand to benefit or require from the index they offer. Might also be a good idea to write comments listing what temp variables each trigger or part of trigger use to avoid conflicts.
The main leaks that you need to handle properly are for Points, Groups, and Special Effects.
Groups include both Unit and Player Groups. Exception is "All Players" player group which is constant and so must never be destroyed.
 
Level 6
Joined
Jul 23, 2018
Messages
243
Localize is not the correct word for this.

I am not sure why you want to do this. Thanks to the folder structure and folder item declared variables feature you can now group global variables together where they are used. This makes managing them much easier, however using them will still be difficult due to other short comings of GUI.

In case you did not know what a local variable was I suggest reading the Wikipedia page. In case of JASS and Lua the local scope is the function it is declared in.
I'm terribly sorry if I used the wrong word. I really don't know how we say these programming languages. Anyway, I want to limit the numbers variables I have now. That's why I made this thread, "Localize an array in GUI." Instead of saying localize, could I say "declare a local array variable"?

A single trigger can produce many functions and the scope of a local variable is the function it is declared in. For group and player loops are an example where GUI creates a separate function for the actions and so will not be able to use the local variables declared in the main action block.
So then I need Unit Indexer?

In this case "handle them properly" means that actions run while the variables hold useful data does not cause other triggers to run that use those variables and so overwrite the useful data. This is why people often use separate arrays.

I would suggest separate temp variables instead of an array. Only use arrays if you stand to benefit or require from the index they offer. Might also be a good idea to write comments listing what temp variables each trigger or part of trigger use to avoid conflicts.
It's true, but when there's too many variables then optimization has to made

Groups include both Unit and Player Groups. Exception is "All Players" player group which is constant and so must never be destroyed.
I'll be careful with that

So this is the gate close triggers I'm fixing.
  • Gate Close Hori South
    • Events
      • Unit - A unit Begins casting an ability
    • Conditions
      • (Ability being cast) Equal to Close Gate
      • (Unit-type of (Casting unit)) Equal to Wooden Gate (Opened) (Hori) (Faces South)
    • Actions
      • Custom script: local location array udg_local_Loc_Gate
      • Set Loc_Gate[0] = (Position of (Casting unit))
      • Set Loc_Gate[1] = (Loc_Gate[CV_Array[0]] offset by (0.00, 100.00))
      • Set Loc_Gate[2] = (Loc_Gate[CV_Array[0]] offset by (0.00, -100.00))
      • Region - Center Gate Hori Det <gen> on Loc_Gate[0]
      • Region - Center Gate Hori T <gen> on Loc_Gate[1]
      • Region - Center Gate Hori B <gen> on Loc_Gate[2]
      • Set Loc_Gate[3] = (Loc_Gate[0] offset by (-200.00, -300.00))
      • Set Loc_Gate[4] = (Loc_Gate[0] offset by (200.00, 300.00))
      • Wait 0.02 seconds
      • Custom script: set bj_wantDestroyGroup = true
      • Unit Group - Pick every unit in (Units in Gate Hori Det <gen> matching ((((Matching unit) is A structure) Equal to False) and (((Matching unit) is A ground unit) Equal to True))) and do (Actions)
        • Loop - Actions
          • If ((Gate Hori T <gen> contains (Picked unit)) Equal to True) then do (Unit - Move (Picked unit) instantly to Loc_Gate[1]) else do (Do nothing)
          • If ((Gate Hori B <gen> contains (Picked unit)) Equal to True) then do (Unit - Move (Picked unit) instantly to Loc_Gate[2]) else do (Do nothing)
      • Wait 0.02 seconds
      • Custom script: call RemoveLocation(udg_Loc_Gate[0])
      • Custom script: call RemoveLocation(udg_Loc_Gate[1])
      • Custom script: call RemoveLocation(udg_Loc_Gate[2])
      • Custom script: call RemoveLocation(udg_Loc_Gate[3])
      • Custom script: call RemoveLocation(udg_Loc_Gate[4])
      • Custom script: set Loc_Gate = null
      • Unit - Replace (Casting unit) with a Wooden Gate (Closed) (Hori) (Faces South) using The old unit's relative life and mana
      • Animation - Play (Last replaced unit)'s stand animation
      • Selection - Select (Last replaced unit) for (Owner of (Casting unit))
Not sure how to set loc_gate to null because it said variables not declared
Edit: do I need to declare variables in jass?
 
Last edited:

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,456
I'm terribly sorry if I used the wrong word. I really don't know how we say these programming languages. Anyway, I want to limit the numbers variables I have now. That's why I made this thread, "Localize an array in GUI." Instead of saying localize, could I say "declare a local array variable"?


So then I need Unit Indexer?


It's true, but when there's too many variables then optimization has to made


I'll be careful with that

So this is the gate close triggers I'm fixing.
  • Gate Close Hori South
    • Events
      • Unit - A unit Begins casting an ability
    • Conditions
      • (Ability being cast) Equal to Close Gate
      • (Unit-type of (Casting unit)) Equal to Wooden Gate (Opened) (Hori) (Faces South)
    • Actions
      • Custom script: local location array udg_local_Loc_Gate
      • Set Loc_Gate[0] = (Position of (Casting unit))
      • Set Loc_Gate[1] = (Loc_Gate[CV_Array[0]] offset by (0.00, 100.00))
      • Set Loc_Gate[2] = (Loc_Gate[CV_Array[0]] offset by (0.00, -100.00))
      • Region - Center Gate Hori Det <gen> on Loc_Gate[0]
      • Region - Center Gate Hori T <gen> on Loc_Gate[1]
      • Region - Center Gate Hori B <gen> on Loc_Gate[2]
      • Set Loc_Gate[3] = (Loc_Gate[0] offset by (-200.00, -300.00))
      • Set Loc_Gate[4] = (Loc_Gate[0] offset by (200.00, 300.00))
      • Wait 0.02 seconds
      • Custom script: set bj_wantDestroyGroup = true
      • Unit Group - Pick every unit in (Units in Gate Hori Det <gen> matching ((((Matching unit) is A structure) Equal to False) and (((Matching unit) is A ground unit) Equal to True))) and do (Actions)
        • Loop - Actions
          • If ((Gate Hori T <gen> contains (Picked unit)) Equal to True) then do (Unit - Move (Picked unit) instantly to Loc_Gate[1]) else do (Do nothing)
          • If ((Gate Hori B <gen> contains (Picked unit)) Equal to True) then do (Unit - Move (Picked unit) instantly to Loc_Gate[2]) else do (Do nothing)
      • Wait 0.02 seconds
      • Custom script: call RemoveLocation(udg_Loc_Gate[0])
      • Custom script: call RemoveLocation(udg_Loc_Gate[1])
      • Custom script: call RemoveLocation(udg_Loc_Gate[2])
      • Custom script: call RemoveLocation(udg_Loc_Gate[3])
      • Custom script: call RemoveLocation(udg_Loc_Gate[4])
      • Custom script: set Loc_Gate = null
      • Unit - Replace (Casting unit) with a Wooden Gate (Closed) (Hori) (Faces South) using The old unit's relative life and mana
      • Animation - Play (Last replaced unit)'s stand animation
      • Selection - Select (Last replaced unit) for (Owner of (Casting unit))
Not sure how to set loc_gate to null because it said variables not declared
Edit: do I need to declare variables in jass?
So the first issue is that you're using (Casting unit) instead of (Triggering unit). You cannot and should not reference Event Responses after using a Wait. These Event Responses are basically Global variables that Blizzard assigns for you. Whenever a unit casts a spell, Casting unit is assigned to that unit. Whenever a unit dies, Dying unit is assigned to that unit. Whenever a unit enters a region, Entering unit is assigned to that unit. Etc... The problem is that these variables becomes lost the moment you use a Wait or if any time passes between the setting of the variable and the attempt to use it. I think there are some exceptions in which they don't get unassigned, but even then they can get overwritten by another trigger that uses them (or even the same trigger).

There is one exception. Triggering Unit is treated like a Local variable for whatever reason, it's the only Event Response that's like this. Meaning you can reference Triggering Unit safely throughout that Gate trigger even after the Waits. That's the ONLY thing that's safe to reference after the Waits in that trigger.

Also, Waits need to be synced when playing online, which makes them imprecise. Wait 0.02 seconds is more like Wait ~0.2 seconds. See this for more information: Wait Command, Good or Bad?
That being said, I don't think you need to use Waits in the first place.

Also, note that the Event "A unit begins casting an ability" is not safe to use in a lot of circumstances. This is because this Event runs the moment you BEGIN casting, which means that the ability hasn't even finished it's cast animation yet. So if your casting unit gets interrupted (the player can simply order the unit to "Stop") then the Event will run but the ability will never go off. If a player is aware of this, they can spam the ability/Stop command to run the trigger over and over again. HOWEVER, since your Gate is being replaced there's no room for exploitation so it's safe to use it.

With all of that being said, here's your Close Gate trigger using only "Temp" variables to handle the Points and a "Temp" Unit Group to move the nearby units:
  • -------- Move Nearby Units --------
  • Set VariableSet TempGroup[0] = (Units within 300.00 of TempPoint[0].)
  • Unit Group - Pick every unit in TempGroup[0] and do (Actions)
    • Loop - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • ((Picked unit) is A structure) Equal to False
          • ((Picked unit) is A ground unit) Equal to True
        • Then - Actions
          • Set VariableSet TempPoint[3] = (Position of (Picked unit))
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Distance between TempPoint[3] and TempPoint[1]) Less than or equal to (Distance between TempPoint[3] and TempPoint[2])
            • Then - Actions
              • Unit - Move (Picked unit) instantly to TempPoint[1]
            • Else - Actions
              • Unit - Move (Picked unit) instantly to TempPoint[2]
          • Custom script: call RemoveLocation (udg_TempPoint[3])
        • Else - Actions

So you know how I said "it's safe to re-use these temp variables as long as you handle them properly." Well, here's a perfect example of having POTENTIAL to leak and cause issues.

So i'm moving the Picked units, there's nothing inherently wrong with this:
  • Unit - Move (Picked unit) instantly to TempPoint[1]
But let's say I move the Picked unit(s) to a REGION, which is used in this other trigger:
  • Enter Region Example
    • Events
      • Unit - A unit enters ExampleRegion <gen>
    • Conditions
    • Actions
      • Set VariableSet TempPoint[1] = (Position of (Triggering unit))
      • Special Effect - Create a special effect at TempPoint[1] using Abilities\Spells\Human\ThunderClap\ThunderClapCaster.mdl
      • Special Effect - Destroy (Last created special effect)
      • Custom script: call RemoveLocation (udg_TempPoint[1])
So this is a problem! This trigger can actually run DURING the Close Gate trigger since Moving the picked unit(s) may move them into the Region, causing the trigger to run. And since both of these triggers use TempPoint[1], conflicts will occur, and the Close Gate trigger will break/have leaks.

This is what it'd look like if you moved a Picked unit into our ExampleRegion:
  • Close Gate
    • Events
      • Unit - A unit Begins casting an ability
    • Conditions
    • Actions
      • -------- Setup Variables --------
      • Set VariableSet TempPoint[0] = (Position of (Triggering unit))
      • Set VariableSet TempPoint[1] = (TempPoint[0] offset by (0.00, 150.00))
      • Set VariableSet TempPoint[2] = (TempPoint[0] offset by (0.00, -150.00))
      • -------- --------
      • -------- Move Nearby Units --------
      • Set VariableSet TempGroup[0] = (Units within 300.00 of TempPoint[0].)
      • Unit Group - Pick every unit in TempGroup[0] and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) is A structure) Equal to False
              • ((Picked unit) is A ground unit) Equal to True
            • Then - Actions
              • Set VariableSet TempPoint[3] = (Position of (Picked unit))
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Distance between TempPoint[3] and TempPoint[1]) Less than or equal to (Distance between TempPoint[3] and TempPoint[2])
                • Then - Actions
                  • Unit - Move (Picked unit) instantly to TempPoint[1]
                  • -------- Let's say that the Picked unit is moved into our ExampleRegion: --------
                  • Set VariableSet TempPoint[1] = (Position of (Picked unit))
                  • Special Effect - Create a special effect at TempPoint[1] using Abilities\Spells\Human\ThunderClap\ThunderClapCaster.mdl
                  • Special Effect - Destroy (Last created special effect)
                  • Custom script: call RemoveLocation (udg_TempPoint[1])
                • Else - Actions
                  • Unit - Move (Picked unit) instantly to TempPoint[2]
              • Custom script: call RemoveLocation (udg_TempPoint[3])
            • Else - Actions
      • Custom script: call DestroyGroup (udg_TempGroup[0])
      • -------- --------
      • -------- Clean Up Point Leaks --------
      • Custom script: call RemoveLocation (udg_TempPoint[0])
      • Custom script: call RemoveLocation (udg_TempPoint[1])
      • Custom script: call RemoveLocation (udg_TempPoint[2])
      • -------- --------
      • -------- Replace Gate --------
      • Unit - Replace (Triggering unit) with a Footman using The old unit's relative life and mana
      • Animation - Play (Last replaced unit)'s stand animation
      • Selection - Add (Last replaced unit) to selection for (Owner of (Triggering unit))
See the issue? TempPoint[1] is used in both of these triggers and it's getting overwritten and Removed. Since it's removed, our Close Gate trigger can no longer reference it which will cause problems. Also, this creates a Leak since our previous TempPoint[1] was overwritten without ever being destroyed.

This is why you need to handle Temp variables properly. A solution in this particular case is to use a separate variable for the Enters region trigger, OR, use an Array index that you know is safe/unused by anything else. Maybe you can reserve TempPoint[10] to be used for your Enter Region triggers for example.
 
Last edited:
Level 6
Joined
Jul 23, 2018
Messages
243
So the first issue is that you're using (Casting unit) instead of (Triggering unit). You cannot and should not reference Event Responses after using a Wait. These Event Responses are basically Global variables that Blizzard assigns for you. Whenever a unit casts a spell, Casting unit is assigned to that unit. Whenever a unit dies, Dying unit is assigned to that unit. Whenever a unit enters a region, Entering unit is assigned to that unit. Etc... The problem is that these variables becomes lost the moment you use a Wait or if any time passes between the setting of the variable and the attempt to use it. I think there are some exceptions in which they don't get unassigned, but even then they can get overwritten by another trigger that uses them (or even the same trigger).

There is one exception. Triggering Unit is treated like a Local variable for whatever reason, it's the only Event Response that's like this. Meaning you can reference Triggering Unit safely throughout that Gate trigger even after the Waits. That's the ONLY thing that's safe to reference after the Waits in that trigger if you plan on making this MUI.

Also, Waits need to be synced when playing online, which makes them imprecise. Wait 0.02 seconds is more like Wait ~0.2 seconds. See this for more information: Wait Command, Good or Bad?
If you reduce the Wait to 0.01 seconds it will probably be closer to what you wanted. Although, I don't think you need Waits in the first place.

Also, note that the Event "A unit begins casting an ability" is not safe to use in a lot of circumstances. This is because this Event runs the moment you BEGIN casting, which means that the the ability hasn't even finished it's cast animation yet. So if your casting unit gets interrupted (the player can simply order the unit to "Stop") then the Event will run but the ability will never go off. If a player is aware of this, they can spam the ability/Stop command to run the trigger over and over again. HOWEVER, since your Gate is being replaced immediately then it's safe to do this since there's no chance for the ability to get interrupted/exploited.

With all of that being said, here's your Close Gate trigger using only "Temp" variables to handle the Points and a "Temp" Unit Group to move the nearby units:
  • Close Gate
    • Events
      • Unit - A unit Begins casting an ability
    • Conditions
    • Actions
      • -------- Setup Variables --------
      • Set VariableSet TempPoint[0] = (Position of (Triggering unit))
      • Set VariableSet TempPoint[1] = (TempPoint[0] offset by (0.00, 150.00))
      • Set VariableSet TempPoint[2] = (TempPoint[0] offset by (0.00, -150.00))
      • -------- --------
      • -------- Move Nearby Units --------
      • Set VariableSet TempGroup[0] = (Units within 300.00 of TempPoint[0].)
      • Unit Group - Pick every unit in TempGroup[0] and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) is A structure) Equal to False
              • ((Picked unit) is A ground unit) Equal to True
            • Then - Actions
              • Set VariableSet TempPoint[3] = (Position of (Picked unit))
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Distance between TempPoint[3] and TempPoint[1]) Less than or equal to (Distance between TempPoint[3] and TempPoint[2])
                • Then - Actions
                  • Unit - Move (Picked unit) instantly to TempPoint[1]
                • Else - Actions
                  • Unit - Move (Picked unit) instantly to TempPoint[2]
              • Custom script: call RemoveLocation (udg_TempPoint[3])
            • Else - Actions
      • Custom script: call DestroyGroup (udg_TempGroup[0])
      • -------- --------
      • -------- Clean Up Point Leaks --------
      • Custom script: call RemoveLocation (udg_TempPoint[0])
      • Custom script: call RemoveLocation (udg_TempPoint[1])
      • Custom script: call RemoveLocation (udg_TempPoint[2])
      • -------- --------
      • -------- Replace Gate --------
      • Unit - Replace (Triggering unit) with a Footman using The old unit's relative life and mana
      • Animation - Play (Last replaced unit)'s stand animation
      • Selection - Add (Last replaced unit) to selection for (Owner of (Triggering unit))


So you know how I said "it's safe to re-use these temp variables as long as you handle them properly." Well, here's a perfect example of having POTENTIAL to leak and cause issues.

So i'm moving the Picked units, there's nothing inherently wrong with this:
  • Unit - Move (Picked unit) instantly to TempPoint[1]
But let's say I move the Picked unit(s) to a REGION, which is used in this other trigger:
  • Enter Region Example
    • Events
      • Unit - A unit enters ExampleRegion <gen>
    • Conditions
    • Actions
      • Set VariableSet TempPoint[1] = (Position of (Triggering unit))
      • Special Effect - Create a special effect at TempPoint[1] using Abilities\Spells\Human\ThunderClap\ThunderClapCaster.mdl
      • Special Effect - Destroy (Last created special effect)
      • Custom script: call RemoveLocation (udg_TempPoint[1])
So this is a problem! This trigger can actually run DURING the Close Gate trigger since Moving the picked unit(s) may move them into the Region, causing the trigger to run. And since both of these triggers use TempPoint[1], conflicts will occur, and the Close Gate trigger will break/have leaks.

This is what it'd look like if you moved a Picked unit into our ExampleRegion:
  • Close Gate
    • Events
      • Unit - A unit Begins casting an ability
    • Conditions
    • Actions
      • -------- Setup Variables --------
      • Set VariableSet TempPoint[0] = (Position of (Triggering unit))
      • Set VariableSet TempPoint[1] = (TempPoint[0] offset by (0.00, 150.00))
      • Set VariableSet TempPoint[2] = (TempPoint[0] offset by (0.00, -150.00))
      • -------- --------
      • -------- Move Nearby Units --------
      • Set VariableSet TempGroup[0] = (Units within 300.00 of TempPoint[0].)
      • Unit Group - Pick every unit in TempGroup[0] and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) is A structure) Equal to False
              • ((Picked unit) is A ground unit) Equal to True
            • Then - Actions
              • Set VariableSet TempPoint[3] = (Position of (Picked unit))
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Distance between TempPoint[3] and TempPoint[1]) Less than or equal to (Distance between TempPoint[3] and TempPoint[2])
                • Then - Actions
                  • Unit - Move (Picked unit) instantly to TempPoint[1]
                  • -------- Let's say that the Picked unit is moved into our ExampleRegion: --------
                  • Set VariableSet TempPoint[1] = (Position of (Picked unit))
                  • Special Effect - Create a special effect at TempPoint[1] using Abilities\Spells\Human\ThunderClap\ThunderClapCaster.mdl
                  • Special Effect - Destroy (Last created special effect)
                  • Custom script: call RemoveLocation (udg_TempPoint[1])
                • Else - Actions
                  • Unit - Move (Picked unit) instantly to TempPoint[2]
              • Custom script: call RemoveLocation (udg_TempPoint[3])
            • Else - Actions
      • Custom script: call DestroyGroup (udg_TempGroup[0])
      • -------- --------
      • -------- Clean Up Point Leaks --------
      • Custom script: call RemoveLocation (udg_TempPoint[0])
      • Custom script: call RemoveLocation (udg_TempPoint[1])
      • Custom script: call RemoveLocation (udg_TempPoint[2])
      • -------- --------
      • -------- Replace Gate --------
      • Unit - Replace (Triggering unit) with a Footman using The old unit's relative life and mana
      • Animation - Play (Last replaced unit)'s stand animation
      • Selection - Add (Last replaced unit) to selection for (Owner of (Triggering unit))
See the issue? TempPoint[1] is used in both of these triggers and it's not getting overwritten (LEAK) and Removed. Since it's removed, our Close Gate trigger can no longer reference it which breaks anything referencing TempPoint[1].

This is why you need to handle Temp variables properly. A solution in this particular case is to use a separate variable for the Enters region trigger, OR, use an Array index that you know is safe/unused by anything else. Maybe you can reserve TempPoint[10] to be used for your Enter Region triggers for example.
Things are getting more and more complicated. Could this problem be solved with unit indexer? And also can I do
JASS:
Call RemoveLocation(Loc_Gate[GetUnitUserData+1])
Call RemoveLocation(Loc_Gate[GetUnitUserData+2])
to not get overwritten location?
 

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,456
Well, I gave you a working Close Gate trigger. Did you take a look at it? There's not really any need for Indexing in this case.

And GetUnitUserData+1 would get you another unit's custom value, so you'd end up referencing some random unit.

The Unit Indexer assigns each unit on the map with a UNIQUE custom value.

So say we spawn 3 units, a Footman, Knight, Rifleman, in that order:

The system kicks in and assigns custom values to each unit:
Footman's custom value = 1
Knight's custom value = 2
Rifleman's custom value = 3

If you reference 1, you're referencing the Footman. If you reference 2, you're referencing the Knight, etc...

So let's say our Footman = Triggering unit:
  • Set Variable TempPoint[((Custom value of (Triggering unit)) + 1)] = (Position of (Triggering unit))
This would be referencing the custom value of our Knight, not out Footman. Since it basically says:
  • Set Variable TempPoint[(1 + 1)] = (Position of (Triggering unit))
 
Last edited:

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
I'm terribly sorry if I used the wrong word. I really don't know how we say these programming languages. Anyway, I want to limit the numbers variables I have now. That's why I made this thread, "Localize an array in GUI." Instead of saying localize, could I say "declare a local array variable"?
It's true, but when there's too many variables then optimization has to made
Variable count has little impact on game performance. For it to have an impact a ludicrous number must be used. For example if variables are being procedurally generated by a program resulting in hundreds of thousands or millions of variables. In such a case I recommend Lua be used instead of JASS since Lua generally scales better and is likely much faster anyway.
Not sure how to set loc_gate to null because it said variables not declared
Your script must have an error in it. You declare a local variable called udg_local_Loc_Gate but then are using a variable called udg_Loc_Gate instead. Even if that was fixed it still would not work for reasons I stated earlier that the local variable scope is just a single function while that code compiles into multiple functions.

With arrays you must null each index used. Another reasons it is often easier to use separate variables instead of arrays with constant indices.
 
Level 6
Joined
Jul 23, 2018
Messages
243
Well, I gave you a working Close Gate trigger. Did you take a look at it? There's not really any need for Indexing in this case.

And GetUnitUserData+1 would get you another unit's custom value, so you'd end up referencing some random unit.

The Unit Indexer assigns each unit on the map with a UNIQUE custom value.

So say we spawn 3 units, a Footman, Knight, Rifleman, in that order:

The system kicks in and assigns custom values to each unit:
Footman's custom value = 1
Knight's custom value = 2
Rifleman's custom value = 3

If you reference 1, you're referencing the Footman. If you reference 2, you're referencing the Knight, etc...

So let's say our Footman = Triggering unit:
  • Set Variable TempPoint[((Custom value of (Triggering unit)) + 1)] = (Position of (Triggering unit))
This would be referencing the custom value of our Knight, not out Footman. Since it basically says:
  • Set Variable TempPoint[(1 + 1)] = (Position of (Triggering unit))
I thought it's gonna work the other way around

Variable count has little impact on game performance. For it to have an impact a ludicrous number must be used. For example if variables are being procedurally generated by a program resulting in hundreds of thousands or millions of variables. In such a case I recommend Lua be used instead of JASS since Lua generally scales better and is likely much faster anyway.

Your script must have an error in it. You declare a local variable called udg_local_Loc_Gate but then are using a variable called udg_Loc_Gate instead. Even if that was fixed it still would not work for reasons I stated earlier that the local variable scope is just a single function while that code compiles into multiple functions.

With arrays you must null each index used. Another reasons it is often easier to use separate variables instead of arrays with constant indices.
For now, I'm sticking with Jass, it seems to a good method to me now. If I switch suddenly, I have to start all over.
 
Status
Not open for further replies.
Top