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

How to turn my custom passive with cooldown skill into MUI

Status
Not open for further replies.
Level 19
Joined
Oct 7, 2014
Messages
2,209
Hello so I have this spell which is a Racial Skill called Gift of Naaru - It is a passive skill that has a 30% chance to activate when a unit is attacked, then the unit is healed based on the amount of damage taken. It have a 20 second cooldown.

For this spell I used Bribe's DDS to compute the damage taken after considering damage deductions and Flux's Passive Cooldown System.

I tried to run some test and it feels weird. I feel like I didn't make a functioning trigger. I am using this spell for my altered melee and a lot of units will be using this passive skill
Then I recently saw Bribe's GUI Spell System, I am trying to use and understand it but my problem is I am having a hard time trying to transform my spell to use his system so that it runs smoothly and right. Also, I am not sure if it checks if the damage comes from a spell or not(It should only activate when damage is from a 'normal' attack) and I don't know how to check if a unit has the passive skill on cooldown.


  • Gift of Naaru
    • Events
      • Game - DamageModifierEvent becomes Equal to 4.00
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • IsDamageSpell Equal to False
          • (Level of Gift of Naaru (Faction Skill) for DamageEventTarget) Greater than or equal to 1
          • (Random integer number between 1 and 10) Less than 3
        • Then - Actions
          • Set PCD_Unit = DamageEventTarget
          • Set PCD_Ability = Gift of Naaru (Faction Skill)
          • Set PCD_DummyAbility = Gift of Naaru (PCD System)
          • Set PCD_Time = 20.00
          • Set PCD_Manacost = 0.00
          • Set DamageEventAmount = (0.00 - DamageEventAmount)
          • Set DamageEventType = DamageTypeHeal
          • Special Effect - Create a special effect attached to the overhead of DamageEventTarget using HolyAwakening.mdx
          • Special Effect - Destroy (Last created special effect)
          • Trigger - Run Passive Cooldown System <gen> (checking conditions)
        • Else - Actions
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,557
It looks like you're on an older version of wc3. In 1.31+ you can get the Remaining cooldown of an ability as a Real Comparison.

Anyway, there's many ways of making this MUI besides checking the cooldown.

1) Add the unit to a Unit Group upon triggering the passive, then remove it from the group 20 seconds later. Additionally, add a new Condition ->
"Is unit in Unit Group Equal to False". This way the the passive can only trigger if the unit isn't in the group.

2) Add a hidden ability to the unit for 20 seconds. Then add a condition that checks if the "Level of ability Equal to 0".

3) Use a Hashtable or Unit Indexer and link a Boolean to the unit. Set this Boolean to True when the passive triggers and False 20 seconds later. Then in your Conditions check the state of this Boolean, if it's False then the passive can trigger again.

The easiest method for handling the 20 second duration is to use this technique: local udg_
  • Custom script: local unit udg_TempUnit = udg_DamageEventTarget
  • Unit - Add Cooldown Ability to TempUnit
  • Wait 20.00 seconds
  • Unit - Remove Cooldown Ability from TempUnit
  • Custom script: set udg_TempUnit = null
^ I'm using #2 in this example. Keep in mind that I've left out some of the necessary Actions from your original trigger. Note that Local Variables need to be declared before any other Actions.

The entire trigger would look something like this:
  • Events:
  • DamageModifierEvent becomes Equal to 4.00
  • Conditions:
  • (Level of Cooldown Ability for DamageEventTarget) Equal to 0
  • Actions:
  • Custom script: local unit udg_TempUnit = udg_DamageEventTarget
  • If Then Else (Put your original Actions here as well as the Add ability/Wait/Remove ability stuff...)
  • Custom script: set udg_TempUnit = null

If you don't like using Waits then you can use a Periodic Interval (a Loop trigger), a Unit Group (same method as #1), and your choice of Unit Indexing/Hashtables to keep track of the Duration of the effect. Something like:
Every 0.05 seconds -> Pick every unit in Cooldown Group -> Set picked unit's Duration = picked unit's Duration - 0.05 -> If Duration <= 0.00 then Remove picked unit from Cooldown Group. Remember to set the unit's Duration back to 20.00 seconds whenever the ability goes on cooldown.
 
Last edited:
Level 19
Joined
Oct 7, 2014
Messages
2,209
I fail to mention that I'm using v.27 version also I have a Unit Indexer in my map. I'll try to use 3 so I can learn haha I'm trying to figure out how to utilize it.

Should I add this first after the DamageModifierEvent fires?

  • Set UDexUnits[UDex] = DamageEventTarget
Is this right? How to I reference the boolean to the unit? I'm sorry if I'm making this complicated.

