[vJASS] [System] Motion Sensor

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Motion Sensor v1.4.0


Note:
As of version v1.4.0, there have been major changes in the API. To maintain backwards compatibility, library MotionSensorBC is available below. Just put it in your map and all will be seamless.

Please read the script header for the complete documentation and for more information.
You might also want to check the demo map below, its nice =). Plus it also has a GUI extension.

Motion Sensor Script
JASS:
library MotionSensor /* v1.4.0 https://www.hiveworkshop.com/threads/system-motion-sensor.287219/


    */requires /*

    */UnitDex       /*  http://www.hiveworkshop.com/threads/248209

    *///! novjass


    |=============|
    | Author: AGD |
    |=============|

    /*  This simple system allows you to check if the current motion
        a unit is stationary or in motion (This includes triggered
        motion). You can also use this to get the instantaneous speed
        of a unit and its direction of motion. This also allows you to
        detect a change in motion event i.e. when either a unit in
        motion stops or a stationary unit moves. Furthermore, the system
        includes many other utilities regarding unit motion.            */


    |==============|
    |  Struct API  |
    |==============|

        struct MotionSensor

            readonly thistype prev
            readonly thistype next
            /*  - unit index links   */

            readonly boolean sensored                    /* Checks if the Sensor instance is registered */
            readonly boolean moving                        /* Checks if it is moving or not */
            readonly real speed                            /* The instantaneous speed */
            readonly real direction                        /* The direction of motion */
            readonly real deltaX                        /* X component of the instantaneous speed */
            readonly real deltaY                        /* Y component of the instantaneous speed */
            readonly real prevX                            /* The previous x-coordinate */
            readonly real prevY                            /* The previous y-coordinate */
            readonly static unit triggerUnit            /* The motion changing unit */
            readonly static integer newMotionState        /* The current motion state of the motion changing unit */

            static method operator [] takes unit u returns Sensor/*
                - Returns a Sensor instance based on the unit parameter

          */static method operator []= takes unit u, boolean flag returns nothing/*
                - Registers/Unregisters a unit to the system

          */static method operator enabled takes nothing returns boolean/*
                - Checks if the Motion Sensor is enabled or disabled

          */static method operator enabled= takes boolean flag returns nothing/*
                - Enables/Disables the Motion Sensor

          */static method registerMotionChangeEvent takes code c returns triggercondition/*
          */static method unregisterMotionChangeEvent takes triggercondition tc returns nothing/*
                - Registers a code to run during a motion change event / Unregisters

          */static method registerMotionStartEvent takes code c returns triggercondition/*
          */static method unregisterMotionStartEvent takes triggercondition tc returns nothing/*
                - Registers a code to run when a unit stops moving / Unregisters

          */static method registerMotionStopEvent takes code c returns triggercondition/*
          */static method unregisterMotionStopEvent takes triggercondition tc returns nothing/*
                - Registers a code to run when a stationary unit moves / Unregisters        */


    |================|
    |  Function API  |
    |================|

    /*  All these functions inline when DEBUG_MODE is OFF except for
        RegisterEvent functions which was done intentionally to allow
        users to pass code that returns nothing

      */function GetInstantaneousSpeed takes unit u returns real/*
            - Returns the instantaneous speed of a unit

      */function GetMotionDirection takes unit u returns real/*
            - Returns the current direction of a unit's motion

      */function GetUnitDeltaX takes unit u returns real/*
      */function GetUnitDeltaY takes unit u returns real/*
            - Returns the x/y-component of a unit's instantaneous velocity

      */function GetUnitPreviousX takes unit u returns real/*
      */function GetUnitPreviousY takes unit u returns real/*
            - Returns the previous x/y-coordinate of the unit

      */function IsUnitMoving takes unit u returns boolean/*
            - Checks if a unit is currently moving or not

      */function IsUnitSensored takes unit u returns boolean/*
            - Checks if a unit is registered to the system or not

      */function RegisterMotionChangeEvent takes code c returns triggercondition/*
      */function UnregisterMotionChangeEvent takes triggercondition tc returns nothing/*
            - Registers a code to run on a motion change event / Unregisters it

      */function RegisterMotionStartEvent takes code c returns triggercondition/*
      */function UnregisterMotionStartEvent takes triggercondition tc returns nothing/*
            - Registers a code to run when a stationary unit moves / Unregisters it

      */function RegisterMotionStopEvent takes code c returns triggercondition/*
      */function UnregisterMotionStopEvent takes triggercondition tc returns nothing/*
            - Registers a code to run when a unit in motion stops / Unregisters it

      */function SensorAddUnit takes unit u returns nothing
        function SensorRemoveUnit takes unit u returns nothing/*
            - Registers/Unregisters a unit to the system

      */function GetNewMotionState takes nothing returns integer/*
            - Refers to the current motion state of the motion changing unit

      */function GetMotionChangingUnit takes nothing returns unit/*
            - Refers to the motion changing unit

      */function MotionSensorEnable takes nothing returns nothing
        function MotionSensorDisable takes nothing returns nothing/*
            - Switches the motion sensor ON/OFF

      */function IsSensorEnabled takes nothing returns boolean/*
            - Checks if the system is enabled of disabled     */


    |===========|
    | Constants |
    |===========|

        Groups:
        group SENSOR_GROUP_MOVING
        group SENSOR_GROUP_STATIONARY

    /*  You can use these groups to easily loop among moving/stationary
        units like so:                                                     */

        loop
            set u = FirstOfGroup(SENSOR_GROUP_MOVING)
            exitwhen u == null
            call GroupRemoveUnit(SENSOR_GROUP_MOVING, u)
            call GroupAddUnit(tempGroup, u)
            // ...Do stuffs...
        endloop
        set forSwap = SENSOR_GROUP_MOVING
        set SENSOR_GROUP_MOVING = tempGroup
        set tempGroup = forSwap
        // Note: Do not destroy these groups nor set them to something
        //       else without setting them back to their original value
        //       before the thread execution is finished

        Integers:
        integer MOTION_STATE_MOVING
        integer MOTION_STATE_STATIONARY

    /*  You can use these constants to check what is the new motion state
        of the event like so:                                              */

        if GetNewMotionState() == MOTION_STATE_MOVING then
            call KillUnit(GetMotionChangingUnit())
        elseif GetNewMotionState() == MOTION_STATE_STATIONARY then
            call RemoveUnit(GetMotionChangingUnit())
        endif


    |=========|
    | Modules |
    |=========|

        module MotionChangeEvent/*
            - You should implement this below your static methods named
              onMotionStart and onMotionStop if you have any. Otherwise,
              it will generate extra code behind the scenes which is not
              so good.
            - If static methods onMotionStart and/or onMotionStop is found
              in your struct, this will automatically register them to run
              on their corresponding motion change events (Note that you
              will not be able to unregister the mentioned methods).       */


    //! endnovjass

    /*=========================== Configuration ===========================*/

    private module Configuration

    /*  Unit position check interval (Values greater than 0.03
        causes a bit of inaccuracy in the given instantaneous
        speed of a unit. As the value lowers, the accuracy
        increases at the cost of performance.)                               */
        static constant real PERIOD                        = 0.03

    /*  Set to true if you want the system to automatically
        register units upon entering the map. Set to false if
        you want to manually register units.                               */
        static constant boolean AUTO_REGISTER_UNITS        = true

    endmodule

    /*======================= End of Configuration ========================*/
    /*   Do not change anything below this line if you're not so sure on   */
    /*                         what you're doing.                               */
    /*=====================================================================*/
    private keyword Init

    globals
        constant integer MOTION_STATE_MOVING        = 1
        constant integer MOTION_STATE_STATIONARY    = 2
        group SENSOR_GROUP_MOVING                   = CreateGroup()
        group SENSOR_GROUP_STATIONARY               = CreateGroup()
    endglobals

    struct MotionSensor extends array

        implement Configuration

        readonly boolean sensored
        readonly boolean moving
        readonly real speed
        readonly real direction
        readonly real deltaX
        readonly real deltaY
        readonly real prevX
        readonly real prevY
        readonly static unit triggerUnit
        readonly static integer newMotionState

        readonly thistype prev
        readonly thistype next
        private static boolean isEnabled
        private static timer staticTimer = CreateTimer()
        private static trigger onMoveTrig = CreateTrigger()
        private static trigger onStopTrig = CreateTrigger()
        private static trigger onMotionTrig = CreateTrigger()

        debug private static method debug takes string msg returns nothing
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 30, "|CFFFFCC00[Motion Sensor]|R " + msg)
        debug endmethod

        private static method onPeriodic takes nothing returns nothing
            local unit u
            local real dx
            local real dy
            local real unitX
            local real unitY
            local boolean prevState
            local thistype node = thistype(0).next
            loop
                exitwhen node == 0
                set u = GetUnitById(node)
                set prevState = node.moving
                set unitX = GetUnitX(u)
                set unitY = GetUnitY(u)
                set dx = unitX - node.prevX
                set dy = unitY - node.prevY
                set node.prevX = unitX
                set node.prevY = unitY
                set node.deltaX = dx
                set node.deltaY = dy
                set node.speed = SquareRoot(dx*dx + dy*dy)/PERIOD
                set node.direction = bj_RADTODEG*Atan2(dy, dx)
                set node.moving = node.speed > 0.00
                if prevState != node.moving then
                    set triggerUnit = u
                    if node.moving then
                        call GroupRemoveUnit(SENSOR_GROUP_STATIONARY, u)
                        call GroupAddUnit(SENSOR_GROUP_MOVING, u)
                        set newMotionState = MOTION_STATE_MOVING
                        call TriggerEvaluate(onMoveTrig)
                    else
                        call GroupRemoveUnit(SENSOR_GROUP_MOVING, u)
                        call GroupAddUnit(SENSOR_GROUP_STATIONARY, u)
                        set newMotionState = MOTION_STATE_STATIONARY
                        call TriggerEvaluate(onStopTrig)
                    endif
                    set newMotionState = 0
                endif
                set u = null
                set node = node.next
            endloop
        endmethod

        static method operator enabled takes nothing returns boolean
            return isEnabled
        endmethod

        static method operator enabled= takes boolean flag returns nothing
            set isEnabled = flag
            if flag then
                if thistype(0).next != 0 then
                    call TimerStart(staticTimer, PERIOD, true, function thistype.onPeriodic)
                endif
            else
                call PauseTimer(staticTimer)
            endif
        endmethod

        static method operator [] takes unit u returns thistype
            debug if not thistype(GetUnitId(u)).sensored then
                debug call debug("|CFFFF0000Operator [] error: Attempt to use an unregistered instance|R")
                debug return 0
            debug endif
            return GetUnitId(u)
        endmethod

        static method operator []= takes unit u, boolean flag returns nothing
            local thistype node
            if u != null then
                set node = GetUnitId(u)
                if flag then
                    debug if node.sensored then
                        debug call debug("|CFFFF0000Operator []= error: Attempt to register an already registered instance|R")
                        debug return
                    debug endif
                    /* Enable the Sensor iterator again when this is the first instance to be added on the list */
                    if enabled and thistype(0).next == 0 then
                        call TimerStart(staticTimer, PERIOD, true, function thistype.onPeriodic)
                    endif
                    set node.prev = thistype(0).prev
                    set node.next = 0
                    set thistype(0).prev = node
                    set node.prev.next = node
                    call GroupAddUnit(SENSOR_GROUP_STATIONARY, u)
                    /* Initialize prevX and prevY for the newly registered unit to
                       prevent it from causing a motion change event false positive */
                    set node.prevX = GetUnitX(u)
                    set node.prevY = GetUnitY(u)
                else
                    debug if not node.sensored then
                        debug call debug("|CFFFF0000Operator []= error: Attempt to unregister an already unregistered instance|R")
                        debug return
                    debug endif
                    if IsUnitInGroup(u, SENSOR_GROUP_MOVING) then
                        call GroupRemoveUnit(SENSOR_GROUP_MOVING, u)
                    else
                        call GroupRemoveUnit(SENSOR_GROUP_STATIONARY, u)
                    endif
                    set node.moving = false
                    set node.deltaX = 0.00
                    set node.deltaY = 0.00
                    set node.prevX = 0.00
                    set node.prevY = 0.00
                    set node.speed = 0.00
                    set node.direction = 0.00
                    set node.next.prev = node.prev
                    set node.prev.next = node.next
                    set node.prev = 0
                    set node.next = 0
                    /* If the list is empty, stop iterating */
                    if enabled and thistype(0).next == 0 then
                        call PauseTimer(staticTimer)
                    endif
                endif
                set node.sensored = flag
            debug else
                debug call debug("|CFFFF0000Operator []= error: Attempt to register a null unit|R")
            endif
        endmethod

        static method registerMotionStartEvent takes code c returns triggercondition
            return TriggerAddCondition(onMoveTrig, Filter(c))
        endmethod
        static method unregisterMotionStartEvent takes triggercondition tc returns nothing
            call TriggerRemoveCondition(onMoveTrig, tc)
        endmethod

        static method registerMotionStopEvent takes code c returns triggercondition
            return TriggerAddCondition(onStopTrig, Filter(c))
        endmethod
        static method unregisterMotionStopEvent takes triggercondition tc returns nothing
            call TriggerRemoveCondition(onStopTrig, tc)
        endmethod

        static method registerMotionChangeEvent takes code c returns triggercondition
            return TriggerAddCondition(onMotionTrig, Filter(c))
        endmethod
        static method unregisterMotionChangeEvent takes triggercondition tc returns nothing
            call TriggerRemoveCondition(onMotionTrig, tc)
        endmethod

        static if thistype.AUTO_REGISTER_UNITS then
            private static method onUnitIndex takes nothing returns nothing
                set thistype[GetIndexedUnit()] = true
            endmethod
        endif

        private static method onUnitDeindex takes nothing returns nothing
            static if thistype.AUTO_REGISTER_UNITS then
                set thistype[GetIndexedUnit()] = false
            else
                if thistype(GetUnitId(GetIndexedUnit())).sensored then 
                    set thistype[GetIndexedUnit()] = false
                endif
            endif
        endmethod

        private static method onMotionChange takes nothing returns nothing
            call TriggerEvaluate(onMotionTrig)
        endmethod

        private static method init takes nothing returns nothing
            local code onUnitDeindex = function thistype.onUnitDeindex
            local code onMotionChange = function thistype.onMotionChange
            static if thistype.AUTO_REGISTER_UNITS then
                local code onUnitIndex = function thistype.onUnitIndex
                call OnUnitIndex(onUnitIndex)
            endif
            call OnUnitDeindex(onUnitDeindex)
            call registerMotionStartEvent(onMotionChange)
            call registerMotionStopEvent(onMotionChange)
            /* Turn on Sensor */
            set enabled = true
        endmethod
        implement Init

    endstruct

    private module Init
        private static method onInit takes nothing returns nothing
            call init()
        endmethod
    endmodule

    module MotionChangeEvent
        static if thistype.onMotionStart.exists and thistype.onMotionStop.exists then
            private static method onInit takes nothing returns nothing
                call RegisterMotionStartEvent(function thistype.onMotionStart)
                call RegisterMotionStopEvent(function thistype.onMotionStop)
            endmethod
        elseif thistype.onMotionStart.exists then
            private static method onInit takes nothing returns nothing
                call RegisterMotionStartEvent(function thistype.onMotionStart)
            endmethod
        elseif thistype.onMotionStop.exists then
            private static method onInit takes nothing returns nothing
                call RegisterMotionStopEvent(function thistype.onMotionStop)
            endmethod
        endif
    endmodule

    /*=====================================================================*/

    function RegisterMotionChangeEvent takes code c returns triggercondition
        return MotionSensor.registerMotionChangeEvent(c)
        return null
    endfunction
    function UnregisterMotionChangeEvent takes triggercondition tc returns nothing
        call MotionSensor.unregisterMotionChangeEvent(tc)
    endfunction

    function RegisterMotionStartEvent takes code c returns triggercondition
        return MotionSensor.registerMotionStartEvent(c)
        return null
    endfunction
    function UnregisterMotionStartEvent takes triggercondition tc returns nothing
        call MotionSensor.unregisterMotionStartEvent(tc)
    endfunction

    function RegisterMotionStopEvent takes code c returns triggercondition
        return MotionSensor.registerMotionStopEvent(c)
        return null
    endfunction
    function UnregisterMotionStopEvent takes triggercondition tc returns nothing
        call MotionSensor.unregisterMotionStopEvent(tc)
    endfunction

    function SensorAddUnit takes unit u returns nothing
        set MotionSensor[u] = true
    endfunction
    function SensorRemoveUnit takes unit u returns nothing
        set MotionSensor[u] = false
    endfunction

    function GetMotionChangingUnit takes nothing returns unit
        return MotionSensor.triggerUnit
    endfunction

    function GetNewMotionState takes nothing returns integer
        return MotionSensor.newMotionState
    endfunction

    function GetInstantaneousSpeed takes unit u returns real
        return MotionSensor[u].speed
    endfunction

    function GetUnitDeltaX takes unit u returns real
        return MotionSensor[u].deltaX
    endfunction
    function GetUnitDeltaY takes unit u returns real
        return MotionSensor[u].deltaY
    endfunction

    function GetUnitPreviousX takes unit u returns real
        return MotionSensor[u].prevX
    endfunction
    function GetUnitPreviousY takes unit u returns real
        return MotionSensor[u].prevY
    endfunction

    function GetMotionDirection takes unit u returns real
        return MotionSensor[u].direction
    endfunction

    function IsUnitMoving takes unit u returns boolean
        return MotionSensor[u].moving
    endfunction

    function IsUnitSensored takes unit u returns boolean
        return MotionSensor(GetUnitId(u)).sensored
    endfunction

    function MotionSensorEnable takes nothing returns nothing
        set MotionSensor.enabled = true
    endfunction
    function MotionSensorDisable takes nothing returns nothing
        set MotionSensor.enabled = false
    endfunction

    function IsSensorEnabled takes nothing returns boolean
        return MotionSensor.enabled
    endfunction

    //! runtextmacro optional MOTION_SENSOR_BC()


