Skip to content

Commit d5761a6

Browse files
committed
Bouncy nades event + example
1 parent f95684e commit d5761a6

File tree

13 files changed

+308
-5
lines changed

13 files changed

+308
-5
lines changed

Gopkg.lock

Lines changed: 31 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
name = "github.com/markus-wa/godispatch"
4242
version = "1.1.0"
4343

44+
[[constraint]]
45+
branch = "master"
46+
name = "github.com/llgcode/draw2d"
47+
4448
[prune]
4549
go-tests = true
4650
unused-packages = true

common/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ func initEqElementToName() {
139139
eqElementToName[EqHelmet] = "Kevlar + Helmet"
140140
eqElementToName[EqDefuseKit] = "Defuse Kit"
141141
eqElementToName[EqKnife] = "Knife"
142+
eqElementToName[EqUnknown] = "UNKNOWN"
142143
}
143144

144145
// MapEquipment creates an EquipmentElement from the name of the weapon / equipment.

datatables.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"strings"
66

77
common "github.com/markus-wa/demoinfocs-golang/common"
8+
events "github.com/markus-wa/demoinfocs-golang/events"
89
st "github.com/markus-wa/demoinfocs-golang/sendtables"
910
)
1011

@@ -346,9 +347,29 @@ func (p *Parser) bindGrenadeProjectiles(event st.EntityCreatedEvent) {
346347
proj.Owner = player
347348
})
348349

