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

How to keep track of the amount of each unit a player owns?

Status
Not open for further replies.
Level 11
Joined
Jan 23, 2015
Messages
788
  • Number of units in (Units owned by (Player 1 (Red)) matching (Unit-type of (Matching Unit) equal to Footman) and ((Matching Unit) is dead equal to false))
You will need to do this for each unit-type. But don't forget to make a variable for the group and use it when checking, then remove the leak.
 
Level 5
Joined
Jul 14, 2014
Messages
115
  • Number of units in (Units owned by (Player 1 (Red)) matching (Unit-type of (Matching Unit) equal to Footman) and ((Matching Unit) is dead equal to false))
You will need to do this for each unit-type. But don't forget to make a variable for the group and use it when checking, then remove the leak.

So that means I would have to create and integer and set it to the amount of units? But how do I remove the leak. I know it's something like calludg_remove...?
 
Level 12
Joined
May 22, 2015
Messages
1,051
  • Number of units in (Units owned by (Player 1 (Red)) matching (Unit-type of (Matching Unit) equal to Footman) and ((Matching Unit) is dead equal to false))
You will need to do this for each unit-type. But don't forget to make a variable for the group and use it when checking, then remove the leak.

This can get really expensive -> you'd select every unit in the map for every single unit type you have and see if they match that unit type and are owned by the chosen player. This could easily be thousands of iterations (10 unit types with 100 units on the map).

Don't forget that the group count function is O(n) as well (it loops over the whole group to count them).

A faster way would be to use a hashtable so you can directly increment the count in the bucket that matches the unit type ID for each player - looping over the units in the map just once.
Basically would look a little like this inside the loop:
JASS:
set id = GetUnitTypeId(pickedUnit)
set playerId = GetPlayerId(GetOwningPlayer(pickedUnit))
set count = LoadInteger(udg_countTable, playerId, id) + 1
call SaveInteger(udg_countTable, playerId, id, count)

Then you can use the player ID + unit type combo to determine the count of that unit type for that player.

Alternatively, you could store the count somewhere (might be doable with an array with some other setup, but just a hashtable is probably easier). Then check when the units enter the map, you increment the according count similar to the other method. You have to properly detect when units die, though - as well as change teams (if they do that), revive, etc. It's probably the fastest overall, but it would get ugly depending on what happens in your map.
 
Level 5
Joined
Jul 14, 2014
Messages
115
This can get really expensive -> you'd select every unit in the map for every single unit type you have and see if they match that unit type and are owned by the chosen player. This could easily be thousands of iterations (10 unit types with 100 units on the map).

Don't forget that the group count function is O(n) as well (it loops over the whole group to count them).

A faster way would be to use a hashtable so you can directly increment the count in the bucket that matches the unit type ID for each player - looping over the units in the map just once.
Basically would look a little like this inside the loop:
JASS:
set id = GetUnitTypeId(pickedUnit)
set playerId = GetPlayerId(GetOwningPlayer(pickedUnit))
set count = LoadInteger(udg_countTable, playerId, id) + 1
call SaveInteger(udg_countTable, playerId, id, count)

Then you can use the player ID + unit type combo to determine the count of that unit type for that player.

Alternatively, you could store the count somewhere (might be doable with an array with some other setup, but just a hashtable is probably easier). Then check when the units enter the map, you increment the according count similar to the other method. You have to properly detect when units die, though - as well as change teams (if they do that), revive, etc. It's probably the fastest overall, but it would get ugly depending on what happens in your map.

So you're suggesting hashtables? I'm sorry for being such a noob, but could please post a trigger of how it would work? Please bare with me /:
 
So that means I would have to create and integer and set it to the amount of units? But how do I remove the leak. I know it's something like calludg_remove...?

Don't use hashtables.

Use the unitgroup method, but don't use (matching unit) just set a group of units owned by player[(player number)], call the group something like 'tempgroup.' Then pick all units in TempGroup every time a unit enters the playable map (and at 0.10 seconds if you use starting units). This way you don't need to keep looping a periodic to check for new units.

Now if (picked unit) is a footman set footmen[(player number)] = Footmen[(player number)] + 1. Do this for each type of unit. This way you only use 1 unitgroup for each player instead of dozens. Then
  • Custom script: call DestroyGroup(udg_TempGroup)
Do a unit dies event the same way but subtract instead of add.

If you use the other method, destroy the leak the same way, but set TempGroup = (units owned by Player (matching) unit is a footman). Then set Footmen = (number of units in TempGroup). Then destroy the temp group and you have no leak.