This also the Unit Indexer I'm using as reference

  • Unit Indexer
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Custom script: call ExecuteFunc("InitializeUnitIndexer")
      • Custom script: endfunction
      • Custom script:
      • Custom script: function ClearUnitIndex takes nothing returns nothing
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Custom value of UDexUnits[UDex]) Equal to 0
        • Then - Actions
          • Set UnitIndexLock[UDex] = (UnitIndexLock[UDex] - 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • UnitIndexLock[UDex] Equal to 0
            • Then - Actions
              • Set UDexNext[UDexPrev[UDex]] = UDexNext[UDex]
              • Set UDexPrev[UDexNext[UDex]] = UDexPrev[UDex]
              • Set UDexPrev[UDex] = 0
              • Set UnitIndexEvent = 0.00
              • Set UnitIndexEvent = 2.00
              • Set UnitIndexEvent = 0.00
              • Set UDexUnits[UDex] = No unit
              • Set UDexNext[UDex] = UDexRecycle
              • Set UDexRecycle = UDex
            • Else - Actions
        • Else - Actions
      • Custom script: endfunction
      • Custom script:
      • Custom script: function IndexUnit takes nothing returns boolean
      • Custom script: local integer pdex = udg_UDex
      • Custom script: local integer ndex
      • -------- - --------
      • -------- You can customize the following block - if conditions are false the (Matching unit) won't be indexed. --------
      • -------- - --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • UnitIndexerEnabled Equal to True
          • (Custom value of (Matching unit)) Equal to 0
        • Then - Actions
          • Set UDexWasted = (UDexWasted + 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • UDexWasted Equal to 32
            • Then - Actions
              • Set UDexWasted = 0
              • Set UDex = UDexNext[0]
              • Custom script: loop
              • Custom script: exitwhen udg_UDex == 0
              • Custom script: set ndex = udg_UDexNext[udg_UDex]
              • Custom script: call ClearUnitIndex()
              • Custom script: set udg_UDex = ndex
              • Custom script: endloop
            • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • UDexRecycle Equal to 0
            • Then - Actions
              • Set UDex = (UDexGen + 1)
              • Set UDexGen = UDex
            • Else - Actions
              • Set UDex = UDexRecycle
              • Set UDexRecycle = UDexNext[UDex]
          • Set UDexUnits[UDex] = (Matching unit)
          • Unit - Set the custom value of UDexUnits[UDex] to UDex
          • Set UDexPrev[UDexNext[0]] = UDex
          • Set UDexNext[UDex] = UDexNext[0]
          • Set UDexNext[0] = UDex
          • Set UnitIndexLock[UDex] = 1
          • Set UnitIndexEvent = 0.00
          • Set UnitIndexEvent = 1.00
          • Set UnitIndexEvent = 0.00
          • Custom script: set udg_UDex = pdex
        • Else - Actions
      • Custom script: return false
      • Custom script: endfunction
      • Custom script:
      • Custom script: function InitializeUnitIndexer takes nothing returns nothing
      • Custom script: local integer i = bj_MAX_PLAYER_SLOTS
      • Custom script: local boolexpr b = Filter(function IndexUnit)
      • Custom script: local region re = CreateRegion()
      • Custom script: local trigger t = GetTriggeringTrigger()
      • Custom script: local rect r = GetWorldBounds()
      • Custom script: call RegionAddRect(re, r)
      • Custom script: call TriggerRegisterEnterRegion(t, re, b)
      • Custom script: call TriggerClearActions(t)
      • Custom script: call TriggerAddAction(t, function ClearUnitIndex)
      • Set UnitIndexerEnabled = True
      • Custom script: loop
      • Custom script: set i = i - 1
      • Custom script: call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, Player(i), b)
      • Custom script: exitwhen i == 0
      • Custom script: endloop
      • Custom script: call RemoveRect(r)
      • Custom script: set re = null
      • Custom script: set r = null
      • Custom script: set t = null
      • Custom script: set b = null
      • Set UnitIndexEvent = 3.00
      • Set UnitIndexEvent = 0.00
  • [\Trigger]
  • [\Hidden]
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,557
Using my previous trigger as a reference, first create a new Boolean Array variable. Let's call it GiftOfNaruu_OnCd.

Then in your DamageEvent trigger check to see if this Boolean is False in the Conditions block:
  • Conditions:
  • GiftOfNaruu_OnCd[custom value of DamageEventTarget] Equal to False

Then in your If Then Else statement's Actions you set this Boolean to True since the ability is now on cooldown:
  • Set GiftOfNaruu_OnCd[custom value of DamageEventTarget] = True

Finally, after the 20.00 second Wait you Set this Boolean back to False:
  • Wait 20.00 seconds
  • Set GiftOfNaruu_OnCd[custom value of TempUnit] = False
Notice how I'm using TempUnit instead of DamageEventTarget when I set the Boolean back to False. That's because TempUnit has been turned into a local variable at the start of the trigger. This is done using the Shadowing technique (See that link I posted).

Local variables are different from global variables in that their information will remain the same throughout the entire trigger. This is why they're so useful for making MUI triggers as opposed to global variables which can have their data overwritten. So whenever you Set a local variable what you're really doing is creating an entirely new variable that is used exclusively for that instance of the trigger. Once that trigger has completed all of it's actions the local variables that it used will never be used again and can be discarded. That's why you "null" the local unit variable TempUnit, because otherwise it would sit in the game's memory serving no purpose. Keep in mind that you don't have to null Integers, Reals, Booleans, and Strings, as these variable types won't leak (you'll get an error if you try to null them).

