Command Parry

Command Parry

A GUI/JASS spell


Description

Attempts to counter an enemy's attack while its animation is under way. If the counter is successful, exchange positions with the enemy, then damage and stun it. Each Command Parry can only affect one attacker.

Explanation

In some combat games, a "command parry" is triggered by pressing a certain combination of buttons when an enemy is in its attack animation and damage hasn't been inflicted yet. If the parry is successful, the parrier takes no damage and the attacker suffers various disadvantages.
Hence, I've made a spell that simulates this mechanism. In the test map, the "Berserk" spell is a Command Parry. Parrying successfully can also cause the Blademaster to play a special animation if required.
Configurable parameters are (on both a global and spell-to-spell basis):
  • Duration in which the parry is considered valid
  • Duration in which the attack is considered parryable
  • Max distance allowed between the parrier and the attacker
  • Damage caused to the attacker if successful
  • Stun caused to the attacker if successful
  • Special Effect
  • Enable/Disable the parrier backstab the attacker
  • Enable/Disable special animation
  • Name of the special animation you want to play
  • Duration of the special animation

How to use

This spell requires no other system to be installed.
  1. Copy the Berserk ('had4') spell from the Test map into your map.
  2. Copy the Dummy ('ndum') unit from the Test map into your map.
  3. First copy the Hash_Parry variable, then copy the Command Parry folder from the Test map's Trigger folder into your map. Variables are automatically created if you have the "automatically create unknown variables" option enabled.
  4. If you're a GUI user, tinker with the global variables in the "Parry cast event" and "Parry attacker cfg" triggers. Leave all the scripts as they are.
  5. As of ver 1.02: GUI users can specify their own effects if a parry is successful. You need to set Parry__EffectStun to 0, then create your own actions in the "Parry user defined effect" trigger. Variables that you can use are Parry__Caster (the parrier unit), Parry__Attacker (the attacking unit), Parry__CastPoint (position of the parrier) and Parry__AttackPoint (position of the attacker). They're automatically cleaned up so you don't have to remove those variable leaks in your user-defined action block.

Changelog

  • 1.00
    • Uploaded.
  • 1.02
    • GUI users can specify their own effects from a separate trigger.
  • 1.04
    • Minor changes for more efficient handling of dynamic timers.
  • 1.05
    • Improved modularity with user-defined triggers for GUI users: Each unique parry can be set to use a unique user-defined trigger.
    • Animation now uses the BlzPauseUnitEx() native, and no longer requires a periodic timer to be run. The Parry__AnimInterval is therefore redundant and no longer used.
    • To use this version, simply delete the old triggers and replace them with the triggers of this version.
  • 1.06
    • Attack Type and Damage Type of the parry damage can now be configured.
  • 1.07
    • Minor efficiency changes.
Previews
Contents

Command Parry (Map)

Reviews
MyPad
The spell works fine, though there is a small activation window between casting the parry ability and the moment the caster is attacked. Parry carefully, and pray to the god-platypus Perry that your ping permits you to do so. Approved
Up until very recently I had no idea how a hashtable worked. But then I read a tutorial about hashtables and learned how they could store very large numbers, including the unique IDs given to handles that distinguish them from each other internally. So I decided to give it a try and came up with this spell because I've always wanted to make a parry that doesn't require a DDS. It has the honor of being my first creation with a hashtable :peasant-grin:
 
I'd say that for your first time using a wc3 hashtable, it is actually quite a decent attempt. The config trigger is nicely documented, local handle variables are nullified properly, dynamic entries are handled appropriately, etc. That said, there are some logical statements that can be simplified:

  • Logical Comparison
    • JASS:
      function ParryAnimTimerEnd takes nothing returns nothing
          // (...)
          if time > time_max or time_max-time<0.01 then
          // (...)
      endfunction

      With a bit of arithmetic manipulation, the equivalent of the statement above becomes:
      if (time_max - time < 0.0) or (time_max - time < 0.01)

      Depending on which expression you choose, you can break it down into a single boolean expression, since these expressions are virtually the same.
  • Storing Ability Handle
    • JASS:
      call BlzSetAbilityRealLevelField( BlzGetUnitAbility(u, 'AHtb'), ConvertAbilityRealLevelField('Htb1'), 0, dmg )
      call BlzSetAbilityRealLevelField( BlzGetUnitAbility(u, 'AHtb'), ConvertAbilityRealLevelField('ahdu'), 0, stunDur )
      call BlzSetAbilityRealLevelField( BlzGetUnitAbility(u, 'AHtb'), ConvertAbilityRealLevelField('adur'), 0, stunDur )

      You can store the result from BlzGetUnitAbility into an ability variable, so that you don't have to call the function thrice to get the same value.
I would also like to point out that the generation of dynamic timer handles for the purpose of running a set of specific callbacks would normally be pointed out as well as the above. However, given that these timer handles do not exist for long anyway, this could be something worth optionally fixing.

I'll test this further for any bugs or potential memory leaks that can be cleaned up (aside from units).
 
