• 🏆 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!

[Trigger] Neutral Hostile Units gain health if unengaged

Status
Not open for further replies.
Level 2
Joined
Jul 29, 2017
Messages
4
I've done my best and I can't get it to work, I need that every 30 seconds it checks if any neutral hostile units are attacking or being attacked and if they are not, they gain an ability that recovers them to full health, and if they are, the ability is removed. This is what I have, i dont know why this trigger i have always give them the Neutral Blight, no matter what i do:
Neutral Blight.png
 
Level 20
Joined
Feb 23, 2014
Messages
1,264
Nice trigger collage (no joke), though this method is easier, both for posting and people who want to quote-answer:How To Post Your Trigger
Another piece of recommended reading, because your triggers leaks a lot: Things That Leak

---

As for your triggers... I don't really get the logic.

I mean, the very concept of checking every 30 seconds is already scuffed. Like, let's say the trigger fires at T=0 and T=30, unit #1 gets attacked at T=5 and unit#2 at T=25 seconds. Why does unit#1 have to wait for the effect to kick in 25 seconds, while unit #1 has it fire after 5 seconds?

I think a much better approach would be to have individualized timers and set up the ability to take effect after a given unit hasn't attacked or been attacked for 30 seconds. Or, better yet, you can use the Damage Engine 5.7.1.2 to check for units actually taking damage, because the "unit is attacked" event can be abused with a little bit of micro, i.e. you can cancel an attack before it completes, resulting in the event firing despite no unit being actually attacked/hit. If that's what you want, great, but I think using the Damage Engine would be more accurate.

I'll explain how to do that below, for now let's look at your triggers.

The first issue is that you're checking random units. For context, how the "Unit is attacked" event works is that when it fires, both the unit that performs the attack and the unit that is being attacked are stored and thus have a set value. So, for example, if you have a Footman attacking a Gnoll, the attacking unit is going to be set to Footman and the attacked unit to Gnoll.

This is important, because it's very specific, so when you're doing conditions, you're effectively checking if <your unit> is equal to the stored value of the <attacked unit> or <attacking unit>. Now, if you select a random unit from a unit group, especially one that probably is quite sizeable like Neutral Hostile units, the chances that this action just so happens to pick exactly the unit that is involved in this particular attack sequence (and thus is "stored" as either the <attacked unit> or <attacking unit> by the even) are very tiny. This makes your trigger extremely unreliable.

At best, because there's another issue, which is that you're requiring BOTH conditions to be true. The only instance when this even has a chance to work correctly is if you have Neutral Hostile units fighting each other, which I doubt you do. In the situation where a Neutral Hostile is attacked or attacking another player's unit, that unit won't be included in (Units owned by Neutral Hostile) group, so no matter how lucky you are, it cannot be randomly selected from it. The result is that in such cases either the first or the second condition will always be false, so the trigger that applies the healing effect to be always on.

---

If you're having a had time understanding what I just said, imagine this - you have 50 white balls. You ask somebody to write "1" on one of the and "2" on another one. Then you're throwing these balls into some container, mix them up and then you tell the other person to randomly take out one of the balls, hoping that they will get the ball with number "1" on it. The chances of that happening are very slim and that's what each of your condition is.

Moreover, going with the same metaphore, what you're actually doing is asking someone to write "1" on a white ball and "2" on a blue ball. Then you tell them to drop only the white ball into the container, while putting the blue one on the table. Finally, you ask them to draw two balls from the box that has only white balls inside it. What are the odds that this person will draw both with numbers on it? Zero, because even if they are lucky and get the "1" ball, the "2" ball isn't even in the container and cannot physically be drawn. This is what your trigger is doing.

---

Another issue is that even if that trigger worked correctly, the one that applies the healing effect does so to EVERY Neutral Hostile unit. So what will end up happening is that if the trigger is turned on, ALL Neutral Hostile units will get the ability, regardless if they were involved in an attack sequence or not.

Additionally, the trigger that removes the healing effect doesn't have an event, nor do I see you manually running it anywhere, so once a unit gets the effect, it's going to have it forever as the removal trigger won't fire. And even if it did, you're making the same mistake as above, so it would remove all healing effects, again, regardless if the unit was attacked or not.

