Staredit Network > Forums > SC1 UMS Theory and Ideas > Topic: Identifying Index IDs
Identifying Index IDs
Aug 15 2011, 9:09 pm
By: Sacrieur  

Aug 15 2011, 9:09 pm Sacrieur Post #1

Still Napping

An index ID is an identification number assigned to every unit in the game. Using EPDs or EUDs, one can detect aspects of unit data otherwise inaccessible normally. However, without an index ID, Starcraft will refer to data from all units. With knowledge of an index ID, data from a single unit can be obtained.

These IDs are given in a chronological order. The first unit created (in an editor) will have an index ID of 0. Every unit thereafter will have an index ID of 1, 2, 3 ... n. Units created in the game are also are assigned in this order. If a unit dies or is removed, its index ID will be added to the pool of available, or inactive, index IDs.

This is based off of a well-known storage system in queuing theory, known as LIFO (Last In First Out). In this system, items are stacked on top of each other, and only the top item is available for use. The only change is the next available index ID (NAIID): an index ID with an assigned pointer. The pointer designates it as the index ID used for the next created unit. While the NAIID is not stored any differently than other inactive index IDs, it can be conveniently thought to be taken out of the LIFO storage entirely, and placed in its own area, where it will be absolutely assigned to the next created unit.


Starcraft's LIFO system with 0 units created.

Example



Index ID Updates
-
The Starcraft engine does not update index IDs during a trigger cycle. Rather, they are updated between cycles. Index IDs freed (remove unit action) during a trigger cycle are not added to the list of available index IDs and are unavailable for use until the cycle ends. It is possible, then, to use all available index IDs (1700 units) and cause a CCMU (Cannot Create More Units) error. These index IDs are then sorted into a list of ascending order and added to the available index IDs after the cycle is complete.

A detailed explanation can be found here.


Identifying Index IDs
-
As one could imagine, detecting an ID is not an easy task. Fortunately, Azrael has developed three methods for detecting index IDs. They can be found here.


Additional Information



More information on index IDs and how to use them can be found in section 6 of A Mapmaker's Guide for Creating EPDs.

Post has been edited 28 time(s), last time on Aug 19 2015, 5:44 pm by Sacrieur.



None.

Aug 15 2011, 9:15 pm Lanthanide Post #2



Quote from Roy
(units like Map Revealers ... have Index IDs)
Can EUDs be used to remove/kill specific units without desync? I think probably not.

Is there anything useful that can be done to map revealers with EUDs when you have their index? I'm asking because map revealers are not detected in locations for either conditions or actions, so there is no way to remove a specific map revealer for example, the best you can do is remove all of them for a particular player (and then add any ones you didn't want deleted back in, which is tedious).



None.

Aug 15 2011, 9:45 pm Roy Post #3

An artist's depiction of an Extended Unit Death

Quote from Lanthanide
Can EUDs be used to remove/kill specific units without desync? I think probably not.
Desynchronization would not really be an issue here, since we're talking about unit data, which is global.

How would you go about removing the unit with EUDs? First, you'd need an Action Enabler to do anything. I tried setting the HP to 0, but apparently, this doesn't automatically kill units like I had imagined (it induced the 0 HP invincibility trick). Maybe one of the unlabeled addresses in http://www.staredit.net/topic/10471/ holds a death stage or something similar.

As I see it right now, though: no, EUD actions cannot specify a specific unit to die or be removed.

On a side note, setting a unit's HP to -1 with EUD actions makes the display appear as 0/40 (or whatever the max health is), but the unit is killable.




Aug 15 2011, 10:31 pm Lanthanide Post #4



From the SC1 quirks and nuances oldwiki post:

Quote from SC quirks and nuances
When units die, there is one frame of animation where they are at 0 health and are designated to die. For that frame, they are technically still alive, they still have a collision box, they can still be detected with Bring (though not with Command), and they can even be given (but cannot be moved). They will have already added a death to their death counter. When you kill a unit with triggers, they are set to that zero-health "dead-but-not-dead" state. Since Hyper Triggers (on Fastest) run every other frame, there is essentially a 50% chance that a unit killed by something other than a trigger will be in that state when the triggers run. The Kill Unit action isn't so much a Kill Unit action as it is a "sentence to death" action.

I vaguely remember running across something that listed the flags for units including the 'kill it' flag. This is a distinct separate flag from the HP as you can surmise from above. Surely it's simply a matter of finding out where this flag is and then setting it with a EUD.

