Skip to content

Entity Queries Guide

AI Summary

Query game entities using query_entities(). Filter by class name (substring or exact match) and select specific properties. Use at_tick to query at a specific game tick (default: end of game). Common classes: Hero (player heroes), Tower, Courier, Creep, CDOTA_Item_MagicStick, CDOTA_Item_MagicWand. Hero properties include m_iHealth, m_iMaxHealth, m_flMana, m_vecOrigin (position). Item properties include m_iCurrentCharges, m_iPlayerOwnerID. Use property_filter for performance.


Overview

Entity queries let you extract game state from the replay, including hero stats, positions, items, and unit properties.

from python_manta import MantaParser

parser = MantaParser()
result = parser.query_entities("match.dem", class_filter="Hero", max_entities=10)

for entity in result.entities:
    health = entity.properties.get("m_iHealth", 0)
    max_hp = entity.properties.get("m_iMaxHealth", 0)
    print(f"{entity.class_name}: {health}/{max_hp} HP")

Filtering by Class

Substring Filter

# All entities with "Hero" in class name
heroes = parser.query_entities("match.dem", class_filter="Hero", max_entities=20)

# All tower entities
towers = parser.query_entities("match.dem", class_filter="Tower", max_entities=30)

# All courier entities
couriers = parser.query_entities("match.dem", class_filter="Courier", max_entities=10)

Exact Class Names

# Specific hero classes
result = parser.query_entities(
    "match.dem",
    class_names=[
        "CDOTA_Unit_Hero_Invoker",
        "CDOTA_Unit_Hero_Pudge",
        "CDOTA_Unit_Hero_AntiMage"
    ],
    max_entities=10
)

# Specific building types
buildings = parser.query_entities(
    "match.dem",
    class_names=[
        "CDOTA_BaseNPC_Tower",
        "CDOTA_BaseNPC_Barracks",
        "CDOTA_BaseNPC_Fort"
    ],
    max_entities=50
)

Property Filtering

For better performance, specify only the properties you need:

# Only get health and position
result = parser.query_entities(
    "match.dem",
    class_filter="Hero",
    property_filter=["m_iHealth", "m_iMaxHealth", "m_vecOrigin"],
    max_entities=10
)

for entity in result.entities:
    pos = entity.properties.get("m_vecOrigin", [0, 0, 0])
    print(f"{entity.class_name} at ({pos[0]:.0f}, {pos[1]:.0f})")

Common Entity Classes

Class Pattern Description
Hero Player-controlled heroes
Tower Tower buildings
Barracks Barracks buildings
Fort Ancient/Throne
Courier Courier units
Creep Lane creeps
NeutralCreep Jungle creeps
Roshan Roshan
Ward Observer/Sentry wards

Hero Properties

Core Stats

Property Type Description
m_iHealth int Current health
m_iMaxHealth int Maximum health
m_flMana float Current mana
m_flMaxMana float Maximum mana
m_iCurrentLevel int Hero level
m_flStrength float Strength attribute
m_flAgility float Agility attribute
m_flIntellect float Intelligence attribute

Position and Movement

Property Type Description
m_vecOrigin list[float] Position [x, y, z]
m_angRotation list[float] Rotation angles
m_flMovementSpeed float Movement speed

Economy

Property Type Description
m_iTotalEarnedGold int Total gold earned
m_iUnreliableGold int Unreliable gold
m_iReliableGold int Reliable gold
m_iLastHits int Last hits
m_iDenies int Denies

KDA

Property Type Description
m_iKills int Kills
m_iDeaths int Deaths
m_iAssists int Assists

Combat

Property Type Description
m_flPhysicalArmorValue float Armor
m_flMagicalResistanceValue float Magic resistance
m_iDamageMin int Minimum damage
m_iDamageMax int Maximum damage
m_iAttackRange int Attack range

Common Use Cases

End Game Scoreboard

result = parser.query_entities(
    "match.dem",
    class_filter="Hero",
    property_filter=[
        "m_iCurrentLevel",
        "m_iKills", "m_iDeaths", "m_iAssists",
        "m_iTotalEarnedGold", "m_iLastHits", "m_iDenies"
    ],
    max_entities=10
)

print("End Game Scoreboard:")
print("-" * 70)
print(f"{'Hero':<30} {'Lvl':>4} {'K':>3} {'D':>3} {'A':>3} {'LH':>5} {'Gold':>8}")
print("-" * 70)

