System Knockback System v1

emjlr3

Change can be a good thing
Reaction score
395
KS beta - by emjlr3

Used as a global knockback system for all applications in a map. Using a users specified physical values for the environment and units, knocks back targeted units using simple controllable values.

Scren1copy.jpg

JASS Script
JASS:
library KS initializer Init, needs PUI

//******************************************************************************************
//*
//*  Knockback System beta - By emjlr3 
//*
//*  Used as a global knockback system for all applications in a map.  Using a users
//*  specified physical values for the environment and units, knocks back targeted
//*  units using simple controllable values.
//* 
//*  Requires:
//*    - "PUI" trigger copied to your map, if not already done so
//*    - A vJASS preprocessor
//*
//******************************************************************************************

//******************************************************************************************
//*
//*  Documentation:
//*
//*   Function List:
//*    KS_KnockbackUnit(unit to knock, real speed to knock at, real angle to knock at, real mass of knocker)
//*      * returns a struct of type KS_Dat if the unit had previously been moving
//*      * contains the real velocity/s (vel) and real angle (ang) in radians of the updated knockback
//*      * these can be used to update the movement of missiles, other units, etc.
//*      * this struct is cleaned after a ~1s wait, so it must be utilized within that time frame
//*
//*    KS_GetUnitMass(unit)
//*      * returns the real mass of the specified unit
//*      * units without a specified mass are given a default value of 100.
//*      * when units die, their mass value of their index is returned to the default value
//*
//*    KS_SetUnitMass(unit, real mass)
//*      * sets the real mass of the specified unit to the input value
//*      * masses of units should be set when they enter the map, or before they are used in this system
//*
//*   Usage:
//*    Call KS_KnockbackUnit for the unit you wish to knockback.  The speed and mass input determine
//*    the speed at which the unit will slide, and the mass of the unit sliding along with your 
//*    gravity and friction values determine the acceleration rate.  The inputed values for speed and
//*    mass should be greater then 0, and your angle should be in radians.  This allows for limitless, completely
//*    realistic movement, however, given the nature of the inputs, you may have to play around with 
//*    the values a bit to find the desired movement and acceleration speeds.  If the unit you wish to knockback
//*    is already doing so, an inelastic collision will occur between the knocker and knockie.  When this
//*    happens, KS_KnockbackUnit returns a struct containing all the data needed to update the movement
//*    of the knocker unit/missile/etc.  Remember, you must use the struct within a ~1s timer frame.
//*
//*    Call KS_SetUnitMass (KS_SUM) to set the mass of a unit.  This determines how it interacts with others when they
//*    collide.  This also determines the speed at which it accelerates.  Units who have not been given a
//*    mass by the user will default to a value of 100.  All masses should be greater then 0.
//*
//*    Call KS_GetUnitMass (KS_GUM) to get the mass of a unit.  This can allow you to alter masses given what they currently are,
//*    input an accurate mass when knocking back a unit, etc.
//*
//*   Pros:
//*    * Removes the need to write such a system yourself, or trigger all knockbacks each time one is needed
//*    * Allows for dynamic, realistic movement and collision, as well as effects from environment options
//*    * Completely MUI, lagless, and realistic
//*    * Few inputs allow for easy usage
//*    * Includes warning messages when inputs are invalid
//*
//*   Cons:
//*    * Can become tedious to create the ideal knockback speed or acceleration
//*    * Does not yet contain collision detection for sliding units
//*    * Does not yet contain terrain change height detection
//*    * You did not write it yourself, so unless you are adept in JASS and basic physics and geometry, you
//*      cannot understand/edit the inner workings yourself
//*
//*   Thanks to:
//*    * Acehart - for his geometry help which finally got my collisions working properly
//*    * Cohadar - for his knockback spell I wrote which originally got me interested in creating this
//*                as well as his PUI system
//*
//******************************************************************************************

// Configuration Options:
globals
    private constant real Friction = .7 // Friction between sliding units and the ground (>0.)
    private constant real Gravity = 9.8 // Gravity in your WC3 world (distance/s, >0.)
    private constant real Interval = .03 // Interval length for periodic timer callback
    private constant real Stop_Velocity = .1 // Speed/interval at which units stop sliding
    private constant boolean Fight = false // Whether units can fight against slides
    private constant boolean Kill_Trees = true // Whether units kill trees as they slide
    private constant string Sfx = "SlideDust.mdx" // Effect created on sliding units