endlibrary



JASS:
library MotionSensorBC/* for versions 1.3b and below*/ requires MotionSensor


    //! textmacro MOTION_SENSOR_BC
    struct Sensor extends array

        method operator flag takes nothing returns boolean
            return MotionSensor(this).sensored
        endmethod
        method operator moving takes nothing returns boolean
            return MotionSensor(this).moving
        endmethod
        method operator speed takes nothing returns real
            return MotionSensor(this).speed
        endmethod
        method operator direction takes nothing returns real
            return MotionSensor(this).direction
        endmethod
        method operator deltaX takes nothing returns real
            return MotionSensor(this).deltaX
        endmethod
        method operator deltaY takes nothing returns real
            return MotionSensor(this).deltaY
        endmethod
        method operator prevX takes nothing returns real
            return MotionSensor(this).prevX
        endmethod
        method operator prevY takes nothing returns real
            return MotionSensor(this).prevY
        endmethod

        static method operator triggerUnit takes nothing returns unit
            return MotionSensor.triggerUnit
        endmethod
        static method operator newMotionState takes nothing returns real
            return I2R(MotionSensor.newMotionState)
        endmethod

        static method operator [] takes unit u returns Sensor
            return MotionSensor[u]
        endmethod
        static method operator []= takes unit u, boolean flag returns nothing
            set MotionSensor[u] = flag
        endmethod
        static method operator enabled= takes boolean flag returns nothing
            set MotionSensor.enabled = flag
        endmethod
        static method operator enabled takes nothing returns boolean
            return MotionSensor.enabled
        endmethod
        static method addMotionChangeEvent takes code c returns triggercondition
            return MotionSensor.registerMotionChangeEvent(c)
        endmethod
        static method addOnMoveEvent takes code c returns triggercondition
            return MotionSensor.registerMotionStartEvent(c)
        endmethod
        static method addOnStopEvent takes code c returns triggercondition
            return MotionSensor.registerMotionStopEvent(c)
        endmethod
        static method removeMotionChangeEvent takes triggercondition tc returns nothing
            call MotionSensor.unregisterMotionChangeEvent(tc)
        endmethod
        static method removeOnMoveEvent takes triggercondition tc returns nothing
            call MotionSensor.unregisterMotionStartEvent(tc)
        endmethod
        static method removeOnStopEvent takes triggercondition tc returns nothing
            call MotionSensor.unregisterMotionStopEvent(tc)
        endmethod

    endstruct

    function RemoveMotionChangeEvent takes triggercondition tc returns nothing
        call UnregisterMotionChangeEvent(tc)
    endfunction
    function RegisterOnMoveEvent takes code c returns triggercondition
        return RegisterMotionStartEvent(c)
    endfunction
    function RemoveOnMoveEvent takes triggercondition tc returns nothing
        call UnregisterMotionStartEvent(tc)
    endfunction
    function RegisterOnStopEvent takes code c returns triggercondition
        return RegisterMotionStopEvent(c)
    endfunction
    function RemoveOnStopEvent takes triggercondition tc returns nothing
        call UnregisterMotionStopEvent(tc)
    endfunction
    //! endtextmacro


