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

[GUI] Simple Frictional Knockback System v1.2

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
- This system allows you to apply knockback to units with a decreasing speed (deceleration).


External Instructions
- Open World Editor > File > Preferences... > General Tab > Tick the Automatically create unknown variables while pasting trigger data
- Copy the Simple Frictional Knockback System folder.

Internal Instructions
- There are 6 variables which you can manipulate;
* FK_UnitSet- The unit that is going to be knockbacked
* FK_AngleSet - The angle which the unit will be knockbacked
* FK_FrictionSet - The knockback friction per second
* FK_SpeedSet - The knockback speed per second
* FK_SFX_Set - The Special Effect for the knockback (make sure that the SFX model does not has any animation duration such as; birth, stand, death, etc)
* FK_IsStop - The option for the unit to be able to move during knockbacked


  • FK Setup
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set FK_IntervalPerMove = 0.03
      • Trigger - Add to FK Loop <gen> the event (Time - Every FK_IntervalPerMove seconds of game time)
  • FK Event
    • Events
    • Conditions
    • Actions
      • Set FK_MaxIndex = (FK_MaxIndex + 1)
      • Set FK_Unit[FK_MaxIndex] = FK_UnitSet
      • Set FK_Speed[FK_MaxIndex] = (FK_SpeedSet x FK_IntervalPerMove)
      • Set FK_Friction[FK_MaxIndex] = ((FK_FrictionSet x FK_IntervalPerMove) x FK_IntervalPerMove)
      • Custom script: set udg_FK_AngleX[udg_FK_MaxIndex] = Cos(udg_FK_AngleSet * bj_DEGTORAD)
      • Custom script: set udg_FK_AngleY[udg_FK_MaxIndex] = Sin(udg_FK_AngleSet * bj_DEGTORAD)
      • Set FK_SFX[FK_MaxIndex] = FK_SFX_Set
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • FK_IsStopSet Equal to True
        • Then - Actions
          • Custom script: call SetUnitPropWindow(udg_FK_Unit[udg_FK_MaxIndex], 0)
        • Else - Actions
      • Trigger - Turn on FK Loop <gen>
  • FK Loop
    • Events
    • Conditions
    • Actions
      • For each (Integer FK_CurrentIndex) from 1 to FK_MaxIndex, do (Actions)
        • Loop - Actions
          • Custom script: if (RectContainsUnit(GetPlayableMapRect(), udg_FK_Unit[udg_FK_CurrentIndex])) and (udg_FK_Speed[udg_FK_CurrentIndex] > 0) then
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Random integer number between 1 and 3) Equal to 1
            • Then - Actions
              • Custom script: call DestroyEffect(AddSpecialEffect(udg_FK_SFX[udg_FK_CurrentIndex], GetUnitX(udg_FK_Unit[udg_FK_CurrentIndex]), GetUnitY(udg_FK_Unit[udg_FK_CurrentIndex])))
            • Else - Actions
          • Custom script: call SetUnitX(udg_FK_Unit[udg_FK_CurrentIndex], GetUnitX(udg_FK_Unit[udg_FK_CurrentIndex]) + udg_FK_Speed[udg_FK_CurrentIndex] * udg_FK_AngleX[udg_FK_CurrentIndex])
          • Custom script: call SetUnitY(udg_FK_Unit[udg_FK_CurrentIndex], GetUnitY(udg_FK_Unit[udg_FK_CurrentIndex]) + udg_FK_Speed[udg_FK_CurrentIndex] * udg_FK_AngleY[udg_FK_CurrentIndex])
          • Set FK_Speed[FK_CurrentIndex] = (FK_Speed[FK_CurrentIndex] - FK_Friction[FK_CurrentIndex])
          • Custom script: else
          • Set FK_Speed[FK_CurrentIndex] = 0.00
          • Custom script: call SetUnitPropWindow(udg_FK_Unit[udg_FK_CurrentIndex], GetUnitDefaultPropWindow(udg_FK_Unit[udg_FK_CurrentIndex]))
          • Set FK_AngleX[FK_CurrentIndex] = FK_AngleX[FK_MaxIndex]
          • Set FK_AngleY[FK_CurrentIndex] = FK_AngleY[FK_MaxIndex]
          • Set FK_Friction[FK_CurrentIndex] = FK_Friction[FK_MaxIndex]
          • Set FK_SFX[FK_CurrentIndex] = FK_SFX[FK_MaxIndex]
          • Set FK_Speed[FK_CurrentIndex] = FK_Speed[FK_MaxIndex]
          • Set FK_Unit[FK_CurrentIndex] = FK_Unit[FK_MaxIndex]
          • Set FK_MaxIndex = (FK_MaxIndex - 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • FK_MaxIndex Equal to 0
            • Then - Actions
              • Trigger - Turn off (This trigger)
            • Else - Actions
          • Custom script: endif
- Leonardo Da Vinci ?
- Tank-Commander - Read v1.1 changelogs
- Magtheridon - Feedbacks


v1.0
- Initial release

v1.1
- Friction calculation has been shortened
- Dead units still gets knockbacked (realism)
- Optimized some variable into caching them

v1.11
- Fixed some importing method

v1.2
- Change from using Hashtable to Dynamic Indexing
- Added 1 manipulative variable: FK_IsStop
- Movement should be slightly more efficient since uses JASS function rather than BJs (GUI)
- Cached some function so it will not keep recalling the same value
- Added chance for SFX to occur (so it won't occur for 32 times, causing potential frame drops)
- Removed unnecessary local variables
- Added comments on the script for better documentations
- Added OutOfWorldBound check


Keywords:
simple, frictional, knockback, system, defskull, hashtable, force, physics, acceleration, deceleration, slowing, down, speed, dynamic, indexing, stop,
Contents

Just another Warcraft III map (Map)

Reviews
22:31, 22nd Aug 2012 Magtheridon96: Since you're creating 1 special effect 32x times a second, you may end up causing lag. In order to reduce the amount of lag, you can create special effects half the time, or maybe even a third of the time...

Moderator

M

Moderator

22:31, 22nd Aug 2012
Magtheridon96:

  • Since you're creating 1 special effect 32x times a second, you may end up causing lag. In order to reduce the amount of lag, you can create special effects half the time, or maybe even a third of the time.
    Look:
    • if randomInteger(0, 2) == 0 then
      • // create special effect
    • endif
  • You don't need to have the local reals x and y since you're only referencing them once. If you're referencing locals only once, then you don't need them.
  • You can cache the Cos/Sin values into the hashtable so you don't have to avoid retrieving the constant value in the loop during every single iteration. It's more efficient this way because Cos/Sin are heavy function calls despite their short name. (Optional)
 

  • Set FK_KB_FrictionPerInterval = (FK_KnockbackFriction x FK_IntervalPerMove)
  • Set FK_KB_FrictionPerInterval = (FK_KB_FrictionPerInterval x FK_IntervalPerMove)
Put it into one line, no point doing it twice.

  • Unit - Order FK_KnockbackUnit to Stop
Why?

  • FK Key
    • Events
    • Conditions
    • Actions
      • Custom script: set udg_FK_Key = GetHandleId(udg_FK_KnockbackUnit)
Is this necessary? I mean you already do that here in FK Save:

  • Custom script: set udg_FK_Key = GetHandleId(udg_FK_KnockbackUnit)
  • Custom script: local real x
Wether these were locals or not wouldn't make any difference to the system and I get the feeling that for good practice they should be created outside of the group pick, since you're just making lots of locals when you only need one of each (x and y)

  • (FK_KnockbackUnit is alive) Equal to True
For realism, a dead unit wouldn't stop moving just because it died.

  • Custom script: call SetUnitX(udg_FK_KnockbackUnit, x + udg_FK_KnockbackSpeedPerInterval * Cos(LoadReal(udg_FK_Hashtable, udg_FK_Key, 3) * bj_DEGTORAD))
  • Custom script: call SetUnitY(udg_FK_KnockbackUnit, y + udg_FK_KnockbackSpeedPerInterval * Sin(LoadReal(udg_FK_Hashtable, udg_FK_Key, 3) * bj_DEGTORAD))
Load "LoadReal(udg_FK_Hashtable, udg_FK_Key, 3)" into another variable before doing anything with it. you call it twice so it's less efficient to load the same value from a hashtable twice.

All I've got for now
 
Level 33
Joined
Mar 27, 2008
Messages
8,035
Radamantus
I dont get it
Why there are two triggers but only have one action?
Which ?
.OmG.
Screenshot is ugly... Convert to JPG format and not PNG
The screenshot is in JNGP.
Tank-Commander
  • Set FK_KB_FrictionPerInterval = (FK_KnockbackFriction x FK_IntervalPerMove)
  • Set FK_KB_FrictionPerInterval = (FK_KB_FrictionPerInterval x FK_IntervalPerMove)
Put it into one line, no point doing it twice.
You mean like this;
  • Set FK_KB_FrictionPerInterval = ((FK_KnockbackFriction x FK_IntervalPerMove) x FK_IntervalPerMove)
  • Unit - Order FK_KnockbackUnit to Stop
Why?
Using SetUnitX/Y does not interrupt orders, therefore units will still be able to move while being knockbacked.
If I order those units to Stop, they won't be able to be ordered around (Move, Attack, etc)
Also, when ordering unit to Stop + SetUnitX/Y, it is by default will stop the units from getting out of the map boundaries (saves check line code)

  • FK Key
  • Events
  • Conditions
  • Actions
  • Custom script: set udg_FK_Key = GetHandleId(udg_FK_KnockbackUnit)
Is this necessary? I mean you already do that here in FK Save:
It is because applying this system to multiples units at once (Unit Group) has different approach.
It's a bit hard to understand, I'll try to change the instructions layout in the next version, making it more easier.

  • Custom script: local real x
Wether these were locals or not wouldn't make any difference to the system and I get the feeling that for good practice they should be created outside of the group pick, since you're just making lots of locals when you only need one of each (x and y)
If you're dealing with IF/THEN/ELSE block + local variables + unit group, you would know where to put that function.

The settings of local variables can only be done inside Unit Group action, if you don't believe me, try it.

  • (FK_KnockbackUnit is alive) Equal to True
For realism, a dead unit wouldn't stop moving just because it died.
Yeah I was thinking hard whether this check should be implemented or not, but since you're thinking that "units just don't stop do they ?", I'll remove that check.

Load "LoadReal(udg_FK_Hashtable, udg_FK_Key, 3)" into another variable before doing anything with it. you call it twice so it's less efficient to load the same value from a hashtable twice.
I've learn from Magtheridon, he said that if you only use the variable once per trigger run, it would be........
Ahhh, I see it now, I called it twice, for X and Y, ahhh thanks :)
 
Level 33
Joined
Mar 27, 2008
Messages
8,035
Ah, you see, one is to acknowledge which unit should the data be saved to and hashtable is for saving angle per unit pick.

This "weird" settings only applies to units picked via Unit Group (FK_TempGroup).

As I mentioned earlier, I will change the layout to be more easier to understand in the next version.
 
Level 33
Joined
Mar 27, 2008
Messages
8,035
Top of the actions where exactly baassee ?
Did you tried it yet ?
I tried, but it compiles error, I have talked with WaterKnight with this issues, locals must be declared inside the Unit Group action.

If you already tried it and succeed, please do share the code.
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
Ah that's true, cause of engine I guess - cannot allocate the local within an if. ops forgot, enumeration creates a whole new function thus the locals have to be declared within that enum func ^^

anyway, these two parts should be stored into the hashtable as well as they are always the same

Cos(udg_FK_KnockbackAngle * bj_DEGTORAD)
Sin(udg_FK_KnockbackAngle * bj_DEGTORAD)

and you can actually get rid of the locals just put

GetUnitY(udg_FK_KnockbackUnit) + udg_FK_KnockbackSpeedPerInterval * Sin(udg_FK_KnockbackAngle * bj_DEGTORAD)

into

call SetUnitX(udg_FK_KnockbackUnit, x)
 
Well, I would like to say on the grounds of using stop, other than stopping them from going out of bounds, I was mostly referring to the doctrine as to why you were disabling their functions, since interrupting orders isn't the most useful of things to do and stun/slow/etc. in the actual WC3 does not prevent orders, and knockback isn't really a "disable" thing. I'd suggest just putting the checking for map bounds in so that their orders aren't cancelled, guess it's preferential, but I'd still have a boolean in the "system" to allow you to enable/disable the stop being run and cancelling orders.
 
Level 14
Joined
Aug 8, 2010
Messages
1,022
Just one suggestion - create a configurable variable that sets where the special effect is spawned. Some people might want it to be on the head, no the origin which makes absolutely no sense but it will be good if you put this... just in case.

The system is great, nice job, defskull! :)
 