Thanks for the feedback! I've uploaded a version which incorporates the suggestions you made.
In it I've also made sure that only one dynamic timer handle will exist for an event at any time, cleaning up the current timer if the event is triggered while it is still running. But even without this cleanup, in standard situations (not making parry timer cooldown lower than parry valid duration, not spamming stop during attack animation), a unit should have only one timer running for each timer type at a time.
 
Last edited:
After a more careful review on the trigger actions, here are some more things to address:
  • The trigger Parry init vars appears to be a config trigger. That said, attempting to modify values within this trigger will not work, since they are overridden by the succeeding triggers.
    • The trigger Parry cast event overrides the following variables:
      • Parry__Range
      • Parry__EffectDamage
      • Parry__EffectStun
      • Parry__EffectName
      • Parry__EnableFaceback
      • Parry__CastLength
      • Parry__EnableAnim
      • Parry__AnimLength
      • Parry__AnimInterval
      • Parry__AnimName
    • The trigger Parry attacker event overrides the following variables:
      • Parry__AttackLength
      • Parry__LoopInterval
    • Since the values are going to be assigned upon map initialization, it would be best to remove the lines which override the above variables within their respective triggers.
  • The trigger Parry cast event can work just as well with (A unit starts the effect of an ability).

  • You might want to caution users regarding the usage of the following variables within the trigger Parry user defined effect:
    • Parry__AttackPoint -> Attacker's location
    • Parry__CastPoint -> Caster's location

      Both of these variables can be read, but not modified nor cleaned up within the trigger's actions, since the script already deals with them.
  • Just to be safe (if abilities do leak), it would be preferable to null the local ability handles at the end of the function ParryEffectAction.

  • Since the coordinates of the source and the target are already accessible, there's no need to use SetUnitPosition to reposition both the source and the target, unless order interruption is desired for both units. You can use SetUnitX and SetUnitY instead.
    • SetUnitPosition usage found in the function ParryEffectAction.
 
After a more careful review on the trigger actions, here are some more things to address:
  • The trigger Parry init vars appears to be a config trigger. That said, attempting to modify values within this trigger will not work, since they are overridden by the succeeding triggers.
    • The trigger Parry cast eventoverrides the following variables:
      • Parry__Range
      • Parry__EffectDamage
      • Parry__EffectStun
      • Parry__EffectName
      • Parry__EnableFaceback
      • Parry__CastLength
      • Parry__EnableAnim
      • Parry__AnimLength
      • Parry__AnimInterval
      • Parry__AnimName
    • The trigger Parry attacker eventoverrides the following variables:
      • Parry__AttackLength
      • Parry__LoopInterval
    • Since the values are going to be assigned upon map initialization, it would be best to remove the lines which override the above variables within their respective triggers.
  • The trigger Parry cast event can work just as well with (A unit starts the effect of an ability).

  • You might want to caution users regarding the usage of the following variables within the trigger Parry user defined effect:
    • Parry__AttackPoint -> Attacker's location
    • Parry__CastPoint -> Caster's location

      Both of these variables can be read, but not modified nor cleaned up within the trigger's actions, since the script already deals with them.
  • Just to be safe (if abilities do leak), it would be preferable to null the local ability handles at the end of the function ParryEffectAction.

  • Since the coordinates of the source and the target are already accessible, there's no need to use SetUnitPosition to reposition both the source and the target, unless order interruption is desired for both units. You can use SetUnitX and SetUnitY instead.
    • SetUnitPosition usage found in the function ParryEffectAction.
The trigger Parry init vars appears to be a config trigger. That said, attempting to modify values within this trigger will not work, since they are overridden by the succeeding triggers.
Done. I've clarified in the comments.
The trigger Parry cast event can work just as well with (A unit starts the effect of an ability).
That is correct. However, for the purpose of demonstration, I used Berserk, which happens to be a spell that only UNIT_SPELL_FINISH event fires consistently with each cast.
You might want to caution users regarding the usage of the following variables within the trigger Parry user defined effect
Done.
Just to be safe (if abilities do leak), it would be preferable to null the local ability handles at the end of the function ParryEffectAction.
Done. Still, I hope abilities don't leak, since they're a new type of handle, yet completely lack "destroy" functions.
Since the coordinates of the source and the target are already accessible, there's no need to use SetUnitPosition to reposition both the source and the target, unless order interruption is desired for both units.
Yes, order interruption is intended here.
 
Level 41
Joined
Feb 27, 2007
Messages
5,241
Is it possible to ''Parry'' not attacks but direct Spells ?
Yes you just need to change the Parry Attacker Event trigger event and all of the event responses that depend on the original event:
  • Events
    • Unit - A unit starts the effect of an ability
  • Conditions
    • (Level of Parry__Ability for (Target unit of ability being cast)) Not equal to 0
    • ((Triggering unit) belongs to an enemy of (Owner of (Target unit of ability being cast)).) Equal to True
  • Actions
    • Custom script: local unit u = GetTriggerUnit()
    • Custom script: call SaveUnitHandle(udg_Hash_Parry,tid,udg_Parry__CKey_Caster,GetSpellTargetUnit())
 
Top