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

Extended Knockback System

Level 6
Joined
Nov 10, 2006
Messages
181
attachment.php

JASS:
Knockback System                               By wraithseeker
   v 1.09

                                        Credits
                                    Vile for SFX.
                                    Dusk for GroupUtils.
                                    PitzerMike for IsDestructableLibrary.
                                    Anitarf for IsTerrainWalkable.
                                    Grim001 for AutoIndex
                                    Pyrogasm & Kenny for Inspiration!
                                    Kenny for another way of creation.
                                    Anyone who has helped me in one way or another.
                                                                    
                                Changelog v1.01
                                    
                - Removed d.destroy() and changed to d.release() for PUI issues.
                - Took away some silly parameters which was totally useless.
                - Optimized the code.
                            
                                Changelog v1.02
                - Optimized code.
                - Indented some code.
                - Added some extra parametres.
                - unit height no longer gets bugged when at the borders of the map.
                
                                Changelog v1.03
                - Removed some useless struct members.
                - Made method Terraincheck not public and not to take knock d for efficient purposes.
                - Updated documention.
                - Code now has comments and names are more friendly.
                
                                Changelog v1.04
                - Used PUI text macro to shorten things.
                - Removed some useless call.
                
                                Changelog v1.05
                - Fixed a serious bug.
                
                                Changelog v1.06
                - Remade the whole system.
                
                                Changelog v1.07
                - Added extra functions and optimized code.
                            
                                Changelog v1.08
                - Removed ParabolaZ and added another new way of jumping.
                - Removed useless stuff.
                - Ported to AutoIndex.
                                Changelog v1.09
                - Changed another way of jumping again.
                - Added 2 different ways of knocking which is distance and duration as variables.
                - Indented code and optimized it.
                - Ported some stuffs to struct
                - Added a new interface member OnChain.
                - Added GetUnitDist to check distance between units 
                  (idea by moyack but I took it as I think it is good for lazy people).
                - Now requires BoundSentinel
                
                
   A knockback system that is made to fulfill almost any knockbacking issues that you can have in a game. It can be customizable
   from a basic creation to a advanced creation.
                                                                                   
    requires                                                                      
        - A vJASS preprocessor (JassNewGenPack).            
        
        - The special effects found in the import/export section of this map.      
         They are the "Dust.mdx" for land, "SlideWater.mdx" for water and "DustAndRocks.mdx" for collision on cliffs.

        - GroupUtils, AutoIndex, IsDestructableLib, IsTerrainWalkable,BoundSentinel
    
    Pros
        - Can support many kind of stuffs you want in a knockback.
        - Leak free and efficient.
        - Made in vJASS.
        - Uses only a single timer and not many.
        - Knockback is realistic.
        - Allows you to knock a unit at a constant speed.
        - Knock a unit while flying.
        - Allows Chain knockbacks which is not found here.
        - You can use a constant knockback speed if you do not want any increment or decrement.
        - You can increase your knockback speed instead of decreasing for dashing spells mainly.
        - Allows interface usage.
        - very very configurable and is easy to use.
        - When a chained unit knocks another target, that target can have chaining properties too!
        
    Cons
        - requires 5 librarys
        - You can only use one UnitUserData indexing system and that is AutoIndex.
        - Might be hard to get used to the syntax.
                             
  Other Information                                                           
        - Angle is taken in Radians. But can easily be changed if you know how.    
        - Units will bounce back when they reach the map borders or just stay there if REFLECT is set to false.          
        - Remember to import the special effect into your map if you need them else it would look really plain.   
        - There are five functions that can be used in this system and more will be added soon if I have the time.               
                                                                                  
    Finding the Distances                                                             
        - This is some general knowledge on how to calculate the distance with a formula if you are using speed and decrement                                
        
        
                    The Formula to calculate the distance.
                    = -1 * V * V / (2 * A / Interval)
                
                 Variable V = The initial speed of the knocked unit which must be positive.
                 Variable A = The decrement of the speed per Interval which must be negative.
          Variable Interval = The timer period that function Update runs, known as TIME in the globals block.
          
          For example, we have initial speed value as 1000 and decrement as 15 and we subsitute them into the formula
          
                            -1 * 1000 * 1000 / (2 * -15 / 0.03) = 1000 distance (Use a calculator to assist you.)
                            
                Note that the default timer interval is 0.03 and try not to use periods that are too high else it will look choppy.
                            


                                                                                  
