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

What would be the best way to do this?

Status
Not open for further replies.
Level 12
Joined
Jan 2, 2016
Messages
973
I want to link 2 units together. Imagine it like a rope between the 2 of them.
None of them can move further than XXX units away from the other, but it can stay closer to it than that.
An important condition is: none of the units should be able to pull the other one!
It would've been quite simple without this condition - I can just do periodic checks if the other unit is too far, and if it is - move it in range. However this way - the "dominant unit" could pull the other one.

At the moment I'm trying to do something else - every 0.03125 seconds I check if any of the units have changed its location: if only 1 of them has moved - I treat the other one as "dominant", if none of them has moved - do nothing, and if both of them have moved - check the distance between them, and if they are too far from eachother - move both of them with half of the "overdistance".

Can anyone think of a better way to do it?

EDIT: Here is the prototype code:
JASS:
function Bond takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit u1 = LoadUnitHandle(udg_Table, id, 'unt1')
    local unit u2 = LoadUnitHandle(udg_Table, id, 'unt2')
    local real x11 = LoadReal(udg_Table, id, 'ut1x')
    local real y11 = LoadReal(udg_Table, id, 'ut1y')
    local real x21 = LoadReal(udg_Table, id, 'ut2x')
    local real y21 = LoadReal(udg_Table, id, 'ut2y')
    local real x12 = GetUnitX(u1)
    local real y12 = GetUnitY(u1)
    local real x22 = GetUnitX(u2)
    local real y22 = GetUnitY(u2)
    local boolean b1 = x11 - x12 < 1 and x11 - x12 > -1 and y11 - y12 < 1 and y11 - y12 > -1
    local boolean b2 = x21 - x22 < 1 and x21 - x22 > -1 and y21 - y22 < 1 and y21 - y22 > -1
    local real a
    local real x
    local real y
    if not (b1 and b2) and (x22-x12)*(x22-x12) + (y22-y12)*(y22-y12) > 90000 then
        if b1 and not b2 then
            set a = Atan2(y22 - y12, x22 - x12)
            set x22 = x11 + 300*Cos(a)
            set y22 = y11 + 300*Sin(a)
            call SetUnitX(u2, x22)
            call SetUnitY(u2, y22)
        elseif b2 and not b1 then
            set a = Atan2(y12 - y22, x12 - x22)
            set x12 = x21 + 300*Cos(a)
            set y12 = y21 + 300*Sin(a)
            call SetUnitX(u1, x12)
            call SetUnitY(u1, y12)
        else
            set x = (x12 + x22)/2
            set y = (y12 + y22)/2
            set a = Atan2(y22 - y, x22 - x)
            set x22 = x + 150*Cos(a)
            set y22 = y + 150*Sin(a)
            call SetUnitX(u2, x22)
            call SetUnitY(u2, y22)
            set a = Atan2(y12 - y, x12 - x)
            set x12 = x + 150*Cos(a)
            set y12 = y + 150*Sin(a)
            call SetUnitX(u1, x12)
            call SetUnitY(u1, y12)
        endif
    endif
    call SaveReal(udg_Table, id, 'ut1x', x12)
    call SaveReal(udg_Table, id, 'ut1y', y12)
    call SaveReal(udg_Table, id, 'ut2x', x22)
    call SaveReal(udg_Table, id, 'ut2y', y22)
    call TimerStart(t, 0.03125, false, function Bond)
    set u1 = null
    set u2 = null
    set t = null
endfunction

function Trig_Magic_Bond_Conditions takes nothing returns boolean
    local timer t
    local unit u1
    local unit u2
    local integer id
    if GetSpellAbilityId() == 'A08O' then
        set t = GetFreeTimer()
        set u1 = GetTriggerUnit()
        set u2 = GetSpellTargetUnit()
        set id = GetHandleId(t)
        call SaveUnitHandle(udg_Table, id, 'unt1', u1)
        call SaveUnitHandle(udg_Table, id, 'unt2', u2)
        call SaveReal(udg_Table, id, 'ut1x', GetUnitX(u1))
        call SaveReal(udg_Table, id, 'ut1y', GetUnitY(u1))
        call SaveReal(udg_Table, id, 'ut2x', GetUnitX(u2))
        call SaveReal(udg_Table, id, 'ut2y', GetUnitY(u2))
        call TimerStart(t, 0.03125, false, function Bond)
        set u1 = null
        set u2 = null
        set t = null
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Magic_Bond takes nothing returns nothing
    set gg_trg_Magic_Bond = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Magic_Bond, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Magic_Bond, Condition( function Trig_Magic_Bond_Conditions ) )
