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

knockback simple v.1.03

  • Like
Reactions: Barade
KNOCKBACK SIMPLE v1.03

Features:
Simple to use knockback: one function call, configurable behavior (16 possible versions), supports diffrent collision sizes
callback function, can destroy knockbacks any time, system is for ground units only
*****************************

Requires:
library IsDestructableTree * by BPower:
http://www.hiveworkshop.com/forums/jass-resources-412/snippet-isdestructabletree-248054/
library TerrainPathability * by Rising_Dusk
TerrainPathability - Wc3C.net
GUI Unit Event v2.2.1.0 * by Bribe
GUI Unit Event v2.2.1.0
*****************************

How to use:
function KnockBack takes:
  1. (code) onHitCode = code that will be executed when unit bounce/hits other unit/obstacles (structure, cliff, doodad, tree), set to "null" if not needed, (onHitCode is executed as trigger action)
  2. (unit) u = knockbacked unit
  3. (real) distance = how far unit will be moved
  4. (real) duration = how long knockback exists [sec]
  5. (real) angle = knockback angle [in radians!]
  6. (boolean) interruptSpells = if "true" it's interrupts orders/channeling spells on unit u, when knockback starts
  7. (boolean) killTrees = if "true" unit(s) will destroy trees if unit travels with >minimum speed, kb_minSpeedToKillTree
  8. (boolean) disableMovement = if "true" unit won't be able to move during knockback, it's using SetUnitPropWindow technique, so if you already using this in your map it may interfere. In this case set this boolean to false.
  9. (integer) kbType see table below, avaiable 4 types: KB_TYPE_NO_BOUNCE , KB_TYPE_SLIDE , KB_TYPE_STOP_ON_OBSTACLES , KB_TYPE_NORMAL
  10. (integer) bounceTypeFiltr see table below, avaiable 4 filters: KB_FILTR_BOUNCE_NONE , KB_FILTR_BOUNCE_ENEMY_ONLY , KB_FILTR_BOUNCE_ALLIED_ONLY , KB_FILTR_BOUNCE_ALL
  11. (string) effects - special effect played on unit(s) every given distance (variable effectOccursDistance), if null, no effect will be played
  12. (string) effectAttachPoint - atachment point for special effect, example: "origin"
  13. (real) effectOccursDistance - special effect declared above will be played every [effectOccursDistance] distance

*****************************
(code) onHitCode example:
JASS:
function OnHitExample takes nothing returns nothing
//  avaiable global variables inside this callback function, read-only:
//  (unit) kb_unit = main unit connected with this function
//  (unit) kb_obstacle_unit = unit who was hit by kb_unit (if a unit was hit)
//  (integer) kb_hitCount = counts all hits done by kb_unit
//  (integer) kb_callback use to determine 2 'events' type: KB_HIT_OBSTACLE_UNIT or //KB_HIT_THE_WALL
//  KB_HIT_THE_WALL = structure or doodad or cliff or tree, if KB_HIT_OBSTACLE_UNIT it refers to //kb_obstacle_unit
//  don't use "waits" here
  if kb_callback == KB_HIT_OBSTACLE_UNIT and kb_hitCount == 1 then
    call DisplayTextToForce(GetPlayersAll(), GetUnitName(kb_unit) + " hits the " + GetUnitName(kb_obstacle_unit))
  endif
endfunction
To destroy knockback(s) use:
function KB_DestroyKnockbackOnUnit takes unit u returns nothing or
function KB_DestroyAllKnockbacks takes nothing returns nothing


full


// *****************************
knockback types / bounce types description
kbType: KB_TYPE_NO_BOUNCE: stops on obstacles /structures/cliffs/doodads
if code "onHitCode" is specifed then it will be executed when unit hits obstacle/unit
  • KB_FILTR_BOUNCE_NONE: ignore all units pathing on my way
  • KB_FILTR_BOUNCE_ENEMY_ONLY: ignore alied units pathing on my way, stops on first enemy unit on my way
  • KB_FILTR_BOUNCE_ALLIED_ONLY: ignore enemy units pathing on my way, stops on first allied unit on my way
  • KB_FILTR_BOUNCE_ALL: stops on first unit found on my way

kb_type: KB_TYPE_SLIDE: bounce me on obstacles and:
if code "onHitCode" is specifed then it will be executed when unit hits obstacle/unit
  • KB_FILTR_BOUNCE_NONE: ignore all units pathing on my way, don't bounce me from other units, and *don't bounce any units on my way
  • KB_FILTR_BOUNCE_ENEMY_ONLY: ignore all units pathing on my way, don't bounce me from other units, but bounce unit on my way- enemies only
  • KB_FILTR_BOUNCE_ALLIED_ONLY: ignore all units pathing on my way, don't bounce me from other units, but bounce unit on my way- allied only
  • KB_FILTR_BOUNCE_ALL: ignore all units pathing on my way, don't bounce me from other units, but bounce all units on my way
--> if 2 knockbacked units meet, KB_TYPE_SLIDE has priority and it *may* overwrite existing knockback <---
--> if KB_TYPE_SLIDE will bounce other units it will be done with angle 90 degrees <--

kb_type: KB_TYPE_STOP_ON_OBSTACLES: don't bounce me on obstacles structures/cliffs/doodads/trees and:
if code "onHitCode" is specifed then it will be executed if unit hits obstacle/unit
  • KB_FILTR_BOUNCE_NONE: bounce me on any other unit, and don't bounce units on my way (allied and enemy)
  • KB_FILTR_BOUNCE_ENEMY_ONLY: ignore only allies pathing on my way, and bounce me and enemy units on my way
  • KB_FILTR_BOUNCE_ALLIED_ONLY: ignore only enemies pathing on my way, and bounce me and allied units on my way
  • KB_FILTR_BOUNCE_ALL: bounce me on other unit, and bounce all units on my way (allied and enemy)
kb_type: KB_TYPE_NORMAL: bounce me on obstacles and:
if code "onHitCode" is specifed then it will be executed if unit hits obstacle/unit
  • KB_FILTR_BOUNCE_NONE: bounce me on any other unit, and don't bounce units on my way (allied and enemy)
  • KB_FILTR_BOUNCE_ENEMY_ONLY: ignore only allies pathing on my way, and bounce me and enemy units on my way
  • KB_FILTR_BOUNCE_ALLIED_ONLY: ignore only enemies pathing on my way, and bounce me and allied units on my way
  • KB_FILTR_BOUNCE_ALL: bounce me on other unit, and bounce all units on my way (allied and enemy)
******************
old code v1.01:
JASS:
library KnockbackSimple initializer Init uses IsDestructableTree, TerrainPathability // by ZibiTheWand3r3r v.1.00

// Features:
// Simple to use knockback: one function call, configurable behavior (16 possible versions), supports diffrent collision sizes
// callback function, can destroy knockbacks any time, system is for ground units only
// *****************************

// Requires:
// library IsDestructableTree * by BPower:
//      http://www.hiveworkshop.com/forums/jass-resources-412/snippet-isdestructabletree-248054/
// library TerrainPathability * by Rising_Dusk
//      http://www.wc3c.net/showthread.php?t=103862
// GUI Unit Event v2.2.1.0 * by Bribe
//      http://www.hiveworkshop.com/threads/gui-unit-event-v2-2-1-0.201641/
// *****************************

// How to use:
// function KnockBack takes:
// (code) onHitCode = code that will be executed when unit bounce/hits other unit/obstacles (structure, cliff, doodad, tree)
//                                set to "null" if not needed, (onHitCode is executed as trigger action)
// (unit) u = knockbacked unit
// (real) distance = how far unit will be moved
// (real) duration = how long knockback exists [sec]
// (real) angle = knockback angle [in radians!]
// (boolean) interruptSpells = if "true" it's interrupts orders/channeling spells on unit u, when knockback starts
// (boolean) killTrees = if "true" unit(s) will destroy trees if unit travels with >minimum speed, kb_minSpeedToKillTree
// (boolean) disableMovement = if "true" unit won't be able to move during knockback, it's using SetUnitPropWindow
//                       technique, so if you already using this in your map it may interfere. In this case set this boolean to false.
// (integer) kbType               see table below, avaiable 4 types: KB_TYPE_NO_BOUNCE ,
//                                KB_TYPE_SLIDE , KB_TYPE_STOP_ON_OBSTACLES , KB_TYPE_NORMAL
// (integer) bounceTypeFiltr  see table below, avaiable 4 filters: KB_FILTR_BOUNCE_NONE ,
//                               KB_FILTR_BOUNCE_ENEMY_ONLY , KB_FILTR_BOUNCE_ALLIED_ONLY , KB_FILTR_BOUNCE_ALL
// (string) effects - special effect played on unit(s) every given distance (variable effectOccursDistance),
//                          if null, no effect will be played
// (real) effectOccursDistance - special effect declared above will be played every [effectOccursDistance] distance
// *****************************

// (code) onHitCode example:
// function OnHitExample takes nothing returns nothing
//      avaiable global variables inside this callback function, read-only:
//      (unit) kb_unit = main unit connected with this function
//      (unit) kb_obstacle_unit = unit who was hit by kb_unit (if a unit was hit)
//      (integer) kb_hitCount = counts all hits done by kb_unit
//      (integer) kb_callback use to determine 2 'events' type: KB_HIT_OBSTACLE_UNIT or KB_HIT_THE_WALL
//         KB_HIT_THE_WALL = structure or doodad or cliff or tree, if KB_HIT_OBSTACLE_UNIT it refers to kb_obstacle_unit
//      don't use "waits" here
//   if kb_callback == KB_HIT_OBSTACLE_UNIT and kb_hitCount == 1 then
//        call DisplayTextToForce(GetPlayersAll(), GetUnitName(kb_unit) + " hits the " + GetUnitName(kb_obstacle_unit))
//   endif
// endfunction

// *****************************
// knockback types / bounce types description
// kbType: KB_TYPE_NO_BOUNCE: stops on obstacles /structures/cliffs/doodads
//  if code "onHitCode" is specifed then it will be executed when unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      ignore all units pathing on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore alied units pathing on my way, stops on first enemy unit on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore enemy units pathing on my way, stops on first allied unit on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      stops on first unit found on my way

// kb_type: KB_TYPE_SLIDE: bounce me on obstacles and:
// if code "onHitCode" is specifed then it will be executed when unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      ignore all units pathing on my way, don't bounce me from other units, and *don't bounce any units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore all units pathing on my way, don't bounce me from other units, but bounce unit on my way- enemies only
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore all units pathing on my way, don't bounce me from other units, but bounce unit on my way- allied only
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      ignore all units pathing on my way, don't bounce me from other units, but bounce all units on my way
//  --> if 2 knockbacked units meet, KB_TYPE_SLIDE has priority and it *may* overwrite existing knockback <---
//  --> if KB_TYPE_SLIDE will bounce other units it will be done with angle 90 degrees <--

// kb_type: KB_TYPE_STOP_ON_OBSTACLES: don't bounce me on obstacles structures/cliffs/doodads/trees and:
// if code "onHitCode" is specifed then it will be executed if unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      bounce me on any other unit, and don't bounce units on my way (allied and enemy)
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore only allies pathing on my way, and bounce me and enemy units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore only enemies pathing on my way, and bounce me and allied units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      bounce me on other unit, and bounce all units on my way (allied and enemy)

// kb_type: KB_TYPE_NORMAL: bounce me on obstacles and:
// if code "onHitCode" is specifed then it will be executed if unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      bounce me on any other unit, and don't bounce units on my way (allied and enemy)
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore only allies pathing on my way, and bounce me and enemy units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore only enemies pathing on my way, and bounce me and allied units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      bounce me on other unit, and bounce all units on my way (allied and enemy)
//

private function KB_FilterOutAbility takes integer abiId returns nothing
    set kb_filterOutAbiInteger = kb_filterOutAbiInteger + 1
    set kb_filterOutAbility[kb_filterOutAbiInteger] = abiId
endfunction

//*****************************************************************
// **************** BEGIN OF USER CONFIGURABLES **********************
private function KB_AdvancedUserConfigurables takes nothing returns nothing
    // these will apply >globaly< to knockback system:

    set kb_minSpeedToKillTree = 500.00 // minimum unit speed that allow to destroy tree, only positive values allowed
    set kb_reduceSpeedOnHitFactor = 20.00 // allowed range: 0 ... 100
    // (very high values of kb_reduceSpeedOnHitFactor may look ugly and cause unit to stop instantly)
    // following settings will filter out units with abilities "ghost" "ghosts visible" and with buff "windwalk"
    // so units with these abilities will not be bounced, as they already can be walked over
    call KB_FilterOutAbility('Agho')
    call KB_FilterOutAbility('Aeth')
    call KB_FilterOutAbility('BOwk')
    // you can add more abilities/buffs here like 3 above
    // call KB_FilterOutAbility('YourAbiId')
endfunction
// **************** END OF USER CONFIGURABLES ************************
//*****************************************************************

globals
    group                             kb_ug
    real array                      kb_distance
    real array                      kb_time
    real array                      kb_angle
    real array                      kb_d1
    real array                      kb_d2
    real array                      kb_sin
    real array                      kb_cos
    boolean array               kb_killTrees
    boolean array               kb_interruptSpells
    boolean array               kb_disableMovement
    integer array                kb_knockbackType
    integer array                kb_bounceFiltrType
    string array                  kb_effect
    real array                      kb_effectOccursDistance
    real array                      kb_lastDistanceEffect
    real array                      kb_collision
    real array                      kb_distanceLeft
    real array                      kb_timeLeft
    trigger array                 kb_trg_onHit
    trigger                           kb_trg_loop
    integer array                 kb_hitCounter               // for callback function
    integer                           kb_hitCount = 0           // for callback function
    unit                                kb_unit = null                // for callback function
    unit                                kb_obstacle_unit = null  // for callback function
    player                            kb_player
    rect                                kb_rect
    group                             kb_group
    timer                              kb_timer
    real                                 kb_minSpeedToKillTree = 500.00
    real                                 kb_reduceSpeedOnHitFactor = 10.00
    real                                 kb_reduceSpeedOnHit = 1.00
    constant real                 KB_INTERVAL = 0.03125
    constant real                 KB_STEPS_PER_SEC = 1.00 / KB_INTERVAL

    boolexpr array                       kb_filtr
    constant integer                    KB_FILTR_BOUNCE_NONE = 0
    constant integer                    KB_FILTR_BOUNCE_ENEMY_ONLY = 2
    constant integer                    KB_FILTR_BOUNCE_ALLIED_ONLY = 3
    constant integer                    KB_FILTR_BOUNCE_ALL = 4
    constant integer                    KB_TYPE_NO_BOUNCE = 0
    constant integer                    KB_TYPE_SLIDE = 10
    constant integer                    KB_TYPE_STOP_ON_OBSTACLES = 20
    constant integer                    KB_TYPE_NORMAL = 30
    constant integer                    KB_HIT_OBSTACLE_UNIT = 101
    constant integer                    KB_HIT_THE_WALL = 102
    integer                                   kb_callback
    integer                                   kb_filterOutAbiInteger = 0
    integer array                         kb_filterOutAbility
endglobals

//-----------------------------------------------------------
//x, y, = centerX, centerY
//-----------------------------------------------------------
function KB_IsPointWalkableForSize takes real x, real y, real whatSize returns boolean  //x, y, = centerX, centerY
    if whatSize <= 16.00 then //one item check:
        return IsTerrainWalkable(x, y)
    endif // for larger units: 5 item checks
    return IsTerrainWalkable(x, y) and IsTerrainWalkable(x+32.00, y) and IsTerrainWalkable(x-32.00, y) and IsTerrainWalkable(x, y+32.00) and IsTerrainWalkable(x, y-32.00)
