Repository: markus-wa/demoinfocs-golang
Pull request #470 (2023-11-12 14:09:36)

feat: add player velocity
Related to https://github.com/markus-wa/demoinfocs-golang/issues/406

It looks like the player's velocity will not be networked in CS2 demos any time soon, so this PR adds it by tracking the player's positions.

To ensure accuracy, I first tried with a CS:GO demo and compared values by printing the velocity at each frame:

```go
// VelocityTemp is the new S2 implementation, the only diff is the framerate being ~32 instead of 64
fmt.Println("Expected X/Y:", player.Velocity().X, player.Velocity().Y, "Got X/Y: ", player.VelocityTemp().X, player.VelocityTemp().Y)
```

<details>
<summary>Partial output</summary>

Expected X/Y: -19.653369903564453 28.658458709716797 Got X/Y:  -15.64716902168722 22.81130925206571
Expected X/Y: -35.63056945800781 51.95634078979492 Got X/Y:  -31.59869694205065 46.08305888930825
Expected X/Y: -51.60777282714844 75.25422668457031 Got X/Y:  -47.56583301106414 69.35480852655078
Expected X/Y: -65.94398498535156 96.15922546386719 Got X/Y:  -62.440398674573295 91.05793922446209
Expected X/Y: -78.04520416259766 113.80516052246094 Got X/Y:  -75.0673909324735 109.4677505572103
Expected X/Y: -88.2598648071289 128.70013427734375 Got X/Y:  -85.7277564604659 124.99785846402204
Expected X/Y: -96.88207244873047 141.2729949951172 Got X/Y:  -94.71024600857662 138.10870333007418
Expected X/Y: -104.16008758544922 151.88577270507812 Got X/Y:  -102.3036103268318 149.18268479729323
Expected X/Y: -110.30348205566406 160.84405517578125 Got X/Y:  -108.71075534768228 158.52416176435537
Expected X/Y: -115.48912048339844 168.40573120117188 Got X/Y:  -114.1189788549288 166.41408090696174
Expected X/Y: -97.48454284667969 142.1515350341797 Got X/Y:  -101.68708845515435 148.27741217558963
Expected X/Y: -82.28685760498047 119.99034118652344 Got X/Y:  -85.82920942669129 125.1617440248477
Expected X/Y: -69.45846557617188 101.2840347290039 Got X/Y:  -72.44522195926308 105.6437541379451
Expected X/Y: -58.62999725341797 85.49400329589844 Got X/Y:  -61.152726410943174 89.18496138645465
Expected X/Y: -49.48967361450195 72.1656265258789 Got X/Y:  -51.61614758575525 75.27810093924934
Expected X/Y: -14.539595603942871 14.628421783447266 Got X/Y:  -7.265593196603891 7.304613568229047
Expected X/Y: -33.95360565185547 35.15204620361328 Got X/Y:  -29.11700130669078 29.94423318514407
Expected X/Y: -52.43074035644531 56.534576416015625 Got X/Y:  -47.82336746379017 51.085470531653144
Expected X/Y: -69.64030456542969 77.79805755615234 Got X/Y:  -65.48398766133539 72.52326270251339
Expected X/Y: -84.84886169433594 97.60599517822266 Got X/Y:  -81.12335260869759 92.75922742731885
Expected X/Y: -97.65898895263672 114.34830474853516 Got X/Y:  -94.49173192747574 110.21694169241327
Expected X/Y: -108.47203826904297 128.4805145263672 Got X/Y:  -105.76861932714559 124.96664216672193
Expected X/Y: -117.59935760498047 140.4095458984375 Got X/Y:  -115.28959000368344 137.4063366408214
Expected X/Y: -125.3037338256836 150.47885131835938 Got X/Y:  -123.31998248414038 147.91062068231315
Expected X/Y: -131.80702209472656 158.97837829589844 Got X/Y:  -130.10952714691737 156.7760491155484
Expected X/Y: -137.2964630126953 166.15284729003906 Got X/Y:  -135.83771770149016 164.26015639325317
Expected X/Y: -141.93011474609375 172.20883178710938 Got X/Y:  -140.6762437830094 170.57365252220328
Expected X/Y: -145.84136962890625 177.3206787109375 Got X/Y:  -144.7499705806756 175.9116393605245
Expected X/Y: -145.84136962890625 177.3206787109375 Got X/Y:  -144.7499705806756 175.9116393605245
Expected X/Y: -145.84136962890625 177.3206787109375 Got X/Y:  -144.7499705806756 175.9116393605245
Expected X/Y: -145.84136962890625 177.3206787109375 Got X/Y:  -144.7499705806756 175.9116393605245
Expected X/Y: -147.56202697753906 179.5695037841797 Got X/Y:  -73.70167792559307 89.68442214325663
Expected X/Y: -150.5952911376953 183.53384399414062 Got X/Y:  -149.7055577770703 182.38902105030027
Expected X/Y: -153.15567016601562 186.88014221191406 Got X/Y:  -152.38235527055593 185.87744227358914
Expected X/Y: -158.23309326171875 193.5517578125 Got X/Y:  -469.07949545464027 573.2794958424546
Expected X/Y: -158.12103271484375 193.64334106445312 Got X/Y:  -394.97980973847064 483.5014248072976
Expected X/Y: -158.08790588378906 193.67037963867188 Got X/Y:  -157.93105211565302 193.45519844319426
Expected X/Y: -158.0599365234375 193.69320678710938 Got X/Y:  -157.8998358183529 193.47861066616937
Expected X/Y: -158.03634643554688 193.7124481201172 Got X/Y:  -157.86861952105275 193.50202288914446
Expected X/Y: -158.01641845703125 193.72869873046875 Got X/Y:  -157.8530113724027 193.51763103779453
Expected X/Y: -157.99960327148438 193.74240112304688 Got X/Y:  -157.8295991494276 193.53323918644458
Expected X/Y: -157.9853973388672 193.7539825439453 Got X/Y:  -157.82179507510259 193.5410432607696
Expected X/Y: -157.97340393066406 193.76376342773438 Got X/Y:  -157.8061869264525 193.5566514094197
Expected X/Y: -157.96328735351562 193.77203369140625 Got X/Y:  -157.79838285212747 193.5566514094197
Expected X/Y: -157.95474243164062 193.77899169921875 Got X/Y:  -157.78277470347743 193.57225955806973
Expected X/Y: -157.94752502441406 193.7848663330078 Got X/Y:  -157.78277470347743 193.57225955806973
Expected X/Y: -157.94143676757812 193.78981018066406 Got X/Y:  -157.76716655482736 193.58006363239477
Expected X/Y: -157.93629455566406 193.7939910888672 Got X/Y:  -157.76716655482736 193.5878677067198
Expected X/Y: -157.9319610595703 193.7975311279297 Got X/Y:  -157.76716655482736 193.5878677067198
Expected X/Y: -157.9282989501953 193.80050659179688 Got X/Y:  -157.75936248050232 193.5878677067198
Expected X/Y: -157.92520141601562 193.8030242919922 Got X/Y:  -157.75155840617728 193.5878677067198
Expected X/Y: -157.92262268066406 193.80517578125 Got X/Y:  -157.75155840617728 193.59567178104484
Expected X/Y: -157.92041015625 193.8069610595703 Got X/Y:  -157.75155840617728 193.60347585536985
Expected X/Y: -157.91854858398438 193.80848693847656 Got X/Y:  -157.75155840617728 193.60347585536985
Expected X/Y: -157.91697692871094 193.80975341796875 Got X/Y:  -157.75155840617728 193.60347585536985
Expected X/Y: -157.91563415527344 193.81082153320312 Got X/Y:  -157.75155840617728 193.60347585536985
Expected X/Y: -157.91453552246094 193.81173706054688 Got X/Y:  -157.75155840617728 193.60347585536985
Expected X/Y: -157.91360473632812 193.81251525878906 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.91281127929688 193.8131561279297 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.91213989257812 193.81370544433594 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.91156005859375 193.8141632080078 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.91107177734375 193.81454467773438 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.9106903076172 193.8148956298828 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.91033935546875 193.81517028808594 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.9100341796875 193.8153839111328 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.9097900390625 193.81558227539062 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.90960693359375 193.81578063964844 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.909423828125 193.81591796875 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.90927124023438 193.8160400390625 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.90914916992188 193.81613159179688 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.90902709960938 193.8162078857422 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.90895080566406 193.8162841796875 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.90887451171875 193.8163604736328 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.9088134765625 193.81639099121094 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.90878295898438 193.8164520263672 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.90872192382812 193.8164825439453 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.90867614746094 193.81649780273438 Got X/Y:  -157.73595025752724 193.60347585536985
Expected X/Y: -157.93690490722656 193.79351806640625 Got X/Y:  -157.75155840617728 193.59567178104484
Expected X/Y: -157.9866943359375 193.75296020507812 Got X/Y:  -157.8061869264525 193.54884733509465
Expected X/Y: -158.02870178222656 193.71868896484375 Got X/Y:  -157.8530113724027 193.51763103779453
Expected X/Y: -158.06414794921875 193.6897735595703 Got X/Y:  -157.88422766970282 193.48641474049438
Expected X/Y: -158.1505584716797 193.61923217773438 Got X/Y:  -157.94666026430306 193.43959029454422
Expected X/Y: -158.35768127441406 193.44985961914062 Got X/Y:  -158.1339580481038 193.29131288236863
Expected X/Y: -158.66879272460938 193.19476318359375 Got X/Y:  -158.4071006494799 193.0571906526177
Expected X/Y: -159.14730834960938 192.80075073242188 Got X/Y:  -158.84412881168163 192.69820323366625
Expected X/Y: -159.82290649414062 192.24110412597656 Got X/Y:  -159.4606506833591 192.19093840253925
Expected X/Y: -160.6869659423828 191.51943969726562 Got X/Y:  -160.28788256181238 191.5041798619365
Expected X/Y: -161.87876892089844 190.51316833496094 Got X/Y:  -161.35704074434165 190.5989072402329
Expected X/Y: -163.47164916992188 189.14813232421875 Got X/Y:  -162.87103116339765 189.30343090227777
Expected X/Y: -165.57644653320312 187.3083953857422 Got X/Y:  -164.80644159600536 187.62555492239608
Expected X/Y: -168.3233642578125 184.84384155273438 Got X/Y:  -167.39739427191566 185.3155489221869
Expected X/Y: -172.0121612548828 181.41612243652344 Got X/Y:  -170.84679512357937 182.13929067189926
Expected X/Y: -175.90182495117188 177.64724731445312 Got X/Y:  -174.70981191446973 178.43235536750953
Expected X/Y: -180.16119384765625 173.32611083984375 Got X/Y:  -178.8771876040363 174.25717560361792
Expected X/Y: -184.5794677734375 168.61322021484375 Got X/Y:  -183.28648959767884 169.61375138022447
Expected X/Y: -189.1682891845703 163.44834899902344 Got X/Y:  -187.81285270619682 164.5879275149045
Expected X/Y: -193.79600524902344 157.93389892578125 Got X/Y:  -192.46408100391534 159.12507548738276
Expected X/Y: -197.88052368164062 152.78514099121094 Got X/Y:  -196.68998725091964 153.87293346663688
Expected X/Y: -201.4800567626953 148.00604248046875 Got X/Y:  -200.3774123694968 149.0266033107926
Expected X/Y: -204.6533660888672 143.58619689941406 Got X/Y:  -203.67853580898492 144.5002402022746
Expected X/Y: -207.58485412597656 139.31448364257812 Got X/Y:  -206.6440840524967 140.21580339783256
Expected X/Y: -210.54788208007812 134.79461669921875 Got X/Y:  -209.57061192438334 135.80650140419004
Expected X/Y: -213.5395050048828 130.00338745117188 Got X/Y:  -212.57908257668282 131.0460160659211
Expected X/Y: -216.5274658203125 124.96341705322266 Got X/Y:  -215.57584711749473 126.06701664655131
Expected X/Y: -219.16412353515625 120.27920532226562 Got X/Y:  -218.30337109409308 121.27531501098225
Expected X/Y: -221.29286193847656 116.31623840332031 Got X/Y:  -220.5470424625395 117.1391556187158
Expected X/Y: -223.000244140625 113.00833892822266 Got X/Y:  -222.3575877059467 113.68195069272706
Expected X/Y: -224.37872314453125 110.24606323242188 Got X/Y:  -223.80914553040247 110.78663911814054
Expected X/Y: -225.501708984375 107.93045806884766 Got X/Y:  -224.99146279064468 108.36737607738092
Expected X/Y: -226.4229278564453 105.98421478271484 Got X/Y:  -225.95916800694852 106.33831675287286
Expected X/Y: -227.18275451660156 104.34555053710938 Got X/Y:  -226.7551835881017 104.62142040136602
Expected X/Y: -227.81211853027344 102.96427154541016 Got X/Y:  -227.41852990572932 103.18547072556032
Expected X/Y: -228.33517456054688 101.79906463623047 Got X/Y:  -227.9648151084815 101.96803513085547
Expected X/Y: -228.77105712890625 100.8156509399414 Got X/Y:  -228.4213534564958 100.93789731995138
Expected X/Y: -229.13514709472656 99.98542785644531 Got X/Y:  -228.79985106125983 100.07944914419797
Expected X/Y: -229.43975830078125 99.28440856933594 Got X/Y:  -229.1198181085861 99.34586615764505
Expected X/Y: -229.6950225830078 98.69245147705078 Got X/Y:  -229.38515663563714 98.72154021164256
Expected X/Y: -229.90916442871094 98.19253540039062 Got X/Y:  -229.60757275390054 98.20647130619052
Expected X/Y: -230.08901977539062 97.77037048339844 Got X/Y:  -229.79487053770129 97.76163906966374
Expected X/Y: -230.24017333984375 97.41386413574219 Got X/Y:  -229.95485406136441 97.38704350206226
Expected X/Y: -230.3673095703125 97.1128158569336 Got X/Y:  -230.08752332488993 97.07488052906102
Expected X/Y: -230.4743194580078 96.8586196899414 Got X/Y:  -230.2006824026029 96.81734607633499
Expected X/Y: -230.56439208984375 96.64397430419922 Got X/Y:  -230.29433129450325 96.5910279209091
Expected X/Y: -230.64028930664062 96.46273803710938 Got X/Y:  -230.37237203775356 96.40373013710834
Expected X/Y: -226.08322143554688 94.40009307861328 Got X/Y:  -225.826498743423 94.33565044097513
Expected X/Y: -226.13711547851562 94.27091217041016 Got X/Y:  -225.8811272636982 94.19517710312456
Expected X/Y: -226.18255615234375 94.16184997558594 Got X/Y:  -225.9279517096484 94.08592006257413
Expected X/Y: -226.22085571289062 94.06977081298828 Got X/Y:  -225.96697208127355 93.98446709634872
Expected X/Y: -226.25315856933594 93.9920425415039 Got X/Y:  -225.99818837857367 93.90642635309841
Expected X/Y: -226.28041076660156 93.92642974853516 Got X/Y:  -226.0294046758738 93.83618968417314
Expected X/Y: -226.30340576171875 93.87102508544922 Got X/Y:  -226.0528168988489 93.78156116389792
Expected X/Y: -226.32281494140625 93.82426452636719 Got X/Y:  -226.07232708466148 93.73473671794774
Expected X/Y: -226.3391571044922 93.78477478027344 Got X/Y:  -226.08793523331153 93.68791227199755
Expected X/Y: -226.35296630859375 93.75144958496094 Got X/Y:  -226.1035433819616 93.65669597469743
Expected X/Y: -226.3646240234375 93.72331237792969 Got X/Y:  -226.11915153061165 93.6254796773973
Expected X/Y: -226.3744354248047 93.69955444335938 Got X/Y:  -226.12695560493668 93.60206745442221
Expected X/Y: -226.38272094726562 93.67950439453125 Got X/Y:  -226.13475967926172 93.57865523144712
Expected X/Y: -226.38970947265625 93.66258239746094 Got X/Y:  -226.14256375358676 93.56304708279706
Expected X/Y: -226.39561462402344 93.64830017089844 Got X/Y:  -226.1503678279118 93.547438934147
Expected X/Y: -226.58596801757812 93.18692779541016 Got X/Y:  -226.2830370914373 93.23527596114575
Expected X/Y: -227.12806701660156 91.85770416259766 Got X/Y:  -226.71226117931403 92.17392185294153
Expected X/Y: -228.30715942382812 88.88666534423828 Got X/Y:  -227.7150847300805 89.66881399460657
Expected X/Y: -230.4272918701172 83.23616790771484 Got X/Y:  -229.5997686795755 84.71322679821186
Expected X/Y: -233.02291870117188 75.66580963134766 Got X/Y:  -232.1126806122355 77.55689064215841
</details>