On top of that, your triggers are leaking a lot and using the wait in the control trigger suggest, you might not grasp the idea of something being MUI.

---

All of these combined result in what you see happening. The conditions in the control trigger are most likely never true (unless you have two Neutral Hostile units fighting each other, but even then the chances of the condition getting passed are extremely low), so the trigger that applies the healing effect is permanently turned on, adding the healing ability to all Neutral Hostile units every 30 seconds. This should explain why they always have it.

--- IF YOU WANT TO ATTEMPT TO FIX YOUR TRIGGERS ---

a) At map initialization

  • Initialization
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set HealingUnitGroupVariable = (Units owned by Neutral Hostile) // Set Variable; use a unit group variable

b) On attack:

  • Control
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions) // If/Then/Else multiple functions
        • If - Conditions
          • (Owner of (Triggering unit)) Equal to Neutral Hostile // Player Condition
        • Then - Actions
          • -------- Remove your healing ability from picked unit here --------
          • Unit Group - Remove (Triggering unit) from HealingUnitGroupVariable // Unit Group - Remove Unit
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Attacking unit)) Equal to Neutral Hostile
        • Then - Actions
          • -------- Remove your healing ability from picked unit here --------
          • Unit Group - Remove (Attacking unit) from HealingUnitGroupVariable
        • Else - Actions
Notice how I used the (Triggering Unit) parameter instead of (Attacked Unit) - I did it, because (Triggering Unit) is generally better and since the unit that is being attacked is also the one that causes the trigger to fire, it just so happens to be the (Triggering Unit) in this case.

As for having two separate condition blocks - you could also do something like this:

  • Control
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Triggering unit)) Equal to Neutral Hostile
        • Then - Actions
          • -------- Remove your healing ability from picked unit here --------
          • Unit Group - Remove (Triggering unit) from HealingUnitGroupVariable
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Owner of (Attacking unit)) Equal to Neutral Hostile
            • Then - Actions
              • -------- Remove your healing ability from picked unit here --------
              • Unit Group - Remove (Attacking unit) from HealingUnitGroupVariable
            • Else - Actions

The difference is that this will only check the second condition if the first one isn't true, which is more efficient, but it also removes the possibility of two Neutral Hostile units fighting each other (which I find funny) as only one "Then" segment will be executed. If you don't plan to do something like this, I'd highly recommend using the version below.

c) Periodic trigger:

  • Periodic
    • Events
      • Time - Every 30.00 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in HealingUnitGroupVariable and do (Actions) // Pick every unit in unit group and do (multiple actions)
        • Loop - Actions
          • -------- Add your ability to (Picked Unit) here --------
      • Custom script: call DestroyGroup(udg_HealingUnitGroupVariable) //clears the leak, put "udg_" before your variable name in the brackets
      • Set HealingUnitGroupVariable = (Units owned by Neutral Hostile)

Okay, so the logic behind this all... At map initialization you create a unit group that holds all Neutral Hostile units. When one of them is attacked or attacks, it's being removed from that group and loses its healing ability. Then every 30 seconds you select all units remaining in the healing group (i.e. ones that haven't attacked or been attacked since the trigger last fired) and you add the healing ability to them. Once that is done, you're setting the healing group variable to once again include all Neutral Hostile units, so that the check is refreshed.

---

I'll come back later to show you a better way to do this, but now I have a monster of a headache, so my brain is not working too great and I need a break from the PC. For that reason, it's possible I screwed something up in my suggestions, so please read what others might have to say in case they point out any blunder on my part. Good luck.
 
Last edited:
Level 20
Joined
Feb 23, 2014
Messages
1,264
I think the first post is long enough to warrant another one for clarity's sake.