Edit: searched around, but I must have been imaging such a list of flags.
Edit 2: Thinking more about this, I wouldn't be surprised if the theoretical flag described above actually doesn't exist in the unit structure itself. I can imagine that when a unit is going to die, a reference to that unit is added onto a death list, which handles playing the death animation and sound etc. You can imagine if there are 1000 units on the map, and 100 are instantly killed (from a trigger), that having a separate death list would be more efficient than sorting through all 1000 units and checking "are you dead?".

Post has been edited 2 time(s), last time on Aug 16 2011, 2:03 am by Lanthanide.



None.

Aug 21 2011, 3:40 am poiuy_qwert Post #5

PyMS and ProTRG developer

Set its order to order 0 (Die)




Aug 21 2011, 4:30 am Lanthanide Post #6



This would be an EUD action and therefore wouldn't work on b.net without the EUD enabler, correct?



None.

Aug 21 2011, 4:55 am Sacrieur Post #7

Still Napping

Quote from Lanthanide
This would be an EUD action and therefore wouldn't work on b.net without the EUD enabler, correct?

Correct =P



None.

Sep 29 2011, 3:14 am Cinolt Post #8



Quote from Sacrieur
Starcraft will scan available index IDs and choose the lowest number to register for the next created unit. Once the available index ID is selected, it is stored and kept until a new unit is assigned to it, even if lower index IDs become available.

This is not entirely accurate. StarCraft actually uses a LIFO stack-like structure, with the top value initially being 0 and incrementing values going downward (1, 2, 3, etc). When a unit is created, the top value is popped off and used, and when a unit is removed the index of that unit is pushed in as the second value (so it might be more accurate to call it a "LISO" stack).

As for identification of the next unit index, the most easiest method is to use this EUD:
Code
0x628438 161845 NEXT UNIT INDEX POINTER: (Unit index pointer)


Attachments:
[EUD] Index.scm
Hits: 10 Size: 59.76kb



None.

Sep 29 2011, 5:34 am Sacrieur Post #9

Still Napping

Thanks for the info!

I've added it to the post, expanding on how SC's LIFO system works with graphics that should be easy to understand.



None.

Mar 28 2012, 7:51 am Azrael Post #10



Unavailable indexes remain unavailable until the end of the trigger cycle. They don't become available mid-cycle.

At the end of the trigger cycle, all indexes which were made available during that cycle are added to the top of the available list in a specific order.

The first indexes added are those which were freed by removing units during the trigger cycle, with indexes being grouped into blocks. Each remove action creates an index block, and the index blocks are added to the available list in the order of their respective remove actions. The first remove action will have the indexes it frees added to the available list first.

Within each of the index blocks, the indexes are also arranged in a specific order, which is based on how long the index was in use before it was freed. The oldest unit that was removed will have its index added to the available list first, followed by the next oldest, and so on. The order in which the remove action actually removed the units is not relevant.

After all the remove actions are taken into account, units which were killed are all added to the top of the available list simultaneously. They are also added in order of how long the index was unavailable, with the oldest unit killed having its index added first, and the next oldest on top of it, and so on. The difference here is that it does not matter how many kill actions were used, or what order they were in, or what order the units are killed in. This effectively makes all killed units the last index block added to the available list.

It should be noted that, in some circumstances, the value that should be at the top of an index block is moved to the bottom. The reason appears to be related to the indexes which are still in use, as this does not seem to happen when all indexes are made available. In the situations it does happen, it will happen consistently.

As an additional note, CCMU occurs when the game attempts to create a unit, but there are no available indexes. It doesn't matter how many units there actually are, only how many indexes are available. An example would be taking a map with no units, creating 1700 units, then removing all of those units. Since the indexes don't become available until the end of the trigger cycle, attempting to create any more units during that trigger cycle will result in CCMU, even though there are no units on the map.

To incorporate the previous update about index order into this, and continuing the same example, all the unit indexes would become available when the trigger cycle ended. Since all indexes would become available simultaneously, and are added to the available list in order of least to most recently used, the NAIID at the beginning of the next trigger cycle would be 1699. At the top of the available index list would be the index 1698, followed by 1697, continuing in descending order to 0.

At least that would be the case, except the NAIID at the beginning of the next trigger cycle is actually 0, followed by 1699, followed by 1698, and so on. The reason is that when the available list and NAIID are empty, the next unit index that is added to the available list will immediately become the NAIID value, even if it is the bottom value of an index block.

Additionally, the first unit removed during a trigger cycle can sometimes become the next NAIID after the current one is used, even if the current NAIID isn't used until a later cycle. In the situations it does happen, it will happen consistently. This potential interference can be easily avoided, however, by removing any unit (even one just created) before removing units that you wish to keep in order.