endfunction
//------------------------------------------------------------
//------------------------------------------------------------
function KB_GetBounceAngle takes real angle, real x, real y, integer id returns real
    local real newAngleVer1 = ( - angle )
    local real newAngleVer2 = ( - bj_PI - angle ) // for negative angles
    if angle>0.00 then
        set newAngleVer2 = (bj_PI - angle)
    endif
    //++++
    if KB_IsPointWalkableForSize(x+kb_d1[id] * Cos(newAngleVer1), y+kb_d1[id] * Sin(newAngleVer1), kb_collision[id]) then
        return newAngleVer1
    elseif KB_IsPointWalkableForSize(x+kb_d1[id] * Cos(newAngleVer2), y+kb_d1[id] * Sin(newAngleVer2), kb_collision[id]) then
        return newAngleVer2
    else //3rd version - reverse direction:
        set newAngleVer1 = (angle + bj_PI) // for negative angle
        if angle>0.00 then
            set newAngleVer1 = (angle - bj_PI)
        endif
    endif
    return newAngleVer1
endfunction
//------------------------------------------------------------
//-----------------------------x/y = obstacleUnit X/Y
function KB_GetBounceAngle90 takes real angle, real x, real y, real unitX, real unitY returns real
    local real newAnglePlus = ModuloReal((angle + (bj_PI/2.00)), (2.00*bj_PI))  // +90
    local real newAngleMinus = ModuloReal((angle - (bj_PI/2.00)), (2.00*bj_PI)) // -90
    local real x1 = x+32.00 * Cos(newAnglePlus)
    local real y1 = y+32.00 * Sin(newAnglePlus)
    local real x2 = x+32.00 * Cos(newAngleMinus)
    local real y2 = y+32.00 * Sin(newAngleMinus)
    //compare distances
    if ((x1-unitX) * (x1-unitX) + (y1-unitY) * (y1-unitY)) > ((x2-unitX) * (x2-unitX) + (y2-unitY) * (y2-unitY)) then
        return newAnglePlus
    endif
    return newAngleMinus
endfunction

//-------------------------------------------------------------------------------
//-------------Zwiebelchen:-------------------------------------------
// The difference between GroupEnumUnitsInRange and IsUnitInRangeXY is, that the former will only enumerate units
// whose coordinate center is inside the radius.
// The IsUnitInRange natives will already return true if only a part of the unit is inside the radius.
//--------------------------------------------------------
// KB_GetUnitCollisionSize checks up to 64 unit collision size  // returns values from 8 ..to.. 64
//--------------------------------------------------------
function KB_GetUnitCollisionSize takes unit u, real unitX, real unitY returns real
    local real i=0.00
    local real size=64.00
    loop
        exitwhen i==64.00
        if not IsUnitInRangeXY(u, (unitX+i), unitY, 0.00) then
            set size = (i-1.00)
            exitwhen true
        endif
        set i=i+1.00
    endloop
    if size<8.00 then
        return 8.00
    endif
    return size
endfunction

//------------------------------------------------------------
function IsUnitAlive takes unit u returns boolean
    return not (GetUnitTypeId(u) == 0 or IsUnitType(u, UNIT_TYPE_DEAD))
endfunction
//------------------------------------------------------------
//  DisableUnitMovement /* v1.0.0.1
//    *   Disables unit movement. They can still turn, but will stay in
//    *   place. It simulates an ensnare-like effect, except that it will
//    *   not ground units, it does not have buffs, does not interrupt
//    *   channeled casts and appears to have no downsides.
//    *   Full credits to WaterKnight for discovering this technique.
//--------------------------------------------------------------------  
function DisableUnitMovement takes unit u returns nothing
    call SetUnitPropWindow(u, 0)
endfunction
//--------------------------------------------------------------------
function EnableUnitMovement takes unit u returns nothing
    call SetUnitPropWindow(u, GetUnitDefaultPropWindow(u) * bj_DEGTORAD)
endfunction
//------------------------------------------------------------
//------------------------------------------------------------
function KBFiltr_1 takes unit u returns boolean
    local integer a=1
    local integer id = GetUnitUserData(u)
    local boolean isKnockbacked = IsUnitInGroup(u, kb_group)
    if IsUnitAlive(u) and (not IsUnitType(u, UNIT_TYPE_FLYING)) and (not IsUnitType(u, UNIT_TYPE_STRUCTURE)) and (IsUnitVisible(u, kb_player)) then
        // also filters out units who sliding: (don't count as obstacle) and with allied/enemy filter
        if not (isKnockbacked and kb_knockbackType[id] == KB_TYPE_SLIDE) then
            if not (isKnockbacked and kb_knockbackType[id] == KB_TYPE_NO_BOUNCE and kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE) then
                if not (IsUnitAlly(u, kb_player) and isKnockbacked and kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_ENEMY_ONLY) then
                    if not (IsUnitEnemy(u, kb_player) and isKnockbacked and kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_ALLIED_ONLY) then
                        loop
                            exitwhen a>kb_filterOutAbiInteger
                            if GetUnitAbilityLevel(u, kb_filterOutAbility[a]) > 0 then
                                return false
                            endif
                            set a=a+1
                        endloop
                        return true  
                    endif
                endif
            endif
        endif
    endif
    return false
endfunction
//--------
//--------
function KBFiltr_groundNotStruc takes nothing returns boolean
    return KBFiltr_1(GetFilterUnit())
endfunction
// set kb_player before confirm this 2 checks!
function KBFiltr_groundNotStrucAlliedOnly takes nothing returns boolean
    return KBFiltr_1(GetFilterUnit()) and IsUnitAlly(GetFilterUnit(), kb_player)
endfunction
function KBFiltr_groundNotStrucEnemyOnly takes nothing returns boolean
    return KBFiltr_1(GetFilterUnit()) and IsUnitEnemy(GetFilterUnit(), kb_player)
endfunction
//------------------------------------------------------------------------
function KB_OnRemoveFromGame takes nothing returns nothing  
    call GroupRemoveUnit(kb_group, udg_UDexUnits[udg_UDex])
    //clean:
    if kb_trg_onHit[udg_UDex] != null then
        call DestroyTrigger(kb_trg_onHit[udg_UDex])
        set kb_trg_onHit[udg_UDex] = null
    endif
    set kb_effect[udg_UDex] = null
    if FirstOfGroup(kb_group)==null then
        call PauseTimer(kb_timer)
    endif
endfunction
//----------------------------------------------------------------------------
function KB_DestroyAllEnum takes nothing returns nothing
    local unit u = GetEnumUnit()
    local integer id = GetUnitUserData(u)
    call GroupRemoveUnit(kb_group, u)
    if kb_trg_onHit[id] != null then
        call DestroyTrigger(kb_trg_onHit[id])
        set kb_trg_onHit[id] = null
    endif
    call EnableUnitMovement(u)
    set kb_effect[id] = null
    if FirstOfGroup(kb_group)==null then
        call PauseTimer(kb_timer)
    endif
    set u=null
endfunction
//----------------------------
function KB_DestroyAllKnockbacks takes nothing returns nothing
    call ForGroup(kb_group, function KB_DestroyAllEnum)
endfunction
//----------------------------
function KB_DestroyKnockbackOnUnit takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    call GroupRemoveUnit(kb_group, u)
    if kb_trg_onHit[id] != null then
        call DestroyTrigger(kb_trg_onHit[id])
        set kb_trg_onHit[id] = null
    endif
    call EnableUnitMovement(u)
    set kb_effect[id] = null
    if FirstOfGroup(kb_group)==null then
        call PauseTimer(kb_timer)
    endif
endfunction
//=======================================================

function KB_CheckAllowedTypes takes integer kbType, integer bounceTypeFiltr returns boolean
    if kbType == KB_TYPE_NO_BOUNCE or kbType == KB_TYPE_SLIDE or kbType == KB_TYPE_STOP_ON_OBSTACLES or kbType == KB_TYPE_NORMAL then
        if bounceTypeFiltr == KB_FILTR_BOUNCE_NONE or bounceTypeFiltr == KB_FILTR_BOUNCE_ENEMY_ONLY or bounceTypeFiltr == KB_FILTR_BOUNCE_ALLIED_ONLY or bounceTypeFiltr == KB_FILTR_BOUNCE_ALL then
            return true
        endif
    endif
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Knockback error: wrong integer pass to function")
    return false
endfunction
//-----------------------------------------------------------------------------------------
//-----main function --------------------------------------------------------------------
function KnockBack takes code onHitCode, unit u, real distance, real duration, real angle, boolean interruptSpells, boolean killTrees, boolean disableMovement, integer kbType, integer bounceTypeFiltr, string effects, real effectOccursDistance returns nothing
    local integer id = GetUnitUserData(u)
    local integer q = R2I(duration / KB_INTERVAL) // (Number of steps)
    local real x = GetUnitX(u)
    local real y = GetUnitY(u)
    if GetUnitDefaultMoveSpeed(u) == 0.00 or (not KB_CheckAllowedTypes(kbType, bounceTypeFiltr)) then //protection
        return
    endif
    set kb_effect[id] = null
    if kb_trg_onHit[id] != null then
        call DestroyTrigger(kb_trg_onHit[id])
        set kb_trg_onHit[id] = null
    endif
    if onHitCode != null then
        set kb_trg_onHit[id] = CreateTrigger()
        call TriggerAddAction(kb_trg_onHit[id], onHitCode)
    endif

    set kb_distance[id] = distance
    set kb_distanceLeft[id] = distance
    set kb_time[id] = duration
    set kb_timeLeft[id] = duration
    set kb_hitCounter[id] = 0
    set kb_angle[id] = angle
    set kb_sin[id] = Sin(angle)
    set kb_cos[id] = Cos(angle)
    set kb_d1[id] = 2 * kb_distance[id] / (q + 1)  // the "bit" in "moving a unit a bit a time"
    set kb_d2[id] = kb_d1[id] / q  // will decrease d1 with each execution
    set kb_killTrees[id] = killTrees
    set kb_interruptSpells[id] = interruptSpells
    set kb_disableMovement[id] = disableMovement
    set kb_knockbackType[id] = kbType
    set kb_bounceFiltrType[id] = bounceTypeFiltr

    if kb_interruptSpells[id] then
        call SetUnitPosition(u, x, y)
    endif
    if kb_disableMovement[id] then
        call DisableUnitMovement(u)
    endif
    if effects != null then
        set kb_effect[id] = effects
    endif
    set kb_lastDistanceEffect[id] = kb_distance[id]
    set kb_effectOccursDistance[id] = effectOccursDistance

    set kb_collision[id] = KB_GetUnitCollisionSize(u, x, y)

    if FirstOfGroup(kb_group)==null then
        call TimerStart(kb_timer, KB_INTERVAL, true, null)
    endif
    call GroupAddUnit(kb_group, u)
endfunction

// KBLOOP part =============================================

function KB_BounceEvent takes unit u, integer id, integer eventType returns nothing
    if kb_trg_onHit[id] != null then
        set kb_callback = eventType //  KB_HIT_OBSTACLE_UNIT or KB_HIT_THE_WALL
        set kb_unit = u
        set kb_hitCounter[id] = kb_hitCounter[id] + 1
        set kb_hitCount = kb_hitCounter[id]
        call TriggerExecute(kb_trg_onHit[id])
    endif
endfunction
//------------------------------------------------------------------------------------------------
// use this for bounce from structures/cliffs/doodads ONLY:
//------------------------------------------------------------------------------------------------
function KB_BounceUnit takes unit u, integer id, real unitX, real unitY returns nothing
    set kb_angle[id] = KB_GetBounceAngle(kb_angle[id], unitX, unitY, id) //change angle
    set kb_sin[id] = Sin(kb_angle[id])
    set kb_cos[id] = Cos(kb_angle[id])
    call SetUnitX(u, unitX + kb_d1[id] * kb_cos[id])
    call SetUnitY(u, unitY + kb_d1[id] * kb_sin[id])
    set kb_d1[id] = kb_d1[id] * kb_reduceSpeedOnHit // reducing distance kb_d1
    call KB_BounceEvent(u, id, KB_HIT_THE_WALL) //fire hit event
endfunction
//------------------------------------------------------------------------------------------------
//-this for better visual effect --
//-KB_BounceEvent is not included here inside this func -----------
function KB_BounceUnitReverseWithMinSpeed takes unit u, integer id returns nothing
    local real angle = (kb_angle[id] + bj_PI)
    if kb_angle[id]>0.00 then // reverse direction:
        set angle = (kb_angle[id] - bj_PI)
    endif // initiate new kb, so old one will be ended:
    call KnockBack(null, u, 25.00, 0.30, angle, false, kb_killTrees[id], kb_disableMovement[id], KB_TYPE_SLIDE, KB_FILTR_BOUNCE_NONE, null, 1.00)
endfunction
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
function KB_TwoUnitsHit takes unit mainUnit, integer id, unit obstacleUnit, integer obstacleId returns nothing
    local integer kb1 = kb_knockbackType[id]
    local integer kb2
    if IsUnitInGroup(obstacleUnit, kb_group) then
        set kb2 = kb_knockbackType[obstacleId]
    
        if kb2 == KB_TYPE_NO_BOUNCE then
            set kb_obstacle_unit = obstacleUnit
            call KB_BounceEvent(mainUnit, id, KB_HIT_OBSTACLE_UNIT)
            set kb_obstacle_unit = mainUnit
            call KB_BounceEvent(obstacleUnit, obstacleId, KB_HIT_OBSTACLE_UNIT)
            if kb2 == kb1 then // both "no-bounce"
                call KB_BounceUnitReverseWithMinSpeed(mainUnit, id)
                call KB_BounceUnitReverseWithMinSpeed(obstacleUnit, obstacleId)
            endif

        elseif (kb2 == KB_TYPE_NORMAL or kb2 == KB_TYPE_STOP_ON_OBSTACLES) and (kb1 == KB_TYPE_NORMAL or kb1 == KB_TYPE_STOP_ON_OBSTACLES or kb1 == KB_TYPE_SLIDE) then
            set kb_obstacle_unit = mainUnit
            call KB_BounceEvent(obstacleUnit, obstacleId, KB_HIT_OBSTACLE_UNIT)
            set kb_obstacle_unit = obstacleUnit
            call KB_BounceEvent(mainUnit, id, KB_HIT_OBSTACLE_UNIT)
        endif
  
    else // obstacle unit *not* in kb_group:
        set kb_obstacle_unit = obstacleUnit
        call KB_BounceEvent(mainUnit, id, KB_HIT_OBSTACLE_UNIT)
        if kb1 == KB_TYPE_NO_BOUNCE then
            call KB_BounceUnitReverseWithMinSpeed(mainUnit, id)
        endif
    endif
endfunction

