Awesome
Demo parser for Counter-Strike 2
We now have a discord channel: Discord
Demoparser is a tool for analysing CS2 replay files "demos". All the heavy lifting is done in Rust, but you use it from the comfort of Python/JavaScript. The parser takes a slightly different approach to exposing the information in the demo. Rather than letting you hook onto events in a streaming fashion, it lets you "query" the demo similarly to how you would interact with a database.
Install
Python: pip install demoparser2
(python >= 3.8)
NodeJS: npm i @laihoe/demoparser2
WASM: npm i demoparser2
Getting started
Python
from demoparser2 import DemoParser
parser = DemoParser("path_to_demo.dem")
event_df = parser.parse_event("player_death", player=["X", "Y"], other=["total_rounds_played"])
ticks_df = parser.parse_ticks(["X", "Y"])
NodeJS
var {parseEvent, parseTicks} = require('@laihoe/demoparser2');
let event_json = parseEvent("path_to_demo.dem", "player_death", ["X", "Y"], ["total_rounds_played"])
let ticks_json = parseTicks("path_to_demo.dem", ["X", "Y"])
Examples in Python and JavaScript
Scuffed Documentaion
Performance
The benchmark finds the coordinates of all player deaths. Code: Benchmark
Benchmarked 50 demos containing a mix of (MM, Faceit, HLTV) demos. Total size: 4.6GB
device | CPU cores | total time | MB/s |
---|---|---|---|
gaming PC | 12 | 6.14s | 749 |
ThinkPad T14 gen 2 | 4 | 14.00s | 328 |
Gaming pc specs: CPU: Ryzen 5900x, SSD: Samsung 980 pro NVME
Thinkpad specs: CPU: i5-1335g7, SSD: Toshiba XG6 NVME
Both devices run Ubuntu 20.04
Python/JS are rougly as fast.
List of fields the parser supports:
Player data
Name | "Real" name |
---|---|
X | m_vec + m_cell |
Y | m_vec + m_cell |
Z | m_vec + m_cell |
health | m_iHealth |
score | m_iScore |
mvps | m_iMVPs |
is_alive | m_bPawnIsAlive |
balance | m_iAccount |
inventory | _ |
inventory_as_ids | - |
life_state | m_lifeState |
pitch | m_angEyeAngles[0] |
yaw | m_angEyeAngles[1] |
is_auto_muted | m_bHasCommunicationAbuseMute |
crosshair_code | m_szCrosshairCodes |
pending_team_num | m_iPendingTeamNum |
player_color | m_iCompTeammateColor |
ever_played_on_team | m_bEverPlayedOnTeam |
is_coach_team | m_iCoachingTeam |
rank | m_iCompetitiveRanking |
rank_if_win | m_iCompetitiveRankingPredicted_Win |
rank_if_loss | m_iCompetitiveRankingPredicted_Loss |
rank_if_tie | m_iCompetitiveRankingPredicted_Tie |
comp_wins | m_iCompetitiveWins |
comp_rank_type | m_iCompetitiveRankType |
is_controlling_bot | m_bControllingBot |
has_controlled_bot_this_round | m_bHasControlledBotThisRound |
can_control_bot | m_bCanControlObservedBot |
has_defuser | m_bPawnHasDefuser |
has_helmet | m_bPawnHasHelmet |
spawn_time | m_iPawnLifetimeStart |
death_time | m_iPawnLifetimeEnd |
game_time | net_tick |
is_connected | m_iConnected |
player_name | m_iszPlayerName |
player_steamid | m_steamID |
fov | m_iDesiredFOV |
start_balance | m_iStartAccount |
total_cash_spent | m_iTotalCashSpent |
cash_spent_this_round | m_iCashSpentThisRound |
music_kit_id | m_unMusicID |
leader_honors | m_nPersonaDataPublicCommendsLeader |
teacher_honors | m_nPersonaDataPublicCommendsTeacher |
friendly_honors | m_nPersonaDataPublicCommendsFriendly |
ping | m_iPing |
move_collide | m_MoveCollide |
move_type | m_MoveType |
team_num | m_iTeamNum |
active_weapon | m_hActiveWeapon |
looking_at_weapon | m_bIsLookingAtWeapon |
holding_look_at_weapon | m_bIsHoldingLookAtWeapon |
next_attack_time | m_flNextAttack |
duck_time_ms | m_nDuckTimeMsecs |
max_speed | m_flMaxspeed |
max_fall_velo | m_flMaxFallVelocity |
duck_amount | m_flDuckAmount |
duck_speed | m_flDuckSpeed |
duck_overrdie | m_bDuckOverride |
old_jump_pressed | m_bOldJumpPressed |
jump_until | m_flJumpUntil |
jump_velo | m_flJumpVel |
fall_velo | m_flFallVelocity |
in_crouch | m_bInCrouch |
crouch_state | m_nCrouchState |
ducked | m_bDucked |
ducking | m_bDucking |
in_duck_jump | m_bInDuckJump |
allow_auto_movement | m_bAllowAutoMovement |
jump_time_ms | m_nJumpTimeMsecs |
last_duck_time | m_flLastDuckTime |
is_rescuing | m_bIsRescuing |
weapon_purchases_this_match | m_iWeaponPurchasesThisMatch |
weapon_purchases_this_round | m_iWeaponPurchasesThisRound |
spotted | m_bSpotted |
approximate_spotted_by | m_bSpottedByMask |
time_last_injury | m_flTimeOfLastInjury |
direction_last_injury | m_nRelativeDirectionOfLastInjury |
player_state | m_iPlayerState |
passive_items | m_passiveItems |
is_scoped | m_bIsScoped |
is_walking | m_bIsWalking |
resume_zoom | m_bResumeZoom |
is_defusing | m_bIsDefusing |
is_grabbing_hostage | m_bIsGrabbingHostage |
blocking_use_in_progess | m_iBlockingUseActionInProgress |
molotov_damage_time | m_fMolotovDamageTime |
moved_since_spawn | m_bHasMovedSinceSpawn |
in_bomb_zone | m_bInBombZone |
in_buy_zone | m_bInBuyZone |
in_no_defuse_area | m_bInNoDefuseArea |
killed_by_taser | m_bKilledByTaser |
move_state | m_iMoveState |
which_bomb_zone | m_nWhichBombZone |
in_hostage_rescue_zone | m_bInHostageRescueZone |
stamina | m_flStamina |
direction | m_iDirection |
shots_fired | m_iShotsFired |
armor_value | m_ArmorValue |
velo_modifier | m_flVelocityModifier |
ground_accel_linear_frac_last_time | m_flGroundAccelLinearFracLastTime |
flash_duration | m_flFlashDuration |
flash_max_alpha | m_flFlashMaxAlpha |
wait_for_no_attack | m_bWaitForNoAttack |
last_place_name | m_szLastPlaceName |
is_strafing | m_bStrafing |
round_start_equip_value | m_unRoundStartEquipmentValue |
current_equip_value | m_unCurrentEquipmentValue |
velocity | - |
velocity_X | - |
velocity_Y | - |
velocity_Z | - |
agent_skin | - |
user_id | - |
entity_id | - |
is_airborne | m_hGroundEntity |
aim_punch_angle | CCSPlayerPawn.m_aimPunchAngle |
aim_punch_angle_vel | CCSPlayerPawn.m_aimPunchAngleVel |
Buttons
True/Flase if player is pressing button.
Name | Real name |
---|---|
FORWARD | m_nButtonDownMaskPrev |
LEFT | m_nButtonDownMaskPrev |
RIGHT | m_nButtonDownMaskPrev |
BACK | m_nButtonDownMaskPrev |
FIRE | m_nButtonDownMaskPrev |
RIGHTCLICK | m_nButtonDownMaskPrev |
RELOAD | m_nButtonDownMaskPrev |
INSPECT | m_nButtonDownMaskPrev |
USE | m_nButtonDownMaskPrev |
ZOOM | m_nButtonDownMaskPrev |
SCOREBOARD | m_nButtonDownMaskPrev |
WALK | m_nButtonDownMaskPrev |
button | m_nButtonDownMaskPrev |
(buttons is the real value of m_nButtonDownMaskPrev and the others are derived from it)
Game State
Name | Real name |
---|---|
team_rounds_total | m_iScore |
team_surrendered | m_bSurrendered |
team_name | m_szTeamname |
team_score_overtime | m_scoreOvertime |
team_match_stat | m_szTeamMatchStat |
team_num_map_victories | m_numMapVictories |
team_score_first_half | m_scoreFirstHalf |
team_score_second_half | m_scoreSecondHalf |
team_clan_name | m_szClanTeamname |
is_freeze_period | m_bFreezePeriod |
is_warmup_period | m_bWarmupPeriod |
warmup_period_end | m_fWarmupPeriodEnd |
warmup_period_start | m_fWarmupPeriodStart |
is_terrorist_timeout | m_bTerroristTimeOutActive |
is_ct_timeout | m_bCTTimeOutActive |
terrorist_timeout_remaining | m_flTerroristTimeOutRemaining |
ct_timeout_remaining | m_flCTTimeOutRemaining |
num_terrorist_timeouts | m_nTerroristTimeOuts |
num_ct_timeouts | m_nCTTimeOuts |
is_technical_timeout | m_bTechnicalTimeOut |
is_waiting_for_resume | m_bMatchWaitingForResume |
match_start_time | m_fMatchStartTime |
round_start_time | m_fRoundStartTime |
restart_round_time | m_flRestartRoundTime |
is_game_restart | m_bGameRestart |
game_start_time | m_flGameStartTime |
time_until_next_phase_start | m_timeUntilNextPhaseStarts |
game_phase | m_gamePhase |
total_rounds_played | m_totalRoundsPlayed |
rounds_played_this_phase | m_nRoundsPlayedThisPhase |
hostages_remaining | m_iHostagesRemaining |
any_hostages_reached | m_bAnyHostageReached |
has_bombites | m_bMapHasBombTarget |
has_rescue_zone | m_bMapHasRescueZone |
has_buy_zone | m_bMapHasBuyZone |
is_matchmaking | m_bIsQueuedMatchmaking |
match_making_mode | m_nQueuedMatchmakingMode |
is_valve_dedicated_server | m_bIsValveDS |
gungame_prog_weap_ct | m_iNumGunGameProgressiveWeaponsCT |
gungame_prog_weap_t | m_iNumGunGameProgressiveWeaponsT |
spectator_slot_count | m_iSpectatorSlotCount |
is_match_started | m_bHasMatchStarted |
n_best_of_maps | m_numBestOfMaps |
is_bomb_dropped | m_bBombDropped |
is_bomb_planted | m_bBombPlanted |
round_win_status | m_iRoundWinStatus |
round_win_reason | m_eRoundWinReason |
terrorist_cant_buy | m_bTCantBuy |
ct_cant_buy | m_bCTCantBuy |
ct_losing_streak | m_iNumConsecutiveCTLoses |
t_losing_streak | m_iNumConsecutiveTerroristLoses |
survival_start_time | m_flSurvivalStartTime |
round_in_progress | m_bRoundInProgress |
Weapon
Name | Real name |
---|---|
active_weapon_name | m_iItemDefinitionIndex + lookup |
active_weapon_skin | m_iRawValue32 + lookup |
active_weapon_ammo | m_iClip1 |
active_weapon_original_owner | m_OriginalOwnerXuidLow + m_OriginalOwnerXuidHigh |
total_ammo_left | m_pReserveAmmo |
item_def_idx | m_iItemDefinitionIndex |
weapon_quality | m_iEntityQuality |
entity_lvl | m_iEntityLevel |
item_id_high | m_iItemIDHigh |
item_id_low | m_iItemIDLow |
item_account_id | m_iAccountID |
inventory_position | m_iInventoryPosition |
is_initialized | m_bInitialized |
econ_item_attribute_def_idx | m_iAttributeDefinitionIndex |
initial_value | m_flInitialValue |
refundable_currency | m_nRefundableCurrency |
set_bonus | m_bSetBonus |
custom_name | m_szCustomName |
orig_owner_xuid_low | m_OriginalOwnerXuidLow |
orig_owner_xuid_high | m_OriginalOwnerXuidHigh |
fall_back_paint_kit | m_nFallbackPaintKit |
fall_back_seed | m_nFallbackSeed |
fall_back_wear | m_flFallbackWear |
fall_back_stat_track | m_nFallbackStatTrak |
m_iState | m_iState |
fire_seq_start_time | m_flFireSequenceStartTime |
fire_seq_start_time_change | m_nFireSequenceStartTimeChange |
is_player_fire_event_primary | m_bPlayerFireEventIsPrimary |
weapon_mode | m_weaponMode |
accuracy_penalty | m_fAccuracyPenalty |
i_recoil_idx | m_iRecoilIndex |
fl_recoil_idx | m_flRecoilIndex |
is_burst_mode | m_bBurstMode |
post_pone_fire_ready_time | m_flPostponeFireReadyTime |
is_in_reload | m_bInReload |
reload_visually_complete | m_bReloadVisuallyComplete |
dropped_at_time | m_flDroppedAtTime |
is_hauled_back | m_bIsHauledBack |
is_silencer_on | m_bSilencerOn |
time_silencer_switch_complete | m_flTimeSilencerSwitchComplete |
orig_team_number | m_iOriginalTeamNumber |
prev_owner | m_hPrevOwner |
last_shot_time | m_fLastShotTime |
iron_sight_mode | m_iIronSightMode |
num_empty_attacks | m_iNumEmptyAttacks |
zoom_lvl | m_zoomLevel |
burst_shots_remaining | m_iBurstShotsRemaining |
needs_bolt_action | m_bNeedsBoltAction |
next_primary_attack_tick | m_nNextPrimaryAttackTick |
next_primary_attack_tick_ratio | m_flNextPrimaryAttackTickRatio |
next_secondary_attack_tick | m_nNextSecondaryAttackTick |
next_secondary_attack_tick_ratio | m_flNextSecondaryAttackTickRatio |
weapon_float | m_iRawValue32 |
weapon_paint_seed | m_iRawValue32 |
weapon_stickers | m_iRawValue32 |
usercommands
Name | Real name |
---|---|
usercmd_viewangle_x | - |
usercmd_viewangle_y | - |
usercmd_viewangle_z | - |
usercmd_buttonstate_1 | - |
usercmd_buttonstate_2 | - |
usercmd_buttonstate_3 | - |
usercmd_consumed_server_angle_changes | - |
usercmd_forward_move | - |
usercmd_left_move | - |
usercmd_impulse | - |
usercmd_mouse_dx | - |
usercmd_mouse_dy | - |
usercmd_left_hand_desired | - |
usercmd_weapon_select | - |
usercmd_input_history | - |
Aggregate stats (updates one time per round)
Name | Real name |
---|---|
kills_total | m_iKills |
deaths_total | m_iDeaths |
assists_total | m_iAssists |
alive_time_total | m_iLiveTime |
headshot_kills_total | m_iHeadShotKills |
ace_rounds_total | m_iEnemy5Ks |
4k_rounds_total | m_iEnemy4Ks |
3k_rounds_total | m_iEnemy3Ks |
damage_total | m_iDamage |
objective_total | m_iObjective |
utility_damage_total | m_iUtilityDamage |
enemies_flashed_total | m_iEnemiesFlashed |
equipment_value_total | m_iEquipmentValue |
money_saved_total | m_iMoneySaved |
kill_reward_total | m_iKillReward |
cash_earned_total | m_iCashEarned |
Other parsers
Go: https://github.com/markus-wa/demoinfocs-golang
C#: https://github.com/saul/demofile-net
Python: https://github.com/pnxenopoulos/awpy
Java: https://github.com/skadistats/clarity
Acknowledgements
Without Dotabuff's dota 2 parser "manta" this would not have been possible. Check it out: https://github.com/dotabuff/manta
The dota 2 demo format is very similar to CS2 demo format with only a few minor changes.
Thank you to Clarity for an elegant implementation of sendtables
Thank you to Dandrews for figuring out voice data extraction