endglobals  

// NO TOUCHING PAST THIS POINT, UNLESS YOU PAIN FOR DEATH !!!

// Global struct to pass updated data to users
public struct Dat
    real vel = 0.
    real ang = 0.
endstruct

// Needed struct
private struct data
    unit u = null
    real vel = 0.
    real accel = 0.
    real ang = 0.
    real cos = 0.
    real sin = 0. 
    effect sfx = null   
endstruct

globals
    // Stores units mass and whether they are sliding
    public real array Mass
    public group Sliding = CreateGroup()
    // Timer callbacks ran every second
    private real Runs = 1./Interval
    // Rect for the killing of trees
    private rect R
    // Data for looping through active sliding units
    private timer Timer = CreateTimer()
    private integer Total = 0
    private data array Data
endglobals 

// Recycle the mass of units
private function Die takes nothing returns nothing
    local integer pui = GetUnitIndex(GetTriggerUnit())
    
    set Mass[pui] = 0.    
endfunction
private function Init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_DEATH)
    call TriggerAddAction(trig,function Die)
    
    // Create tree killing rect
    set R = Rect(0.,0.,250.,250.)
endfunction

// Set the mass of a unit, affects collision effects
public function SUM takes unit u, real mass returns nothing
    local integer pui = GetUnitIndex(u)
    
    set Mass[pui] = mass
endfunction
public function SetUnitMass takes unit u, real mass returns nothing // BJ, easier to remember for users
    if u==null or mass<=0. then
        call BJDebugMsg("KS Warning: Invalid input in SetUnitMass")
        return
    endif
    call SUM(u,mass)
endfunction
// Get the mass of a unit, default mass = 100.
public function GUM takes unit u returns real
    local integer pui = GetUnitIndex(u)
    
    if Mass[pui]==0. then
        return 100.
    endif
    return Mass[pui]
endfunction
public function GetUnitMass takes unit u returns real // BJ, easier to remember for users
    if u==null then
        call BJDebugMsg("KS Warning: Invalid input in GetUnitMass.")
        return 0.
    endif
    return GUM(u)
endfunction

// Kill trees in an area 100. around the unit
private function KillTrees_Child takes nothing returns nothing
    call KillDestructable(GetEnumDestructable())    
endfunction
private function KillTrees takes real x, real y returns nothing
    // Ever thought to do it like this??  No more creating and destroying rects each time
    call MoveRectTo(R,x,y)
    call EnumDestructablesInRect(R,null,function KillTrees_Child)
endfunction
// Timer callback, which loops through active sliding units
private function KnockbackUnit_Child takes nothing returns nothing
    local data d
    local integer i = 1
    local real x
    local real y
    
    loop
        exitwhen i > Total
        set d = Data<i>
        
        if d.vel&gt;Stop_Velocity then // Still moving fast enough to continue sliding
            // Slide to new x and y spots
            set x = GetUnitX(d.u)+(d.vel/Runs)*d.cos
            set y = GetUnitY(d.u)+(d.vel/Runs)*d.sin 
            if Fight then
                call SetUnitX(d.u,x)
                call SetUnitY(d.u,y)
            else
                call SetUnitPosition(d.u,x,y)
            endif 
            if Kill_Trees then
                call KillTrees(GetUnitX(d.u),GetUnitY(d.u))
            endif
            // Vo - a = Vnew
            set d.vel = d.vel - d.accel
        else // Its all over for this unit
            call DestroyEffect(d.sfx)  
            call GroupRemoveUnit(Sliding,d.u)
            call d.destroy()
            set Data<i> = Data[Total]
            set Total = Total - 1
            if Total==0 then
                call PauseTimer(Timer)
            endif
            set i = i - 1
        endif
     
        set i = i + 1
    endloop  
endfunction
private function RemoveStruct takes Dat dd returns nothing
    call TriggerSleepAction(1.)
    call dd.destroy()
