• 🏆 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] How to make unit attack random target.

Status
Not open for further replies.
Level 2
Joined
Mar 16, 2023
Messages
9
Hi

I want to use triggers to make it so that a spawned unit will choose a random structure, either from a unit group or region, to make their target and attack it.

How do I set something like that up? And is it even possible to make the spawned unit choose the nearest target as well?
 
Level 20
Joined
Aug 29, 2012
Messages
829
This is one way to detect the closest unit of some sort

  • Actions
    • Set VariableSet Point_Temp = (Position of (Triggering unit))
    • Set VariableSet UnitGroup_Temp = (Units within 5000.00 of Point_Temp matching (((Matching unit) belongs to an enemy of (Owner of (Triggering unit)).) Equal to True).)
    • Set VariableSet Real_Distance = 5000.00
    • Unit Group - Pick every unit in UnitGroup_Temp and do (Actions)
      • Loop - Actions
        • Set VariableSet Point_Temp2 = (Position of (Picked unit))
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • (Distance between Point_Temp and Point_Temp2) Less than or equal to Real_Distance
          • Then - Actions
            • Set VariableSet Real_Distance = (Distance between Point_Temp and Point_Temp2)
            • Set VariableSet Unit_Target = (Picked unit)
          • Else - Actions
        • Custom script: call RemoveLocation(udg_Point_Temp2)
    • Custom script: call RemoveLocation(udg_Point_Temp)
    • Custom script: call DestroyGroup(udg_UnitGroup_Temp)
    • Unit - Order (Triggering unit) to Attack Unit_Target
In this example, the game will add all enemy units in a group, and then for each unit, calculate the distance between them and your unit. If it's less than the previous one, it now serves as a base for the least distance, if not, it's ignored and goes to the next, etc etc. Ultimately, the unit stored in the variable is the nearest target

Just change the distance if you want a bigger radius check, and the conditions of the unit group if you need to be more precise

If you just want to attack a random target, set up a basic unit group like this and you're good to go
  • Set VariableSet UnitGroup_Temp = (Units in (Playable map area) matching (((Matching unit) is A structure) Equal to True))
Change (Playable map area) to be your region or something else, same for the conditions.
 
Level 2
Joined
Mar 16, 2023
Messages
9
This is one way to detect the closest unit of some sort

  • Actions
    • Set VariableSet Point_Temp = (Position of (Triggering unit))
    • Set VariableSet UnitGroup_Temp = (Units within 5000.00 of Point_Temp matching (((Matching unit) belongs to an enemy of (Owner of (Triggering unit)).) Equal to True).)
    • Set VariableSet Real_Distance = 5000.00
    • Unit Group - Pick every unit in UnitGroup_Temp and do (Actions)
      • Loop - Actions
        • Set VariableSet Point_Temp2 = (Position of (Picked unit))
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • (Distance between Point_Temp and Point_Temp2) Less than or equal to Real_Distance
          • Then - Actions
            • Set VariableSet Real_Distance = (Distance between Point_Temp and Point_Temp2)
            • Set VariableSet Unit_Target = (Picked unit)
          • Else - Actions
        • Custom script: call RemoveLocation(udg_Point_Temp2)
    • Custom script: call RemoveLocation(udg_Point_Temp)
    • Custom script: call DestroyGroup(udg_UnitGroup_Temp)
    • Unit - Order (Triggering unit) to Attack Unit_Target
In this example, the game will add all enemy units in a group, and then for each unit, calculate the distance between them and your unit. If it's less than the previous one, it now serves as a base for the least distance, if not, it's ignored and goes to the next, etc etc. Ultimately, the unit stored in the variable is the nearest target

Just change the distance if you want a bigger radius check, and the conditions of the unit group if you need to be more precise

If you just want to attack a random target, set up a basic unit group like this and you're good to go
  • Set VariableSet UnitGroup_Temp = (Units in (Playable map area) matching (((Matching unit) is A structure) Equal to True))
Change (Playable map area) to be your region or something else, same for the conditions.

Thanks, I'll give it a try and see how it goes!

By the way, why did you use Custom script as opposed to a trigger to remove the temp variables?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,558
I recommend tracking the "attackable" Structures in a permanent Unit Group. This way you can avoid the need for the UnitGroup_Temp variable which has to test potentially 100's of units to find your desired Structures each time a unit spawns.

