Skip to content

Unit selection event

Graylin Kim edited this page Jan 18, 2012 · 1 revision

Unit Selection Event - Code 0x[0-A]C

This event occurs whenever a player's selection changes, but NOT when it is changed by pressing a hotkey. The reason the event is triggered is either an explicit player action or changes made to a current selection like a death or morph. There is no way to know for sure whether the system triggered the event or the player.

The actual code used to call this event [0-A]C indicates which selection buffer is targeted: 0-9 are the corresponding hotkeys and A is the selection on the HUD (the player selection). There seems to be a bit of overlap with certain hotkey events and selection events on hotkeys. An explanation might be that selection events on hotkeys are caused by morphs and not kills, but that is not certain yet.

Selection events structure (newer replays)

We're 99% sure this is correct, we just aren't sure quite how far back this is accurate. If someone finds out then let us know. It goes at least back to build 17811, quite likely a bit further.

Selection events have two parts: a section with changes to the current selection and a section with new additions.

Header

  • Event flag (1 byte): Usually 0x00 but ~1% have 0x01. Unexplained yet.

Modifications to the current selection

The rightmost two bits of the next byte mark the modification mode:

  • 0x1 = Deselect by bit mask.
  • 0x2 = Deselect by list of indexes.
  • 0x3 = Replace current selection with list of indexes.

For the rest of this event every byte block read must be read with a bit shift (except in the event that the deselect by bit mask has a bit count that evens is out again). For more information on reading with a bit shift, check out our bit shift documentation.

Deselect by bitmask

This mode is the hardest and trickiest because you are reading bits not bytes. This means that the bit shift after reading will likely be different than when you started (2 bits).

The first (bitshifted!) byte indicates how many bits are in the mask. This bits are then read according to the bit shift rules in our bit shift documentation.

Deselect or replace by index

This one is refreshingly easier: in our current 2-bit-shifted mode read 1 (bitshifted!) byte for the length of the list and read that amount of (bitshifted!) bytes. The bytes indicate the 0-based index in the current selection. Replace means pick those out and deselect the rest, deselect means the opposite: deselect one those objects.

Additions

The rest of the bytes will almost always be bit shifted (the bit mask has a chance or realigning us), check out our bit shift documentation to learn how to read them correctly. If you read them without the bit shift, things will break!

The structure is of additions is roughly:

  • 1st byte = number of Unit Types
  • Unit Type List (4 bytes each)
    • 2 bytes for each unit type to represent the unitTypeID
    • 1 byte for Hallucinated units (0x01 = normal, 0x02 = hallucinated)
    • 1 byte for each unit type to represent the unitCount
  • Next Byte = totalUnitCount
  • Unit List (4 bytes each)
    • 2 bytes for the recycled unit ID
    • 2 bytes for the usage counter that makes it unique

Older Replays - Pre Build 16561 I think (aka how phpsc2replay handles them)

This is here for completeness, we currently have no opinion on how to parse selections in older replay files. This code appears use the correct number of bytes but may not have the correct information.

The following flow is an sc2reader translation of the logic implemented by the phpsc2replay project:

selectFlags,deselectCount = bytes.getBigInt(1),bytes.getBigInt(1)

if deselectCount > 0:
    #Skip over the deselection bytes
    bytes.skip(deselectCount >> 3)

#Find the bits left over
extras = deselectCount & 0x07
if extras == 0:
    #This is easy when we are byte aligned
    unitTypeCount = bytes.getBigInt(1)

    #The first 3 bytes make the UnitType and 4th represents the count
    for i in range(0,unitTypeCount):
        unitType,unitCount = bytes.getBigInt(3),bytes.getBigInt(1)
    totalUnits = bytes.getBigInt(1)

    #UnitID's are recycled. The first 2 bytes are the ID
    #the second 2 represent the number of assignments
    for i in range(0,totalUnits):
        unitId,useCount = bytes.getBigInt(2),bytes.getBigInt(2)

else:
    #We're not byte aligned, so need do so some bit shifting
    #and combine parts of bytes according to a weird mask
    #This this doesn't seem quite right to me, but its what the
    #people at phpsc2replay think it is so I'll deal for now
    tailMask = 0xFF >> (8-extras)
    headMask = ~tailMask & 0xFF
    wTailMask = 0xFF >> extras
    wHeadMask = ~wTailMask & 0xFF

    #Follow the same format as before, just bit shifted by combining bytes
    prevByte,nextByte = bytes.getBigInt(1), bytes.getBigInt(1)
    unitTypeCount = prevByte & headMask | nextByte & tailMask

    for i in range(0,unitTypeCount):
        prevByte,nextByte = nextByte,bytes.getBigInt(1)
        unitType = prevByte & headMask | ((nextByte & wHeadMask) >> (8-extras))
        prevByte,nextByte = nextByte,bytes.getBigInt(1)
        unitType = unitType << 8 | (prevByte & wTailMask) << extras | nextByte & tailMask
        prevByte,nextByte = nextByte,bytes.getBigInt(1)
        unitType = unitType << 8 | (prevByte & headMask) << extras | nextByte & tailMask
        prevByte,nextByte = nextByte,bytes.getBigInt(1)
        unitCount = prevByte & headMask | nextByte & tailMask

    prevByte,nextByte = nextByte,bytes.getBigInt(1)
    totalUnits = prevByte & headMask | nextByte & tailMask

    for i in range(0,totalUnits):
        prevByte,nextByte = nextByte,bytes.getBigInt(1)
        unitId = prevByte & headMask | ((nextByte & wHeadMask) >> (8-extras))
        prevByte,nextByte = nextByte,bytes.getBigInt(1)
        unitId = unitId << 8 | prevByte & wTailMask << extras | ((nextByte & wHeadMask) >> (8-extras))
        prevByte,nextByte = nextByte,bytes.getBigInt(1)
        unitId = unitId << 8 | prevByte & wTailMask << extras | ((nextByte & wHeadMask) >> (8-extras))
        prevByte,nextByte = nextByte,bytes.getBigInt(1)
        unitId = unitId << 8 | prevByte & wTailMask << extras | nextByte & tailMask
Clone this wiki locally