Implementation:                                                                
    - First off you need JNGP known as JassNewGenPack which is a modified world editor.  
    
    - Create a new trigger and then convert it to custom text and then you copy paste
    the code into the trigger.
    
    - Now you will either need to export the special effects from this map to  
    special effects found in this map.   
    
    - Once you have the effects in your map. Find the configuration block      
    that is underneath all this green text. Change the paths of the effect
    to the path in the system.     
    
    - It is not recommended if you do not know much about importing or exporting to change the paths
    else just use the paths in the system.

    - Save a map and hope for the best, if it fails report the bug to me.
    
                                                                                   
    NOTE: Please report any bugs to me at thehelper.net via PM or at the system thread. (Username: wraithseeker)
                                                                                 
    Usage                                                                        
        call Knock.create(source,t,angle,speed,decrement,distance,duration)        
                    
    Source      =   The unit who started the knockback.       
    Target      =   The unit that will be used in the knockback. 
    Angle       =   The angle that the unit will be knocked back.
    Speed       =   The initial speed of the unit.
    Decrement   =   The amount of speed reduced every interval.
    Distance    =   The amount of distance you want the unit to get knocked.
    Duration    =   The amount of time you want a unit to get knocked.
    
    
            Note that if you can only have one choice out of two choice that is
                                
                call Knock.create(source,t,angle,speed,decrement,0,0)
                    
                Use that method if you prefer using speed and decrement as the variables
                and remember to set distance and duration to 0 else you will have a error message.
                
                call Knock.create(source,t,angle,0,0,distance,duration)
                
                       
                Use that method if you prefer using distance and duration as the variables
                and remember to set speed and decrement to 0 else you will have a error message.
                    

            For example Atan2(cy-y,cx-x)
            
        X is the coordinate of unit Target
        Y is the coordinate of unit Target
        CX is the coordinate of unit source
        CY is the coordinate of unit source
            
    Decrement        =   The variable that will deduct from speed every interval.
    
    Increment        =   The Increment speed of the unit target per interval, usually you should set it to 0 or null
                            unless you have boolean increment set to true.
                        
    Tree             =   This boolean will decide whether you want the unit target to knock down trees or not
    
    Chain            =   if set to 0, it does nothing but if set to 1, it does normal chaining but if set to 2 it creates chaining
                            properties for units who has been chained by the unit target of the knockback.
                    
    Fly              =   This boolean will decide whether you want the unit target to fly while getting knocked back
                            and pathing will be disabled.
    LineDamage       =  The amount of damage dealed to units around the target unit who has got knocked back. 
                            A value of 0 and below means nothing will happen.
                            
    ToTarget         =  Whether you want the unit to get knocked to a unit
                            A default value means that it wont get knocked to a unit
                        
    Move             =  Whether you want a unit to be moved with SetUnitX or SetUnitY
    
    Mode             =  if set to 0, nothing will happen but if set to 1, unit target will get knocked at a constant speed
                            if set to 2, unit target will get knocked at a increasing speed.
                            
    SFX              =  The string of the SFX that you want to display else if it is not "".
    
    attachpoint      =  The point where you want to attach the effect, if you did not set it to a value,
                            the default would be "origin".
                            
    GRAVITY          = Default value is -981. Adjust it as you wish.
    
    z                = The higher the value, the higher the unit jumps.
    
    Example of Usage                                                            
                                                                                  
    call Knock.create(t,u,a,1000.00,15.00,0,0)                            
    call Knock.create(source,t,angle,800,20,0,0) 
                                                                                  
    The first one will cause the target unit of a spell to be knocked back 1000 distance away using the formula
    and it will have decrement speed of 15 per interval.
                                                                                   
    The second one will cause the target unit of a spell to be knocked back 480 distance away using the formula
    and it will have decrement speed of 20 per interval.         
    
    For the other Misc functions you can do it like this (Note that it must be used after call Knock.create(......)
    
        set d.Trees = true which means that it will kill trees upon impact with them.
        set d.Fly = true which means that unit target will fly.
        set .cos = Your angle values for the coordinate X.
        set .sin = Your angle values for the coordinate Y.
        and so on for other variables.
        
interface usuage
        
    scope Tsmash initializer Init

private struct data extends Knock // extends our EKB struct

static method create takes nothing returns data
    local data d // initialize the struct
    local unit u = GetTriggerUnit() // caster
    local real x = GetUnitX(u) // X axis of caster
    local real y = GetUnitY(u) // Y axis of caster
    local location L = GetSpellTargetLoc() // location of target point
    local real lx = GetLocationX(L) // get dist between X axis
    local real ly = GetLocationY(L) // get distance between Y axis
    local real angle = Atan2(ly-y,lx-x) // find the angle
    set d = data.allocate(u,u,angle,0,0,1000,2.3) // create the struct using allocate which will be turned to .create.
    set d.fly = true // Fly!
    set d.Move = true // Moves the unit with SetUnitX & Y
    set d.ElevationA = GetRandomReal(30.,70.) // a random elevation angle for random height
    set u = null // null the unit after use
    return d // return struct that's right!
endmethod

method OnStop takes nothing returns nothing // we use the interface from our EKB struct which extends a interface
    local unit dummy  // initialize dummy
    local integer i = 0 // intialize i
    loop // loops through 5 instances and create a dummy unit with TSA 0.1 and cast thunder clap and then
            //apply a timed life duration of 1 second
        exitwhen i > 5
        call TriggerSleepAction(0.1)
        set dummy = CreateUnit(GetOwningPlayer(.target),'hfoo',GetUnitX(.target),GetUnitY(.target),0)
        call UnitAddAbility(dummy,'A002')
        call IssueImmediateOrder(dummy,"thunderclap")
        call UnitApplyTimedLife(dummy,'BTLF',1)
        set i = i + 1
    endloop
    set dummy = null // null dummy unit after use
endmethod
endstruct

globals
    private constant integer SPELL = 'A000' // our dummy spell
endglobals

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL // checks whether spell cast is equal to dummy spell
endfunction

private function Actions takes nothing returns nothing
    local data d = data.create() // create the struct!
endfunction

//===========================================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddAction(t, function Actions)
    call TriggerAddCondition(t,Condition(function Conditions))
endfunction

endscope

Always remember that you can always use other interface members but this provides only a interface example usuage of OnStop.

Other functions                                                           
                                                                                  
    call d.IsKnockedBack(Target)                                               
                                                                                   
    This function checks if the unit is currently sliding using this        
    system. It will return true if it is.                                        
                                                                                   
    call d.IsUnitSliding(Target)                                             
                                                                                   
    This function will stop the unit from sliding (using this system).      
    It also returns true if the unit is stopped.                                 
                          
    call GetUnitDist(your KB target, knock to target)
    
    This function will get distance between the two units if you call the function.
    
    These functions can be used in conjunction with each other, by checking if a 
      unit is sliding then stopping it if it is.
      
                    I hope you have a great day after reading this documention, enjoy EKB!

JASS:
library Knockback initializer Init requires DestructableLib, IsTerrainWalkable, GroupUtils, AutoIndex

globals
    private constant real    TIME         = 0.03                     // The timer interval.
    private constant real    CHAINRADIUS  = 160.00                   // The radius a unit can get chained when near the unit target.
    private constant real    SPEEDFACTOR  = 0.75                     // How much speed will be reduced when a unit chains another unit.
    private constant real    RADIUS       = 128.00                   // The radius to check tree.
    private constant real    HEIGHTLEVEL  = 200.00                   // The default height level that stops chaining or killing trees when in the air.
    private constant string  GROUND       = "MDX\\Dust.mdx"          // The effect for ground.
    private constant string  WATER        = "MDX\\SlideWater.mdx"    // The effect for water.
    private constant string  COLLISION    = "MDX\\DustAndRocks.mdx"  // The effect when at cliff
    private constant boolean REFLECT = false // whether you want the unit to get reflected back when they touch map boundaries
endglobals

globals
    private constant integer INVULNERABLE   = 'Avul'               
    private constant integer FLYID          = 'Amrf' 
    private rect     TreeRect               = null
    private boolexpr TreeCheck              = null
    private boolexpr ChainFilter            = null
    private boolexpr LineFilter             = null
    private real MapMaxX                    = 0
    private real MapMaxY                    = 0
    private real MapMinX                    = 0
    private real MapMinY                    = 0
endglobals

function GetUnitDist takes unit a, unit b returns real
    local real x = GetUnitX(a)
    local real y = GetUnitY(a)
    local real tx = GetUnitX(b)
    local real ty = GetUnitY(b)
    local real dx = tx-x
    local real dy = ty-y
	return SquareRoot(dx*dx+dy*dy)
endfunction

private interface face
    method OnPeriodic takes nothing returns nothing defaults nothing
    method OnStop takes nothing returns nothing defaults nothing
    method OnStopCondition takes nothing returns boolean defaults false
    method OnChain takes nothing returns nothing defaults nothing
    method OnChainCondition takes nothing returns boolean defaults false
endinterface
    

struct Knock extends face
    private static timer Timer    = CreateTimer()
    private static integer Count  = 0
    private static Knock array D
    private static Knock data
    private static integer array Entries
    private     integer      mode            = 0
    private     group        hit             = null
    private     group        linehit         = null
    private     real         VariableSpeed   = 0
    private     effect      effects          = null
    unit        source          = null
    unit        target          = null
    unit        dashtarget      = null
    unit        chaintarget     = null
    real        cos             = 0
    real        sin             = 0 
    real        speed           = 0
    real        decrement       = 0
    real        Increment       = 0
    real        LineDamage      = 0
    real        z               = 0
    real        ElevationAngle  = 0.
    real        h               = 0.
    real        GRAVITY         = -981.
    real        vf              = 0.
    string      SFX             = ""
    string      attachpoint     = "origin"
    integer     chain           = 0
    integer     Mode            = 0
    boolean     fly             = false
    boolean     ToTarget        = false
    boolean     Move            = false
    boolean     trees           = false
    
static method KnockbackStop takes unit target returns boolean
    local Knock this
    local integer id = GetUnitId(target)
    local integer i = .Count - 1
    if .Entries[id] != 0 then
        if .Count > 0 then
            set .D[i]= .D[.Count]
        else 
            call PauseTimer(.Timer) 
        endif
        set .Entries[id] = .Entries[id] - 1
        call .destroy()
        return true
    endif
    return false
endmethod

static method IsKnockedBack takes unit target returns boolean
    local Knock this
    local integer id = GetUnitId(target)
    return .Entries[id] != 0
endmethod
   
private static method CheckTrees takes nothing returns boolean
    return IsDestructableTree(GetFilterDestructable())
endmethod

private static method Trees takes nothing returns nothing
    call KillDestructable(GetEnumDestructable())
endmethod

private static method ChainCheck takes nothing returns boolean
    local Knock this = .data
    return .target != GetFilterUnit() and IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(.source)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true and IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) == false and GetWidgetLife(GetFilterUnit()) > 0.405 and GetUnitAbilityLevel(GetFilterUnit(),INVULNERABLE) <= 0
