diff --git a/main.go b/main.go index 03f1bb1..0c87b9a 100644 --- a/main.go +++ b/main.go @@ -19,30 +19,38 @@ type Team struct { } type PlayerStats struct { - Name string `json:"name"` - SteamID string `json:"steamId"` - Team string `json:"team"` - Kills int `json:"kills"` - Deaths int `json:"deaths"` - Assists int `json:"assists"` - FlashAssists int `json:"flashAssists"` - TotalDamage int `json:"totalDamage"` - UtilityDamage int `json:"utilityDamage"` - 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"` - RankChange int `json:"rankChange,omitempty"` - WinCount int `json:"winCount,omitempty"` + Name string `json:"name"` + SteamID string `json:"steamId"` + Team string `json:"team"` + Kills int `json:"kills"` + Deaths int `json:"deaths"` + Assists int `json:"assists"` + FlashAssists int `json:"flashAssists"` + TotalDamage int `json:"totalDamage"` + UtilityDamage int `json:"utilityDamage"` + 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"` + RankChange int `json:"rankChange,omitempty"` + WinCount int `json:"winCount,omitempty"` + OneK int `json:"oneK"` + TwoK int `json:"twoK"` + ThreeK int `json:"threeK"` + FourK int `json:"fourK"` + FiveK int `json:"fiveK"` + ShotsFired int `json:"shotsFired"` + ShotsHit int `json:"shotsHit"` + Aim float64 `json:"aim"` // Prozent } type RoundResult struct { @@ -107,6 +115,22 @@ func parseSteamID(id string) (uint64, error) { return sid, err } +// Bullet-Waffen (Schusswaffen) erkennen – keine Nades/Knife/Zeus/Equipment +func isBulletWeapon(eq *common.Equipment) bool { + if eq == nil { + return false + } + switch eq.Type { + case common.EqKnife, common.EqZeus, common.EqHE, common.EqMolotov, common.EqIncendiary, common.EqFlash, common.EqSmoke, common.EqDecoy: + return false + } + cls := eq.Class() + if cls == common.EqClassGrenade || cls == common.EqClassEquipment { + return false + } + return true // Pistols/SMG/Rifles/Heavy/Sniper +} + func main() { if len(os.Args) < 2 { fmt.Println("❌ Demo-Datei fehlt") @@ -129,18 +153,23 @@ func main() { var roundHistory []RoundResult var teamHistory = make(PlayerTeamHistory) + // Per-Runde Kills, die bis zum *nächsten* RoundStart gehalten werden + var roundKills = make(map[uint64]int) // SteamID64 -> Kills in der aktuellen (zuletzt gestarteten) Runde + var haveOpenRound bool // true ab erstem RoundStart nach MatchStart + var lastNonSpecTeam = make(map[uint64]string) // "CT" oder "T" + 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) + fmt.Printf("⚠️ Ungültiges matchId-Argument: %v\n", err) matchId = 0 } } - p.RegisterNetMessageHandler(func(msg *msg.CSVCMsg_ServerInfo) { - if msg != nil && msg.GetMapName() != "" { - mapName = msg.GetMapName() + p.RegisterNetMessageHandler(func(m *msg.CSVCMsg_ServerInfo) { + if m != nil && m.GetMapName() != "" { + mapName = m.GetMapName() } }) @@ -166,17 +195,79 @@ func main() { return stat } - p.RegisterEventHandler(func(e events.RoundStart) { - round := roundCount + 1 // nächste Runde - teamHistory[round] = map[uint64]string{} - - for _, p := range p.GameState().Participants().Playing() { - if p.Team == common.TeamCounterTerrorists { - teamHistory[round][p.SteamID64] = "CT" - } else if p.Team == common.TeamTerrorists { - teamHistory[round][p.SteamID64] = "T" + // Hilfsfunktion: roundKills -> 1k/2k/3k/4k/5k übertragen + flushRoundKills := func() { + for sid, k := range roundKills { + stat := playerStats[sid] + if stat == nil { + continue + } + switch { + case k == 1: + stat.OneK++ + case k == 2: + stat.TwoK++ + case k == 3: + stat.ThreeK++ + case k == 4: + stat.FourK++ + case k >= 5: + stat.FiveK++ } } + roundKills = make(map[uint64]int) + } + + p.RegisterEventHandler(func(e events.RoundStart) { + if !p.GameState().IsMatchStarted() { + return + } + + if haveOpenRound { + flushRoundKills() + } + haveOpenRound = true + + round := roundCount + 1 + teamHistory[round] = map[uint64]string{} + for _, pl := range p.GameState().Participants().Playing() { + switch pl.Team { + case common.TeamCounterTerrorists: + teamHistory[round][pl.SteamID64] = "CT" + lastNonSpecTeam[pl.SteamID64] = "CT" + case common.TeamTerrorists: + teamHistory[round][pl.SteamID64] = "T" + lastNonSpecTeam[pl.SteamID64] = "T" + } + } + }) + + p.RegisterEventHandler(func(e events.PlayerTeamChange) { + if e.Player == nil { + return + } + sid := e.Player.SteamID64 + // Nur Nicht-Spectator übernehmen; Spectator/Unassigned löscht NICHT den letzten Stand + switch e.NewTeam { + case common.TeamCounterTerrorists: + lastNonSpecTeam[sid] = "CT" + case common.TeamTerrorists: + lastNonSpecTeam[sid] = "T" + } + }) + + // Schüsse zählen (nur Schusswaffen) + p.RegisterEventHandler(func(e events.WeaponFire) { + if !haveOpenRound { + return // Warmup ignorieren + } + if e.Shooter == nil || e.Weapon == nil { + return + } + if !isBulletWeapon(e.Weapon) { + return + } + getOrCreate(*e.Shooter).ShotsFired++ }) p.RegisterEventHandler(func(e events.Kill) { @@ -187,6 +278,12 @@ func main() { if killerTeam != victimTeam && killerTeam != common.TeamSpectators { stat := getOrCreate(*e.Killer) stat.Kills++ + + // Nur zählen, wenn wir uns in/zwischen echten Runden befinden + if haveOpenRound { + roundKills[e.Killer.SteamID64]++ + } + if e.IsHeadshot { stat.Headshots++ } @@ -214,32 +311,39 @@ func main() { } if e.Victim != nil { - stat := getOrCreate(*e.Victim) - stat.Deaths++ + getOrCreate(*e.Victim).Deaths++ } - if e.Assister != nil { - stat := getOrCreate(*e.Assister) - stat.Assists++ + getOrCreate(*e.Assister).Assists++ } }) p.RegisterEventHandler(func(e events.PlayerFlashed) { if e.Attacker != nil && e.Attacker.SteamID64 != e.Player.SteamID64 { - stat := getOrCreate(*e.Attacker) - stat.FlashAssists++ + getOrCreate(*e.Attacker).FlashAssists++ } }) p.RegisterEventHandler(func(e events.PlayerHurt) { if e.Attacker != nil && e.Attacker != e.Player { stat := getOrCreate(*e.Attacker) + + // Utility-Damage separat zählen if e.Weapon != nil { switch e.Weapon.Type { case common.EqHE, common.EqMolotov, common.EqIncendiary: stat.UtilityDamage += e.HealthDamage } } + + // Treffer (nur Schusswaffen, nur Gegner, nur echte Runden) + if haveOpenRound && e.HealthDamage > 0 && e.Weapon != nil && isBulletWeapon(e.Weapon) { + attTeam := e.Attacker.Team + vicTeam := e.Player.Team + if attTeam != vicTeam && attTeam != common.TeamSpectators { + stat.ShotsHit++ + } + } } }) @@ -259,6 +363,7 @@ func main() { }) p.RegisterEventHandler(func(e events.RoundEnd) { + // Scores & Round-History wie gehabt scoreCT = p.GameState().TeamCounterTerrorists().Score() scoreT = p.GameState().TeamTerrorists().Score() roundCount++ @@ -278,6 +383,7 @@ func main() { Winner: winner, WinReason: reasonToString(e.Reason), }) + // KEIN flush hier! Nach-Runden-Kills sollen noch in diese Runde fallen. }) p.RegisterEventHandler(func(e events.RankUpdate) { @@ -296,6 +402,11 @@ func main() { os.Exit(1) } + // Letztes Flush falls Match endet ohne weiteren RoundStart + if haveOpenRound && len(roundKills) > 0 { + flushRoundKills() + } + teamAName := p.GameState().TeamCounterTerrorists().ClanName() if teamAName == "" { teamAName = "CT" @@ -307,32 +418,44 @@ func main() { for _, stat := range playerStats { sid, _ := parseSteamID(stat.SteamID) + + if t, ok := lastNonSpecTeam[sid]; ok && (t == "CT" || t == "T") { + stat.Team = t + continue + } + + // Fallback: letzter Runden-Eintrag aus teamHistory lastTeam := "" lastRound := 0 - for roundNum, roundTeams := range teamHistory { if team, ok := roundTeams[sid]; ok && roundNum > lastRound { lastTeam = team lastRound = roundNum } } - stat.Team = lastTeam } - for _, player := range p.GameState().Participants().All() { - sid := player.SteamID64 + for _, pl := range p.GameState().Participants().All() { + sid := pl.SteamID64 stat, ok := playerStats[sid] - if !ok || player.Entity == nil { + if !ok || pl.Entity == nil { continue } - - val, ok := player.Entity.PropertyValue("m_pActionTrackingServices.m_iDamage") - if ok { + if val, ok := pl.Entity.PropertyValue("m_pActionTrackingServices.m_iDamage"); ok { stat.TotalDamage = val.Int() } } + // Aim-Wert berechnen + for _, stat := range playerStats { + if stat.ShotsFired > 0 { + stat.Aim = sanitizeFloat(float64(stat.ShotsHit) * 100.0 / float64(stat.ShotsFired)) + } else { + stat.Aim = 0 + } + } + var ctPlayers, tPlayers []PlayerStats for _, stat := range playerStats { switch stat.Team { diff --git a/parser_cs2-linux b/parser_cs2-linux index 5fdc809..5ca578d 100644 Binary files a/parser_cs2-linux and b/parser_cs2-linux differ diff --git a/parser_cs2-win.exe b/parser_cs2-win.exe index a080fad..5d47f63 100644 Binary files a/parser_cs2-win.exe and b/parser_cs2-win.exe differ