Okay, so how do we do it my way? It'd be the easiest to use either hashtables or dynamic indexing. The links in previous sentence will take you to really good guides on both. It doesn't really matter which one you pick, both are fine and both will allow you to keep track of each unit individually. Since I prefer hashtables (and think they're much easier to get into than indexing), I'll explain how to use them.

---

Imagine a regular table, where you named rows by using letters A, B, C (...) columns by using numbers 1, 2, 3 (...):

Hashtables.jpg


Now, if I asked you to tell me which table cell is green, you can easily do so by describing both the row and the column it uses. In the case of the example above, the answer would be Row C and Column 4 -> C4. Alternatively, you could also say something like this: "Column 4 of Row C". Remember this.

That's exactly how hashtables work. Okay, but how does this help us? Well, each unit has a certain hidden unique number attached to it called the handle ID, which can be used by hashtables to describe where the data should be or is stored. For instance, let's say a Footman unit has the ID 24312 - you can read and use that number to point toward a data cell inside a hashtable.

That way you can have a row 24312 where in columns 1, 2, 3, etc. you store the data corresponding to that Footman. If you want to store some data about it then you can just tell the hashtable to use "Column 1 of Row 24312". That's exactly what this action does:

Hashtables2.jpg


Now, the key to navigating hashtables is consistency. If you're going to store a certain data type (e.g. a duration of an effect), it's best that you use the same "column" for every row to do it. What this does is create a situation where just by the unit's handle ID (which is your row number), you can easily access desired data, because the column for that type of data is always the same and known to you.

So, let's say you want to store a 30 seconds timer for each unit - 30 is going to be the information you store, unit's handle ID will be your row number and the column number is your choice, though it's preferrable to start with the first one, which in hashtables is described by number 0.

Let's do this:

1. If we want to use a hashtable, we have to first create it. It's also smart to put it under some hashtable variable, so that you can easily reference it. You're free to do it whenever you want, but since the intention appears to be for the timer to run the entire game, it'd probably be best to do it at map initialization. All you need to do that are these two simple actions:

  • Actions
    • Hashtable - Create a hashtable
    • Set MyFirstHashtable = (Last created hashtable)

2. Now that we have our hashtable ready to go, we need to store our data. And here's where you have to make a choice - how accurate do you want to be with it? If you're comfortable checking/updating the timer every second, I'd recommend going with integers (full numbers), you might want to go with reals (decimal numbers). This matters, because both integers and reals use a different action to store its value in a hashtable.

For the sake of keeping this easy, I'll go with integers and checking the timer every 1 second. Okay, so let's store our data. First pick all units owned by the Neutral Hostile player and bind it to a unit group variable (you'll need it!):

  • Actions
    • Set NeutralHostileUnits = (Units owned by Neutral Hostile)

I'd recommend doing this right after creating the hashtable, so that the timer starts immediately when the map launches. Now, pick all units in the group:

  • Actions
    • Set NeutralHostileUnits = (Units owned by Neutral Hostile)
    • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
      • Loop - Actions

And add a hashtable action - Save Integer - and fill it in with the timer duration (30 seconds), your "column" number (0) and the hashtable variable:

Hashtables3.jpg


Now, when you click at the last unfilled value, you're going to be greeted with this:

Hashtables4.jpg


This allows you to read the units handle ID. Since we're using this action inside a "Pick all units" action, we want to read the handle ID of a unit that is currently being picked, so click on "Handle" and find "Picked Unit" in the dropdown menu. That's all. Your completed action should look like this:

  • Actions
    • Set NeutralHostileUnits = (Units owned by Neutral Hostile)
    • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
      • Loop - Actions
        • Hashtable - Save 30 as 0 of (Key (Picked unit)) in MyFirstHashtable

What this does is that every unit in the group (i.e. every unit owned by Neutral Hostile), will have 30 stored under "Column 0" of their handle ID "Row" in the hashtable. One more important thing - don't remove this unit group. It's not a leak, because we're going to be using it. Here's how:

a) Create a trigger like this:

  • TriggerName
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
        • Loop - Actions

b) Create an integer variable and set it's value under "Loop - Actions". When you click on the "Value" field, select the function called: "Hashtable - Load Integer Value (hashtable). This one:

Hashtables5.jpg