//-------------------------------------------------------------------------------
// kb_u primary unit, unitX = GetUnitX(kb_u)
// bouncePrimaryUnit - if "true" will bounce kb_u in opposite direction to new unit on way
// bounceNewUnit -if "true" will bounce new unit in opposite direction to kb_u unit
function KB_CheckUnitsAroundAndBounce takes unit kb_u, integer kb_id, real unitX, real unitY, real targetX, real targetY, integer filtrNr, boolean bouncePrimaryUnit, boolean bounceNewUnit returns boolean
    local unit obst_u
    local integer obst_id
    local real obstX
    local real obstY
    local integer foundUnits=0
    local real angle
    local integer t
    local integer b
    local real Tx
    local real Ty
    local real c

    set kb_player = GetOwningPlayer(kb_u)
    call GroupEnumUnitsInRange(kb_ug, targetX, targetY, 150.00, kb_filtr[filtrNr]) // alive+ground+not structure
    loop
        set obst_u = FirstOfGroup(kb_ug)
        exitwhen obst_u == null
        set obstX = GetUnitX(obst_u)
        set obstY = GetUnitY(obst_u)
        set c = KB_GetUnitCollisionSize(obst_u, obstX, obstY) //pick bigger collision:
        if c < kb_collision[kb_id] then
            set c = kb_collision[kb_id]
        endif
        if obst_u != kb_u and IsUnitInRange(obst_u, kb_u, c) then
            set foundUnits = 1
            set obst_id = GetUnitUserData(obst_u)
            call KB_TwoUnitsHit(kb_u, kb_id, obst_u, obst_id)
            // if bounce primary unit:
            if bouncePrimaryUnit then
                set angle = Atan2(unitY - obstY, unitX - obstX) // reverse angle
                set Tx = unitX+kb_d1[kb_id] * Cos(angle)
                set Ty = unitY+kb_d1[kb_id] * Sin(angle)          
                if KB_IsPointWalkableForSize(Tx, Ty, kb_collision[kb_id]) then
                    call SetUnitX(kb_u, Tx)
                    call SetUnitY(kb_u, Ty)
                    set kb_angle[kb_id] = angle //change angle
                    set kb_sin[kb_id] = Sin(kb_angle[kb_id])
                    set kb_cos[kb_id] = Cos(kb_angle[kb_id])
                    set kb_d1[kb_id] = kb_d1[kb_id] * kb_reduceSpeedOnHit // reducing distance kb_d1
                else // not walkable
                    set kb_d1[kb_id] = 0.00 // end knockback
                endif
            else // if not bounce, then move for slide type only:
                if kb_knockbackType[kb_id] == KB_TYPE_SLIDE then
                    call SetUnitX(kb_u, targetX)
                    call SetUnitY(kb_u, targetY)
                endif
            endif      

            // if bounce obstacle unit:
            if bounceNewUnit then
                set angle = Atan2(obstY - unitY, obstX - unitX) // reverse angle
                set c = KB_GetUnitCollisionSize(obst_u, obstX, obstY)
                if IsUnitInGroup(obst_u, kb_group) then
                    if kb_knockbackType[kb_id] == KB_TYPE_SLIDE then // hit by sliding unit, initiate new slide, slide overwrites existing one
                        if kb_timeLeft[kb_id] * kb_distanceLeft[kb_id] > 1.00 then //don't run very low knockbacks! <---
                            set b = kb_bounceFiltrType[kb_id] // new bounce filter:
                            if kb_bounceFiltrType[kb_id] == KB_FILTR_BOUNCE_ENEMY_ONLY then
                                set b = KB_FILTR_BOUNCE_ALLIED_ONLY
                            endif
                            set angle = KB_GetBounceAngle90(kb_angle[kb_id], obstX, obstY, unitX, unitY) //angle 90 degrees
                            if KB_IsPointWalkableForSize((obstX+kb_d1[obst_id] * Cos(angle)), (obstY+kb_d1[obst_id] * Sin(angle)), c) then
                                call KnockBack(null, obst_u, kb_distanceLeft[kb_id], kb_timeLeft[kb_id], angle, kb_interruptSpells[kb_id], kb_killTrees[kb_id], kb_disableMovement[kb_id], kb_knockbackType[kb_id], b, kb_effect[kb_id], kb_effectOccursDistance[kb_id])
                            endif
                        endif
                    else // hit by non-sliding unit, update existing kb:
                        set Tx = obstX+kb_d1[obst_id] * Cos(angle)
                        set Ty = obstY+kb_d1[obst_id] * Sin(angle)
                        if KB_IsPointWalkableForSize(Tx, Ty, c) then
                            call SetUnitX(obst_u, Tx)
                            call SetUnitY(obst_u, Ty)
                            set kb_angle[obst_id] = angle //change angle
                            set kb_sin[obst_id] = Sin(kb_angle[obst_id])
                            set kb_cos[obst_id] = Cos(kb_angle[obst_id])
                            set kb_d1[obst_id] = kb_d1[obst_id] * kb_reduceSpeedOnHit // reducing distance kb_d1
                        endif
                    endif
                else // unit NOT in kb_group, initial new kb:
                    if kb_timeLeft[kb_id] * kb_distanceLeft[kb_id] > 1.00 then //don't run very low knockbacks! <---
                        set b = kb_bounceFiltrType[kb_id] // new bounce filter:
                        if b == KB_FILTR_BOUNCE_ENEMY_ONLY then
                            set b = KB_FILTR_BOUNCE_ALLIED_ONLY
                        endif                  
                        if kb_knockbackType[kb_id] == KB_TYPE_SLIDE then // if obstacle hits by sliding unit, change angle to 90
                            set angle = KB_GetBounceAngle90(kb_angle[kb_id], obstX, obstY, unitX, unitY) //angle 90 degrees
                        endif
                        if KB_IsPointWalkableForSize((obstX+kb_d1[obst_id] * Cos(angle)), (obstY+kb_d1[obst_id] * Sin(angle)), c) then
                            call KnockBack(null, obst_u, kb_distanceLeft[kb_id], kb_timeLeft[kb_id], angle, kb_interruptSpells[kb_id], kb_killTrees[kb_id], kb_disableMovement[kb_id], kb_knockbackType[kb_id], b, kb_effect[kb_id], kb_effectOccursDistance[kb_id])
                        endif
                    endif
                endif
            endif
            //bounce obstacle unit:end
        endif
        call GroupRemoveUnit(kb_ug, obst_u)
    endloop
    call GroupClear (kb_ug)
  
    set obst_u=null
    return foundUnits > 0
endfunction
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
function KB_MoveUnit takes unit u, integer id, real unitX, real unitY, real targetX, real targetY returns nothing
    local boolean isWalkable = KB_IsPointWalkableForSize(targetX, targetY, kb_collision[id])

    if kb_knockbackType[id] == KB_TYPE_NO_BOUNCE then // no bounce at all
        if not isWalkable then
            call KB_BounceEvent(u, id, KB_HIT_THE_WALL) //fire hit event, obstacle
            call KB_BounceUnitReverseWithMinSpeed(u, id)
        else    // walkable
            if kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE then
                call SetUnitX(u, targetX)
                call SetUnitY(u, targetY)
            else // 3 other filters:  
                if (not KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, kb_bounceFiltrType[id], false, false)) then
                    call SetUnitX(u, targetX)
                    call SetUnitY(u, targetY)          
                endif
            endif
        endif
  
    //---------------------------------SLIDE:-------------------------------------------------  
    elseif kb_knockbackType[id] == KB_TYPE_SLIDE then
        if not isWalkable then
            call KB_BounceUnit(u, id, unitX, unitY) //fires hit event
        else
            if kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE then
                call SetUnitX(u, targetX)
                call SetUnitY(u, targetY)
            else // 3 other filters:
                if not KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, kb_bounceFiltrType[id], false, true) then
                    call SetUnitX(u, targetX)
                    call SetUnitY(u, targetY)
                endif      
            endif
        endif
  
    //--------------------------------- KB_TYPE_NORMAL / KB_TYPE_STOP_ON_OBSTACLES  -------------------  
    elseif kb_knockbackType[id] == KB_TYPE_NORMAL or kb_knockbackType[id] == KB_TYPE_STOP_ON_OBSTACLES then
        if not isWalkable then
            if kb_knockbackType[id] == KB_TYPE_NORMAL then
                call KB_BounceUnit(u, id, unitX, unitY)      
            elseif kb_knockbackType[id] == KB_TYPE_STOP_ON_OBSTACLES then
                call KB_BounceEvent(u, id, KB_HIT_THE_WALL) //fire hit event, obstacle
                call KB_BounceUnitReverseWithMinSpeed(u, id)
            endif
        else //walkable:---
            if kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE then
                if not KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, KB_FILTR_BOUNCE_ALL, true, false) then
                    call SetUnitX(u, targetX)
                    call SetUnitY(u, targetY)
                endif
            else // 3 other filters
                if not KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, kb_bounceFiltrType[id], true, true) then
                    call SetUnitX(u, targetX)
                    call SetUnitY(u, targetY)
                endif      
            endif
        endif

    endif
endfunction
//-----------------------------------------------------------------
function KB_KillTree takes nothing returns nothing
    call KillTree(GetEnumDestructable())
endfunction
//------------------------------------------------------------------      
function KB_ShowEffect takes unit u, integer id returns nothing
    if kb_effect[id] != null and (kb_distanceLeft[id] < (kb_lastDistanceEffect[id] - kb_effectOccursDistance[id])) then
        call DestroyEffect(AddSpecialEffectTarget(kb_effect[id], u, "origin"))      
        set kb_lastDistanceEffect[id] = kb_distanceLeft[id]
    endif
endfunction
//------------------------------------------------------------------
function KB_LoopEnum takes nothing returns nothing
    local unit u = GetEnumUnit()
    local integer id = GetUnitUserData(u)
    local real unitX = GetUnitX(u)
    local real unitY = GetUnitY(u)
    local real targetX = unitX + kb_d1[id] * kb_cos[id]
    local real targetY = unitY + kb_d1[id] * kb_sin[id]

    if (not IsUnitAlive(u)) or kb_d1[id] <= 0.00 then
        call KB_DestroyKnockbackOnUnit(u)
        set u=null
        return
    endif
    if kb_killTrees[id] and ((kb_d1[id]*KB_STEPS_PER_SEC) > kb_minSpeedToKillTree) then
        call MoveRectTo(kb_rect, targetX, targetY)
        call EnumDestructablesInRect(kb_rect, null, function KB_KillTree)
    endif
    call KB_ShowEffect(u, id)
    call KB_MoveUnit(u, id, unitX, unitY, targetX, targetY)

    set kb_d1[id] = kb_d1[id] - kb_d2[id] // decrease speed
    set kb_distanceLeft[id] = kb_distanceLeft[id] - kb_d1[id] // for additional kb-ed units
    set kb_timeLeft[id] = kb_timeLeft[id] - KB_INTERVAL // for additional kb-ed units
    set u=null
endfunction
//--------------------------------------------------------------------------
function KB_Loop takes nothing returns boolean
    call ForGroup(kb_group, function KB_LoopEnum)
    return false
endfunction
//=======================================================
//=======================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    set kb_ug = CreateGroup()
    set kb_group = CreateGroup()
    set kb_timer = CreateTimer()
    set kb_rect = Rect(0.0, 0.0, 256.0, 256.0)
    set kb_trg_loop = CreateTrigger()
    call TriggerRegisterTimerExpireEvent(kb_trg_loop, kb_timer) //timer kb_timer expires
    call TriggerAddCondition(kb_trg_loop, Condition(function KB_Loop))
    //----------------------------------------------------
    call KB_AdvancedUserConfigurables()
    if kb_minSpeedToKillTree < 0.00 then
        set kb_minSpeedToKillTree = 0.00
    endif
    if kb_reduceSpeedOnHitFactor < 0.00 then
        set kb_reduceSpeedOnHitFactor = 0.00
    elseif kb_reduceSpeedOnHitFactor > 100.00 then
        set kb_reduceSpeedOnHitFactor = 100.00
    endif
    set kb_reduceSpeedOnHit = 1.00 - (kb_reduceSpeedOnHitFactor / 100.00) // for kb_d1, range: 0.00 ... 1.00

    //----------------------------------------------------  
    set kb_filtr[KB_FILTR_BOUNCE_NONE] =            Condition(function KBFiltr_groundNotStruc)
    set kb_filtr[KB_FILTR_BOUNCE_ALL] =                Condition(function KBFiltr_groundNotStruc)
    set kb_filtr[KB_FILTR_BOUNCE_ALLIED_ONLY] = Condition(function KBFiltr_groundNotStrucAlliedOnly)
    set kb_filtr[KB_FILTR_BOUNCE_ENEMY_ONLY] = Condition(function KBFiltr_groundNotStrucEnemyOnly)
    call TriggerRegisterVariableEvent( t, "udg_UnitIndexEvent", EQUAL, 2.00 ) // remove unit detection
    call TriggerAddAction(t, function KB_OnRemoveFromGame)
    set t=null
endfunction
endlibrary

old code v1.02:
JASS:
library KnockbackSimple initializer Init uses IsDestructableTree, TerrainPathability // by ZibiTheWand3r3r v.1.02

// Features:
// Simple to use knockback: one function call, configurable behavior (16 possible versions), supports diffrent collision sizes
// callback function, can destroy knockbacks any time, system is for ground units only
// *****************************

// Requires:
// library IsDestructableTree * by BPower:
//      http://www.hiveworkshop.com/forums/jass-resources-412/snippet-isdestructabletree-248054/
// library TerrainPathability * by Rising_Dusk
//      http://www.wc3c.net/showthread.php?t=103862
// GUI Unit Event v2.2.1.0 * by Bribe
//      http://www.hiveworkshop.com/threads/gui-unit-event-v2-2-1-0.201641/
// *****************************

// How to use:
// function KnockBack takes:
// (code) onHitCode = code that will be executed when unit bounce/hits other unit/obstacles (structure, cliff, doodad, tree)
//                                set to "null" if not needed, (onHitCode is executed as trigger action)
// (unit) u = knockbacked unit
// (real) distance = how far unit will be moved
// (real) duration = how long knockback exists [sec]
// (real) angle = knockback angle [in radians!]
// (boolean) interruptSpells = if "true" it's interrupts orders/channeling spells on unit u, when knockback starts
// (boolean) killTrees = if "true" unit(s) will destroy trees if unit travels with >minimum speed, kb_minSpeedToKillTree
// (boolean) disableMovement = if "true" unit won't be able to move during knockback, it's using SetUnitPropWindow
//                       technique, so if you already using this in your map it may interfere. In this case set this boolean to false.
// (integer) kbType               see table below, avaiable 4 types: KB_TYPE_NO_BOUNCE ,
//                                KB_TYPE_SLIDE , KB_TYPE_STOP_ON_OBSTACLES , KB_TYPE_NORMAL
// (integer) bounceTypeFiltr  see table below, avaiable 4 filters: KB_FILTR_BOUNCE_NONE ,
//                               KB_FILTR_BOUNCE_ENEMY_ONLY , KB_FILTR_BOUNCE_ALLIED_ONLY , KB_FILTR_BOUNCE_ALL
// (string) effects - special effect played on unit(s) every given distance (variable effectOccursDistance),
//                          if null, no effect will be played
// (string) effectAttachPoint - atachment point for special effect, example: "origin"
// (real) effectOccursDistance - special effect declared above will be played every [effectOccursDistance] distance
// *****************************

// (code) onHitCode example:
// function OnHitExample takes nothing returns nothing
//      avaiable global variables inside this callback function, read-only:
//      (unit) kb_unit = main unit connected with this function
//      (unit) kb_obstacle_unit = unit who was hit by kb_unit (if a unit was hit)
//      (integer) kb_hitCount = counts all hits done by kb_unit
//      (integer) kb_callback use to determine 2 'events' type: KB_HIT_OBSTACLE_UNIT or KB_HIT_THE_WALL
//         KB_HIT_THE_WALL = structure or doodad or cliff or tree, if KB_HIT_OBSTACLE_UNIT it refers to kb_obstacle_unit
//      don't use "waits" here
//   if kb_callback == KB_HIT_OBSTACLE_UNIT and kb_hitCount == 1 then
//        call DisplayTextToForce(GetPlayersAll(), GetUnitName(kb_unit) + " hits the " + GetUnitName(kb_obstacle_unit))
//   endif
// endfunction

// *****************************
// knockback types / bounce types description
// kbType: KB_TYPE_NO_BOUNCE: stops on obstacles /structures/cliffs/doodads
//  if code "onHitCode" is specifed then it will be executed when unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      ignore all units pathing on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore alied units pathing on my way, stops on first enemy unit on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore enemy units pathing on my way, stops on first allied unit on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      stops on first unit found on my way

