Skip to content

Commit d8e1150

Browse files
authored
Merge pull request #470 from markus-wa/cs2-vel-2
feat: add player velocity
2 parents 0c77732 + f88e6d5 commit d8e1150

File tree

5 files changed

+82
-22
lines changed

5 files changed

+82
-22
lines changed

pkg/demoinfocs/common/common_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,14 @@ type demoInfoProviderMock struct {
213213
tickRate float64
214214
ingameTick int
215215
playersByHandle map[int]*Player
216+
entitiesByHandle map[uint64]st.Entity
216217
playerResourceEntity st.Entity
217218
equipment *Equipment
218219
isSource2 bool
219220
}
220221

221222
func (p demoInfoProviderMock) FindEntityByHandle(handle uint64) st.Entity {
222-
panic("implement me")
223+
return p.entitiesByHandle[handle]
223224
}
224225

225226
func (p demoInfoProviderMock) IsSource2() bool {

pkg/demoinfocs/common/player.go

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,25 @@ import (
1515
type Player struct {
1616
demoInfoProvider demoInfoProvider // provider for demo info such as tick-rate or current tick
1717

18-
SteamID64 uint64 // 64-bit representation of the user's Steam ID. See https://developer.valvesoftware.com/wiki/SteamID
19-
LastAlivePosition r3.Vector // The location where the player was last alive. Should be equal to Position if the player is still alive.
20-
UserID int // Mostly used in game-events to address this player
21-
Name string // Steam / in-game user name
22-
Inventory map[int]*Equipment // All weapons / equipment the player is currently carrying. See also Weapons().
23-
AmmoLeft [32]int // Ammo left for special weapons (e.g. grenades), index corresponds Equipment.AmmoType
24-
EntityID int // Usually the same as Entity.ID() but may be different between player death and re-spawn.
25-
Entity st.Entity // May be nil between player-death and re-spawn
26-
FlashDuration float32 // Blindness duration from the flashbang currently affecting the player (seconds)
27-
FlashTick int // In-game tick at which the player was last flashed
28-
TeamState *TeamState // When keeping the reference make sure you notice when the player changes teams
29-
Team Team // Team identifier for the player (e.g. TeamTerrorists or TeamCounterTerrorists).
30-
IsBot bool // True if this is a bot-entity. See also IsControllingBot and ControlledBot().
31-
IsConnected bool
32-
IsDefusing bool
33-
IsPlanting bool
34-
IsReloading bool
35-
IsUnknown bool // Used to identify unknown/broken players. see https://github.com/markus-wa/demoinfocs-golang/issues/162
18+
SteamID64 uint64 // 64-bit representation of the user's Steam ID. See https://developer.valvesoftware.com/wiki/SteamID
19+
LastAlivePosition r3.Vector // The location where the player was last alive. Should be equal to Position if the player is still alive.
20+
UserID int // Mostly used in game-events to address this player
21+
Name string // Steam / in-game user name
22+
Inventory map[int]*Equipment // All weapons / equipment the player is currently carrying. See also Weapons().
23+
AmmoLeft [32]int // Ammo left for special weapons (e.g. grenades), index corresponds Equipment.AmmoType
24+
EntityID int // Usually the same as Entity.ID() but may be different between player death and re-spawn.
25+
Entity st.Entity // May be nil between player-death and re-spawn
26+
FlashDuration float32 // Blindness duration from the flashbang currently affecting the player (seconds)
27+
FlashTick int // In-game tick at which the player was last flashed
28+
TeamState *TeamState // When keeping the reference make sure you notice when the player changes teams
29+
Team Team // Team identifier for the player (e.g. TeamTerrorists or TeamCounterTerrorists).
30+
IsBot bool // True if this is a bot-entity. See also IsControllingBot and ControlledBot().
31+
IsConnected bool
32+
IsDefusing bool
33+
IsPlanting bool
34+
IsReloading bool
35+
IsUnknown bool // Used to identify unknown/broken players. see https://github.com/markus-wa/demoinfocs-golang/issues/162
36+
PreviousFramePosition r3.Vector // CS2 only, used to compute velocity as it's not networked in CS2 demos
3637
}
3738

3839
func (p *Player) PlayerPawnEntity() st.Entity {
@@ -521,7 +522,14 @@ func (p *Player) PositionEyes() r3.Vector {
521522
// Velocity returns the player's velocity.
522523
func (p *Player) Velocity() r3.Vector {
523524
if p.demoInfoProvider.IsSource2() {
524-
panic("Velocity() is not supported for Source 2 demos")
525+
t := 64.0
526+
diff := p.Position().Sub(p.PreviousFramePosition)
527+
528+
return r3.Vector{
529+
X: diff.X * t,
530+
Y: diff.Y * t,
531+
Z: diff.Z * t,
532+
}
525533
}
526534

527535
if p.Entity == nil {
@@ -801,8 +809,9 @@ type demoInfoProvider interface {
801809
// Intended for internal use only.
802810
func NewPlayer(demoInfoProvider demoInfoProvider) *Player {
803811
return &Player{
804-
Inventory: make(map[int]*Equipment),
805-
demoInfoProvider: demoInfoProvider,
812+
Inventory: make(map[int]*Equipment),
813+
demoInfoProvider: demoInfoProvider,
814+
PreviousFramePosition: r3.Vector{},
806815
}
807816
}
808817

pkg/demoinfocs/common/player_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,46 @@ func TestPlayer_Velocity(t *testing.T) {
428428
assert.Equal(t, expected, pl.Velocity())
429429
}
430430

431+
func createPlayerForVelocityTest() *Player {
432+
controllerEntity := entityWithProperties([]fakeProp{
433+
{propName: "m_hPlayerPawn", value: st.PropertyValue{Any: uint64(1), S2: true}},
434+
})
435+
pawnEntity := new(stfake.Entity)
436+
position := r3.Vector{X: 20, Y: 300, Z: 100}
437+
438+
pawnEntity.On("Position").Return(position)
439+
440+
pl := &Player{
441+
Entity: controllerEntity,
442+
}
443+
444+
demoInfoProvider := demoInfoProviderMock{
445+
isSource2: true,
446+
entitiesByHandle: map[uint64]st.Entity{
447+
1: pawnEntity,
448+
},
449+
}
450+
pl.demoInfoProvider = demoInfoProvider
451+
452+
return pl
453+
}
454+
455+
func TestPlayer_VelocityS2(t *testing.T) {
456+
pl := createPlayerForVelocityTest()
457+
pl.PreviousFramePosition = r3.Vector{X: 10, Y: 200, Z: 50}
458+
459+
expected := r3.Vector{X: 640, Y: 6400, Z: 3200}
460+
assert.Equal(t, expected, pl.Velocity())
461+
}
462+
463+
func TestPlayer_VelocityDidNotChangeS2(t *testing.T) {
464+
pl := createPlayerForVelocityTest()
465+
pl.PreviousFramePosition = r3.Vector{X: 20, Y: 300, Z: 100}
466+
467+
expected := r3.Vector{X: 0, Y: 0, Z: 0}
468+
assert.Equal(t, expected, pl.Velocity())
469+
}
470+
431471
func TestPlayer_Velocity_EntityNil(t *testing.T) {
432472
pl := new(Player)
433473
pl.demoInfoProvider = s1DemoInfoProvider

pkg/demoinfocs/parsing.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,10 @@ func (p *parser) handleFrameParsed(*frameParsedTokenType) {
525525

526526
p.currentFrame++
527527
p.eventDispatcher.Dispatch(events.FrameDone{})
528+
529+
if p.isSource2() {
530+
p.updatePlayersPreviousFramePosition()
531+
}
528532
}
529533

530534
// CS2 demos playback info are available in the CDemoFileInfo message that should be parsed at the end of the demo.

pkg/demoinfocs/s2_commands.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,9 @@ func (p *parser) handleDemoFileHeader(msg *msgs2.CDemoFileHeader) {
378378
p.header.MapName = msg.GetMapName()
379379
p.header.NetworkProtocol = int(msg.GetNetworkProtocol())
380380
}
381+
382+
func (p *parser) updatePlayersPreviousFramePosition() {
383+
for _, player := range p.GameState().Participants().Playing() {
384+
player.PreviousFramePosition = player.Position()
385+
}
386+
}

0 commit comments

Comments
 (0)