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

GUI Knockback 2.5D v4.2.5.0

Knockback 2D will knock a unit back - without interrupting orders - while also considering pathing checks for different collision sizes. It destroys trees and/or debris without destroying bridges and the user can fully configure it. It allows for deep-water movement if desired.

You specify the speed of the knockback indirectly by giving it a distance to travel and a time to travel it within. This uses the same formula as Berb's Knockback Lite.

If the unit hits impassible terrain, it will "bounce" in a form of a geometric deflection angle. This can be toggled on or off per knockback.

The demo map features a template where you can add on-loop behavior. To the knockback.

It is in GUI, meaning it is World Editor friendly. I hope that it is also easy to understand how it works as the variables are pretty straightforward:
- A unit to knock back (Knockback2DUnit), an angle (in degrees) to knock it back (Knockback2DAngle), a distance it will travel (Knockback2DDistance) and a length of time to complete the travel within (Knockback2DTime).


  1. Copy the Unit Indexer and Knockback 2D triggers into your map.
  2. There is one dummy created in the Config trigger that is a simple Undead Ghoul. It could just as well be a Peasant or Peon. Just make sure that the unit type created has the harvest ability and doesn't have some passive effects like Phoenix fire or immolation.


  • Knockback 2D Config
    • Events
      • Game - UnitIndexEvent becomes Equal to 3.00
    • Conditions
    • Actions
      • Set CenterPoint = (Point(K2DX, K2DY))
      • Set UnitIndexerEnabled = False
      • -------- --------
      • -------- Configure things below --------
      • -------- --------
      • -------- Set the timeout to be used throughout the system --------
      • -------- --------
      • Set K2DTimeout = (1.00 / 60.00)
      • -------- --------
      • -------- Robust Pathing at 0 is only safe for collision sizes 16 and lower, but requires only one SetItemPosition check per timeout. --------
      • -------- -------- 1 checks collision vertically and horizontally to momentum. Uses an extra 4 SetItemPosition checks pet timeout. --------
      • -------- -------- 2 checks collision diagonally with momentum. Uses a total of 9 SetItemPosition checks per timeout. --------
      • -------- In any case, if the unit only has size 16 or lower collision, only one SetItemPosition check will be used for it. --------
      • -------- If RobustPathing is set to 2 and the unit has 36 or less collision, it will only use the normal check of 5 SetItemPosition calls --------
      • -------- The only reason to use robustness above 1 is for visual continuity - it features potentially-less glitchy movement. --------
      • -------- --------
      • Set Knockback2DRobustPathing = 2
      • -------- --------
      • -------- Keep the friction between 0.00 and 1.00, At 0.00, friction keeps the unit at the same speed for the knockback --------
      • -------- 1.00 friction will be an evenly-distributed deceleration which sees the unit slow to a complete stop --------
      • -------- Friction outside of these bounds gives the knockback a boomerang-effect, so you are welcome to experiment. --------
      • -------- --------
      • Set Knockback2DDefaultFriction = 1.00
      • Set Knockback2DFriction = Knockback2DDefaultFriction
      • -------- --------
      • -------- Determine the default bouncing behavior of units. You can set this before knocking a unit back. --------
      • -------- --------
      • Set Knockback2DDefaultBounce = True
      • Set Knockback2DBounces = Knockback2DDefaultBounce
      • -------- --------
      • -------- Determine the default mechanics of whether a unit should be unable to move while knocked back --------
      • -------- --------
      • Set Knockback2DDefaultPause = False
      • Set Knockback2DPause = Knockback2DDefaultPause
      • -------- --------
      • -------- Determine if surrounding trees should be killed by default or not --------
      • -------- --------
      • Set Knockback2DDefaultKillTrees = True
      • Set Knockback2DKillTrees = Knockback2DDefaultKillTrees
      • -------- --------
      • -------- If so, how wide should the radius be? 128.00 should be the minimum if you use pathing robustness greater than 0. --------
      • -------- The minimum should be 64 if you use a robustness of 0. --------
      • -------- --------
      • Set Knockback2DDefaultDestRadius = 128.00
      • Set Knockback2DDestRadius = Knockback2DDefaultDestRadius
      • -------- --------
      • -------- The "attack" option below will destroy any valid debris, from trees to barrels to creep homes. --------
      • -------- If you just want to destroy trees, change the string to: harvest --------
      • -------- --------
      • Set Knockback2DTreeOrDebris = attack
      • -------- --------
      • -------- 0.50 gravity will have equal ascend and decline rate, 1.00 is instant descend, 0.67 is twice as fast, 0.75 is three times as fast. --------
      • -------- --------
      • Set Knockback2DDefaultGravity = 0.71
      • Set Knockback2DGravity = Knockback2DDefaultGravity
      • -------- --------
      • -------- Change the following to the default type of looping FX you want to have if you use Knockback Effects --------
      • -------- --------
      • Set Knockback2DDefaultFX = Abilities\Weapons\AncientProtectorMissile\AncientProtectorMissile.mdl
      • Set Knockback2DLoopFX = Knockback2DDefaultFX
      • -------- --------
      • -------- How frequently should the effects appear per unit? This can also be customized per-knockback --------
      • -------- --------
      • Set Knockback2DDefaultFXRate = 0.10
      • Set Knockback2DFXRate = Knockback2DDefaultFXRate
      • -------- --------
      • -------- Create an item to help verify pathing throughout the game --------
      • -------- --------
      • Item - Create Slippers of Agility +3 at CenterPoint
      • Set K2DItem = (Last created item)
      • -------- --------
      • -------- Create a harvest-capable unit to check if debris can be killed --------
      • -------- --------
      • Unit - Create 1 Ghoul for Neutral Passive at CenterPoint facing 0.00 degrees
      • Set K2DDebrisKiller = (Last created unit)
      • -------- --------
      • -------- End Configuration --------
      • -------- --------
      • Set UnitIndexerEnabled = True
      • Custom script: call RemoveLocation(udg_CenterPoint)
      • Game - Preload Knockback2DDefaultFX
      • Item - Hide K2DItem
      • Unit - Hide K2DDebrisKiller
      • Unit - Pause K2DDebrisKiller
      • Custom script: call UnitAddAbility(udg_K2DDebrisKiller, 'Aloc')
      • Set Radians_Turn = (Radians(360.00))
      • Set Radians_QuarterTurn = (Radians(90.00))
      • Set Radians_QuarterPi = (Radians(45.00))
      • Set K2DRegion = (Entire map)
      • Set K2DMaxX = (Max X of K2DRegion)
      • Set K2DMaxY = (Max Y of K2DRegion)
      • Set K2DMinX = (Min X of K2DRegion)
      • Set K2DMinY = (Min Y of K2DRegion)
      • Set K2DMaxDestRadius = (Knockback2DDefaultDestRadius x 2.00)
      • Custom script: call SetRect(udg_K2DRegion, 0.00, 0.00, udg_K2DMaxDestRadius, udg_K2DMaxDestRadius)
      • Set K2DItemsFound = False
      • Set K2DItemOffset = False
  • Knockback 2D
    • Events
    • Conditions
      • (Default movement speed of Knockback2DUnit) Not equal to 0.00
      • K2DOverride[(Custom value of Knockback2DUnit)] Equal to False
    • Actions
      • Custom script: local integer pdex = udg_UDex
      • Set UDex = (Custom value of Knockback2DUnit)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • IsUnitBeingKnockedBack[UDex] Equal to True
        • Then - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Knockback2DOverride Equal to False
            • Then - Actions
              • Set K2DAngle[UDex] = (Degrees(K2DAngle[UDex]))
              • Set Knockback2DAngle = ((Knockback2DAngle + K2DAngle[UDex]) x 0.50)
              • Set Knockback2DDistance = ((K2DDistanceLeft[UDex] + Knockback2DDistance) x 0.50)
              • Set Knockback2DTime = ((K2DTimeLeft[UDex] + Knockback2DTime) x 0.50)
            • Else - Actions
          • Trigger - Run Knockback 2D Destroy <gen> (ignoring conditions)
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • K2DNext[0] Equal to 0
        • Then - Actions
          • Custom script: call ExecuteFunc("StartKnockback2DTimer")
        • Else - Actions
      • Set IsUnitBeingKnockedBack[UDex] = True
      • Set K2DPrev[K2DNext[0]] = UDex
      • Set K2DNext[UDex] = K2DNext[0]
      • Set K2DNext[0] = UDex
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Knockback2DHeight Not equal to 0.00
        • Then - Actions
          • Set K2DHeight[UDex] = Knockback2DHeight
          • Custom script: if UnitAddAbility(udg_Knockback2DUnit, 'Amrf') then
          • Custom script: call UnitRemoveAbility(udg_Knockback2DUnit, 'Amrf')
          • Custom script: endif
          • Animation - Change Knockback2DUnit flying height to ((Default flying height of Knockback2DUnit) + Knockback2DHeight) at ((Knockback2DHeight - (Default flying height of Knockback2DUnit)) / (Knockback2DGravity x Knockback2DTime))
          • Set K2DHeightThreshold[UDex] = ((1.00 - Knockback2DGravity) x Knockback2DTime)
          • Set Knockback2DHeight = 0.00
        • Else - Actions
          • Set K2DHeight[UDex] = 0.00
      • Custom script: set udg_K2DX = GetUnitX(udg_Knockback2DUnit)
      • Custom script: set udg_K2DY = GetUnitY(udg_Knockback2DUnit)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Knockback2DPause Equal to True
        • Then - Actions
          • Set K2DLastX[UDex] = K2DX
          • Set K2DLastY[UDex] = K2DY
        • Else - Actions
      • Set K2DAngle[UDex] = (Radians(Knockback2DAngle))
      • Set K2DCos[UDex] = (Cos(Knockback2DAngle))
      • Set K2DSin[UDex] = (Sin(Knockback2DAngle))
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Knockback2DRobustPathing Greater than 0
        • Then - Actions
          • -------- --------
          • -------- Handle the pathing checker based on the unit's collision size --------
          • -------- --------
          • Custom script: if not IsUnitInRangeXY(udg_Knockback2DUnit, udg_K2DX + 17, udg_K2DY, 0) then
          • Set K2DRadius[UDex] = 0
          • Custom script: else
          • Custom script: if not IsUnitInRangeXY(udg_Knockback2DUnit, udg_K2DX + 25, udg_K2DY, 0) then
          • Set K2DRadius[UDex] = 8
          • Custom script: elseif not IsUnitInRangeXY(udg_Knockback2DUnit, udg_K2DX + 33, udg_K2DY, 0) then
          • Set K2DRadius[UDex] = 16
          • Custom script: elseif not IsUnitInRangeXY(udg_Knockback2DUnit, udg_K2DX + 49, udg_K2DY, 0) then
          • Set K2DRadius[UDex] = 32
          • Custom script: else
          • Set K2DRadius[UDex] = 48
          • Custom script: endif
          • Set Knockback2DAngle = ((Knockback2DAngle + 90.00) mod 360.00)
          • Set K2DCosH[UDex] = (Cos(Knockback2DAngle))
          • Set K2DSinH[UDex] = (Sin(Knockback2DAngle))
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Knockback2DRobustPathing Equal to 2
              • K2DRadius[UDex] Greater than 16
            • Then - Actions
              • Set Knockback2DAngle = ((Knockback2DAngle + 45.00) mod 360.00)
              • Set K2DCosD1[UDex] = (Cos(Knockback2DAngle))
              • Set K2DSinD1[UDex] = (Sin(Knockback2DAngle))
              • Set Knockback2DAngle = ((Knockback2DAngle + 90.00) mod 360.00)
              • Set K2DCosD2[UDex] = (Cos(Knockback2DAngle))
              • Set K2DSinD2[UDex] = (Sin(Knockback2DAngle))
            • Else - Actions
          • Custom script: endif
        • Else - Actions
      • Set K2DDistanceLeft[UDex] = Knockback2DDistance
      • Set Knockback2DDistance = (((1.00 + Knockback2DFriction) x Knockback2DDistance) / Knockback2DTime)
      • Set K2DFriction[UDex] = ((Knockback2DDistance / Knockback2DTime) x ((1.00 - (1.00 - Knockback2DFriction)) x (K2DTimeout x K2DTimeout)))
      • Set K2DVelocity[UDex] = (Knockback2DDistance x K2DTimeout)
      • -------- --------
      • Set K2DKillTrees[UDex] = Knockback2DKillTrees
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Knockback2DKillTrees Equal to True
        • Then - Actions
          • -------- Square the radius so we don't have to use SquareRoot when comparing distance. --------
          • Set K2DDestRadius[UDex] = (Knockback2DDestRadius x Knockback2DDestRadius)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Knockback2DDestRadius x 2.00) Greater than K2DMaxDestRadius
            • Then - Actions
              • -------- Update the size of the enumerating rect to compensate for the desired extra radius. --------
              • Set K2DMaxDestRadius = (Knockback2DDestRadius x 2.00)
              • Custom script: call SetRect(udg_K2DRegion, 0.00, 0.00, udg_K2DMaxDestRadius, udg_K2DMaxDestRadius)
            • Else - Actions
          • Set Knockback2DDestRadius = Knockback2DDefaultDestRadius
        • Else - Actions
      • -------- --------
      • Set K2DAmphibious[UDex] = Knockback2DAmphibious
      • Set K2DBounce[UDex] = Knockback2DBounces
      • Set K2DCollision[UDex] = Knockback2DCollision
      • Set K2DFreeze[UDex] = False
      • Set K2DFXModel[UDex] = Knockback2DLoopFX
      • Set K2DFXRate[UDex] = Knockback2DFXRate
      • Set K2DFXTimeLeft[UDex] = Knockback2DFXRate
      • Set K2DImpact[UDex] = Knockback2DOnImpact
      • Set K2DOverride[UDex] = Knockback2DOverride
      • Set K2DPause[UDex] = Knockback2DPause
      • Set K2DSimple[UDex] = Knockback2DSimple
      • Set K2DSource[UDex] = Knockback2DSource
      • Set K2DTimeLeft[UDex] = Knockback2DTime
      • Set K2DUnbiasedCollision[UDex] = Knockback2DUnbiasedCollision
      • Set Knockback2DAmphibious = False
      • Set Knockback2DBounces = Knockback2DDefaultBounce
      • Set Knockback2DCollision = 0.00
      • Set Knockback2DFriction = Knockback2DDefaultFriction
      • Set Knockback2DFXRate = Knockback2DDefaultFXRate
      • Set Knockback2DGravity = Knockback2DDefaultGravity
      • Set Knockback2DKillTrees = Knockback2DDefaultKillTrees
      • Set Knockback2DLoopFX = Knockback2DDefaultFX
      • Custom script: set udg_Knockback2DOnImpact = null
      • Set Knockback2DOverride = False
      • Set Knockback2DPause = Knockback2DDefaultPause
      • Set Knockback2DSimple = False
      • Set Knockback2DSource = No unit
      • Set Knockback2DUnbiasedCollision = False
      • Custom script: set udg_UDex = pdex