// kb_type: KB_TYPE_SLIDE: bounce me on obstacles and:
// if code "onHitCode" is specifed then it will be executed when unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      ignore all units pathing on my way, don't bounce me from other units, and *don't bounce any units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore all units pathing on my way, don't bounce me from other units, but bounce unit on my way- enemies only
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore all units pathing on my way, don't bounce me from other units, but bounce unit on my way- allied only
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      ignore all units pathing on my way, don't bounce me from other units, but bounce all units on my way
//  --> if 2 knockbacked units meet, KB_TYPE_SLIDE has priority and it *may* overwrite existing knockback <---
//  --> if KB_TYPE_SLIDE will bounce other units it will be done with angle 90 degrees <--

// kb_type: KB_TYPE_STOP_ON_OBSTACLES: don't bounce me on obstacles structures/cliffs/doodads/trees and:
// if code "onHitCode" is specifed then it will be executed if unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      bounce me on any other unit, and don't bounce units on my way (allied and enemy)
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore only allies pathing on my way, and bounce me and enemy units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore only enemies pathing on my way, and bounce me and allied units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      bounce me on other unit, and bounce all units on my way (allied and enemy)

// kb_type: KB_TYPE_NORMAL: bounce me on obstacles and:
// if code "onHitCode" is specifed then it will be executed if unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      bounce me on any other unit, and don't bounce units on my way (allied and enemy)
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore only allies pathing on my way, and bounce me and enemy units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore only enemies pathing on my way, and bounce me and allied units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      bounce me on other unit, and bounce all units on my way (allied and enemy)
//

private function KB_FilterOutAbility takes integer abiId returns nothing
    set kb_filterOutAbiInteger = kb_filterOutAbiInteger + 1
    set kb_filterOutAbility[kb_filterOutAbiInteger] = abiId
endfunction

//*****************************************************************
// **************** BEGIN OF USER CONFIGURABLES **********************
private function KB_AdvancedUserConfigurables takes nothing returns nothing
    // these will apply >globaly< to knockback system:

    set kb_minSpeedToKillTree = 500.00 // minimum unit speed that allow to destroy tree, only positive values allowed
    set kb_reduceSpeedOnHitFactor = 20.00 // allowed range: 0 ... 100
    // (very high values of kb_reduceSpeedOnHitFactor may look ugly and cause unit to stop instantly)
    // following settings will filter out units with abilities "ghost" "ghosts visible" and with buff "windwalk"
    // so units with these abilities will not be bounced, as they already can be walked over
    call KB_FilterOutAbility('Agho')
    call KB_FilterOutAbility('Aeth')
    call KB_FilterOutAbility('BOwk')
    // you can add more abilities/buffs here like 3 above
    // call KB_FilterOutAbility('YourAbiId')
endfunction
// **************** END OF USER CONFIGURABLES ************************
//*****************************************************************

globals
    group                             kb_ug
    real array                      kb_distance
    real array                      kb_time
    real array                      kb_angle
    real array                      kb_d1
    real array                      kb_d2
    real array                      kb_sin
    real array                      kb_cos
    boolean array               kb_killTrees
    boolean array               kb_interruptSpells
    boolean array               kb_disableMovement
    integer array                kb_knockbackType
    integer array                kb_bounceFiltrType
    string array                  kb_effect
    string array                  kb_effectAttachPoint
    real array                      kb_effectOccursDistance
    real array                      kb_lastDistanceEffect
    real array                      kb_collision
    real array                      kb_distanceLeft
    real array                      kb_timeLeft
    trigger array                 kb_trg_onHit
    trigger                           kb_trg_loop
    integer array                 kb_hitCounter               // for callback function
    integer                           kb_hitCount = 0           // for callback function
    unit                                kb_unit = null                // for callback function
    unit                                kb_obstacle_unit = null  // for callback function
    player                            kb_player
    rect                                kb_rect
    group                             kb_group
    timer                              kb_timer
    real                                 kb_minSpeedToKillTree = 500.00
    real                                 kb_reduceSpeedOnHitFactor = 10.00
    real                                 kb_reduceSpeedOnHit = 1.00
    constant real                 KB_INTERVAL = 0.03125
    constant real                 KB_STEPS_PER_SEC = 1.00 / KB_INTERVAL
    constant real                 KB_PI_DIVIDED_BY_2 = bj_PI / 2.00
    constant real                 KB_PI_MULTIPLIED_BY_2 = bj_PI * 2.00
    boolexpr array                       kb_filtr
    constant integer                    KB_FILTR_BOUNCE_NONE = 0
    constant integer                    KB_FILTR_BOUNCE_ENEMY_ONLY = 2
    constant integer                    KB_FILTR_BOUNCE_ALLIED_ONLY = 3
    constant integer                    KB_FILTR_BOUNCE_ALL = 4
    constant integer                    KB_TYPE_NO_BOUNCE = 0
    constant integer                    KB_TYPE_SLIDE = 10
    constant integer                    KB_TYPE_STOP_ON_OBSTACLES = 20
    constant integer                    KB_TYPE_NORMAL = 30
    constant integer                    KB_HIT_OBSTACLE_UNIT = 101
    constant integer                    KB_HIT_THE_WALL = 102
    integer                                   kb_callback
    integer                                   kb_filterOutAbiInteger = 0
    integer array                         kb_filterOutAbility
endglobals

//-----------------------------------------------------------
//x, y, = centerX, centerY
//-----------------------------------------------------------
function KB_IsPointWalkableForSize takes real x, real y, real whatSize returns boolean  //x, y, = centerX, centerY
    if whatSize <= 16.00 then //one item check:
        return IsTerrainWalkable(x, y)
    endif // for larger units: 5 item checks
    return IsTerrainWalkable(x, y) and IsTerrainWalkable(x+32.00, y) and IsTerrainWalkable(x-32.00, y) and IsTerrainWalkable(x, y+32.00) and IsTerrainWalkable(x, y-32.00)
endfunction
//------------------------------------------------------------
//------------------------------------------------------------
function KB_GetBounceAngle takes real angle, real x, real y, integer id returns real
    local real newAngleVer1 = ( - angle )
    local real newAngleVer2 = ( - bj_PI - angle ) // for negative angles
    if angle>0.00 then
        set newAngleVer2 = (bj_PI - angle)
    endif
    //++++
    if KB_IsPointWalkableForSize(x+kb_d1[id] * Cos(newAngleVer1), y+kb_d1[id] * Sin(newAngleVer1), kb_collision[id]) then
        return newAngleVer1
    elseif KB_IsPointWalkableForSize(x+kb_d1[id] * Cos(newAngleVer2), y+kb_d1[id] * Sin(newAngleVer2), kb_collision[id]) then
        return newAngleVer2
    else //3rd version - reverse direction:
        set newAngleVer1 = (angle + bj_PI) // for negative angle
        if angle>0.00 then
            set newAngleVer1 = (angle - bj_PI)
        endif
    endif
    return newAngleVer1
endfunction
//------------------------------------------------------------
//-----------------------------x/y = obstacleUnit X/Y
function KB_GetBounceAngle90 takes real angle, real x, real y, real unitX, real unitY returns real
    local real newAnglePlus = ModuloReal((angle + KB_PI_DIVIDED_BY_2), KB_PI_MULTIPLIED_BY_2)  // +90
    local real newAngleMinus = ModuloReal((angle - KB_PI_DIVIDED_BY_2), KB_PI_MULTIPLIED_BY_2) // -90
    local real x1 = x+32.00 * Cos(newAnglePlus)
    local real y1 = y+32.00 * Sin(newAnglePlus)
    local real x2 = x+32.00 * Cos(newAngleMinus)
    local real y2 = y+32.00 * Sin(newAngleMinus)
    //compare distances
    if ((x1-unitX) * (x1-unitX) + (y1-unitY) * (y1-unitY)) > ((x2-unitX) * (x2-unitX) + (y2-unitY) * (y2-unitY)) then
        return newAnglePlus
    endif
    return newAngleMinus
endfunction

//-------------------------------------------------------------------------------
//-------------Zwiebelchen:-------------------------------------------
// The difference between GroupEnumUnitsInRange and IsUnitInRangeXY is, that the former will only enumerate units
// whose coordinate center is inside the radius.
// The IsUnitInRange natives will already return true if only a part of the unit is inside the radius.
//--------------------------------------------------------
// KB_GetUnitCollisionSize checks up to 64 unit collision size  // returns values from 8 ..to.. 64
//--------------------------------------------------------
function KB_GetUnitCollisionSize takes unit u, real unitX, real unitY returns real
    local real i=0.00
    local real size=64.00
    loop
        exitwhen i==64.00
        if not IsUnitInRangeXY(u, (unitX+i), unitY, 0.00) then
            set size = (i-1.00)
            exitwhen true
        endif
        set i=i+1.00
    endloop
    if size<8.00 then
        return 8.00
    endif
    return size
endfunction
//------------------------------------------------------------
function IsUnitAlive takes unit u returns boolean
    return not (GetUnitTypeId(u) == 0 or IsUnitType(u, UNIT_TYPE_DEAD))
endfunction
//------------------------------------------------------------
//  DisableUnitMovement /* v1.0.0.1
//    *   Disables unit movement. They can still turn, but will stay in
//    *   place. It simulates an ensnare-like effect, except that it will
//    *   not ground units, it does not have buffs, does not interrupt
//    *   channeled casts and appears to have no downsides.
//    *   Full credits to WaterKnight for discovering this technique.
//--------------------------------------------------------------------   
function DisableUnitMovement takes unit u returns nothing
    call SetUnitPropWindow(u, 0)
endfunction
//--------------------------------------------------------------------
function EnableUnitMovement takes unit u returns nothing
    call SetUnitPropWindow(u, GetUnitDefaultPropWindow(u) * bj_DEGTORAD)
endfunction
//------------------------------------------------------------
function KBFiltr_1 takes unit u returns boolean
    local integer a=1
    local integer id = GetUnitUserData(u)
    local boolean isKb = IsUnitInGroup(u, kb_group) // is knockbacked?
    if IsUnitAlive(u) and (not IsUnitType(u, UNIT_TYPE_FLYING)) and (not IsUnitType(u, UNIT_TYPE_STRUCTURE)) and (IsUnitVisible(u, kb_player)) then
        if not (isKb and ((kb_knockbackType[id]==KB_TYPE_SLIDE) or (kb_knockbackType[id]==KB_TYPE_NO_BOUNCE and kb_bounceFiltrType[id]==KB_FILTR_BOUNCE_NONE))) then
            if not (isKb and ((IsUnitAlly(u, kb_player) and kb_bounceFiltrType[id]==KB_FILTR_BOUNCE_ENEMY_ONLY) or (IsUnitEnemy(u, kb_player) and kb_bounceFiltrType[id]==KB_FILTR_BOUNCE_ALLIED_ONLY))) then
                loop
                    exitwhen a>kb_filterOutAbiInteger
                    if GetUnitAbilityLevel(u, kb_filterOutAbility[a]) > 0 then
                        return false
                    endif
                    set a=a+1
                endloop
                return true   
            endif
        endif
    endif
    return false
endfunction
//--------
function KBFiltr_groundNotStruc takes nothing returns boolean
    return KBFiltr_1(GetFilterUnit())
endfunction
// set kb_player before confirm this 2 checks!
function KBFiltr_groundNotStrucAlliedOnly takes nothing returns boolean
    return KBFiltr_1(GetFilterUnit()) and IsUnitAlly(GetFilterUnit(), kb_player)
endfunction
function KBFiltr_groundNotStrucEnemyOnly takes nothing returns boolean
    return KBFiltr_1(GetFilterUnit()) and IsUnitEnemy(GetFilterUnit(), kb_player)
endfunction
//------------------------------------------------------------------------
function KB_OnRemoveFromGame takes nothing returns nothing   
    call GroupRemoveUnit(kb_group, udg_UDexUnits[udg_UDex])
    //clean:
    if kb_trg_onHit[udg_UDex] != null then
        call DestroyTrigger(kb_trg_onHit[udg_UDex])
        set kb_trg_onHit[udg_UDex] = null
    endif
    set kb_effect[udg_UDex] = null
    set kb_effectAttachPoint[udg_UDex] = null
    if FirstOfGroup(kb_group)==null then
        call PauseTimer(kb_timer)
    endif
endfunction
//----------------------------------------------------------------------------
function KB_DestroyKnockbackOnUnit takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    call GroupRemoveUnit(kb_group, u)
    if kb_trg_onHit[id] != null then
        call DestroyTrigger(kb_trg_onHit[id])
        set kb_trg_onHit[id] = null
    endif
    call EnableUnitMovement(u)
    set kb_effect[id] = null
    set kb_effectAttachPoint[id] = null
    if FirstOfGroup(kb_group)==null then
        call PauseTimer(kb_timer)
    endif
endfunction
//----------------------------------------------------------------------------
function KB_DestroyAllEnum takes nothing returns nothing
    call KB_DestroyKnockbackOnUnit(GetEnumUnit())
endfunction
//----------------------------
function KB_DestroyAllKnockbacks takes nothing returns nothing
    call ForGroup(kb_group, function KB_DestroyAllEnum)
endfunction
//----------------------------
//=======================================================

function KB_CheckAllowedTypes takes integer kbType, integer bounceTypeFiltr returns boolean
    if kbType == KB_TYPE_NO_BOUNCE or kbType == KB_TYPE_SLIDE or kbType == KB_TYPE_STOP_ON_OBSTACLES or kbType == KB_TYPE_NORMAL then
        if bounceTypeFiltr == KB_FILTR_BOUNCE_NONE or bounceTypeFiltr == KB_FILTR_BOUNCE_ENEMY_ONLY or bounceTypeFiltr == KB_FILTR_BOUNCE_ALLIED_ONLY or bounceTypeFiltr == KB_FILTR_BOUNCE_ALL then
            return true
        endif
    endif
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Knockback error: wrong integer pass to function")
    return false
endfunction
//-----------------------------------------------------------------------------------------
//-----main function --------------------------------------------------------------------
function KnockBack takes code onHitCode, unit u, real distance, real duration, real angle, boolean interruptSpells, boolean killTrees, boolean disableMovement, integer kbType, integer bounceTypeFiltr, string effects, string effectAttachPoint, real effectOccursDistance returns nothing
    local integer id = GetUnitUserData(u)
    local integer q = R2I(duration / KB_INTERVAL) // (Number of steps)
    local real x = GetUnitX(u)
    local real y = GetUnitY(u)
    if GetUnitDefaultMoveSpeed(u) == 0.00 or (not KB_CheckAllowedTypes(kbType, bounceTypeFiltr)) then //protection
        return
    endif
    set kb_effect[id] = null
    set kb_effectAttachPoint[id] = null
    if kb_trg_onHit[id] != null then
        call DestroyTrigger(kb_trg_onHit[id])
        set kb_trg_onHit[id] = null
    endif
    if onHitCode != null then
        set kb_trg_onHit[id] = CreateTrigger()
        call TriggerAddAction(kb_trg_onHit[id], onHitCode)
    endif

    set kb_distance[id] = distance
    set kb_distanceLeft[id] = distance
    set kb_time[id] = duration
    set kb_timeLeft[id] = duration
    set kb_hitCounter[id] = 0
    set kb_angle[id] = angle
    set kb_sin[id] = Sin(angle)
    set kb_cos[id] = Cos(angle)
    set kb_d1[id] = 2 * kb_distance[id] / (q + 1)  // the "bit" in "moving a unit a bit a time"
    set kb_d2[id] = kb_d1[id] / q  // will decrease d1 with each execution
    set kb_killTrees[id] = killTrees
    set kb_interruptSpells[id] = interruptSpells
    set kb_disableMovement[id] = disableMovement
    set kb_knockbackType[id] = kbType
    set kb_bounceFiltrType[id] = bounceTypeFiltr

    if kb_interruptSpells[id] then
        call SetUnitPosition(u, x, y)
    endif
    if kb_disableMovement[id] then
        call DisableUnitMovement(u)
    endif
    if effects != null then
        set kb_effect[id] = effects
    endif
    set kb_effectAttachPoint[id] = effectAttachPoint
    set kb_lastDistanceEffect[id] = kb_distance[id]
    set kb_effectOccursDistance[id] = effectOccursDistance

    set kb_collision[id] = KB_GetUnitCollisionSize(u, x, y)

    if FirstOfGroup(kb_group)==null then
        call TimerStart(kb_timer, KB_INTERVAL, true, null)
    endif
    call GroupAddUnit(kb_group, u)
