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

[Spell] Charge Ability

Status
Not open for further replies.
Level 24
Joined
Jun 26, 2020
Messages
1,853
I wanna make a spell that do the same thing as "Charge" of the Zealot in Starcraft 2 but it can be stopped if the caster do another action (and this include if someone stop him) and if the caster colides with the target so the caster inflicts damage to the target, and if you can tell me if I can increase the limit movement speed it would be nice, I know I can move the unit to a position, but I wanna the caster don't turn off its collision; this can be in GUI or JASS (I don't know about libraries or structs), thanks.
 
Level 24
Joined
Jun 26, 2020
Messages
1,853
Ok, for the movement I'm using the PurgeandFire's MoveSpeedX, and these are the triggers to do what I want, but the created trigger isn't work, what is happening?
JASS:
function Trig_Charge_Conditions takes nothing returns boolean
    return (GetSpellAbilityId()=='A07O')
endfunction

function ChargeIsInRangeConditions takes nothing returns boolean
    return (udg_Charge[GetUnitUserData(GetTriggerUnit())])
endfunction

function ChargeIsInRangeActions takes nothing returns nothing
    local unit u=GetTriggerUnit()
    call UnitDamageTargetBJ(u,udg_ChargeTarget[GetUnitUserData(u)],100.00,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL)
    call IssueImmediateOrder(u,"stop")
    set u=null
endfunction

function Trig_Charge_Actions takes nothing returns nothing
    local unit caster=GetSpellAbilityUnit()
    local integer i=GetUnitUserData(caster)
    set udg_Charge[i]=true
    set udg_ChargeTrigger[i]=CreateTrigger()
    set udg_ChargeTarget[i]=GetSpellTargetUnit()
    set udg_ChargeEffect[i]=AddSpecialEffectTarget("war3mapImported\\Valiant Charge Royal.mdx",caster,"origin")
    call SetUnitMoveSpeed(caster,600.00)
    call IssueTargetOrder(caster,"attack",GetSpellTargetUnit())
    call TriggerRegisterUnitInRangeSimple(udg_ChargeTrigger[i],150.00,GetSpellTargetUnit())
    call TriggerAddCondition(udg_ChargeTrigger[i],Condition(function ChargeIsInRangeConditions))
    call TriggerAddAction(udg_ChargeTrigger[i],function ChargeIsInRangeActions)
    set caster=null
endfunction

//===========================================================================
function InitTrig_Charge takes nothing returns nothing
    set gg_trg_Charge=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Charge,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_Charge,Condition(function Trig_Charge_Conditions))
    call TriggerAddAction(gg_trg_Charge,function Trig_Charge_Actions)
endfunction
JASS:
function Trig_Charge_stop_Conditions takes nothing returns boolean
    set udg_Atemp_Int=GetUnitUserData(GetTriggerUnit())
    if GetOrderTargetUnit()==udg_ChargeTarget[udg_Atemp_Int] then
        return false
    elseif udg_Charge[udg_Atemp_Int]==false then
        return false
    endif
    return true
endfunction

function Trig_Charge_stop_Actions takes nothing returns nothing
    set udg_Charge[udg_Atemp_Int]=false
    call SetUnitMoveSpeed(GetTriggerUnit(),GetUnitDefaultMoveSpeed(GetTriggerUnit()))
    call DestroyEffectBJ(udg_ChargeEffect[udg_Atemp_Int])
    set udg_ChargeTarget[udg_Atemp_Int]=null
    call TriggerClearActions(udg_ChargeTrigger[udg_Atemp_Int])
    call TriggerClearConditions(udg_ChargeTrigger[udg_Atemp_Int])
    call DestroyTrigger(udg_ChargeTrigger[udg_Atemp_Int])
endfunction

//===========================================================================
function InitTrig_Charge_stop takes nothing returns nothing
    set gg_trg_Charge_stop=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Charge_stop,EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Charge_stop,EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Charge_stop,EVENT_PLAYER_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(gg_trg_Charge_stop,Condition(function Trig_Charge_stop_Conditions))
    call TriggerAddAction(gg_trg_Charge_stop,function Trig_Charge_stop_Actions)
endfunction
 
