Skip to content

Commit cf7da23

Browse files
committed
implement fallback to CSVCMsg_ServerInfo for tick rate info (#184)
1 parent a9c672c commit cf7da23

File tree

10 files changed

+174
-22
lines changed

10 files changed

+174
-22
lines changed

common/common.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ type DemoHeader struct {
3636

3737
// FrameRate returns the frame rate of the demo (frames / demo-ticks per second).
3838
// Not necessarily the tick-rate the server ran on during the game.
39+
//
40+
// Returns 0 if PlaybackTime or PlaybackFrames are 0 (corrupt demo headers).
3941
func (h DemoHeader) FrameRate() float64 {
42+
if h.PlaybackTime == 0 {
43+
return 0
44+
}
45+
4046
return float64(h.PlaybackFrames) / h.PlaybackTime.Seconds()
4147
}
4248

@@ -47,17 +53,28 @@ func (h DemoHeader) FrameTime() time.Duration {
4753
if h.PlaybackFrames == 0 {
4854
return 0
4955
}
56+
5057
return time.Duration(h.PlaybackTime.Nanoseconds() / int64(h.PlaybackFrames))
5158
}
5259

5360
// TickRate returns the tick-rate the server ran on during the game.
61+
// Deprecated: this function might return 0 in some cases (corrupt demo headers), use Parser.TickRate() instead.
5462
// VolvoPlx128TixKTnxBye
5563
func (h DemoHeader) TickRate() float64 {
64+
if h.PlaybackTime == 0 {
65+
return 0
66+
}
67+
5668
return float64(h.PlaybackTicks) / h.PlaybackTime.Seconds()
5769
}
5870

5971
// TickTime returns the time a single tick takes in seconds.
72+
// Deprecated: this function might return 0 in some cases (corrupt demo headers), use Parser.TickTime() instead.
6073
func (h DemoHeader) TickTime() time.Duration {
74+
if h.PlaybackTicks == 0 {
75+
return 0
76+
}
77+
6178
return time.Duration(h.PlaybackTime.Nanoseconds() / int64(h.PlaybackTicks))
6279
}
6380

common/common_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,22 @@ func TestDemoHeader(t *testing.T) {
4242
assert.Equal(t, time.Second/128, header.TickTime(), "TickTime should be 1/128")
4343
}
4444

45+
func TestDemoHeader_FrameRate_PlaybackTime_Zero(t *testing.T) {
46+
assert.Zero(t, DemoHeader{}.FrameRate())
47+
}
48+
4549
func TestDemoHeader_FrameTime_PlaybackFrames_Zero(t *testing.T) {
4650
assert.Zero(t, DemoHeader{}.FrameTime())
4751
}
4852

53+
func TestDemoHeader_TickRate_PlaybackTicks_Zero(t *testing.T) {
54+
assert.Zero(t, DemoHeader{}.TickTime())
55+
}
56+
57+
func TestDemoHeader_TickTime_PlaybackTime_Zero(t *testing.T) {
58+
assert.Zero(t, DemoHeader{}.TickRate())
59+
}
60+
4961
func TestTeamState_Team(t *testing.T) {
5062
assert.Equal(t, TeamTerrorists, NewTeamState(TeamTerrorists, nil).Team())
5163
assert.Equal(t, TeamCounterTerrorists, NewTeamState(TeamCounterTerrorists, nil).Team())
@@ -79,6 +91,26 @@ func TestTeamState_FreezeTimeEndEquipmentValue(t *testing.T) {
7991
assert.Equal(t, 300, state.FreezeTimeEndEquipmentValue())
8092
}
8193

94+
func TestTeamState_CashSpentThisRound(t *testing.T) {
95+
members := []*Player{
96+
{AdditionalPlayerInformation: &AdditionalPlayerInformation{CashSpentThisRound: 100}},
97+
{AdditionalPlayerInformation: &AdditionalPlayerInformation{CashSpentThisRound: 200}},
98+
}
99+
state := NewTeamState(TeamTerrorists, func(Team) []*Player { return members })
100+
101+
assert.Equal(t, 300, state.CashSpentThisRound())
102+
}
103+
104+
func TestTeamState_CashSpentTotal(t *testing.T) {
105+
members := []*Player{
106+
{AdditionalPlayerInformation: &AdditionalPlayerInformation{TotalCashSpent: 100}},
107+
{AdditionalPlayerInformation: &AdditionalPlayerInformation{TotalCashSpent: 200}},
108+
}
109+
state := NewTeamState(TeamTerrorists, func(Team) []*Player { return members })
110+
111+
assert.Equal(t, 300, state.CashSpentTotal())
112+
}
113+
82114
type demoInfoProviderMock struct {
83115
tickRate float64
84116
ingameTick int

common/player.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ func (p *Player) flashDurationTimeFull() time.Duration {
103103
// It takes into consideration FlashDuration, FlashTick, DemoHeader.TickRate() and GameState.IngameTick().
104104
func (p *Player) FlashDurationTimeRemaining() time.Duration {
105105
// In case the demo header is broken
106-
// TODO: read tickRate from CVARs as fallback
107106
tickRate := p.demoInfoProvider.TickRate()
108107
if tickRate == 0 {
109108
return p.flashDurationTimeFull()

convars.go

Lines changed: 0 additions & 18 deletions
This file was deleted.

fake/parser.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ func (p *Parser) CurrentTime() time.Duration {
110110
return p.Called().Get(0).(time.Duration)
111111
}
112112

113+
// TickRate is a mock-implementation of IParser.TickRate().
114+
func (p *Parser) TickRate() float64 {
115+
return p.Called().Get(0).(float64)
116+
}
117+
118+
// TickTime is a mock-implementation of IParser.TickTime().
119+
func (p *Parser) TickTime() time.Duration {
120+
return p.Called().Get(0).(time.Duration)
121+
}
122+
113123
// Progress is a mock-implementation of IParser.Progress().
114124
func (p *Parser) Progress() float32 {
115125
return p.Called().Get(0).(float32)

entities.go renamed to net_messages.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55

66
bit "github.com/markus-wa/demoinfocs-golang/bitread"
7+
"github.com/markus-wa/demoinfocs-golang/events"
78
"github.com/markus-wa/demoinfocs-golang/msg"
89
)
910

@@ -50,3 +51,20 @@ func (p *Parser) handlePacketEntities(pe *msg.CSVCMsg_PacketEntities) {
5051
}
5152
r.Pool()
5253
}
54+
55+
func (p *Parser) handleSetConVar(setConVar *msg.CNETMsg_SetConVar) {
56+
updated := make(map[string]string)
57+
for _, cvar := range setConVar.Convars.Cvars {
58+
updated[cvar.Name] = cvar.Value
59+
p.gameState.conVars[cvar.Name] = cvar.Value
60+
}
61+
62+
p.eventDispatcher.Dispatch(events.ConVarsUpdated{
63+
UpdatedConVars: updated,
64+
})
65+
}
66+
67+
func (p *Parser) handleServerInfo(srvInfo *msg.CSVCMsg_ServerInfo) {
68+
// srvInfo.MapCrc might be interesting as well
69+
p.tickInterval = srvInfo.TickInterval
70+
}

parser.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type Parser struct {
5252
userMessageHandler userMessageHandler
5353
eventDispatcher dp.Dispatcher
5454
currentFrame int // Demo-frame, not ingame-tick
55+
tickInterval float32 // Duration between ticks in seconds
5556
header *common.DemoHeader // Pointer so we can check for nil
5657
gameState *GameState
5758
demoInfoProvider demoInfoProvider // Provides demo infos to other packages that the core package depends on
@@ -122,14 +123,37 @@ func (p *Parser) CurrentFrame() int {
122123

123124
// CurrentTime returns the time elapsed since the start of the demo
124125
func (p *Parser) CurrentTime() time.Duration {
125-
return time.Duration(p.currentFrame) * p.header.FrameTime()
126+
return time.Duration(float32(p.gameState.ingameTick) * p.tickInterval * float32(time.Second))
127+
}
128+
129+
// TickRate returns the tick-rate the server ran on during the game.
130+
func (p *Parser) TickRate() float64 {
131+
if p.tickInterval != 0 {
132+
return 1.0 / float64(p.tickInterval)
133+
}
134+
135+
return p.header.TickRate()
136+
}
137+
138+
// TickTime returns the time a single tick takes in seconds.
139+
func (p *Parser) TickTime() time.Duration {
140+
if p.tickInterval != 0 {
141+
return time.Duration(float32(time.Second) * p.tickInterval)
142+
}
143+
144+
return p.header.TickTime()
126145
}
127146

128147
// Progress returns the parsing progress from 0 to 1.
129148
// Where 0 means nothing has been parsed yet and 1 means the demo has been parsed to the end.
130149
//
131150
// Might not be 100% correct since it's just based on the reported tick count of the header.
151+
// May always return 0 if the demo header is corrupt.
132152
func (p *Parser) Progress() float32 {
153+
if p.header == nil || p.header.PlaybackFrames == 0 {
154+
return 0
155+
}
156+
133157
return float32(p.currentFrame) / float32(p.header.PlaybackFrames)
134158
}
135159

@@ -255,6 +279,7 @@ func NewParserWithConfig(demostream io.Reader, config ParserConfig) *Parser {
255279
p.msgDispatcher.RegisterHandler(p.handleUserMessage)
256280
p.msgDispatcher.RegisterHandler(p.handleSetConVar)
257281
p.msgDispatcher.RegisterHandler(p.handleFrameParsed)
282+
p.msgDispatcher.RegisterHandler(p.handleServerInfo)
258283
p.msgDispatcher.RegisterHandler(p.gameState.handleIngameTickNumber)
259284

260285
if config.MsgQueueBufferSize >= 0 {
@@ -280,8 +305,7 @@ func (p demoInfoProvider) IngameTick() int {
280305
}
281306

282307
func (p demoInfoProvider) TickRate() float64 {
283-
// TODO: read tickRate from CVARs as fallback
284-
return p.parser.header.TickRate()
308+
return p.parser.TickRate()
285309
}
286310

287311
func (p demoInfoProvider) FindPlayerByHandle(handle int) *common.Player {

parser_interface.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,15 @@ type IParser interface {
4747
CurrentFrame() int
4848
// CurrentTime returns the time elapsed since the start of the demo
4949
CurrentTime() time.Duration
50+
// TickRate returns the tick-rate the server ran on during the game.
51+
TickRate() float64
52+
// TickTime returns the time a single tick takes in seconds.
53+
TickTime() time.Duration
5054
// Progress returns the parsing progress from 0 to 1.
5155
// Where 0 means nothing has been parsed yet and 1 means the demo has been parsed to the end.
5256
//
5357
// Might not be 100% correct since it's just based on the reported tick count of the header.
58+
// May always return 0 if the demo header is corrupt.
5459
Progress() float32
5560
/*
5661
RegisterEventHandler registers a handler for game events.

parser_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package demoinfocs
2+
3+
import (
4+
"math"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/assert"
9+
10+
"github.com/markus-wa/demoinfocs-golang/common"
11+
)
12+
13+
func TestParser_CurrentFrame(t *testing.T) {
14+
assert.Equal(t, 1, (&Parser{currentFrame: 1}).CurrentFrame())
15+
}
16+
17+
func TestParser_GameState(t *testing.T) {
18+
gs := new(GameState)
19+
assert.Equal(t, gs, (&Parser{gameState: gs}).GameState())
20+
}
21+
22+
func TestParser_CurrentTime(t *testing.T) {
23+
p := &Parser{
24+
tickInterval: 2,
25+
gameState: &GameState{ingameTick: 3},
26+
}
27+
28+
assert.Equal(t, 6*time.Second, p.CurrentTime())
29+
}
30+
31+
func TestParser_TickRate(t *testing.T) {
32+
assert.Equal(t, float64(5), math.Round((&Parser{tickInterval: 0.2}).TickRate()))
33+
}
34+
35+
func TestParser_TickRate_FallbackToHeader(t *testing.T) {
36+
p := &Parser{
37+
header: &common.DemoHeader{
38+
PlaybackTime: time.Second,
39+
PlaybackTicks: 5,
40+
},
41+
}
42+
43+
assert.Equal(t, float64(5), p.TickRate())
44+
}
45+
46+
func TestParser_TickTime(t *testing.T) {
47+
assert.Equal(t, time.Duration(200)*time.Millisecond, (&Parser{tickInterval: 0.2}).TickTime())
48+
}
49+
50+
func TestParser_TickTime_FallbackToHeader(t *testing.T) {
51+
p := &Parser{
52+
header: &common.DemoHeader{
53+
PlaybackTime: time.Second,
54+
PlaybackTicks: 5,
55+
},
56+
}
57+
58+
assert.Equal(t, time.Duration(200)*time.Millisecond, p.TickTime())
59+
}
60+
61+
func TestParser_Progress_NoHeader(t *testing.T) {
62+
assert.Zero(t, new(Parser).Progress())
63+
assert.Zero(t, (&Parser{header: &common.DemoHeader{}}).Progress())
64+
}

parsing.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ var defaultNetMessageCreators = map[int]NetMessageCreator{
262262
int(msg.SVC_Messages_svc_CreateStringTable): func() proto.Message { return new(msg.CSVCMsg_CreateStringTable) },
263263
int(msg.SVC_Messages_svc_UpdateStringTable): func() proto.Message { return new(msg.CSVCMsg_UpdateStringTable) },
264264
int(msg.SVC_Messages_svc_UserMessage): func() proto.Message { return new(msg.CSVCMsg_UserMessage) },
265+
int(msg.SVC_Messages_svc_ServerInfo): func() proto.Message { return new(msg.CSVCMsg_ServerInfo) },
265266
int(msg.NET_Messages_net_SetConVar): func() proto.Message { return new(msg.CNETMsg_SetConVar) },
266267
}
267268

0 commit comments

Comments
 (0)