endlibrary





Rupture library
JASS:
library Rupture uses MotionSensor, UnitDex, TimerUtils


    struct Rupture extends array

        private timer timer
        private boolean allocated
        private effect sfx
        private real interval
        private real elapsed
        private string periodicSfx
        private string markAttachpoint
        private string periodicAttachpoint
        unit caster
        unit target
        real dpd
        real duration
        attacktype at
        damagetype dt
        weapontype wt

        method destroy takes nothing returns nothing
            call DestroyEffect(.sfx)
            call ReleaseTimer(.timer)
            set .allocated = false
            set .sfx = null
            set .timer = null
            set .interval = 0
            set .dpd = 0
            set .elapsed = 0
            set .duration = 0
            set .at = null
            set .dt = null
            set .wt = null
            set .periodicSfx = null
            set .periodicAttachpoint = null
            set .markAttachpoint = null
            set .caster = null
            set .target = null
        endmethod

        private static method periodic takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local Sensor s = Sensor[.target]
            set .elapsed = .elapsed + .interval
            if IsUnitType(.target, UNIT_TYPE_DEAD) or (.duration != 0.00 and .elapsed > .duration) then
                call .destroy()
            elseif s.moving then
                if .dpd > 0.00 then
                    call UnitDamageTarget(.caster, .target, s.speed*.dpd*.interval, true, false, .at, .dt, .wt)
                elseif .dpd < 0.00 then
                    call SetWidgetLife(.target, GetWidgetLife(.target) - s.speed*.dpd*.interval)
                    call UnitDamageTarget(.caster, .target, 0.01, true, false, .at, .dt, .wt)
                endif
                call DestroyEffect(AddSpecialEffectTarget(.periodicSfx, .target, .periodicAttachpoint))
            endif
        endmethod

        static method create takes real period, unit caster, unit target, real duration, real impactDamage, real damagePerDistance, string impactSfx, string markSfx, string periodicSfx, string impactAttachpoint, string markAttachpoint, string periodicAttachpoint, attacktype at, damagetype dt, weapontype wt returns thistype
            local thistype this = GetUnitId(target)
            if target != null and not .allocated then
                if caster == null then
                    set caster = target
                endif
                set .allocated = true
                set .at = at
                set .dt = dt
                set .wt = wt
                set .interval = period
                set .dpd = damagePerDistance
                set .duration = duration
                set .caster = caster
                set .target = target
                set .markAttachpoint = markSfx
                set .periodicAttachpoint = periodicAttachpoint
                set .periodicSfx = periodicSfx
                set .sfx = AddSpecialEffectTarget(markSfx, target, markAttachpoint)
                set .timer = NewTimerEx(this)
                if impactDamage < 0.00 then
                    call SetWidgetLife(target, GetWidgetLife(target) - impactDamage)
                    call UnitDamageTarget(caster, target, 0.01, true, false, at, dt, wt)
                else
                    call UnitDamageTarget(caster, target, impactDamage, true, false, at, dt, wt)
                endif
                call DestroyEffect(AddSpecialEffectTarget(impactSfx, target, impactAttachpoint))
                call TimerStart(.timer, 0.05, true, function thistype.periodic)
            endif
            return this
        endmethod

        method operator effect= takes string model returns nothing
            call DestroyEffect(.sfx)
            set .sfx = AddSpecialEffectTarget(model, .target, .markAttachpoint)
        endmethod

        method changeMarkEffect takes string newModel, string newAttachpoint returns nothing
            set .markAttachpoint = newAttachpoint
            set .effect = newModel
        endmethod

        method changePeriodicEffect takes string newModel, string newAttachpoint returns nothing
            set .periodicSfx = newModel
            set .periodicAttachpoint = newAttachpoint
        endmethod

        method operator period= takes real newPeriod returns nothing
            set .interval = newPeriod
            call TimerStart(.timer, newPeriod, true, function thistype.periodic)
        endmethod

    endstruct