endmethod

private static method LineCheck takes nothing returns boolean
    local Knock this = .data
    return GetWidgetLife(GetFilterUnit()) > 0.405 and IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(.source)) and GetFilterUnit() != .source
endmethod

private method IsPointOutside takes real x, real y returns nothing
    local real tx = GetUnitX(.target) + 50 * .cos
    local real ty = GetUnitY(.target) + 50 * .sin
    if REFLECT and (x > MapMaxX or y > MapMaxY or x < MapMinX or y < MapMinY) and (tx > MapMaxX or ty > MapMaxY or tx < MapMinX or ty < MapMinY) then
        set .cos = -.cos
        set .sin = -.sin
    endif
endmethod
    
private method TerrainCheck takes nothing returns integer
    local real x = GetUnitX(.target)
    local real y = GetUnitY(.target)
    local real height = GetUnitFlyHeight(.target)
    if IsTerrainWalkable(x + 50.00 * .cos,y + 50.00 * .sin) == false then
        return 3
   elseif IsTerrainPathable(x,y,PATHING_TYPE_FLOATABILITY) then
        return 1
    elseif not IsTerrainPathable(x,y,PATHING_TYPE_WALKABILITY) then
        return 2
    endif 
    return 0
endmethod

method operator ElevationA takes nothing returns real
    return .ElevationAngle
