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

How to categorize multiple buildings as one

Status
Not open for further replies.
Level 5
Joined
Jul 14, 2014
Messages
115
[Solved] How to categorize multiple buildings as one

So I'm working on an AI. Now I want the AI to always keep a certain amount of builders. Here's the trigger (It's very crude):


  • Events
    • Time - Every 10.00 seconds of game time
  • Conditions
    • (Number of units in (Units owned by Player 1 (Red) of type Worker)) less than 2
  • Actions
    • Unit Group - Pick every unit in (Units owned by Player 1 (Red) of type Capital) and do (Actions)
      • Loop - Actions
        • Unit - Order (Picked unit) to train/upgrade to a Worker


Now all would be fine if the capital wouldn't have any upgrades, but it does. So when upgrading I still want this trigger to work instead of checking if the AI has upgraded, then making another trigger, etc. etc. etc.

Now I've figured that I could assign all of the capital's upgrades to some kind of category and then pick all units of this category and order them to train a worker etc. Is that possible?
 
Last edited:
Level 12
Joined
May 22, 2015
Messages
1,051
The easiest way to get around this is to see what:
  • trigger
  • Units owned by Player 1 (Red) of type Capital
is doing.

It is basically checking all the units in the map to see if they are owned by player 1 and are this unit type called "Capital".

The way you can do this differently is by checking if they are owned by player 1 and are the right unit type inside the loop. I updated the trigger below to help you see what I mean:


  • Events
    • Time - Every 10.00 seconds of game time
  • Conditions
    • (Number of units in (Units owned by Player 1 (Red) of type Worker)) less than 2
  • Actions
    • Unit Group - Pick every unit in (Units in <Playable map area>) and do (Actions)
      • Loop - Actions
        • If (Actions)
          • Conditions
            • (Owner of (Picked Unit)) is equal to Player 1 (Red)
            • Or
              • Unit type of (Picked Unit) is equal to Capital
              • Unit type of (Picked Unit) is equal to Capital Level 2
          • Then - Actions
            • Unit - Order (Picked unit) to train/upgrade to a Worker
          • Else - Actions


Capital Level 2 is what I decided to use for your next level town hall (don't know the name of it). Notice that it is now looping over all the units in the map, but it is making the same checks that the "Units owned by Player 1 (Red) of type Capital" would make before doing anything.

You can do this instead of using the built-in filtering group functions because you have a lot more control over it. It is also way easier to edit later if the filter needs to change (such as a new town hall tier or something).
 
Level 5
Joined
Jul 14, 2014
Messages
115
A unit group or an indexed unit variable.

I don't see how you could place all town hall tiers into a unit group if they haven't existed on the map yet /:

By indexed unit variable you mean an array? That actually sounds alright, but the way SAUS suggested seems better with no vars used. Still thanks for your reply.

The easiest way to get around this is to see what:
  • trigger
  • Units owned by Player 1 (Red) of type Capital
is doing.

It is basically checking all the units in the map to see if they are owned by player 1 and are this unit type called "Capital".

The way you can do this differently is by checking if they are owned by player 1 and are the right unit type inside the loop. I updated the trigger below to help you see what I mean:


  • Events
    • Time - Every 10.00 seconds of game time
  • Conditions
    • (Number of units in (Units owned by Player 1 (Red) of type Worker)) less than 2
  • Actions
    • Unit Group - Pick every unit in (Units in <Playable map area>) and do (Actions)
      • Loop - Actions
        • If (Actions)
          • Conditions
            • (Owner of (Picked Unit)) is equal to Player 1 (Red)
            • Or
              • Unit type of (Picked Unit) is equal to Capital
              • Unit type of (Picked Unit) is equal to Capital Level 2
          • Then - Actions
            • Unit - Order (Picked unit) to train/upgrade to a Worker
          • Else - Actions


Capital Level 2 is what I decided to use for your next level town hall (don't know the name of it). Notice that it is now looping over all the units in the map, but it is making the same checks that the "Units owned by Player 1 (Red) of type Capital" would make before doing anything.

You can do this instead of using the built-in filtering group functions because you have a lot more control over it. It is also way easier to edit later if the filter needs to change (such as a new town hall tier or something).

That seems very simple. Again, sorry for being a complete dumbass but your help is really appreciated. Thanks a lot! (+rep)

Don't forget the leak. You should create a TempGroup then remove it after being used.

Don't worry. I know how to remove these types of leaks but thanks for your reply.
 
Last edited:
Level 37
Joined
Jul 22, 2015
Messages
3,485
Don't forget the leak. You should create a TempGroup then remove it after being used.

Don't worry. I know how to remove these types of leaks but thanks for your reply.

"Unit Group - Pick all units of type Unit Type" has a reference leak that cannot be removed. You will need to use a generic Unit Group function and filter it out with If/Then/Else if you want to remove it properly.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,192
It is meant to be done with dependency equivelents and the AI actions. How WC3 does this I honestly do not know.

In StarCraft II you just declare all the town halls with the same alias and then requirements (which you can evaluate with triggers) will count all of the structure with the same alias. I am guessing WC3 must support this somehow but I do not know if it is only for the actual object editor requirements or if you can extend it to triggers. Town Halls are also special in that they had hard-coded lists to support them.
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
Sorry, I don't exactly understand what you mean by that. Could you provide and example? I'm just a complete noob

Using set bj_wantDestroyGroup = true or call DestroyGroup() will not do anything to get rid of the reference leak the function creates:
  • Custom script: set bj_wantDestroyGroup = true
  • Unit Group - Pick every unit in (Units of type Footman) and do (Actions)
    • Loop - Actions
      • -------- unit group actions --------

If you want to filter out units without having that reference leak, you should do it this way:
  • Custom script: set bj_wantDestroyGroup = true
  • Unit Group - Pick every unit in (Units in (Playable map area) and do (Actions)
    • Loop - Actions
      • Set TempUnit = (Picked unit)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of TempUnit) Equal to Footman
          • -------- easily add more conditions here --------
        • Then - Actions
          • -------- unit group actions --------
        • Else - Actions
          • -------- TempUnit did not pass filter; do nothing --------

You can alternatively do it this way, but I don't recommend it because of how tedious it will be to add/remove filters, horrific readability, and the unneccesary (Matching unit) function calls:
  • Set TempGroup = (Units in (Playable map area) matching ((Unit-type of (Matching unit)) Equal to Footman))
  • Unit Group - Pick every unit in TempGroup and do (Actions)
    • Loop - Actions
      • -------- unit group actions --------
  • Custom script: call DestroyGroup(udg_TempGroup)
 
Last edited:
Level 5
Joined
Jul 14, 2014
Messages
115
Now one more question. So I've made this trigger:


  • AIBuilders
    • Events
      • Time - Every 30.00 seconds of game time
    • Conditions
      • And - All (Conditions) are true
        • Conditions
          • (Number of units in (Units owned by Player 1 (Red) of type |cffFF8824Builder)) Less than 2
          • (Player 1 (Red) controller) Equal to Computer
    • Actions
      • Set AI_Temp_Group_Capitals[1] = (Units in (Playable map area) owned by Player 1 (Red))
      • Unit Group - Pick every unit in AI_Temp_Group_Capitals[1] and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Or - Any (Conditions) are true
                • Conditions
                  • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Dark Age
                  • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Feudal Age
                  • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Castle Age
                  • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Imperial Age
                  • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Powder Age
            • Then - Actions
              • Unit - Order (Picked unit) to train/upgrade to a |cffFF8824Builder
            • Else - Actions
      • Custom script: call DestroyGroup (udg_AI_Temp_Group_Capitals[1])


To make this work for all AI that are in the game I'd have to copy this trigger 11 times for each slot (which I did already). But I'm looking on how to fit this one trigger to test all slots for an AI and do the actions for each AI slot
 
Level 5
Joined
Jul 14, 2014
Messages
115
You don't need 12 triggers.
Just remove the conditions, and put a Loop in the begining of actions. Loop from 1 to 12.
And instead of player 1, player 2, just do player(loop)

Seems good, but I don't exactly understand what you mean by player(loop). Mind posting a trigger? Again, sorry for being a complete dumbass

EDIT:
Would it look like this? :


  • AIBuilders
    • Events
      • Time - Every 30.00 seconds of game time
    • Conditions
    • Actions
      • For each (Integer A) from 1 to 12, do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • And - All (Conditions) are true
                • Conditions
                  • (Number of units in (Units owned by (Player((Integer A))) of type |cffFF8824Builder)) Less than 2
                  • ((Player((Integer A))) controller) Equal to Computer
            • Then - Actions
              • Set AI_Temp_Group_Capitals[(Integer A)] = (Units in (Playable map area) owned by (Player((Integer A))))
              • Unit Group - Pick every unit in AI_Temp_Group_Capitals[(Integer A)] and do (Actions)
                • Loop - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • Or - Any (Conditions) are true
                        • Conditions
                          • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Dark Age
                          • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Feudal Age
                          • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Castle Age
                          • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Imperial Age
                          • (Unit-type of (Picked unit)) Equal to |cffFF8824Capital|r in Powder Age
                    • Then - Actions
                      • Unit - Order (Picked unit) to train/upgrade to a |cffFF8824Builder
                    • Else - Actions
              • Custom script: call DestroyGroup (udg_AI_Temp_Group_Capitals[1])
            • Else - Actions


If so then what do I do with the custom script?
 
Level 12
Joined
Jan 2, 2016
Messages
973
Yes, that's it, but it will be better if you make your own integer variable, instead of using Integer A or Integer B :p

and when you do so, if your variable is called "Loop" - in the DestroyGroup, use call DestroyGroup(udg_AI_Temp_Group_Capitals[udg_Loop])
- put and udg_ before the variable's name.
 
Level 5
Joined
Jul 14, 2014
Messages
115
Yes, that's it, but it will be better if you make your own integer variable, instead of using Integer A or Integer B :p

and when you do so, if your variable is called "Loop" - in the DestroyGroup, use call DestroyGroup(udg_AI_Temp_Group_Capitals[udg_Loop])
- put and udg_ before the variable's name.

Why would it be better?

I also figured that to remove the custom groups I need this script:

Code:
call DestroyGroup (udg_AI_Temp_Group_Capitals[GetForLoopIndexA()])

notice the index :

Code:
[GetForLoopIndexA()]

is that correct?
 
Level 12
Joined
Jan 2, 2016
Messages
973
If you have a basic knowledge of jass this may help.
Do have in mind that if you don't need sub-groups - you can return a boolean instead of integer, and just put one super-long condition :p

Or you can use IcemanBo's idea, and make a hashtable. In it - save true for each unit-type, that you want to match the conditions, and when doing the matchup - load this boolean and check if it's true or false.
 
Level 5
Joined
Jul 14, 2014
Messages
115
If you have a basic knowledge of jass this may help.
Do have in mind that if you don't need sub-groups - you can return a boolean instead of integer, and just put one super-long condition :p

Or you can use IcemanBo's idea, and make a hashtable. In it - save true for each unit-type, that you want to match the conditions, and when doing the matchup - load this boolean and check if it's true or false.

I have no idea how to code in Jass. The hashtables seem quite complex. Could you provide a trigger?
 
Level 12
Joined
Jan 2, 2016
Messages
973
Well, I don't have a trigger for THIS at the moment, but it looks something like this:
Event - Map Initialization
Actions: Create hashtable
set Table = Last created hashtable
(all the remaining actions require custom script):
call SaveBoolean(udg_Table, 'A001' , 0 , true)
call SaveBoolean(udg_Table, 'A002' , 0 , true)
call SaveBoolean(udg_Table, 'A003' , 0 , true)
etc...
the 'A001/2/3/' are the unit type ID. You can check it in the object editor by pressing Ctrl + D

ALTERNATIVELY:
If you don't want to manually input all the unit-type IDs, you can set a variable to them and it will look like this:
  • Configurations
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set Table = (Last created hashtable)
      • Set U_Type = Archer
      • Custom script: call SaveBoolean(udg_Table, udg_U_Type, 0 , true)
      • Set U_Type = Rifleman
      • Custom script: call SaveBoolean(udg_Table, udg_U_Type, 0 , true)
the 2-nd way is easier (imo), cuz you get the GUI interface when selecting the units and you just need to copy-paste the custom script.

And now for the main trigger:
When you are doing the 'check' - you need to add a "local integer id = GetUnitTypeId(GetEnumUnit())" in the begining of the loop (where you check the unit type)
and then, right after that write "set udg_Temp_Boolean = LoadBoolean(udg_Table, id, 0)"
and for condition in the if, put "Temp_Boolean is equal to true" (this one is GUI)
 
Last edited:
Level 12
Joined
May 22, 2015
Messages
1,051
Now the last question. What if I need to filter around 200 different types of buildings. That would be too laggy. So what do I do?

Hopefully you don't need to make 200 unit types, but I suppose the specific number is just as an example. Anyway, I had this idea a while ago that I am using in quite a few spots in my map.

Step 1:
Make an ability that has no icon (example: Item damage bonus).
Remove any bonuses it applies.
Name it something like "Town Hall" (for this case, but you can name them whatever you want).

Step 2:
Add this ability to all the buildings you want to classify as "Town Hall".

Step 3:
Instead of checking for each building type, just check if the unit has your new ability.

I call them "Ability Tags". You give them the ability only to tag them for your triggers.
 
Level 5
Joined
Jul 14, 2014
Messages
115
Well, I don't have a trigger for THIS at the moment, but it looks something like this:
Event - Map Initialization
Actions: Create hashtable
set Table = Last created hashtable
(all the remaining actions require custom script):
call SaveBoolean(udg_Table, 'A001' , 0 , true)
call SaveBoolean(udg_Table, 'A002' , 0 , true)
call SaveBoolean(udg_Table, 'A003' , 0 , true)
etc...
the 'A001/2/3/' are the unit type ID. You can check it in the object editor by pressing Ctrl + D

ALTERNATIVELY:
If you don't want to manually input all the unit-type IDs, you can set a variable to them and it will look like this:
  • Configurations
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set Table = (Last created hashtable)
      • Set U_Type = Archer
      • Custom script: call SaveBoolean(udg_Table, udg_U_Type, 0 , true)
      • Set U_Type = Rifleman
      • Custom script: call SaveBoolean(udg_Table, udg_U_Type, 0 , true)
the 2-nd way is easier (imo), cuz you get the GUI interface when selecting the units and you just need to copy-paste the custom script.

And now for the main trigger:
When you are doing the 'check' - you need to add a "local integer id = GetUnitTypeId(GetEnumUnit())" in the begining of the loop (where you check the unit type)
and then, right after that write "set udg_Temp_Boolean = LoadBoolean(udg_Table, id, 0)"
and for condition in the if, put "Temp_Boolean is equal to true" (this one is GUI)

Hm. Ok. I will use that. Thanks! (+rep)

Hopefully you don't need to make 200 unit types, but I suppose the specific number is just as an example. Anyway, I had this idea a while ago that I am using in quite a few spots in my map.

Step 1:
Make an ability that has no icon (example: Item damage bonus).
Remove any bonuses it applies.
Name it something like "Town Hall" (for this case, but you can name them whatever you want).

Step 2:
Add this ability to all the buildings you want to classify as "Town Hall".

Step 3:
Instead of checking for each building type, just check if the unit has your new ability.

I call them "Ability Tags". You give them the ability only to tag them for your triggers.

I was not joking or providing that as an example. I literally mean it. I have around 200 unit types for buildings. Sorry but your way would involve me adding this auro to every one of those unit types and that would take way longer than WereElf's way. Thanks for your reply!
 
Status
Not open for further replies.
Top