Another thing to note about unit indexes is that the Goliath and Siege Tank, along with their hero counterparts, use two unit indexes each. When they are created, the unit itself receives the NAIID, and its turret receives the next index after it.

Post has been edited 10 time(s), last time on Aug 11 2012, 9:59 am by Azrael.




Apr 18 2012, 5:52 am Sacrieur Post #11

Still Napping

Updated with the new information.

I feel as though this works in our favor for actually identifying index IDs. I'll get the system worked out sometime.



None.

May 3 2012, 5:20 pm Azrael Post #12



Based on the information from my previous post, here are a few methods of determining the index of units created mid-game. These can be combined in various ways to be used in the same map.

Attached below is a map demonstrating a unit index tracking system, using Method A as described below. The map constantly respawns Goliaths, which are killed by lockdown. The physical indicators checked are unit coordinates, and unit health (to differentiate the Goliath from its turret).


Method A: Create the unit, and then use physical indicators to find its index. The physical indicators can be anything unique to the unit which is checked by its index, such as unit coordinates, unit health, unit energy, and unit status.

As an example, a small square in a corner of the map can be set aside for this purpose. Every time a unit will be created, create it in the corner of the map, and then check each index to see if it is at those coordinates. Since that is the only unit at those coordinates, only one index will be found there. After determining the index, the unit can be immediately moved to where it's needed, and any number of additional units can be created the same way during the same cycle.

The only downside to this method is the necessity of unique physical indicators.

This is the primary method which should be considered for most systems. It's flexible, intuitive, and reliable.


Method B: Change the unit settings for map revealers to a health higher than any other unit, such as 100000. When it is time to create a unit, instead create a map revealer with 98% health, and remove it. For any additional units which will be made during the same cycle, create a map revealer at 97% health, then 96%, and so on, removing each one afterwards and recording the index that changes via death counts. After all units which are desired have had their respective map revealer created, then create a map revealer with 99% health. At the beginning of the next cycle, begin creating map revealers with 100% health until the index at 99% health changes to 100% health (this is to take into account units which were killed during the first cycle or between the two cycles). At this point, you will know the order of all the indexes after that, as they will be ordered from least to greatest health (if there were three units, then they will be 96%, then 97%, then 98%).

There are a few downsides to this. One issue is that players cannot create units from factory buildings, as they would use up the stored unit indexes. It could be detected that the indexes were compromised, and the process started over each cycle until it is successful.

Another potential problem is that the act of creating map revealers at 100% health until the 99% index changes will add a large number of triggers to the system (1700 additional triggers for every unit which may have died during or since the previous cycle). However, these are optional, assuming that you could ensure no units could die during or since the previous cycle. For stopping units from dying during the cycle (by triggers), you could make all triggers with kill actions delayed via a switch which is set whenever tracked units need to be created. As for preventing units from dying between the cycles, if the map requires players to be able to kill units, it could make all units invincible during the first trigger cycle and make them vulnerable again during the second one.

Ideally, this system would be used in a map specially tailored for it. For example, if the players cannot create or kill other units outside of triggers, or if units were always killed in predictable amounts, this system would be easier to setup.

A benefit of this method would be that it does not require any unique physical indicators to differentiate the units, since it is determining the index each unit will have before they are created.


Method C: This is a one-shot system which can produce a large number of unit indexes to be used on the next trigger cycle. When the units are to be made, 1700 map revealers are created and removed. This will result in the list of available indexes being reversed, putting 1699 at the top, followed by 1698, then 1697, and so on. The NAIID will be something unknown (whatever the bottom index would have been in the available list), so at the beginning of the next cycle, a single map revealer should be created. The rest of the indexes will be used in a predictable sequence.

The main downside of this system is, as stated, that it is geared toward a single use. It can be modified to be used repeatedly, keeping track of which indexes have been used and then flipping the stack again in a similar method to preserve the order of the highest indexes. This would require a map specifically designed to accomplish this, and would have to restrict the player's ability to create or kill units (or have additional systems safeguarding against this activity).

Another potential problem is, in the case of creating a large number of units, there are only a finite number of indexes that can be used in this way, as indexes are counted down, not recycled. In most maps this number of uses isn't likely to be reached, unless the tracked units respawn infinitely. Even in this case, an additional system can be put into place to make sure the expected indexes are being used in the anticipated order, and stopping the tracking of indexes in this way if the ordered set is used up.

As with the previous method, since there are two cycles involved, if any units are killed during or after the first cycle, the system will be offset on the second cycle. However, this can easily be checked by creating a single map revealer (with a reduced percentage of health) repeatedly until the index 1699 has its health changed to that of the damaged map revealer. A similar concept would be used if players can create units from factory buildings, detecting the highest index with the health of a map revealer (as they were created to flip the available list), and starting the unit index distribution from there.