endfunction

// KBLOOP part =============================================

function KB_BounceEvent takes unit u, integer id, integer eventType returns nothing
    if kb_trg_onHit[id] != null then
        set kb_callback = eventType //  KB_HIT_OBSTACLE_UNIT or KB_HIT_THE_WALL
        set kb_unit = u
        set kb_hitCounter[id] = kb_hitCounter[id] + 1
        set kb_hitCount = kb_hitCounter[id]
        call TriggerExecute(kb_trg_onHit[id])
    endif
endfunction
//------------------------------------------------------------------------------------------------
// use this for bounce from structures/cliffs/doodads ONLY:
//------------------------------------------------------------------------------------------------
function KB_BounceUnit takes unit u, integer id, real unitX, real unitY returns nothing
    set kb_angle[id] = KB_GetBounceAngle(kb_angle[id], unitX, unitY, id) //change angle
    set kb_sin[id] = Sin(kb_angle[id])
    set kb_cos[id] = Cos(kb_angle[id])
    call SetUnitX(u, unitX + kb_d1[id] * kb_cos[id])
    call SetUnitY(u, unitY + kb_d1[id] * kb_sin[id])
    set kb_d1[id] = kb_d1[id] * kb_reduceSpeedOnHit // reducing distance kb_d1
    call KB_BounceEvent(u, id, KB_HIT_THE_WALL) //fire hit event
endfunction
//------------------------------------------------------------------------------------------------
//-this for better visual effect --
//-KB_BounceEvent is not included here inside this func -----------
function KB_BounceUnitReverseWithMinSpeed takes unit u, integer id returns nothing
    local real angle = (kb_angle[id] + bj_PI)
    if kb_angle[id]>0.00 then // reverse direction:
        set angle = (kb_angle[id] - bj_PI)
    endif // initiate new kb, so old one will be ended:
    call KnockBack(null, u, 25.00, 0.30, angle, false, kb_killTrees[id], kb_disableMovement[id], KB_TYPE_SLIDE, KB_FILTR_BOUNCE_NONE, null, null, 1.00)
endfunction
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
function KB_TwoUnitsHit takes unit mainUnit, integer id, unit obstacleUnit, integer obstacleId returns nothing
    local integer kb1 = kb_knockbackType[id]
    local integer kb2
    if IsUnitInGroup(obstacleUnit, kb_group) then
        set kb2 = kb_knockbackType[obstacleId]
     
        if kb2 == KB_TYPE_NO_BOUNCE then
            set kb_obstacle_unit = obstacleUnit
            call KB_BounceEvent(mainUnit, id, KB_HIT_OBSTACLE_UNIT)
            set kb_obstacle_unit = mainUnit
            call KB_BounceEvent(obstacleUnit, obstacleId, KB_HIT_OBSTACLE_UNIT)
            if kb2 == kb1 then // both "no-bounce"
                call KB_BounceUnitReverseWithMinSpeed(mainUnit, id)
                call KB_BounceUnitReverseWithMinSpeed(obstacleUnit, obstacleId)
            endif

        elseif (kb2 == KB_TYPE_NORMAL or kb2 == KB_TYPE_STOP_ON_OBSTACLES) and (kb1 == KB_TYPE_NORMAL or kb1 == KB_TYPE_STOP_ON_OBSTACLES or kb1 == KB_TYPE_SLIDE) then
            set kb_obstacle_unit = mainUnit
            call KB_BounceEvent(obstacleUnit, obstacleId, KB_HIT_OBSTACLE_UNIT)
            set kb_obstacle_unit = obstacleUnit
            call KB_BounceEvent(mainUnit, id, KB_HIT_OBSTACLE_UNIT)
        endif
   
    else // obstacle unit *not* in kb_group:
        set kb_obstacle_unit = obstacleUnit
        call KB_BounceEvent(mainUnit, id, KB_HIT_OBSTACLE_UNIT)
        if kb1 == KB_TYPE_NO_BOUNCE then
            call KB_BounceUnitReverseWithMinSpeed(mainUnit, id)
        endif
    endif
endfunction

//-------------------------------------------------------------------------------
// kb_u primary unit, unitX = GetUnitX(kb_u)
// bouncePrimaryUnit - if "true" will bounce kb_u in opposite direction to new unit on way
// bounceNewUnit -if "true" will bounce new unit in opposite direction to kb_u unit
function KB_CheckUnitsAroundAndBounce takes unit kb_u, integer kb_id, real unitX, real unitY, real targetX, real targetY, integer filtrNr, boolean bouncePrimaryUnit, boolean bounceNewUnit returns boolean
    local unit obst_u
    local integer obst_id
    local real obstX
    local real obstY
    local integer foundUnits=0
    local real angle
    local integer t
    local integer b
    local real Tx
    local real Ty
    local real c

    set kb_player = GetOwningPlayer(kb_u)
    call GroupEnumUnitsInRange(kb_ug, targetX, targetY, 150.00, kb_filtr[filtrNr]) // alive+ground+not structure
    loop
        set obst_u = FirstOfGroup(kb_ug)
        exitwhen obst_u == null
        set obstX = GetUnitX(obst_u)
        set obstY = GetUnitY(obst_u)
        set c = KB_GetUnitCollisionSize(obst_u, obstX, obstY)
        if (obst_u != kb_u) and IsUnitInRange(obst_u, kb_u, RMaxBJ(c, kb_collision[kb_id])) then //pick bigger collision
            set foundUnits = 1
            set obst_id = GetUnitUserData(obst_u)
            call KB_TwoUnitsHit(kb_u, kb_id, obst_u, obst_id)
            // if bounce primary unit:
            if bouncePrimaryUnit then
                set angle = Atan2(unitY - obstY, unitX - obstX) // reverse angle
                set Tx = unitX+kb_d1[kb_id] * Cos(angle)
                set Ty = unitY+kb_d1[kb_id] * Sin(angle)           
                if KB_IsPointWalkableForSize(Tx, Ty, kb_collision[kb_id]) then
                    call SetUnitX(kb_u, Tx)
                    call SetUnitY(kb_u, Ty)
                    set kb_angle[kb_id] = angle //change angle
                    set kb_sin[kb_id] = Sin(kb_angle[kb_id])
                    set kb_cos[kb_id] = Cos(kb_angle[kb_id])
                    set kb_d1[kb_id] = kb_d1[kb_id] * kb_reduceSpeedOnHit // reducing distance kb_d1
                else // not walkable:
                    set kb_d1[kb_id] = 0.00 // end knockback
                endif
            elseif kb_knockbackType[kb_id] == KB_TYPE_SLIDE then // if not bounce, then move [for slide type only]:
                call SetUnitX(kb_u, targetX)
                call SetUnitY(kb_u, targetY)
            endif       

            // if bounce obstacle unit:
            if bounceNewUnit then
                set angle = Atan2(obstY - unitY, obstX - unitX) // reverse angle
                if IsUnitInGroup(obst_u, kb_group) then
                    if kb_knockbackType[kb_id] == KB_TYPE_SLIDE then // hit by sliding unit, initiate new slide, slide overwrites existing one
                        if kb_timeLeft[kb_id] * kb_distanceLeft[kb_id] > 1.00 then //don't run very low knockbacks! <---
                            set b = kb_bounceFiltrType[kb_id] // new bounce filter:
                            if kb_bounceFiltrType[kb_id] == KB_FILTR_BOUNCE_ENEMY_ONLY then
                                set b = KB_FILTR_BOUNCE_ALLIED_ONLY
                            endif
                            set angle = KB_GetBounceAngle90(kb_angle[kb_id], obstX, obstY, unitX, unitY) //angle 90 degrees
                            if KB_IsPointWalkableForSize((obstX+kb_d1[obst_id] * Cos(angle)), (obstY+kb_d1[obst_id] * Sin(angle)), c) then
                                call KnockBack(null, obst_u, kb_distanceLeft[kb_id], kb_timeLeft[kb_id], angle, kb_interruptSpells[kb_id], kb_killTrees[kb_id], kb_disableMovement[kb_id], kb_knockbackType[kb_id], b, kb_effect[kb_id], kb_effectAttachPoint[kb_id], kb_effectOccursDistance[kb_id])
                            endif
                        endif
                    else // hit by non-sliding unit, update existing kb:
                        set Tx = obstX+kb_d1[obst_id] * Cos(angle)
                        set Ty = obstY+kb_d1[obst_id] * Sin(angle)
                        if KB_IsPointWalkableForSize(Tx, Ty, c) then
                            call SetUnitX(obst_u, Tx)
                            call SetUnitY(obst_u, Ty)
                            set kb_angle[obst_id] = angle //change angle
                            set kb_sin[obst_id] = Sin(kb_angle[obst_id])
                            set kb_cos[obst_id] = Cos(kb_angle[obst_id])
                            set kb_d1[obst_id] = kb_d1[obst_id] * kb_reduceSpeedOnHit // reducing distance kb_d1
                        endif
                    endif
                elseif (kb_timeLeft[kb_id] * kb_distanceLeft[kb_id]) > 1.00 then //unit NOT in kb_group, initial new kb; don't run very low knockbacks! <---
                    set b = kb_bounceFiltrType[kb_id] // new bounce filter:
                    if b == KB_FILTR_BOUNCE_ENEMY_ONLY then
                        set b = KB_FILTR_BOUNCE_ALLIED_ONLY
                    endif                   
                    if kb_knockbackType[kb_id] == KB_TYPE_SLIDE then // if obstacle hits by sliding unit, change angle to 90
                        set angle = KB_GetBounceAngle90(kb_angle[kb_id], obstX, obstY, unitX, unitY) //angle 90 degrees
                    endif
                    if KB_IsPointWalkableForSize((obstX+kb_d1[obst_id] * Cos(angle)), (obstY+kb_d1[obst_id] * Sin(angle)), c) then
                        call KnockBack(null, obst_u, kb_distanceLeft[kb_id], kb_timeLeft[kb_id], angle, kb_interruptSpells[kb_id], kb_killTrees[kb_id], kb_disableMovement[kb_id], kb_knockbackType[kb_id], b, kb_effect[kb_id], kb_effectAttachPoint[kb_id], kb_effectOccursDistance[kb_id])
                    endif
                endif
            endif
            //bounce obstacle unit:end
        endif
        call GroupRemoveUnit(kb_ug, obst_u)
    endloop
    call GroupClear (kb_ug)
   
    set obst_u=null
    return foundUnits > 0
endfunction
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
function KB_MoveUnit takes unit u, integer id, real unitX, real unitY, real targetX, real targetY returns nothing
    local boolean isWalkable = KB_IsPointWalkableForSize(targetX, targetY, kb_collision[id])

    if kb_knockbackType[id] == KB_TYPE_NO_BOUNCE then // no bounce at all
        if not isWalkable then
            call KB_BounceEvent(u, id, KB_HIT_THE_WALL) //fire hit event, obstacle
            call KB_BounceUnitReverseWithMinSpeed(u, id)
        elseif kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE then // walkable:
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        elseif (not KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, kb_bounceFiltrType[id], false, false)) then //3 other filters:
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        endif

    //---------------------------------SLIDE:-------------------------------------------------   
    elseif kb_knockbackType[id] == KB_TYPE_SLIDE then
        if not isWalkable then
            call KB_BounceUnit(u, id, unitX, unitY) //fires hit event
        elseif kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE then
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        elseif (not KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, kb_bounceFiltrType[id], false, true)) then //3 other filters:
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        endif
   
    //--------------------------------- KB_TYPE_NORMAL / KB_TYPE_STOP_ON_OBSTACLES  -------------------   
    elseif kb_knockbackType[id] == KB_TYPE_NORMAL or kb_knockbackType[id] == KB_TYPE_STOP_ON_OBSTACLES then
        if not isWalkable then
            if kb_knockbackType[id] == KB_TYPE_NORMAL then
                call KB_BounceUnit(u, id, unitX, unitY)       
            elseif kb_knockbackType[id] == KB_TYPE_STOP_ON_OBSTACLES then
                call KB_BounceEvent(u, id, KB_HIT_THE_WALL) //fire hit event, obstacle
                call KB_BounceUnitReverseWithMinSpeed(u, id)
            endif
        elseif (kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE) then //walkable:
            if (not (KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, KB_FILTR_BOUNCE_ALL, true, false))) then
                call SetUnitX(u, targetX)
                call SetUnitY(u, targetY)
            endif
        elseif (not (KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, kb_bounceFiltrType[id], true, true))) then //3 other filters
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        endif
   
    endif
endfunction
//-----------------------------------------------------------------
function KB_KillTree takes nothing returns nothing
    call KillTree(GetEnumDestructable())
endfunction
//------------------------------------------------------------------       
function KB_ShowEffect takes unit u, integer id returns nothing
    if kb_effect[id] != null and (kb_distanceLeft[id] < (kb_lastDistanceEffect[id] - kb_effectOccursDistance[id])) then
        call DestroyEffect(AddSpecialEffectTarget(kb_effect[id], u, kb_effectAttachPoint[id]))
        set kb_lastDistanceEffect[id] = kb_distanceLeft[id]
    endif
endfunction
//------------------------------------------------------------------
function KB_LoopEnum takes nothing returns nothing
    local unit u = GetEnumUnit()
    local integer id = GetUnitUserData(u)
    local real unitX = GetUnitX(u)
    local real unitY = GetUnitY(u)
    local real targetX = unitX + kb_d1[id] * kb_cos[id]
    local real targetY = unitY + kb_d1[id] * kb_sin[id]

    if (not IsUnitAlive(u)) or kb_d1[id] <= 0.00 then
        call KB_DestroyKnockbackOnUnit(u)
        set u=null
        return
    endif
    if kb_killTrees[id] and ((kb_d1[id]*KB_STEPS_PER_SEC) > kb_minSpeedToKillTree) then
        call MoveRectTo(kb_rect, targetX, targetY)
        call EnumDestructablesInRect(kb_rect, null, function KB_KillTree)
    endif
    call KB_ShowEffect(u, id)
    call KB_MoveUnit(u, id, unitX, unitY, targetX, targetY)

    set kb_d1[id] = kb_d1[id] - kb_d2[id] // decrease speed
    set kb_distanceLeft[id] = kb_distanceLeft[id] - kb_d1[id] // for additional kb-ed units
    set kb_timeLeft[id] = kb_timeLeft[id] - KB_INTERVAL // for additional kb-ed units
    set u=null
endfunction
//--------------------------------------------------------------------------
function KB_Loop takes nothing returns boolean
    call ForGroup(kb_group, function KB_LoopEnum)
    return false
