Attacks Reference¶
AI Summary
Complete reference for attack tracking from both TE_Projectile (ranged) and combat log (melee). Captures ALL auto-attacks in the game with consistent fields: entity indices, names, timing, and damage. Typically 30,000+ events per match combining ranged projectiles and melee hits.
Overview¶
The attacks collector unifies two data sources into a single consistent API:
- Ranged attacks: From
TE_Projectilemessages (projectile speed, dodgeable) - Melee attacks: From combat log DAMAGE events with no ability (damage values)
Both types share the same AttackEvent model with all fields populated where data is available.
Key Use Cases:
- Last-hit analysis (who attacked which creep)
- Lane farming patterns
- Hero attack tracking (both melee and ranged heroes)
- Tower/creep/neutral combat analysis
AttackEvent Fields¶
Every AttackEvent contains these fields, populated consistently for both ranged and melee:
Timing¶
| Field | Type | Description |
|---|---|---|
tick |
int |
Game tick (~30/second) |
game_time |
float |
Game time in seconds from horn |
game_time_str |
str |
Formatted time (e.g., "15:34") |
launch_tick |
int |
Tick when projectile was launched (ranged only) |
Entity Identification¶
| Field | Type | Ranged | Melee | Description |
|---|---|---|---|---|
source_index |
int |
✓ | ✓ | Entity ID of attacker |
target_index |
int |
✓ | ✓* | Entity ID of target |
attacker_name |
str |
✓ | ✓ | Unit name (e.g., npc_dota_hero_troll_warlord) |
target_name |
str |
✓ | ✓ | Target unit name |
*Melee target_index populated for heroes, may be 0 for creeps/neutrals
Attack Properties¶
| Field | Type | Ranged | Melee | Description |
|---|---|---|---|---|
is_melee |
bool |
False |
True |
Attack type identifier |
damage |
int |
0 | ✓ | Damage dealt |
target_health |
int |
0 | ✓ | Target health AFTER attack |
projectile_speed |
int |
✓ | 0 | Projectile move speed |
dodgeable |
bool |
✓ | False |
Can be disjointed |
location_x |
float |
✓ | ✓* | Attacker position X |
location_y |
float |
✓ | ✓* | Attacker position Y |
attacker_team |
int |
0 | ✓ | Attacker team (2=Radiant, 3=Dire) |
target_team |
int |
0 | ✓ | Target team (2=Radiant, 3=Dire) |
is_attacker_hero |
bool |
False |
✓ | Attacker is a hero |
is_target_hero |
bool |
False |
✓ | Target is a hero |
*Melee location from entity lookup (heroes ~37%, creeps 0)
Raw Handles (Advanced)¶
| Field | Type | Description |
|---|---|---|
source_handle |
int |
Raw entity handle (ranged only) |
target_handle |
int |
Raw entity handle (ranged only) |
Basic Usage¶
from python_manta import Parser
parser = Parser("match.dem")
# Collect all attack events (ranged + melee)
result = parser.parse(attacks={})
print(f"Total attacks: {result.attacks.total_events}")
# Separate by type
melee = [a for a in result.attacks.events if a.is_melee]
ranged = [a for a in result.attacks.events if not a.is_melee]
print(f"Melee: {len(melee)}, Ranged: {len(ranged)}")
Limiting Events¶
Hero Name Format¶
All hero names use the canonical Dota 2 format with underscores:
# Correct format (matches combat log)
"npc_dota_hero_troll_warlord"
"npc_dota_hero_faceless_void"
"npc_dota_hero_shadow_shaman"
"npc_dota_hero_monkey_king"
"npc_dota_hero_storm_spirit"
# NOT: "npc_dota_hero_trollwarlord" (incorrect)
Use Case Examples¶
Lane Farming Analysis¶
result = parser.parse(attacks={})
# Find all attacks on lane creeps
lane_creep_attacks = [
a for a in result.attacks.events
if "creep_goodguys" in a.target_name or "creep_badguys" in a.target_name
]
# Group by attacker
from collections import Counter
attackers = Counter(a.attacker_name for a in lane_creep_attacks)
for name, count in attackers.most_common(10):
print(f"{name}: {count} attacks on lane creeps")
Contested Last Hits¶
result = parser.parse(attacks={}, entity_deaths={"include_creeps": True})
# Find creeps that were attacked by multiple heroes before death
from collections import defaultdict
creep_attackers = defaultdict(set)
for attack in result.attacks.events:
if "npc_dota_hero" in attack.attacker_name:
creep_attackers[attack.target_name].add(attack.attacker_name)
contested = {k: v for k, v in creep_attackers.items() if len(v) > 1}
print(f"Creeps contested by multiple heroes: {len(contested)}")
Hero vs Hero Combat¶
result = parser.parse(attacks={})
# Find hero-on-hero attacks
hero_fights = [
a for a in result.attacks.events
if "npc_dota_hero" in a.attacker_name and "npc_dota_hero" in a.target_name
]
print(f"Hero vs hero attacks: {len(hero_fights)}")
# Sample output
for a in hero_fights[:5]:
atk = a.attacker_name.replace("npc_dota_hero_", "")
tgt = a.target_name.replace("npc_dota_hero_", "")
dmg = f", dmg={a.damage}" if a.is_melee else ""
print(f"[{a.game_time_str}] {atk} -> {tgt}{dmg}")
Melee Hero Tracking (e.g., Troll Warlord)¶
result = parser.parse(attacks={})
# Troll Warlord has both ranged and melee forms
troll_attacks = [a for a in result.attacks.events if "troll_warlord" in a.attacker_name]
troll_melee = [a for a in troll_attacks if a.is_melee]
troll_ranged = [a for a in troll_attacks if not a.is_melee]
print(f"Troll melee attacks: {len(troll_melee)}")
print(f"Troll ranged attacks: {len(troll_ranged)}")
# Melee attacks have damage
if troll_melee:
avg_dmg = sum(a.damage for a in troll_melee) / len(troll_melee)
print(f"Average melee damage: {avg_dmg:.1f}")
Entity Index Consistency¶
Entity indices match between attacks and snapshots:
result = parser.parse(attacks={})
snap = parser.snapshot(target_tick=30000)
# Build hero index map
hero_by_id = {h.entity_id: h.hero_name for h in snap.heroes}
# Verify consistency
for attack in result.attacks.events[:100]:
if attack.source_index in hero_by_id:
assert attack.attacker_name == hero_by_id[attack.source_index]
Statistics (Real Match Data)¶
From match 8447659831 (Team Spirit vs Tundra, ~46 min):
| Metric | Value |
|---|---|
| Total attacks | 32,895 |
| Melee attacks | 17,000 (52%) |
| Ranged attacks | 15,895 (48%) |
| Troll Warlord melee | 2,791 |
| Pugna ranged | 235 |
Combat Log vs Attacks¶
| Data Type | Combat Log | Attacks Collector |
|---|---|---|
| Hero damage | Yes (all) | Yes (auto-attacks only) |
| Melee auto-attacks | Yes (as damage) | Yes (with entity ID) |
| Ranged auto-attacks | No | Yes (with projectile data) |
| Tower attacks on creeps | No | Yes |
| Neutral attacks | No | Yes |
| Ability damage | Yes | No |
| Kill attribution | Yes | No |
Rule of thumb: - Use combat log for damage analysis and kill tracking - Use attacks for attack pattern analysis and entity correlation
Performance Notes¶
- Total events: ~30,000+ per match (ranged + melee)
- Use
max_eventsto limit for faster parsing - Events are ordered by tick
- Entity name → index mapping is automatic for heroes