• 🏆 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] Trigger for swapping multiple units' position.

Level 27
Joined
Nov 25, 2021
Messages
480
So I have four static buildings, and a trigger running periodically that allows each of them to randomly teleport to one of the other three Locations. They also must not stay still, and must not teleport to an already occupied Location.


  • Init Unit
    • Events
    • Conditions
    • Actions
      • -------- Run after Map Initialization --------
      • Set BossUnit[1] = My Structure 1 <gen>
      • Set BossUnit[2] = My Structure 2 <gen>
      • Set BossUnit[3] = My Structure 3 <gen>
      • Set BossUnit[4] = My Structure 4 <gen>
  • Swap
    • Events
      • Time - Every 5.00 seconds of game time
    • Conditions
    • Actions
      • Custom script: local boolean array Occupied
      • For each (Integer A) from 1 to 4, do (Actions)
        • Loop - Actions
          • Unit - Hide BossUnit[(Integer A)]
          • Set BossPoint[(Integer A)] = (Position of BossUnit[(Integer A)])
      • For each (Integer A) from 1 to 4, do (Actions)
        • Loop - Actions
          • Custom script: loop
          • Custom script: exitwhen Occupied[udg_BossInt] == false and not(udg_BossInt == bj_forLoopAIndex )
          • Set BossInt = (Random integer number between 1 and 4)
          • Custom script: endloop
          • Custom script: set Occupied[udg_BossInt] = true
          • Unit - Move BossUnit[(Integer A)] instantly to BossPoint[BossInt]
          • Unit - Unhide BossUnit[(Integer A)]
Sometimes, the units/buildings just jump to the wrong position, or worse, don't even unhide themselves at the end, and I don't know how to fix it. Can someone help me?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,539
BossInt's default value is 0 so Occupied[0] will be equal to FALSE the first time the loop runs. Set it to TRUE or make sure BossInt starts at a random number between 1-4 beforehand.

Don't use (Integer A), create a unique For Loop variable for this trigger.
  • For each integer BossLoop from 1 to 4, do (Actions)
I know moving Buildings can be weird. Have you tried using SetUnitX/Y? (Note: could make it even worse)
  • Custom script: call SetUnitX( udg_BossUnit[udg_BossLoop], GetLocationX(udg_BossPoint[udg_BossInt] ))
  • Custom script: call SetUnitY( udg_BossUnit[udg_BossLoop], GetLocationY(udg_BossPoint[udg_BossInt] ))
Lastly, you're leaking BossPoint four times every 5.00 seconds.
  • Custom script: call RemoveLocation( udg_BossPoint[udg_BossLoop] )
Edit:
It may be worthwhile to Move the buildings first, then run a 0.00 second Timer which Unhides them all. Often times giving the game 1 frame to process things will fix any weird issues.
 
Last edited:
Level 27
Joined
Nov 25, 2021
Messages
480
So, I make a few differences, mostly to run the Init Unit and the loop from 0-3 instead 1-4, and to make Occupied into a global variable instead of a local one for readability's sake. Plus, I removed the leaks like you said as well.

Also, Unhide doesn't work consistently so I removed it, despite it never failing me before. And for buildings, they are really quirky with MoveUnitPositionLoc, and sometimes just flies to the middle of the map even with SetUnitX and SetUnitY, so I gave up and replaced them with static units instead.

  • Swap
    • Events
      • Time - Every 5.00 seconds of game time
    • Conditions
    • Actions
      • For each (Integer BossLoop) from 0 to 3, do (Actions)
        • Loop - Actions
          • Set Occupied[BossLoop] = False
          • Set BossPoint[BossLoop] = (Position of BossUnit[BossLoop])
      • For each (Integer BossLoop) from 0 to 3, do (Actions)
        • Loop - Actions
          • Custom script: loop
          • Custom script: exitwhen udg_Occupied[udg_BossInt] == false and not(udg_BossInt == udg_BossLoop )
          • Set BossInt = (Random integer number between 0 and 3)
          • Custom script: endloop
          • Set Occupied[BossInt] = True
          • Custom script: call SetUnitX( udg_BossUnit[udg_BossLoop], GetLocationX(udg_BossPoint[udg_BossInt] ))
          • Custom script: call SetUnitY( udg_BossUnit[udg_BossLoop], GetLocationY(udg_BossPoint[udg_BossInt] ))
      • For each (Integer BossLoop) from 0 to 3, do (Actions)
        • Loop - Actions
          • Custom script: call RemoveLocation( udg_BossPoint[udg_BossLoop] )