endmethod

method operator ElevationA= takes real a returns nothing
    local real speed = .speed/0.03
    set .ElevationAngle = a
    set .z = speed * Sin(.ElevationAngle*bj_DEGTORAD)
endmethod
            
static method create takes unit source, unit target, real angle, real speed, real decrement, real distance, real duration returns Knock
    local Knock d = Knock.allocate()
    local integer i = GetUnitId(target)
    if speed >= 0. and decrement >= 0. and distance <= 0. and duration <= 0. then
        if target == null or source == null or speed <= 0.00 or decrement <= 0.00 then
            call BJDebugMsg("Invalid values for Knockback.!")
            call d.destroy()
        endif
        set d.speed         = speed * TIME
        set d.decrement     = decrement * TIME
    else
        if target == null or source == null or distance <= 0.00 or duration <= 0.00 then
            call BJDebugMsg("Invalid values for Knockback.!")
            call d.destroy()
        endif
        set duration = duration/TIME
        set d.speed = (2.00 * distance) / (duration + 1.00)
        set d.decrement = d.speed/duration
    endif
    set d.source        = source
    set d.target        = target
    set d.trees         = false
    set d.fly           = false
    set d.chain         = 0
    set d.ToTarget      = false
    set d.Move          = false
    set d.hit           = NewGroup()
    set d.linehit       = NewGroup()
    set d.sin           = Sin(angle)
    set d.cos           = Cos(angle)
    set d.LineDamage    = 0
    set d.Mode          = 0
    set d.VariableSpeed = 0
    set d.Increment     = 0
    set d.dashtarget    = null
    set d.chaintarget   = null
    set d.VariableSpeed = d.speed
    set d.SFX           = ""
    set d.attachpoint   = "origin"
    set d.mode = d.TerrainCheck()
    set d.GRAVITY = -981.
    set d.z = 0.
    set d.vf = 0.
    set d.h = 0.
    if d.mode == 1 then
        set d.effects = AddSpecialEffectTarget(GROUND,d.target,d.attachpoint)
    elseif d.mode == 2 then
        set d.effects = AddSpecialEffectTarget(WATER,d.target,d.attachpoint)
    elseif d.mode == 3 then
        set d.effects = AddSpecialEffectTarget(COLLISION,d.target,d.attachpoint)
    endif
    set .D[.Count] = d
       if .Count == 0 then
        call TimerStart(.Timer,TIME,true,function Knock.action)
    endif
    set .Count = .Count + 1
    set .Entries[i] = .Entries[i] + 1
    return d
endmethod
    
static method get takes unit u returns integer
    local integer id = GetUnitId(u)
    return .Entries[id]
endmethod

private method onDestroy takes nothing returns nothing
    call DestroyEffect(.effects)
    call ReleaseGroup(.hit)
    call ReleaseGroup(.linehit)
    call SetUnitFlyHeight(.target,GetUnitDefaultFlyHeight(.target),0)
    call SetUnitPathing(.target,true)
endmethod
        