349-
event.Entity.FindProperty("m_vecOrigin").RegisterPropertyUpdateHandler(func(val st.PropValue) {
350+
event.Entity.FindProperty("m_vecOrigin").RegisterPropertyUpdateHandler(func(st.PropValue) {
350351
proj.Position = event.Entity.Position()
351352
})
353+
354+
// Some demos don't have this property as it seems
355+
// So we need to check for nil and can't send out bounce events if it's missing
356+
bounceProp := event.Entity.FindProperty("m_nBounces")
357+
if bounceProp != nil {
358+
bounceProp.RegisterPropertyUpdateHandler(func(val st.PropValue) {
359+
if val.IntVal != 0 {
360+
p.eventDispatcher.Dispatch(events.NadeProjectileBouncedEvent{
361+
Projectile: proj,
362+
BounceNr: val.IntVal,
363+
})
364+
}
365+
})
366+
}
367+
368+
event.Entity.FindProperty(st.CreateFinishedDummyPropertyName).RegisterPropertyUpdateHandler(func(st.PropValue) {
369+
p.eventDispatcher.Dispatch(events.NadeProjectileThrownEvent{
370+
Projectile: proj,
371+
})
372+
})
352373
}
353374

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

events/events.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ type WeaponFiredEvent struct {
103103
// FIXME: Currently handling NadeEventIf is really annoying. Improve that
104104
// Same with BombEventIf
105105

106-
// NadeEventIf is the interface for all NadeEvents. Used to catch
107-
// the different events with the same handler.
106+
// NadeEventIf is the interface for all NadeEvents (except NadeProjectile* events).
107+
// Used to catch the different events with the same handler.
108108
type NadeEventIf interface {
109109
Base() NadeEvent
110110
}
@@ -164,6 +164,18 @@ type FireNadeEndEvent struct {
164164
NadeEvent
165165
}
166166

167+
// NadeProjectileBouncedEvent signals that a nade has just bounced off a wall/floor/ceiling or object
168+
type NadeProjectileBouncedEvent struct {
169+
Projectile *common.GrenadeProjectile
170+
BounceNr int
171+
}
172+
173+
// NadeProjectileThrownEvent signals that a nade has just been thrown.
174+
// This is different from the WeaponFiredEvent because it's sent out when the projectile entity is created.
175+
type NadeProjectileThrownEvent struct {
176+
Projectile *common.GrenadeProjectile
177+
}
178+
167179
// PlayerFlashedEvent signals that a player was flashed.
168180
type PlayerFlashedEvent struct {
169181
Player *common.Player

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ Here you can find a overview of examples on how to use demoinfocs-golang.
55
|Example|Description
66
|-|-|
77
|[heatmap](heatmap)|Creating a heatmap from positions where players fired shots from|
8+
|[nade-trajectories](nade-trajectories)|Map overview with grenade trajectories|
89
|[net-messages](net-messages)|Parsing and handling custom net-messages|
910
|[print-events](print-events)|Printig kills & scores|

examples/de_cache.jpg

223 KB
Loading
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Nade trajectory overview
2+
3+
This example shows how to create a overview of grenade trajectories of a match.
4+
5+
## Running the example
6+
7+
`go run nade_trajectories.go -demo /path/to/demo > out.jpg`
8+
9+
This will create a JPGE with trajectories of the first half's CT-team. The reason it doesn't do all nades is because the image would look quite cluttered otherwise.
10+
11+
![Resulting map overview with trajectories](https://raw.githubusercontent.com/markus-wa/demoinfocs-golang/master/examples/nade-trajectories/nade_trajectories.jpg)
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"image"
6+
"image/color"
7+
"image/draw"
8+
"image/jpeg"
9+
_ "image/jpeg"
10+
"log"
11+
"os"
12+
13+
r3 "github.com/golang/geo/r3"
14+
draw2dimg "github.com/llgcode/draw2d/draw2dimg"
15+
16+
dem "github.com/markus-wa/demoinfocs-golang"
17+
common "github.com/markus-wa/demoinfocs-golang/common"
18+
events "github.com/markus-wa/demoinfocs-golang/events"
19+
ex "github.com/markus-wa/demoinfocs-golang/examples"
20+
)
21+
22+
type nadePath struct {
23+
wep common.EquipmentElement
24+
path []r3.Vector
25+
team common.Team
26+
}
27+
28+
var (
29+
colorFire color.Color = color.RGBA{0xff, 0x00, 0x00, 0xff} // Red
30+
colorHE color.Color = color.RGBA{0xff, 0xff, 0x00, 0xff} // Yellow
31+
colorFlash color.Color = color.RGBA{0x00, 0x00, 0xff, 0xff} // Blue, because of the color on the nade
32+
colorSmoke color.Color = color.RGBA{0xbe, 0xbe, 0xbe, 0xff} // Light gray
33+
colorDecoy color.Color = color.RGBA{0x96, 0x4b, 0x00, 0xff} // Brown, because it's shit :)
34+
)
35+
36+
// Run like this: go run bouncynades.go -demo /path/to/demo.dem > bouncynades.jpg
37+
func main() {
38+
f, err := os.Open(ex.DemoPathFromArgs())
39+
checkError(err)
40+
defer f.Close()
41+
42+
p := dem.NewParser(f)
43+
44+
_, err = p.ParseHeader()
45+
checkError(err)
46+
47+
nadePaths := make([]*nadePath, 0) // Paths of detonated projectiles
48+
currentNadePaths := make(map[int]*nadePath) // Currently live projectiles
49+
50+
storeNadePath := func(entityID int, pos r3.Vector, wep common.EquipmentElement, team common.Team) {
51+
if currentNadePaths[entityID] == nil {
52+
currentNadePaths[entityID] = &nadePath{
53+
wep: wep,
54+
team: team,
55+
}
56+
}
57+
58+
currentNadePaths[entityID].path = append(currentNadePaths[entityID].path, pos)
59+
}
60+
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+
74+
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)
83+
})
84+
85+
p.RegisterEventHandler(func(e events.NadeProjectileBouncedEvent) {
86+
storeNadePath(e.Projectile.EntityID, e.Projectile.Position, e.Projectile.Weapon, e.Projectile.Thrower.Team)
87+
})
88+
89+
var nadePathsFirstHalf []*nadePath
90+
round := 0
91+
p.RegisterEventHandler(func(events.RoundEndedEvent) {
92+
round++
93+
// Very cheap first half check (we only want the first teams CT-side nades in the example).
94+
// Won't work with demos that have match-restarts etc.
95+
if round == 15 {
96+
nadePathsFirstHalf = nadePaths
97+
nadePaths = make([]*nadePath, 0)
98+
}
99+
})
100+
101+
err = p.ParseToEnd()
102+
checkError(err)
103+
104+
// Draw image
105+
106+
// Create output canvas
107+
dest := image.NewRGBA(image.Rect(0, 0, 1024, 1024))
108+
109+
// Use cache map overview as base
110+
fCache, err := os.Open("../de_cache.jpg")
111+
checkError(err)
112+
113+
imgCache, _, err := image.Decode(fCache)
114+
checkError(err)
115+
draw.Draw(dest, dest.Bounds(), imgCache, image.Point{0, 0}, draw.Src)
116+
117+
// Initialize the graphic context
118+
gc := draw2dimg.NewGraphicContext(dest)
119+
120+
gc.SetLineWidth(1) // 1 px lines
121+
gc.SetFillColor(color.RGBA{0, 0, 0, 0}) // No fill, alpha 0
122+
123+
// Add any pending paths
124+
for _, np := range currentNadePaths {
125+
nadePaths = append(nadePaths, np)
126+
}
127+
128+
for _, np := range nadePathsFirstHalf {
129+
if np.team != common.TeamCounterTerrorists {
130+
// Only draw CT nades
131+
continue
132+
}
133+
134+
// Set colors
135+
switch np.wep {
136+
case common.EqMolotov:
137+
fallthrough
138+
case common.EqIncendiary:
139+
gc.SetStrokeColor(colorFire)
140+
141+
case common.EqHE:
142+
gc.SetStrokeColor(colorHE)
143+
144+
case common.EqFlash:
145+
gc.SetStrokeColor(colorFlash)
146+
147+
case common.EqSmoke:
148+
gc.SetStrokeColor(colorSmoke)
149+
150+
case common.EqDecoy:
151+
gc.SetStrokeColor(colorDecoy)
152+
153+
default:
154+
// Set alpha to 0 so we don't draw unknown stuff
155+
gc.SetStrokeColor(color.RGBA{0x00, 0x00, 0x00, 0x00})
156+
fmt.Println("Unknown grenade type", np.wep)
157+
}
158+
159+
// Draw path
160+
gc.MoveTo(translateX(np.path[0].X), translateY(np.path[0].Y)) // Move to a position to start the new path
161+
162+
for _, pos := range np.path[1:] {
163+
gc.LineTo(translateX(pos.X), translateY(pos.Y))
164+
}
165+
166+
gc.FillStroke()
167+
}
168+
169+
// Write to standard output
170+
jpeg.Encode(os.Stdout, dest, &jpeg.Options{
171+
Quality: 90,
172+
})
173+
}
174+
175+
// Rough translations for x & y coordinates from de_cache to 1024x1024 px.
176+
// This could be done nicer by only having to provide the mapping between two source & target coordinates and the max size.
177+
// Then we could calculate the correct stretch & offset automatically.
178+
179+
const (
180+
stretchX = 0.18
181+
offsetX = 414
182+
183+
stretchY = -0.18
184+
offsetY = 630
185+
)
186+
187+
func translateX(x float64) float64 {
188+
return x*stretchX + offsetX
189+
}
190+
191+
func translateY(y float64) float64 {
192+
return y*stretchY + offsetY
193+
}
194+
195+
func checkError(err error) {
196+
if err != nil {
197+
log.Fatal(err)
198+
}
199+
}
238 KB
Loading

0 commit comments

Comments
 (0)