For example here's a more optimized trigger:
  • Actions
    • Set VariableSet Unit_SpawnedUnit = (Triggering unit)
    • Set VariableSet Point_Temp = (Position of Unit_SpawnedUnit)
    • Set VariableSet Real_ClosestDistance = 999999.00
    • Set VariableSet Unit_ClosestTarget = No unit
    • Unit Group - Pick every unit in UnitGroup_Structures and do (Actions)
      • Loop - Actions
        • Set VariableSet Unit_Target = (Picked unit)
        • Set VariableSet Point_Temp2 = (Position of Unit_Target)
        • Set VariableSet Real_Distance = (Distance between Point_Temp and Point_Temp2)
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • Real_Distance Less than Real_ClosestDistance
          • Then - Actions
            • Set VariableSet Real_ClosestDistance = Real_Distance
            • Set VariableSet Unit_ClosestTarget = Unit_Target
          • Else - Actions
        • Custom script: call RemoveLocation(udg_Point_Temp2)
    • Custom script: call RemoveLocation(udg_Point_Temp)
    • Unit - Order Unit_SpawnedUnit to Attack Unit_ClosestTarget

In order to track the Structures you simply Add them to the UnitGroup_Structures variable as they come into play:
  • Unit - Create 1 Barracks for Player 1 (Red)...
  • Unit Group - Add (Last created unit) to UnitGroup_Structures
The Structures may be preplaced or constructed so you would want to adapt this as necessary.

Also, you'll want to Remove them from the Unit Group when they die:
  • Events
    • Unit - A unit Dies
  • Conditions
    • It's one of your Structures
  • Actions
    • Unit Group - Remove (Triggering unit) from UnitGroup_Structures
Dead units will remain in Unit Groups forever and can cause problems unless you manually Remove them.

Also, since units die often (in most maps, at least) then the above trigger will run a lot, so I suggest using a lightweight Condition like this:
  • Conditions
    • (Level of Structure Classification for (Triggering unit)) Equal to 1
Structure Classification would be a hidden passive ability with no effect, for example Storm Hammers with it's Art - Button Positions set to 0, -11.

Checking if a unit has an ability is an efficient Condition since it's one simple question. This is better than using the "Is Unit in Unit Group" condition since that enumerates over the Unit Group and checks each Unit inside until it finds the correct one (very inefficient).

Then you simply Add this ability to each of your desired Structures. This could be done through the Object Editor or with triggers:
  • Unit - Create 1 Barracks for Player 1 (Red)...
  • Unit Group - Add (Last created unit) to UnitGroup_Structures
  • Unit - Add Structure Classification to (Last created unit)
The end result is a Unit Group which will only ever contain living structures of the desired kind.
 
Last edited:
Level 8
Joined
Apr 26, 2020
Messages
61
By the way, why did you use Custom script as opposed to a trigger to remove the temp variables?
As to clarify things I'll explain these triggers:
set.gif
Set VariableSet Point_Temp = (Position of (Triggering unit))
set.gif
Set VariableSet UnitGroup_Temp = (Units within 5000.00 of Point_Temp matching (((Matching unit) belongs to an enemy of (Owner of (Triggering unit)).) Equal to True).)
set.gif
Set VariableSet Real_Distance = 5000.00
By this you now have the position of the spawned unit "Point_Temp", enemy units within 5000 (you can set this to Real_Distance) range "UnitGroup_Temp" and the maximum distance "Real_Distance".

- For unit group, Uncle's previously set unit group is recommended, but then you should also have to remove dead units from unit group.
unitgroup.gif
Unit Group - Pick every unit in UnitGroup_Temp and do (Actions)
joinbottomminus.gif
actions.gif
Loop - Actions
line.gif
set.gif
Set VariableSet Point_Temp2 = (Position of (Picked unit))
unitgroup.gif
Unit Group - Pick every unit in UnitGroup_Structures and do (Actions)
joinbottomminus.gif
actions.gif
Loop - Actions
line.gif
set.gif
Set VariableSet Unit_Target = (Picked unit)
line.gif
set.gif
Set VariableSet Point_Temp2 = (Position of Unit_Target)
line.gif
set.gif
Set VariableSet Real_Distance = (Distance between Point_Temp and Point_Temp2)
After you've set your variables, now yo pick each unit in your previously set "unit group" and do set their position in a "Point_Temp2".
if.gif
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
joinminus.gif
cond.gif
If - Conditions
line.gif
joinbottom.gif
if.gif
(Distance between Point_Temp and Point_Temp2) Less than or equal to Real_Distance
line.gif
joinminus.gif
actions.gif
Then - Actions
line.gif
set.gif
Set VariableSet Real_Distance = (Distance between Point_Temp and Point_Temp2)
line.gif
set.gif
Set VariableSet Unit_Target = (Picked unit)
if.gif
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
joinminus.gif
cond.gif
If - Conditions
line.gif
joinbottom.gif
if.gif
Real_Distance Less than Real_ClosestDistance
line.gif
joinminus.gif
actions.gif
Then - Actions
line.gif
set.gif
Set VariableSet Real_ClosestDistance = Real_Distance
line.gif
set.gif
Set VariableSet Unit_ClosestTarget = Unit_Target
Now that you have spawned unit's and picked enemy unit's position, check if the distance of spawned unit to picked unit is less than maximum distance, then set the maximum distance to the newly distance between these two points and set the picked unit as the "Unit_Target".
Loop (repeat) this for every unit from unit group. With each unit that has a less distance to the spawned unit, being the target unit.

