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

[JASS] Peculiar Bug with Triggered Movement

Status
Not open for further replies.
Level 18
Joined
Jul 3, 2010
Messages
536
READ FIRST:
  • These triggers use vanilla JASS. If that is unacceptable for you, please do not bother to post. I have my reasons for not wanting to switch to any other version of JASS and I will simply ignore any post along the lines of "You should switch to [whatever your favourite version of JASS is]".
  • This problem requires a general understanding of Parallel Arrays and Recycled Timers.
  • I will not use Hashtables.

PROBLEM:
I have created two functions:
  • (1) moves a projectly unit at a constant real speed towards a fixed real angle a constant maximum distance.
  • (2) moves a projectly unit at a constant real speed towards a target unit.
The syntax for these two functions, as you can imagine, are extremely similiar. However, while (1) works, (2) does a peculiar bug where it will only execute properly if the target unit is standing perfectly still. If the target unit is issued a move command or otherwise moves, the projectly unit seems to also move towards target point of issued order. At other times, the projectly unit stops completely and instead shakes around its current location, which would mean that it is either moved by multiple sources or moved back and forth by the same trigger.
By the way, no other triggers in my map ever move a unit, currently.

BACKGROUND:
I am fairly certain that the problem is not with these background functions. I am posting them so you can be sure they work and on the off chance the problem is with these functions.

globals:
  • boolean udg_SysTim_Boo[], arrayed with size 100.
  • timer udg_SysTim_Timer[], arrayed with size 100.

JASS:
// (1)
function RecycleTimers_GetNewIndex takes nothing returns integer
    local integer Index = 0
    local integer ReturnIndex = 0
    loop
        set Index = Index + 1
        if udg_SysTim_Boo[Index] == FALSE then
            set ReturnIndex = Index
            set udg_SysTim_Boo[Index] = TRUE
        endif
        exitwhen ReturnIndex != 0
    endloop
    return ReturnIndex
endfunction

// (2)
function RecycleTimers_GetCurrentIndex takes timer WhichTimer returns integer
    local integer Index = 0
    local integer ReturnIndex = 0
    loop
        set Index = Index + 1
        if udg_SysTim_Timer[Index] == WhichTimer then
            set ReturnIndex = Index
        endif
        exitwhen ReturnIndex != 0
    endloop
    return ReturnIndex
endfunction

// (3)
function RecycleTimers_RecycleIndex takes integer WhichIndex returns nothing
    set udg_SysTim_Boo[WhichIndex] = FALSE
endfunction

// (4)
function IsUnitAlive takes unit WhichUnit returns boolean
    return not IsUnitType(WhichUnit, UNIT_TYPE_DEAD) and (GetUnitTypeId(WhichUnit) != 0)
endfunction
Basically, all they do is recycle global declared timers so reals and the sort can be attached to them. This is so they can be used in a timer's callback function.

CODE
  • I swapped the order of the call trigger and the callback for clarity. This is of course done properly in WE.
  • The real 0.04 is otherwise a decleared global that point to the real 0.04. I substituted it for clarity.
  • You will probably notice that from right after the local declarations of (2) it is basically analogous to (1). It still doesn't work.
JASS:
function Projectly_LineMove takes real Distance, real Speed, real Angle, unit Projectly returns nothing
    local integer Index = RecycleTimers_GetNewIndex()
    set udg_SysTim_Save_Real_01[Index] = Distance
    set udg_SysTim_Save_Real_02[Index] = Speed
    set udg_SysTim_Save_Real_03[Index] = Angle
    set udg_SysTim_Save_Unit_01[Index] = Projectly
    set Projectly = null
    call TimerStart(udg_SysTim_Timer[Index], 0.04, false, function Projectly_LineMove_Callback)
endfunction

function Projectly_LineMove_Callback takes nothing returns nothing
// LOCALS
    local integer Index = RecycleTimers_GetCurrentIndex(GetExpiredTimer())
    local real Distance = udg_SysTim_Save_Real_01[Index]
    local real Speed = udg_SysTim_Save_Real_02[Index]
    local real Angle = udg_SysTim_Save_Real_03[Index]
    local unit Projectly = udg_SysTim_Save_Unit_01[Index]
    local location TempPoint = GetUnitLoc(Projectly)

