Skip to content

Commit 55d16e7

Browse files
authored
Merge pull request #155 from markus-wa/issue/132-player-additional-status-info
add Player.IsPlanting, Player.IsReloading and Player.IsAirborne()
2 parents 1bcd368 + 5cd67ef commit 55d16e7

File tree

8 files changed

+119
-17
lines changed

8 files changed

+119
-17
lines changed

common/player.go

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

11+
const (
12+
maxEdictBits = 11
13+
entityHandleSerialNumberBits = 10
14+
entityHandleBits = maxEdictBits + entityHandleSerialNumberBits
15+
invalidEntityHandle = (1 << entityHandleBits) - 1
16+
)
17+
1118
// Player contains mostly game-relevant player information.
1219
type Player struct {
1320
demoInfoProvider demoInfoProvider // provider for demo info such as tick-rate or current tick
@@ -40,6 +47,8 @@ type Player struct {
4047
IsConnected bool
4148
IsDucking bool
4249
IsDefusing bool
50+
IsPlanting bool
51+
IsReloading bool
4352
HasDefuseKit bool
4453
HasHelmet bool
4554
}
@@ -65,6 +74,17 @@ func (p *Player) IsBlinded() bool {
6574
return p.FlashDurationTimeRemaining() > 0
6675
}
6776

77+
// IsAirborne returns true if the player is jumping or falling.
78+
func (p *Player) IsAirborne() bool {
79+
if p.Entity == nil {
80+
return false
81+
}
82+
83+
groundEntityHandle := p.Entity.FindPropertyI("m_hGroundEntity").Value().IntVal
84+
85+
return groundEntityHandle == invalidEntityHandle
86+
}
87+
6888
// FlashDurationTime returns the duration of the blinding effect as time.Duration instead of float32 in seconds.
6989
// Will return 0 if IsBlinded() returns false.
7090
func (p *Player) FlashDurationTime() time.Duration {

common/player_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,22 @@ func TestPlayer_IsScoped(t *testing.T) {
190190
assert.True(t, pl.IsScoped())
191191
}
192192

193+
func TestPlayer_IsAirborne_NilEntity(t *testing.T) {
194+
pl := new(Player)
195+
196+
assert.False(t, pl.IsAirborne())
197+
}
198+
199+
func TestPlayer_IsAirborne(t *testing.T) {
200+
pl := playerWithProperty("m_hGroundEntity", st.PropertyValue{IntVal: 0})
201+
202+
assert.False(t, pl.IsAirborne())
203+
204+
pl = playerWithProperty("m_hGroundEntity", st.PropertyValue{IntVal: 2097151})
205+
206+
assert.True(t, pl.IsAirborne())
207+
}
208+
193209
func newPlayer(tick int) *Player {
194210
return NewPlayer(mockDemoInfoProvider(128, tick))
195211
}

datatables.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,27 @@ func (p *Parser) bindEntities() {
7272
func (p *Parser) bindBomb() {
7373
bomb := &p.gameState.bomb
7474

75-
// Track bomb when it is not held by a player
76-
scDroppedC4 := p.stParser.ServerClasses().FindByName("CC4")
77-
scDroppedC4.OnEntityCreated(func(bomb *st.Entity) {
78-
bomb.OnPositionUpdate(func(pos r3.Vector) {
75+
// Track bomb when it is dropped on the ground or being held by a player
76+
scC4 := p.stParser.ServerClasses().FindByName("CC4")
77+
scC4.OnEntityCreated(func(bombEntity *st.Entity) {
78+
bombEntity.OnPositionUpdate(func(pos r3.Vector) {
7979
// Bomb only has a position when not held by a player
80-
p.gameState.bomb.Carrier = nil
80+
bomb.Carrier = nil
8181

82-
p.gameState.bomb.LastOnGroundPosition = pos
82+
bomb.LastOnGroundPosition = pos
83+
})
84+
85+
bombEntity.FindPropertyI("m_hOwner").OnUpdate(func(val st.PropertyValue) {
86+
bomb.Carrier = p.gameState.Participants().FindByHandle(val.IntVal)
87+
})
88+
89+
bombEntity.FindPropertyI("m_bStartedArming").OnUpdate(func(val st.PropertyValue) {
90+
if val.IntVal != 0 {
91+
p.gameState.currentPlanter = bomb.Carrier
92+
} else if p.gameState.currentPlanter != nil {
93+
p.gameState.currentPlanter.IsPlanting = false
94+
p.eventDispatcher.Dispatch(events.BombPlantAborted{Player: p.gameState.currentPlanter})
95+
}
8396
})
8497
})
8598

@@ -91,14 +104,6 @@ func (p *Parser) bindBomb() {
91104

92105
bomb.LastOnGroundPosition = bombEntity.Position()
93106
})
94-
95-
// Track bomb when it is being held by a player
96-
scPlayerC4 := p.stParser.ServerClasses().FindByName("CC4")
97-
scPlayerC4.OnEntityCreated(func(bombEntity *st.Entity) {
98-
bombEntity.FindPropertyI("m_hOwner").OnUpdate(func(val st.PropertyValue) {
99-
bomb.Carrier = p.gameState.Participants().FindByHandle(val.IntVal)
100-
})
101-
})
102107
}
103108

104109
func (p *Parser) bindTeamStates() {
@@ -299,6 +304,7 @@ func (p *Parser) bindNewPlayer(playerEntity st.IEntity) {
299304

300305
// Active weapon
301306
playerEntity.FindPropertyI("m_hActiveWeapon").OnUpdate(func(val st.PropertyValue) {
307+
pl.IsReloading = false
302308
pl.ActiveWeaponID = val.IntVal & entityHandleIndexMask
303309
})
304310

@@ -426,6 +432,10 @@ func (p *Parser) bindWeapon(entity *st.Entity, wepType common.EquipmentElement)
426432

427433
entity.FindPropertyI("m_iClip1").OnUpdate(func(val st.PropertyValue) {
428434
eq.AmmoInMagazine = val.IntVal - 1
435+
436+
if eq.Owner != nil {
437+
eq.Owner.IsReloading = false
438+
}
429439
})
430440
// Some weapons in some demos might be missing this property
431441
if reserveAmmoProp := entity.FindPropertyI("m_iPrimaryReserveAmmoCount"); reserveAmmoProp != nil {

demoinfocs_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,46 @@ func TestDemoInfoCs(t *testing.T) {
9898
}
9999
})
100100

101+
// bomb planting checks
102+
p.RegisterEventHandler(func(begin events.BombPlantBegin) {
103+
if !begin.Player.IsPlanting {
104+
t.Error("Player started planting but IsPlanting is false")
105+
}
106+
})
107+
p.RegisterEventHandler(func(abort events.BombPlantAborted) {
108+
if abort.Player.IsPlanting {
109+
t.Error("Player aborted planting but IsPlanting is true")
110+
}
111+
})
112+
p.RegisterEventHandler(func(planted events.BombPlanted) {
113+
if planted.Player.IsPlanting {
114+
t.Error("Player finished planting but IsPlanting is true")
115+
}
116+
})
117+
118+
// airborne checks
119+
// we don't check RoundStart or RoundFreezetimeEnd since players may spawn airborne
120+
p.RegisterEventHandler(func(plantBegin events.BombPlantBegin) {
121+
if plantBegin.Player.IsAirborne() {
122+
t.Error("Player is airborne during plant")
123+
}
124+
})
125+
126+
// reload checks
127+
p.RegisterEventHandler(func(reload events.WeaponReload) {
128+
if !reload.Player.IsReloading {
129+
t.Error("Player started reloading but IsReloading is false")
130+
}
131+
})
132+
133+
p.RegisterEventHandler(func(start events.RoundFreezetimeEnd) {
134+
for _, pl := range p.GameState().Participants().All() {
135+
if pl.IsReloading {
136+
t.Error("Player is reloading at the start of the round")
137+
}
138+
}
139+
})
140+
101141
// Check some things at match start
102142
p.RegisterEventHandler(func(events.MatchStart) {
103143
participants := gs.Participants()

events/events.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,13 @@ type BombPlantBegin struct {
286286
BombEvent
287287
}
288288

289+
// BombPlantAbort signals the abortion of a plant.
290+
type BombPlantAborted struct {
291+
Player *common.Player
292+
}
293+
294+
func (BombPlantAborted) implementsBombEventIf() {}
295+
289296
// BombPlanted signals that the bomb has been planted.
290297
type BombPlanted struct {
291298
BombEvent

game_events.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,10 @@ func (geh gameEventHandler) weaponFire(data map[string]*msg.CSVCMsg_GameEventKey
281281
}
282282

283283
func (geh gameEventHandler) weaponReload(data map[string]*msg.CSVCMsg_GameEventKeyT) {
284+
pl := geh.playerByUserID32(data["userid"].GetValShort())
285+
pl.IsReloading = true
284286
geh.dispatch(events.WeaponReload{
285-
Player: geh.playerByUserID32(data["userid"].GetValShort()),
287+
Player: pl,
286288
})
287289
}
288290

@@ -433,11 +435,17 @@ func (geh gameEventHandler) playerTeam(data map[string]*msg.CSVCMsg_GameEventKey
433435
}
434436

435437
func (geh gameEventHandler) bombBeginPlant(data map[string]*msg.CSVCMsg_GameEventKeyT) {
436-
geh.dispatch(events.BombPlantBegin{BombEvent: geh.bombEvent(data)})
438+
event := events.BombPlantBegin{BombEvent: geh.bombEvent(data)}
439+
event.Player.IsPlanting = true
440+
geh.parser.gameState.currentPlanter = event.Player
441+
geh.dispatch(event)
437442
}
438443

439444
func (geh gameEventHandler) bombPlanted(data map[string]*msg.CSVCMsg_GameEventKeyT) {
440-
geh.dispatch(events.BombPlanted{BombEvent: geh.bombEvent(data)})
445+
event := events.BombPlanted{BombEvent: geh.bombEvent(data)}
446+
event.Player.IsPlanting = false
447+
geh.parser.gameState.currentPlanter = nil
448+
geh.dispatch(event)
441449
}
442450

443451
func (geh gameEventHandler) bombDefused(data map[string]*msg.CSVCMsg_GameEventKeyT) {

game_state.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type GameState struct {
2626
isMatchStarted bool
2727
lastFlasher *common.Player // Last player whose flash exploded, used to find the attacker for player_blind events
2828
currentDefuser *common.Player // Player currently defusing the bomb, if any
29+
currentPlanter *common.Player // Player currently planting the bomb, if any
2930
}
3031

3132
type ingameTickNumber int

test/default.golden

49 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)