endlibrary

Rupture Moving Units
JASS:
library RuptureSpell requires MotionSensor, Rupture


    globals
        private constant real LOOP_INTERVAL = 0.05
        private constant real DPD = 0.50
        private constant real DURATION = 0.00 //Permanent
        private constant real START_DAMAGE = 60.00
        private constant attacktype AT = ATTACK_TYPE_HERO
        private constant damagetype DT = DAMAGE_TYPE_NORMAL
        private constant weapontype WT = null
        private constant string SFX_MODEL_1 = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
        private constant string SFX_MODEL_2 = "Abilities\\Spells\\Orc\\TrollBerserk\\HeadhunterWEAPONSLeft.mdl"
        private constant string SFX_MODEL_3 = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl"
        private constant string SFX_MODEL_4 = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl"
        private constant string SFX_MODEL_5 = "Abilities\\Spells\\Other\\Drain\\DrainTarget.mdl"
        private constant string SFX_MODEL_6 = null
        private constant string ATTACH_POINT_1 = "overhead"
        private constant string ATTACH_POINT_2 = "origin"
        private constant string ATTACH_POINT_3 = "chest"
        private constant string ATTACH_POINT_4 = "origin"
        private constant string ATTACH_POINT_5 = "origin"
        private constant string ATTACH_POINT_6 = null
    endglobals

    //===================================================================================================

    private struct Spell extends array

        private static trigger trig1 = CreateTrigger()
        private static trigger trig2 = CreateTrigger()

        private static method onMotionStart takes nothing returns nothing
            if GetWidgetLife(Sensor.triggerUnit) < START_DAMAGE + 1.00 then
                call Rupture.create(LOOP_INTERVAL, Sensor.triggerUnit, Sensor.triggerUnit, DURATION, -START_DAMAGE, -DPD, SFX_MODEL_4, SFX_MODEL_5, SFX_MODEL_6, ATTACH_POINT_4, ATTACH_POINT_5, ATTACH_POINT_6, AT, DT, WT)
            else
                call Rupture.create(LOOP_INTERVAL, Sensor.triggerUnit, Sensor.triggerUnit, DURATION, START_DAMAGE, DPD, SFX_MODEL_1, SFX_MODEL_2, SFX_MODEL_3, ATTACH_POINT_1, ATTACH_POINT_2, ATTACH_POINT_3, AT, DT, WT)
            endif
        endmethod

        private static method onMotionStop takes nothing returns nothing
            call Rupture(GetUnitId(Sensor.triggerUnit)).destroy()
        endmethod

        private static method shiftEffect1 takes nothing returns nothing
            local Rupture this
            local unit u = GetTriggerUnit()
            if Sensor[u].moving then
                set this = Rupture(GetUnitId(u))
                set this.dpd = -this.dpd
                call this.changeMarkEffect(SFX_MODEL_5, ATTACH_POINT_5)
                call this.changePeriodicEffect(SFX_MODEL_6, ATTACH_POINT_6)
            endif
            set u = null
        endmethod

        private static method shiftEffect2 takes nothing returns nothing
            local Rupture this
            local unit u = GetTriggerUnit()
            if Sensor[u].moving then
                set this = Rupture(GetUnitId(u))
                set this.dpd = -this.dpd
                call this.changeMarkEffect(SFX_MODEL_2, ATTACH_POINT_2)
                call this.changePeriodicEffect(SFX_MODEL_3, ATTACH_POINT_3)
            endif
            set u = null
        endmethod

        private static method registerUnit takes nothing returns nothing
            local unit u = GetIndexedUnit()
            call TriggerRegisterUnitStateEvent(trig1, u, UNIT_STATE_LIFE, LESS_THAN, 20.00)
            call TriggerRegisterUnitStateEvent(trig2, u, UNIT_STATE_LIFE, GREATER_THAN, GetUnitState(u, UNIT_STATE_MAX_LIFE) - 10.00)
            set u = null
        endmethod

        private static method onInit takes nothing returns nothing
            local code register = function thistype.registerUnit
            local code shiftEffect1 = function thistype.shiftEffect1
            local code shiftEffect2 = function thistype.shiftEffect2
            call OnUnitIndex(register)
            call TriggerAddCondition(trig1, Filter(shiftEffect1))
            call TriggerAddCondition(trig2, Filter(shiftEffect2))
        endmethod

        implement MotionChangeEvent

    endstruct


