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

[System] Knockback 2D

Level 33
Joined
Apr 24, 2012
Messages
5,113
I don't know if i have problems inside the script,because i coded it in Notepad++ and i don't have any WE or JNGP to check its syntax.

JASS:
library Knockback2D /* v 1.5
*************************************************************************************
*
*
*    Knockback 2D
*        
*        by: Almia aka Radamantus
*
*
*    --------------------------------------------------------------------------
*
*        Description
*
*        ------------------------------------------
*
*            Knockback 2D is a system for creating and handling 2D knockbacks.
*            The system features safe-pathing, bounded movement, optional tree
*            destroying which also decelerates knockback speed,destroys knockback
*            when path is blocked.
*
*            The system safe-pathing recognizes units, destructables and terrain
*            path by collision size. Bounded movement disables units for leaving
*            map bounds.
*
*            The system also features personal events(onLoop, onEnd),which makes
*            this user-friendly system.
*
*            The system creates knockback by means of speed and time,but not
*            distance.
*
*    --------------------------------------------------------------------------
*
*        Notes
*
*            - Not recommended for knockbacking crowded units
*
*    --------------------------------------------------------------------------
*
*        */ requires /*
*
*        ------------------------------------------
*
*        */ Event /*             hiveworkshop.com/forums/jass-resources-412/snippet-event-186555/
*        */ GetUnitCollision /*  hiveworkshop.com/forums/jass-resources-412/snippet-getunitcollision-180495/
*        */ IsDestTree /*        hiveworkshop.com/forums/spells-569/isdestree-v1-2-a-224623/
*        */ IsTerrainWalkable /* pastebin.com/0zjeaBc7
*        */ MapBounds /*         hiveworkshop.com/forums/jass-resources-412/snippet-mapbounds-222870/
*
*    --------------------------------------------------------------------------
*
*        Credits
*
*        ------------------------------------------
*
*            Nestharus for Event and Collision Libs
*            Vexorian for IsTerrainWalkable Lib
*            Adiktuz for MapBounds Lib
*            Spinnaker for spotting errors
*            Magtheridon96 for explaining public groups properly
*            Bribe for Table
*
*    --------------------------------------------------------------------------
*
*        API
*
*        ------------------------------------------
*
*        private function PathUnitFilter takes unit u, unit up returns boolean
*        - filter function for enumerating units for pathing
*
*        private function PathDestFilter takes nothing returns boolean
*        - filter function for enumerating destructables for pathing
*
*        struct Knockback2D extends array
*
*            static method find takes unit tu returns integer
*            - checks if a unit is already been knockbacked by finding it inside the list
*            - this is used for rewriting knockback data of the unit for safety
*
*            static method destroy takes nothing returns nothing
*            - destroys a knockback instance
*
*            private static method periodic takes nothing returns nothing
*            - periodic method of system
*            - basically, the system core
*
*            static method add takes unit tu, real tspeed, real tangle, real ttime returns nothing
*            - creates a knockback instance
*            - can also be used for updating knockback data
*
*            static method registerEvent takes integer KBevent, code c returns nothing
*            - registers a code to the knockback event.
*            - the code will be fired once the event is fired.
*
*            private static method init takes nothing returns nothing
*            - system initializer
*
*        function GetKnockbackOnLoopUnit takes nothing returns unit
*        function GetKnockbackOnEndUnit takes nothing returns unit
*        function AddKnockback takes unit u, real speed, real angle, real time returns nothing
*        function DestroyKnockback takes unit u returns nothing
*        function RegisterKnockbackEvent takes code c, integer KBEvent returns nothing
*
*    --------------------------------------------------------------------------
*
*        Settings
*
*        ------------------------------------------
*/
    globals
        /*
        *    The bonus collision of pathing check
        */
        private constant real COLLISION_BONUS = 50
        
        /*
        *    Percentage of speed to be removed
        *    for deceleration
        */
        private constant real DECEL_SPEED_SCALE = 0.4
        
        /*
        *    System Timeout
        */
        private constant real TIMEOUT = 0.031250000
        
        /*
        *    System Events
        */
        constant integer EVENT_KNOCKBACK_LOOP = 1
        constant integer EVENT_KNOCKBACK_END = 2
        
    endglobals
