• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[System] Knockback3D

Knockback3D

Preface

Jumps, Knockbacks, and throws are a common jass implementation because there are many design objectives desired for their behavior. No system does everything, and this is no different, but I do hope its features are useful to you.

Limitations

There are a few important things about this system that make it imperfect.

  • It isn't configurable to use SetUnitPosition(), because it handles flying units with SetUnitMoveSpeed (Either 0 or GetUnitDefaultMoveSpeed). This means that if other scripts in your map use SetUnitMoveSpeed, this will interfere and may reset those effects. They should use a CC lock system to circumvent this.
  • No support for custom callback functions. If you want units to damage surrounding enemies when they hit the ground, or explode when they kill trees, or conserve momentum by taking into account a unit's size, or bounce off of walls/units/destructables, this script can't help you.
  • This script uses a constant gravity value. If you want to make a projectile that falls slower than normal units, you can't do that.
  • This script stores velocities as final Cartesian components. That means if you want a projectile to track a mobile target, this won't help you.
  • This system cannot be used to throw an object to a target location, unless you painstakingly do lots of math yourself. If you want that, don't use this library.

Pre-requisites

JASS:
 *        Optional IsDestructableTree
 *             Supports both PitzerMike's ([url]http://goo.gl/zZHhGc[/url]) and BPower's ([url]http://goo.gl/jGYFQK[/url])
 *            implementation. Use one or none.
 *
 *        IsTerrainWalkable or TerrainPathability
 *             To more accurately detect collisions, you must have either IsTerrainWalkable by Anitarf
 *            and Vexorian ([url]http://goo.gl/bf1wpN[/url]) OR TerrainPathability by Rising_Dusk
 *            ([url]http://goo.gl/UTzPdG[/url]).

In addition to this library, the script is written in vJass and therefore requires JassHelper. I recommend using Jass Newgen Pack.

API

JASS:
 * API:
 *         constant boolean USE_MOVESPEED_MODIFIERS - Prevent a unit from moving while airborne
 *      constant boolean USE_TREE_CHECKER - Check destructables for trees only before destroying
 *      constant boolean DESTROY_DESTRUCTABLES_ONHIT - Destroy destructables hit by projectiles
 *        constant real CLOCK_PERIOD - how often to iterate through projectile bodies
 *        constant real COEFF_RESTITUTION_GROUND - Fraction of velocity to keep after hitting ground
 *         constant real COEFF_RESTITUTION_DSTRBL - Fraction of velocity to keep after hitting destruc.
 *         constant real FRICTION_ITER_MULTIPLIER - Fraction of velocity to lose while sliding per ite.
 *        constant real GRAVITY - Acceleration rate in units per second per second
 *         constant real MAX_Z_VELOCITY_TO_BOUNCE - The necessary z-velocity to bounce off ground
 *        constant real MIN_Z_VELOCITY_TO_BECOME_AIRBORNE - necessay z-velocity to stop sliding
 *         constant real MIN_FLY_HEIGHT - A height threshold (for floating units)
 *        constant real MIN_FOR_KNOCKBACK - Minimum velocity to maintain knockback (units / second)
 *        constant real MIN_SPEED_FRICTION_FX - The minimum speed to draw friction FX
 *        constant string FRICTION_MODEL - The FX to draw during high-friction sliding.
 *        constant real DESTRUCTABLE_ENUM_RADIUS - Size of square to enumerate destructables.
 *        constant real MIN_VEL_DESTROY_DESTRUCTABLE - Minimum velocity to destroy destructable
 *         constant real MAX_HEIGHT_DESTROY_DESTRUCTABLE - The flying height at which destru. destroyed
 *
 *        Knockback3D.updateMapArea(rect r)
 *        Knockback3D.add(unit,real a, real b, real c) - Apply vector of size a to unit towards b on
 *            the XY plane, and c on the Z axis.
 *        Knockback3D.setVel(unit,real a, real b, real c) - Set unit's knockback vector to a towards b
 *            on the XY plan and c on the Z axis.

The script


JASS:
/**
 * Knockback3D by Cokemonkey11, a projectile motion emulator for unit knockback.
 *
 * Requirements:
 *      Optional IsDestructableTree
 *          Supports both PitzerMike's ([url]http://goo.gl/zZHhGc[/url]) and BPower's ([url]http://goo.gl/jGYFQK[/url])
 *          implementation. Use one or none.
 *
 *      IsTerrainWalkable or TerrainPathability
 *          To more accurately detect collisions, you must have either IsTerrainWalkable by Anitarf
 *          and Vexorian ([url]http://goo.gl/bf1wpN[/url]) OR TerrainPathability by Rising_Dusk
 *          ([url]http://goo.gl/UTzPdG[/url]).
 *
 * API:
 *      constant boolean USE_MOVESPEED_MODIFIERS - Prevent a unit from moving while airborne
 *      constant boolean USE_TREE_CHECKER - Check destructables for trees only before destroying
 *      constant boolean DESTROY_DESTRUCTABLES_ONHIT - Destroy destructables hit by projectiles
 *      constant real CLOCK_PERIOD - how often to iterate through projectile bodies
 *      constant real COEFF_RESTITUTION_GROUND - Fraction of velocity to keep after hitting ground
 *      constant real COEFF_RESTITUTION_DSTRBL - Fraction of velocity to keep after hitting destruc.
 *      constant real FRICTION_ITER_MULTIPLIER - Fraction of velocity to lose while sliding per ite.
 *      constant real GRAVITY - Acceleration rate in units per second per second
 *      constant real MAX_Z_VELOCITY_TO_BOUNCE - The necessary z-velocity to bounce off ground
 *      constant real MIN_Z_VELOCITY_TO_BECOME_AIRBORNE - necessay z-velocity to stop sliding
 *      constant real MIN_FLY_HEIGHT - A height threshold (for floating units)
 *      constant real MIN_FOR_KNOCKBACK - Minimum velocity to maintain knockback (units / second)
 *      constant real MIN_SPEED_FRICTION_FX - The minimum speed to draw friction FX
 *      constant string FRICTION_MODEL - The FX to draw during high-friction sliding.
 *      constant real DESTRUCTABLE_ENUM_RADIUS - Size of square to enumerate destructables.
 *      constant real MIN_VEL_DESTROY_DESTRUCTABLE - Minimum velocity to destroy destructable
 *      constant real MAX_HEIGHT_DESTROY_DESTRUCTABLE - The flying height at which destru. destroyed
 *
 *      Knockback3D.updateMapArea(rect r)
 *      Knockback3D.add(unit,real a, real b, real c) - Apply vector of size a to unit towards b on
 *          the XY plane, and c on the Z axis.
 *      Knockback3D.setVel(unit,real a, real b, real c) - Set unit's knockback vector to a towards b
 *          on the XY plan and c on the Z axis.
 */
library Knockback3D uses optional IsDestructableTree, /*
                      */ optional IsTerrainWalkable,  /*
                      */ optional TerrainPathability

    // =========================================================================
    // Begin Customizable Section
    // =========================================================================
    globals
        // Defines whether units should have their movement speed set to 0 while
        // in motion, and then later back to their "default" speed. If false,
        // units in mid air can still fully control themselves. Warning: This is
        // not a lock-safe crowd-control implementation.
        private constant boolean USE_MOVESPEED_MODIFIERS=true

        // Defines whether the script should check enumerated destructables as
        // being trees or not. If enabled, will only work if IsDestructableTree
        // library is available.
        private constant boolean USE_TREE_CHECKER=true

        // Defines whether to enumerate and destroy destructables in contact
        // with projectile bodies.
        private constant boolean DESTROY_DESTRUCTABLES_ONHIT=true
    endglobals

    /**
     * Object which holds both static and instance knockback data. Not to be
     * modified except in designated CUSTOMIZE areas.
     */
    struct Knockback3D
        // A parameter for controlling the system clock, in seconds. 1/30 runs
        // 30 times per second.
        private static constant real CLOCK_PERIOD=1./30.

        // A measure of velocity retention after colliding with ground. 0.4
        // means 40% retention.
        private static constant real COEFF_RESTITUTION_GROUND=.4

        // How much velocity should be retained after hitting a destructable. A
        // value of .3 means 30% velocity is retained.
        private static constant real COEFF_RESTITUTION_DSTRBL=.3

        // What fraction of velocity should be lost with every iteration of
        // ground friction. Note that simulating an abstraction of friction in
        // units per second overflows real precision numbers. Thus, you must
        // adjust this according to your clock period.
        private static constant real FRICTION_ITER_MULTIPLIER=.15

        // The downward acceleration of units in motion. A value of
        // CLOCK_PERIOD*41.25 means they accelerate downwards by 41.25 units per
        // second.
        private static constant real GRAVITY=CLOCK_PERIOD*45.

        // The minimum fall-speed for a unit to bounce. CLOCK_PERIOD*-300. means
        // that the a unit must be falling at 300 units per second to bounce.
        private static constant real MAX_Z_VELOCITY_TO_BOUNCE=CLOCK_PERIOD*-300.

        // The minimum z-velocity of a unit to have it's flying height changed,
        // instead of simply sliding.
        private static constant real MIN_Z_VELOCITY_TO_BECOME_AIRBORNE=CLOCK_PERIOD*150.

        // This is the minimum height a unit can be at before friction is
        // applied. A value greater than 0 is recommended as some units have a
        // small non-zero flying height.
        private static constant real MIN_FLY_HEIGHT=5.

        // The minimum horizontal velocity a unit can be sliding before the
        // system ignores it. A value of CLOCK_PERIOD*30 means the unit will
        // stop sliding when its slide speed reduces past 30 units per second.
        private static constant real MIN_FOR_KNOCKBACK=CLOCK_PERIOD*30.

        // The minimum speed a sliding unit must be moving to spawn a "friction"
        // effect. A value of CLOCK_PERIOD*180 means the effect is applied while
        // units are moving faster than 180 units per second.
        private static constant real MIN_SPEED_FRICTION_FX=CLOCK_PERIOD*180.

        // The effect model to spawn when a unit's horizontal velocity is
        // greater than MIN_SPEED_FRICTION_FX .
        private static constant string FRICTION_MODEL="Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"

        // The square size to search for destructables when destroying them.
        // Note that a square's diagonal is Sqrt(2) times bigger than this.
        private static constant real DESTRUCTABLE_ENUM_RADIUS=130.

        // The minimum horizontal velocity a unit must have to destroy a
        // destructable. You can set this to a very high number to disable the
        // feature. A value of CLOCK_PERIOD*300 means the unit must travel at
        // 300 units per second on the XY plane, to destroy obstacles.
        private static constant real MIN_VEL_DESTROY_DESTRUCTABLE=CLOCK_PERIOD*300.

        // The height below which a flying unit is elligible to destroy
        // destructables. Ideally it should be the maximum height of your
        // destructables.
        private static constant real MAX_HEIGHT_DESTROY_DESTRUCTABLE=150.

        // =====================================================================
        // End Customizable Section
        // =====================================================================

        private static constant integer CROW_ID='Arav'

        private static boolean hitDestructable

        // A stack size counter.
        private static integer dbIndex=-1

        // Stack of knockback data blobs.
        private static thistype array knockDB

        // Movable location for the getZ shim.
        private static location zLoc=Location(0.,0.)

        // Copies of map boundary co-ordinates.
        private static real mapMinX
        private static real mapMaxX
        private static real mapMinY
        private static real mapMaxY

        // Used to enumerate destructables.
        private static rect destructableRect

        private static timer clock=CreateTimer()


        // For getting the z-height of a co-ordinate pair.
        private static method getZ takes real x, real y returns real
            call MoveLocation(zLoc,x,y)
            return GetLocationZ(zLoc)
        endmethod

        // The callback function when enumerating destructables.
        private static method destructableCallback takes nothing returns nothing
            local destructable des=GetEnumDestructable()
            if GetDestructableLife(des)>0. then
                static if DESTROY_DESTRUCTABLES_ONHIT then
                    static if USE_TREE_CHECKER and LIBRARY_IsDestructableTree then
                        if IsDestructableTree(des) then
                            call KillDestructable(des)
                        endif
                    else
                        call KillDestructable(des)
                    endif
                endif
                set hitDestructable=true
            endif
            set des=null
        endmethod

        // The periodic function which iterates through all objects in flight.
        private static method p takes nothing returns nothing
            local boolean newInMap
            local integer index=0
            local real flyHeight
            local real unitX
            local real unitY
            local real heightDifference
            local real newX
            local real newY
            local real velXY
            local thistype tempDat
            loop
                exitwhen index>dbIndex
                set tempDat=thistype.knockDB[index]
                set unitX=GetUnitX(tempDat.u)
                set unitY=GetUnitY(tempDat.u)
                set newX=unitX+tempDat.delX
                set newY=unitY+tempDat.delY
                set newInMap=newX>mapMinX and newX<mapMaxX and newY>mapMinY and newY<mapMaxY
                set flyHeight=GetUnitFlyHeight(tempDat.u)
                set velXY=(tempDat.delX*tempDat.delX+tempDat.delY*tempDat.delY)
                if flyHeight<MIN_FLY_HEIGHT then
                    if IsTerrainWalkable(newX,newY) and newInMap then
                        call SetUnitX(tempDat.u,unitX+tempDat.delX)
                        call SetUnitY(tempDat.u,unitY+tempDat.delY)
                        if tempDat.delZ<=MIN_FLY_HEIGHT then
                            set tempDat.delX=tempDat.delX*(1.-FRICTION_ITER_MULTIPLIER)
                            set tempDat.delY=tempDat.delY*(1.-FRICTION_ITER_MULTIPLIER)
                            if velXY>MIN_SPEED_FRICTION_FX then
                                call DestroyEffect(AddSpecialEffect(FRICTION_MODEL,unitX,unitY))
                            endif
                        endif
                        static if USE_MOVESPEED_MODIFIERS then
                            call SetUnitMoveSpeed(tempDat.u,GetUnitDefaultMoveSpeed(tempDat.u))
                        endif
                    else
                        set tempDat.delX=0
                        set tempDat.delY=0
                    endif

                    if tempDat.delZ<MAX_Z_VELOCITY_TO_BOUNCE then
                        set tempDat.delZ=tempDat.delZ*-1.*COEFF_RESTITUTION_GROUND
                    endif
                    if tempDat.delZ>MIN_Z_VELOCITY_TO_BECOME_AIRBORNE then
                        call SetUnitFlyHeight(tempDat.u,flyHeight+tempDat.delZ,0)
                        set tempDat.delZ=tempDat.delZ-GRAVITY
                    endif
                elseif newInMap then
                    set tempDat.delZ=tempDat.delZ-GRAVITY
                    set heightDifference=getZ(newX,newY)-getZ(unitX,unitY)
                    call SetUnitFlyHeight(tempDat.u,flyHeight+tempDat.delZ-heightDifference,0)
                    call SetUnitX(tempDat.u,newX)
                    call SetUnitY(tempDat.u,newY)
                    static if USE_MOVESPEED_MODIFIERS then
                        call SetUnitMoveSpeed(tempDat.u,0)
                    endif
                else
                    set tempDat.delX=0
                    set tempDat.delY=0
                endif
                if velXY<MIN_FOR_KNOCKBACK and tempDat.delZ>MAX_Z_VELOCITY_TO_BOUNCE and tempDat.delZ<-1*MAX_Z_VELOCITY_TO_BOUNCE and flyHeight<MIN_FLY_HEIGHT then
                    set knockDB[index]=knockDB[dbIndex]
                    set dbIndex=dbIndex-1
                    call SetUnitFlyHeight(tempDat.u,0,0)
                    static if USE_MOVESPEED_MODIFIERS then
                        call SetUnitMoveSpeed(tempDat.u,GetUnitDefaultMoveSpeed(tempDat.u))
                    endif
                    call tempDat.destroy()
                    set index=index-1
                    if dbIndex<0 then
                        call PauseTimer(clock)
                    endif
                endif
                if velXY>MIN_VEL_DESTROY_DESTRUCTABLE and flyHeight<MAX_HEIGHT_DESTROY_DESTRUCTABLE then
                    set hitDestructable=false
                    call MoveRectTo(destructableRect,newX,newY)
                    call EnumDestructablesInRect(destructableRect,null,function thistype.destructableCallback)
                    if hitDestructable then
                        set tempDat.delX=tempDat.delX*COEFF_RESTITUTION_DSTRBL
                        set tempDat.delY=tempDat.delY*COEFF_RESTITUTION_DSTRBL
                    endif
                endif
                set index=index+1
            endloop
        endmethod

        // Get a unit's stack index.
        private static method getUnitIndexFromStack takes unit u returns integer
            local integer index=0
            local integer returner=-1
            local thistype tempDat
            loop
                // A potential future improvement would be to use optional Table
                // instead of linear search.
                exitwhen index>dbIndex or returner!=-1
                set tempDat=knockDB[index]
                if tempDat.u==u then
                    set returner=index
                endif
                set index=index+1
            endloop
            return returner
        endmethod

        private static method onInit takes nothing returns nothing
            set destructableRect=Rect(-1*DESTRUCTABLE_ENUM_RADIUS,-1*DESTRUCTABLE_ENUM_RADIUS,DESTRUCTABLE_ENUM_RADIUS,DESTRUCTABLE_ENUM_RADIUS)
            set mapMinX=GetRectMinX(bj_mapInitialPlayableArea)
            set mapMaxX=GetRectMaxX(bj_mapInitialPlayableArea)
            set mapMinY=GetRectMinY(bj_mapInitialPlayableArea)
            set mapMaxY=GetRectMaxY(bj_mapInitialPlayableArea)
        endmethod

        /**
         * A function for updating the valid map co-ordinates, in case the playable map area changes
         * dynamically.
         */
        public static method updateMapArea takes rect rct returns nothing
            set thistype.mapMinX=GetRectMinX(rct)
            set thistype.mapMinY=GetRectMinY(rct)
            set thistype.mapMaxX=GetRectMaxX(rct)
            set thistype.mapMaxY=GetRectMaxY(rct)
        endmethod

        /**
         * Add a knockback vector to a unit. If the unit is already in the system, the new vector
         * will be emulated as a secondary knockback source.
         *
         * Parameters:
         *      u: unit to knock back
         *      velocity: speed in units per second at which to knock the unit back
         *      angle: The angle on the XY plane to knock the unit, in radians.
         *      alpha: The angle of attack (z-axis) to knock the unit, in radians (where 0 is no AoA)
         */
        public static method add takes unit u, real velocity, real angle, real alpha returns nothing
            local integer index=getUnitIndexFromStack(u)
            local thistype tempDat
            local real instVel=velocity*CLOCK_PERIOD
            if index==-1 then
                set tempDat=thistype.create()
                set tempDat.u=u
                set tempDat.delX=instVel*Cos(angle)*Cos(alpha)
                set tempDat.delY=instVel*Sin(angle)*Cos(alpha)
                set tempDat.delZ=instVel*Sin(alpha)
                set dbIndex=dbIndex+1
                set knockDB[dbIndex]=tempDat
                if UnitAddAbility(tempDat.u,CROW_ID) then
                    call UnitRemoveAbility(tempDat.u,CROW_ID)
                endif
                if dbIndex==0 then
                    call TimerStart(clock,CLOCK_PERIOD,true,function thistype.p)
                endif
            else
                set tempDat=knockDB[index]
                set tempDat.delX=tempDat.delX+instVel*Cos(angle)*Cos(alpha)
                set tempDat.delY=tempDat.delY+instVel*Sin(angle)*Cos(alpha)
                set tempDat.delZ=tempDat.delZ+instVel*Sin(alpha)
            endif
        endmethod

        /**
         * Set the knockback vector of a unit. If the unit is already in the system, the new vector
         * will replace the old one.
         *
         * Parameters:
         *      u: unit to knock back
         *      velocity: speed in units per second at which to knock the unit back
         *      angle: The angle on the XY plane to knock the unit, in radians.
         *      alpha: The angle of attack (z-axis) to knock the unit, in radians (where 0 is no AoA)
         */
        public static method setVel takes unit u, real velocity, real angle, real alpha returns nothing
            local integer index=getUnitIndexFromStack(u)
            local thistype tempDat
            local real instVel=velocity*CLOCK_PERIOD
            if index==-1 then
            set tempDat=thistype.create()
                set tempDat.u=u
                set tempDat.delX=instVel*Cos(angle)*Cos(alpha)
                set tempDat.delY=instVel*Sin(angle)*Cos(alpha)
                set tempDat.delZ=instVel*Sin(alpha)
                set dbIndex=dbIndex+1
                set knockDB[dbIndex]=tempDat
                if UnitAddAbility(tempDat.u,CROW_ID) then
                    call UnitRemoveAbility(tempDat.u,CROW_ID)
                endif
                if dbIndex==0 then
                    call TimerStart(clock,CLOCK_PERIOD,true,function thistype.p)
                endif
            else
                set tempDat=knockDB[index]
                set tempDat.delX=instVel*Cos(angle)*Cos(alpha)
                set tempDat.delY=instVel*Sin(angle)*Cos(alpha)
                set tempDat.delZ=instVel*Sin(alpha)
            endif
        endmethod


        // Instance Variables.

        // The unit being knocked back.
        private unit u

        // The knockback vector's x, y, and z components.
        private real delX
        private real delY
        private real delZ
    endstruct
endlibrary

/**
 * Add a knockback vector to a unit. If the unit is already in the system, the new vector
 * will be emulated as a secondary knockback source.
 *
 * Parameters:
 *      u: unit to knock back
 *      velocity: speed in units per second at which to knock the unit back
 *      angle: The angle on the XY plane to knock the unit, in radians.
 *      alpha: The angle of attack (z-axis) to knock the unit, in radians (where 0 is no AoA)
 *
 * Deprecated: Use Knockback3D.add() instead.
 */
function Knockback3D_add takes unit u, real velocity, real angle, real alpha returns nothing
    call Knockback3D.add(u,velocity,angle,alpha)
    debug call BJDebugMsg("Warning: Knockback3D_add() called. Use " + /*
        */ "Knockback3D.add() instead.")
endfunction

/**
 * Set the knockback vector of a unit. If the unit is already in the system, the new vector
 * will replace the old one.
 *
 * Parameters:
 *      u: unit to knock back
 *      velocity: speed in units per second at which to knock the unit back
 *      angle: The angle on the XY plane to knock the unit, in radians.
 *      alpha: The angle of attack (z-axis) to knock the unit, in radians (where 0 is no AoA)
 *
 * Deprecated: Use Knockback3D.setVel() instead.
 */
function Knockback3D_setVel takes unit u, real velocity, real angle, real alpha returns nothing
    call Knockback3D.setVel(u,velocity,angle,alpha)
    debug call BJDebugMsg("Warning: Knockback3D_setVal() called. Use " + /*
        */ "Knockback3D.setVel() instead.")
endfunction

/**
 * A function for updating the valid map co-ordinates, in case the playable map area changes
 * dynamically.
 *
 * Deprecated: Use Knockback3D.updateMapArea(r) instead.
 */
function Knockback3D_updateMapArea takes rect r returns nothing
    call Knockback3D.updateMapArea(r)
    debug call BJDebugMsg("Warning: Knockback3D_updateMapArea() called. Use " + /*
        */ "Knockback3D.updateMapArea() instead.")
endfunction


Example Test Scope


JASS:
scope test initializer i
    private function c takes nothing returns boolean
        local integer index = 0
        local unit u

        loop
            exitwhen index > 4

            set u = CreateUnit(Player(0), 'hfoo', -512. + 256.*index, 0., 90.)
            call UnitApplyTimedLife(u, 'BTLF', 5.)
            call Knockback3D.add(u, GetRandomReal(300., 1000.), bj_PI/2., GetRandomReal(0., bj_PI/2.))

            set index = index + 1
        endloop

        set u = null
        return false
    endfunction

    private function i takes nothing returns nothing
        local trigger t = CreateTrigger()
        call FogMaskEnable(false)
        call FogEnable(false)
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddCondition(t, Condition(function c))
        set t = null
    endfunction
endscope


Maintenance and Contributions

This script is maintained on GitHub. I am happy to accept well-formed pull requests that fix issues. For new features, please discuss in a GitHub issue or here in this thread.

Change Log

2016.05.08 - Fix an accidentally removed line.
2016.05.06 - Fix out of bounds bug as demonstrated by ZiBitheWand3r3r.
2014.05.07 - Now supports optional tree recognition, now supports the TerrainPathability library as an optional replacement for IsTerrainWalkable, documentation improvements and refactorings, fixed some logic issues
2012.06.09 - Replaced the "power" argument with "velocity". Initial vector is now calculated based on FIDELITY, and therefore you can use a velocity value in game units per second. Updated the add API to make sure the client understands which angles it references, and added a comment to remind them the script is for radians.
2012.06.01 #2 - Updated the 'add' and 'setVel' functions to not remove 'Arav' from units which already have it.
2012.06.01 - Initial upload to hive workshop jass submissions section.

Special Thanks

  • Anitarf and Vexorian for IsTerrainWalkable.
  • Vexorian for JassHelper.
  • The various developers of JNGP including PitzerMike and MindworX.
 
Last edited:
Level 10
Joined
May 27, 2009
Messages
495
the link is somewhere around wc3jass but can't open the site as of now...
there's also in wc3c.net.. but someone already updated it (not so sure, or perhaps it's a different lib)

anyways
JASS:
call UnitAddAbility(tempDat.u,CROW_ID)
call UnitRemoveAbility(tempDat.u,CROW_ID)
to
JASS:
if UnitAddAbility(tempDat.u,CROW_ID) then
     call UnitRemoveAbility(tempDat.u,CROW_ID)
endif
just to be safe in case the unit has the crow id already

oh if you won't mind.. i guess you may make the user have an opportunity to use a dynamic effect (or any string provided in the call) so it won't be limited to the constant provided
 
the link is somewhere around wc3jass but can't open the site as of now...
there's also in wc3c.net.. but someone already updated it (not so sure, or perhaps it's a different lib)

anyways
JASS:
call UnitAddAbility(tempDat.u,CROW_ID)
call UnitRemoveAbility(tempDat.u,CROW_ID)
to
JASS:
if UnitAddAbility(tempDat.u,CROW_ID) then
     call UnitRemoveAbility(tempDat.u,CROW_ID)
endif
just to be safe in case the unit has the crow id already

Cool, thanks! I updated it. Never seen that before actually.

oh if you won't mind.. i guess you may make the user have an opportunity to use a dynamic effect (or any string provided in the call) so it won't be limited to the constant provided

Hmm I'm not sure that's worth while. Perhaps I'll make a function addEx which allows the user to define a custom friction model. I'm not sure how useful it will be, but if you get another "vote", I'll make it happen.
 
My GUI Knockback 2D resource uses one trick to determine collision size, though it is unfortunately a bit flawed. It uses a custom inferno ability with 4 levels corresponding to collision sizes 16, 32, 48 and 64, and casts the ability on the queried point. If the point is pathable for that collision size, IssuePointOrderById returns true, if it's not, it returns false.

A better approach, and one I intend to use in the next update, is to use those 4 units combined with SetUnitPosition.
 
I forgot to respond to that.

The way I understand it, 16, 32, 48 and 64 are the only necessary values because wc3 pathing map uses cells of 16x16 units, right? So therefore this system would work with units of a collision size 0.-64.

...but that uses object data :( Come on man I've never had a problem with IsTerrainWalkable.


What if someone were to make IsTerrainWalkable which returns a real instead of a boolean, which determines the pathing availability of the target point.

Like "if IsTerrainWalkable(x,y)<16. then" ...

But then you come to the problem that there's no native to get a unit's collision size, so I don't see how either method really helps avoid the issue.
 
But then you come to the problem that there's no native to get a unit's collision size, so I don't see how either method really helps avoid the issue.

There is a function to get it in the JASS section, but that shouldn't be used for these purposes ; \.

IsTerrainWalkable is not 100% accurate ;\. If you look at the block style comments in the primary function of Is Pathable or w/e the heck it is in the JASS section, you'll come to understand why. They explain it very clearly and include diagrams ; ).
 
IsTerrainWalkable is not 100% accurate ;\. If you look at the block style comments in the primary function of Is Pathable or w/e the heck it is in the JASS section, you'll come to understand why. They explain it very clearly and include diagrams ; ).

I checked it out. That looks really nice actually, and quite useful...

But it still uses LUA to generate object data ;(

What did you think of my idea?

Like "if IsTerrainWalkable(x,y)<16. then" ...

Even if that ignores units/structures changing pathing, it could be worthwhile since it does't need any object data.

Also you didn't answer my question about collision sizing. You said that's not necessary here, but actually how can you tell the system how big a space it needs if you don't know how big the unit (in the Knockback3D stack) is?
 
public function add takes unit u, real power, real direction, real trajectory returns nothing

"real power" what power? Needs defining. For example, what does the value 2 represent as opposed to 1.
"real direction" is this the horizontal angle? Usually this keyword is "angle" as defined by Blizzard. More intuitive to stick to tried and true keywords.
"real trajectory" this one I didn't get until I looked at your description. Just name it zAngle and be done with it.

Make sure the user only passes radians by adding warnings.
 
"real power" what power? Needs defining. For example, what does the value 2 represent as opposed to 1.

Good call. That's actually quite important. I've updated the script so that it takes a velocity in game units per second instead of just some step-size offset "power". Now the script will make units fly the same speed regardless of how fast or slow FIDELITY is set. Thanks by the way.

"real direction" is this the horizontal angle? Usually this keyword is "angle" as defined by Blizzard. More intuitive to stick to tried and true keywords.
"real trajectory" this one I didn't get until I looked at your description. Just name it zAngle and be done with it.

Blizzard doesn't handle vectors in a single native. direction and trajectory make much more sense than angle and zAngle, but I've updated it anyway.

Make sure the user only passes radians by adding warnings.

I kind of liked mag's idea, but I've put a comment in the script plus an extra sentence in the API to remind the client.

Thanks for the comments guys
 
It doesn't matter, as long as you can stay on-topic most of the time, these silly comments are fine.
Also, no one reads the JASS section but Bribe, Nestharus, Luorax, Troll-Brain, LeP, you, Dirac, Berb, jim7777, Alain.Mark, mckill2009, Zwiebelchen, watermelon_1234 and I.

There are probably 2 or 3 more guys and that's probably just about it.
It's like a private section :ogre_hurrhurr:

edit
Here's a good optimization:
If the velocity is being set to 0, you could just ignore the entire knockback call.
 
Of course no one is going to pass the value 0, but he might be using some timer that constantly calls your Knockback function every once in a while, and there may be times when the velocity he calculates is actually 0.

(Warlock is a good example of a map that may use things like these heavily without any direct value passing to knockback functions)
 
Level 23
Joined
Jan 1, 2009
Messages
1,615
(So you're never going to get 0 velocity even if the unit isn't actually moving anywhere)

what?

well, OT:

Do the math for point aiming (initial velocity)

You could add a collision response too.

Else it's just kinda simple (imo)
Make physical values ( friction, restitution[bounce-coefficient (lol at that name)], etc. ) customizable for each knockback?
 
Do the math for point aiming

Because of the fact that this system uses CoR (elasticity in bounces), that would be pointless without support for callback fucntions (to interrupt the system on bounce)

You could add a collision response too

You mean as callback functions? The issue is that if I wanted callback functions, the best way to do that would be to re-write the script to use hashtables. That way callback functions can use Knockback3D.getUnitVelocity() and stuff like that without O(n). For now this is a no-go.

Make physical values ( friction, restitution[bounce-coefficient (lol at that name)], etc. ) customizable for each knockback?

I could, but it will just make the API more complex. Perhaps I could make Knocback3D.addEx() if enough people wanted this feature (it wouldn't be difficult to make).

Anyway, if this system is too simple for you, I'd say just don't use it (check the FAQ).
 
You should attach the dust effect to the unit

Have you tested this with my system? I thought about it and it seems like it would be pretty ugly to see the dust coming out of the unit as it bounces back into the air

and not create new ones that often

I could do that. Is there any reason or do you think it would simply look better?


You mean as an option instead of destroying all destructables? Why exactly?
 
Are you still updating this?

If so, I think there should be static if's for the destructable feature in general, so that you can turn off the option to destroy destructables globally if you don't need it at all.

I see that setting the min height for the destructable thing to 0 pretty much disables the destructable feature. Still, a toggle to turn it off globally might save a little bit of performance for unneccessary condition checks.

Other than that, I like the API of this and its ease-of-use. I don't need custom callbacks so it's perfect for my applications.
 
Are you still updating this?

Some day I will :) I just put it on my to-do list, since I never seem to make time for wc3 modding anymore.

If so, I think there should be static if's for the destructable feature in general, so that you can turn off the option to destroy destructables globally if you don't need it at all.

I agree 100%. I'll include this in the next update.

Other than that, I like the API of this and its ease-of-use. I don't need custom callbacks so it's perfect for my applications.

I'm glad you find it suitable. Feel free to edit it in my absence as well.
 
Updated to version 4B:

* Now supports optional tree recognition using IsDestructableTree

* Now supports TerrainPathability library as an optional replacement to IsTerrainWalkable

* Documentation improvements and refactorings

* Changed some logic issues

* Modified API: Now call using Knockback3D.add(), Knockback3D.setVel(), Knockback3D.updateMapArea(). The old methods (Knockback3D_ prefix) are deprecated and maps using them should be advised to remove them.
 
Level 23
Joined
Jan 1, 2009
Messages
1,615
I still don't understand why you don't wanna add a simple function to calculate velocity for tossing something into a point.
It could be as simple as this:
Wurst:
	function setTarget( vec2 tpos, real speed )
		var t = pos.distToVec2d(tpos) / speed
		let tangle = pos.angle2d(tpos)
		let e = getTerrainZ(tpos)
		if t < 1.
			t = 1./speed
		
		let startZVelocity = ((-gravity.z * t) / 2 - pos.z/t + e/t)
		this.setVel( vec3( tangle.radians().cos() * speed, tangle.radians().sin() * speed, startZVelocity) )

Where pos is the initial position.
 
I'm using call Knockback3D.add(u, 1200, angle, 1.0) but the unit barely even lifts off the ground...

Shouldn't 1.0 as alpha value be enough to launch the unit to a significant height? That's almost a 60 degree launch angle after all...?

Yes, that should send the unit flying through the air. Maybe there's more code to see?
 
Yes, that should send the unit flying through the air. Maybe there's more code to see?
I'll do some troubleshooting on this and post what I find.


In the meantime, there's something I'd like to ask as a feature:

Can you please add a toggle boolean to this that makes flying units not ignore pathing?
This would be extremely useful, as many users use pathing blockers in terraining to block of areas that shouldn't be accessable to the player. But most of the time, you won't "fill" these areas completely with pathing blockers, you just wall-off the entry and be done with it.

This makes it possible to enter these blocked areas with an unfortunate knockback.

So a toggle that would basicly stop the X,Y movement of airborne units when the ground is not pathable (just like it works for sliding on the ground) would help.
 
Can you please add a toggle boolean to this that makes flying units not ignore pathing?
This would be extremely useful, as many users use pathing blockers in terraining to block of areas that shouldn't be accessable to the player. But most of the time, you won't "fill" these areas completely with pathing blockers, you just wall-off the entry and be done with it.

This makes it possible to enter these blocked areas with an unfortunate knockback.

So a toggle that would basicly stop the X,Y movement of airborne units when the ground is not pathable (just like it works for sliding on the ground) would help.

Yes, this is a characteristic of the system that needs to be re-looked in general. I suggest just modifying the script to suit your needs for now, because I'm not really active right now.

It might be possible to change MAX_HEIGHT_DESTROY_DESTRUCTABLE so that all destructables are hit? I don't remember.
 
Top