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

Level 13
Joined
Nov 22, 2006
Messages
1,260
Knockback(Ex)

By: Silvenon


Credits to:

Fixes:
  • renamed UnitKnockback/UnitKnockback_Execute into Knockback/Knockback_Execute (actually made Execute public)
  • removed UnitKnockbackWithLightning, found out it's useless
  • optimized the performance, it only uses Jass NewGen Pack now
  • added KnockbackEx function
  • changed the parameters a bit, added new feature(s)
  • uses initializer Init_Knockback, so no map initialization trigger is required anymore
More fixes:
  • this version actually works
  • doesn't use the stupid CheckPathability function anymore
  • has a different system instead
  • added instructions for GUI users
  • removed the CSSafety requirement
  • renamed Knockback_Init function
  • added a new radius feature (look in the parameter explanation)
  • removed the Data returnage, PP was right, it can only screw things up
  • modified the code, so now it uses create and onDestroy methods, I hope it's more readable now

JASS:
library Knockback initializer Init

// **************************************************************************
// **                                                                      **
// ** Knockback(Ex)                                                        **
// ** —————————————                                                        **
// **                                                                      **
// ** A function made for efficient knockbacking                           **
// **                                                                      **
// ** By: Silvenon                                                         **
// **                                                                      **
// **************************************************************************

//=======================================//
//Credits to PitzerMike for this function//
//=======================================//

globals
    private constant integer DUMMY_ID = 'h000'
endglobals

private function TreeFilter takes nothing returns boolean
    local destructable d = GetFilterDestructable()
    local boolean i = IsDestructableInvulnerable(d)
    local unit u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, GetWidgetX(d), GetWidgetY(d), 0)
    local boolean result = false

    call UnitAddAbility(u, 'Ahrl')

    if i then
        call SetDestructableInvulnerable(d, false)
    endif

    set result = IssueTargetOrder(u, "harvest", d)
    call RemoveUnit(u)

    if i then
      call SetDestructableInvulnerable(d, true)
    endif

    set u = null
    set d = null
    return result
endfunction

//===========================================================================

globals
    private timer Tim = CreateTimer()
    private integer Total = 0
    private boolexpr Cond = null

    private integer array Ar
    private boolean array BoolAr

    private real MAX_X
    private real MAX_Y
    private real MIN_X
    private real MIN_Y
endglobals

private constant function Interval takes nothing returns real
    return 0.04
endfunction

private function KillTree takes nothing returns nothing
    if BoolAr[0] then
        call KillDestructable(GetEnumDestructable())
    else
        set BoolAr[1] = true
    endif
endfunction

public struct Data
    unit u
    real d1
    real d2

    real sin
    real cos

    real r

    string s = ""
    effect e = null
    
    static method create takes unit u, integer q, real d, real a, real r, integer t, string s, string p returns Data
        local Data dat = Data.allocate()

        set dat.u = u
        set dat.d1 = 2 * d / (q + 1)
        set dat.d2 = dat.d1 / q

        set dat.sin = Sin(a)
        set dat.cos = Cos(a)

        set dat.r = r

        if s != "" and s != null then
            if t == 2 then
                if p != "" and p != null then
                    set dat.e = AddSpecialEffectTarget(s, u, p)
                else
                    set dat.e = AddSpecialEffectTarget(s, u, "chest")
                endif
            elseif t == 1 then
                set dat.s = s
            endif
        endif

        call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
        call PauseUnit(u, true)

        if Total == 0 then
            call TimerStart(Tim, Interval(), true, function Data.Execute)
        endif

        set Total = Total + 1
        set Ar[Total - 1] = dat

        return dat
    endmethod
    
    static method Execute takes nothing returns nothing
        local Data dat
        local integer i = 0
        local real x
        local real y
        local rect r
        local real rad
        
        loop
            exitwhen i >= Total
            set dat = Ar[i]
            
            if dat.s != "" and dat.s != null then
                set x = GetUnitX(dat.u)
                set y = GetUnitY(dat.u)
                
                call DestroyEffect(AddSpecialEffect(dat.s, x, y))
                
                set x = x + dat.d1 * dat.cos
                set y = y + dat.d1 * dat.sin
            else
                set x = GetUnitX(dat.u) + dat.d1 * dat.cos
                set y = GetUnitY(dat.u) + dat.d1 * dat.sin
            endif
            
            if dat.r != 0 then
                set BoolAr[0] = dat.r > 0
                
                set rad = dat.r
                
                if not BoolAr[0] then
                    set rad = rad * (-1)
                endif
                
                set r = Rect(x - rad, y - rad, x + rad, y + rad)

                call EnumDestructablesInRect(r, Cond, function KillTree)
                call RemoveRect(r)
                
                set r = null
            endif
            
            if (x < MAX_X and y < MAX_Y and x > MIN_X and y > MIN_Y) and not BoolAr[1] then
                call SetUnitX(dat.u, x)
                call SetUnitY(dat.u, y)
            endif
            
            set dat.d1 = dat.d1 - dat.d2
            
            if dat.d1 <= 0 or (x > MAX_X or y > MAX_Y or x < MIN_X or y < MIN_Y) or BoolAr[1] then
                set Ar[i] = Ar[Total - 1]
                set Total = Total - 1
                
                call dat.destroy()
            endif
            
            set i = i + 1
        endloop
        
        if Total == 0 then
            call PauseTimer(Tim)
        endif
    endmethod
    
    method onDestroy takes nothing returns nothing
        if .e != null then
            call DestroyEffect(.e)
        endif

        call PauseUnit(.u, false)

        set BoolAr[0] = false
        set BoolAr[1] = false
    endmethod