After the loop finishes, remove temp variables by custom scripts to prevent leak.
- Learn about memory leaks and how to clean 'em here: [GUI] Advanced Triggering Tips
unit.gif
Unit - Order (Triggering unit) to Attack Unit_Target
At last, issue order the spawned unit to attack the nearby target unit. You be all done'
 
Level 2
Joined
Mar 16, 2023
Messages
9
Thanks for the feedback, I've put together a set of triggers that look like this

  • Borghax Decides Target
    • Events
    • Conditions
    • Actions
      • Set VariableSet Point_Temp = (Position of Borghax the Glut 0042 <gen>)
      • Set VariableSet Real_ClosestDistance = 999999.00
      • Set VariableSet Unit_ClosestTarget = No unit
      • Set VariableSet Real_Distance = 5000.00
      • Unit Group - Pick every unit in CityStructures and do (Actions)
        • Loop - Actions
          • Set VariableSet Unit_Target = (Picked unit)
          • Set VariableSet Point_Temp2 = (Position of Unit_Target)
          • Set VariableSet Real_Distance = (Distance between Point_Temp and Point_Temp2)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Real_Distance Less than Real_ClosestDistance
            • Then - Actions
              • Set VariableSet Real_ClosestDistance = Real_Distance
              • Set VariableSet Unit_ClosestTarget = Unit_Target
            • Else - Actions
              • Custom script: call RemoveLocation(udg_Point_Temp2)
      • Custom script: call RemoveLocation(udg_Point_Temp)
      • Unit - Order Borghax the Glut 0042 <gen> to Attack-Move To (Position of Unit_Target)
      • Trigger - Turn on Borghax Continues attack <gen>
Where 'Borghax the Glut 0042' is the enemy hero, and 'CityStructures' is the UnitGroup for the preplaced structures on the map. I added another trigger below called 'Borghax Picks New Target' for the hero to pick a new target if the previous target dies. 'RaidUnderway' is a variable that is checked whenever the hero spawns in for a new wave, and is unchecked whenever the wave is defeated.

  • Borghax Picks New Target
    • Events
      • Unit - A unit Dies
    • Conditions
      • (Triggering unit) Equal to Unit_Target
      • RaidUnderway Equal to True
    • Actions
      • Trigger - Run Borghax Decides Target <gen> (checking conditions)
I also added in a trigger to make the hero continuously attack, which is enabled when the wave starts, and disabled when the wave is defeated. 'BorghaxInCombat' is a Boolean variable based off two triggers that act like a combat detector for the hero's faction (player 5).

  • Borghax Continues attack
    • Events
      • Time - Every 5.00 seconds of game time
    • Conditions
      • BorghaxInCombat Equal to False
      • RaidUnderway Equal to True
    • Actions
      • Unit - Order Borghax the Glut 0042 <gen> to Attack-Move To (Position of Unit_Target)
  • Combat Detected
    • Events
      • Unit - A unit owned by Player 5 (Yellow) Is attacked
    • Conditions
    • Actions
      • Countdown Timer - Start BorghaxCombatTimer as a Repeating timer that will expire in 4.00 seconds
      • Set VariableSet BorghaxInCombat = True
  • Combat Resolved
    • Events
      • Time - BorghaxCombatTimer expires
    • Conditions
    • Actions
      • Set VariableSet BorghaxInCombat = False

