Parsing a Counter-Strike 2 Demo
In this notebook, we show how to install Awpy and parse a Counter-Strike 2 demo file, also called a replay file or simply, demo. To start, install Awpy via pip by running pip install awpy. Python >= 3.11 is a prerequisite! You can get a demo either through the game itself, or by visiting a website like HLTV or FACEIT. Then, to parse the demo, you can run the following:
[1]:
from awpy import Demo
# Demo: https://www.hltv.org/matches/2372746/spirit-vs-natus-vincere-blast-premier-spring-final-2024 (de_dust2, Map 2)
dem = Demo("spirit-vs-natus-vincere-m2-dust2.dem", verbose=True)
dem.parse()
# Available properties (all demos)
print(f"\nHeader: \n{dem.header}")
print(f"\nRounds: \n{dem.rounds.head(n=3)}")
print(f"\nKills: \n{dem.kills.head(n=3)}")
print(f"\nDamages: \n{dem.damages.head(n=3)}")
print(f"\nWeapon Fires: \n{dem.shots.head(n=3)}")
print(f"\nBomb: \n{dem.bomb.head(n=3)}")
print(f"\nSmokes: \n{dem.smokes.head(n=3)}")
print(f"\nInfernos: \n{dem.infernos.head(n=3)}")
print(f"\nGrenades: \n{dem.grenades.head(n=3)}")
print(f"\nFootsteps: \n{dem.footsteps.head(n=3)}")
print(f"\nTicks: \n{dem.ticks.head(n=3)}")
2025-02-17 11:27:34.708 | DEBUG | awpy.demo:parse:214 - Starting to parse spirit-vs-natus-vincere-m2-dust2.dem
2025-02-17 11:27:39.170 | SUCCESS | awpy.demo:parse:258 - Finished parsing spirit-vs-natus-vincere-m2-dust2.dem, took 4.46 seconds
Header:
{'game_directory': '/home/csserver001/cs2/game/csgo', 'allow_clientside_entities': True, 'demo_version_guid': '8e9d71ab-04a1-4c01-bb61-acfede27c046', 'server_name': 'BLAST Premier 2024', 'demo_file_stamp': 'PBDEMS2\x00', 'client_name': 'SourceTV Demo', 'allow_clientside_particles': True, 'fullpackets_version': '2', 'addons': '', 'demo_version_name': 'valve_demo_2', 'map_name': 'de_dust2', 'network_protocol': '14011'}
Rounds:
shape: (3, 9)
┌───────────┬───────┬────────────┬───────┬───┬────────┬──────────┬────────────┬─────────────┐
│ round_num ┆ start ┆ freeze_end ┆ end ┆ … ┆ winner ┆ reason ┆ bomb_plant ┆ bomb_site │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
│ u32 ┆ i32 ┆ i32 ┆ i32 ┆ ┆ str ┆ str ┆ i64 ┆ str │
╞═══════════╪═══════╪════════════╪═══════╪═══╪════════╪══════════╪════════════╪═════════════╡
│ 1 ┆ 576 ┆ 7461 ┆ 12853 ┆ … ┆ CT ┆ t_killed ┆ null ┆ not_planted │
│ 2 ┆ 13301 ┆ 14581 ┆ 17224 ┆ … ┆ CT ┆ t_killed ┆ null ┆ not_planted │
│ 3 ┆ 17672 ┆ 18952 ┆ 22872 ┆ … ┆ CT ┆ t_killed ┆ null ┆ not_planted │
└───────────┴───────┴────────────┴───────┴───┴────────┴──────────┴────────────┴─────────────┘
Kills:
shape: (3, 41)
┌────────────┬────────────┬────────────┬────────────┬───┬───────────┬───────────┬───────────┬──────┐
│ assistedfl ┆ assister_X ┆ assister_Y ┆ assister_Z ┆ … ┆ weapon_fa ┆ weapon_it ┆ weapon_or ┆ wipe │
│ ash ┆ --- ┆ --- ┆ --- ┆ ┆ uxitemid ┆ emid ┆ iginalown ┆ --- │
│ --- ┆ f32 ┆ f32 ┆ f32 ┆ ┆ --- ┆ --- ┆ er_xuid ┆ i32 │
│ bool ┆ ┆ ┆ ┆ ┆ str ┆ str ┆ --- ┆ │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ str ┆ │
╞════════════╪════════════╪════════════╪════════════╪═══╪═══════════╪═══════════╪═══════════╪══════╡
│ false ┆ null ┆ null ┆ null ┆ … ┆ 172938225 ┆ 327493063 ┆ ┆ 0 │
│ ┆ ┆ ┆ ┆ ┆ 691435991 ┆ 75 ┆ ┆ │
│ ┆ ┆ ┆ ┆ ┆ 44 ┆ ┆ ┆ │
│ false ┆ null ┆ null ┆ null ┆ … ┆ 172938225 ┆ 327493063 ┆ ┆ 0 │
│ ┆ ┆ ┆ ┆ ┆ 691435991 ┆ 75 ┆ ┆ │
│ ┆ ┆ ┆ ┆ ┆ 44 ┆ ┆ ┆ │
│ false ┆ null ┆ null ┆ null ┆ … ┆ 172938225 ┆ 374009057 ┆ ┆ 0 │
│ ┆ ┆ ┆ ┆ ┆ 691357348 ┆ 41 ┆ ┆ │
│ ┆ ┆ ┆ ┆ ┆ 45 ┆ ┆ ┆ │
└────────────┴────────────┴────────────┴────────────┴───┴───────────┴───────────┴───────────┴──────┘
Damages:
shape: (3, 22)
┌───────┬────────────┬────────────┬────────────┬───┬────────────┬────────────┬─────────┬───────────┐
│ armor ┆ attacker_X ┆ attacker_Y ┆ attacker_Z ┆ … ┆ victim_nam ┆ victim_ste ┆ weapon ┆ dmg_healt │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ e ┆ amid ┆ --- ┆ h_real │
│ i32 ┆ f32 ┆ f32 ┆ f32 ┆ ┆ --- ┆ --- ┆ str ┆ --- │
│ ┆ ┆ ┆ ┆ ┆ str ┆ str ┆ ┆ i32 │
╞═══════╪════════════╪════════════╪════════════╪═══╪════════════╪════════════╪═════════╪═══════════╡
│ 85 ┆ -407.51477 ┆ -328.71130 ┆ 0.913547 ┆ … ┆ zont1x ┆ 7656119899 ┆ ssg08 ┆ 100 │
│ ┆ 1 ┆ 4 ┆ ┆ ┆ ┆ 5880877 ┆ ┆ │
│ 0 ┆ -462.22937 ┆ 28.99902 ┆ 0.499369 ┆ … ┆ donk ┆ 7656119838 ┆ ssg08 ┆ 31 │
│ ┆ ┆ ┆ ┆ ┆ ┆ 6265483 ┆ ┆ │
│ 100 ┆ 1593.97204 ┆ 1501.21667 ┆ 0.608216 ┆ … ┆ b1t ┆ 7656119824 ┆ hkp2000 ┆ 100 │
│ ┆ 6 ┆ 5 ┆ ┆ ┆ ┆ 6607476 ┆ ┆ │
└───────┴────────────┴────────────┴────────────┴───┴────────────┴────────────┴─────────┴───────────┘
Weapon Fires:
shape: (3, 10)
┌──────────┬──────┬────────────┬────────────┬───┬────────────┬────────────┬────────────┬───────────┐
│ silenced ┆ tick ┆ player_X ┆ player_Y ┆ … ┆ player_las ┆ player_nam ┆ player_ste ┆ weapon │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ t_place_na ┆ e ┆ amid ┆ --- │
│ bool ┆ i32 ┆ f32 ┆ f32 ┆ ┆ me ┆ --- ┆ --- ┆ str │
│ ┆ ┆ ┆ ┆ ┆ --- ┆ str ┆ str ┆ │
│ ┆ ┆ ┆ ┆ ┆ str ┆ ┆ ┆ │
╞══════════╪══════╪════════════╪════════════╪═══╪════════════╪════════════╪════════════╪═══════════╡
│ false ┆ 41 ┆ -407.51477 ┆ -328.71130 ┆ … ┆ TopofMid ┆ b1t ┆ 7656119824 ┆ weapon_ss │
│ ┆ ┆ 1 ┆ 4 ┆ ┆ ┆ ┆ 6607476 ┆ g08 │
│ false ┆ 128 ┆ -413.88311 ┆ 1693.21997 ┆ … ┆ MidDoors ┆ donk ┆ 7656119838 ┆ weapon_aw │
│ ┆ ┆ 8 ┆ 1 ┆ ┆ ┆ ┆ 6265483 ┆ p │
│ false ┆ 214 ┆ 195.1064 ┆ -357.82635 ┆ … ┆ OutsideLon ┆ w0nderful ┆ 7656119906 ┆ weapon_kn │
│ ┆ ┆ ┆ 5 ┆ ┆ g ┆ ┆ 3068840 ┆ ife_m9_ba │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ yonet │
└──────────┴──────┴────────────┴────────────┴───┴────────────┴────────────┴────────────┴───────────┘
Bomb:
shape: (3, 8)
┌──────┬────────┬─────────────┬─────────────┬────────────┬──────────────────┬───────────┬──────────┐
│ tick ┆ event ┆ X ┆ Y ┆ Z ┆ steamid ┆ name ┆ bombsite │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i32 ┆ str ┆ f32 ┆ f32 ┆ f32 ┆ str ┆ str ┆ str │
╞══════╪════════╪═════════════╪═════════════╪════════════╪══════════════════╪═══════════╪══════════╡
│ 449 ┆ pickup ┆ -367.0 ┆ -808.0 ┆ 83.744965 ┆ 7656119817687830 ┆ jL ┆ null │
│ ┆ ┆ ┆ ┆ ┆ 3 ┆ ┆ │
│ 7503 ┆ drop ┆ -639.103088 ┆ -832.525757 ┆ 116.030685 ┆ 7656119906306884 ┆ w0nderful ┆ null │
│ ┆ ┆ ┆ ┆ ┆ 0 ┆ ┆ │
│ 7569 ┆ pickup ┆ -167.492432 ┆ -806.045715 ┆ 34.974846 ┆ 7656119801324332 ┆ Aleksib ┆ null │
│ ┆ ┆ ┆ ┆ ┆ 6 ┆ ┆ │
└──────┴────────┴─────────────┴─────────────┴────────────┴──────────────────┴───────────┴──────────┘
Smokes:
shape: (3, 13)
┌───────────┬───────────┬──────────┬───────────┬───┬───────────┬───────────┬───────────┬───────────┐
│ entity_id ┆ start_tic ┆ end_tick ┆ thrower_X ┆ … ┆ thrower_s ┆ X ┆ Y ┆ Z │
│ --- ┆ k ┆ --- ┆ --- ┆ ┆ teamid ┆ --- ┆ --- ┆ --- │
│ i64 ┆ --- ┆ i64 ┆ f64 ┆ ┆ --- ┆ f64 ┆ f64 ┆ f64 │
│ ┆ i64 ┆ ┆ ┆ ┆ str ┆ ┆ ┆ │
╞═══════════╪═══════════╪══════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪═══════════╡
│ 219 ┆ 9601 ┆ 11013 ┆ 847.12634 ┆ … ┆ 765611990 ┆ 394.15988 ┆ 1967.7786 ┆ 98.03125 │
│ ┆ ┆ ┆ 3 ┆ ┆ 63238565 ┆ 2 ┆ 87 ┆ │
│ 108 ┆ 11885 ┆ 13297 ┆ -1508.248 ┆ … ┆ 765611980 ┆ -1286.896 ┆ 2265.1994 ┆ 4.593013 │
│ ┆ ┆ ┆ 657 ┆ ┆ 13243326 ┆ 729 ┆ 63 ┆ │
│ 507 ┆ 14801 ┆ 16213 ┆ 579.01184 ┆ … ┆ 765611980 ┆ -150.5394 ┆ 2082.2810 ┆ -123.6676 │
│ ┆ ┆ ┆ 1 ┆ ┆ 81484775 ┆ 9 ┆ 06 ┆ 94 │
└───────────┴───────────┴──────────┴───────────┴───┴───────────┴───────────┴───────────┴───────────┘
Infernos:
shape: (3, 13)
┌───────────┬───────────┬──────────┬───────────┬───┬───────────┬───────────┬───────────┬───────────┐
│ entity_id ┆ start_tic ┆ end_tick ┆ thrower_X ┆ … ┆ thrower_s ┆ X ┆ Y ┆ Z │
│ --- ┆ k ┆ --- ┆ --- ┆ ┆ teamid ┆ --- ┆ --- ┆ --- │
│ i64 ┆ --- ┆ i64 ┆ f64 ┆ ┆ --- ┆ f64 ┆ f64 ┆ f64 │
│ ┆ i64 ┆ ┆ ┆ ┆ str ┆ ┆ ┆ │
╞═══════════╪═══════════╪══════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪═══════════╡
│ 371 ┆ 12801 ┆ 13250 ┆ -1513.689 ┆ … ┆ 765611980 ┆ -1101.931 ┆ 2602.0991 ┆ 60.730347 │
│ ┆ ┆ ┆ 087 ┆ ┆ 81484775 ┆ 763 ┆ 21 ┆ │
│ 488 ┆ 15122 ┆ 15437 ┆ -1517.362 ┆ … ┆ 765611989 ┆ -1993.518 ┆ 1637.2847 ┆ 33.104042 │
│ ┆ ┆ ┆ 061 ┆ ┆ 95880877 ┆ 311 ┆ 9 ┆ │
│ 487 ┆ 15132 ┆ 15485 ┆ 1254.0328 ┆ … ┆ 765611990 ┆ 534.49383 ┆ 661.59167 ┆ 18.419727 │
│ ┆ ┆ ┆ 37 ┆ ┆ 63238565 ┆ 5 ┆ 5 ┆ │
└───────────┴───────────┴──────────┴───────────┴───┴───────────┴───────────┴───────────┴───────────┘
Grenades:
shape: (3, 9)
┌──────────────┬─────────┬─────────────┬──────┬───┬────────────┬───────────┬───────────┬───────────┐
│ thrower_stea ┆ thrower ┆ grenade_typ ┆ tick ┆ … ┆ Y ┆ Z ┆ entity_id ┆ round_num │
│ mid ┆ --- ┆ e ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
│ --- ┆ str ┆ --- ┆ i32 ┆ ┆ f32 ┆ f32 ┆ i32 ┆ u32 │
│ u64 ┆ ┆ str ┆ ┆ ┆ ┆ ┆ ┆ │
╞══════════════╪═════════╪═════════════╪══════╪═══╪════════════╪═══════════╪═══════════╪═══════════╡
│ 765611980132 ┆ Aleksib ┆ flashbang ┆ 8238 ┆ … ┆ -393.78125 ┆ 100.84375 ┆ 445 ┆ 1 │
│ 43326 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 765611980132 ┆ Aleksib ┆ flashbang ┆ 8239 ┆ … ┆ -384.03125 ┆ 107.90625 ┆ 445 ┆ 1 │
│ 43326 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 765611980132 ┆ Aleksib ┆ flashbang ┆ 8240 ┆ … ┆ -374.3125 ┆ 114.90625 ┆ 445 ┆ 1 │
│ 43326 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
└──────────────┴─────────┴─────────────┴──────┴───┴────────────┴───────────┴───────────┴───────────┘
Footsteps:
shape: (3, 11)
┌──────────┬────────┬───────┬──────┬───┬───────────────┬──────────────┬─────────────┬──────────────┐
│ duration ┆ radius ┆ step ┆ tick ┆ … ┆ player_health ┆ player_last_ ┆ player_name ┆ player_steam │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ place_name ┆ --- ┆ id │
│ f32 ┆ i32 ┆ bool ┆ i32 ┆ ┆ i32 ┆ --- ┆ str ┆ --- │
│ ┆ ┆ ┆ ┆ ┆ ┆ str ┆ ┆ str │
╞══════════╪════════╪═══════╪══════╪═══╪═══════════════╪══════════════╪═════════════╪══════════════╡
│ 0.5 ┆ 1100 ┆ true ┆ 4 ┆ … ┆ 100 ┆ MidDoors ┆ zont1x ┆ 765611989958 │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ 80877 │
│ 0.1 ┆ 597 ┆ false ┆ 15 ┆ … ┆ 100 ┆ MidDoors ┆ zont1x ┆ 765611989958 │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ 80877 │
│ 0.5 ┆ 1100 ┆ true ┆ 16 ┆ … ┆ 100 ┆ UnderA ┆ chopper ┆ 765611980458 │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ 98864 │
└──────────┴────────┴───────┴──────┴───┴───────────────┴──────────────┴─────────────┴──────────────┘
Ticks:
shape: (3, 4)
┌──────┬───────────────────┬───────────┬───────────┐
│ tick ┆ steamid ┆ name ┆ round_num │
│ --- ┆ --- ┆ --- ┆ --- │
│ i32 ┆ u64 ┆ str ┆ u32 │
╞══════╪═══════════════════╪═══════════╪═══════════╡
│ 577 ┆ 76561198386265483 ┆ donk ┆ 1 │
│ 577 ┆ 76561199063238565 ┆ magixx ┆ 1 │
│ 577 ┆ 76561199063068840 ┆ w0nderful ┆ 1 │
└──────┴───────────────────┴───────────┴───────────┘
Getting player and global properties
Awpy uses demoparser2 as its parsing backend. This means that you can pass a list of player_props or other_props. In the following example, we get some player position properties. If you do not pass any props, we choose a default list of properties (which is already quite extensive). To see a list of available properties, visit demoparser2’s repository.
[3]:
dem = Demo("spirit-vs-natus-vincere-m2-dust2.dem")
dem.parse(player_props=["X", "Y", "Z", "health", "armor_value", "has_helmet", "has_defuser", "inventory"])
print(f"\nTicks: \n{dem.ticks.head(n=3)}")
2025-02-17 11:27:58.733 | DEBUG | awpy.demo:parse:214 - Starting to parse spirit-vs-natus-vincere-m2-dust2.dem
2025-02-17 11:28:09.006 | SUCCESS | awpy.demo:parse:258 - Finished parsing spirit-vs-natus-vincere-m2-dust2.dem, took 10.27 seconds
Ticks:
shape: (3, 12)
┌─────────────┬────────┬─────────────┬────────────┬───┬──────┬─────────────┬───────────┬───────────┐
│ inventory ┆ health ┆ has_defuser ┆ has_helmet ┆ … ┆ tick ┆ steamid ┆ name ┆ round_num │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
│ list[str] ┆ i32 ┆ bool ┆ bool ┆ ┆ i32 ┆ u64 ┆ str ┆ u32 │
╞═════════════╪════════╪═════════════╪════════════╪═══╪══════╪═════════════╪═══════════╪═══════════╡
│ ["knife_but ┆ 100 ┆ false ┆ false ┆ … ┆ 577 ┆ 76561198386 ┆ donk ┆ 1 │
│ terfly", ┆ ┆ ┆ ┆ ┆ ┆ 265483 ┆ ┆ │
│ "USP-S"] ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ ["knife_kar ┆ 100 ┆ false ┆ false ┆ … ┆ 577 ┆ 76561199063 ┆ magixx ┆ 1 │
│ ambit", ┆ ┆ ┆ ┆ ┆ ┆ 238565 ┆ ┆ │
│ "USP-S"] ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ ["knife_m9_ ┆ 100 ┆ false ┆ false ┆ … ┆ 577 ┆ 76561199063 ┆ w0nderful ┆ 1 │
│ bayonet", ┆ ┆ ┆ ┆ ┆ ┆ 068840 ┆ ┆ │
│ "Glock-18… ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
└─────────────┴────────┴─────────────┴────────────┴───┴──────┴─────────────┴───────────┴───────────┘
Obtaining all events
Because we use the demoparser2 backend, we can parse many kinds of events in the demo. To see the default list, you can check Demo.default_events. These events are parsed unless specified otherwise (via the events=[...] argument in .parse()). To access parsed events, after parsing a demo, simply check the .events property, which is a dictionary where the key is the event name and the value is the parsed Polars dataframe.
[5]:
for event_name, event in dem.events.items():
print(f"{event_name}: {event.shape[0]} rows x {event.shape[1]} columns")
bomb_planted: 12 rows x 13 columns
player_given_c4: 24 rows x 12 columns
bomb_dropped: 63 rows x 13 columns
round_freeze_end: 23 rows x 2 columns
bomb_defused: 2 rows x 13 columns
player_spawn: 242 rows x 12 columns
inferno_startburn: 108 rows x 16 columns
smokegrenade_expired: 135 rows x 16 columns
player_death: 165 rows x 53 columns
weapon_fire: 3402 rows x 14 columns
bomb_pickup: 75 rows x 12 columns
player_sound: 23783 rows x 15 columns
inferno_expire: 108 rows x 16 columns
bomb_exploded: 6 rows x 13 columns
smokegrenade_detonate: 146 rows x 16 columns
round_end: 22 rows x 5 columns
item_pickup: 1724 rows x 15 columns
player_hurt: 588 rows x 29 columns
round_start: 24 rows x 3 columns
hegrenade_detonate: 64 rows x 16 columns
round_officially_ended: 44 rows x 2 columns
flashbang_detonate: 183 rows x 16 columns