Level 24
Joined
Jun 26, 2020
Messages
1,853
This is a bit of a mess here. I'd highly suggest you to work with timers instead of creating and destroying triggers all over the place.
What do you suggest?, Because if I use a timer, so How can I reffer to the caster when is close to the target and make it MUI?, using a hashtable is not an option because I have the 2nd trigger to stop the spell when the caster do another action and I can't pass data.
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
I created a quick example, i didnt know exactly what you were trying to achieve, but i think this comes pretty close. Maybe you can modify this to your needs. (There is a small mistake with setting the units movement speed, the unit has slightly less movement speed after the cast than before, you will figure it out yourself)


TimerUtils (required)
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

Charge Ability
JASS:
library Charge initializer Init uses TimerUtils

    struct Charge

        static constant real timeout = 10
        static constant real minDistance = 22000
        static constant real interval = 0.1
        static constant real damage = 200
        static constant real speedBonus = 200
        real time = 0
        unit caster = null
        unit target = null
        timer t = null

        static method create takes unit caster, unit target returns thistype
            local thistype this = thistype.allocate()
            set this.caster = caster
            set this.target = target
            call IssueTargetOrder(this.caster, "attack", this.target)
            call SetUnitMoveSpeed(this.caster, GetUnitMoveSpeed(this.caster) + thistype.speedBonus)
            set this.t = NewTimer()
            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 dx = GetUnitX(this.caster) - GetUnitX(this.target)
            local real dy = GetUnitY(this.caster) - GetUnitY(this.target)
            local real distance = dx * dx + dy * dy
            if distance < thistype.minDistance then
                call UnitDamageTarget(this.caster, this.target, thistype.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                call this.destroy()
            elseif this.time >= thistype.timeout then
                call this.destroy()
            endif
            set this.time = this.time + thistype.interval
        endmethod

        static method callPeriodic takes nothing returns nothing
            local Charge this = GetTimerData(GetExpiredTimer())
            call this.periodic()
        endmethod

        method destroy takes nothing returns nothing
            call DestroyTimer(this.t)
            call SetUnitMoveSpeed(this.caster, GetUnitMoveSpeed(this.caster) - thistype.speedBonus)
            set this.t = null
            set this.caster = null
            set this.target = null
            call this.deallocate()
        endmethod
      
    endstruct


    function OnCast takes nothing returns nothing
        if GetSpellAbilityId() == 'AHtb' then
            call Charge.create(GetTriggerUnit(), GetSpellTargetUnit())
        endif
    endfunction

    function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddAction(t, function OnCast)
        set t = null
    endfunction

endlibrary
 
Level 24
Joined
Jun 26, 2020
Messages
1,853
I created a quick example, i didnt know exactly what you were trying to achieve, but i think this comes pretty close. Maybe you can modify this to your needs. (There is a small mistake with setting the units movement speed, the unit has slightly less movement speed after the cast than before, you will figure it out yourself)


TimerUtils (required)
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

Charge Ability
JASS:
library Charge initializer Init uses TimerUtils

    struct Charge

        static constant real timeout = 10
        static constant real minDistance = 22000
        static constant real interval = 0.1
        static constant real damage = 200
        static constant real speedBonus = 200
        real time = 0
        unit caster = null
        unit target = null
        timer t = null

        static method create takes unit caster, unit target returns thistype
            local thistype this = thistype.allocate()
            set this.caster = caster
            set this.target = target
            call IssueTargetOrder(this.caster, "attack", this.target)
            call SetUnitMoveSpeed(this.caster, GetUnitMoveSpeed(this.caster) + thistype.speedBonus)
            set this.t = NewTimer()
            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 dx = GetUnitX(this.caster) - GetUnitX(this.target)
            local real dy = GetUnitY(this.caster) - GetUnitY(this.target)
            local real distance = dx * dx + dy * dy
            if distance < thistype.minDistance then
                call UnitDamageTarget(this.caster, this.target, thistype.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                call this.destroy()
            elseif this.time >= thistype.timeout then
                call this.destroy()
            endif
            set this.time = this.time + thistype.interval
        endmethod

        static method callPeriodic takes nothing returns nothing
            local Charge this = GetTimerData(GetExpiredTimer())
            call this.periodic()
        endmethod

        method destroy takes nothing returns nothing
            call DestroyTimer(this.t)
            call SetUnitMoveSpeed(this.caster, GetUnitMoveSpeed(this.caster) - thistype.speedBonus)
            set this.t = null
            set this.caster = null
            set this.target = null
            call this.deallocate()
        endmethod
     
    endstruct


    function OnCast takes nothing returns nothing
        if GetSpellAbilityId() == 'AHtb' then
            call Charge.create(GetTriggerUnit(), GetSpellTargetUnit())
        endif
    endfunction

    function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddAction(t, function OnCast)
        set t = null
    endfunction

endlibrary
I said I don't know about libraries or structs, but well, I think at the end I have to do that, I don't say I not apreciate what you did, but I wanna know what I am doing before doing it, I even don't wanna use a timer because I wanna the effect ends in the exact moment the caster reaches the target.
In Jass, it is possible by set local id = GetUnitUserData()
Then set your caster[id] = your caster. With this, you can set and recall it in another function as reference, use it anytime. ForGroup() and EnumUnitinRange(). MUI at its best.
Check trigger inside UB project. You will learn it in no time.
Basically you are suggesting:
  • Trigger1
    • Events
      • Unit - A unit begins the effect of an ability
    • Conditions
    • Actions
      • Unit Group - Add "Caster" to "Unit Group"
      • Set Target[Custom value of "Caster"]="Target"
  • Trigger2
    • Events
      • Time - Every "time" seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in "Unit Group" and do actions:
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Condtions
              • Distance between Position of (Picked unit) and Position of Target[Custom value of (Picked unit)] less than 100
            • Then - Actions
              • End the spell
            • Else - Actions
If I wanna do something like dynamic index I wouldn't even use Jass.
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
I even don't wanna use a timer because I wanna the effect ends in the exact moment the caster reaches the target
You can set the interval for more precise results

Basically you are suggesting...
You could do it this way. My solution doesn't require a unit group or any location tho. It doesn't use custom value aswell, so you could cast this spell multiple times on one unit (if that is required for some reason)
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
The second reason I don't wanna use a timer is because I don't wanna the spell have a limit, I wanna the unique thing that stop the spell is or the caster reaches the target, or the caster do another action.
I just put that in because i thought it made sense. You can get rid of the limit if you want.

You need a timer in some way, even the event "unit in range" probably does check periodically for distance, so it is nothing else than some sort of timer running in the background.

If you want to, i can modify the code to your needs.
 
Level 24
Joined
Jun 26, 2020
Messages
1,853
I just put that in because i thought it made sense. You can get rid of the limit if you want.

You need a timer in some way, even the event "unit in range" probably does check periodically for distance, so it is nothing else than some sort of timer running in the background.

If you want to, i can modify the code to your needs.
Ok, but first I have to understand how works structs and libraries.
 
Level 4
Joined
Jul 26, 2017
Messages
66
Doing it on a repeating timer is the best way for me, like in this trigger I based on this system. It uses hashtables to store data (in this case the position of the enemy). you can also change the point offset value to make it faster or slower.

  • Events
    • Unit - A unit Starts the effect of an ability
  • Conditions
    • (Ability being cast) Equal to Charge
  • Actions
    • Set Point[1] = (Position of (Triggering unit))
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      • If - Conditions
        • ((Target unit of ability being cast) is alive) Equal to True
      • Then - Actions
        • Set Point[3] = (Position of (Target unit of ability being cast))
      • Else - Actions
        • Set Point[2] = (Target point of ability being cast)
        • Set Point[3] = (Point[1] offset by 900.00 towards (Angle from Point[1] to Point[2]) degrees)
        • Custom script: call RemoveLocation(udg_Point[2])
    • Unit - Create 1 dummy for (Owner of (Triggering unit)) at Point[3] facing 0.00 degrees
    • Unit - Add a 3.00 second Generic expiration timer to (Last created unit)
    • Hashtable - Save Handle Of(Last created unit) as (Key destination) of (Key (Triggering unit)) in HT_Charge
    • Unit Group - Add (Triggering unit) to Rushers
    • Unit - Turn collision for (Triggering unit) Off
    • Trigger - Turn on Loop <gen>
    • Custom script: call RemoveLocation(udg_Point[1])
    • Custom script: call RemoveLocation(udg_Point[3])
  • Loop
    • Events
      • Time - Every 0.03 seconds of game time
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Rushers is empty) Equal to True
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions
          • Unit Group - Pick every unit in Rushers and do (Actions)
            • Loop - Actions
              • Set Point[1] = (Position of (Picked unit))
              • Set Point[2] = (Position of (Load (Key destination) of (Key (Picked unit)) in HT_Charge))
              • Set Point[3] = (Point[1] offset by 50.00 towards (Angle from Point[1] to Point[2]) degrees)
              • Special Effect - Destroy (Last created special effect)
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Terrain pathing at Point[3] of type Walkability is off) Equal to False
                  • (Distance between Point[2] and Point[3]) Greater than 100.00
                • Then - Actions
                  • Unit - Move (Picked unit) instantly to Point[3], facing Point[2]
                • Else - Actions
                  • Unit Group - Remove (Picked unit) from Rushers
                  • Unit - Turn collision for (Picked unit) On
                  • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in HT_Charge
              • Custom script: call RemoveLocation(udg_Point[1])
              • Custom script: call RemoveLocation(udg_Point[2])
              • Custom script: call RemoveLocation(udg_Point[3])
 