You'll need to fill in the "Column" and "Row" numbers plus the name of the Hashtable. It's done exactly the same as above. You should get this:

  • Actions
    • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
      • Loop - Actions
        • Set IntegerVariable = (Load 0 of (Key (Picked unit)) from MyFirstHashtable)

Here's where the magic happens. Add actions to make the trigger look like this:

  • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
    • Loop - Actions
      • Set IntegerVariable = (Load 0 of (Key (Picked unit)) from MyFirstHashtable)
      • Set IntegerVariable = (IntegerVariable - 1) // use the Arithmetic function in the value field of Set Variable
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • IntegerVariable Equal to 0
        • Then - Actions
          • -------- Add your ability to (Picked unit) here --------
        • Else - Actions
      • Hashtable - Save IntegerVariable as 0 of (Key (Picked unit)) in MyFirstHashtable

Why? Let's go through it together - what happens is that you read the timer value from the hashtable and store it in the IntegerVariable. Then you reduce it by 1 to account for the fact that 1 second has passed since you last read the timer value. And then you check - if the variable is equal to 0, that means the timer was depleted (i.e. the unit wasn't in combat for the required amount of time) and so you can give it your ability.

Then you simply store the new updated timer value in the hashtable (overwriting the old one) for future use. So, in practice - each unit will have its "0" column in hashtable go like this: 30 -> 29 -> 28 -> ... -> 0 (apply ability) -> -1 -> -2, etc. That's the individualized timer I've talked about.

---

So, how does this solve your problem? Well, if you do something like this:

  • TriggerName
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Triggering unit)) Equal to Neutral Hostile
        • Then - Actions
          • -------- Remove your healing ability here --------
          • Hashtable - Save 30 as 0 of (Key (Triggering unit)) in MyFirstHashtable
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Owner of (Attacking unit)) Equal to Neutral Hostile
            • Then - Actions
              • -------- Remove your healing ability here --------
              • Hashtable - Save 30 as 0 of (Key (Attacking unit)) in MyFirstHashtable
            • Else - Actions

Then every time a neutral hostile unit is attacked or attacks, the timer value stored in the hashtable is going to be set back to its full value of 30, meaning that in order for the unit to get the healing ability, it will have to wait full 30 seconds.

The final piece of the puzzle is this:

  • TriggerName
    • Events
      • Unit - A unit Dies
    • Conditions
      • ((Triggering unit) is in NeutralHostileUnits) Equal to True // Boolean condition, function: Unit - Unit in Unit Group
    • Actions
      • Unit Group - Remove (Triggering unit) from NeutralHostileUnits
      • Hashtable - Clear all child hashtables of child (Key (Triggering unit)) in MyFirstHashtable // Hashtable - Clear Child Hashtable

The purpose of this little trigger is to remove all Neutral Hostile units from the timer system and clear all their timer data, so that you don't have any unnecessary timer checks or pointlessly stored data.

That'd be all. I know it's a lot of reading, but trust me - hashtables are EXTREMELY useful, so learning them (or indexing) is really worth it :)
 
Last edited:
Level 2
Joined
Jul 29, 2017
Messages
4
I think the first post is long enough to warrant another one for clarity's sake.

