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

[Snippet] BindUnitMovement

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
BindUnitMovement v1.1


Importing
- Make an new trigger in the trigger editor of your map
- Go to the Edit tab and click "Convert to custom script"
- Delete the text in that trigger and paste code found below
- You're done


Script
JASS:
library BindUnitMovement /* v1.1


    */requires /*

    */UnitDex             /*  http://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209
    */optional GroupUtils /*  http://www.wc3c.net/showthread.php?t=104464

    A simple library which provides functions for binding a unit to a specific
    location for a certain duration. Binded units can still attack, perform
    orders, cast spells, and display their walk animation but they are not able
    to move outside their bind location.

*///! novjass

    [Author] : AGD

        |=====|
        | API |
        |=====|

            function BindUnitMovement takes unit u, real duration returns nothing/*
                - Prevents a unit from moving out of its current location
                - Bind duration does not stack but overrides the previous one

          */function UnbindUnitMovement takes unit u returns nothing/*
                - Unbinds a unit, allowing it to move outside its current location

          */function IsUnitMovementBinded takes unit u returns boolean/*
                - Checks is the unit's movement is binded

          */function SetBindedUnitX takes unit u, real x returns nothing/*
          */function SetBindedUnitY takes unit u, real y returns nothing/*
                - Sets new coordinates for a unit's bind location
                - Can only be applied to currently binded units

          */function GetBindedUnitX takes unit u returns real/*
          */function GetBindedUnitY takes unit u returns real/*
                - Gets the coordinates of the unit's bind location

*///! endnovjass

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

    private module Init
        private static method onInit takes nothing returns nothing
            local code c = function thistype.onLeave
            call OnUnitDeindex(c)
        endmethod
    endmodule

    private struct Binder extends array

        real uX
        real uY
        real duration
        static unit tempUnit
        static timer timer = CreateTimer()
        static group forSwap
        static group bindedGroup = CreateGroup()

        static if not LIBRARY_GroupUtils then
            static group ENUM_GROUP = CreateGroup()
        endif

        static method periodic takes nothing returns nothing
            local thistype this
            loop
                set tempUnit = FirstOfGroup(bindedGroup)
                exitwhen tempUnit == null
                call GroupRemoveUnit(bindedGroup, tempUnit)
                set this = GetUnitId(tempUnit)
                if .duration > 0 then
                    set .duration = .duration - 0.031250000
                    call SetUnitX(tempUnit, .uX)
                    call SetUnitY(tempUnit, .uY)
                    call GroupAddUnit(ENUM_GROUP, tempUnit)
                endif
            endloop
            set forSwap = bindedGroup
            set bindedGroup = ENUM_GROUP
            set ENUM_GROUP = forSwap
            if FirstOfGroup(bindedGroup) == null then
                call PauseTimer(timer)
            endif
        endmethod

        static method onLeave takes nothing returns nothing
            call GroupRemoveUnit(bindedGroup, GetIndexedUnit())
        endmethod

        implement Init

    endstruct

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

    function BindUnitMovement takes unit u, real dur returns nothing
        local Binder this = GetUnitId(u)
        set this.duration = dur
        if not IsUnitInGroup(u, Binder.bindedGroup) then
            set this.uX = GetUnitX(u)
            set this.uY = GetUnitY(u)
            if FirstOfGroup(Binder.bindedGroup) == null then
                call TimerStart(Binder.timer, 0.031250000, true, function Binder.periodic)
            endif
            call GroupAddUnit(Binder.bindedGroup, u)
        endif
    endfunction

    function UnbindUnitMovement takes unit u returns nothing
        call GroupRemoveUnit(Binder.bindedGroup, u)
    endfunction

    function IsUnitMovementBinded takes unit u returns boolean
        return IsUnitInGroup(u, Binder.bindedGroup)
    endfunction

    function SetBindedUnitX takes unit u, real x returns nothing
        set Binder(GetUnitId(u)).uX = x
        call SetUnitX(u, x)
    endfunction

    function SetBindedUnitY takes unit u, real y returns nothing
        set Binder(GetUnitId(u)).uY = y
        call SetUnitX(u, y)
    endfunction

    function GetBindedUnitX takes unit u returns real
        if IsUnitMovementBinded(u) then
            return Binder(GetUnitId(u)).uX
        endif
        return 0.00
    endfunction

    function GetBindedUnitY takes unit u returns real
        if IsUnitMovementBinded(u) then
            return Binder(GetUnitId(u)).uY
        endif
        return 0.00
    endfunction


endlibrary



v1.1
- Removed function SetBindedUnitPosition because one could easily use SetBindedUnitX() and SetBindedUnitY()
- Removed the optional requirement TimerUtils
- Added the API functions IsUnitMovementBounded(), GetBindedUnitX(), GetBindedUnitY()
- Some code restructuring

v1.0
- First Release
 
Last edited:
Wouldn't it be better to work with UnitPropWindow, to simulate "ensnare"? It would also not break with forced moving actions by user that may occur, or teleport abilites. Example:

JASS:
struct Demo
  
    private static boolean enabled = false
    private static unit u
  
    private static method foo takes nothing returns nothing
        if enabled then
            call SetUnitPropWindow(u, 0)
        else
            call SetUnitPropWindow(u, GetUnitDefaultPropWindow(u))
        endif
        set enabled = not enabled
    endmethod
  
    private static method onInit takes nothing returns nothing
      
        local trigger t = CreateTrigger(  )
        set u = CreateUnit(Player(0), 'Hpal', 0, 0, 0)
        call TriggerRegisterPlayerEventEndCinematic( t, Player(0) )
        call TriggerAddAction( t, function thistype.foo )
    endmethod
