This commit is contained in:
Linrador 2025-08-11 15:36:56 +02:00
parent e41bb63fbd
commit 9533b13806
3 changed files with 175 additions and 52 deletions

227
main.go
View File

@ -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 {

Binary file not shown.

Binary file not shown.