Okay, so how do we do it my way? It'd be the easiest to use either hashtables or dynamic indexing. The links in previous sentence will take you to really good guides on both. It doesn't really matter which one you pick, both are fine and both will allow you to keep track of each unit individually. Since I prefer hashtables (and think they're much easier to get into than indexing), I'll explain how to use them.

---

Imagine a regular table, where you named rows by using letters A, B, C (...) columns by using numbers 1, 2, 3 (...):

View attachment 381314

Now, if I asked you to tell me which table cell is green, you can easily do so by describing both the row and the column it uses. In the case of the example above, the answer would be Row C and Column 4 -> C4. Alternatively, you could also say something like this: "Column 4 of Row C". Remember this.

That's exactly how hashtables work. Okay, but how does this help us? Well, each unit has a certain hidden unique number attached to it called the handle ID, which can be used by hashtables to describe where the data should be or is stored. For instance, let's say a Footman unit has the ID 24312 - you can read and use that number to point toward a data cell inside a hashtable.

That way you can have a row 24312 where in columns 1, 2, 3, etc. you store the data corresponding to that Footman. If you want to store some data about it then you can just tell the hashtable to use "Column 1 of Row 24312". That's exactly what this action does:

View attachment 381320

Now, the key to navigating hashtables is consistency. If you're going to store a certain data type (e.g. a duration of an effect), it's best that you use the same "column" for every row to do it. What this does is create a situation where just by the unit's handle ID (which is your row number), you can easily access desired data, because the column for that type of data is always the same and known to you.

So, let's say you want to store a 30 seconds timer for each unit - 30 is going to be the information you store, unit's handle ID will be your row number and the column number is your choice, though it's preferrable to start with the first one, which in hashtables is described by number 0.

Let's do this:

1. If we want to use a hashtable, we have to first create it. It's also smart to put it under some hashtable variable, so that you can easily reference it. You're free to do it whenever you want, but since the intention appears to be for the timer to run the entire game, it'd probably be best to do it at map initialization. All you need to do that are these two simple actions:

  • Actions
    • Hashtable - Create a hashtable
    • Set MyFirstHashtable = (Last created hashtable)

2. Now that we have our hashtable ready to go, we need to store our data. And here's where you have to make a choice - how accurate do you want to be with it? If you're comfortable checking/updating the timer every second, I'd recommend going with integers (full numbers), you might want to go with reals (decimal numbers). This matters, because both integers and reals use a different action to store its value in a hashtable.

For the sake of keeping this easy, I'll go with integers and checking the timer every 1 second. Okay, so let's store our data. First pick all units owned by the Neutral Hostile player and bind it to a unit group variable (you'll need it!):

  • Actions
    • Set NeutralHostileUnits = (Units owned by Neutral Hostile)

I'd recommend doing this right after creating the hashtable, so that the timer starts immediately when the map launches. Now, pick all units in the group:

  • Actions
    • Set NeutralHostileUnits = (Units owned by Neutral Hostile)
    • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
      • Loop - Actions

And add a hashtable action - Save Integer - and fill it in with the timer duration (30 seconds), your "column" number (0) and the hashtable variable:

View attachment 381322

Now, when you click at the last unfilled value, you're going to be greeted with this:

View attachment 381323

This allows you to read the units handle ID. Since we're using this action inside a "Pick all units" action, we want to read the handle ID of a unit that is currently being picked, so click on "Handle" and find "Picked Unit" in the dropdown menu. That's all. Your completed action should look like this:

  • Actions
    • Set NeutralHostileUnits = (Units owned by Neutral Hostile)
    • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
      • Loop - Actions
        • Hashtable - Save 30 as 0 of (Key (Picked unit)) in MyFirstHashtable

What this does is that every unit in the group (i.e. every unit owned by Neutral Hostile), will have 30 stored under "Column 0" of their handle ID "Row" in the hashtable. One more important thing - don't remove this unit group. It's not a leak, because we're going to be using it. Here's how:

a) Create a trigger like this:

  • TriggerName
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
        • Loop - Actions

b) Create an integer variable and set it's value under "Loop - Actions". When you click on the "Value" field, select the function called: "Hashtable - Load Integer Value (hashtable). This one:

View attachment 381325

You'll need to fill in the "Column" and "Row" numbers plus the name of the Hashtable. It's done exactly the same as above. You should get this:

  • Actions
    • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
      • Loop - Actions
        • Set IntegerVariable = (Load 0 of (Key (Picked unit)) from MyFirstHashtable)

Here's where the magic happens. Add actions to make the trigger look like this:

  • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
    • Loop - Actions
      • Set IntegerVariable = (Load 0 of (Key (Picked unit)) from MyFirstHashtable)
      • Set IntegerVariable = (IntegerVariable - 1) // use the Arithmetic function in the value field of Set Variable
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • IntegerVariable Equal to 0
        • Then - Actions
          • -------- Add your ability to (Picked unit) here --------
        • Else - Actions
      • Hashtable - Save IntegerVariable as 0 of (Key (Picked unit)) in MyFirstHashtable