for entity in result.entities:
    props = entity.properties
    print(f"{entity.class_name:<30} "
          f"{props.get('m_iCurrentLevel', 0):>4} "
          f"{props.get('m_iKills', 0):>3} "
          f"{props.get('m_iDeaths', 0):>3} "
          f"{props.get('m_iAssists', 0):>3} "
          f"{props.get('m_iLastHits', 0):>5} "
          f"{props.get('m_iTotalEarnedGold', 0):>8,}")

Hero Positions Map

result = parser.query_entities(
    "match.dem",
    class_filter="Hero",
    property_filter=["m_vecOrigin", "m_iTeamNum"],
    max_entities=10
)

print("Hero Positions (end of game):")
for entity in result.entities:
    pos = entity.properties.get("m_vecOrigin", [0, 0, 0])
    team = entity.properties.get("m_iTeamNum", 0)
    team_name = "Radiant" if team == 2 else "Dire" if team == 3 else "Unknown"

    print(f"{entity.class_name}: ({pos[0]:.0f}, {pos[1]:.0f}) - {team_name}")

Building Status

# Check tower status
towers = parser.query_entities(
    "match.dem",
    class_filter="Tower",
    property_filter=["m_iHealth", "m_iMaxHealth", "m_iTeamNum"],
    max_entities=30
)

print("Tower Status:")
radiant_towers = 0
dire_towers = 0

for tower in towers.entities:
    health = tower.properties.get("m_iHealth", 0)
    max_health = tower.properties.get("m_iMaxHealth", 0)
    team = tower.properties.get("m_iTeamNum", 0)

    if health > 0:
        if team == 2:
            radiant_towers += 1
        elif team == 3:
            dire_towers += 1

print(f"Radiant towers remaining: {radiant_towers}")
print(f"Dire towers remaining: {dire_towers}")

Item Slots

result = parser.query_entities(
    "match.dem",
    class_filter="Hero",
    property_filter=[
        "m_hItems.0000", "m_hItems.0001", "m_hItems.0002",
        "m_hItems.0003", "m_hItems.0004", "m_hItems.0005"
    ],
    max_entities=10
)

for entity in result.entities:
    print(f"\n{entity.class_name} items:")
    for i in range(6):
        item_handle = entity.properties.get(f"m_hItems.{i:04d}")
        if item_handle and item_handle != 16777215:  # Invalid handle
            print(f"  Slot {i}: {item_handle}")

Neutral Creeps

result = parser.query_entities(
    "match.dem",
    class_filter="NeutralCreep",
    property_filter=["m_iHealth", "m_iMaxHealth", "m_vecOrigin"],
    max_entities=50
)

alive_creeps = [e for e in result.entities if e.properties.get("m_iHealth", 0) > 0]
print(f"Neutral creeps alive: {len(alive_creeps)}")

Entity Data Structure

Each EntityData object contains:

class EntityData(BaseModel):
    index: int                    # Entity index
    serial: int                   # Entity serial number
    class_name: str               # Entity class name (e.g., "CDOTA_Unit_Hero_Invoker")
    properties: Dict[str, Any]    # Entity properties

The properties dictionary contains all requested or available properties for the entity.


Result Information

The EntitiesResult includes metadata:

result = parser.query_entities("match.dem", class_filter="Hero", max_entities=10)

print(f"Total entities returned: {result.total_entities}")
print(f"Captured at tick: {result.tick}")
print(f"Network tick: {result.net_tick}")
print(f"Parse success: {result.success}")

Performance Tips

  1. Use property_filter - Specify only needed properties to reduce memory and processing
  2. Use class_names for exact matches - More efficient than substring filter
  3. Set max_entities - Limit results when you only need a few entities
  4. Query once, process multiple times - Entity queries are at end of replay, cache results
# Efficient: specific properties
result = parser.query_entities(
    "match.dem",
    class_filter="Hero",
    property_filter=["m_iHealth", "m_iKills"],
    max_entities=10
)

# Less efficient: all properties
result = parser.query_entities(
    "match.dem",
    class_filter="Hero",
    max_entities=10
)

Querying at Specific Ticks

Use the at_tick parameter to query entity state at a specific game tick instead of end of game:

# Query heroes at tick 50000
result = parser.query_entities(
    "match.dem",
    class_filter="Hero",
    property_filter=["m_iHealth", "m_iMaxHealth"],
    at_tick=50000,
    max_entities=10
)