endstruct

function KnockbackEx takes unit u, real d, real a, real w, real r, integer t, string s, string p returns nothing
    call Data.create(u, R2I(w / Interval()), d, a, r, t, s, p)
endfunction

function Knockback takes unit u, real d, real a, real w returns nothing
    call Data.create(u, R2I(w / Interval()), d, a, 0, 0, "", "")
endfunction

private function Init takes nothing returns nothing
    set Cond = Filter(function TreeFilter)

    set BoolAr[0] = false
    set BoolAr[1] = false

    set MAX_X = GetRectMaxX(bj_mapInitialPlayableArea) - 64
    set MAX_Y = GetRectMaxY(bj_mapInitialPlayableArea) - 64
    set MIN_X = GetRectMinX(bj_mapInitialPlayableArea) + 64
    set MIN_Y = GetRectMinY(bj_mapInitialPlayableArea) + 64
endfunction

endlibrary


Are you getting tired of writing a knockback code every single time when you want to knockback something? If the answer is yes, then this is what you need. This function knockbacks a unit according to the parameters below:
  • unit u = the unit being knockbacked
  • real d = the distance the unit is knockbacked to
  • real a = the angle (direction of the knockback) in RADIANS
  • real w = the duration of the knockback
  • real r = the radius of tree destroying, but there's a new trick: if you put a negative number as the radius, the unit will stop knockbacking when it gets in that range of a tree and if you put a positive value, the unit will destroy trees when it gets in that range of a tree (if you don't want any of those, just put 0)
  • integer t = type 0 means no effect (both s and p should be ""), type 1 means periodic effect (meaning you should put "" as the attachment point, parameter p), type 2 means a special effect attached on the unit and destroyed when the knockback is finished (this is where you use parameter p)
  • string s = the path to the special effect you want to use (periodic), that effect will be destroyed immediatelly (you can put "" if you don't want a special effect)
  • string p = used for type 2, it's the attachment point of the effect, "chest" is default (so if you put "", it will be "chest")

Instructions:

Create a dummy unit according to one of these two tutorials: 1, 2 (I suggest the second one). You MUST change the DUMMY_ID constant integer to that unit's id.

You can deprivate any function from the library you want to use, I just made it like that so it doesn't interfere with other stuff. If you don't want any special effects and tree destroying, you can use Knockback function that takes only the first 4 parameters.

If you by any chance have angle in degrees and want to use it for this function, when calling the function just multiply that angle with bj_DEGTORAD, example:

call Knockback(u, d, a * bj_DEGTORAD, w)


You can change the interval of execution in the Knockback_Interval constant (default is 0.035).

Example of usage:

[jass=JASS]function Test takes nothing returns nothing
local unit c = GetTriggerUnit()
local unit t = GetSpellTargetUnit()
local real x1 = GetUnitX(c)
local real y1 = GetUnitY(c)
local real x2 = GetUnitX(t)
local real y2 = GetUnitY(t)
local real dist = 500
local real angle = Atan2(y2 - y1, x2 - x1)
local real dur = 2
call Knockback(t, dist, angle, dur)
set c = null
set t = null
endfunction[/code]


[trigger=GUI]Test
Events
Unit - A unit starts the effect of an ability
Conditions
(Ability being cast) Equal to <Some Spell>
Actions
Set Caster = (Triggering unit)
Set Target = (Target unit of ability being cast)
Set Distance = (400 + (100 x (Ability level of <Some Spell> for Caster)))
Set Angle = (Facing angle of Caster)
Set Duration = 2
Custom script: call Knockback(udg_Target, udg_Distance, udg_Angle * bj_DEGTORAD, udg_Duration)[/trigger]



On the same principle you use the KnockbackEx function


Requires: Jass NewGen Pack

Find more about the function in my Knockback tutorial

Please report any errors.
 
Last edited:
Level 10
Joined
Nov 10, 2004
Messages
351
If you are using CSCache, how come that you don't use tables instead? they are faster, and else i don't really see any reason not to use handle vars, as people here are more familiar with them.

I suggest you to use constant functions, as it is faster than attaching stuff and much easier to change.
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
What he means by using Constants is instead of doing something like this

JASS:
function myFunc takes paramArgs returns type
    local integer imaconstant = 5
    call DoSomething( imaconstant )
endfunction

do something like

JASS:
constant function imaconstant takes nothing returns integer
    return 5
endfunction

function myFunc takes paramArgs returns type
    call DoSomething( imaconstant() )
endfunction

Tools like Vexorian's Map Optimizer inline the call anyways, and it makes the function more readable+easier to change, as you know by looking at the top what alot of the things do (if you name the functions well)

Anyways, you should probably also remove the damage option from the function. It's clogging up the parameters with stuff that will often be left unused, and is much less flexible than UnitDamageTarget.

It's probably better to either

A) rename that UnitKnockbackEx and have a copy without the damage feature
B) remove it altogether