endfunction
I will likely make it more readable later. I kind a did this just to test my concept.
The main problem with this code is that when 1 of the units is faster than the other, and both of them are moving in oposite directions - the faster 1 is slightly pulling the slower one. Apart from that, it actually works rather well.
 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
5,013
JASS:
//Instead of this:
local boolean b1 = x11 - x12 < 1 and x11 - x12 > -1 and y11 - y12 < 1 and y11 - y12 > -1
local boolean b2 = x21 - x22 < 1 and x21 - x22 > -1 and y21 - y22 < 1 and y21 - y22 > -1
//You could do this:
local boolean b1 = x11 != x12 and y11 != y12
local boolean b2 = x21 != x22 and y21 != y22
//Or this:
local boolean b1 = Abs(x11 - x12) < 1 and Abs(y11 - y12) < 1
local boolean b2 = Abs(x21 - x22) < 1 and Abs(y21 - y22) < 1
The main problem with this code is that when 1 of the units is faster than the other, and both of them are moving in oposite directions - the faster 1 is slightly pulling the slower one.
You can probably make use of native GetUnitMoveSpeed takes unit whichUnit returns real to weight how much to move each unit rather than just splitting it 50/50.
JASS:
else
    set Offset = (GetUnitMoveSpeed(u2) - GetUnitMoveSpeed(u1))/2*TimerPeriod
    //guessing that unit 2 is faster, it works the same either way but you have to presume one or the other to get signs right below

    set x = (x12 + x22)/2
    set y = (y12 + y22)/2
    set a = Atan2(y22 - y, x22 - x)

    set x22 = x + (150-Offset)*Cos(a)
    set y22 = y + (150-Offset)*Sin(a)

    call SetUnitX(u2, x22)
    call SetUnitY(u2, y22)
    set a = Atan2(y12 - y, x12 - x)

    set x12 = x + (150+Offset)*Cos(a)
    set y12 = y + (150+Offset)*Sin(a)

    call SetUnitX(u1, x12)
    call SetUnitY(u1, y12)
endif

To be fair: that will still only be perfect when the units are moving perfectly opposite one another (and aren't being moved by triggers at a rate different from their move speed). It could be improved by replacing the GetUnitMoveSpeed() calls with some math that gives you the component of each unit's movement speed parallel to the line connecting them.
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, maths saved the day.
JASS:
            set x1 = (x11 + x21)/2
            set y1 = (y11 + y21)/2
            set A = (y22 - y12)/(x22 - x12)
            set B = (x21 - x11)/(y21 - y11)
            set x = (x12*A - y12 + x1*B + y1)/(A+B)
            set y = A*(x - x12) + y12
            set a = Atan2(y22 - y, x22 - x)
            set x22 = x + 150*Cos(a)
            set y22 = y + 150*Sin(a)
            call SetUnitX(u2, x22)
            call SetUnitY(u2, y22)
            set a = Atan2(y12 - y, x12 - x)
            set x12 = x + 150*Cos(a)
            set y12 = y + 150*Sin(a)
            call SetUnitX(u1, x12)
            call SetUnitY(u1, y12)
Now I just need to make it more readable :p

I made the units able to move somewhat sideways if they are moving for example unit 1 at 30 deg, and unit 2 at 150 deg.
Both of them will move slightly upwards, but none of them will be draggin the other :)
Which is exactly what I was trying to do.

And to think some other students form the university claimed that maths isn't needed for anything :D
 
Last edited:
Level 18
Joined
Nov 21, 2012
Messages
835
Pretty nice spell idea :)
As to the code: I imagine this can be made with IsUnitInRangeXY(u, centerX, centerY, maxRange)
you have to save center point between units u1 and u2 at spell cast
in loop:
if not IsUnitInRangeXY(u1, centerX, centerY, maxRange) then
move u1 to the edge calculating from center point and angle between center and u1
endif
the same for unit u2
then
recalculate center point between (new) units' positions
 