The trigger works pretty fine, but on very rare occasions, only three units teleport and one will stay still. Unfortunately, I had another brainfart, and failed to see where the problem lies.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,539
You can remove the Point inside of the previous For Loop:
  • Custom script: call RemoveLocation( udg_BossPoint[udg_BossInt] )
Why it rarely doesn't work is odd. Maybe it hits a pattern of bad luck and the thread crashes. The loop could technically last forever which is concerning.

Ideally, the loop that gets BossInt would be designed in a way where it guarantees different results and has a limit of executions.

Here's an idea using a Unit Indexer to store the Index of the given BossPoint to each BossUnit and then automatically fallback to a working pattern if 100 attempts fail in a row:
  • Swap
    • Events
      • Time - Every 5.00 seconds of game time
    • Conditions
    • Actions
      • Set FailSafe = 0
      • For each (Integer BossLoop) from 0 to 3, do (Actions)
        • Loop - Actions
          • Set Occupied[BossLoop] = False
          • Set BossPoint[BossLoop] = (Position of BossUnit[BossLoop])
          • Set BossCV = (Custom value of BossUnit[BossLoop])
          • Set BossIndex[BossCV] = BossLoop
      • For each (Integer BossLoop) from 0 to 3, do (Actions)
        • Loop - Actions
          • Custom script: loop
          • Custom script: exitwhen udg_Occupied[udg_BossInt] == false and udg_BossInt != udg_BossLoop and udg_FailSize < 100
          • Set BossInt = (Random integer number between 0 and 3)
          • Set FailSafe = FailSafe + 1
          • Set BossCV = (Custom value of BossUnit[BossLoop])
          • Set BossIndex[BossCV] = BossInt
          • Custom script: endloop
      • If FailSafe Greater than or Equal to 100 then do (Actions)
        • Then - Actions
          • For each (Integer BossLoop) from 0 to 3, do (Actions)
            • Loop - Actions
              • Set BossCV = (Custom value of BossUnit[BossLoop])
              • Set BossIndex[BossCV] = BossIndex[BossCV] + 1
              • If BossIndex[BossCV] Greater than 3 then do (Actions)
                • Then - Actions
                  • Set BossIndex[BossCV] = 0
                  • Set BossInt = BossIndex[BossCV]
                  • Custom script: call SetUnitX( udg_BossUnit[udg_BossLoop], GetLocationX(udg_BossPoint[udg_BossInt] ))
                  • Custom script: call SetUnitY( udg_BossUnit[udg_BossLoop], GetLocationY(udg_BossPoint[udg_BossInt] ))
                  • Custom script: call RemoveLocation( udg_BossPoint[udg_BossInt] )
        • Else - Actions
          • For each (Integer BossLoop) from 0 to 3, do (Actions)
            • Loop - Actions
              • Set BossCV = (Custom value of BossUnit[BossLoop])
              • Set BossInt = BossIndex[BossCV]
              • Custom script: call SetUnitX( udg_BossUnit[udg_BossLoop], GetLocationX(udg_BossPoint[udg_BossInt] ))
              • Custom script: call SetUnitY( udg_BossUnit[udg_BossLoop], GetLocationY(udg_BossPoint[udg_BossInt] ))
              • Custom script: call RemoveLocation( udg_BossPoint[udg_BossInt] )
