Skip to content

Commit 6019894

Browse files
committed
common: add functions to check if players are spotted (#114)
1 parent 03b772f commit 6019894

17 files changed

+347
-44
lines changed

common/player.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package common
33
import (
44
"time"
55

6-
r3 "github.com/golang/geo/r3"
6+
"github.com/golang/geo/r3"
77
st "github.com/markus-wa/demoinfocs-golang/sendtables"
88
)
99

@@ -114,6 +114,30 @@ func (p *Player) Weapons() []*Equipment {
114114
return res
115115
}
116116

117+
// IsSpottedBy returns true if the player has been spotted by the other player.
118+
func (p *Player) IsSpottedBy(other *Player) bool {
119+
if p.Entity == nil {
120+
return false
121+
}
122+
123+
// TODO extract ClientSlot() function
124+
clientSlot := other.EntityID - 1
125+
bit := uint(clientSlot)
126+
var mask st.IProperty
127+
if bit < 32 {
128+
mask = p.Entity.FindPropertyI("m_bSpottedByMask.000")
129+
} else {
130+
bit -= 32
131+
mask = p.Entity.FindPropertyI("m_bSpottedByMask.001")
132+
}
133+
return (mask.Value().IntVal & (1 << bit)) != 0
134+
}
135+
136+
// HasSpotted returns true if the player has spotted the other player.
137+
func (p *Player) HasSpotted(other *Player) bool {
138+
return other.IsSpottedBy(p)
139+
}
140+
117141
// AdditionalPlayerInformation contains mostly scoreboard information.
118142
type AdditionalPlayerInformation struct {
119143
Kills int

common/player_test.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import (
44
"testing"
55
"time"
66

7-
assert "github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/markus-wa/demoinfocs-golang/sendtables"
10+
"github.com/markus-wa/demoinfocs-golang/sendtables/fake"
811
)
912

1013
func TestPlayerActiveWeapon(t *testing.T) {
@@ -121,6 +124,64 @@ func TestPlayer_FlashDurationTimeRemaining_Fallback(t *testing.T) {
121124
assert.Equal(t, 2*time.Second, pl.FlashDurationTimeRemaining())
122125
}
123126

127+
func TestPlayer_IsSpottedBy_HasSpotted_True(t *testing.T) {
128+
pl := newPlayer(0)
129+
entity := new(fake.Entity)
130+
pl.Entity = entity
131+
pl.EntityID = 1
132+
prop := new(fake.Property)
133+
prop.On("Value").Return(sendtables.PropertyValue{IntVal: 2})
134+
entity.On("FindPropertyI", "m_bSpottedByMask.000").Return(prop)
135+
136+
other := newPlayer(0)
137+
other.EntityID = 2
138+
139+
assert.True(t, pl.IsSpottedBy(other))
140+
assert.True(t, other.HasSpotted(pl))
141+
}
142+
143+
func TestPlayer_IsSpottedBy_HasSpotted_False(t *testing.T) {
144+
pl := newPlayer(0)
145+
entity := new(fake.Entity)
146+
pl.Entity = entity
147+
pl.EntityID = 1
148+
prop := new(fake.Property)
149+
prop.On("Value").Return(sendtables.PropertyValue{IntVal: 0})
150+
entity.On("FindPropertyI", "m_bSpottedByMask.000").Return(prop)
151+
152+
other := newPlayer(0)
153+
other.EntityID = 2
154+
155+
assert.False(t, pl.IsSpottedBy(other))
156+
assert.False(t, other.HasSpotted(pl))
157+
}
158+
159+
func TestPlayer_IsSpottedBy_HasSpotted_BitOver32(t *testing.T) {
160+
pl := newPlayer(0)
161+
entity := new(fake.Entity)
162+
prop := new(fake.Property)
163+
prop.On("Value").Return(sendtables.PropertyValue{IntVal: 1})
164+
entity.On("FindPropertyI", "m_bSpottedByMask.001").Return(prop)
165+
pl.Entity = entity
166+
pl.EntityID = 1
167+
168+
other := newPlayer(0)
169+
other.EntityID = 33
170+
171+
assert.True(t, pl.IsSpottedBy(other))
172+
assert.True(t, other.HasSpotted(pl))
173+
}
174+
175+
func TestPlayer_IsSpottedBy_EntityNull(t *testing.T) {
176+
pl := new(Player)
177+
pl.EntityID = 1
178+
other := new(Player)
179+
other.EntityID = 2
180+
181+
assert.False(t, pl.IsSpottedBy(other))
182+
assert.False(t, other.HasSpotted(pl))
183+
}
184+
124185
func newPlayer(tick int) *Player {
125186
return NewPlayer(128, tickProvider(tick))
126187
}

datatables.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import (
44
"fmt"
55
"strings"
66

7-
r3 "github.com/golang/geo/r3"
7+
"github.com/golang/geo/r3"
88

9-
common "github.com/markus-wa/demoinfocs-golang/common"
10-
events "github.com/markus-wa/demoinfocs-golang/events"
9+
"github.com/markus-wa/demoinfocs-golang/common"
10+
"github.com/markus-wa/demoinfocs-golang/events"
1111
st "github.com/markus-wa/demoinfocs-golang/sendtables"
1212
)
1313

@@ -220,7 +220,7 @@ func (p *Parser) bindNewPlayer(playerEntity st.IEntity) {
220220
})
221221

222222
// General info
223-
playerEntity.FindProperty("m_iTeamNum").OnUpdate(func(val st.PropertyValue) {
223+
playerEntity.FindPropertyI("m_iTeamNum").OnUpdate(func(val st.PropertyValue) {
224224
pl.Team = common.Team(val.IntVal) // Need to cast to team so we can't use BindProperty
225225
pl.TeamState = p.gameState.Team(pl.Team)
226226
})
@@ -233,7 +233,7 @@ func (p *Parser) bindNewPlayer(playerEntity st.IEntity) {
233233

234234
playerEntity.BindProperty("m_angEyeAngles[1]", &pl.ViewDirectionX, st.ValTypeFloat32)
235235
playerEntity.BindProperty("m_angEyeAngles[0]", &pl.ViewDirectionY, st.ValTypeFloat32)
236-
playerEntity.FindProperty("m_flFlashDuration").OnUpdate(func(val st.PropertyValue) {
236+
playerEntity.FindPropertyI("m_flFlashDuration").OnUpdate(func(val st.PropertyValue) {
237237
if val.FloatVal == 0 {
238238
pl.FlashTick = 0
239239
} else {
@@ -254,7 +254,7 @@ func (p *Parser) bindNewPlayer(playerEntity st.IEntity) {
254254

255255
// Some demos have an additional prefix for player weapons weapon
256256
var wepPrefix string
257-
if playerEntity.FindProperty(playerWeaponPrefix+"000") != nil {
257+
if playerEntity.FindPropertyI(playerWeaponPrefix+"000") != nil {
258258
wepPrefix = playerWeaponPrefix
259259
} else {
260260
wepPrefix = playerWeaponPrePrefix + playerWeaponPrefix
@@ -264,7 +264,7 @@ func (p *Parser) bindNewPlayer(playerEntity st.IEntity) {
264264
var cache [maxWeapons]int
265265
for i := range cache {
266266
i2 := i // Copy for passing to handler
267-
playerEntity.FindProperty(wepPrefix + fmt.Sprintf("%03d", i)).OnUpdate(func(val st.PropertyValue) {
267+
playerEntity.FindPropertyI(wepPrefix + fmt.Sprintf("%03d", i)).OnUpdate(func(val st.PropertyValue) {
268268
entityID := val.IntVal & entityHandleIndexMask
269269
if entityID != entityHandleIndexMask {
270270
if cache[i2] != 0 {
@@ -289,7 +289,7 @@ func (p *Parser) bindNewPlayer(playerEntity st.IEntity) {
289289
}
290290

291291
// Active weapon
292-
playerEntity.FindProperty("m_hActiveWeapon").OnUpdate(func(val st.PropertyValue) {
292+
playerEntity.FindPropertyI("m_hActiveWeapon").OnUpdate(func(val st.PropertyValue) {
293293
pl.ActiveWeaponID = val.IntVal & entityHandleIndexMask
294294
})
295295

@@ -298,7 +298,7 @@ func (p *Parser) bindNewPlayer(playerEntity st.IEntity) {
298298
playerEntity.BindProperty("m_iAmmo."+fmt.Sprintf("%03d", i2), &pl.AmmoLeft[i2], st.ValTypeInt)
299299
}
300300

301-
playerEntity.FindProperty("m_bIsDefusing").OnUpdate(func(val st.PropertyValue) {
301+
playerEntity.FindPropertyI("m_bIsDefusing").OnUpdate(func(val st.PropertyValue) {
302302
if p.gameState.currentDefuser == pl && pl.IsDefusing && val.IntVal == 0 {
303303
p.eventDispatcher.Dispatch(events.BombDefuseAborted{Player: pl})
304304
p.gameState.currentDefuser = nil

datatables_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func fakePlayerEntity(id int) st.IEntity {
8484
destroyCallback = args.Get(0).(func())
8585
})
8686
entity.On("OnPositionUpdate", mock.Anything).Return()
87-
entity.On("FindProperty", mock.Anything).Return(new(st.Property))
87+
entity.On("FindPropertyI", mock.Anything).Return(new(st.Property))
8888
entity.On("BindProperty", mock.Anything, mock.Anything, mock.Anything).Return(new(st.Property))
8989
entity.On("Destroy").Run(func(mock.Arguments) {
9090
destroyCallback()

fake/game_state.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
st "github.com/markus-wa/demoinfocs-golang/sendtables"
99
)
1010

11+
var _ dem.IGameState = new(GameState)
12+
1113
// GameState is a mock for of demoinfocs.IGameState.
1214
type GameState struct {
1315
mock.Mock
@@ -28,6 +30,11 @@ func (gs *GameState) TeamTerrorists() *common.TeamState {
2830
return gs.Called().Get(0).(*common.TeamState)
2931
}
3032

33+
// Team is a mock-implementation of IGameState.Team().
34+
func (gs *GameState) Team(team common.Team) *common.TeamState {
35+
return gs.Called().Get(0).(*common.TeamState)
36+
}
37+
3138
// Participants is a mock-implementation of IGameState.Participants().
3239
func (gs *GameState) Participants() dem.IParticipants {
3340
return gs.Called().Get(0).(dem.IParticipants)

fake/parser.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
st "github.com/markus-wa/demoinfocs-golang/sendtables"
1414
)
1515

16+
var _ dem.IParser = new(Parser)
17+
1618
// Parser is a mock for of demoinfocs.IParser.
1719
type Parser struct {
1820
mock.Mock

fake/participants.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package fake
22

33
import (
4-
mock "github.com/stretchr/testify/mock"
4+
"github.com/stretchr/testify/mock"
55

6-
common "github.com/markus-wa/demoinfocs-golang/common"
6+
dem "github.com/markus-wa/demoinfocs-golang"
7+
"github.com/markus-wa/demoinfocs-golang/common"
78
)
89

10+
var _ dem.IParticipants = new(Participants)
11+
912
// Participants is a mock for of demoinfocs.IParticipants.
1013
type Participants struct {
1114
mock.Mock
@@ -45,3 +48,13 @@ func (ptcp *Participants) TeamMembers(team common.Team) []*common.Player {
4548
func (ptcp *Participants) FindByHandle(handle int) *common.Player {
4649
return ptcp.Called().Get(0).(*common.Player)
4750
}
51+
52+
// SpottersOf is a mock-implementation of IParticipants.SpottersOf().
53+
func (ptcp *Participants) SpottersOf(spotted *common.Player) []*common.Player {
54+
return ptcp.Called().Get(0).([]*common.Player)
55+
}
56+
57+
// SpottedBy is a mock-implementation of IParticipants.SpottedBy().
58+
func (ptcp *Participants) SpottedBy(spotter *common.Player) []*common.Player {
59+
return ptcp.Called().Get(0).([]*common.Player)
60+
}

game_events.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import (
44
"fmt"
55
"strconv"
66

7-
r3 "github.com/golang/geo/r3"
7+
"github.com/golang/geo/r3"
88

9-
common "github.com/markus-wa/demoinfocs-golang/common"
10-
events "github.com/markus-wa/demoinfocs-golang/events"
11-
msg "github.com/markus-wa/demoinfocs-golang/msg"
9+
"github.com/markus-wa/demoinfocs-golang/common"
10+
"github.com/markus-wa/demoinfocs-golang/events"
11+
"github.com/markus-wa/demoinfocs-golang/msg"
1212
)
1313

1414
func (p *Parser) handleGameEventList(gel *msg.CSVCMsg_GameEventList) {

game_state.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,23 @@ func (ptcp Participants) initalizeSliceFromByUserID() ([]*common.Player, map[int
228228
byUserID := ptcp.ByUserID()
229229
return make([]*common.Player, 0, len(byUserID)), byUserID
230230
}
231+
232+
// SpottersOf returns a list of all players who have spotted the passed player.
233+
func (ptcp Participants) SpottersOf(spotted *common.Player) (spotters []*common.Player) {
234+
for _, other := range ptcp.playersByUserID {
235+
if spotted.IsSpottedBy(other) {
236+
spotters = append(spotters, other)
237+
}
238+
}
239+
return
240+
}
241+
242+
// SpottedBy returns a list of all players that the passed player has spotted.
243+
func (ptcp Participants) SpottedBy(spotter *common.Player) (spotted []*common.Player) {
244+
for _, other := range ptcp.playersByUserID {
245+
if other.Entity != nil && other.IsSpottedBy(spotter) {
246+
spotted = append(spotted, other)
247+
}
248+
}
249+
return
250+
}

game_state_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/markus-wa/demoinfocs-golang/common"
99
st "github.com/markus-wa/demoinfocs-golang/sendtables"
10+
"github.com/markus-wa/demoinfocs-golang/sendtables/fake"
1011
)
1112

1213
func TestNewGameState(t *testing.T) {
@@ -180,6 +181,78 @@ func TestParticipants_Connected_SuppressNotConnected(t *testing.T) {
180181
assert.ElementsMatch(t, []*common.Player{pl}, allPlayers)
181182
}
182183

184+
func TestParticipants_SpottersOf(t *testing.T) {
185+
spotter1 := newPlayer()
186+
spotter1.EntityID = 1
187+
spotter2 := newPlayer()
188+
spotter2.EntityID = 35
189+
nonSpotter := newPlayer()
190+
nonSpotter.EntityID = 5
191+
192+
spotted := newPlayer()
193+
entity := new(fake.Entity)
194+
prop0 := new(fake.Property)
195+
prop0.On("Value").Return(st.PropertyValue{IntVal: 1})
196+
entity.On("FindPropertyI", "m_bSpottedByMask.000").Return(prop0)
197+
prop1 := new(fake.Property)
198+
prop1.On("Value").Return(st.PropertyValue{IntVal: 1 << 2})
199+
entity.On("FindPropertyI", "m_bSpottedByMask.001").Return(prop1)
200+
spotted.Entity = entity
201+
spotted.EntityID = 3
202+
203+
ptcps := Participants{
204+
playersByUserID: map[int]*common.Player{
205+
0: spotted,
206+
1: spotter1,
207+
2: spotter2,
208+
3: nonSpotter,
209+
},
210+
}
211+
212+
spotters := ptcps.SpottersOf(spotted)
213+
214+
assert.ElementsMatch(t, []*common.Player{spotter1, spotter2}, spotters)
215+
}
216+
217+
func TestParticipants_SpottedBy(t *testing.T) {
218+
spotted1 := newPlayer()
219+
spotted1.EntityID = 1
220+
spotted2 := newPlayer()
221+
spotted2.EntityID = 35
222+
223+
entity := new(fake.Entity)
224+
prop0 := new(fake.Property)
225+
prop0.On("Value").Return(st.PropertyValue{IntVal: 1})
226+
entity.On("FindPropertyI", "m_bSpottedByMask.000").Return(prop0)
227+
spotted1.Entity = entity
228+
spotted2.Entity = entity
229+
230+
unSpotted := newPlayer()
231+
unSpotted.EntityID = 5
232+
spotter := newPlayer()
233+
spotter.EntityID = 1
234+
235+
unSpottedEntity := new(fake.Entity)
236+
unSpottedProp := new(fake.Property)
237+
unSpottedProp.On("Value").Return(st.PropertyValue{IntVal: 0})
238+
unSpottedEntity.On("FindPropertyI", "m_bSpottedByMask.000").Return(unSpottedProp)
239+
unSpotted.Entity = unSpottedEntity
240+
spotter.Entity = unSpottedEntity
241+
242+
ptcps := Participants{
243+
playersByUserID: map[int]*common.Player{
244+
0: spotter,
245+
1: spotted1,
246+
2: spotted2,
247+
3: unSpotted,
248+
},
249+
}
250+
251+
spotted := ptcps.SpottedBy(spotter)
252+
253+
assert.ElementsMatch(t, []*common.Player{spotted1, spotted2}, spotted)
254+
}
255+
183256
func newPlayer() *common.Player {
184257
pl := common.NewPlayer(0, func() int { return 0 })
185258
pl.Entity = new(st.Entity)

0 commit comments

Comments
 (0)