From 3b8cde44fdeeb3d54a90d8be083e122ae5965f7b Mon Sep 17 00:00:00 2001 From: AkiVer Date: Tue, 6 Aug 2024 23:00:18 +0200 Subject: [PATCH 1/2] feat: add BulletDamage event --- pkg/demoinfocs/events/events.go | 14 ++++++++++++++ pkg/demoinfocs/game_events.go | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pkg/demoinfocs/events/events.go b/pkg/demoinfocs/events/events.go index b500b5aa..25b980c8 100644 --- a/pkg/demoinfocs/events/events.go +++ b/pkg/demoinfocs/events/events.go @@ -399,6 +399,20 @@ type HostageStateChanged struct { Hostage *common.Hostage } +// BulletDamage signals that a bullet did some damage. +// Available only with CS2 demos after the 22/07/2024 update. +type BulletDamage struct { + Attacker *common.Player + Victim *common.Player + Distance float32 + DamageDirX float32 + DamageDirY float32 + DamageDirZ float32 + NumPenetrations int + IsNoScope bool + IsAttackerInAir bool +} + // HitGroup is the type for the various HitGroupXYZ constants. // // See PlayerHurt. diff --git a/pkg/demoinfocs/game_events.go b/pkg/demoinfocs/game_events.go index eb16120f..036c0ada 100644 --- a/pkg/demoinfocs/game_events.go +++ b/pkg/demoinfocs/game_events.go @@ -208,6 +208,7 @@ func newGameEventHandler(parser *parser, ignoreBombsiteIndexNotFound bool) gameE "bomb_pickup": delayIfNoPlayers(geh.bombPickup), // Bomb picked up "bomb_planted": delayIfNoPlayers(geh.bombPlanted), // Plant finished "bot_takeover": delay(geh.botTakeover), // Bot got taken over + "bullet_damage": delayIfNoPlayers(geh.bulletDamage), // CS2 only "buytime_ended": nil, // Not actually end of buy time, seems to only be sent once per game at the start "choppers_incoming_warning": nil, // Helicopters are coming (Danger zone mode) "cs_intermission": nil, // Dunno, only in locally recorded (POV) demo @@ -653,6 +654,22 @@ func (geh gameEventHandler) HostageRescuedAll(map[string]*msg.CSVCMsg_GameEventK geh.dispatch(events.HostageRescuedAll{}) } +func (geh gameEventHandler) bulletDamage(data map[string]*msg.CSVCMsg_GameEventKeyT) { + event := events.BulletDamage{ + Attacker: geh.playerByUserID32(data["attacker"].GetValShort()), + Victim: geh.playerByUserID32(data["victim"].GetValShort()), + Distance: data["distance"].GetValFloat(), + DamageDirX: data["damage_dir_x"].GetValFloat(), + DamageDirY: data["damage_dir_y"].GetValFloat(), + DamageDirZ: data["damage_dir_z"].GetValFloat(), + NumPenetrations: int(data["num_penetrations"].GetValShort()), + IsNoScope: data["no_scope"].GetValBool(), + IsAttackerInAir: data["in_air"].GetValBool(), + } + + geh.dispatch(event) +} + func (geh gameEventHandler) playerConnect(data map[string]*msg.CSVCMsg_GameEventKeyT) { pl := common.PlayerInfo{ UserID: int(data["userid"].GetValShort()), From 715d9ace830ede0fe4e23ddb89632ab62074411f Mon Sep 17 00:00:00 2001 From: AkiVer Date: Tue, 6 Aug 2024 23:07:15 +0200 Subject: [PATCH 2/2] feat: add SayText/SayText2 and ChatMessage events for CS2 demos It looks like chat messages are now networked in recent Valve demos. --- pkg/demoinfocs/net_messages.go | 46 ++++++++++++++++++++++++++++++++++ pkg/demoinfocs/parser.go | 2 ++ 2 files changed, 48 insertions(+) diff --git a/pkg/demoinfocs/net_messages.go b/pkg/demoinfocs/net_messages.go index 7dc9def2..29fafc0c 100644 --- a/pkg/demoinfocs/net_messages.go +++ b/pkg/demoinfocs/net_messages.go @@ -109,6 +109,52 @@ func (p *parser) handleServerInfoS2(srvInfo *msgs2.CSVCMsg_ServerInfo) { }) } +func (p *parser) handleMessageSayText(msg *msgs2.CUserMessageSayText) { + p.eventDispatcher.Dispatch(events.SayText{ + EntIdx: int(msg.GetPlayerindex()), + IsChat: msg.GetChat(), + IsChatAll: false, + Text: msg.GetText(), + }) +} + +func (p *parser) handleMessageSayText2(msg *msgs2.CUserMessageSayText2) { + p.eventDispatcher.Dispatch(events.SayText2{ + EntIdx: int(msg.GetEntityindex()), + IsChat: msg.GetChat(), + IsChatAll: false, + MsgName: msg.GetMessagename(), + Params: []string{msg.GetParam1(), msg.GetParam2(), msg.GetParam3(), msg.GetParam4()}, + }) + + switch msg.GetMessagename() { + case "Cstrike_Chat_All": + fallthrough + case "Cstrike_Chat_AllDead": + sender := p.gameState.playersByEntityID[int(msg.GetEntityindex())] + + p.eventDispatcher.Dispatch(events.ChatMessage{ + Sender: sender, + Text: msg.GetParam2(), + IsChatAll: false, + }) + + case "#CSGO_Coach_Join_T": // Ignore these + case "#CSGO_Coach_Join_CT": + case "#Cstrike_Name_Change": + case "Cstrike_Chat_T_Loc": + case "Cstrike_Chat_CT_Loc": + case "Cstrike_Chat_T_Dead": + case "Cstrike_Chat_CT_Dead": + + default: + errMsg := fmt.Sprintf("skipped sending ChatMessageEvent for SayText2 with unknown MsgName %q", msg.GetMessagename()) + + p.eventDispatcher.Dispatch(events.ParserWarn{Message: errMsg}) + unassert.Error(errMsg) + } +} + func (p *parser) handleServerRankUpdate(msg *msgs2.CCSUsrMsg_ServerRankUpdate) { for _, v := range msg.RankUpdate { steamID32 := uint32(v.GetAccountId()) diff --git a/pkg/demoinfocs/parser.go b/pkg/demoinfocs/parser.go index 2a7ed6c3..4b25eade 100644 --- a/pkg/demoinfocs/parser.go +++ b/pkg/demoinfocs/parser.go @@ -427,6 +427,8 @@ func NewParserWithConfig(demostream io.Reader, config ParserConfig) Parser { p.msgDispatcher.RegisterHandler(p.handleUpdateStringTableS2) p.msgDispatcher.RegisterHandler(p.handleSetConVarS2) p.msgDispatcher.RegisterHandler(p.handleServerRankUpdate) + p.msgDispatcher.RegisterHandler(p.handleMessageSayText) + p.msgDispatcher.RegisterHandler(p.handleMessageSayText2) if config.MsgQueueBufferSize >= 0 { p.initMsgQueue(config.MsgQueueBufferSize)