• 🏆 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] Do you have ideas to improve this system?

Status
Not open for further replies.
Level 24
Joined
Jun 26, 2020
Messages
1,852
I made this system to emulate the intangibility of the Wind Walk ability to don't have the risk of end the spell when you cast an spell (since the "solution" of using Ghost (Visible) never work to me), but I'm new in this of using libraries and structs, so I'm here to ask you if there is a thing that I can add to this.
You need TimerUtils:
JASS:
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//*  To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//*  To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass)   More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//*  * Attaching
//*  * Recycling (with double-free protection)
//*
//* set t=NewTimer()      : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x)   : Get a timer (alternative to CreateTimer), call
//*                            Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t)       : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2)     : Attach value 2 to timer
//* GetTimerData(t)       : Get the timer's value.
//*                         You can assume a timer's value is 0
//*                         after NewTimer.
//*
//* Multi-flavor:
//*    Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************

//================================================================
    globals
        //How to tweak timer utils:
        // USE_HASH_TABLE = true  (new blue)
        //  * SAFEST
        //  * SLOWEST (though hash tables are kind of fast)
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true  (orange)
        //  * kinda safe (except there is a limit in the number of timers)
        //  * ALMOST FAST
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
        //  * THE FASTEST (though is only  faster than the previous method
        //                  after using the optimizer on the map)
        //  * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
        //                     work)
        //
        private constant boolean USE_HASH_TABLE      = true
        private constant boolean USE_FLEXIBLE_OFFSET = false

        private constant integer OFFSET     = 0x100000
        private          integer VOFFSET    = OFFSET
            
        //Timers to preload at map init:
        private constant integer QUANTITY   = 256
      
        //Changing this  to something big will allow you to keep recycling
        // timers even when there are already AN INCREDIBLE AMOUNT of timers in
        // the stack. But it will make things far slower so that's probably a bad idea...
        private constant integer ARRAY_SIZE = 8190

    endglobals

    //==================================================================================================
    globals
        private integer array data[ARRAY_SIZE]
        private hashtable     ht
    endglobals
  
  

    //It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
    function SetTimerData takes timer t, integer value returns nothing
        static if(USE_HASH_TABLE) then
            // new blue
            call SaveInteger(ht,0,GetHandleId(t), value)
          
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-VOFFSET]=value
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-OFFSET]=value
        endif    
    endfunction

    function GetTimerData takes timer t returns integer
        static if(USE_HASH_TABLE) then
            // new blue
            return LoadInteger(ht,0,GetHandleId(t) )
          
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-VOFFSET]
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-OFFSET]
        endif    
    endfunction

    //==========================================================================================
    globals
        private timer array tT[ARRAY_SIZE]
        private integer tN = 0
        private constant integer HELD=0x28829022
        //use a totally random number here, the more improbable someone uses it, the better.
      
        private boolean       didinit = false
    endglobals
    private keyword init

    //==========================================================================================
    // I needed to decide between duplicating code ignoring the "Once and only once" rule
    // and using the ugly textmacros. I guess textmacros won.
    //
    //! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
    // On second thought, no.
    //! endtextmacro

    function NewTimerEx takes integer value returns timer
        if (tN==0) then
            if (not didinit) then
                //This extra if shouldn't represent a major performance drawback
                //because QUANTITY rule is not supposed to be broken every day.
                call init.evaluate()
                set tN = tN - 1
            else
                //If this happens then the QUANTITY rule has already been broken, try to fix the
                // issue, else fail.
                debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
                set tT[0]=CreateTimer()
                static if( not USE_HASH_TABLE) then
                    debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
                    static if( USE_FLEXIBLE_OFFSET) then
                        if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    else
                        if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    endif
                endif
            endif
        else
            set tN=tN-1
        endif
        call SetTimerData(tT[tN],value)
     return tT[tN]
    endfunction
  
    function NewTimer takes nothing returns timer
        return NewTimerEx(0)
    endfunction


    //==========================================================================================
    function ReleaseTimer takes timer t returns nothing
        if(t==null) then
            debug call BJDebugMsg("Warning: attempt to release a null timer")
            return
        endif
        if (tN==ARRAY_SIZE) then
            debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")

            //stack is full, the map already has much more troubles than the chance of bug
            call DestroyTimer(t)
        else
            call PauseTimer(t)
            if(GetTimerData(t)==HELD) then
                debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
                return
            endif
            call SetTimerData(t,HELD)
            set tT[tN]=t
            set tN=tN+1
        endif
    endfunction

    private function init takes nothing returns nothing
     local integer i=0
     local integer o=-1
     local boolean oops = false
        if ( didinit ) then
            return
        else
            set didinit = true
        endif
    
        static if( USE_HASH_TABLE ) then
            set ht = InitHashtable()
            loop
                exitwhen(i==QUANTITY)
                set tT[i]=CreateTimer()
                call SetTimerData(tT[i], HELD)
                set i=i+1
            endloop
            set tN = QUANTITY
        else
            loop
                set i=0
                loop
                    exitwhen (i==QUANTITY)
                    set tT[i] = CreateTimer()
                    if(i==0) then
                        set VOFFSET = GetHandleId(tT[i])
                        static if(USE_FLEXIBLE_OFFSET) then
                            set o=VOFFSET
                        else
                            set o=OFFSET
                        endif
                    endif
                    if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
                        exitwhen true
                    endif
                    if (GetHandleId(tT[i])-o>=0)  then
                        set i=i+1
                    endif
                endloop
                set tN = i
                exitwhen(tN == QUANTITY)
                set oops = true
                exitwhen not USE_FLEXIBLE_OFFSET
                debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")            
            endloop
          
            if(oops) then
                static if ( USE_FLEXIBLE_OFFSET) then
                    debug call BJDebugMsg("The problem has been fixed.")
                    //If this message doesn't appear then there is so much
                    //handle id fragmentation that it was impossible to preload
                    //so many timers and the thread crashed! Therefore this
                    //debug message is useful.
                elseif(DEBUG_MODE) then
                    call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
                    call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
                    call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
                    call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
                endif
            endif
        endif

    endfunction