So if it fails 100+ times then instead of relying on completely random BossPoints it simply gives each BossUnit the "next" BossPoint. So for example, if BossUnit[1] was at BossPoint[1], it would be moved to BossPoint[2]. Then if BossUnit[3] was at BossPoint[2] it'd be moved to BossPoint[3]. It's a basic clockwise pattern. The BossUnit at BossPoint[3] gets shifted to BossPoint[0].

Increase FailSafe to find a nice sweet spot where it has an extremely unlikely chance of failing but also doesn't crash (assuming that was the issue). At the very least this addresses the potential infinite loop.
 
Last edited:
Level 27
Joined
Nov 25, 2021
Messages
480
I copy-pasted the whole thing in plus the Unit Indexer and it doesn't work. All four units stay still.

I feel like it's missing a Occupied = True somewhere, but I just don't know where. I run the trigger a few times in my mind, and I understand what it does and how it should work, but somehow it doesn't.

I'm gonna put my new trigger here, which is basically the same as above, and Bribe's Unit Indexer, in case the bugs are coming from a misinput on my side.

  • Swap
    • Events
      • Time - Every 5.00 seconds of game time
    • Conditions
    • Actions
      • Set Failsafe = 0
      • For each (Integer BossLoop) from 0 to 3, do (Actions)
        • Loop - Actions
          • Set Occupied[BossLoop] = False
          • Set BossPoint[BossLoop] = (Position of BossUnit[BossLoop])
          • Set BossCV = (Custom value of BossUnit[BossLoop])
          • Set BossIndex[BossCV] = BossLoop
      • For each (Integer BossLoop) from 0 to 3, do (Actions)
        • Loop - Actions
          • Custom script: loop
          • Custom script: exitwhen udg_Occupied[udg_BossInt] == false and udg_BossInt != udg_BossLoop and udg_Failsafe < 100
          • Set BossInt = (Random integer number between 0 and 3)
          • Set Failsafe = (Failsafe + 1)
          • Set BossCV = (Custom value of BossUnit[BossLoop])
          • Set BossIndex[BossCV] = BossLoop
          • Custom script: endloop
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Failsafe Greater than or equal to 100
        • Then - Actions
          • For each (Integer BossLoop) from 0 to 3, do (Actions)
            • Loop - Actions
              • Set BossCV = (Custom value of BossUnit[BossLoop])
              • Set BossIndex[BossCV] = (BossIndex[BossCV] + 1)
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • BossIndex[BossCV] Greater than or equal to 3
                • Then - Actions
                  • Set BossIndex[BossCV] = 0
                  • Set BossInt = BossIndex[BossCV]
                  • Custom script: call SetUnitX( udg_BossUnit[udg_BossLoop], GetLocationX(udg_BossPoint[udg_BossInt] ))
                  • Custom script: call SetUnitY( udg_BossUnit[udg_BossLoop], GetLocationY(udg_BossPoint[udg_BossInt] ))
                  • Custom script: call RemoveLocation( udg_BossPoint[udg_BossInt] )
                • Else - Actions
        • Else - Actions
          • For each (Integer BossLoop) from 0 to 3, do (Actions)
            • Loop - Actions
              • Set BossCV = (Custom value of BossUnit[BossLoop])
              • Set BossInt = BossIndex[BossCV]
              • Custom script: call SetUnitX( udg_BossUnit[udg_BossLoop], GetLocationX(udg_BossPoint[udg_BossInt] ))
              • Custom script: call SetUnitY( udg_BossUnit[udg_BossLoop], GetLocationY(udg_BossPoint[udg_BossInt] ))
              • Custom script: call RemoveLocation( udg_BossPoint[udg_BossInt] )

  • Unit Indexer
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Custom script: call ExecuteFunc("InitializeUnitIndexer")
      • Custom script: endfunction
      • -------- --------
      • -------- This is the core function - it provides an index all existing units and for units as they enter the map --------
      • -------- --------
      • Custom script: function IndexUnit takes nothing returns boolean
      • Custom script: local integer pdex = udg_UDex
      • Custom script: local integer ndex
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • IsUnitPreplaced[0] Equal to False
        • Then - Actions
          • -------- --------
          • -------- Check for removed units for every (32) new units created --------
          • -------- --------
          • Set UDexWasted = (UDexWasted + 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • UDexWasted Equal to 32
            • Then - Actions
              • Set UDexWasted = 0
              • Set UDex = UDexNext[0]
              • Custom script: loop
              • Custom script: exitwhen udg_UDex == 0
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Custom value of UDexUnits[UDex]) Equal to 0
                • Then - Actions
                  • -------- --------
                  • -------- Remove index from linked list --------
                  • -------- --------
                  • Custom script: set ndex = udg_UDexNext[udg_UDex]
                  • Custom script: set udg_UDexNext[udg_UDexPrev[udg_UDex]] = ndex
                  • Custom script: set udg_UDexPrev[ndex] = udg_UDexPrev[udg_UDex]
                  • Set UDexPrev[UDex] = 0
                  • Set IsUnitPreplaced[UDex] = False
                  • -------- --------
                  • -------- Fire deindex event for UDex --------
                  • -------- --------
                  • Set UnitIndexEvent = 2.00
                  • Set UnitIndexEvent = 0.00
                  • -------- --------
                  • -------- Recycle the index for later use --------
                  • -------- --------
                  • Set UDexUnits[UDex] = No unit
                  • Set UDexNext[UDex] = UDexRecycle
                  • Set UDexRecycle = UDex
                  • Custom script: set udg_UDex = ndex
                • Else - Actions
                  • Set UDex = UDexNext[UDex]
              • Custom script: endloop
            • Else - Actions
        • Else - Actions
      • -------- --------
      • -------- You can use the boolean UnitIndexerEnabled to protect some of your undesirable units from being indexed --------
      • -------- - Example: --------
      • -------- -- Set UnitIndexerEnabled = False --------
      • -------- -- Unit - Create 1 Dummy for (Triggering player) at TempLoc facing 0.00 degrees --------
      • -------- -- Set UnitIndexerEnabled = True --------
      • -------- --------
      • -------- You can also customize the following block - if conditions are false the (Matching unit) won't be indexed. --------
      • -------- --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • UnitIndexerEnabled Equal to True
          • (Custom value of (Matching unit)) Equal to 0
        • Then - Actions
          • -------- --------
          • -------- Generate a unique integer index for this unit --------
          • -------- --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • UDexRecycle Equal to 0
            • Then - Actions
              • Set UDex = (UDexGen + 1)
              • Set UDexGen = UDex
            • Else - Actions
              • Set UDex = UDexRecycle
              • Set UDexRecycle = UDexNext[UDex]
          • -------- --------
          • -------- Link index to unit, unit to index --------
          • -------- --------
          • Set UDexUnits[UDex] = (Matching unit)
          • Unit - Set the custom value of UDexUnits[UDex] to UDex
          • Set IsUnitPreplaced[UDex] = IsUnitPreplaced[0]
          • -------- --------
          • -------- Use a doubly-linked list to store all active indexes --------
          • -------- --------
          • Set UDexPrev[UDexNext[0]] = UDex
          • Set UDexNext[UDex] = UDexNext[0]
          • Set UDexNext[0] = UDex
          • -------- --------
          • -------- Fire index event for UDex --------
          • -------- --------
          • Set UnitIndexEvent = 0.00
          • Set UnitIndexEvent = 1.00
          • Set UnitIndexEvent = 0.00
        • Else - Actions
      • Custom script: set udg_UDex = pdex
      • Custom script: return false
      • Custom script: endfunction
      • -------- --------
      • -------- The next function initializes the core of the system --------
      • -------- --------
      • Custom script: function InitializeUnitIndexer takes nothing returns nothing
      • Custom script: local integer i = 0
      • Custom script: local region re = CreateRegion()
      • Custom script: local rect r = GetWorldBounds()
      • Custom script: local boolexpr b = Filter(function IndexUnit)
      • Set UnitIndexEvent = -1.00
      • Set UnitIndexerEnabled = True
      • Set IsUnitPreplaced[0] = True
      • Custom script: call RegionAddRect(re, r)
      • Custom script: call TriggerRegisterEnterRegion(CreateTrigger(), re, b)
      • Custom script: call RemoveRect(r)
      • Custom script: set re = null
      • Custom script: set r = null
      • Custom script: loop
      • Custom script: call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, Player(i), b)
      • Custom script: set i = i + 1
      • Custom script: exitwhen i == bj_MAX_PLAYER_SLOTS
      • Custom script: endloop
      • Custom script: set b = null
      • -------- --------
      • -------- This is the "Unit Indexer Initialized" event, use it instead of "Map Initialization" for best results --------
      • -------- --------
      • Set IsUnitPreplaced[0] = False
      • Set UnitIndexEvent = 3.00
 
