Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
Hey Guys,
Trying to develop something somewhat unique for my new map, however I've run into some confusion on what Functions and such to approach to accomplish this task. I'm trying to generate Income for my Players based on the number of Peons they currently have burrowed in an Orc Burrow. I've been going through the triggers and have gotten close to figuring it out but at the same time I'm trying to overcome the remainder of my learning curve with the editor. I've written the following pseudocode as well as how i think it would look as a Trigger in the Editor to further demonstrate my thinking and to help find answers to the question.
Pseudocode:
For each Peon loaded into an Orc Burrow, owned by Player X give Player X Gold Equal to the Number of Loaded Peons x 2
How I think the Trigger would look:
Player - Add(((Number of Player Owned Peon Units owned by Player(IntegerA)) matching (Is Being Transported) x 2) to Player(IntegerA) Current Gold
*********************************************
How could I successfully complete this kind of trigger? What functions and things do I click through as i go through making it? Thank you all so much as always, any help is always greatly appreciated!
Custom script: set bj_wantDestroyGroup = true //do this first line of the loop
Player - Add _ to Player (Integer A) Current Gold
Arithmetic: _ x 2.00
Unit Group - Count units in _
Unit Group - Units owned by Player (Integer A) matching _
AND: _ and _
1: Unit-type Comparison - (Unit type of (Matching unit) equal to (Peon))
2: Boolean Comparison - ((Matching unit) is being transported) equal to true
This will still give you gold for peons in like goblin transports, so that could use some expansion.
Going to try to implement this right now, thank you so much! Quick question however, what exactly is the line "set bj_wantDestroyGroup = true" doing in the code? Is it necessary? Just curious, as I just tried using a custom script for another Trigger earlier only to have it fail and cause an error.
The Unit Group - Unis owned by Player line creates a unit group which takes up memory until the group is destroyed. You can do it manually by saving the group into a variable and then call DestroyGroup(udg_yourvariablename) on it. This and points are the most common type of leak and they're cleared in nearly identical ways. However, groups also have an automatic cleanup way (what I wrote). If that variable is set to true then the next group created will be automatically destroyed immediately, fixing the leak.
The way I imagined it would have to be written is along the lines of having the triggering unit (the unit entering a transport) raise and integer by one, and then if it is matching two conditions (the transport is a burrow) and (the unit being transported is a peon) then it would increment as hitherto. But then something would need to be written to decrement the value.
That is the hard part. A unit can be ordered to leave multiple times, even after it has left. This was a problem with my factory system I wrote when I was 12, in multiplayer one could order the units to leave multiple times and the loaded count would be wrong.
Custom script: set bj_wantDestroyGroup = true //do this first line of the loop
Player - Add _ to Player (Integer A) Current Gold
Arithmetic: _ x 2.00
Unit Group - Count units in _
Unit Group - Units owned by Player (Integer A) matching _
AND: _ and _
1: Unit-type Comparison - (Unit type of (Matching unit) equal to (Peon))
2: Boolean Comparison - ((Matching unit) is being transported) equal to true
This will still give you gold for peons in like goblin transports, so that could use some expansion.
Would there be anyway you could provide this section of code as a screenshot from within the editor?
**Edit:
I'm only asking this because the way you had initially written it out makes sense logically, but being still somewhat new to triggers I don't understand where and how this is being placed when going through the editor. I've created a rough version based on my understanding but where you've stated these functions to be placed is not clear to me. If you could please expand on it, it would be greatly appreciated. I've included an image of what I have so far to clarify how i'm having difficulties. How can I have a AND Function in the Actions section?
I've taken a different approach on this one, to be honest. I don't know exactly how familiar you are with triggers or programming in general, but I ended up using a hashtable in order to make it work. I believe that's the easiest way to achieve this.
I'd like to explain a few things about variables in general, then introduce you to the hashtable. Later, it's going to be easier to understand the rest of the triggers.
ARRAYS:
As you may know, a variable is capable of storing a value for you until you overwrite it with another, or simply setting it to null.
-------- I'm going to be using an integer data type from now on --------
Set Score = 0
-------- Then, I'll increase its value every time I kill an enemy unit --------
Set Score = (Score + 1)
-------- However, this way is quite hard to work with variables for multiple players, --------
-------- because I don't want to create a new variable with the same purpose just for another player --------
Set ScoreP1 = 0
Set ScoreP2 = 0
-------- It would be a pain in the #&*% to work with them like that.
Now, you need to do such task - Increase and store a value for each player whenever a certain event fires. What do you do? Generally, you would just go to the variable editor and create an array variable.
Set Score[X] = 0
Pretend that the variable Score[X] will store the amount of enemy units killed by a player (X is the number of a player).
Score
Events
Unit - A unit Dies
Conditions
((Triggering unit) belongs to an enemy of (Owner of (Killing unit))) Equal to True
Actions
Set Score[(Player number of (Owner of (Killing unit)))] = (Score[(Player number of (Owner of (Killing unit)))] + 1)
-------- If I were to use a leaderboard or multiboard, it would be easy to know how many units have been killed by a player --------
Now, what If I were to store the amount of kills and deaths of a specific player in the same variable?
I'd need a 2D array variable, i.e.:
Set Score[x][y] = z
With it, I can map it like this:
Set Score[PlayerNumber][0] = value
Set Score[PlayerNumber][1] = value
Where [0] could be used for counting the number of kills, and [1] for deaths. This way, you can control your data easily instead of creating 2 different array variables for this purpose. Unfortunately, Warcraft 3 does not support 2D array (matrices). Even though, there are people out there with a cool trick where you simulate a 2D array, but this is not what we want for now.
With all that info in mind, I can now talk about the hashtable:
HASHTABLE:
A hashtable is like a 2D array variable, but it can also store many different data types (integer, real, boolean, unit, special effect, etc...) at once.
In Warcraft 3, you need to initialize a hashtable before you can use it. People generally initialize a hashtable as soon as the map finishes loading.
Hashtable - Create a hashtable
Set variable_Hashtable = (Last created hashtable)
This is how a hashtable looks like in GUI whenever you want to store a value in it:
The very first value is the number that you want to save in your hashtable. The second and third values are basically 2D array - I'll tell you something else about them later [*].
Hashtable - Save 0 as 1 of 1 in Hashtable
-------- which is the same as --------
Set Integer[1][1] = 0
Now that you know how to store an integer value in a hashtable, I'll tell you how to load that value whenever you want to use it:
Click it to zoom in
As you can see, whenever you want to use a value stored in a hashtable, you use the Hashtable - Load... function.
Now, I'll store the player kills and give them 50 gold as soon as they slay 10 enemy units:
Score
Events
Unit - A unit Dies
Conditions
((Triggering unit) belongs to an enemy of (Owner of (Killing unit))) Equal to True
Actions
Hashtable - Save ((Load 0 of (Player number of (Owner of (Killing unit))) from Hashtable) + 1) as 0 of (Player number of (Owner of (Killing unit))) in Hashtable
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Load 0 of (Player number of (Owner of (Killing unit))) from Hashtable) Equal to 10
Then - Actions
Player - Add 50 to (Owner of (Killing unit)) Current gold
Else - Actions
Which is the same as:
Score
Events
Unit - A unit Dies
Conditions
((Triggering unit) belongs to an enemy of (Owner of (Killing unit))) Equal to True
Actions
Set Score[(Player number of (Owner of (Killing unit)))][0] = (Score[(Player number of (Owner of (Killing unit)))][0] + 1)
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
Score[(Player number of (Owner of (Killing unit)))][0] Equal to 10
Then - Actions
Player - Add 50 to (Owner of (Killing unit)) Current gold
Else - Actions
Unfortunately, both codes look like a real mess, and I didn't even need to add many actions for that. Since the braces of an array [ ] and the last two values of a hashtable only accept integer data, we can take advantage of that and use a temporary integer variable in order to organize the code a little bit. Another cool thing is that we are not going to be repeating ourselves by simply calling that function once.
Score
Events
Unit - A unit Dies
Conditions
((Triggering unit) belongs to an enemy of (Owner of (Killing unit))) Equal to True
Actions
Set killer = (Player number of (Owner of (Killing unit)))
Hashtable - Save ((Load 0 of killer from Hashtable) + 1) as 0 of killer in Hashtable
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Load 0 of killer from Hashtable) Equal to 10
Then - Actions
Player - Add 50 to (Owner of (Killing unit)) Current gold
Else - Actions
Which is the same as:
Score
Events
Unit - A unit Dies
Conditions
((Triggering unit) belongs to an enemy of (Owner of (Killing unit))) Equal to True
Actions
Set killer = (Player number of (Owner of (Killing unit)))
Set Score[killer][0] = (Score[killer][0] + 1)
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
Score[killer][0] Equal to 10
Then - Actions
Player - Add 50 to (Owner of (Killing unit)) Current gold
Else - Actions
Now you can save and load data from your hashtable. It works the same way for the other data types too (unit, special effect, point, destructible, real, boolean, etc...).
THE MAGIC:
The best thing about hashtable is that you can combine it with another function which returns the unique id of every handle present in the game.
You may be wondering now: What's a handle? A handle is, basically, any object present in your map; It's a data type that all objects (other data types) are derived from. Here's a small list of handles:
unit
item
destructible
special effect
lightning
point/location
and many more.
This list of mine is really small if compared to all existent handles. You can find a complete list here.
It's even possible to declare a new handle variable and do this:
Set varHandle = (Triggering Unit)
All right, straight to the point:
As I've mentioned: Every object in the game has a unique id. You can take advantage of that, and use it in a hashtable. By doing that, you won't interfere another value present in a hashtable, which might belong to another handle.
In GUI, some map-makers would do this to get the handle id of a unit:
Score
Events
Unit - A unit Dies
Conditions
((Triggering unit) belongs to an enemy of (Owner of (Killing unit))) Equal to True
Actions
Set varHandle = (Killing unit)
-------- Increment +1 for the killing unit in the hashtable --------
Hashtable - Save ((Load 0 of (Key varHandle) from Hashtable) + 1) as 0 of (Key varHandle) in Hashtable
The function in the hashtable that gets the handle id is here: (Key varHandle)
The problem with this is that you're always calling the function that gets the handle id of a unit every time you use it in a hashtable. Personally, even if I'm creating anything in GUI, I would store the unique id in an integer variable, and then use it in the hashtable - It won't be calling the same function multiple times.
Actions
Set HandleID = (Key (Killing unit))
-------- Increment +1 for the killing unit in the hashtable --------
Hashtable - Save ((Load 0 of HandleID from Hashtable) + 1) as 0 of HandleID in Hashtable
Cool, now that we have used a handle id in order to store some value in our hashtable, we need to obtain the same handle id later. Otherwise, we won't get the desired value when we need it. As for the units, you can add them to a Unit Group, and then use it to get the handle id as soon as you want to.
Consider the following example: A unit will be healed 3 times within some duration when it kills an enemy unit. Every time the unit is healed, it needs to wait 1.5 second for the next instance.
Trigger A:
UnitKills
Events
Unit - A unit Dies
Conditions
((Triggering unit) belongs to an enemy of (Owner of (Killing unit))) Equal to True
Actions
Set Unit = (Killing unit)
Set HandleID = (Key (Killing unit))
-------- If you're wondering: "Why are you using "Picked Unit" again?" --------
-------- Because GUI is bullshit. I can't select any other variable (that's not a handle data type) whenever I'm in a handle function parameter --------
-------- But you can use the custom script, and get the handle id through it --------
Custom script: set udg_HandleID = GetHandleId(udg_Unit)
-------- Check if loop trigger is turned off. If so, turn it on. --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(HealKillers <gen> is on) Equal to False
Then - Actions
Trigger - Turn on HealKillers <gen>
Else - Actions
-------- I'm adding the number of instances that will heal the killer later (3) --------
Hashtable - Save 3 as 0 of HandleID in Hashtable
-------- And also some interval between instances so that the unit won't receive all instances at once --------
Hashtable - Save 1.50 as 1 of HandleID in Hashtable
-------- If killing unit is in the group, that means it is regenerating life already. --------
-------- Then, I'll avoid attaching a second special effect to it in order to not cause any memory leak. --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Unit is in UnitGroup) Equal to False
Then - Actions
Unit Group - Add Unit to UnitGroup
Special Effect - Create a special effect attached to the chest of Unit using Abilities\Spells\NightElf\Rejuvenation\RejuvenationTarget.mdl
Hashtable - Save Handle Of(Last created special effect) as 2 of HandleID in Hashtable
Else - Actions
Trigger B:
HealKillers
Events
Time - Every 0.50 seconds of game time
Conditions
Actions
-------- Check if group is not empty. If it's empty, turn off this trigger --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(UnitGroup is empty) Equal to True
Then - Actions
Trigger - Turn off HealKillers <gen>
Skip remaining actions
Else - Actions
-------- Loop/Enumerate --------
Unit Group - Pick every unit in UnitGroup and do (Actions)
Loop - Actions
Set Unit = (Picked unit)
Set HandleID = (Key (Picked unit))
-------- If the number of instances is greater than 0 --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Load 0 of HandleID from Hashtable) Greater than 0
Then - Actions
-------- If the interval is greater than 0, the unit should wait until it's equal to 0, so we can heal the unit --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Load 1 of HandleID from Hashtable) Greater than 0.00
Then - Actions
Hashtable - Save ((Load 1 of HandleID from Hashtable) - 0.50) as 1 of HandleID in Hashtable
Else - Actions
-------- Reset interval --------
Hashtable - Save 1.50 as 1 of HandleID in Hashtable
-------- Decrease the number of instances --------
Hashtable - Save ((Load 0 of HandleID from Hashtable) - 1) as 0 of HandleID in Hashtable
-------- Increase killer life --------
Unit - Set life of Unit to ((Life of Unit) + 25.00)
Else - Actions
-------- There's no more instance left. Remove the unit from the group --------
Special Effect - Destroy (Load 2 of HandleID in Hashtable)
Unit Group - Remove Unit from UnitGroup
Hashtable - Clear all child hashtables of child HandleID in Hashtable
* Remember when I said I would talk about those 2 values of the hashtable? Well, you have to be careful with them. In GUI, the first one is considered as the "child", and the second one is considered as the "parent".
"What's that for?"
Well, after you're done using a handle in a hashtable, their data will still remain there. Then, you can use a function that clears that "branch" up from the hashtable.
Hashtable - Clear all child hashtables of child HandleID in Hashtable
It takes the HandleID and will remove every data related to it that you stored in the hashtable.
That's it for the hashtable. I hope you have a better understanding about it.
All right, I will try to explain a bit the steps I've taken so far in order to make your income work.
Income - Triggers:
I've used different triggers/events in order to: Configure how the income should work, detect whenever an Orc Burrow loads/unloads a peon, and - at last - a periodic event to give players some gold per peon.
Here's all the events that I used, and I'll try to explain why I chose them:
-------- TRIGGER A --------
Time - Elapsed game time is 0.00 seconds
-------- TRIGGER B --------
Unit - A unit Is loaded into a transport
-------- TRIGGER C --------
Unit - A unit Dies
Unit - A unit Is issued an order targeting an object
Unit - A unit Is issued an order with no target
-------- TRIGGER D --------
Time - Every 60.00 seconds of game time
Trigger A:
This is one of the very first events that will run as soon as your map finishes loading. It's going to load a few variables and initialize your hashtable. I need to do this in order to not repeat myself whenever I'm using variables that are constant, such as: amount of gold per peon (2), store which unit type is going to give gold for every peon in it (Orc Burrow).
Trigger B:
This one is quite easy. It's when a peon enters an Orc Burrow unit, then I just increase a certain value that will be used later when a player is about to receive some income.
Trigger C:
This one decreases the value mentioned above whenever: a player orders a burrow to unload a peon, unload all peons (ordering a burrow to cast Stand Down ability), or when the Orc Burrow unit dies.
Trigger D:
This one is basically the income interval. I just load that "certain value" and multiply it by the constant variable "GoldPerPeon".
NOTE: The trigger C might seem to be a little bit complicated, because it's the one that has the most events at once.
Now, here's some detailed information about the triggers.
Variables:
Code:
OBI_AllowMessage (Boolean) : If set to true, the player will see a message when they receive their income.
OBI_Burrow (Unit-type) : Orc Burrow
OBI_GoldPerPeon (Integer) : Constant variable, it's the number that will be multiplied by the number of peons in a burrow.
OBI_Hashtable (Hashtable) : Hashtable - hehehe
OBI_KeyA (Integer) : Parent of hashtable. This is used for storing the amount of peons per burrow.
OBI_KeyB (Integer) : Parent of hashtable. This one stores the values related to the player (Current number of peons).
OBI_Message (String) : Currently as showing as: "Income: %d", where %d is amount of gold a player will receive.
OBI_Player (Player) : Temporary variable.
OBI_Unit (Unit) : Temporary variable.
Hashtable - Childs and parents:
Child: 0 - Parent: 0 - This counts the number of active orc burrows; It's used for turning on/off the trigger D (loop).
Child: 0 - Parent: OBI_KeyA - It always uses the handle id of an Orc Burrow. I'm doing this in order to store the amount of peons inside an orc burrow, so that when it dies or unloads all peons, I'll know how many of them were in it.
Child: 0 - Parent: OBI_KeyB - Used for counting all active peons that belong to a specific player.
Order Ids:
Code:
852047 - Unload (via UI)
852113 - Stand Down (Unload all)
The triggers:
Trigger A:
OBI Init
Events
Time - Elapsed game time is 0.00 seconds
Conditions
Actions
-------- Adjust your income below --------
Set OBI_Burrow = Orc Burrow
Set OBI_GoldPerPeon = 2
Set OBI_AllowMessage = True
Set OBI_Message = |cffffcc00Income:|r
Trigger - Add to OBI Loop <gen> the event (Time - Every 2.00 seconds of game time)
-------- - --------
Hashtable - Create a hashtable
Set OBI_Hashtable = (Last created hashtable)
Trigger B:
OBI Register Peons
Events
Unit - A unit Is loaded into a transport
Conditions
(Unit-type of (Transporting unit)) Equal to OBI_Burrow
Actions
Set OBI_Unit = (Transporting unit)
Custom script: set udg_OBI_KeyA = GetHandleId(udg_OBI_Unit)
Set OBI_KeyB = (Player number of (Owner of OBI_Unit))
-------- Increment the number of peons in this orc burrow --------
Hashtable - Save ((Load 0 of OBI_KeyA from OBI_Hashtable) + 1) as 0 of OBI_KeyA in OBI_Hashtable
-------- Increment the total number of active peons for this player --------
Hashtable - Save ((Load 0 of OBI_KeyB from OBI_Hashtable) + 1) as 0 of OBI_KeyB in OBI_Hashtable
-------- Is this the first time a peon enters this orc burrow? --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Load 0 of OBI_KeyA from OBI_Hashtable) Equal to 1
Then - Actions
-------- Is the loop trigger turned off? --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(OBI Loop <gen> is on) Equal to False
Then - Actions
-------- Turn it on --------
Trigger - Turn on OBI Loop <gen>
Else - Actions
-------- Increase the number of active orc burrows --------
Hashtable - Save ((Load 0 of 0 from OBI_Hashtable) + 1) as 0 of 0 in OBI_Hashtable
Else - Actions
Trigger C:
OBI Unregister Peons
Events
Unit - A unit Dies
Unit - A unit Is issued an order targeting an object
Unit - A unit Is issued an order with no target
Conditions
(Unit-type of (Triggering unit)) Equal to OBI_Burrow
Actions
Set OBI_Unit = (Triggering unit)
Custom script: set udg_OBI_KeyA = GetHandleId(udg_OBI_Unit)
Set OBI_KeyB = (Player number of (Owner of OBI_Unit))
-------- Time to distinguish which event triggered this trigger --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Load 0 of OBI_KeyA from OBI_Hashtable) Greater than 0
Then - Actions
Custom script: if GetTriggerEventId() == EVENT_PLAYER_UNIT_DEATH or GetIssuedOrderId() == 852113 then // If the Orc Burrow just died or cast Stand Down
-------- Discount peons currently working by the number of peons that were in this burrow. --------
Hashtable - Save ((Load 0 of OBI_KeyB from OBI_Hashtable) - (Load 0 of OBI_KeyA from OBI_Hashtable)) as 0 of OBI_KeyB in OBI_Hashtable
-------- Reduce the number of active orc burrows. --------
Hashtable - Save ((Load 0 of 0 from OBI_Hashtable) - 1) as 0 of 0 in OBI_Hashtable
-------- Clear some data up from the hashtable related to this orc burrow. --------
Hashtable - Clear all child hashtables of child OBI_KeyA in OBI_Hashtable
Custom script: elseif GetIssuedOrderId() == 852047 then // The other events run within this block
-------- Only one peon left the orc burrow (player ordered it to leave through command card) --------
-------- Reduce the number of peons for this burrow and the player. --------
Hashtable - Save ((Load 0 of OBI_KeyA from OBI_Hashtable) - 1) as 0 of OBI_KeyA in OBI_Hashtable
Hashtable - Save ((Load 0 of OBI_KeyB from OBI_Hashtable) - 1) as 0 of OBI_KeyB in OBI_Hashtable
-------- This burrow has no more peons in it? --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Load 0 of OBI_KeyA from OBI_Hashtable) Equal to 0
Then - Actions
-------- Reduce the number of active burrows by 1 --------
Hashtable - Save ((Load 0 of 0 from OBI_Hashtable) - 1) as 0 of 0 in OBI_Hashtable
-------- Clear its data from hashtable --------
Hashtable - Clear all child hashtables of child OBI_KeyA in OBI_Hashtable
Else - Actions
Custom script: endif
-------- Note that this is outside the custom script IF/THEN/ELSE --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Load 0 of 0 from OBI_Hashtable) Equal to 0
Then - Actions
-------- There's no active orc burrows. Turn this trigger off --------
Trigger - Turn off OBI Loop <gen>
Else - Actions
Else - Actions
Trigger D:
OBI Loop
Events
Conditions
Actions
For each (Integer OBI_KeyA) from 1 to 12, do (Actions)
Loop - Actions
Set OBI_Player = (Player(OBI_KeyA))
-------- Load the total number of active peons this player owns. --------
Set OBI_KeyB = ((Load 0 of (Player number of OBI_Player) from OBI_Hashtable) x OBI_GoldPerPeon)
Player - Add OBI_KeyB to OBI_Player Current gold
-------- Am I allowed to tell the players how much gold they've just received? --------
-------- Keep in mind that the player must have, at least, 1 active peon. --------
Custom script: if udg_OBI_AllowMessage and GetLocalPlayer() == udg_OBI_Player and udg_OBI_KeyB > 0 then
The variables OBI_KeyA, OBI_KeyB, and OBI_Player are temporary, and also used in the loop.
The trigger A gives Trigger D the periodic event.
In trigger C, I used the function GetTriggerEventId() in order to know which event triggered those actions.
Trigger C again - The function GetIssuedOrderId() returns the order id of an orc burrow; Very important to determine which order the orc burrow executed (Unload all / a peon).
That's *it*! I hope this helps you in your future projects and/or triggers from now on. I probably committed many mistakes regarding the English language (RIP).
My man this thread is 5 years old and the original poster hasn't logged in to the site since 2019. A reply like you made doesn't help anyone, it just confuses people into thinking this thread is active and has a current problem that needs to be resolved.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.