endlibrary
My system
JASS:
//This system is to emulate the intangibility of the wind walk
//To use it use "call WindWalkData(<caster>,<ID of the buff>)"
//To detect if the unit is affected by this just use "IsUnitInGroup(<Your unit>,udg_PWW_group)"
//If you use GUI create a group variable called "PWW_group", if you use Jass, uncomment the global section

library WindWalk uses TimerUtils, start //This start is something of my map, erase it

//Uncomment this if you use Jass
/*globals
    group udg_PWW_group=CreateGroup()
endglobals*/

struct WindWalk
    static constant real RANGE=112.00   //This is the max. range of the units has to do with the caster
    static constant real INTERVAL=0.01  //This the interval of the timer to check the nearby units
    unit caster
    integer mybuff
    group g
    timer t
    static method create takes unit caster, integer mybuff returns thistype
        local thistype this=thistype.allocate()
        set this.caster=caster
        set this.mybuff=mybuff
        set this.g=CreateGroup()
        set this.t=CreateTimer()
        call SetTimerData(this.t,this)
        call TimerStart(this.t,thistype.INTERVAL,true,function thistype.callPeriodic)
        return this
    endmethod

    method periodic takes nothing returns nothing
        local real x=GetUnitX(this.caster)
        local real y=GetUnitY(this.caster)
        local unit u
        local boolean b=false
        call GroupEnumUnitsInRange(this.g,x,y,thistype.RANGE,null)
        loop
            set u=FirstOfGroup(this.g)
            exitwhen u==null
            if IsUnitType(u,UNIT_TYPE_STRUCTURE)==false and IsUnitType(u, UNIT_TYPE_FLYING)==false and u!=this.caster then
                set b=true
                call GroupClear(this.g)
            endif
            call GroupRemoveUnit(this.g,u)
        endloop
        if b then
            call SetUnitPathing(this.caster,false)
        else
        //I create this function for my map to enable the colision of the unit under
        //specific conditions, change this to "SetUnitPathing(this.caster,true)"
            call OnColision(this.caster)
        endif
    endmethod

    static method callPeriodic takes nothing returns nothing
        local WindWalk this=GetTimerData(GetExpiredTimer())
        if UnitHasBuffBJ(this.caster,this.mybuff) then
        //If there is more than one spell on the same caster that uses this system
            if IsUnitInGroup(this.caster,udg_PWW_group)==false then
                call GroupAddUnit(udg_PWW_group,this.caster)
            endif
            call this.periodic()
        else
            call this.destroy()
        endif
    endmethod

    method destroy takes nothing returns nothing
        call DestroyTimer(this.t)
        call GroupRemoveUnit(udg_PWW_group,this.caster)
        //Same case here
        call OnColision(this.caster)
        set this.t=null
        set this.caster=null
        call this.deallocate()
    endmethod
