[Solved] Building is not created on specified point

Level 10
Joined
Aug 15, 2008
Messages
448
Greetings everyone,

I've been attempting to create a trigger that, essentially, searches the map for specific markers (based on Circle of Power) on initialization and then forms a base around them.
It works fairly well, all things considered, but the problem lies in how buildings are placed: it's supposed to spawn 8 defensive structures (at 45, 90, 135, 180, 225, 270, 315 and 360 degrees) on the edge and every time there are cases where they get placed slightly off, as if the placement snapped to the next tile.
To illustrate:

Why tho.jpg


The second column is what is expected (and usually happens), the third is how it actually works sometimes. It's especially noticeable on the diagonal ones.

As you may or may not have noticed, the trigger works by finding the base marker, finding its position and then placing the various structures by means of "points with polar offsets" (I can, of course, post the trigger if need be, I just figured it wasn't needed for this).
Just to be clear, I have placed everything by hand first and calculated the various distances first, all on an empty terrain as to avoid collision issues.
I have also tried working with raw coordinates (so adding/subtracting to the main point's X and Y) but the result has been the same.

Is there some way I can make sure those buildings snap in place where I want them?
If it helps, I'm working on patch 1.29.
Thanks in advance.


EDIT: added testmap in the attachments.
 

Attachments

  • BaseSpawnTest.w3x
    25 KB · Views: 26
Last edited:

Uncle

Warcraft Moderator
Level 48
Joined
Aug 10, 2018
Messages
4,697
It would help a lot to see your triggers but I have a few ideas.

For starters, I believe this is because of the imprecision of Reals. Reals have a tendency to shave off a very small fraction after using arithmetic, resulting in something like 0+1028 becoming 1027.99999 or 1028.00000001.
Even a small change of offset like 0.00001 distance could push the structure to the next tile.

And some solutions:

Integers don't suffer from this imprecision, so some combination of converting reals to integers and integers to reals could possibly work.

Another idea, if the Markers are preplaced, you could store their coordinates and the surrounding 8 points around them in a table, this way no arithmetic is needed as their values will remain static. You can then reference this table. Or store everything as Points and create the Markers at these Points.
 
Level 10
Joined
Aug 15, 2008
Messages
448

Ok, I did some pre-emptive testing by adding some debug messages, which gave me a comprehensive log of all the points' coordinates (including the position of the placed buildings) as well as the angles:

log.jpg

And here's the trigger:

  • Create Bases
    • Events
    • Conditions
    • Actions
      • -------- Spawn Bases --------
      • Set TEMPGroup_1 = (Units in (Entire map) matching ((Unit-type of (Matching unit)) Equal to Base Marker))
      • Unit Group - Pick every unit in TEMPGroup_1 and do (Actions)
        • Loop - Actions
          • Unit Group - Add (Picked unit) to UNITGROUPBases
          • Unit - Hide (Picked unit)
          • Set TEMPPoint_1 = (Position of (Picked unit))
          • Visibility - Create an initially Enabled visibility modifier for (Owner of (Picked unit)) emitting Visibility from TEMPPoint_1 to a radius of 2560.00
          • Hashtable - Save Handle Of(Last created visibility modifier) as 0 of (Key (Picked unit)) in HASHTABLEWarfare
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • _DEBUG Equal to True
            • Then - Actions
              • Game - Display to (All players) for 30.00 seconds the text: |cffffcc00Marker's ...
              • Game - Display to (All players) for 30.00 seconds the text: (String((X of TEMPPoint_1)))
              • Game - Display to (All players) for 30.00 seconds the text: (String((Y of TEMPPoint_1)))
              • Game - Display to (All players) for 30.00 seconds the text: ====================
            • Else - Actions
          • -------- Form Inner Square --------
          • For each (Integer Forloop_1) from 1 to 4, do (Actions)
            • Loop - Actions
              • Set TEMPReal_1 = (((Real(Forloop_1)) x 90.00) - 45.00)
              • Set TEMPPoint_2 = (TEMPPoint_1 offset by 768.00 towards TEMPReal_1 degrees)
              • For each (Integer Forloop_2) from 1 to 2, do (Actions)
                • Loop - Actions
                  • Set TEMPReal_2 = (TEMPReal_1 + 135.00)
                  • Set TEMPPoint_3 = (TEMPPoint_2 offset by ((Real(Forloop_2)) x 64.00) towards TEMPReal_2 degrees)
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing (TEMPReal_2 - 90.00) with scale 1.00 and variation 0
              • For each (Integer Forloop_2) from 1 to 2, do (Actions)
                • Loop - Actions
                  • Set TEMPReal_2 = (TEMPReal_1 - 135.00)
                  • Set TEMPPoint_3 = (TEMPPoint_2 offset by ((Real(Forloop_2)) x 64.00) towards TEMPReal_2 degrees)
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing (TEMPReal_2 + 90.00) with scale 1.00 and variation 0
          • -------- Spawn Bunkers --------
          • For each (Integer Forloop_1) from 1 to 4, do (Actions)
            • Loop - Actions
              • Set TEMPReal_1 = (((Real(Forloop_1)) x 90.00) - 0.00)
              • Set TEMPPoint_2 = (TEMPPoint_1 offset by 1216.00 towards TEMPReal_1 degrees)
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • _DEBUG Equal to True
                • Then - Actions
                  • Game - Display to (All players) for 30.00 seconds the text: (|cffffcc00Angle:|r + (String(TEMPReal_1)))
                  • Game - Display to (All players) for 30.00 seconds the text: (String((X of TEMPPoint_2)))
                  • Game - Display to (All players) for 30.00 seconds the text: (String((Y of TEMPPoint_2)))
                • Else - Actions
              • Unit - Create 1 Bunker for (Owner of (Picked unit)) at TEMPPoint_2 facing ((Real(Forloop_1)) x 90.00) degrees
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • _DEBUG Equal to True
                • Then - Actions
                  • Set TEMPPoint_3 = (Position of (Last created unit))
                  • Game - Display to (All players) for 30.00 seconds the text: |cffffcc00Building'...
                  • Game - Display to (All players) for 30.00 seconds the text: (String((X of TEMPPoint_3)))
                  • Game - Display to (All players) for 30.00 seconds the text: (String((Y of TEMPPoint_3)))
                  • Game - Display to (All players) for 30.00 seconds the text: ====================
                • Else - Actions
              • -------- Left Side Sandbags --------
              • Set TEMPPoint_3 = (TEMPPoint_2 offset by 64.00 towards (TEMPReal_1 + 0.00) degrees)
              • Set TEMPPoint_3 = (TEMPPoint_2 offset by 32.00 towards (TEMPReal_1 + 90.00) degrees)
              • For each (Integer Forloop_2) from 1 to 3, do (Actions)
                • Loop - Actions
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 90.00) degrees)
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing TEMPReal_1 with scale 1.00 and variation 0
              • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 90.00) degrees)
              • For each (Integer Forloop_2) from 1 to 3, do (Actions)
                • Loop - Actions
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 180.00) degrees)
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing (TEMPReal_1 + 90.00) with scale 1.00 and variation 0
              • -------- Right Side Sandbags --------
              • Set TEMPPoint_3 = (TEMPPoint_2 offset by 64.00 towards (TEMPReal_1 + 0.00) degrees)
              • Set TEMPPoint_3 = (TEMPPoint_2 offset by 32.00 towards (TEMPReal_1 - 90.00) degrees)
              • For each (Integer Forloop_2) from 1 to 3, do (Actions)
                • Loop - Actions
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 - 90.00) degrees)
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing TEMPReal_1 with scale 1.00 and variation 0
              • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 - 90.00) degrees)
              • For each (Integer Forloop_2) from 1 to 3, do (Actions)
                • Loop - Actions
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 - 180.00) degrees)
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing (TEMPReal_1 - 90.00) with scale 1.00 and variation 0
          • For each (Integer Forloop_1) from 1 to 4, do (Actions)
            • Loop - Actions
              • Set TEMPReal_1 = (((Real(Forloop_1)) x 90.00) - 45.00)
              • Set TEMPPoint_2 = (TEMPPoint_1 offset by 832.00 towards (TEMPReal_1 - 45.00) degrees)
              • Set TEMPPoint_2 = (TEMPPoint_2 offset by 832.00 towards (TEMPReal_1 + 45.00) degrees)
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • _DEBUG Equal to True
                • Then - Actions
                  • Game - Display to (All players) for 30.00 seconds the text: (|cffffcc00Angle:|r + (String(TEMPReal_1)))
                  • Game - Display to (All players) for 30.00 seconds the text: (String((X of TEMPPoint_2)))
                  • Game - Display to (All players) for 30.00 seconds the text: (String((Y of TEMPPoint_2)))
                • Else - Actions
              • Unit - Create 1 Bunker for (Owner of (Picked unit)) at TEMPPoint_2 facing TEMPReal_1 degrees
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • _DEBUG Equal to True
                • Then - Actions
                  • Set TEMPPoint_3 = (Position of (Last created unit))
                  • Game - Display to (All players) for 30.00 seconds the text: |cffffcc00Building'...
                  • Game - Display to (All players) for 30.00 seconds the text: (String((X of TEMPPoint_3)))
                  • Game - Display to (All players) for 30.00 seconds the text: (String((Y of TEMPPoint_3)))
                  • Game - Display to (All players) for 30.00 seconds the text: ====================
                • Else - Actions
              • -------- Left Side Sandbags --------
              • Set TEMPPoint_3 = (TEMPPoint_2 offset by 32.00 towards (TEMPReal_1 + 135.00) degrees)
              • Set TEMPPoint_3 = (TEMPPoint_3 offset by 32.00 towards (TEMPReal_1 + 45.00) degrees)
              • For each (Integer Forloop_2) from 1 to 2, do (Actions)
                • Loop - Actions
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 135.00) degrees)
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 45.00) degrees)
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing TEMPReal_1 with scale 1.00 and variation 0
              • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 135.00) degrees)
              • For each (Integer Forloop_2) from 1 to 2, do (Actions)
                • Loop - Actions
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing (TEMPReal_1 + 90.00) with scale 1.00 and variation 0
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 135.00) degrees)
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 225.00) degrees)
              • -------- Right Side Sandbags --------
              • Set TEMPPoint_3 = (TEMPPoint_2 offset by 32.00 towards (TEMPReal_1 - 45.00) degrees)
              • Set TEMPPoint_3 = (TEMPPoint_3 offset by 32.00 towards (TEMPReal_1 + 225.00) degrees)
              • For each (Integer Forloop_2) from 1 to 2, do (Actions)
                • Loop - Actions
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 - 45.00) degrees)
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 225.00) degrees)
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing TEMPReal_1 with scale 1.00 and variation 0
              • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 225.00) degrees)
              • For each (Integer Forloop_2) from 1 to 2, do (Actions)
                • Loop - Actions
                  • Destructible - Create a Sandbags at TEMPPoint_3 facing (TEMPReal_1 - 90.00) with scale 1.00 and variation 0
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 135.00) degrees)
                  • Set TEMPPoint_3 = (TEMPPoint_3 offset by 64.00 towards (TEMPReal_1 + 225.00) degrees)