Why? Let's go through it together - what happens is that you read the timer value from the hashtable and store it in the IntegerVariable. Then you reduce it by 1 to account for the fact that 1 second has passed since you last read the timer value. And then you check - if the variable is equal to 0, that means the timer was depleted (i.e. the unit wasn't in combat for the required amount of time) and so you can give it your ability.

Then you simply store the new updated timer value in the hashtable (overwriting the old one) for future use. So, in practice - each unit will have its "0" column in hashtable go like this: 30 -> 29 -> 28 -> ... -> 0 (apply ability) -> -1 -> -2, etc. That's the individualized timer I've talked about.

---

So, how does this solve your problem? Well, if you do something like this:

  • TriggerName
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Triggering unit)) Equal to Neutral Hostile
        • Then - Actions
          • -------- Remove your healing ability here --------
          • Hashtable - Save 30 as 0 of (Key (Triggering unit)) in MyFirstHashtable
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Owner of (Attacking unit)) Equal to Neutral Hostile
            • Then - Actions
              • -------- Remove your healing ability here --------
              • Hashtable - Save 30 as 0 of (Key (Attacking unit)) in MyFirstHashtable
            • Else - Actions

Then every time a neutral hostile unit is attacked or attacks, the timer value stored in the hashtable is going to be set back to its full value of 30, meaning that in order for the unit to get the healing ability, it will have to wait full 30 seconds.

The final piece of the puzzle is this:

  • TriggerName
    • Events
      • Unit - A unit Dies
    • Conditions
      • ((Triggering unit) is in NeutralHostileUnits) Equal to True // Boolean condition, function: Unit - Unit in Unit Group
    • Actions
      • Unit Group - Remove (Triggering unit) from NeutralHostileUnits
      • Hashtable - Clear all child hashtables of child (Key (Triggering unit)) in MyFirstHashtable // Hashtable - Clear Child Hashtable

The purpose of this little trigger is to remove all Neutral Hostile units from the timer system and clear all their timer data so that you don't have any unnecessary timer checks or pointlessly stored data.

That'd be all. I know it's a lot of reading, but trust me - hashtables are EXTREMELY useful, so learning them (or indexing) is really worth it :)

First of all, Thank you very much for taking the time to give such a thorough and complete explanation I really needed that, but I've tried both solutions you gave me and it doesn't want to work, it never gives the critters the aura, here are my triggers, maybe I'm screwing something up, FYI all neutral units are already placed in.

  • Starter
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set MyFirstHashtable = (Last created hashtable)
  • Action
    • Events
    • Conditions
    • Actions
      • Set NeutralHostileUnits = (Units owned by Neutral Hostile)
      • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
        • Loop - Actions
          • Hashtable - Save 30 as 0 of (Key (Picked unit)) in MyFirstHashtable
  • Trrigger
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
        • Loop - Actions
          • Set IntegerVariable = (Load 0 of (Key (Picked unit)) from MyFirstHashtable)
          • Set IntegerVariable = (IntegerVariable - 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • IntegerVariable Equal to 0
            • Then - Actions
              • Unit - Add Neutral Blight (Neutral) to (Picked unit)
            • Else - Actions
      • Hashtable - Save IntegerVariable as 0 of (Key (Picked unit)) in MyFirstHashtable
  • Attack
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Triggering unit)) Equal to Neutral Hostile
        • Then - Actions
          • Unit - Remove Neutral Blight (Neutral) from (Triggering unit)
          • Hashtable - Save 30 as 0 of (Key (Triggering unit)) in MyFirstHashtable
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Owner of (Attacking unit)) Equal to Neutral Hostile
            • Then - Actions
              • Unit - Remove Neutral Blight (Neutral) from (Attacking unit)
              • Hashtable - Save 30 as 0 of (Key (Attacking unit)) in MyFirstHashtable
            • Else - Actions
  • Death
    • Events
      • Unit - A unit Dies
    • Conditions
      • ((Triggering unit) is in NeutralHostileUnits) Equal to True
    • Actions
      • Unit Group - Remove (Triggering unit) from NeutralHostileUnits
      • Hashtable - Clear all child hashtables of child (Key (Triggering unit)) in MyFirstHashtable
 