Level 33
Joined
Mar 27, 2008
Messages
8,035
baassee
anyway, these two parts should be stored into the hashtable as well as they are always the same

Cos(udg_FK_KnockbackAngle * bj_DEGTORAD)
Sin(udg_FK_KnockbackAngle * bj_DEGTORAD)
You mean to store those whole calculation or just FK_KnockbackAngle ?
If you're asking me to only save FK_KnockbackAngle, I already did that in v1.1.
But if you're asking me to save those whole calculation, I do not do it yet.
Ah yeah, they are constants right.

and you can actually get rid of the locals just put

GetUnitY(udg_FK_KnockbackUnit) + udg_FK_KnockbackSpeedPerInterval * Sin(udg_FK_KnockbackAngle * bj_DEGTORAD)
I thought that repeating the same function without caching it reduces performance ?
That's why I used locals, x and y to cache the function, instead of repeating the GetUnitX/Y each time.
Tank-Commander
I was thinking this too, to add boolean whether the knockback should or should not "disable" the units.
Well I guess you have convinced me.
Because some people wants those knockbacked units to still be able to do anything, while at the same time, the knockback occurs to the unit.
CoLd Bon3
Just one suggestion - create a configurable variable that sets where the special effect is spawned. Some people might want it to be on the head, no the origin which makes absolutely no sense but it will be good if you put this... just in case.

