Skip to content

Unit Orders Guide

AI Summary

Parse player commands using CDOTAUserMsg_SpectatorPlayerUnitOrders. Detect attack commands (order_type=4) to analyze creep aggro triggers, last-hit patterns, and harass behavior. Each order includes player entity, order type, target, position, and ability. Key for lane mechanics analysis: when a hero issues ATTACK_TARGET on enemy hero within 500 units of enemy creeps, it triggers creep aggro.


Overview

The CDOTAUserMsg_SpectatorPlayerUnitOrders callback captures every command a player issues to their units. This is essential for analyzing:

  • Creep aggro triggers - When players right-click enemy heroes
  • Last-hit patterns - Attack commands on creeps
  • Ability usage - Cast orders with targets
  • Movement patterns - Move and patrol commands
  • Micro decisions - Multi-unit control
from python_manta import MantaParser

parser = MantaParser()
result = parser.parse_universal(
    "match.dem",
    "CDOTAUserMsg_SpectatorPlayerUnitOrders",
    max_messages=10000
)

for msg in result.messages:
    order_type = msg.data.get("order_type", 0)
    target = msg.data.get("target_index", 0)
    print(f"[{msg.tick}] Order {order_type}, target={target}")

Message Structure

Fields

Field Type Description
entindex int Entity index of the player/unit issuing the order
order_type int Type of order (see Order Types below)
units list[int] Entity indices of units receiving the order
target_index int Target entity index (for targeted orders)
ability_id int Ability ID (for cast orders)
position dict Target position {x, y, z} (for position orders)
queue bool Whether order is queued (shift-click)
sequence_number int Order sequence number
flags int Order flags

Order Types

The order_type field maps to dotaunitorder_t enum values:

Movement Orders

Value Name Description
0 NONE No order
1 MOVE_TO_POSITION Move to ground position (right-click ground)
2 MOVE_TO_TARGET Move to/follow a unit
28 MOVE_TO_DIRECTION Move in direction
29 PATROL Patrol command
39 MOVE_RELATIVE Relative movement

Attack Orders

Value Name Description
3 ATTACK_MOVE Attack-move (A-click ground)
4 ATTACK_TARGET Attack specific target (right-click enemy)

Ability Orders

Value Name Description
5 CAST_POSITION Cast ability on ground
6 CAST_TARGET Cast ability on unit
7 CAST_TARGET_TREE Cast ability on tree
8 CAST_NO_TARGET Cast ability (no target)
9 CAST_TOGGLE Toggle ability
20 CAST_TOGGLE_AUTO Toggle autocast
40 CAST_TOGGLE_ALT Alt toggle

Control Orders

Value Name Description
10 HOLD_POSITION Hold position (H key)
21 STOP Stop command (S key)
33 CONTINUE Continue previous order

Item Orders

Value Name Description
12 DROP_ITEM Drop item on ground
13 GIVE_ITEM Give item to ally
14 PICKUP_ITEM Pick up item
15 PICKUP_RUNE Pick up rune
16 PURCHASE_ITEM Purchase item
17 SELL_ITEM Sell item
18 DISASSEMBLE_ITEM Disassemble item
19 MOVE_ITEM Move item in inventory
41 CONSUME_ITEM Consume item

Other Orders

Value Name Description
11 TRAIN_ABILITY Level up ability
22 TAUNT Taunt
23 BUYBACK Buyback
24 GLYPH Activate glyph
26 CAST_RUNE Use bottle on rune
27 PING_ABILITY Ping ability cooldown
30 VECTOR_TARGET_POSITION Vector targeted ability
31 RADAR Scan ability

Creep Aggro Detection

How Creep Aggro Works in Dota 2

Creep aggro is triggered when: 1. A hero issues an attack command (order_type=4) on an enemy hero 2. The hero is within 500 units of enemy lane creeps 3. This puts creeps on a 2.5 second aggro cooldown

Detecting Aggro Triggers

from python_manta import MantaParser

parser = MantaParser()

