From 421329cd7b5870bfd438b7687e6e2ef75d4b930a Mon Sep 17 00:00:00 2001 From: AkiVer Date: Sun, 12 Nov 2023 23:20:28 +0100 Subject: [PATCH] fix: incorrect velocity when standing still --- pkg/demoinfocs/common/player.go | 51 +++++++++++++--------------- pkg/demoinfocs/common/player_test.go | 12 ++++--- pkg/demoinfocs/datatables.go | 6 ---- pkg/demoinfocs/parsing.go | 4 +++ pkg/demoinfocs/s2_commands.go | 6 ++++ 5 files changed, 41 insertions(+), 38 deletions(-) diff --git a/pkg/demoinfocs/common/player.go b/pkg/demoinfocs/common/player.go index 5c22bdcf..b7b034ae 100644 --- a/pkg/demoinfocs/common/player.go +++ b/pkg/demoinfocs/common/player.go @@ -15,25 +15,25 @@ import ( type Player struct { demoInfoProvider demoInfoProvider // provider for demo info such as tick-rate or current tick - SteamID64 uint64 // 64-bit representation of the user's Steam ID. See https://developer.valvesoftware.com/wiki/SteamID - LastAlivePosition r3.Vector // The location where the player was last alive. Should be equal to Position if the player is still alive. - UserID int // Mostly used in game-events to address this player - Name string // Steam / in-game user name - Inventory map[int]*Equipment // All weapons / equipment the player is currently carrying. See also Weapons(). - AmmoLeft [32]int // Ammo left for special weapons (e.g. grenades), index corresponds Equipment.AmmoType - EntityID int // Usually the same as Entity.ID() but may be different between player death and re-spawn. - Entity st.Entity // May be nil between player-death and re-spawn - FlashDuration float32 // Blindness duration from the flashbang currently affecting the player (seconds) - FlashTick int // In-game tick at which the player was last flashed - TeamState *TeamState // When keeping the reference make sure you notice when the player changes teams - Team Team // Team identifier for the player (e.g. TeamTerrorists or TeamCounterTerrorists). - IsBot bool // True if this is a bot-entity. See also IsControllingBot and ControlledBot(). - IsConnected bool - IsDefusing bool - IsPlanting bool - IsReloading bool - IsUnknown bool // Used to identify unknown/broken players. see https://github.com/markus-wa/demoinfocs-golang/issues/162 - LastPositions []r3.Vector // CS2 only, used to compute velocity as it's not networked in CS2 demos + SteamID64 uint64 // 64-bit representation of the user's Steam ID. See https://developer.valvesoftware.com/wiki/SteamID + LastAlivePosition r3.Vector // The location where the player was last alive. Should be equal to Position if the player is still alive. + UserID int // Mostly used in game-events to address this player + Name string // Steam / in-game user name + Inventory map[int]*Equipment // All weapons / equipment the player is currently carrying. See also Weapons(). + AmmoLeft [32]int // Ammo left for special weapons (e.g. grenades), index corresponds Equipment.AmmoType + EntityID int // Usually the same as Entity.ID() but may be different between player death and re-spawn. + Entity st.Entity // May be nil between player-death and re-spawn + FlashDuration float32 // Blindness duration from the flashbang currently affecting the player (seconds) + FlashTick int // In-game tick at which the player was last flashed + TeamState *TeamState // When keeping the reference make sure you notice when the player changes teams + Team Team // Team identifier for the player (e.g. TeamTerrorists or TeamCounterTerrorists). + IsBot bool // True if this is a bot-entity. See also IsControllingBot and ControlledBot(). + IsConnected bool + IsDefusing bool + IsPlanting bool + IsReloading bool + IsUnknown bool // Used to identify unknown/broken players. see https://github.com/markus-wa/demoinfocs-golang/issues/162 + PreviousFramePosition r3.Vector // CS2 only, used to compute velocity as it's not networked in CS2 demos } func (p *Player) PlayerPawnEntity() st.Entity { @@ -522,12 +522,8 @@ func (p *Player) PositionEyes() r3.Vector { // Velocity returns the player's velocity. func (p *Player) Velocity() r3.Vector { if p.demoInfoProvider.IsSource2() { - if !p.IsAlive() || len(p.LastPositions) != 2 { - return r3.Vector{} - } - t := 64.0 - diff := p.LastPositions[1].Sub(p.LastPositions[0]) + diff := p.Position().Sub(p.PreviousFramePosition) return r3.Vector{ X: diff.X * t, @@ -813,9 +809,10 @@ type demoInfoProvider interface { // Intended for internal use only. func NewPlayer(demoInfoProvider demoInfoProvider) *Player { return &Player{ - Inventory: make(map[int]*Equipment), - demoInfoProvider: demoInfoProvider, - LastAlivePosition: r3.Vector{}, + Inventory: make(map[int]*Equipment), + demoInfoProvider: demoInfoProvider, + LastAlivePosition: r3.Vector{}, + PreviousFramePosition: r3.Vector{}, } } diff --git a/pkg/demoinfocs/common/player_test.go b/pkg/demoinfocs/common/player_test.go index d5c3faea..93473760 100644 --- a/pkg/demoinfocs/common/player_test.go +++ b/pkg/demoinfocs/common/player_test.go @@ -432,9 +432,10 @@ func createPlayerForVelocityTest() *Player { controllerEntity := entityWithProperties([]fakeProp{ {propName: "m_hPlayerPawn", value: st.PropertyValue{Any: uint64(1), S2: true}}, }) - pawnEntity := entityWithProperties([]fakeProp{ - {propName: "m_iHealth", value: st.PropertyValue{Any: int32(100), S2: true}}, - }) + pawnEntity := new(stfake.Entity) + position := r3.Vector{X: 20, Y: 300, Z: 100} + + pawnEntity.On("Position").Return(position) pl := &Player{ Entity: controllerEntity, @@ -453,14 +454,15 @@ func createPlayerForVelocityTest() *Player { func TestPlayer_VelocityS2(t *testing.T) { pl := createPlayerForVelocityTest() - pl.LastPositions = []r3.Vector{{X: 10, Y: 200, Z: 50}, {X: 20, Y: 300, Z: 100}} + pl.PreviousFramePosition = r3.Vector{X: 10, Y: 200, Z: 50} expected := r3.Vector{X: 640, Y: 6400, Z: 3200} assert.Equal(t, expected, pl.Velocity()) } -func TestPlayer_VelocityS2WithoutPositions(t *testing.T) { +func TestPlayer_VelocityDidNotChangeS2(t *testing.T) { pl := createPlayerForVelocityTest() + pl.PreviousFramePosition = r3.Vector{X: 20, Y: 300, Z: 100} expected := r3.Vector{X: 0, Y: 0, Z: 0} assert.Equal(t, expected, pl.Velocity()) diff --git a/pkg/demoinfocs/datatables.go b/pkg/demoinfocs/datatables.go index 35521b09..e0041dbc 100644 --- a/pkg/demoinfocs/datatables.go +++ b/pkg/demoinfocs/datatables.go @@ -569,12 +569,6 @@ func (p *parser) bindNewPlayerPawnS2(pawnEntity st.Entity) { } if pl.IsAlive() { pl.LastAlivePosition = pos - pl.LastPositions = append(pl.LastPositions, pos) - if len(pl.LastPositions) > 2 { - pl.LastPositions = pl.LastPositions[1:] - } - } else { - pl.LastPositions = nil } }) diff --git a/pkg/demoinfocs/parsing.go b/pkg/demoinfocs/parsing.go index 5be68023..90895bc6 100644 --- a/pkg/demoinfocs/parsing.go +++ b/pkg/demoinfocs/parsing.go @@ -525,6 +525,10 @@ func (p *parser) handleFrameParsed(*frameParsedTokenType) { p.currentFrame++ p.eventDispatcher.Dispatch(events.FrameDone{}) + + if p.isSource2() { + p.updatePlayersPreviousFramePosition() + } } // CS2 demos playback info are available in the CDemoFileInfo message that should be parsed at the end of the demo. diff --git a/pkg/demoinfocs/s2_commands.go b/pkg/demoinfocs/s2_commands.go index 46b710fd..66b712fe 100644 --- a/pkg/demoinfocs/s2_commands.go +++ b/pkg/demoinfocs/s2_commands.go @@ -378,3 +378,9 @@ func (p *parser) handleDemoFileHeader(msg *msgs2.CDemoFileHeader) { p.header.MapName = msg.GetMapName() p.header.NetworkProtocol = int(msg.GetNetworkProtocol()) } + +func (p *parser) updatePlayersPreviousFramePosition() { + for _, player := range p.GameState().Participants().Playing() { + player.PreviousFramePosition = player.Position() + } +}