print(f"Hero health at tick {result.tick}:")
for entity in result.entities:
    health = entity.properties.get("m_iHealth", 0)
    max_hp = entity.properties.get("m_iMaxHealth", 0)
    print(f"  {entity.class_name}: {health}/{max_hp}")

Item Charges at Specific Time

Get Magic Stick/Wand charges for all players at a specific tick:

from python_manta import MantaParser

parser = MantaParser()

def get_magic_stick_charges(demo_path: str, at_tick: int = 0) -> dict:
    """
    Get magic stick/wand charges for all players at a specific game tick.

    Args:
        demo_path: Path to the .dem file
        at_tick: Game tick to query (0 = end of game)

    Returns:
        Dict mapping player_id -> charges
    """
    result = parser.query_entities(
        demo_path,
        class_names=['CDOTA_Item_MagicStick', 'CDOTA_Item_MagicWand'],
        property_filter=['m_iCurrentCharges', 'm_iPlayerOwnerID'],
        at_tick=at_tick
    )

    player_charges = {}
    for entity in result.entities:
        owner = entity.properties.get('m_iPlayerOwnerID', -1)
        charges = entity.properties.get('m_iCurrentCharges', 0)
        if owner >= 0:
            player_charges[owner] = max(player_charges.get(owner, 0), charges)

    return player_charges

# Get charges at tick 50000
charges = get_magic_stick_charges("match.dem", at_tick=50000)
# {0: 6, 2: 18, 4: 7, 6: 6, 8: 1, 10: 15, 12: 4, 14: 20, 16: 0, 18: 5}

Important Notes

Note

By default (at_tick=0), entity queries return the state at the end of the replay. Use at_tick to query state at a specific game tick.

Entity Handles

Some properties reference other entities via handles:

# Item handles reference item entities
item_handle = hero.properties.get("m_hItems.0000")

# These are internal entity references, not item IDs
# Use string tables or item events for item identification

Team Numbers

Team ID Team
2 Radiant
3 Dire
0/1 Neutral/Spectator

Hero Position Tracking with parse_entities()

For tracking hero positions over time (not just end of game), use parse_entities() instead of query_entities().

Basic Position Tracking

from python_manta import MantaParser

parser = MantaParser()

# Capture snapshots every 30 seconds (900 ticks at 30 ticks/sec)
result = parser.parse_entities("match.dem", interval_ticks=900, max_snapshots=100)

for snapshot in result.snapshots:
    print(f"Game time: {snapshot.game_time:.1f}s")
    for hero in snapshot.heroes:
        print(f"  {hero.hero_name}: ({hero.x:.0f}, {hero.y:.0f})")

Position at Specific Ticks

Use target_ticks to capture state at exact moments:

# Get hero positions at specific ticks
result = parser.parse_entities("match.dem", target_ticks=[30000, 45000, 60000])

for snapshot in result.snapshots:
    print(f"Tick {snapshot.tick}:")
    for hero in snapshot.heroes:
        print(f"  {hero.hero_name}: ({hero.x:.0f}, {hero.y:.0f})")

Filtering by Hero

Use target_heroes to only get specific heroes. Use the npc_dota_hero_* format (same as combat log):

# Get only specific heroes
result = parser.parse_entities(
    "match.dem",
    target_ticks=[50000],
    target_heroes=["npc_dota_hero_axe", "npc_dota_hero_lina"]
)

Getting Death Positions

Combat log location_x/location_y are always 0. To get death positions, combine combat log with entity snapshots:

# Get deaths from combat log
combat = parser.parse_combat_log("match.dem", types=[1])  # type 1 = deaths

for death in combat.entries:
    # Get victim's position at death tick
    result = parser.parse_entities(
        "match.dem",
        target_ticks=[death.tick],
        target_heroes=[death.target_name]  # e.g., "npc_dota_hero_axe"
    )

    if result.snapshots and result.snapshots[0].heroes:
        victim = result.snapshots[0].heroes[0]
        print(f"{victim.hero_name} died at ({victim.x:.0f}, {victim.y:.0f})")

Position Coordinate System

Hero positions use world coordinates centered on the map:

  • X axis: West (Radiant, negative) to East (Dire, positive)
  • Y axis: South (negative) to North (positive)
  • Origin (0,0): Center of the map
  • Range: Approximately -8000 to +8000 for both axes