endstruct


function WindWalkData takes unit caster, integer mybuff returns nothing
    call GroupAddUnit(udg_PWW_group,caster)
    call WindWalk.create(caster,mybuff)
endfunction
endlibrary
 
  • As TimerUtils is used, its handle recycle functionality could be used:
    set this.t = NewTimer(this) instead of creating
    and
    call ReleaseTimer(this.t) instead of destroying

  • A unit won't be added twice to a group. The check is irrelevant:
    if IsUnitInGroup(this.caster,udg_PWW_group)==false then call GroupAddUnit(udg_PWW_group,this.caster) endif

  • When a new Windwalk instance is created, it should directly apply, so here the non-pathing, instead of waiting for the periodic check. The time difference might be very low, but I guess it makes no sense not to do it.

  • JASS:
    if IsUnitType(u,UNIT_TYPE_STRUCTURE)==false and IsUnitType(u, UNIT_TYPE_FLYING)==false and u!=this.caster then
        set b=true
        call GroupClear(this.g)
    endif
    could be:
    JASS:
    b = not (IsUnitType(u,UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_FLYING) or u==this.caster)
    exitwhen b
    ^the exitwhen breaks out of the loop when the expression (b) evaluates to true.

  • One static group could be used instead of creating and destroying one group per instance.
    If one group per instance is kept, it could be nulled at the destructor.

  • What can be private should be private.

  • PWW_group,caster doesn't seem important for the system, so function WindWalkData probably should be not in the system itself.
    You could provide an API function that returns a boolean, and keep the group internal, if its only purpose is to check if a unit is currenly under systems's effect.

  • To run user-code there should not be arbitary function calls in the core code, only if it's needed in one specific map.
    But there could be a possibility for the user to register code at certain events, like at the onCollision example. With registering code, I mean something similar like I used here HeroReviveCancelEvent v1.1.
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
@IcemanBo thank you for answer
As TimerUtils is used, its handle recycle functionality could be used:
set this.t =
NewTimer(this)
instead of creating
and
call ReleaseTimer(this.t) instead of destroying
I forget that I read this in the description of TimerUtils, Is this the unique thing that I should change?
A unit won't be added twice to a group. The check is irrelevant:
if IsUnitInGroup(this.caster,udg_PWW_group)==false then call GroupAddUnit(udg_PWW_group,this.caster) endif
There it says that //If there is more than one spell on the same caster that uses this system because if the buff of the first spell ends so the unit is not longer in the group although the unit still using the system because the 2nd spell.
When a new Windwalk instance is created, it should directly apply, so here the non-pathing, instead of waiting for the periodic check. The time difference might be very low, but I guess it makes no sense not to do it.
It can be.
JASS:
if IsUnitType(u,UNIT_TYPE_STRUCTURE)==false and IsUnitType(u, UNIT_TYPE_FLYING)==false and u!=this.caster then
    set b=true
    call GroupClear(this.g)
endif
could be:
JASS:
b = not (IsUnitType(u,UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_FLYING) or u==this.caster)
exitwhen b
^the exitwhen breaks out of the loop when the expression (b) evaluates to true.
I though do something like that, but I did like that form to not repeat the actions of null the variable u and clear the group g in case the loop ends without the boolean b being true.
One static group could be used instead of creating and destroying one group per instance.
If one group per instance is kept, it could be nulled at the destructor.
True, and I forgot null the group.
What can be private should be private.
That is?, I said I'm new in this of structs and libraries.
PWW_group,caster doesn't seem important for the system, so function WindWalkData probably should be not in the system itself.
You could provide an API function that returns a boolean, and keep the group internal, if its only purpose is to check if a unit is currenly under systems's effect.
Explain?
To run user-code there should not be arbitary function calls in the core code, only if it's needed in one specific map.
But there could be a possibility for the user to register code at certain events, like at the onCollision example. With registering code, I mean something similar like I used here HeroReviveCancelEvent v1.1.
Slow down please.
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,852
The line to add the unit to the group can always run, without the if-clause, as a group will or contain the unit once or none at all. But not twice anyways.
Ok, but what cost more, checking if the unit is in the group or try to adding it? At least the function already checks that.