endfunction
// Start a knockback for a unit, angle is in radians, mass/speed&gt;0.
public function KnockbackUnit takes unit u, real speed, real angle, real mass returns Dat
    local data d
    local Dat dd
    local integer pui = GetUnitIndex(u)
    local boolean b = IsUnitInGroup(u,Sliding)
    local real m = GUM(u)
    
    local integer i = 1
    local real theta
    local real velx1
    local real vely1
    local real velx2
    local real vely2
    local real new_ang
    local real new_vel
    
    if u==null or mass&lt;=0. or speed&lt;=0. then
        call BJDebugMsg(&quot;KS Warning: Invalid input in KnockbackUnit&quot;)
        return 0
    endif
    if b==false then
        set d = data.create()           
        set d.u = u  
        // Conservation of momentum, m1*v1 = m2*v2
        set d.vel = (mass*speed) / m
        // Fnet/m = a, the net force is the force of friction on the unit
        set d.accel = (m*9.8*Friction) / m
        set d.ang = angle
        set d.cos = Cos(angle)
        set d.sin = Sin(angle)  
        set d.sfx = AddSpecialEffectTarget(Sfx,u,&quot;origin&quot;)    
    
        if Total==0 then     
            call TimerStart(Timer, Interval, true, function KnockbackUnit_Child)
        endif
        set Total = Total + 1
        set Data[Total] = d
        call GroupAddUnit(Sliding,u)
        
        return 0
    else
        loop
            set d = Data<i>
            exitwhen d.u==u or i&gt;Total
        endloop
        if i&gt;Total then
            call BJDebugMsg(&quot;KS Warning: This unit should be sliding but it is not, please report this error to emjlr3, with the replay and map.&quot;)
            return 0
        endif
        
        // X and Y components of velocities
        set theta = ModuloReal(d.ang*bj_RADTODEG,90.)
        set vely1 = Sin(theta)*speed
        set velx1 = Cos(theta)*speed
        
        set theta = ModuloReal(angle*bj_RADTODEG,90.)
        set vely2 = Sin(theta)*d.vel
        set velx2 = Cos(theta)*d.vel
        
        // Conservation of momentum
        set velx1 = ((mass*velx1)+(m*velx2))/(mass+m)
        set vely1 = ((mass*vely1)+(m*vely2))/(mass+m)
        // Determine new velocity and angle from X and Y components
        set new_vel = SquareRoot(velx1*velx1 + vely1*vely1)
        set new_ang = Atan(vely1/velx1)
        
        // Store new values to the sliding units struct
        set d.ang = new_ang
        set d.cos = Cos(d.ang)
        set d.sin = Sin(d.ang)
        set d.vel = new_vel
        
        // Return said values to the user so he can use it at his end
        set dd = Dat.create()
        set dd.ang = new_ang
        set dd.vel = d.vel 
        call RemoveStruct.execute(dd)
        return dd
    endif
endfunction

endlibrary</i></i></i>


Release Notes:

beta
  • No longer needs ABC or ABCT
  • Fixed inelastic collision bugs
  • Fixed currently sliding bugs
  • KS_Dat now contains the angle (ang) in radians, as opposd to the cos and sin of this
  • Updated error messages to contain more relevant information
  • Fixed tree destruction bugs

alpha
  • initial release
 

Attachments

  • Knock Back System beta - emjlr3.w3x
    92.3 KB · Views: 385

Demi666

New Member
Reaction score
127
>library KS initializer Init, needs PUI

so i just got my map back after formating the comp,
and im thinking of using your system

im sorry im not experianced and auto-understand this but i have never used a system before really,

Do i need to import both PUI (perfect unit indexing) and this system? or is both in one?
 

emjlr3

Change can be a good thing
Reaction score
395
actually, it needs PUI, and ABC and ABCT, forgot to add that in there, just so happened this is under those systems in the map so it did not matter

you would need to have PUI, ABC and ABCT systems in your map, as well as this one
 

Somatic

You can change this now in User CP.
Reaction score
84
Looks complicated, but counld understand its theory behind it. Nice Work usual. +Rep
 

emjlr3

Change can be a good thing
Reaction score
395
have fun making this in GUI....
 

soulreaping

New Member
Reaction score
17
Very nice system, just to make it more realistc, when you wrote this line:

JASS:

    elseif mass&lt;0. then
        call BJDebugMsg(&quot;KS Warning: A negative mass was provided to knockback.&quot;)
        return 0


A unit mass cannot be less or equal to 0.

A unit's mass is always > 0 if you want it to be really realistic.

Besides that everything looks fine, the measurements inspection looks OK, very useful system +rep.
 

0zaru

Learning vJASS ;)
Reaction score
60
That check is if the you have a negative value. It must be equal to 0 or more than 0 (Notice the < only).
 