To recap:

1. Create integer variables for each unit type you want to track.
2. Make the variables have arrays so that you can index them according to owning player.
for example Footmen[1] could be the number of footmen owned by player 1.
3. Make the following triggers:

  • Events
    • Time - time elapsed 0.10 seconds of game time
    • Unit - A unit enters (Playable map area)
  • Conditions
  • Actions
    • Player Group - Pick (all players) in (playable map) and do
      • Actions Loop
        • Set TempGroup = (all units owned by (picked Player))
        • Unit Group - Pick all units in TempGroup and do
          • Actions Loop
            • If (all conditions are true) the (do actions)
              • If - conditions
                • (type of unit of (Picked unit) equal to (unit-type (footman))
              • Then Actions
                • Set Footmen[(Player number of (picked player)] = Footmen[(Player number of (picked player)] + 1
              • Else then
            • ---------repeat for each type of unit-------------
            • Custom script: call DestroyGroup(udg_TempGroup)
4. Then do the same for the event "unit dies" except use -1.

Player Group (all players) does not leak.

Then check your map for leaks with Hive's Leak Finder.
 
Last edited:
Level 16
Joined
Mar 27, 2011
Messages
1,349
Man that trigger must lag like a bi*ch. Picking every unit on the map every 0.1 seconds? There could be over 100 units on the map at a time! Why not do add the integer each time the unit enters the map instead? And remove when they die?
 
Level 12
Joined
May 22, 2015
Messages
1,051
Man that trigger must lag like a bi*ch. Picking every unit on the map every 0.1 seconds? There could be over 100 units on the map at a time! Why not do add the integer each time the unit enters the map instead? And remove when they die?

I agree, but I did describe why counting them like that is a problem. Reanimated units will have to be recounted, as well as switched player units (counts as leaving one player and entering another player), etc. It could get ugly depending on what your map is like.

Easiest method is to just add all the units into player groups - one group for each player (this will be in a group array). Add any unit they get to the list.

When you need to count, you have to loop over and count the unit type you want. Make sure to check if the unit is alive when doing that, though.

Then, you can either try to detect all death and removal events to remove the units from the group, or you can have the groups be rebuilt every few minutes to make sure they don't fill up with dead / removed units.
 
Man that trigger must lag like a bi*ch. Picking every unit on the map every 0.1 seconds? There could be over 100 units on the map at a time! Why not do add the integer each time the unit enters the map instead? And remove when they die?

WRONG! It only runs once at 0.10 seconds. Not ever again until a unit enters the map. Radicool is thinking of a periodic event, but that is not what I said so his advise is flawed. His questions are actually exactly what I suggested. Sorry he is so confused.

SAUS brings up a good point though, which is possession and charm. If a unit is stolen by another team it will not register as entering the map or dying. So maybe a 1 second periodic trigger is better. That won't lag either, picking every unit on the map is not that expensive if you clean your leaks. Also, my trigger needs to reset the variables at the beginning of it so it can count fresh each time. I forgot this. So you will need to:
  • Set footmen[(player number of (picked player))] = 0
after you pick all players in the trigger. Do this for each integer variable you are storing.
 
Last edited:
Level 5
Joined
Jul 14, 2014
Messages
115
Thank you all for your help. The map can easily have 150 - 200 units for each player. Legal Ease's method seems a little laggy for such an amount but I'll try it anyway. SAUS, I'm sorry for being such a retard, but you only type your ideas in text. I can't really fully understand without seeing a trigger. Sorry again
 
Level 12
Joined
May 22, 2015
Messages
1,051
Thank you all for your help. The map can easily have 150 - 200 units for each player. Legal Ease's method seems a little laggy for such an amount but I'll try it anyway. SAUS, I'm sorry for being such a retard, but you only type your ideas in text. I can't really fully understand without seeing a trigger. Sorry again

No worries. I just don't like working with GUI and, in my own map, I would copy-paste so much of the code that I am only writing maybe 30% of the lines of code manually. I can't do that when I'm on here because I go on here mostly when I'm not at home :p

  • Collect Units
  • Events
    • Unit - A unit enters <Playable map area>
  • Conditions
  • Actions
    • set tempUnit = Triggering Unit
    • set tempPlayer = Owner of (Triggering Unit)
    • set tempInt = Player number of (tempPlayer)
    • Unit Group - Add (tempUnit) to playerUnits[tempInt]
This trigger will collect all the units into the unit group for their owner as they enter the map. You will need a unit group array variable (called playerUnits in my trigger) which will need to be size 13 (or maybe just 12, but going higher is safer).

  • Refresh Groups
  • Events
    • Time - Every 60.00 seconds
  • Conditions
  • Actions
    • For each integer A do
      • Loop (Actions)
        • Unit Group - Empty playerUnits[(For loop integer A)]
    • set tempGroup = Units in <Playable map area>
    • Unit Group - Pick every unit in tempGroup and do
      • Actions
        • Unit Group - Add (picked unit) to playerUnits[Player number of (Owner of (Picked unit))]
    • Custom script: call DestroyGroup(udg_tempGroup)
This trigger will clean the removed units out of the groups and also will update units that switched teams (by completely emptying the current set of groups and adding all the units). When you want to get the count of a unit, you will have to count the units in your trigger. Here is an example:

  • Example Use
  • Events
    • Time - Every 30.00 seconds
  • Conditions
  • Actions
    • set tempInt = 0
    • Unit Group - Pick every unit in playerUnits[1] and do
      • Actions
        • If (multiple actions)
          • Conditions
            • (Picked unit) is alive
            • (Picked unit) belongs to Player(1)
            • Unit type of (Picked unit) matches (Footman)
          • Then
            • set tempInt = tempInt + 1
          • Else
    • If (multiple actions)
      • Conditions
        • tempInt is greater than or equal to 100
      • Then
        • Game - End the game in victory for Player(1)
      • Else
This trigger will check every 30 seconds if player 1 has at least 100 footmen and will make them win if they do.

These triggers are still not perfectly accurate. If a unit switches teams, they are not counted in the new team until the Refresh Groups trigger is run - you can increase how often the Refresh Groups trigger is run, but it will still have this problem (though it will be more rare). However, you can manually run it before counting if you really want to (there is a GUI action like Trigger - Run (Refresh Groups <gen>)). Just remember that you don't want to run this trigger too often because it loops over all the units in the entire map.

Hopefully these triggers are helpful. I think I did okay on the GUI syntax, but I wrote these by hand. They may not be exactly as they look in the editor.

EDIT:
Typos and hidden tags.
 
Level 5
Joined
Jul 14, 2014
Messages
115
No worries. I just don't like working with GUI and, in my own map, I would copy-paste so much of the code that I am only writing maybe 30% of the lines of code manually. I can't do that when I'm on here because I go on here mostly when I'm not at home :p

  • Collect Units
  • Events
    • Unit - A unit enters <Playable map area>
  • Conditions
  • Actions
    • set tempUnit = Triggering Unit
    • set tempPlayer = Owner of (Triggering Unit)
    • set tempInt = Player number of (tempPlayer)
    • Unit Group - Add (tempUnit) to playerUnits[tempInt]
This trigger will collect all the units into the unit group for their owner as they enter the map. You will need a unit group array variable (called playerUnits in my trigger) which will need to be size 13 (or maybe just 12, but going higher is safer).

  • Refresh Groups
  • Events
    • Time - Every 60.00 seconds
  • Conditions
  • Actions
    • For each integer A do
      • Loop (Actions)
        • Unit Group - Empty playerUnits[(For loop integer A)]
    • set tempGroup = Units in <Playable map area>
    • Unit Group - Pick every unit in tempGroup and do
      • Actions
        • Unit Group - Add (picked unit) to playerUnits[Player number of (Owner of (Picked unit))]
    • Custom script: call DestroyGroup(udg_tempGroup)
This trigger will clean the removed units out of the groups and also will update units that switched teams (by completely emptying the current set of groups and adding all the units). When you want to get the count of a unit, you will have to count the units in your trigger. Here is an example:

  • Example Use
  • Events
    • Time - Every 30.00 seconds
  • Conditions
  • Actions
    • set tempInt = 0
    • Unit Group - Pick every unit in playerUnits[1] and do
      • Actions
        • If (multiple actions)
          • Conditions
            • (Picked unit) is alive
            • (Picked unit) belongs to Player(1)
            • Unit type of (Picked unit) matches (Footman)
          • Then
            • set tempInt = tempInt + 1
          • Else
    • If (multiple actions)
      • Conditions
        • tempInt is greater than or equal to 100
      • Then
        • Game - End the game in victory for Player(1)
      • Else
This trigger will check every 30 seconds if player 1 has at least 100 footmen and will make them win if they do.

These triggers are still not perfectly accurate. If a unit switches teams, they are not counted in the new team until the Refresh Groups trigger is run - you can increase how often the Refresh Groups trigger is run, but it will still have this problem (though it will be more rare). However, you can manually run it before counting if you really want to (there is a GUI action like Trigger - Run (Refresh Groups <gen>)). Just remember that you don't want to run this trigger too often because it loops over all the units in the entire map.

Hopefully these triggers are helpful. I think I did okay on the GUI syntax, but I wrote these by hand. They may not be exactly as they look in the editor.

EDIT:
Typos and hidden tags.

Thanks so much for your help! Now I was wondering which way is better. Yours or Legal Ease's one. Both seem pretty good. In my case I need to keep track of units only for trigger AI and it will not summon anything (except non-permanent units) or steal any units.
 
Level 12
Joined
May 22, 2015
Messages
1,051
Summoning units is fine - they "enter playable map area" when they are summoned. Resurrection and animate dead spells are the problem there - the units are already in the map - they are just dead. They would not be re-added to the group. That is not a problem if you don't try to remove them in the first place, though.

You need to refresh the group every once in a while because a group can have "NULL" units in it (units that have died and fully decayed no longer exist, but the group still has that unit in it - it will just loop over it and fail checks like "is alive"). Without refreshing, looping over the group will take longer and longer as the game goes on.

Legal_Ease's version would count faster (when you go to use it) and would also be less code inside the trigger that needs to use the count, but it would also require that you make an array for each unit type you want to check (which might not be that many, anyway).

I don't know if you have anything that would break the counting, however, it means you cannot add one in later without doing some extra work. I don't know all the details of your map, so it is hard to say what is better.

If your map has no reanimation / unit stealing or giving, you should be okay to use Legal_Ease's method. I think it is overall a faster method, just less robust once you have those kinds of things added to your map.
 
Level 5
Joined
Jul 14, 2014
Messages
115
Summoning units is fine - they "enter playable map area" when they are summoned. Resurrection and animate dead spells are the problem there - the units are already in the map - they are just dead. They would not be re-added to the group. That is not a problem if you don't try to remove them in the first place, though.

You need to refresh the group every once in a while because a group can have "NULL" units in it (units that have died and fully decayed no longer exist, but the group still has that unit in it - it will just loop over it and fail checks like "is alive"). Without refreshing, looping over the group will take longer and longer as the game goes on.

Legal_Ease's version would count faster (when you go to use it) and would also be less code inside the trigger that needs to use the count, but it would also require that you make an array for each unit type you want to check (which might not be that many, anyway).

I don't know if you have anything that would break the counting, however, it means you cannot add one in later without doing some extra work. I don't know all the details of your map, so it is hard to say what is better.

If your map has no reanimation / unit stealing or giving, you should be okay to use Legal_Ease's method. I think it is overall a faster method, just less robust once you have those kinds of things added to your map.

Alright. Thanks a lot for all your help!:grin:
 
If your map has no reanimation / unit stealing or giving, you should be okay to use Legal_Ease's method. I think it is overall a faster method, just less robust once you have those kinds of things added to your map.

I accounted for this and fixed it about 4 posts back. Check it. You're confusing him by making it seem like there are 2 choices. They are really the samething. Yours just doesn't save the integer, but instead finds it when the time comes. He asked how to "keep track" of the number of units. Your method doesn't do that. Maybe that's all he needed, but it wasn't what he asked for.

Hashtables is probably the best solution. You could create a single system for all units that way.

I got the idea he didn't know how to use them. But this is probably true.

My method does not lag at all and is at least as efficient as any others. In fact they are really all the same. Using matching unit is probably the simplest. I've been told to avoid "matching unit" because it can be buggy. If you want to set the periodic to more than 1 second, fine. That just makes your system super inaccurate, but 1 second will not lag so long as you remove all leaks. Unless you use hashtables you will need an integer array for each unit type you want. I don't see why this needs to be a contest. I was willing to write a trigger that was easy to understand and it wasn't wrong. It would be more helpful to build off of it or modify it rather than saying it lags. Let's be clear here. IT DOES NOT LAG!!! Anyone who says so it just dumb. Arrays are fast to calculate and even faster than hashtables for many applications. A 1 second periodic is not a problem, wc3 can handle this with ease. People should try to provide accurate information and assist others working on projects. Disinformation for the purpose of ego building is counter productive. SAUS and Radicool, I mean you. Consider yourselves scolded. Melonhead is a noob and doesn't know any better, but next time check the rep. Dr. Supergood is obviously the authority in this thread. SAUS is a good coder (probably better than me) and an up-and-comer for sure, but he is also young and likes being right more than he likes being helpful.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,202
Legal_Ease your trigger does not look like it will scale very well and could cause performance problems in some maps. Every time a unit is created, eg a dummy unit for a spell, it will recount every unit on the entire map. Worse is if you use separate triggers for each unit type it will recount every unit on the entire map for every unit type. You can see that this could easily be 20,000 unit pick operations or more per unit creation in a map with a thousand or more units and 10 or 20 types and multiple unit creations can occur at once.

Or at least that is what you have lead us to believe. Your trigger is quite confusing, especially the decrement part and later amendment you posted. It looks like you changed your approach mid-way so it is inconsistent.

One is not meant to be recounting every unit on the map every time, but only modifying the unit count on creation or destruction events. How accurately you can detect such events depends on how accurate the count is. Even if you need to recount periodically to avoid inaccuracy you can do so in a trivial way by staggering the counting to 1 unit type and player per counting tick.

Assuming you have accurate unit creation/destruction (factoring in ownership) events then you will need 3 triggers.
1. Map initialization to determine the unit count at the start of the map. Could possibly be a hard-coded constant.
2. A unit is created event which increments the unit type count for the owning player.
3. A unit is destroyed event which decrements the unit type count for the owning player.

A 4th trigger could be used...
Every X seconds recount so many units. This could possibly be speeded up by keeping track of counted units in groups and checking if anything in the group changes ownership (charm, etc) or is now "null" (was destroyed).

Hashtables are useful for the storage medium because using them you could use a single 3 trigger solution for practically infinite unit type numbers. It also provides the best scaling as no matter how many unit types you use only a single hashtable operation will be used. The multiple array approach falls short here because you will need to perform a number of tests directly proportional to the number of different types you are logging. It also is easier to maintain as for 200 types you only need 3 triggers as opposed to 600 triggers or 3 triggers with 200 conditional tests each (arranged in a binary search tree for 8 test access performance). I would only recommend hard-wiring arrays if you want to count only 1 to 3 different unit types, beyond that a hashtable solution is easier to implement and faster to execute. Access performance of the hashtables is slower, but the speed difference is trivial compared with overall complexity savings, implementation ease and scaling. Numbers I recall reading is that a hashtable access is about 3 or 4 times slower than an array access, which is probably sill considerably less than a single ForGroup unit pick.
 
Last edited:
Level 12
Joined
May 22, 2015
Messages
1,051
Turning 26 next week :p I'm not that young, unless you consider mid-20's young (really depends who you ask).

Didn't mean to be like that. I wanted to try to give a solution based on what I described that Melonslise could use. I didn't want to go with a hashtable solution because GUI hashtables are soooo ugly to work with and I don't actually remember what you can and can't do with them (I remember you couldn't use variables inside the function calls or something like that).

My solution just adds the units into the group, but then you have to count the units in the group each time. Refreshing the group is actually just so the removed units (completely decayed and gone from the game) don't start to build up lag when you count.

I attack solutions that I think will cause problems. I was wrong about your trigger because I did not read it well and kind of jumped on the band wagon. Sorry for that.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,202
Why not just use a Unit Indexer and use the event of "unit gets indexed" to add +1, and then use "A unit dies" to -1? :p
That is only half the problem solved because what do you "add +1" or "-1" from? This is where hashtables work the best because you can use the unit type as the parent key.

It also does not solve the problem of units which do not die. Such as trigger removed units or units which change ownership. You will either need to deal with such events separately (RemoveUnit extension, add triggers to catch Charm ability casts), not allow such events to occur (eg a nice stable classic TD game) or have some sort of error correction mechanism which chugs along quietly in the background.
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
That is only half the problem solved because what do you "add +1" or "-1" from? This is where hashtables work the best because you can use the unit type as the parent key.

It also does not solve the problem of units which do not die. Such as trigger removed units or units which change ownership. You will either need to deal with such events separately (RemoveUnit extension, add triggers to catch Charm ability casts), not allow such events to occur (eg a nice stable classic TD game) or have some sort of error correction mechanism which chugs along quietly in the background.

Ahh sorry I read his post too fast, I didn't realize he needed to count Unit-Types. Thanks for the clarification.
 
Status
Not open for further replies.
Top