I also tested with a CS2 demo - values are OK, and "jump throws" reported by the parser are accurate.


Patch:
diff --git a/pkg/demoinfocs/common/player.go b/pkg/demoinfocs/common/player.go
index 811ae90..c65fb0c 100644
--- a/pkg/demoinfocs/common/player.go
+++ b/pkg/demoinfocs/common/player.go
@@ -15,24 +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
+	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 {
@@ -521,7 +522,14 @@ func (p *Player) PositionEyes() r3.Vector {
 // Velocity returns the player's velocity.
 func (p *Player) Velocity() r3.Vector {
 	if p.demoInfoProvider.IsSource2() {
-		panic("Velocity() is not supported for Source 2 demos")
+		t := 64.0
+		diff := p.Position().Sub(p.PreviousFramePosition)
+
+		return r3.Vector{
+			X: diff.X * t,
+			Y: diff.Y * t,
+			Z: diff.Z * t,
+		}
 	}
 
 	if p.Entity == nil {
@@ -801,8 +809,9 @@ type demoInfoProvider interface {
 // Intended for internal use only.
 func NewPlayer(demoInfoProvider demoInfoProvider) *Player {
 	return &Player{
-		Inventory:        make(map[int]*Equipment),
-		demoInfoProvider: demoInfoProvider,
+		Inventory:             make(map[int]*Equipment),
+		demoInfoProvider:      demoInfoProvider,
+		PreviousFramePosition: r3.Vector{},
 	}
 }
 
diff --git a/pkg/demoinfocs/parsing.go b/pkg/demoinfocs/parsing.go
index 5be6802..90895bc 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 46b710f..66b712f 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()
+	}
+}


Test patch:
diff --git a/pkg/demoinfocs/common/common_test.go b/pkg/demoinfocs/common/common_test.go
index 2182379..5fac082 100644
--- a/pkg/demoinfocs/common/common_test.go
+++ b/pkg/demoinfocs/common/common_test.go
@@ -213,13 +213,14 @@ type demoInfoProviderMock struct {
 	tickRate             float64
 	ingameTick           int
 	playersByHandle      map[int]*Player
+	entitiesByHandle     map[uint64]st.Entity
 	playerResourceEntity st.Entity
 	equipment            *Equipment
 	isSource2            bool
 }
 
 func (p demoInfoProviderMock) FindEntityByHandle(handle uint64) st.Entity {
-	panic("implement me")
+	return p.entitiesByHandle[handle]
 }
 
 func (p demoInfoProviderMock) IsSource2() bool {
diff --git a/pkg/demoinfocs/common/player_test.go b/pkg/demoinfocs/common/player_test.go
index a9e2f39..9347376 100644
--- a/pkg/demoinfocs/common/player_test.go
+++ b/pkg/demoinfocs/common/player_test.go
@@ -428,6 +428,46 @@ func TestPlayer_Velocity(t *testing.T) {
 	assert.Equal(t, expected, pl.Velocity())
 }
 
+func createPlayerForVelocityTest() *Player {
+	controllerEntity := entityWithProperties([]fakeProp{
+		{propName: "m_hPlayerPawn", value: st.PropertyValue{Any: uint64(1), S2: true}},
+	})
+	pawnEntity := new(stfake.Entity)
+	position := r3.Vector{X: 20, Y: 300, Z: 100}
+
+	pawnEntity.On("Position").Return(position)
+
+	pl := &Player{
+		Entity: controllerEntity,
+	}
+
+	demoInfoProvider := demoInfoProviderMock{
+		isSource2: true,
+		entitiesByHandle: map[uint64]st.Entity{
+			1: pawnEntity,
+		},
+	}
+	pl.demoInfoProvider = demoInfoProvider
+
+	return pl
+}
+
+func TestPlayer_VelocityS2(t *testing.T) {
+	pl := createPlayerForVelocityTest()
+	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_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())
+}
+
 func TestPlayer_Velocity_EntityNil(t *testing.T) {
 	pl := new(Player)
 	pl.demoInfoProvider = s1DemoInfoProvider