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

[Spell] Multiple Thunder Claps with Interval

Level 6
Joined
May 13, 2023
Messages
44
Hi, I would like an ability which is basically multiple thunder clap from single unit with interval between each thunder claps. Here's the catch the ability is based off of Channel if the caster still casting Channel based ability, the following thunder claps will occur if not they won't. I tried making it see below.
  • Chaotic Tremor
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Chaotic Tremor
    • Actions
      • Set ChaoticTremorCaster = (Triggering unit)
      • Unit - Create 1 Dummy (Nothing) for (Owner of ChaoticTremorCaster) at (Position of ChaoticTremorCaster) facing Default building facing degrees
      • Unit - Add a 6.00 second Generic expiration timer to (Last created unit)
      • Unit - Add Chaotic Tremor (First) to (Last created unit)
      • Unit - Order (Last created unit) to Human Mountain King - Thunder Clap
      • Wait 3.00 seconds
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Ability being cast) Equal to Chaotic Tremor
        • Then - Actions
          • Unit - Create 1 Dummy (Nothing) for (Owner of ChaoticTremorCaster) at (Position of ChaoticTremorCaster) facing Default building facing degrees
          • Unit - Add a 6.00 second Generic expiration timer to (Last created unit)
          • Unit - Add Chaotic Tremor (Second) to (Last created unit)
          • Unit - Order (Last created unit) to Human Mountain King - Thunder Clap
        • Else - Actions
          • Skip remaining actions
      • Wait 3.00 seconds
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Ability being cast) Equal to Chaotic Tremor
        • Then - Actions
          • Unit - Create 1 Dummy (Nothing) for (Owner of ChaoticTremorCaster) at (Position of ChaoticTremorCaster) facing Default building facing degrees
          • Unit - Add a 6.00 second Generic expiration timer to (Last created unit)
          • Unit - Add Chaotic Tremor (Third) to (Last created unit)
          • Unit - Order (Last created unit) to Human Mountain King - Thunder Clap
        • Else - Actions
          • Skip remaining actions
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
(Ability being cast) is a global variable that gets set whenever a Unit casts an ability.

It doesn't make any sense to check this variable 3.00 seconds later since no ability was cast at that time.

You're treating it as if it was an Order, which you could technically check 3.00 seconds later:
  • If - Conditions
    • ((Current order) of ChaoticTremorCaster) Equal to channel
But even that is a bad idea since:
1) Waits are imprecise and open up the door to issues related to unset/changed variables/event responses.
2) We can design this to be Event based where everything starts and stops precisely when it should.

So we will want to use three triggers, one for starting the channeling process, one for stopping the channeling process, and one for handling the thunder claps every 3 seconds.

Here's our start trigger, this gets everything setup for our caster the moment they begin channeling the ability:
  • TCC Channel Start
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Thunder Clap Channeling
    • Actions
      • Set TCC_Caster = (Triggering unit)
      • Set TCC_CV = (Custom value of TCC_Caster)
      • -------- --------
      • -------- Reset the duration in case it still has a duration value from a previous cast: --------
      • Set TCC_Duration[TCC_CV] = 0
      • -------- --------
      • Set TCC_Point = (Position of TCC_Caster)
      • -------- --------
      • -------- Create a dummy, link it to our caster, and order it to cast thunder clap: --------
      • Unit - Create 1 Dummy for (Owner of TCC_Caster) at TCC_Point facing Default building facing degrees
      • Set TCC_Dummy[TCC_CV] = (Last created unit)
      • Unit - Add Thunder Clap (Dummy) to TCC_Dummy[TCC_CV]
      • Unit - Order TCC_Dummy[TCC_CV] to Human Mountain King - Thunder Clap.
      • -------- --------
      • Custom script: call RemoveLocation( udg_TCC_Point )
      • -------- --------
      • -------- Start the loop (if necessary): --------
      • Unit Group - Add TCC_Caster to TCC_Caster_Group
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of units in TCC_Caster_Group) Equal to 1
        • Then - Actions
          • Trigger - Turn on TCC Loop <gen>
        • Else - Actions
Our stop trigger, this ends the process for the caster the moment they stop channeling the ability:
  • TCC Channel Stop
    • Events
      • Unit - A unit Stops casting an ability
    • Conditions
      • (Ability being cast) Equal to Thunder Clap Channeling
    • Actions
      • Set TCC_Caster = (Triggering unit)
      • Set TCC_CV = (Custom value of TCC_Caster)
      • -------- --------
      • -------- Since the thunder clap animation is attached to the dummy make sure it dies a couple seconds later: --------
      • Unit - Add a 2.00 second Generic expiration timer to TCC_Dummy[TCC_CV]
      • -------- --------
      • -------- End the loop (if necessary): --------
      • Unit Group - Remove TCC_Caster from TCC_Caster_Group.
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of units in TCC_Caster_Group) Equal to 0
        • Then - Actions
          • Trigger - Turn off TCC Loop <gen>
        • Else - Actions
