Skip to content

Commit 869a520

Browse files
authored
pkg/demoinfocs/common: fix panic in Player.Color() for old demos (#359)
1 parent 64c381e commit 869a520

File tree

3 files changed

+97
-10
lines changed

3 files changed

+97
-10
lines changed

pkg/demoinfocs/common/common_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,13 @@ func entityWithProperty(propName string, value st.PropertyValue) *stfake.Entity
220220

221221
return entity
222222
}
223+
224+
func entityWithoutProperty(propName string) *stfake.Entity {
225+
entity := entityWithID(1)
226+
227+
entity.On("Property", propName).Return(nil)
228+
entity.On("PropertyValue", propName).Return(st.PropertyValue{}, false)
229+
entity.On("PropertyValueMust", propName).Panic("property not found")
230+
231+
return entity
232+
}

pkg/demoinfocs/common/player.go

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"time"
66

77
"github.com/golang/geo/r3"
8+
"github.com/pkg/errors"
89

910
"github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/constants"
1011
st "github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/sendtables"
@@ -343,20 +344,24 @@ func (pf PlayerFlags) OnGround() bool {
343344
}
344345

345346
// Ducking returns true if the player is/was fully crouched.
346-
// Fully ducked: Ducking() && DuckingKeyPressed()
347-
// Previously fully ducked, unducking in progress: Ducking() && !DuckingKeyPressed()
348-
// Fully unducked: !Ducking() && !DuckingKeyPressed()
349-
// Previously fully unducked, ducking in progress: !Ducking() && DuckingKeyPressed()
347+
//
348+
// Fully ducked: Ducking() && DuckingKeyPressed()
349+
// Previously fully ducked, unducking in progress: Ducking() && !DuckingKeyPressed()
350+
// Fully unducked: !Ducking() && !DuckingKeyPressed()
351+
// Previously fully unducked, ducking in progress: !Ducking() && DuckingKeyPressed()
352+
//
350353
// See m_fFlags FL_DUCKING https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/public/const.h#L146-L188
351354
func (pf PlayerFlags) Ducking() bool {
352355
return pf.Get(flDucking)
353356
}
354357

355358
// DuckingKeyPressed returns true if the player is holding the crouch key pressed.
356-
// Fully ducked: Ducking() && DuckingKeyPressed()
357-
// Previously fully ducked, unducking in progress: Ducking() && !DuckingKeyPressed()
358-
// Fully unducked: !Ducking() && !DuckingKeyPressed()
359-
// Previously fully unducked, ducking in progress: !Ducking() && DuckingKeyPressed()
359+
//
360+
// Fully ducked: Ducking() && DuckingKeyPressed()
361+
// Previously fully ducked, unducking in progress: Ducking() && !DuckingKeyPressed()
362+
// Fully unducked: !Ducking() && !DuckingKeyPressed()
363+
// Previously fully unducked, ducking in progress: !Ducking() && DuckingKeyPressed()
364+
//
360365
// See m_fFlags FL_ANIMDUCKING https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/public/const.h#L146-L188
361366
func (pf PlayerFlags) DuckingKeyPressed() bool {
362367
return pf.Get(flAnimDucking)
@@ -406,9 +411,43 @@ func (p *Player) Score() int {
406411
return getInt(p.resourceEntity(), "m_iScore."+p.entityIDStr())
407412
}
408413

409-
// Color returns the players color as shown on the match.
414+
// Color returns the players color as shown on the minimap.
415+
// It will return Grey (-1) if the resource entity does not exist when the function is called or when the demo does not support player colors.
416+
// Deprecated: Use ColorOrErr() instead.
410417
func (p *Player) Color() Color {
411-
return Color(getInt(p.resourceEntity(), "m_iCompTeammateColor."+p.entityIDStr()))
418+
resourceEnt := p.resourceEntity()
419+
if resourceEnt == nil {
420+
return Grey
421+
}
422+
423+
n, ok := resourceEnt.PropertyValue("m_iCompTeammateColor." + p.entityIDStr())
424+
if !ok {
425+
return Grey
426+
}
427+
428+
return Color(n.IntVal)
429+
}
430+
431+
var (
432+
ErrDataNotAvailable = errors.New("some data is not (yet) available (reading the same data later during parsing may work)")
433+
ErrNotSupportedByDemo = errors.New("this data is not supported by the demo (this may be because the demos is too old)")
434+
)
435+
436+
// ColorOrErr returns the players color as shown on the minimap.
437+
// Returns ErrDataNotAvailable if the resource entity does not exist (it may exist later during parsing).
438+
// Returns ErrNotSupportedByDemo if the demo does not support player colors (e.g. very old demos).
439+
func (p *Player) ColorOrErr() (Color, error) {
440+
resourceEnt := p.resourceEntity()
441+
if resourceEnt == nil {
442+
return Grey, errors.Wrap(ErrDataNotAvailable, "player resource entity is nil")
443+
}
444+
445+
colorVal, ok := resourceEnt.PropertyValue("m_iCompTeammateColor." + p.entityIDStr())
446+
if !ok {
447+
return Grey, errors.Wrap(ErrNotSupportedByDemo, "failed to get player color from resource entity")
448+
}
449+
450+
return Color(colorVal.IntVal), nil
412451
}
413452

414453
// Kills returns the amount of kills the player has as shown on the scoreboard.

pkg/demoinfocs/common/player_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,44 @@ func TestPlayer_Color(t *testing.T) {
422422
pl := playerWithResourceProperty("m_iCompTeammateColor", st.PropertyValue{IntVal: int(Yellow)})
423423

424424
assert.Equal(t, Yellow, pl.Color())
425+
426+
pl = &Player{demoInfoProvider: demoInfoProviderMock{}}
427+
428+
assert.Equal(t, Grey, pl.Color())
429+
430+
pl = &Player{
431+
EntityID: 1,
432+
demoInfoProvider: demoInfoProviderMock{
433+
playerResourceEntity: entityWithoutProperty("m_iCompTeammateColor.001"),
434+
},
435+
}
436+
437+
assert.Equal(t, Grey, pl.Color())
438+
}
439+
440+
func TestPlayer_ColorOrErr(t *testing.T) {
441+
pl := playerWithResourceProperty("m_iCompTeammateColor", st.PropertyValue{IntVal: int(Yellow)})
442+
443+
color, err := pl.ColorOrErr()
444+
assert.NoError(t, err)
445+
assert.Equal(t, Yellow, color)
446+
447+
pl = &Player{demoInfoProvider: demoInfoProviderMock{}}
448+
449+
color, err = pl.ColorOrErr()
450+
assert.ErrorIs(t, err, ErrDataNotAvailable)
451+
assert.EqualValues(t, Grey, color)
452+
453+
pl = &Player{
454+
EntityID: 1,
455+
demoInfoProvider: demoInfoProviderMock{
456+
playerResourceEntity: entityWithoutProperty("m_iCompTeammateColor.001"),
457+
},
458+
}
459+
460+
color, err = pl.ColorOrErr()
461+
assert.ErrorIs(t, err, ErrNotSupportedByDemo)
462+
assert.Equal(t, Grey, color)
425463
}
426464

427465
func TestPlayer_Kills(t *testing.T) {

0 commit comments

Comments
 (0)