endlibrary




v1.4.0
- Major API changes (Also added a backwards compatibility lib)
- Made the configuration constant public
- Added a readonly linked-list for unit indexes registered in the system
- Changes in the GUI Plugin
- Other changes

v1.3c
- Not uploaded

v1.3b
- Added a new public module for running motion events within a struct
- Added two new groups SENSOR_GROUP_MOVING and SENSOR_GROUP_STATIONARY to allow users to easily loop through desired units
- The motion change event now uses TriggerEvaluate() instead of TRVE for a faster execution
- Changed prevX and prevY's privacy from being private to readonly
- Added function wrappers for getting the prevX and prevY of a unit
- Added a debug message in the operator []= to warm users when they try to double-free or double-register a unit
- When enabling the Sensor, it checks first if the sensorGroup is empty before running the periodic actions
- Updated the Rupture Spell code and the demo map
- Updated the documentation
- Other changes

v1.3
- Not uploaded

v1.2c
- Renamed some global constants
- Updated the demo map

v1.2b
- Made some proper functions/methods arrangements
- Removed the method operators < and ==
- Removed some unnecessary debug messages
- Fixed some compile errors on the demo script
- Other changes

v1.2
- The system now uses struct APIs but function wrappers are still available for users who are not so accustomed to using structs
- Now provides a boolean struct member for checking if a unit is registered to the system or not
- Some naming changes
- Optimized some parts of the script
- Some fixes and other changes