private static method action takes nothing returns nothing
    local Knock d       = 0
    local Knock this    = 0
    local real    x     = 0.
    local real    y     = 0.
    local real    cx    = 0.
    local real    cy    = 0.
    local real    tx    = 0.
    local real    ty    = 0.
    local integer id    = 0
    local integer mode  = 0
    local real height   = 0.00
    local real angle    = 0.00
    local integer i     = 0
    local unit t        = null
    loop
        exitwhen i >= .Count
        set this = .D[i]
        set id = GetUnitId(.target)
        set x = GetUnitX(.target)
        set y = GetUnitY(.target)
        set height = GetUnitFlyHeight(.target)
        set mode = .mode
        set .vf = .z + .GRAVITY * TIME
        set .h = .h + (.vf*.vf-.z*.z) / (2 * .GRAVITY)
        set .z = .vf
        if .OnPeriodic.exists then
            call .OnPeriodic.execute()
        endif
        
        if .ToTarget and not .OnStopCondition.exists then
            set cx = GetUnitX(.dashtarget)
            set cy = GetUnitY(.dashtarget)
            set angle = Atan2(cy-y,cx-x)
            set .sin = Sin(angle)
            set .cos = Cos(angle)
            if SquareRoot((cx - x) * (cx - x) + (cy - y) * (cy - y)) <= 125 then
                if .OnStop.exists then
                    call .OnStop.execute()
                endif
                call .destroy()
                set .Count = .Count - 1
                set .Entries[id] = .Entries[id] - 1
                if .Count > 0 then
                    set .D[i]= .D[.Count]
                    set i = i - 1 
                else 
                    call PauseTimer(.Timer) 
                endif
            endif
        
        elseif .OnStopCondition.exists and not .ToTarget then
            if .OnStopCondition() then
                if .OnStop.exists then
                    call .OnStop.execute()
                endif
                call .destroy()
                set .Count = .Count - 1
                set .Entries[id] = .Entries[id] - 1
                if .Count > 0 then
                    set .D[i]= .D[.Count]
                    set i = i - 1 
                else 
                    call PauseTimer(.Timer) 
                endif
            endif
    
        elseif .speed <= 0 and not .ToTarget and not .OnStopCondition.exists then
            if .OnStop.exists then
                call .OnStop.execute()
            endif
            call .destroy()
            set .Count = .Count - 1
            set .Entries[id] = .Entries[id] - 1
            if .Count > 0 then
                set .D[i]= .D[.Count]
                set i = i - 1 
            else 
                call PauseTimer(.Timer) 
            endif
        else
        
        call .IsPointOutside(x,y)
        
        if height >= HEIGHTLEVEL then
            set .chain = 0
            set .trees = false
        endif
    
        if .Mode == 0 then
            set x = x + .speed * .cos
            set y = y + .speed * .sin
        elseif .Mode == 1 then
            set x = x + .speed * .cos
            set y = y + .speed * .sin
        elseif .Mode == 2 then
            set x = x + .VariableSpeed *.cos
            set y = y + .VariableSpeed *.sin
            set .VariableSpeed = .VariableSpeed + .Increment
        endif
    
        if .Move then
            call SetUnitX(.target,x)
            call SetUnitY(.target,y)
        else
            call SetUnitPosition(.target,x,y)
        endif
     
        if .LineDamage > 0 then
            set .data = this
            call GroupClear(ENUM_GROUP)
            call GroupEnumUnitsInRange(ENUM_GROUP,x,y,100,LineFilter)
            loop
                set t = FirstOfGroup(ENUM_GROUP)
                exitwhen t == null
                if not IsUnitInGroup(t,.linehit) then
                    call UnitDamageTarget(.source,t,.LineDamage,false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,null)
                    call GroupAddUnit(.linehit,t)
                endif
                call GroupRemoveUnit(ENUM_GROUP,t)
            endloop
        endif
     
        if .trees then
            call SetRect(TreeRect,x-RADIUS,y-RADIUS,x+RADIUS,y+RADIUS)
            call EnumDestructablesInRect(TreeRect,TreeCheck,function Knock.Trees)
        endif
    
        set height = GetUnitFlyHeight(.target)   
        if .fly  then
            call UnitAddAbility(.target,FLYID)
            call UnitRemoveAbility(.target,FLYID)
            call SetUnitPathing(.target,false)
            call SetUnitFlyHeight(.target,.h,0)
        endif

        set height = GetUnitFlyHeight(.target)   
    if .SFX == "" and height >= GetUnitDefaultFlyHeight(.target) and height <= GetUnitDefaultFlyHeight(.target) +1 then
            set .mode = .TerrainCheck()
            if .mode == 1 and (mode == 2 or mode == 3)then
                call DestroyEffect(.effects)
                set .effects = AddSpecialEffectTarget(GROUND,.target,.attachpoint)
            elseif .mode == 2 and (mode == 1 or mode == 3) then
                call DestroyEffect(.effects)
                set .effects = AddSpecialEffectTarget(WATER,.target,.attachpoint)
            elseif .mode == 3 and (mode == 1 or mode == 2) then
                call DestroyEffect(.effects)
                set .effects = AddSpecialEffectTarget(COLLISION,.target,.attachpoint)
            endif
        else
            call DestroyEffect(.effects)
            set .effects = AddSpecialEffectTarget(.SFX,.target,.attachpoint)
        endif

        if .chain == 1 or .chain == 2 then
            set .data = this
            call GroupClear(ENUM_GROUP)
            call GroupEnumUnitsInRange(ENUM_GROUP,x,y,CHAINRADIUS,ChainFilter)
            loop
                set t = FirstOfGroup(ENUM_GROUP)
                exitwhen t == null
                    if not IsUnitInGroup(t,.hit) then
                         if not .OnChainCondition.exists or .OnChainCondition() then
                            set cx = GetUnitX(t)
                            set cy = GetUnitY(t)
                            call GroupAddUnit(.hit,t)
                            set d = Knock.create(.source,t,Atan2(cy-y,cx-x),(.speed/TIME)*SPEEDFACTOR,(.decrement/TIME),0,0)
                            if .chain == 2 then
                                set d.chain = 2
                            endif
                            call GroupAddUnit(d.hit,.target)
                            set .chaintarget = t
                            if .OnChain.exists then
                                call .OnChain.execute()
                            endif
                            set .D[.Count] = d
                            set .Count = .Count + 1       
                        endif
                    endif
                call GroupRemoveUnit(ENUM_GROUP,t)
            endloop
        endif
        endif
        set .speed = .speed - .decrement
        set i = i + 1
    endloop
endmethod
    