endfunction
//=======================================================
//=======================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    set kb_ug = CreateGroup()
    set kb_group = CreateGroup()
    set kb_timer = CreateTimer()
    set kb_rect = Rect(0.0, 0.0, 256.0, 256.0)
    set kb_trg_loop = CreateTrigger()
    call TriggerRegisterTimerExpireEvent(kb_trg_loop, kb_timer) //timer kb_timer expires
    call TriggerAddCondition(kb_trg_loop, Condition(function KB_Loop))
    //----------------------------------------------------
    call KB_AdvancedUserConfigurables()
    set kb_minSpeedToKillTree = RMaxBJ(kb_minSpeedToKillTree, 0.00)

    if kb_reduceSpeedOnHitFactor < 0.00 then
        set kb_reduceSpeedOnHitFactor = 0.00
    elseif kb_reduceSpeedOnHitFactor > 100.00 then
        set kb_reduceSpeedOnHitFactor = 100.00
    endif
    set kb_reduceSpeedOnHit = 1.00 - (kb_reduceSpeedOnHitFactor / 100.00) // for kb_d1, range: 0.00 ... 1.00

    //----------------------------------------------------   
    set kb_filtr[KB_FILTR_BOUNCE_NONE] =            Condition(function KBFiltr_groundNotStruc) 
    set kb_filtr[KB_FILTR_BOUNCE_ALL] =                Condition(function KBFiltr_groundNotStruc)
    set kb_filtr[KB_FILTR_BOUNCE_ALLIED_ONLY] = Condition(function KBFiltr_groundNotStrucAlliedOnly)
    set kb_filtr[KB_FILTR_BOUNCE_ENEMY_ONLY] = Condition(function KBFiltr_groundNotStrucEnemyOnly)
    call TriggerRegisterVariableEvent( t, "udg_UnitIndexEvent", EQUAL, 2.00 ) // remove unit detection
    call TriggerAddAction(t, function KB_OnRemoveFromGame)
    set t=null
endfunction
endlibrary

JASS:
library KnockbackSimple initializer Init uses IsDestructableTree, TerrainPathability // by ZibiTheWand3r3r v.1.03

// Features:
// Simple to use knockback: one function call, configurable behavior (16 possible versions), supports diffrent collision sizes
// callback function, can destroy knockbacks any time, system is for ground units only
// *****************************

// Requires:
// library IsDestructableTree * by BPower:
//      http://www.hiveworkshop.com/forums/jass-resources-412/snippet-isdestructabletree-248054/
// library TerrainPathability * by Rising_Dusk
//      http://www.wc3c.net/showthread.php?t=103862
// GUI Unit Event v2.2.1.0 * by Bribe
//      http://www.hiveworkshop.com/threads/gui-unit-event-v2-2-1-0.201641/
// *****************************

// How to use:
// function KnockBack takes:
// (code) onHitCode = code that will be executed when unit bounce/hits other unit/obstacles (structure, cliff, doodad, tree)
//                                set to "null" if not needed, (onHitCode is executed as trigger action)
// (unit) u = knockbacked unit
// (real) distance = how far unit will be moved
// (real) duration = how long knockback exists [sec]
// (real) angle = knockback angle [in radians!]
// (boolean) interruptSpells = if "true" it's interrupts orders/channeling spells on unit u, when knockback starts
// (boolean) killTrees = if "true" unit(s) will destroy trees if unit travels with >minimum speed, kb_minSpeedToKillTree
// (boolean) disableMovement = if "true" unit won't be able to move during knockback, it's using SetUnitPropWindow
//                       technique, so if you already using this in your map it may interfere. In this case set this boolean to false.
// (integer) kbType               see table below, avaiable 4 types: KB_TYPE_NO_BOUNCE ,
//                                KB_TYPE_SLIDE , KB_TYPE_STOP_ON_OBSTACLES , KB_TYPE_NORMAL
// (integer) bounceTypeFiltr  see table below, avaiable 4 filters: KB_FILTR_BOUNCE_NONE ,
//                               KB_FILTR_BOUNCE_ENEMY_ONLY , KB_FILTR_BOUNCE_ALLIED_ONLY , KB_FILTR_BOUNCE_ALL
// (string) effects - special effect played on unit(s) every given distance (variable effectOccursDistance),
//                          if null, no effect will be played
// (string) effectAttachPoint - atachment point for special effect, example: "origin"
// (real) effectOccursDistance - special effect declared above will be played every [effectOccursDistance] distance
// *****************************

// (code) onHitCode example:
// function OnHitExample takes nothing returns nothing
//      avaiable global variables inside this callback function, read-only:
//      (unit) kb_unit = main unit connected with this function
//      (unit) kb_obstacle_unit = unit who was hit by kb_unit (if a unit was hit)
//      (integer) kb_hitCount = counts all hits done by kb_unit
//      (integer) kb_callback use to determine 2 'events' type: KB_HIT_OBSTACLE_UNIT or KB_HIT_THE_WALL
//         KB_HIT_THE_WALL = structure or doodad or cliff or tree, if KB_HIT_OBSTACLE_UNIT it refers to kb_obstacle_unit
//      don't use "waits" here
//   if kb_callback == KB_HIT_OBSTACLE_UNIT and kb_hitCount == 1 then
//        call DisplayTextToForce(GetPlayersAll(), GetUnitName(kb_unit) + " hits the " + GetUnitName(kb_obstacle_unit))
//   endif
// endfunction

// *****************************
// knockback types / bounce types description
// kbType: KB_TYPE_NO_BOUNCE: stops on obstacles /structures/cliffs/doodads
//  if code "onHitCode" is specifed then it will be executed when unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      ignore all units pathing on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore alied units pathing on my way, stops on first enemy unit on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore enemy units pathing on my way, stops on first allied unit on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      stops on first unit found on my way

// kb_type: KB_TYPE_SLIDE: bounce me on obstacles and:
// if code "onHitCode" is specifed then it will be executed when unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      ignore all units pathing on my way, don't bounce me from other units, and *don't bounce any units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore all units pathing on my way, don't bounce me from other units, but bounce unit on my way- enemies only
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore all units pathing on my way, don't bounce me from other units, but bounce unit on my way- allied only
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      ignore all units pathing on my way, don't bounce me from other units, but bounce all units on my way
//  --> if 2 knockbacked units meet, KB_TYPE_SLIDE has priority and it *may* overwrite existing knockback <---
//  --> if KB_TYPE_SLIDE will bounce other units it will be done with angle 90 degrees <--

// kb_type: KB_TYPE_STOP_ON_OBSTACLES: don't bounce me on obstacles structures/cliffs/doodads/trees and:
// if code "onHitCode" is specifed then it will be executed if unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      bounce me on any other unit, and don't bounce units on my way (allied and enemy)
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore only allies pathing on my way, and bounce me and enemy units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore only enemies pathing on my way, and bounce me and allied units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      bounce me on other unit, and bounce all units on my way (allied and enemy)

// kb_type: KB_TYPE_NORMAL: bounce me on obstacles and:
// if code "onHitCode" is specifed then it will be executed if unit hits obstacle/unit
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_NONE:
//      bounce me on any other unit, and don't bounce units on my way (allied and enemy)
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ENEMY_ONLY:
//      ignore only allies pathing on my way, and bounce me and enemy units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALLIED_ONLY:
//      ignore only enemies pathing on my way, and bounce me and allied units on my way
//  kb_bounceFiltrType: KB_FILTR_BOUNCE_ALL:
//      bounce me on other unit, and bounce all units on my way (allied and enemy)
//

private function KB_FilterOutAbility takes integer abiId returns nothing
    set kb_filterOutAbiInteger = kb_filterOutAbiInteger + 1
    set kb_filterOutAbility[kb_filterOutAbiInteger] = abiId
endfunction

//*****************************************************************
// **************** BEGIN OF USER CONFIGURABLES **********************
private function KB_AdvancedUserConfigurables takes nothing returns nothing
    // these will apply >globaly< to knockback system:
  
    set kb_minSpeedToKillTree = 500.00 // minimum unit speed that allow to destroy tree, only positive values allowed
    set kb_reduceSpeedOnHitFactor = 20.00 // allowed range: 0 ... 100
    // (very high values of kb_reduceSpeedOnHitFactor may look ugly and cause unit to stop instantly)
    // following settings will filter out units with abilities "ghost" "ghosts visible" and with buff "windwalk"
    // so units with these abilities will not be bounced, as they already can be walked over
    call KB_FilterOutAbility('Agho')
    call KB_FilterOutAbility('Aeth')
    call KB_FilterOutAbility('BOwk')
    // you can add more abilities/buffs here like 3 above
    // call KB_FilterOutAbility('YourAbiId')
endfunction
// **************** END OF USER CONFIGURABLES ************************
//***************************************************************** 

globals
    group                             kb_ug
    real array                      kb_distance
    real array                      kb_time
    real array                      kb_angle
    real array                      kb_d1
    real array                      kb_d2
    real array                      kb_sin
    real array                      kb_cos
    boolean array               kb_killTrees  
    boolean array               kb_interruptSpells
    boolean array               kb_disableMovement
    integer array                kb_knockbackType
    integer array                kb_bounceFiltrType
    string array                  kb_effect
    string array                  kb_effectAttachPoint  
    real array                      kb_effectOccursDistance  
    real array                      kb_lastDistanceEffect
    real array                      kb_collision  
    real array                      kb_distanceLeft
    real array                      kb_timeLeft
    trigger array                 kb_trg_onHit
    trigger                           kb_trg_loop
    integer array                 kb_hitCounter               // for callback function  
    integer                           kb_hitCount = 0           // for callback function  
    unit                                kb_unit = null                // for callback function
    unit                                kb_obstacle_unit = null  // for callback function
    player                            kb_player
    rect                                kb_rect
    group                             kb_group
    timer                              kb_timer
    real                                 kb_minSpeedToKillTree = 500.00
    real                                 kb_reduceSpeedOnHitFactor = 10.00
    real                                 kb_reduceSpeedOnHit = 1.00  
    constant real                 KB_DETECT_OBSTACLE_UNIT_RADIUS = 150.00
    constant real                 KB_INTERVAL = 0.03125
    constant real                 KB_STEPS_PER_SEC = 1.00 / KB_INTERVAL
    constant real                 KB_PI_DIVIDED_BY_2 = bj_PI / 2.00
    constant real                 KB_PI_MULTIPLIED_BY_2 = bj_PI * 2.00
    boolexpr array                       kb_filtr
    constant integer                    KB_FILTR_BOUNCE_NONE = 0
    constant integer                    KB_FILTR_BOUNCE_ENEMY_ONLY = 2
    constant integer                    KB_FILTR_BOUNCE_ALLIED_ONLY = 3
    constant integer                    KB_FILTR_BOUNCE_ALL = 4
    constant integer                    KB_TYPE_NO_BOUNCE = 0  
    constant integer                    KB_TYPE_SLIDE = 10  
    constant integer                    KB_TYPE_STOP_ON_OBSTACLES = 20  
    constant integer                    KB_TYPE_NORMAL = 30
    constant integer                    KB_HIT_OBSTACLE_UNIT = 101
    constant integer                    KB_HIT_THE_WALL = 102  
    integer                                   kb_callback  
    integer                                   kb_filterOutAbiInteger = 0
    integer array                         kb_filterOutAbility
endglobals

//-----------------------------------------------------------
//x, y, = centerX, centerY
//-----------------------------------------------------------
function KB_IsPointWalkableForSize takes real x, real y, real whatSize returns boolean  //x, y, = centerX, centerY
    if whatSize <= 16.00 then //one item check:
        return IsTerrainWalkable(x, y)
    endif // for larger units: 5 item checks
    return IsTerrainWalkable(x, y) and IsTerrainWalkable(x+32.00, y) and IsTerrainWalkable(x-32.00, y) and IsTerrainWalkable(x, y+32.00) and IsTerrainWalkable(x, y-32.00)
endfunction
//------------------------------------------------------------
//------------------------------------------------------------
function KB_GetBounceAngle takes real angle, real x, real y, integer id returns real
    local real newAngleVer1 = ( - angle )
    local real newAngleVer2 = ( - bj_PI - angle ) // for negative angles
    if angle>0.00 then
        set newAngleVer2 = (bj_PI - angle)
    endif
    //++++  
    if KB_IsPointWalkableForSize(x+kb_d1[id] * Cos(newAngleVer1), y+kb_d1[id] * Sin(newAngleVer1), kb_collision[id]) then
        return newAngleVer1
    elseif KB_IsPointWalkableForSize(x+kb_d1[id] * Cos(newAngleVer2), y+kb_d1[id] * Sin(newAngleVer2), kb_collision[id]) then
        return newAngleVer2
    else //3rd version - reverse direction:
        set newAngleVer1 = (angle + bj_PI) // for negative angle
        if angle>0.00 then
            set newAngleVer1 = (angle - bj_PI)
        endif
    endif
    return newAngleVer1
endfunction
//------------------------------------------------------------
//-----------------------------x/y = obstacleUnit X/Y
function KB_GetBounceAngle90 takes real angle, real x, real y, real unitX, real unitY returns real
    local real newAnglePlus = ModuloReal((angle + KB_PI_DIVIDED_BY_2), KB_PI_MULTIPLIED_BY_2)  // +90
    local real newAngleMinus = ModuloReal((angle - KB_PI_DIVIDED_BY_2), KB_PI_MULTIPLIED_BY_2) // -90
    local real x1 = x+32.00 * Cos(newAnglePlus)
    local real y1 = y+32.00 * Sin(newAnglePlus)
    local real x2 = x+32.00 * Cos(newAngleMinus)
    local real y2 = y+32.00 * Sin(newAngleMinus)
    //compare distances
    if ((x1-unitX) * (x1-unitX) + (y1-unitY) * (y1-unitY)) > ((x2-unitX) * (x2-unitX) + (y2-unitY) * (y2-unitY)) then
        return newAnglePlus
    endif
    return newAngleMinus  
endfunction

//-------------------------------------------------------------------------------
//-------------Zwiebelchen:-------------------------------------------
// The difference between GroupEnumUnitsInRange and IsUnitInRangeXY is, that the former will only enumerate units
// whose coordinate center is inside the radius.
// The IsUnitInRange natives will already return true if only a part of the unit is inside the radius.
//--------------------------------------------------------
// KB_GetUnitCollisionSize checks up to 64 unit collision size  // returns values from 8 ..to.. 64
//--------------------------------------------------------
function KB_GetUnitCollisionSize takes unit u, real unitX, real unitY returns real
    local real i=0.00
    local real size=64.00
    loop
        exitwhen i==64.00
        if not IsUnitInRangeXY(u, (unitX+i), unitY, 0.00) then
            set size = (i-1.00)
            exitwhen true
        endif
        set i=i+1.00
    endloop
    if size<8.00 then
        return 8.00
    endif
    return size
endfunction
//------------------------------------------------------------
function IsUnitAlive takes unit u returns boolean
    return not (GetUnitTypeId(u) == 0 or IsUnitType(u, UNIT_TYPE_DEAD))
endfunction
//------------------------------------------------------------
//  DisableUnitMovement /* v1.0.0.1
//    *   Disables unit movement. They can still turn, but will stay in
//    *   place. It simulates an ensnare-like effect, except that it will
//    *   not ground units, it does not have buffs, does not interrupt
//    *   channeled casts and appears to have no downsides.
//    *   Full credits to WaterKnight for discovering this technique.
//--------------------------------------------------------------------      
function DisableUnitMovement takes unit u returns nothing
    call SetUnitPropWindow(u, 0)
endfunction
//--------------------------------------------------------------------   
function EnableUnitMovement takes unit u returns nothing
    call SetUnitPropWindow(u, GetUnitDefaultPropWindow(u) * bj_DEGTORAD)
endfunction
//------------------------------------------------------------
function KBFiltr_1 takes unit u returns boolean
    local integer a=1
    local integer id = GetUnitUserData(u)
    local boolean isKb = IsUnitInGroup(u, kb_group) // is knockbacked?  
    if IsUnitAlive(u) and (not IsUnitType(u, UNIT_TYPE_FLYING)) and (not IsUnitType(u, UNIT_TYPE_STRUCTURE)) and (IsUnitVisible(u, kb_player)) then
        if not (isKb and ((kb_knockbackType[id]==KB_TYPE_SLIDE) or (kb_knockbackType[id]==KB_TYPE_NO_BOUNCE and kb_bounceFiltrType[id]==KB_FILTR_BOUNCE_NONE))) then
            if not (isKb and ((IsUnitAlly(u, kb_player) and kb_bounceFiltrType[id]==KB_FILTR_BOUNCE_ENEMY_ONLY) or (IsUnitEnemy(u, kb_player) and kb_bounceFiltrType[id]==KB_FILTR_BOUNCE_ALLIED_ONLY))) then
                loop
                    exitwhen a>kb_filterOutAbiInteger
                    if GetUnitAbilityLevel(u, kb_filterOutAbility[a]) > 0 then
                        return false
                    endif
                    set a=a+1
                endloop
                return true      
            endif
        endif
    endif
    return false