darkRae

Ueki Fan (Ueki is watching you)
Reaction score
173
Well, I found some typo :

Code:
Call KS_SetUnitMass (KS_SUM) to set the mass of a unit.  This determines how it interacts with others when they
//*    collide.  This also determines the speed at which it accelerates.  Units who have not been given a
//*    mass by the user will default to a value of 100.  All masses should be [B]greate thenr[/B] 0.

Code:
Thanks to:
//*    * Acehart - for [B]is[/B] geometry help which finally got my collisions working properly

Still, this is an awesome system :D
 

Cohadar

master of fugue
Reaction score
209
I think it is time I get off my lazy ass and make ABCT version that is independent of ABC (for sake of simplicity and maybe some small speed gain)...

EDIT:
Just checked the map, excellent choice of hero for testing.
One thing I did not like: if storm hammer instantly kills unit the unit does not get knocked.
Dying units can also be knocked (at least in pyramidal..) so improve that if it is not too problematic.
 

phyrex1an

Staff Member and irregular helper
Reaction score
447
JASS:
public struct Dat
    real cos = 0.
    real sin = 0.
    real vel = 0.
endstruct

Strange returning the cos(a) sin(a) values instead the x and y coordinates of the vector.

JASS:
// Set the mass of a unit, affects collision effects
public function SUM takes unit u, real mass returns nothing
    local integer pui = GetUnitIndex(u)
    
    set Mass[pui] = mass
endfunction
public function SetUnitMass takes unit u, real mass returns nothing // BJ, easier to remember for users
    if u==null then
        call BJDebugMsg(&quot;KS Warning: No unit was provided to set the mass of.&quot;)
        return
    endif
    call SUM(u,mass)
endfunction

What's the reason for this? Properly named functions > strange abbreviations any time. If you really don't want to have the if block in the "real" function then put it as a debug, the error isn't fatal anyway. Same goes for GUM.

JASS:
private function KillTrees takes real x, real y returns nothing
    // Ever thought to do it like this??  No more creating and destroying rects each time
    call MoveRectTo(R,x,y)
    call EnumDestructablesInRect(R,null,function KillTrees_Child)
endfunction

I like code that have a bit of thinking behind them before wildly creating stuff.
Noticeable difference? Probably not. Optimization leetness? Yes.

JASS:
            set x = GetUnitX(d.u)+(d.vel/Runs)*d.cos
            set y = GetUnitY(d.u)+(d.vel/Runs)*d.sin

Yet another reason to store the x and y coordinates instead of sin and cos values.
JASS:
            set x = GetUnitX(d.u)+d.xSpeed

looks slightly better.

JASS:
        // X and Y components of velocities
        set theta = ModuloReal(angle*bj_RADTODEG,90.)
        set vely1 = Sin(angle)*speed
        set velx1 = Cos(angle)*speed
        
        set theta = ModuloReal(angle*bj_RADTODEG,90.)
        set vely2 = Sin(angle)*d.vel
        set velx2 = Cos(angle)*d.vel

theta looks very unused.

Very well done coding, I suppose it looks good to.
 

soulreaping

New Member
Reaction score
17
That check is if the you have a negative value. It must be equal to 0 or more than 0 (Notice the < only).

There is no unit in the world that has 0 mass AFAIK and as far as every physics man knows, so a unit's mass must be greater than 0.
 

emjlr3

Change can be a good thing
Reaction score
395
I think it is time I get off my lazy ass and make ABCT version that is independent of ABC (for sake of simplicity and maybe some small speed gain)...

EDIT:
Just checked the map, excellent choice of hero for testing.
One thing I did not like: if storm hammer instantly kills unit the unit does not get knocked.
Dying units can also be knocked (at least in pyramidal..) so improve that if it is not too problematic.

agreed on both acounts

Well, I found some typo :

Code:
Call KS_SetUnitMass (KS_SUM) to set the mass of a unit.  This determines how it interacts with others when they
//*    collide.  This also determines the speed at which it accelerates.  Units who have not been given a
//*    mass by the user will default to a value of 100.  All masses should be [B]greate thenr[/B] 0.

Code:
Thanks to:
//*    * Acehart - for [B]is[/B] geometry help which finally got my collisions working properly

Still, this is an awesome system :D

thanks for pointing that out

JASS:
public struct Dat
    real cos = 0.
    real sin = 0.
    real vel = 0.