/*
*    --------------------------------------------------------------------------
*
*        Knockback Struct and Functions
*
*    --------------------------------------------------------------------------
*/

    /*
    *    Misc Globals
    */
    globals
        private boolean hit = false
        private boolean hitTree = false
        private timer tm = CreateTimer()
        
        private unit KnockbackEventOnDestroyUnit
        private unit KnockbackEventOnLoopUnit
        
        private group g = CreateGroup()
        private hashtable ht
    endglobals
    
    /*
    *    Pathing enum unit filter
    */
    private function PathUnitFilter takes unit u, unit up returns boolean
        return not IsUnitType(u, UNIT_TYPE_DEAD)/*
            */ and GetWidgetLife(u) > 0.405     /*
            */ and u != up and u != null        /*
            */ and not IsUnitType(u, UNIT_TYPE_FLYING)
    endfunction
    /*
    *    Pathing enum doodad filter
    */
    private function PathDestFilter takes nothing returns boolean
        local destructable d = GetFilterDestructable()
        if d != null then
            if GetWidgetLife(d) > 0.405 then
                if IsDestTree(d) then
                    set hitTree = true
                    call KillDestructable(d)
                else
                    set hit = true
                endif
            endif
        endif
        set d = null
        return false
    endfunction
    
    private module Init
        static method onInit takes nothing returns nothing
            call init()
        endmethod
    endmodule
    
    struct Knockback2D extends array
    
        /*
        *    Linked List
        */
        private static thistype ic = 0
        private static thistype array rc
        private static thistype array n
        private static thistype array p
        private static integer c = 0
        /*
        *    Events
        */
        private static Event onLoop
        private static Event onDestroy
        /*
        *    Iteration and Instance Var
        */
        private static unit array u
        private static real array speed
        private static real array angle
        private static real array time
        private static real array csize
        
        method destroy takes nothing returns nothing
            set KnockbackEventOnDestroyUnit = u[this]
            call FireEvent(onDestroy)
            set rc[this] = rc[0]
            set rc[0] = this
            set n[p[this]] = n[this]
            set p[n[this]] = p[this]
            call SaveInteger(ht, GetHandleId(u[this]), 0, 0)
            set u[this] = null
            set speed[this] = 0
            set angle[this] = 0
            set time[this] = 0
            set csize[this] = 0
            set c = c - 1
            if 0 == c then
                call PauseTimer(tm)
            endif
        endmethod
        
        private static method periodic takes nothing returns nothing
            local real x
            local real y
            local rect r
            local unit tu
            
            local thistype i = n[0]
            loop
                exitwhen 0 == i
                if 0 < time[i] then
                    set time[i] = time[i] - TIMEOUT
                    set KnockbackEventOnLoopUnit = u[i]
                    call FireEvent(onLoop)
                    set x = GetUnitX(u[i]) + speed[i] * Cos(angle[i])
                    set y = GetUnitY(u[i]) + speed[i] * Sin(angle[i])
                    if MapContainsX(x) and MapContainsY(y) then
                        set r = Rect(x - csize[i], y - csize[i], x + csize[i], y + csize[i])
                        call EnumDestructablesInRect(r, Filter(function PathDestFilter), null)                    
                        call RemoveRect(r)
                        set r = null
                        call GroupEnumUnitsInRange(g, x, y, csize[i], null)
                        loop
                            set tu = FirstOfGroup(g)
                            exitwhen null == tu
                            if PathUnitFilter(tu, u[i]) then
                                set hit = true
                            endif
                            call GroupRemoveUnit(g, tu)
                        endloop
                        if hitTree then
                            set speed[i] = speed[i] - (speed[i] * DECEL_SPEED_SCALE)
                        endif
                        if hit or not IsTerrainWalkable(x, y) then
                            call i.destroy()
                        endif
                        call SetUnitX(u[i], x)
                        call SetUnitY(u[i], y)
                    else
                        call i.destroy()
                    endif
                else
                    call i.destroy()
                endif
                set hit = false
                set hitTree = false
                set i = n[i]
            endloop
        endmethod
        
        static method add takes unit tu, real tspeed, real tangle, real ttime returns nothing
            local integer i = GetHandleId(tu)
            local thistype this = thistype(LoadInteger(ht, i, 0))
            if this == 0 then
                set this = rc[0]
                if 0 == this then
                    set this = ic + 1
                    set ic = this
                else
                    set rc[0] = rc[this]
                endif
                set n[this] = 0
                set p[this] = 0
                set n[p[0]] = this
                set p[0] = this
                set c = c + 1
                if 1 == c then
                    call TimerStart(tm, TIMEOUT, true, function thistype.periodic)
                endif
                set csize[this] = GetUnitCollision(tu) + COLLISION_BONUS
                set u[this] = tu
                
                call SaveInteger(ht, i, 0, integer(this))
            endif
            set speed[this] = tspeed * TIMEOUT
            set angle[this] = tangle * bj_DEGTORAD
            set time[this] = ttime
        endmethod
        
        static method registerEvent takes integer KBEvent, code c returns nothing
            if KBEvent == EVENT_KNOCKBACK_LOOP then
                call onLoop.register(Filter(c))
            elseif KBEvent == EVENT_KNOCKBACK_END then
                call onDestroy.register(Filter(c))
            endif
        endmethod
        
        private static method init takes nothing returns nothing        
            set onLoop = CreateEvent()
            set onDestroy = CreateEvent()
            set ht = InitHashtable()          
        endmethod
        
        implement Init
    endstruct
    
    /*
    *    Wrappers
    */
    function GetKnockbackOnLoopUnit takes nothing returns unit
        return KnockbackEventOnLoopUnit
    endfunction
    
    function GetKnockbackOnEndUnit takes nothing returns unit
        return KnockbackEventOnDestroyUnit
    endfunction
    
    function AddKnockback takes unit u, real speed, real angle, real time returns nothing
        call Knockback2D.add(u, speed, angle, time)
    endfunction
    
    function DestroyKnockback takes unit u returns nothing
        local Knockback2D i = Knockback2D(LoadInteger(ht, GetHandleId(u), 0))
        call i.destroy()
    endfunction
    
    function RegisterKnockbackEvent takes code c, integer KBEvent returns nothing
        call Knockback2D.registerEvent(KBEvent, c)
    endfunction
