Skip to content

Commit 99027b4

Browse files
authored
Fix molotov & incendiary never being deleted (#32)
Add Entity.OnDestroy() for pre-deletion actions Add Entity.OnCreateFinished() for post-creation actions (replaces CreateFinishedDummyProp) Also updated the nade-trajectories example using UniqueID Documented some functions intended for internal use only
1 parent d5b878b commit 99027b4

File tree

8 files changed

+60
-63
lines changed

8 files changed

+60
-63
lines changed

datatables.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -307,15 +307,14 @@ func (p *Parser) bindWeapons() {
307307

308308
}
309309

310-
// bindGrenadeProjectiles keeps track of the location of live grenades, actively thrown by players.
310+
// bindGrenadeProjectiles keeps track of the location of live grenades (Parser.gameState.grenadeProjectiles), actively thrown by players.
311311
// It does track the location of grenades lying on the ground, i.e. that were dropped by dead players.
312-
//
313-
// NOTE: Parser.gameState.grenadeProjectiles is updated here. We rely on code during the handling of the game events
314-
// "[nade]_detonate" and "[nade]_started" to remove projectiles from Parser.gameState.grenadeProjectiles once they detonate.
315312
func (p *Parser) bindGrenadeProjectiles(event st.EntityCreatedEvent) {
316-
if _, ok := p.gameState.grenadeProjectiles[event.Entity.ID]; !ok {
317-
p.gameState.grenadeProjectiles[event.Entity.ID] = common.NewGrenadeProjectile()
318-
}
313+
p.gameState.grenadeProjectiles[event.Entity.ID] = common.NewGrenadeProjectile()
314+
315+
event.Entity.OnDestroy(func() {
316+
delete(p.gameState.grenadeProjectiles, event.Entity.ID)
317+
})
319318

320319
proj := p.gameState.grenadeProjectiles[event.Entity.ID]
321320
proj.EntityID = event.Entity.ID
@@ -362,7 +361,7 @@ func (p *Parser) bindGrenadeProjectiles(event st.EntityCreatedEvent) {
362361
})
363362
}
364363