# Get all attack orders
orders = parser.parse_universal(
    "match.dem",
    "CDOTAUserMsg_SpectatorPlayerUnitOrders",
    max_messages=50000
)

ATTACK_TARGET = 4

attack_commands = []
for msg in orders.messages:
    order_type = msg.data.get("order_type", 0)

    if order_type == ATTACK_TARGET:
        attack_commands.append({
            "tick": msg.tick,
            "player_entity": msg.data.get("entindex"),
            "target_entity": msg.data.get("target_index"),
            "units": msg.data.get("units", []),
            "queued": msg.data.get("queue", False),
        })

print(f"Found {len(attack_commands)} attack commands")

Identifying Hero-on-Hero Attacks

To determine if an attack triggers creep aggro, you need to: 1. Check if the attacker is a hero 2. Check if the target is an enemy hero 3. Check proximity to enemy creeps (requires entity position data)

from python_manta import MantaParser

parser = MantaParser()

# Get attack orders
orders = parser.parse_universal(
    "match.dem",
    "CDOTAUserMsg_SpectatorPlayerUnitOrders",
    max_messages=50000
)

# Get entity data to identify heroes
entities = parser.query_entities(
    "match.dem",
    class_filter="Hero",
    max_entities=20
)

# Build hero entity index set
hero_entities = {e.index for e in entities.entities}

ATTACK_TARGET = 4

potential_aggro_triggers = []
for msg in orders.messages:
    if msg.data.get("order_type") != ATTACK_TARGET:
        continue

    target = msg.data.get("target_index", 0)

    # Check if target is a hero (potential aggro trigger)
    if target in hero_entities:
        potential_aggro_triggers.append({
            "tick": msg.tick,
            "attacker": msg.data.get("entindex"),
            "target_hero": target,
        })

print(f"Potential aggro triggers: {len(potential_aggro_triggers)}")

Full Aggro Analysis with Positions

For complete aggro analysis, combine orders with entity snapshots:

from python_manta import MantaParser
import math

parser = MantaParser()

# Parse attack orders
orders = parser.parse_universal(
    "match.dem",
    "CDOTAUserMsg_SpectatorPlayerUnitOrders",
    max_messages=50000
)

# Get attack target orders
ATTACK_TARGET = 4
attack_ticks = []

for msg in orders.messages:
    if msg.data.get("order_type") == ATTACK_TARGET:
        attack_ticks.append(msg.tick)

# Sample entity positions at attack moments
# Use unique ticks to avoid duplicates
unique_ticks = sorted(set(attack_ticks))[:100]  # Limit for performance

if unique_ticks:
    snapshots = parser.parse_entities(
        "match.dem",
        target_ticks=unique_ticks,
        max_snapshots=100
    )

    # Analyze each snapshot for creep proximity
    AGGRO_RANGE = 500

    for snapshot in snapshots.snapshots:
        # Get hero positions
        heroes = {h.hero_name: (h.x, h.y) for h in snapshot.heroes if h.hero_name}

        # Would need creep positions from entity data
        # This is a simplified example
        print(f"Tick {snapshot.tick}: {len(heroes)} heroes tracked")

Lane Mechanics Analysis

Last-Hit Pattern Detection

from python_manta import MantaParser
from collections import defaultdict

parser = MantaParser()
orders = parser.parse_universal(
    "match.dem",
    "CDOTAUserMsg_SpectatorPlayerUnitOrders",
    max_messages=100000
)

ATTACK_TARGET = 4
ATTACK_MOVE = 3

player_attacks = defaultdict(list)

for msg in orders.messages:
    order_type = msg.data.get("order_type", 0)

    if order_type in [ATTACK_TARGET, ATTACK_MOVE]:
        player = msg.data.get("entindex")
        player_attacks[player].append({
            "tick": msg.tick,
            "type": "target" if order_type == ATTACK_TARGET else "move",
            "target": msg.data.get("target_index"),
        })