One more thing, I realised that using the condition "Unit has a buff" is not a good idea, because what happen in the last moment the caster lost the buff but before the system could check it the caster cast the spell again obtaining the buff again before check it, so it would like never lost the buff and now have the system running 2 times on the same unit, in this system that won't be cause a difference but even so.
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,852
probably about the same, it does not matter. People tend to save performance in negligible spots
Well.

Do you know a solution for this?
One more thing, I realised that using the condition "Unit has a buff" is not a good idea, because what happen in the last moment the caster lost the buff but before the system could check it the caster cast the spell again obtaining the buff again before check it, so it would like never lost the buff and now have the system running 2 times on the same unit, in this system that won't be cause a difference but even so.
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
make use of a UnitIndexer, so you can check if there is already a running instance for the unit. If so, destroy the running instance
Something like this?
JASS:
globals
    private WindWalk array instance
endglobals

function WindWalkData takes unit caster, integer mybuff returns nothing
    local integer i=GetUnitUserData(caster)
    if instance[i]!=null and instance[i].mybuff==mybuff then
        call instance[i].destroy()
    endif
    call GroupAddUnit(udg_PWW_group,caster)
    set instance[i]=WindWalk.create(caster,mybuff)
endfunction
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
At the end I used dynamic indexing and a hashtable (Because I need 2 indexes):
JASS:
//For this system you need TimerUtils and a Unit Indexer
//This system is to emulate the intangibility of the wind walk
//To use it use "call WindWalkData(<caster>,<ID of the buff>)"
//To detect if the unit is affected by this just use "IsUnitInGroup(<Your unit>,udg_PWW_group)"
//If you use GUI create a group variable called "PWW_group", if you use Jass, uncomment the global section

library WindWalk uses TimerUtils, start //This start is something of my map, erase it

globals
    private hashtable Instance=InitHashtable()
    private integer array InstanceMax
    /*Uncomment this if you use Jass
    group udg_PWW_group=CreateGroup()*/
endglobals

private struct WindWalk

    static constant real RANGE=112.00   //This is the max. range of the units has to do with the caster
    static constant real INTERVAL=0.01  //This the interval of the timer to check the nearby units
    static group g=CreateGroup()
    unit caster
    integer mybuff
    timer t
    integer instance_actual

    static method create takes unit caster, integer mybuff returns thistype
        local thistype this=thistype.allocate()
        local integer i=GetUnitUserData(caster)
        set InstanceMax[i]=InstanceMax[i]+1
        set this.instance_actual=InstanceMax[i]
        set this.caster=caster
        set this.mybuff=mybuff
        set this.t=NewTimerEx(this)
        call TimerStart(this.t,thistype.INTERVAL,true,function thistype.callPeriodic)
        return this
    endmethod

    method periodic takes nothing returns nothing
        local real x=GetUnitX(this.caster)
        local real y=GetUnitY(this.caster)
        local unit u
        local boolean b=false
        if not(IsTerrainPathable(x+16*CosBJ(GetUnitFacing(this.caster)),y+16*SinBJ(GetUnitFacing(this.caster)),PATHING_TYPE_WALKABILITY)) then
            call GroupEnumUnitsInRange(thistype.g,x,y,thistype.RANGE,null)
            loop
                set u=FirstOfGroup(thistype.g)
                exitwhen u==null
                if IsUnitType(u,UNIT_TYPE_STRUCTURE)==false and IsUnitType(u, UNIT_TYPE_FLYING)==false and u!=this.caster then
                    set b=true
                    call GroupClear(thistype.g)
                endif
                call GroupRemoveUnit(thistype.g,u)
            endloop
        endif
        if b then
            call SetUnitPathing(this.caster,false)
        else
        //I create this function for my map to enable the colision of the unit under
        //specific conditions, change this to "SetUnitPathing(this.caster,true)"
            call OnColision(this.caster)
        endif
    endmethod

    static method callPeriodic takes nothing returns nothing
        local WindWalk this=GetTimerData(GetExpiredTimer())
        if UnitHasBuffBJ(this.caster,this.mybuff) then
            call GroupAddUnit(udg_PWW_group,this.caster)
            call this.periodic()
        else
            call this.destroy()
        endif
    endmethod

    method destroy takes nothing returns nothing
        local integer i=GetUnitUserData(this.caster)
        call ReleaseTimer(this.t)
        call GroupRemoveUnit(udg_PWW_group,this.caster)
        //Same case here
        call OnColision(this.caster)
        call SaveInteger(Instance,i,this.instance_actual,LoadInteger(Instance,i,InstanceMax[i]))
        call RemoveSavedInteger(Instance,i,InstanceMax[i])
        set InstanceMax[i]=InstanceMax[i]-1
        set this.t=null
        set this.caster=null
        call this.deallocate()
    endmethod