365-
event.Entity.FindProperty(st.CreateFinishedDummyPropertyName).RegisterPropertyUpdateHandler(func(st.PropValue) {
364+
event.Entity.OnCreateFinished(func() {
366365
p.eventDispatcher.Dispatch(events.NadeProjectileThrownEvent{
367366
Projectile: proj,
368367
})

entities.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func (p *Parser) handlePacketEntities(pe *msg.CSVCMsg_PacketEntities) {
2727

2828
if r.ReadBit() {
2929
// Leave PVS
30+
p.entities[currentEntity].Destroy()
3031
delete(p.entities, currentEntity)
3132

3233
// 'Force Delete' flag, not exactly sure what it's supposed to do

examples/nade-trajectories/nade_trajectories.go

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ var (
3333
colorDecoy color.Color = color.RGBA{0x96, 0x4b, 0x00, 0xff} // Brown, because it's shit :)
3434
)
3535

36-
// Run like this: go run bouncynades.go -demo /path/to/demo.dem > bouncynades.jpg
36+
// Run like this: go run nade_trajectories.go -demo /path/to/demo.dem > nade_trajectories.jpg
3737
func main() {
3838
f, err := os.Open(ex.DemoPathFromArgs())
3939
checkError(err)
@@ -44,46 +44,25 @@ func main() {
4444
_, err = p.ParseHeader()
4545
checkError(err)
4646

47-
nadePaths := make([]*nadePath, 0) // Paths of detonated projectiles
48-
currentNadePaths := make(map[int]*nadePath) // Currently live projectiles
47+
nadePaths := make(map[int64]*nadePath) // Currently live projectiles
4948

50-
storeNadePath := func(entityID int, pos r3.Vector, wep common.EquipmentElement, team common.Team) {
51-
if currentNadePaths[entityID] == nil {
52-
currentNadePaths[entityID] = &nadePath{
49+
storeNadePath := func(id int64, pos r3.Vector, wep common.EquipmentElement, team common.Team) {
50+
if nadePaths[id] == nil {
51+
nadePaths[id] = &nadePath{
5352
wep: wep,
5453
team: team,
5554
}
5655
}
5756

58-
currentNadePaths[entityID].path = append(currentNadePaths[entityID].path, pos)
57+
nadePaths[id].path = append(nadePaths[id].path, pos)
5958
}
6059

61-
p.RegisterEventHandler(func(e events.NadeEventIf) {
62-
ne := e.Base()
63-
64-
var team common.Team
65-
if ne.Thrower != nil {
66-
team = ne.Thrower.Team
67-
}
68-
69-
storeNadePath(ne.NadeEntityID, ne.Position, ne.NadeType, team)
70-
nadePaths = append(nadePaths, currentNadePaths[ne.NadeEntityID])
71-
delete(currentNadePaths, ne.NadeEntityID)
72-
})
73-
7460
p.RegisterEventHandler(func(e events.NadeProjectileThrownEvent) {
75-
// Save previous projectile and delete from current, just a safeguard for missing NadeEvents
76-
np := currentNadePaths[e.Projectile.EntityID]
77-
if np != nil {
78-
nadePaths = append(nadePaths, np)
79-
delete(currentNadePaths, e.Projectile.EntityID)
80-
}
81-
82-
storeNadePath(e.Projectile.EntityID, e.Projectile.Position, e.Projectile.Weapon, e.Projectile.Thrower.Team)
61+
storeNadePath(e.Projectile.UniqueID(), e.Projectile.Position, e.Projectile.Weapon, e.Projectile.Thrower.Team)
8362
})
8463

8564
p.RegisterEventHandler(func(e events.NadeProjectileBouncedEvent) {
86-
storeNadePath(e.Projectile.EntityID, e.Projectile.Position, e.Projectile.Weapon, e.Projectile.Thrower.Team)
65+
storeNadePath(e.Projectile.UniqueID(), e.Projectile.Position, e.Projectile.Weapon, e.Projectile.Thrower.Team)
8766
})
8867

8968
var nadePathsFirstHalf []*nadePath
@@ -93,8 +72,11 @@ func main() {
9372
// Very cheap first half check (we only want the first teams CT-side nades in the example).
9473
// Won't work with demos that have match-restarts etc.
9574
if round == 15 {
96-
nadePathsFirstHalf = nadePaths
97-
nadePaths = make([]*nadePath, 0)
75+
// Copy all nade paths from the first 15 rounds
76+
for _, np := range nadePaths {
77+
nadePathsFirstHalf = append(nadePathsFirstHalf, np)
78+
}
79+
nadePaths = make(map[int64]*nadePath)
9880
}
9981
})
10082

@@ -120,11 +102,6 @@ func main() {
120102
gc.SetLineWidth(1) // 1 px lines
121103
gc.SetFillColor(color.RGBA{0, 0, 0, 0}) // No fill, alpha 0
122104

123-
// Add any pending paths
124-
for _, np := range currentNadePaths {
125-
nadePaths = append(nadePaths, np)
126-
}
127-
128105
for _, np := range nadePathsFirstHalf {
129106
if np.team != common.TeamCounterTerrorists {
130107
// Only draw CT nades
-5.72 KB
Loading

examples/nade-trajectories/nade_trajectories_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,12 @@ import (
99
func TestBouncyNades(t *testing.T) {
1010
os.Args = []string{"cmd", "-demo", "../../cs-demos/default.dem"}
1111

12+
// Redirect stdout, the resulting image is written to this
13+
old := os.Stdout
14+
_, w, _ := os.Pipe()
15+
os.Stdout = w
16+
1217
main()
18+
19+
os.Stdout = old
1320
}

game_events.go

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

87
r3 "github.com/golang/geo/r3"
98

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

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-
192186
switch d.Name {
193187
case "flashbang_detonate": // Flash exploded
194188
p.eventDispatcher.Dispatch(events.FlashExplodedEvent{NadeEvent: buildNadeEvent(common.EqFlash, thrower, position, nadeEntityID)})

sendtables/entity.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ type Entity struct {
1414
ID int
1515
ServerClass *ServerClass
1616
props []PropertyEntry
17+
18+
onCreateFinished []func()
19+
onDestroy []func()
1720
}
1821

1922
// Props returns all properties (PropertyEntry) for the Entity.
@@ -46,6 +49,7 @@ var updatedPropIndicesPool = sync.Pool{
4649

4750
// ApplyUpdate reads an update to an Enitiy's properties and
4851
// triggers registered PropertyUpdateHandlers if values changed.
52+
// Intended for internal use only.
4953
func (e *Entity) ApplyUpdate(reader *bit.BitReader) {
5054
idx := -1
5155
newWay := reader.ReadBit()
@@ -96,6 +100,7 @@ func readFieldIndex(reader *bit.BitReader, lastIndex int, newWay bool) int {
96100
}
97101

98102
// InitializeBaseline applies an update and collects a baseline (default values) from the update.
103+
// Intended for internal use only.
99104
func (e *Entity) InitializeBaseline(r *bit.BitReader) map[int]PropValue {
100105
baseline := make(map[int]PropValue)
101106
for i := range e.props {
@@ -116,7 +121,8 @@ func (e *Entity) InitializeBaseline(r *bit.BitReader) map[int]PropValue {
116121
return baseline
117122
}
118123

119-
// ApplyBaseline baseline applies a previously collected baseline
124+
// ApplyBaseline baseline applies a previously collected baseline.
125+
// Intended for internal use only.
120126
func (e *Entity) ApplyBaseline(baseline map[int]PropValue) {
121127
for idx := range baseline {
122128
e.props[idx].value = baseline[idx]
@@ -145,10 +151,29 @@ func coordFromCell(cell, cellWidth int, offset float64) float64 {
145151
return float64(cell*cellWidth-maxCoordInt) + offset
146152
}
147153

154+
// OnDestroy registers a function to be called on the entity's destruction.
155+
func (e *Entity) OnDestroy(delegate func()) {
156+
e.onDestroy = append(e.onDestroy, delegate)
157+
}
158+
159+
// Destroy triggers all via OnDestroy() registered functions.
160+
// Intended for internal use only.
161+
func (e *Entity) Destroy() {
162+
for _, f := range e.onDestroy {
163+
f()
164+
}
165+
}
166+
167+
// OnCreateFinished registers a function to be called once the entity is fully created -
168+
// i.e. once all property updates have been sent out.
169+
func (e *Entity) OnCreateFinished(delegate func()) {
170+
e.onCreateFinished = append(e.onCreateFinished, delegate)
171+
}
172+
148173
// NewEntity creates a new Entity with a given id and ServerClass and returns it.
149174
func NewEntity(id int, serverClass *ServerClass) *Entity {
150175
propCount := len(serverClass.FlattenedProps)
151-
props := make([]PropertyEntry, propCount, propCount+1) // Cap +1 for CreateFinishedDummyProp
176+
props := make([]PropertyEntry, propCount)
152177
for i := range serverClass.FlattenedProps {
153178
props[i] = PropertyEntry{entry: &serverClass.FlattenedProps[i]}
154179
}

sendtables/sendtables.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,9 @@ type ServerClass struct {
3838
entityCreatedHandlers []EntityCreatedHandler
3939
}
4040

41-
// CreateFinishedDummyPropertyName is a dummy property for entities.
42-
// When it's updated it signals that the entity creation has finished (all property updates sent out).
43-
// TODO: Figure out a better way to do this.
44-
const CreateFinishedDummyPropertyName = "CreateFinishedDummyProp"
45-
4641
// FireEntityCreatedEvent triggers all registered EntityCreatedHandlers
4742
// on the ServerClass with a new EntityCreatedEvent.
4843
func (sc *ServerClass) FireEntityCreatedEvent(entity *Entity) {
49-
entity.props = append(entity.props, PropertyEntry{
50-
entry: &FlattenedPropEntry{
51-
name: CreateFinishedDummyPropertyName,
52-
},
53-
})
54-
5544
for _, h := range sc.entityCreatedHandlers {
5645
if h != nil {
5746
h(EntityCreatedEvent{Entity: entity, ServerClass: sc})
@@ -61,6 +50,11 @@ func (sc *ServerClass) FireEntityCreatedEvent(entity *Entity) {
6150
for _, v := range entity.props {
6251
v.firePropertyUpdate()
6352
}
53+
54+
// Fire all create-finished actions
55+
for _, f := range entity.onCreateFinished {
56+
f()
57+
}
6458
}
6559

6660
// RegisterEntityCreatedHandler registers a EntityCreatedHandler on the ServerClass.

0 commit comments

Comments
 (0)