python-manta 1.4.5.3__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_manta/__init__.py +159 -0
- python_manta/libmanta_wrapper.h +139 -0
- python_manta/libmanta_wrapper.so +0 -0
- python_manta/manta_python.py +2332 -0
- python_manta-1.4.5.3.dist-info/METADATA +1432 -0
- python_manta-1.4.5.3.dist-info/RECORD +8 -0
- python_manta-1.4.5.3.dist-info/WHEEL +5 -0
- python_manta-1.4.5.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1432 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-manta
|
|
3
|
+
Version: 1.4.5.3
|
|
4
|
+
Summary: Python interface for the Manta Dota 2 replay parser
|
|
5
|
+
Author-email: Equilibrium Coach Team <contact@equilibrium-coach.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DeepBlueCoding/python-manta
|
|
8
|
+
Project-URL: Repository, https://github.com/DeepBlueCoding/python-manta
|
|
9
|
+
Project-URL: Documentation, https://deepbluecoding.github.io/python-manta/
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/DeepBlueCoding/python-manta/issues
|
|
11
|
+
Keywords: dota2,replay,parser,gaming,esports,manta
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Go
|
|
23
|
+
Classifier: Topic :: Games/Entertainment
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Requires-Dist: pydantic>=2.0.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: isort>=5.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
34
|
+
Provides-Extra: build
|
|
35
|
+
Requires-Dist: cibuildwheel>=2.17.0; extra == "build"
|
|
36
|
+
|
|
37
|
+
# Python Manta
|
|
38
|
+
|
|
39
|
+
> **Python bindings for the [dotabuff/manta](https://github.com/dotabuff/manta) Dota 2 replay parser**
|
|
40
|
+
|
|
41
|
+
[](https://pypi.org/project/python-manta/)
|
|
42
|
+
[](https://deepbluecoding.github.io/python-manta/)
|
|
43
|
+
[](https://github.com/DeepBlueCoding/python-manta/actions/workflows/build-wheels.yml)
|
|
44
|
+
[](https://opensource.org/licenses/MIT)
|
|
45
|
+
[](https://www.python.org/downloads/)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## What This Library Does
|
|
50
|
+
|
|
51
|
+
**Python Manta is a wrapper/bindings library** that provides Python access to the excellent [Manta](https://github.com/dotabuff/manta) Go library for parsing Dota 2 replay files (`.dem`).
|
|
52
|
+
|
|
53
|
+
### Important Attribution
|
|
54
|
+
|
|
55
|
+
**All the heavy lifting is done by [dotabuff/manta](https://github.com/dotabuff/manta)** - the battle-tested Go replay parser maintained by [Dotabuff](https://www.dotabuff.com). This Python library simply:
|
|
56
|
+
|
|
57
|
+
1. Wraps the Manta Go library using CGO
|
|
58
|
+
2. Exposes a Pythonic API via ctypes
|
|
59
|
+
3. Provides type-safe Pydantic models for parsed data
|
|
60
|
+
|
|
61
|
+
If you're working in Go, use [Manta](https://github.com/dotabuff/manta) directly. This library exists for Python developers who need replay parsing capabilities.
|
|
62
|
+
|
|
63
|
+
### Library Philosophy
|
|
64
|
+
|
|
65
|
+
Python Manta is a **low-level data extraction library**, not an analytics tool. We provide:
|
|
66
|
+
|
|
67
|
+
| ✅ In Scope | ❌ Out of Scope |
|
|
68
|
+
|-------------|-----------------|
|
|
69
|
+
| Raw data extraction | Analysis/aggregation logic |
|
|
70
|
+
| Enums/constants for game data (`RuneType`, `EntityType`, `CombatLogType`, `DamageType`, `Team`, `NeutralItemTier`, `NeutralItem`) | Fight detection algorithms |
|
|
71
|
+
| Type-safe Pydantic models | Statistics computation |
|
|
72
|
+
| Simple helper properties (e.g., `is_pro_match()`) | Data interpretation |
|
|
73
|
+
|
|
74
|
+
**The line**: If it's mapping/typing game data → library. If it's interpreting/analyzing → user code.
|
|
75
|
+
|
|
76
|
+
Users should build analysis logic on top of the raw data we provide.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Table of Contents
|
|
81
|
+
|
|
82
|
+
- [Documentation](https://deepbluecoding.github.io/python-manta/) ← **Full docs with examples**
|
|
83
|
+
- [Versioning](#versioning)
|
|
84
|
+
- [Installation](#installation)
|
|
85
|
+
- [Quick Start](#quick-start)
|
|
86
|
+
- [V2 Parser API (Recommended)](#v2-parser-api-recommended)
|
|
87
|
+
- [API Reference](#api-reference)
|
|
88
|
+
- [Game Events](#game-events)
|
|
89
|
+
- [Modifiers](#modifiers)
|
|
90
|
+
- [Entity Queries](#entity-queries)
|
|
91
|
+
- [String Tables](#string-tables)
|
|
92
|
+
- [Combat Log](#combat-log)
|
|
93
|
+
- [Parser Info](#parser-info)
|
|
94
|
+
- [Supported Callbacks (272 Total)](#supported-callbacks-272-total)
|
|
95
|
+
- [Data Models](#data-models)
|
|
96
|
+
- [Common Use Cases](#common-use-cases)
|
|
97
|
+
- [Development Setup](#development-setup)
|
|
98
|
+
- [Architecture](#architecture)
|
|
99
|
+
- [AI Integration Guide](#ai-integration-guide)
|
|
100
|
+
- [Troubleshooting](#troubleshooting)
|
|
101
|
+
- [Contributing](#contributing)
|
|
102
|
+
- [License](#license)
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Versioning
|
|
107
|
+
|
|
108
|
+
Python Manta follows a **4-part versioning scheme** that tracks the upstream [dotabuff/manta](https://github.com/dotabuff/manta) version:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
v{manta_major}.{manta_minor}.{manta_patch}.{python_manta_release}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Version Part | Meaning |
|
|
115
|
+
|--------------|---------|
|
|
116
|
+
| `1.4.5` | Base dotabuff/manta version this release is built on |
|
|
117
|
+
| `.1`, `.2`, etc. | Python Manta release number for that manta version |
|
|
118
|
+
|
|
119
|
+
**Examples:**
|
|
120
|
+
- `v1.4.5` - Initial release based on manta v1.4.5
|
|
121
|
+
- `v1.4.5.1` - First update/bugfix release, still using manta v1.4.5
|
|
122
|
+
- `v1.4.5.2` - Second update, still using manta v1.4.5
|
|
123
|
+
- `v1.4.6` - New release when manta updates to v1.4.6
|
|
124
|
+
|
|
125
|
+
This scheme allows us to release updates (new features, bugfixes, documentation) without waiting for upstream manta releases (which happen ~twice per year).
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Installation
|
|
130
|
+
|
|
131
|
+
### From PyPI (Recommended)
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pip install python-manta
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Pre-built wheels are available for:
|
|
138
|
+
- Linux (x86_64)
|
|
139
|
+
- macOS (Intel and Apple Silicon)
|
|
140
|
+
- Windows (AMD64)
|
|
141
|
+
|
|
142
|
+
**No Go installation required** - wheels include pre-compiled binaries.
|
|
143
|
+
|
|
144
|
+
### Version Pinning
|
|
145
|
+
|
|
146
|
+
**Always use the latest release for your target Manta version** to get bug fixes and improvements:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Latest release for Manta 1.4.5.x (recommended)
|
|
150
|
+
pip install "python-manta>=1.4.5,<1.4.6"
|
|
151
|
+
|
|
152
|
+
# Or use compatible release operator
|
|
153
|
+
pip install "python-manta~=1.4.5"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### From Source
|
|
157
|
+
|
|
158
|
+
See [Building from Source](#building-from-source) section below.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Quick Start
|
|
163
|
+
|
|
164
|
+
### Parse Demo Header
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from python_manta import Parser
|
|
168
|
+
|
|
169
|
+
parser = Parser("match.dem")
|
|
170
|
+
result = parser.parse(header=True)
|
|
171
|
+
|
|
172
|
+
print(f"Map: {result.header.map_name}")
|
|
173
|
+
print(f"Server: {result.header.server_name}")
|
|
174
|
+
print(f"Build: {result.header.build_num}")
|
|
175
|
+
print(f"Network Protocol: {result.header.network_protocol}")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Parse Specific Messages
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from python_manta import Parser
|
|
182
|
+
|
|
183
|
+
parser = Parser("match.dem")
|
|
184
|
+
|
|
185
|
+
# Extract chat messages (limit to 100)
|
|
186
|
+
result = parser.parse(messages={"filter": "CDOTAUserMsg_ChatMessage", "max_messages": 100})
|
|
187
|
+
|
|
188
|
+
if result.success:
|
|
189
|
+
for msg in result.messages.messages:
|
|
190
|
+
print(f"[Tick {msg.tick}] Player {msg.data['source_player_id']}: {msg.data['message_text']}")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Parse Draft (Picks & Bans)
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from python_manta import Parser
|
|
197
|
+
|
|
198
|
+
parser = Parser("match.dem")
|
|
199
|
+
result = parser.parse(game_info=True)
|
|
200
|
+
|
|
201
|
+
for pick_ban in result.game_info.picks_bans:
|
|
202
|
+
action = "PICK" if pick_ban.is_pick else "BAN"
|
|
203
|
+
team = "Radiant" if pick_ban.team == 2 else "Dire"
|
|
204
|
+
print(f"{team} {action}: Hero ID {pick_ban.hero_id}")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Parser API
|
|
210
|
+
|
|
211
|
+
The `Parser` class provides **single-pass parsing** - all data collected in one file traversal. This is much more efficient when extracting multiple data types.
|
|
212
|
+
|
|
213
|
+
### Single-Pass Parsing
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from python_manta import Parser
|
|
217
|
+
|
|
218
|
+
# Create parser bound to file
|
|
219
|
+
parser = Parser("match.dem")
|
|
220
|
+
|
|
221
|
+
# Collect all data types in ONE parse (instead of 5 separate parses)
|
|
222
|
+
result = parser.parse(
|
|
223
|
+
header=True,
|
|
224
|
+
game_info=True,
|
|
225
|
+
combat_log={"types": [0, 4], "max_entries": 100},
|
|
226
|
+
entities={"interval_ticks": 1800, "max_snapshots": 50},
|
|
227
|
+
messages={"filter": "ChatMessage", "max_messages": 100},
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Access all results
|
|
231
|
+
print(result.header.map_name)
|
|
232
|
+
print(result.game_info.match_id)
|
|
233
|
+
print(len(result.combat_log.entries))
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Index/Seek API (Random Access)
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from python_manta import Parser
|
|
240
|
+
|
|
241
|
+
parser = Parser("match.dem")
|
|
242
|
+
|
|
243
|
+
# Build keyframe index for seeking
|
|
244
|
+
index = parser.build_index(interval_ticks=1800) # Every 60 seconds
|
|
245
|
+
print(f"Total ticks: {index.total_ticks}, Keyframes: {len(index.keyframes)}")
|
|
246
|
+
|
|
247
|
+
# Get hero state at specific tick
|
|
248
|
+
snap = parser.snapshot(target_tick=36000) # 20 minutes
|
|
249
|
+
for hero in snap.heroes:
|
|
250
|
+
print(f"{hero.hero_name}: HP={hero.health}/{hero.max_health} at ({hero.x:.0f}, {hero.y:.0f})")
|
|
251
|
+
print(f" LH={hero.last_hits} Gold={hero.gold} NW={hero.net_worth} KDA={hero.kda}")
|
|
252
|
+
|
|
253
|
+
# Include illusions/clones
|
|
254
|
+
snap = parser.snapshot(target_tick=36000, include_illusions=True)
|
|
255
|
+
for hero in snap.heroes:
|
|
256
|
+
if hero.is_clone:
|
|
257
|
+
print(f"Clone: {hero.hero_name}")
|
|
258
|
+
elif hero.is_illusion:
|
|
259
|
+
print(f"Illusion: {hero.hero_name}")
|
|
260
|
+
|
|
261
|
+
# Parse events in tick range
|
|
262
|
+
result = parser.parse_range(start_tick=25000, end_tick=35000, combat_log=True)
|
|
263
|
+
for entry in result.combat_log:
|
|
264
|
+
print(f"Tick {entry['tick']}: {entry['target_name']}")
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### API Reference
|
|
268
|
+
|
|
269
|
+
| Method | Description |
|
|
270
|
+
|--------|-------------|
|
|
271
|
+
| `Parser(demo_path)` | Create parser bound to file |
|
|
272
|
+
| `parse(**collectors)` | Single-pass parsing with multiple collectors |
|
|
273
|
+
| `build_index(interval_ticks)` | Build keyframe index for seeking |
|
|
274
|
+
| `snapshot(target_tick, include_illusions=False)` | Get hero state at tick |
|
|
275
|
+
| `find_keyframe(index, target_tick)` | Find nearest keyframe |
|
|
276
|
+
| `parse_range(start, end, **collectors)` | Parse events in tick range |
|
|
277
|
+
| `stream(**options)` | Stream events from demo |
|
|
278
|
+
|
|
279
|
+
### Parser Class
|
|
280
|
+
|
|
281
|
+
The main class for parsing Dota 2 replay files.
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
class Parser:
|
|
285
|
+
def __init__(self, demo_path: str, library_path: Optional[str] = None)
|
|
286
|
+
|
|
287
|
+
# Main parsing method
|
|
288
|
+
def parse(
|
|
289
|
+
self,
|
|
290
|
+
header: bool = False,
|
|
291
|
+
game_info: bool = False,
|
|
292
|
+
combat_log: Optional[Dict] = None,
|
|
293
|
+
entities: Optional[Dict] = None,
|
|
294
|
+
game_events: Optional[Dict] = None,
|
|
295
|
+
modifiers: Optional[Dict] = None,
|
|
296
|
+
string_tables: Optional[Dict] = None,
|
|
297
|
+
messages: Optional[Dict] = None,
|
|
298
|
+
parser_info: bool = False,
|
|
299
|
+
) -> ParseResult
|
|
300
|
+
|
|
301
|
+
# Advanced features
|
|
302
|
+
def build_index(self, interval_ticks: int = 1800) -> DemoIndex
|
|
303
|
+
def snapshot(self, target_tick: int, include_illusions: bool = False) -> EntityStateSnapshot
|
|
304
|
+
def parse_range(self, start_tick: int, end_tick: int, ...) -> RangeParseResult
|
|
305
|
+
def stream(self, combat_log: bool = False, messages: bool = False, ...) -> Iterator[StreamEvent]
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### Constructor
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
parser = Parser("match.dem") # Uses bundled library
|
|
312
|
+
parser = Parser("match.dem", library_path="/path/to/libmanta_wrapper.so") # Custom library
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### parse(**collectors) -> ParseResult
|
|
316
|
+
|
|
317
|
+
Single-pass parsing with multiple data collectors. Collects all requested data in ONE file traversal.
|
|
318
|
+
|
|
319
|
+
**Parameters (all optional):**
|
|
320
|
+
- `header`: Set to `True` to collect header metadata
|
|
321
|
+
- `game_info`: Set to `True` to collect draft/game info
|
|
322
|
+
- `combat_log`: Dict with `types`, `max_entries`, `heroes_only`
|
|
323
|
+
- `entities`: Dict with `interval_ticks`, `max_snapshots`, `target_heroes`
|
|
324
|
+
- `game_events`: Dict with `event_filter`, `max_events`
|
|
325
|
+
- `modifiers`: Dict with `max_modifiers`, `debuffs_only`, `auras_only`
|
|
326
|
+
- `string_tables`: Dict with `table_names`, `include_values`, `max_entries`
|
|
327
|
+
- `messages`: Dict with `filter`, `max_messages`
|
|
328
|
+
- `parser_info`: Set to `True` to collect parser state
|
|
329
|
+
|
|
330
|
+
**Returns:** `ParseResult` with all requested data
|
|
331
|
+
|
|
332
|
+
**Raises:**
|
|
333
|
+
- `FileNotFoundError`: If demo file doesn't exist
|
|
334
|
+
- `ValueError`: If parsing fails
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Game Events
|
|
339
|
+
|
|
340
|
+
Parse Source 1 legacy game events with typed field access:
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from python_manta import Parser
|
|
344
|
+
|
|
345
|
+
parser = Parser("match.dem")
|
|
346
|
+
|
|
347
|
+
# Parse specific events
|
|
348
|
+
result = parser.parse(game_events={"event_filter": "dota_combatlog", "max_events": 100})
|
|
349
|
+
for event in result.game_events.events:
|
|
350
|
+
print(f"[{event.tick}] {event.name}: {event.fields}")
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Modifiers
|
|
356
|
+
|
|
357
|
+
Track buffs, debuffs, and auras on units:
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
from python_manta import Parser
|
|
361
|
+
|
|
362
|
+
parser = Parser("match.dem")
|
|
363
|
+
|
|
364
|
+
# Get all modifiers
|
|
365
|
+
result = parser.parse(modifiers={"max_modifiers": 100})
|
|
366
|
+
for mod in result.modifiers.modifiers:
|
|
367
|
+
print(f"[{mod.tick}] {mod.name} on entity {mod.parent}, duration={mod.duration}, stacks={mod.stack_count}")
|
|
368
|
+
|
|
369
|
+
# Filter for auras only
|
|
370
|
+
result = parser.parse(modifiers={"max_modifiers": 100, "auras_only": True})
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Entity Queries
|
|
376
|
+
|
|
377
|
+
Query entities by class name and extract properties:
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
from python_manta import Parser
|
|
381
|
+
|
|
382
|
+
parser = Parser("match.dem")
|
|
383
|
+
|
|
384
|
+
# Query hero entities
|
|
385
|
+
result = parser.parse(entities={"class_filter": "Hero", "max_entities": 10})
|
|
386
|
+
for entity in result.entities.entities:
|
|
387
|
+
print(f"{entity.class_name} (index={entity.index})")
|
|
388
|
+
print(f" Health: {entity.properties.get('m_iHealth')}")
|
|
389
|
+
|
|
390
|
+
# Query specific properties only
|
|
391
|
+
result = parser.parse(entities={
|
|
392
|
+
"class_filter": "Hero",
|
|
393
|
+
"property_filter": ["m_iHealth", "m_iMaxHealth", "m_vecOrigin"],
|
|
394
|
+
"max_entities": 10
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
# Query by exact class names
|
|
398
|
+
result = parser.parse(entities={
|
|
399
|
+
"class_names": ["CDOTA_Unit_Hero_Invoker", "CDOTA_Unit_Hero_Pudge"],
|
|
400
|
+
"max_entities": 20
|
|
401
|
+
})
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## String Tables
|
|
407
|
+
|
|
408
|
+
Extract string tables (userinfo, instancebaseline, etc.):
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
from python_manta import Parser
|
|
412
|
+
|
|
413
|
+
parser = Parser("match.dem")
|
|
414
|
+
|
|
415
|
+
# Get specific table
|
|
416
|
+
result = parser.parse(string_tables={"table_names": ["userinfo"], "max_entries": 50})
|
|
417
|
+
for entry in result.string_tables.entries:
|
|
418
|
+
print(f"[{entry.table}] {entry.key}: {entry.value[:50]}...")
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Combat Log
|
|
424
|
+
|
|
425
|
+
Parse combat log with filtering and typed entries:
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
from python_manta import Parser
|
|
429
|
+
|
|
430
|
+
parser = Parser("match.dem")
|
|
431
|
+
|
|
432
|
+
# Get all combat log entries
|
|
433
|
+
result = parser.parse(combat_log={"max_entries": 100})
|
|
434
|
+
for entry in result.combat_log.entries:
|
|
435
|
+
print(f"[{entry.timestamp:.1f}s] {entry.type_name}: {entry.attacker_name} -> {entry.target_name}")
|
|
436
|
+
|
|
437
|
+
# Filter by type (0=DAMAGE, 1=HEAL, 2=MODIFIER_ADD, etc.)
|
|
438
|
+
result = parser.parse(combat_log={"types": [0], "max_entries": 100}) # Damage only
|
|
439
|
+
|
|
440
|
+
# Filter for hero-related entries
|
|
441
|
+
result = parser.parse(combat_log={"heroes_only": True, "max_entries": 100})
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Parser Info
|
|
447
|
+
|
|
448
|
+
Get parser metadata and state:
|
|
449
|
+
|
|
450
|
+
```python
|
|
451
|
+
from python_manta import Parser
|
|
452
|
+
|
|
453
|
+
parser = Parser("match.dem")
|
|
454
|
+
result = parser.parse(parser_info=True)
|
|
455
|
+
info = result.parser_info
|
|
456
|
+
|
|
457
|
+
print(f"Final tick: {info.tick}")
|
|
458
|
+
print(f"Entity count: {info.entity_count}")
|
|
459
|
+
print(f"String tables: {info.string_tables}")
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Supported Callbacks (272 Total)
|
|
465
|
+
|
|
466
|
+
Python Manta implements **all 272 Manta callbacks**. Use these exact names with `parse_universal()`.
|
|
467
|
+
|
|
468
|
+
### Communication & Chat
|
|
469
|
+
|
|
470
|
+
| Callback Name | Description |
|
|
471
|
+
|---------------|-------------|
|
|
472
|
+
| `CDOTAUserMsg_ChatMessage` | Player text chat messages |
|
|
473
|
+
| `CDOTAUserMsg_ChatEvent` | System chat events (kills, items, etc.) |
|
|
474
|
+
| `CDOTAUserMsg_ChatWheel` | Chat wheel phrases |
|
|
475
|
+
| `CDOTAUserMsg_BotChat` | Bot chat messages |
|
|
476
|
+
| `CUserMessageSayText` | Generic say text |
|
|
477
|
+
| `CUserMessageSayText2` | Extended say text |
|
|
478
|
+
|
|
479
|
+
### Map & Location
|
|
480
|
+
|
|
481
|
+
| Callback Name | Description |
|
|
482
|
+
|---------------|-------------|
|
|
483
|
+
| `CDOTAUserMsg_LocationPing` | Map ping locations |
|
|
484
|
+
| `CDOTAUserMsg_MapLine` | Map drawing/lines |
|
|
485
|
+
| `CDOTAUserMsg_WorldLine` | World-space lines |
|
|
486
|
+
| `CDOTAUserMsg_MinimapEvent` | Minimap events |
|
|
487
|
+
| `CDOTAUserMsg_Ping` | Generic pings |
|
|
488
|
+
| `CDOTAUserMsg_CoachHUDPing` | Coach pings |
|
|
489
|
+
|
|
490
|
+
### Game State & Events
|
|
491
|
+
|
|
492
|
+
| Callback Name | Description |
|
|
493
|
+
|---------------|-------------|
|
|
494
|
+
| `CDemoFileHeader` | Demo file metadata |
|
|
495
|
+
| `CDemoFileInfo` | Extended demo info (draft, players) |
|
|
496
|
+
| `CDOTAUserMsg_GamerulesStateChanged` | Game state transitions |
|
|
497
|
+
| `CDOTAUserMsg_OverheadEvent` | Damage numbers, XP, gold |
|
|
498
|
+
| `CDOTAUserMsg_UnitEvent` | Unit actions and abilities |
|
|
499
|
+
| `CMsgDOTACombatLogEntry` | Combat log entries |
|
|
500
|
+
|
|
501
|
+
### Draft & Hero Selection
|
|
502
|
+
|
|
503
|
+
| Callback Name | Description |
|
|
504
|
+
|---------------|-------------|
|
|
505
|
+
| `CDOTAUserMsg_PlayerDraftPick` | Player draft picks |
|
|
506
|
+
| `CDOTAUserMsg_PlayerDraftSuggestPick` | Draft suggestions |
|
|
507
|
+
| `CDOTAUserMsg_SuggestHeroPick` | Hero suggestions |
|
|
508
|
+
| `CDOTAUserMsg_SuggestHeroRole` | Role suggestions |
|
|
509
|
+
|
|
510
|
+
### Items & Economy
|
|
511
|
+
|
|
512
|
+
| Callback Name | Description |
|
|
513
|
+
|---------------|-------------|
|
|
514
|
+
| `CDOTAUserMsg_ItemPurchased` | Item purchases |
|
|
515
|
+
| `CDOTAUserMsg_ItemSold` | Item sales |
|
|
516
|
+
| `CDOTAUserMsg_ItemAlert` | Item alerts |
|
|
517
|
+
| `CDOTAUserMsg_ItemFound` | Found items |
|
|
518
|
+
| `CDOTAUserMsg_FoundNeutralItem` | Neutral item drops |
|
|
519
|
+
| `CDOTAUserMsg_QuickBuyAlert` | Quick buy alerts |
|
|
520
|
+
|
|
521
|
+
### Combat & Abilities
|
|
522
|
+
|
|
523
|
+
| Callback Name | Description |
|
|
524
|
+
|---------------|-------------|
|
|
525
|
+
| `CDOTAUserMsg_AbilityPing` | Ability pings |
|
|
526
|
+
| `CDOTAUserMsg_AbilitySteal` | Rubick spell steal |
|
|
527
|
+
| `CDOTAUserMsg_DamageReport` | Damage reports |
|
|
528
|
+
| `CDOTAUserMsg_TE_Projectile` | Projectile events |
|
|
529
|
+
| `CDOTAUserMsg_CreateLinearProjectile` | Linear projectiles |
|
|
530
|
+
|
|
531
|
+
### Network & Technical
|
|
532
|
+
|
|
533
|
+
| Callback Name | Description |
|
|
534
|
+
|---------------|-------------|
|
|
535
|
+
| `CNETMsg_Tick` | Network tick synchronization |
|
|
536
|
+
| `CNETMsg_SetConVar` | Console variable changes |
|
|
537
|
+
| `CNETMsg_SignonState` | Connection state changes |
|
|
538
|
+
| `CSVCMsg_ServerInfo` | Server configuration |
|
|
539
|
+
| `CSVCMsg_PacketEntities` | Entity updates |
|
|
540
|
+
|
|
541
|
+
### Demo Control
|
|
542
|
+
|
|
543
|
+
| Callback Name | Description |
|
|
544
|
+
|---------------|-------------|
|
|
545
|
+
| `CDemoPacket` | Demo packets |
|
|
546
|
+
| `CDemoStop` | Demo end marker |
|
|
547
|
+
| `CDemoSyncTick` | Sync tick markers |
|
|
548
|
+
| `CDemoStringTables` | String table data |
|
|
549
|
+
| `CDemoClassInfo` | Class information |
|
|
550
|
+
|
|
551
|
+
### Full Callback List by Category
|
|
552
|
+
|
|
553
|
+
<details>
|
|
554
|
+
<summary><strong>Demo Messages (15 callbacks)</strong></summary>
|
|
555
|
+
|
|
556
|
+
- `CDemoAnimationData`
|
|
557
|
+
- `CDemoAnimationHeader`
|
|
558
|
+
- `CDemoClassInfo`
|
|
559
|
+
- `CDemoConsoleCmd`
|
|
560
|
+
- `CDemoCustomData`
|
|
561
|
+
- `CDemoCustomDataCallbacks`
|
|
562
|
+
- `CDemoFileHeader`
|
|
563
|
+
- `CDemoFileInfo`
|
|
564
|
+
- `CDemoFullPacket`
|
|
565
|
+
- `CDemoPacket`
|
|
566
|
+
- `CDemoRecovery`
|
|
567
|
+
- `CDemoSaveGame`
|
|
568
|
+
- `CDemoSendTables`
|
|
569
|
+
- `CDemoSpawnGroups`
|
|
570
|
+
- `CDemoStop`
|
|
571
|
+
- `CDemoStringTables`
|
|
572
|
+
- `CDemoSyncTick`
|
|
573
|
+
- `CDemoUserCmd`
|
|
574
|
+
|
|
575
|
+
</details>
|
|
576
|
+
|
|
577
|
+
<details>
|
|
578
|
+
<summary><strong>Network Messages (15 callbacks)</strong></summary>
|
|
579
|
+
|
|
580
|
+
- `CNETMsg_DebugOverlay`
|
|
581
|
+
- `CNETMsg_NOP`
|
|
582
|
+
- `CNETMsg_SetConVar`
|
|
583
|
+
- `CNETMsg_SignonState`
|
|
584
|
+
- `CNETMsg_SpawnGroup_Load`
|
|
585
|
+
- `CNETMsg_SpawnGroup_LoadCompleted`
|
|
586
|
+
- `CNETMsg_SpawnGroup_ManifestUpdate`
|
|
587
|
+
- `CNETMsg_SpawnGroup_SetCreationTick`
|
|
588
|
+
- `CNETMsg_SpawnGroup_Unload`
|
|
589
|
+
- `CNETMsg_SplitScreenUser`
|
|
590
|
+
- `CNETMsg_StringCmd`
|
|
591
|
+
- `CNETMsg_Tick`
|
|
592
|
+
|
|
593
|
+
</details>
|
|
594
|
+
|
|
595
|
+
<details>
|
|
596
|
+
<summary><strong>SVC Messages (25 callbacks)</strong></summary>
|
|
597
|
+
|
|
598
|
+
- `CSVCMsg_BSPDecal`
|
|
599
|
+
- `CSVCMsg_Broadcast_Command`
|
|
600
|
+
- `CSVCMsg_ClassInfo`
|
|
601
|
+
- `CSVCMsg_ClearAllStringTables`
|
|
602
|
+
- `CSVCMsg_CmdKeyValues`
|
|
603
|
+
- `CSVCMsg_CreateStringTable`
|
|
604
|
+
- `CSVCMsg_FlattenedSerializer`
|
|
605
|
+
- `CSVCMsg_FullFrameSplit`
|
|
606
|
+
- `CSVCMsg_GetCvarValue`
|
|
607
|
+
- `CSVCMsg_HLTVStatus`
|
|
608
|
+
- `CSVCMsg_HltvFixupOperatorStatus`
|
|
609
|
+
- `CSVCMsg_Menu`
|
|
610
|
+
- `CSVCMsg_PacketEntities`
|
|
611
|
+
- `CSVCMsg_PacketReliable`
|
|
612
|
+
- `CSVCMsg_PeerList`
|
|
613
|
+
- `CSVCMsg_Prefetch`
|
|
614
|
+
- `CSVCMsg_Print`
|
|
615
|
+
- `CSVCMsg_RconServerDetails`
|
|
616
|
+
- `CSVCMsg_ServerInfo`
|
|
617
|
+
- `CSVCMsg_ServerSteamID`
|
|
618
|
+
- `CSVCMsg_SetPause`
|
|
619
|
+
- `CSVCMsg_SetView`
|
|
620
|
+
- `CSVCMsg_Sounds`
|
|
621
|
+
- `CSVCMsg_SplitScreen`
|
|
622
|
+
- `CSVCMsg_StopSound`
|
|
623
|
+
- `CSVCMsg_UpdateStringTable`
|
|
624
|
+
- `CSVCMsg_UserMessage`
|
|
625
|
+
- `CSVCMsg_VoiceData`
|
|
626
|
+
- `CSVCMsg_VoiceInit`
|
|
627
|
+
|
|
628
|
+
</details>
|
|
629
|
+
|
|
630
|
+
<details>
|
|
631
|
+
<summary><strong>User Messages (35 callbacks)</strong></summary>
|
|
632
|
+
|
|
633
|
+
- `CUserMessageAchievementEvent`
|
|
634
|
+
- `CUserMessageAmmoDenied`
|
|
635
|
+
- `CUserMessageAudioParameter`
|
|
636
|
+
- `CUserMessageCameraTransition`
|
|
637
|
+
- `CUserMessageCloseCaption`
|
|
638
|
+
- `CUserMessageCloseCaptionDirect`
|
|
639
|
+
- `CUserMessageCloseCaptionPlaceholder`
|
|
640
|
+
- `CUserMessageColoredText`
|
|
641
|
+
- `CUserMessageCreditsMsg`
|
|
642
|
+
- `CUserMessageCurrentTimescale`
|
|
643
|
+
- `CUserMessageDesiredTimescale`
|
|
644
|
+
- `CUserMessageFade`
|
|
645
|
+
- `CUserMessageGameTitle`
|
|
646
|
+
- `CUserMessageHapticsManagerEffect`
|
|
647
|
+
- `CUserMessageHapticsManagerPulse`
|
|
648
|
+
- `CUserMessageHudMsg`
|
|
649
|
+
- `CUserMessageHudText`
|
|
650
|
+
- `CUserMessageItemPickup`
|
|
651
|
+
- `CUserMessageLagCompensationError`
|
|
652
|
+
- `CUserMessageRequestDiagnostic`
|
|
653
|
+
- `CUserMessageRequestDllStatus`
|
|
654
|
+
- `CUserMessageRequestInventory`
|
|
655
|
+
- `CUserMessageRequestState`
|
|
656
|
+
- `CUserMessageRequestUtilAction`
|
|
657
|
+
- `CUserMessageResetHUD`
|
|
658
|
+
- `CUserMessageRumble`
|
|
659
|
+
- `CUserMessageSayText`
|
|
660
|
+
- `CUserMessageSayText2`
|
|
661
|
+
- `CUserMessageSayTextChannel`
|
|
662
|
+
- `CUserMessageSendAudio`
|
|
663
|
+
- `CUserMessageServerFrameTime`
|
|
664
|
+
- `CUserMessageShake`
|
|
665
|
+
- `CUserMessageShakeDir`
|
|
666
|
+
- `CUserMessageShowMenu`
|
|
667
|
+
- `CUserMessageTextMsg`
|
|
668
|
+
- `CUserMessageScreenTilt`
|
|
669
|
+
- `CUserMessageUpdateCssClasses`
|
|
670
|
+
- `CUserMessageVoiceMask`
|
|
671
|
+
- `CUserMessageWaterShake`
|
|
672
|
+
|
|
673
|
+
</details>
|
|
674
|
+
|
|
675
|
+
<details>
|
|
676
|
+
<summary><strong>DOTA User Messages (140+ callbacks)</strong></summary>
|
|
677
|
+
|
|
678
|
+
- `CDOTAUserMsg_AbilityDraftRequestAbility`
|
|
679
|
+
- `CDOTAUserMsg_AbilityPing`
|
|
680
|
+
- `CDOTAUserMsg_AbilitySteal`
|
|
681
|
+
- `CDOTAUserMsg_AddQuestLogEntry`
|
|
682
|
+
- `CDOTAUserMsg_AghsStatusAlert`
|
|
683
|
+
- `CDOTAUserMsg_AIDebugLine`
|
|
684
|
+
- `CDOTAUserMsg_AllStarEvent`
|
|
685
|
+
- `CDOTAUserMsg_BeastChat`
|
|
686
|
+
- `CDOTAUserMsg_BoosterState`
|
|
687
|
+
- `CDOTAUserMsg_BotChat`
|
|
688
|
+
- `CDOTAUserMsg_BuyBackStateAlert`
|
|
689
|
+
- `CDOTAUserMsg_ChatEvent`
|
|
690
|
+
- `CDOTAUserMsg_ChatMessage`
|
|
691
|
+
- `CDOTAUserMsg_ChatWheel`
|
|
692
|
+
- `CDOTAUserMsg_ChatWheelCooldown`
|
|
693
|
+
- `CDOTAUserMsg_ClientLoadGridNav`
|
|
694
|
+
- `CDOTAUserMsg_CoachHUDPing`
|
|
695
|
+
- `CDOTAUserMsg_CombatHeroPositions`
|
|
696
|
+
- `CDOTAUserMsg_CombatLogBulkData`
|
|
697
|
+
- `CDOTAUserMsg_CompendiumState`
|
|
698
|
+
- `CDOTAUserMsg_ContextualTip`
|
|
699
|
+
- `CDOTAUserMsg_CourierKilledAlert`
|
|
700
|
+
- `CDOTAUserMsg_CreateLinearProjectile`
|
|
701
|
+
- `CDOTAUserMsg_CustomHeaderMessage`
|
|
702
|
+
- `CDOTAUserMsg_CustomHudElement_Create`
|
|
703
|
+
- `CDOTAUserMsg_CustomHudElement_Destroy`
|
|
704
|
+
- `CDOTAUserMsg_CustomHudElement_Modify`
|
|
705
|
+
- `CDOTAUserMsg_CustomMsg`
|
|
706
|
+
- `CDOTAUserMsg_DamageReport`
|
|
707
|
+
- `CDOTAUserMsg_DebugChallenge`
|
|
708
|
+
- `CDOTAUserMsg_DestroyLinearProjectile`
|
|
709
|
+
- `CDOTAUserMsg_DismissAllStatPopups`
|
|
710
|
+
- `CDOTAUserMsg_DodgeTrackingProjectiles`
|
|
711
|
+
- `CDOTAUserMsg_DuelAccepted`
|
|
712
|
+
- `CDOTAUserMsg_DuelOpponentKilled`
|
|
713
|
+
- `CDOTAUserMsg_DuelRequested`
|
|
714
|
+
- `CDOTAUserMsg_EmptyItemSlotAlert`
|
|
715
|
+
- `CDOTAUserMsg_EmptyTeleportAlert`
|
|
716
|
+
- `CDOTAUserMsg_EnemyItemAlert`
|
|
717
|
+
- `CDOTAUserMsg_ESArcanaCombo`
|
|
718
|
+
- `CDOTAUserMsg_ESArcanaComboSummary`
|
|
719
|
+
- `CDOTAUserMsg_FacetPing`
|
|
720
|
+
- `CDOTAUserMsg_FlipCoinResult`
|
|
721
|
+
- `CDOTAUserMsg_FoundNeutralItem`
|
|
722
|
+
- `CDOTAUserMsg_GamerulesStateChanged`
|
|
723
|
+
- `CDOTAUserMsg_GiftPlayer`
|
|
724
|
+
- `CDOTAUserMsg_GlobalLightColor`
|
|
725
|
+
- `CDOTAUserMsg_GlobalLightDirection`
|
|
726
|
+
- `CDOTAUserMsg_GlyphAlert`
|
|
727
|
+
- `CDOTAUserMsg_GuildChallenge_Progress`
|
|
728
|
+
- `CDOTAUserMsg_HalloweenDrops`
|
|
729
|
+
- `CDOTAUserMsg_HeroRelicProgress`
|
|
730
|
+
- `CDOTAUserMsg_HighFiveCompleted`
|
|
731
|
+
- `CDOTAUserMsg_HighFiveLeftHanging`
|
|
732
|
+
- `CDOTAUserMsg_HotPotato_Created`
|
|
733
|
+
- `CDOTAUserMsg_HotPotato_Exploded`
|
|
734
|
+
- `CDOTAUserMsg_HPManaAlert`
|
|
735
|
+
- `CDOTAUserMsg_HudError`
|
|
736
|
+
- `CDOTAUserMsg_InnatePing`
|
|
737
|
+
- `CDOTAUserMsg_InvalidCommand`
|
|
738
|
+
- `CDOTAUserMsg_ItemAlert`
|
|
739
|
+
- `CDOTAUserMsg_ItemFound`
|
|
740
|
+
- `CDOTAUserMsg_ItemPurchased`
|
|
741
|
+
- `CDOTAUserMsg_ItemSold`
|
|
742
|
+
- `CDOTAUserMsg_KillcamDamageTaken`
|
|
743
|
+
- `CDOTAUserMsg_LocationPing`
|
|
744
|
+
- `CDOTAUserMsg_MadstoneAlert`
|
|
745
|
+
- `CDOTAUserMsg_MapLine`
|
|
746
|
+
- `CDOTAUserMsg_MarsArenaOfBloodAttack`
|
|
747
|
+
- `CDOTAUserMsg_MinimapDebugPoint`
|
|
748
|
+
- `CDOTAUserMsg_MinimapEvent`
|
|
749
|
+
- `CDOTAUserMsg_MiniKillCamInfo`
|
|
750
|
+
- `CDOTAUserMsg_MiniTaunt`
|
|
751
|
+
- `CDOTAUserMsg_ModifierAlert`
|
|
752
|
+
- `CDOTAUserMsg_MoveCameraToUnit`
|
|
753
|
+
- `CDOTAUserMsg_MuertaReleaseEvent_AssignedTargetKilled`
|
|
754
|
+
- `CDOTAUserMsg_MutedPlayers`
|
|
755
|
+
- `CDOTAUserMsg_NeutralCampAlert`
|
|
756
|
+
- `CDOTAUserMsg_NeutralCraftAvailable`
|
|
757
|
+
- `CDOTAUserMsg_NevermoreRequiem`
|
|
758
|
+
- `CDOTAUserMsg_OMArcanaCombo`
|
|
759
|
+
- `CDOTAUserMsg_OutpostCaptured`
|
|
760
|
+
- `CDOTAUserMsg_OutpostGrantedXP`
|
|
761
|
+
- `CDOTAUserMsg_OverheadEvent`
|
|
762
|
+
- `CDOTAUserMsg_PauseMinigameData`
|
|
763
|
+
- `CDOTAUserMsg_Ping`
|
|
764
|
+
- `CDOTAUserMsg_PingConfirmation`
|
|
765
|
+
- `CDOTAUserMsg_PlayerDraftPick`
|
|
766
|
+
- `CDOTAUserMsg_PlayerDraftSuggestPick`
|
|
767
|
+
- `CDOTAUserMsg_ProjectionAbility`
|
|
768
|
+
- `CDOTAUserMsg_ProjectionEvent`
|
|
769
|
+
- `CDOTAUserMsg_QoP_ArcanaSummary`
|
|
770
|
+
- `CDOTAUserMsg_QuestStatus`
|
|
771
|
+
- `CDOTAUserMsg_QueuedOrderRemoved`
|
|
772
|
+
- `CDOTAUserMsg_QuickBuyAlert`
|
|
773
|
+
- `CDOTAUserMsg_RadarAlert`
|
|
774
|
+
- `CDOTAUserMsg_ReceivedXmasGift`
|
|
775
|
+
- `CDOTAUserMsg_ReplaceQueryUnit`
|
|
776
|
+
- `CDOTAUserMsg_RockPaperScissorsFinished`
|
|
777
|
+
- `CDOTAUserMsg_RockPaperScissorsStarted`
|
|
778
|
+
- `CDOTAUserMsg_RollDiceResult`
|
|
779
|
+
- `CDOTAUserMsg_RoshanTimer`
|
|
780
|
+
- `CDOTAUserMsg_SalutePlayer`
|
|
781
|
+
- `CDOTAUserMsg_SelectPenaltyGold`
|
|
782
|
+
- `CDOTAUserMsg_SendFinalGold`
|
|
783
|
+
- `CDOTAUserMsg_SendGenericToolTip`
|
|
784
|
+
- `CDOTAUserMsg_SendRoshanPopup`
|
|
785
|
+
- `CDOTAUserMsg_SendRoshanSpectatorPhase`
|
|
786
|
+
- `CDOTAUserMsg_SendStatPopup`
|
|
787
|
+
- `CDOTAUserMsg_SetNextAutobuyItem`
|
|
788
|
+
- `CDOTAUserMsg_SharedCooldown`
|
|
789
|
+
- `CDOTAUserMsg_ShovelUnearth`
|
|
790
|
+
- `CDOTAUserMsg_ShowGenericPopup`
|
|
791
|
+
- `CDOTAUserMsg_ShowSurvey`
|
|
792
|
+
- `CDOTAUserMsg_SpectatorPlayerClick`
|
|
793
|
+
- `CDOTAUserMsg_SpectatorPlayerUnitOrders`
|
|
794
|
+
- `CDOTAUserMsg_SpeechBubble`
|
|
795
|
+
- `CDOTAUserMsg_StatsHeroMinuteDetails`
|
|
796
|
+
- `CDOTAUserMsg_StatsMatchDetails`
|
|
797
|
+
- `CDOTAUserMsg_SuggestHeroPick`
|
|
798
|
+
- `CDOTAUserMsg_SuggestHeroRole`
|
|
799
|
+
- `CDOTAUserMsg_SwapVerify`
|
|
800
|
+
- `CDOTAUserMsg_TalentTreeAlert`
|
|
801
|
+
- `CDOTAUserMsg_TE_DestroyProjectile`
|
|
802
|
+
- `CDOTAUserMsg_TE_DotaBloodImpact`
|
|
803
|
+
- `CDOTAUserMsg_TE_Projectile`
|
|
804
|
+
- `CDOTAUserMsg_TE_ProjectileLoc`
|
|
805
|
+
- `CDOTAUserMsg_TE_UnitAnimation`
|
|
806
|
+
- `CDOTAUserMsg_TE_UnitAnimationEnd`
|
|
807
|
+
- `CDOTAUserMsg_TimerAlert`
|
|
808
|
+
- `CDOTAUserMsg_TipAlert`
|
|
809
|
+
- `CDOTAUserMsg_TutorialFade`
|
|
810
|
+
- `CDOTAUserMsg_TutorialFinish`
|
|
811
|
+
- `CDOTAUserMsg_TutorialMinimapPosition`
|
|
812
|
+
- `CDOTAUserMsg_TutorialPingMinimap`
|
|
813
|
+
- `CDOTAUserMsg_TutorialRequestExp`
|
|
814
|
+
- `CDOTAUserMsg_TutorialTipInfo`
|
|
815
|
+
- `CDOTAUserMsg_UnitEvent`
|
|
816
|
+
- `CDOTAUserMsg_UpdateLinearProjectileCPData`
|
|
817
|
+
- `CDOTAUserMsg_UpdateQuestProgress`
|
|
818
|
+
- `CDOTAUserMsg_UpdateSharedContent`
|
|
819
|
+
- `CDOTAUserMsg_VersusScene_PlayerBehavior`
|
|
820
|
+
- `CDOTAUserMsg_VoteEnd`
|
|
821
|
+
- `CDOTAUserMsg_VoteStart`
|
|
822
|
+
- `CDOTAUserMsg_VoteUpdate`
|
|
823
|
+
- `CDOTAUserMsg_WillPurchaseAlert`
|
|
824
|
+
- `CDOTAUserMsg_WK_Arcana_Progress`
|
|
825
|
+
- `CDOTAUserMsg_WorldLine`
|
|
826
|
+
- `CDOTAUserMsg_WRArcanaProgress`
|
|
827
|
+
- `CDOTAUserMsg_WRArcanaSummary`
|
|
828
|
+
- `CDOTAUserMsg_XPAlert`
|
|
829
|
+
|
|
830
|
+
</details>
|
|
831
|
+
|
|
832
|
+
<details>
|
|
833
|
+
<summary><strong>Entity Messages (6 callbacks)</strong></summary>
|
|
834
|
+
|
|
835
|
+
- `CEntityMessageDoSpark`
|
|
836
|
+
- `CEntityMessageFixAngle`
|
|
837
|
+
- `CEntityMessagePlayJingle`
|
|
838
|
+
- `CEntityMessagePropagateForce`
|
|
839
|
+
- `CEntityMessageRemoveAllDecals`
|
|
840
|
+
- `CEntityMessageScreenOverlay`
|
|
841
|
+
|
|
842
|
+
</details>
|
|
843
|
+
|
|
844
|
+
<details>
|
|
845
|
+
<summary><strong>Miscellaneous Messages (15 callbacks)</strong></summary>
|
|
846
|
+
|
|
847
|
+
- `CMsgClearDecalsForSkeletonInstanceEvent`
|
|
848
|
+
- `CMsgClearEntityDecalsEvent`
|
|
849
|
+
- `CMsgClearWorldDecalsEvent`
|
|
850
|
+
- `CMsgDOTACombatLogEntry`
|
|
851
|
+
- `CMsgGCToClientTournamentItemDrop`
|
|
852
|
+
- `CMsgPlaceDecalEvent`
|
|
853
|
+
- `CMsgSosSetLibraryStackFields`
|
|
854
|
+
- `CMsgSosSetSoundEventParams`
|
|
855
|
+
- `CMsgSosStartSoundEvent`
|
|
856
|
+
- `CMsgSosStopSoundEvent`
|
|
857
|
+
- `CMsgSosStopSoundEventHash`
|
|
858
|
+
- `CMsgSource1LegacyGameEvent`
|
|
859
|
+
- `CMsgSource1LegacyGameEventList`
|
|
860
|
+
- `CMsgSource1LegacyListenEvents`
|
|
861
|
+
- `CMsgVDebugGameSessionIDEvent`
|
|
862
|
+
- `CDOTAMatchMetadataFile`
|
|
863
|
+
|
|
864
|
+
</details>
|
|
865
|
+
|
|
866
|
+
---
|
|
867
|
+
|
|
868
|
+
## Data Models
|
|
869
|
+
|
|
870
|
+
All models use [Pydantic](https://docs.pydantic.dev/) for validation and serialization.
|
|
871
|
+
|
|
872
|
+
### HeaderInfo
|
|
873
|
+
|
|
874
|
+
```python
|
|
875
|
+
class HeaderInfo(BaseModel):
|
|
876
|
+
map_name: str # Map name (e.g., "dota")
|
|
877
|
+
server_name: str # Server identifier
|
|
878
|
+
client_name: str # Client type
|
|
879
|
+
game_directory: str # Game directory path
|
|
880
|
+
network_protocol: int # Network protocol version
|
|
881
|
+
demo_file_stamp: str # Demo file signature
|
|
882
|
+
build_num: int # Game build number
|
|
883
|
+
game: str # Game identifier
|
|
884
|
+
server_start_tick: int # Server start tick
|
|
885
|
+
success: bool # Parse success flag
|
|
886
|
+
error: Optional[str] # Error message if failed
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
### CHeroSelectEvent
|
|
890
|
+
|
|
891
|
+
```python
|
|
892
|
+
class CHeroSelectEvent(BaseModel):
|
|
893
|
+
is_pick: bool # True for pick, False for ban
|
|
894
|
+
team: int # 2 = Radiant, 3 = Dire
|
|
895
|
+
hero_id: int # Hero ID (see Dota 2 Wiki for mappings)
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
### CDotaGameInfo
|
|
899
|
+
|
|
900
|
+
```python
|
|
901
|
+
class CDotaGameInfo(BaseModel):
|
|
902
|
+
picks_bans: List[CHeroSelectEvent] # Draft sequence
|
|
903
|
+
success: bool
|
|
904
|
+
error: Optional[str]
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
### MessageEvent
|
|
908
|
+
|
|
909
|
+
```python
|
|
910
|
+
class MessageEvent(BaseModel):
|
|
911
|
+
type: str # Callback name
|
|
912
|
+
tick: int # Game tick
|
|
913
|
+
net_tick: int # Network tick
|
|
914
|
+
data: Any # Message-specific data (dict)
|
|
915
|
+
timestamp: Optional[int] # Unix timestamp (ms)
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
### UniversalParseResult
|
|
919
|
+
|
|
920
|
+
```python
|
|
921
|
+
class UniversalParseResult(BaseModel):
|
|
922
|
+
messages: List[MessageEvent] # Matched messages
|
|
923
|
+
success: bool # Parse success flag
|
|
924
|
+
error: Optional[str] # Error message
|
|
925
|
+
count: int # Number of messages
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
### GameEventData
|
|
929
|
+
|
|
930
|
+
```python
|
|
931
|
+
class GameEventData(BaseModel):
|
|
932
|
+
name: str # Event name (e.g., "dota_combatlog")
|
|
933
|
+
tick: int # Game tick
|
|
934
|
+
net_tick: int # Network tick
|
|
935
|
+
fields: Dict[str, Any] # Event-specific fields
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
### ModifierEntry
|
|
939
|
+
|
|
940
|
+
```python
|
|
941
|
+
class ModifierEntry(BaseModel):
|
|
942
|
+
tick: int # Game tick
|
|
943
|
+
name: str # Modifier name
|
|
944
|
+
parent: int # Parent entity handle
|
|
945
|
+
duration: float # Duration in seconds (-1 = permanent)
|
|
946
|
+
stack_count: int # Number of stacks
|
|
947
|
+
is_aura: bool # Whether this is an aura
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
### EntityData
|
|
951
|
+
|
|
952
|
+
```python
|
|
953
|
+
class EntityData(BaseModel):
|
|
954
|
+
index: int # Entity index
|
|
955
|
+
class_name: str # Entity class name
|
|
956
|
+
properties: Dict[str, Any] # Entity properties
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
### CombatLogEntry
|
|
960
|
+
|
|
961
|
+
```python
|
|
962
|
+
class CombatLogEntry(BaseModel):
|
|
963
|
+
tick: int # Game tick
|
|
964
|
+
type: int # Combat log type ID
|
|
965
|
+
type_name: str # Human-readable type name
|
|
966
|
+
attacker_name: str # Attacker name
|
|
967
|
+
target_name: str # Target name
|
|
968
|
+
inflictor_name: str # Ability/item name
|
|
969
|
+
value: int # Damage/heal value
|
|
970
|
+
health: int # Target HP after event
|
|
971
|
+
timestamp: float # Game time in seconds
|
|
972
|
+
is_attacker_hero: bool # Whether attacker is a hero
|
|
973
|
+
is_target_hero: bool # Whether target is a hero
|
|
974
|
+
stun_duration: float # Stun duration applied
|
|
975
|
+
assist_players: List[int] # Assist player IDs (for kills)
|
|
976
|
+
# ... 80+ fields total - see documentation for complete list
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
### ParserInfo
|
|
980
|
+
|
|
981
|
+
```python
|
|
982
|
+
class ParserInfo(BaseModel):
|
|
983
|
+
tick: int # Final parser tick
|
|
984
|
+
net_tick: int # Final network tick
|
|
985
|
+
entity_count: int # Number of entities
|
|
986
|
+
string_tables: List[str] # List of string table names
|
|
987
|
+
success: bool # Parse success flag
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
### HeroSnapshot
|
|
991
|
+
|
|
992
|
+
Captured via `parser.snapshot()` for hero state at a specific tick:
|
|
993
|
+
|
|
994
|
+
```python
|
|
995
|
+
class HeroSnapshot(BaseModel):
|
|
996
|
+
# Identity
|
|
997
|
+
hero_name: str # e.g., "npc_dota_hero_axe"
|
|
998
|
+
hero_id: int # Hero ID
|
|
999
|
+
player_id: int # Player index (0-9)
|
|
1000
|
+
team: int # 2 = Radiant, 3 = Dire
|
|
1001
|
+
index: int # Entity index
|
|
1002
|
+
|
|
1003
|
+
# Position
|
|
1004
|
+
x: float # X coordinate
|
|
1005
|
+
y: float # Y coordinate
|
|
1006
|
+
z: float # Z coordinate
|
|
1007
|
+
|
|
1008
|
+
# Vital stats
|
|
1009
|
+
health: int # Current HP
|
|
1010
|
+
max_health: int # Max HP
|
|
1011
|
+
mana: float # Current mana
|
|
1012
|
+
max_mana: float # Max mana
|
|
1013
|
+
level: int # Hero level
|
|
1014
|
+
is_alive: bool # Whether hero is alive
|
|
1015
|
+
|
|
1016
|
+
# Economy
|
|
1017
|
+
gold: int # Current gold
|
|
1018
|
+
net_worth: int # Total net worth
|
|
1019
|
+
last_hits: int # Last hits
|
|
1020
|
+
denies: int # Denies
|
|
1021
|
+
xp: int # Experience points
|
|
1022
|
+
|
|
1023
|
+
# KDA
|
|
1024
|
+
kills: int # Kills
|
|
1025
|
+
deaths: int # Deaths
|
|
1026
|
+
assists: int # Assists
|
|
1027
|
+
|
|
1028
|
+
# Combat stats
|
|
1029
|
+
armor: float # Armor value
|
|
1030
|
+
magic_resistance: float # Magic resistance %
|
|
1031
|
+
damage_min: int # Min damage
|
|
1032
|
+
damage_max: int # Max damage
|
|
1033
|
+
attack_range: int # Attack range
|
|
1034
|
+
|
|
1035
|
+
# Attributes
|
|
1036
|
+
strength: float # Strength
|
|
1037
|
+
agility: float # Agility
|
|
1038
|
+
intellect: float # Intelligence
|
|
1039
|
+
|
|
1040
|
+
# Abilities and talents
|
|
1041
|
+
abilities: List[AbilitySnapshot] # List of abilities
|
|
1042
|
+
talents: List[TalentChoice] # Selected talents
|
|
1043
|
+
ability_points: int # Unspent ability points
|
|
1044
|
+
|
|
1045
|
+
# Clone/illusion flags
|
|
1046
|
+
is_clone: bool # MK clone, Morph replicate
|
|
1047
|
+
is_illusion: bool # Regular illusion
|
|
1048
|
+
|
|
1049
|
+
@property
|
|
1050
|
+
def kda(self) -> str: # Returns "K/D/A" format
|
|
1051
|
+
return f"{self.kills}/{self.deaths}/{self.assists}"
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
---
|
|
1055
|
+
|
|
1056
|
+
## Common Use Cases
|
|
1057
|
+
|
|
1058
|
+
### Extract All Chat Messages
|
|
1059
|
+
|
|
1060
|
+
```python
|
|
1061
|
+
from python_manta import Parser
|
|
1062
|
+
|
|
1063
|
+
parser = Parser("match.dem")
|
|
1064
|
+
result = parser.parse(messages={"filter": "CDOTAUserMsg_ChatMessage", "max_messages": 1000})
|
|
1065
|
+
|
|
1066
|
+
for msg in result.messages.messages:
|
|
1067
|
+
player_id = msg.data.get('source_player_id', 'Unknown')
|
|
1068
|
+
text = msg.data.get('message_text', '')
|
|
1069
|
+
print(f"Player {player_id}: {text}")
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
### Track Item Purchases
|
|
1073
|
+
|
|
1074
|
+
```python
|
|
1075
|
+
from python_manta import Parser
|
|
1076
|
+
|
|
1077
|
+
parser = Parser("match.dem")
|
|
1078
|
+
result = parser.parse(messages={"filter": "CDOTAUserMsg_ItemPurchased", "max_messages": 1000})
|
|
1079
|
+
|
|
1080
|
+
for msg in result.messages.messages:
|
|
1081
|
+
player_id = msg.data.get('player_id')
|
|
1082
|
+
item_id = msg.data.get('item_ability_id')
|
|
1083
|
+
tick = msg.tick
|
|
1084
|
+
print(f"[{tick}] Player {player_id} purchased item {item_id}")
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
### Analyze Location Pings
|
|
1088
|
+
|
|
1089
|
+
```python
|
|
1090
|
+
from python_manta import Parser
|
|
1091
|
+
|
|
1092
|
+
parser = Parser("match.dem")
|
|
1093
|
+
result = parser.parse(messages={"filter": "CDOTAUserMsg_LocationPing", "max_messages": 1000})
|
|
1094
|
+
|
|
1095
|
+
for msg in result.messages.messages:
|
|
1096
|
+
ping_data = msg.data.get('location_ping', {})
|
|
1097
|
+
x = ping_data.get('x', 0)
|
|
1098
|
+
y = ping_data.get('y', 0)
|
|
1099
|
+
player_id = msg.data.get('player_id')
|
|
1100
|
+
print(f"Player {player_id} pinged at ({x}, {y})")
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
### Extract Combat Log (Structured)
|
|
1104
|
+
|
|
1105
|
+
```python
|
|
1106
|
+
from python_manta import Parser
|
|
1107
|
+
|
|
1108
|
+
parser = Parser("match.dem")
|
|
1109
|
+
result = parser.parse(combat_log={"max_entries": 1000})
|
|
1110
|
+
|
|
1111
|
+
for entry in result.combat_log.entries:
|
|
1112
|
+
print(f"[{entry.timestamp:.1f}s] {entry.attacker_name} -> {entry.target_name}: {entry.value} damage")
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
### Get Match Statistics
|
|
1116
|
+
|
|
1117
|
+
```python
|
|
1118
|
+
from python_manta import Parser
|
|
1119
|
+
|
|
1120
|
+
parser = Parser("match.dem")
|
|
1121
|
+
result = parser.parse(messages={"filter": "CDOTAUserMsg_StatsMatchDetails", "max_messages": 10})
|
|
1122
|
+
|
|
1123
|
+
if result.success and result.messages.messages:
|
|
1124
|
+
stats = result.messages.messages[0].data
|
|
1125
|
+
print(f"Match stats: {stats}")
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
### Multiple Data Types in Single Pass
|
|
1129
|
+
|
|
1130
|
+
```python
|
|
1131
|
+
from python_manta import Parser
|
|
1132
|
+
|
|
1133
|
+
parser = Parser("match.dem")
|
|
1134
|
+
|
|
1135
|
+
# Collect ALL data in ONE parse instead of multiple passes
|
|
1136
|
+
result = parser.parse(
|
|
1137
|
+
header=True,
|
|
1138
|
+
game_info=True,
|
|
1139
|
+
messages={"filter": "ChatMessage", "max_messages": 100},
|
|
1140
|
+
combat_log={"heroes_only": True, "max_entries": 500},
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
print(f"Map: {result.header.map_name}")
|
|
1144
|
+
print(f"Picks: {len([p for p in result.game_info.picks_bans if p.is_pick])}")
|
|
1145
|
+
print(f"Chat messages: {len(result.messages.messages)}")
|
|
1146
|
+
print(f"Combat entries: {len(result.combat_log.entries)}")
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
---
|
|
1150
|
+
|
|
1151
|
+
## Development Setup
|
|
1152
|
+
|
|
1153
|
+
When you clone this repository, the shared library (`.so`/`.dylib`/`.dll`) is not included. You have two options:
|
|
1154
|
+
|
|
1155
|
+
### Option 1: Download Pre-built Library (Recommended)
|
|
1156
|
+
|
|
1157
|
+
```bash
|
|
1158
|
+
git clone https://github.com/DeepBlueCoding/python-manta.git
|
|
1159
|
+
cd python-manta
|
|
1160
|
+
python scripts/download_library.py
|
|
1161
|
+
pip install -e '.[dev]'
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
### Option 2: Build from Source
|
|
1165
|
+
|
|
1166
|
+
Requires Go 1.19+ installed.
|
|
1167
|
+
|
|
1168
|
+
```bash
|
|
1169
|
+
git clone https://github.com/DeepBlueCoding/python-manta.git
|
|
1170
|
+
cd python-manta
|
|
1171
|
+
git clone https://github.com/dotabuff/manta.git ../manta
|
|
1172
|
+
./build.sh
|
|
1173
|
+
pip install -e '.[dev]'
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
### Verify Installation
|
|
1177
|
+
|
|
1178
|
+
```bash
|
|
1179
|
+
python -c "from python_manta import Parser; print('Success!')"
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
### Running Tests
|
|
1183
|
+
|
|
1184
|
+
```bash
|
|
1185
|
+
# Unit tests only
|
|
1186
|
+
python run_tests.py --unit
|
|
1187
|
+
|
|
1188
|
+
# Integration tests (requires .dem files)
|
|
1189
|
+
python run_tests.py --integration
|
|
1190
|
+
|
|
1191
|
+
# All tests with coverage
|
|
1192
|
+
python run_tests.py --all --coverage
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
---
|
|
1196
|
+
|
|
1197
|
+
## Architecture
|
|
1198
|
+
|
|
1199
|
+
```
|
|
1200
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
1201
|
+
│ Python Application │
|
|
1202
|
+
├─────────────────────────────────────────────────────────────┤
|
|
1203
|
+
│ python_manta Package │
|
|
1204
|
+
│ ├── Parser (main interface) │
|
|
1205
|
+
│ ├── Pydantic Models (type-safe data structures) │
|
|
1206
|
+
│ └── ctypes bindings (FFI to shared library) │
|
|
1207
|
+
├─────────────────────────────────────────────────────────────┤
|
|
1208
|
+
│ libmanta_wrapper.so (CGO Shared Library) │
|
|
1209
|
+
│ ├── CGO exports (Parse, BuildIndex, GetSnapshot, etc.) │
|
|
1210
|
+
│ ├── 272 callback implementations │
|
|
1211
|
+
│ └── JSON serialization │
|
|
1212
|
+
├─────────────────────────────────────────────────────────────┤
|
|
1213
|
+
│ dotabuff/manta (Go Library) │
|
|
1214
|
+
│ ├── PBDEMS2 format parser │
|
|
1215
|
+
│ ├── Protobuf message decoding │
|
|
1216
|
+
│ └── Callback system │
|
|
1217
|
+
└─────────────────────────────────────────────────────────────┘
|
|
1218
|
+
│
|
|
1219
|
+
▼
|
|
1220
|
+
┌─────────────────┐
|
|
1221
|
+
│ .dem Replay │
|
|
1222
|
+
│ File │
|
|
1223
|
+
└─────────────────┘
|
|
1224
|
+
```
|
|
1225
|
+
|
|
1226
|
+
### Data Flow
|
|
1227
|
+
|
|
1228
|
+
1. Python creates `Parser("match.dem")` and calls `parse(**collectors)`
|
|
1229
|
+
2. ctypes marshals parameters to C strings
|
|
1230
|
+
3. CGO wrapper receives call, opens file
|
|
1231
|
+
4. Manta Go library parses the binary .dem file
|
|
1232
|
+
5. Registered callbacks capture matching messages based on collectors
|
|
1233
|
+
6. All data collected in single pass
|
|
1234
|
+
7. Data serialized to JSON and returned to Python
|
|
1235
|
+
8. Pydantic models validate and structure the data
|
|
1236
|
+
|
|
1237
|
+
---
|
|
1238
|
+
|
|
1239
|
+
## AI Integration Guide
|
|
1240
|
+
|
|
1241
|
+
This section helps AI systems (LLMs, agents, coding assistants) understand and use this library effectively.
|
|
1242
|
+
|
|
1243
|
+
### Library Philosophy (Important for AI Agents)
|
|
1244
|
+
|
|
1245
|
+
Python Manta is a **low-level data extraction library**, not an analytics tool.
|
|
1246
|
+
|
|
1247
|
+
| ✅ In Scope | ❌ Out of Scope |
|
|
1248
|
+
|-------------|-----------------|
|
|
1249
|
+
| Raw data extraction | Analysis/aggregation logic |
|
|
1250
|
+
| Enums/constants (`RuneType`, `EntityType`, `CombatLogType`, `DamageType`, `Team`) | Fight detection algorithms |
|
|
1251
|
+
| Type-safe Pydantic models | Statistics computation |
|
|
1252
|
+
| Helper properties (e.g., `is_pro_match()`) | Data interpretation |
|
|
1253
|
+
|
|
1254
|
+
**When generating code**: Extract raw data with this library, then write analysis logic separately. Don't expect high-level functions like "get_teamfights()" or "calculate_gpm()" - those belong in user code.
|
|
1255
|
+
|
|
1256
|
+
### Quick Reference
|
|
1257
|
+
|
|
1258
|
+
```python
|
|
1259
|
+
from python_manta import Parser
|
|
1260
|
+
|
|
1261
|
+
parser = Parser("match.dem")
|
|
1262
|
+
|
|
1263
|
+
# Single-pass parsing - collect ALL data at once
|
|
1264
|
+
result = parser.parse(
|
|
1265
|
+
header=True, # Match metadata
|
|
1266
|
+
game_info=True, # Picks, bans, teams
|
|
1267
|
+
messages={"filter": "ChatMessage", "max_messages": 100}, # Chat messages
|
|
1268
|
+
combat_log={"heroes_only": True, "max_entries": 100}, # Combat events
|
|
1269
|
+
entities={"interval_ticks": 900, "max_snapshots": 50}, # Hero positions
|
|
1270
|
+
game_events={"event_filter": "dota_combatlog", "max_events": 100},
|
|
1271
|
+
modifiers={"max_modifiers": 100},
|
|
1272
|
+
parser_info=True,
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
# Access all results from the single parse
|
|
1276
|
+
print(result.header.map_name)
|
|
1277
|
+
print(len(result.game_info.picks_bans))
|
|
1278
|
+
print(len(result.messages.messages))
|
|
1279
|
+
print(len(result.combat_log.entries))
|
|
1280
|
+
```
|
|
1281
|
+
|
|
1282
|
+
### Which API to Use
|
|
1283
|
+
|
|
1284
|
+
| Task | Collector Config | Notes |
|
|
1285
|
+
|------|-----------------|-------|
|
|
1286
|
+
| Match metadata | `header=True` | Build number, map, server |
|
|
1287
|
+
| Draft sequence | `game_info=True` | Picks/bans with hero IDs |
|
|
1288
|
+
| Pro match info | `game_info=True` | Teams, league, players, winner |
|
|
1289
|
+
| Hero positions | `entities={"interval_ticks": 900}` | Position, stats at intervals |
|
|
1290
|
+
| Chat messages | `messages={"filter": "ChatMessage"}` | Player text chat |
|
|
1291
|
+
| Item purchases | `messages={"filter": "ItemPurchased"}` | Item buy events |
|
|
1292
|
+
| Map pings | `messages={"filter": "LocationPing"}` | Ping coordinates |
|
|
1293
|
+
| Combat damage | `combat_log={"types": [0]}` | Structured damage events |
|
|
1294
|
+
| Hero kills | `combat_log={"heroes_only": True}` | Hero-related combat |
|
|
1295
|
+
| Buff tracking | `modifiers={}` | Active buffs/debuffs |
|
|
1296
|
+
| Hero state | `entities={}` | Entity state snapshots |
|
|
1297
|
+
| Game events | `game_events={}` | 364 named event types |
|
|
1298
|
+
| Player info | `string_tables={"table_names": ["userinfo"]}` | Steam IDs, names |
|
|
1299
|
+
|
|
1300
|
+
### Common Patterns
|
|
1301
|
+
|
|
1302
|
+
**Extract multiple data types in single pass:**
|
|
1303
|
+
```python
|
|
1304
|
+
from python_manta import Parser
|
|
1305
|
+
|
|
1306
|
+
parser = Parser("match.dem")
|
|
1307
|
+
result = parser.parse(
|
|
1308
|
+
header=True,
|
|
1309
|
+
game_info=True,
|
|
1310
|
+
combat_log={"heroes_only": True, "max_entries": 500},
|
|
1311
|
+
)
|
|
1312
|
+
|
|
1313
|
+
print(f"Map: {result.header.map_name}")
|
|
1314
|
+
for entry in result.combat_log.entries:
|
|
1315
|
+
print(f"{entry.attacker_name} hit {entry.target_name} for {entry.value}")
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
**Track all damage to heroes:**
|
|
1319
|
+
```python
|
|
1320
|
+
parser = Parser("match.dem")
|
|
1321
|
+
result = parser.parse(combat_log={"types": [0], "heroes_only": True, "max_entries": 1000})
|
|
1322
|
+
for entry in result.combat_log.entries:
|
|
1323
|
+
print(f"{entry.attacker_name} hit {entry.target_name} for {entry.value} damage")
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
**Find specific game events:**
|
|
1327
|
+
```python
|
|
1328
|
+
parser = Parser("match.dem")
|
|
1329
|
+
result = parser.parse(game_events={"event_filter": "dota_player_kill", "max_events": 100})
|
|
1330
|
+
for event in result.game_events.events:
|
|
1331
|
+
print(f"Kill at tick {event.tick}: {event.fields}")
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
### Key Constraints
|
|
1335
|
+
|
|
1336
|
+
1. **Callback names are case-sensitive** - Use exact names from the callback list
|
|
1337
|
+
2. **Message filter uses substring matching** - `"Chat"` matches `CDOTAUserMsg_ChatMessage` and `CDOTAUserMsg_ChatEvent`
|
|
1338
|
+
3. **Always set `max_*` limits** - Prevents memory issues with large replays
|
|
1339
|
+
4. **Entity queries return end-of-replay state** - For time-series data, use combat log or game events
|
|
1340
|
+
5. **Combat log only starts after ~12-17 minutes** - HLTV broadcast delay; use entity snapshots for early game
|
|
1341
|
+
|
|
1342
|
+
---
|
|
1343
|
+
|
|
1344
|
+
## Troubleshooting
|
|
1345
|
+
|
|
1346
|
+
### Library Not Found
|
|
1347
|
+
|
|
1348
|
+
```
|
|
1349
|
+
FileNotFoundError: Shared library not found
|
|
1350
|
+
```
|
|
1351
|
+
|
|
1352
|
+
**Solution:** Install from PyPI (`pip install python-manta`) or build from source with `./build.sh`.
|
|
1353
|
+
|
|
1354
|
+
### Demo File Not Found
|
|
1355
|
+
|
|
1356
|
+
```
|
|
1357
|
+
FileNotFoundError: Demo file not found: match.dem
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
**Solution:** Provide absolute path or verify the file exists.
|
|
1361
|
+
|
|
1362
|
+
### Parsing Returns Empty Results
|
|
1363
|
+
|
|
1364
|
+
1. Check the callback name is exact (case-sensitive)
|
|
1365
|
+
2. The message type may not exist in that replay
|
|
1366
|
+
3. Try without a filter to see all messages: `parser.parse(messages={"filter": "", "max_messages": 100})`
|
|
1367
|
+
|
|
1368
|
+
### Memory Issues with Large Replays
|
|
1369
|
+
|
|
1370
|
+
**Solution:** Always set `max_messages` to a reasonable limit:
|
|
1371
|
+
```python
|
|
1372
|
+
# Good - limits memory usage
|
|
1373
|
+
result = parser.parse_universal("match.dem", "CNETMsg_Tick", 1000)
|
|
1374
|
+
|
|
1375
|
+
# Bad - could consume gigabytes of RAM
|
|
1376
|
+
result = parser.parse_universal("match.dem", "CNETMsg_Tick", 0)
|
|
1377
|
+
```
|
|
1378
|
+
|
|
1379
|
+
### Platform-Specific Issues
|
|
1380
|
+
|
|
1381
|
+
**macOS Apple Silicon:**
|
|
1382
|
+
- Ensure you have the ARM64 wheel or build from source on ARM
|
|
1383
|
+
|
|
1384
|
+
**Windows:**
|
|
1385
|
+
- The library file is `libmanta_wrapper.dll`
|
|
1386
|
+
- Ensure Visual C++ redistributables are installed
|
|
1387
|
+
|
|
1388
|
+
**Linux:**
|
|
1389
|
+
- The library file is `libmanta_wrapper.so`
|
|
1390
|
+
- Ensure `glibc` version compatibility
|
|
1391
|
+
|
|
1392
|
+
---
|
|
1393
|
+
|
|
1394
|
+
## Project Links
|
|
1395
|
+
|
|
1396
|
+
- **GitHub:** https://github.com/DeepBlueCoding/python-manta
|
|
1397
|
+
- **Documentation:** https://deepbluecoding.github.io/python-manta/
|
|
1398
|
+
- **PyPI:** https://pypi.org/project/python-manta/
|
|
1399
|
+
- **Original Manta (Go):** https://github.com/dotabuff/manta
|
|
1400
|
+
- **Dotabuff:** https://www.dotabuff.com
|
|
1401
|
+
|
|
1402
|
+
### Related Projects
|
|
1403
|
+
|
|
1404
|
+
- [clarity](https://github.com/skadistats/clarity) - Java Dota 2 replay parser
|
|
1405
|
+
- [demoinfo-go](https://github.com/markus-wa/demoinfocs-golang) - CS:GO demo parser in Go
|
|
1406
|
+
- [Yasha](https://github.com/dotabuff/yasha) - Source 1 Dota 2 parser (archived)
|
|
1407
|
+
|
|
1408
|
+
---
|
|
1409
|
+
|
|
1410
|
+
## Contributing
|
|
1411
|
+
|
|
1412
|
+
Contributions are welcome! Please:
|
|
1413
|
+
|
|
1414
|
+
1. Fork the repository
|
|
1415
|
+
2. Create a feature branch
|
|
1416
|
+
3. Make your changes
|
|
1417
|
+
4. Run tests: `python run_tests.py --all`
|
|
1418
|
+
5. Submit a pull request
|
|
1419
|
+
|
|
1420
|
+
---
|
|
1421
|
+
|
|
1422
|
+
## License
|
|
1423
|
+
|
|
1424
|
+
MIT License - see [LICENSE](LICENSE) file.
|
|
1425
|
+
|
|
1426
|
+
---
|
|
1427
|
+
|
|
1428
|
+
## Acknowledgments
|
|
1429
|
+
|
|
1430
|
+
- **[Manta](https://github.com/dotabuff/manta)** - The Go replay parser that does all the real work
|
|
1431
|
+
- **[Dotabuff](https://www.dotabuff.com)** - For maintaining Manta and supporting the community
|
|
1432
|
+
- **Valve Corporation** - For Dota 2 and the replay format
|