Documentation
¶
Overview ¶
Package s2replay parses Source 2 PBDEMS2 replays and emits a clean, attributed event stream for downstream analysis.
The first release targets Deadlock (Citadel) only. The parser decodes the demo container, network and Citadel user messages, the entity framework, and the active-modifier table, then exposes damage, modifier, purchase, and entity-sample events keyed by entity index and game time.
Index ¶
- Constants
- Variables
- func BuildRevision() (string, bool)
- func MatchClock(rules *Entity, clock *Clock) (float64, error)
- type AbilityCastEvent
- type AbilityChargesEvent
- type AbilityNotifyEvent
- type Clock
- type Command
- type ControllerSample
- type DamageEvent
- type DamageSummaryEvent
- type DamageSummaryModifierRecord
- type DamageSummaryRecord
- type Entity
- func (e *Entity) Bool(name string) (bool, bool)
- func (e *Entity) ClassID() int32
- func (e *Entity) ClassName() string
- func (e *Entity) Float32(name string) (float32, bool)
- func (e *Entity) Get(name string) any
- func (e *Entity) Index() int32
- func (e *Entity) Int32(name string) (int32, bool)
- func (e *Entity) String(name string) (string, bool)
- func (e *Entity) UInt32(name string) (uint32, bool)
- type EntitySample
- type Event
- type EventType
- type ImportantAbilityUsedEvent
- type JumpStateEvent
- type KillStreakEvent
- type Message
- type ModifierEvent
- type ModifierTransition
- type ObjectiveEvent
- type Parser
- func (p *Parser) Clock() *Clock
- func (p *Parser) CollectDamage(limit int) ([]DamageEvent, error)
- func (p *Parser) CollectEvents(limit int) ([]Event, error)
- func (p *Parser) CollectModifierEvents(limit int) ([]ModifierEvent, error)
- func (p *Parser) FindEntity(index int32) *Entity
- func (p *Parser) FindEntityByHandle(handle uint64) *Entity
- func (p *Parser) Next() (*Command, error)
- func (p *Parser) NextDamage() (DamageEvent, error)
- func (p *Parser) NextEntitySample() (EntitySample, error)
- func (p *Parser) NextEvent() (Event, error)
- func (p *Parser) NextMessage() (*Message, error)
- func (p *Parser) NextModifierEvent() (ModifierEvent, error)
- func (p *Parser) ReleasePendingQueues()
- func (p *Parser) SampleEntity(e *Entity) (EntitySample, bool)
- func (p *Parser) ServerWorld() (game, mapName string)
- func (p *Parser) SetEventMode(enabled bool)
- func (p *Parser) Stop()
- func (p *Parser) TickAtMatchClock(seconds float64) (uint32, error)
- func (p *Parser) WorldEntitySnapshot(tick uint32) ([]EntitySample, error)
- type PostMatchEvent
- type PostMatchObjective
- type PostMatchPlayer
- type PostMatchPlayerItem
- type PostMatchPlayerStat
- type PurchaseEvent
- type StaminaConsumedEvent
- type WorldEntitySampleError
- type WorldSnapshotError
Constants ¶
const DefaultTickInterval = 1.0 / 64.0
DefaultTickInterval is the seconds-per-tick used until the server's CSVCMsg_ServerInfo tick_interval is decoded from the packet stream. Source 2 servers record at 1/64 s by default; SetInterval overwrites this with the exact value once message dispatch is in place.
const ParserSourceDigest = "ffccb7c19cf6214733cbacfad41b234e6b95637a61ae3133b6150e5c63224934"
ParserSourceDigest identifies the canonical parser source and module inputs.
const PreGameTick = ^uint32(0)
PreGameTick is the sentinel tick Source 2 stamps on pre-game commands; it is ignored for clock advancement so game time stays monotonic.
const Version = "0.0.0-dev"
Version is the current s2replay release identifier.
Variables ¶
var ErrMatchClockNotReached = errors.New("the replay ends before that match time")
ErrMatchClockNotReached reports a replay that ends before the requested match time.
Functions ¶
func BuildRevision ¶
BuildRevision identifies the parser source in the running binary. Standalone builds require clean VCS metadata; dependencies require a checksummed module pseudo-version. Local replacements and unknown revisions are refused.
Types ¶
type AbilityCastEvent ¶
type AbilityCastEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
ClassName string `json:"class_name"`
OwnerEntity int32 `json:"owner_entity"`
CooldownStart float32 `json:"cooldown_start"`
CooldownEnd float32 `json:"cooldown_end"`
CastCompletedTime float32 `json:"cast_completed_time"`
AbilitySlot uint32 `json:"ability_slot,omitempty"`
HasAbilitySlot bool `json:"has_ability_slot,omitempty"`
SubclassID uint32 `json:"subclass_id,omitempty"`
HasSubclassID bool `json:"has_subclass_id,omitempty"`
}
AbilityCastEvent is one use of an ability or item, observed as a new cooldown start on the ability entity. Entity on the enclosing Event is the ability entity; OwnerEntity is the hero pawn that holds it, or -1 when the owner is not resolved. Times are game seconds.
type AbilityChargesEvent ¶
type AbilityChargesEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
ClassName string `json:"class_name"`
RemainingCharges int32 `json:"remaining_charges"`
}
AbilityChargesEvent is one ability charge-count change observed on an ability entity (dash charges are the primary consumer). Emitted only when the count differs from the previously seen value for that entity.
type AbilityNotifyEvent ¶
type AbilityNotifyEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
EntindexVictim int32 `json:"entindex_victim"`
EntindexAttacker int32 `json:"entindex_attacker"`
AbilityID uint32 `json:"ability_id"`
StatusImpact uint32 `json:"status_impact"`
HasEntindexVictim bool `json:"has_entindex_victim"`
HasEntindexAttacker bool `json:"has_entindex_attacker"`
HasAbilityID bool `json:"has_ability_id"`
HasStatusImpact bool `json:"has_status_impact"`
}
AbilityNotifyEvent is one game-authored ability contact notification.
type Clock ¶
type Clock struct {
// contains filtered or unexported fields
}
Clock converts demo ticks to game-time seconds. The interval starts at DefaultTickInterval and is replaced by the exact server value when known; callers read GameTime and never depend on the placeholder interval.
func (*Clock) ServerTick ¶
ServerTick returns the last observed network tick and its enclosing demo tick. It is absent until a CNETMsg_Tick with an explicit tick is decoded outside signon. No header offset, extrapolation, or demo-clock value is substituted.
func (*Clock) SetInterval ¶
SetInterval replaces the seconds-per-tick with the exact server value.
func (*Clock) TickInterval ¶
TickInterval returns the current seconds-per-tick.
func (*Clock) TickIntervalKnown ¶
TickIntervalKnown reports whether ServerInfo provided the exact interval.
type Command ¶
type Command struct {
Kind protocol.EDemoCommands
Tick uint32
Payload []byte
}
Command is one outer demo record: its kind, the tick it applies to, and the decompressed payload bytes awaiting message decode.
type ControllerSample ¶
type ControllerSample struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
Entity int32 `json:"entity"`
ClassID int32 `json:"class_id"`
ClassName string `json:"class_name"`
SteamID uint64 `json:"steam_id"`
PlayerName string `json:"player_name"`
NetWorth int32 `json:"net_worth"`
HeroDamage int32 `json:"hero_damage"`
HeroHealing int32 `json:"hero_healing"`
CreepGold int32 `json:"creep_gold"`
CreepGoldKill int32 `json:"creep_gold_kill"`
CreepGoldNeutral int32 `json:"creep_gold_neutral"`
CreepGoldAirOrb int32 `json:"creep_gold_air_orb"`
CreepGoldGroundOrb int32 `json:"creep_gold_ground_orb"`
CreepGoldDeny int32 `json:"creep_gold_deny"`
CreepGoldSoloBonus int32 `json:"creep_gold_solo_bonus"`
}
ControllerSample is one periodic snapshot of a player controller entity: the live scoreboard the game itself maintains.
type DamageEvent ¶
type DamageEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
Damage int32 `json:"damage"`
PreDamage float32 `json:"pre_damage"`
VictimHealthNew int32 `json:"victim_health_new"`
VictimHealthMax int32 `json:"victim_health_max"`
DamageAbsorbed float32 `json:"damage_absorbed"`
Effectiveness float32 `json:"effectiveness"`
CritDamage float32 `json:"crit_damage"`
Hits int32 `json:"hits"`
Attacker int32 `json:"attacker"`
Victim int32 `json:"victim"`
Inflictor int32 `json:"inflictor"`
AbilityEntity int32 `json:"ability_entity"`
AbilityID uint32 `json:"ability_id"`
DamageType int32 `json:"damage_type"`
CitadelDamageType int32 `json:"citadel_damage_type"`
AttackingObject int32 `json:"attacking_object"`
VictimShieldNew int32 `json:"victim_shield_new"`
VictimShieldMax int32 `json:"victim_shield_max"`
HealthLost int32 `json:"health_lost"`
HitgroupID int32 `json:"hitgroup_id"`
IsSecondaryStat bool `json:"is_secondary_stat"`
OriginX float32 `json:"origin_x"`
OriginY float32 `json:"origin_y"`
OriginZ float32 `json:"origin_z"`
DamageDirectionX float32 `json:"damage_direction_x"`
DamageDirectionY float32 `json:"damage_direction_y"`
DamageDirectionZ float32 `json:"damage_direction_z"`
ServerTick int32 `json:"server_tick"`
Flags uint64 `json:"flags"`
AttackerClass uint32 `json:"attacker_class"`
VictimClass uint32 `json:"victim_class"`
PreDamageDeprecated int32 `json:"pre_damage_deprecated"`
DamageAbsorbedDeprecated int32 `json:"damage_absorbed_deprecated"`
}
DamageEvent is the Phase 3 product projection of a Deadlock damage user message. Later phases add entity class and item attribution around it.
type DamageSummaryEvent ¶
type DamageSummaryEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
PlayerSlot int32 `json:"player_slot"`
TotalDamage int32 `json:"total_damage"`
LostGold int32 `json:"lost_gold"`
StartTime float32 `json:"start_time"`
EndTime float32 `json:"end_time"`
DamageRecords []DamageSummaryRecord `json:"damage_records"`
ModifierRecords []DamageSummaryModifierRecord `json:"modifier_records"`
}
DamageSummaryEvent is the engine-compiled recent-damage summary the game itself uses for death recap and scoreboard attribution.
type DamageSummaryModifierRecord ¶
type DamageSummaryModifierRecord struct {
AbilityID uint32 `json:"ability_id"`
ModifierTypeID uint32 `json:"modifier_type_id"`
EntindexCaster int32 `json:"entindex_caster"`
StartTime float32 `json:"start_time"`
EndTime float32 `json:"end_time"`
Debuff bool `json:"debuff"`
}
DamageSummaryModifierRecord is one engine-attributed modifier entry in a RecentDamageSummary user message.
type DamageSummaryRecord ¶
type DamageSummaryRecord struct {
Damage int32 `json:"damage"`
Hits int32 `json:"hits"`
DamageType uint32 `json:"damage_type"`
HeroID uint32 `json:"hero_id"`
AbilityID uint32 `json:"ability_id"`
AttackerClass uint32 `json:"attacker_class"`
DamageAbsorbed float32 `json:"damage_absorbed"`
IsKillingBlow bool `json:"is_killing_blow"`
VictimHeroID uint32 `json:"victim_hero_id"`
PreDamage float32 `json:"pre_damage"`
CritDamage float32 `json:"crit_damage"`
}
DamageSummaryRecord is one engine-attributed damage entry in a RecentDamageSummary user message.
type Entity ¶
type Entity struct {
// contains filtered or unexported fields
}
Entity is the parser-owned current state for one networked entity.
func (*Entity) Int32 ¶
Int32 returns the current field value as an int32 when it can be represented without changing sign.
type EntitySample ¶
type EntitySample struct {
// CameraAngles is the client camera orientation, distinct from aim-facing angles.
CameraAngles [3]float32 `json:"camera_angles"`
// CameraAnglesTicks identifies the last update of each camera component.
CameraAnglesTicks [3]uint32 `json:"camera_angles_ticks"`
// CameraAnglesSourceFields names the recorded fields for each component.
CameraAnglesSourceFields [3]string `json:"camera_angles_source_fields"`
// HasCameraAngles distinguishes observed zero components from missing evidence.
HasCameraAngles [3]bool `json:"has_camera_angles"`
// LifeState is the native lifecycle state; zero means alive, independently of health.
LifeState uint32 `json:"life_state,omitempty"`
// LifeStateTick identifies the last recorded lifecycle update.
LifeStateTick uint32 `json:"life_state_tick,omitempty"`
// HasLifeState distinguishes a recorded alive state from absent evidence.
HasLifeState bool `json:"has_life_state,omitempty"`
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
Entity int32 `json:"entity"`
EntitySerial int32 `json:"entity_serial,omitempty"`
ClassID int32 `json:"class_id"`
ClassName string `json:"class_name"`
Health float32 `json:"health"`
MaxHealth float32 `json:"max_health"`
Shield float32 `json:"shield"`
MaxShield float32 `json:"max_shield"`
HealthTick uint32 `json:"health_tick,omitempty"`
MaxHealthTick uint32 `json:"max_health_tick,omitempty"`
ShieldTick uint32 `json:"shield_tick,omitempty"`
MaxShieldTick uint32 `json:"max_shield_tick,omitempty"`
PositionX float32 `json:"position_x"`
PositionY float32 `json:"position_y"`
PositionZ float32 `json:"position_z"`
PositionXTick uint32 `json:"position_x_tick,omitempty"`
PositionYTick uint32 `json:"position_y_tick,omitempty"`
PositionZTick uint32 `json:"position_z_tick,omitempty"`
PositionXSourceField string `json:"position_x_source_field,omitempty"`
PositionYSourceField string `json:"position_y_source_field,omitempty"`
PositionZSourceField string `json:"position_z_source_field,omitempty"`
FacingXSourceField string `json:"facing_x_source_field,omitempty"`
FacingYSourceField string `json:"facing_y_source_field,omitempty"`
FacingZSourceField string `json:"facing_z_source_field,omitempty"`
VelocityXSourceField string `json:"velocity_x_source_field,omitempty"`
VelocityYSourceField string `json:"velocity_y_source_field,omitempty"`
VelocityZSourceField string `json:"velocity_z_source_field,omitempty"`
FacingX float32 `json:"facing_x,omitempty"`
FacingY float32 `json:"facing_y,omitempty"`
FacingZ float32 `json:"facing_z,omitempty"`
VelocityX float32 `json:"velocity_x,omitempty"`
VelocityY float32 `json:"velocity_y,omitempty"`
VelocityZ float32 `json:"velocity_z,omitempty"`
FacingXTick uint32 `json:"facing_x_tick,omitempty"`
FacingYTick uint32 `json:"facing_y_tick,omitempty"`
FacingZTick uint32 `json:"facing_z_tick,omitempty"`
VelocityXTick uint32 `json:"velocity_x_tick,omitempty"`
VelocityYTick uint32 `json:"velocity_y_tick,omitempty"`
VelocityZTick uint32 `json:"velocity_z_tick,omitempty"`
HasFacing bool `json:"has_facing,omitempty"`
HasFacingX bool `json:"has_facing_x,omitempty"`
HasFacingY bool `json:"has_facing_y,omitempty"`
HasFacingZ bool `json:"has_facing_z,omitempty"`
HasVelocity bool `json:"has_velocity,omitempty"`
HasVelocityX bool `json:"has_velocity_x,omitempty"`
HasVelocityY bool `json:"has_velocity_y,omitempty"`
HasVelocityZ bool `json:"has_velocity_z,omitempty"`
HeroID uint32 `json:"hero_id,omitempty"`
HeroIDTick uint32 `json:"hero_id_tick,omitempty"`
Team int32 `json:"team,omitempty"`
TeamTick uint32 `json:"team_tick,omitempty"`
HasHealth bool `json:"has_health"`
HasShield bool `json:"has_shield"`
HasPosition bool `json:"has_position"`
HasHeroID bool `json:"has_hero_id"`
HasTeam bool `json:"has_team"`
InvalidFields []string `json:"invalid_fields,omitempty"`
// PlayerSlot is the replay-local lobby slot attributed by the parser when
// known; negative means unattributed.
PlayerSlot int32 `json:"player_slot,omitempty"`
// Level is the observed hero level with its source tick.
Level uint32 `json:"level,omitempty"`
LevelTick uint32 `json:"level_tick,omitempty"`
HasLevel bool `json:"has_level,omitempty"`
// SubclassID identifies the concrete VData definition behind generic classes.
SubclassID uint32 `json:"subclass_id,omitempty"`
SubclassIDTick uint32 `json:"subclass_id_tick,omitempty"`
HasSubclassID bool `json:"has_subclass_id,omitempty"`
// AbilitySlot and UpgradeInfo retain equipment placement and the packed engine upgrade state.
AbilitySlot uint32 `json:"ability_slot,omitempty"`
AbilitySlotTick uint32 `json:"ability_slot_tick,omitempty"`
HasAbilitySlot bool `json:"has_ability_slot,omitempty"`
UpgradeInfo uint32 `json:"upgrade_info,omitempty"`
UpgradeInfoTick uint32 `json:"upgrade_info_tick,omitempty"`
HasUpgradeInfo bool `json:"has_upgrade_info,omitempty"`
// OwnerEntity and OwnerEntitySerial identify the generation in m_hOwnerEntity.
OwnerEntitySerial int32 `json:"owner_entity_serial,omitempty"`
OwnerEntity int32 `json:"owner_entity,omitempty"`
OwnerEntityTick uint32 `json:"owner_entity_tick,omitempty"`
HasOwnerEntity bool `json:"has_owner_entity,omitempty"`
// PawnEntity and PawnEntitySerial identify the controller hero pawn generation.
PawnEntitySerial int32 `json:"pawn_entity_serial,omitempty"`
PawnEntity int32 `json:"pawn_entity,omitempty"`
PawnEntityTick uint32 `json:"pawn_entity_tick,omitempty"`
HasPawnEntity bool `json:"has_pawn_entity,omitempty"`
// NetWorth is the controller scoreboard net worth.
NetWorth int32 `json:"net_worth,omitempty"`
NetWorthTick uint32 `json:"net_worth_tick,omitempty"`
HasNetWorth bool `json:"has_net_worth,omitempty"`
// Souls is the controller's unspent souls.
Souls int32 `json:"souls,omitempty"`
SoulsTick uint32 `json:"souls_tick,omitempty"`
HasSouls bool `json:"has_souls,omitempty"`
// Lane is the controller's original lane assignment, numbered as
// troopers' m_iLane.
Lane int32 `json:"lane,omitempty"`
LaneTick uint32 `json:"lane_tick,omitempty"`
HasLane bool `json:"has_lane,omitempty"`
// RemainingCharges is the ability charge count.
RemainingCharges int32 `json:"remaining_charges,omitempty"`
RemainingChargesTick uint32 `json:"remaining_charges_tick,omitempty"`
HasRemainingCharges bool `json:"has_remaining_charges,omitempty"`
// StaminaLatchTime is the observed stamina interpolation anchor in server seconds.
StaminaLatchTime float32 `json:"stamina_latch_time,omitempty"`
// StaminaLatchTimeTick identifies the source update for StaminaLatchTime.
StaminaLatchTimeTick uint32 `json:"stamina_latch_time_tick,omitempty"`
// HasStaminaLatchTime distinguishes observed zero from unavailable evidence.
HasStaminaLatchTime bool `json:"has_stamina_latch_time,omitempty"`
// StaminaLatchValue is the stamina value at StaminaLatchTime, not current stamina.
StaminaLatchValue float32 `json:"stamina_latch_value,omitempty"`
// StaminaLatchValueTick identifies the source update for StaminaLatchValue.
StaminaLatchValueTick uint32 `json:"stamina_latch_value_tick,omitempty"`
// HasStaminaLatchValue distinguishes observed zero from unavailable evidence.
HasStaminaLatchValue bool `json:"has_stamina_latch_value,omitempty"`
// CooldownStart records the start of the active cooldown interval in server seconds.
CooldownStart float32 `json:"cooldown_start,omitempty"`
// CooldownStartTick identifies the last replay update for CooldownStart.
CooldownStartTick uint32 `json:"cooldown_start_tick,omitempty"`
// HasCooldownStart distinguishes an observed zero from an absent timer.
HasCooldownStart bool `json:"has_cooldown_start,omitempty"`
// ChargeRechargeStart records the start of the charge recovery interval in server seconds.
ChargeRechargeStart float32 `json:"charge_recharge_start,omitempty"`
// ChargeRechargeStartTick identifies the last replay update for ChargeRechargeStart.
ChargeRechargeStartTick uint32 `json:"charge_recharge_start_tick,omitempty"`
// HasChargeRechargeStart distinguishes an observed zero from an absent timer.
HasChargeRechargeStart bool `json:"has_charge_recharge_start,omitempty"`
// ChargeRechargeEnd records the end of the charge recovery interval in server seconds.
ChargeRechargeEnd float32 `json:"charge_recharge_end,omitempty"`
// ChargeRechargeEndTick identifies the last replay update for ChargeRechargeEnd.
ChargeRechargeEndTick uint32 `json:"charge_recharge_end_tick,omitempty"`
// HasChargeRechargeEnd distinguishes an observed zero from an absent timer.
HasChargeRechargeEnd bool `json:"has_charge_recharge_end,omitempty"`
// CooldownEnd is the ability cooldown end time in game seconds.
CooldownEnd float32 `json:"cooldown_end,omitempty"`
CooldownEndTick uint32 `json:"cooldown_end_tick,omitempty"`
HasCooldownEnd bool `json:"has_cooldown_end,omitempty"`
// Scoreboard components observed on the controller at sample time.
Deaths int32 `json:"deaths,omitempty"`
DeathsTick uint32 `json:"deaths_tick,omitempty"`
HasDeaths bool `json:"has_deaths,omitempty"`
LastHits int32 `json:"last_hits,omitempty"`
LastHitsTick uint32 `json:"last_hits_tick,omitempty"`
HasLastHits bool `json:"has_last_hits,omitempty"`
Denies int32 `json:"denies,omitempty"`
DeniesTick uint32 `json:"denies_tick,omitempty"`
HasDenies bool `json:"has_denies,omitempty"`
KillStreak int32 `json:"kill_streak,omitempty"`
KillStreakTick uint32 `json:"kill_streak_tick,omitempty"`
HasKillStreak bool `json:"has_kill_streak,omitempty"`
HeroDamage int32 `json:"hero_damage,omitempty"`
HeroDamageTick uint32 `json:"hero_damage_tick,omitempty"`
HasHeroDamage bool `json:"has_hero_damage,omitempty"`
// Grounded and Crouching retain observed movement state, including false.
Grounded bool `json:"grounded"`
Crouching bool `json:"crouching"`
// HasGrounded and HasCrouching distinguish observations from absent fields.
HasGrounded bool `json:"has_grounded,omitempty"`
HasCrouching bool `json:"has_crouching,omitempty"`
}
EntitySample is a typed snapshot of one entity's state at a tick, consumed by downstream event code.
func (EntitySample) MatchesHandle ¶
func (s EntitySample) MatchesHandle(handle uint32) bool
MatchesHandle reports whether a valid full handle names this exact snapshot entity incarnation, including its serial rather than only its index.
type Event ¶
type Event struct {
SchemaVersion int `json:"schema_version"`
Type EventType `json:"type"`
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
Entity int32 `json:"entity"`
EntitySerial int32 `json:"entity_serial,omitempty"`
PlayerSlot int32 `json:"player_slot"`
// OwnedItems is the player item set at event time when attribution is
// available.
OwnedItems []uint32 `json:"owned_items,omitempty"`
Damage *DamageEvent `json:"damage,omitempty"`
Modifier *ModifierEvent `json:"modifier,omitempty"`
Purchase *PurchaseEvent `json:"purchase,omitempty"`
EntitySample *EntitySample `json:"entity_sample,omitempty"`
DamageSummary *DamageSummaryEvent `json:"damage_summary,omitempty"`
PostMatch *PostMatchEvent `json:"post_match,omitempty"`
ControllerSample *ControllerSample `json:"controller_sample,omitempty"`
KillStreak *KillStreakEvent `json:"kill_streak,omitempty"`
StaminaConsumed *StaminaConsumedEvent `json:"stamina_consumed,omitempty"`
ImportantAbilityUsed *ImportantAbilityUsedEvent `json:"important_ability_used,omitempty"`
AbilityNotify *AbilityNotifyEvent `json:"ability_notify,omitempty"`
JumpState *JumpStateEvent `json:"jump_state,omitempty"`
AbilityCharges *AbilityChargesEvent `json:"ability_charges,omitempty"`
AbilityCast *AbilityCastEvent `json:"ability_cast,omitempty"`
Objective *ObjectiveEvent `json:"objective_event,omitempty"`
}
Event is the unified typed stream used by downstream Deadlock analysis.
func (*Event) EmitProto ¶
EmitProto marshals the event as a length-delimited protobuf frame appended to dst.
func (*Event) ToProto ¶
func (e *Event) ToProto() *protocol.ReplayEvent
ToProto converts the unified event to its framed binary representation.
type EventType ¶
type EventType string
EventType identifies a record in the unified replay event stream.
const ( // EventSchemaVersion is the schema version emitted with each replay event. EventSchemaVersion = 1 // EventDamage identifies a damage event. EventDamage EventType = "damage" // EventModifier identifies a modifier lifecycle event. EventModifier EventType = "modifier" // EventPurchase identifies an item or ability ownership transition. EventPurchase EventType = "purchase" // EventEntitySample identifies a sampled entity state. EventEntitySample EventType = "entity_sample" // EventDamageSummary identifies an engine-compiled recent-damage summary. EventDamageSummary EventType = "damage_summary" // EventPostMatch identifies the engine-compiled post-match summary. EventPostMatch EventType = "post_match" // EventControllerSample identifies a player controller scoreboard sample. EventControllerSample EventType = "controller_sample" // EventKillStreak identifies a kill streak update from the user message // stream. EventKillStreak EventType = "kill_streak" // EventStaminaConsumed identifies a stamina spend from the user message // stream. EventStaminaConsumed EventType = "stamina_consumed" // EventImportantAbilityUsed identifies the game-authored ability-use // notification from the user message stream. EventImportantAbilityUsed EventType = "important_ability_used" // EventAbilityNotify identifies the game-authored ability contact // notification from the user message stream. EventAbilityNotify EventType = "ability_notify" // EventJumpState identifies an observed jump-ability state transition. EventJumpState EventType = "jump_state" // EventAbilityCharges identifies an ability charge-count change from the // entity stream. EventAbilityCharges EventType = "ability_charges" // EventTrooperSample identifies a throttled lane trooper state sample; it // carries an EntitySample. EventTrooperSample EventType = "trooper_sample" // EventAbilityCast identifies an ability or item use, observed as a new // cooldown start on the ability entity. EventAbilityCast EventType = "ability_cast" // EventObjective identifies a map objective lifecycle observation. EventObjective EventType = "objective_event" )
type ImportantAbilityUsedEvent ¶
type ImportantAbilityUsedEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
Player uint32 `json:"player"`
Caster uint32 `json:"caster"`
AbilityName string `json:"ability_name"`
HasPlayer bool `json:"has_player"`
HasCaster bool `json:"has_caster"`
HasAbilityName bool `json:"has_ability_name"`
}
ImportantAbilityUsedEvent is one game-authored important ability-use notification. Player and Caster retain the message's complete entity handles.
type JumpStateEvent ¶
type JumpStateEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
ClassName string `json:"class_name"`
InitialObservation bool `json:"initial_observation"`
Jumped bool `json:"jumped"`
DesiredAirJumpCount int32 `json:"desired_air_jump_count"`
ExecutedAirJumpCount int32 `json:"executed_air_jump_count"`
ConsecutiveAirJumps int32 `json:"consecutive_air_jumps"`
ConsecutiveWallJumps int32 `json:"consecutive_wall_jumps"`
CanDashJump bool `json:"can_dash_jump"`
InSlideJump bool `json:"in_slide_jump"`
HasJumped bool `json:"has_jumped"`
HasDesiredAirJumpCount bool `json:"has_desired_air_jump_count"`
HasExecutedAirJumpCount bool `json:"has_executed_air_jump_count"`
HasConsecutiveAirJumps bool `json:"has_consecutive_air_jumps"`
HasConsecutiveWallJumps bool `json:"has_consecutive_wall_jumps"`
HasCanDashJump bool `json:"has_can_dash_jump"`
HasInSlideJump bool `json:"has_in_slide_jump"`
ChangedJumped bool `json:"changed_jumped"`
ChangedDesiredAirJumpCount bool `json:"changed_desired_air_jump_count"`
ChangedExecutedAirJumpCount bool `json:"changed_executed_air_jump_count"`
ChangedConsecutiveAirJumps bool `json:"changed_consecutive_air_jumps"`
ChangedConsecutiveWallJumps bool `json:"changed_consecutive_wall_jumps"`
ChangedCanDashJump bool `json:"changed_can_dash_jump"`
ChangedInSlideJump bool `json:"changed_in_slide_jump"`
}
JumpStateEvent is one initial observation or changed sample from CCitadel_Ability_Jump. Field names retain the measured game-state semantics.
type KillStreakEvent ¶
type KillStreakEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
PlayerPawn uint32 `json:"player_pawn"`
NumKills int32 `json:"num_kills"`
IsFirstBlood bool `json:"is_first_blood"`
StreakEnded bool `json:"streak_ended"`
Duration float32 `json:"duration"`
}
KillStreakEvent is one kill streak update: a player pawn reached num_kills consecutive kills. First blood and streak ends are flagged.
type Message ¶
type Message struct {
Kind int32
Name string
Tick uint32
GameTime float64
Payload decodedProto
// contains filtered or unexported fields
}
Message is a decoded packet or user message with demo tick and game time.
func (*Message) DamageEvent ¶
func (m *Message) DamageEvent() (DamageEvent, bool)
DamageEvent returns the full-context damage projection for this message.
type ModifierEvent ¶
type ModifierEvent struct {
// PayloadProto is a serialized CModifierTableEntry after same-instance merging.
// JSON base64 preserves unknown fields and source float bits without interpretation.
PayloadProto []byte `json:"payload_proto,omitempty"`
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
Transition ModifierTransition `json:"transition"`
TableIndex int32 `json:"table_index"`
Parent uint32 `json:"parent"`
HasSerialNumber bool `json:"has_serial_number"`
SerialNumber uint32 `json:"serial_number"`
ModifierSubclass uint32 `json:"modifier_subclass"`
StackCount int32 `json:"stack_count"`
MaxStackCount int32 `json:"max_stack_count"`
HasLastAppliedTime bool `json:"has_last_applied_time"`
LastAppliedTime float32 `json:"last_applied_time"`
HasDuration bool `json:"has_duration"`
Duration float32 `json:"duration"`
Caster uint32 `json:"caster"`
Ability uint32 `json:"ability"`
AuraProviderSerialNumber int32 `json:"aura_provider_serial_number"`
AuraProviderEHandle uint32 `json:"aura_provider_ehandle"`
AbilitySubclass uint32 `json:"ability_subclass"`
InAuraRange bool `json:"in_aura_range"`
MatchedPrior bool `json:"matched_prior"`
}
ModifierEvent records an instance transition and the last observed payload. Presence distinguishes unknown serial/timing from explicit zero or indefinite duration.
type ModifierTransition ¶
type ModifierTransition string
const ( ModifierAdd ModifierTransition = "add" ModifierRemove ModifierTransition = "remove" ModifierRefresh ModifierTransition = "refresh" )
type ObjectiveEvent ¶
type ObjectiveEvent struct {
Kind string `json:"kind"`
ObjectiveTeam int32 `json:"objective_team"`
ObjectiveID int32 `json:"objective_id"`
KillingTeam int32 `json:"killing_team"`
EntityType int32 `json:"entity_type"`
BossesRemaining int32 `json:"bosses_remaining"`
GameTimeF float32 `json:"game_time_f"`
}
ObjectiveEvent is one map-objective lifecycle observation: mid boss spawn, boss damage, boss kill, or rejuvenator pickup.
type Parser ¶
type Parser struct {
// contains filtered or unexported fields
}
Parser walks a Source 2 PBDEMS2 demo container. It validates the header, yields the outer command stream one record at a time, and owns the Clock. Packet unpacking, message dispatch, and entity decoding layer on top of this container.
func NewParser ¶
NewParser validates the PBDEMS2 header and returns a Parser positioned at the first command. It reads demo as a stream; see NewParserFromReader.
func NewParserFromReader ¶
NewParserFromReader validates the PBDEMS2 header and returns a Parser that reads the command stream from src one command at a time, so memory use does not grow with the size of the replay. The parser consumes src until it reports io.EOF and does not close it.
func NewParserWithContext ¶
NewParserWithContext constructs a parser whose command reads stop on context cancellation. It retains ctx for its lifetime. A command already being decoded finishes before the next read observes cancellation.
func (*Parser) CollectDamage ¶
func (p *Parser) CollectDamage(limit int) ([]DamageEvent, error)
CollectDamage reads up to limit damage events. A non-positive limit reads the whole demo.
func (*Parser) CollectEvents ¶
CollectEvents reads up to limit unified events. A non-positive limit reads the whole demo.
func (*Parser) CollectModifierEvents ¶
func (p *Parser) CollectModifierEvents(limit int) ([]ModifierEvent, error)
func (*Parser) FindEntity ¶
FindEntity returns the current parser state for an entity index, or nil when absent.
func (*Parser) FindEntityByHandle ¶
FindEntityByHandle returns the current entity for a Source 2 entity handle.
func (*Parser) Next ¶
Next reads the next outer command, decompressing its payload when the compression bit is set, and advances the clock. It returns io.EOF once the stream is exhausted or after Stop. A context-bound parser returns its context error before reading another command when canceled.
func (*Parser) NextDamage ¶
func (p *Parser) NextDamage() (DamageEvent, error)
NextDamage returns the next decoded Deadlock damage event.
func (*Parser) NextEntitySample ¶
func (p *Parser) NextEntitySample() (EntitySample, error)
NextEntitySample returns the next typed hero/entity sample from packet entity updates.
func (*Parser) NextEvent ¶
NextEvent returns the next unified typed event produced while walking the demo stream. Calling it declares an event consumer: entity samples then arrive only as events and are not also queued for NextEntitySample, so the parser holds no sample nobody reads.
func (*Parser) NextMessage ¶
NextMessage returns the next decoded packet or user message. It unwraps DEM_Packet, DEM_SignonPacket, and DEM_FullPacket command payloads, routes inner ids through generated dispatch, and updates the clock from ServerInfo.
func (*Parser) NextModifierEvent ¶
func (p *Parser) NextModifierEvent() (ModifierEvent, error)
func (*Parser) ReleasePendingQueues ¶
func (p *Parser) ReleasePendingQueues()
ReleasePendingQueues releases decoded queues while preserving entity delta state.
func (*Parser) SampleEntity ¶
func (p *Parser) SampleEntity(e *Entity) (EntitySample, bool)
SampleEntity samples e at the parser's current tick, with the same fields and world coordinates as an entity sample event. Event streams emit samples only for heroes; this reads any entity, such as an objective, on demand. It reports false when e is nil, inactive, or has no class.
func (*Parser) ServerWorld ¶
ServerWorld identifies the active root world at the current parser tick. A loaded root world supersedes the server's bootstrap map. Multiple distinct non-bootstrap roots are ambiguous and return an empty map name.
func (*Parser) SetEventMode ¶
SetEventMode configures unified event consumption without retaining duplicate samples.
func (*Parser) TickAtMatchClock ¶
TickAtMatchClock advances the parser to the first tick whose match clock reaches seconds and returns it. The parser is consumed: a snapshot of that tick needs a fresh parser. A replay that ends first returns ErrMatchClockNotReached.
func (*Parser) WorldEntitySnapshot ¶
func (p *Parser) WorldEntitySnapshot(tick uint32) ([]EntitySample, error)
WorldEntitySnapshot advances the parser through tick and samples every active entity once. A request older than the parser position is refused. It includes ability, projectile, and other ephemeral entities still active at the boundary; entities deleted before the boundary are absent. It consumes the parser and does not retain event records.
type PostMatchEvent ¶
type PostMatchEvent struct {
MatchID uint64 `json:"match_id"`
DurationS uint32 `json:"duration_s"`
MatchOutcome int32 `json:"match_outcome"`
WinningTeam int32 `json:"winning_team"`
GameMode int32 `json:"game_mode"`
MatchMode int32 `json:"match_mode"`
Players []PostMatchPlayer `json:"players"`
Objectives []PostMatchObjective `json:"objectives"`
}
PostMatchEvent is the engine-compiled post-match summary embedded in the demo. It is the authoritative record for final builds, buy and sell times, and scoreboard totals.
type PostMatchObjective ¶
type PostMatchObjective struct {
LegacyObjectiveID int32 `json:"legacy_objective_id"`
TeamObjectiveID int32 `json:"team_objective_id"`
Team int32 `json:"team"`
DestroyedTimeS uint32 `json:"destroyed_time_s"`
FirstDamageTimeS uint32 `json:"first_damage_time_s"`
CreepDamage uint32 `json:"creep_damage"`
CreepDamageMitigated uint32 `json:"creep_damage_mitigated"`
PlayerDamage uint32 `json:"player_damage"`
PlayerDamageMitigated uint32 `json:"player_damage_mitigated"`
PlayerSpiritDamage uint32 `json:"player_spirit_damage"`
}
PostMatchObjective is one map objective in the post-match record.
type PostMatchPlayer ¶
type PostMatchPlayer struct {
AccountID uint32 `json:"account_id"`
PlayerSlot uint32 `json:"player_slot"`
Team int32 `json:"team"`
Kills uint32 `json:"kills"`
Deaths uint32 `json:"deaths"`
Assists uint32 `json:"assists"`
NetWorth uint32 `json:"net_worth"`
HeroID uint32 `json:"hero_id"`
LastHits uint32 `json:"last_hits"`
Denies uint32 `json:"denies"`
AbilityPoints uint32 `json:"ability_points"`
Level uint32 `json:"level"`
Items []PostMatchPlayerItem `json:"items"`
Stats []PostMatchPlayerStat `json:"stats"`
}
PostMatchPlayer is one player in the engine-compiled post-match summary.
type PostMatchPlayerItem ¶
type PostMatchPlayerItem struct {
ItemID uint32 `json:"item_id"`
GameTimeS uint32 `json:"game_time_s"`
SoldTimeS uint32 `json:"sold_time_s"`
UpgradeID uint32 `json:"upgrade_id"`
Flags uint32 `json:"flags"`
ImbuedAbilityID uint32 `json:"imbued_ability_id"`
UpgradeInfo uint32 `json:"upgrade_info"`
}
PostMatchPlayerItem is one item or ability purchase in the post-match record, with buy and sell times.
type PostMatchPlayerStat ¶
type PostMatchPlayerStat struct {
TimeStampS uint32 `json:"time_stamp_s"`
NetWorth uint32 `json:"net_worth"`
Kills uint32 `json:"kills"`
Deaths uint32 `json:"deaths"`
Assists uint32 `json:"assists"`
Level uint32 `json:"level"`
LastHits uint32 `json:"last_hits"`
Denies uint32 `json:"denies"`
PlayerDamage uint32 `json:"player_damage"`
PlayerDamageTaken uint32 `json:"player_damage_taken"`
PlayerHealing uint32 `json:"player_healing"`
CreepDamage uint32 `json:"creep_damage"`
NeutralDamage uint32 `json:"neutral_damage"`
BossDamage uint32 `json:"boss_damage"`
DamageAbsorbed uint32 `json:"damage_absorbed"`
DamageMitigated uint32 `json:"damage_mitigated"`
ShotsHit uint32 `json:"shots_hit"`
ShotsMissed uint32 `json:"shots_missed"`
WeaponPower uint32 `json:"weapon_power"`
TechPower uint32 `json:"tech_power"`
}
PostMatchPlayerStat is one timed snapshot of a player's scoreboard state.
type PurchaseEvent ¶
type PurchaseEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
PlayerSlot int32 `json:"player_slot"`
UserID int32 `json:"user_id"`
AbilityID uint32 `json:"ability_id"`
Change string `json:"change"`
Sell bool `json:"sell"`
Quickbuy bool `json:"quickbuy"`
Source string `json:"source"`
}
PurchaseEvent is an item/ability ownership transition observed in the user message stream.
type StaminaConsumedEvent ¶
type StaminaConsumedEvent struct {
Tick uint32 `json:"tick"`
GameTime float64 `json:"game_time"`
EntindexTarget int32 `json:"entindex_target"`
StaminaBefore float32 `json:"stamina_before"`
StaminaAfter float32 `json:"stamina_after"`
Drained bool `json:"drained"`
StaminaMax float32 `json:"stamina_max"`
MessageGameTime float32 `json:"message_game_time"`
HasEntindexTarget bool `json:"has_entindex_target"`
HasStaminaBefore bool `json:"has_stamina_before"`
HasStaminaAfter bool `json:"has_stamina_after"`
HasDrained bool `json:"has_drained"`
HasStaminaMax bool `json:"has_stamina_max"`
HasMessageGameTime bool `json:"has_message_game_time"`
}
StaminaConsumedEvent is one stamina spend observed in the user message stream. Before/After/Max are the player's stamina bars at the spend.
type WorldEntitySampleError ¶
type WorldEntitySampleError struct {
// EntityID identifies the replay entity with malformed evidence.
EntityID int32
// EntitySerial distinguishes generations that reuse EntityID.
EntitySerial int32
// Field names the non-finite sample field.
Field string
}
WorldEntitySampleError reports malformed direct entity evidence.
func (*WorldEntitySampleError) Error ¶
func (e *WorldEntitySampleError) Error() string
Error identifies the malformed field; entity identity remains in the record.
type WorldSnapshotError ¶
type WorldSnapshotError struct {
// RequestedTick is the snapshot tick requested by the caller.
RequestedTick uint32
// FinalTick is the last tick observed before the replay ended.
FinalTick uint32
}
WorldSnapshotError reports that a requested snapshot tick was not observed.
func (*WorldSnapshotError) Error ¶
func (e *WorldSnapshotError) Error() string
Error describes the missing snapshot without changing its structured evidence.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package analysis derives replay-local timelines from s2replay events.
|
Package analysis derives replay-local timelines from s2replay events. |
|
cmd
|
|
|
runback-facts
command
Command runback-facts extracts the replay state at one exact tick, with parser provenance, and writes it as JSON on stdout.
|
Command runback-facts extracts the replay state at one exact tick, with parser provenance, and writes it as JSON on stdout. |
|
s2replay
command
Command s2replay parses Source 2 / Deadlock replays.
|
Command s2replay parses Source 2 / Deadlock replays. |
|
generator
|
|
|
dispatchgen
command
|
|
|
scripts
|
|
|
calibration
command
Command calibration extracts a pro-calibration movement dataset from s2replay analyze JSON dumps.
|
Command calibration extracts a pro-calibration movement dataset from s2replay analyze JSON dumps. |
|
parserdigest
command
Command parserdigest computes the canonical parser source digest.
|
Command parserdigest computes the canonical parser source digest. |