v1.1b
- Added a new API function IsSensorEnabled() for checking if the MotionSensor is enabled or disabled
- Some changes on the Demo Map
- Functions for registering codes now do not inline for the reason that it will throw an error when the code passed returns nothing in case it inlines
- Fixed the problem regarding Pause/Unpause timer
- The periodic function now only runs when there is a unit registered
- Other changes

v1.1
- API functions for getting a unit's motion stats are now inlined to array lookups
- Added more functionality to the system
- Now allows you to remove a triggercondition from a motion event
- Users can now choose if they want to automatically add all units to the system or if they want to add units manually
- Now uses arrays instead of hashtable
- Added UnitDex as a requirement, TimerUtils and GroupUtils as optional requirements
- The system no longer enumerates units in the map every period but stores them to a global group
- All public functions now inline when DEBUG_MODE is OFF
- Added a demo map
- Other changes

v1.0
- First Release
 

Attachments

  • Motion Sensor v1.3b.w3x
    56.1 KB · Views: 128
  • Motion Sensor v1.4.0.w3x
    56.7 KB · Views: 52
Last edited:
Calculating on each API function call is not efficient. It should be calculated in each onPeriod run, and then attached to the unit, so it just can be loaded in O(1) style.

You might use a UnitIndexer to bind the data to the unit over using hashtable. It might also give the possibility to have one static group of units ofwhich you remove units always onDeindex event and inwhich you add always units onIndex event.
This would allow you not to enuemrate always all units over the map in each function call onPeriod.