endfunction
//--------
function KBFiltr_groundNotStruc takes nothing returns boolean
    return KBFiltr_1(GetFilterUnit())
endfunction
// set kb_player before confirm this 2 checks!
function KBFiltr_groundNotStrucAlliedOnly takes nothing returns boolean
    return KBFiltr_1(GetFilterUnit()) and IsUnitAlly(GetFilterUnit(), kb_player)
endfunction
function KBFiltr_groundNotStrucEnemyOnly takes nothing returns boolean
    return KBFiltr_1(GetFilterUnit()) and IsUnitEnemy(GetFilterUnit(), kb_player)
endfunction
//------------------------------------------------------------------------
function KB_OnRemoveFromGame takes nothing returns nothing      
    call GroupRemoveUnit(kb_group, udg_UDexUnits[udg_UDex])
    //clean:
    if kb_trg_onHit[udg_UDex] != null then
        call DestroyTrigger(kb_trg_onHit[udg_UDex])
        set kb_trg_onHit[udg_UDex] = null
    endif
    set kb_effect[udg_UDex] = null
    set kb_effectAttachPoint[udg_UDex] = null
    if FirstOfGroup(kb_group)==null then
        call PauseTimer(kb_timer)
    endif  
endfunction
//----------------------------------------------------------------------------
function KB_DestroyKnockbackOnUnit takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    call GroupRemoveUnit(kb_group, u)
    if kb_trg_onHit[id] != null then
        call DestroyTrigger(kb_trg_onHit[id])
        set kb_trg_onHit[id] = null
    endif
    call EnableUnitMovement(u)
    set kb_effect[id] = null
    set kb_effectAttachPoint[id] = null
    if FirstOfGroup(kb_group)==null then
        call PauseTimer(kb_timer)
    endif  
endfunction
//----------------------------------------------------------------------------
function KB_DestroyAllEnum takes nothing returns nothing
    call KB_DestroyKnockbackOnUnit(GetEnumUnit())
endfunction
//----------------------------
function KB_DestroyAllKnockbacks takes nothing returns nothing
    call ForGroup(kb_group, function KB_DestroyAllEnum)
endfunction
//----------------------------
//=======================================================

function KB_CheckAllowedTypes takes integer kbType, integer bounceTypeFiltr returns boolean
    if kbType == KB_TYPE_NO_BOUNCE or kbType == KB_TYPE_SLIDE or kbType == KB_TYPE_STOP_ON_OBSTACLES or kbType == KB_TYPE_NORMAL then
        if bounceTypeFiltr == KB_FILTR_BOUNCE_NONE or bounceTypeFiltr == KB_FILTR_BOUNCE_ENEMY_ONLY or bounceTypeFiltr == KB_FILTR_BOUNCE_ALLIED_ONLY or bounceTypeFiltr == KB_FILTR_BOUNCE_ALL then
            return true
        endif
    endif
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Knockback error: wrong integer pass to function")
    return false
endfunction
//-----------------------------------------------------------------------------------------
//-----main function --------------------------------------------------------------------
function KnockBack takes code onHitCode, unit u, real distance, real duration, real angle, boolean interruptSpells, boolean killTrees, boolean disableMovement, integer kbType, integer bounceTypeFiltr, string effects, string effectAttachPoint, real effectOccursDistance returns nothing
    local integer id = GetUnitUserData(u)
    local integer q = R2I(duration / KB_INTERVAL) // (Number of steps)
    local real x = GetUnitX(u)
    local real y = GetUnitY(u)
    if GetUnitDefaultMoveSpeed(u) == 0.00 or (not KB_CheckAllowedTypes(kbType, bounceTypeFiltr)) then //protection
        return
    endif
    set kb_effect[id] = null
    set kb_effectAttachPoint[id] = null
    if kb_trg_onHit[id] != null then
        call DestroyTrigger(kb_trg_onHit[id])
        set kb_trg_onHit[id] = null
    endif
    if onHitCode != null then
        set kb_trg_onHit[id] = CreateTrigger()
        call TriggerAddAction(kb_trg_onHit[id], onHitCode)
    endif
  
    set kb_distance[id] = distance
    set kb_distanceLeft[id] = distance
    set kb_time[id] = duration
    set kb_timeLeft[id] = duration
    set kb_hitCounter[id] = 0
    set kb_angle[id] = angle
    set kb_sin[id] = Sin(angle)
    set kb_cos[id] = Cos(angle)
    set kb_d1[id] = 2 * kb_distance[id] / (q + 1)  // the "bit" in "moving a unit a bit a time"
    set kb_d2[id] = kb_d1[id] / q  // will decrease d1 with each execution
    set kb_killTrees[id] = killTrees
    set kb_interruptSpells[id] = interruptSpells
    set kb_disableMovement[id] = disableMovement
    set kb_knockbackType[id] = kbType
    set kb_bounceFiltrType[id] = bounceTypeFiltr

    if kb_interruptSpells[id] then
        call SetUnitPosition(u, x, y)
    endif
    if kb_disableMovement[id] then
        call DisableUnitMovement(u)
    endif
    if effects != null then
        set kb_effect[id] = effects
    endif
    set kb_effectAttachPoint[id] = effectAttachPoint
    set kb_lastDistanceEffect[id] = kb_distance[id]
    set kb_effectOccursDistance[id] = effectOccursDistance
  
    set kb_collision[id] = KB_GetUnitCollisionSize(u, x, y)
  
    if FirstOfGroup(kb_group)==null then
        call TimerStart(kb_timer, KB_INTERVAL, true, null)
    endif
    call GroupAddUnit(kb_group, u)
endfunction

// KBLOOP part =============================================

function KB_BounceEvent takes unit u, integer id, integer eventType returns nothing
    if kb_trg_onHit[id] != null then
        set kb_callback = eventType //  KB_HIT_OBSTACLE_UNIT or KB_HIT_THE_WALL
        set kb_unit = u
        set kb_hitCounter[id] = kb_hitCounter[id] + 1
        set kb_hitCount = kb_hitCounter[id]
        call TriggerExecute(kb_trg_onHit[id])
    endif
endfunction
//------------------------------------------------------------------------------------------------
// use this for bounce from structures/cliffs/doodads ONLY:
//------------------------------------------------------------------------------------------------
function KB_BounceUnit takes unit u, integer id, real unitX, real unitY returns nothing
    set kb_angle[id] = KB_GetBounceAngle(kb_angle[id], unitX, unitY, id) //change angle
    set kb_sin[id] = Sin(kb_angle[id])
    set kb_cos[id] = Cos(kb_angle[id])
    call SetUnitX(u, unitX + kb_d1[id] * kb_cos[id])
    call SetUnitY(u, unitY + kb_d1[id] * kb_sin[id])
    set kb_d1[id] = kb_d1[id] * kb_reduceSpeedOnHit // reducing distance kb_d1
    call KB_BounceEvent(u, id, KB_HIT_THE_WALL) //fire hit event
endfunction
//------------------------------------------------------------------------------------------------
//-this for better visual effect --
//-KB_BounceEvent is not included here inside this func -----------
function KB_BounceUnitReverseWithMinSpeed takes unit u, integer id returns nothing
    local real angle = (kb_angle[id] + bj_PI)
    if kb_angle[id]>0.00 then // reverse direction:
        set angle = (kb_angle[id] - bj_PI)
    endif // initiate new kb, so old one will be ended:
    call KnockBack(null, u, 25.00, 0.30, angle, false, kb_killTrees[id], kb_disableMovement[id], KB_TYPE_SLIDE, KB_FILTR_BOUNCE_NONE, null, null, 1.00)
endfunction
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
function KB_TwoUnitsHit takes unit mainUnit, integer id, unit obstacleUnit, integer obstacleId returns nothing
    local integer kb1 = kb_knockbackType[id]
    local integer kb2
    if IsUnitInGroup(obstacleUnit, kb_group) then
        set kb2 = kb_knockbackType[obstacleId]
        
        if kb2 == KB_TYPE_NO_BOUNCE then
            set kb_obstacle_unit = obstacleUnit
            call KB_BounceEvent(mainUnit, id, KB_HIT_OBSTACLE_UNIT)
            set kb_obstacle_unit = mainUnit
            call KB_BounceEvent(obstacleUnit, obstacleId, KB_HIT_OBSTACLE_UNIT)
            if kb2 == kb1 then // both "no-bounce"
                call KB_BounceUnitReverseWithMinSpeed(mainUnit, id)
                call KB_BounceUnitReverseWithMinSpeed(obstacleUnit, obstacleId)
            endif
 
        elseif (kb2 == KB_TYPE_NORMAL or kb2 == KB_TYPE_STOP_ON_OBSTACLES) and (kb1 == KB_TYPE_NORMAL or kb1 == KB_TYPE_STOP_ON_OBSTACLES or kb1 == KB_TYPE_SLIDE) then
            set kb_obstacle_unit = mainUnit
            call KB_BounceEvent(obstacleUnit, obstacleId, KB_HIT_OBSTACLE_UNIT)
            set kb_obstacle_unit = obstacleUnit
            call KB_BounceEvent(mainUnit, id, KB_HIT_OBSTACLE_UNIT)
        endif
      
    else // obstacle unit *not* in kb_group:
        set kb_obstacle_unit = obstacleUnit
        call KB_BounceEvent(mainUnit, id, KB_HIT_OBSTACLE_UNIT)
        if kb1 == KB_TYPE_NO_BOUNCE then
            call KB_BounceUnitReverseWithMinSpeed(mainUnit, id)
        endif
    endif
endfunction

//-------------------------------------------------------------------------------
// kb_u primary unit, unitX = GetUnitX(kb_u)
// bouncePrimaryUnit - if "true" will bounce kb_u in opposite direction to new unit on way
// bounceNewUnit -if "true" will bounce new unit in opposite direction to kb_u unit
function KB_CheckUnitsAroundAndBounce takes unit kb_u, integer kb_id, real unitX, real unitY, real targetX, real targetY, integer filtrNr, boolean bouncePrimaryUnit, boolean bounceNewUnit returns boolean
    local unit obst_u
    local integer obst_id
    local real obstX
    local real obstY  
    local integer foundUnits=0
    local real angle
    local integer t
    local integer b
    local real Tx
    local real Ty
    local real c
    local real newDistance
  
    set kb_player = GetOwningPlayer(kb_u)
    call GroupEnumUnitsInRange(kb_ug, targetX, targetY, KB_DETECT_OBSTACLE_UNIT_RADIUS, kb_filtr[filtrNr])
    loop
        set obst_u = FirstOfGroup(kb_ug)
        exitwhen obst_u == null
        set obstX = GetUnitX(obst_u)
        set obstY = GetUnitY(obst_u)
        set c = KB_GetUnitCollisionSize(obst_u, obstX, obstY)
        if (obst_u != kb_u) and IsUnitInRange(obst_u, kb_u, RMaxBJ(c, kb_collision[kb_id])) then //pick bigger collision
            set foundUnits = 1
            set obst_id = GetUnitUserData(obst_u)
            call KB_TwoUnitsHit(kb_u, kb_id, obst_u, obst_id)
            // if bounce primary unit:
            if bouncePrimaryUnit then
                set angle = Atan2(unitY - obstY, unitX - obstX) // reverse angle
                set Tx = unitX+kb_d1[kb_id] * Cos(angle)
                set Ty = unitY+kb_d1[kb_id] * Sin(angle)              
                if KB_IsPointWalkableForSize(Tx, Ty, kb_collision[kb_id]) then
                    call SetUnitX(kb_u, Tx)
                    call SetUnitY(kb_u, Ty)
                    set kb_angle[kb_id] = angle //change angle
                    set kb_sin[kb_id] = Sin(kb_angle[kb_id])
                    set kb_cos[kb_id] = Cos(kb_angle[kb_id])
                    set kb_d1[kb_id] = kb_d1[kb_id] * kb_reduceSpeedOnHit // reducing distance kb_d1
                else // not walkable:
                    set kb_d1[kb_id] = 0.00 // end knockback
                endif
            elseif kb_knockbackType[kb_id] == KB_TYPE_SLIDE then // if not bounce, then move [for slide type only]:
                call SetUnitX(kb_u, targetX)
                call SetUnitY(kb_u, targetY)
            endif          

            // if bounce obstacle unit:
            if bounceNewUnit then
                set angle = Atan2(obstY - unitY, obstX - unitX) // reverse angle
                if IsUnitInGroup(obst_u, kb_group) then
                    if kb_knockbackType[kb_id] == KB_TYPE_SLIDE then // hit by sliding unit, initiate new slide, slide overwrites existing one
                        if kb_timeLeft[kb_id] * kb_distanceLeft[kb_id] > 1.00 then //don't run very low knockbacks! <---
                            set b = kb_bounceFiltrType[kb_id] // new bounce filter:
                            if kb_bounceFiltrType[kb_id] == KB_FILTR_BOUNCE_ENEMY_ONLY then
                                set b = KB_FILTR_BOUNCE_ALLIED_ONLY
                            endif
                            set angle = KB_GetBounceAngle90(kb_angle[kb_id], obstX, obstY, unitX, unitY) //angle 90 degrees
                            if KB_IsPointWalkableForSize((obstX+kb_d1[obst_id] * Cos(angle)), (obstY+kb_d1[obst_id] * Sin(angle)), c) then
                                call KnockBack(null, obst_u, kb_distanceLeft[kb_id], kb_timeLeft[kb_id], angle, kb_interruptSpells[kb_id], kb_killTrees[kb_id], kb_disableMovement[kb_id], kb_knockbackType[kb_id], b, kb_effect[kb_id], kb_effectAttachPoint[kb_id], kb_effectOccursDistance[kb_id])
                            endif
                        endif
                    else // hit by non-sliding unit, update existing kb:
                        set Tx = obstX+kb_d1[obst_id] * Cos(angle)
                        set Ty = obstY+kb_d1[obst_id] * Sin(angle)
                        if KB_IsPointWalkableForSize(Tx, Ty, c) then
                            call SetUnitX(obst_u, Tx)
                            call SetUnitY(obst_u, Ty)
                            set kb_angle[obst_id] = angle //change angle
                            set kb_sin[obst_id] = Sin(kb_angle[obst_id])
                            set kb_cos[obst_id] = Cos(kb_angle[obst_id])
                            set kb_d1[obst_id] = kb_d1[obst_id] * kb_reduceSpeedOnHit // reducing distance kb_d1
                        endif
                    endif
                elseif (kb_timeLeft[kb_id] * kb_distanceLeft[kb_id]) > 1.00 then //unit NOT in kb_group, initial new kb; don't run very low knockbacks! <---
                    set b = kb_bounceFiltrType[kb_id] // new bounce filter:
                    if b == KB_FILTR_BOUNCE_ENEMY_ONLY then
                        set b = KB_FILTR_BOUNCE_ALLIED_ONLY
                    endif                      
                    if kb_knockbackType[kb_id] == KB_TYPE_SLIDE then // if obstacle hits by sliding unit, change angle to 90
                        set angle = KB_GetBounceAngle90(kb_angle[kb_id], obstX, obstY, unitX, unitY) //angle 90 degrees
                    endif // now calculate new distance to get the same speed as kb_u
                    set newDistance = (kb_d1[kb_id] * ((I2R(R2I(kb_timeLeft[kb_id] / KB_INTERVAL)) + 1))) / 2.00
                    if KB_IsPointWalkableForSize((obstX+kb_d1[obst_id] * Cos(angle)), (obstY+kb_d1[obst_id] * Sin(angle)), c) then
                        call KnockBack(null, obst_u, newDistance, kb_timeLeft[kb_id], angle, kb_interruptSpells[kb_id], kb_killTrees[kb_id], kb_disableMovement[kb_id], kb_knockbackType[kb_id], b, kb_effect[kb_id], kb_effectAttachPoint[kb_id], kb_effectOccursDistance[kb_id])
                    endif
                endif
            endif
            //bounce obstacle unit:end
        endif
        call GroupRemoveUnit(kb_ug, obst_u)
    endloop
    call GroupClear (kb_ug)
      
    set obst_u=null
    return foundUnits > 0
