demoinfocs-golang
demoinfocs-golang copied to clipboard
Players are assigned to the wrong teams
Describe the bug Players are assigned to wrong teams (T vs. CT). Sometimes there can be 9 players on CT and just one on T for example.
To Reproduce players-in-wrong-team-rmr-1.dem players-in-wrong-team-rmr-2.dem
Code used for printing out player state:
go run test.go -demo /path/to/demo.dem
package main
import (
"fmt"
"os"
ex "github.com/markus-wa/demoinfocs-golang/v4/examples"
demoinfocs "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs"
common "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/common"
events "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/events"
)
// Run like this: go run test.go -demo /path/to/demo.dem
func main() {
f, err := os.Open(ex.DemoPathFromArgs())
checkError(err)
defer f.Close()
p := demoinfocs.NewParser(f)
defer p.Close()
// Parse header
header, err := p.ParseHeader()
checkError(err)
fmt.Println("Map:", header.MapName)
p.RegisterEventHandler(func(e events.RoundStart) {
gs := p.GameState()
fmt.Printf("\nRound %d\n", gs.TotalRoundsPlayed()+1)
fmt.Println("PARTICIPANTS:")
for _, player := range gs.Participants().All() {
fmt.Printf("PLAYER %s (%d) Team: %d Connected: %t\n", formatPlayer(player), player.SteamID64, player.Team, player.IsConnected)
}
})
err = p.ParseToEnd()
checkError(err)
}
func formatPlayer(p *common.Player) string {
if p == nil {
return "?"
}
switch p.Team {
case common.TeamTerrorists:
return "[T]" + p.Name
case common.TeamCounterTerrorists:
return "[CT]" + p.Name
}
return p.Name
}
func checkError(err error) {
if err != nil {
panic(err)
}
}
Expected behavior
$ go run test.go -demo /path/to/players-in-wrong-team-rmr-1.dem
Round 1
PARTICIPANTS:
PLAYER [CT]koh415 (76561198159074559) Team: 3 Connected: true
PLAYER [T]Klarje (76561198097422106) Team: 2 Connected: true
PLAYER [CT]astonishred (76561198112681840) Team: 3 Connected: true
PLAYER [CT]ryncs (76561198328604687) Team: 3 Connected: true
PLAYER [CT]FanaticDuck (76561198302499325) Team: 3 Connected: true
PLAYER [CT]clutchllama (76561198094509053) Team: 3 Connected: true
PLAYER GOTV (0) Team: 0 Connected: false
PLAYER [CT]kolsy (76561198212592484) Team: 3 Connected: true
PLAYER [CT]milo_916 (76561198172505994) Team: 3 Connected: true
PLAYER [CT]ghost_solder (76561198029761801) Team: 3 Connected: true
PLAYER [CT]signal1 (76561198054121335) Team: 3 Connected: true
PLAYER GOTV (0) Team: 0 Connected: false
...
CURRENT: 9 players on CT, only 1 on T side EXPECTED: Normal 5 vs. 5
$ go run test.go -demo /path/to/players-in-wrong-team-rmr-2.dem
Round 1
PARTICIPANTS:
PLAYER [T]Kollo (76561198074134659) Team: 2 Connected: true
PLAYER [T]AwaykeN (76561198440676599) Team: 2 Connected: true
PLAYER [T]Duplicate (76561198152075689) Team: 2 Connected: true
PLAYER [CT]LYNXi (76561198083574557) Team: 3 Connected: true
PLAYER [CT]welhoboy87 (76561198181320650) Team: 3 Connected: true
PLAYER GOTV (0) Team: 0 Connected: false
PLAYER [CT]monoxx (76561198149834476) Team: 3 Connected: true
PLAYER [CT]bevvexyz (76561199103903099) Team: 3 Connected: true
PLAYER [CT]adam9130 (76561198040547485) Team: 3 Connected: true
PLAYER [CT]p12 (76561197966038702) Team: 3 Connected: true
PLAYER [T]doblean (76561198249125106) Team: 2 Connected: true
PLAYER GOTV (0) Team: 0 Connected: false
...
CURRENT: 6 players on CT, only 4 on T side EXPECTED: Normal 5 vs. 5
Library version latest in master 935fd38f2da60d196f4cbd3a107b181caa69e08c
Additional context
- Both demos are from latest RMR qualifiers where there were some problems with the servers
- For
players-in-wrong-team-rmr-1.demuser says the demo works perfectly on Skybox Edge (can't confirm myself)
This problem occurs for most demos from the RMR qualifiers.
Two randomly chosen examples from hltv:
Demo: nixuh-vs-bravado
Round 1
PARTICIPANTS:
PLAYER [T]march_ (76561198051922966) Team: 2 Connected: false
PLAYER GOTV (0) Team: 0 Connected: false
PLAYER [CT]Fadey (76561198141759568) Team: 3 Connected: true
PLAYER [T]RustyYG (76561198248601350) Team: 2 Connected: true
PLAYER [T]trit9n (76561198096199303) Team: 2 Connected: true
PLAYER [T]kaniioh (76561197992995849) Team: 2 Connected: true
PLAYER [CT]FROZ3Ncs (76561198070468233) Team: 3 Connected: true
PLAYER [T]IDoruI (76561198096170937) Team: 2 Connected: true
PLAYER [T]bvdwilj (76561198143694974) Team: 2 Connected: true
PLAYER GOTV (0) Team: 0 Connected: false
PLAYER [T]_Spitfire_ (76561198951001059) Team: 2 Connected: true
PLAYER [CT]flexeeee (76561198138004745) Team: 3 Connected: true
...
Demo: fnatic-vs-havu
Round 1
PARTICIPANTS:
PLAYER GOTV (0) Team: 0 Connected: false
PLAYER [CT]kyuubii (76561198144926364) Team: 3 Connected: true
PLAYER [CT]Matys176 (76561199046478501) Team: 3 Connected: false
PLAYER GOTV (0) Team: 0 Connected: false
PLAYER [T]AIRAX666 (76561198010426775) Team: 2 Connected: true
PLAYER [T]Banjo7 (76561198276486363) Team: 2 Connected: true
PLAYER [CT]krimz1337 (76561198031651584) Team: 3 Connected: true
PLAYER [CT]ULI (76561198012528425) Team: 3 Connected: true
PLAYER [CT]ottond (76561198035334871) Team: 3 Connected: true
PLAYER [CT]bodyy (76561198013295375) Team: 3 Connected: true
PLAYER [CT]afrocs2 (76561198035581101) Team: 3 Connected: true
PLAYER [T]sLowiCSGO (76561197976197744) Team: 2 Connected: true
...
Info from the tournament organizer:
- They used some kind of mod to enforce teams, which may or may not be the culprit
- These demos apparently work fine with https://github.com/akiver/cs-demo-analyzer
I replayed the players-in-wrong-team-rmr-1.dem and I added a print:
if pl.TeamState != nil {
fmt.Printf("bindNewPlayerControllerS2 %s\tTeamState.Team %d Team %d\n", pl.Name, pl.TeamState.Team(), pl.Team)
}
inside controllerEntity.Property("m_iTeamNum").OnUpdate(func(val st.PropertyValue)
https://github.com/markus-wa/demoinfocs-golang/blob/935fd38f2da60d196f4cbd3a107b181caa69e08c/pkg/demoinfocs/datatables.go#L523C4-L523C4
This was the result of that print:
bindNewPlayerControllerS2 kolsy TeamState.Team 2 Team 2
bindNewPlayerControllerS2 koh415 TeamState.Team 3 Team 3
bindNewPlayerControllerS2 milo_916 TeamState.Team 3 Team 3
bindNewPlayerControllerS2 Klarje TeamState.Team 2 Team 2
bindNewPlayerControllerS2 astonishred TeamState.Team 2 Team 2
bindNewPlayerControllerS2 ryncs TeamState.Team 2 Team 2
bindNewPlayerControllerS2 ghost_solder TeamState.Team 3 Team 3
bindNewPlayerControllerS2 FanaticDuck TeamState.Team 3 Team 3
bindNewPlayerControllerS2 signal1 TeamState.Team 2 Team 2
bindNewPlayerControllerS2 clutchllama TeamState.Team 3 Team 3
So I think the information is comming alright.
I also find out that player.Team != player.TeamState.Team(). With similar to your print:
p.RegisterEventHandler(func(e events.RoundStart) {
gs := p.GameState()
fmt.Printf("\nRound %d\n", gs.TotalRoundsPlayed()+1)
fmt.Println("PARTICIPANTS:")
for _, player := range gs.Participants().All() {
if !player.IsBot{
fmt.Printf("PLAYER %s\t(%d)\tTeam: %d\tTeamState.Team: %d\n", formatPlayer(player), player.SteamID64, player.Team, player.TeamState.Team())
}
}
}
Round 1
PARTICIPANTS:
PLAYER [CT] clutchllama (76561198094509053) Team: 3 TeamState.Team: 3
PLAYER [CT] kolsy (76561198212592484) Team: 3 TeamState.Team: 2
PLAYER [CT] koh415 (76561198159074559) Team: 3 TeamState.Team: 3
PLAYER [CT] milo_916 (76561198172505994) Team: 3 TeamState.Team: 3
PLAYER [CT] ghost_solder (76561198029761801) Team: 3 TeamState.Team: 3
PLAYER [CT] FanaticDuck (76561198302499325) Team: 3 TeamState.Team: 3
PLAYER [T] Klarje (76561198097422106) Team: 2 TeamState.Team: 2
PLAYER [CT] astonishred (76561198112681840) Team: 3 TeamState.Team: 2
PLAYER [CT] ryncs (76561198328604687) Team: 3 TeamState.Team: 2
PLAYER [CT] signal1 (76561198054121335) Team: 3 TeamState.Team: 2
I don't know how to fix it. But I hope this might help.
We're very much experiencing the same. Currently using Anubis from this match for debugging: https://www.hltv.org/matches/2368983/500-vs-ecstatic-pgl-cs2-major-copenhagen-2024-europe-rmr-open-qualifier-3
Info from the tournament organizer: They used some kind of mod to enforce teams, which may or may not be the culprit These demos apparently work fine with https://github.com/akiver/cs-demo-analyzer
@akiver how do you read the teams there? maybe we can copy that approach :eyes:
this PR should fix the problem https://github.com/markus-wa/demoinfocs-golang/pull/499, could you try on this branch please?
this PR should fix the problem #499, could you try on this branch please?
This seems to fix it for all demos :+1:
this PR should fix the problem #499, could you try on this branch please?
This seems to fix it for all demos 👍
I unfortunately disagree - I've got a few demos it doesn't work for. I'm not at the right PC at the moment, so I'll post an example or two tonight
So, here are the examples:
In anubis of this match, the teams are incorrectly reported to only include 3 and 4 players respectively when fetching data from the game state. Here's an example:
package main
import (
"fmt"
"log"
"os"
"slices"
dem "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"
)
func main() {
testFile := "pgl-cs2-major-copenhagen-2024-europe-rmr-open-qualifier-3-500-vs-ecstatic-bo3-M9LZlEbSCkGLCNY1nbFxsI/500-ecstatic-m1-anubis.dem"
f, err := os.Open(testFile)
requireNoError(err)
p := dem.NewParser(f)
round := 0
p.RegisterEventHandler(func(e events.RoundEndOfficial) {
round += 1
fmt.Printf("round: %d\n", round)
for _, t := range []common.Team{common.TeamCounterTerrorists, common.TeamTerrorists} {
players := make([]string, 0, 5)
for _, p := range p.GameState().Participants().TeamMembers(t) {
players = append(players, p.Name)
}
slices.Sort(players)
fmt.Printf("\tteam: %v: %+v\n", t, players)
}
})
err = p.ParseToEnd()
requireNoError(err)
}
func requireNoError(err error) {
if err != nil {
log.Fatalf("error: %s", err)
}
}
Here are examples of the output I'm getting when running it locally:
round: 5
team: 3: [Kraghen Nodios Pattiaten Queenix salazar]
team: 2: [Patrick Rainwaker dennyslaw]
round: 6
team: 3: [Kraghen Nodios Queenix salazar]
team: 2: [Patrick Rainwaker dennyslaw]
The problem seems to be that the data in the game state isn't being updated correctly. The fix we're currently relying on is to grab the data directly from CCSTeam.m_aPlayers in pretty much the same way that we're doing it for weapons. This obviously does not produce the expected PlayerTeamChange event, nor is it a nice solution in general. But that's our fix so far
p.stParser.ServerClasses().FindByName("CCSTeam").OnEntityCreated(func(entity st.Entity) {
...
if !p.isSource2() {
return
}
// TODO: this is a major hack to track player teams without having
// to refactor the whole handling of player team states.
var teamSize uint64 = 16
internalMembers := make([]*common.Player, 64)
setTeamPlayers := func() {
members := make([]*common.Player, 0, teamSize)
for i := uint64(0); i < teamSize; i++ {
player := internalMembers[i]
if player == nil || player.IsUnknown {
continue
}
members = append(members, player)
}
// TODO: members now holds the correct team members. We're currently shoving
// this into `TeamState`, circumventing having to fiddle with the Participants callback.
}
entity.Property("m_aPlayers").OnUpdate(func(pv st.PropertyValue) {
// NOTE: Sometimes pv.Any contains an array with bitmasked values
// that can be converted to actual player ids with entityIDFromHandle([value], true).
//
// We've only experienced this the first time the value is updated.
size, ok := pv.Any.(uint64)
if !ok {
return
}
teamSize = size
setTeamPlayers()
})
for i := 0; i < 32; i++ {
i := i
slot := fmt.Sprintf("m_aPlayers.%04d", i)
entity.Property(slot).OnUpdate(func(pv st.PropertyValue) {
var player *common.Player
if pv.Any != nil {
playerEntityID := entityIDFromHandle(pv.S2UInt64(), true)
player = p.gameState.playersByEntityID[playerEntityID]
player.Team = s.Team()
internalMembers[i] = player
}
setTeamPlayers()
})
}
}
I have an additional example of a match with some an edge case that requires handling the team size correctly:
@micvbang could you try with the last commit from https://github.com/markus-wa/demoinfocs-golang/pull/499?
Some players were seen as disconnected I think that's why they were missing.
@micvbang could you try with the last commit from #499? Some players were seen as disconnected I think that's why they were missing.
Thank you! Yes, this looks promising! I haven't been able to spot any issues with it
I will be closing this for now, feel free to reopen if the issue persists with v4.0.2