- Joined
- Nov 10, 2006
- Messages
- 181
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
Last edited: