Skip to content

Commit 73caec2

Browse files
committed
Add GrenadeProjectile.Trajectory
Add NadeProjectileDestroyedEvent for easy access to the final trajectory Update nade-trajectories example accordingly Add nade trajectories to feature list in README
1 parent 280ea29 commit 73caec2

File tree

7 files changed

+96
-40
lines changed

7 files changed

+96
-40
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ This doesn't look too interesting on it's own but that can be helped by quickly
8787
## Features
8888

8989
* Game events
90+
* Grenade projectiles / trajectories - [doc](https://godoc.org/github.com/markus-wa/demoinfocs-golang#GameState.GrenadeProjectiles) [example](https://github.com/markus-wa/demoinfocs-golang/tree/master/examples/nade-trajectories)
9091
* Access to entities, server-classes & data-tables
9192
* Access to all net-messages
9293
* Chat & console messages <sup id="achat1">1</sup>

common/structs.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,15 @@ type Equipment struct {
116116
// GrenadeProjectile is a grenade thrown intentionally by a player. It is used to track grenade projectile
117117
// positions between the time at which they are thrown and until they detonate.
118118
type GrenadeProjectile struct {
119-
EntityID int
120-
Weapon EquipmentElement
121-
Thrower *Player
122-
Owner *Player
119+
EntityID int
120+
Weapon EquipmentElement
121+
Thrower *Player
122+
Owner *Player
123+
Position r3.Vector
124+
Trajectory []r3.Vector
123125

124126
// uniqueID is used to distinguish different grenades (which potentially have the same, reused entityID) from each other.
125127
uniqueID int64
126-
127-
Position r3.Vector
128128
}
129129

130130
// Class returns the class of the equipment.

datatables.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55
"strings"
66

7+
r3 "github.com/golang/geo/r3"
8+
79
common "github.com/markus-wa/demoinfocs-golang/common"
810
events "github.com/markus-wa/demoinfocs-golang/events"
911
st "github.com/markus-wa/demoinfocs-golang/sendtables"
@@ -270,15 +272,19 @@ func (p *Parser) bindWeapons() {
270272
// It does NOT track the location of grenades lying on the ground, i.e. that were dropped by dead players.
271273
func (p *Parser) bindGrenadeProjectiles(entity *st.Entity) {
272274
entityID := entity.ID()
273-
p.gameState.grenadeProjectiles[entityID] = common.NewGrenadeProjectile()
275+
276+
proj := common.NewGrenadeProjectile()
277+
proj.EntityID = entityID
278+
p.gameState.grenadeProjectiles[entityID] = proj
274279

275280
entity.OnDestroy(func() {
281+
p.eventDispatcher.Dispatch(events.NadeProjectileDestroyedEvent{
282+
Projectile: proj,
283+
})
284+
276285
delete(p.gameState.grenadeProjectiles, entityID)
277286
})
278287

279-
proj := p.gameState.grenadeProjectiles[entityID]
280-
proj.EntityID = entityID
281-
282288
entity.FindProperty("m_nModelIndex").OnUpdate(func(val st.PropertyValue) {
283289
proj.Weapon = p.grenadeModelIndices[val.IntVal]
284290
})
@@ -297,8 +303,10 @@ func (p *Parser) bindGrenadeProjectiles(entity *st.Entity) {
297303
proj.Owner = player
298304
})
299305

300-
entity.FindProperty("m_vecOrigin").OnUpdate(func(st.PropertyValue) {
301-
proj.Position = entity.Position()
306+
entity.OnPositionUpdate(func(newPos r3.Vector) {
307+
proj.Position = newPos
308+
309+
proj.Trajectory = append(proj.Trajectory, newPos)
302310
})
303311

304312
// Some demos don't have this property as it seems

events/events.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ type NadeProjectileThrownEvent struct {
209209
Projectile *common.GrenadeProjectile
210210
}
211211

212+
// NadeProjectileDestroyedEvent signals that a nade entity has been destroyed (i.e. it detonated / expired).
213+
// This is different from the other Nade events because it's sent out when the projectile entity is destroyed.
214+
//
215+
// Mainly useful for getting the full trajectory of the projectile.
216+
type NadeProjectileDestroyedEvent struct {
217+
Projectile *common.GrenadeProjectile
218+
}
219+
212220
// PlayerFlashedEvent signals that a player was flashed.
213221
type PlayerFlashedEvent struct {
214222
Player *common.Player

examples/nade-trajectories/nade_trajectories.go

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,39 +44,33 @@ func main() {
4444
_, err = p.ParseHeader()
4545
checkError(err)
4646

47-
nadePaths := make(map[int64]*nadePath) // Currently live projectiles
48-
49-
storeNadePath := func(id int64, pos r3.Vector, wep common.EquipmentElement, team common.Team) {
50-
if nadePaths[id] == nil {
51-
nadePaths[id] = &nadePath{
52-
wep: wep,
53-
team: team,
47+
nadeTrajectories := make(map[int64]*nadePath) // Trajectories of all destroyed nades
48+
49+
p.RegisterEventHandler(func(e events.NadeProjectileDestroyedEvent) {
50+
id := e.Projectile.UniqueID()
51+
if nadeTrajectories[id] == nil {
52+
nadeTrajectories[id] = &nadePath{
53+
wep: e.Projectile.Weapon,
54+
team: e.Projectile.Thrower.Team,
5455
}
5556
}
5657

57-
nadePaths[id].path = append(nadePaths[id].path, pos)
58-
}
59-
60-
p.RegisterEventHandler(func(e events.NadeProjectileThrownEvent) {
61-
storeNadePath(e.Projectile.UniqueID(), e.Projectile.Position, e.Projectile.Weapon, e.Projectile.Thrower.Team)
62-
})
63-
64-
p.RegisterEventHandler(func(e events.NadeProjectileBouncedEvent) {
65-
storeNadePath(e.Projectile.UniqueID(), e.Projectile.Position, e.Projectile.Weapon, e.Projectile.Thrower.Team)
58+
nadeTrajectories[id].path = e.Projectile.Trajectory
6659
})
6760

68-
var nadePathsFirstHalf []*nadePath
61+
var nadeTrajectoriesFirstHalf []*nadePath
6962
round := 0
7063
p.RegisterEventHandler(func(events.RoundEndedEvent) {
7164
round++
72-
// Very cheap first half check (we only want the first teams CT-side nades in the example).
65+
// Very cheap first half check
66+
// We only want the first teams CT-side nades in the example so the image is not too cluttered
7367
// Won't work with demos that have match-restarts etc.
7468
if round == 15 {
7569
// Copy all nade paths from the first 15 rounds
76-
for _, np := range nadePaths {
77-
nadePathsFirstHalf = append(nadePathsFirstHalf, np)
70+
for _, np := range nadeTrajectories {
71+
nadeTrajectoriesFirstHalf = append(nadeTrajectoriesFirstHalf, np)
7872
}
79-
nadePaths = make(map[int64]*nadePath)
73+
nadeTrajectories = make(map[int64]*nadePath)
8074
}
8175
})
8276

@@ -102,7 +96,7 @@ func main() {
10296
gc.SetLineWidth(1) // 1 px lines
10397
gc.SetFillColor(color.RGBA{0, 0, 0, 0}) // No fill, alpha 0
10498

105-
for _, np := range nadePathsFirstHalf {
99+
for _, np := range nadeTrajectoriesFirstHalf {
106100
if np.team != common.TeamCounterTerrorists {
107101
// Only draw CT nades
108102
continue
6.61 KB
Loading

sendtables/entity.go

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ func (e *Entity) ApplyUpdate(reader *bit.BitReader) {
8181

8282
for _, idx := range *updatedPropIndices {
8383
propDecoder.decodeProp(&e.props[idx], reader)
84+
}
85+
86+
// Fire update only after all properties have been updated
87+
// That way data that is made up of multiple properties won't be wrong
88+
// For instance the entity's position
89+
for _, idx := range *updatedPropIndices {
8490
e.props[idx].firePropertyUpdate()
8591
}
8692

@@ -147,15 +153,22 @@ func (e *Entity) applyBaseline(baseline map[int]PropertyValue) {
147153
}
148154
}
149155

150-
const maxCoordInt = 16384
156+
const (
157+
maxCoordInt = 16384
158+
propCellBits = "m_cellbits"
159+
propCellX = "m_cellX"
160+
propCellY = "m_cellY"
161+
propCellZ = "m_cellZ"
162+
propVecOrigin = "m_vecOrigin"
163+
)
151164

152165
// Position returns the entity's position in world coordinates.
153166
func (e *Entity) Position() r3.Vector {
154-
cellWidth := 1 << uint(e.FindProperty("m_cellbits").value.IntVal)
155-
cellX := e.FindProperty("m_cellX").value.IntVal
156-
cellY := e.FindProperty("m_cellY").value.IntVal
157-
cellZ := e.FindProperty("m_cellZ").value.IntVal
158-
offset := e.FindProperty("m_vecOrigin").value.VectorVal
167+
cellWidth := 1 << uint(e.FindProperty(propCellBits).value.IntVal)
168+
cellX := e.FindProperty(propCellX).value.IntVal
169+
cellY := e.FindProperty(propCellY).value.IntVal
170+
cellZ := e.FindProperty(propCellZ).value.IntVal
171+
offset := e.FindProperty(propVecOrigin).value.VectorVal
159172

160173
return r3.Vector{
161174
X: coordFromCell(cellX, cellWidth, offset.X),
@@ -164,6 +177,38 @@ func (e *Entity) Position() r3.Vector {
164177
}
165178
}
166179

180+
// OnPositionUpdate registers a handler for the entity's position update.
181+
// The handler is called with the new position every time a position-relevant property is updated.
182+
// This does NOT work for players as their position is calculated differently.
183+
//
184+
// See also Position()
185+
func (e *Entity) OnPositionUpdate(h func(pos r3.Vector)) {
186+
pos := new(r3.Vector)
187+
firePosUpdate := func(PropertyValue) {
188+
newPos := e.Position()
189+
if *pos != newPos {
190+
h(newPos)
191+
*pos = newPos
192+
}
193+
}
194+
195+
e.FindProperty(propCellX).OnUpdate(firePosUpdate)
196+
e.FindProperty(propCellY).OnUpdate(firePosUpdate)
197+
e.FindProperty(propCellZ).OnUpdate(firePosUpdate)
198+
e.FindProperty(propVecOrigin).OnUpdate(firePosUpdate)
199+
}
200+
201+
// BindPosition binds the entity's position to a pointer variable.
202+
// The pointer is updated every time a position-relevant property is updated.
203+
// This does NOT work for players as their position is calculated differently.
204+
//
205+
// See also OnPositionUpdate()
206+
func (e *Entity) BindPosition(pos *r3.Vector) {
207+
e.OnPositionUpdate(func(newPos r3.Vector) {
208+
*pos = newPos
209+
})
210+
}
211+
167212
// Returns a coordinate from a cell + offset
168213
func coordFromCell(cell, cellWidth int, offset float64) float64 {
169214
return float64(cell*cellWidth-maxCoordInt) + offset

0 commit comments

Comments
 (0)