IMPORTANT:
I should note that this solution isn't perfect. There's two issues that come to mind:
1) If the DamageEventTarget is removed from the game then it's custom value will no longer accessible and the Boolean will never reset to False.
2) If the Unit Indexer recycles the custom value of the DamageEventTarget and gives it to a newly created unit then this new unit will have GiftOfNaruu_OnCd set to True forever.

Perhaps it would be better to use the Ability method that I described before. That method is pretty much bulletproof and won't suffer from these problems.
 
Last edited:
Level 19
Joined
Oct 7, 2014
Messages
2,209
1) If the DamageEventTarget is removed from the game then it's custom value will no longer accessible and the Boolean will never reset to False.
As far as I know DamageEventTarget value is stored in the DDS. I'm not sure though, I still don't fully understand the Damage Detection System.

2) If the Unit Indexer recycles the custom value of the DamageEventTarget and gives it to a newly created unit then this new unit will have GiftOfNaruu_OnCd set to True forever.
I am still noobish in understanding triggers etc., so I'm not really sure. This might pose problems. I'll change to your 2nd method if I really can't understand the systems and/or can't find a way to optimize these issues.

This is the final trigger for now

  • Gift of Naaru
    • Events
      • Game - DamageModifierEvent becomes Equal to 4.00
    • Conditions
      • GiftofNaaruOnCD[(Custom value of DamageEventTarget)] Equal to False
    • Actions
      • Custom script: local unit udg_TempUnit = DamageEventTarget
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • IsDamageSpell Equal to False
          • (Level of Gift of Naaru (Faction Skill) for DamageEventTarget) Greater than or equal to 1
          • (Random integer number between 1 and 10) Less than 3
        • Then - Actions
          • Set GiftofNaaruOnCD[(Custom value of DamageEventTarget)] = True
          • Set PCD_Unit = DamageEventTarget
          • Set PCD_Ability = Gift of Naaru (Faction Skill)
          • Set PCD_DummyAbility = Gift of Naaru (PCD System)
          • Set PCD_Time = 20.00
          • Set PCD_Manacost = 0.00
          • Set DamageEventAmount = (0.00 - DamageEventAmount)
          • Set DamageEventType = DamageTypeHeal
          • Special Effect - Create a special effect attached to the overhead of DamageEventTarget using HolyAwakening.mdx
          • Special Effect - Destroy (Last created special effect)
          • Trigger - Run Passive Cooldown System <gen> (checking conditions)
          • Wait 20.00 seconds
          • Set GiftofNaaruOnCD[(Custom value of TempUnit)] = False
        • Else - Actions
      • Custom script: set udg_TempUnit = null
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,557
That's not exactly what I was saying about #1. The problem is that the trigger references the Custom value of TempUnit after 20.00 seconds, but if TempUnit is removed from the game then it won't be able to do so.

And #2 is pretty much guaranteed to happen at some point.

I probably should've told you to use the Add/Remove Ability method since it doesn't suffer from these issues but I wanted to show how the Unit Indexing method was done.

The Unit Indexing system is actually very simple and easy to understand. Whenever a unit is created, the system increases an Integer variable by 1. This variable is then assigned to the unit using the unit's "custom value" field. Custom value is an extra stat that Blizzard provided that can be used for anything (hence the name "custom" value). It's up to us to decide what it's used for.

In this case the Unit Indexing system turns custom value into a much more useful stat by making the custom value act like a unique ID for each unit. This ID can then be plugged into the [Index] of a Variable Array, allowing us to create as many "custom stats" as we'd like for our units. In other words, it allows us to save near endless amounts of data to units all while being MUI.

So why does the Unit Indexing system have issues and why does it need to recycle unused custom values?

A Variable Array has an Index limit of 32,768 (1/4 this amount in older versions). This means that you can't put a number larger than 32,768 inside one.
So GiftofNaruuOnCD[999999] wouldn't work, it's too large of an Index. What the Unit Indexing system does to combat this is recycle unused custom values. So when a unit dies, it's custom value becomes marked as unused, meaning that the system can then assign that custom value again. Because of this the Unit Indexing system can support up to 32,768 units at a given time, since when one unit is killed/removed it's custom value becomes free and a new unit can replace it.