The system is great, nice job, defskull! :)
Hah, I thought this too, but the more I think about it, the more crazy it becomes because hey, knockbacked and friction is related to ground, that's why the SFX will be spawned at the feet of the unit, but I'll add this small configuration anyway, it's a small add btw, thanks for the convincing :)
 
Level 14
Joined
Aug 8, 2010
Messages
1,022
CoLd Bon3

Hah, I thought this too, but the more I think about it, the more crazy it becomes because hey, knockbacked and friction is related to ground, that's why the SFX will be spawned at the feet of the unit, but I'll add this small configuration anyway, it's a small add btw, thanks for the convincing :)
Well, someone may create a spell that shoots a knockbacking arrow and he may want to get an effect on the chest for example. :)
 
Level 33
Joined
Mar 27, 2008
Messages
8,035
Don't order the unit to stop.
Since it uses SetUnitX/Y, if I don't order the unit to stop, what if the Player order Move to that unit ?
You are 'knockbacked' but you can move, weird much ?

Don't create a special effect after every iteration, check if a random number between 1 and something is 1, and then create the effect. This 'something' would be "Effect Frequency Denominator". The less this factor, the more the effects. Something like 3-5 would be ideal as a default value.
Do you mean the SFX should spawn at random ?
Not each interval ?
But the random has a high chance to occur, is that it ?

