Skip to content

Commit 2a0d19c

Browse files
authored
Merge pull request #227 from markus-wa/pov-demos
full support for POV demos
2 parents 2f08f35 + d8d49cf commit 2a0d19c

File tree

7 files changed

+53
-67
lines changed

7 files changed

+53
-67
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,13 @@ Check out the [examples](examples) folder for more examples, like [how to genera
129129
* Access to all net-messages - [docs](https://pkg.go.dev/github.com/markus-wa/demoinfocs-golang/v2/pkg/demoinfocs?tab=doc#NetMessageCreator) / [example](https://github.com/markus-wa/demoinfocs-golang/tree/master/examples/net-messages)
130130
* Chat & console messages <sup id="achat1">1</sup> - [docs](https://pkg.go.dev/github.com/markus-wa/demoinfocs-golang/v2/pkg/demoinfocs/events?tab=doc#ChatMessage) / [example](https://github.com/markus-wa/demoinfocs-golang/tree/master/examples/print-events)
131131
* Matchmaking ranks (official MM demos only) - [docs](https://pkg.go.dev/github.com/markus-wa/demoinfocs-golang/v2/pkg/demoinfocs/events?tab=doc#RankUpdate)
132-
* POV demo support <sup id="achat1">2</sup>
132+
* Full POV demo support <sup id="achat1">2</sup>
133133
* JavaScript (browser / Node.js) support via WebAssembly - [example](https://github.com/markus-wa/demoinfocs-wasm)
134134
* [Easy debugging via build-flags](#debugging)
135135
* Built with performance & concurrency in mind
136136

137137
1. <small id="f1">Only for some demos; in MM demos the chat is encrypted for example.</small>
138-
2. <small id="f2">Only partially supported (as good as other parsers), some POV demos seem to be inherently broken</small>
138+
2. <small id="f2">Better than some other parsers at the time of writing.</small>
139139

140140
## Performance / Benchmarks
141141

examples/print-events/print_events.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func main() {
3535
if e.PenetratedObjects > 0 {
3636
wallBang = " (WB)"
3737
}
38-
fmt.Printf("%s <%v%s%s> %s\n", formatPlayer(e.Killer), e.Weapon.Type, hs, wallBang, formatPlayer(e.Victim))
38+
fmt.Printf("%s <%v%s%s> %s\n", formatPlayer(e.Killer), e.Weapon, hs, wallBang, formatPlayer(e.Victim))
3939
})
4040

4141
// Register handler on round end to figure out who won
@@ -78,6 +78,7 @@ func formatPlayer(p *common.Player) string {
7878
case common.TeamCounterTerrorists:
7979
return "[CT]" + p.Name
8080
}
81+
8182
return p.Name
8283
}
8384

pkg/demoinfocs/datatables.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -472,26 +472,24 @@ func (p *parser) bindWeapon(entity st.Entity, wepType common.EquipmentType) {
472472
modelIndex := entity.Property("m_nModelIndex").Value().IntVal
473473
eq.OriginalString = p.modelPreCache[modelIndex]
474474

475-
wepFix := func(defaultName, altName string, alt common.EquipmentType) {
475+
wepFix := func(altName string, alt common.EquipmentType) {
476476
// Check 'altName' first because otherwise the m4a1_s is recognized as m4a4
477477
if strings.Contains(eq.OriginalString, altName) {
478478
eq.Type = alt
479-
} else if !strings.Contains(eq.OriginalString, defaultName) {
480-
p.setError(fmt.Errorf("unknown weapon model %q", eq.OriginalString))
481479
}
482480
}
483481

484482
switch eq.Type {
485483
case common.EqP2000:
486-
wepFix("_pist_hkp2000", "_pist_223", common.EqUSP)
484+
wepFix("_pist_223", common.EqUSP)
487485
case common.EqM4A4:
488-
wepFix("_rif_m4a1", "_rif_m4a1_s", common.EqM4A1)
486+
wepFix("_rif_m4a1_s", common.EqM4A1)
489487
case common.EqP250:
490-
wepFix("_pist_p250", "_pist_cz_75", common.EqCZ)
488+
wepFix("_pist_cz_75", common.EqCZ)
491489
case common.EqDeagle:
492-
wepFix("_pist_deagle", "_pist_revolver", common.EqRevolver)
490+
wepFix("_pist_revolver", common.EqRevolver)
493491
case common.EqMP7:
494-
wepFix("_smg_mp7", "_smg_mp5sd", common.EqMP5)
492+
wepFix("_smg_mp5sd", common.EqMP5)
495493
}
496494
}
497495

pkg/demoinfocs/game_events.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ func (p *parser) handleGameEventList(gel *msg.CSVCMsg_GameEventList) {
2222
func (p *parser) handleGameEvent(ge *msg.CSVCMsg_GameEvent) {
2323
if p.gameEventDescs == nil {
2424
p.eventDispatcher.Dispatch(events.ParserWarn{Message: "received GameEvent but event descriptors are missing"})
25-
unassert.Error("received GameEvent but event descriptors are missing")
2625

2726
return
2827
}
@@ -107,6 +106,7 @@ func newGameEventHandler(parser *parser) gameEventHandler {
107106

108107
geh.gameEventNameToHandler = map[string]gameEventHandlerFunc{
109108
// sorted alphabetically
109+
"ammo_pickup": nil, // Dunno, only in locally recorded (POV) demo
110110
"announce_phase_end": nil, // Dunno
111111
"begin_new_match": geh.beginNewMatch, // Match started
112112
"bomb_beep": nil, // Bomb beep
@@ -119,6 +119,7 @@ func newGameEventHandler(parser *parser) gameEventHandler {
119119
"bomb_planted": delayIfNoPlayers(geh.bombPlanted), // Plant finished
120120
"bot_takeover": delay(geh.botTakeover), // Bot got taken over
121121
"buytime_ended": nil, // Not actually end of buy time, seems to only be sent once per game at the start
122+
"cs_intermission": nil, // Dunno, only in locally recorded (POV) demo
122123
"cs_match_end_restart": nil, // Yawn
123124
"cs_pre_restart": nil, // Not sure, doesn't seem to be important
124125
"cs_round_final_beep": nil, // Final beep
@@ -128,24 +129,26 @@ func newGameEventHandler(parser *parser) gameEventHandler {
128129
"decoy_detonate": geh.decoyDetonate, // Decoy exploded/expired
129130
"decoy_started": delay(geh.decoyStarted), // Decoy started. Delayed because projectile entity is not yet created
130131
"endmatch_cmm_start_reveal_items": nil, // Drops
131-
"entity_visible": nil, // Dunno, only in locally recorded demo
132-
"enter_bombzone": nil, // Dunno, only in locally recorded demo
133-
"exit_bombzone": nil, // Dunno, only in locally recorded demo
134-
"enter_buyzone": nil, // Dunno, only in locally recorded demo
135-
"exit_buyzone": nil, // Dunno, only in locally recorded demo
132+
"entity_visible": nil, // Dunno, only in locally recorded (POV) demo
133+
"enter_bombzone": nil, // Dunno, only in locally recorded (POV) demo
134+
"exit_bombzone": nil, // Dunno, only in locally recorded (POV) demo
135+
"enter_buyzone": nil, // Dunno, only in locally recorded (POV) demo
136+
"exit_buyzone": nil, // Dunno, only in locally recorded (POV) demo
136137
"flashbang_detonate": geh.flashBangDetonate, // Flash exploded
137138
"hegrenade_detonate": geh.heGrenadeDetonate, // HE exploded
138139
"hltv_chase": nil, // Don't care
139140
"hltv_fixed": nil, // Dunno
140141
"hltv_message": nil, // No clue
141142
"hltv_status": nil, // Don't know
143+
"hostname_changed": nil, // Only present in locally recorded (POV) demos
142144
"inferno_expire": geh.infernoExpire, // Incendiary expired
143145
"inferno_startburn": delay(geh.infernoStartBurn), // Incendiary exploded/started. Delayed because inferno entity is not yet created
144-
"inspect_weapon": nil, // Dunno, only in locally recorded demo
146+
"inspect_weapon": nil, // Dunno, only in locally recorded (POV) demos
145147
"item_equip": delay(geh.itemEquip), // Equipped / weapon swap, I think. Delayed because of #142 - Bot entity possibly not yet created
146148
"item_pickup": delay(geh.itemPickup), // Picked up or bought? Delayed because of #119 - Equipment.UniqueID()
149+
"item_pickup_slerp": nil, // Not sure, only in locally recorded (POV) demos
147150
"item_remove": geh.itemRemove, // Dropped?
148-
"jointeam_failed": nil, // Dunno, only in locally recorded demo
151+
"jointeam_failed": nil, // Dunno, only in locally recorded (POV) demos
149152
"other_death": nil, // Dunno
150153
"player_blind": delay(geh.playerBlind), // Player got blinded by a flash. Delayed because Player.FlashDuration hasn't been updated yet
151154
"player_changename": nil, // Name change
@@ -158,7 +161,8 @@ func newGameEventHandler(parser *parser) gameEventHandler {
158161
"player_hurt": geh.playerHurt, // Player got hurt
159162
"player_jump": geh.playerJump, // Player jumped
160163
"player_spawn": nil, // Player spawn
161-
"player_given_c4": nil, // Dunno, only present in POV demos
164+
"player_spawned": nil, // Only present in locally recorded (POV) demos
165+
"player_given_c4": nil, // Dunno, only present in locally recorded (POV) demos
162166

163167
// Player changed team. Delayed for two reasons
164168
// - team IDs of other players changing teams in the same tick might not have changed yet
@@ -187,7 +191,7 @@ func newGameEventHandler(parser *parser) gameEventHandler {
187191
"weapon_fire_on_empty": nil, // Sounds boring
188192
"weapon_reload": geh.weaponReload, // Weapon reloaded
189193
"weapon_zoom": nil, // Zooming in
190-
"weapon_zoom_rifle": nil, // Dunno, only in locally recorded demo
194+
"weapon_zoom_rifle": nil, // Dunno, only in locally recorded (POV) demo
191195
}
192196

193197
return geh
@@ -523,7 +527,11 @@ func (geh gameEventHandler) bombPlanted(data map[string]*msg.CSVCMsg_GameEventKe
523527
}
524528

525529
event := events.BombPlanted{BombEvent: bombEvent}
526-
event.Player.IsPlanting = false
530+
531+
if event.Player != nil { // if not nil check is necessary for POV demos
532+
event.Player.IsPlanting = false
533+
}
534+
527535
geh.parser.gameState.currentPlanter = nil
528536
geh.dispatch(event)
529537
}
@@ -681,10 +689,6 @@ func (geh gameEventHandler) getThrownGrenade(p *common.Player, wepType common.Eq
681689
}
682690
}
683691

684-
// smokes might have duplicate smokegrenade_expired events, so it could have already been deleted.
685-
// if it's not a smoke this should never be reached
686-
unassert.Samef(wepType, common.EqSmoke, "tried to get non-existing grenade from gameState.thrownGrenades")
687-
688692
return nil
689693
}
690694

@@ -705,10 +709,6 @@ func (geh gameEventHandler) deleteThrownGrenade(p *common.Player, wepType common
705709
return
706710
}
707711
}
708-
709-
// smokes might have duplicate smokegrenade_expired events, so it might already be deleted.
710-
// besides that this code should never be reached
711-
unassert.Samef(wepType, common.EqSmoke, "trying to delete non-existing grenade from gameState.thrownGrenades")
712712
}
713713

714714
func (geh gameEventHandler) attackerWeaponType(wepType common.EquipmentType, victimUserID int32) common.EquipmentType {

pkg/demoinfocs/game_state.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -276,18 +276,8 @@ func (ptcp participants) FindByHandle(handle int) *common.Player {
276276
}
277277

278278
entityID := handle & constants.EntityHandleIndexMask
279-
player := ptcp.playersByEntityID[entityID]
280-
281-
if player == nil {
282-
for _, p := range ptcp.playersByUserID {
283-
if p.EntityID == entityID {
284-
player = p
285-
break
286-
}
287-
}
288-
}
289279

290-
return player
280+
return ptcp.playersByEntityID[entityID]
291281
}
292282

293283
func (ptcp participants) initializeSliceFromByUserID() ([]*common.Player, map[int]*common.Player) {

pkg/demoinfocs/net_messages.go

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import (
88
msg "github.com/markus-wa/demoinfocs-golang/v2/pkg/demoinfocs/msg"
99
)
1010

11-
const entitySentinel = 9999
12-
1311
func (p *parser) handlePacketEntities(pe *msg.CSVCMsg_PacketEntities) {
1412
defer func() {
1513
p.setError(recoverFromUnexpectedEOF(recover()))
@@ -21,31 +19,30 @@ func (p *parser) handlePacketEntities(pe *msg.CSVCMsg_PacketEntities) {
2119
for i := 0; i < int(pe.UpdatedEntries); i++ {
2220
currentEntity += 1 + int(r.ReadUBitInt())
2321

24-
if currentEntity > entitySentinel {
25-
break
26-
}
27-
28-
if r.ReadBit() {
29-
// Leave PVS
30-
if entity := p.gameState.entities[currentEntity]; entity != nil {
31-
entity.Destroy()
32-
delete(p.gameState.entities, currentEntity)
33-
}
22+
cmd := r.ReadBitsToByte(2)
23+
if cmd&1 == 0 {
24+
if cmd&2 != 0 {
25+
// Enter PVS
26+
if existing := p.gameState.entities[currentEntity]; existing != nil {
27+
// Sometimes entities don't get destroyed when they should be
28+
// For instance when a player is replaced by a BOT
29+
existing.Destroy()
30+
}
3431

35-
// 'Force Delete' flag, not exactly sure what it's supposed to do
36-
r.ReadBit()
37-
} else if r.ReadBit() {
38-
// Enter PVS
39-
if existing := p.gameState.entities[currentEntity]; existing != nil {
40-
// Sometimes entities don't get destroyed when they should be
41-
// For instance when a player is replaced by a BOT
42-
existing.Destroy()
32+
p.gameState.entities[currentEntity] = p.stParser.ReadEnterPVS(r, currentEntity)
33+
} else { //nolint:gocritic
34+
// Delta Update
35+
if entity := p.gameState.entities[currentEntity]; entity != nil {
36+
entity.ApplyUpdate(r)
37+
}
4338
}
44-
p.gameState.entities[currentEntity] = p.stParser.ReadEnterPVS(r, currentEntity)
45-
} else { //nolint:gocritic
46-
// Delta Update
47-
if entity := p.gameState.entities[currentEntity]; entity != nil {
48-
entity.ApplyUpdate(r)
39+
} else {
40+
if cmd&2 != 0 {
41+
// Leave PVS
42+
if entity := p.gameState.entities[currentEntity]; entity != nil {
43+
entity.Destroy()
44+
delete(p.gameState.entities, currentEntity)
45+
}
4946
}
5047
}
5148
}

test/cs-demos

Submodule cs-demos updated from e8d2851 to 87e0ac7

0 commit comments

Comments
 (0)