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

Detecting if a Building is Under Construction

Status
Not open for further replies.
Level 14
Joined
Aug 31, 2009
Messages
775
This seems like something that should be simple, and yet I can't find a simple solution.

I want to detect if a building is under construction. That's it.

I looked around online and found a thread on thehelper.net from 2010: Checking if a building is currently under Construction

His method seems really simple, but when I try the method, the "GetUnitAbility(...)" always returns 1 - whether the building is finished or still under construction.


The reason I want to achieve this is because in my map there is a building called a Repair Bay. It automatically searches for nearby damaged buildings, picks one at random, and attempts to heal it at the cost of resources. The problems is that the Repair bay automatically picks buildings which are under construction too (because their health != their max health). It's unnecessary to heal them, as they will get full health the moment their construction is finished anyway.

So - how do I check for if a building has finished construction or not, without having to resort to an annoying workaround (e.g. using custom value when the building enters the map, and changing it on construction finished)?
 
Level 12
Joined
Feb 5, 2018
Messages
521
What about a BUFF like devotion aura?
Unit starts construction - add ability (whatever works the best)
Unit finishes construction - remove ability, remove buff

Healing bay - heal target (conditions have buff then -- nothing, else heal)
 
Level 14
Joined
Aug 31, 2009
Messages
775
This is the same as the workaround, no?
Detecting when it enters, adding ability, detecting when it finishes, remove ability.

My point is to do this without a workaround, so I don't have quite so many triggers and event registries to go through.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,503
Edit: Just saw that you explicitly stated that you don't want to use a Unit Indexer, but you also said "using custom value when the building enters the map", which is totally unnecessary.

You'll have to use triggers.

I would use a Unit Indexer as you can handle this with a single Boolean variable -> isConstructing[] = True/False.

The triggers would be similar to what DoomBlade suggested.

A unit begins construction -> Set Variable isConstructing[custom value of triggering unit] = True

A unit finishes construction -> Set Variable isConstructing[custom value of triggering unit] = False

Then anytime you want to check the state of a structure, you simply plug in it's custom value into isConstructing and you will get True or False. Example:
  • Events:
  • A unit dies
  • Conditions:
  • isConstructing[custom value of triggering unit] Equal to True
  • Actions:
  • Set Variable isConstructing[custom value of triggering unit] equal to False
  • Display text message "A constructing structure has been destroyed"
GUI Unit Indexer 1.4.0.0

Edit: Don't forget to set isConstructing to False when the structure dies. This ensures that the variable is reset for future uses.
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,850
I think the best solution is this:
  • Events
    • Unit - A unit begins a construction
  • Conditions
  • Actions
    • Unit Group - Add Constructing Unit to Unit_Group
  • Events
    • Unit - A unit finishes a construction
  • Conditions
  • Actions
    • Unit Group - Remove Constructed Unit from Unit_Group
  • Events
    • Unit - A unit begins the cast of an ability
  • Conditions
    • Ability being cast Equal to Search
  • Actions
    • Unit Group - Pick Every in Unit_Group and "Do the search"
 
Level 14
Joined
Aug 31, 2009
Messages
775
I mean, you're clearly not reading my first post at all.
I know you can achieve it using that method. My question is - can it be done WITHOUT using multiple events and setting flags / adding to groups?

If not, I'll just use a simplified method of checking when a unit finishes construction, and then adding an ability with no icon. The search will then check for that ability. It's not ideal though, because it adds another event listener, which I'm trying to avoid if possible.

For now at least, I've done it as follows (C# Code):
C#:
            trigger = CreateTrigger();
            TriggerAddAction(trigger, FinishedConstruction);
            for (var i = 0; i < 3; i++)
            {
                TriggerRegisterPlayerUnitEvent(trigger, Player(i), EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, null);
            }


        public static void FinishedConstruction()
        {
            // Needed for repair bay logic.
            var building = GetTriggerUnit();
            if (GetUnitAbilityLevel(building, ABILITY_FINISHED_CONSTRUCTION) == 0)
            {
                UnitAddAbility(building, ABILITY_FINISHED_CONSTRUCTION);
            }
        }

The repairbay then just checks for the "Finished Construction" ability.
Ideally, I'd like to do it without having to create events / triggers specifically for it.
 
Last edited:
The "constructing" could also be set to "false" when building dies, or construction gets canceled.
It might be no problem at first if there are checks for dead unit next to constructing check, but especially at later usage, when also UnitIndexer recylces some indices, then there could come unexpected results, if it still hold "constructing" as "true".
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,503
I mean, you're clearly not reading my first post at all.
I know you can achieve it using that method. My question is - can it be done WITHOUT using multiple events and setting flags / adding to groups?