One thing I haven't figured out though was how to deal with some VFX. I wanted to play a special effect on the units as they teleported in, so I tried a custom script, but it doesn't remove the VFX on it as it ends up sticking to them permanently. My other idea was to label each and every unit's VFX with a unique variable to delete later, but that does seem like it'd be a bit cumbersome. Here's the trigger for the first attack.

  • First attack
    • Events
      • Time - Borghaxattacks expires
    • Conditions
      • GameOver Equal to False
    • Actions
      • Trigger - Turn off (This trigger)
      • Unit - Move Borghax the Glut 0042 <gen> instantly to (Center of Spawn 1 Borghax <gen>)
      • Set VariableSet RaidUnderway = True
      • If ((Difficulty level) Equal to Hard) then do (Unit - Create 1 Doom Guard (Lillith) for Player 5 (Yellow) at (Center of Spawn 1 FG 1 <gen>) facing Default building facing degrees) else do (Unit - Create 1 Felguard for Player 5 (Yellow) at (Center of Spawn 1 FG 1 <gen>) facing Default building facing degrees)
      • Unit Group - Add (Last created unit) to Borghax_Guard
      • Unit - Create 1 Felguard for Player 5 (Yellow) at (Center of Spawn 1 FG 2 <gen>) facing Default building facing degrees
      • Unit Group - Add (Last created unit) to Borghax_Guard
      • Unit - Create 1 Fel Hunter for Player 5 (Yellow) at (Center of Spawn 1 FH 1 <gen>) facing Default building facing degrees
      • Unit Group - Add (Last created unit) to Borghax_Guard
      • Unit - Create 1 Fel Hunter for Player 5 (Yellow) at (Center of Spawn 1 FH 2 <gen>) facing Default building facing degrees
      • Unit Group - Add (Last created unit) to Borghax_Guard
      • Unit Group - Pick every unit in Borghax_Guard and do (Unit Group - Add (Picked unit) to RaidingParty)
      • Unit Group - Pick every unit in RaidingParty and do (Unit - Pause (Picked unit))
      • Unit Group - Pick every unit in RaidingParty and do (Actions)
        • Loop - Actions
          • Custom script: local effect vfx
          • Special Effect - Create a special effect attached to the origin of (Picked unit) using Abilities\Spells\Undead\Darksummoning\DarkSummonTarget.mdl
          • Custom script: set vfx = bj_lastCreatedEffect
          • Wait 2.00 seconds
          • Custom script: call DestroyEffect(vfx)
      • Wait 3.00 seconds
      • Unit Group - Pick every unit in RaidingParty and do (Unit - Unpause (Picked unit))
      • Trigger - Turn on Borghax guard follows <gen>
      • Wait 0.01 seconds
      • Trigger - Run Borghax Decides Target <gen> (checking conditions)
      • Trigger - Turn on Second Attack <gen>
 
Level 2
Joined
Mar 16, 2023
Messages
9
Most of the time you can use Destroy last created special effect directly after creating it, it will play the full animation and remove it once it's over, so I'd suggest trying without the wait

I noticed that worked with a spell effect like Reanimate dead, but it doesn't seem to work with the DarkSummon green teleport thingy.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,558
So I see a few mistakes. Borghax Decides Target should look like this:
  • Borghax Decides Target
    • Events
    • Conditions
    • Actions
      • Set VariableSet Point_Temp = (Position of Borghax the Glut 0042 <gen>)
      • Set VariableSet Real_ClosestDistance = 999999.00
      • Set VariableSet Unit_ClosestTarget = No unit
      • Unit Group - Pick every unit in CityStructures and do (Actions)
        • Loop - Actions
          • Set VariableSet Unit_Target = (Picked unit)
          • Set VariableSet Point_Temp2 = (Position of Unit_Target)
          • Set VariableSet Real_Distance = (Distance between Point_Temp and Point_Temp2)
          • Custom script: call RemoveLocation(udg_Point_Temp2)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Real_Distance Less than Real_ClosestDistance
            • Then - Actions
              • Set VariableSet Real_ClosestDistance = Real_Distance
              • Set VariableSet Unit_ClosestTarget = Unit_Target
            • Else - Actions
      • Custom script: call RemoveLocation(udg_Point_Temp)
      • Set VariableSet Point_Temp = (Position of Unit_ClosestTarget)
      • Unit - Order Borghax the Glut 0042 <gen> to Attack-Move To Point_Temp
      • Custom script: call RemoveLocation(udg_Point_Temp)
      • Trigger - Turn on Borghax Continues attack <gen>
ClosestTarget is the Unit that Borghax should be attacking towards. Unit_Target is NOT guaranteed to be the closest unit and will be equal to the very last City that was checked in the Loop, which is essentially random. Make sure your other triggers reference Unit_ClosestTarget and not Unit_Target. Also, I would rename the variable to like "Borghax_Target" since that's more specific to your needs. These "temp" variables are meant to be reused throughout the rest of your triggers and their generic naming reflects this.