Last edited:
Level 20
Joined
Feb 23, 2014
Messages
1,264
First of all, Thank you very much for taking the time to give such a thorough and complete explanation I really needed that, but I've tried both solutions you gave me and it doesn't want to work, it never gives the critters the aura, here are my triggers, maybe I'm screwing something up, FYI all neutral units are already placed in.

You're welcome. On my part, I'm happy to see that you're willing to learn :) That's great! :)

  • Action
    • Events
    • Conditions
    • Actions
      • Set NeutralHostileUnits = (Units owned by Neutral Hostile)
      • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
        • Loop - Actions
          • Hashtable - Save 30 as 0 of (Key (Picked unit)) in MyFirstHashtable

This trigger doesn't have an event. Put these actions in the initialization/starter trigger, after setting the value of the hashtable variable. Like this:

  • Starter
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set MyFirstHashtable = (Last created hashtable)
      • Set NeutralHostileUnits = (Units owned by Neutral Hostile)
      • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
        • Loop - Actions
          • Hashtable - Save 30 as 0 of (Key (Picked unit)) in MyFirstHashtable

---

  • Trrigger
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
        • Loop - Actions
          • Set IntegerVariable = (Load 0 of (Key (Picked unit)) from MyFirstHashtable)
          • Set IntegerVariable = (IntegerVariable - 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • IntegerVariable Equal to 0
            • Then - Actions
              • Unit - Add Neutral Blight (Neutral) to (Picked unit)
            • Else - Actions
      • Hashtable - Save IntegerVariable as 0 of (Key (Picked unit)) in MyFirstHashtable

The last action is outside of the loop, so it won't work, because there's no unit picked. And yeah, sometimes it can be a bit annoying to properly place actions in GUI, so here's a quick tip - drag the save integervariable action directly above the if/then/else block, then drag the if/then/else block above that action :D

You should get this:

  • Trrigger
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
        • Loop - Actions
          • Set IntegerVariable = (Load 0 of (Key (Picked unit)) from MyFirstHashtable)
          • Set IntegerVariable = (IntegerVariable - 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • IntegerVariable Equal to 0
            • Then - Actions
              • Unit - Add Neutral Blight (Neutral) to (Picked unit)
            • Else - Actions
          • Hashtable - Save IntegerVariable as 0 of (Key (Picked unit)) in MyFirstHashtable

P.S. Also - feel free to name variables however you like, you don't have to use the names I had in my example above :)
 
Last edited:

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,455
Nice explanation!

One minor thing I wanted to comment on, I don't think you want to Pick every unit in the Starter trigger:
  • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
  • Loop -> Save 30 as 0...
You're starting the timer for all of the hostile units despite them not having been attacked yet.

I realize you made an effort to keep this as simple as possible but I want to point out some other optimizations that could be made:
1) Design it to Add/Remove units to and from the NeutralHostileUnits group so that it doesn't enumerate over unnecessary units.
2) Turn off the Timer while NeutralHostileUnits is empty and turn it back on when it's needed again.
3) Maybe add another trigger to detect when a unit becomes the target of an ability. This would be similar to the Attack trigger, just for detecting things like when you Storm Bolt a Neutral unit. Ideally, a Damage Engine would be used if you wanted all of this to work properly but that's a lot of extra overhead and complication.
 
Last edited:

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,455
I'm confused. Is picking all units in a unit group broken at map initialization? How else is he going to store the intial timer value in the hashtable?
It's not broken but why would he need to store an initial value? Isn't that value only given when a unit is attacked.

Another thing, with the current setup the Timer will continue to subtract from the unit's hashtable value and bring it below 0.

I believe the optimizations I mentioned in my previous post would fix all of this.
 
Last edited:
Level 20
Joined
Feb 23, 2014
Messages
1,264
Okay, so it took me a couple of minutes to process what you said and you're right - the units most likely start at full health anyway, so they don't need the ability until they get attacked and when they do, the value is stored anyway, so it doesn't make sense to do it at initialization. And if you also add units to unit group at the same time you store the value, then the periodic trigger will only go over the units that have an active timer, instead of all of them.

