• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[System] Movement

Level 6
Joined
Oct 23, 2011
Messages
182
A vJass system to handle natural movements of units.
Criticism is welcome and appreciated.

Credits to Jesus4Lyf for original ideas.

JASS:
library Movement requires CTL, Event, UnitIndexer

    /*  
    
    Movement v0.2.0

        Combined vJass system managing movements of units.

    Requirements :  
    
        UnitIndexer by Nestharus
        Event by Nesetharus
        CTL by Nestharus
        
    API :
    
        Movement[unit]
        
            Movement[unit].speed *returns real*
                Retrieves the movement speed of the unit.
                
            Movement[unit].speed = real
                Changes the movement speed of a unit.
                Setting it to <= 0 will remove the unit from list and disallow it from firing moving/stop events.
                Setting it to 0 ~ 522 allows the unit to fire move/stop events.
                Setting it above 522 allows the units movement to be handled differently, allowing it to move at speed over 522
                
                *WARNING : Setting the speed above 1000~ish causes bugs.
                
            Movement[unit].x/y *returns real*
                Retrieves the x/y value of unit.
                
            Movement[unit].x/y = real
                Changes units coordinates.
                This is necessary because changing units x/y while the speed is above 522 will cause problems.
                
            Movement[unit].moving *returns boolean*
                Checks if unit is moving or not.
                
            Movement.onStart.register(boolexpr code)
                Registers a code to be fired when a unit starts moving.
                                
            Movement.onStop.register(boolexpr code)
                Registers a code to be fired when a unit stops moving.


        GetMovingUnit() *returns unit*
            Retrieves the unit firing event

            
    Credits : 
    
        Bribe for IsUnitMoving
        PurgeandFire111 for MoveSpeedX
        Jesus4Lyf for original ideas  
        
    */
    
    private function FilterFunc takes unit b returns boolean
        return not IsUnitPaused(b) and GetUnitAbilityLevel(b, 'Bstn') == 0
    endfunction

    globals
        private constant real MARGIN = .15625
        
        private boolean array j
        private boolean array f
        private boolean array m
        private integer array next
        private integer array prev
        private real array t
        private real array s
        private real array g
        private real array h
        private real d
        private real dx
        private real dy
        private unit u
    endglobals
    
    function GetMovingUnit takes nothing returns unit
        return u
    endfunction
    
    private module M
        private static method onInit takes nothing returns nothing
            set onStart = CreateEvent()
            set onStop = CreateEvent()
        endmethod
    endmodule

    struct Movement extends array
        implement M
    
        readonly static Event onStart
        readonly static Event onStop
        
        private method index takes nothing returns nothing
            if (GetUnitMoveSpeed(GetUnitById(this)) != 0) then
                call .add()
            endif
            
            set g[this] = GetUnitX(GetUnitById(this))
            set h[this] = GetUnitY(GetUnitById(this))
        endmethod
        
        private method deindex takes nothing returns nothing
            set f[this] = false
            set m[this] = false
            call .remove()
        endmethod
        
        private method add takes nothing returns nothing
            if not j[this] then
                set j[this] = true
                set next[this] = 0
                set prev[this] = prev[0]
                set next[prev[0]] = this
                set prev[0] = this
            endif
        endmethod
        
        private method remove takes nothing returns nothing
            if j[this] then
                set j[this] = false
                set next[prev[this]] = next[this]
                set prev[next[this]] = prev[this]
            endif
        endmethod
        
        method operator x takes nothing returns real
            return g[this]
        endmethod
        method operator x= takes real nx returns nothing
            set g[this] = nx
            
            call SetUnitX(GetUnitById(this), nx)
        endmethod
        
        method operator y takes nothing returns real
            return h[this]
        endmethod
        method operator y= takes real ny returns nothing
            set h[this] = ny
            
            call SetUnitY(GetUnitById(this), ny)
        endmethod
        
        method operator moving takes nothing returns boolean
            return m[this]
        endmethod
    
        method operator speed takes nothing returns real
            if f[this] then
                return (s[this] / .03125) + 522
            endif
            
            return GetUnitMoveSpeed(GetUnitById(this))
        endmethod
        method operator speed= takes real ns returns nothing
            call SetUnitMoveSpeed(GetUnitById(this), ns)
            
            if f[this] then
                if (ns > 522) then
                    set s[this] = (ns - 522) * .03125
                else
                    set f[this] = false
                endif
            elseif (ns > 522) then
                set f[this] = true
                set s[this] = (ns - 522) * .03125
            elseif (ns <= 0) then
                if m[this] then
                    set m[this] = false
                    set u = GetUnitById(this)
                        
                    call onStop.fire()
                endif
                
                call .remove()
            else
                call .add()
            endif
        endmethod
    
        implement CT32
            local thistype this = next[0]
            local integer oid
            local real nx
            local real ny
            
            loop
                exitwhen (0 == this)
                
                set u = GetUnitById(this)
                set oid = GetUnitCurrentOrder(u)
                set nx = GetUnitX(u)
                set ny = GetUnitY(u)
                
                if FilterFunc(u) and (oid == 851971 or oid == 851983 or oid == 851990) and (nx != g[this] or ny != h[this]) then
                    if f[this] then
                        set dx = nx - g[this]
                        set dy = ny - h[this]
                        set d = s[this] / SquareRoot(dx * dx + dy * dy)
                        set nx = nx + dx * d
                        set ny = ny + dy * d
                            
                        call SetUnitX(u, nx)
                        call SetUnitY(u, ny)
                    endif
                    
                    set t[this] = MARGIN
                        
                    if (not m[this] and GetUnitTypeId(u) != 0) then
                        set m[this] = true
                            
                        call onStart.fire()
                    endif
                else
                    set t[this] = t[this] - .03125
                        
                    if (t[this] <= 0) then
                        if m[this] then
                            set m[this] = false
                                        
                            call onStop.fire()
                        endif
                    endif
                endif
                
                set g[this] = nx
                set h[this] = ny
                
                set this = next[this]
            endloop
        implement CT32End
        implement UnitIndexStruct
    endstruct
    