JASS:
function K2DItemCheckXY takes real x, real y returns boolean
    call SetItemPosition(udg_K2DItem, x, y)
    return RAbsBJ(GetWidgetX(udg_K2DItem) - x +GetWidgetY(udg_K2DItem) - y) < 1
endfunction

function K2DItemCheckAxis takes real x, real y returns boolean
    local real x2 = x*udg_K2DRadius[udg_UDex]
    local real y2 = y*udg_K2DRadius[udg_UDex]
    set x = udg_K2DX + x2
    set y = udg_K2DY + y2
    if K2DItemCheckXY(x, y) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
        set x = udg_K2DX - x2
        set y = udg_K2DY - y2
        return K2DItemCheckXY(x, y) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
    endif
    return false
endfunction

function K2DItemCheck takes nothing returns boolean
    local boolean result = K2DItemCheckXY(udg_K2DX, udg_K2DY)
 
    //Only perform additional pathing checks if the unit has a larger collision.
    if result and udg_Knockback2DRobustPathing > 0 and udg_K2DRadius[udg_UDex] > 0 then

        //Check horizontal axis of unit to make sure nothing is going to collide
        set result = K2DItemCheckAxis(udg_K2DCosH[udg_UDex], udg_K2DSinH[udg_UDex])
     
        //Check vertical axis of unit to ensure nothing will collide
        set result = result and K2DItemCheckAxis(udg_K2DCos[udg_UDex], udg_K2DSin[udg_UDex])
     
        if result and udg_Knockback2DRobustPathing == 2 and udg_K2DRadius[udg_UDex] > 16 then

            //Check diagonal axis of unit if more thorough pathing is desired
            set result = K2DItemCheckAxis(udg_K2DCosD1[udg_UDex], udg_K2DSinD1[udg_UDex])
            set result = result and K2DItemCheckAxis(udg_K2DCosD2[udg_UDex], udg_K2DSinD2[udg_UDex])
        endif
    endif
 
    //Reset item so it won't interfere with the map
    call SetItemPosition(udg_K2DItem, udg_K2DMaxX, udg_K2DMaxY)
    call SetItemVisible(udg_K2DItem, false)
 
    return result
endfunction

function K2DItemFilter takes nothing returns boolean
    //Check for visible items, temporarily hide them and add them to the filter.
    if IsItemVisible(GetFilterItem()) then
        call SetItemVisible(GetFilterItem(), false)
        return true
    endif
    return false
endfunction
function K2DItemCode takes nothing returns nothing
    //Perform the item-pathing check only once, then unhide those filtered items
    if not udg_K2DItemsFound then
        set udg_K2DItemsFound = true
        set udg_K2DItemOffset = K2DItemCheck()
    endif
    call SetItemVisible(GetEnumItem(), true)
endfunction

function K2DKillDest takes nothing returns nothing
    local real x
    local real y
    //Handle destruction of debris
    set bj_destRandomCurrentPick = GetEnumDestructable()
    if GetWidgetLife(bj_destRandomCurrentPick) > 0.405 and IssueTargetOrder(udg_K2DDebrisKiller, udg_Knockback2DTreeOrDebris, bj_destRandomCurrentPick) then
        set x = GetWidgetX(bj_destRandomCurrentPick) - udg_K2DX
        set y = GetWidgetY(bj_destRandomCurrentPick) - udg_K2DY
        if x*x + y*y <= udg_K2DDestRadius[udg_UDex] then
            call KillDestructable(bj_destRandomCurrentPick)
        endif
    endif
endfunction

function K2DEnumDests takes nothing returns nothing
    call MoveRectTo(udg_K2DRegion, udg_K2DX, udg_K2DY)
    if udg_K2DKillTrees[udg_UDex] then
        call SetUnitX(udg_K2DDebrisKiller, udg_K2DX)
        call SetUnitY(udg_K2DDebrisKiller, udg_K2DY)
        call EnumDestructablesInRect(udg_K2DRegion, null, function K2DKillDest)
    endif
endfunction

function Knockback2DCheckXY takes real x, real y returns boolean
    set udg_K2DX = x + udg_K2DVelocity[udg_UDex]*udg_K2DCos[udg_UDex]
    set udg_K2DY = y + udg_K2DVelocity[udg_UDex]*udg_K2DSin[udg_UDex]
    if udg_K2DSimple[udg_UDex] then
        //A "pull" effect or a missile system does not require complex pathing.
        if udg_K2DX <= udg_K2DMaxX and udg_K2DX >= udg_K2DMinX and udg_K2DY <= udg_K2DMaxY and udg_K2DY >= udg_K2DMinY then
            call K2DEnumDests()
            return true
        endif
        return false
    elseif udg_K2DFlying[udg_UDex] then
        return not IsTerrainPathable(udg_K2DX, udg_K2DY, PATHING_TYPE_FLYABILITY)
    elseif not IsTerrainPathable(udg_K2DX, udg_K2DY, PATHING_TYPE_WALKABILITY) then
        call K2DEnumDests()
        set udg_K2DItemOffset = false
        call EnumItemsInRect(udg_K2DRegion, Filter(function K2DItemFilter), function K2DItemCode)
        if udg_K2DItemsFound then
            //If items were found, the check was already performed.
            set udg_K2DItemsFound = false
        else
            //Otherwise, perform the check right now.
            set udg_K2DItemOffset = K2DItemCheck()
        endif
        return udg_K2DItemOffset
    endif
    return udg_K2DAmphibious[udg_UDex] and not IsTerrainPathable(udg_K2DX, udg_K2DY, PATHING_TYPE_FLOATABILITY)