Our loop trigger, this manages the time aspect of our spell, creating a thunder clap every 3 seconds:
  • TCC Loop
    • Events
      • Time - Every 0.02 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in TCC_Caster_Group and do (Actions)
        • Loop - Actions
          • Set TCC_Caster = (Picked unit)
          • Set TCC_CV = (Custom value of TCC_Caster)
          • -------- --------
          • -------- Track how much time has elapsed (this duration variable increases by 50 over 1.00 second): --------
          • Set TCC_Duration[TCC_CV] = (TCC_Duration[TCC_CV] + 1)
          • -------- --------
          • -------- Modulo let's us check if our duration integer is a multiple of 150 (3.00 seconds): --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (TCC_Duration[TCC_CV] mod 150) Equal to 0
            • Then - Actions
              • Set TCC_Point = (Position of TCC_Caster)
              • -------- --------
              • -------- Cast thunder clap again: --------
              • Unit - Move TCC_Dummy[TCC_CV] instantly to TCC_Point
              • Unit - Order TCC_Dummy[TCC_CV] to Human Mountain King - Thunder Clap.
              • -------- --------
              • Custom script: call RemoveLocation( udg_TCC_Point )
            • Else - Actions
Thunder Clap Channeling is based on the Channel ability with a Follow Through time of 6.00 seconds. You can make it last however long you want though since the triggers will automatically adapt. You can change the value 150 in the mod condition to adjust the interval of the thunder claps. Remember that TCC_Duration increases by 1 every 0.02 seconds (50 times per second) so for example you could change 150 to 50 to make it release a thunder clap once every 1.00 second.

The variables used here:
vars.png


All of these triggers rely on a Unit Indexer which needs to be copied into your map. This is a lightweight and powerful system which allows you to link data directly to your units. It's like a more simple and easy to use version of a Hashtable.

If you already have a Unit Indexer in your map then don't import a new one. Also, if you're using the Set Custom Value action anywhere in your triggers then you'll need to replace them with Integer array variables that take advantage of the Unit Indexer system. It's a very easy fix assuming you haven't used custom value 100's of times or something.
 

Attachments

  • Thunderclap Channeling 1.w3m
    30.1 KB · Views: 1
Last edited:
Level 17
Joined
Apr 13, 2008
Messages
1,597
I think you guys are overcomplicating it.
There is no need for unit indexing.

First, be aware of a few things:
Each order has an ID attached to it. It's an integer.
For example, if the unit has no current order, and is idle, then its current order ID will be 0.
If the unit is currently casting an ability, then its current order ID will be something similar to 14122112 or whatever.
There is also a function that converts these integers into order strings. Order strings are humanly readable orders associated to the gibberish integer order IDs, such as "aerialshackle" or "attack" or "thunderclap".

1690824510241.png


I would refrain from using global variables when creating triggered abilities unless they really are necessary. So it's best not to store units in globals. If you use globals in your case it will mean that only one unit on the map will be able to use this. Which is fine if you make it a unique hero ability, but the moment you have 2 players with the same hero, or would like to make it able to cast by multiple units, you are toast.

In Warcraft we have local variables, which are variables that are created and stored in the memory when the trigger starts and are released from the memory once the trigger finishes (some variables leak memory due to lousy programming by Blizzard, but you can fight that war another day).
This is how you declare a unit type local variable in JASS:
local unit SourceUnit = GetTriggerUnit()

The problem is that while you could use the Custom Script action in GUI, GUI does not let you refer to local variables without using more JASS.

You seem like a highly intelligent guy. Gather the courage and initial momentum to use JASS. It's simple. It took me less than 2 minutes to put this together.
You can just copy it into your map then change a few things:

JASS:
function Trig_AstralJGHelp_Actions takes nothing returns nothing
local unit SourceUnit = GetTriggerUnit()
local integer AbID = GetSpellAbilityId()
local real SourceX = GetUnitX(SourceUnit)
local real SourceY = GetUnitY(SourceUnit)
local unit Dummy
local player SourcePlayer = GetOwningPlayer(SourceUnit)

