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

Creating triggers dynamically

Status
Not open for further replies.
Level 12
Joined
May 22, 2015
Messages
1,051
I'll explain what I am thinking first:

I have a hero defense where there are a couple dozen heroes. Some of the triggers are of the form:
Every 0.5 seconds
Update health or mana or ability level etc...

Basically, in order to keep some ability at a level based on one of their attributes or use some custom regeneration for an ability (or whatever various other things), I am running the trigger periodically. I am not sure how detrimental this is to performance, but the amount of these types of triggers will only increase as I add more heroes.

Here's the deal, though. I typically only end up playing with 2-3 players, so 20 or so heroes go unused every game. I had the idea that I could skip making these triggers until later and have them created when a hero is chosen (hero gets chosen - create all relevant triggers).

Is this a good idea?
Is this a waste of my time?
Is there a better way to do this? (I was thinking maybe just adding the events upon hero chosen to the triggers might be less crazy)
 
Level 12
Joined
May 22, 2015
Messages
1,051
I'd suggest simply adding events upon hero being chosen, because making dynamic triggers is not very reasonable in GUI.
Alternatively you can toggle the triggers off and activate when necessary.

I am using JASS now (slowly converting all my old stuff haha), but I figured it would be more obvious for everyone if I worded it as I did. Adding the events seems like it should be good enough, though.

Just so I know, what does turning a trigger off do? I know the trigger won't be run, but is it as good as not having any events for the trigger or no trigger at all?

I want to make my triggers run as fast as possible so I don't make my map really slow as I add more to it.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Also... I am kind of afraid that you are enumerating all units in the map and filter out the specific units.
If you do so, then replace those with a simple group iteration and add the heroes to those groups when they are chosen as well as turning on those triggers.
That way, you will only have the triggers active of which the heroes are in the map and you increase performance by... well... just massive performance increase.

Turning a trigger off is less performing than not having those at all... i assume.
But as you are using JASS, you can simple create the trigger if one of those heroes is chosen...
Even better, you dont even need a trigger at all if you know how timers work.
 
Level 12
Joined
May 22, 2015
Messages
1,051
Also... I am kind of afraid that you are enumerating all units in the map and filter out the specific units.
If you do so, then replace those with a simple group iteration and add the heroes to those groups when they are chosen as well as turning on those triggers.
That way, you will only have the triggers active of which the heroes are in the map and you increase performance by... well... just massive performance increase.

Turning a trigger off is less performing than not having those at all... i assume.
But as you are using JASS, you can simple create the trigger if one of those heroes is chosen...
Even better, you dont even need a trigger at all if you know how timers work.
I'm not looping over all units, just the heroes. They get added to a unit group when they are selected.

I don't think there are any triggers in my map that loop over all units periodically, but I do have a lot of "unit is attacked" events - some of which are entirely hero-specific. There are quite a few units in my map, so the "unit is attacked" event will fire a lot (not exactly sure how often, but a lot). In any case, I think having the unused hero triggers not exist will help at least. The performance isn't that bad, but sometimes the frame rate drops a little bit here and there or there is a small input delay for online games.

Is there a tutorial I can look at for timers?
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
About the frame rate... you should let someone take a look at the map if it is really annoying...
But if you use Wait or Wait Gametime, then you are pretty much doomed to have those delays. They just dont work functionally in online gaming.

For timers... I am not quite sure if there is any tutorial even though there should be one.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
Well, in JASS you can do something like this. Note that although timer systems exist, they usually can't beat this speed. Timer systems are useful when you have several timers that have the same timeout.

I've not tested if this piece of code is without typos, as I just wrote it in a few minutes.
JASS:
function handler takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer tID = GetHandleId(t)
    local integer value = LoadInteger(TimerHash,tID,0)
    //Do stuff here
    set t = null
endfunction

function action takes nothing returns nothing
    local timer t = CreateTimer()
    local integer tID = GetHandleId(t)
    //Save some value(s) to timer
    call SaveInteger(TimerHash,tID,0,value)
    call TimerStart(t,time,true,function handler)
    set t = null
endfunction
 
Level 12
Joined
May 22, 2015
Messages
1,051
About the frame rate... you should let someone take a look at the map if it is really annoying...
But if you use Wait or Wait Gametime, then you are pretty much doomed to have those delays. They just dont work functionally in online gaming.