endfunction

function Knockback2DApplyAngle takes real angle returns nothing
    set angle = ModuloReal(angle, udg_Radians_Turn)
    set udg_K2DCos[udg_UDex] = Cos(angle)
    set udg_K2DSin[udg_UDex] = Sin(angle)
    set udg_K2DAngle[udg_UDex] = angle
    if udg_Knockback2DRobustPathing > 0 then
        set angle = ModuloReal(angle + udg_Radians_QuarterTurn, udg_Radians_Turn)
        set udg_K2DCosH[udg_UDex] = Cos(angle)
        set udg_K2DSinH[udg_UDex] = Sin(angle)
        if udg_Knockback2DRobustPathing == 2 and udg_K2DRadius[udg_UDex] > 16 then
            set angle = ModuloReal(angle + udg_Radians_QuarterPi, udg_Radians_Turn)
            set udg_K2DCosD1[udg_UDex] = Cos(angle)
            set udg_K2DSinD1[udg_UDex] = Sin(angle)
            set angle = ModuloReal(angle + udg_Radians_QuarterTurn, udg_Radians_Turn)
            set udg_K2DCosD2[udg_UDex] = Cos(angle)
            set udg_K2DSinD2[udg_UDex] = Sin(angle)
        endif
    endif
endfunction

function Knockback2DLooper takes nothing returns nothing
    local integer i = 0
    local unit u
    local real x
    local real y
 
    call PauseUnit(udg_K2DDebrisKiller, false)
 
    loop
        set i = udg_K2DNext[i]
        exitwhen i == 0
        set udg_UDex = i
        set udg_K2DTimeLeft[i] = udg_K2DTimeLeft[i] - udg_K2DTimeout
        set udg_K2DDistanceLeft[i] = udg_K2DDistanceLeft[i] - udg_K2DVelocity[i]
        set u = udg_UDexUnits[i]
     
        if udg_K2DTimeLeft[i] > 0.00 then
            if udg_K2DTimeLeft[i] < udg_K2DHeightThreshold[i] and udg_K2DHeightThreshold[i] != 0.00 then
                call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), GetUnitFlyHeight(u) - GetUnitDefaultFlyHeight(u)/udg_K2DHeightThreshold[i])
                set udg_K2DHeightThreshold[i] = 0.00
            endif
            if udg_K2DPause[i] then
                set x = udg_K2DLastX[i]
                set y = udg_K2DLastY[i]
            else
                set x = GetUnitX(u)
                set y = GetUnitY(u)
            endif
         
            if not Knockback2DCheckXY(x, y) then
                if not udg_K2DFreeze[i] and IsTriggerEnabled(udg_K2DImpact[i]) and TriggerEvaluate(udg_K2DImpact[i]) then
                    call TriggerExecute(udg_K2DImpact[i])
                endif
                if udg_K2DBounce[i] then
                    call Knockback2DApplyAngle(udg_Radians_Turn - udg_K2DAngle[i])
                    if not Knockback2DCheckXY(x, y) then
                        call Knockback2DApplyAngle(udg_K2DAngle[i] + bj_PI)
                        if not Knockback2DCheckXY(x, y) then
                            call Knockback2DApplyAngle(udg_Radians_Turn - udg_K2DAngle[i])
                            set udg_K2DX = x
                            set udg_K2DY = y
                        endif
                    endif
                else
                    set udg_K2DX = x
                    set udg_K2DY = y
                    set udg_K2DFreeze[i] = true
                endif
            endif
            call SetUnitX(u, udg_K2DX)
            call SetUnitY(u, udg_K2DY)
            set udg_K2DLastX[i] = udg_K2DX
            set udg_K2DLastY[i] = udg_K2DY
            if udg_K2DFXModel[i] != "" then
                set udg_K2DFXTimeLeft[i] = udg_K2DFXTimeLeft[i] - udg_K2DTimeout
                if udg_K2DFXTimeLeft[i] <= 0.00 then
                    set udg_K2DFXTimeLeft[i] = udg_K2DFXRate[i]
                    if udg_K2DFlying[i] then
                        call DestroyEffect(AddSpecialEffectTarget(udg_K2DFXModel[i], u, "origin"))
                    else
                        call DestroyEffect(AddSpecialEffect(udg_K2DFXModel[i], udg_K2DX, udg_K2DY))
                    endif
                endif
            endif
            if udg_K2DCollision[i] >= 0.00 then
                set udg_Knockback2DSource = u
                call GroupEnumUnitsInRange(bj_lastCreatedGroup, udg_K2DX, udg_K2DY, 200.00, null)
                call GroupRemoveUnit(bj_lastCreatedGroup, u)
                loop
                    set udg_Knockback2DUnit = FirstOfGroup(bj_lastCreatedGroup)
                    exitwhen udg_Knockback2DUnit == null
                    call GroupRemoveUnit(bj_lastCreatedGroup, udg_Knockback2DUnit)
                 
                    if IsUnitInRange(udg_Knockback2DUnit, u, udg_K2DCollision[i]) and udg_K2DFlying[i] == IsUnitType(udg_Knockback2DUnit, UNIT_TYPE_FLYING) and (not IsUnitType(udg_Knockback2DUnit, UNIT_TYPE_STRUCTURE)) and not IsUnitType(udg_Knockback2DUnit, UNIT_TYPE_DEAD) and (udg_K2DUnbiasedCollision[i] or IsUnitAlly(udg_Knockback2DUnit, GetOwningPlayer(u))) and TriggerEvaluate(gg_trg_Knockback_2D) then
                        set udg_Knockback2DAngle = bj_RADTODEG * Atan2(GetUnitY(udg_Knockback2DUnit) - udg_K2DY, GetUnitX(udg_Knockback2DUnit) - udg_K2DX)
                        set udg_Knockback2DDistance = udg_K2DDistanceLeft[i]
                        set udg_Knockback2DBounces = udg_K2DBounce[i]
                        set udg_Knockback2DCollision = udg_K2DCollision[i]
                        if udg_K2DHeight[i] != 0.00 then
                            set udg_Knockback2DHeight = GetUnitFlyHeight(u) - GetUnitDefaultFlyHeight(u)
                        endif
                        set udg_Knockback2DLoopFX = udg_K2DFXModel[I]
                        set udg_Knockback2DTime = udg_K2DTimeLeft[i]
                        set udg_Knockback2DUnbiasedCollision = udg_K2DUnbiasedCollision[i]
                        call TriggerExecute(gg_trg_Knockback_2D)
                        set udg_Knockback2DSource = u //in case of a recursive knockback
                    endif
                endloop
            endif
            set udg_K2DVelocity[i] = udg_K2DVelocity[i] - udg_K2DFriction[i]
        else
            call TriggerExecute(gg_trg_Knockback_2D_Destroy)
        endif
    endloop
    set u = null
 
    //Disable dummy after the loop finishes so it doesn't interfere with the map
    call PauseUnit(udg_K2DDebrisKiller, true)
endfunction

//===========================================================================
function StartKnockback2DTimer takes nothing returns nothing
    call TimerStart(udg_K2DTimer, udg_K2DTimeout, true, function Knockback2DLooper)