endstruct

function WindWalkData takes unit caster, integer mybuff returns nothing
    local integer i=GetUnitUserData(caster)
    local integer j=1
    local boolean b=false
    local WindWalk t
    //--If the caster is affected by the same buff, this is to "restart" the system for that buff--
    loop
        exitwhen j>InstanceMax[i] or b
        set t=LoadInteger(Instance,i,j)
        if HaveSavedInteger(Instance,i,j) and t.mybuff==mybuff then
            call t.destroy()
            set b=true
        endif
        set j=j+1
    endloop
    //-- --
    call GroupAddUnit(udg_PWW_group,caster)
    call SaveInteger(Instance,i,InstanceMax[i],WindWalk.create(caster,mybuff))
endfunction
endlibrary
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,852
I realized that with the condition
JASS:
not(IsTerrainPathable(x+16*CosBJ(GetUnitFacing(this.caster)),y+16*SinBJ(GetUnitFacing(this.caster)),PATHING_TYPE_WALKABILITY))
There is a moment when the unit is not intangible, so what do you think I can do to solve this?
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
I edit the system again to solve the previous problem.
JASS:
//For this system you need TimerUtils, Unit Indexer and a Unit Move Detector
//This system is to emulate the intangibility of the wind walk
//To use it use "call WindWalkData(<caster>,<ID of the buff>)"
//To detect if the unit is affected by this just use "IsUnitInGroup(<Your unit>,udg_PWW_group)"
//If you use GUI create a group variable called "PWW_group", if you use Jass, uncomment the global section

library WindWalk uses TimerUtils, start //This start is something of my map, erase it

globals
    private hashtable Instance=InitHashtable()
    private integer array InstanceMax
    /*Uncomment this if you use Jass
    group udg_PWW_group=CreateGroup()*/
endglobals