endlibrary

Demo:
JASS:
struct Test extends array
    private static method periodic takes nothing returns boolean
        call DestroyEffect(AddSpecialEffect("Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl", GetUnitX(GetKnockbackOnLoopUnit()), GetUnitY(GetKnockbackOnLoopUnit())))
        return false
    endmethod
    
    private static method run takes nothing returns boolean
        local unit target
        local unit caster = GetTriggerUnit()
        local real x = GetUnitX(caster)
        local real y = GetUnitY(caster)
        local group g = CreateGroup()
        call GroupEnumUnitsInRange(g, x, y, 500, null)
        loop
            set target = FirstOfGroup(g)
            exitwhen target == null
            if target != caster then
                call AddKnockback(target, 500, Atan2(GetUnitY(target) - y, GetUnitX(target) - x) * bj_RADTODEG, 1.25)
            endif
            call GroupRemoveUnit(g, target)
        endloop
        call DestroyGroup(g)
        set g = null
        set caster = null
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function thistype.run))
        call RegisterKnockbackEvent(function thistype.periodic, EVENT_KNOCKBACK_LOOP)
        set t = null
    endmethod
endstruct

Changelogs
1.0
- Release
1.1
- Fix code errors(thanks to Spinnaker
1.2
- Added wrappers
- Fix code.
1.3
- code now stable
- Added test map.
1.4
- Optimized code.
- Cleaned some leaks.
- Add Table as a required library for finding unit index.
1.5
-Updated version,still buggy.

(Test map code is outdated,due to update)
 

Attachments

  • Kb2d.w3x
    44.8 KB · Views: 92
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
For Gods sake IsDestTree is pity requirement since it isn't yours. Link proper library: IsDestructableTree. The fact that mod aproved ur lib dones't mean it's ur resource, and you havent even gave credits - your lib still differs from Bribes version by dummy id.

method onInit must be static.
elseif GetWidgetLife(d) > 0.405 missing then.
elseif KBEvent = EVENT_KNOCKBACK_END thenoperator conflict; you require comparison operator instead of assign one.

KnockbackEventUnit misses.
method find takes unit tu - in loop you probably meant to use tu instead of u. Even if not, u is missing it's index.

Knockback periodic - typo: if MapContainsX(x) and MapContains(y) then ->> MapContainsY(y)
static method add - typo: you probably meant unit tu instead of u in: local thistype this = Knockback2D.find(tu)
static method registerEvent typo - parameter KBevent -> KBEvent or change the reference to given local within method.
method .register via Event API requires boolexpr instead of code thus: .register(Filter(c))
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
I told you its that library is mine.I coded it by myself.And that day,i didn't know about PitzerMike's so i coded my own.
There are some reasons why it is from me:
1) That resource was first a pure GUI resource and only features Destroying Trees.
2) I saw a resource from Chaosy(his IsDestTree) so i got the idea of updating my own resource.
3) I updated the resource from GUI to vJASS. First,i was planning to use Pure JASS but its gonna waste my time,so i coded it in much easier and faster way. The code was based on my way of checking and destroying trees,the IsDestructableTree function came from Magtheridon.