endlibrary

Demo

JASS:
library Demo uses Movement

    private module M
        private static method onInit takes nothing returns nothing
            local trigger trg = CreateTrigger()
            
            call TriggerRegisterPlayerChatEvent(trg, Player(0), "-", false)
            call TriggerAddCondition(trg, Filter(function thistype.onChat))
            call Movement.onStart.register(Filter(function thistype.onStart))
            call Movement.onStop.register(Filter(function thistype.onStop))
            
            set trg = null
        endmethod
    endmodule

    private struct Demo extends array
        implement M
    
        static unit u
        
        private static method onStart takes nothing returns boolean
            call BJDebugMsg(GetUnitName(GetMovingUnit()) + " started moving.")
            
            return false
        endmethod
        
        private static method onStop takes nothing returns boolean
            call BJDebugMsg(GetUnitName(GetMovingUnit()) + " stopped moving.")
            
            return false
        endmethod
        
        private static method onChat takes nothing returns boolean  
            set u = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
            set Movement[u].speed = S2R(SubString(GetEventPlayerChatString(), 1, 4))
            
            return false
        endmethod
    endstruct
    
endlibrary
 
Last edited:
I propose you change the linked list :p

JASS:
                set n[this] = n[0]
                set p[n[0]] = this
                set n[0] = this
                set p[this] = 0

->

JASS:
set next[this] = 0
set prev[this] = prev[0]
set next[prev[0]] = this
set prev[0] = this

Longer names == Readable
But too long == Slow
So I guess next and prev would be suitable here ^.^

The array j also needs to be changed :eek:

JASS:
        method operator x takes nothing returns real
            return g[this]
        endmethod
        method operator x= takes real nx returns nothing
            set g[this] = nx
            call SetUnitX(GetUnitById(this), nx)
        endmethod
        
        method operator y takes nothing returns real
            return h[this]
        endmethod
        method operator y= takes real ny returns nothing
            set h[this] = ny
            call SetUnitY(GetUnitById(this), ny)
        endmethod

megusta_1.jpg


set d = s[t] / SquareRoot(dx * dx + dy * dy)

D:
You can change it to this:
set d = s[t] * s[t] / (dx * dx + dy * dy)
(Close enough)


I also propose that you add in a "destroy" method in which you remove the id from the linked list and cleanup the variables so that you'd have less code.

The only time I would inline the destroy method is when I'm only calling it once.

And instead of "a" and "b", you could declare locals x and y under the Constant Timer Loop module implementation. (Locals are referenced faster)
 
Level 6
Joined
Oct 23, 2011
Messages
182
What bugs did you find with IsUnitMoving?

I don't think they are exactly bugs, but probably not intended

1. Using Teleport / SetUnitX/Y / etc. fires events.
They share different purposes and the event firing at same time would not be preferred
Maybe add a feature to disable the system?
or maybe you just intended to make it like that.
It was much of a greater issue with Purge's though =O

With systems like knockback, I think you can just put the event inside the knockback system for greater efficiency.

2. Ordering move/patrol on somewhere out of frontal cone fires stop/move event again, due to unit stopping to turn around.

You cannot really determine whether you're going to fire the events or not because it is hard to point a coordinate within your frontal angle with plain eyes (may be abusible).
This changes with the period you assigned to the system so it's troublesome
 
Last edited:
To reduce the amount of code, you could use the CTL modules (not CT32)
When you want to create a new node and add it to the list, call create()
create returns the node.

this.destroy() will destroy the node and remove it from the list.

Your code would look like this:

JASS:
implement CTL
    local real nx
    local real ny
implement CTLExpire
    set u = GetUnitById(this)
    set nx = GetUnitX(u)
    set ny = GetUnitY(u)
                
    if (GetUnitAbilityLevel(u, STUN_ID) == 0) then
        if not (nx >= (g[this] - .01) and nx <= (g[this] + .01)) or not (ny >= (h[this] - .01) and ny <= (h[this] + .01)) then
            if f[this] then
                set dx = nx - g[this]
                set dy = ny - h[this]
                set d = s[this] / SquareRoot(dx * dx + dy * dy)
                set nx = nx + dx * d
                set ny = ny + dy * d
                            
                call SetUnitX(u, nx)
                call SetUnitY(u, ny)
            endif
        
            set g[this] = nx
            set h[this] = ny
            set t[this] = MARGIN
                        
            if (not m[this] and GetUnitTypeId(u) != 0) then
                set m[this] = true
                            
                call onStart.fire()
            endif
        else
            set t[this] = t[this] - .03125
                        
            if (t[this] <= 0) then
                if m[this] then
                    set m[this] = false
                                
                    call onStop.fire()
                endif
                call .remove()
            endif
        endif
    endif
implement CTLNull
implement CTLEnd

Slightly shorter :p
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
if (GetUnitAbilityLevel(u, STUN_ID) == 0) then

There are many, MANY more ways to prevent a unit from moving (pause, ensnare, entangling roots, different stun buffs...).

A system like yours should be built in conjunction with some other buff/status system, which allows other systems and the user himself to control those "disables" (because when using one of those systems, the user is expected to apply all the buffs through it) and to correctly and efficiently check if a unit is "disabled." I'm too lazy to check if there's such a system here at the Hive, but in case you didn't understand, I'm talking about something like Jesus4Lyf's Status (although I don't like it personally).
 
Top