Level 12
Joined
Jan 2, 2016
Messages
973
Pretty nice spell idea :)
As to the code: I imagine this can be made with IsUnitInRangeXY(u, centerX, centerY, maxRange)
you have to save center point between units u1 and u2 at spell cast
in loop:
if not IsUnitInRangeXY(u1, centerX, centerY, maxRange) then
move u1 to the edge calculating from center point and angle between center and u1
endif
the same for unit u2
then
recalculate center point between (new) units' positions
IsUnitInRange considers the unit's collision size too, so things started looking weird, and I would've had to add units' collision into my calculations as well.
However, there was a bit of a bug in the last code I presented - when a fast unit was linked to a slower one, and both of them were headed in the same direction - it acted like they were headed in oposite directions, and wasn't allowing them to move.
Here is the bug-free code, before optimizations, and integrations:
JASS:
function Bond takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit u1 = LoadUnitHandle(udg_Table, id, 'unt1')
    local unit u2 = LoadUnitHandle(udg_Table, id, 'unt2')
    local real x11 = LoadReal(udg_Table, id, 'ut1x')
    local real y11 = LoadReal(udg_Table, id, 'ut1y')
    local real x21 = LoadReal(udg_Table, id, 'ut2x')
    local real y21 = LoadReal(udg_Table, id, 'ut2y')
    local real x12 = GetUnitX(u1)
    local real y12 = GetUnitY(u1)
    local real x22 = GetUnitX(u2)
    local real y22 = GetUnitY(u2)
    local boolean b1 = x11 - x12 < 1 and x11 - x12 > -1 and y11 - y12 < 1 and y11 - y12 > -1
    local boolean b2 = x21 - x22 < 1 and x21 - x22 > -1 and y21 - y22 < 1 and y21 - y22 > -1
    local real a
    local real x1
    local real y1
    local real x
    local real y
    local real A
    local real B
    if not (b1 and b2) and (x22 - x12)*(x22 - x12) + (y22 - y12)*(y22 - y12) > 90000 then
        if b1 and not b2 then
            set a = Atan2(y22 - y12, x22 - x12)
            set x22 = x11 + 300*Cos(a)
            set y22 = y11 + 300*Sin(a)
            call SetUnitX(u2, x22)
            call SetUnitY(u2, y22)
        elseif b2 and not b1 then
            set a = Atan2(y12 - y22, x12 - x22)
            set x12 = x21 + 300*Cos(a)
            set y12 = y21 + 300*Sin(a)
            call SetUnitX(u1, x12)
            call SetUnitY(u1, y12)
        else
            set x1 = (x11 + x21)/2
            set y1 = (y11 + y21)/2
            set A = (y22 - y12)/(x22 - x12)
            set B = (x21 - x11)/(y21 - y11)
            set x = (x12*A - y12 + x1*B + y1)/(A+B)
            set y = A*(x - x12) + y12
            if (x12 - x)*(x12 - x) + (y12 - y)*(y12 - y) < 22500 then
                set a = Atan2(y22 - y12, x22 - x12)
                set x22 = x11 + 300*Cos(a)
                set y22 = y11 + 300*Sin(a)
                call SetUnitX(u2, x22)
                call SetUnitY(u2, y22)
            elseif (x22 - x)*(x22 - x) + (y22 - y)*(y22 - y) < 22500 then
                set a = Atan2(y12 - y22, x12 - x22)
                set x12 = x21 + 300*Cos(a)
                set y12 = y21 + 300*Sin(a)
                call SetUnitX(u1, x12)
                call SetUnitY(u1, y12)
            else
                set a = Atan2(y22 - y, x22 - x)
                set x22 = x + 150*Cos(a)
                set y22 = y + 150*Sin(a)
                call SetUnitX(u2, x22)
                call SetUnitY(u2, y22)
                set a = a + bj_PI
                set x12 = x + 150*Cos(a)
                set y12 = y + 150*Sin(a)
                call SetUnitX(u1, x12)
                call SetUnitY(u1, y12)
            endif
        endif
    endif
    call SaveReal(udg_Table, id, 'ut1x', x12)
    call SaveReal(udg_Table, id, 'ut1y', y12)
    call SaveReal(udg_Table, id, 'ut2x', x22)
    call SaveReal(udg_Table, id, 'ut2y', y22)
    call TimerStart(t, 0.03125, false, function Bond)
    set u1 = null
    set u2 = null
    set t = null
endfunction

function Trig_Magic_Bond_Conditions takes nothing returns boolean
    local timer t
    local unit u1
    local unit u2
    local integer id
    if GetSpellAbilityId() == 'A08O' then
        set t = GetFreeTimer()
        set u1 = GetTriggerUnit()
        set u2 = GetSpellTargetUnit()
        set id = GetHandleId(t)
        call SaveUnitHandle(udg_Table, id, 'unt1', u1)
        call SaveUnitHandle(udg_Table, id, 'unt2', u2)
        call SaveReal(udg_Table, id, 'ut1x', GetUnitX(u1))
        call SaveReal(udg_Table, id, 'ut1y', GetUnitY(u1))
        call SaveReal(udg_Table, id, 'ut2x', GetUnitX(u2))
        call SaveReal(udg_Table, id, 'ut2y', GetUnitY(u2))
        call TimerStart(t, 0.03125, false, function Bond)
        set u1 = null
        set u2 = null
        set t = null
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Magic_Bond takes nothing returns nothing
    set gg_trg_Magic_Bond = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Magic_Bond, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Magic_Bond, Condition( function Trig_Magic_Bond_Conditions ) )
endfunction
 
Status
Not open for further replies.
Top