HeroSnapshot Fields

Each hero in a snapshot includes:

Field Type Description
player_id int Player slot (0-9)
hero_id int Hero ID
hero_name str Hero name (npc_dota_hero_* format)
team int 2=Radiant, 3=Dire
x float X world coordinate
y float Y world coordinate
health int Current health
max_health int Maximum health
mana float Current mana
max_mana float Maximum mana
level int Hero level
last_hits int Last hits
denies int Denies
gold int Current gold
net_worth int Net worth
gpm int Gold per minute
xpm int XP per minute
kills int Kills
deaths int Deaths
assists int Assists
armor float Current armor
magic_resistance float Magic resistance (0-1)
damage_min int Minimum damage
damage_max int Maximum damage
strength float Strength attribute
agility float Agility attribute
intellect float Intellect attribute
abilities list List of AbilitySnapshot
talents list List of TalentChoice
is_illusion bool True if illusion
is_clone bool True if clone (e.g., Meepo)

Position Data Sources Comparison

Method Use Case Position Data
parse_entities() Time-series or specific ticks position_x, position_y
query_entities() End-of-game state ✅ Raw CBodyComponent.m_cellX/Y
parse_combat_log() Combat events location_x/y always 0

Hero Abilities and Talents

The parser.snapshot() method returns hero state including abilities and talent choices.

Basic Ability Tracking

from python_manta import Parser

parser = Parser("match.dem")
snap = parser.snapshot(target_tick=60000)  # ~33 minutes

for hero in snap.heroes:
    print(f"{hero.hero_name} (Level {hero.level})")

    # Show all abilities
    for ability in hero.abilities:
        if ability.level > 0:
            print(f"  {ability.short_name}: Level {ability.level}")

Ability Properties

Each AbilitySnapshot includes:

Field Type Description
slot int Ability slot (0-5 for regular abilities)
name str Full ability class name
level int Current ability level (0-4)
cooldown float Current cooldown remaining
max_cooldown float Maximum cooldown length
mana_cost int Mana cost
charges int Current charges
is_ultimate bool True if slot 5 (ultimate)

Helper Properties:

Property Type Description
short_name str Name without "CDOTA_Ability_" prefix
is_maxed bool True if at max level
is_on_cooldown bool True if cooldown > 0

Talent Tracking

Talents are tracked separately from abilities:

snap = parser.snapshot(target_tick=120000)  # Late game

for hero in snap.heroes:
    print(f"{hero.hero_name}: {hero.talents_chosen}/4 talents")

    for talent in hero.talents:
        print(f"  Level {talent.tier}: {talent.side}")

Each TalentChoice includes:

Field Type Description
tier int Talent tier (10, 15, 20, or 25)
slot int Raw ability slot index
is_left bool True if left talent chosen
name str Talent ability name
side str "left" or "right"

Finding Specific Abilities

Use helper methods to find abilities:

for hero in snap.heroes:
    # Find ability by name (partial match)
    omnislash = hero.get_ability("Omnislash")
    if omnislash and omnislash.level > 0:
        print(f"Omnislash level {omnislash.level}")

    # Check if ultimate is learned
    if hero.has_ultimate:
        print("Has ultimate!")

    # Get talent at specific tier
    lvl20_talent = hero.get_talent_at_tier(20)
    if lvl20_talent:
        print(f"Level 20 talent: {lvl20_talent.side}")

Tracking Skill Builds Over Time

Combine multiple snapshots to track skill progression:

from python_manta import Parser

parser = Parser("match.dem")

# Get snapshots at different times
ticks = [30000, 60000, 90000, 120000]

for tick in ticks:
    snap = parser.snapshot(target_tick=tick)

    for hero in snap.heroes:
        if "Juggernaut" in hero.hero_name:
            print(f"Tick {tick} - Level {hero.level}")
            for ab in hero.abilities:
                if ab.level > 0:
                    print(f"  {ab.short_name}: {ab.level}")
            break

Cooldown Analysis

Track ability cooldowns for timing analysis:

snap = parser.snapshot(target_tick=60000)

for hero in snap.heroes:
    print(f"\n{hero.hero_name} cooldowns:")
    for ability in hero.abilities:
        if ability.is_on_cooldown:
            print(f"  {ability.short_name}: {ability.cooldown:.1f}s remaining")
        elif ability.level > 0:
            print(f"  {ability.short_name}: ready")