Thanks for spotting those,i can't install WE here because my sister is a mad owner.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
No you haven't. Dude, look at linked thread - last post - yours at 2013's april with OMG unit id change (!!!) - no configurables, no backwards compatibility. Yes you probably know that I've forgotten to add: no credits. How can we talk seriously with those fact lying down on your desk?

And how old is she? It sounds strange.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
im too young to buy my own. (15)

And here is the status of my lib :
Uploaded: 10:49, 26th Oct 2012
Last Updated: 16:36, 17th Feb 2013

You can also ask Mag if my lib is not mine.
Also here is another one,I saw Maddeem's crazy IsDestructableTree lib and there are no other libs approved that does that function.So i wrote it.

edit
I have 0(except this) programming language knowledge.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Then how you understand structs representation in JNPG compiler and the linking process between multiple lib data?
vJass presents us some of advenced features. Your knowledge can not be 0 or you wouldn't write anything.

OK - end of conversation; mods will say that it's going offtopic.
 
IsDestructableTree function came from Magtheridon.

Well, just the inlining version :v
The method was used ages before though.
Only thing I haven't seen on wc3c.net is a destructable test function that inlines while using the harvest method, so this is probably the closest I'm getting to making something original for wc3 :p

edit
Your demo doesn't compile.
When you declare a variable public, you refer to it like this outside the library: LibraryName_variableName
Don't use public variables here, just make functions for event data. (like GetTriggerUnit)
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
@Magtheridon96
Thanks for knowing that. I was also gonna make some wrappers for struct methods ( Knockback2D.add == AddKnockback) so that it will have readability and much better syntax.

@Orthane
We have a few knockback systems in vJASS.
Cokemonkey's is not destructable friendly.
The one in the spell's section(forgot) is not user-friendly.

My system was just an easier version of my Knockback 2D.


Script Updating. Gonna check this if the system works fine and if it compiles error.

edit
Updated. View changelogs for updates. Haven't tested the script yet.

edit
Updated again.
Found some errors and now system works properly.
Added a test map for demo.
 
Last edited:
- You don't need to null r and g at the end of the periodic function.
- You're leaking the local destructable in the dest filter function. Null it.
- You should not be using an O(n) find function. Use an O(1) Table lookup instead. set instances[GetHandleId(myUnit)] = this when you create an instance and set it to 0 when you destroy the instance.
- Use a global group instead of a locally created one. You'd still be able to handle recursion so it's totally fine.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Distinguish between addVel and setVel.

Adding velocity should take the current knockback and speed into account.
Setting velocity ovverides the current knockback.
If there is no current knockback running for a unit addVel equals setVel.

velX = speed*Cos(angle)
velY = speed*Sin(angle)

set x = GetUnitX(u) + velX
set y = GetUnitY(u) + velY

I recommend to use a static rect
 
Top