private struct WindWalk

    static constant real RANGE=112.00   //This is the max. range of the units has to do with the caster
    static constant real INTERVAL=0.01  //This the interval of the timer to check the nearby units
    static group g=CreateGroup()
    unit caster
    integer mybuff
    timer t
    integer instance_actual

    static method create takes unit caster, integer mybuff returns thistype
        local thistype this=thistype.allocate()
        local integer i=GetUnitUserData(caster)
        set InstanceMax[i]=InstanceMax[i]+1
        set this.instance_actual=InstanceMax[i]
        set this.caster=caster
        set this.mybuff=mybuff
        set this.t=NewTimerEx(this)
        call TimerStart(this.t,thistype.INTERVAL,true,function thistype.callPeriodic)
        return this
    endmethod
   
    static method UnitCondition takes unit u, unit other returns boolean
        if IsUnitType(u,UNIT_TYPE_STRUCTURE) then
            return false 
        elseif IsUnitType(u, UNIT_TYPE_FLYING) then
            return false
        elseif u==other then
            return false
        endif
        return true
    endmethod
   
    static method PointCondition takes real x, real y, real angle returns boolean
        return IsTerrainPathable(x+16*Cos(angle),y+16*Sin(angle),PATHING_TYPE_WALKABILITY)
    endmethod

    method periodic takes nothing returns nothing
        local real x=GetUnitX(this.caster)
        local real y=GetUnitY(this.caster)
        local unit u
        local boolean b=false
        local real f=bj_DEGTORAD*GetUnitFacing(this.caster)
        call GroupEnumUnitsInRange(thistype.g,x,y,thistype.RANGE,null)
        loop
            set u=FirstOfGroup(thistype.g)
            exitwhen u==null
            if thistype.UnitCondition(u,this.caster) then
                set b=true
                call GroupClear(thistype.g)
            endif
            call GroupRemoveUnit(thistype.g,u)
        endloop
        if thistype.PointCondition(x,y,f) and udg_UnitMoving[GetUnitUserData(this.caster)] then
            call SetUnitPosition(this.caster,x-32*Cos(f),y-32*Sin(f))
        endif
        if b then
            call SetUnitPathing(this.caster,false)
        else
            //I create this function for my map to enable the colision of the unit under
            //specific conditions, change this to "SetUnitPathing(this.caster,true)" or your own "OnColision"
            call OnColision(this.caster)
        endif
    endmethod

    static method callPeriodic takes nothing returns nothing
        local WindWalk this=GetTimerData(GetExpiredTimer())
        if UnitHasBuffBJ(this.caster,this.mybuff) then
            call GroupAddUnit(udg_PWW_group,this.caster)
            call this.periodic()
        else
            call this.destroy()
        endif
    endmethod

    method destroy takes nothing returns nothing
        local integer i=GetUnitUserData(this.caster)
        call ReleaseTimer(this.t)
        call GroupRemoveUnit(udg_PWW_group,this.caster)
        //Same case here
        call OnColision(this.caster)
        call SaveInteger(Instance,i,this.instance_actual,LoadInteger(Instance,i,InstanceMax[i]))
        call RemoveSavedInteger(Instance,i,InstanceMax[i])
        set InstanceMax[i]=InstanceMax[i]-1
        set this.t=null
        set this.caster=null
        call this.deallocate()
    endmethod
endstruct

function WindWalkData takes unit caster, integer mybuff returns nothing
    local integer i=GetUnitUserData(caster)
    local integer j=1
    local boolean b=false
    local WindWalk t
    //--If the caster is affected by the same buff, this is to "restart" the system for that buff--
    loop
        exitwhen j>InstanceMax[i] or b
        set t=LoadInteger(Instance,i,j)
        if HaveSavedInteger(Instance,i,j) and t.mybuff==mybuff then
            call t.destroy()
            set b=true
        endif
        set j=j+1
    endloop
    //-- --
    call GroupAddUnit(udg_PWW_group,caster)
    call SaveInteger(Instance,i,InstanceMax[i],WindWalk.create(caster,mybuff))
endfunction
endlibrary
But now with the part of
JASS:
call SetUnitPosition(this.caster,x-32*Cos(f),y-32*Sin(f))
I have the risk of interump the orders of the unit, how can I solve this?
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
I think SetUnitX / SetUnitY doesnt interrupt orders as far as i can remember
Actually yes, but it doesn't solve the problem as it causes the unit to get stuck in a loop, so I opted to other solution that actually works better, but is not perfect because the unit can get caught but with low frecuency.
JASS:
//For this system you need TimerUtils, Unit Indexer and a Unit Move Detector
//This system is to emulate the intangibility of the wind walk
//To use it use "call WindWalkData(<caster>,<ID of the buff>)"
//To detect if the unit is affected by this just use "IsUnitInGroup(<Your unit>,udg_PWW_group)"
//If you use GUI create a group variable called "PWW_group", if you use Jass, uncomment the global section

library WindWalk initializer Init uses TimerUtils, start //This start is something of my map, erase it

globals
    private hashtable Instance=InitHashtable()
    private integer array InstanceMax
    private trigger Order=CreateTrigger()
    private unit Ordered
    /*Uncomment this if you use Jass
    group udg_PWW_group=CreateGroup()*/
endglobals

