• Check out the results of the Techtree Contest #19!
  • Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!

[Trigger] Show remaining time for multiple summoned buildings [Solved]

Level 4
Joined
Jul 5, 2022
Messages
22
Hi everyone! Please help me to solve an issue with a trigger that shows remaining time left for summoned buildings.

As far as I know, there's no way to enable default expiration bar for summoned units if they are marked as buildings (Stats - Is a Building: True). So I figured out that I can show remaining timer with a simple Floating Text in combination with a Countdown Timer. If I have only 1 building — it works. But for my map there can be a total of 8 buildings (based on cooldown and duration of abilities) and I need to show expiration text for every one of them.

So far I made two triggers that work only for the first building, but breaks on others:
  • PeonBuild
    • Events
      • Unit - A unit enters (Playable map area)
    • Conditions
      • Or - Any (Conditions) are true
        • Conditions
          • (Unit-type of (Triggering unit)) Equal to Watch Tower
          • (Unit-type of (Triggering unit)) Equal to Burrow
    • Actions
      • Set VariableSet tmp_unit = (Triggering unit)
      • Set VariableSet tmp_player = (Owner of tmp_unit)
      • Unit Group - Add (Triggering unit) to UGRP_BuildingsGoup
      • Custom script: set bj_wantDestroyGroup = true
      • Unit Group - Pick every unit in (Units owned by tmp_player of type Peon) and do (Actions)
        • Loop - Actions
          • Set VariableSet tmp_int = (Level of Duration Upgrade for (Picked unit))
      • Set VariableSet tmp_int = (60 + (20 x tmp_int))
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of tmp_unit) Equal to Burrow
        • Then - Actions
          • Unit - Add a (Real(tmp_int)) second Generic expiration timer to tmp_unit // Burrows made from "Build Tiny Castle" ability, so I need to specify timed life for them
        • Else - Actions
          • Animation - Play tmp_unit's stand animation // Towers made from "Serpent Ward" ability, so I need to cancel Building animation
      • For each (Integer A) from 1 to 8, do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (UNIT_PeonBuilding[(Integer A)] is alive) Equal to False
            • Then - Actions
              • Unit - Remove UNIT_PeonBuilding[(Integer A)] from the game // just in case
              • Set VariableSet UNIT_PeonBuilding[(Integer A)] = tmp_unit
              • Countdown Timer - Start TIMER_PeonBuilding[(Integer A)] as a One-shot timer that will expire in (Real(tmp_int)) seconds
              • Floating Text - Create floating text that reads (String(tmp_int)) above UNIT_PeonBuilding[(Integer A)] with Z offset 0.00, using font size 10.00, color (100.00%, 100.00%, 100.00%), and 0.00% transparency
              • Set VariableSet FTXT_BuildingText[(Integer A)] = (Last created floating text)
              • Custom script: exitwhen true
            • Else - Actions
      • Trigger - Turn on PeonBuildingTimers <gen>
  • PeonBuildingTimers
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of units in UGRP_BuildingsGoup) Equal to 0
        • Then - Actions
          • Trigger - Turn off (This trigger)
          • Skip remaining actions
        • Else - Actions
          • For each (Integer A) from 1 to 8, do (Actions)
            • Loop - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (UNIT_PeonBuilding[(Integer A)] is alive) Equal to True
                • Then - Actions
                  • Floating Text - Change text of FTXT_BuildingText[(Integer A)] to (String((Integer((Remaining time for TIMER_PeonBuilding[(Integer A)]))))) using font size 10.00
                • Else - Actions
                  • Floating Text - Destroy FTXT_BuildingText[(Integer A)]
                  • Unit Group - Remove UNIT_PeonBuilding[(Integer A)] from UGRP_BuildingsGoup.

So when I build my first building it shows the timer correctly. But on the second and following ones it shows 0 after the first tick of the second trigger or shows nothing at all.

My two questions are:
1. Is there any other way to show remaining time for buildings?
2. What am I doing wrong with the triggers? Apparently the problem is in the first one (since the timer works correctly for the first building), but I can't figure it out.
 