Having "speed" might be handy, too, but having deltaX and deltaY should be provided, too.

I'm not sure multiple triggers are needed to fire events. Try to reduce it to one only. :)

I would find it handy if user has an API to exclude and include again certain units from the system.
Because for example in a big map with many units it would fire so many events and so often, even maybe the user only wanted to use it for hero-units.

Honestly, I'm personaly not a big fan of such moving detection methods, because the reason for a change of x/y cooridnates can be much more than moving,
so in the end it just must get interpreted again, but we have such similar system in Spells Section by Bribe, and maybe we also can have something for jassers here in case it's different a bit from Bribe's and better userable for jassers.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
You might use a UnitIndexer to bind the data to the unit over using hashtable.
I suppose, but I still need to save prevX and prevY in hashtable so that I can do comparison like prevX == null which will fail when using arrays as their default real values are 0., which in turn will register motion change event false positives for newly entering/indexed units.
Okay, I can easily solve that by setting their prevX/Y to their current positon during a OnUnitIndex :)

Having "speed" might be handy, too, but having deltaX and deltaY should be provided, too....
...I would find it handy if user has an API to exclude and include again certain units from the system.
Because for example in a big map with many units it would fire so many events and so often, even maybe the user only wanted to use it for hero-units.
Nice addition, will add

I'm not sure multiple triggers are needed to fire events. Try to reduce it to one only.
Hmm I don't think its possible
 
Last edited:
Ah, you are very right with trigger usage at the moment, I wrongly read the code.
But why are the triggers returned? The user should not really be able to destroy it anyways.

But though TriggerCondition might be a win as return type, because the system could also provide unregistration functions, which would delink the TriggerCondition again from trigger.

call Register(whichtrigger, which code/boolexpr) -> returns TriggerCondition
call Unregister(whichtrigger, whichCondition)
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
UPDATED to v1.1
- API functions for getting a unit's motion stats are now inlined to array lookups
- Added more functionality to the system
- Now allows you to remove a triggercondition from a motion event
- Users can now choose if they want to automatically add all units to the system or if they want to add units manually
- Now uses arrays instead of hashtable
- Added UnitDex as a requirement, TimerUtils and GroupUtils as optional requirements
- The system no longer enumerates units in the map every period but stores them to a global group
- All public functions now inline when DEBUG_MODE is OFF
- Added a demo map
- Other changes
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Well I think there's no problem regarding the filterfuncs not being removed as same function would produce the same filterfuncs which means its not such a waste of memory since you will be creating filterfuncs from functions all the time and they'll most likely be just recycled.
JASS:
call BJDebugMsg(I2S(GetHandleId(Filter(function A))))
call BJDebugMsg(I2S(GetHandleId(Filter(function A))))
call BJDebugMsg(I2S(GetHandleId(Filter(function A))))
// They have similar handleids
So unless there's like a thousand different function you use Filter() from, it would not be much of a problem I think.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Small update
v1.1b
- Added a new API function IsSensorEnabled() for checking if the MotionSensor is enabled or disabled
- Some changes on the Demo Map
- Functions for registering codes now do not inline for the reason that it will throw an error when the code passed returns nothing in case it inlines
- Fixed the problem regarding Pause/Unpause timer
- The periodic function now only runs when there is a unit registered
- Other changes


I hope the demo now compiles, cause I'm using the experimental version of pjass.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
UPDATED to v1.2
- The system now uses struct APIs but function wrappers are still available for users who are not so accustomed to using structs
- Now provides a boolean struct member for checking if a unit is registered to the system or not
- Some naming changes
- Optimized some parts of the script
- Some fixes and other changes
 
I can definitly see fine improvements overall, good job.

JASS:
    method operator == takes thistype other returns boolean
        debug if not .flag or not other.flag then
            debug call debug("|CFFFF0000Operator == error: Attempt to use an unregistered instance|R")
            debug return false
        debug endif
        return R2I(.speed + 0.5) == R2I(other.speed + 0.5)
    endmethod