Level 27
Joined
Nov 25, 2021
Messages
480
So I fixed the trigger...and it returned to the bug of units randomly warping to the middle of the map. I tried to fix it, doing random shit basically, and the same thing still happens.

As a stroke of not-so-brilliant idea strikes me, I make this extremely bad trigger:

  • Why
    • Events
    • Conditions
    • Actions
      • For each (Integer BossLoop) from 0 to 3, do (Actions)
        • Loop - Actions
          • Set BossPoint[BossLoop] = (Position of BossUnit[BossLoop])
      • Set BossInteger[1] = (Random integer number between 1 and 3)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BossInteger[1] Not equal to 2
        • Then - Actions
          • Set BossInteger[2] = (Integer((((6.00 - (Real(BossInteger[1]))) / 2.00) + 0.50)))
          • Set BossInteger[3] = (Integer((((6.00 - (Real(BossInteger[1]))) / 2.00) - 0.50)))
        • Else - Actions
          • Set BossInteger[2] = 3
          • Set BossInteger[3] = 1
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Random integer number between 1 and 2) Equal to 1
        • Then - Actions
          • Custom script: call SetUnitX( udg_BossUnit[udg_BossInteger[2]], GetLocationX(udg_BossPoint[udg_BossInteger[3]] ))
          • Custom script: call SetUnitY( udg_BossUnit[udg_BossInteger[2]], GetLocationY(udg_BossPoint[udg_BossInteger[3]] ))
          • Custom script: call SetUnitX( udg_BossUnit[udg_BossInteger[3]], GetLocationX(udg_BossPoint[udg_BossInteger[2]] ))
          • Custom script: call SetUnitY( udg_BossUnit[udg_BossInteger[3]], GetLocationY(udg_BossPoint[udg_BossInteger[2]] ))
          • Custom script: call SetUnitX( udg_BossUnit[udg_BossInteger[1]], GetLocationX(udg_BossPoint[0] ))
          • Custom script: call SetUnitY( udg_BossUnit[udg_BossInteger[1]], GetLocationY(udg_BossPoint[0] ))
          • Custom script: call SetUnitX( udg_BossUnit[0], GetLocationX(udg_BossPoint[udg_BossInteger[1]] ))
          • Custom script: call SetUnitY( udg_BossUnit[0], GetLocationY(udg_BossPoint[udg_BossInteger[1]] ))
        • Else - Actions
          • Custom script: call SetUnitX( udg_BossUnit[0], GetLocationX(udg_BossPoint[udg_BossInteger[1]] ))
          • Custom script: call SetUnitY( udg_BossUnit[0], GetLocationY(udg_BossPoint[udg_BossInteger[1]] ))
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Random integer number between 1 and 2) Equal to 1
            • Then - Actions
              • Custom script: call SetUnitX( udg_BossUnit[udg_BossInteger[1]], GetLocationX(udg_BossPoint[udg_BossInteger[3]] ))
              • Custom script: call SetUnitY( udg_BossUnit[udg_BossInteger[1]], GetLocationY(udg_BossPoint[udg_BossInteger[3]] ))
              • Custom script: call SetUnitX( udg_BossUnit[udg_BossInteger[3]], GetLocationX(udg_BossPoint[udg_BossInteger[2]] ))
              • Custom script: call SetUnitY( udg_BossUnit[udg_BossInteger[3]], GetLocationY(udg_BossPoint[udg_BossInteger[2]] ))
              • Custom script: call SetUnitX( udg_BossUnit[udg_BossInteger[2]], GetLocationX(udg_BossPoint[0] ))
              • Custom script: call SetUnitY( udg_BossUnit[udg_BossInteger[2]], GetLocationY(udg_BossPoint[0] ))
            • Else - Actions
              • Custom script: call SetUnitX( udg_BossUnit[udg_BossInteger[1]], GetLocationX(udg_BossPoint[udg_BossInteger[2]] ))
              • Custom script: call SetUnitY( udg_BossUnit[udg_BossInteger[1]], GetLocationY(udg_BossPoint[udg_BossInteger[2]] ))
              • Custom script: call SetUnitX( udg_BossUnit[udg_BossInteger[2]], GetLocationX(udg_BossPoint[udg_BossInteger[3]] ))
              • Custom script: call SetUnitY( udg_BossUnit[udg_BossInteger[2]], GetLocationY(udg_BossPoint[udg_BossInteger[3]] ))
              • Custom script: call SetUnitX( udg_BossUnit[udg_BossInteger[3]], GetLocationX(udg_BossPoint[0] ))
              • Custom script: call SetUnitY( udg_BossUnit[udg_BossInteger[3]], GetLocationY(udg_BossPoint[0] ))
