gem-dota 0.1.0__tar.gz

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.
Files changed (197) hide show
  1. gem_dota-0.1.0/PKG-INFO +289 -0
  2. gem_dota-0.1.0/README.md +258 -0
  3. gem_dota-0.1.0/pyproject.toml +116 -0
  4. gem_dota-0.1.0/src/gem/__init__.py +173 -0
  5. gem_dota-0.1.0/src/gem/__main__.py +72 -0
  6. gem_dota-0.1.0/src/gem/combat_aggregator.py +178 -0
  7. gem_dota-0.1.0/src/gem/combatlog.py +313 -0
  8. gem_dota-0.1.0/src/gem/constants.py +190 -0
  9. gem_dota-0.1.0/src/gem/data/__init__.py +0 -0
  10. gem_dota-0.1.0/src/gem/data/abilities.json +1 -0
  11. gem_dota-0.1.0/src/gem/data/heroes.json +1 -0
  12. gem_dota-0.1.0/src/gem/data/items.json +1 -0
  13. gem_dota-0.1.0/src/gem/data/leagues.json +1 -0
  14. gem_dota-0.1.0/src/gem/data/map_constants.json +13 -0
  15. gem_dota-0.1.0/src/gem/data/permanent_buffs.json +1 -0
  16. gem_dota-0.1.0/src/gem/data/xp_level.json +1 -0
  17. gem_dota-0.1.0/src/gem/dataframes.py +115 -0
  18. gem_dota-0.1.0/src/gem/entities.py +611 -0
  19. gem_dota-0.1.0/src/gem/extractors/__init__.py +46 -0
  20. gem_dota-0.1.0/src/gem/extractors/_snapshots.py +191 -0
  21. gem_dota-0.1.0/src/gem/extractors/courier.py +116 -0
  22. gem_dota-0.1.0/src/gem/extractors/draft.py +241 -0
  23. gem_dota-0.1.0/src/gem/extractors/objectives.py +216 -0
  24. gem_dota-0.1.0/src/gem/extractors/players.py +509 -0
  25. gem_dota-0.1.0/src/gem/extractors/teamfights.py +219 -0
  26. gem_dota-0.1.0/src/gem/extractors/wards.py +372 -0
  27. gem_dota-0.1.0/src/gem/field_decoder.py +446 -0
  28. gem_dota-0.1.0/src/gem/field_path.py +554 -0
  29. gem_dota-0.1.0/src/gem/field_reader.py +69 -0
  30. gem_dota-0.1.0/src/gem/field_state.py +72 -0
  31. gem_dota-0.1.0/src/gem/game_events.py +213 -0
  32. gem_dota-0.1.0/src/gem/match_builder.py +248 -0
  33. gem_dota-0.1.0/src/gem/models.py +231 -0
  34. gem_dota-0.1.0/src/gem/parser.py +549 -0
  35. gem_dota-0.1.0/src/gem/proto/__init__.py +0 -0
  36. gem_dota-0.1.0/src/gem/proto/dota2/__init__.py +0 -0
  37. gem_dota-0.1.0/src/gem/proto/dota2/base_gcmessages_pb2.py +174 -0
  38. gem_dota-0.1.0/src/gem/proto/dota2/base_gcmessages_pb2.pyi +889 -0
  39. gem_dota-0.1.0/src/gem/proto/dota2/c_peer2peer_netmessages_pb2.py +48 -0
  40. gem_dota-0.1.0/src/gem/proto/dota2/c_peer2peer_netmessages_pb2.pyi +136 -0
  41. gem_dota-0.1.0/src/gem/proto/dota2/clientmessages_pb2.py +48 -0
  42. gem_dota-0.1.0/src/gem/proto/dota2/clientmessages_pb2.pyi +110 -0
  43. gem_dota-0.1.0/src/gem/proto/dota2/connectionless_netmessages_pb2.py +36 -0
  44. gem_dota-0.1.0/src/gem/proto/dota2/connectionless_netmessages_pb2.pyi +80 -0
  45. gem_dota-0.1.0/src/gem/proto/dota2/demo_pb2.py +86 -0
  46. gem_dota-0.1.0/src/gem/proto/dota2/demo_pb2.pyi +443 -0
  47. gem_dota-0.1.0/src/gem/proto/dota2/dota_broadcastmessages_pb2.py +40 -0
  48. gem_dota-0.1.0/src/gem/proto/dota2/dota_broadcastmessages_pb2.pyi +85 -0
  49. gem_dota-0.1.0/src/gem/proto/dota2/dota_client_enums_pb2.py +44 -0
  50. gem_dota-0.1.0/src/gem/proto/dota2/dota_client_enums_pb2.pyi +170 -0
  51. gem_dota-0.1.0/src/gem/proto/dota2/dota_clientmessages_pb2.py +270 -0
  52. gem_dota-0.1.0/src/gem/proto/dota2/dota_clientmessages_pb2.pyi +1568 -0
  53. gem_dota-0.1.0/src/gem/proto/dota2/dota_commonmessages_pb2.py +64 -0
  54. gem_dota-0.1.0/src/gem/proto/dota2/dota_commonmessages_pb2.pyi +349 -0
  55. gem_dota-0.1.0/src/gem/proto/dota2/dota_fighting_game_p2p_messages_pb2.py +44 -0
  56. gem_dota-0.1.0/src/gem/proto/dota2/dota_fighting_game_p2p_messages_pb2.pyi +145 -0
  57. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_bingo_pb2.py +100 -0
  58. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_bingo_pb2.pyi +422 -0
  59. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_candy_shop_pb2.py +120 -0
  60. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_candy_shop_pb2.pyi +542 -0
  61. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_chat_pb2.py +88 -0
  62. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_chat_pb2.pyi +644 -0
  63. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_coaching_pb2.py +168 -0
  64. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_coaching_pb2.pyi +833 -0
  65. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_craftworks_pb2.py +60 -0
  66. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_craftworks_pb2.pyi +167 -0
  67. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_fantasy_pb2.py +208 -0
  68. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_fantasy_pb2.pyi +1352 -0
  69. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_guild_events_pb2.py +124 -0
  70. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_guild_events_pb2.pyi +539 -0
  71. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_guild_pb2.py +212 -0
  72. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_guild_pb2.pyi +1344 -0
  73. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_match_management_pb2.py +162 -0
  74. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_match_management_pb2.pyi +1264 -0
  75. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_pb2.py +1228 -0
  76. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_pb2.pyi +7913 -0
  77. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_showcase_pb2.py +204 -0
  78. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_showcase_pb2.pyi +1164 -0
  79. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_team_pb2.py +98 -0
  80. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_team_pb2.pyi +697 -0
  81. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_tournament_pb2.py +90 -0
  82. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_tournament_pb2.pyi +536 -0
  83. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_watch_pb2.py +80 -0
  84. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_client_watch_pb2.pyi +589 -0
  85. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_battle_report_pb2.py +638 -0
  86. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_battle_report_pb2.pyi +1009 -0
  87. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_bot_script_pb2.py +142 -0
  88. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_bot_script_pb2.pyi +1006 -0
  89. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_craftworks_pb2.py +40 -0
  90. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_craftworks_pb2.pyi +59 -0
  91. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_fighting_game_pb2.py +52 -0
  92. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_fighting_game_pb2.pyi +105 -0
  93. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_league_pb2.py +108 -0
  94. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_league_pb2.pyi +1097 -0
  95. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_lobby_pb2.py +116 -0
  96. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_lobby_pb2.pyi +1250 -0
  97. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_match_management_pb2.py +94 -0
  98. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_match_management_pb2.pyi +2074 -0
  99. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_monster_hunter_pb2.py +172 -0
  100. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_monster_hunter_pb2.pyi +762 -0
  101. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_overworld_pb2.py +184 -0
  102. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_overworld_pb2.pyi +1073 -0
  103. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_pb2.py +552 -0
  104. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_pb2.pyi +5659 -0
  105. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_survivors_pb2.py +44 -0
  106. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_common_survivors_pb2.pyi +131 -0
  107. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_msgid_pb2.py +32 -0
  108. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_msgid_pb2.pyi +2008 -0
  109. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_server_pb2.py +708 -0
  110. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_server_pb2.pyi +4751 -0
  111. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_webapi_pb2.py +134 -0
  112. gem_dota-0.1.0/src/gem/proto/dota2/dota_gcmessages_webapi_pb2.pyi +1130 -0
  113. gem_dota-0.1.0/src/gem/proto/dota2/dota_hud_types_pb2.py +172 -0
  114. gem_dota-0.1.0/src/gem/proto/dota2/dota_hud_types_pb2.pyi +62 -0
  115. gem_dota-0.1.0/src/gem/proto/dota2/dota_match_metadata_pb2.py +134 -0
  116. gem_dota-0.1.0/src/gem/proto/dota2/dota_match_metadata_pb2.pyi +1301 -0
  117. gem_dota-0.1.0/src/gem/proto/dota2/dota_modifiers_pb2.py +36 -0
  118. gem_dota-0.1.0/src/gem/proto/dota2/dota_modifiers_pb2.pyi +194 -0
  119. gem_dota-0.1.0/src/gem/proto/dota2/dota_scenariomessages_pb2.py +76 -0
  120. gem_dota-0.1.0/src/gem/proto/dota2/dota_scenariomessages_pb2.pyi +777 -0
  121. gem_dota-0.1.0/src/gem/proto/dota2/dota_shared_enums_pb2.py +166 -0
  122. gem_dota-0.1.0/src/gem/proto/dota2/dota_shared_enums_pb2.pyi +1899 -0
  123. gem_dota-0.1.0/src/gem/proto/dota2/dota_usercmd_pb2.py +32 -0
  124. gem_dota-0.1.0/src/gem/proto/dota2/dota_usercmd_pb2.pyi +56 -0
  125. gem_dota-0.1.0/src/gem/proto/dota2/dota_usermessages_pb2.py +460 -0
  126. gem_dota-0.1.0/src/gem/proto/dota2/dota_usermessages_pb2.pyi +4286 -0
  127. gem_dota-0.1.0/src/gem/proto/dota2/econ_gcmessages_pb2.py +418 -0
  128. gem_dota-0.1.0/src/gem/proto/dota2/econ_gcmessages_pb2.pyi +2558 -0
  129. gem_dota-0.1.0/src/gem/proto/dota2/econ_shared_enums_pb2.py +38 -0
  130. gem_dota-0.1.0/src/gem/proto/dota2/econ_shared_enums_pb2.pyi +73 -0
  131. gem_dota-0.1.0/src/gem/proto/dota2/engine_gcmessages_pb2.py +32 -0
  132. gem_dota-0.1.0/src/gem/proto/dota2/engine_gcmessages_pb2.pyi +53 -0
  133. gem_dota-0.1.0/src/gem/proto/dota2/enums_clientserver_pb2.py +45 -0
  134. gem_dota-0.1.0/src/gem/proto/dota2/enums_clientserver_pb2.pyi +3077 -0
  135. gem_dota-0.1.0/src/gem/proto/dota2/gameevents_pb2.py +66 -0
  136. gem_dota-0.1.0/src/gem/proto/dota2/gameevents_pb2.pyi +297 -0
  137. gem_dota-0.1.0/src/gem/proto/dota2/gcsdk_gcmessages_pb2.py +208 -0
  138. gem_dota-0.1.0/src/gem/proto/dota2/gcsdk_gcmessages_pb2.pyi +1266 -0
  139. gem_dota-0.1.0/src/gem/proto/dota2/gcsystemmsgs_pb2.py +34 -0
  140. gem_dota-0.1.0/src/gem/proto/dota2/gcsystemmsgs_pb2.pyi +55 -0
  141. gem_dota-0.1.0/src/gem/proto/dota2/netmessages_pb2.py +240 -0
  142. gem_dota-0.1.0/src/gem/proto/dota2/netmessages_pb2.pyi +2057 -0
  143. gem_dota-0.1.0/src/gem/proto/dota2/network_connection_pb2.py +736 -0
  144. gem_dota-0.1.0/src/gem/proto/dota2/network_connection_pb2.pyi +258 -0
  145. gem_dota-0.1.0/src/gem/proto/dota2/networkbasetypes_pb2.py +94 -0
  146. gem_dota-0.1.0/src/gem/proto/dota2/networkbasetypes_pb2.pyi +634 -0
  147. gem_dota-0.1.0/src/gem/proto/dota2/networksystem_protomessages_pb2.py +40 -0
  148. gem_dota-0.1.0/src/gem/proto/dota2/networksystem_protomessages_pb2.pyi +36 -0
  149. gem_dota-0.1.0/src/gem/proto/dota2/prediction_events_pb2.py +38 -0
  150. gem_dota-0.1.0/src/gem/proto/dota2/prediction_events_pb2.pyi +60 -0
  151. gem_dota-0.1.0/src/gem/proto/dota2/steamdatagram_messages_auth_pb2.py +45 -0
  152. gem_dota-0.1.0/src/gem/proto/dota2/steamdatagram_messages_auth_pb2.pyi +204 -0
  153. gem_dota-0.1.0/src/gem/proto/dota2/steamdatagram_messages_sdr_pb2.py +159 -0
  154. gem_dota-0.1.0/src/gem/proto/dota2/steamdatagram_messages_sdr_pb2.pyi +958 -0
  155. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_base_pb2.py +81 -0
  156. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_base_pb2.pyi +961 -0
  157. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_cloud/steamworkssdk_pb2.py +106 -0
  158. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_cloud/steamworkssdk_pb2.pyi +104 -0
  159. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_gamenetworkingui_pb2.py +41 -0
  160. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_gamenetworkingui_pb2.pyi +214 -0
  161. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_helprequest/steamworkssdk_pb2.py +47 -0
  162. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_helprequest/steamworkssdk_pb2.pyi +30 -0
  163. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_int_pb2.py +200 -0
  164. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_int_pb2.pyi +1149 -0
  165. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_oauth/steamworkssdk_pb2.py +64 -0
  166. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_oauth/steamworkssdk_pb2.pyi +20 -0
  167. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_pb2.py +56 -0
  168. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_pb2.pyi +287 -0
  169. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_player/steamworkssdk_pb2.py +225 -0
  170. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_player/steamworkssdk_pb2.pyi +437 -0
  171. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_publishedfile/steamworkssdk_pb2.py +366 -0
  172. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_publishedfile/steamworkssdk_pb2.pyi +635 -0
  173. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_steamlearn/steamworkssdk_pb2.py +232 -0
  174. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_steamlearn/steamworkssdk_pb2.pyi +1116 -0
  175. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_unified_base/steamworkssdk_pb2.py +35 -0
  176. gem_dota-0.1.0/src/gem/proto/dota2/steammessages_unified_base/steamworkssdk_pb2.pyi +26 -0
  177. gem_dota-0.1.0/src/gem/proto/dota2/steamnetworkingsockets_messages_certs_pb2.py +43 -0
  178. gem_dota-0.1.0/src/gem/proto/dota2/steamnetworkingsockets_messages_certs_pb2.pyi +106 -0
  179. gem_dota-0.1.0/src/gem/proto/dota2/steamnetworkingsockets_messages_pb2.py +67 -0
  180. gem_dota-0.1.0/src/gem/proto/dota2/steamnetworkingsockets_messages_pb2.pyi +548 -0
  181. gem_dota-0.1.0/src/gem/proto/dota2/steamnetworkingsockets_messages_udp_pb2.py +51 -0
  182. gem_dota-0.1.0/src/gem/proto/dota2/steamnetworkingsockets_messages_udp_pb2.pyi +216 -0
  183. gem_dota-0.1.0/src/gem/proto/dota2/te_pb2.py +82 -0
  184. gem_dota-0.1.0/src/gem/proto/dota2/te_pb2.pyi +672 -0
  185. gem_dota-0.1.0/src/gem/proto/dota2/uifontfile_format_pb2.py +36 -0
  186. gem_dota-0.1.0/src/gem/proto/dota2/uifontfile_format_pb2.pyi +40 -0
  187. gem_dota-0.1.0/src/gem/proto/dota2/usercmd_pb2.py +40 -0
  188. gem_dota-0.1.0/src/gem/proto/dota2/usercmd_pb2.pyi +155 -0
  189. gem_dota-0.1.0/src/gem/proto/dota2/usermessages_pb2.py +302 -0
  190. gem_dota-0.1.0/src/gem/proto/dota2/usermessages_pb2.pyi +2279 -0
  191. gem_dota-0.1.0/src/gem/proto/dota2/valveextensions_pb2.py +32 -0
  192. gem_dota-0.1.0/src/gem/proto/dota2/valveextensions_pb2.pyi +42 -0
  193. gem_dota-0.1.0/src/gem/py.typed +0 -0
  194. gem_dota-0.1.0/src/gem/reader.py +498 -0
  195. gem_dota-0.1.0/src/gem/sendtable.py +456 -0
  196. gem_dota-0.1.0/src/gem/stream.py +182 -0
  197. gem_dota-0.1.0/src/gem/string_table.py +304 -0
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.3
2
+ Name: gem-dota
3
+ Version: 0.1.0
4
+ Summary: Dota 2 Source 2 replay parser for data science and ML workflows
5
+ Keywords: dota2,replay,parser,esports,dem,source2
6
+ Author: whanyu1212
7
+ Author-email: whanyu1212 <whanyu1212@hotmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Games/Entertainment
18
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
19
+ Requires-Dist: numpy>=2.2.6
20
+ Requires-Dist: pandas>=2.3.3
21
+ Requires-Dist: pillow>=12.1.1
22
+ Requires-Dist: plotly>=6.6.0
23
+ Requires-Dist: protobuf>=7.34.0
24
+ Requires-Dist: protoc-wheel-0>=30.2
25
+ Requires-Dist: python-snappy>=0.7.3
26
+ Requires-Python: >=3.10
27
+ Project-URL: Homepage, https://github.com/whanyu1212/gem
28
+ Project-URL: Repository, https://github.com/whanyu1212/gem
29
+ Project-URL: Issues, https://github.com/whanyu1212/gem/issues
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Gem
33
+
34
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue?logo=python&logoColor=white)
35
+ ![License](https://img.shields.io/badge/license-MIT-green?logo=opensourceinitiative&logoColor=white)
36
+ ![Coverage](https://img.shields.io/badge/coverage-77%25-green)
37
+ ![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)
38
+ ![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)
39
+
40
+ **Gem of True Sight** — a Python Dota 2 replay parser.
41
+
42
+ Reads Source 2 `.dem` binary replay files and exposes structured output: per-tick hero state, combat events, ward placements, smoke usage, Roshan kills, gold/XP timelines, draft picks/bans, courier state, ability levels, and more.
43
+
44
+ ---
45
+
46
+ ## Why Gem?
47
+
48
+ “Gem” is inspired by **Gem of True Sight** in Dota — something that reveals what is normally hidden. Replays are dense binary data; this library aims to surface that hidden information in a form people can actually work with.
49
+
50
+ We built `gem` in **Python** because most people in data, ML, and AI workflows already live in Python ecosystems. Go/Java parsers are excellent, but they are often not the first language for this audience. The goal is to democratize replay parsing: make it approachable from scratch, easy to inspect, and simple to plug into notebooks, pandas, and ML pipelines.
51
+
52
+ There is also a practical high-MMR reason: once your MMR is around **8500+**, ranked games are typically **Immortal Draft**, and many matches become effectively private to public stats ecosystems. In those cases, services like OpenDota, Dotabuff, and STRATZ often cannot parse or expose the game through normal API flows, so the most reliable path for serious self-review is parsing your own replays (or replays shared by trusted friends/pro teammates).
53
+
54
+ Another core reason is data ownership and transparency. API/GraphQL outputs from sites like OpenDota and STRATZ are already processed interpretations, which can involve information loss and hidden assumptions. With `gem`, we want to help people understand replay parsing from first principles in a user-friendly, widely adopted language, with an implementation that is open source and inspectable end-to-end. Skadistats once open-sourced SMOKE years ago (Cython-based rather than pure Python), but it is no longer maintained; `gem` aims to help fill that gap for today’s Python/data community.
55
+
56
+ ---
57
+
58
+ ## Installation
59
+
60
+ Requires Python 3.10+. Uses [`uv`](https://github.com/astral-sh/uv) for dependency management.
61
+
62
+ ```bash
63
+ git clone https://github.com/whanyu1212/gem
64
+ cd gem
65
+ uv sync
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Quick start
71
+
72
+ ```python
73
+ import gem
74
+
75
+ match = gem.parse("my_replay.dem")
76
+
77
+ # Draft — who was picked and banned?
78
+ for event in match.draft:
79
+ action = "PICK" if event.is_pick else "BAN"
80
+ print(f"{action}: {gem.constants.hero_display(event.hero_name)}")
81
+
82
+ # Per-player summary
83
+ for player in match.players:
84
+ print(
85
+ f"{player.player_name} ({gem.constants.hero_display(player.hero_name)}): "
86
+ f"{player.kills}/{player.deaths}/{player.assists} "
87
+ f"{player.net_worth:,} NW {player.stuns_dealt:.1f}s stuns"
88
+ )
89
+ ```
90
+
91
+ ```python
92
+ # Parse to DataFrames
93
+ dfs = gem.parse_to_dataframe("my_replay.dem")
94
+ players = dfs["players"] # one row per player per sample tick
95
+ positions = dfs["positions"] # one row per (player, tick) with x/y coords
96
+ combat = dfs["combat_log"] # all combat log entries
97
+ wards = dfs["wards"] # ward placements
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Showcase — what you can do today
103
+
104
+ `gem` can power a full match analysis workflow out of the box, including:
105
+ - overview dashboards,
106
+ - combat and teamfight breakdowns,
107
+ - vision timelines/maps,
108
+ - economy progression,
109
+ - draft + objectives + chat context,
110
+ - movement trails and time-series graphs.
111
+
112
+ ### Report screenshots
113
+
114
+ <table width="100%" style="table-layout:fixed;border-collapse:separate;border-spacing:8px 8px;">
115
+ <tr>
116
+ <td align="center" valign="top" width="33.33%"><img src="assets/overview.png" alt="Overview" width="100%" height="auto"><br><sub>Overview</sub></td>
117
+ <td align="center" valign="top" width="33.33%"><img src="assets/gold_xp_graph.png" alt="Gold XP Graph" width="100%" height="auto"><br><sub>Gold / XP</sub></td>
118
+ <td align="center" valign="top" width="33.33%"><img src="assets/combat_log.png" alt="Combat Log" width="100%" height="auto"><br><sub>Combat</sub></td>
119
+ </tr>
120
+ <tr>
121
+ <td align="center" valign="top" width="33.33%"><img src="assets/teamfight.png" alt="Teamfight" width="100%" height="auto"><br><sub>Teamfight</sub></td>
122
+ <td align="center" valign="top" width="33.33%"><img src="assets/ward_map.png" alt="Ward Map" width="100%" height="auto"><br><sub>Vision Map</sub></td>
123
+ <td align="center" valign="top" width="33.33%"><img src="assets/warding_log.png" alt="Warding Log" width="100%" height="auto"><br><sub>Warding Log</sub></td>
124
+ </tr>
125
+ <tr>
126
+ <td align="center" valign="top" width="33.33%"><img src="assets/economy.png" alt="Economy" width="100%" height="auto"><br><sub>Economy</sub></td>
127
+ <td align="center" valign="top" width="33.33%"><img src="assets/draft.png" alt="Draft" width="100%" height="auto"><br><sub>Draft</sub></td>
128
+ <td align="center" valign="top" width="33.33%"><img src="assets/misc.png" alt="Misc" width="100%" height="auto"><br><sub>Misc</sub></td>
129
+ </tr>
130
+ </table>
131
+
132
+ <p align="center">
133
+ <img src="assets/movement_trail.png" alt="Movement Trail" width="45%"><br>
134
+ <sub>Movement Trail</sub>
135
+ </p>
136
+
137
+ ### Reproduce this analysis
138
+
139
+ Run the match report generator in `examples/`:
140
+
141
+ ```bash
142
+ uv run python examples/match_report.py path/to/your_replay.dem
143
+ ```
144
+
145
+ By default it writes:
146
+ - `<replay_stem>_report.html` in the project root.
147
+
148
+ ---
149
+
150
+ ## Expected output of `gem.parse(dem_path)`
151
+
152
+ `gem.parse(dem_path)` returns a **`ParsedMatch`** object — a structured, analysis-ready view of the replay.
153
+
154
+ High-level shape:
155
+ - **Match metadata**: match ID, timing/tick context, and global match-level fields.
156
+ - **Players (`match.players`)**: one `ParsedPlayer` per player with summary stats (K/D/A, damage, net worth, stuns, logs) plus time-series snapshots.
157
+ - **Timeline/event collections**: draft events, combat log entries, wards/smokes, Roshan/aegis events, objectives, chat, teamfights, and courier snapshots.
158
+ - **Advantage/time-series arrays**: values like radiant gold/XP advantage across game time.
159
+
160
+ In short: think of `ParsedMatch` as one container holding both **per-player summaries** and **time-ordered match events**, ready for direct Python analysis or conversion via `parse_to_dataframe`.
161
+
162
+ ---
163
+
164
+ ## What you can extract
165
+
166
+ | Data | API |
167
+ |---|---|
168
+ | Hero picks and bans with timestamps | `ParsedMatch.draft` |
169
+ | Per-player K/D/A, damage, net worth | `ParsedPlayer.kills` / `.damage` / `.net_worth` |
170
+ | Gold and XP over time | `ParsedPlayer.snapshots` |
171
+ | Radiant gold / XP advantage curves | `ParsedMatch.radiant_gold_adv` / `.radiant_xp_adv` |
172
+ | Ward placements with exact coordinates | `ParsedMatch.wards` |
173
+ | Smoke of Deceit activations + groups | `ParsedMatch.wards` (smoke entries) |
174
+ | Roshan kills + aegis events | `ParsedMatch.roshans` / `.aegis_events` |
175
+ | Tower and barracks kills | `ParsedMatch.towers` / `.barracks` |
176
+ | Teamfights with per-player breakdown | `ParsedMatch.teamfights` |
177
+ | Courier state snapshots per team | `ParsedMatch.courier_snapshots` |
178
+ | Ability levels per hero per tick | `PlayerStateSnapshot.ability_levels` |
179
+ | Stun seconds dealt per player | `ParsedPlayer.stuns_dealt` |
180
+ | Rune pickups per player | `ParsedPlayer.runes_log` |
181
+ | Buybacks per player | `ParsedPlayer.buyback_log` |
182
+ | Lane position heatmaps | `ParsedPlayer.lane_pos` |
183
+ | Chat messages | `ParsedMatch.chat` |
184
+ | Purchase log per player | `ParsedPlayer.purchase_log` |
185
+ | Hero / item / ability display names | `gem.constants` |
186
+
187
+ ---
188
+
189
+ ## Components
190
+
191
+ | Component | Description |
192
+ |---|---|
193
+ | `reader.py` | `BitReader` — LSB-first bit reading, varint decoding, all binary primitives |
194
+ | `stream.py` | `DemoStream` — outer message loop, Snappy decompression, magic check |
195
+ | `sendtable.py` | Schema layer — serializer + field tree parsed from `CDemoSendTables` |
196
+ | `field_decoder.py` | Type-dispatch decoders including quantized floats |
197
+ | `field_path.py` | Huffman-coded field path ops for addressing into the serializer tree |
198
+ | `field_state.py` | Nested mutable field-value tree for entity state storage |
199
+ | `field_reader.py` | Field decoder dispatch and entity field reading |
200
+ | `string_table.py` | Incremental key-history string tables |
201
+ | `entities.py` | Entity create/update/delete lifecycle and state |
202
+ | `game_events.py` | Game event schema and typed dispatch |
203
+ | `combatlog.py` | S1 (game event) and S2 (user message) combat log ingestion |
204
+ | `parser.py` | Top-level orchestrator wiring all subsystems together |
205
+ | `models.py` | `ParsedMatch` / `ParsedPlayer` output dataclasses |
206
+ | `constants.py` | Bundled hero, item, ability display names |
207
+ | `extractors/` | Per-tick polling of entity state — players, objectives, wards, courier, draft, teamfights |
208
+ | `dataframes.py` | DataFrame export from `ParsedMatch` |
209
+
210
+ ---
211
+
212
+ ## Examples
213
+
214
+ ```bash
215
+ # Comprehensive HTML analysis report (draft, combat, vision, economy, movement, etc.)
216
+ python examples/match_report.py path/to/your.dem
217
+
218
+ # Full replay summary — combat log + entity snapshots (developer-oriented baseline)
219
+ python examples/extraction_demo.py path/to/your.dem
220
+
221
+ # Match info from Steam API (requires STEAM_API_KEY env var)
222
+ python examples/steam_match_info.py <match_id>
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Documentation
228
+
229
+ Full concepts guide, API reference, and architecture diagrams:
230
+
231
+ ```bash
232
+ uv run mkdocs serve
233
+ ```
234
+
235
+ Or visit the hosted docs at [whanyu1212.github.io/gem](https://whanyu1212.github.io/gem).
236
+
237
+ Topics covered: DEM binary format, Protocol Buffers, varint encoding, the entity delta system, field paths, combat log ingestion, and more.
238
+
239
+ ---
240
+
241
+ ## Performance & benchmarking (cross-language)
242
+
243
+ Replay parsers in **Go** and **Java** are often faster in raw throughput, while `gem` prioritizes **Python-native ergonomics** for data/ML/AI workflows. Our goal is to be fast enough for research/production analysis while remaining easy to inspect, extend, and integrate with pandas/notebooks.
244
+
245
+ To keep comparisons fair, benchmark parsers with the same:
246
+ - replay set (size + patch range),
247
+ - extracted outputs (same scope),
248
+ - hardware/CPU and OS,
249
+ - warmup policy and run count.
250
+
251
+ > Benchmark results vary heavily by extraction scope (event-only vs full per-tick state), so we recommend reporting both **replays/sec** and **time per replay** with replay sizes.
252
+
253
+ | Parser | Language | Scope | Throughput (replays/sec) | Notes |
254
+ |---|---|---|---:|---|
255
+ | gem | Python | Full extraction | TBD | Focused on analytics-first workflows |
256
+ | Manta (reference) | Go | TBD | TBD | High-throughput backend-oriented parser |
257
+ | Clarity (reference) | Java | TBD | TBD | Mature JVM parser ecosystem |
258
+
259
+ If you run a benchmark, please open an issue/PR with:
260
+ - hardware specs,
261
+ - command/config used,
262
+ - replay sample list,
263
+ - median/p95 numbers.
264
+
265
+ ---
266
+
267
+ ## Known limitations
268
+
269
+ - **Roshan drops** — Aegis, Cheese, Refresher Shard, and Aghanim's Blessing pickups are not in the combat log. Roshan kills are tracked, but the specific drop items are not.
270
+ - **Smoke empty groups** — if a smoke breaks instantly on activation (hero inside sentry truesight), the group list will be empty. This is correct game behaviour, not a parsing gap.
271
+ - **Truncated/live replays** — incomplete replays may return partial parsed output (or stop near the final corrupt block) instead of a perfect full-match result.
272
+ - **Draft ID quirks** — replay pick/ban IDs can differ from static hero API IDs in some patches/formats (commonly transformed IDs). `gem` normalizes these, but edge cases may still appear.
273
+ - **Purchase attribution in spectator/HLTV paths** — purchase events are not always directly hero-attributed in combat log data; reconstruction relies on entity state and may be incomplete in edge cases.
274
+ - **Summon ownership edge cases** — most summoned-unit attribution is handled, but complex ownership cases can still produce occasional mismatches.
275
+ - **Hero icons** — not bundled in the package. Run `python scripts/fetch_hero_icons.py` to download them locally before using the draft or teamfight report examples.
276
+ - **Item icons** — not bundled in the package. Run `python scripts/fetch_item_icons.py` to download them locally before using reports that render item/rune icons.
277
+
278
+ ---
279
+
280
+ ## Roadmap
281
+
282
+ | Item | Status |
283
+ |---|---|
284
+ | Release `v0.1` on PyPI (packaging + metadata) | Planned |
285
+ | CI on GitHub Actions (tests, lint, type checks) | Planned |
286
+ | Validation harness against OpenDota-style outputs | Ongoing |
287
+ | Docs expansion (cookbook + parsing-from-scratch walkthroughs) | Planned |
288
+ | Frontend demo application (interactive replay analysis UI showcasing parser capabilities) | Planned |
289
+ | Rust acceleration for selected hot paths (PyO3 + maturin) | Deferred |
@@ -0,0 +1,258 @@
1
+ # Gem
2
+
3
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue?logo=python&logoColor=white)
4
+ ![License](https://img.shields.io/badge/license-MIT-green?logo=opensourceinitiative&logoColor=white)
5
+ ![Coverage](https://img.shields.io/badge/coverage-77%25-green)
6
+ ![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)
7
+ ![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)
8
+
9
+ **Gem of True Sight** — a Python Dota 2 replay parser.
10
+
11
+ Reads Source 2 `.dem` binary replay files and exposes structured output: per-tick hero state, combat events, ward placements, smoke usage, Roshan kills, gold/XP timelines, draft picks/bans, courier state, ability levels, and more.
12
+
13
+ ---
14
+
15
+ ## Why Gem?
16
+
17
+ “Gem” is inspired by **Gem of True Sight** in Dota — something that reveals what is normally hidden. Replays are dense binary data; this library aims to surface that hidden information in a form people can actually work with.
18
+
19
+ We built `gem` in **Python** because most people in data, ML, and AI workflows already live in Python ecosystems. Go/Java parsers are excellent, but they are often not the first language for this audience. The goal is to democratize replay parsing: make it approachable from scratch, easy to inspect, and simple to plug into notebooks, pandas, and ML pipelines.
20
+
21
+ There is also a practical high-MMR reason: once your MMR is around **8500+**, ranked games are typically **Immortal Draft**, and many matches become effectively private to public stats ecosystems. In those cases, services like OpenDota, Dotabuff, and STRATZ often cannot parse or expose the game through normal API flows, so the most reliable path for serious self-review is parsing your own replays (or replays shared by trusted friends/pro teammates).
22
+
23
+ Another core reason is data ownership and transparency. API/GraphQL outputs from sites like OpenDota and STRATZ are already processed interpretations, which can involve information loss and hidden assumptions. With `gem`, we want to help people understand replay parsing from first principles in a user-friendly, widely adopted language, with an implementation that is open source and inspectable end-to-end. Skadistats once open-sourced SMOKE years ago (Cython-based rather than pure Python), but it is no longer maintained; `gem` aims to help fill that gap for today’s Python/data community.
24
+
25
+ ---
26
+
27
+ ## Installation
28
+
29
+ Requires Python 3.10+. Uses [`uv`](https://github.com/astral-sh/uv) for dependency management.
30
+
31
+ ```bash
32
+ git clone https://github.com/whanyu1212/gem
33
+ cd gem
34
+ uv sync
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Quick start
40
+
41
+ ```python
42
+ import gem
43
+
44
+ match = gem.parse("my_replay.dem")
45
+
46
+ # Draft — who was picked and banned?
47
+ for event in match.draft:
48
+ action = "PICK" if event.is_pick else "BAN"
49
+ print(f"{action}: {gem.constants.hero_display(event.hero_name)}")
50
+
51
+ # Per-player summary
52
+ for player in match.players:
53
+ print(
54
+ f"{player.player_name} ({gem.constants.hero_display(player.hero_name)}): "
55
+ f"{player.kills}/{player.deaths}/{player.assists} "
56
+ f"{player.net_worth:,} NW {player.stuns_dealt:.1f}s stuns"
57
+ )
58
+ ```
59
+
60
+ ```python
61
+ # Parse to DataFrames
62
+ dfs = gem.parse_to_dataframe("my_replay.dem")
63
+ players = dfs["players"] # one row per player per sample tick
64
+ positions = dfs["positions"] # one row per (player, tick) with x/y coords
65
+ combat = dfs["combat_log"] # all combat log entries
66
+ wards = dfs["wards"] # ward placements
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Showcase — what you can do today
72
+
73
+ `gem` can power a full match analysis workflow out of the box, including:
74
+ - overview dashboards,
75
+ - combat and teamfight breakdowns,
76
+ - vision timelines/maps,
77
+ - economy progression,
78
+ - draft + objectives + chat context,
79
+ - movement trails and time-series graphs.
80
+
81
+ ### Report screenshots
82
+
83
+ <table width="100%" style="table-layout:fixed;border-collapse:separate;border-spacing:8px 8px;">
84
+ <tr>
85
+ <td align="center" valign="top" width="33.33%"><img src="assets/overview.png" alt="Overview" width="100%" height="auto"><br><sub>Overview</sub></td>
86
+ <td align="center" valign="top" width="33.33%"><img src="assets/gold_xp_graph.png" alt="Gold XP Graph" width="100%" height="auto"><br><sub>Gold / XP</sub></td>
87
+ <td align="center" valign="top" width="33.33%"><img src="assets/combat_log.png" alt="Combat Log" width="100%" height="auto"><br><sub>Combat</sub></td>
88
+ </tr>
89
+ <tr>
90
+ <td align="center" valign="top" width="33.33%"><img src="assets/teamfight.png" alt="Teamfight" width="100%" height="auto"><br><sub>Teamfight</sub></td>
91
+ <td align="center" valign="top" width="33.33%"><img src="assets/ward_map.png" alt="Ward Map" width="100%" height="auto"><br><sub>Vision Map</sub></td>
92
+ <td align="center" valign="top" width="33.33%"><img src="assets/warding_log.png" alt="Warding Log" width="100%" height="auto"><br><sub>Warding Log</sub></td>
93
+ </tr>
94
+ <tr>
95
+ <td align="center" valign="top" width="33.33%"><img src="assets/economy.png" alt="Economy" width="100%" height="auto"><br><sub>Economy</sub></td>
96
+ <td align="center" valign="top" width="33.33%"><img src="assets/draft.png" alt="Draft" width="100%" height="auto"><br><sub>Draft</sub></td>
97
+ <td align="center" valign="top" width="33.33%"><img src="assets/misc.png" alt="Misc" width="100%" height="auto"><br><sub>Misc</sub></td>
98
+ </tr>
99
+ </table>
100
+
101
+ <p align="center">
102
+ <img src="assets/movement_trail.png" alt="Movement Trail" width="45%"><br>
103
+ <sub>Movement Trail</sub>
104
+ </p>
105
+
106
+ ### Reproduce this analysis
107
+
108
+ Run the match report generator in `examples/`:
109
+
110
+ ```bash
111
+ uv run python examples/match_report.py path/to/your_replay.dem
112
+ ```
113
+
114
+ By default it writes:
115
+ - `<replay_stem>_report.html` in the project root.
116
+
117
+ ---
118
+
119
+ ## Expected output of `gem.parse(dem_path)`
120
+
121
+ `gem.parse(dem_path)` returns a **`ParsedMatch`** object — a structured, analysis-ready view of the replay.
122
+
123
+ High-level shape:
124
+ - **Match metadata**: match ID, timing/tick context, and global match-level fields.
125
+ - **Players (`match.players`)**: one `ParsedPlayer` per player with summary stats (K/D/A, damage, net worth, stuns, logs) plus time-series snapshots.
126
+ - **Timeline/event collections**: draft events, combat log entries, wards/smokes, Roshan/aegis events, objectives, chat, teamfights, and courier snapshots.
127
+ - **Advantage/time-series arrays**: values like radiant gold/XP advantage across game time.
128
+
129
+ In short: think of `ParsedMatch` as one container holding both **per-player summaries** and **time-ordered match events**, ready for direct Python analysis or conversion via `parse_to_dataframe`.
130
+
131
+ ---
132
+
133
+ ## What you can extract
134
+
135
+ | Data | API |
136
+ |---|---|
137
+ | Hero picks and bans with timestamps | `ParsedMatch.draft` |
138
+ | Per-player K/D/A, damage, net worth | `ParsedPlayer.kills` / `.damage` / `.net_worth` |
139
+ | Gold and XP over time | `ParsedPlayer.snapshots` |
140
+ | Radiant gold / XP advantage curves | `ParsedMatch.radiant_gold_adv` / `.radiant_xp_adv` |
141
+ | Ward placements with exact coordinates | `ParsedMatch.wards` |
142
+ | Smoke of Deceit activations + groups | `ParsedMatch.wards` (smoke entries) |
143
+ | Roshan kills + aegis events | `ParsedMatch.roshans` / `.aegis_events` |
144
+ | Tower and barracks kills | `ParsedMatch.towers` / `.barracks` |
145
+ | Teamfights with per-player breakdown | `ParsedMatch.teamfights` |
146
+ | Courier state snapshots per team | `ParsedMatch.courier_snapshots` |
147
+ | Ability levels per hero per tick | `PlayerStateSnapshot.ability_levels` |
148
+ | Stun seconds dealt per player | `ParsedPlayer.stuns_dealt` |
149
+ | Rune pickups per player | `ParsedPlayer.runes_log` |
150
+ | Buybacks per player | `ParsedPlayer.buyback_log` |
151
+ | Lane position heatmaps | `ParsedPlayer.lane_pos` |
152
+ | Chat messages | `ParsedMatch.chat` |
153
+ | Purchase log per player | `ParsedPlayer.purchase_log` |
154
+ | Hero / item / ability display names | `gem.constants` |
155
+
156
+ ---
157
+
158
+ ## Components
159
+
160
+ | Component | Description |
161
+ |---|---|
162
+ | `reader.py` | `BitReader` — LSB-first bit reading, varint decoding, all binary primitives |
163
+ | `stream.py` | `DemoStream` — outer message loop, Snappy decompression, magic check |
164
+ | `sendtable.py` | Schema layer — serializer + field tree parsed from `CDemoSendTables` |
165
+ | `field_decoder.py` | Type-dispatch decoders including quantized floats |
166
+ | `field_path.py` | Huffman-coded field path ops for addressing into the serializer tree |
167
+ | `field_state.py` | Nested mutable field-value tree for entity state storage |
168
+ | `field_reader.py` | Field decoder dispatch and entity field reading |
169
+ | `string_table.py` | Incremental key-history string tables |
170
+ | `entities.py` | Entity create/update/delete lifecycle and state |
171
+ | `game_events.py` | Game event schema and typed dispatch |
172
+ | `combatlog.py` | S1 (game event) and S2 (user message) combat log ingestion |
173
+ | `parser.py` | Top-level orchestrator wiring all subsystems together |
174
+ | `models.py` | `ParsedMatch` / `ParsedPlayer` output dataclasses |
175
+ | `constants.py` | Bundled hero, item, ability display names |
176
+ | `extractors/` | Per-tick polling of entity state — players, objectives, wards, courier, draft, teamfights |
177
+ | `dataframes.py` | DataFrame export from `ParsedMatch` |
178
+
179
+ ---
180
+
181
+ ## Examples
182
+
183
+ ```bash
184
+ # Comprehensive HTML analysis report (draft, combat, vision, economy, movement, etc.)
185
+ python examples/match_report.py path/to/your.dem
186
+
187
+ # Full replay summary — combat log + entity snapshots (developer-oriented baseline)
188
+ python examples/extraction_demo.py path/to/your.dem
189
+
190
+ # Match info from Steam API (requires STEAM_API_KEY env var)
191
+ python examples/steam_match_info.py <match_id>
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Documentation
197
+
198
+ Full concepts guide, API reference, and architecture diagrams:
199
+
200
+ ```bash
201
+ uv run mkdocs serve
202
+ ```
203
+
204
+ Or visit the hosted docs at [whanyu1212.github.io/gem](https://whanyu1212.github.io/gem).
205
+
206
+ Topics covered: DEM binary format, Protocol Buffers, varint encoding, the entity delta system, field paths, combat log ingestion, and more.
207
+
208
+ ---
209
+
210
+ ## Performance & benchmarking (cross-language)
211
+
212
+ Replay parsers in **Go** and **Java** are often faster in raw throughput, while `gem` prioritizes **Python-native ergonomics** for data/ML/AI workflows. Our goal is to be fast enough for research/production analysis while remaining easy to inspect, extend, and integrate with pandas/notebooks.
213
+
214
+ To keep comparisons fair, benchmark parsers with the same:
215
+ - replay set (size + patch range),
216
+ - extracted outputs (same scope),
217
+ - hardware/CPU and OS,
218
+ - warmup policy and run count.
219
+
220
+ > Benchmark results vary heavily by extraction scope (event-only vs full per-tick state), so we recommend reporting both **replays/sec** and **time per replay** with replay sizes.
221
+
222
+ | Parser | Language | Scope | Throughput (replays/sec) | Notes |
223
+ |---|---|---|---:|---|
224
+ | gem | Python | Full extraction | TBD | Focused on analytics-first workflows |
225
+ | Manta (reference) | Go | TBD | TBD | High-throughput backend-oriented parser |
226
+ | Clarity (reference) | Java | TBD | TBD | Mature JVM parser ecosystem |
227
+
228
+ If you run a benchmark, please open an issue/PR with:
229
+ - hardware specs,
230
+ - command/config used,
231
+ - replay sample list,
232
+ - median/p95 numbers.
233
+
234
+ ---
235
+
236
+ ## Known limitations
237
+
238
+ - **Roshan drops** — Aegis, Cheese, Refresher Shard, and Aghanim's Blessing pickups are not in the combat log. Roshan kills are tracked, but the specific drop items are not.
239
+ - **Smoke empty groups** — if a smoke breaks instantly on activation (hero inside sentry truesight), the group list will be empty. This is correct game behaviour, not a parsing gap.
240
+ - **Truncated/live replays** — incomplete replays may return partial parsed output (or stop near the final corrupt block) instead of a perfect full-match result.
241
+ - **Draft ID quirks** — replay pick/ban IDs can differ from static hero API IDs in some patches/formats (commonly transformed IDs). `gem` normalizes these, but edge cases may still appear.
242
+ - **Purchase attribution in spectator/HLTV paths** — purchase events are not always directly hero-attributed in combat log data; reconstruction relies on entity state and may be incomplete in edge cases.
243
+ - **Summon ownership edge cases** — most summoned-unit attribution is handled, but complex ownership cases can still produce occasional mismatches.
244
+ - **Hero icons** — not bundled in the package. Run `python scripts/fetch_hero_icons.py` to download them locally before using the draft or teamfight report examples.
245
+ - **Item icons** — not bundled in the package. Run `python scripts/fetch_item_icons.py` to download them locally before using reports that render item/rune icons.
246
+
247
+ ---
248
+
249
+ ## Roadmap
250
+
251
+ | Item | Status |
252
+ |---|---|
253
+ | Release `v0.1` on PyPI (packaging + metadata) | Planned |
254
+ | CI on GitHub Actions (tests, lint, type checks) | Planned |
255
+ | Validation harness against OpenDota-style outputs | Ongoing |
256
+ | Docs expansion (cookbook + parsing-from-scratch walkthroughs) | Planned |
257
+ | Frontend demo application (interactive replay analysis UI showcasing parser capabilities) | Planned |
258
+ | Rust acceleration for selected hot paths (PyO3 + maturin) | Deferred |
@@ -0,0 +1,116 @@
1
+ [project]
2
+ name = "gem-dota"
3
+ version = "0.1.0"
4
+ description = "Dota 2 Source 2 replay parser for data science and ML workflows"
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ authors = [
8
+ { name = "whanyu1212", email = "whanyu1212@hotmail.com" }
9
+ ]
10
+ requires-python = ">=3.10"
11
+ keywords = ["dota2", "replay", "parser", "esports", "dem", "source2"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Intended Audience :: Developers",
15
+ "Intended Audience :: Science/Research",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: Games/Entertainment",
22
+ "Topic :: Scientific/Engineering :: Information Analysis",
23
+ ]
24
+ dependencies = [
25
+ "numpy>=2.2.6",
26
+ "pandas>=2.3.3",
27
+ "pillow>=12.1.1",
28
+ "plotly>=6.6.0",
29
+ "protobuf>=7.34.0",
30
+ "protoc-wheel-0>=30.2",
31
+ "python-snappy>=0.7.3",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/whanyu1212/gem"
36
+ Repository = "https://github.com/whanyu1212/gem"
37
+ Issues = "https://github.com/whanyu1212/gem/issues"
38
+
39
+ [build-system]
40
+ requires = ["uv_build>=0.9.8,<0.10.0"]
41
+ build-backend = "uv_build"
42
+
43
+ [tool.uv.build-backend]
44
+ module-name = "gem"
45
+ # Icons are downloaded locally by fetch_hero_icons.py / fetch_item_icons.py
46
+ # and must not be shipped in the published wheel.
47
+ exclude-packages = ["gem.data.hero_icons", "gem.data.item_icons"]
48
+
49
+ [dependency-groups]
50
+ dev = [
51
+ "mkdocs>=1.6.1,<2.0.0",
52
+ "mkdocs-material>=9.7.4,<10.0.0",
53
+ "mkdocstrings[python]>=1.0.3,<2.0.0",
54
+ "mypy>=1.19.1",
55
+ "pre-commit>=4.5.1",
56
+ "pymdown-extensions>=10.21",
57
+ "pytest>=9.0.2",
58
+ "pytest-cov>=7.0.0",
59
+ "rich>=14.0.0",
60
+ "ruff>=0.15.5",
61
+ ]
62
+
63
+ [tool.pytest.ini_options]
64
+ testpaths = ["tests"]
65
+ addopts = "-v --tb=short"
66
+ markers = [
67
+ "slow: marks tests that require real replay files",
68
+ "integration: marks integration tests against full replays",
69
+ ]
70
+
71
+ [tool.coverage.run]
72
+ source = ["gem"]
73
+ omit = ["src/gem/proto/*"]
74
+
75
+ [tool.coverage.report]
76
+ show_missing = true
77
+
78
+ [tool.ruff]
79
+ target-version = "py310"
80
+ line-length = 100
81
+ exclude = ["src/gem/proto/", ".venv/"]
82
+
83
+ [tool.ruff.lint]
84
+ select = [
85
+ "E", # pycodestyle errors
86
+ "W", # pycodestyle warnings
87
+ "F", # pyflakes (unused imports, undefined names)
88
+ "I", # isort
89
+ "UP", # pyupgrade (modern Python syntax)
90
+ "B", # flake8-bugbear (common bugs/footguns)
91
+ "SIM", # flake8-simplify
92
+ "C4", # flake8-comprehensions (list/dict/set comp style)
93
+ ]
94
+ ignore = [
95
+ "E501", # line too long — handled by ruff-format
96
+ "B008", # do not perform function calls in default args
97
+ ]
98
+
99
+ [tool.ruff.lint.isort]
100
+ known-first-party = ["gem"]
101
+
102
+ [tool.mypy]
103
+ python_version = "3.10"
104
+ mypy_path = "src"
105
+ disallow_untyped_defs = true
106
+ disallow_incomplete_defs = true
107
+ check_untyped_defs = true
108
+ no_implicit_optional = true
109
+ warn_redundant_casts = true
110
+ warn_unused_ignores = true
111
+ ignore_missing_imports = true
112
+ exclude = ["src/gem/proto/", "\\.venv/", "tests/", ".*_pb2\\.pyi$"]
113
+
114
+ [[tool.mypy.overrides]]
115
+ module = "gem.proto.*"
116
+ ignore_errors = true