NOTE: I realized while compiling this that the distance I set some of the buildings at was not a multiple of 64 - I fixed it afterwards, although the result hasn't changed, seeing as some buildings still get misplaced.

Interestingly enough, when I fixed it, the 90° angled structure was placed at 7936 distance. I'm wondering if the game itself has slightly "wobbly" coordinates (sorry, couldn't come up with a better term), seeing as multiple bases in different positions tend to have different misplaced structures...


EDIT: just to clarify, the trigger is run by another one at initialization and variables get cleaned up there.

EDIT 2: added properly formatted trigger.
 
Last edited:

Uncle

Warcraft Moderator
Level 48
Joined
Aug 10, 2018
Messages
4,697
Was the Bunker offset the one you fixed? Because I see it's 1218 even though it's probably supposed to be 1216.

Also, what iceman said.

Edit:

Another possible solution, hide each object immediately after creating it. Then once everything has been created, loop through and unhide them all. Maybe this will prevent their collision from interfering with one another during the creation process.
 
Last edited:
Level 10
Joined
Aug 15, 2008
Messages
448
Might you show a small demo map, showing the problem?
Also, triggers can be directly copy & pasted into a post How To Post Your Trigger.

Woopsie about the trigger, forgot about that function! Just updated it.
As for the map, can do, I'll make one asap.