endfunction
function InitTrig_Knockback_2D_System takes nothing returns nothing
endfunction

  • Knockback 2D Destroy
    • Events
      • Game - UnitIndexEvent becomes Equal to 2.00
    • Conditions
      • IsUnitBeingKnockedBack[UDex] Equal to True
    • Actions
      • -------- --------
      • -------- This trigger destroys any knockback; you can execute it yourself by first setting UDex to the custom value --------
      • -------- --------
      • Set IsUnitBeingKnockedBack[UDex] = False
      • Set K2DNext[K2DPrev[UDex]] = K2DNext[UDex]
      • Set K2DPrev[K2DNext[UDex]] = K2DPrev[UDex]
      • Set K2DPrev[UDex] = 0
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • K2DNext[0] Equal to 0
        • Then - Actions
          • Countdown Timer - Pause K2DTimer
        • Else - Actions
      • Set Knockback2DUnit = UDexUnits[UDex]
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • K2DHeight[UDex] Not equal to 0.00
        • Then - Actions
          • Animation - Change Knockback2DUnit flying height to (Default flying height of Knockback2DUnit) at 0.00
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • K2DFreeze[UDex] Not equal to True
          • (K2DImpact[UDex] is on) Equal to True
        • Then - Actions
          • Trigger - Run K2DImpact[UDex] (checking conditions)
        • Else - Actions
  • On Damage
    • Events
      • Game - DamageModifierEvent becomes Equal to 1.00
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • IsDamageSpell Equal to False
          • (Owner of DamageEventSource) Equal to Player 1 (Red)
        • Then - Actions
          • -------- --------
          • -------- There are four required variables when issuing a knockback --------
          • -------- --------
          • -------- 1. Knockback2DAngle -------- this is the direction angle the unit is knocked back (in degrees) --------
          • -------- 2. Knockback2DTime -------- this is how long the unit will be knocked back (in seconds) --------
          • -------- 3. Knockback2DDistance -------- this is how far the unit will be knocked back --------
          • -------- 4. Knockback2DUnit -------- this is the unit being knocked back --------
          • -------- --------
          • -------- When all four variables are set, you can run the Knockback 2D trigger, ignoring conditions --------
          • -------- --------
          • Set CenterPoint = (Position of DamageEventSource)
          • Set TargetPoint = (Position of DamageEventTarget)
          • Set Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
          • Custom script: call RemoveLocation(udg_CenterPoint)
          • Custom script: call RemoveLocation(udg_TargetPoint)
          • Set Knockback2DTime = 0.90
          • Set Knockback2DDistance = 500.00
          • Set Knockback2DUnit = DamageEventTarget
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Owner of DamageEventSource) Not equal to (Owner of DamageEventTarget)
            • Then - Actions
              • -------- --------
              • -------- There are optional variables you can consider when issuing a knockback --------
              • -------- If they are not set, "0.00, false, null" or their default states assigned in the Knockback 2D Config trigger will be used --------
              • -------- --------
              • -------- Knockback2DAmphibious -------- Allows the unit to pass over deep water, but it is more performance-intensive --------
              • -------- -------- If you aren't ToolOrDie from TheHiveWorkshop, you probably won't use this --------
              • -------- Knockback2DBounces -------- if the unit will bounce off of walls or remain stunned there. --------
              • -------- --------
              • -------- Knockback2DCollision -------- You can specify the collision radius of units to be adjacently knocked back if you want --------
              • -------- -------- A value of 0 will require the units' collision sizes to overlap to knock back --------
              • -------- -------- A value greater than 0 will allow that much space between the units to still permit the knockback --------
              • -------- -------- A value less than 0 will disable collision checking altogether. --------
              • -------- --------
              • -------- Knockback2DDestRadius -------- If debris is to be killed, how far away must it be? --------
              • -------- Knockback2DFriction -------- How quickly the unit will slow down from its initial speed. --------
              • -------- Knockback2DFX -------- What kind of special effect will occasionally be played on the ground of the unit --------
              • -------- Knockback2DFXRate -------- How often should the effect appear? --------
              • -------- Knockback2DGravity -------- If you want to specify a custom gravity for each knockback, you can --------
              • -------- Knockback2DHeight -------- How high you want the unit to go during the knockback --------
              • -------- Knockback2DKillTrees -------- If you want surrounding trees to die, this is for you --------
              • -------- Knockback2DOnImpact -------- this trigger is run when the unit hits a wall or structure. --------
              • -------- -------- If it is bouncing, it can hit multiple walls and fire this trigger multiple times --------
              • -------- -------- The trigger will not run if it is off, so you can use this if you only want it to run on the first bounce --------
              • -------- Knockback2DOverride -------- Set this to true to prevent future knockbacks from interrupting this one --------
              • -------- Knockback2DPause -------- confines the unit to its knockback velocity (no running to change course) --------
              • -------- Knockback2DSimple -------- Only checks to make sure unit movement is within the map bounds --------
              • -------- Knockback2DSource -------- If you need to store this unit to deal damage on-impact or within the timer loop --------
              • -------- Knockback2DUnbiasedCollision -------- Should adjacent knockbacks consider alliances or be neutral? --------
              • -------- --------
              • Set Knockback2DAmphibious = True
              • Set Knockback2DBounces = False
              • Set Knockback2DCollision = 16.00
              • Set Knockback2DDestRadius = 128.00
              • Set Knockback2DGravity = 0.67
              • Set Knockback2DHeight = 137.00
              • Set Knockback2DKillTrees = False
              • Set Knockback2DOnImpact = On Impact <gen>
              • Set Knockback2DOverride = True
              • Set Knockback2DPause = True
              • Set Knockback2DSimple = True
              • Set Knockback2DSource = DamageEventSource
              • Set Knockback2DUnbiasedCollision = False
            • Else - Actions
              • -------- Allied units shouldn't allow collision with each other, but for the sake of the demo let's make it interesting --------
              • Set Knockback2DCollision = 32.00
              • -------- Also make it so that enemies will bounce with these experiments --------
              • Set Knockback2DUnbiasedCollision = True
              • Set Knockback2DLoopFX = Abilities\Weapons\FaerieDragonMissile\FaerieDragonMissile.mdl
              • Set Knockback2DFXRate = 0.20
          • -------- --------
          • -------- When all variables are set, run the Knockback 2D trigger, checking conditions if you want to be safe --------
          • -------- --------
          • Trigger - Run Knockback 2D <gen> (checking conditions)
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Or - Any (Conditions) are true
                • Conditions
                  • Tauren_Shockwave[(Custom value of DamageEventSource)] Equal to True
                  • Tauren_WarStomp[(Custom value of DamageEventSource)] Equal to True
            • Then - Actions
              • Set CenterPoint = (Position of DamageEventSource)
              • Set TargetPoint = (Position of DamageEventTarget)
              • Set Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
              • Custom script: call RemoveLocation(udg_CenterPoint)
              • Custom script: call RemoveLocation(udg_TargetPoint)
              • Set Knockback2DTime = 0.50
              • Set Knockback2DDistance = 75.00
              • Set Knockback2DUnit = DamageEventTarget
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • Tauren_WarStomp[(Custom value of DamageEventSource)] Equal to True
                • Then - Actions
                  • -------- For War Stomp, add a height factor --------
                  • Set Knockback2DHeight = 250.00
                  • Set Knockback2DDistance = 100.00
                • Else - Actions
                  • Set Knockback2DDistance = 300.00
              • Trigger - Run Knockback 2D <gen> (ignoring conditions)
            • Else - Actions
      • -------- Deleting the next line will re-enable damage in the map --------
      • Set DamageEventAmount = 0.00
  • On Spell Cast
    • Events
      • Unit - A unit owned by Player 1 (Red) Starts the effect of an ability
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Ability being cast) Equal to Shockwave
        • Then - Actions
          • Set Tauren_Shockwave[(Custom value of (Triggering unit))] = True
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Ability being cast) Equal to War Stomp
            • Then - Actions
              • Set Tauren_WarStomp[(Custom value of (Triggering unit))] = True
            • Else - Actions
  • On Spell Stop
    • Events
      • Unit - A unit owned by Player 1 (Red) Stops casting an ability
    • Conditions
    • Actions
      • Unit - Reset ability cooldowns for (Triggering unit)
      • Unit - Set mana of (Triggering unit) to 100.00%
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Ability being cast) Equal to Shockwave
        • Then - Actions
          • -------- In case the unit had other damaging spells, we don't want those spells to trigger an unwanted knockback --------
          • Set Tauren_Shockwave[(Custom value of (Triggering unit))] = False
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Ability being cast) Equal to War Stomp
            • Then - Actions
              • -------- Same goes for here --------
              • Set Tauren_WarStomp[(Custom value of (Triggering unit))] = False
            • Else - Actions



  • 1.0.0.0 - Release
  • 1.0.0.1 - Changed IssuePointOrder to IssuePointOrderById.
    Now uses 1 trigger instead of 2.
  • 1.1.0.0 - Added a "bounce" functionality to the knockback.
    Made an add-on to support knocking back adjecent units (collateral knockback)
  • 1.2.0.0 - Added an additional flavor, "Knockback2DPause", which can be set only during map initialization. I also added a SetUnitPropWindow for when units are not paused, so that they appear motionless aside from the knockback.
  • 2.0.0.0 - Added five new, optional variables: height, pause, bounce, kill trees and an on-impact trigger. I will elaborate more on what these do in a future post.
  • 2.0.1.0 - Added a Gravity configurable that determines how quickly the unit will descend after it reaches its peak.
  • 2.0.2.0 - Gravity can now be configured per-knockback
  • 2.1.0.0 - You can now specify looping FX strings per-knockback. Changed the addons to custom script as they were mostly custom script anyway.
  • 3.0.0.0 - Split the one monolithic trigger into four smaller and more manageable ones. Units will no longer potentially pass through buildings if they had a collision size greater than 16.
  • 3.1.0.0 - Updated the Knockback 2D and Knockback 2D System triggers to add an option "Knockback2DSimple". When this boolean is true, all normal pathing is ignored except to make sure the unit is within the world bounds.
  • 4.0.0.0 - Everything has been overhauled.
    • No custom object editor data is used. Inferno was really only useful on my demo map, but it had issues like ignoring structures, pathing blockers and actually not considering collision sizes. That's right, despite that I had all the object data set properly to make it recognize various collision sizes, it was not doing that. Also, the custom Flame Strike ability had a delay on it which I could not get rid of no matter what I did. The ability could be cast at most per 0.02 seconds, and when you set its Object Editor duration to 0.00 instead of 0.01, the game crashes.
    • Real collision is used now.
      Code:
      //Each [ ] represents a SetItemPosition call on that space
      
      //Collision when pathing robustness is set to 0 or the unit's collision size is
      //set to 16 is simply one SetItemPosition call.
      
      //With robustness set to 1 or the unit has 32 collision:
         [ ]
      [ ][ ][ ]
         [ ]
         
      //With robustness set to 2 and the unit has 48 collision:
            [ ]
        [ ]     [ ]
      [ ]   [ ]   [ ]
        [ ]     [ ]
            [ ]
      
      //At 64 collision, the white spaces just become more pronounced.
      
      //As you can see, knocking back a unit with more than 32 collision size and
      //robustness set to 2 requires quite a few SetItemPosition calls. I am not using
      //SetUnitPosition with a dummy due to potential "enter/leave region" events in a
      //user's map.
    • The friction can now be customized per-knockback. I also fixed the default friction as I had improperly copied Berb's formula used in Knockback Lite - that problem is fixed now.
    • There is a dummy harvest unit created to sample if a destructable is debris or a tree (the user can change the issued order from attack to harvest if they only want to kill trees).
    • The Knockback 2D System trigger has been converted into optimized JASS so I could make the system run more smoothly.
    • The two addons have been merged into the Knockback2D System JASS script.
    • You can configure the rate at which special effects spawn under a unit being knocked back.
  • 4.1.0.0 - After being notified by Arad MNK that distance wasn't working properly, I found a pretty serious thing I've neglected the whole time this resource has been around. The friction I had been using was nearly 0 because I didn't copy Berb's formula properly, so the unit wasn't visibly slowing down. Therefore, the unit was always going too far. This update corrects that issue. You can change the friction to 0 to go back to the way it was before.
  • 4.2.0.0 - Friction can now be customized between 0 and 1 without adverse effects. Greatly improved the fluidity of secondary knockbacks by averaging the angle, distance and time of the new knockback with those of the first. A new option, Knockback2DUnbiasedCollision, has been added to remove alliance checking from the built-in knockback collider.
  • 4.2.1.0 - Added a way to customize the destructable death radius and it can also be done per-knockback. Also added a fix so that items don't get displaced when unhidden.
  • 4.2.2.0 - Flying units no longer knock back ground units and ground units no longer knock back flying units.
  • 4.2.3.0 - Flying units now display special effect art attached to their "origin". Simplified the pathing check for flying units to only check for air unit pathability.
  • 4.2.3.1 - Fixed an issue where Knockback2DOverride would break all subsequent attempts to knockback the unit running Knockback 2D <gen> (checking conditions) instead of ignoring them.
  • 4.2.4.0 - Knockback collision now tracks Knockback2DSource
  • 4.2.5.0 - Fixed an issue with large maps not working correctly. Just update the JASS script to apply this fix. The map isn't updated yet but I've updated the posted script.


Keywords:
Knockback, Berb, Berbanog, Unit Indexer
Contents

