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

[Trigger] [Solved] Questions about Timers

Status
Not open for further replies.
Level 6
Joined
Jul 12, 2021
Messages
95
There are three questions I have.

In the next trigger is it possible to put a single event instead of many?
  • Untitled Trigger 005
    • Events
      • Time - DeathWardTimerB[1] expires
      • Time - DeathWardTimerB[2] expires
      • Time - DeathWardTimerB[3] expires
      • Time - DeathWardTimerB[4] expires
      • Time - DeathWardTimerB[5] expires
      • Time - DeathWardTimerB[6] expires
    • Conditions
    • Actions
Instead of having 6 events is there a way to have a single event that tracks when each timer expire? Something like: a Timer from 1 to 6 expires.



In the next trigger is it possible to put a single action instead of many?
  • BloodPactDamage Copy
    • Events
      • Time - TimerBloodPact[0] expires
      • Time - TimerBloodPact[1] expires
      • Time - TimerBloodPact[2] expires
      • Time - TimerBloodPact[3] expires
    • Conditions
    • Actions
      • If ((Units[0] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[0] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
      • If ((Units[1] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[1] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
      • If ((Units[2] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[2] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
      • If ((Units[3] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[3] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
Instead of having 4 actions is there a way to have a single action and having each unit linked to each timer? Something like checking if Units 0 to 3 have the buff, if the Unit[0] has the buff then start TimerBloodPact[0]. If Unit[1] has the buff then start TimerBloodPact[1], etc.


Finally, if the answer to my questions is no, would the answer be yes if Jass or Lua were used instead of GUI?
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
Here is a direct transliteration of your GUI, answering your questions, in wurst:

"A timer from 1 to 3 expires":

Wurst:
package Temp
import LinkedList
constant timers = asList(CreateTimer(), CreateTimer(), CreateTimer())
init
    let trig = CreateTrigger()..addAction() ->
        print(GetExpiredTimer().getHandleId().toString() + " expired.")
    timers.forEach() (timer t) ->
        trig.registerTimerExpireEvent(t)

"Separate handling for each unit's recurring timer":

Wurst:
package Temp2
import HashMap
import UnitIds
constant ID_MY_BUFF = 'B000'
function skelly() returns unit
    return createUnit(players[0], UnitIds.skeletalarcher, ZERO2, 0..asAngleDegrees())
constant skelly_timers = new IterableMap<timer, unit>()
    ..put(CreateTimer(), skelly())
    ..put(CreateTimer(), skelly())
    ..put(CreateTimer(), skelly())
    ..put(CreateTimer(), skelly())
init
    let trig = CreateTrigger()..addAction() ->
        let expired_timer = GetExpiredTimer()
        if skelly_timers.get(expired_timer).hasAbility(ID_MY_BUFF)
            expired_timer.start(1., () -> skip)
    skelly_timers.forEach() (timer key, unit _value) ->
        trig.registerTimerExpireEvent(key)

In both of these cases there are much more ergonomic approaches to solving the same problems you're trying to solve, but this should be sufficient for learning purposes
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
There is a way of doing it in GUI but it's relying on Custom script which is Jass (or Lua):
  • Example
    • Events
      • Time - Timer[1] expires
      • Time - Timer[2] expires
      • Time - Timer[3] expires
    • Conditions
    • Actions
      • For each (Integer Loop) from 1 to 3, do (Actions)
        • Loop - Actions
          • Custom script: if GetExpiredTimer() == udg_Timer[udg_Loop] then
          • Game - Display to (All players) for 2.00 seconds the text: (This timer has expired: + (String(Loop)))
          • Custom script: endif

I can't say for certain what you plan on doing with those triggers but you may be approaching it the wrong way. One timer may be able to handle everything.
 
Level 6
Joined
Jul 12, 2021
Messages
95
Here is a direct transliteration of your GUI, answering your questions, in wurst:

"A timer from 1 to 3 expires":

Wurst:
package Temp
import LinkedList
constant timers = asList(CreateTimer(), CreateTimer(), CreateTimer())
init
    let trig = CreateTrigger()..addAction() ->
        print(GetExpiredTimer().getHandleId().toString() + " expired.")
    timers.forEach() (timer t) ->
        trig.registerTimerExpireEvent(t)

"Separate handling for each unit's recurring timer":

Wurst:
package Temp2
import HashMap
import UnitIds
constant ID_MY_BUFF = 'B000'
function skelly() returns unit
    return createUnit(players[0], UnitIds.skeletalarcher, ZERO2, 0..asAngleDegrees())
constant skelly_timers = new IterableMap<timer, unit>()
    ..put(CreateTimer(), skelly())
    ..put(CreateTimer(), skelly())
    ..put(CreateTimer(), skelly())
    ..put(CreateTimer(), skelly())
init
    let trig = CreateTrigger()..addAction() ->
        let expired_timer = GetExpiredTimer()
        if skelly_timers.get(expired_timer).hasAbility(ID_MY_BUFF)
            expired_timer.start(1., () -> skip)
    skelly_timers.forEach() (timer key, unit _value) ->
        trig.registerTimerExpireEvent(key)

In both of these cases there are much more ergonomic approaches to solving the same problems you're trying to solve, but this should be sufficient for learning purposes
Thanks for the explanation.


I can't say for certain what you plan on doing with those triggers but you may be approaching it the wrong way. One timer may be able to handle everything.
Thanks for the reply.

I want that when the remaining time of a summoned unit time is 5 seconds to display with floating texts a countdown to 0. I'm kind of lost in what to do.

Trigger 1a:
  • Trigger 1a
    • Events
      • Unit - A unit Spawns a summoned unit
    • Conditions
      • Or - Any (Conditions) are true
        • Conditions
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 1)
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 2)
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 3)
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 4)
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 5)
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 6)
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 7)
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 8)
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 9)
          • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 10)
    • Actions
      • Countdown Timer - Start DeathWardTimerCountdown1[(Player number of (Owner of (Triggering unit)))] as a One-shot timer that will expire in 29.50 seconds
      • Set VariableSet DeathWardInteger[(Custom value of (Triggering unit))] = 5
      • Set VariableSet DeathWardSummoned[(Custom value of (Triggering unit))] = (Summoning unit)
Trigger 2a
  • Trigger 2a
    • Events
      • Time - DeathWardTimer[1] expires
      • Time - DeathWardTimer[2] expires
      • Time - DeathWardTimer[3] expires
      • Time - DeathWardTimer[4] expires
      • Time - DeathWardTimer[5] expires
      • Time - DeathWardTimer[6] expires
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • DeathWardInteger[0] Greater than 0
        • Then - Actions
          • Floating Text - Create floating text that reads (String(DeathWardInteger[0])) above DeathWardSummoned[0] with Z offset 0.00, using font size 10.00, color (100.00%, 100.00%, 100.00%), and 0.00% transparency
          • Floating Text - Set the velocity of (Last created floating text) to 80.00 towards 90.00 degrees
          • Floating Text - Change (Last created floating text): Disable permanence
          • Floating Text - Change the lifespan of (Last created floating text) to 2.00 seconds
          • Floating Text - Change the fading age of (Last created floating text) to 1.50 seconds
          • Countdown Timer - Start DeathWardTimer[1] as a One-shot timer that will expire in 1.00 seconds
          • Set VariableSet DeathWardInteger[0] = (DeathWardInteger[0] - 1)
        • Else - Actions
I have many problems with the second trigger. The first problem with the second trigger is that I don't know the array index of DeathWardTimer. In the first trigger the array index of DeathWardTimer is defined as "[(Custom value of (Triggering unit))]" but I have no idea which number is that. That's why I wanted to put something like if DeathWardTimer[1 to 10000] expires then do actions.
The second problem is that I don't have linked the array indexes of DeathWardTimer with the array indexes of DeathWardInteger and DeathWardSummoned. Another problem is that I dont know what to put in DeathWardSummoned array index and DeathWardInteger array index. Also the Countdown Timer on the actions is supposed to start the same timer that activated the trigger.




For the second trigger I want a unit to take damage every second as long as the unit has a buff.
I already did the GUI MUI method of putting a Time - Periodic Event and deal the damage to a unit group, but I dont like it because the first tick isn't precise, the damage always happens in less than one second; also because all the units with the buff take the damage at exactly the same time.