Was the Bunker offset the one you fixed? Because I see it's 1218 even though it's probably supposed to be 1216.

Also, what iceman said.

Those logs/triggers (the trigger is now updated though) were used with the 1218 distance, I just didn't bother remaking the images.
 

Uncle

Warcraft Moderator
Level 48
Joined
Aug 10, 2018
Messages
4,697
It appears as though it's a Real precision problem. You can read about it here: Imprecision of timers and working around it (Lua)

So here's one potential fix. I tried converting the Reals to Integers and then back to Reals and it seemed to work. I had it setup so that I was running these Actions AFTER you set TEMPPoint_3.
  • Actions
    • -------- Slightly increase OR decrease X so it rounds when it's converted to an Integer --------
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      • If - Conditions
        • (X of TEMPPoint_3) Greater than 0.00
      • Then - Actions
        • Set VariableSet Real_1 = ((X of TEMPPoint_3) + 0.10)
      • Else - Actions
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • (X of TEMPPoint_3) Less than 0.00
          • Then - Actions
            • Set VariableSet Real_1 = ((X of TEMPPoint_3) - 0.10)
          • Else - Actions
            • Set VariableSet Real_1 = 0.00
    • -------- --------
    • -------- Slightly increase OR decrease Y so it rounds when it's converted to an Integer --------
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      • If - Conditions
        • (Y of TEMPPoint_3) Greater than 0.00
      • Then - Actions
        • Set VariableSet Real_2 = ((Y of TEMPPoint_3) + 0.10)
      • Else - Actions
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • (Y of TEMPPoint_3) Less than 0.00
          • Then - Actions
            • Set VariableSet Real_2 = ((Y of TEMPPoint_3) - 0.10)
          • Else - Actions
            • Set VariableSet Real_2 = 0.00
    • -------- --------
    • -------- Convert the Reals to Integers, rounding them and fixing any imprecision, then convert them back to Reals --------
    • Set VariableSet Integer_1 = (Integer(Real_1))
    • Set VariableSet Integer_2 = (Integer(Real_2))
    • Set VariableSet X_Real = (Real(Integer_1))
    • Set VariableSet Y_Real = (Real(Integer_2))
    • Destructible - Create a Barrel at (Point(X_Real, Y_Real)) facing Default building facing with scale 1.00 and variation 0