// MAIN
    if Distance >= 0.04 * Speed then
        call SetUnitX(Projectly, (GetLocationX(TempPoint) + Cos(Angle) * 0.04 * Speed))
        call SetUnitY(Projectly, (GetLocationY(TempPoint) + Sin(Angle) * 0.04 * Speed))
        set Distance = Distance - (0.04 * Speed)
    else
        call SetUnitX(Projectly, (GetLocationX(TempPoint) + Cos(Angle) * Distance))
        call SetUnitY(Projectly, (GetLocationY(TempPoint) + Sin(Angle) * Distance))
        set Distance = 0
    endif
    if Distance != 0 and IsUnitAlive(Projectly) == TRUE then
        set udg_SysTim_Save_Real_01[Index] = Distance
        call TimerStart(udg_SysTim_Timer[Index], 0.04, false, function Projectly_LineMove_Callback)
    else
    // END OF DISTANCE NULL
        set udg_SysTim_Save_Real_01[Index] = 0.00
        set udg_SysTim_Save_Real_02[Index] = 0.00
        set udg_SysTim_Save_Real_03[Index] = 0.00
        set udg_SysTim_Save_Unit_01[Index] = null
        call RecycleTimers_RecycleIndex(Index)
        if IsUnitAlive(Projectly) == FALSE then
            call KillUnit(Projectly)
        endif
    endif
// NULL
    set Projectly = null
    call RemoveLocation(TempPoint)
endfunction
JASS:
function Projectly_ToUnitMove takes real Speed, unit Projectly, unit Target returns nothing
    local integer Index = RecycleTimers_GetNewIndex()
    set udg_SysTim_Save_Real_01[Index] = Speed
    set udg_SysTim_Save_Unit_01[Index] = Projectly
    set udg_SysTim_Save_Unit_02[Index] = Target
    set Projectly = null
    set Target = null
    call TimerStart(udg_SysTim_Timer[Index], 0.04, false, function Projectly_ToUnitMove_Callback)
endfunction

function Projectly_ToUnitMove_Callback takes nothing returns nothing
// LOCALS
    local integer Index = RecycleTimers_GetCurrentIndex(GetExpiredTimer())
    local real Speed = udg_SysTim_Save_Real_01[Index]
    local unit Projectly = udg_SysTim_Save_Unit_01[Index]
    local unit Target = udg_SysTim_Save_Unit_02[Index]
    local location TempPoint_Proc = GetUnitLoc(Projectly)
    local location TempPoint_Targ = GetUnitLoc(Target)
    local real Angle = AngleBetweenPoints(TempPoint_Proc, TempPoint_Targ)
    local real Distance = DistanceBetweenPoints(TempPoint_Proc, TempPoint_Targ)

// MAIN
    if Distance >= 0.04 * Speed then
        call SetUnitX(Projectly, (GetLocationX(TempPoint_Proc) + Cos(Angle) * 0.04 * Speed))
        call SetUnitY(Projectly, (GetLocationY(TempPoint_Proc) + Sin(Angle) * 0.04 * Speed))
    else
        call SetUnitX(Projectly, (GetLocationX(TempPoint_Proc) + Cos(Angle) * Distance))
        call SetUnitY(Projectly, (GetLocationY(TempPoint_Proc) + Sin(Angle) * Distance))
    endif
    if IsUnitAlive(Projectly) == TRUE then
        call TimerStart(udg_SysTim_Timer[Index], 0.04, false, function Projectly_ToUnitMove_Callback)
    else
        set udg_SysTim_Save_Real_01[Index] = 0.00
        set udg_SysTim_Save_Unit_01[Index] = null
        set udg_SysTim_Save_Unit_02[Index] = null
    endif

// NULL
    set Projectly = null
    set Target = null
    call RemoveLocation(TempPoint_Proc)
    call RemoveLocation(TempPoint_Targ)
endfunction
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
You forgot to add the termination condition(the condition to terminate the knockback) on code (2). Also, your RecycledTimer system might also cause the knockback to fail by a limit op termination due to the seek algorithm if there are a lot of timers on the stack.
 
Level 18
Joined
Jul 3, 2010
Messages
536
You're right, of course, about the RecycledTimer system. I will fix that.

However, the lack of such condition on (2) is intended. You may notice that the timer only keeps restarting so long as the projectly unit is still alive. The intention here is that another trigger will kill the projectly unit when it has done whatever was required of it. (Such as simply reach the target, or follow it for X seconds.) Once that other trigger kills it, the movement also stops.

(1) has a condition of its own because I know when I want it to clear up, when it has reached its maximum distance. However, (1) also stops if the unit is killed, for when I need it to stop prematurely, such as if it only affects the first target it acquires. (Which would once again be done by another trigger.)
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
Ah, I think I got it now. You need to convert the angle to radians. IIRC, AngleBetweenPoints return angle in degrees and jass' Trigonometric functions require them in radians.

local real Angle = AngleBetweenPoints(TempPoint_Proc, TempPoint_Targ)*bj_DEGTORAD

EDIT: I'd suggest inlining the function, since the BJ function uses bj_RADTODEG.
 
Level 18
Joined
Jul 3, 2010
Messages
536
Oh my, many thanks to you.
I never thought of this as the same problem is present in (1). However, I tested (1) with 45 as an angle. Turns out 45 radians are about 58 degrees. I didn't notice that (1) was slightly off and just assumed the problem has to be with (2).

Another thank you for the heads up on my timers.

This is now resolved and can be closed.
 
Status
Not open for further replies.
Top