Repository: markus-wa/demoinfocs-golang
Pull request #491 (2023-12-30 12:06:25)

fix: panic on bot_takeover events
The demo from this [issue](https://github.com/akiver/cs-demo-manager/issues/725) has the problem described and fixed in this [PR](https://github.com/markus-wa/demoinfocs-golang/pull/489).

The other PR fixes the first panic, but another happens due to a missing prop when a player takes control of a bot.

The prop `m_iControlledBotEntIndex` expected in `player.ControlledBot()` doesn't exist in CS2 demos.
As a replacement I use the prop `m_hOriginalControllerOfCurrentPawn`.

I also had to change the signature of `FindPlayerByHandle` to `FindPlayerByHandle(handle uint64) *Player` since the controllers are `uin64` values.



Patch:
diff --git a/pkg/demoinfocs/common/hostage.go b/pkg/demoinfocs/common/hostage.go
index 123227c..9431844 100644
--- a/pkg/demoinfocs/common/hostage.go
+++ b/pkg/demoinfocs/common/hostage.go
@@ -57,7 +57,7 @@ func (hostage *Hostage) Leader() *Player {
 		return hostage.demoInfoProvider.FindPlayerByPawnHandle(getUInt64(hostage.Entity, "m_leader"))
 	}
 
-	return hostage.demoInfoProvider.FindPlayerByHandle(getInt(hostage.Entity, "m_leader"))
+	return hostage.demoInfoProvider.FindPlayerByHandle(uint64(getInt(hostage.Entity, "m_leader")))
 }
 
 // NewHostage creates a hostage.
diff --git a/pkg/demoinfocs/common/inferno.go b/pkg/demoinfocs/common/inferno.go
index 9b62725..4c4a892 100644
--- a/pkg/demoinfocs/common/inferno.go
+++ b/pkg/demoinfocs/common/inferno.go
@@ -57,7 +57,7 @@ func (inf *Inferno) Thrower() *Player {
 		return inf.demoInfoProvider.FindPlayerByPawnHandle(handleProp.Handle())
 	}
 
-	return inf.demoInfoProvider.FindPlayerByHandle(handleProp.Int())
+	return inf.demoInfoProvider.FindPlayerByHandle(uint64(handleProp.Int()))
 }
 
 // Fires returns all fires (past + present).
diff --git a/pkg/demoinfocs/common/player.go b/pkg/demoinfocs/common/player.go
index c65fb0c..33f63d3 100644
--- a/pkg/demoinfocs/common/player.go
+++ b/pkg/demoinfocs/common/player.go
@@ -347,9 +347,15 @@ func (p *Player) ControlledBot() *Player {
 		return nil
 	}
 
-	botHandle := p.Entity.Property("m_iControlledBotEntIndex").Value().IntVal
+	if p.demoInfoProvider.IsSource2() {
+		controllerHandler := p.Entity.Property("m_hOriginalControllerOfCurrentPawn").Value().S2UInt64()
+
+		return p.demoInfoProvider.FindPlayerByHandle(controllerHandler)
+	}
+
+	botHandle := p.Entity.Property("m_iControlledBotEntIndex").Value().Int()
 
-	return p.demoInfoProvider.FindPlayerByHandle(botHandle)
+	return p.demoInfoProvider.FindPlayerByHandle(uint64(botHandle))
 }
 
 // Health returns the player's health points, normally 0-100.