In other news, I'll approve this if those're fixed. (well, the second one, the first one doesn't matter that much)
 
Level 13
Joined
Nov 22, 2006
Messages
1,260
*Removed damage options. Heh my original function didn't have damage options, but I thought people would want that, but now I realize it was a mistake (I thought damage over time was better).

I know what are constants and how to use them, but can you please explain how would i use constants in my knockback functions?? What values would constants return? How would it then allow different knocbacks? Please give more details, as I would love to improve it.
 
Level 13
Joined
Nov 22, 2006
Messages
1,260
Ok. No feedback people? No one wants to comment this brilliant function?? :(:(

EDIT: Is it ok if I add IsTerrainPathable? Because it can get annoying if the unit gets knocked outside the playable map area.
 
Last edited:
Level 4
Joined
Dec 14, 2004
Messages
85
You have a bunch of functions that start with alot of typing and then a _ and then a sub name for the function. Why not just set these into scopes and then make them public? it does the same thing but if you use the function inside the scope you don't have to type or read all of that.
 
Level 10
Joined
Nov 10, 2004
Messages
351
A way to make it more efficient would be getting rid of the Cos() and Sin() in the Excecute function.

JASS:
    local real x=GetUnitX(SomeUnitWhoKnocks)
    local real y=GetUnitY(SomeUnitWhoKnocks)
    local real x2=GetUnitX(SomeKnockedUnit)
    local real y2=GetUnitY(SomeKnockedUnit)
    local real angle=Atan2(y2-y, x2-x)
    local real xspeed
    local real yspeed
    local real speed= 22 //How long the unit is knocked every 0.035 second or whatever the interval is
    set xspeed=speed*Cos(angle)
    set yspeed=speed*Sin(angle)
How to use then in a periodical trigger.
JASS:
    call SetUnitX(u, GetUnitX(u)+xspeed)
    call SetUnitY(u, GetUnitY(u)+yspeed)
instead of calling Cos() and Sin() every time the unit is moved you can just add x/yspeed onto it's current position.
 
Level 13
Joined
Nov 22, 2006
Messages
1,260
But the speed lowers with each execution, shouldn't I just put Cos(angle) and Sin(angle) in a variable? Then it won't be calling Cos and Sin in every execution. I will update this function when I get back from the holidays, that would be in 2 weeks. You probably noticed that it's missing an important update: prevent going outside the playable map area. :)
 
Level 13
Joined
Nov 22, 2006
Messages
1,260
No it doesn't, then that would be a simple knockback code that everybody can write (not that this is complicated, but it looks nicer that way). This is the line that lowers the distance:

JASS:
set kd.d1 = kd.d1 - kd.d2

Ok, then I will do that.....later though. One question: I think this knocback code isn't gonna break a channeling spell, because after it's unpaused, it continues channeling right? So should I put a SetUnitPosition(u, GetUnitX(u), GetUnitY(u)) in the beginning? (Because SetUnitPosition, unlike SetUnitX/Y, breaks channeling spells, among other things)

EDIT: Fixes:

- the knockbacked unit stops if it reaches an unpathable point (that includes the outside of playable map area)
- a small performance improvement, thanks to Diablo-dk
- now the knocback breaks channeling spells

Also, if using this function, you need to credit Vexorian because of his CheckPathability function

I didn't test this function after these fixes, so let me know if I made a mistake somewhere
 
Last edited:
Level 40
Joined
Dec 14, 2005
Messages
10,532
Quote it and CnP it from that -- that's what I do.

Anyhow, MoodestMoo, basically...

First, you need to be running on the NewGenWE from JassNewGenPack, found in the Tools section over at www.wc3campaigns.net

Next, copy the script into any blank trigger of your choice, by "Converting the trigger to custom text". Delete anything else that was in the trigger before you paste this script in.

Next, just call the function via a Custom Script action.

Syntax:

Custom script: call UnitKnockback( unit (unit), dist (real), angle (real), dur (real), sfxpath (string) )

So, let's say you had a unit var called MyUnit. The first parameter would be udg_MyUnit, giving us

Custom script: call UnitKnockback( udg_MyUnit, dist (real), angle (real), dur (real), sfxpath (string) )

Next, let's say we wanted it to knockback 500. Well, add that to the second field.

Custom script: call UnitKnockback( udg_MyUnit, 500, angle (real), dur (real), sfxpath (string) )

Next, add the angle (IN RADIANS, 0-2pi) to the third field. If you don't know how to use radians, instead add the angle in Degrees and then add *bj_DEGTORAD at the end.

Eg:

Custom script: call UnitKnockback( udg_MyUnit, 500, GetUnitFacing(udg_MyUnit)*bj_DEGTORAD, dur (real), sfxpath (string) )

Next, add how long it should take, eg 5 seconds

Custom script: call UnitKnockback( udg_MyUnit, 500, GetUnitFacing(udg_MyUnit)*bj_DEGTORAD, 5, sfxpath (string) )

Finally, add the effect's string path. You can find this by:

First, pick your effect in the object editor. In the "Custom" field, you should see a path that looks something like

Abilities\Spells\Human\HolyBolt\HolyBoltSpecialArt.mdl
(that one's the holy light path, if I recall correctly)

So, enclose that in quotes, then double all the slashes. Put that into the last field:

Custom script: call UnitKnockback( udg_MyUnit, 500, GetUnitFacing(udg_MyUnit)*bj_DEGTORAD, 5, "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl" )
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
Bump: found a bug: call SetUnitPosition(kd.u, GetUnitX(kd.u), GetUnitY(kd.u)) in function UnitKnockbackWithLightning should instead be call SetUnitPosition(kwld.u, GetUnitX(kwld.u), GetUnitY(kwld.u)) so that the script can compile properly.

~updated your post.

Oh, btw, just use the same struct for both, and leave unused vars with the normal knockback. It'd be alot more efficient.

EDIT: Also made it require a library called LocalHandleVars, just in case ;)
 
Level 13
Joined
Nov 22, 2006
Messages
1,260
Thanks for the help, but there is one more problem..... it returns an error "kd is not a type that allows . sintax" on this line:

JASS:
local real x = GetUnitX(kd.u) + kd.d1 * kd.cos

Any ideas why that happened?

I would love to rep you but I have to spread first :)

EDIT: Misspelled Knockback
 
Last edited:
Level 13
Joined
Nov 22, 2006
Messages
1,260
Thanks, I hate when I misspell.... :)

Oh, tip to code a bit faster: (StructType).destroy(varName) is equivelant to (varName).destroy()

Yeah, I know, I found out about that later after I've already coded that function, then I didn't see any reason to change it :p

Another question: why are you helping me so much?? What did I ever do to you?? :D
 
Level 13
Joined
Nov 22, 2006
Messages
1,260
Two questions for Pooty:

1.) Do you think I should add a tree-hitting option? (so the knockbacked unit can take down a tree if it hits one)