Ice Map (Map)

Reviews
10:13, 12th Dec 2015: BPower: The previous given rating ( in 2011 ) does no longer match with the current state of the Knockback2D code. I very recommend this resource to everyone, not only GUI'ers, who need a Knockback system for his/her map...

Edy

Edy

Level 9
Joined
Nov 21, 2015
Messages
226
Is there any way to exclude a unit that is being knocked back to bumb (scatter) othe units, allies and enemies alike. I'm using this system quite often it would be of great help.
 
Level 39
Joined
Feb 27, 2007
Messages
5,012
Is there any way to exclude a unit that is being knocked back to bumb (scatter) othe units, allies and enemies alike. I'm using this system quite often it would be of great help.
Read the system documentation, please:
Bribe said:
-------- Knockback2DCollision --------
-------- You can specify the collision radius of units to be adjacently knocked back if you want --------
-------- A value of 0 will require the units' collision sizes to overlap to knock back --------
-------- A value greater than 0 will allow that much space between the units to still permit the knockback --------
-------- A value less than 0 will disable collision checking altogether. --------
 
Level 3
Joined
Oct 9, 2012
Messages
28
Hi Bribe, this is awesome lib. But why doesn't your code interrupt spell? When I set unit position it stops my order. I look into your code at a glance, finding you are setting position too. What's the magic?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
Hi Bribe, this is awesome lib. But why doesn't your code interrupt spell? When I set unit position it stops my order. I look into your code at a glance, finding you are setting position too. What's the magic?
Move unit instantly issues a stop command where as this doesn't:
  • Custom script: call SetUnitX (udg_Unit, GetLocationX(udg_Point))
  • Custom script: call SetUnitY (udg_Unit, GetLocationY(udg_Point))
Edit: Just realized you said you're already doing that. Dunno, set unit position doesn't interrupt for me.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Hi Bribe, this is awesome lib. But why doesn't your code interrupt spell? When I set unit position it stops my order. I look into your code at a glance, finding you are setting position too. What's the magic?

I don't use SetUnitPosition except for with the dummy unit. As Uncle already said, the system uses SetUnitX/Y which are light operations that don't issue a Stop order.
 
Level 13
Joined
Mar 24, 2013
Messages
1,105
Hi Bribe,

I am trying to replicate your destructable search and destroy method for spells that will destroy trees as units moves along a path.

However, I don't follow where I would set or decide this portion of the code:

  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • Knockback2DKillTrees Equal to True
    • Then - Actions
      • -------- Square the radius so we don't have to use SquareRoot when comparing distance. --------
      • Set K2DDestRadius[UDex] = (Knockback2DDestRadius x Knockback2DDestRadius)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Knockback2DDestRadius x 2.00) Greater than K2DMaxDestRadius
        • Then - Actions
          • -------- Update the size of the enumerating rect to compensate for the desired extra radius. --------
          • Set K2DMaxDestRadius = (Knockback2DDestRadius x 2.00)
          • Custom script: call SetRect(udg_K2DRegion, 0.00, 0.00, udg_K2DMaxDestRadius, udg_K2DMaxDestRadius)
        • Else - Actions
      • Set Knockback2DDestRadius = Knockback2DDefaultDestRadius
    • Else - Actions

JASS:
library TreeSearch initializer init

        globals
      
        private rect TREE_REGION = GetWorldBounds()
        private real maxX = GetRectMaxX(TREE_REGION)
        private real maxY = GetRectMaxY(TREE_REGION)
        private real minX = GetRectMinX(TREE_REGION)
        private real minY = GetRectMinY(TREE_REGION)
      
        private real defaultDestRadius = 128.
        private real maxDestRadius = defaultDestRadius * 2.
        private string treeOrDebris = "attack"
        private unit debrisKiller
        private real tX
        private real tY
        private real tRadius
      
        endglobals
      
private function KillTree takes nothing returns nothing
    local real x
    local real y
    local destructable d = GetEnumDestructable()
    if GetWidgetLife(d) > .405 and IssueTargetOrder(debrisKiller, treeOrDebris, d) then
        set x = GetWidgetX(d) - tX
        set y = GetWidgetY(d) - tY
        if x*x + y*y <= tRadius then
            call KillDestructable(d)
        endif
    endif
  
    set d = null
endfunction

function TreeSearch takes real x, real y, real radius returns nothing
    call MoveRectTo(TREE_REGION, x, y)
    call SetUnitX(debrisKiller, x)
    call SetUnitY(debrisKiller, y)
    set tX = x
    set tY = y
    if radius == 0. then
        set radius = defaultDestRadius
    endif
    set tRadius = radius
    call EnumDestructablesInRect(TREE_REGION, null, function KillTree)
endfunction

private function init takes nothing returns nothing
    set debrisKiller = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'ugho', 0,0, 270.)
    call UnitAddAbility(debrisKiller, 'Aloc')
    call ShowUnit(debrisKiller, false)
    call PauseUnit(debrisKiller, true)
    call SetRect(TREE_REGION, 0., 0., maxDestRadius, maxDestRadius)

endfunction


endlibrary
 
Last edited:
Level 13
Joined
Mar 24, 2013
Messages
1,105
Thank you! Really appreciate the effort you put into your code comments and your responsiveness.

JASS:
library TreeSearch initializer init

        globals
      
        private rect TREE_REGION
        private real maxX
        private real maxY
        private real minX
        private real minY
      
        private real defaultDestRadius = 128.
        private real maxDestRadius = defaultDestRadius * 2.
        private string treeOrDebris = "attack"
        private unit debrisKiller
        private real tX
        private real tY
        private real tRadius
      
        endglobals
      
private function KillTree takes nothing returns nothing
    local real x
    local real y
    local destructable d = GetEnumDestructable()
    if GetWidgetLife(d) > .405 and IssueTargetOrder(debrisKiller, treeOrDebris, d) then
        set x = GetWidgetX(d) - tX
        set y = GetWidgetY(d) - tY
        if x*x + y*y <= tRadius then
            call KillDestructable(d)
        endif
    endif
  
    set d = null
endfunction

function TreeSearch takes real x, real y, real radius returns nothing
    call SetUnitX(debrisKiller, x)
    call SetUnitY(debrisKiller, y)
    call PauseUnit(debrisKiller, false)
    set tX = x
    set tY = y
    if radius == 0. then
        call SetRect(TREE_REGION, 0., 0., maxDestRadius, maxDestRadius)
        set radius = defaultDestRadius
    else
        call SetRect(TREE_REGION, 0., 0., radius*2, radius*2)
    endif
  
    set tRadius = radius*radius
    call MoveRectTo(TREE_REGION, x, y)
    call EnumDestructablesInRect(TREE_REGION, null, function KillTree)
    call PauseUnit(debrisKiller, true)
endfunction

private function init takes nothing returns nothing
    set TREE_REGION = GetWorldBounds()
    set maxX = GetRectMaxX(TREE_REGION)
    set maxY = GetRectMaxY(TREE_REGION)
    set minX = GetRectMinX(TREE_REGION)
    set minY = GetRectMinY(TREE_REGION)
  
    set debrisKiller = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'ugho', 0,0, 270.)
    call UnitAddAbility(debrisKiller, 'Aloc')
    call ShowUnit(debrisKiller, false)
    call PauseUnit(debrisKiller, true)
    call SetRect(TREE_REGION, 0., 0., maxDestRadius, maxDestRadius)

endfunction


endlibrary
 
Level 11
Joined
Jul 17, 2013
Messages
544
View attachment 332262View attachment 332263

View attachment 332264View attachment 332265
@Bribe hey with thw help of the uncle i am using greater bash ability it has most of things from ur system but there is problem. Some units get pulled into air and lands at same spot without direction to push them more



Look for frodo at photo for him its fine he lands away. Do you know how to fix this bug.i realised that for units with 15 colizion it always works but for units with 20 they get only pulled up and land at same spot. On map i copied it from everything works fine and theres no such bug.
 
Last edited:

Edy

Edy

Level 9
Joined
Nov 21, 2015
Messages
226
View attachment 332262View attachment 332263

View attachment 332264View attachment 332265
@Bribe hey with thw help of the uncle i am using greater bash ability it has most of things from ur system but there is problem. Some units get pulled into air and lands at same spot without direction to push them more



Look for frodo at photo for him its fine he lands away. Do you know how to fix this bug.i realised that for units with 15 colizion it always works but for units with 20 they get only pulled up and land at same spot. On map i copied it from everything works fine and theres no such bug.

Had a simmilar issue.
Depends if they're pre-placed or spawned.
On spawned enemies it works perfectly, I think it's a problem with the Unit Indexer..IDK wait for Bribe - maybe you need to register all preplaced units somehow.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I think the problem here is the line in the Knockback 2D Destroy trigger "Animation - Change Knockback2DUnit flying height to (Default flying height of Knockback2DUnit) at 0.00"

That line should instead be: "Animation - Change UDexUnits[UDex] flying height to (Default flying height of UDexUnits[UDex]) at 0.00"

I've attached an updated map. Please let me know if it fixes the problem.
 

Attachments

  • Knockback 2_5D.w3x
    68.9 KB · Views: 20
Level 11
Joined
Jul 17, 2013
Messages
544
Yeah but that shouldn't have an impact.
well it works fine only at testing zone in single player.but at multiplayer in normal game when heroes fight together sometimes they get slided on ground only. so should i change that line? btw this is map im using. can u take a look at it and tell me if problem is in my map? because i dont use all of ur triggers only some.
 

Attachments

  • Knockback Demo v.7.w3x
    76 KB · Views: 10
Level 11
Joined
Jul 17, 2013
Messages
544
Message from uncle



You should tell Bribe that Uncle edited his Knockback 2D system in order to implement some stuff that you wanted and that I already fixed the problem with Knockback 2D Destroy. Tell him that I replaced the flying height effect that he was using before with a new one that has a parabola effect so the unit's height changes smoothly over the course of the knockback duration. Explain that it works perfectly fine in the Knockback v.7 Demo map that I created for you even with Robust Pathing set to 0/1/2. However, for some reason the system has problems when we import it into your LotR map and for whatever reason setting the Robust Pathing to 0 helped fix some of those problems. Note that we tried using his original system in your LotR map but it had the same problems.