Level 24
Joined
Jun 26, 2020
Messages
1,853
Doing it on a repeating timer is the best way for me, like in this trigger I based on this system. It uses hashtables to store data (in this case the position of the enemy). you can also change the point offset value to make it faster or slower.

  • Events
    • Unit - A unit Starts the effect of an ability
  • Conditions
    • (Ability being cast) Equal to Charge
  • Actions
    • Set Point[1] = (Position of (Triggering unit))
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      • If - Conditions
        • ((Target unit of ability being cast) is alive) Equal to True
      • Then - Actions
        • Set Point[3] = (Position of (Target unit of ability being cast))
      • Else - Actions
        • Set Point[2] = (Target point of ability being cast)
        • Set Point[3] = (Point[1] offset by 900.00 towards (Angle from Point[1] to Point[2]) degrees)
  • things Custom script: call RemoveLocation(udg_Point[2])
    • Unit - Create 1 dummy for (Owner of (Triggering unit)) at Point[3] facing 0.00 degrees
    • Unit - Add a 3.00 second Generic expiration timer to (Last created unit)
    • Hashtable - Save Handle Of(Last created unit) as (Key destination) of (Key (Triggering unit)) in HT_Charge
    • Unit Group - Add (Triggering unit) to Rushers
    • Unit - Turn collision for (Triggering unit) Off
    • Trigger - Turn on Loop <gen>
    • Custom script: call RemoveLocation(udg_Point[1])
    • Custom script: call RemoveLocation(udg_Point[3])
  • Loop
    • Events
      • Time - Every 0.03 seconds of game time
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Rushers is empty) Equal to True
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions
          • Unit Group - Pick every unit in Rushers and do (Actions)
            • Loop - Actions
              • Set Point[1] = (Position of (Picked unit))
              • Set Point[2] = (Position of (Load (Key destination) of (Key (Picked unit)) in HT_Charge))
              • Set Point[3] = (Point[1] offset by 50.00 towards (Angle from Point[1] to Point[2]) degrees)
              • Special Effect - Destroy (Last created special effect)
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Terrain pathing at Point[3] of type Walkability is off) Equal to False
                  • (Distance between Point[2] and Point[3]) Greater than 100.00
                • Then - Actions
                  • Unit - Move (Picked unit) instantly to Point[3], facing Point[2]
                • Else - Actions
                  • Unit Group - Remove (Picked unit) from Rushers
                  • Unit - Turn collision for (Picked unit) On
                  • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in HT_Charge
              • Custom script: call RemoveLocation(udg_Point[1])
              • Custom script: call RemoveLocation(udg_Point[2])
              • Custom script: call RemoveLocation(udg_Point[3])
Pls, don't speak me like I don't know about this things and don't pass me things that other people already passed me.
 
Level 24
Joined
Jun 26, 2020
Messages
1,853
Seems like you haven't learned from past mistakes
I'm sorry, but it almost feels like my intelligence is insulted.
I know he don't have that intention and I apreciate the people of this forum wanna help, I just felt uncomfortable and just said why, I have the opinion that if something annoys you and you have a legit reason, so say it, I don't consider the tolerance as something necessarly good and not just intolerate the intolerance.
 
Last edited:
Status
Not open for further replies.
Top