private struct WindWalk

    static constant real RANGE=112.00   //This is the max. range of the units has to do with the caster
    static constant real INTERVAL=0.01  //This the interval of the timer to check the nearby units
    static group g=CreateGroup()
    unit caster
    integer mybuff
    timer t
    integer instance_actual

    static method create takes unit caster, integer mybuff returns thistype
        local thistype this=thistype.allocate()
        local integer i=GetUnitUserData(caster)
        set InstanceMax[i]=InstanceMax[i]+1
        set this.instance_actual=InstanceMax[i]
        set this.caster=caster
        set this.mybuff=mybuff
        set this.t=NewTimerEx(this)
        call TimerStart(this.t,thistype.INTERVAL,true,function thistype.callPeriodic)
        return this
    endmethod
   
    static method UnitCondition takes unit u, unit other returns boolean
        if IsUnitType(u,UNIT_TYPE_STRUCTURE) then
            return false 
        elseif IsUnitType(u, UNIT_TYPE_FLYING) then
            return false
        elseif u==other then
            return false
        endif
        return true
    endmethod
   
    method periodic takes nothing returns nothing
        local real x=GetUnitX(this.caster)
        local real y=GetUnitY(this.caster)
        local unit u
        local boolean b=false
        local real f=bj_DEGTORAD*GetUnitFacing(this.caster)
        call GroupEnumUnitsInRange(thistype.g,x,y,thistype.RANGE,null)
        loop
            set u=FirstOfGroup(thistype.g)
            exitwhen u==null
            if thistype.UnitCondition(u,this.caster) then
                set b=true
                call GroupClear(thistype.g)
            endif
            call GroupRemoveUnit(thistype.g,u)
        endloop
       
        if b then
            call SetUnitPathing(this.caster,false)
        else
            //I create this function for my map to enable the colision of the unit under
            //specific conditions, change this to "SetUnitPathing(this.caster,true)" or your own "OnColision"
            call OnColision(this.caster)
        endif
    endmethod

    static method callPeriodic takes nothing returns nothing
        local WindWalk this=GetTimerData(GetExpiredTimer())
        if UnitHasBuffBJ(this.caster,this.mybuff) then
            call GroupAddUnit(udg_PWW_group,this.caster)
            call this.periodic()
        else
            call this.destroy()
        endif
    endmethod

    method destroy takes nothing returns nothing
        local integer i=GetUnitUserData(this.caster)
        call ReleaseTimer(this.t)
        call GroupRemoveUnit(udg_PWW_group,this.caster)
        //Same case here
        call OnColision(this.caster)
        call SaveInteger(Instance,i,this.instance_actual,LoadInteger(Instance,i,InstanceMax[i]))
        call RemoveSavedInteger(Instance,i,InstanceMax[i])
        set InstanceMax[i]=InstanceMax[i]-1
        set this.t=null
        set this.caster=null
        call this.deallocate()
    endmethod
endstruct

function WindWalkData takes unit caster, integer mybuff returns nothing
    local integer i=GetUnitUserData(caster)
    local integer j=1
    local boolean b=false
    local WindWalk t
    //--If the caster is affected by the same buff, this is to "restart" the system for that buff--
    loop
        exitwhen j>InstanceMax[i] or b
        set t=LoadInteger(Instance,i,j)
        if HaveSavedInteger(Instance,i,j) and t.mybuff==mybuff then
            call t.destroy()
            set b=true
        endif
        set j=j+1
    endloop
    //-- --
    call GroupAddUnit(udg_PWW_group,caster)
    call SaveInteger(Instance,i,InstanceMax[i],WindWalk.create(caster,mybuff))
endfunction

private function Conditions takes nothing returns boolean
    set Ordered=GetOrderedUnit()
    return IsUnitInGroup(Ordered,udg_PWW_group) and IsTerrainPathable(GetUnitX(Ordered),GetUnitY(Ordered),PATHING_TYPE_WALKABILITY)
endfunction

private function Actions takes nothing returns nothing
    local location l=GetOrderPointLoc()
    call DisableTrigger(Order)
    //Same case here
    call OnColision(Ordered)
    if l==null then
        call IssueTargetOrder(Ordered,OrderId2String(GetIssuedOrderId()),GetOrderTarget())
    else
        call IssuePointOrderLoc(Ordered,OrderId2String(GetIssuedOrderId()),l)
    endif
    call SetUnitPathing(Ordered,false)
    call RemoveLocation(l)
    set l=null
    call EnableTrigger(Order)
endfunction

private function Init takes nothing returns nothing
    call TriggerRegisterAnyUnitEventBJ(Order,EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
    call TriggerRegisterAnyUnitEventBJ(Order,EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    call TriggerAddCondition(Order,Condition(function Conditions))
    call TriggerAddAction(Order,function Actions)
endfunction

endlibrary
 
Status
Not open for further replies.
Top