My problem is currently when i m testing this knockback on single player on single units it works fine. Its supossed to make unit fly in some height then land unit away with direction. But when 9 Heroes fight together i noticed that theres a bug. Units are often slided throught ground only. Without hright to go up into air.idk really what causes it. Maybe it happens when heroes are too close to eachother? I sent u a map im using.it has ur system but bit edited. I can send u the lotr map where this system has issues. My robous pathning is 0.

Also the thing u were talking about is fixed alreday. Colision of my units is 15 18 and 20
 
Level 11
Joined
Jul 17, 2013
Messages
544
Yeah but that shouldn't have an impact.
I have second problem i am using ur system and the uncle remade it a bit. it works fine on cave trolls on my map. but i added it for sauron the last boss on map. knockback sometimes just only stuns, sometimes work with height only and sometimes just direction. and sometimes knocks normally. and after few minuts of fighting sauron game total freezes everything stops and i have to press alt+f4 to exit game. all i would like to know is how to fix this issue. there isnt much difference beetwen cave trolsl and sauron, so i got no idea why its broken on sauron. cave trolls and both sauron are on map all the time, they both have 48 pathning. sauron has few custom spells with other coding too but no one of these spells enables knockback triggers i think, it used to do so but i fixed it.
 
Level 3
Joined
Oct 13, 2017
Messages
10
Hello Bribe, hello everyone. Maybe I miss something but how can I change this system to ignore obstacles completely? It seems like even when unit is flying or amphibious it still hits the walls (water) and bounces off it, even though normally unit would pass it completely ignoring it. Seems like your system checks collision even if it doesn't exist sadly.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Hello Bribe, hello everyone. Maybe I miss something but how can I change this system to ignore obstacles completely? It seems like even when unit is flying or amphibious it still hits the walls (water) and bounces off it, even though normally unit would pass it completely ignoring it. Seems like your system checks collision even if it doesn't exist sadly.

Non-destructable obstacles are indistinguishable from an impasse without some very immense overhead to do some calculating like "is point reachable by unit". That's simple to just determine by looking at it with your eyes, but the code is messy AF.
 
Level 3
Joined
Oct 13, 2017
Messages
10
Non-destructable obstacles are indistinguishable from an impasse without some very immense overhead to do some calculating like "is point reachable by unit". That's simple to just determine by looking at it with your eyes, but the code is messy AF.
I'm just bad at this, is there a way to add some condition to make your system ignore pathing for some units/passive abilities/etc? If there is pls tell me how can I change it without breaking whole system.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I'm just bad at this, is there a way to add some condition to make your system ignore pathing for some units/passive abilities/etc? If there is pls tell me how can I change it without breaking whole system.
Each knockback instance can be defined with its own pathing configuration. So a missile-type unit could use this system, hypothetically, using the Simple pathing option.
 
@Bribe So, Ironically after some time I've made most of the systems listed under your signature in GUI only to just find out that you had already made them all. Anyway I'm now familiar with them so I might as well finish the knockback one haha. I'm less familiar with Jass but I understand it is the only way to move a unit without "stunning" it. As you know the jass-based movement will send a unit flying through walls and anything else.

Looking at your System here:
function K2DItemCheckXY takes real x, real y returns boolean
call SetItemPosition(udg_K2DItem, x, y)
return GetWidgetX(udg_K2DItem) == x and GetWidgetY(udg_K2DItem) == y
endfunction
function K2DItemCheckAxis takes real x, real y returns boolean
local real x2 = x*udg_K2DRadius[udg_UDex]
local real y2 = y*udg_K2DRadius[udg_UDex]
set x = udg_K2DX + x2
set y = udg_K2DY + y2
if K2DItemCheckXY(x, y) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
set x = udg_K2DX - x2
set y = udg_K2DY - y2
return K2DItemCheckXY(x, y) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endif
return false
endfunction
Could you tell me:
1. What the item is for and why it would fail without it?
2. Why my GUI trigger does not appear to be functioning? (Listed point is the origin of a tree)
  • (Terrain pathing at (Point(-1306.00, -1700.00)) of type Walkability is off) Equal to True
^Does not consider that point unwalkable when paired in if/then/else.

Thanks, much appreciated!
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
@Veronnis
1. When you try to move an Item to somewhere unwalkable like Water it will automatically move to the next closest possible location on the map. This can be used to determine pathing.

Example: You move an Item to the 0,0 coordinates on the map. After moving it you then check the Items X,Y coordinates. If the Item's X,Y coordinates are different than 0,0 then we know that you tried to move the item to an unwalkable point and that the Item was moved somewhere nearby. That's the basic idea of what he's doing but he does it in a slightly more complex way by moving the Item to different positions around the knocked back units. If the Item isn't where it's supposed to be then we know it hit something Unpathable.

2. I think Terrain Pathing checks for Cliffs/Doodads/Water/Boundary/etc... but not Destructibles.
 
@Uncle
Oh okay, I was already using the same method with the item here but, not being very familiar with Jass I had misread his code to be functioning without the item (Point based-only) and yet the item was included anyway. Now with that clarified it sounds like it moves the item (to check for pathing), then offsets a new point based on a variable collision size and moves the end destination to that new point (to accommodate for valid placement after collision size is considered). Is that correct?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
@Uncle
Oh okay, I was already using the same method with the item here but, not being very familiar with Jass I had misread his code to be functioning without the item (Point based-only) and yet the item was included anyway. Now with that clarified it sounds like it moves the item (to check for pathing), then offsets a new point based on a variable collision size and moves the end destination to that new point (to accommodate for valid placement after collision size is considered). Is that correct?
To be honest I don't entirely understand what he's doing I just know the gist of it. Anyway, I'm off to bed, hopefully the man himself can break it down.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
@Uncle
Oh okay, I was already using the same method with the item here but, not being very familiar with Jass I had misread his code to be functioning without the item (Point based-only) and yet the item was included anyway. Now with that clarified it sounds like it moves the item (to check for pathing), then offsets a new point based on a variable collision size and moves the end destination to that new point (to accommodate for valid placement after collision size is considered). Is that correct?

That sounds about right. But let me explain just to make sure.

In order for a unit to get from 0,0 to 0, 100, it needs to slide. Let's say in ten steps for simplicity. So in one pass, I check if 0,10 is safe using an item. But I'll also check if there is enough "padding" around 0,10 so that's why I use "robustness" in case of large collision sizes.

Then I do the same for 0,20/30, etc. Until either the point is unsafe or the destination is reached.

But with bouncing enabled, the destination can change geometrically.
 
@Bribe , sweet thanks for confirming that. I presume the item is used for the check because it does not trigger "unit enters region". Im on my phone right now or I'd just look at your code, but when you move the unit do you move it to x and y simultaneously or separately? If done separately I presume said unit might accidentally enter a region before it is moved to the other axis. Does that happen if assigned in 2 separate commands?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
@Bribe , sweet thanks for confirming that. I presume the item is used for the check because it does not trigger "unit enters region". Im on my phone right now or I'd just look at your code, but when you move the unit do you move it to x and y simultaneously or separately? If done separately I presume said unit might accidentally enter a region before it is moved to the other axis. Does that happen if assigned in 2 separate commands?
I've not tested if "enters region" might trigger twice if the angle severe enough to trigger two separate regions at once, but given the general fluidity of motion and micro steps of the coordinates this is inconsequential.
 
Level 12
Joined
Feb 5, 2018
Messages
521
  • Set Knockback2DDefaultBounce = True
^I think you change that to False in the Knockback 2D Config trigger.

Otherwise, post your trigger.

Unfortunately, it wasn't that. But I found it. Im stupid. :D

  • Set VariableSet Knockback2DCollision = -1.00
This needed to be negative value, it was even explained in this map triggers.
 
Level 14
Joined
Feb 7, 2020
Messages
387
This is an awesome KB system. I ran the JASS source through a Lua converter and Lua-fied the GUI. Almost have it working.

Something got tweaked with calculations, so working through that still. It randomly works from very specific angles.

If someone else also desires a Lua variant but better understands its syntax and any conversion woes, feel free to have a gander (attached).
 

Attachments

  • lua_test.w3m
    65.1 KB · Views: 22

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
It'd be great if Bribe came out with a Lua version.
@Planetary
Note that you can throw away the Unit Indexer entirely since you can plug units directly into your Tables:
Lua:
function LuaIndex()
    KnockbackTable = {}
    local unit = CreateUnit(Player(0), FourCC("Hpal"), 0, 0, 270) --Create a Paladin at x: 0, y: 0, for Player 1
    table.insert(KnockbackTable , unit)
end

function LuaLoop()
    for k,v in pairs(KnockbackTable) do
        print(k,v) -- prints the Table Key (1, 2, 3) and the Table Value (Our Units)
        --Move the unit, etc...
        local x = 100
        local y = 100
        SetUnitPosition(v, x, y) -- v = our Paladin (units)
    end
end
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
That's a damn shame... I'm guessing you're not too interested in doing it all over again, at least not for a while.
Yeah to be honest I built this resource because GUI didn't have a good enough Knockback system at the time. The math wasn't even mine to begin with. I ended up changing a few things in the math to make it configurable and to check for outbound angles, but I don't know enough about velocity to do a proper movement arc without yet again copying someone else's math.
 
Level 3
Joined
Aug 26, 2020
Messages
12
Hi, i'm not sure if this is the place to ask and i'm new to WorldEdit, but is there some form of tutorial on how to use this? I'd like to learn how to use this and trigger a knockback without the use of the damage engine. I have tried fiddling around triggers for a while now, but i am unable to get it to work.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
Hi, i'm not sure if this is the place to ask and i'm new to WorldEdit, but is there some form of tutorial on how to use this? I'd like to learn how to use this and trigger a knockback without the use of the damage engine. I have tried fiddling around triggers for a while now, but i am unable to get it to work.
I recommend copying these actions from the "On Damage" trigger in Bribe's test map and pasting them into a new trigger. This new trigger can then be referenced whenever you want to issue a Knockback.
  • Actions
    • -------- There are four required variables when issuing a knockback --------
    • -------- --------
    • -------- 1. Knockback2DAngle -------- this is the direction angle the unit is knocked back (in degrees) --------
    • -------- 2. Knockback2DTime -------- this is how long the unit will be knocked back (in seconds) --------
    • -------- 3. Knockback2DDistance -------- this is how far the unit will be knocked back --------
    • -------- 4. Knockback2DUnit -------- this is the unit being knocked back --------
    • -------- --------
    • -------- When all four variables are set, you can run the Knockback 2D trigger, ignoring conditions --------
    • -------- --------
    • Set VariableSet CenterPoint = (Position of DamageEventSource)
    • Set VariableSet TargetPoint = (Position of DamageEventTarget)
    • Set VariableSet Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
    • Custom script: call RemoveLocation(udg_CenterPoint)
    • Custom script: call RemoveLocation(udg_TargetPoint)
    • Set VariableSet Knockback2DTime = 0.90
    • Set VariableSet Knockback2DDistance = 500.00
    • Set VariableSet Knockback2DUnit = DamageEventTarget
    • -------- --------
    • -------- When all variables are set, run the Knockback 2D trigger, checking conditions if you want to be safe --------
    • -------- --------
    • Trigger - Run Knockback 2D <gen> (checking conditions)