I create the Barrel at a Point using Convert Coordinates To Point. I use X_Real and Y_Real (which should be set to the newly fixed coordinates) as the X and Y for that Point.

I'm not sure if the logic is foolproof though. The idea was that if you ended up with something like 127.999 as your X, this would increase it by 0.10 making it 128.099. Then when you converted it to an Integer it would get rounded down to 128. Then when it was converted back to a Real it would become 128.00, the correct amount. It seems to work.
 
Last edited:
I think it's a question of which build location gets chosen.

What I mean can be illustarted with terrain tiles a bit better. For exmple when we want to change a terrain type at x/y, but x/y is exactly in center of 2 terrain tiles... which one will be chosen?

Like in here:
upload_2020-5-23_18-49-50.png


... if we tell a trigger to change terrain of x/y where the red line points to. Let's say the x coordinates is the maximum X of the left tile. But it's also the minimum X of the right tile. (maths wise)
Warcraft will change the right tile in this case. The rule is, "maximum" gets excluded, while "minimum" gets included for a tile. For x and for y.

And depending of which direction you come, and which exact offset it is, it might appear the wrong location gets chosen for user. And this might be same problem when trying to create a building at x/y, which is only allowed to be placed at certain points. (64 offset steps)
At least that's my theory, because I tested your demo with "Pathing - Pathing map" set to "none" and it towers seemed then placed correctly.
 

Uncle

Warcraft Moderator
Level 48
Joined
Aug 10, 2018
Messages
4,697
When the objects are created at the Point, they're moved ever so slightly. The DEBUG messages are misleading because they're displaying the X/Y of the Point which is always going to be correct.

It's the X/Y of the Position of the Object after it's created that you need to test. And you'll see that some objects will be off their mark ever so slightly (127.999 instead of 128.00 for example).

The best solution I can think of is what I did in my trigger above. This way 127.999 gets converted to 128.00.
 
Last edited:
Level 10
Joined
Aug 15, 2008
Messages
448

Ah yes, Real imprecision; whenever I don't think it is, it is, and when I think it is, I can't come to terms on how to deal with it quick enough. I figured that having a debug message showing the point coordinates would be proof enough that the values weren't off, but alas, I it seems I was wrong.

I tested your method and it works, for the most part... there seems to be some quirky interactions with coordinates, let me explain:

If the main point (TEMPPoint_1) has coordinate X < 0, then the EAST structure will be slightly outwards.
If X > 0, then the WEST structure will be off.
At Y < 0, the NORTH structure will be affected.
And of course, at Y > 0, the SOUTH structure will be misplaced.

