| 03-30-2008, 11:06 PM | #1 | |
Background: I made this system awhile ago and have had it lying around for awhile now. Someone posted something about needing one in the trigger forum, and then someone linked them to the one in the database. Well, I was going through that old system and noticed that it is so terrible! It needed cache, ran each unit on an individual timer, and so forth. So I decided in my infinite generosity (Haha, yeah right) to clean this thing up and submit it. Requirements:
System Code: System Code://****************************************************************************** //* * //* K N O C K B A C K * //* Actual Code * //* v1.07 * //* * //* By: Rising_Dusk * //* * //****************************************************************************** library Knockback initializer Init needs TerrainPathability, GroupUtils, UnitIndexingUtils, LastOrder globals //********************************************************* //* These are the configuration constants for the system //* //* EFFECT_ATTACH_POINT: Where on the unit the effect attaches //* EFFECT_PATH_WATER: What special effect to attach over water //* EFFECT_PATH_GROUND: What special effect to attach over ground //* DEST_RADIUS: Radius around which destructs die //* DEST_RADIUS_SQUARED: Radius squared around which destructs die //* ADJACENT_RADIUS: Radius for knocking back adjacent units //* ADJACENT_FACTOR: Factor for collision speed transfers //* TIMER_INTERVAL: The interval for the timer that gets run //* ISSUE_LAST_ORDER: A boolean to issue last orders or not //* private constant string EFFECT_ATTACH_POINT = "origin" private constant string EFFECT_PATH_WATER = "MDX\\KnockbackWater.mdx" private constant string EFFECT_PATH_GROUND = "MDX\\KnockbackDust.mdx" private constant real DEST_RADIUS = 180. private constant real DEST_RADIUS_SQUARED = DEST_RADIUS*DEST_RADIUS private constant real ADJACENT_RADIUS = 180. private constant real ADJACENT_FACTOR = 0.75 private constant real TIMER_INTERVAL = 0.05 private constant boolean ISSUE_LAST_ORDER = true //********************************************************* //* These are static constants used by the system and shouldn't be changed //* //* Timer: The timer that runs all of the effects for the spell //* Counter: The counter for how many KB instances exist //* HitIndex: Indexes for a given unit's knockback //* Knockers: The array of all struct instances that exist //* Entries: Counters for specific unit instances in system //* ToClear: How many instances to remove on next run //* DesBoolexpr: The check used for finding destructables //* AdjBoolexpr: The check for picking adjacent units to knockback //* DestRect: The rect used to check for destructables //* private timer Timer = CreateTimer() private integer Counter = 0 private integer array HitIndex private integer array Knockers private integer array Entries private integer array ToClear private boolexpr DesBoolexpr = null private boolexpr AdjBoolexpr = null private rect DestRect = Rect(0,0,1,1) //* Temporary variables used by the system private real TempX = 0. private real TempY = 0. private unit TempUnit1 = null private unit TempUnit2 = null endglobals //* Boolean for whether or not to display effects on a unit private function ShowEffects takes unit u returns boolean return not IsUnitType(u, UNIT_TYPE_FLYING) endfunction //* Functions for the destructable destruction private function KillDests_Check takes nothing returns boolean local real x = GetDestructableX(GetFilterDestructable()) local real y = GetDestructableY(GetFilterDestructable()) return (TempX-x)*(TempX-x) + (TempY-y)*(TempY-y) <= DEST_RADIUS_SQUARED endfunction private function KillDests takes nothing returns nothing call KillDestructable(GetEnumDestructable()) endfunction //* Functions for knocking back adjacent units private function KnockAdj_Check takes nothing returns boolean return TempUnit2 != GetFilterUnit() and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(TempUnit1)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) and GetWidgetLife(GetFilterUnit()) > 0.405 and GetUnitAbilityLevel(GetFilterUnit(), 'Avul') <= 0 endfunction //****************************************************************************** //* Some additional functions that can be used function KnockbackStop takes unit targ returns boolean local integer id = GetUnitId(targ) set ToClear[id] = Entries[id] return ToClear[id] > 0 endfunction function IsKnockedBack takes unit targ returns boolean return Entries[GetUnitId(targ)] > 0 endfunction //* Struct for the system, I recommend leaving it alone private struct knocker unit Source = null unit Target = null group HitGroup = null effect KBEffect = null integer FXMode = 0 boolean KillDest = false boolean KnockAdj = false boolean ChainAdj = false boolean ShowEff = false real Decrement = 0. real Displace = 0. real CosA = 0. real SinA = 0. public method checkterrain takes knocker n returns integer local integer i = 0 local real x = GetUnitX(n.Target) local real y = GetUnitY(n.Target) if IsTerrainPathingType(x, y, TERRAIN_PATHING_LAND) then set i = 1 elseif IsTerrainPathingType(x, y, TERRAIN_PATHING_SHALLOW) then set i = 2 endif return i endmethod static method create takes unit source, unit targ, real angle, real disp, real dec, boolean killDestructables, boolean knockAdjacent, boolean chainAdjacent returns knocker local knocker n = knocker.allocate() set n.Target = targ set n.Source = source set n.FXMode = n.checkterrain(n) set n.HitGroup = NewGroup() set n.KillDest = killDestructables set n.KnockAdj = knockAdjacent set n.ChainAdj = chainAdjacent set n.ShowEff = ShowEffects(targ) set n.Decrement = dec set n.Displace = disp set n.CosA = Cos(angle) set n.SinA = Sin(angle) if n.ShowEff then if n.FXMode == 1 then set n.KBEffect = AddSpecialEffectTarget(EFFECT_PATH_GROUND, n.Target, EFFECT_ATTACH_POINT) elseif n.FXMode == 2 then set n.KBEffect = AddSpecialEffectTarget(EFFECT_PATH_WATER, n.Target, EFFECT_ATTACH_POINT) debug else debug call BJDebugMsg(SCOPE_PREFIX+" Error (On Create): Unknown Terrain Type") endif endif return n endmethod private method onDestroy takes nothing returns nothing local integer id = GetUnitId(this.Target) set Entries[id] = Entries[id] - 1 if GetWidgetLife(this.Target) > 0.405 and Entries[id] <= 0 and ISSUE_LAST_ORDER then //* Issue last order if activated call IssueLastOrder(this.Target) endif if this.ShowEff then //* Destroy effect if it exists call DestroyEffect(this.KBEffect) endif call ReleaseGroup(this.HitGroup) endmethod endstruct private function Update takes nothing returns nothing local unit u = null local unit s = null local rect r = null local knocker n = 0 local knocker m = 0 local integer i = Counter - 1 local integer j = 0 local integer mode = 0 local integer id = 0 local real xi = 0. local real yi = 0. local real xf = 0. local real yf = 0. loop exitwhen i < 0 set n = Knockers[i] set u = n.Target set mode = n.FXMode set id = GetUnitId(u) set xi = GetUnitX(u) set yi = GetUnitY(u) if n.Displace <= 0 or ToClear[id] > 0 then //* Clean up the knockback when it is over if ToClear[id] > 0 then set ToClear[id] = ToClear[id] - 1 endif call n.destroy() set Counter = Counter - 1 if Counter < 0 then call PauseTimer(Timer) set Counter = 0 else set Knockers[i] = Knockers[Counter] endif else //* Propagate the knockback in space and time set xf = xi + n.Displace*n.CosA set yf = yi + n.Displace*n.SinA call SetUnitPosition(u, xf, yf) set n.FXMode = n.checkterrain(n) //* Modify the special effect if necessary if n.ShowEff then if n.FXMode == 1 and mode == 2 then call DestroyEffect(n.KBEffect) set n.KBEffect = AddSpecialEffectTarget(EFFECT_PATH_GROUND, n.Target, EFFECT_ATTACH_POINT) elseif n.FXMode == 2 and mode == 1 then call DestroyEffect(n.KBEffect) set n.KBEffect = AddSpecialEffectTarget(EFFECT_PATH_WATER, n.Target, EFFECT_ATTACH_POINT) debug elseif n.FXMode == 0 then debug call BJDebugMsg(SCOPE_PREFIX+" Error (In Update): Unknown Terrain Type") endif endif //* Decrement displacement left to go set n.Displace = n.Displace - n.Decrement //* Destroy destructables if desired if n.KillDest then set TempX = GetUnitX(u) set TempY = GetUnitY(u) call MoveRectTo(DestRect, TempX, TempY) call EnumDestructablesInRect(DestRect, DesBoolexpr, function KillDests) endif //* Knockback nearby units if desired if n.KnockAdj then set xi = GetUnitX(u) set yi = GetUnitY(u) set TempUnit1 = n.Source set TempUnit2 = u call GroupEnumUnitsInRange(ENUM_GROUP, xi, yi, ADJACENT_RADIUS, AdjBoolexpr) loop set s = FirstOfGroup(ENUM_GROUP) exitwhen s == null if not IsUnitInGroup(s, n.HitGroup) then set xf = GetUnitX(s) set yf = GetUnitY(s) call GroupAddUnit(n.HitGroup, s) set m = knocker.create(n.Source, s, Atan2(yf-yi, xf-xi), n.Displace*ADJACENT_FACTOR, n.Decrement, n.KillDest, n.ChainAdj, n.ChainAdj) call GroupAddUnit(m.HitGroup, u) set Knockers[Counter] = m set Counter = Counter + 1 endif call GroupRemoveUnit(ENUM_GROUP, s) endloop endif endif set i = i - 1 endloop set u = null set s = null endfunction //****************************************************************************** //* How to knockback a unit function KnockbackTarget takes unit source, unit targ, real angle, real startspeed, real decrement, boolean killDestructables, boolean knockAdjacent, boolean chainAdjacent returns boolean local knocker n = 0 local integer id = GetUnitId(targ) local boolean b = true //* Protect users from themselves if decrement <= 0. or startspeed <= 0. or targ == null then debug call BJDebugMsg(SCOPE_PREFIX+" Error (On Call): Invalid Starting Conditions") set b = false else //* Can't chain if you don't knockback adjacent units if not knockAdjacent and chainAdjacent then set chainAdjacent = false endif set n = knocker.create(source, targ, angle*bj_DEGTORAD, startspeed*TIMER_INTERVAL, decrement*TIMER_INTERVAL*TIMER_INTERVAL, killDestructables, knockAdjacent, chainAdjacent) if Counter == 0 then call TimerStart(Timer, TIMER_INTERVAL, true, function Update) endif set Entries[id] = Entries[id] + 1 set HitIndex[id] = Counter + 1 set Knockers[Counter] = n set Counter = Counter + 1 endif return b endfunction private function Init takes nothing returns nothing call SetRect(DestRect, -DEST_RADIUS, -DEST_RADIUS, DEST_RADIUS, DEST_RADIUS) set DesBoolexpr = Condition(function KillDests_Check) set AdjBoolexpr = Condition(function KnockAdj_Check) endfunction endlibrary Special Thanks: All credits to anything used in the spell are in the testmap, but I'll do it again here. Thanks to Vex for vJass, thanks to xombie for reporting a critical bug in the system, thanks to wc3c for being awesome, and thanks to whoever actually takes the time to read through this! |
| 03-30-2008, 11:16 PM | #2 |
I think it's faster to store cartesian velocity (vx,vy) than polar (speed, angle). You don't need to use trig functions at all when using cartesian. JASS://given vx, vy, subtract F from speed c = 1 - F/SquareRoot(vx*vx + vy*vy) vx *= c vy *= c |
| 03-30-2008, 11:20 PM | #3 |
It could be, but squareroot is a really slow function for computation and I managed to avoid it entirely doing it this way. I'll see what some other people think before doing anything. EDIT: I just noticed I forgot to do something, damned! Stupid additional functions! |
| 03-30-2008, 11:46 PM | #4 | |
Quote:
|
| 03-30-2008, 11:53 PM | #5 |
Neat system, works smoothly, easy to implement & use. |
| 03-31-2008, 12:02 AM | #6 |
Most of your globals should be constant globals so they get inlined, like so (you should also move the TimerInterval constant to the top so that it can more easily be seen an changed): JASS:globals //********************************************************* //* These are the configuration constants for the system //* //* EffectLocation: Where on the unit the effect attaches //* EffectPath_Water: What special effect to attach over water //* EffectPath_Ground: What special effect to attach over ground //* DestDestroy_Radius: Radius around which destructs die //* private constant string EffectLocation = "origin" private constant string EffectPath_Water = "MDX\\KnockbackWater.mdx" private constant string EffectPath_Ground = "MDX\\KnockbackDust.mdx" private constant real DestDestroy_Radius = 180. private constant real TimerInterval = 0.05 endglobals This line (and the other 5 lines in the script like it): set n.KBEffect = AddSpecialEffectTarget(EffectPath_Ground, n.Target, "origin") Should be changed to: set n.KBEffect = AddSpecialEffectTarget(EffectPath_Ground, n.Target, EffectLocation) You forgot a "debug" here (both places you wrote this): JASS:debug else debug call BJDebugMsg("System Error: Unknown Terrain Type") In fact, where you check this in the Update function can be re-written in a more optimized form: JASS:if (n.FXMode == 1) and (mode == 2) then call DestroyEffect(n.KBEffect) set n.KBEffect = AddSpecialEffectTarget(EffectPath_Ground, n.Target, "origin") elseif n.FXMode == 2) and (mode == 1) then call DestroyEffect(n.KBEffect) set n.KBEffect = AddSpecialEffectTarget(EffectPath_Water, n.Target, "origin") debug elseif n.FXMode == 0 then debug call BJDebugMsg("System Error: Unknown Terrain Type") endif You can remove the InitTrig function, as they are no longer forced by the editor. Any time you have local knocker n = 0, you don't need to initialize "n" at all. Another thing: why are you typecasting to integers in the global array? It would make more sense as a knocker array: private knocker array Knockers The order in which you used SetUnitPosition is wrong; since it takes into account pathing and sorts of things like that, it is conceivable that the unit does not end up where you think it will and it might be on a different type of terrain than the effect you are using. It should be in this order: JASS:set xf = xi + (n.Speed) * Cos(n.Angle*0.01745328) set yf = yi + (n.Speed) * Sin(n.Angle*0.01745328) call SetUnitPosition(n.Target, xf, yf) set n.FXMode = n.checkterrain(n) You don't need a local variable to reference n.Target; your usage of "u" is unnecessary. Instead of using a local rect, r, and creating a new handle every iteration of the loop, you should use a global rect and use SetRect on it: JASS:globals //... private rect R endglobals //... call SetRect(R, xf-DestDestroy_Radius, yf-DestDestroy_Radius, xf+DestDestroy_Radius, yf+DestDestroy_Radius) //... private function KnockbackSystemInit takes nothing returns nothing set Boolexpr = Condition(function KillDests_Check) set R = CreateRect() endfunction Why even bother to deal with degrees at all? Radians are more standard anyway, and you should just tell people that the functions take radians in the ReadMe. This will save you from some calculations Finally (on the subject of angles and cartesian velocity and all that crap), the way you've done it (as opposed to what Strilanc said) makes more sense because you're changing the velocity constantly. That being said, you should store the Cosine and Sine of the angle instead of just the angle in the struct: JASS:private struct knocker unit Target effect KBEffect integer FXMode boolean KillDest real Decrement real Speed real ASin real ACos method checkterrain takes knocker n returns integer local real x = GetUnitX(n.Target) local real y = GetUnitY(n.Target) if IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) then return 1 elseif not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then return 2 endif return 0 endmethod static method create takes unit targ, real angle, real startspeed, real decrement, boolean killDestructables returns knocker local knocker n = knocker.allocate() set n.Target = targ set n.FXMode = n.checkterrain(n) if n.FXMode == 1 then set n.KBEffect = AddSpecialEffectTarget(EffectPath_Ground, n.Target, EffectLocation) elseif n.FXMode == 2 then set n.KBEffect = AddSpecialEffectTarget(EffectPath_Water, n.Target, EffectLocation) debug else debug call BJDebugMsg("System Error: Unknown Terrain Type") endif set n.KillDest = killDestructables set n.Decrement = decrement set n.Speed = startspeed set n.ASin = Sin(angle) set n.ACos = Cos(angle) return n endmethod private method onDestroy takes nothing returns nothing call DestroyEffect(this.KBEffect) endmethod endstruct //... set xf = xi + (n.Speed) * n.ACos set yf = yi + (n.Speed) * n.ASin Aside from that... |
| 03-31-2008, 12:14 AM | #7 | |||||||
Now, that's impressive. Consider it all done. (Some things I disagree with, but the majority is good stuff) Quote:
Quote:
Quote:
Quote:
Quote:
Quote:
Quote:
EDIT: And you don't have to give me all of the examples. You should've saved yourself all of that time, I can figure stuff out strictly by words alone. :p |
| 03-31-2008, 12:19 AM | #8 |
Yay, a decent knockback system. I would use it if I weren't forcing myself to code my own (using a tutorial) so I can both have a systm AND learn vJass. |
| 03-31-2008, 12:40 AM | #9 | |
Yeah, well I like examples :) Quote:
JASS:function Actions takes nothing returns nothing local unit U = GetTriggerUnit() local unit Target = GetSpellTargetUnit() call KnockbackUnit(Target, Atan2(GetUnitY(Target)-GetUnitY(U), GetUnitX(Target)-GetUnitX(U)), 500.00, 15.00, true) set U = null set Target = null endfunction Out of curiosity, what happens when you initiate a knockback on a unit that is already being knocked back? Does the system bug out at all (I think KnockbackStop would)? |
| 03-31-2008, 12:53 AM | #10 | |||
Quote:
Quote:
Quote:
Updated, by the way. |
| 03-31-2008, 03:00 AM | #11 |
ahHAAA!!! using my technique of calling random creeps to attack your hero!!! How dare you :P One question: why you stop the timer when there's no units to move? to improve performance??? that's not necessary, and you save code. Even better, you can replace (optionally) the timer with a periodic trigger. But, in general, I see no other issue. |
| 03-31-2008, 03:45 AM | #12 | ||||
Quote:
Quote:
Quote:
Quote:
On that note, I shall approve this! One question though: how did you update your post without the "last edited by" message? Special Admin edits? |
| 03-31-2008, 04:02 AM | #13 | |||
Quote:
Quote:
Quote:
|
| 03-31-2008, 08:43 AM | #14 |
Would be neat if you added a little collision causes knockback, i.e. when a unit is sliding back and knocks into another unit, it causes it to get knockbacked aswell. |
| 03-31-2008, 06:17 PM | #15 |
hmm, i wonder what happens when units are pushed over the edge of a pathing type (eg: from floatability to walkability). The Effect shouldnt change, afaics. nvm, just noticed the if statement in the uptdate function. Besides, from reading i would say, that units will move far too fast, youll have to replace (n.Speed) with (n.Speed*TimerInterval) to make it userfriendly, or you put into the readme, that users have to pass (desiredspeed * TimerInterval). Same applies to decrement. |