^These are the important Actions for issuing a Knockback.

So now whenever you want to issue a Knockback, you copy and paste these Actions from the trigger I told you to create, and adjust the variables accordingly:

1) Set CenterPoint to be the position of the Source of the Knockback. So you want to change DamageEventSource to be equal to your Source Unit.
2) Set TargetPoint to be the position of the Target of the Knockback. So you want to change DamageEventTarget to be equal to your Target Unit.
3) Set Knockback2DTime to be how long you want the Knockback to last.
4) Set Knockback2DDistance to be how far you want the Knockback to travel.
5) Set Knockback2DUnit = The Target of the Knockback.

The final step is to run the Knockback trigger:
  • Trigger - Run Knockback 2D <gen> (checking conditions)
This tells the system to create a Knockback based on your designated settings.

Here's an example using Storm Bolt:
  • Storm Bolt Example
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Storm Bolt
    • Actions
      • Set VariableSet CenterPoint = (Position of (Casting unit))
      • Set VariableSet TargetPoint = (Position of (Target unit of ability being cast))
      • Set VariableSet Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
      • Custom script: call RemoveLocation(udg_CenterPoint)
      • Custom script: call RemoveLocation(udg_TargetPoint)
      • Set VariableSet Knockback2DTime = 2.00
      • Set VariableSet Knockback2DDistance = 1000.00
      • Set VariableSet Knockback2DUnit = (Target unit of ability being cast)
      • Trigger - Run Knockback 2D <gen> (checking conditions)
Note that there are quite a few other Variables that you can set when issuing a Knockback, here's just a few:
  • Set VariableSet Knockback2DKillTrees = True
  • Set VariableSet Knockback2DPause = True
  • Set VariableSet Knockback2DBounces = True
If you don't set these yourself then the System will automatically use their Default values. The Default values can be found in "Knockback 2D Config". You can adjust these to your liking.
 
Last edited:
Level 3
Joined
Aug 26, 2020
Messages
12
I recommend copying these actions from the "On Damage" trigger in Bribe's test map and pasting them into a new trigger. This new trigger can then be referenced whenever you want to issue a Knockback.
  • Actions
    • -------- There are four required variables when issuing a knockback --------
    • -------- --------
    • -------- 1. Knockback2DAngle -------- this is the direction angle the unit is knocked back (in degrees) --------
    • -------- 2. Knockback2DTime -------- this is how long the unit will be knocked back (in seconds) --------
    • -------- 3. Knockback2DDistance -------- this is how far the unit will be knocked back --------
    • -------- 4. Knockback2DUnit -------- this is the unit being knocked back --------
    • -------- --------
    • -------- When all four variables are set, you can run the Knockback 2D trigger, ignoring conditions --------
    • -------- --------
    • Set VariableSet CenterPoint = (Position of DamageEventSource)
    • Set VariableSet TargetPoint = (Position of DamageEventTarget)
    • Set VariableSet Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
    • Custom script: call RemoveLocation(udg_CenterPoint)
    • Custom script: call RemoveLocation(udg_TargetPoint)
    • Set VariableSet Knockback2DTime = 0.90
    • Set VariableSet Knockback2DDistance = 500.00
    • Set VariableSet Knockback2DUnit = DamageEventTarget
    • -------- --------
    • -------- When all variables are set, run the Knockback 2D trigger, checking conditions if you want to be safe --------
    • -------- --------
    • Trigger - Run Knockback 2D <gen> (checking conditions)
^These are the important Actions for issuing a Knockback.

So now whenever you want to issue a Knockback, you copy and paste these Actions from the trigger I told you to create, and adjust the variables accordingly:

1) Set CenterPoint to be the position of the Source of the Knockback. So you want to change DamageEventSource to be equal to your Source Unit.
2) Set TargetPoint to be the position of the Target of the Knockback. So you want to change DamageEventTarget to be equal to your Target Unit.
3) Set Knockback2DTime to be how long you want the Knockback to last.
4) Set Knockback2DDistance to be how far you want the Knockback to travel.
5) Set Knockback2DUnit = The Target of the Knockback.

The final step is to run the Knockback trigger:
  • Trigger - Run Knockback 2D <gen> (checking conditions)
This tells the system to create a Knockback based on your designated settings.

Here's an example using Storm Bolt:
  • Storm Bolt Example
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Storm Bolt
    • Actions
      • Set VariableSet CenterPoint = (Position of (Casting unit))
      • Set VariableSet TargetPoint = (Position of (Target unit of ability being cast))
      • Set VariableSet Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
      • Custom script: call RemoveLocation(udg_CenterPoint)
      • Custom script: call RemoveLocation(udg_TargetPoint)
      • Set VariableSet Knockback2DTime = 2.00
      • Set VariableSet Knockback2DDistance = 1000.00
      • Set VariableSet Knockback2DUnit = (Target unit of ability being cast)
      • Trigger - Run Knockback 2D <gen> (checking conditions)
Note that there are quite a few other Variables that you can set when issuing a Knockback, here's just a few:
  • Set VariableSet Knockback2DKillTrees = True
  • Set VariableSet Knockback2DPause = True
  • Set VariableSet Knockback2DBounces = True
If you don't set these yourself then the System will automatically use their Default values. The Default values can be found in "Knockback 2D Config". You can adjust these to your liking.

Aaaah, now i understand, thanks a lot my man.
Also, will (Position of (Target unit of ability being cast)) work for abilities like War Stomp and Thunder Clap?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
Aaaah, now i understand, thanks a lot my man.
Also, will (Position of (Target unit of ability being cast)) work for abilities like War Stomp and Thunder Clap?
Nope, there is no Target unit of ability being cast for those abilities. You need to use a Pick Every Unit function:
  • AoE Knockback
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to War Stomp
    • Actions
      • -------- Area of Effect = 250/300/350 (War Stomp levels 1 to 3) --------
      • Set VariableSet AreaOfEffect = (200.00 + (50.00 x (Real((Level of (Ability being cast) for (Triggering unit))))))
      • -------- --------
      • -------- For efficiency sake we can alter the setup of these variables. Instead of creating CenterPoint over and over again, simply create it once at the start. --------
      • -------- Then remove it once after the Pick Every Unit function. --------
      • Set VariableSet CenterPoint = (Position of (Triggering unit))
      • Custom script: set bj_wantDestroyGroup = true
      • Unit Group - Pick every unit in (Units within AreaOfEffect of CenterPoint.) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) belongs to an enemy of (Triggering player).) Equal to True
              • ((Picked unit) is A flying unit) Equal to False
              • ((Picked unit) is Magic Immune) Equal to False
              • ((Picked unit) is Mechanical) Equal to False
              • ((Picked unit) is invulnerable) Equal to False
              • ((Picked unit) is A structure) Equal to False
              • ((Picked unit) is dead) Equal to False
            • Then - Actions
              • Set VariableSet TargetPoint = (Position of (Picked unit))
              • Set VariableSet Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
              • Custom script: call RemoveLocation(udg_TargetPoint)
              • Set VariableSet Knockback2DTime = 0.90
              • Set VariableSet Knockback2DDistance = 500.00
              • Set VariableSet Knockback2DUnit = (Picked unit)
              • Trigger - Run Knockback 2D <gen> (checking conditions)
            • Else - Actions
      • Custom script: call RemoveLocation(udg_CenterPoint)
Note that I altered it slightly for efficiency. I set CenterPoint before the Pick Every Unit function and remove it afterwards. This is done because it's unnecessary to create/remove CenterPoint over and over again since it's position never changes.

Not to overwhelm you, but there is a minor issue with the above trigger. The Pick every unit within range function doesn't work exactly the same as how Area of Effect abilities work. For Area of Effect abilities like War Stomp, the collision size of the enemy unit is taken into consideration when checking if it's in range. Pick every unit within range does not take the collision size into consideration, resulting in inconsistencies between the two. Because of this a unit may be stunned by War Stomp but not get knocked back if it's near the edge of the abilities AoE. The solution is just two extra lines of code as well as a minor adjustment of an existing line:
  • AoE Knockback Improved
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to War Stomp
    • Actions
      • -------- Area of Effect = 250/300/350 (War Stomp levels 1 to 3) --------
      • Set VariableSet AreaOfEffect = (200.00 + (50.00 x (Real((Level of (Ability being cast) for (Triggering unit))))))
      • -------- --------
      • -------- For efficiency sake we can alter the setup of these variables. Instead of creating CenterPoint over and over again, simply create it once at the start. --------
      • -------- Then remove it once after the Pick Every Unit function. --------
      • Set VariableSet CenterPoint = (Position of (Triggering unit))
      • Custom script: set bj_wantDestroyGroup = true
      • Unit Group - Pick every unit in (Units within (AreaOfEffect + 300.00) of CenterPoint.) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) belongs to an enemy of (Triggering player).) Equal to True
              • ((Picked unit) is A flying unit) Equal to False
              • ((Picked unit) is Magic Immune) Equal to False
              • ((Picked unit) is Mechanical) Equal to False
              • ((Picked unit) is invulnerable) Equal to False
              • ((Picked unit) is A structure) Equal to False
              • ((Picked unit) is dead) Equal to False
            • Then - Actions
              • Custom script: if IsUnitInRange(GetEnumUnit(), GetTriggerUnit(), udg_AreaOfEffect) then
              • Set VariableSet TargetPoint = (Position of (Picked unit))
              • Set VariableSet Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
              • Custom script: call RemoveLocation(udg_TargetPoint)
              • Set VariableSet Knockback2DTime = 0.90
              • Set VariableSet Knockback2DDistance = 500.00
              • Set VariableSet Knockback2DUnit = (Picked unit)
              • Trigger - Run Knockback 2D <gen> (checking conditions)
              • Custom script: endif
            • Else - Actions
      • Custom script: call RemoveLocation(udg_CenterPoint)
