Repository: markus-wa/demoinfocs-golang
Pull request #195 (2020-03-25 22:15:55)

Weapons() functionality in GameState
Re-creation of #192 due to travis-ci issues.

codecov[bot]: # [Codecov](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195?src=pr&el=h1) Report
> Merging [#195](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195?src=pr&el=desc) into [master](https://codecov.io/gh/markus-wa/demoinfocs-golang/commit/9bc6fd3847063a8a2b4fae7f7d2afe2eeea89681&el=desc) will **decrease** coverage by `0.04%`.
> The diff coverage is `90.90%`.

[![Impacted file tree graph](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195/graphs/tree.svg?width=650&height=150&src=pr&token=20emCaZRHz)](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #195      +/-   ##
==========================================
- Coverage   92.54%   92.50%   -0.05%     
==========================================
  Files          28       28              
  Lines        2644     2655      +11     
==========================================
+ Hits         2447     2456       +9     
- Misses        146      148       +2     
  Partials       51       51              
```


| [Impacted Files](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [parser.go](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195/diff?src=pr&el=tree#diff-cGFyc2VyLmdv) | `95.18% <ø> (ø)` | |
| [game\_state.go](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195/diff?src=pr&el=tree#diff-Z2FtZV9zdGF0ZS5nbw==) | `88.23% <71.42%> (-1.42%)` | :arrow_down: |
| [common/equipment.go](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195/diff?src=pr&el=tree#diff-Y29tbW9uL2VxdWlwbWVudC5nbw==) | `98.73% <100.00%> (ø)` | |
| [datatables.go](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195/diff?src=pr&el=tree#diff-ZGF0YXRhYmxlcy5nbw==) | `98.97% <100.00%> (+0.02%)` | :arrow_up: |
| [game\_events.go](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195/diff?src=pr&el=tree#diff-Z2FtZV9ldmVudHMuZ28=) | `97.16% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195?src=pr&el=footer). Last update [9bc6fd3...5a7f815](https://codecov.io/gh/markus-wa/demoinfocs-golang/pull/195?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/common/equipment.go b/common/equipment.go
index 8ab8015..84881c0 100644
--- a/common/equipment.go
+++ b/common/equipment.go
@@ -333,8 +333,8 @@ func (e Equipment) AmmoReserve2() int {
 // NewEquipment creates a new Equipment and sets the UniqueID.
 //
 // Intended for internal use only.
-func NewEquipment(wep EquipmentElement) Equipment {
-	return Equipment{Weapon: wep, uniqueID: rand.Int63()}
+func NewEquipment(wep EquipmentElement) *Equipment {
+	return &Equipment{Weapon: wep, uniqueID: rand.Int63()}
 }
 
 var equipmentToAlternative = map[EquipmentElement]EquipmentElement{
diff --git a/datatables.go b/datatables.go
index 3c3880f..0e47580 100644
--- a/datatables.go
+++ b/datatables.go
@@ -302,7 +302,15 @@ func (p *Parser) bindNewPlayer(playerEntity st.IEntity) {
 				}
 				cache[i2] = entityID
 
-				wep := &p.weapons[entityID]
+				wep := p.gameState.weapons[entityID]
+
+				if wep == nil {
+					// Something is clearly wrong here
+					// But since we had an empty Equipment instance here before the change
+					// from array with default elements to map, let's create a new instance.
+					wep = new(common.Equipment)
+					p.gameState.weapons[entityID] = wep
+				}
 
 				// Clear previous owner
 				if wep.Owner != nil {
@@ -470,13 +478,22 @@ func (p *Parser) nadeProjectileDestroyed(proj *common.GrenadeProjectile) {
 
 func (p *Parser) bindWeapon(entity *st.Entity, wepType common.EquipmentElement) {
 	entityID := entity.ID()
-	currentOwner := p.weapons[entityID].Owner
-	p.weapons[entityID] = common.NewEquipment(wepType)
-	eq := &p.weapons[entityID]
+
+	var currentOwner *common.Player
+	if wep, ok := p.gameState.weapons[entityID]; ok {
+		currentOwner = wep.Owner
+	}
+
+	p.gameState.weapons[entityID] = common.NewEquipment(wepType)
+	eq := p.gameState.weapons[entityID]
 	eq.Owner = currentOwner
 	eq.EntityID = entityID
 	eq.AmmoInMagazine = -1
 
+	entity.OnDestroy(func() {
+		delete(p.gameState.weapons, entityID)
+	})
+
 	entity.FindPropertyI("m_iClip1").OnUpdate(func(val st.PropertyValue) {
 		eq.AmmoInMagazine = val.IntVal - 1
 
diff --git a/fake/game_state.go b/fake/game_state.go
index be3ad50..4af43b5 100644
--- a/fake/game_state.go
+++ b/fake/game_state.go
@@ -50,6 +50,11 @@ func (gs *GameState) Infernos() map[int]*common.Inferno {
 	return gs.Called().Get(0).(map[int]*common.Inferno)
 }
 
+// Weapons is a mock-implementation of IGameState.Weapons().
+func (gs *GameState) Weapons() map[int]*common.Equipment {
+	return gs.Called().Get(0).(map[int]*common.Equipment)
+}
+
 // Entities is a mock-implementation of IGameState.Entities().
 func (gs *GameState) Entities() map[int]*st.Entity {
 	return gs.Called().Get(0).(map[int]*st.Entity)
diff --git a/game_events.go b/game_events.go
index 70720f7..c16439a 100644
--- a/game_events.go
+++ b/game_events.go
@@ -694,7 +694,8 @@ func getPlayerWeapon(player *common.Player, wepType common.EquipmentElement) *co
 	}
 
 	wep := common.NewEquipment(wepType)
-	return &wep
+
+	return wep
 }
 
 func mapGameEventData(d *msg.CSVCMsg_GameEventListDescriptorT, e *msg.CSVCMsg_GameEvent) map[string]*msg.CSVCMsg_GameEventKeyT {
diff --git a/game_state.go b/game_state.go
index a4b18f3..e9f1981 100644
--- a/game_state.go
+++ b/game_state.go
@@ -17,6 +17,7 @@ type GameState struct {
 	playersByEntityID  map[int]*common.Player            // Maps entity-IDs to players
 	grenadeProjectiles map[int]*common.GrenadeProjectile // Maps entity-IDs to active nade-projectiles. That's grenades that have been thrown, but have not yet detonated.
 	infernos           map[int]*common.Inferno           // Maps entity-IDs to active infernos.
+	weapons            map[int]*common.Equipment         // Maps entity IDs to weapons. Used to remember what a weapon is (p250 / cz etc.)
 	entities           map[int]*st.Entity                // Maps entity IDs to entities
 	conVars            map[string]string
 	bomb               common.Bomb
@@ -99,6 +100,11 @@ func (gs GameState) Infernos() map[int]*common.Inferno {
 	return gs.infernos
 }
 
+// Weapons returns a map from entity-IDs to all weapons currently in the game.
+func (gs GameState) Weapons() map[int]*common.Equipment {
+	return gs.weapons
+}
+
 // Entities returns all currently existing entities.
 // (Almost?) everything in the game is an entity, such as weapons, players, fire etc.
 func (gs GameState) Entities() map[int]*st.Entity {
@@ -143,6 +149,7 @@ func newGameState() *GameState {
 		playersByUserID:    make(map[int]*common.Player),
 		grenadeProjectiles: make(map[int]*common.GrenadeProjectile),
 		infernos:           make(map[int]*common.Inferno),
+		weapons:            make(map[int]*common.Equipment),
 		entities:           make(map[int]*st.Entity),
 		conVars:            make(map[string]string),
 		thrownGrenades:     make(map[*common.Player][]*common.Equipment),
@@ -223,7 +230,7 @@ func (ptcp Participants) All() []*common.Player {
 // Connected returns all currently connected players & spectators.
 // The returned slice is a snapshot and is not updated on changes.
 func (ptcp Participants) Connected() []*common.Player {
-	res, original := ptcp.initalizeSliceFromByUserID()
+	res, original := ptcp.initializeSliceFromByUserID()
 	for _, p := range original {
 		res = append(res, p)
 	}
@@ -234,7 +241,7 @@ func (ptcp Participants) Connected() []*common.Player {
 // Playing returns all players that aren't spectating or unassigned.
 // The returned slice is a snapshot and is not updated on changes.
 func (ptcp Participants) Playing() []*common.Player {
-	res, original := ptcp.initalizeSliceFromByUserID()
+	res, original := ptcp.initializeSliceFromByUserID()
 	for _, p := range original {
 		if p.Team != common.TeamSpectators && p.Team != common.TeamUnassigned {
 			res = append(res, p)
@@ -247,7 +254,7 @@ func (ptcp Participants) Playing() []*common.Player {
 // TeamMembers returns all players belonging to the requested team at this time.
 // The returned slice is a snapshot and is not updated on changes.
 func (ptcp Participants) TeamMembers(team common.Team) []*common.Player {
-	res, original := ptcp.initalizeSliceFromByUserID()
+	res, original := ptcp.initializeSliceFromByUserID()
 	for _, p := range original {
 		if p.Team == team {
 			res = append(res, p)
@@ -281,7 +288,7 @@ func (ptcp Participants) FindByHandle(handle int) *common.Player {
 	return player
 }
 
-func (ptcp Participants) initalizeSliceFromByUserID() ([]*common.Player, map[int]*common.Player) {
+func (ptcp Participants) initializeSliceFromByUserID() ([]*common.Player, map[int]*common.Player) {
 	byUserID := ptcp.ByUserID()
 	return make([]*common.Player, 0, len(byUserID)), byUserID
 }
diff --git a/game_state_interface.go b/game_state_interface.go
index c7686dd..ffa830b 100644
--- a/game_state_interface.go
+++ b/game_state_interface.go
@@ -37,6 +37,8 @@ type IGameState interface {
 	GrenadeProjectiles() map[int]*common.GrenadeProjectile
 	// Infernos returns a map from entity-IDs to all currently burning infernos (fires from incendiaries and Molotovs).
 	Infernos() map[int]*common.Inferno
+	// Weapons returns a map from entity-IDs to all weapons currently in the game.
+	Weapons() map[int]*common.Equipment
 	// Entities returns all currently existing entities.
 	// (Almost?) everything in the game is an entity, such as weapons, players, fire etc.
 	Entities() map[int]*st.Entity
diff --git a/parser.go b/parser.go
index 042e628..e3825ee 100644
--- a/parser.go
+++ b/parser.go
@@ -68,7 +68,6 @@ type Parser struct {
 	rawPlayers           map[int]*playerInfo                             // Maps entity IDs to 'raw' player info
 	additionalPlayerInfo [maxPlayers]common.AdditionalPlayerInformation  // Maps entity IDs to additional player info (scoreboard info)
 	modelPreCache        []string                                        // Used to find out whether a weapon is a p250 or cz for example (same id)
-	weapons              [maxEntities]common.Equipment                   // Used to remember what a weapon is (p250 / cz etc.)
 	triggers             map[int]*boundingBoxInformation                 // Maps entity IDs to triggers (used for bombsites)
 	gameEventDescs       map[int32]*msg.CSVCMsg_GameEventListDescriptorT // Maps game-event IDs to descriptors
 	grenadeModelIndices  map[int]common.EquipmentElement                 // Used to map model indices to grenades (used for grenade projectiles)
diff --git a/sendtables/fake/property.go b/sendtables/fake/property.go
index 23f7d61..3a90a41 100644
--- a/sendtables/fake/property.go
+++ b/sendtables/fake/property.go
@@ -13,7 +13,7 @@ type Property struct {
 	mock.Mock
 }
 
-// Namp is a mock-implementation of IProperty.Name().
+// Name is a mock-implementation of IProperty.Name().
 func (p *Property) Name() string {
 	return p.Called().Get(0).(string)
 }


Test patch:
diff --git a/common/player_test.go b/common/player_test.go
index 4596429..0f009d7 100644
--- a/common/player_test.go
+++ b/common/player_test.go
@@ -15,12 +15,12 @@ func TestPlayerActiveWeapon(t *testing.T) {
 	ak47 := NewEquipment(EqAK47)
 
 	pl := newPlayer(0)
-	pl.RawWeapons[1] = &knife
-	pl.RawWeapons[2] = &glock
-	pl.RawWeapons[3] = &ak47
+	pl.RawWeapons[1] = knife
+	pl.RawWeapons[2] = glock
+	pl.RawWeapons[3] = ak47
 	pl.ActiveWeaponID = 3
 
-	assert.Equal(t, &ak47, pl.ActiveWeapon(), "Should have AK-47 equipped")
+	assert.Equal(t, ak47, pl.ActiveWeapon(), "Should have AK-47 equipped")
 }
 
 func TestPlayerWeapons(t *testing.T) {
@@ -29,11 +29,11 @@ func TestPlayerWeapons(t *testing.T) {
 	ak47 := NewEquipment(EqAK47)
 
 	pl := newPlayer(0)
-	pl.RawWeapons[1] = &knife
-	pl.RawWeapons[2] = &glock
-	pl.RawWeapons[3] = &ak47
+	pl.RawWeapons[1] = knife
+	pl.RawWeapons[2] = glock
+	pl.RawWeapons[3] = ak47
 
-	expected := []*Equipment{&knife, &glock, &ak47}
+	expected := []*Equipment{knife, glock, ak47}
 	assert.ElementsMatch(t, expected, pl.Weapons(), "Should have expected weapons")
 }
 
diff --git a/examples/mocking/mocking_test.go b/examples/mocking/mocking_test.go
index 63b91e7..96157a3 100644
--- a/examples/mocking/mocking_test.go
+++ b/examples/mocking/mocking_test.go
@@ -32,7 +32,7 @@ func kill(wep common.EquipmentElement) events.Kill {
 	eq := common.NewEquipment(wep)
 	return events.Kill{
 		Killer: new(common.Player),
-		Weapon: &eq,
+		Weapon: eq,
 		Victim: new(common.Player),
 	}
 }
diff --git a/fake/parser_test.go b/fake/parser_test.go
index 7dbfa5a..1b3f847 100644
--- a/fake/parser_test.go
+++ b/fake/parser_test.go
@@ -53,7 +53,7 @@ func kill(wepType common.EquipmentElement) events.Kill {
 	wep := common.NewEquipment(wepType)
 	return events.Kill{
 		Killer: new(common.Player),
-		Weapon: &wep,
+		Weapon: wep,
 		Victim: new(common.Player),
 	}
 }
diff --git a/game_events_test.go b/game_events_test.go
index 1853495..c141fd7 100644
--- a/game_events_test.go
+++ b/game_events_test.go
@@ -93,7 +93,7 @@ func TestAddThrownGrenade_NilPlayer(t *testing.T) {
 
 	assert.Empty(t, p.gameState.thrownGrenades)
 
-	p.gameEventHandler.addThrownGrenade(nil, &he)
+	p.gameEventHandler.addThrownGrenade(nil, he)
 
 	assert.Empty(t, p.gameState.thrownGrenades)
 }
@@ -105,11 +105,11 @@ func TestAddThrownGrenade(t *testing.T) {
 
 	assert.Empty(t, p.gameState.thrownGrenades)
 
-	p.gameEventHandler.addThrownGrenade(pl, &he)
+	p.gameEventHandler.addThrownGrenade(pl, he)
 
 	assert.NotEmpty(t, p.gameState.thrownGrenades)
 	assert.NotEmpty(t, p.gameState.thrownGrenades[pl])
-	assert.Equal(t, p.gameState.thrownGrenades[pl][0], &he)
+	assert.Equal(t, p.gameState.thrownGrenades[pl][0], he)
 }
 
 func TestGetThrownGrenade_NilPlayer(t *testing.T) {
@@ -137,11 +137,11 @@ func TestGetThrownGrenade_Found(t *testing.T) {
 	pl := &common.Player{}
 	he := common.NewEquipment(common.EqHE)
 
-	p.gameEventHandler.addThrownGrenade(pl, &he)
+	p.gameEventHandler.addThrownGrenade(pl, he)
 	wep := p.gameEventHandler.getThrownGrenade(pl, he.Weapon)
 
 	assert.Equal(t, wep.Weapon, he.Weapon)
-	assert.Equal(t, wep, &he)
+	assert.Equal(t, wep, he)
 }
 
 func TestDeleteThrownGrenade_NilPlayer(t *testing.T) {
@@ -159,7 +159,7 @@ func TestDeleteThrownGrenade_NotFound(t *testing.T) {
 
 	assert.Empty(t, p.gameState.thrownGrenades)
 
-	p.gameEventHandler.addThrownGrenade(pl, &he)
+	p.gameEventHandler.addThrownGrenade(pl, he)
 
 	assert.NotEmpty(t, p.gameState.thrownGrenades[pl])
 
@@ -175,7 +175,7 @@ func TestDeleteThrownGrenade_Found(t *testing.T) {
 
 	assert.Empty(t, p.gameState.thrownGrenades)
 
-	p.gameEventHandler.addThrownGrenade(pl, &he)
+	p.gameEventHandler.addThrownGrenade(pl, he)
 
 	assert.NotEmpty(t, p.gameState.thrownGrenades[pl])
 
@@ -207,10 +207,10 @@ func TestGetEquipmentInstance_Grenade_Thrown(t *testing.T) {
 	pl := &common.Player{}
 	he := common.NewEquipment(common.EqHE)
 
-	p.gameEventHandler.addThrownGrenade(pl, &he)
+	p.gameEventHandler.addThrownGrenade(pl, he)
 	wep := p.gameEventHandler.getEquipmentInstance(pl, he.Weapon)
 
-	assert.Equal(t, &he, wep)
+	assert.Equal(t, he, wep)
 }
 
 func TestGetCommunityId(t *testing.T) {
diff --git a/game_state_test.go b/game_state_test.go
index de29667..cd40d33 100644
--- a/game_state_test.go
+++ b/game_state_test.go
@@ -17,6 +17,7 @@ func TestNewGameState(t *testing.T) {
 	assert.NotNil(t, gs.playersByUserID)
 	assert.NotNil(t, gs.grenadeProjectiles)
 	assert.NotNil(t, gs.infernos)
+	assert.NotNil(t, gs.weapons)
 	assert.NotNil(t, gs.entities)
 	assert.Equal(t, common.TeamTerrorists, gs.tState.Team())
 	assert.Equal(t, common.TeamCounterTerrorists, gs.ctState.Team())