# Analyze attack patterns
for player, attacks in player_attacks.items():
    target_attacks = [a for a in attacks if a["type"] == "target"]
    move_attacks = [a for a in attacks if a["type"] == "move"]

    print(f"Player entity {player}:")
    print(f"  Direct attacks: {len(target_attacks)}")
    print(f"  Attack-moves: {len(move_attacks)}")

Harass Timing Analysis

Analyze when players harass vs last-hit:

from python_manta import MantaParser

parser = MantaParser()

# Get orders and combat log for correlation
orders = parser.parse_universal(
    "match.dem",
    "CDOTAUserMsg_SpectatorPlayerUnitOrders",
    max_messages=50000
)

# Filter to laning phase (first 10 minutes ~ 18000 ticks at 30 ticks/sec)
LANING_END_TICK = 18000
ATTACK_TARGET = 4

laning_attacks = []
for msg in orders.messages:
    if msg.tick > LANING_END_TICK:
        break

    if msg.data.get("order_type") == ATTACK_TARGET:
        laning_attacks.append({
            "tick": msg.tick,
            "player": msg.data.get("entindex"),
            "target": msg.data.get("target_index"),
        })

print(f"Laning phase attacks: {len(laning_attacks)}")

# Group by time windows (30 second intervals)
from collections import defaultdict
TICKS_PER_30_SEC = 900

time_windows = defaultdict(int)
for attack in laning_attacks:
    window = attack["tick"] // TICKS_PER_30_SEC
    time_windows[window] += 1

print("\nAttack frequency by 30-second windows:")
for window, count in sorted(time_windows.items()):
    minutes = (window * 30) // 60
    seconds = (window * 30) % 60
    print(f"  {minutes}:{seconds:02d}: {count} attacks")

Multi-Unit Control Analysis

For heroes with summons or illusions:

from python_manta import MantaParser

parser = MantaParser()
orders = parser.parse_universal(
    "match.dem",
    "CDOTAUserMsg_SpectatorPlayerUnitOrders",
    max_messages=50000
)

# Find orders with multiple units
multi_unit_orders = []
for msg in orders.messages:
    units = msg.data.get("units", [])
    if len(units) > 1:
        multi_unit_orders.append({
            "tick": msg.tick,
            "order_type": msg.data.get("order_type"),
            "unit_count": len(units),
            "units": units,
        })

print(f"Multi-unit orders: {len(multi_unit_orders)}")

# Analyze micro patterns
from collections import Counter
order_types = Counter(o["order_type"] for o in multi_unit_orders)
print("\nMulti-unit order types:")
for order_type, count in order_types.most_common():
    print(f"  Type {order_type}: {count}")

Queued Commands Analysis

Detect shift-queued commands:

from python_manta import MantaParser

parser = MantaParser()
orders = parser.parse_universal(
    "match.dem",
    "CDOTAUserMsg_SpectatorPlayerUnitOrders",
    max_messages=50000
)

queued_orders = []
for msg in orders.messages:
    if msg.data.get("queue", False):
        queued_orders.append({
            "tick": msg.tick,
            "order_type": msg.data.get("order_type"),
            "player": msg.data.get("entindex"),
        })

print(f"Queued orders: {len(queued_orders)}")
print(f"Queue usage rate: {len(queued_orders) / len(orders.messages) * 100:.1f}%")

Performance Considerations

High Volume Callback

CDOTAUserMsg_SpectatorPlayerUnitOrders fires frequently - expect 10,000-50,000+ messages per match. Always set max_messages appropriately.

# For full match analysis
orders = parser.parse_universal("match.dem", "SpectatorPlayerUnitOrders", 100000)

# For laning phase only
orders = parser.parse_universal("match.dem", "SpectatorPlayerUnitOrders", 20000)

# For sampling/overview
orders = parser.parse_universal("match.dem", "SpectatorPlayerUnitOrders", 5000)

Efficient Filtering

Filter early to reduce memory:

ATTACK_TARGET = 4
ATTACK_MOVE = 3