^Why the real to integer typecast?

In
static method operator enabled= takes boolean flag returns nothing
you can just set isEnabled = flag

Furthermore private static method iterate takes nothing returns nothing is not above static method operator enabled= (and also the []= operator).
Jass nativly requires functions to be above to be able to access them. struct syntax (with methods) allows you to avoid this limitation and doesn't throw an error, but behind the scenes it will generate extra code , like prototype functions to make it work. So it is better if we even follow the jass rules in structs and avoid extra code when possible.

JASS:
call TriggerAddCondition(trig1, Filter(function ShiftEffect1))
call TriggerAddCondition(trig2, Filter(function ShiftEffect2))
^These functions in the demo don't return a boolean and so JassHelper throws an error.

JASS:
    debug if uDex.moving and IsUnitSelected(tempUnit, GetLocalPlayer()) then
        debug call debug(name + " is moving at velocity " + I2S(R2I(uDex.speed)) + " towards " + R2S(uDex.direction) + " degrees")
    debug endif
^Something like this is not a real debug purpose. That is printing regular information also in case everything is working fine.
Debugging messages by nature do only print if something is wrong or does not function as it should.
Please only keep required debug messages.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
^Why the real to integer typecast?
I want it to compare the speeds that are rounded off to the nearest whole number, otherwise they'll have to use Sensor[unit].speed == Sensor[otherUnit].speed which I think is just a very little bit longer than neglecting the words speed, same for the < and > operators.

In
static method operator enabled= takes boolean flag returns nothing
you can just
set isEnabled = flag
I need to use the enabled= operator because aside from just setting the boolean isEnabled to some value, the timer also needs to be stopped/resumed.

Jass nativly requires functions to be above to be able to access them. struct syntax (with methods) allows you to avoid this limitation and doesn't throw an error, but behind the scenes it will generate extra code , like prototype functions to make it work. So it is better if we even follow the jass rules in structs and avoid extra code when possible.
^These functions in the demo don't return a boolean and so JassHelper throws an error.
Will change

^Something like this is not a real debug purpose. That is printing regular information also in case everything is working fine.
Debugging messages by nature do only print if something is wrong or does not function as it should.
Please only keep required debug messages.
Yes, but maybe these should remain
JASS:
        static method operator enabled= takes boolean flag returns nothing
            if flag then
                debug call debug("Motion sensor is turned ON")
                //...
            else
                debug call debug("Motion sensor is turned OFF")
                //...
            endif
        endmethod
to keep users aware, because some might wonder why something does not work when the only problem is that they turned the system off and forgot to turn it on =)
 
I need to use the enabled= operator because aside from just setting the boolean
isEnabled
to some value, the timer also needs to be stopped/resumed.
I meant inside the function you can remove one line. You set to constants true/false, and destinguish with help of an if-statement. But you could just write 1line, with setting it to the given parameter "flag".

Yes, but maybe these should remain
Sure, if you wish.

I'm not sure I understand your stance to the == operator.

JASS:
    method operator == takes thistype other returns boolean
        debug if not .flag or not other.flag then
            debug call debug("|CFFFF0000Operator == error: Attempt to use an unregistered instance|R")
            debug return false
        debug endif
        return not (this.speed != other.speed)
    endmethod

// later

    if this == other then
        // same speed
    endif

Why do you need integers?
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I meant inside the function you can remove one line. You set to constants true/false, and destinguish with help of an if-statement. But you could just write 1line, with setting it to the given parameter "flag".
I understand now =D

I'm not sure I understand your stance to the == operator.
I mean the == operator is used to compare if speeds are 'almost equal' such as 304.85 & 305.45. But I guess I should just remove the < operator since it doesn't make much sense now that I think of it using my reason here.
On the other hand, I can't think of any use for this == so maybe I will just remove it.
 
Just one minor thing came in mind:

JASS:
globals
    constant real MOVING          = 1.00
    constant real STATIONARY      = 2.00
endglobals
Even it's not very likely the user will also have such global variables, but if we choose 1-word names for global variables it might better to give them an identifier prefix, or just making them static struct variables.

Example:

JASS:
struct Sensor
    static constant real MOVING          = 1.00
endstruct

...

if Sensor.MOVING ...
So it's clearly coming from the system. But anyways we can go on already I guess, everything looks good.


Submission:
MotionSensor v1.2b

Date:
19 Oktober 16

Status:
Approved
Note:

Nicely coded, readable, and efficient.
A good system to track units' movement behaviour with also enough management power for the user. Approved.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
UPDATED to v1.3b

- Added a new public module for running motion events within a struct
- Added two new groups SENSOR_GROUP_MOVING and SENSOR_GROUP_STATIONARY to allow users to easily loop through desired units
- The motion change event now uses TriggerEvaluate() instead of TRVE for a faster execution
- Changed prevX and prevY's privacy from being private to readonly
- Added function wrappers for getting the prevX and prevY of a unit
- Added a debug message in the operator []= to warm users when they try to double-free or double-register a unit
- When enabling the Sensor, it checks first if the sensorGroup is empty before running the periodic actions
- Updated the Rupture Spell code and the demo map
- Updated the documentation
- Other changes
 
Top