commit 319ba38ea22c24df8cae576369a470dde2511651 Author: chris Date: Tue May 27 22:12:13 2025 +0000 Dateien nach "/" hochladen diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..df5747d --- /dev/null +++ b/build.bat @@ -0,0 +1,10 @@ +@echo off +echo 🔨 Baue Windows 64-bit... +go build -o parser_cs2-win.exe main.go + +echo 🔨 Baue Linux 64-bit... +set GOOS=linux +set GOARCH=amd64 +go build -o parser_cs2-linux main.go + +echo ✅ Build abgeschlossen! diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..777b286 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module cs2-parser + +go 1.23.2 + +require github.com/markus-wa/demoinfocs-golang/v4 v4.3.3 + +require ( + github.com/golang/geo v0.0.0-20250505201543-5b58c72585db // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/markus-wa/go-unassert v0.1.3 // indirect + github.com/markus-wa/gobitread v0.2.4 // indirect + github.com/markus-wa/godispatch v1.4.1 // indirect + github.com/markus-wa/ice-cipher-go v0.0.0-20230901094113-348096939ba7 // indirect + github.com/markus-wa/quickhull-go/v2 v2.2.0 // indirect + github.com/oklog/ulid/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/main.go b/main.go new file mode 100644 index 0000000..29529c9 --- /dev/null +++ b/main.go @@ -0,0 +1,215 @@ +package main + +import ( + "encoding/json" + "fmt" + "math" + "os" + + demoinfocs "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs" + "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/common" + "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/events" + msg "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/msgs2" +) + +type PlayerStats struct { + Name string `json:"name"` + SteamID string `json:"steamId"` + Kills int `json:"kills"` + Deaths int `json:"deaths"` + Assists int `json:"assists"` + FlashAssists int `json:"flashAssists"` + MVPs int `json:"mvps"` + MVPReason1 int `json:"mvpEliminations"` + MVPReason2 int `json:"mvpDefuse"` + MVPReason3 int `json:"mvpPlant"` + KnifeKills int `json:"knifeKills"` + ZeusKills int `json:"zeusKills"` + WallbangKills int `json:"wallbangKills"` + SmokeKills int `json:"smokeKills"` + Headshots int `json:"headshots"` + NoScopes int `json:"noScopes"` + BlindKills int `json:"blindKills"` + + RankOld int `json:"rankOld,omitempty"` + RankNew int `json:"rankNew,omitempty"` + WinCount int `json:"winCount,omitempty"` +} + +type DemoMeta struct { + MatchID uint64 `json:"matchId"` + Map string `json:"map"` + Players []PlayerStats `json:"players"` + Duration float64 `json:"duration"` + TickRate float64 `json:"tickRate"` +} + +func sanitizeFloat(value float64) float64 { + if math.IsNaN(value) || math.IsInf(value, 0) { + return 0 + } + return value +} + +func main() { + if len(os.Args) < 2 { + fmt.Println("❌ Demo-Datei fehlt") + os.Exit(1) + } + + filePath := os.Args[1] + f, err := os.Open(filePath) + if err != nil { + fmt.Printf("❌ Datei konnte nicht geöffnet werden: %v\n", err) + os.Exit(1) + } + defer f.Close() + + p := demoinfocs.NewParser(f) + + var mapName string + var matchId uint64 + + if len(os.Args) >= 3 { + inputId := os.Args[2] + _, err := fmt.Sscanf(inputId, "%d", &matchId) + if err != nil { + fmt.Printf("⚠️ Ungültige matchId-Argument: %v\n", err) + matchId = 0 + } + } + + // Hole Mapname aus ServerInfo (zuverlässiger als Header.MapName) + p.RegisterNetMessageHandler(func(msg *msg.CSVCMsg_ServerInfo) { + if msg != nil && msg.GetMapName() != "" { + mapName = msg.GetMapName() + } + }) + + header, err := p.ParseHeader() + if err != nil { + fmt.Printf("❌ Header konnte nicht gelesen werden: %v\n", err) + os.Exit(1) + } + + playerStats := make(map[uint64]*PlayerStats) + + getOrCreate := func(sid uint64, name string) *PlayerStats { + stat := playerStats[sid] + if stat == nil { + stat = &PlayerStats{ + Name: name, + SteamID: fmt.Sprintf("%d", sid), + } + playerStats[sid] = stat + } + return stat + } + + p.RegisterEventHandler(func(e events.Kill) { + if e.Killer != nil && e.Killer.SteamID64 != e.Victim.SteamID64 { + stat := getOrCreate(e.Killer.SteamID64, e.Killer.Name) + stat.Kills++ + if e.IsHeadshot { + stat.Headshots++ + } + if e.NoScope { + stat.NoScopes++ + } + if e.AttackerBlind { + stat.BlindKills++ + } + if e.ThroughSmoke { + stat.SmokeKills++ + } + if e.IsWallBang() { + stat.WallbangKills++ + } + if e.Weapon != nil { + switch e.Weapon.Type { + case common.EqKnife: + stat.KnifeKills++ + case common.EqZeus: + stat.ZeusKills++ + } + } + } + if e.Victim != nil { + stat := getOrCreate(e.Victim.SteamID64, e.Victim.Name) + stat.Deaths++ + } + if e.Assister != nil { + stat := getOrCreate(e.Assister.SteamID64, e.Assister.Name) + stat.Assists++ + } + }) + + p.RegisterEventHandler(func(e events.PlayerFlashed) { + if e.Attacker != nil && e.Attacker.SteamID64 != e.Player.SteamID64 { + stat := getOrCreate(e.Attacker.SteamID64, e.Attacker.Name) + stat.FlashAssists++ + } + }) + + p.RegisterEventHandler(func(e events.RoundMVPAnnouncement) { + if e.Player != nil { + stat := getOrCreate(e.Player.SteamID64, e.Player.Name) + stat.MVPs++ + switch e.Reason { + case events.MVPReasonMostEliminations: + stat.MVPReason1++ + case events.MVPReasonBombDefused: + stat.MVPReason2++ + case events.MVPReasonBombPlanted: + stat.MVPReason3++ + } + } + }) + + // 📊 Premier Rank / Elo Veränderung + p.RegisterEventHandler(func(e events.RankUpdate) { + if e.Player != nil { + stat := getOrCreate(e.Player.SteamID64, e.Player.Name) + stat.RankOld = e.RankOld + stat.RankNew = e.RankNew + stat.WinCount = e.WinCount + } + }) + + err = p.ParseToEnd() + if err != nil { + fmt.Printf("❌ Fehler beim Parsen: %v\n", err) + os.Exit(1) + } + + duration := sanitizeFloat(header.PlaybackTime.Seconds()) + tickRate := 0.0 + if duration > 0 { + tickRate = sanitizeFloat(float64(header.PlaybackTicks) / duration) + } + + // Fallback auf Header-Map, falls kein ServerInfo-Mapname verfügbar + if mapName == "" { + mapName = header.MapName + } + + result := DemoMeta{ + MatchID: matchId, + Map: mapName, + Duration: duration, + TickRate: tickRate, + } + + for _, stat := range playerStats { + result.Players = append(result.Players, *stat) + } + + jsonData, err := json.Marshal(result) + if err != nil { + fmt.Printf("❌ Fehler beim JSON-Export: %v\n", err) + fmt.Printf("⛔ Dump vor Fehler: %+v\n", result) + os.Exit(1) + } + + fmt.Println(string(jsonData)) +} diff --git a/parser_cs2-linux b/parser_cs2-linux new file mode 100644 index 0000000..dccf0de Binary files /dev/null and b/parser_cs2-linux differ diff --git a/parser_cs2-win.exe b/parser_cs2-win.exe new file mode 100644 index 0000000..f9eaf3e Binary files /dev/null and b/parser_cs2-win.exe differ