If the system didn't recycle the custom values then the size of the unit IDs would continue to grow and grow as the game went on. This may not be an issue depending on how your map works, but take a map like DotA that spawns waves of units every 30 seconds. Eventually, the Integer variable that gets increased by 1 (the ID) would surpass 32,768 and the Arrays would stop working. Recycling prevents this from happening.

The unfortunate side effect of recycling is that the issue I described in my previous post can happen. Say our DamageEventTarget has a custom value of 100 and GiftofNaruuOnCD[100] is Set to True. Then say that unit gets removed before the 20.00 second Wait is finished. The trigger will fail to Set GiftofNaruuOnCD[100] back to False because the unit has been removed. We cannot access the custom value of a unit that doesn't exist. Then say a new unit is created and given the custom value 100. This new unit now claims the GiftofNaruuOnCd[100] variable including whatever value it had been previously set to, which is True in this case. This results in that new unit being unable to ever use Gift of Naruu.
 
Last edited:
Level 19
Joined
Oct 7, 2014
Messages
2,209
I am making an altered melee map so I don't think 32,768 units will be active at the same time haha
Well thank you for helping me out and explaining lots of things to me. I may not fully understand all of it quickly but I will try to do my best.
:goblin_yeah:
 
From a quick gaze, the problem is registering the unit into a MUI like state. I'm confused though, why it does not do it as it seems to like it at a glance.

Why do you use DamageModifierEvent?

Is Bribe DDS you map's starting DDS or did you switch midway?

I think I need to reread the thread. It feels like something is being over complicated than is needed to be.

***

I was thinking of this approach instead, based on spell description:
DamageEvent 1.00 or 4.00 (or whatever that be in this particular patch) > cancels damage > force cool down at the same trigger for that unit.

I don't see any need for the whole boolean shenanigans. Something must've been wrong to have this spell not MUI given Flux system should handle most of the MUI problems easily, and Bribe system is distinct enough with it's unit identification.
 
Last edited:
Level 19
Joined
Oct 7, 2014
Messages
2,209
From a quick gaze, the problem is registering the unit into a MUI like state. I'm confused though, why it does not do it as it seems to like it at a glance.

Why do you use DamageModifierEvent?

Is Bribe DDS you map's starting DDS or did you switch midway?

I think I need to reread the thread. It feels like something is being over complicated than is needed to be.

***

I was thinking of this approach instead, based on spell description:
DamageEvent 1.00 or 4.00 (or whatever that be in this particular patch) > cancels damage > force cool down at the same trigger for that unit.

I don't see any need for the whole boolean shenanigans. Something must've been wrong to have this spell not MUI given Flux system should handle most of the MUI problems easily, and Bribe system is distinct enough with it's unit identification.
Because I made that's why it looks like it doesn't seem to do it's work.

I used DamageModifierEvent = 4.00 so the damage that will be registered would be the final damage after armor deductions etc. Because if I use true damage it might be too OP as a racial passive skill.

I only have Bribe DDS in this.

Maybe I overanxious about the spell that it is already functioning enough? It might be because I am having a difficulty in fully understanding the systems I am trying to implement. Also I haven't tried testing a lot tbh.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,557
I think I misread the original post o_O

After looking over it again it seems like everything should work perfectly fine. The only possible issue I can see is that the ability will trigger even while on cooldown. That and/or maybe the Event is wrong, I never used the older version of Bribe's DDS so I just assumed that he already tested this and it was correct.

In the case of the ability triggering while on cooldown this Condition should fix it:
  • (Level of Gift of Naaru (PCD System) for DamageEventTarget) Equal to 0
 
Level 19
Joined
Oct 7, 2014
Messages
2,209
I think I misread the original post o_O

After looking over it again it seems like everything should work perfectly fine. The only possible issue I can see is that the ability will trigger even while on cooldown. That and/or maybe the Event is wrong, I never used the older version of Bribe's DDS so I just assumed that he already tested this and it was correct.

In the case of the ability triggering while on cooldown this Condition should fix it:
  • (Level of Gift of Naaru (PCD System) for DamageEventTarget) Equal to 0

Fortunately it works it does not fire while it's on cooldown but I'll take not of that.

Do you even code the heal correctly? Is the ability meant to cancel the damage and heal, or just heal? I mean, if it's the latter, and the heal is same as taken damage, of course the heal won't feel like it's there.

With 100% chance no damage should be taken, theorically.

Yeahh that's why it feels odd. I just tweaked it a little bit and just multiply it into a constant. So it would be useful that it is now. I also change the event to DamageModifierEvent becomes Equal to 1 instead of the previous one with the same reason why I add a multiplier to the heal.
 
Status
Not open for further replies.
Top