if AbID == 'PRYR' then
// Go to the Object Editor, find your Chaotic Tremor ability. Press Control+D, and the Editor will reveal the Ability's unique ID.
// I will use PRYR, which is an ability ID on my map. This ID is an integer, but for reasons they are basically 4 letters and numbers between ''.
// Similarly you have to find in the Object Editor and Control+D your dummy unit's unit ID. My dummy unit is 'n00W'
    set Dummy = CreateUnit(SourcePlayer, 'n00W', SourceX, SourceY, 0)
    // Find the thunder clap ability's ID, I'll just use 'A001' as an example:
    call UnitAddAbility(Dummy, 'A001')
    // 'BTLF' here is just a standard expiry buff, pay no attention to it
    call UnitApplyTimedLife(Dummy, 'BTLF', 6)
    // The order string of your ability will be "thunderclap" if you based it on Thunder Clap
    call IssueImmediateOrder(Dummy, "thunderclap")
    // If this ability goes on infinitely (until some conditions are satisfied it is best to set up a simple loop,
    // but if there will be only 3 claps, there is no point to use loops
    // Wait 3 seconds:
    call TriggerSleepAction(3)

    // Now here is the main thing where you made a mistake. Ability being cast is not what you are looking for. You want to know the unit's current order.
    // Abilities have order strings. If you used the Channel ability, then its default order string is "channel".
    // You might have set it to something else, then use that instead
    if GetUnitCurrentOrder(SourceUnit) == String2OrderIdBJ("channel") then
        set Dummy = CreateUnit(SourcePlayer, 'n00W', SourceX, SourceY, 0)
        call UnitAddAbility(Dummy, 'A001')
        call UnitApplyTimedLife(Dummy, 'BTLF', 6)
        call IssueImmediateOrder(Dummy, "thunderclap")
        call TriggerSleepAction(3)
      
        if GetUnitCurrentOrder(SourceUnit) == String2OrderIdBJ("channel") then
            set Dummy = CreateUnit(SourcePlayer, 'n00W', SourceX, SourceY, 0)
            call UnitAddAbility(Dummy, 'A001')
            call UnitApplyTimedLife(Dummy, 'BTLF', 6)
            call IssueImmediateOrder(Dummy, "thunderclap")
            call TriggerSleepAction(3)
        endif
    endif
endif

endfunction

//===========================================================================
function InitTrig_AstralJGHelp takes nothing returns nothing
    set gg_trg_AstralJGHelp = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_AstralJGHelp, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddAction( gg_trg_AstralJGHelp, function Trig_AstralJGHelp_Actions )
endfunction
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
This is bad. Don't do this:
vJASS:
call TriggerSleepAction(3)
Waits are not your friend, aside from working with cinematics and other "one time" things like initializing your map. By using them you're creating room for errors and asking for bugs.

Remember, a Wait has to be synced between all Players when playing online and in situations like this where timing is everything it's important that your timing is precise. I can already see a situation where the Hero finishes channeling the ability prior to the last Wait firing, causing the last thunder clap to get skipped. Also, one Dummy unit is all that's needed, but that's just a minor optimization.

OP is using GUI, and doesn't have a perfect understanding of it, so I don't think suggesting code is going to be any easier for him or really helpful in the long run. We should teach him how to make advanced triggers using the tools he understands (GUI). If we want to delve into coding solutions then I suggest Lua as it makes things even more simple, although it's only available in version 1.31+. Otherwise, using a library like TimerUtils in Jass could further simplify the code since Timers are precise, unlike Waits. In that case I would also add a trigger that detects when the spell stops casting in order to kill the timer.
 
Last edited:
Level 17
Joined
Apr 13, 2008
Messages
1,597
Yes, I know waits are evil, and the other stuff, but did not want to confuse OP with stuff. He was already working with Waits, I have a skill on my current map that uses a somewhat similar logic, and I'm using timers, of course.

I disagree with the GUI / JASS thing. GUI is really convoluted and icky and buggy compared to JASS (or Lua). I think it's better to jump into JASS at his level of understanding already. But then again, it's up to him.
 
Level 6
Joined
May 13, 2023
Messages
44
Thank you both for your replies, I tried emperor_d3st code and it worked perfectly,
Waits are not your friend, aside from working with cinematics and other "one time" things like initializing your map. By using them you're creating room for errors and asking for bugs.
From now on I'll try to avoid using Wait function.
I think it's better to jump into JASS at his level of understanding already. But then again, it's up to him.
I am well aware of how powerful Jass is, but most triggers I make aren't really that complicated so I tend to sleep on jass and hope I can do it with GUI, But I will take your advice and start learning Jass
 
Top