If not, I'll just use a simplified method of checking when a unit finishes construction, and then adding an ability with no icon. The search will then check for that ability. It's not ideal though, because it adds another event listener, which I'm trying to avoid if possible.

For now at least, I've done it as follows (C# Code):
Code:
            trigger = CreateTrigger();
            TriggerAddAction(trigger, FinishedConstruction);
            for (var i = 0; i < 3; i++)
            {
                TriggerRegisterPlayerUnitEvent(trigger, Player(i), EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, null);
            }


        public static void FinishedConstruction()
        {
            // Needed for repair bay logic.
            var building = GetTriggerUnit();
            if (GetUnitAbilityLevel(building, ABILITY_FINISHED_CONSTRUCTION) == 0)
            {
                UnitAddAbility(building, ABILITY_FINISHED_CONSTRUCTION);
            }
        }

The repairbay then just checks for the "Finished Construction" ability.
Ideally, I'd like to do it without having to create events / triggers specifically for it.
With Lua you can avoid the Unit Indexer entirely and just plug the units handle into the the Index.
Lua:
--Create the table with a default value of False
isConstructing = __jarray(False)

--This would run in response to: EVENT_PLAYER_UNIT_CONSTRUCT_START
local u = GetTriggerUnit()
isConstructing[u] = True
@IcemanBo
True, forgot about that. I'll add it to the post.
 
Last edited:
Level 14
Joined
Aug 31, 2009
Messages
775
With Lua you can avoid the Unit Indexer entirely and just plug the units handle into the the Index.
Lua:
--Create the table with a default value of False
isConstructing = __jarray(False)

--This would run in response to: EVENT_PLAYER_UNIT_CONSTRUCT_START
local u = GetTriggerUnit()
isConstructing[u] = True
@IcemanBo
True, forgot about that. I'll add it to the post.

Wouldn't this create an ever expanding array that eats up more and more memory as buildings are placed? Or am I wrong here?

Assuming you set the index back to false again later, you've now created 2 listeners (Building starts constructing, building cancels / dies) - so you've actually made it worse than the original.

Also, wouldn't it be the reverse implementation? Setting "isConstructed" to be __jarray(false) and then setting it to true when the building finishes construction. The method you described would set it to true the moment construction began, and it would stay true permanently after that (meaning there's no distinction between a building under construction, and one that has finished construction).
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,503
Wouldn't this create an ever expanding array that eats up more and more memory as buildings are placed? Or am I wrong here?
Not exactly sure about the technical side of it, I know Lua will automatically garbage collect but I don't know how that works with a Table. I was told by others to do this instead of using a traditional Unit Indexer, but i'm not sure if performance/efficiency was taken into consideration.

You shouldn't need to set anything to False since the unit handles won't be reused.
 
Level 18
Joined
Jan 1, 2018
Messages
728
Since you're using C# you can use a HashSet<unit> to keep track of buildings that are under construction.
C#:
    public static class ConstructingUnitTracker
    {
        private static readonly HashSet<unit> _constructingUnits = new HashSet<unit>();

        public static void Initialize()
        {
            var triggerAdd = CreateTrigger();
            TriggerAddCondition(triggerAdd, Condition(() =>
            {
                _constructingUnits.Add(GetTriggerUnit());
                return false;
            }));
            
            var triggerRemove = CreateTrigger();
            TriggerAddCondition(triggerRemove, Condition(() =>
            {
                _constructingUnits.Remove(GetTriggerUnit());
                return false;
            }));

            const int PlayerCount = 3;
            for (var i = 0; i < PlayerCount; i++)
            {
                TriggerRegisterPlayerUnitEvent(triggerAdd, Player(i), EVENT_PLAYER_UNIT_CONSTRUCT_START, null);
                TriggerRegisterPlayerUnitEvent(triggerRemove, Player(i), EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, null);
                TriggerRegisterPlayerUnitEvent(triggerRemove, Player(i), EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, null);
            }
        }

        public static bool IsUnitUnderConstruction(unit whichUnit) => _constructingUnits.Contains(whichUnit);
    }

I tried with a static constructor at first, but that only runs when the class gets referenced the first time, so might as well use a method for it.
To use it, just call 'ConstructingUnitTracker.Initialize();' somewhere at map init, and 'ConstructingUnitTracker.IsUnitUnderConstruction(whichUnit);' to check if a building is under construction.

EDIT: Forgot about cases where a building dies or gets removed. For death, you can replace EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL with EVENT_PLAYER_UNIT_DEATH, since cancelling construction also triggers the death event. For remove, I don't think there's an event for that, so you'd probably have to remove the unit from the HashSet manually if you use RemoveUnit anywhere in your code.
 
Last edited:
Status
Not open for further replies.
Top