Last edited:
That's because you're using primitive gui crutches instead of YDWE gui or just Jass, blessed by god.
The correct way to implement things like this is to use a Hashtable, not global arrays and loops.
If you had done the right thing, using any of the methods listed, you would have never encountered this problem.

You wanted to know what the problem was? Answer:
Each created object has its own Handle id, this allows you, for example, to distinguish two identical units from each other.
So, in the first trigger, when you create a new unit, you also create a Text Tag for it to display the "time".
Text Tag gets a Handle id, for example, 300001, then you create another Text Tag, he gets the next number - 300002.
You write them in global variables:
FTXT_BuildingText[1] = 300001
FTXT_BuildingText[2] = 300002

This way you save not the object itself into a variable, but its Handle id, that is, a reference to it.
Then in the second trigger, after the units die, you delete their Text Tags, which causes their Handle ids to be freed up, so these numbers can be occupied again by new Text Tags.

Then you create a new unit and Text Tag, this Text Tag takes a free number, for example our old 300001.
Then the second trigger-timer is triggered, in which you always try to remove Text Tags from the array when their units not alive.
But since you do not clear the entry in the array after destroying the Text Tag, they still store old references.
As a result, the old Handle Id from the array is sent to the Text Tag deletion function, and since this number is already occupied by the new Text Tag, the function deletes it.

If after deleting the Text Tag, you also delete it from the array, everything would be fine, of course if default gui can be named fine.
But there is also good news, soon you will see your best friend "custom code" again, because otherwise you will not be able to set variable Text Tag = null/no text tag.
You could figure out the problem yourself, but you don't know what a Handle id is and you don't know how to work with a Hashtable, because default gui = a dead end, there's 0 development and no answers, only endless questions.
 
But since you do not clear the entry in the array after destroying the Text Tag, they still store old references.
As a result, the old Handle Id from the array is sent to the Text Tag deletion function, and since this number is already occupied by the new Text Tag, the function deletes it.
Will it help, if I null the corresponding array item calling custom script?

Something like this (not sure, how to do it correctly):
  • Custom script: set udg_FTXT_BuildingText[GetForLoopIndexB()] = null


UPD:
And no, it seems that the problem is not with the deletion of something (or not only with it), since the second building (before something gets deleted) doesn't work:
1756122807672.png

It shows correct time at start, but when the ticking begins it shows 0 until the building is gone.
 
Last edited:
There's nothing wrong with your triggers as far as I can see. Most likely you did not set proper array size for TIMER_PeonBuilding[].
Timer arrays need to have size specified - size determines how many timers are pre-created for you. So a size 8 means first 8 indices in TIMER_PeonBuilding will be initialized with timer objects.

The issue you face is basically this:
  • You create first building
  • It starts TIMER_PeonBuilding[1], which is always initialized (default array size is 1)
  • Periodic trigger tries to get remaining time from TIMER_PeonBuilding[1] and succeeds, showing correct time in floating text

  • You create second/third/... building
  • It starts timers TIMER_PeonBuilding[2/3/...], but currently under those indices there are no timers, so this fails and does not do anything
  • Periodic trigger attempts to read remaining time from TIMER_PeonBuilding[2/3/...], but as there are no timers assigned under those indices, the returned value is default value for 'real', which is 0.00

Why second/third/.. building shows correct number after their creation and then zero afterwards is because when you create floating text, you do not show remaining time of a timer, you show precomputed value from tmp_int.
 
Last edited:
Most likely you did not set proper array size for TIMER_PeonBuilding[].
That is absolutely true! Thank you so much!

Lesson from this: never edit array size from the trigger screen — always open variables screen (Ctrl+B) 🤣
For some stupid reason (and I always forget about it) array size is not modified if edited from here:
1756139399824.png


Don't make stupid mistakes and open Variables window instead:
1756139433536.png



Solved!
 
I noticed too that it does not work properly - just writing that number while keeping the input box focused will not update the value. You need to unfocus it by for example opening the variable type dropdown. Probably the safest thing to do is click into the variable name input box - you cannot change values there, but it will unfocus the Size input box :D Maybe the variable editor is really the safest choice
 
Back
Top