Skip to main content

gem: a Dota 2 replay parser for Python

Turn Dota 2 replays into analysis-ready Python data.
Parse Source 2 .dem files offline into typed models, pandas DataFrames, JSON, Parquet, and interactive HTML reports.

PyPI version Python versions CI status Coverage License

Documentation · Quickstart · API reference · Changelog


Install

Gem requires Python 3.10 or newer.

pip install gem-dota

Using another package manager? Run uv add gem-dota or poetry add gem-dota. Parquet export additionally requires pyarrow or fastparquet; see the DataFrame and export reference for current engine notes.

Quickstart

import gem

match = gem.parse("match.dem")

print(f"Score: {match.radiant_score}–{match.dire_score}")
for player in match.players:
    hero = gem.constants.hero_display(player.hero_name)
    print(f"{hero:20} {player.kills}/{player.deaths}/{player.assists}  {player.net_worth:,} NW")

# Convert the same structured result to JSON.
json_payload = gem.to_json(match, indent=2)

Need tables instead?

frames = gem.parse_to_dataframe("match.dem")
players = frames["player_summary"]     # one row per player
series = frames["player_timeseries"]   # sampled gold/XP/net worth
positions = frames["positions"]
combat = frames["combat_log"]

Or use the CLI:

python -m gem match.dem
python -m gem match.dem --format json --output match.json
python -m gem batch replays/ --format parquet --output out/ --workers 4

Why Gem?

🐍 Python-native
Typed match objects plug directly into notebooks, pandas, ML pipelines, and ordinary Python code.
🔎 Replay-first
Analyze local replays without depending on third-party match-history availability.
⚔️ Full-match context
Draft, combat, economy, vision, movement, objectives, items, chat, and fights in one model.
📦 Flexible outputs
Work with dataclasses, DataFrames, JSON, Parquet, batch exports, or a self-contained HTML report.

Gem is named after the Gem of True Sight: it reveals the structured match state hidden inside dense replay bytes. The parser is an independent Python implementation, cross-checked against Manta, Clarity, the OpenDota parser, and replay-derived validation fixtures.

Facts, not verdicts. Gem reports what the replay records: who was where, what they did, what it cost and what happened next. It doesn't score plays or decide whether a Roshan was "converted"; that interpretation is yours to build on top. Gem 0.12 deprecates the older helpers that did this, and 0.13 removes them.

Match reports

Gem can turn a parsed replay into a self-contained interactive report with overview, combat, laning, farming, fight, Roshan, vision, economy, draft, and movement views.

Gem interactive Dota 2 match report overview
Interactive HTML report generated from a real .dem replay.

Gem interactive ward map at 16 minutes
Vision — scrub through observer and sentry ward activity.
Gem fight breakdown with positions, combat statistics and an Aegis badge
Fights — step through positions, then inspect damage, abilities, and reveals.
from gem.reports import fetch_match_icons, write_html_report

assets = fetch_match_icons(match)  # downloads the hero/item icons this match needs
write_html_report(match, "match-report.html", assets=assets)

Hero and item icons are Valve's artwork, so Gem doesn't ship them. fetch_match_icons downloads the ones a match needs into a local cache; write_html_report itself never downloads, and shows a hero's name where its icon is missing. examples/match_report.py fetches the icons for you (--offline skips this). See the asset-cache guide and the report API for details.

What you get

Domain Examples
Match and players Scores, winner, duration, teams, K/D/A, level, GPM/XPM, final net worth
Draft and objectives Picks/bans, towers, barracks, Roshan, Aegis, Tormentor, building status
Combat Normalized combat log, damage/healing, kills, ability and item usage, fights
Economy Gold, XP, net-worth and minute-aligned advantage curves, purchases, per-player gold ledger, exact buyback costs
Map state Player positions, map regions (river, halves, lotus pools), neutral camps, lane heatmaps, wards, smoke groups, courier snapshots
Items Final inventories, neutral-item finds, consumed upgrades, Roshan drops and banner plants
Lookups Position, net worth and ability level at a tick, nearby heroes, the fight at a tick, hero visibility, smoke lifecycles and first fights, the Aegis lifecycle
Exports DataFrames, JSON, Parquet, multi-replay processing, interactive HTML reports

