Skip to content

Commit 6d66659

Browse files
micvbangmarkus-wa
authored andcommitted
Add tracking of grenade projectiles.
1 parent c2ae3b1 commit 6d66659

File tree

8 files changed

+150
-15
lines changed

8 files changed

+150
-15
lines changed

common/common.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func init() {
2222

2323
func initEqNameToWeapon() {
2424
eqNameToWeapon = make(map[string]EquipmentElement)
25+
2526
eqNameToWeapon["ak47"] = EqAK47
2627
eqNameToWeapon["aug"] = EqAUG
2728
eqNameToWeapon["awp"] = EqAWP
@@ -30,7 +31,7 @@ func initEqNameToWeapon() {
3031
eqNameToWeapon["deagle"] = EqDeagle
3132
eqNameToWeapon["decoy"] = EqDecoy
3233
eqNameToWeapon["decoygrenade"] = EqDecoy
33-
eqNameToWeapon["decoy_projectile"] = EqDecoy
34+
eqNameToWeapon["decoyprojectile"] = EqDecoy
3435
eqNameToWeapon["elite"] = EqDualBarettas
3536
eqNameToWeapon["famas"] = EqFamas
3637
eqNameToWeapon["fiveseven"] = EqFiveSeven
@@ -49,7 +50,7 @@ func initEqNameToWeapon() {
4950
eqNameToWeapon["mag7"] = EqSwag7
5051
eqNameToWeapon["molotov"] = EqMolotov
5152
eqNameToWeapon["molotovgrenade"] = EqMolotov
52-
eqNameToWeapon["molotov_projectile"] = EqMolotov
53+
eqNameToWeapon["molotovprojectile"] = EqMolotov
5354
eqNameToWeapon["mp7"] = EqMP7
5455
eqNameToWeapon["mp9"] = EqMP9
5556
eqNameToWeapon["negev"] = EqNegev
@@ -60,7 +61,7 @@ func initEqNameToWeapon() {
6061
eqNameToWeapon["scar20"] = EqScar20
6162
eqNameToWeapon["sg556"] = EqSG556
6263
eqNameToWeapon["smokegrenade"] = EqSmoke
63-
eqNameToWeapon["ssg08"] = EqScout
64+
eqNameToWeapon["smokegrenadeprojectile"] = EqSmoke
6465
eqNameToWeapon["taser"] = EqZeus
6566
eqNameToWeapon["tec9"] = EqTec9
6667
eqNameToWeapon["ump45"] = EqUMP

common/structs.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package common
22

33
import (
4+
"math/rand"
5+
46
r3 "github.com/golang/geo/r3"
57

68
st "github.com/markus-wa/demoinfocs-golang/sendtables"
@@ -100,12 +102,36 @@ type Equipment struct {
100102
ReserveAmmo int
101103
}
102104

105+
// GrenadeProjectile is a grenade thrown intentionally by a player. It is used to track grenade projectile
106+
// positions between the time at which they are thrown and until they detonate.
107+
type GrenadeProjectile struct {
108+
EntityID int
109+
Weapon EquipmentElement
110+
Thrower *Player
111+
Owner *Player
112+
113+
// uniqueID is used to distinguish different grenades (which potentially have the same, reused entityID) from each other.
114+
uniqueID int64
115+
116+
Position r3.Vector
117+
}
118+
103119
// Class returns the class of the equipment.
104120
// E.g. pistol, smg, heavy etc.
105121
func (e Equipment) Class() EquipmentClass {
106122
return e.Weapon.Class()
107123
}
108124

125+
// NewGrenadeProjectile creates a grenade projectile and sets.
126+
func NewGrenadeProjectile() *GrenadeProjectile {
127+
return &GrenadeProjectile{uniqueID: rand.Int63()}
128+
}
129+
130+
// UniqueID returns the internal id of the grenade, which is used to distinguish grenades that reuse another entity's entity id.
131+
func (g GrenadeProjectile) UniqueID() int64 {
132+
return g.uniqueID
133+
}
134+
109135
// NewEquipment is a wrapper for NewSkinEquipment to create weapons without skins.
110136
func NewEquipment(eqName string) Equipment {
111137
return NewSkinEquipment(eqName, "")

datatables.go

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ const (
2020

2121
func (p *Parser) mapEquipment() {
2222
for _, sc := range p.stParser.ServerClasses() {
23+
for _, bc := range sc.BaseClasses {
24+
if bc.Name == "CBaseGrenade" { // Grenades projectiles, i.e. thrown by player
25+
p.equipmentMapping[sc] = common.MapEquipment(strings.ToLower(sc.DTName[3:]))
26+
}
27+
}
28+
2329
if len(sc.BaseClasses) > 6 && sc.BaseClasses[6].Name == "CWeaponCSBase" {
2430
if len(sc.BaseClasses) > 7 {
2531
switch sc.BaseClasses[7].Name {
@@ -36,7 +42,6 @@ func (p *Parser) mapEquipment() {
3642
switch sc.Name {
3743
case "CC4":
3844
p.equipmentMapping[sc] = common.EqBomb
39-
4045
case "CWeaponNOVA":
4146
fallthrough
4247
case "CWeaponSawedoff":
@@ -253,18 +258,18 @@ func (p *Parser) bindNewPlayer(playerEntity *st.Entity) {
253258
for i := range cache {
254259
i2 := i // Copy for passing to handler
255260
playerEntity.FindProperty(wepPrefix + fmt.Sprintf("%03d", i)).RegisterPropertyUpdateHandler(func(val st.PropValue) {
256-
idx := val.IntVal & indexMask
257-
if idx != indexMask {
261+
entityID := val.IntVal & indexMask
262+
if entityID != indexMask {
258263
if cache[i2] != 0 {
259264
// Player already has a weapon in this slot.
260265
pl.RawWeapons[cache[i2]] = nil
261266
}
262-
cache[i2] = idx
267+
cache[i2] = entityID
263268

264269
// Attribute weapon to player
265-
wep := &p.weapons[idx]
270+
wep := &p.weapons[entityID]
266271
wep.Owner = pl
267-
pl.RawWeapons[idx] = wep
272+
pl.RawWeapons[entityID] = wep
268273
} else {
269274
if cache[i2] != 0 && pl.RawWeapons[cache[i2]] != nil {
270275
pl.RawWeapons[cache[i2]].Owner = nil
@@ -290,11 +295,60 @@ func (p *Parser) bindWeapons() {
290295

291296
for _, sc := range p.stParser.ServerClasses() {
292297
for _, bc := range sc.BaseClasses {
293-
if bc.Name == "CWeaponCSBase" {
298+
switch bc.Name {
299+
case "CWeaponCSBase":
294300
sc.RegisterEntityCreatedHandler(p.bindWeapon)
301+
case "CBaseGrenade": // Grenade that has been thrown by player.
302+
sc.RegisterEntityCreatedHandler(p.bindGrenadeProjectiles)
303+
case "CBaseCSGrenade":
304+
// @micvbang TODO: handle grenades dropped by dead player.
305+
// Grenades that were dropped by a dead player (and can be picked up by other players).
295306
}
296307
}
297308
}
309+
310+
}
311+
312+
// bindGrenadeProjectiles keeps track of the location of live grenades, actively thrown by players.
313+
// It does track the location of grenades lying on the ground, i.e. that were dropped by dead players.
314+
//
315+
// NOTE: Parser.gameState.grenadeProjectiles is updated here. We rely on code during the handling of the game events
316+
// "[nade]_detonate" and "[nade]_started" to remove projectiles from Parser.gameState.grenadeProjectiles once they detonate.
317+
func (p *Parser) bindGrenadeProjectiles(event st.EntityCreatedEvent) {
318+
if _, ok := p.gameState.grenadeProjectiles[event.Entity.ID]; !ok {
319+
p.gameState.grenadeProjectiles[event.Entity.ID] = common.NewGrenadeProjectile()
320+
}
321+
322+
proj := p.gameState.grenadeProjectiles[event.Entity.ID]
323+
proj.EntityID = event.Entity.ID
324+
325+
event.Entity.FindProperty("m_nModelIndex").RegisterPropertyUpdateHandler(func(val st.PropValue) {
326+
proj.Weapon = p.grenadeModelIndicies[val.IntVal]
327+
})
328+
329+
// @micvbang: not quite sure what the difference between Thrower and Owner is.
330+
event.Entity.FindProperty("m_hThrower").RegisterPropertyUpdateHandler(func(val st.PropValue) {
331+
throwerID := val.IntVal & indexMask
332+
throwerIndex := throwerID - 1
333+
334+
thrower := p.entityIDToPlayers[throwerIndex]
335+
proj.Thrower = thrower
336+
337+
if proj.Thrower == nil && thrower != nil {
338+
proj.Position = thrower.Position
339+
}
340+
})
341+
342+
event.Entity.FindProperty("m_hOwnerEntity").RegisterPropertyUpdateHandler(func(val st.PropValue) {
343+
ownerID := val.IntVal & indexMask
344+
ownerIndex := ownerID - 1
345+
player := p.entityIDToPlayers[ownerIndex]
346+
proj.Owner = player
347+
})
348+
349+
event.Entity.FindProperty("m_vecOrigin").RegisterPropertyUpdateHandler(func(val st.PropValue) {
350+
proj.Position = event.Entity.Position()
351+
})
298352
}
299353

300354
func (p *Parser) bindWeapon(event st.EntityCreatedEvent) {

game_events.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package demoinfocs
33
import (
44
"fmt"
55
"strconv"
6+
"strings"
67

78
r3 "github.com/golang/geo/r3"
89

@@ -183,6 +184,11 @@ func (p *Parser) handleGameEvent(ge *msg.CSVCMsg_GameEvent) {
183184
}
184185
nadeEntityID := int(data["entityid"].GetValShort())
185186

187+
// Grenade projectiles should be removed once the grenade detonates.
188+
if strings.Contains(d.Name, "detonate") || strings.Contains(d.Name, "started") {
189+
delete(p.gameState.grenadeProjectiles, nadeEntityID)
190+
}
191+
186192
switch d.Name {
187193
case "flashbang_detonate": // Flash exploded
188194
p.eventDispatcher.Dispatch(events.FlashExplodedEvent{NadeEvent: buildNadeEvent(common.EqFlash, thrower, position, nadeEntityID)})

game_state.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import (
66

77
// GameState contains all game-state relevant information.
88
type GameState struct {
9-
ingameTick int
10-
tState TeamState
11-
ctState TeamState
12-
players map[int]*common.Player
9+
ingameTick int
10+
tState TeamState
11+
ctState TeamState
12+
players map[int]*common.Player
13+
grenadeProjectiles map[int]*common.GrenadeProjectile // Used to keep track of grenades that have been thrown, but have not yet detonated.
1314
}
1415

1516
type ingameTickNumber int
@@ -69,8 +70,17 @@ func (gs GameState) TeamMembers(team common.Team) []*common.Player {
6970
return r
7071
}
7172

73+
// GrenadeProjectiles returns a map with all grenade projectiles. The map contains only projectiles
74+
// that are currently in-flight, i.e. have been thrown but have yet to detonate.
75+
func (gs GameState) GrenadeProjectiles() map[int]*common.GrenadeProjectile {
76+
return gs.grenadeProjectiles
77+
}
78+
7279
func newGameState() GameState {
73-
return GameState{players: make(map[int]*common.Player)}
80+
return GameState{
81+
players: make(map[int]*common.Player),
82+
grenadeProjectiles: make(map[int]*common.GrenadeProjectile),
83+
}
7484
}
7585

7686
// TeamState contains a team's ID, score, clan name & country flag.

model_precache.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package demoinfocs
2+
3+
import (
4+
"strings"
5+
6+
common "github.com/markus-wa/demoinfocs-golang/common"
7+
)
8+
9+
var modelPrecacheNameToEq = []struct {
10+
name string
11+
eq common.EquipmentElement
12+
}{
13+
{"flashbang_dropped", common.EqFlash},
14+
{"fraggrenade_dropped", common.EqHE},
15+
{"smokegrenade_thrown", common.EqSmoke},
16+
{"molotov_dropped", common.EqMolotov},
17+
{"incendiarygrenade_dropped", common.EqIncendiary},
18+
{"decoy_dropped", common.EqDecoy},
19+
// @micvbang TODO: add all other weapons too.
20+
}
21+
22+
func (p *Parser) processModelPreCacheUpdate() {
23+
for i, name := range p.modelPreCache {
24+
for _, nade := range modelPrecacheNameToEq {
25+
if strings.Contains(name, nade.name) {
26+
p.grenadeModelIndicies[i] = nade.eq
27+
}
28+
}
29+
}
30+
}

parser.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Parser struct {
4141
instanceBaselines map[int][]byte
4242
preprocessedBaselines map[int]map[int]st.PropValue
4343
gameEventDescs map[int32]*msg.CSVCMsg_GameEventListDescriptorT
44+
grenadeModelIndicies map[int]common.EquipmentElement // Used to map model indicies to grenades (used for grenade projectiles)
4445
stringTables []*msg.CSVCMsg_CreateStringTable
4546
cancelChan chan struct{}
4647
err error
@@ -207,6 +208,7 @@ func NewParserWithConfig(demostream io.Reader, config ParserConfig) *Parser {
207208
p.triggers = make(map[int]*boundingBoxInformation)
208209
p.cancelChan = make(chan struct{}, 1)
209210
p.gameState = newGameState()
211+
p.grenadeModelIndicies = make(map[int]common.EquipmentElement)
210212

211213
// Attach proto msg handlers
212214
p.msgDispatcher.RegisterHandler(p.handlePacketEntities)

stringtables.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func (p *Parser) parseStringTables() {
4545
tableName := p.bitReader.ReadString()
4646
p.parseSingleStringTable(tableName)
4747
}
48+
p.processModelPreCacheUpdate()
4849
p.bitReader.EndChunk()
4950
}
5051

@@ -211,6 +212,11 @@ func (p *Parser) processStringTable(tab *msg.CSVCMsg_CreateStringTable) {
211212
p.modelPreCache[entryIndex] = entry
212213
}
213214
}
215+
216+
if tab.Name == stNameModelPreCache {
217+
p.processModelPreCacheUpdate()
218+
}
219+
214220
br.Pool()
215221
}
216222

0 commit comments

Comments
 (0)