Skip to content

Commit d03a884

Browse files
authored
Merge pull request #121 from markus-wa/player-spotted
Player spotting information [Player.IsSpottedBy()]
2 parents 03b772f + 1a1e245 commit d03a884

20 files changed

+451
-88
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: 37 additions & 29 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

@@ -95,15 +95,15 @@ func (p *Parser) bindBomb() {
9595
// Track bomb when it is being held by a player
9696
scPlayerC4 := p.stParser.ServerClasses().FindByName("CC4")
9797
scPlayerC4.OnEntityCreated(func(bombEntity *st.Entity) {
98-
bombEntity.FindProperty("m_hOwner").OnUpdate(func(val st.PropertyValue) {
98+
bombEntity.FindPropertyI("m_hOwner").OnUpdate(func(val st.PropertyValue) {
9999
bomb.Carrier = p.gameState.Participants().FindByHandle(val.IntVal)
100100
})
101101
})
102102
}
103103

104104
func (p *Parser) bindTeamStates() {
105105
p.stParser.ServerClasses().FindByName("CCSTeam").OnEntityCreated(func(entity *st.Entity) {
106-
team := entity.FindProperty("m_szTeamname").Value().StringVal
106+
team := entity.FindPropertyI("m_szTeamname").Value().StringVal
107107

108108
var s *common.TeamState
109109

@@ -127,7 +127,7 @@ func (p *Parser) bindTeamStates() {
127127
entity.BindProperty("m_szClanTeamname", &s.ClanName, st.ValTypeString)
128128
entity.BindProperty("m_szTeamFlagImage", &s.Flag, st.ValTypeString)
129129

130-
entity.FindProperty("m_scoreTotal").OnUpdate(func(val st.PropertyValue) {
130+
entity.FindPropertyI("m_scoreTotal").OnUpdate(func(val st.PropertyValue) {
131131
oldScore := s.Score
132132
s.Score = val.IntVal
133133

@@ -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,14 +298,23 @@ 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
305305
}
306306
pl.IsDefusing = val.IntVal != 0
307307
})
308308

309+
spottedByMaskProp := playerEntity.FindPropertyI("m_bSpottedByMask.000")
310+
if spottedByMaskProp != nil {
311+
spottersChanged := func(val st.PropertyValue) {
312+
p.eventDispatcher.Dispatch(events.PlayerSpottersChanged{Spotted: pl})
313+
}
314+
spottedByMaskProp.OnUpdate(spottersChanged)
315+
playerEntity.FindPropertyI("m_bSpottedByMask.001").OnUpdate(spottersChanged)
316+
}
317+
309318
if isNew && pl.SteamID != 0 {
310319
p.eventDispatcher.Dispatch(events.PlayerConnect{Player: pl})
311320
}
@@ -349,16 +358,16 @@ func (p *Parser) bindGrenadeProjectiles(entity *st.Entity) {
349358
p.nadeProjectileDestroyed(proj)
350359
})
351360

352-
entity.FindProperty("m_nModelIndex").OnUpdate(func(val st.PropertyValue) {
361+
entity.FindPropertyI("m_nModelIndex").OnUpdate(func(val st.PropertyValue) {
353362
proj.Weapon = p.grenadeModelIndices[val.IntVal]
354363
})
355364

356365
// @micvbang: not quite sure what the difference between Thrower and Owner is.
357-
entity.FindProperty("m_hThrower").OnUpdate(func(val st.PropertyValue) {
366+
entity.FindPropertyI("m_hThrower").OnUpdate(func(val st.PropertyValue) {
358367
proj.Thrower = p.gameState.Participants().FindByHandle(val.IntVal)
359368
})
360369

361-
entity.FindProperty("m_hOwnerEntity").OnUpdate(func(val st.PropertyValue) {
370+
entity.FindPropertyI("m_hOwnerEntity").OnUpdate(func(val st.PropertyValue) {
362371
proj.Owner = p.gameState.Participants().FindByHandle(val.IntVal)
363372
})
364373

@@ -370,8 +379,7 @@ func (p *Parser) bindGrenadeProjectiles(entity *st.Entity) {
370379

371380
// Some demos don't have this property as it seems
372381
// So we need to check for nil and can't send out bounce events if it's missing
373-
bounceProp := entity.FindProperty("m_nBounces")
374-
if bounceProp != nil {
382+
if bounceProp := entity.FindPropertyI("m_nBounces"); bounceProp != nil {
375383
bounceProp.OnUpdate(func(val st.PropertyValue) {
376384
if val.IntVal != 0 {
377385
p.eventDispatcher.Dispatch(events.GrenadeProjectileBounce{
@@ -405,19 +413,19 @@ func (p *Parser) bindWeapon(entity *st.Entity, wepType common.EquipmentElement)
405413
eq.EntityID = entityID
406414
eq.AmmoInMagazine = -1
407415

408-
entity.FindProperty("m_iClip1").OnUpdate(func(val st.PropertyValue) {
416+
entity.FindPropertyI("m_iClip1").OnUpdate(func(val st.PropertyValue) {
409417
eq.AmmoInMagazine = val.IntVal - 1
410418
})
411419

412420
// Only weapons with scopes have m_zoomLevel property
413-
if entity.FindProperty("m_zoomLevel") != nil {
414-
entity.BindProperty("m_zoomLevel", &eq.ZoomLevel, st.ValTypeInt)
421+
if zoomLvlProp := entity.FindPropertyI("m_zoomLevel"); zoomLvlProp != nil {
422+
zoomLvlProp.Bind(&eq.ZoomLevel, st.ValTypeInt)
415423
}
416424

417-
eq.AmmoType = entity.FindProperty("LocalWeaponData.m_iPrimaryAmmoType").Value().IntVal
425+
eq.AmmoType = entity.FindPropertyI("LocalWeaponData.m_iPrimaryAmmoType").Value().IntVal
418426

419427
// Detect alternative weapons (P2k -> USP, M4A4 -> M4A1-S etc.)
420-
modelIndex := entity.FindProperty("m_nModelIndex").Value().IntVal
428+
modelIndex := entity.FindPropertyI("m_nModelIndex").Value().IntVal
421429
eq.OriginalString = p.modelPreCache[modelIndex]
422430

423431
wepFix := func(defaultName, altName string, alt common.EquipmentElement) {
@@ -459,13 +467,13 @@ func (p *Parser) bindNewInferno(entity *st.Entity) {
459467

460468
origin := entity.Position()
461469
nFires := 0
462-
entity.FindProperty("m_fireCount").OnUpdate(func(val st.PropertyValue) {
470+
entity.FindPropertyI("m_fireCount").OnUpdate(func(val st.PropertyValue) {
463471
for i := nFires; i < val.IntVal; i++ {
464472
iStr := fmt.Sprintf("%03d", i)
465473
offset := r3.Vector{
466-
X: float64(entity.FindProperty("m_fireXDelta." + iStr).Value().IntVal),
467-
Y: float64(entity.FindProperty("m_fireYDelta." + iStr).Value().IntVal),
468-
Z: float64(entity.FindProperty("m_fireZDelta." + iStr).Value().IntVal),
474+
X: float64(entity.FindPropertyI("m_fireXDelta." + iStr).Value().IntVal),
475+
Y: float64(entity.FindPropertyI("m_fireYDelta." + iStr).Value().IntVal),
476+
Z: float64(entity.FindPropertyI("m_fireZDelta." + iStr).Value().IntVal),
469477
}
470478

471479
fire := &common.Fire{Vector: origin.Add(offset), IsBurning: true}
@@ -499,7 +507,7 @@ func (p *Parser) bindGameRules() {
499507

500508
gameRules := p.ServerClasses().FindByName("CCSGameRulesProxy")
501509
gameRules.OnEntityCreated(func(entity *st.Entity) {
502-
entity.FindProperty(grPrefix("m_gamePhase")).OnUpdate(func(val st.PropertyValue) {
510+
entity.FindPropertyI(grPrefix("m_gamePhase")).OnUpdate(func(val st.PropertyValue) {
503511
oldGamePhase := p.gameState.gamePhase
504512
p.gameState.gamePhase = common.GamePhase(val.IntVal)
505513

@@ -517,7 +525,7 @@ func (p *Parser) bindGameRules() {
517525
})
518526

519527
entity.BindProperty(grPrefix("m_totalRoundsPlayed"), &p.gameState.totalRoundsPlayed, st.ValTypeInt)
520-
entity.FindProperty(grPrefix("m_bWarmupPeriod")).OnUpdate(func(val st.PropertyValue) {
528+
entity.FindPropertyI(grPrefix("m_bWarmupPeriod")).OnUpdate(func(val st.PropertyValue) {
521529
oldIsWarmupPeriod := p.gameState.isWarmupPeriod
522530
p.gameState.isWarmupPeriod = val.IntVal == 1
523531

@@ -527,7 +535,7 @@ func (p *Parser) bindGameRules() {
527535
})
528536
})
529537

530-
entity.FindProperty(grPrefix("m_bHasMatchStarted")).OnUpdate(func(val st.PropertyValue) {
538+
entity.FindPropertyI(grPrefix("m_bHasMatchStarted")).OnUpdate(func(val st.PropertyValue) {
531539
oldMatchStarted := p.gameState.isMatchStarted
532540
p.gameState.isMatchStarted = val.IntVal == 1
533541

0 commit comments

Comments
 (0)