2.) Maybe I should put the instructions for GUI users? Though 'JASS functions' title probably already scared away all of them.
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
1.) Do you think I should add a tree-hitting option? (so the knockbacked unit can take down a tree if it hits one)
Entirely your choice

2.) Maybe I should put the instructions for GUI users? Though 'JASS functions' title probably already scared away all of them.
Nah, there are plenty of other ways for them to find out.. they'll have to sometime -.-
 
Level 4
Joined
May 6, 2007
Messages
87
Hey guys... is there any way I can use this on a Mac? Because from the looks of it, the NewGen pack can't run on one, and so when I just try to CnP the text and try to call it (the way PurplePoot described), it just gave me a thousand errors. :cry:

Thanks in advance.
 
Level 13
Joined
Nov 22, 2006
Messages
1,260
Why didn't you suggest I single timer my knockback code? Hasn't slip your mind, huh? :)

Anyways, I'm going to edit my code big, as you can see from this tut :p

Another thing, why didn't you add me in your contacts in msn (I added you)? Not cool enough for ya, huh? ;)

I understand, I'll take it like a man....... :(
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
I'm not too much of a single-timer fanatic as others - it doesn't bother me for most things.

Anyways, plan on posting that tut here?


?

Haha, I thought it automatically does, I just click Ok and don't fill out any of the crap...

Maybe that's why it's spamming me about all the unanswered things...

Meh, whenever I try to fill it out it rejects me :p

If I'm on, though, I'll pull up a convo - that's rare recently due to school, though.
 
Top