You're also creating memory leaks whenever you Order Borghax to Attack-Move to (Position of unit). I fixed this issue by using Point_Temp again.

Also, careful with the use of the Pause unit action. Pausing a unit will also freeze any buffs on it which may extend the duration to unwanted lengths. I prefer to use the new pause unit action which actually Stuns the unit instead:
  • Custom script: call BlzPauseUnitEx(udg_TempUnit, true)
  • Custom script: call BlzPauseUnitEx(udg_TempUnit, false)
It only exists in code so we need to use custom script to access it. True = stun, False = unstunned. The only catch here is that this action will stack, meaning if you stun a unit twice and then unstun it once, it'll still be stunned. It basically uses a counter system that goes +1 or -1 depending on whether you stun/unstun.

Also, I attached a map which contains a simple system for destroying special effects after a delay. It's pretty self explanatory.
 

Attachments

  • Delayed Destroy Effect.w3m
    17.7 KB · Views: 2
Last edited:
One thing I haven't figured out though was how to deal with some VFX. I wanted to play a special effect on the units as they teleported in, so I tried a custom script, but it doesn't remove the VFX on it as it ends up sticking to them permanently. My other idea was to label each and every unit's VFX with a unique variable to delete later, but that does seem like it'd be a bit cumbersome. Here's the trigger for the first attack.

  • First attack
    • Events
      • Time - Borghaxattacks expires
    • Conditions
      • GameOver Equal to False
    • Actions
      • Trigger - Turn off (This trigger)
      • Unit - Move Borghax the Glut 0042 <gen> instantly to (Center of Spawn 1 Borghax <gen>)
      • Set VariableSet RaidUnderway = True
      • If ((Difficulty level) Equal to Hard) then do (Unit - Create 1 Doom Guard (Lillith) for Player 5 (Yellow) at (Center of Spawn 1 FG 1 <gen>) facing Default building facing degrees) else do (Unit - Create 1 Felguard for Player 5 (Yellow) at (Center of Spawn 1 FG 1 <gen>) facing Default building facing degrees)
      • Unit Group - Add (Last created unit) to Borghax_Guard
      • Unit - Create 1 Felguard for Player 5 (Yellow) at (Center of Spawn 1 FG 2 <gen>) facing Default building facing degrees
      • Unit Group - Add (Last created unit) to Borghax_Guard
      • Unit - Create 1 Fel Hunter for Player 5 (Yellow) at (Center of Spawn 1 FH 1 <gen>) facing Default building facing degrees
      • Unit Group - Add (Last created unit) to Borghax_Guard
      • Unit - Create 1 Fel Hunter for Player 5 (Yellow) at (Center of Spawn 1 FH 2 <gen>) facing Default building facing degrees
      • Unit Group - Add (Last created unit) to Borghax_Guard
      • Unit Group - Pick every unit in Borghax_Guard and do (Unit Group - Add (Picked unit) to RaidingParty)
      • Unit Group - Pick every unit in RaidingParty and do (Unit - Pause (Picked unit))
      • Unit Group - Pick every unit in RaidingParty and do (Actions)
        • Loop - Actions
          • Custom script: local effect vfx
          • Special Effect - Create a special effect attached to the origin of (Picked unit) using Abilities\Spells\Undead\Darksummoning\DarkSummonTarget.mdl
          • Custom script: set vfx = bj_lastCreatedEffect
          • Wait 2.00 seconds
          • Custom script: call DestroyEffect(vfx)
      • Wait 3.00 seconds
      • Unit Group - Pick every unit in RaidingParty and do (Unit - Unpause (Picked unit))
      • Trigger - Turn on Borghax guard follows <gen>
      • Wait 0.01 seconds
      • Trigger - Run Borghax Decides Target <gen> (checking conditions)
      • Trigger - Turn on Second Attack <gen>

Either v1.1 or v1.2 Beta works fine for your VFX purposes. In your case, follow TestEffect 1 sample, but place all the triggers in the example inside the loop. Something like the following:

  • Unit Group - Pick every unit in RaidingParty and do (Actions)
    • Loop - Actions
      • Set Variable Set TSEDuration = "2.00"
      • Set Variable Set TSEAttachPoint = "origin"
      • Set Variable Set TSEEffectName = "Abilities\Spells\Undead\Darksummoning\DarkSummonTarget.mdl"
      • Set Variable Set TSEUnit = (Picked unit)
      • Trigger - Run TSERegister (checking conditions)
Uncle alternative also works for your purposes.
 
Status
Not open for further replies.
Top