@WidescreeN what we're talking about would look like this:

  • Starter
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set MyFirstHashtable = (Last created hashtable)

  • Trrigger
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in NeutralHostileUnits and do (Actions)
        • Loop - Actions
          • Set IntegerVariable = (Load 0 of (Key (Picked unit)) from MyFirstHashtable)
          • Set IntegerVariable = (IntegerVariable - 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • IntegerVariable Equal to 0
            • Then - Actions
              • Unit - Add Neutral Blight (Neutral) to (Picked unit)
              • Unit Group - Remove (Picked unit) from NeutralHostileUnits // the timer has expired, so you no longer need to count it down for that unit
              • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in MyFirstHashtable // there's no timer, so you don't need that value stored
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • NeutralHostileUnits is empty Equal to True // this is to check if there are still units with the timer, if not then...
                • Then - Actions
                  • Trigger - Turn off (This trigger) // you don't need to count down the timer, so you can turn this trigger off
                • Else - Actions
            • Else - Actions
              • Hashtable - Save IntegerVariable as 0 of (Key (Picked unit)) in MyFirstHashtable // moved this under "Else", because you only need to store the updated timer value if it's above (not equal to) 0

  • Attack
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Triggering unit)) Equal to Neutral Hostile
        • Then - Actions
          • Unit Group - Add (Triggering unit) to NeutralHostileUnits
          • Unit - Remove Neutral Blight (Neutral) from (Triggering unit)
          • Hashtable - Save 30 as 0 of (Key (Triggering unit)) in MyFirstHashtable
          • Trigger - Turn on Trrigger <gen> // you turn on that trigger, because you need it to be on to count down the timer
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Owner of (Attacking unit)) Equal to Neutral Hostile
            • Then - Actions
              • Unit Group - Add (Attacking unit) to NeutralHostileUnits
              • Unit - Remove Neutral Blight (Neutral) from (Attacking unit)
              • Hashtable - Save 30 as 0 of (Key (Attacking unit)) in MyFirstHashtable
              • Trigger - Turn on Trrigger <gen>
            • Else - Actions

  • Death
    • Events
      • Unit - A unit Dies
    • Conditions
      • ((Triggering unit) is in NeutralHostileUnits) Equal to True
    • Actions
      • Unit Group - Remove (Triggering unit) from NeutralHostileUnits
      • Hashtable - Clear all child hashtables of child (Key (Triggering unit)) in MyFirstHashtable

@Uncle - is this version more agreeable? :)
 
Last edited:

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,455
Looks good, just need to make sure that Trrigger is set to Initially OFF (Unchecked) otherwise it'll be on from the get go.

Also, in the Else - Actions you're Adding the Triggering unit to NeutralHostileUnits when it should be the Attacking unit:
  • Unit Group - Add (Triggering unit) to NeutralHostileUnits // Should be the Attacking unit
  • Unit - Remove Neutral Blight (Neutral) from (Attacking unit)
  • Hashtable - Save 30 as 0 of (Key (Attacking unit)) in MyFirstHashtable
  • Trigger - Turn on Trrigger <gen>
 
Level 20
Joined
Feb 23, 2014
Messages
1,264
QQ: If I'm using Bribe's DamageEngine, how can I store the Handle ID of the "DamageEventTarget"?
Create an integer variable, for instance called HandleID, then set its value to this:

  • Custom script: set udg_HandleID = GetHandleIdBJ(udg_DamageEventTarget)

And then use this variable as your handle ID:

  • Hashtable - Save 30 as 0 of HandleID in MyFirstHashtable
 
Last edited:

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,455
Create an integer variable, for instance called HandleID, then set its value to this:

  • Custom script: set udg_HandleID = GetHandleIdBJ(udg_DamageEventTarget)

And then use this variable as your handle ID:

  • Hashtable - Save 30 as 0 of HandleID in MyFirstHashtable
Forgive me for this one because it really is trivial but GetHandleIdBJ should be GetHandleId.

We must avoid those nasty BJ functions whenever possible :p
 
Status
Not open for further replies.
Top