From 4d7029cf9b16b4c9cb7c91bf6254836f89ef0559 Mon Sep 17 00:00:00 2001 From: Michael Bang Date: Sun, 19 Nov 2023 13:43:57 +0100 Subject: [PATCH 1/3] datatables cs2: rewrite player equipment tracking NOTE: this is not 100% accurate yet, but it's a big step in the right direction. Thanks to esbengc for describing the required logic! --- pkg/demoinfocs/datatables.go | 80 +++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/pkg/demoinfocs/datatables.go b/pkg/demoinfocs/datatables.go index b73a6b30..a1df0858 100644 --- a/pkg/demoinfocs/datatables.go +++ b/pkg/demoinfocs/datatables.go @@ -688,44 +688,66 @@ func (p *parser) bindPlayerWeapons(playerEntity st.Entity, pl *common.Player) { } func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { - var cache [maxWeapons]uint64 - for i := range cache { - i2 := i // Copy for passing to handler + const inventoryCapacity = 64 + var inventorySize uint64 = 64 + playerInventory := make(map[int]*common.Equipment) + + getWep := func(wepSlotPropertyValue st.PropertyValue) (uint64, *common.Equipment) { + entityID := wepSlotPropertyValue.S2UInt64() & constants.EntityHandleIndexMaskSource2 + wep := p.gameState.weapons[int(entityID)] + if wep == nil { + // sometimes a weapon is assigned to a player before the weapon entity is created + wep = common.NewEquipment(common.EqUnknown) + p.gameState.weapons[int(entityID)] = wep + } + + return entityID, wep + } + + setPlayerInventory := func() { + inventory := make(map[int]*common.Equipment, inventorySize) + for i := uint64(0); i < inventorySize; i++ { + val := pawnEntity.Property(playerWeaponPrefixS2 + fmt.Sprintf("%04d", i)).Value() + if val.Any == nil { + continue + } + + entityID, wep := getWep(val) + inventory[int(entityID)] = wep + } + pl.Inventory = inventory + } + + pawnEntity.Property("m_pWeaponServices.m_hMyWeapons").OnUpdate(func(pv st.PropertyValue) { + inventorySize = pv.S2UInt64() + setPlayerInventory() + }) + + for i := 0; i < inventoryCapacity; i++ { + i := i updateWeapon := func(val st.PropertyValue) { if val.Any == nil { return } - entityID := val.S2UInt64() & constants.EntityHandleIndexMaskSource2 - if entityID != constants.EntityHandleIndexMaskSource2 { - if cache[i2] != 0 { - // Player already has a weapon in this slot. - delete(pl.Inventory, int(cache[i2])) - } - cache[i2] = entityID - - wep := p.gameState.weapons[int(entityID)] - if wep == nil { - // sometimes a weapon is assigned to a player before the weapon entity is created - wep = common.NewEquipment(common.EqUnknown) - p.gameState.weapons[int(entityID)] = wep - } + entityID, wep := getWep(val) + wep.Owner = pl - // Clear previous owner - if wep.Owner != nil && wep.Entity != nil { - delete(wep.Owner.Inventory, wep.Entity.ID()) - } + entityWasCreated := entityID != constants.EntityHandleIndexMaskSource2 - // Attribute weapon to player - wep.Owner = pl - pl.Inventory[int(entityID)] = wep - } else { - if cache[i2] != 0 && pl.Inventory[int(cache[i2])] != nil { - pl.Inventory[int(cache[i2])].Owner = nil + if uint64(i) < inventorySize { + if entityWasCreated { + existingWeapon, exists := playerInventory[i] + if exists { + delete(pl.Inventory, existingWeapon.Entity.ID()) + } + pl.Inventory[int(entityID)] = wep + playerInventory[i] = wep + } else { + delete(pl.Inventory, int(entityID)) } - delete(pl.Inventory, int(cache[i2])) - cache[i2] = 0 + setPlayerInventory() } } property := pawnEntity.Property(playerWeaponPrefixS2 + fmt.Sprintf("%04d", i)) From f6a5b796ac5efaa5177d17084f786561bd14d387 Mon Sep 17 00:00:00 2001 From: Markus Walther Date: Mon, 20 Nov 2023 19:52:49 +0000 Subject: [PATCH 2/3] cs2 weapons handling: fix nil pointer deref panic --- pkg/demoinfocs/datatables.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/demoinfocs/datatables.go b/pkg/demoinfocs/datatables.go index a1df0858..933f0873 100644 --- a/pkg/demoinfocs/datatables.go +++ b/pkg/demoinfocs/datatables.go @@ -690,7 +690,12 @@ func (p *parser) bindPlayerWeapons(playerEntity st.Entity, pl *common.Player) { func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { const inventoryCapacity = 64 var inventorySize uint64 = 64 - playerInventory := make(map[int]*common.Equipment) + + type eq struct { + *common.Equipment + entityID int + } + playerInventory := make(map[int]eq) getWep := func(wepSlotPropertyValue st.PropertyValue) (uint64, *common.Equipment) { entityID := wepSlotPropertyValue.S2UInt64() & constants.EntityHandleIndexMaskSource2 @@ -739,10 +744,14 @@ func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { if entityWasCreated { existingWeapon, exists := playerInventory[i] if exists { - delete(pl.Inventory, existingWeapon.Entity.ID()) + delete(pl.Inventory, existingWeapon.entityID) } + pl.Inventory[int(entityID)] = wep - playerInventory[i] = wep + playerInventory[i] = eq{ + Equipment: wep, + entityID: int(entityID), + } } else { delete(pl.Inventory, int(entityID)) } From 42c7b7e6dd4ec2689158ddb435bd4618cd0b4337 Mon Sep 17 00:00:00 2001 From: Markus Walther Date: Mon, 20 Nov 2023 19:56:36 +0000 Subject: [PATCH 3/3] linting fixes --- pkg/demoinfocs/datatables.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/demoinfocs/datatables.go b/pkg/demoinfocs/datatables.go index 933f0873..547e69a7 100644 --- a/pkg/demoinfocs/datatables.go +++ b/pkg/demoinfocs/datatables.go @@ -689,17 +689,20 @@ func (p *parser) bindPlayerWeapons(playerEntity st.Entity, pl *common.Player) { func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { const inventoryCapacity = 64 + var inventorySize uint64 = 64 type eq struct { *common.Equipment entityID int } + playerInventory := make(map[int]eq) getWep := func(wepSlotPropertyValue st.PropertyValue) (uint64, *common.Equipment) { entityID := wepSlotPropertyValue.S2UInt64() & constants.EntityHandleIndexMaskSource2 wep := p.gameState.weapons[int(entityID)] + if wep == nil { // sometimes a weapon is assigned to a player before the weapon entity is created wep = common.NewEquipment(common.EqUnknown) @@ -711,6 +714,7 @@ func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { setPlayerInventory := func() { inventory := make(map[int]*common.Equipment, inventorySize) + for i := uint64(0); i < inventorySize; i++ { val := pawnEntity.Property(playerWeaponPrefixS2 + fmt.Sprintf("%04d", i)).Value() if val.Any == nil { @@ -720,6 +724,7 @@ func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { entityID, wep := getWep(val) inventory[int(entityID)] = wep } + pl.Inventory = inventory } @@ -759,6 +764,7 @@ func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { setPlayerInventory() } } + property := pawnEntity.Property(playerWeaponPrefixS2 + fmt.Sprintf("%04d", i)) updateWeapon(property.Value()) property.OnUpdate(updateWeapon)