Useful entry points include:

  • gem.parse() → ParsedMatch
  • gem.parse_to_dataframe() / gem.to_json() / gem.to_parquet()
  • gem.parse_many*() for parallel replay batches
  • gem.find_player(), gem.position_at_tick(), and gem.fight_at_tick()
  • gem.region_of() to label a position as river, either half, or a lotus pool
  • gem.fetch_replay() for OpenDota/Valve replay download and decompression

For current complete replays, Gem's minute curves are validated against OpenDota's effective sampling boundaries. Embedded MatchDetails data enables exact postgame duration, damage, healing, GPM, and XPM values. Older or incomplete replays use replay-derived fallbacks. Fields whose absence is meaningful—such as consumed-upgrade flags—remain None; other outputs use the documented defaults for their field type.

Documentation

The hosted documentation covers both the public API and the replay format itself:

New to replay internals? Start with the Bits & Bytes Primer, then continue into the parser and entity-system deep dives.

Performance and scope

Gem is a pure-Python parser optimized for research, batch analysis, and direct use in the Python data ecosystem. Multi-replay APIs distribute work across processes, while bounded Parquet export avoids retaining every completed match at once. The v0.8 performance study records the benchmark method, compatibility checks, and current optimization boundary without claiming an apples-to-oranges win over Go or Java parsers.

Some outputs are necessarily reconstructed:

  • Farming routes, smoke analysis, fight positioning and Roshan records are experimental: they join replay facts with documented, inspectable rules. Their tags, scores and territory estimates are deprecated in 0.12 and removed in 0.13.
  • Incomplete replays can return partial output, and some exact postgame fields require embedded match details.
  • Healing Lotus pickups are not available from the replay event stream.
  • Hero/item icons and the report map image are not shipped in the wheel; call gem.reports.fetch_match_icons(match) to download the icons a report needs.

See Replay Edge Cases and the experimental-feature guides for the detailed boundaries.

Development

git clone https://github.com/whanyu1212/gem-dota.git
cd gem-dota
uv sync --group dev

uv run pytest
uv run pytest -m "integration and not network"
uv run ruff check src/ tests/
uv run mypy src/gem/

Contributions are welcome. Read CONTRIBUTING.md for the workflow and PR checklist. Parser changes should be checked against the pinned upstream reference revisions listed in CLAUDE.md and accompanied by focused regression tests. Additional tool guidance is available in AGENTS.md.

Acknowledgements

Gem builds on years of open work by the Dota replay community: Manta, Clarity, OpenDota parser, and dotaconstants.

No source code was copied from these projects; they are reference implementations used to understand protocol behavior and validate Gem's independent Python implementation. See THIRD_PARTY_LICENSES for license texts.

Sponsor Gem

Metadata

Release files for gem-dota 0.12.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gem-dota 0.12.0
File Size Uploaded
gem_dota-0.12.0.tar.gz 1.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for gem-dota 0.12.0
File Interpreter ABI Platform
gem_dota-0.12.0-py3-none-any.whl Python 3 none any Details

Total release size: 2.6 MB

Release files / gem_dota-0.12.0.tar.gz

Download URL gem_dota-0.12.0.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
0dcdf64161500b008eafeda6d84beecaa6cc1252f4cd75e83c4f535fa38de24b
BLAKE2b-256 checksum
How to use checksums
7297e474760982fd3cdf6b7eb84b711f68cbe94d35c98f11059ff059daa5c30f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / gem_dota-0.12.0-py3-none-any.whl

Download URL gem_dota-0.12.0-py3-none-any.whl
Size 1.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
8e702a89ec9194b0ed58242bf93e90e6c87480a3714eb59e5baafaf9fa0466ba
BLAKE2b-256 checksum
How to use checksums
148ca6372c41e3cea890669dbda572a63ef74f37a674b7b1b24affa26550fdac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.13.0

2 release files

This release

0.12.0 This release

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page