For timers... I am not quite sure if there is any tutorial even though there should be one.
I have heard waits are inaccurate and generally not good to use, but, unfortunately, my map has been kicking around for years and most of my triggers are old GUI stuff that I am slowly converting over to JASS.

How should I be handling waits in general? Should I be using a hashtable to store durations for things?

As an example, let's say I have an enemy where the hero loses 5 strength whenever they attack this unit but then gains it back after 5 seconds (for this example, ignore the issue of getting stuck at 1 strength and various other things like checking if the attacking unit is a hero).

Also note: I am using JASS for things, but I learned everything using GUI, so I think about triggers that way (and also I am constantly looking up JASS functions - I don't know them off the top of my head). I will just write the triggers in GUI, but if your solution uses JASS, please share as JASS.

Instead of doing:
  • MyTrigger
  • Events
    • Unit - Unit is attacked
  • Conditions
    • Unit type of attacked unit equal to SpecialEnemy
  • Actions
    • Hero - Modify hero attribute of Attacking Unit: subtract 5 strength
    • Wait 5 seconds
    • Hero - Modify hero attribute of Attacking Unit: add 5 strength
I could do:
  • Events
    • Unit - Unit is attacked
  • Conditions
    • Unit type of attacked unit equal to SpecialEnemy
  • Actions
    • Hero - Modify hero attribute of Attacking Unit: subtract 5 strength
    • Hashtable - save real 5 as 0 of key(Attacking Unit) in myHashtable
    • Unit Group - add Attacking Unit to myGroup
and
  • Events
    • Every 0.5 seconds
  • Conditions
  • Actions
    • Unit Group - Pick every unit in myGroup and do:
      • set TempInt = Hashtable - load real 0 of key(Picked Unit) in myHashtable
      • set TempInt = TempInt - 0.5
      • if (TempInt less than or equal to 0)
        • Hero - Modify hero attribute of Attacking Unit: add 5 strength
        • Unit Group - remove Picked Unit to myGroup
        • Hashtable - clear all children for key(Picked Unit) in myHashtable
My first thoughts are that this would be inaccurate by up to 0.5 seconds. It also creates more periodic events, which I can only imagine are not good to have tons of.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
The GUI way of showing things isn't very good for timers, because GUI is trigger-based, while timers are essentially like dynamic triggers in some sense.
About the way you showed, "Pick every unit in group" is very slow, so if you want performance you should rather use dynamic indexing and loop through an array.
Currently your example does use a hashtable, but doesn't use a timer, so it's not really an example of the proposed method at all.
 
Level 12
Joined
May 22, 2015
Messages
1,051
The GUI way of showing things isn't very good for timers, because GUI is trigger-based, while timers are essentially like dynamic triggers in some sense.
About the way you showed, "Pick every unit in group" is very slow, so if you want performance you should rather use dynamic indexing and loop through an array.
Currently your example does use a hashtable, but doesn't use a timer, so it's not really an example of the proposed method at all.
Ya I think maybe I forgot where I was going with my other post haha. I meant to ask how to make it work not using waits in general and if the method I had there was any good.

Your post with the timer code in JASS is perfect for me. I'll be testing that out when I get the chance. Thanks for the help!
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
Well, the design that you used could be quite useful in some cases if you replace ForGroup with a dynamically indexed array. The strength of that design is that you can loop through the array cheaply, so it works well for things like projectile systems where you need to do lots of actions on a group of units.
Timers are more useful in cases where there is no periodic event, because they don't really cost anything(afaik) when they're waiting for their timeout. However, for the sake of responsiveness I still sometimes use timers for periodic events if the timeout is long enough.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Ill explain how timers are used... how they are really used.
(In gui you dont even use timers.)

In JASS you have a simple action called TimerStart(). That action starts a given timer for a given duration and you can even tell if that timer should restart when it expired until it is destroyed.
JASS:
function foo takes nothing returns nothing
    local timer t = CreateTimer()
    call TimerStart(t, 5, false, null)
    
    set t = null
endfunction

Cant be much simpler isnt it?
It starts timer t, for 5 seconds and does not restart when it expires.
That is how GUI uses timers...
Now there is a big difference between GUI and JASS timers.
In GUI, you cannot use that last value which is null in the example.
That value is a callback code.
The callback code is ran when the timer expires.
So in this example:
JASS:
function callback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    
    set t = null
endfunction
function foo takes nothing returns nothing
    local timer t = CreateTimer()
    call TimerStart(t, 5, false, function callback)
    
    set t = null
endfunction
The function callback is ran when that timer expires... EXACTLY 5 in-game seconds after the function foo ran.
You can get the expired timer by calling GetExpiredTimer().
(Always remember to null local variables except string, integer, real and boolean.)

Now... You want to remove and add a number of strength when function foo runs and you want to add it again when function callback runs.
Function foo is simple, but in callback, you have forgotten who that unit was.
(NEVER EVER EVER design your code to work for one unit only. Always base your desig MUI and if that is not possible due to dynamic arrays or MPI functions, then use MPI.)

What we can do is store the unit and the amount of strength added inside a hashtable.
However, hashtables are quite slow and I use a lot of data stored on a timer.
Therefor I have written a kind of Timer indexer which I use to do these kind of things.
(Today I learned how to use hooks and I will use those instead of the manually remove but that doesnt really matter.)
JASS:
library timerIndex
    
    globals
        
        hashtable udg_HandleIndex_Hashtable = InitHashtable()//Can be replaced by table.
        
        //Timers
        integer udg_NextTimerIndex = 0
        boolean array udg_TimerIndex_Occupied
        
    endglobals
    
    
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    //  Timers
    //
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    function CreateTimerIndex takes timer t returns integer
        loop
            set udg_NextTimerIndex = udg_NextTimerIndex + 1
            if udg_NextTimerIndex > 8191 then
                set udg_NextTimerIndex = 1
            endif
            
            exitwhen not udg_TimerIndex_Occupied[udg_NextTimerIndex]
        endloop
        
        set udg_TimerIndex_Occupied[udg_NextTimerIndex] = true
        call SaveInteger(udg_HandleIndex_Hashtable, GetHandleId(t), 0, udg_NextTimerIndex)
        return udg_NextTimerIndex
    endfunction
    function ReleaseTimerIndex takes timer t returns nothing
        local integer id = GetHandleId(t)
        
        set udg_TimerIndex_Occupied[LoadInteger(udg_HandleIndex_Hashtable, id, 0)] = false
        call RemoveSavedInteger(udg_HandleIndex_Hashtable, id, 0)
    endfunction
    function GetTimerIndex takes timer t returns integer
        return LoadInteger(udg_HandleIndex_Hashtable, GetHandleId(t), 0)
    endfunction
    
endlibrary
With that library, I can create a 1-8191 integer for each timer.
Assuming that there will not be more than 8191 timers that use that indexer at once.
It is your choice to use it or not.
It will improve the performance but will not do anything functionally.

Anyway, with that system and a little code, you can make something really awesome.
Just look at this piece of code:
udg_ stuff are global variables.
JASS:
function callback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetTimerIndex(t)
    
    call SetHeroStat(udg_TimedStat_Unit[id], udg_TimedStat_Type[id], GetHeroStatBJ(udg_TimedStat_Type[id], udg_TimedStat_Unit[id], false) - udg_TimedStat_Amount[id])
    set udg_TimedStat_Unit[id] = null //handle pointer leak.
    
    call ReleaseTimerIndex(t)
    call DestroyTimer(t)
    set t = null
endfunction
function AddTimedStat takes unit whichUnit, integer whichStat, integer value, real duration returns nothing
    local timer t = CreateTimer()
    local integer id = CreateTimerIndex(t)
    call TimerStart(t, duration, false, function callback)
    
    //bj_HEROSTAT_STR = Strength
    //bj_HEROSTAT_AGI = Agility
    //bj_HEROSTAT_INT = Intelligence
    
    call SetHeroStat(whichUnit, whichStat, GetHeroStatBJ(whichStat, whichUnit, false) + value)
    
    set udg_TimedStat_Unit[id] = whichUnit
    set udg_TimedStat_Amount[id] = value
    set udg_TimedStat_Type[id] = whichStat
    
    set t = null
endfunction
I assume it works (not looking at negative attributes etc).
You can add a negative value and it will remove that value for the set amount of time.
Because I use my system, the code looks much cleaner as there are no hashtables involved in these actions themselves.

I hope that this was a little bit helpfull and that you can see how usefull timers are.
 
Status
Not open for further replies.
Top