# Process in batches if needed
attack_orders = []
result = parser.parse_universal("match.dem", "SpectatorPlayerUnitOrders", 50000)

for msg in result.messages:
    order_type = msg.data.get("order_type", 0)
    if order_type in [ATTACK_TARGET, ATTACK_MOVE]:
        attack_orders.append(msg)

# Now work with filtered subset
print(f"Attack orders: {len(attack_orders)} of {len(result.messages)} total")

Task API
Entity positions parse_entities()
Combat damage parse_combat_log()
Hero state query_entities(class_filter="Hero")
Ability usage parse_universal("CDOTAUserMsg_UnitEvent")

Order Type Reference

Complete dotaunitorder_t enum:

0  = DOTA_UNIT_ORDER_NONE
1  = DOTA_UNIT_ORDER_MOVE_TO_POSITION
2  = DOTA_UNIT_ORDER_MOVE_TO_TARGET
3  = DOTA_UNIT_ORDER_ATTACK_MOVE
4  = DOTA_UNIT_ORDER_ATTACK_TARGET
5  = DOTA_UNIT_ORDER_CAST_POSITION
6  = DOTA_UNIT_ORDER_CAST_TARGET
7  = DOTA_UNIT_ORDER_CAST_TARGET_TREE
8  = DOTA_UNIT_ORDER_CAST_NO_TARGET
9  = DOTA_UNIT_ORDER_CAST_TOGGLE
10 = DOTA_UNIT_ORDER_HOLD_POSITION
11 = DOTA_UNIT_ORDER_TRAIN_ABILITY
12 = DOTA_UNIT_ORDER_DROP_ITEM
13 = DOTA_UNIT_ORDER_GIVE_ITEM
14 = DOTA_UNIT_ORDER_PICKUP_ITEM
15 = DOTA_UNIT_ORDER_PICKUP_RUNE
16 = DOTA_UNIT_ORDER_PURCHASE_ITEM
17 = DOTA_UNIT_ORDER_SELL_ITEM
18 = DOTA_UNIT_ORDER_DISASSEMBLE_ITEM
19 = DOTA_UNIT_ORDER_MOVE_ITEM
20 = DOTA_UNIT_ORDER_CAST_TOGGLE_AUTO
21 = DOTA_UNIT_ORDER_STOP
22 = DOTA_UNIT_ORDER_TAUNT
23 = DOTA_UNIT_ORDER_BUYBACK
24 = DOTA_UNIT_ORDER_GLYPH
25 = DOTA_UNIT_ORDER_EJECT_ITEM_FROM_STASH
26 = DOTA_UNIT_ORDER_CAST_RUNE
27 = DOTA_UNIT_ORDER_PING_ABILITY
28 = DOTA_UNIT_ORDER_MOVE_TO_DIRECTION
29 = DOTA_UNIT_ORDER_PATROL
30 = DOTA_UNIT_ORDER_VECTOR_TARGET_POSITION
31 = DOTA_UNIT_ORDER_RADAR
32 = DOTA_UNIT_ORDER_SET_ITEM_COMBINE_LOCK
33 = DOTA_UNIT_ORDER_CONTINUE
34 = DOTA_UNIT_ORDER_VECTOR_TARGET_CANCELED
35 = DOTA_UNIT_ORDER_CAST_RIVER_PAINT
36 = DOTA_UNIT_ORDER_PREGAME_ADJUST_ITEM_ASSIGNMENT
37 = DOTA_UNIT_ORDER_DROP_ITEM_AT_FOUNTAIN
38 = DOTA_UNIT_ORDER_TAKE_ITEM_FROM_NEUTRAL_ITEM_STASH
39 = DOTA_UNIT_ORDER_MOVE_RELATIVE
40 = DOTA_UNIT_ORDER_CAST_TOGGLE_ALT
41 = DOTA_UNIT_ORDER_CONSUME_ITEM
42 = DOTA_UNIT_ORDER_SET_ITEM_MARK_FOR_SELL