Skip to content

Commit f425f2f

Browse files
committed
S2 PlayerController + PlayerPawn
1 parent 9a30b33 commit f425f2f

File tree

13 files changed

+208
-107
lines changed

13 files changed

+208
-107
lines changed

pkg/demoinfocs/datatables.go

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,15 +192,15 @@ func (p *parser) bindBombSites() {
192192
func (p *parser) bindPlayers() {
193193
if p.isSource2() {
194194
p.stParser.ServerClasses().FindByName("CCSPlayerPawn").OnEntityCreated(func(player st.Entity) {
195-
p.bindNewPlayer(player)
195+
p.bindNewPlayerPawnS2(player)
196196
})
197197
} else {
198198
p.stParser.ServerClasses().FindByName("CCSPlayerResource").OnEntityCreated(func(entity st.Entity) {
199199
p.gameState.playerResourceEntity = entity
200200
})
201201

202202
p.stParser.ServerClasses().FindByName("CCSPlayer").OnEntityCreated(func(player st.Entity) {
203-
p.bindNewPlayer(player)
203+
p.bindNewPlayerS1(player)
204204
})
205205
}
206206
}
@@ -244,7 +244,7 @@ func (p *parser) getOrCreatePlayer(entityID int, rp *common.PlayerInfo) (isNew b
244244
}
245245

246246
//nolint:funlen
247-
func (p *parser) bindNewPlayer(playerEntity st.Entity) {
247+
func (p *parser) bindNewPlayerS1(playerEntity st.Entity) {
248248
entityID := playerEntity.ID()
249249
rp := p.rawPlayers[entityID-1]
250250

@@ -282,12 +282,96 @@ func (p *parser) bindNewPlayer(playerEntity st.Entity) {
282282
pl.FlashDuration = val.FloatVal
283283
})
284284

285+
p.bindPlayerWeapons(playerEntity, pl) // FIXME: make weapons work for S2
286+
287+
for i := 0; i < 32; i++ {
288+
i2 := i // Copy so it stays the same
289+
playerEntity.BindProperty("m_iAmmo."+fmt.Sprintf("%03d", i2), &pl.AmmoLeft[i2], st.ValTypeInt)
290+
}
291+
292+
activeWep := playerEntity.Property("m_hActiveWeapon")
293+
294+
activeWep.OnUpdate(func(val st.PropertyValue) {
295+
pl.IsReloading = false
296+
})
297+
298+
playerEntity.Property("m_bIsDefusing").OnUpdate(func(val st.PropertyValue) {
299+
if p.gameState.currentDefuser == pl && pl.IsDefusing && val.IntVal == 0 {
300+
p.eventDispatcher.Dispatch(events.BombDefuseAborted{Player: pl})
301+
p.gameState.currentDefuser = nil
302+
}
303+
304+
pl.IsDefusing = val.IntVal != 0
305+
})
306+
307+
spottedByMaskProp := playerEntity.Property("m_bSpottedByMask.000")
308+
if spottedByMaskProp != nil {
309+
spottersChanged := func(val st.PropertyValue) {
310+
p.eventDispatcher.Dispatch(events.PlayerSpottersChanged{Spotted: pl})
311+
}
312+
313+
spottedByMaskProp.OnUpdate(spottersChanged)
314+
playerEntity.Property("m_bSpottedByMask.001").OnUpdate(spottersChanged)
315+
}
316+
317+
if isNew {
318+
if pl.SteamID64 != 0 {
319+
p.eventDispatcher.Dispatch(events.PlayerConnect{Player: pl})
320+
} else {
321+
p.eventDispatcher.Dispatch(events.BotConnect{Player: pl})
322+
}
323+
}
324+
}
325+
326+
func (p *parser) bindNewPlayerPawnS2(pawnEntity st.Entity) {
327+
controllerHandle := pawnEntity.Property("m_hController").Value().Handle()
328+
329+
controllerEntity := p.gameState.EntityByHandle(controllerHandle)
330+
331+
controllerEntityID := controllerEntity.ID()
332+
333+
rp := p.rawPlayers[controllerEntityID-1]
334+
335+
isNew, pl := p.getOrCreatePlayer(controllerEntityID, rp)
336+
337+
pl.EntityID = controllerEntityID
338+
pl.Entity = controllerEntity
339+
pl.IsConnected = true
340+
341+
controllerEntity.OnDestroy(func() {
342+
delete(p.gameState.playersByEntityID, controllerEntityID)
343+
pl.Entity = nil
344+
})
345+
346+
// Position
347+
controllerEntity.OnPositionUpdate(func(pos r3.Vector) {
348+
if pl.IsAlive() {
349+
pl.LastAlivePosition = pos
350+
}
351+
})
352+
353+
// General info
354+
pawnEntity.Property("m_iTeamNum").OnUpdate(func(val st.PropertyValue) {
355+
pl.Team = common.Team(val.IntVal)
356+
pl.TeamState = p.gameState.Team(pl.Team)
357+
})
358+
359+
pawnEntity.Property("m_flFlashDuration").OnUpdate(func(val st.PropertyValue) {
360+
if val.FloatVal == 0 {
361+
pl.FlashTick = 0
362+
} else {
363+
pl.FlashTick = p.gameState.ingameTick
364+
}
365+
366+
pl.FlashDuration = val.FloatVal
367+
})
368+
285369
if !p.isSource2() {
286-
p.bindPlayerWeapons(playerEntity, pl) // FIXME: make weapons work for S2
370+
p.bindPlayerWeapons(controllerEntity, pl) // FIXME: make weapons work for S2
287371

288372
for i := 0; i < 32; i++ {
289373
i2 := i // Copy so it stays the same
290-
playerEntity.BindProperty("m_iAmmo."+fmt.Sprintf("%03d", i2), &pl.AmmoLeft[i2], st.ValTypeInt)
374+
controllerEntity.BindProperty("m_iAmmo."+fmt.Sprintf("%03d", i2), &pl.AmmoLeft[i2], st.ValTypeInt)
291375
}
292376
}
293377

@@ -297,18 +381,18 @@ func (p *parser) bindNewPlayer(playerEntity st.Entity) {
297381
)
298382

299383
if p.isSource2() {
300-
activeWep = playerEntity.Property("m_pWeaponServices.m_hActiveWeapon")
384+
activeWep = pawnEntity.Property("m_pWeaponServices.m_hActiveWeapon")
301385
spottedByPrefix = "m_bSpottedByMask.000"
302386
} else {
303-
activeWep = playerEntity.Property("m_hActiveWeapon")
387+
activeWep = pawnEntity.Property("m_hActiveWeapon")
304388
spottedByPrefix = "m_bSpottedByMask.00"
305389
}
306390

307391
activeWep.OnUpdate(func(val st.PropertyValue) {
308392
pl.IsReloading = false
309393
})
310394

311-
playerEntity.Property("m_bIsDefusing").OnUpdate(func(val st.PropertyValue) {
395+
pawnEntity.Property("m_bIsDefusing").OnUpdate(func(val st.PropertyValue) {
312396
if p.gameState.currentDefuser == pl && pl.IsDefusing && val.IntVal == 0 {
313397
p.eventDispatcher.Dispatch(events.BombDefuseAborted{Player: pl})
314398
p.gameState.currentDefuser = nil
@@ -317,14 +401,14 @@ func (p *parser) bindNewPlayer(playerEntity st.Entity) {
317401
pl.IsDefusing = val.IntVal != 0
318402
})
319403

320-
spottedByMaskProp := playerEntity.Property(spottedByPrefix + "0")
404+
spottedByMaskProp := pawnEntity.Property(spottedByPrefix + "0")
321405
if spottedByMaskProp != nil {
322406
spottersChanged := func(val st.PropertyValue) {
323407
p.eventDispatcher.Dispatch(events.PlayerSpottersChanged{Spotted: pl})
324408
}
325409

326410
spottedByMaskProp.OnUpdate(spottersChanged)
327-
playerEntity.Property(spottedByPrefix + "1").OnUpdate(spottersChanged)
411+
pawnEntity.Property(spottedByPrefix + "1").OnUpdate(spottersChanged)
328412
}
329413

330414
if isNew {

pkg/demoinfocs/datatables_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ func TestParser_BindNewPlayer_Issue98(t *testing.T) {
3636
}
3737

3838
bot := fakePlayerEntity(1)
39-
p.bindNewPlayer(bot)
39+
p.bindNewPlayerS1(bot)
4040
bot.Destroy()
4141

4242
player := fakePlayerEntity(2)
43-
p.bindNewPlayer(player)
43+
p.bindNewPlayerS1(player)
4444

4545
assert.Len(t, p.GameState().Participants().Connected(), 1)
4646
}
@@ -58,13 +58,13 @@ func TestParser_BindNewPlayer_Issue98_Reconnect(t *testing.T) {
5858
}
5959

6060
player := fakePlayerEntity(1)
61-
p.bindNewPlayer(player)
61+
p.bindNewPlayerS1(player)
6262
player.Destroy()
6363

6464
p.RegisterEventHandler(func(events.PlayerConnect) {
6565
t.Error("expected no more PlayerConnect events but got one")
6666
})
67-
p.bindNewPlayer(player)
67+
p.bindNewPlayerS1(player)
6868

6969
assert.Len(t, p.GameState().Participants().All(), 1)
7070
}
@@ -100,7 +100,7 @@ func testPlayerSpotted(t *testing.T, propName string) {
100100

101101
spotted.On("Property", propName).Return(spottedByProp0)
102102
configurePlayerEntityMock(1, spotted)
103-
p.bindNewPlayer(spotted)
103+
p.bindNewPlayerS1(spotted)
104104

105105
var actual events.PlayerSpottersChanged
106106
p.RegisterEventHandler(func(e events.PlayerSpottersChanged) {

pkg/demoinfocs/fake/game_state.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,8 @@ func (gs *GameState) PlayerResourceEntity() st.Entity {
9999
func (gs *GameState) Hostages() []*common.Hostage {
100100
return gs.Called().Get(0).([]*common.Hostage)
101101
}
102+
103+
// EntityByHandle is a mock-implementation of GameState.EntityByHandle().
104+
func (gs *GameState) EntityByHandle(handle uint64) st.Entity {
105+
return gs.Called(handle).Get(0).(st.Entity)
106+
}

pkg/demoinfocs/game_state.go

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,24 @@ func (gs gameState) IsMatchStarted() bool {
170170

171171
// PlayerResourceEntity returns the game's CCSPlayerResource entity.
172172
// Contains scoreboard information and more.
173-
func (gs *gameState) PlayerResourceEntity() st.Entity {
173+
func (gs gameState) PlayerResourceEntity() st.Entity {
174174
return gs.playerResourceEntity
175175
}
176176

177+
func entityIDFromHandle(handle uint64) int {
178+
if handle == constants.InvalidEntityHandle {
179+
return -1
180+
}
181+
182+
return int(handle & constants.EntityHandleIndexMask)
183+
}
184+
185+
// EntityByHandle returns the entity corresponding to the given handle.
186+
// Returns nil if the handle is invalid.
187+
func (gs gameState) EntityByHandle(handle uint64) st.Entity {
188+
return gs.entities[entityIDFromHandle(handle)]
189+
}
190+
177191
func newGameState(demoInfo demoInfoProvider) *gameState {
178192
gs := &gameState{
179193
playersByEntityID: make(map[int]*common.Player),
@@ -357,34 +371,22 @@ func (ptcp participants) TeamMembers(team common.Team) []*common.Player {
357371
return res
358372
}
359373

360-
// FindByHandle attempts to find a player by his entity-handle.
374+
// FindByHandle64 attempts to find a player by his entity-handle.
361375
// The entity-handle is often used in entity-properties when referencing other entities such as a weapon's owner.
362376
//
363377
// Returns nil if not found or if handle == invalidEntityHandle (used when referencing no entity).
364-
//
365-
// Deprecated: Use FindByHandle64 instead.
366-
func (ptcp participants) FindByHandle(handle int) *common.Player {
367-
if handle == constants.InvalidEntityHandle {
368-
return nil
369-
}
370-
371-
entityID := handle & constants.EntityHandleIndexMask
372-
373-
return ptcp.playersByEntityID[entityID]
378+
func (ptcp participants) FindByHandle64(handle uint64) *common.Player {
379+
return ptcp.playersByEntityID[entityIDFromHandle(handle)]
374380
}
375381

376-
// FindByHandle64 attempts to find a player by his entity-handle.
382+
// FindByHandle attempts to find a player by his entity-handle.
377383
// The entity-handle is often used in entity-properties when referencing other entities such as a weapon's owner.
378384
//
379385
// Returns nil if not found or if handle == invalidEntityHandle (used when referencing no entity).
380-
func (ptcp participants) FindByHandle64(handle uint64) *common.Player {
381-
if handle == constants.InvalidEntityHandle {
382-
return nil
383-
}
384-
385-
entityID := handle & constants.EntityHandleIndexMask
386-
387-
return ptcp.playersByEntityID[int(entityID)]
386+
//
387+
// Deprecated: Use FindByHandle64 instead.
388+
func (ptcp participants) FindByHandle(handle int) *common.Player {
389+
return ptcp.FindByHandle64(uint64(handle))
388390
}
389391

390392
func (ptcp participants) initializeSliceFromByUserID() ([]*common.Player, map[int]*common.Player) {

pkg/demoinfocs/game_state_interface.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,7 @@ type GameState interface {
6060
// PlayerResourceEntity returns the game's CCSPlayerResource entity.
6161
// Contains scoreboard information and more.
6262
PlayerResourceEntity() st.Entity
63+
// EntityByHandle returns the entity corresponding to the given handle.
64+
// Returns nil if the handle is invalid.
65+
EntityByHandle(handle uint64) st.Entity
6366
}

pkg/demoinfocs/net_messages.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
events "github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/events"
1313
msg "github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/msg"
1414
"github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/msgs2"
15+
"github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/sendtables"
1516
)
1617

1718
func (p *parser) handlePacketEntitiesS1(pe *msg.CSVCMsg_PacketEntities) {
@@ -52,6 +53,16 @@ func (p *parser) handlePacketEntitiesS1(pe *msg.CSVCMsg_PacketEntities) {
5253
p.poolBitReader(r)
5354
}
5455

56+
func (p *parser) onEntity(e sendtables.Entity, op sendtables.EntityOp) error {
57+
if op&sendtables.EntityOpCreated > 0 {
58+
p.gameState.entities[e.ID()] = e
59+
} else if op&sendtables.EntityOpDeleted > 0 {
60+
delete(p.gameState.entities, e.ID())
61+
}
62+
63+
return nil
64+
}
65+
5566
func (p *parser) handleSetConVar(setConVar *msg.CNETMsg_SetConVar) {
5667
updated := make(map[string]string)
5768
for _, cvar := range setConVar.Convars.Cvars {

pkg/demoinfocs/parser.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type sendTableParser interface {
3030
OnDemoClassInfo(m *msgs2.CDemoClassInfo) error
3131
OnServerInfo(m *msgs2.CSVCMsg_ServerInfo) error
3232
OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error
33+
OnEntity(h st.EntityHandler)
3334
}
3435

3536
type createStringTable struct {

pkg/demoinfocs/parsing.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ func (p *parser) ParseHeader() (common.DemoHeader, error) {
7272

7373
p.stParser = sendtables2.NewParser()
7474

75+
p.stParser.OnEntity(p.onEntity)
76+
7577
p.RegisterNetMessageHandler(p.stParser.OnServerInfo)
7678
p.RegisterNetMessageHandler(p.stParser.OnPacketEntities)
7779

pkg/demoinfocs/participants_interface.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ type Participants interface {
3737
// TeamMembers returns all players belonging to the requested team at this time.
3838
// The returned slice is a snapshot and is not updated on changes.
3939
TeamMembers(team common.Team) []*common.Player
40+
// FindByHandle64 attempts to find a player by his entity-handle.
41+
// The entity-handle is often used in entity-properties when referencing other entities such as a weapon's owner.
42+
//
43+
// Returns nil if not found or if handle == invalidEntityHandle (used when referencing no entity).
44+
FindByHandle64(handle uint64) *common.Player
4045
// FindByHandle attempts to find a player by his entity-handle.
4146
// The entity-handle is often used in entity-properties when referencing other entities such as a weapon's owner.
4247
//
4348
// Returns nil if not found or if handle == invalidEntityHandle (used when referencing no entity).
4449
//
4550
// Deprecated: Use FindByHandle64 instead.
4651
FindByHandle(handle int) *common.Player
47-
// FindByHandle64 attempts to find a player by his entity-handle.
48-
// The entity-handle is often used in entity-properties when referencing other entities such as a weapon's owner.
49-
//
50-
// Returns nil if not found or if handle == invalidEntityHandle (used when referencing no entity).
51-
FindByHandle64(handle uint64) *common.Player
5252
// SpottersOf returns a list of all players who have spotted the passed player.
5353
// This is NOT "Line of Sight" / FOV - look up "CSGO TraceRay" for that.
5454
// May not behave as expected with multiple spotters.

pkg/demoinfocs/sendtables/entity_interface.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package sendtables
44

55
import (
66
"github.com/golang/geo/r3"
7-
87
bit "github.com/markus-wa/demoinfocs-golang/v3/internal/bitread"
98
)
109

0 commit comments

Comments
 (0)