Here is my Trigger 1b:
  • Trigger 1b
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Blood Pact (Skeleton Warrior)
    • Actions
      • Set VariableSet Units[(Custom value of (Triggering unit))] = (Triggering unit)
      • Countdown Timer - Start TimerBloodPact[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 30.00 seconds
Here is my Trigger 2b:
  • Trigger 2b
    • Events
      • Time - TimerBloodPact[0] expires
      • Time - TimerBloodPact[1] expires
      • Time - TimerBloodPact[2] expires
      • Time - TimerBloodPact[3] expires
    • Conditions
    • Actions
      • If ((Units[0] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Unit - Cause Necropolis (Page 1) 1486 <gen> to damage Units[0], dealing 10.00 damage of attack type Spells and damage type Normal) else do (Do nothing)
      • If ((Units[0] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[0] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
      • If ((Units[1] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Unit - Cause Necropolis (Page 1) 1486 <gen> to damage Units[1], dealing 10.00 damage of attack type Spells and damage type Normal) else do (Do nothing)
      • If ((Units[1] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[1] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
      • If ((Units[2] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Unit - Cause Necropolis (Page 1) 1486 <gen> to damage Units[2], dealing 10.00 damage of attack type Spells and damage type Normal) else do (Do nothing)
      • If ((Units[2] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[2] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
      • If ((Units[3] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Unit - Cause Necropolis (Page 1) 1486 <gen> to damage Units[3], dealing 10.00 damage of attack type Spells and damage type Normal) else do (Do nothing)
      • If ((Units[3] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[3] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
The problem with the second trigger is that I don't know the array index of TimerBloodPact and of Units. In the first trigger the array index of TimerBloodPact is defined as the [(Custom value of (Triggering unit))] but I have no idea which number is that. It's the same situation for the array index of Units, in the first trigger it is defined as [(Custom value of (Triggering unit))] but I don't know which number is that. That's why I wanted to put something like if TimerBloodPact[1 to 10000] expires then do actions. Same situation with Units array.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
I showed you how to get the array number (index) of the expired timer. See the Loop in my trigger that goes from 1 to 3, look closely at what it's doing -> If Expired timer is equal to YourTimer[Loop] then do stuff. Loop is the index of the timer that expired.

Anyway, I would stick with the GUI MUI method unless you're truly determined to learn Jass (Although I recommend Lua/TypeScript/C# over Jass), the units don't have to all take damage at the same time if you do it properly. I believe I explained this to you before but you can keep track of how much time has elapsed for each picked unit. Look into Unit Indexing/Hashtables, these will teach you how to save data directly to a unit which allows you to achieve what I just described among many other things.

For example here's how you can do it with a Unit Indexer:
Event:
Every 0.05 seconds
Actions:
Pick every unit in your Unit Group -> Set Variable cv = custom value of picked unit -> Set Variable Duration[cv] = Duration[cv] + 1 -> If Duration[cv] is Equal to 20 then Deal 100 damage to picked unit.

^ After 1 second the picked unit will take 100 damage. If you wanted to deal damage every 1 second then you could use Modulo in your Condition and check if: Duration[cv] mod 20 is Equal to 0

I realize this isn't perfectly precise since a unit could be added to the Unit Group at any given time but it gets very close. The shorter you make the Periodic Interval the smaller this gap in precision becomes, although that comes at a cost of performance.
 
Last edited:
Level 6
Joined
Jul 12, 2021
Messages
95
I showed you how to get the array number (index) of the expired timer. See the Loop in my trigger that goes from 1 to 3, look closely at what it's doing -> If Expired timer is equal to YourTimer[Loop] then do stuff. Loop is the index of the timer that expired.
Yes, but (if I understood correctly) you have to put as the event all the timers you need, instead of just putting a single event with timers[1-10000].

Example. The next two triggers are for a MUI spell.
Trigger 1a:
  • Trigger 1a
    • Events
      • Unit - A unit Spawns a summoned unit
    • Conditions
      • (Unit-type of (Summoned unit)) Equal to Death Ward (Level 1)
    • Actions
      • Countdown Timer - Start DeathWardTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 30 seconds

In the previous trigger DeathWardTimer the array index is [(Custom value of (Triggering unit))]. Since we don't know what number is that, the next trigger will have trouble setting the event:

Trigger 2a
  • Trigger 2a
    • Events
      • Time - DeathWardTimer[?] expires
      • Time - DeathWardTimer[?] expires
      • Time - DeathWardTimer[?] expires
      • Time - DeathWardTimer[?] expires
      • Time - DeathWardTimer[?] expires
      • Time - DeathWardTimer[?] expires
    • Conditions
    • Actions
It would be really awesome if we could put in the event a single event: DeathWardTimer[1-10000].


Anyway, I would stick with the GUI MUI method unless truly you're determined to learn Jass (I recommend Lua/TypeScript/C# over Jass), the units don't have to all take damage at the same time if you do it properly. I believe I explained this to you before but you can keep track of how much time has elapsed for each picked unit. Look into Unit Indexing/Hashtables, these will teach you how to save data directly to a unit which allows you to achieve what I just described among many other things.

For example here's how you can do it with a Unit Indexer:
Event:
Every 0.05 seconds
Actions:
Pick every unit in your Unit Group -> Set Variable cv = custom value of picked unit -> Set Variable Duration[cv] = Duration[cv] + 1 -> If Duration[cv] is Equal to 20 then Deal 100 damage to picked unit.

^ After 1 second the picked unit will take 100 damage. If you wanted to deal damage every 1 second then you could use Modulo in your Condition and check if: Duration[cv] mod 20 is Equal to 0

I realize this isn't perfectly precise since a unit could be added to the Unit Group at any given time but it gets very close. The shorter you make the Periodic Interval the smaller this gap in precision becomes, although that comes at a cost of performance.
I understand it better now. I like GUI MUI more than before.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
Unfortunately timers in GUI are rather limited as you may have noticed. You're stuck with Events which is an undesirable way of handling them.

In Lua you could do something as simple as this for a damage over time effect. The timer's callback function will retain all of the local variables that you declared at cast time so there's no need for global variables or annoying indexing/hashtable systems:
Lua:
function DamageOverTime()
    local caster = GetTriggerUnit()
    local target = GetSpellTargetUnit()
    local counter = 0
    local counterMax = 10
    local dmg = 50 * GetUnitAbilityLevel(caster, GetSpellAbilityId())
    local timer = CreateTimer()
  
    TimerStart(timer, 1.00, true, function()
        UnitDamageTarget(caster, target, dmg, false, false, ATTACK_TYPE_HERO, DAMAGE_TYPE_NORMAL, nil)
        counter = counter + 1
        if (counter == counterMax or IsUnitAliveBJ(target)) == false then
            PauseTimer(timer)
            DestroyTimer(timer)
        end
    end)
end
You would call this function in response to a unit casting your ability.
 
Level 6
Joined
Jul 12, 2021
Messages
95
Part 1 of my reply:
I understand it better now. I like GUI MUI more than before.
There's only one thing that makes me refrain from using GUI MUI. It is that I think it's very taxing.
For example here's how you can do it with a Unit Indexer:
Event:
Every 0.05 seconds
Actions:
Pick every unit in your Unit Group -> Set Variable cv = custom value of picked unit -> Set Variable Duration[cv] = Duration[cv] + 1 -> If Duration[cv] is Equal to 20 then Deal 100 damage to picked unit.

^ After 1 second the picked unit will take 100 damage. If you wanted to deal damage every 1 second then you could use Modulo in your Condition and check if: Duration[cv] mod 20 is Equal to 0
If we put the Time - Periodic event to 0.01 it would be very precise, but very taxing (I think). Even every 0.05 seems taxing. I may be wrong, I have no clue. Is it taxing?



Part 2 of my reply:
Unfortunately timers in GUI are rather limited as you may have noticed. You're stuck with Events which is an undesirable way of handling them.
Damn.
I'm getting really interested in Lua. It really provides a very different approach for timers.

There's a question I have. I found a tutorial about Lua and I'm learning a lot about the language, but the tutorial isn't about Warcraft. How am I supposed to know the commands that are used in warcraft?

What if I want to change the target in your previous code from this (targeting a unit):
Lua:
[QUOTE="Uncle, post: 3505063, member: 268855"]
    local target = GetSpellTargetUnit()
[/QUOTE]
To this (targeting a unit group):
  • Unit Group - Pick every unit in (Units within 750.00 of (Position of (Triggering unit)) matching ((((Matching player) is an ally of (Owner of (Triggering unit)).) Not equal to True) and ((Owner of (Matching unit)) Not equal to (Owner of (Triggering unit)))).) and do (Actions)

I would easily do it in GUI since you are given all the possible options; but in Lua and JASS everything is free code! It would be really helpful a complete list of all the possible commands or examples of codes (learned a lot with your code). So, how do you realize which commands exist in Warcraft 3?
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
1) Telling the game to do something 100 times per second is going to be more taxing than 20 times per second. But of course WHAT you tell it to do each interval is the most important factor. Simple arithmetic is a lot less taxing than creating units/special effects for example. Keep in mind that Periodic Interval can only run at a minimum of 0.03125 times per second (1/32) if I recall correctly. You can use a Timer to bypass this limitation.

2) Warcraft 3 uses an API which is basically a set of functions. These functions are things like KillUnit(), CreateUnit(), etc. and regardless of what programming language you're using you will be handling these same functions. Even your GUI triggers use these functions, GUI just displays them in a non-code form so that they're easier to understand. You can look up this API online to find a list of all of the functions, just search for Jass API. Another thing you can do is Convert a GUI trigger to Custom Text (Jass only). This often gives you "bad" Jass code but at the same time it will probably show you some form of what you need.

But it gets even easier, there's a thing called Intellisense, which is a form of Autocomplete which you can use while coding in an IDE (a code editing tool). See the attached picture for an example. There's no need to memorize every little thing!

Here's how you can get started using Lua. Note that I personally use C# which compiles to Lua, you can find a link to it in this thread.

Here's some examples of Pick Every Unit in Lua:
Lua:
--I didn't test this one but I BELIEVE it should work and should be the most efficient of the bunch
function Example1()
    local g = CreateGroup()
    local x = 0
    local y = 0
    local aoe = 1000
    GroupEnumUnitsInRange(g, x, y, aoe, nil)
    local v = FirstOfGroup(g)
    while v ~= nil do
        v = FirstOfGroup(g)
        --Do stuff
        KillUnit(v)
        GroupRemoveUnit(g, v)
    end
    DestroyGroup(g)
end

function Example2()
    local g = CreateGroup()
    local v
    local x = 0
    local y = 0
    local aoe = 1000
    GroupEnumUnitsInRange(g, x, y, aoe, nil)
    ForGroup(g, function()
        v = GetEnumUnit()
        --Do stuff
        KillUnit(v)
    end)
    DestroyGroup(g)
end

--I don't like this approach but I wanted to show that this is still possible
function Example3()
    local g = CreateGroup()
    local x = 0
    local y = 0
    local aoe = 1000
    GroupEnumUnitsInRange(g, x, y, aoe, nil)
    ForGroup(g, GroupCallback)
    DestroyGroup(g)
end

function GroupCallback()
    local v = GetEnumUnit()
    KillUnit(v)
end
I'm leaving some stuff out like Conditions/Filters but if you search around for Jass examples I'm sure you can find these. Remember that basically anything that worked in Jass will also work in Lua, you'll just need to change the syntax (how things are worded) here and there. The function names are ALL the same since they both use the same API.

There's a decent more that goes into all of this but it's hard to touch on everything. You'll learn as you go, just remember that most questions have been asked already so if you dig around you should be able to find an answer out there. Also, I should say that I'm not an expert on the matter and I'm still learning as well.
 

Attachments

  • exa.png
    exa.png
    39.4 KB · Views: 29
Last edited:
Level 6
Joined
Jul 12, 2021
Messages
95
Keep in mind that Periodic Interval can only run at a minimum of 0.3125 times per second (1/32) if I recall correctly. You can use a Timer to bypass this limitation.
1. Did you mean 0.03125? If yes, then, does that mean that a Time - Periodic Event lower than 0.0325 seconds (For example, every 0.01 seconds) will become 0.0325? How do you know this?


2) Warcraft 3 uses an API which is basically a set of functions.
2. I hope I'm not being too fuzzy but... where does the API come from? How did the first person who created the API realized all those functions? Are the first persons who created the API the creators of the World Editor?


Here's how you can get started using Lua. Note that I personally use C# which compiles to Lua, you can find a link to it in this thread.
3. Do you use the transpiler from c# to Lua?


GroupEnumUnitsInRange(g, x, y, aoe, nil)[/code]
4. Thanks for the Lua Code!
What is x, y, nil?
I found this in the API: GroupEnumUnitsInRange (group whichGroup, real x, real y, real radius, boolexpr filter). Yet, I don't know, instructions are lacking.


5. I understood your function Example 2. v = GetEnumUnit() is the picked unit. The actions are a loop, so every unit will be part of the trigger. But I didn't understood your function Example 1. If v is defined as v = FirstOfGroup(g), then won't it only affect the first unit of the group?


Remember that basically anything that worked in Jass will also work in Lua, you'll just need to change the syntax (how things are worded) here and there. The function names are ALL the same since they both use the same API.
Interesting
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
1) Yes, 0.03125. I've read it in many posts and I'm pretty sure I've tested it before. EDIT: Confirmed to be false!

2) The developers of Warcraft 3 created the API.

3) I use this library which handles all of that stuff for me: Home · Orden4/WCSharp Wiki

4) You would set aoe, x, and y yourself. x is the x coordinate, y is the y coordinate. So you're picking units within aoe range of that pair of x/y coordinates. To give you an example, x:0 / y:0 is the center of the map. Setting aoe to 1000 will pick units within 1000 range.
Lua:
local g = CreateGroup()
local x = 0
local y = 0
local aoe = 1000
GroupEnumUnitsInRange(g, x, y, aoe, nil)
Nil is null, which means nothing. You have the choice of adding a filter there which would filter out unwanted units from being added to the group. It's what you use when you check for matching conditions in GUI -> Pick every unit MATCHING....

5) The picked unit "v" is removed from the group after you're finished doing whatever you wanted to do with it. This means that when that function is used again it will get the NEXT "First Unit", which will be a different unit. You can sort of think of it like getting the next unit in line.
 
Last edited:
Level 19
Joined
Feb 27, 2019
Messages
582
Code is always the best.

These are my thoughts:
Not so much options with GUI.
I guess dy index would work well for a low amount of timers for MUI.
Player index would work excellent with hashtable if the timer was set like so.
  • For each (Integer A) from 1 to 6, do (Actions)
    • Loop - Actions
      • Countdown Timer - Start InvulTimer[(Integer A)] as a One-shot timer that will expire in 0.00 seconds
      • Hashtable - Save (Integer A) as 1 of (Key (Last started timer).) in Hashtable_Pigs.
  • Invul Timer Expires
    • Events
      • Time - InvulTimer[1] expires
      • Time - InvulTimer[2] expires
      • Time - InvulTimer[3] expires
      • Time - InvulTimer[4] expires
      • Time - InvulTimer[5] expires
      • Time - InvulTimer[6] expires
    • Conditions
    • Actions
      • -------- InvulTimer starts whenever Invulnerability is applied in a Trigger --------
      • Set VariableSet Int = (Load 1 of (Key (Expiring timer).) from Hashtable_Pigs.)
      • Set VariableSet PigIsInvul[Int] = False
      • -------- Remove Invulnerability --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • StopSpells Equal to 0
        • Then - Actions
          • Unit - Make AllPigsArray[Int] Vulnerable
        • Else - Actions
 
Level 6
Joined
Jul 12, 2021
Messages
95
  • Actions
    • For each (Integer Loop) from 1 to 3, do (Actions)
      • Loop - Actions
        • Custom script: if GetExpiredTimer() == udg_Timer[udg_Loop] then
        • Custom script: endif
Uncle, thank you again for sharing this useful command. I finally found the way to use it!
I found the solution to my Second Trigger.
The purpose of my Second Trigger is a MUI spell that makes a unit take damage every second as long as the unit has the buff Immolation (100% time precision is mandatory, as well as a solution that compromise performance the minimum possible). Perhaps with code there is a solution that compromises the performance even less. I don't know.


For the second trigger I want a unit to take damage every second as long as the unit has a buff.
I already did the GUI MUI method of putting a Time - Periodic Event and deal the damage to a unit group, but I dont like it because the first tick isn't precise, the damage always happens in less than one second; also because all the units with the buff take the damage at exactly the same time.

Here is my Trigger 1b:
  • Trigger 1b
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Blood Pact (Skeleton Warrior)
    • Actions
      • Set VariableSet Units[(Custom value of (Triggering unit))] = (Triggering unit)
      • Countdown Timer - Start TimerBloodPact[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 30.00 seconds
Here is my Trigger 2b:
  • Trigger 2b
    • Events
      • Time - TimerBloodPact[0] expires
      • Time - TimerBloodPact[1] expires
      • Time - TimerBloodPact[2] expires
      • Time - TimerBloodPact[3] expires
    • Conditions
    • Actions
      • If ((Units[0] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Unit - Cause Necropolis (Page 1) 1486 <gen> to damage Units[0], dealing 10.00 damage of attack type Spells and damage type Normal) else do (Do nothing)
      • If ((Units[0] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[0] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
      • If ((Units[1] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Unit - Cause Necropolis (Page 1) 1486 <gen> to damage Units[1], dealing 10.00 damage of attack type Spells and damage type Normal) else do (Do nothing)
      • If ((Units[1] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[1] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
      • If ((Units[2] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Unit - Cause Necropolis (Page 1) 1486 <gen> to damage Units[2], dealing 10.00 damage of attack type Spells and damage type Normal) else do (Do nothing)
      • If ((Units[2] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[2] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
      • If ((Units[3] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Unit - Cause Necropolis (Page 1) 1486 <gen> to damage Units[3], dealing 10.00 damage of attack type Spells and damage type Normal) else do (Do nothing)
      • If ((Units[3] has buff Blood Pact (SkeletonWarrior)) Equal to True) then do (Countdown Timer - Start TimerBloodPact[3] as a One-shot timer that will expire in 1.00 seconds) else do (Do nothing)
The problem with the second trigger is that I don't know the array value of TimerBloodPact and of Units. In the first trigger the array value of TimerBloodPact is defined as the [(Custom value of (Triggering unit))] but I have no idea which number is that. It's the same situation for the array value of Units, in the first trigger it is defined as [(Custom value of (Triggering unit))] but I don't know which number is that. That's why I wanted to put something like if TimerBloodPact[1 to 10000] expires then do actions. Same situation with Units array.
The solution for knowing the array value of timers was using the action "Trigger - Add New Event"
  • Trigger - Add to DamageBloodPactUnit <gen> the event (Time - (Last started timer) expires)
Also using the next custom script:
  • Custom script: if GetExpiredTimer() == udg_BloodPactTimer[bj_forLoopAIndex] then
The solution for knowing the units array value was witih the script above too. The array value of the expired timer is the same as the array value of Units

I changed the name of some variables, triggers and values, but here is the solution:

BloodPact.png

The variables that are arrays have an array size of 1, except for BloodPactTimer wich array size is 32768.


  • SetBloodPactMinimumCustomValueValue
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set VariableSet BloodPactMinimumCustomValue = 10000


  • BloodPactCastedForTheFirstTime
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Blood Pact (Skeleton Warrior)
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey1[(Custom value of (Triggering unit))] Equal to 0
        • Then - Actions
          • Set VariableSet BloodPactUnit[(Custom value of (Triggering unit))] = (Triggering unit)
          • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
          • Trigger - Add to DamageBloodPactUnit <gen> the event (Time - (Last started timer) expires)
          • Set VariableSet BloodPactMinimumCustomValue = (Min(BloodPactMinimumCustomValue, (Custom value of (Triggering unit))))
          • Set VariableSet BloodPactMaximumCustomValue = (Max(BloodPactMaximumCustomValue, (Custom value of (Triggering unit))))
          • Set VariableSet BloodPactKey1[(Custom value of (Triggering unit))] = 1
        • Else - Actions


  • DamageBloodPactUnit
    • Events
    • Conditions
    • Actions
      • For each (Integer A) from BloodPactMinimumCustomValue to BloodPactMaximumCustomValue, do (Actions)
        • Loop - Actions
          • Custom script: if GetExpiredTimer() == udg_BloodPactTimer[bj_forLoopAIndex] then
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (BloodPactUnit[(Integer A)] has buff Blood Pact (SkeletonWarrior)) Equal to True
            • Then - Actions
              • Countdown Timer - Start (Expiring timer) as a One-shot timer that will expire in 1.00 seconds
              • Unit - Cause Necropolis (Page 1) 1486 <gen> to damage BloodPactUnit[(Integer A)], dealing 9999990.00 damage of attack type Spells and damage type Normal
              • Set VariableSet BloodPactKey2[(Integer A)] = 1
            • Else - Actions
              • Set VariableSet BloodPactKey2[(Integer A)] = 0
          • Custom script: endif


  • BloodPactCasted
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Blood Pact (Skeleton Warrior)
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey1[(Custom value of (Triggering unit))] Equal to 1
        • Then - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • BloodPactKey2[(Custom value of (Triggering unit))] Equal to 1
            • Then - Actions
            • Else - Actions
              • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
        • Else - Actions

1) Yes, 0.03125. I've read it in many posts and I'm pretty sure I've tested it before.

2) The developers of Warcraft 3 created the API.

3) I use this library which handles all of that stuff for me: Home · Orden4/WCSharp Wiki

4) You would set aoe, x, and y yourself. x is the x coordinate, y is the y coordinate. So you're picking units within aoe range of that pair of x/y coordinates. To give you an example, x:0 / y:0 is the center of the map.
Lua:
local g = CreateGroup()
local x = 0
local y = 0
local aoe = 1000
GroupEnumUnitsInRange(g, x, y, aoe, nil)
Nil is null, which means nothing. You have the choice of adding a filter there which would filter out unwanted units from being added to the group. It's what you use when you check for matching conditions -> Pick every unit MATCHING....

5) The picked unit "v" is removed from the group after you're finished doing whatever you wanted to do with it. This means that when that function is used again it will get the NEXT "First Unit", which will be a different unit. You can sort of think of it like getting the next unit in line.
Alright, everything understood.


Code is always the best.

These are my thoughts:
Not so much options with GUI.
I guess dy index would work well for a low amount of timers for MUI.
Player index would work excellent with hashtable if the timer was set like so.
  • For each (Integer A) from 1 to 6, do (Actions)
    • Loop - Actions
      • Countdown Timer - Start InvulTimer[(Integer A)] as a One-shot timer that will expire in 0.00 seconds
      • Hashtable - Save (Integer A) as 1 of (Key (Last started timer).) in Hashtable_Pigs.
  • Invul Timer Expires
    • Events
      • Time - InvulTimer[1] expires
      • Time - InvulTimer[2] expires
      • Time - InvulTimer[3] expires
      • Time - InvulTimer[4] expires
      • Time - InvulTimer[5] expires
      • Time - InvulTimer[6] expires
    • Conditions
    • Actions
      • -------- InvulTimer starts whenever Invulnerability is applied in a Trigger --------
      • Set VariableSet Int = (Load 1 of (Key (Expiring timer).) from Hashtable_Pigs.)
      • Set VariableSet PigIsInvul[Int] = False
      • -------- Remove Invulnerability --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • StopSpells Equal to 0
        • Then - Actions
          • Unit - Make AllPigsArray[Int] Vulnerable
        • Else - Actions
Thank you for the reply. I'm curious; is there a reason (other than preferences) to use Hashtables instead of Arrays?


Keep in mind that Periodic Interval can only run at a minimum of 0.03125 times per second (1/32) if I recall correctly. You can use a Timer to bypass this limitation.
I don't believe this limitation is true.

It isn't true; I just tested it. I measured with a stopwatch the Timer and it did last the value settled.
The map is attached to this reply so that everyone can test it too.


How do you know this?
I've read it in many posts.
Apparently it's a myth; like the one that says evasion stacks.
 

Attachments

  • Time-PeriodicEvenetExperiment.w3m
    16.4 KB · Views: 17
Last edited:
Level 19
Joined
Feb 27, 2019
Messages
582
Thank you for the reply. I'm curious; is there a reason (other than preferences) to use Hashtables instead of Arrays?
The reason I used hashtable is because thats the only way I found to reference the timers in the event in GUI without extra steps. Id be glad to find another solution, but hey, it works excellent with player index.

Did you find another solution to use arrays instead of hashtable, except those already mentioned?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
I really don't like the design of your second trigger, I don't think it's MUI and it's definitely not efficient. If you insist on precision then I feel like you NEED to use Jass/Lua.

That being said, I whipped up a map that allows you to use what I'm calling the GUI Timer System. It's hardly a system in it's current state since I didn't really flesh it out, but it is somewhat useful, if anything at least for your damage over time ability. What it does is allow you to create local timers using custom script that don't require Timer global variables and nasty Timer Events. You can then attach a trigger to these timers which will run whenever the timer expires.

One function allows you to start a timer and link a caster unit, target unit, and Trigger to it. When the timer expires you're able to reference the caster and the target inside of your chosen Trigger using the TS_Caster and TS_Target variables. I have examples in the map, the Shaman's purge ability kills a unit after a 3 second delay and the Bloodlust ability deals 50 damage per second for 5 seconds.

Another function allows you to start a timer which has no links to anything. This one is extremely basic but could serve useful in certain situations. I use it in the map to respawn a Shaman when it's killed after a 2 second delay.

There's still room for many more additions as this is pretty barebones but I didn't really intend for you to use this system in it's current state anyway. It's more so a proof of concept and a way to approach coding in a way that GUI'ers may prefer. Maybe I'll keep adding to it if GUI'ers out there find it useful. And maybe something like this already exists...

Edit: Added a new version of the timer system that changes the syntax around and adds a new function for single unit timers.
 

Attachments

  • GUI Timer System 2.w3m
    23.5 KB · Views: 21
  • GUI Timer System 3.w3m
    24.8 KB · Views: 17
Last edited:
Level 6
Joined
Jul 12, 2021
Messages
95
Did you find another solution to use arrays instead of hashtable, except those already mentioned?
Just the solution of my previous post in the button "Second Trigger Solution". Here it is again:



View attachment 388326
The variables that are arrays have an array size of 1, except for BloodPactTimer, wich array size is 32768.

  • SetBloodPactMinimumCustomValueValue
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set VariableSet BloodPactMinimumCustomValue = 10000


  • BloodPactCastedForTheFirstTime
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Blood Pact (Skeleton Warrior)
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey1[(Custom value of (Triggering unit))] Equal to 0
        • Then - Actions
          • Set VariableSet BloodPactUnit[(Custom value of (Triggering unit))] = (Triggering unit)
          • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
          • Hashtable - Save (Custom value of (Triggering unit)) as 1 of (Key (Last started timer).) in BloodPactHashtable.
          • Set VariableSet BloodPactKey0[(Custom value of (Triggering unit))] = (BloodPactKey0[(Custom value of (Triggering unit))] + 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Custom value of (Triggering unit)) Equal to 1
            • Then - Actions
              • Trigger - Add to DamageBloodPactUnit <gen> the event (Time - (Last started timer) expires)
            • Else - Actions
          • Set VariableSet BloodPactKey1[(Custom value of (Triggering unit))] = 1
        • Else - Actions


  • DamageBloodPactUnit
    • Events
    • Conditions
    • Actions
      • For each (Integer A) from BloodPactMinimumCustomValue to BloodPactMaximumCustomValue, do (Actions)
        • Loop - Actions
          • Custom script: if GetExpiredTimer() == udg_BloodPactTimer[bj_forLoopAIndex] then
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (BloodPactUnit[(Integer A)] has buff Blood Pact (SkeletonWarrior)) Equal to True
            • Then - Actions
              • Countdown Timer - Start (Expiring timer) as a One-shot timer that will expire in 1.00 seconds
              • Unit - Cause Necropolis (Page 1) 1486 <gen> to damage BloodPactUnit[(Integer A)], dealing 9999990.00 damage of attack type Spells and damage type Normal
              • Set VariableSet BloodPactKey2[(Integer A)] = 1
            • Else - Actions
              • Set VariableSet BloodPactKey2[(Integer A)] = 0
          • Custom script: endif

  • BloodPactCasted
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Blood Pact (Skeleton Warrior)
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey1[(Custom value of (Triggering unit))] Equal to 1
        • Then - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • BloodPactKey2[(Custom value of (Triggering unit))] Equal to 1
            • Then - Actions
            • Else - Actions
              • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
        • Else - Actions
This custom script is the main solution:
  • For each (Integer A) from x to x, do (Actions)
    • Loop - Actions
      • Custom script: if GetExpiredTimer() == udg_BloodPactTimer[bj_forLoopAIndex] then
      • Custom script: endif

I'm curious; is there a reason (other than preferences) to use Hashtables instead of Arrays?
How could I be so blind! Of course there is! You gave me the solution in your first post. If you use hashtables you don't have to use the performance-compromising loop! Here is my new solution to my "Second Trigger":


BloodPactTriggers.png


All the variables that are arrays have array size of 1, with the exception of BloodPactTimer which array size is 32,768.

  • BloodPactHashtable
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set VariableSet BloodPactHashtable = (Last created hashtable)

  • BloodPactCasted
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Blood Pact (Skeleton Warrior)
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey1[(Custom value of (Triggering unit))] Equal to 0
        • Then - Actions
          • Set VariableSet BloodPactUnit[(Custom value of (Triggering unit))] = (Triggering unit)
          • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
          • Hashtable - Save (Custom value of (Triggering unit)) as 1 of (Key (Last started timer).) in BloodPactHashtable.
          • Trigger - Add to DamageBloodPactUnit <gen> the event (Time - (Last started timer) expires)
          • Set VariableSet BloodPactKey1[(Custom value of (Triggering unit))] = 1
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey1[(Custom value of (Triggering unit))] Equal to 1
          • BloodPactKey2[(Custom value of (Triggering unit))] Equal to 0
        • Then - Actions
          • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
        • Else - Actions

  • DamageBloodPactUnit
    • Events
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (BloodPactUnit[(Load 1 of (Key (Expiring timer).) from BloodPactHashtable.)] has buff Blood Pact (SkeletonWarrior)) Equal to True
        • Then - Actions
          • Countdown Timer - Start (Expiring timer) as a One-shot timer that will expire in 1.00 seconds
          • Unit - Cause Necropolis (Page 1) 1486 <gen> to damage BloodPactUnit[(Load 1 of (Key (Expiring timer).) from BloodPactHashtable.)], dealing 9999990.00 damage of attack type Spells and damage type Normal
          • Set VariableSet BloodPactKey2[(Load 1 of (Key (Expiring timer).) from BloodPactHashtable.)] = 1
        • Else - Actions
          • Set VariableSet BloodPactKey2[(Load 1 of (Key (Expiring timer).) from BloodPactHashtable.)] = 0
Uncle what do you think of my new solution, is it efficient? If no, why not?


I don't think it's MUI and it's definitely not efficient.
It is MUI, I worked for around 20 hours to come up with that solution and I can visualize it clearly. In addition to that, I did tests to prove it really is MUI.

I might be wrong, but I dont think it is that inefficient because the for loop runs with the variables BloodPactMinimumCustomValue and BloodPactMaximumValue. In my test the BloodPactMinimumValue was 1450 and the BloodPactMaximumValue was 1466. That is a short loop. Of course, in a different scenario, the differences between those two variables could be greater, which would indeed make it inefficient. Other than that I don't see why it is inneficient ;confused;.

After checking the map you attached I can see how it can be more efficient with code.

If you insist on precision then I feel like you NEED to use Jass/Lua.
First I wanted to exploit GUI. My next step is a Lua attempt.


That being said, I whipped up a map that allows you to use what I'm calling the GUI Timer System. It's hardly a system in it's current state since I didn't really flesh it out, but it is somewhat useful...
Thank you, thank you, thank you! Those examples clear my doubts and help me to introduce myself into coding! I understand functions better now.

EDIT: Changed trigger "BloodPactCastedForTheFirstTime" from the "Second Trigger New Solution" button.
EDIT (22/10/21): Changed triggers from the "Second Trigger New Solution" button.

EDIT (24/10/21):
I just found out that if you use several Timers with a large array size other triggers will malfunction. Here is a new solution that uses array size of 1 for the timer variable:

BloodPactTriggers.png


  • CreateHashtable1
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set VariableSet Hashtable1 = (Last created hashtable)

  • BloodPactCasted
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Blood Pact (Skeleton Warrior)
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey0[(Custom value of (Triggering unit))] Equal to 0
        • Then - Actions
          • Set VariableSet BloodPactUnit[(Custom value of (Triggering unit))] = (Triggering unit)
          • Custom script: set udg_BloodPactTimer[GetUnitUserData(GetTriggerUnit())]=CreateTimer()
          • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
          • Trigger - Add to DamageBloodPactUnit <gen> the event (Time - (Last started timer) expires)
          • Hashtable - Save (Custom value of (Triggering unit)) as 1 of (Key (Last started timer).) in Hashtable1.
          • Set VariableSet BloodPactKey0[(Custom value of (Triggering unit))] = 1
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey0[(Custom value of (Triggering unit))] Equal to 1
          • BloodPactKey1[(Custom value of (Triggering unit))] Equal to 0
        • Then - Actions
          • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
        • Else - Actions

  • DamageBloodPactUnit
    • Events
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (BloodPactUnit[(Load 1 of (Key (Expiring timer).) from Hashtable1.)] has buff Blood Pact (SkeletonWarrior)) Equal to True
        • Then - Actions
          • Countdown Timer - Start (Expiring timer) as a One-shot timer that will expire in 1.00 seconds
          • Unit - Cause Necropolis (Page 1) 1486 <gen> to damage BloodPactUnit[(Load 1 of (Key (Expiring timer).) from Hashtable1.)], dealing
          • Set VariableSet BloodPactKey1[(Load 1 of (Key (Expiring timer).) from Hashtable1.)] = 1
        • Else - Actions
          • Set VariableSet BloodPactKey1[(Load 1 of (Key (Expiring timer).) from Hashtable1.)] = 0
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
Well, if it works then I can't argue with that. Also, you're welcome, I had fun making that little Timer System.

Just be careful with adding Events to triggers, I'm pretty sure there is an Event limit. Also, units aren't removed from the Events when they die so they will remain there bloating the event forever. Sometimes things work for all the wrong reasons and sometimes they appear to work until the game lasts long enough that they break due to their poor design. Keep that in mind!
 
Level 6
Joined
Jul 12, 2021
Messages
95
That being said, I whipped up a map that allows you to use what I'm calling the GUI Timer System. It's hardly a system in it's current state since I didn't really flesh it out, but it is somewhat useful, if anything at least for your damage over time ability. What it does is allow you to create local timers using custom script that don't require Timer global variables and nasty Timer Events. You can then attach a trigger to these timers which will run whenever the timer expires.

One function allows you to start a timer and link a caster unit, target unit, and Trigger to it. When the timer expires you're able to reference the caster and the target inside of your chosen Trigger using the TS_Caster and TS_Target variables. I have examples in the map, the Shaman's purge ability kills a unit after a 3 second delay and the Bloodlust ability deals 50 damage per second for 5 seconds.

Another function allows you to start a timer which has no links to anything. This one is extremely basic but could serve useful in certain situations. I use it in the map to respawn a Shaman when it's killed after a 2 second delay.

There's still room for many more additions as this is pretty barebones but I didn't really intend for you to use this system in it's current state anyway. It's more so a proof of concept and a way to approach coding in a way that GUI'ers may prefer. Maybe I'll keep adding to it if GUI'ers out there find it useful. And maybe something like this already exists...

Edit: Added a new version of the timer system that changes the syntax around and adds a new function for single unit timers.

Uncle I can't thank you enough for making this system, it was invaluable for me. Without it I don't know how would I've introduced myself to coding. When I downloaded GUI Timer System 3 I could barely understand the code, but after analyzing a lot, I can understand everything well and have the necessary skills to modify it, add new features or even create code for something unrelated.

That being said, I'm afraid there are some mistakes in the system. I fixed the mistakes and attached the map in this post (hopefully I'm not wrong!).

This is the trigger Example1 of Gui Timer System 3
  • Example1
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Bloodlust
    • Actions
      • -------- call TS_StartTwoUnits( unit source, unit target, real interval, boolean repeating, integer key, trigger trig ) --- Don't forget to prefice your trigger name with "gg_trg_" --------
      • Custom script: call TS_StartTwoUnits( GetTriggerUnit(), GetSpellTargetUnit(), 1.00, true, udg_Key_DoT, gg_trg_Example1Callback )
      • Set VariableSet CV = (Custom value of (Target unit of ability being cast))
      • Set VariableSet DoT_Ticks[CV] = 0
This is the trigger Example1Callback of Gui Timer System 3
  • Example1Callback
    • Events
    • Conditions
    • Actions
      • -------- Variables used in this function: --------
      • -------- TS_Source = Casting unit. --------
      • -------- TS_Target = Target unit of ability being cast. --------
      • -------- If your timer is repeating then you must destroy it using "Custom script: call TS_Clear" when you're done with it. --------
      • -------- --------
      • Set VariableSet CV = (Custom value of TS_Target)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (TS_Target has buff Bloodlust) Equal to True
          • DoT_Ticks[CV] Less than or equal to 5
        • Then - Actions
          • Special Effect - Create a special effect attached to the chest of TS_Target using Abilities\Weapons\LordofFlameMissile\LordofFlameMissile.mdl
          • Special Effect - Destroy (Last created special effect)
          • Unit - Cause TS_Source to damage TS_Target, dealing 50.00 damage of attack type Spells and damage type Normal
          • Set VariableSet DoT_Ticks[CV] = (DoT_Ticks[CV] + 1)
        • Else - Actions
          • Custom script: call TS_Clear()
There's a minor mistake in Example1Callback. According to the trigger comment this trigger does the following: "The caster deals 50 damage per second to the target for 5 seconds.". It is true that it does 50 damage per second for 5 seconds; BUT, the reason it functions like that is because the buff lasts 5 seconds, not because the trigger works that way. This can be easily tested; if you increase the duration of Bloodlust you will see that it does 50 damage per second for 6 seconds. To fix this you just have to move the next action outside of the if:
  • Set VariableSet DoT_Ticks[CV] = (DoT_Ticks[CV] + 1)
Here is the solution:

  • Example1Callback
    • Events
    • Conditions
    • Actions
      • -------- Variables used in this function: --------
      • -------- TS_Source = Casting unit. --------
      • -------- TS_Target = Target unit of ability being cast. --------
      • -------- If your timer is repeating then you must destroy it using "Custom script: call TS_Clear" when you're done with it. --------
      • -------- --------
      • Set VariableSet CV = (Custom value of TS_Target)
      • Set VariableSet DoT_Ticks[CV] = (DoT_Ticks[CV] + 1)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (TS_Target has buff Bloodlust) Equal to True
          • DoT_Ticks[CV] Less than or equal to 5
        • Then - Actions
          • Special Effect - Create a special effect attached to the chest of TS_Target using Abilities\Weapons\LordofFlameMissile\LordofFlameMissile.mdl
          • Special Effect - Destroy (Last created special effect)
          • Unit - Cause TS_Source to damage TS_Target, dealing 50.00 damage of attack type Spells and damage type Normal
        • Else - Actions
          • Custom script: call TS_Clear()



"In "GUI Timer System 3" the parameter "key" is an integer that you can use in order to prevent your timers from stacking. A key value of 0 will allow stacking. Keys above 0 will not. No two keys should share a value."
In your trigger Example1 the mistake is that when the key is set to 0 the stacking does not function properly. This is because your trigger Example1 does the following when a unit casts bloodlust:
  • Set VariableSet CV = (Custom value of (Target unit of ability being cast))
  • Set VariableSet DoT_Ticks[CV] = 0
This means that DoT_Ticks[CV] will be 0 again. The trigger Example1Callback will deal damage until DoT_Ticks[CV] is less than or equal to 5.
This means that the number of ticks will be set to 5 if you cast it again. This will ignore the number of ticks remaining from the first time you casted bloodlust. It won't matter if your first bloodlust has done 1 tick of damage or 4 ticks of damage, the ticks will be set to 5, instead of: "5 + Remaining ticks from first cast".

Instead of using only the custom value to track the ticks that will be done, use the hashtable to track the ticks that will be done. This will work because hashtables are 2D arrays. The integers that will be used will be the Id of the timer doing the ticks and the custom value of the target unit. The Id of the timer will be the parentKey and the custom value of the unit will be the childKey.
Here is the trigger Example1 fixed:
  • Example1Edited
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Bloodlust
    • Actions
      • -------- call TS_StartTwoUnits( unit source, unit target, real interval, boolean repeating, integer key, trigger trig ) --- Don't forget to prefice your trigger name with "gg_trg_" --------
      • Custom script: call TS_StartTwoUnits( GetTriggerUnit(), GetSpellTargetUnit(), 1.00, true, udg_Key_DoT, gg_trg_Example1Callback )
      • Set VariableSet TS_Timer = (Key (Last started timer).)
      • Set VariableSet CV = (Custom value of (Target unit of ability being cast))
      • Hashtable - Save 0 as CV of TS_Timer in TS_Hash.
The integer TS_Timer is set to the Id of Last started timer, which is the timer that the function "TS_StartTwoUnits" starts when bloodlust is casted. The integer CV remains as the custom value of the target unit.


JASS:
function TS_StartTwoUnits takes unit source, unit target, real interval, boolean repeating, integer key, trigger trig returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)

    // you can link the timer to the target if you provide a unique key > 0
    if (key > 0) then
        call TS_ClearParent(GetHandleId(target), key)
        call SaveTimerHandle(udg_TS_Hash, GetHandleId(target), key, t)
    endif
  
    // save to hashtable
    call SaveUnitHandle(udg_TS_Hash, id, 0, source)
    call SaveUnitHandle(udg_TS_Hash, id, 1, target)
    call SaveBoolean(udg_TS_Hash, id, 2, repeating)
    call SaveTriggerHandle(udg_TS_Hash, id, 3, trig)

    // start timer
    call TimerStart(t, interval, repeating, function TS_StartTwoUnits_Expire)
  
    // clean up memory leaks
    set t = null
endfunction

Now we modify the trigger Example1Callback so that it uses hashtable instead of custom value to track the ticks:
  • Example1CallbackEdited
    • Events
    • Conditions
    • Actions
      • -------- Variables used in this function: --------
      • -------- TS_Source = Casting unit. --------
      • -------- TS_Target = Target unit of ability being cast. --------
      • -------- If your timer is repeating then you must destroy it using "Custom script: call TS_Clear" when you're done with it. --------
      • -------- --------
      • Set VariableSet TS_Timer = (Key (Expiring timer).)
      • Set VariableSet CV = (Custom value of TS_Target)
      • Hashtable - Save ((Load CV of TS_Timer from TS_Hash.) + 1) as CV of TS_Timer in TS_Hash.
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (TS_Target has buff Bloodlust) Equal to True
          • (Load CV of TS_Timer from TS_Hash.) Less than or equal to 5
        • Then - Actions
          • Special Effect - Create a special effect attached to the chest of TS_Target using Abilities\Weapons\LordofFlameMissile\LordofFlameMissile.mdl
          • Special Effect - Destroy (Last created special effect)
          • Unit - Cause TS_Source to damage TS_Target, dealing 50.00 damage of attack type Spells and damage type Normal
        • Else - Actions
          • Custom script: call TS_Clear()
With the triggers Example1Edited and Example1CallbackEdited the number of ticks that will be done when you cast bloodlust a second time will be 5 + the remaining ticks from first cast. It will function properly no matter how many times you stack bloodlust.
The variable DoT_Ticks is no longer used, so, it is deleted.


The function TS_ClearParent has a memory leak and a line of code that does nothing.

TS_ClearParent has the next line of code: "call RemoveSavedInteger(udg_TS_Hash, parent, key)". The issue here is that there is no saved integer in there. What is saved in "(udg_TS_Hash, parent, key)" is a timer Handle. The functions TS_ClearOneUnit and TS_ClearTwoUnits save it. This timer handle does not gets flushed, so, it leaks.
JASS:
function TS_ClearParent takes integer parent, integer key returns nothing
    local timer t = LoadTimerHandle(udg_TS_Hash, parent, key)
    if (t == null) then
        return
    endif

    call RemoveSavedInteger(udg_TS_Hash, parent, key)
    call FlushChildHashtable(udg_TS_Hash, GetHandleId(t))
    call PauseTimer(t)
    call DestroyTimer(t)
    set t = null
endfunction
JASS:
function TS_StartOneUnit takes unit source, real interval, boolean repeating, integer key, trigger trig returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)

    // you can link the timer to the source if you provide a unique key > 0
    if (key > 0) then
        call TS_ClearParent(GetHandleId(source), key)
        call SaveTimerHandle(udg_TS_Hash, GetHandleId(source), key, t)
    endif
   
    // save to hashtable
    call SaveUnitHandle(udg_TS_Hash, id, 0, source)
    call SaveBoolean(udg_TS_Hash, id, 1, repeating)
    call SaveTriggerHandle(udg_TS_Hash, id, 2, trig)

    // start timer
    call TimerStart(t, interval, repeating, function TS_StartOneUnit_Expire)
   
    // clean up memory leaks
    set t = null
endfunction
JASS:
function TS_StartTwoUnits takes unit source, unit target, real interval, boolean repeating, integer key, trigger trig returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)

    // you can link the timer to the target if you provide a unique key > 0
    if (key > 0) then
        call TS_ClearParent(GetHandleId(target), key)
        call SaveTimerHandle(udg_TS_Hash, GetHandleId(target), key, t)
    endif
   
    // save to hashtable
    call SaveUnitHandle(udg_TS_Hash, id, 0, source)
    call SaveUnitHandle(udg_TS_Hash, id, 1, target)
    call SaveBoolean(udg_TS_Hash, id, 2, repeating)
    call SaveTriggerHandle(udg_TS_Hash, id, 3, trig)

    // start timer
    call TimerStart(t, interval, repeating, function TS_StartTwoUnits_Expire)
   
    // clean up memory leaks
    set t = null
endfunction

To flush the timer handle we add in TS_ClearParent the next line of code: "call RemoveSavedHandle(udg_TS_Hash, parent, key)". We remove the following line of code because it does nothing: "call RemoveSavedInteger(udg_TS_Hash, parent, key)". Here is TS_ClearParentFixed:
JASS:
function TS_ClearParentFixed takes integer parent, integer key returns nothing
    local timer t = LoadTimerHandle(udg_TS_Hash, parent, key)
    if (t == null) then
        return
    endif

    //function added:
    call RemoveSavedHandle(udg_TS_Hash, parent, key)

    //function removed:
    //call RemoveSavedInteger(udg_TS_Hash, parent, key)


    call FlushChildHashtable(udg_TS_Hash, GetHandleId(t))
    call PauseTimer(t)
    call DestroyTimer(t)
    set t = null
endfunction




Mistake 4 derives from mistake 3. The functions TS_StartOneUnit and TS_StartTwoUnits save a timer handle which never gets flushed. To solve this, the functions TS_StartOneUnit, TS_StartTwoUnits, TS_StartOneUnit_Expire and TS_StartTwoUnits_Expire need to be edited. TS_StartOneUnit and TS_StartTwoUnits will be modified to save the integer "key" in the hashtable TS_Hash. TS_StartOneUnit_Expire nad TS_StartTwoUnits_Expire will be modified to flush the timer handle.
JASS:
function TS_StartOneUnitFixed takes unit source, real interval, boolean repeating, integer key, trigger trig returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)

    // you can link the timer to the source if you provide a unique key > 0
    if (key > 0) then
        call TS_ClearParent(GetHandleId(source), key)
        call SaveTimerHandle(udg_TS_Hash, GetHandleId(source), key, t)
    endif
    
    // save to hashtable
    call SaveUnitHandle(udg_TS_Hash, id, 0, source)
    call SaveBoolean(udg_TS_Hash, id, 1, repeating)
    call SaveTriggerHandle(udg_TS_Hash, id, 2, trig)

   // function added
    call SaveInteger(udg_TS_Hash, id, 4, key)

    // start timer
    call TimerStart(t, interval, repeating, function TS_StartOneUnit_Expire)
    
    // clean up memory leaks
    set t = null
endfunction
JASS:
function TS_StartTwoUnitsFixed takes unit source, unit target, real interval, boolean repeating, integer key, trigger trig returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)

    // you can link the timer to the target if you provide a unique key > 0
    if (key > 0) then
        call TS_ClearParent(GetHandleId(target), key)
        call SaveTimerHandle(udg_TS_Hash, GetHandleId(target), key, t)
    endif
    
    // save to hashtable
    call SaveUnitHandle(udg_TS_Hash, id, 0, source)
    call SaveUnitHandle(udg_TS_Hash, id, 1, target)
    call SaveBoolean(udg_TS_Hash, id, 2, repeating)
    call SaveTriggerHandle(udg_TS_Hash, id, 3, trig)

    //function added
    call SaveInteger(udg_TS_Hash, id, 4, key)

    // start timer
    call TimerStart(t, interval, repeating, function TS_StartTwoUnits_Expire)
    
    // clean up memory leaks
    set t = null
endfunction
JASS:
function TS_StartOneUnit_ExpireFixed takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local trigger trig = LoadTriggerHandle(udg_TS_Hash, id, 2)
    local boolean repeating = LoadBoolean(udg_TS_Hash, id, 1)
    //local added
    local integer key = LoadInteger(udg_TS_Hash, id, 4)

    set udg_TS_Source = LoadUnitHandle(udg_TS_Hash, id, 0)

    // run the GUI trigger
    call TriggerExecute(trig)

    // the timer is not repeating so destroy it
    if (repeating == false) then
        call FlushChildHashtable(udg_TS_Hash, id)
    //function added
        call RemoveSavedHandle(udg_TS_Hash, GetHandleId(udg_TS_Source), key)

        call PauseTimer(t)
        call DestroyTimer(t)
    endif

    // clean up memory leaks
    set t = null
    set trig = null
endfunction
JASS:
function TS_StartTwoUnits_ExpireFixed takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local trigger trig = LoadTriggerHandle(udg_TS_Hash, id, 3)
    local boolean repeating = LoadBoolean(udg_TS_Hash, id, 2)
    //local added
    local integer key = LoadInteger(udg_TS_Hash, id, 4)
    set udg_TS_Source = LoadUnitHandle(udg_TS_Hash, id, 0)
    set udg_TS_Target = LoadUnitHandle(udg_TS_Hash, id, 1)

    // run the GUI trigger
    call TriggerExecute(trig)

    // the timer is not repeating so destroy it
    if (repeating == false) then
        call FlushChildHashtable(udg_TS_Hash, id)
    //function added
        call RemoveSavedHandle(udg_TS_Hash, GetHandleId(udg_TS_Target), key)

        call PauseTimer(t)
        call DestroyTimer(t)
    endif

    // clean up memory leaks
    set t = null
    set trig = null
endfunction

We still have an issue. The function that flushes the timer in TS_StartOneUnit_Expire and TS_StartTwoUnits_Expire is inside an if. It will only get flushed when the condition (the timer is repeating) is false. When this condition is true, this system uses the function TS_Clear to destroy the timer. Now we have to edit TS_Clear to flush the timer.
JASS:
function TS_ClearFixed takes nothing returns nothing
    local timer t = GetExpiredTimer()

    //locals added
    local integer id = GetHandleId(t)
    local unit source = LoadUnitHandle(udg_TS_Hash, id, 0)
    local unit target = LoadUnitHandle(udg_TS_Hash, id, 1)

    //Conditions and functions added
    if source != null then
    call RemoveSavedHandle(udg_TS_Hash, GetHandleId(source), key)
    endif
    if target != null then
    call RemoveSavedHandle(udg_TS_Hash, GetHandleId(target), key)
    endif


    call FlushChildHashtable(udg_TS_Hash, GetHandleId(t))
    call PauseTimer(t)
    call DestroyTimer(t)
    set t = null
endfunction





I have some questions that might be too basic. I hope you can help me. Sorry if I'm asking too many questions.

Here is the function TS_StartTwoUnits_Expire without being edited.

JASS:
function TS_StartTwoUnits_Expire takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local trigger trig = LoadTriggerHandle(udg_TS_Hash, id, 3)
    local boolean repeating = LoadBoolean(udg_TS_Hash, id, 2)
    set udg_TS_Source = LoadUnitHandle(udg_TS_Hash, id, 0)
    set udg_TS_Target = LoadUnitHandle(udg_TS_Hash, id, 1)

    // run the GUI trigger
    call TriggerExecute(trig)

    // the timer is not repeating so destroy it
    if (repeating == false) then
        call FlushChildHashtable(udg_TS_Hash, id)
        call PauseTimer(t)
        call DestroyTimer(t)
    endif

    // clean up memory leaks
    set t = null
    set trig = null
endfunction


Why only set t = null and trig = null? What about the other local variables, won't they leak if they aren't set to null?




How do I change the code to Lua or C#?



In the API there are many functions that are almost exactly equal as some natives. What is the difference between a native and a function?


Here is the function TS_StartOneUnit without being edited.

JASS:
function TS_StartOneUnit takes unit source, real interval, boolean repeating, integer key, trigger trig returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)

    // you can link the timer to the source if you provide a unique key > 0
    if (key > 0) then
        call TS_ClearParent(GetHandleId(source), key)
        call SaveTimerHandle(udg_TS_Hash, GetHandleId(source), key, t)
    endif
   
    // save to hashtable
    call SaveUnitHandle(udg_TS_Hash, id, 0, source)
    call SaveBoolean(udg_TS_Hash, id, 1, repeating)
    call SaveTriggerHandle(udg_TS_Hash, id, 2, trig)

    // start timer
    call TimerStart(t, interval, repeating, function TS_StartOneUnit_Expire)
   
    // clean up memory leaks
    set t = null
endfunction


Exactly what does the next sentence mean?
"// you can link the timer to the source if you provide a unique key > 0"


The function SaveTimerHandle is used to save a timer in a hashtable. The API says that the function returns boolean. How is it possible that it returns a boolean?


Is it really necessary to pause a timer before destroying it?


Here is the function TS_StartTwoUnits_ExpireFixed and the GUI trigger that this function runs. Both are edited.

JASS:
function TS_StartOneUnit_ExpireFixed takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local trigger trig = LoadTriggerHandle(udg_TS_Hash, id, 2)
    local boolean repeating = LoadBoolean(udg_TS_Hash, id, 1)
    //local added
    local integer key = LoadInteger(udg_TS_Hash, id, 4)

    set udg_TS_Source = LoadUnitHandle(udg_TS_Hash, id, 0)

    // run the GUI trigger
    call TriggerExecute(trig)

    // the timer is not repeating so destroy it
    if (repeating == false) then
        call FlushChildHashtable(udg_TS_Hash, id)
    //function added
        call RemoveSavedHandle(udg_TS_Hash, GetHandleId(udg_TS_Source), key)

        call PauseTimer(t)
        call DestroyTimer(t)
    endif

    // clean up memory leaks
    set t = null
    set trig = null
endfunction

  • Example1CallbackEdited
    • Events
    • Conditions
    • Actions
      • -------- Variables used in this function: --------
      • -------- TS_Source = Casting unit. --------
      • -------- TS_Target = Target unit of ability being cast. --------
      • -------- If your timer is repeating then you must destroy it using "Custom script: call TS_Clear" when you're done with it. --------
      • -------- --------
      • Set VariableSet TS_Timer = (Key (Expiring timer).)
      • Set VariableSet CV = (Custom value of TS_Target)
      • Hashtable - Save ((Load CV of TS_Timer from TS_Hash.) + 1) as CV of TS_Timer in TS_Hash.
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (TS_Target has buff Bloodlust) Equal to True
          • (Load CV of TS_Timer from TS_Hash.) Less than or equal to 5
        • Then - Actions
          • Special Effect - Create a special effect attached to the chest of TS_Target using Abilities\Weapons\LordofFlameMissile\LordofFlameMissile.mdl
          • Special Effect - Destroy (Last created special effect)
          • Unit - Cause TS_Source to damage TS_Target, dealing 50.00 damage of attack type Spells and damage type Normal
        • Else - Actions
          • Custom script: call TS_Clear()

Function TS_StartTwoUnits_Expire and trigger trig are large. Isn't it inefficient that the whole function and trigger is ran every time the timer expires?
My method leaks events, but it uses much more less execution of functions and triggers. This makes me believe (perhaps I'm wrong) that my method is more effiicient.

BloodPactTriggers.png


  • CreateHashtable1
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set VariableSet Hashtable1 = (Last created hashtable)

  • BloodPactCasted
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Blood Pact (Skeleton Warrior)
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey0[(Custom value of (Triggering unit))] Equal to 0
        • Then - Actions
          • Set VariableSet BloodPactUnit[(Custom value of (Triggering unit))] = (Triggering unit)
          • Custom script: set udg_BloodPactTimer[GetUnitUserData(GetTriggerUnit())]=CreateTimer()
          • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
          • Trigger - Add to DamageBloodPactUnit <gen> the event (Time - (Last started timer) expires)
          • Hashtable - Save (Custom value of (Triggering unit)) as 1 of (Key (Last started timer).) in Hashtable1.
          • Set VariableSet BloodPactKey0[(Custom value of (Triggering unit))] = 1
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • BloodPactKey0[(Custom value of (Triggering unit))] Equal to 1
          • BloodPactKey1[(Custom value of (Triggering unit))] Equal to 0
        • Then - Actions
          • Countdown Timer - Start BloodPactTimer[(Custom value of (Triggering unit))] as a One-shot timer that will expire in 1.00 seconds
        • Else - Actions

  • DamageBloodPactUnit
    • Events
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (BloodPactUnit[(Load 1 of (Key (Expiring timer).) from Hashtable1.)] has buff Blood Pact (SkeletonWarrior)) Equal to True
        • Then - Actions
          • Countdown Timer - Start (Expiring timer) as a One-shot timer that will expire in 1.00 seconds
          • Unit - Cause Necropolis (Page 1) 1486 <gen> to damage BloodPactUnit[(Load 1 of (Key (Expiring timer).) from Hashtable1.)], dealing
          • Set VariableSet BloodPactKey1[(Load 1 of (Key (Expiring timer).) from Hashtable1.)] = 1
        • Else - Actions
          • Set VariableSet BloodPactKey1[(Load 1 of (Key (Expiring timer).) from Hashtable1.)] = 0
I'm trying to recycle the events so that they don't leak, but I haven't been successful. Shall I succeed I will post it in this thread.







As many people do, I plan to put in my map a special section thanking to people who helped make the map. Uncle, since you have helped me so much in several threads, you definitely have to be in an even more special section, lol!
 

Attachments

  • GUI Timer System 3 Edited by Paragonhugope.w3m
    25.8 KB · Views: 7

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
Nice job figuring it all out, I'm glad I could help.

I imagine there's some leaks and mistakes in there, I threw it together rather hastily.

To answer your questions:
1) Integers, Reals, Strings, and Booleans don't leak so there's no reason to null them.

2) Lua can be found in -> Scenario/Map Options/Scripting Language. I have a link in my signature for getting started.

3) Natives are functions. If you're talking about the BJ functions, they're basically altered versions of functions that are more GUI friendly
but often less efficient. There's also GUI limitations that are accounted for. For example, GUI uses Points (Locations) to get positions in the game world. Jass/Lua can reference x/y coordinates directly, which means you can bypass the need for a Point (Location) and save yourself some unnecessary code/object generation.

4) The Key is what determines whether a Timer will restart or Start a new one. If you Set the Key to a value > 0 then it will tell the system to restart any existing timer associated with the Source (unit) and that Key, if it can find one that is. This Key system has some MUI issues and requires all Keys to be unique, I addressed this in a newer system I made.

5) You could write your code to return anything you wanted. I'm not certain what it's return value is supposed to represent but maybe the function returns True/False depending on if it actually managed to Save the Timer or not. Or maybe True/False depending on whether the Timer handle existed in the first place.

6) I've experienced some weird behavior before like Timers lasting a moment too long when not Pausing before Destroying.

7) Inefficient? Probably, but I was going for ease of use over efficiency. That's why I wanted 1 line Custom scripts for starting the timers and little to no work needed inside of your Expiration trigger aside from calling TS_Clear() on Repeating timers.


Here's a newer version of the system I made a couple of weeks ago. You can find it at the very bottom of the thread:
I fixed some issues that were bothering me, although I may have missed some of your fixes. I also tried to make it a bit more GUI friendly since I know the Custom script scares a lot of people off.
 
Last edited:
Status
Not open for further replies.
Top