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.
@@ -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
+ [![PyPI version](https://badge.fury.io/py/python-manta.svg)](https://pypi.org/project/python-manta/)
42
+ [![Documentation](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://deepbluecoding.github.io/python-manta/)
43
+ [![Build Status](https://github.com/DeepBlueCoding/python-manta/actions/workflows/build-wheels.yml/badge.svg)](https://github.com/DeepBlueCoding/python-manta/actions/workflows/build-wheels.yml)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
45
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](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