@@ -796,7 +802,7 @@ func (p *Player) IsGrabbingHostage() bool {
 type demoInfoProvider interface {
 	IngameTick() int   // current in-game tick, used for IsBlinded()
 	TickRate() float64 // in-game tick rate, used for Player.IsBlinded()
-	FindPlayerByHandle(handle int) *Player
+	FindPlayerByHandle(handle uint64) *Player
 	FindPlayerByPawnHandle(handle uint64) *Player
 	PlayerResourceEntity() st.Entity
 	FindWeaponByEntityID(id int) *Equipment
diff --git a/pkg/demoinfocs/parser.go b/pkg/demoinfocs/parser.go
index 7f545dd..94f79c3 100644
--- a/pkg/demoinfocs/parser.go
+++ b/pkg/demoinfocs/parser.go
@@ -447,8 +447,8 @@ func (p demoInfoProvider) TickRate() float64 {
 	return p.parser.TickRate()
 }
 
-func (p demoInfoProvider) FindPlayerByHandle(handle int) *common.Player {
-	return p.parser.gameState.Participants().FindByHandle(handle)
+func (p demoInfoProvider) FindPlayerByHandle(handle uint64) *common.Player {
+	return p.parser.gameState.Participants().FindByHandle64(handle)
 }
 
 func (p demoInfoProvider) FindPlayerByPawnHandle(handle uint64) *common.Player {


Test patch:
diff --git a/pkg/demoinfocs/common/common_test.go b/pkg/demoinfocs/common/common_test.go
index 5fac082..c88417f 100644
--- a/pkg/demoinfocs/common/common_test.go
+++ b/pkg/demoinfocs/common/common_test.go
@@ -212,7 +212,7 @@ type fakeProp struct {
 type demoInfoProviderMock struct {
 	tickRate             float64
 	ingameTick           int
-	playersByHandle      map[int]*Player
+	playersByHandle      map[uint64]*Player
 	entitiesByHandle     map[uint64]st.Entity
 	playerResourceEntity st.Entity
 	equipment            *Equipment
@@ -235,12 +235,12 @@ func (p demoInfoProviderMock) IngameTick() int {
 	return p.ingameTick
 }
 
-func (p demoInfoProviderMock) FindPlayerByHandle(handle int) *Player {
+func (p demoInfoProviderMock) FindPlayerByHandle(handle uint64) *Player {
 	return p.playersByHandle[handle]
 }
 
 func (p demoInfoProviderMock) FindPlayerByPawnHandle(handle uint64) *Player {
-	return p.playersByHandle[int(handle)]
+	return p.playersByHandle[handle]
 }
 
 func (p demoInfoProviderMock) PlayerResourceEntity() st.Entity {
diff --git a/pkg/demoinfocs/common/hostage_test.go b/pkg/demoinfocs/common/hostage_test.go
index 9dabe31..68cf489 100644
--- a/pkg/demoinfocs/common/hostage_test.go
+++ b/pkg/demoinfocs/common/hostage_test.go
@@ -12,7 +12,7 @@ func TestHostage_Leader(t *testing.T) {
 	player := new(Player)
 	player.EntityID = 10
 	provider := demoInfoProviderMock{
-		playersByHandle: map[int]*Player{10: player},
+		playersByHandle: map[uint64]*Player{10: player},
 	}
 	hostage := hostageWithProperty("m_leader", st.PropertyValue{IntVal: 10}, provider)
 
diff --git a/pkg/demoinfocs/common/inferno_test.go b/pkg/demoinfocs/common/inferno_test.go
index 84cbad2..bb6bc88 100644
--- a/pkg/demoinfocs/common/inferno_test.go
+++ b/pkg/demoinfocs/common/inferno_test.go
@@ -116,7 +116,7 @@ func TestInferno_Thrower(t *testing.T) {
 
 	player := new(Player)
 	provider := demoInfoProviderMock{
-		playersByHandle: map[int]*Player{1: player},
+		playersByHandle: map[uint64]*Player{1: player},
 	}
 
 	assert.Equal(t, player, NewInferno(provider, entity, nil).Thrower())
diff --git a/pkg/demoinfocs/common/player_test.go b/pkg/demoinfocs/common/player_test.go
index 9347376..48c9d77 100644
--- a/pkg/demoinfocs/common/player_test.go
+++ b/pkg/demoinfocs/common/player_test.go
@@ -331,7 +331,7 @@ func TestPlayer_ControlledBot(t *testing.T) {
 		IsBot: true,
 	}
 	demoInfoProvider := &demoInfoProviderMock{
-		playersByHandle: map[int]*Player{
+		playersByHandle: map[uint64]*Player{
 			12: dave,
 		},
 	}
@@ -347,6 +347,29 @@ func TestPlayer_ControlledBot(t *testing.T) {
 	assert.Same(t, dave, pl.ControlledBot())
 }
 
+func TestPlayer_ControlledBotS2(t *testing.T) {
+	dave := &Player{
+		Name:  "Dave",
+		IsBot: true,
+	}
+	demoInfoProvider := &demoInfoProviderMock{
+		playersByHandle: map[uint64]*Player{
+			12: dave,
+		},
+		isSource2: true,
+	}
+
+	pl := playerWithProperty("m_hOriginalControllerOfCurrentPawn", st.PropertyValue{Any: uint64(0)})
+	pl.demoInfoProvider = demoInfoProvider
+
+	assert.Nil(t, pl.ControlledBot())
+
+	pl = playerWithProperty("m_hOriginalControllerOfCurrentPawn", st.PropertyValue{Any: uint64(12)})
+	pl.demoInfoProvider = demoInfoProvider
+
+	assert.Same(t, dave, pl.ControlledBot())
+}
+
 /*
 TODO: fix for CS2
 func TestPlayer_Armor(t *testing.T) {
diff --git a/pkg/demoinfocs/events/events_test.go b/pkg/demoinfocs/events/events_test.go
index a555206..13dc400 100644
--- a/pkg/demoinfocs/events/events_test.go
+++ b/pkg/demoinfocs/events/events_test.go
@@ -75,7 +75,7 @@ func (p demoInfoProviderMock) TickRate() float64 {
 	return 128
 }
 
-func (p demoInfoProviderMock) FindPlayerByHandle(int) *common.Player {
+func (p demoInfoProviderMock) FindPlayerByHandle(uint64) *common.Player {
 	return nil
 }