So I changed (Units within AreaOfEffect) to (Units within (AreaOfEffect + 300.00)). This should guarantee that all nearby units, regardless of collision size, are taken into consideration.

Then once a unit is picked we run this line of code:
  • Custom script: if IsUnitInRange(GetEnumUnit(), GetTriggerUnit(), udg_AreaOfEffect) then
And at the bottom we close it off with this:
  • Custom script: endif
Think of this as an extra Condition. It checks if the Picked unit (GetEnumUnit) is within AreaOfEffect range of the Triggering unit (GetTriggerUnit). The reason I use this is because it takes into consideration Collision Size, meaning it will work exactly the same as War Stomp and other AoE abilities.

So using War Stomp level 1 as an example:
-AreaOfEffect is set to 250.00.
-I pick every unit within 250.00 + 300.00 (550.00) AoE of the Caster regardless of their collision sizes.
-I then run the IsUnitInRange function which DOES take into consideration collision sizes, using the original AreaOfEffect (250.00) for the comparison.
-If the unit is indeed "InRange" then I knock it back.

This fixes the issue I described above with collision sizes and calculating AoE.
 

Attachments

  • Knockback 2_5D w Examples.w3x
    69 KB · Views: 19
Last edited:
Level 3
Joined
Aug 26, 2020
Messages
12
Nope, there is no Target unit of ability being cast for those abilities. You need to use a Pick Every Unit function:
  • AoE Knockback
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to War Stomp
    • Actions
      • -------- Area of Effect = 250/300/350 (War Stomp levels 1 to 3) --------
      • Set VariableSet AreaOfEffect = (200.00 + (50.00 x (Real((Level of (Ability being cast) for (Triggering unit))))))
      • -------- --------
      • -------- For efficiency sake we can alter the setup of these variables. Instead of creating CenterPoint over and over again, simply create it once at the start. --------
      • -------- Then remove it once after the Pick Every Unit function. --------
      • Set VariableSet CenterPoint = (Position of (Triggering unit))
      • Custom script: set bj_wantDestroyGroup = true
      • Unit Group - Pick every unit in (Units within AreaOfEffect of CenterPoint.) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) belongs to an enemy of (Triggering player).) Equal to True
              • ((Picked unit) is A flying unit) Equal to False
              • ((Picked unit) is Magic Immune) Equal to False
              • ((Picked unit) is Mechanical) Equal to False
              • ((Picked unit) is invulnerable) Equal to False
              • ((Picked unit) is A structure) Equal to False
              • ((Picked unit) is dead) Equal to False
            • Then - Actions
              • Set VariableSet TargetPoint = (Position of (Picked unit))
              • Set VariableSet Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
              • Custom script: call RemoveLocation(udg_TargetPoint)
              • Set VariableSet Knockback2DTime = 0.90
              • Set VariableSet Knockback2DDistance = 500.00
              • Set VariableSet Knockback2DUnit = (Picked unit)
              • Trigger - Run Knockback 2D <gen> (checking conditions)
            • Else - Actions
      • Custom script: call RemoveLocation(udg_CenterPoint)
Note that I altered it slightly for efficiency. I set CenterPoint before the Pick Every Unit function and remove it afterwards. This is done because it's unnecessary to create/remove CenterPoint over and over again since it's position never changes.

Not to overwhelm you, but there is a minor issue with the above trigger. The Pick every unit within range function doesn't work exactly the same as how Area of Effect abilities work. For Area of Effect abilities like War Stomp, the collision size of the enemy unit is taken into consideration when checking if it's in range. Pick every unit within range does not take the collision size into consideration, resulting in inconsistencies between the two. Because of this a unit may be stunned by War Stomp but not get knocked back if it's near the edge of the abilities AoE. The solution is just two extra lines of code as well as a minor adjustment of an existing line:
  • AoE Knockback Improved
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to War Stomp
    • Actions
      • -------- Area of Effect = 250/300/350 (War Stomp levels 1 to 3) --------
      • Set VariableSet AreaOfEffect = (200.00 + (50.00 x (Real((Level of (Ability being cast) for (Triggering unit))))))
      • -------- --------
      • -------- For efficiency sake we can alter the setup of these variables. Instead of creating CenterPoint over and over again, simply create it once at the start. --------
      • -------- Then remove it once after the Pick Every Unit function. --------
      • Set VariableSet CenterPoint = (Position of (Triggering unit))
      • Custom script: set bj_wantDestroyGroup = true
      • Unit Group - Pick every unit in (Units within (AreaOfEffect + 300.00) of CenterPoint.) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) belongs to an enemy of (Triggering player).) Equal to True
              • ((Picked unit) is A flying unit) Equal to False
              • ((Picked unit) is Magic Immune) Equal to False
              • ((Picked unit) is Mechanical) Equal to False
              • ((Picked unit) is invulnerable) Equal to False
              • ((Picked unit) is A structure) Equal to False
              • ((Picked unit) is dead) Equal to False
            • Then - Actions
              • Custom script: if IsUnitInRange(GetEnumUnit(), GetTriggerUnit(), udg_AreaOfEffect) then
              • Set VariableSet TargetPoint = (Position of (Picked unit))
              • Set VariableSet Knockback2DAngle = (Angle from CenterPoint to TargetPoint)
              • Custom script: call RemoveLocation(udg_TargetPoint)
              • Set VariableSet Knockback2DTime = 0.90
              • Set VariableSet Knockback2DDistance = 500.00
              • Set VariableSet Knockback2DUnit = (Picked unit)
              • Trigger - Run Knockback 2D <gen> (checking conditions)
              • Custom script: endif
            • Else - Actions
      • Custom script: call RemoveLocation(udg_CenterPoint)
So I changed (Units within AreaOfEffect) to (Units within (AreaOfEffect + 300.00)). This should guarantee that all units, regardless of collision size, are taken into consideration.

Then once a unit is picked we run this line of code:
  • Custom script: if IsUnitInRange(GetEnumUnit(), GetTriggerUnit(), udg_AreaOfEffect) then
And at the bottom we close it off with this:
  • Custom script: endif
Think of this as an extra Condition. It checks if the Picked unit (GetEnumUnit) is within AreaOfEffect range of the Triggering unit (GetTriggerUnit). The reason I use this is because it takes into consideration Collision Size, meaning it will work exactly the same as War Stomp and other AoE abilities.

So using War Stomp level 1 as an example:
-AreaOfEffect is set to 250.00.
-I pick every unit within 250.00 + 300.00 (550.00) AoE of the Caster regardless of their collision sizes. -I then run the IsUnitInRange function which DOES take into consideration collision sizes, using the original AoE (250.00) for the comparison.
-If the unit is indeed "InRange" then I knock it back

This fixes the issue I described above with collision sizes and calculating AoE.

I can't seem to open your test map, it's giving me a "-Level info data missing or invalid", but that's no problem i'll try to recreate it manually.
I'd like to know how to make the "AreaOfEffect" variable and if the "= true" part of the first custom script in the improved trigger is part of the text that has to be written in or needs to be checked in a comparison menu thingy (don't know how it's called)
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
I can't seem to open your test map, it's giving me a "-Level info data missing or invalid", but that's no problem i'll try to recreate it manually.
I'd like to know how to make the "AreaOfEffect" variable and if the "= true" part of the first custom script in the improved trigger is part of the text that has to be written in or needs to be checked in a comparison menu thingy (don't know how it's called)
Yeah, I use the latest version of Warcraft 3 so you won't be able to open it unless you're up to date.

AreaOfEffect is a Real variable.

The most common variables you'll use: Integer = whole number, Real = number with a decimal, Boolean = True/False, String = "some text", and many more.

And Custom script is always written by the user. Understand that Custom script is the actual code that Warcraft 3 uses. For example, here's a Unit variable being set in GUI (the standard trigger editor):
  • Set Variable SomeUnit = (Triggering unit)
And this is what that Action looks like when converted to code or "custom script".
vJASS:
set udg_SomeUnit = GetTriggerUnit()
You can see that the code looks fairly similar to the GUI example above it.

And to further explain GUI:
GUI stands for Graphical User Interface, which is what the Trigger Editor is. It's a simplified version of coding that has an easy to use interface (buttons, graphics, search for text, etc) to make coding easier.

So when you use Custom script you're skipping the pretty GUI shell that surrounds the code and going right to the source. It can be confusing sometimes because you'll notice that the naming conventions will change between GUI and code (Custom script). For example, a Point variable in GUI is actually called a Location in code. It's not that confusing though because their names both convey the same idea, which is a reference to some position in the game.
 
Last edited:
Level 3
Joined
Aug 26, 2020
Messages
12
Yeah, I use the latest version of Warcraft 3 so you won't be able to open it unless you're up to date.

AreaOfEffect is a Real variable.

The most common variables you'll use: Integer = whole number, Real = number with a decimal, Boolean = True/False, String = "some text", and many more.

And Custom script is always written by the user. Understand that Custom script is the actual code that Warcraft 3 uses. For example, here's a Unit variable being set in GUI (the standard trigger editor):
  • Set Variable SomeUnit = (Triggering unit)
And this is what that Action looks like when converted to code or "custom script".
vJASS:
set udg_SomeUnit = GetTriggerUnit()
You can see that the code looks fairly similar to the GUI example above it.

And to further explain GUI:
GUI stands for Graphical User Interface, which is what the Trigger Editor is. It's a simplified version of coding that has an easy to use interface (buttons, graphics, search for text, etc) to make coding easier.

So when you use Custom script you're skipping the pretty GUI shell that surrounds the code and going right to the source. It can be confusing sometimes because you'll notice that the naming conventions will change between GUI and code (Custom script). For example, a Point variable in GUI is actually called a Location in code. It's not that confusing though because their names both convey the same idea, which is a reference to some position in the game.

I see, the trigger works perfectly.
Thank you for taking the time to explain all these things to me.
 
Top