Skip to content

Commit e3feeed

Browse files
authored
Merge branch 'master' into micvbang/weapon-owner
2 parents a4106dc + d8e1150 commit e3feeed

File tree

9 files changed

+195
-85
lines changed

9 files changed

+195
-85
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: 33 additions & 22 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 {
@@ -380,7 +381,9 @@ func (p *Player) Armor() int {
380381
// CS2 values:
381382
// -1 -> Not available, demo probably not coming from a Valve server
382383
// 0 -> None?
383-
// 11 -> Classic Competitive
384+
// 7 -> Wingman 2v2
385+
// 11 -> Premier mode
386+
// 12 -> Classic Competitive
384387
func (p *Player) RankType() int {
385388
if p.demoInfoProvider.IsSource2() {
386389
return getInt(p.Entity, "m_iCompetitiveRankType")
@@ -519,7 +522,14 @@ func (p *Player) PositionEyes() r3.Vector {
519522
// Velocity returns the player's velocity.
520523
func (p *Player) Velocity() r3.Vector {
521524
if p.demoInfoProvider.IsSource2() {
522-
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+
}
523533
}
524534

525535
if p.Entity == nil {
@@ -799,8 +809,9 @@ type demoInfoProvider interface {
799809
// Intended for internal use only.
800810
func NewPlayer(demoInfoProvider demoInfoProvider) *Player {
801811
return &Player{
802-
Inventory: make(map[int]*Equipment),
803-
demoInfoProvider: demoInfoProvider,
812+
Inventory: make(map[int]*Equipment),
813+
demoInfoProvider: demoInfoProvider,
814+
PreviousFramePosition: r3.Vector{},
804815
}
805816
}
806817

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/datatables.go

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,12 @@ func (p *parser) bindBomb() {
159159
bomb.LastOnGroundPosition = bombEntity.Position()
160160

161161
if p.isSource2() {
162-
isTicking := true
163162
ownerProp := bombEntity.PropertyValueMust("m_hOwnerEntity")
164163
planter := p.gameState.Participants().FindByPawnHandle(ownerProp.Handle())
164+
if planter == nil {
165+
return
166+
}
167+
isTicking := true
165168
planter.IsPlanting = false
166169

167170
siteNumber := bombEntity.PropertyValueMust("m_nBombSite").Int()
@@ -524,6 +527,15 @@ func (p *parser) bindNewPlayerControllerS2(controllerEntity st.Entity) {
524527
func (p *parser) bindNewPlayerPawnS2(pawnEntity st.Entity) {
525528
var prevControllerHandle uint64
526529

530+
getPlayerFromPawnEntity := func(pawnEntity st.Entity) *common.Player {
531+
controllerProp, hasProp := pawnEntity.PropertyValue("m_hController")
532+
if !hasProp {
533+
return nil
534+
}
535+
536+
return p.gameState.Participants().FindByHandle64(controllerProp.Handle())
537+
}
538+
527539
pawnEntity.Property("m_hController").OnUpdate(func(controllerHandleVal st.PropertyValue) {
528540
controllerHandle := controllerHandleVal.Handle()
529541
if controllerHandle == constants.InvalidEntityHandleSource2 || controllerHandle == prevControllerHandle {
@@ -537,65 +549,89 @@ func (p *parser) bindNewPlayerPawnS2(pawnEntity st.Entity) {
537549

538550
pl := p.getOrCreatePlayerFromControllerEntity(controllerEntity)
539551

552+
p.bindPlayerWeaponsS2(pawnEntity, pl)
553+
540554
if !pl.IsConnected {
541555
pl.IsConnected = true
542-
543556
if pl.SteamID64 != 0 {
544557
p.eventDispatcher.Dispatch(events.PlayerConnect{Player: pl})
545558
} else {
546559
p.eventDispatcher.Dispatch(events.BotConnect{Player: pl})
547560
}
548561
}
562+
})
549563

550-
// Position
551-
pawnEntity.OnPositionUpdate(func(pos r3.Vector) {
552-
if pl.IsAlive() {
553-
pl.LastAlivePosition = pos
554-
}
555-
})
556-
557-
pawnEntity.Property("m_flFlashDuration").OnUpdate(func(val st.PropertyValue) {
558-
if val.Float() == 0 {
559-
pl.FlashTick = 0
560-
} else {
561-
pl.FlashTick = p.gameState.ingameTick
562-
}
564+
// Position
565+
pawnEntity.OnPositionUpdate(func(pos r3.Vector) {
566+
pl := getPlayerFromPawnEntity(pawnEntity)
567+
if pl == nil {
568+
return
569+
}
570+
if pl.IsAlive() {
571+
pl.LastAlivePosition = pos
572+
}
573+
})
563574

564-
pl.FlashDuration = val.Float()
575+
pawnEntity.Property("m_flFlashDuration").OnUpdate(func(val st.PropertyValue) {
576+
pl := getPlayerFromPawnEntity(pawnEntity)
577+
if pl == nil {
578+
return
579+
}
580+
if val.Float() == 0 {
581+
pl.FlashTick = 0
582+
} else {
583+
pl.FlashTick = p.gameState.ingameTick
584+
}
565585

566-
if pl.FlashDuration > 0 {
567-
if len(p.gameState.flyingFlashbangs) == 0 {
568-
return
569-
}
586+
pl.FlashDuration = val.Float()
570587

571-
flashbang := p.gameState.flyingFlashbangs[0]
572-
flashbang.flashedEntityIDs = append(flashbang.flashedEntityIDs, pl.EntityID)
588+
if pl.FlashDuration > 0 {
589+
if len(p.gameState.flyingFlashbangs) == 0 {
590+
return
573591
}
574-
})
575592

576-
p.bindPlayerWeaponsS2(pawnEntity, pl)
593+
flashbang := p.gameState.flyingFlashbangs[0]
594+
flashbang.flashedEntityIDs = append(flashbang.flashedEntityIDs, pl.EntityID)
595+
}
596+
})
577597

578-
pawnEntity.Property("m_pWeaponServices.m_hActiveWeapon").OnUpdate(func(val st.PropertyValue) {
579-
pl.IsReloading = false
580-
})
598+
pawnEntity.Property("m_pWeaponServices.m_hActiveWeapon").OnUpdate(func(val st.PropertyValue) {
599+
pl := getPlayerFromPawnEntity(pawnEntity)
600+
if pl == nil {
601+
return
602+
}
603+
pl.IsReloading = false
604+
})
581605

582-
pawnEntity.Property("m_bIsDefusing").OnUpdate(func(val st.PropertyValue) {
583-
pl.IsDefusing = val.BoolVal()
584-
})
606+
pawnEntity.Property("m_bIsDefusing").OnUpdate(func(val st.PropertyValue) {
607+
pl := getPlayerFromPawnEntity(pawnEntity)
608+
if pl == nil {
609+
return
610+
}
611+
pl.IsDefusing = val.BoolVal()
612+
})
585613

586-
spottedByMaskProp := pawnEntity.Property("m_bSpottedByMask.0000")
587-
if spottedByMaskProp != nil {
588-
spottersChanged := func(val st.PropertyValue) {
589-
p.eventDispatcher.Dispatch(events.PlayerSpottersChanged{Spotted: pl})
614+
spottedByMaskProp := pawnEntity.Property("m_bSpottedByMask.0000")
615+
if spottedByMaskProp != nil {
616+
spottersChanged := func(val st.PropertyValue) {
617+
pl := getPlayerFromPawnEntity(pawnEntity)
618+
if pl == nil {
619+
return
590620
}
591621

592-
spottedByMaskProp.OnUpdate(spottersChanged)
593-
pawnEntity.Property("m_bSpottedByMask.0001").OnUpdate(spottersChanged)
622+
p.eventDispatcher.Dispatch(events.PlayerSpottersChanged{Spotted: pl})
594623
}
595624

596-
pawnEntity.OnDestroy(func() {
597-
pl.IsConnected = false
598-
})
625+
spottedByMaskProp.OnUpdate(spottersChanged)
626+
pawnEntity.Property("m_bSpottedByMask.0001").OnUpdate(spottersChanged)
627+
}
628+
629+
pawnEntity.OnDestroy(func() {
630+
pl := getPlayerFromPawnEntity(pawnEntity)
631+
if pl == nil {
632+
return
633+
}
634+
pl.IsConnected = false
599635
})
600636
}
601637

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)