static method onInit takes nothing returns nothing
    set TreeRect    = Rect(0.00,0.00,1.00,1.00)
    set TreeCheck   = Filter(function Knock.CheckTrees)
    set ChainFilter = Filter(function Knock.ChainCheck)
    set LineFilter  = Filter(function Knock.LineCheck)
endmethod
endstruct

private function Init takes nothing returns nothing
    set MapMaxX     = GetRectMaxX(bj_mapInitialPlayableArea)
    set MapMaxY     = GetRectMaxY(bj_mapInitialPlayableArea)
    set MapMinX     = GetRectMinX(bj_mapInitialPlayableArea)
    set MapMinY     = GetRectMinY(bj_mapInitialPlayableArea)     
endfunction

endlibrary
 

Attachments

  • Extended Knockback System v1.09.w3x
    114.2 KB · Views: 82
Last edited:
Level 11
Joined
Nov 4, 2007
Messages
337
I think, the struct has waaaay too many members.
Each knockback uses a lot of Cache.

And I think
JASS:
            if d.AllowMove == true then
                call SetUnitX(d.target,x)
                call SetUnitY(d.target,y)
            else
                call SetUnitPosition(d.target,x,y)
            endif
..
You should add various options.
Actually, IsTerrainWalkable fits perfectly into Knockback needs.
Otherwise: Good

Edit: The booleans constant and bla are useless, you can just add the value 'friction' or startspeed and 'endspeed'.
 
Level 6
Joined
Nov 10, 2006
Messages
181
Many member = many configuration


> What kind of options?

I don't see how my knockback system uses cache at all. I do not even use game cache. I only use structs.

The booleans are not useless. I have checked with people over at thehelper and at wc3c.

I don't get why you need friction though, I already have start speed and I don't need endspeed.

I also have no intend to convert this to a mini physics system or a physics system.
 
Last edited:
Level 21
Joined
Aug 21, 2005
Messages
3,699
JASS:
public method TerrainCheck takes Knock d returns integer
endmethod

Bad method. methods, unless they're static, are supposed to be called on an object of the struct type. Do not "take Knock d" when you don't need to.

Better way to do this:
JASS:
    public method TerrainCheck takes nothing returns integer
        local real x = GetUnitX(this.target)
        local real y = GetUnitY(this.target)

        if IsTerrainWalkable(x + 50.00 * this.cos,y + 50.00 * this.sin) == false then
            return 3
        else
            if IsTerrainPathable(x,y,PATHING_TYPE_FLOATABILITY) then
                return 1
            elseif not IsTerrainPathable(x,y,PATHING_TYPE_WALKABILITY) then
                return 2
            endif
        endif

        return 0
    endmethod

    local Knock k = ...
    if k.IsTerrainWalkable() then
    endif

There's also no reason to make this a public method.

I think it's also a convention to start method identifiers with a lower case character (isTerrainPathable rather than IsTerrainPathable)

As said by Mapper, you're using too many useless struct members. The cos and sin might be acceptable, but why do you need x and y?? Use local variables for them...

Most of your struct members should be private, while they're currently all public. You also have a huuuuge "Update" function which is using a struct's members. IMO, a function should never be touching members of a struct. That's what methods are for. Your "Update" function should look more like this:
JASS:
private function Update takes nothing returns nothing
    local Knock d
    local integer i = 0
    ...
    loop
        set d = Knocker[i]
        call d.update()
        set i = i + 1
    endloop
endfunction
Where d.update simply is a method that does the actual updating. Hopefully, this method also uses submethods so it doesn't look like a huge method itself.

MapperMalte said:
Each struct member is one filled array place per knockback!
The fact there are so many members isn't that bad for your memory though. Each member costs 1 to 32 kb, which is nothing even for low-end pc's. But the problem lies at the fact there's a maximum amount of variables you can have...
 
Level 6
Joined
Nov 10, 2006
Messages
181
Why shouldn't a function never be touching members of a struct?

I have checked over at the sites, TheHelper and WC3C, they don't use methods to update stuff which just makes the code longer by adding another method.