endstruct
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
The only difference is that SetUnitPropWindow() disables actual movement while this does not. This allows unit to play its walk animation while at the same time staying on its place. This can be important when you want to get the distance transversed by a unit while still not moving from its place. In fact this can be useful for making a modified version of DotA's rupture (with the difference that the unit can't move outside its place but still is able to take damage when trying to move) and similar spells.

It would also not break with forced moving actions by user that may occur, or teleport abilites
For this they can use
JASS:
             function SetBindedUnitPosition takes unit u, real x, real y returns nothing/*
                - Sets a new bind location for a unit

          */function SetBindedUnitX takes unit u, real x returns nothing/*
          */function SetBindedUnitY takes unit u, real y returns nothing/*
                - Sets new coordinates for a unit's bind location
as alternatives to SetUnitX(), SetUnitY(), and SetUnitPosition()
 
Level 13
Joined
Nov 7, 2014
Messages
571
I think setting the unit's movement speed to 0 (requres modifing the Gameplay constants/ Minimum move speed = 0) is better than calling SetUniX/Y on a unit that is ordered to move to some location because it results in a "jumpy" movement which is not nice in my opinion.

I think you got a bug as well, i.e you are not removing the unit from the bindedGroup/tempGroup after the duration is over which means that subsequent calls to BindUnitMovement will pin the unit to where it was when the first call to the function was made.

I don't know why you require a timer library just to make a single timer and destroy/recycle it, when you can do it with a single timer without destroying it.

Edit: I don't know what unit's prop window does =)
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I think setting the unit's movement speed to 0 (requres modifing the Gameplay constants/ Minimum move speed = 0) is better than calling SetUniX/Y on a unit that is ordered to move to some location because it results in a "jumpy" movement which is not nice in my opinion.
This allows unit to play its walk animation while at the same time staying on its place. This can be important when you want to get the distance transversed by a unit while still not moving from its place. In fact this can be useful for making a modified version of DotA's rupture (with the difference that the unit can't move outside its place but still is able to take damage when trying to move) and similar spells.
Although theres a catch as what you said


I think you got a bug as well, i.e you are not removing the unit from the bindedGroup/tempGroup after the duration is over which means that subsequent calls to BindUnitMovement will pin the unit to where it was when the first call to the function was made.
Yes, I forgot to do it.

I don't know why you require a timer library just to make a single timer and destroy/recycle it, when you can do it with a single timer without destroying it.
In case the user has TimerUtils in his/her map, its better to take advantage of it. Its optional anyway ;)
 
Okay, if you need such a strict binding, maybe someone else does, too.

There can be a function to check if a unit is currently binded. (may be used when teleporting or so, and user needs to re-assign x/y binds)
Additionaly maybe also just getters for x/y ? I'm not sure if you need it.

There can be something like a RemoveUnit when GetUnitTypeId == 0 (is removed), or as UnitDex is imported, maybe just RemoveOnDeindex.

JASS:
function SetBindedUnitPosition takes unit u, real x, real y returns nothing
        local integer i = GetUnitId(u)
        set uX[i] = x
        set uY[i] = y
        call SetUnitPosition(u, x, y)
    endfunction
Iirc SetUnitPosition checks for pathing, so also here should be used SetUnitX / Y.

===

I personaly would prefer probabyl struct syntax, bur staying with current API is also totaly fine. Just to show:

JASS:
struct BindUnitMovement
    real x
    real y
    real time
    real activated

    public static operator[] takes unit u returns thistype
        return GetUnitId(u)
    endmethod
  
    public operator duration takes nothing returns real
        return .time
    endmethod
  
    public operator duration= takes real r returns nothing
        set .time = r
    endmethod
  
    public operator enabled takes nothing returns boolean
        return .activated
    endmethod
  
    public operator enabled= takes boolean flag returns nothing
        if (flag) then
            // Add
        else
            // Remove
        endif
        set .activated = flag
    endmethod

endstruct

JASS:
    local BindUnitMovement instance = BindUnitMovement[GetTriggerUnit()]
    set instance.enabled = true
    set instance.x = 3
    set instance.y = 4
    set instance.duration = 5

^But something like this is totaly up to you. Else it's pretty straight forward and fine.
 
I believe not. Removed units will be "null" if you try to enumerate through them via FoG methods, so it would break the loop, but it won't be removed from group.
That's why it's a bit tricky with FoG sometimes and we need to care to remove them ourselves if we keep global static groups / or the user.
Internaly the game would need to check all groups where the unit could belong to, and call the remove fuction automaticaly-- but it does not iirc.
 

AGD

AGD

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

- Removed function SetBindedUnitPosition because one could easily use SetBindedUnitX() and SetBindedUnitY()
- Removed the optional requirement TimerUtils
- Added the API functions IsUnitMovementBounded(), GetBindedUnitX(), GetBindedUnitY()
- Some code restructuring

Still retained the current function API because its neater this way imo =)
 
You forgot the onDeindex to be private.
Optionaly GetBindedUnitX/Y could write a debug BJDebug message when they return "0.00" not by the system but as safety.


Submission:
[Snippet] BindUnitMovement v1.1

Date:
4 November

Status:
Approved
Note:

A bit a more specific library to strictly bind a unit to a certain location via trigger movement.
I can't myself imagine much usecases for it, but there's AGD's example with moving/damaging while "ruptured" (see thread).
Code and API looks good. When it could be useful for some, there we go. Approved.
 
Top