You don't need to have the local reals x and y since you're only referencing them once.
Noted.

You can cache the Cos/Sin values into the hashtable so you don't have to avoid retrieving the constant value in the loop during every single iteration.
Alright sir.
 
Since it uses SetUnitX/Y, if I don't order the unit to stop, what if the Player order Move to that unit ?
You are 'knockbacked' but you can move, weird much ?

Sometimes, you want the knockback to be something like a force interfering with a unit (the way it is in Warlock). So I just thought that a user may not want you to be ordering the unit to stop ;o

Do you mean the SFX should spawn at random ?
Not each interval ?
But the random has a high chance to occur, is that it ?

Yeah, so, instead of having it spawn /every/ interval, you would have it spawn on some intervals.

Pseudo code:
FrequencyFactor = 5 // The greater this is, the less the effects will appear.
if GetRandomInt(0, FrequencyFactor) == 0 then
createEffect()
endif

edit

Okay,

When to Cache a Value

GUI

Any time you repeat a call more than once, cache it. It's very simple.
If you're only going to reference the variable once, caching it will only make your code a bit slower.

JASS

If you're repeating a call that returns:
  • A Scalar-type that doesn't need to be nulled:
    • Once, don't cache it.
    • Twice or more, cache it.
  • A Handle-type that needs to be nulled:
    • Once, don't cache it.
    • Twice, don't cache it.
    • Thrice or more, cache it.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Hey defskull, nice to see you updating this. My observations tell me that this needs work to be acceptable, and this system also needs a niche. I'll elaborate:

1) Pathing is not considered except to keep the unit in the overall map. This is only good for knocking back units over completely flat and walkable terrain.
2) If the goal is to have a completely bare knockback, the special effect doesn't serve a purpose and neither should the prop window be used.
3) If you did enhance this to use pathing on non-flat maps, you basically end up with Knockback 2.5D.
 
Level 33
Joined
Mar 27, 2008
Messages
8,035
The dead-moving units are already fixed in the previous version(s) lol.
Also, this one has decelerating knockback which Bribe's 2.5D doesn't support.

Oh well, I guess my system is more simpler to be used for simple map I suppose.
 
Top