Basically, it counts for all situation. After my calculation, when Unit[0] move to the spot of Unit[1], which is randomly picked from the remaining three units, one of two things can happen:
1. Unit[1] moves to the position of Unit[0], and the remaining two will swap between themselves.
2. The other units will continue to move in an Ouroboros-like pattern, like [0]-[1]-[2]-[3]-[0], with Unit[2] and Unit[3] being randomly chosen between the remaining two.

The fact that this dog poop works without bugs infuriates me. This is so ugly compared to the other triggers, and if I ever decide to add the fifth unit, everything will break.

But if it works, it works. I guess.
 
Level 27
Joined
Nov 25, 2021
Messages
480
You also were setting Set BossIndex[BossCV] = BossLoop when it was supposed to be equal to BossInt.
I'm kinda sure that was among the many unlisted fixes that I made, which causes the "unit teleport to middle of the map" shenanigan in the first place.

Then again, I must admit that I'm not very good at reading those codes, and maybe more mistakes were made somewhere along the line that I failed to spot.

I'll try to fix one more time then post it here when I have the time. Least I can do is thank you for being very patient.

Edit: I'm too lazy to figure this out, so I'll just stack it up as a problem for future me to solve. I'll close the thread and thanks everyone who helped me.
 
Last edited:
Level 25
Joined
Sep 26, 2009
Messages
2,378
As far as I know, if you call SetUnitX/Y for a building, it's pathing map won't get updated unless you hide->unhide the structure. That means units would collide with building's previous position, not the new one.

I would also approach the problem from different angle. Instead of having multiple locations and trying to figure out which location is yet available or a building was already moved there, I would just swap the buildings themselves.
For example:
  • You have 4 buildings: A, B, C, D
  • Pick two random buildings, swap them
  • Finally, swap the remaining two buildings you have not yet picked.

Or a more complex approach which gives you (I think) more options:
  • You have 4 buildings: A, B, C, D
  • Pick random building. Let's say it's 'A'
  • Pick random building from the remaining set [B, C, D]. Let's say it's 'B'.
  • Swap location of A and B. Keep track of B
  • Pick random building from the remaining set [C, D]. Let's say it's 'C'.
  • Swap location of B and C. Keep track of C
  • Swap location of C and D.
You can see that this approach does not care if a location was already chosen by some other building and it should always move all buildings.
 
Top