This method is exceptional at what it does, with a huge benefit being the relatively low number of triggers required to make it functional. There is no need for triggers that determine the index of a unit before or after its creation, as you know in advance the order of all the indexes and how they will be used. This works well in conjunction with both of the previous two methods.

Attachments:
Unit Index Tracker A.scx
Hits: 109 Size: 979.75kb

Post has been edited 4 time(s), last time on May 24 2012, 8:46 pm by Azrael. Reason: Update.




May 28 2012, 6:33 pm TiKels Post #13



Holy crap there's a wealth of information here. A lot of work was put into this, no?



"If a topic that clearly interest noone needs to be closed to underline the "we don't want this here" message, is up to debate."

-NudeRaider

Aug 7 2012, 2:35 am Kaias Post #14



Quote from Azrael
Unavailable indexes remain unavailable until the end of the trigger cycle. They don't become available mid-cycle.

At the end of the trigger cycle, all indexes which were made available during that cycle are added to the top of the available list in a specific order.

The first indexes added are those which were freed by removing units during the trigger cycle, with indexes being grouped into blocks. Each remove action creates an index block, and the index blocks are added to the available list in the order of their respective remove actions. The first remove action will have the indexes it frees added to the available list first.

Within each of the index blocks, the indexes are also arranged in a specific order, which is based on how long the index was in use before it was freed. The oldest unit that was removed will have its index added to the available list first, followed by the next oldest, and so on. The order in which the remove action actually removed the units is not relevant.

After all the remove actions are taken into account, units which were killed are all added to the top of the available list simultaneously. They are also added in order of how long the index was unavailable, with the oldest unit killed having its index added first, and the next oldest on top of it, and so on. The difference here is that it does not matter how many kill actions were used, or what order they were in, or what order the units are killed in. This effectively makes all killed units the last index block added to the available list.
How did you get this information?

My goal is to be able to switch out a unit for a new one in the same static index. So for example, I might want to change index 50-55 from what they currently are (Zerglings) to Zealots (but not necessarily all at the same time).

According to your above post, I should be able to do this by removing the zerglings one by one at the very end of the trigger cycle, then removing a "marker" unit (say index 56). Then at the very beginning of the next loop, I create and remove units until the NAIID is at the marker unit (56), recreate the marker unit and then create all my Zealots in reverse the order that I removed the Zerglings in. This would make it so that it doesn't matter what I remove or kill each loop, the Killed units should be at the top of the availability list followed by my marker and then by the Zerglings.

Problem is, in practice this isn't working. Sometimes my marker comes after the zerglings in the available units list and sometimes doesn't despite removing them in that specific order.



None.

Aug 7 2012, 7:53 am Kaias Post #15



Also, Azrael, you should know your inbox is full.

Post has been edited 1 time(s), last time on Aug 8 2012, 2:33 am by Kaias.



None.

Aug 11 2012, 8:32 am Azrael Post #16



Quote from Kaias
How did you get this information?

All the information I posted came from a long period of rigorously experimenting with countless test maps.

Since it is based on observational data, it is possible that the information could be expanded on or improved upon even further.

As for your problem, I've attached a map that achieves your desired result. In the map, five firebats are constantly replaced with new firebats, and each one retains the index of the one it replaced.

Hopefully this will prove useful to you, or to someone seeking to accomplish the same thing.

Attachments:
Unit Index Recycler.scx
Hits: 6 Size: 383.92kb




Aug 16 2012, 3:46 am Sacrieur Post #17

Still Napping

I've discovered information about Starcraft's storage of memory and index IDs in regards to unit position. It has been added to the documentation.



None.

Options
  Back to forum
Please log in to reply to this topic or to report it.
Members in this topic: None.
[04:16 am]
jjf28 -- he sometimes pops in discord
[04:15 am]
jjf28 -- we hung out with him the other week at SENCon
[03:38 am]
dumbducky -- Whatever happened to MIke Lat?
[02:07 am]
Corbo -- Nice cats
[09:12 pm]
IlyaSnopchenko -- Miau
[09:11 pm]
Suicidal Insanity -- "Open wide"
[07:24 pm]
IlyaSnopchenko -- Will do... Though only tomorrow i guess
[07:19 pm]
Pr0nogo -- check images.dat, compare that to other attacking buildings
[07:19 pm]
IlyaSnopchenko -- Guess I'll have to spend more time on this
Please log in to shout.


Members Online: Roy