endfunction
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
function KB_MoveUnit takes unit u, integer id, real unitX, real unitY, real targetX, real targetY returns nothing
    local boolean isWalkable = KB_IsPointWalkableForSize(targetX, targetY, kb_collision[id])

    if kb_knockbackType[id] == KB_TYPE_NO_BOUNCE then // no bounce at all
        if not isWalkable then
            call KB_BounceEvent(u, id, KB_HIT_THE_WALL) //fire hit event, obstacle
            call KB_BounceUnitReverseWithMinSpeed(u, id)
        elseif kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE then // walkable:
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        elseif (not KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, kb_bounceFiltrType[id], false, false)) then //3 other filters:
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        endif

    //---------------------------------SLIDE:-------------------------------------------------      
    elseif kb_knockbackType[id] == KB_TYPE_SLIDE then
        if not isWalkable then
            call KB_BounceUnit(u, id, unitX, unitY) //fires hit event
        elseif kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE then
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        elseif (not KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, kb_bounceFiltrType[id], false, true)) then //3 other filters:
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        endif
      
    //--------------------------------- KB_TYPE_NORMAL / KB_TYPE_STOP_ON_OBSTACLES  -------------------      
    elseif kb_knockbackType[id] == KB_TYPE_NORMAL or kb_knockbackType[id] == KB_TYPE_STOP_ON_OBSTACLES then
        if not isWalkable then
            if kb_knockbackType[id] == KB_TYPE_NORMAL then
                call KB_BounceUnit(u, id, unitX, unitY)          
            elseif kb_knockbackType[id] == KB_TYPE_STOP_ON_OBSTACLES then
                call KB_BounceEvent(u, id, KB_HIT_THE_WALL) //fire hit event, obstacle
                call KB_BounceUnitReverseWithMinSpeed(u, id)
            endif
        elseif (kb_bounceFiltrType[id] == KB_FILTR_BOUNCE_NONE) then //walkable:
            if (not (KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, KB_FILTR_BOUNCE_ALL, true, false))) then
                call SetUnitX(u, targetX)
                call SetUnitY(u, targetY)
            endif
        elseif (not (KB_CheckUnitsAroundAndBounce(u, id, unitX, unitY, targetX, targetY, kb_bounceFiltrType[id], true, true))) then //3 other filters
            call SetUnitX(u, targetX)
            call SetUnitY(u, targetY)
        endif
      
    endif
endfunction
//-----------------------------------------------------------------
function KB_KillTree takes nothing returns nothing
    call KillTree(GetEnumDestructable())
endfunction
//------------------------------------------------------------------          
function KB_ShowEffect takes unit u, integer id returns nothing
    if kb_effect[id] != null and (kb_distanceLeft[id] < (kb_lastDistanceEffect[id] - kb_effectOccursDistance[id])) then
        call DestroyEffect(AddSpecialEffectTarget(kb_effect[id], u, kb_effectAttachPoint[id]))
        set kb_lastDistanceEffect[id] = kb_distanceLeft[id]
    endif
endfunction
//------------------------------------------------------------------
function KB_LoopEnum takes nothing returns nothing
    local unit u = GetEnumUnit()
    local integer id = GetUnitUserData(u)
    local real unitX = GetUnitX(u)
    local real unitY = GetUnitY(u)  
    local real targetX = unitX + kb_d1[id] * kb_cos[id]
    local real targetY = unitY + kb_d1[id] * kb_sin[id]

    if (not IsUnitAlive(u)) or kb_d1[id] <= 0.00 then
        call KB_DestroyKnockbackOnUnit(u)
        set u=null
        return
    endif  
    if kb_killTrees[id] and ((kb_d1[id]*KB_STEPS_PER_SEC) > kb_minSpeedToKillTree) then
        call MoveRectTo(kb_rect, targetX, targetY)
        call EnumDestructablesInRect(kb_rect, null, function KB_KillTree)
    endif
    call KB_ShowEffect(u, id)
    call KB_MoveUnit(u, id, unitX, unitY, targetX, targetY)

    set kb_d1[id] = kb_d1[id] - kb_d2[id] // decrease speed
    set kb_distanceLeft[id] = kb_distanceLeft[id] - kb_d1[id] // for additional kb-ed units
    set kb_timeLeft[id] = kb_timeLeft[id] - KB_INTERVAL // for additional kb-ed units  
    set u=null
endfunction
//--------------------------------------------------------------------------
function KB_Loop takes nothing returns boolean
    call ForGroup(kb_group, function KB_LoopEnum)
    return false
endfunction
//=======================================================
//=======================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    set kb_ug = CreateGroup()
    set kb_group = CreateGroup()
    set kb_timer = CreateTimer()
    set kb_rect = Rect(0.0, 0.0, 256.0, 256.0)
    set kb_trg_loop = CreateTrigger()
    call TriggerRegisterTimerExpireEvent(kb_trg_loop, kb_timer) //timer kb_timer expires
    call TriggerAddCondition(kb_trg_loop, Condition(function KB_Loop))
    //----------------------------------------------------
    call KB_AdvancedUserConfigurables()
    set kb_minSpeedToKillTree = RMaxBJ(kb_minSpeedToKillTree, 0.00)
  
    if kb_reduceSpeedOnHitFactor < 0.00 then
        set kb_reduceSpeedOnHitFactor = 0.00
    elseif kb_reduceSpeedOnHitFactor > 100.00 then
        set kb_reduceSpeedOnHitFactor = 100.00
    endif
    set kb_reduceSpeedOnHit = 1.00 - (kb_reduceSpeedOnHitFactor / 100.00) // for kb_d1, range: 0.00 ... 1.00
  
    //----------------------------------------------------      
    set kb_filtr[KB_FILTR_BOUNCE_NONE] =            Condition(function KBFiltr_groundNotStruc)    
    set kb_filtr[KB_FILTR_BOUNCE_ALL] =                Condition(function KBFiltr_groundNotStruc)
    set kb_filtr[KB_FILTR_BOUNCE_ALLIED_ONLY] = Condition(function KBFiltr_groundNotStrucAlliedOnly)
    set kb_filtr[KB_FILTR_BOUNCE_ENEMY_ONLY] = Condition(function KBFiltr_groundNotStrucEnemyOnly)  
    call TriggerRegisterVariableEvent( t, "udg_UnitIndexEvent", EQUAL, 2.00 ) // remove unit detection
    call TriggerAddAction(t, function KB_OnRemoveFromGame)
    set t=null
endfunction
endlibrary
Previews
Contents

library knockback simple v.1.03 (Map)

You have a lot of needless extra IF statements - a large amount of them can be combined.
if your code follows the structure
JASS:
if (condition) then
  if (condition) then
    //actions
  endif
endif

then you can just make it
JASS:
if (condition) and (condition) then
  //actions
endif
Though I'm aware you've obviously done this you should do this all the time when possible

I'd also suggest commonly used constant values like 2 * PI and PI / 2 should be stored in constant variables
Attachment Points should be configurable (or should be selectable when adding a knockback)
In addition using integers is faster than using enum (ForGroup)
via the structure:
JASS:
local unit u = FirstOfGroup(group)

loop
  exitwhen u == null

  //if filtering
  if (condition) then
    //actions
  endif

  set u = FirstOfGroup(group)
endloop

(obviously only use the priming if this is all the function is going to do, otherwise you'd intialise it as null and move set u = FirstOfGroup(group) to above the exitwhen) Again this is something you do sometimes but not others, do it all the time when you can
 
Last edited:
Level 18
Joined
Nov 21, 2012
Messages
835
Ok. Tank, thanks for your tips.
I hope I cut off all of needless "if" statements.
I'd also suggest commonly used constant values like 2 * PI and PI / 2 should be stored in constant variables
done
Attachment Points should be configurable
done
In addition using integers is faster than using enum
I based this concept on ForGroup and group where I keep kb'ed units. If you think it is acceptable please don't force me to change this
I don't get your last suggeston, FirstOfGroup is used once in function KB_CheckUnitsAroundAndBounce, part of the code:
call GroupEnumUnitsInRange(kb_ug, targetX, targetY, 150.00, kb_filtr[filtrNr]) // alive+ground+not structure loop set obst_u = FirstOfGroup(kb_ug) exitwhen obst_u == null

btw is image readable ? I tryed to put there all info about knockbacked unit's behavior.
@KILLCIDE thanks for help;]
 
I noticed extra functions that aren't needed, for example this line:
"call GroupEnumUnitsInRange(kb_ug, targetX, targetY, 150.00, kb_filtr[filtrNr])"
calls kb_filtr[filtrNr] which is 1 line long
"return KBFiltr_1(GetFilterUnit())"
you can cut out that function entirely and skip straight to it by using if (KB_Filtr_1(u)) then inside you loop like this:
JASS:
    set kb_player = GetOwningPlayer(kb_u)
    call GroupEnumUnitsInRange(kb_ug, targetX, targetY, 150.00, null)
    loop
        set obst_u = FirstOfGroup(kb_ug)
        exitwhen obst_u == null
        if KBFiltr_1(u) then // alive+ground+not structure
           //actions
        endif
    endloop
doing that will cut the amount of function calls to filter units in half
Aside from that why is it called KBFiltr_1 when you don't have a KBFiltr_2? and what are the other functions for exactly (in the sense that it appears KBFiltr_1 already has the ability to tell which filter is being used and respond accordingly, wouldn't that make the other filters useless as they're only used on that one line I reference above and never otherwise called) Correct me if I'm wrong of course but that's how it seems to me

That said also that 150.00 hardcoded value could be configurable - pretty much all constants can
I'll need to set some time aside for a more detailed review of the code considering the need to compare this with other similar knockback systems so I apologize that I'm just dropping the odd note or two you can expect a full review in about half a week (I'm out for the next few days)
 
Level 18
Joined
Nov 21, 2012
Messages
835
I think you were misled of my bad description: // alive+ground+not structure
sorry for that. function KB_CheckUnitsAroundAndBounce among others takes integer filtrNr. This integer refers to 4 avaiable boolexpr array kb_filtr, they are set up on init function at bottom of the library:
JASS:
 set kb_filtr[KB_FILTR_BOUNCE_NONE] =  Condition(function KBFiltr_groundNotStruc)   
 set kb_filtr[KB_FILTR_BOUNCE_ALL] =  Condition(function KBFiltr_groundNotStruc)
 set kb_filtr[KB_FILTR_BOUNCE_ALLIED_ONLY] = Condition(function KBFiltr_groundNotStrucAlliedOnly)
 set kb_filtr[KB_FILTR_BOUNCE_ENEMY_ONLY] = Condition(function KBFiltr_groundNotStrucEnemyOnly)
condition functions :
JASS:
function KBFiltr_groundNotStruc takes nothing returns boolean
   return KBFiltr_1(GetFilterUnit())
endfunction
// set kb_player before confirm this 2 checks!
function KBFiltr_groundNotStrucAlliedOnly takes nothing returns boolean
  return KBFiltr_1(GetFilterUnit()) and IsUnitAlly(GetFilterUnit(), kb_player)
endfunction
function KBFiltr_groundNotStrucEnemyOnly takes nothing returns boolean
  return KBFiltr_1(GetFilterUnit()) and IsUnitEnemy(GetFilterUnit(), kb_player)   
endfunction
you are right that func KBFiltr_1 can be deleted, and its content moved to inside KBFiltr_groundNotStruc
but in this case I have to add few more lines (declare local unit u = GetFilterUnit(), and null it 3 times before "return"), so it would look like this:
JASS:
function KBFiltr_groundNotStruc takes nothing returns boolean
    local unit u = GetFilterUnit()
    local integer a=1
    local integer id = GetUnitUserData(u)
    local boolean isKb = IsUnitInGroup(u, kb_group) // is knockbacked?    
    if IsUnitAlive(u) and (not IsUnitType(u, UNIT_TYPE_FLYING)) and (not IsUnitType(u, UNIT_TYPE_STRUCTURE)) and (IsUnitVisible(u, kb_player)) then
        if not (isKb and ((kb_knockbackType[id]==KB_TYPE_SLIDE) or (kb_knockbackType[id]==KB_TYPE_NO_BOUNCE and kb_bounceFiltrType[id]==KB_FILTR_BOUNCE_NONE))) then
            if not (isKb and ((IsUnitAlly(u, kb_player) and kb_bounceFiltrType[id]==KB_FILTR_BOUNCE_ENEMY_ONLY) or (IsUnitEnemy(u, kb_player) and kb_bounceFiltrType[id]==KB_FILTR_BOUNCE_ALLIED_ONLY))) then
                loop
                    exitwhen a>kb_filterOutAbiInteger
                    if GetUnitAbilityLevel(u, kb_filterOutAbility[a]) > 0 then
                        set u=null
                        return false
                    endif
                    set a=a+1
                endloop
                set u=null
                return true        
            endif
        endif
    endif
    set u=null
    return false
endfunction
I dont know if we get some noticeable advantage with change above?
For "150.00 hardcoded value" to be honest : for what reason?(Bribe has also hardcoded such a value in his gui kb):oops:
I'll wait with future updates for deeper look at the code. Thanks for all your current notes.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
For "150.00 hardcoded value" to be honest : for what reason?(Bribe has also hardcoded such a value in his gui kb):oops:

Hardcoded values are now criticized these days, I also disagree in this matter somehow my old spells have these hardcoded haven't been configurable because they are not prefer to be touch by the user. But still, its better to do it for extensive configuration also :p enabling the user to reach the beyond limitation of the system or spell modifying.
 
Last edited:
Level 18
Joined
Nov 21, 2012
Messages
835
because they are not prefer to be touch by the user
yeah, the same feeling here, jake
but of course no problem for me to add constant variable instead of 150.00
In next update I'll change calculations for new unit who was hit by kb'ed unit. Starting speed for new unit will be the same as kb'ed unit's speed at the moment of collision. Small fix but importand for more natural look.
 
This is more or less approvable aside from the change you're working on above there (I noticed it myself when I was testing), the screenshot used for the system is pretty unclear though without testing the system but that's fine though I'd still recommend using an ingame screenshot and including that one in the description while we have a number of knockback systems this offers a few additional features most others don't have

Notify me when you've updated so I can approve this
 
Level 18
Joined
Nov 21, 2012
Messages
835
Tank, I have a question about code.
I'm using dynamicaly created triggers with trigger action as a callback code.
When knockback expires (or new kb applied to a unit already kb'ed) I destroy trigger like this:
JASS:
  if kb_trg_onHit[id] != null then // id is unitUserData
    call DestroyTrigger(kb_trg_onHit[id])
    set kb_trg_onHit[id] = null
  endif

should I first disable trigger and then destroy, or current code if fine?
 
Level 18
Joined
Nov 21, 2012
Messages
835
updated to ver 1.03:
  • changes in function KB_CheckUnitsAroundAndBounce, replaced real value 150.00 by constant variable KB_DETECT_OBSTACLE_UNIT_RADIUS, and
  • changed new knockbacked unit distance calculations so starting speed for new unit will be the same as kb'ed unit's speed at the moment of collision
  • uploaded pictures
 
Top