I manually fixed it with this horrible mess:

  • wtf
    • Events
    • Conditions
    • Actions
      • -------- TEST --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Or - Any (Conditions) are true
            • Conditions
              • And - All (Conditions) are true
                • Conditions
                  • Forloop_1 Equal to 1
                  • (Y of TEMPPoint_1) Less than 0.00
              • And - All (Conditions) are true
                • Conditions
                  • Forloop_1 Equal to 2
                  • (X of TEMPPoint_1) Greater than 0.00
              • And - All (Conditions) are true
                • Conditions
                  • Forloop_1 Equal to 3
                  • (Y of TEMPPoint_1) Greater than 0.00
              • And - All (Conditions) are true
                • Conditions
                  • Forloop_1 Equal to 4
                  • (X of TEMPPoint_1) Less than 0.00
        • Then - Actions
          • Set TEMPPoint_2 = (TEMPPoint_1 offset by 1152.00 towards TEMPReal_1 degrees)
        • Else - Actions
          • Set TEMPPoint_2 = (TEMPPoint_1 offset by 1216.00 towards TEMPReal_1 degrees)
      • -------- TEST --------
And hey, it looks like crap but it works. I've also tried it multiple times on different locations (especially towards the middle of the map) and it seems to be fine, so yeah. Problem solved, many thanks!


That's something I've noticed as well, however the structures have the same collision size as the main marker, so I figured it wouldn't be as much of an issue in this case. Either way, thanks.

EDIT:
Yes, because when the objects are created at the Point, they're moved ever so slightly. Your DEBUG messages are misleading because you're simply displaying the X/Y of the Point which is always going to be correct. It's the X/Y of the Position of the Object after it's created that you need to test. And you'll see that some objects will be off their mark ever so slightly (127.999 instead of 128.00 for example).

I did put this when showing the points:

  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • _DEBUG Equal to True
    • Then - Actions
      • Set TEMPPoint_3 = (Position of (Last created unit))
      • Game - Display to (All players) for 30.00 seconds the text: |cffffcc00Building'...
      • Game - Display to (All players) for 30.00 seconds the text: (String((X of TEMPPoint_3)))
      • Game - Display to (All players) for 30.00 seconds the text: (String((Y of TEMPPoint_3)))
      • Game - Display to (All players) for 30.00 seconds the text: ====================
    • Else - Actions
So it should in theory take in account the position of the actual unit. Then again, it may just be that there's not enough numbers shown after the period.

EDIT 2: Now that I think about it, I guess you mean that it's not showing me the "TempPoint + Real" and the point itself comes out already "corrected", so yeah, I get it now.
 
Last edited:

Uncle

Warcraft Moderator
Level 48
Joined
Aug 10, 2018
Messages
4,697
Ah, yeah, because I recalled seeing all precise values displayed in your debug messages, and seeing imprecise values in the triggers I threw together. Whatever, as long as it works!

Also, you're leaking A LOT of points, lol.
 
Last edited:
Level 10
Joined
Aug 15, 2008
Messages
448
Also, you're leaking A LOT of points, lol.

You know, since I'm at it... I do use the RemoveLocation function, but it's outside of that trigger (I did a similar thing in the testmap, with the init trigger calling it and then clearing the variables at the end)...
Does that work or am I supposed to clear the variables on each "Set Variable"? I normally do that but I figured it'd look cleaner this way...
 

Uncle

Warcraft Moderator
Level 48
Joined
Aug 10, 2018
Messages
4,697
Yes, you need to Remove the Point before you Set it again or else it will leak. That being said, technically leaks only happens IF you Set it again, because otherwise you still have access to the Point so it's still Removable.

Whenever you do "Set Point3 = Point3 offset by..." you're creating a new Point as well. You should try to avoid this when possible.

Instead of doing Point3 = Point3 offset by X, you'd want to do something like:
Set Point4 = Point3 offset by X
Set Point5 = Point4 offset by X
Set Point6 = Point5 offset by X
Then you can create the Unit/Destructible at Point6, and safely remove the Points.

You can also clean that trigger up by using the Run Trigger function to break it apart into separate pieces.
 
That's something I've noticed as well, however the structures have the same collision size as the main marker, so I figured it wouldn't be as much of an issue in this case. Either way, thanks.
It isn't specifically related to collision, but to path point finding in general. (in this case the 64 offset steps)
Your solution to choose an extra 64 offset at hardcoded cases seems to strengthen it.

Anyways, good luck going on. Good it's working. :)
 
Top