endstruct

Strange returning the cos(a) sin(a) values instead the x and y coordinates of the vector.

I think ppl can do more with them....

[
JASS:
// Set the mass of a unit, affects collision effects
public function SUM takes unit u, real mass returns nothing
    local integer pui = GetUnitIndex(u)
    
    set Mass[pui] = mass
endfunction
public function SetUnitMass takes unit u, real mass returns nothing // BJ, easier to remember for users
    if u==null then
        call BJDebugMsg(&quot;KS Warning: No unit was provided to set the mass of.&quot;)
        return
    endif
    call SUM(u,mass)
endfunction

What's the reason for this? Properly named functions > strange abbreviations any time. If you really don't want to have the if block in the "real" function then put it as a debug, the error isn't fatal anyway. Same goes for GUM.

simpler for me to type at a later data, and users can still use the easily named ones

[
JASS:
private function KillTrees takes real x, real y returns nothing
    // Ever thought to do it like this??  No more creating and destroying rects each time
    call MoveRectTo(R,x,y)
    call EnumDestructablesInRect(R,null,function KillTrees_Child)
endfunction

I like code that have a bit of thinking behind them before wildly creating stuff.
Noticeable difference? Probably not. Optimization leetness? Yes.

^^

[
JASS:
            set x = GetUnitX(d.u)+(d.vel/Runs)*d.cos
            set y = GetUnitY(d.u)+(d.vel/Runs)*d.sin

Yet another reason to store the x and y coordinates instead of sin and cos values.
JASS:
            set x = GetUnitX(d.u)+d.xSpeed

looks slightly better.

d.vel is constantly updated, however...

[
JASS:
        // X and Y components of velocities
        set theta = ModuloReal(angle*bj_RADTODEG,90.)
        set vely1 = Sin(angle)*speed
        set velx1 = Cos(angle)*speed
        
        set theta = ModuloReal(angle*bj_RADTODEG,90.)
        set vely2 = Sin(angle)*d.vel
        set velx2 = Cos(angle)*d.vel

theta looks very unused.

Very well done coding, I suppose it looks good to.

yea....that could very well be a bug.....well, this is betay, will have to look at that end, angle after theta should be theta, and the first angle should actually be d.ang I believe
that is what i get for not having a collision example in the map after I made my final updates...

There is no unit in the world that has 0 mass AFAIK and as far as every physics man knows, so a unit's mass must be greater than 0.

yup
 

phyrex1an

Staff Member and irregular helper
Reaction score
447
>d.vel is constantly updated, however...
You might as well update the x and y speed. Anyway, it's just me thinking it's strange.
A vector is usually represented as an angle and a distance or x and y coordinates. You use some sort of mixture between the two ^^
 

Cohadar

master of fugue
Reaction score
209
>d.vel is constantly updated, however...
You might as well update the x and y speed. Anyway, it's just me thinking it's strange.
A vector is usually represented as an angle and a distance or x and y coordinates. You use some sort of mixture between the two ^^

As long as it is internally used noone cares :)

@emjlr3 -> Improved ABCT.
 

waaaks!

Zinctified
Reaction score
255
can u add an option to set on how far a unit will be throwed and a duration maybe?
 

emjlr3

Change can be a good thing
Reaction score
395
that is directly against what this system allows you to do

duration is always related to the friction and gravity you set, as well as the initial acceleration of the unit

since the acceleration is linear, distance thrown is a function of this, I cannot really see how I could make other options to allow a person to actually say, throw this unit this far, I could return an initial acceleration needed to do that, but the user still needs to use the basic laws of momentum to decide what mass and velocity to use

what you want is not what this system gives you, if you get what I am saying

btw I am going to attempt and get a new version of this out in the not so distance future, though don't count on it ^^
 

notsoexpert

New Member
Reaction score
4
System looks great from the demo map, but I came across a bit of an issue (?) while messing around. I have a screenshot and a replay of many error messages saying KS Warning: This unit should be sliding but it is not, please report this error to emjlr3, with the replay.
...I'd post the screenshot but I don't have anything installed to convert tga's... I just wanted to bring this little thing to your attention.
 

Cohadar

master of fugue
Reaction score
209
Take a screenshot of the screen Ctrl-PrtScr than paste it into paint and save the image as whatever...
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      • Ghan
        Administrator - Servers are fun

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top