For the x and y struct, thats my bad :(, used them for some testing / debugging purpose.

Will be removed soon.

Many members = many configurations.

For the method, I'll rewrite it.
 
Level 8
Joined
Aug 6, 2008
Messages
451
JASS:
private function Update takes nothing returns nothing
    local Knock d
    local integer i = 0
    ...
    loop
        set d = Knocker[i]
        call d.update()
        set i = i + 1
    endloop
endfunction
Where d.update simply is a method that does the actual updating. Hopefully, this method also uses submethods so it doesn't look like a huge method itself.

This is jass, function calls are slow. If you want it fast it will look ugly.

The uglier, the better.

The fact there are so many members isn't that bad for your memory though. Each member costs 1 to 32 kb, which is nothing even for low-end pc's. But the problem lies at the fact there's a maximum amount of variables you can have...

This is not really a problem, since people dont make that big maps anyways.
If they make, I feel sorry for them.

Why shouldn't a function never be touching members of a struct?

Because it looks neat.
 
Level 21
Joined
Aug 21, 2005
Messages
3,699
This is jass, function calls are slow. If you want it fast it will look ugly.

The uglier, the better.
It's hard to read right now. 1 additional function call will not be an incredible performance drain. Don't sacrifice readability for the 0,01 seconds performance gain; it isn't worth it. Besides, as you say next, people don't make big maps anyway. If you're going to have, say, 10 knocks at the same time, you won't notice a thing of performance problems with 1 additional method.

This is not really a problem, since people dont make that big maps anyways.
If they make, I feel sorry for them.

I have checked over at the sites, TheHelper and WC3C, they don't use methods to update stuff which just makes the code longer by adding another method.

The helper...
/facepalm

WC3C...
/facepalm

For the x and y struct, thats my bad :(, used them for some testing / debugging purpose.
Same goes for about all those booleans. They have nothing to do with customizability, and should be local variables, preferably in a separate method.
I mean, what do you need chain for? Maybe I'm overlooking something, but chain and tree are always the same?

Why shouldn't a function never be touching members of a struct?
One of the conventions is that a classes members should always remain private and only useable by its methods. External functions should use a struct's methods in order to modify certain properties. One of the reasons is that a function using a struct's methods doesn't really care about how the method looks like, as long as it gets the job done. You can change the implementation of certain methods by only changing that method, rather than having to change each function that uses a struct.

Especially with the latest inline optimizations, using private members & methods shouldn't have much impact on your performance. For that matter, your struct should perhaps be public (so other jassers can directly use the Knock struct rather than having to use the wrapper functions).
 
Level 6
Joined
Nov 10, 2006
Messages
181
You mean ChainHeight? They are both the same thing, couldn't think of a better name.

Struct was public if you even bothered to see the code >.>.

I won't make another method just for readability, the user doesn't even need to look at the code and I don't think it is that hard to read unless you don't have the mood.

Why /facepalm both sites? It's nice to get feedbacks anyway.

I prefer speed over readability, aren't going to change it into methods as it isn't really necessary.
 
Level 14
Joined
Nov 18, 2007
Messages
816
Eleandor, what you proposed has no benefits whatsoever. Its fine the way it is, though it could use some comments.

And WC3C>THW in terms of vJass coding.


A few things:
- move the Update function into your Struct as a static method
- Make your library require BoundSentinel and simply use SetUnitXY() without checking anything
- move all private functions into your struct, replace the liberary initializer with a static onInit method in your struct,
- -1 * speed * speed / (2 * -decrement / TIME) == TIME * speed * speed / (2 * decrement)
- huge parameter list on .create() is huge. Try avoiding some of them (increment for instance, you can just pass a negative decrement parameter)
 
Level 6
Joined
Nov 10, 2006
Messages
181
Why move the update function? It's fine as it is, nobody complained about that in WC3C and I don't need BoundSentinel, I have a built in check since I guess people wouldn't want to implement a thousand librarys so I didn't use it.

- huge parameter list on .create() is huge. Try avoiding some of them (increment for instance, you can just pass a negative decrement parameter)

For readability, and most customizability since it is just KnockbackTargetEx.
 
Level 6
Joined
Nov 10, 2006
Messages
181
I will NEVER ever do that even if it means that it doesn't get approved in THW since even WC3C users doesn't even ask me to.

It's just a matter of preference. Period.
 
Level 14
Joined
Nov 18, 2007
Messages
816
You see, those built-in checks arent necessary anymore with BoundSentinel. Using BoundSentinel gives a slight performance boost.

And no, moving the Update function is NOT a matter of preference. Its a matter of encapsulation and preventing users from accessing and changing data they better shouldnt access or change.

Do you really want me to post the same things over at WC3C?
 
Level 6
Joined
Nov 10, 2006
Messages
181
Sure do post it over there, rising_dusk and some knockback system I used doesn't do methods much?

Users ARE not supposed to touch anything except the globals part where it is configurable, if they touch the code and they don't know what to do, that's their fault.

With BoundSentinel I tried recently, sometimes units can get into the black spots but won't crash though, so I used this and I think users doesn't like the part where you need to have 5x librarys just for a knockback system.
 
Level 21
Joined
Aug 21, 2005
Messages
3,699
Why /facepalm both sites? It's nice to get feedbacks anyway.
Well, I think many people agree with me that the helper is pretty meh.
And well, wc3c is a useless site once you've downloaded the best systems there. If you got the most important libraries from there, why bother visiting it ever again. Maybe to enjoy another downtime?

Struct was public if you even bothered to see the code >.>.
You probably edited it. But even then, it would have been better if it was private, considering the fact all its members are public.

Users ARE not supposed to touch anything except the globals part where it is configurable, if they touch the code and they don't know what to do, that's their fault.
I see. That's why all your struct-members are public?? That's probably why I thought the "Knock" struct was private. Seeing that it isn't private, you're essentially giving the users access to the struct. public/private qualifiers are available, so use them properly. If you make the struct public, at least make its members private. Since users shouldn't touch anything, at least use the available tools to limit their access to the variables...

I prefer speed over readability, aren't going to change it into methods as it isn't really necessary.[/QUOTE]
Oh, please. Performance junky.
Since we're talking about performance now, remove TerrainCheck. It's another method you call, so it slows down the system, just put it in the update function. I mean, frankly, it's pretty worthless to have it as a separate method anyway.

By the way: the more parameters a function takes, the slower the function is called. Maybe consider removing all parameters and passing the information through globals!!! Yeah!

JASS:
if IsTerrainWalkable(x + 50.00 * this.cos,y + 50.00 * this.sin) == false then
Should be
JASS:
 if not IsTerrainWalkable(x + 50.00 * this.cos,y + 50.00 * this.sin then

JASS:
set d = Knock.create(source,target,angle,speed,decrement,increment,maxheight,chainheight,tree,move,constantspeed,chain,fly,increase)
    set Index[GetUnitIndex(target)] = d
should be
JASS:
set Index[GetUnitIndex(target)] = Knock.create(source,target,angle,speed,decrement,increment,maxheight,chainheight,tree,move,constantspeed,chain,fly,increase)
Another 0,000025 seconds gained! Hurray!

and I can keep nitpicking on how you should stop freaking out about useless performance twinks for a system that's going to be cast once or twice every minute. I mean, you might as well just merge EVERYTHING in 1 huge function! Hey, maybe add the allocate() code in your create method! Another call you saved!
 
Level 8
Joined
Aug 6, 2008
Messages
451
Speed really matters in functions or methods that are called by some fast period timer, otherwise its not so important.

But yea this really needs a lot of work.

Well, I think many people agree with me that the helper is pretty meh.
And well, wc3c is a useless site once you've downloaded the best systems there. If you got the most important libraries from there, why bother visiting it ever again. Maybe to enjoy another downtime?

wc3c has the best vJassers and The Helper has far better Jass help section than Hive has. If you have a Jass problem its always TheHelper or wc3c where you get your answers.
 
Level 3
Joined
Apr 17, 2008
Messages
25
wc3c has the best vJassers and The Helper has far better Jass help section than Hive has. If you have a Jass problem its always TheHelper or wc3c where you get your answers.
I agree with you on this one.




If you code parts of the system into the struct then why not code everything into the struct? A static method does exactly the same as a normal function but it makes your code look nicer.


It's harder for noobs to learn from the code.
You code things so noobs can use them, you don't code things so noobs can learn how to code their own crappy copies.

Methods should never be static and take a struct.
You need to take a struct if you want to use it with static methods since they doesn't get the this parameter generated by vJASS when they gets compiled and therefore you can't use the normal method syntax in static methods.
 
Level 3
Joined
Apr 17, 2008
Messages
25
> I think the system is fine as it is now, no need to use static methods for the update function.

If you are trying to use encapsulation why not take it all the way for the sake of readability?
 
Level 6
Joined
Nov 10, 2006
Messages
181
I'll discuss this with one of my friends and ask for their opinion and see what happens.

Actually, you don't need to read a system but only need to use it.
 
Level 14
Joined
Nov 18, 2007
Messages
816
A few things:
- move the Update function into your Struct as a static method
- Make your library require BoundSentinel and simply use SetUnitXY() without checking anything
- move all private functions into your struct, replace the liberary initializer with a static onInit method in your struct,
- -1 * speed * speed / (2 * -decrement / TIME) == TIME * speed * speed / (2 * decrement)
- huge parameter list on .create() is huge. Try avoiding some of them (increment for instance, you can just pass a negative decrement parameter)
Just to remind you.
 
Level 6
Joined
Nov 10, 2006
Messages
181
I am not going to use SetUnitX and Y as they do not block orders and PauseUnit looks ugly or if I use a stunbolt buff, it looks nasty too.

I can't pass a negative decrement parametre, needed for some calculations.
 
Level 6
Joined
Nov 10, 2006
Messages
181
SetUnitX and SetUnitY is optional, take a look at the parameters, there is a boolean AllowMove there and a documention to explain it. I know it disables pathings.

I am not stubborn when it comes to people giving suggestions like It was already implemented already.
 
Level 11
Joined
Nov 4, 2007
Messages
337
JASS:
     if .Mode == 0 then
        set x = x + .speed * .cos
        set y = y + .speed * .sin
    elseif .Mode == 1 then
        set x = x + .VariableSpeed *.cos
        set y = y + .VariableSpeed *.sin
    elseif .Mode == 2 then
        set x = x + .VariableSpeed *.cos
        set y = y + .VariableSpeed *.sin
    endif

==>

JASS:
     if .Mode == 0 then
        set x = x + .speed * .cos
        set y = y + .speed * .sin
    else
        set x = x + .VariableSpeed *.cos
        set y = y + .VariableSpeed *.sin
    endif

Oo

JASS:
call GroupEnumUnitsInRange(g,x,y,100,LineFilter)

100`?
You could use 3 new members instead ( :D ):

real DamageRange
boolean DoDamage
boolean ConstantRange

so:

JASS:
if .DoDamage then
if .ConstantRange then
call GroupEnumUnitsInRange(g,x,y,100,LineFilter)
elseif .ConstantRange == false then
call GroupEnumUnitsInRange(g,x,y,.DamageRange,LineFilter)
endif
else
call GroupEnumUnitsInRange(g,x,y,0,LineFilter)
endif

:D

More members => more configuration
 
Level 6
Joined
Nov 10, 2006
Messages
181
Your way of doing = More members => more confusing.

Mine is optimized as it is. Although the damage range can be put as a global.

There is 3 modes, increment, decrement and constant so no.
 
Level 6
Joined
Nov 10, 2006
Messages
181
Tell me more about how efficient it is.

I might use it if it gets stable enough though, discussions are still going on with that thing so updates will surely come.
 
Level 3
Joined
Apr 17, 2008
Messages
25
Wraith, AutoIndex = a GetUnitUserdata call getting inlined, there's nothing faster than that at the current time.


Also the way AutoIndex is implemented is very very nice.
 
Top