Skip to content

Commit 98f7ad9

Browse files
committed
events: delay dispatching of ItemEquip (#142)
If a bot takes over a player the item_equip event may be sent out before the new (bot) player-entity is created. To avoid ItemEquip.Player == nil we need to delay the event to the end of the tick. This commit also changes how events are delayed. Now the whole event handler function is executed at the end of the tick, not just the dispatching.
1 parent bd95f6d commit 98f7ad9

File tree

3 files changed

+91
-96
lines changed

3 files changed

+91
-96
lines changed

game_events.go

Lines changed: 87 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,6 @@ func (geh gameEventHandler) dispatch(event interface{}) {
5555
geh.parser.eventDispatcher.Dispatch(event)
5656
}
5757

58-
func (geh gameEventHandler) delay(event interface{}) {
59-
geh.parser.delayedEvents = append(geh.parser.delayedEvents, event)
60-
}
61-
6258
func (geh gameEventHandler) gameState() *GameState {
6359
return geh.parser.gameState
6460
}
@@ -76,73 +72,80 @@ type gameEventHandlerFunc func(map[string]*msg.CSVCMsg_GameEventKeyT)
7672
func newGameEventHandler(parser *Parser) gameEventHandler {
7773
geh := gameEventHandler{parser: parser}
7874

75+
delay := func(f gameEventHandlerFunc) gameEventHandlerFunc {
76+
return func(data map[string]*msg.CSVCMsg_GameEventKeyT) { f(data) }
77+
}
7978
geh.gameEventNameToHandler = map[string]gameEventHandlerFunc{
8079
// sorted alphabetically
81-
"announce_phase_end": nil, // Dunno
82-
"begin_new_match": geh.beginNewMatch, // Match started
83-
"bomb_beep": nil, // Bomb beep
84-
"bomb_begindefuse": geh.bombBeginDefuse, // Defuse started
85-
"bomb_beginplant": geh.bombBeginPlant, // Plant started
86-
"bomb_defused": geh.bombDefused, // Defuse finished
87-
"bomb_dropped": geh.bombDropped, // Bomb dropped
88-
"bomb_exploded": geh.bombExploded, // Bomb exploded
89-
"bomb_pickup": geh.bombPickup, // Bomb picked up
90-
"bomb_planted": geh.bombPlanted, // Plant finished
91-
"bot_takeover": geh.botTakeover, // Bot got taken over
92-
"buytime_ended": nil, // Not actually end of buy time, seems to only be sent once per game at the start
93-
"cs_match_end_restart": nil, // Yawn
94-
"cs_pre_restart": nil, // Not sure, doesn't seem to be important
95-
"cs_round_final_beep": nil, // Final beep
96-
"cs_round_start_beep": nil, // Round start beeps
97-
"cs_win_panel_match": geh.csWinPanelMatch, // Not sure, maybe match end event???
98-
"cs_win_panel_round": nil, // Win panel, (==end of match?)
99-
"decoy_detonate": geh.decoyDetonate, // Decoy exploded/expired
100-
"decoy_started": geh.decoyStarted, // Decoy started
101-
"endmatch_cmm_start_reveal_items": nil, // Drops
102-
"flashbang_detonate": geh.flashBangDetonate, // Flash exploded
103-
"hegrenade_detonate": geh.heGrenadeDetonate, // HE exploded
104-
"hltv_chase": nil, // Don't care
105-
"hltv_fixed": nil, // Dunno
106-
"hltv_status": nil, // Don't know
107-
"inferno_expire": geh.infernoExpire, // Incendiary expired
108-
"inferno_startburn": geh.infernoStartBurn, // Incendiary exploded/started
109-
"item_equip": geh.itemEquip, // Equipped, I think
110-
"item_pickup": geh.itemPickup, // Picked up or bought?
111-
"item_remove": geh.itemRemove, // Dropped?
112-
"other_death": nil, // Dunno
113-
"player_blind": geh.playerBlind, // Player got blinded by a flash
114-
"player_changename": nil, // Name change
115-
"player_connect": geh.playerConnect, // Bot connected or player reconnected, players normally come in via string tables & data tables
116-
"player_connect_full": nil, // Connecting finished
117-
"player_death": geh.playerDeath, // Player died
118-
"player_disconnect": geh.playerDisconnect, // Player disconnected (kicked, quit, timed out etc.)
119-
"player_falldamage": nil, // Falldamage
120-
"player_footstep": geh.playerFootstep, // Footstep sound
121-
"player_hurt": geh.playerHurt, // Player got hurt
122-
"player_jump": geh.playerJump, // Player jumped
123-
"player_spawn": nil, // Player spawn
124-
"player_team": geh.playerTeam, // Player changed team
125-
"round_announce_final": geh.roundAnnounceFinal, // 30th round for normal de_, not necessarily matchpoint
126-
"round_announce_last_round_half": geh.roundAnnounceLastRoundHalf, // Last round of the half
127-
"round_announce_match_point": nil, // Match point announcement
128-
"round_announce_match_start": nil, // Special match start announcement
129-
"round_announce_warmup": nil, // Dunno
130-
"round_end": geh.roundEnd, // Round ended and the winner was announced
131-
"round_freeze_end": geh.roundFreezeEnd, // Round start freeze ended
132-
"round_mvp": geh.roundMVP, // Round MVP was announced
133-
"round_officially_ended": geh.roundOfficiallyEnded, // The event after which you get teleported to the spawn (=> You can still walk around between round_end and this event)
134-
"round_poststart": nil, // Ditto
135-
"round_prestart": nil, // Ditto
136-
"round_start": geh.roundStart, // Round started
137-
"round_time_warning": nil, // Round time warning
138-
"server_cvar": nil, // Dunno
139-
"smokegrenade_detonate": geh.smokeGrenadeDetonate, // Smoke popped
140-
"smokegrenade_expired": geh.smokeGrenadeExpired, // Smoke expired
141-
"tournament_reward": nil, // Dunno
142-
"weapon_fire": geh.weaponFire, // Weapon was fired
143-
"weapon_fire_on_empty": nil, // Sounds boring
144-
"weapon_reload": geh.weaponReload, // Weapon reloaded
145-
"weapon_zoom": nil, // Zooming in
80+
"announce_phase_end": nil, // Dunno
81+
"begin_new_match": geh.beginNewMatch, // Match started
82+
"bomb_beep": nil, // Bomb beep
83+
"bomb_begindefuse": geh.bombBeginDefuse, // Defuse started
84+
"bomb_beginplant": geh.bombBeginPlant, // Plant started
85+
"bomb_defused": geh.bombDefused, // Defuse finished
86+
"bomb_dropped": geh.bombDropped, // Bomb dropped
87+
"bomb_exploded": geh.bombExploded, // Bomb exploded
88+
"bomb_pickup": geh.bombPickup, // Bomb picked up
89+
"bomb_planted": geh.bombPlanted, // Plant finished
90+
"bot_takeover": geh.botTakeover, // Bot got taken over
91+
"buytime_ended": nil, // Not actually end of buy time, seems to only be sent once per game at the start
92+
"cs_match_end_restart": nil, // Yawn
93+
"cs_pre_restart": nil, // Not sure, doesn't seem to be important
94+
"cs_round_final_beep": nil, // Final beep
95+
"cs_round_start_beep": nil, // Round start beeps
96+
"cs_win_panel_match": geh.csWinPanelMatch, // Not sure, maybe match end event???
97+
"cs_win_panel_round": nil, // Win panel, (==end of match?)
98+
"decoy_detonate": geh.decoyDetonate, // Decoy exploded/expired
99+
"decoy_started": delay(geh.decoyStarted), // Decoy started. Delayed because projectile entity is not yet created
100+
"endmatch_cmm_start_reveal_items": nil, // Drops
101+
"flashbang_detonate": geh.flashBangDetonate, // Flash exploded
102+
"hegrenade_detonate": geh.heGrenadeDetonate, // HE exploded
103+
"hltv_chase": nil, // Don't care
104+
"hltv_fixed": nil, // Dunno
105+
"hltv_status": nil, // Don't know
106+
"inferno_expire": geh.infernoExpire, // Incendiary expired
107+
"inferno_startburn": delay(geh.infernoStartBurn), // Incendiary exploded/started. Delayed because inferno entity is not yet created
108+
"item_equip": delay(geh.itemEquip), // Equipped / weapon swap, I think. Delayed because of #142 - Bot entity possibly not yet created
109+
"item_pickup": delay(geh.itemPickup), // Picked up or bought? Delayed because of #119 - Equipment.UniqueID()
110+
"item_remove": geh.itemRemove, // Dropped?
111+
"other_death": nil, // Dunno
112+
"player_blind": geh.playerBlind, // Player got blinded by a flash
113+
"player_changename": nil, // Name change
114+
"player_connect": geh.playerConnect, // Bot connected or player reconnected, players normally come in via string tables & data tables
115+
"player_connect_full": nil, // Connecting finished
116+
"player_death": geh.playerDeath, // Player died
117+
"player_disconnect": geh.playerDisconnect, // Player disconnected (kicked, quit, timed out etc.)
118+
"player_falldamage": nil, // Falldamage
119+
"player_footstep": geh.playerFootstep, // Footstep sound
120+
"player_hurt": geh.playerHurt, // Player got hurt
121+
"player_jump": geh.playerJump, // Player jumped
122+
"player_spawn": nil, // Player spawn
123+
124+
// Player changed team. Delayed for two reasons
125+
// - team IDs of other players changing teams in the same tick might not have changed yet
126+
// - player entities might not have been re-created yet after a reconnect
127+
"player_team": delay(geh.playerTeam),
128+
"round_announce_final": geh.roundAnnounceFinal, // 30th round for normal de_, not necessarily matchpoint
129+
"round_announce_last_round_half": geh.roundAnnounceLastRoundHalf, // Last round of the half
130+
"round_announce_match_point": nil, // Match point announcement
131+
"round_announce_match_start": nil, // Special match start announcement
132+
"round_announce_warmup": nil, // Dunno
133+
"round_end": geh.roundEnd, // Round ended and the winner was announced
134+
"round_freeze_end": geh.roundFreezeEnd, // Round start freeze ended
135+
"round_mvp": geh.roundMVP, // Round MVP was announced
136+
"round_officially_ended": geh.roundOfficiallyEnded, // The event after which you get teleported to the spawn (=> You can still walk around between round_end and this event)
137+
"round_poststart": nil, // Ditto
138+
"round_prestart": nil, // Ditto
139+
"round_start": geh.roundStart, // Round started
140+
"round_time_warning": nil, // Round time warning
141+
"server_cvar": nil, // Dunno
142+
"smokegrenade_detonate": geh.smokeGrenadeDetonate, // Smoke popped
143+
"smokegrenade_expired": geh.smokeGrenadeExpired, // Smoke expired
144+
"tournament_reward": nil, // Dunno
145+
"weapon_fire": geh.weaponFire, // Weapon was fired
146+
"weapon_fire_on_empty": nil, // Sounds boring
147+
"weapon_reload": geh.weaponReload, // Weapon reloaded
148+
"weapon_zoom": nil, // Zooming in
146149
}
147150

148151
return geh
@@ -286,7 +289,7 @@ func (geh gameEventHandler) playerHurt(data map[string]*msg.CSVCMsg_GameEventKey
286289
func (geh gameEventHandler) playerBlind(data map[string]*msg.CSVCMsg_GameEventKeyT) {
287290
// Player.FlashDuration hasn't been updated yet,
288291
// so we need to wait until the end of the tick before dispatching
289-
geh.delay(events.PlayerFlashed{
292+
geh.dispatch(events.PlayerFlashed{
290293
Player: geh.playerByUserID32(data["userid"].GetValShort()),
291294
Attacker: geh.gameState().lastFlasher,
292295
})
@@ -308,7 +311,7 @@ func (geh gameEventHandler) heGrenadeDetonate(data map[string]*msg.CSVCMsg_GameE
308311
}
309312

310313
func (geh gameEventHandler) decoyStarted(data map[string]*msg.CSVCMsg_GameEventKeyT) {
311-
geh.delay(events.DecoyStart{
314+
geh.dispatch(events.DecoyStart{
312315
GrenadeEvent: geh.nadeEvent(data, common.EqDecoy),
313316
})
314317
}
@@ -332,7 +335,7 @@ func (geh gameEventHandler) smokeGrenadeExpired(data map[string]*msg.CSVCMsg_Gam
332335
}
333336

334337
func (geh gameEventHandler) infernoStartBurn(data map[string]*msg.CSVCMsg_GameEventKeyT) {
335-
geh.delay(events.FireGrenadeStart{
338+
geh.dispatch(events.FireGrenadeStart{
336339
GrenadeEvent: geh.nadeEvent(data, common.EqIncendiary),
337340
})
338341
}
@@ -382,25 +385,18 @@ func (geh gameEventHandler) playerTeam(data map[string]*msg.CSVCMsg_GameEventKey
382385
if player != nil {
383386
if player.Team != newTeam {
384387
player.Team = newTeam
385-
386-
oldTeam := common.Team(data["oldteam"].GetValByte())
387-
// Delayed for two reasons
388-
// - team IDs of other players changing teams in the same tick might not have changed yet
389-
// - player entities might not have been re-created yet after a reconnect
390-
geh.delay(events.PlayerTeamChange{
391-
Player: player,
392-
IsBot: data["isbot"].GetValBool(),
393-
Silent: data["silent"].GetValBool(),
394-
NewTeam: newTeam,
395-
NewTeamState: geh.gameState().Team(newTeam),
396-
OldTeam: oldTeam,
397-
OldTeamState: geh.gameState().Team(oldTeam),
398-
})
399-
} else {
400-
geh.dispatch(events.ParserWarn{
401-
Message: "Player team swap game-event occurred but player.Team == newTeam",
402-
})
403388
}
389+
390+
oldTeam := common.Team(data["oldteam"].GetValByte())
391+
geh.dispatch(events.PlayerTeamChange{
392+
Player: player,
393+
IsBot: data["isbot"].GetValBool(),
394+
Silent: data["silent"].GetValBool(),
395+
NewTeam: newTeam,
396+
NewTeamState: geh.gameState().Team(newTeam),
397+
OldTeam: oldTeam,
398+
OldTeamState: geh.gameState().Team(oldTeam),
399+
})
404400
} else {
405401
geh.dispatch(events.ParserWarn{
406402
Message: "Player team swap game-event occurred but player is nil",
@@ -479,8 +475,7 @@ func (geh gameEventHandler) itemEquip(data map[string]*msg.CSVCMsg_GameEventKeyT
479475

480476
func (geh gameEventHandler) itemPickup(data map[string]*msg.CSVCMsg_GameEventKeyT) {
481477
player, weapon := geh.itemEvent(data)
482-
// Delayed because of #119 - Equipment.UniqueID()
483-
geh.parser.delayedEvents = append(geh.parser.delayedEvents, events.ItemPickup{
478+
geh.dispatch(events.ItemPickup{
484479
Player: player,
485480
Weapon: *weapon,
486481
})

parser.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ type Parser struct {
7272
gameEventDescs map[int32]*msg.CSVCMsg_GameEventListDescriptorT // Maps game-event IDs to descriptors
7373
grenadeModelIndices map[int]common.EquipmentElement // Used to map model indices to grenades (used for grenade projectiles)
7474
stringTables []*msg.CSVCMsg_CreateStringTable // Contains all created sendtables, needed when updating them
75-
delayedEvents []interface{} // Contains events that need to be dispatched at the end of a tick (e.g. flash events because FlashDuration isn't updated before that)
75+
delayedEventHandlers []func() // Contains event handlers that need to be executed at the end of a tick (e.g. flash events because FlashDuration isn't updated before that)
7676
}
7777

7878
// NetMessageCreator creates additional net-messages to be dispatched to net-message handlers.

parsing.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,10 +333,10 @@ var frameParsedToken = new(frameParsedTokenType)
333333
func (p *Parser) handleFrameParsed(*frameParsedTokenType) {
334334
// PlayerFlashed events need to be dispatched at the end of the tick
335335
// because Player.FlashDuration is updated after the game-events are parsed.
336-
for _, e := range p.delayedEvents {
337-
p.eventDispatcher.Dispatch(e)
336+
for _, eventHandler := range p.delayedEventHandlers {
337+
eventHandler()
338338
}
339-
p.delayedEvents = p.delayedEvents[:0]
339+
p.delayedEventHandlers = p.delayedEventHandlers[:0]
340340

341341
p.currentFrame++
342342
p.eventDispatcher.Dispatch(events.TickDone{})

0 commit comments

Comments
 (0)