markus-wa / demoinfocs-golang

A Counter-Strike 2 & CS:GO demo parser for Go (demoinfo)
https://pkg.go.dev/github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs?tab=doc
MIT License
710 stars 95 forks source link

Source 2: when I call inferno.Fires(), i get the exception : "property 'm_fireXDelta.0000' not found" #474

Closed onduty1981 closed 11 months ago

onduty1981 commented 11 months ago

Describe the bug when I call the function { func (inf *Inferno) Fires() Fires }, I can't get the Inferno position.

To Reproduce

https://r2-demos.hltv.org/demos/100901/epl-world-series-americas-season-4-w7m-vs-solid-bo3-dSsvx95GYl9EHOvss0vDrv.rar

Code: func parseGameStates(parser dem.Parser, match *Match) []common.OverviewState { playbackFrames := parser.Header().PlaybackFrames states := make([]common.OverviewState, 0, playbackFrames)

for ok, err := parser.ParseNextFrame(); ok; ok, err = parser.ParseNextFrame() {
    if err != nil {
        log.Println(err)
        // return here or not?
        continue
    }

    // do this only once
    if match.FrameRate == 0 {
        if val, ok := parser.GameState().Rules().ConVars()["tv_snapshotrate"]; ok {
            floatVal, _ := strconv.ParseFloat(val, 64)
            match.FrameRate = int(math.Round(floatVal))
        } else {
            // if !ok the variable has the default value
            match.FrameRate = 32
        }

        if match.FrameRate == 128 {
            match.takeNthFrame = 4
            match.FrameRate = 32
        }
        if match.FrameRate == 64 {
            match.takeNthFrame = 2
            match.FrameRate = 32
        }
        match.SmokeEffectLifetime = int32(18 * match.FrameRate)

        //pb.SetTotal(pb.Total() + int64(playbackFrames/match.takeNthFrame))
    }

    if parser.CurrentFrame()%match.takeNthFrame != 0 {
        continue
    }

    match.currentFrame = len(states)

    gameState := parser.GameState()

    var isOnNormalElevation bool
    players := make([]common.Player, 0, 10)
    ctPrefix, tPrefix, ctPostfix, tPostfix := getTeamXfixes(gameState.Participants().Playing())

    for _, p := range gameState.Participants().Playing() {
        var hasBomb bool
        inventory := make([]demoinfo.EquipmentType, 0)
        for _, w := range p.Weapons() {
            if w.Type == demoinfo.EqBomb {
                hasBomb = true
            }
            if isWeaponOrGrenade(w.Type) {
                if w.Type == demoinfo.EqFlash && w.AmmoReserve() > 0 {
                    inventory = append(inventory, w.Type)
                }
                inventory = append(inventory, w.Type)
            }
        }
        sort.Slice(inventory, func(i, j int) bool { return inventory[i] < inventory[j] })
        if common.MapHasAlternateVersion(match.MapName) {
            if p.Position().Z > common.MapGetHeightThreshold(match.MapName) {
                isOnNormalElevation = true
            } else {
                isOnNormalElevation = false
            }
        }
        name := p.Name
        if p.Team == demoinfo.TeamCounterTerrorists {
            if len(ctPrefix) > 1 {
                name = name[len(ctPrefix):]
            }
            if len(ctPostfix) > 1 {
                name = name[:len(name)-len(ctPostfix)]
            }
        } else if p.Team == demoinfo.TeamTerrorists {
            if len(tPrefix) > 1 {
                name = name[len(tPrefix):]
            }
            if len(tPostfix) > 1 {
                name = name[:len(name)-len(tPostfix)]
            }
        }
        var activeWeapon demoinfo.EquipmentType
        if p.ActiveWeapon() == nil {
            // player is dead probably
            activeWeapon = demoinfo.EqUnknown
        } else {
            activeWeapon = p.ActiveWeapon().Type
        }

        player := common.Player{
            Name: name,
            ID:   p.UserID,
            Team: p.Team,
            Position: common.Point{
                X: float32(p.Position().X),
                Y: float32(p.Position().Y),
                Z: float32(p.Position().Z),
            },
            LastAlivePosition: common.Point{
                X: float32(p.LastAlivePosition.X),
                Y: float32(p.LastAlivePosition.Y),
            },
            ViewDirectionX:      p.ViewDirectionX(),
            ViewDirectionY:      p.ViewDirectionY(),
            FlashDuration:       p.FlashDurationTime(),
            FlashTimeRemaining:  p.FlashDurationTimeRemaining(),
            Inventory:           inventory,
            ActiveWeapon:        activeWeapon,
            Health:              int16(p.Health()),
            Armor:               int16(p.Armor()),
            Money:               int16(p.Money()),
            Kills:               int16(p.Kills()),
            Deaths:              int16(p.Deaths()),
            Assists:             int16(p.Assists()),
            IsAlive:             p.IsAlive(),
            IsDefusing:          p.IsDefusing,
            IsOnNormalElevation: isOnNormalElevation,
            HasHelmet:           p.HasHelmet(),
            HasDefuseKit:        p.HasDefuseKit(),
            HasBomb:             hasBomb,
        }
        players = append(players, player)
    }
    sort.Slice(players, func(i, j int) bool { return players[i].ID < players[j].ID })

    grenades := make([]common.GrenadeProjectile, 0)

    for _, grenade := range gameState.GrenadeProjectiles() {
        if common.MapHasAlternateVersion(match.MapName) {
            if grenade.Position().Z > common.MapGetHeightThreshold(match.MapName) {
                isOnNormalElevation = true
            } else {
                isOnNormalElevation = false
            }
        }
        g := common.GrenadeProjectile{
            Position: common.Point{
                X: float32(grenade.Position().X),
                Y: float32(grenade.Position().Y),
            },
            Type:                grenade.WeaponInstance.Type,
            IsOnNormalElevation: isOnNormalElevation,
        }
        grenades = append(grenades, g)
    }

    infernos := make([]common.Inferno, 0)
    for _, inferno := range gameState.Infernos() {
        r2Points := inferno.Fires().Active().ConvexHull2D()
        commonPoints := make([]common.Point, 0)
        for _, point := range r2Points {
            commonPoint := common.Point{
                X: float32(point.X),
                Y: float32(point.Y),
            }
            commonPoints = append(commonPoints, commonPoint)
        }
        if common.MapHasAlternateVersion(match.MapName) {
            if inferno.Fires().Active().ConvexHull3D().Vertices[0].Z > common.MapGetHeightThreshold(match.MapName) {
                isOnNormalElevation = true
            } else {
                isOnNormalElevation = false
            }
        }
        i := common.Inferno{
            ConvexHull2D:        commonPoints,
            IsOnNormalElevation: isOnNormalElevation,
        }
        infernos = append(infernos, i)
    }

    var isBeingCarried bool
    if gameState.Bomb().Carrier != nil {
        isBeingCarried = true
    } else {
        isBeingCarried = false
    }
    if common.MapHasAlternateVersion(match.MapName) {
        if gameState.Bomb().Position().Z > common.MapGetHeightThreshold(match.MapName) {
            isOnNormalElevation = true
        } else {
            isOnNormalElevation = false
        }
    }
    bomb := common.Bomb{
        Position: common.Point{
            X: float32(gameState.Bomb().Position().X),
            Y: float32(gameState.Bomb().Position().Y),
        },
        IsBeingCarried:      isBeingCarried,
        IsOnNormalElevation: isOnNormalElevation,
    }

    cts := common.TeamState{
        ClanName: gameState.TeamCounterTerrorists().ClanName(),
        Score:    byte(gameState.TeamCounterTerrorists().Score()),
    }
    ts := common.TeamState{
        ClanName: gameState.TeamTerrorists().ClanName(),
        Score:    byte(gameState.TeamTerrorists().Score()),
    }

    var timer common.Timer

    if gameState.IsWarmupPeriod() {
        timer = common.Timer{
            TimeRemaining: 0,
            Phase:         common.PhaseWarmup,
        }
    } else {
        switch match.currentPhase {
        case common.PhaseFreezetime:
            freezetime, _ := strconv.Atoi(gameState.Rules().ConVars()["mp_freezetime"])
            remaining := time.Duration(freezetime)*time.Second - (parser.CurrentTime() - match.latestTimerEventTime)
            timer = common.Timer{
                TimeRemaining: remaining,
                Phase:         common.PhaseFreezetime,
            }
        case common.PhaseRegular:
            roundtime, _ := strconv.ParseFloat(gameState.Rules().ConVars()["mp_roundtime_defuse"], 64)
            remaining := time.Duration(roundtime*60)*time.Second - (parser.CurrentTime() - match.latestTimerEventTime)
            timer = common.Timer{
                TimeRemaining: remaining,
                Phase:         common.PhaseRegular,
            }
        case common.PhasePlanted:
            // mp_c4timer is not set in testdemo
            //bombtime, _ := strconv.Atoi(gameState.ConVars()["mp_c4timer"])
            bombtime := c4timer
            remaining := time.Duration(bombtime)*time.Second - (parser.CurrentTime() - match.latestTimerEventTime)
            timer = common.Timer{
                TimeRemaining: remaining,
                Phase:         common.PhasePlanted,
            }
        case common.PhaseRestart:
            restartDelay, _ := strconv.Atoi(gameState.Rules().ConVars()["mp_round_restart_delay"])
            remaining := time.Duration(restartDelay)*time.Second - (parser.CurrentTime() - match.latestTimerEventTime)
            timer = common.Timer{
                TimeRemaining: remaining,
                Phase:         common.PhaseRestart,
            }
        case common.PhaseHalftime:
            halftimeDuration, _ := strconv.Atoi(gameState.Rules().ConVars()["mp_halftime_duration"])
            remaining := time.Duration(halftimeDuration)*time.Second - (parser.CurrentTime() - match.latestTimerEventTime)
            timer = common.Timer{
                TimeRemaining: remaining,
                Phase:         common.PhaseRestart,
            }
        }
    }

    state := common.OverviewState{
        IngameTick:            parser.GameState().IngameTick(),
        Players:               players,
        Grenades:              grenades,
        Infernos:              infernos,
        Bomb:                  bomb,
        TeamCounterTerrorists: cts,
        TeamTerrorists:        ts,
        Timer:                 timer,
    }

    states = append(states, state)

}

return states

}



**Expected behavior**
I want to get the Inferno  position, but  I   get the the exception:
"property 'm_fireXDelta.0000' not found"
Stack:
    2  0x00000000010f6428 in github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/sendtables2.(*Entity).PropertyValueMust
        at C:/Users/user/go/pkg/mod/github.com/markus-wa/demoinfocs-golang/v4@v4.0.0-beta.5/pkg/demoinfocs/sendtables2/entity.go:158
    3  0x00000000010b1689 in github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/common.(*Inferno).Fires
        at C:/Users/user/go/pkg/mod/github.com/markus-wa/demoinfocs-golang/v4@v4.0.0-beta.5/pkg/demoinfocs/common/inferno.go:78

**Library version**
e.g. v4.0.0-beta.5

**Additional context**
I think  the property name had changed ?
markus-wa commented 11 months ago

Thanks for the report - the property is now a vector (length 64) and is just called m_fireXDelta, so this will definitely need to be adjusted.

akiver commented 11 months ago

Hi, I tried to run the following code with the 3 demos linked in the issue, and it didn't crash.
Which demo exactly is affected?

    p.RegisterEventHandler(func(e events.FrameDone) {
        for _, inferno := range p.GameState().Infernos() {
            points := inferno.Fires().Active().ConvexHull2D()
            for _, p := range points {
                fmt.Println(p.X, p.Y)
            }
        }
    })

@markus-wa the prop m_fireXDelta has been removed in the 11/2/2023 update so we should now check if m_fireXDelta exists here.
We use m_fireXDelta only when m_firePositions doesn't exist, but I can't find a demo with such a case.

Screenshot 2023-11-18 at 10 01 19
penjiu commented 11 months ago

Hi, I tried to run the following code with the 3 demos linked in the issue, and it didn't crash. Which demo exactly is affected?

  p.RegisterEventHandler(func(e events.FrameDone) {
      for _, inferno := range p.GameState().Infernos() {
          points := inferno.Fires().Active().ConvexHull2D()
          for _, p := range points {
              fmt.Println(p.X, p.Y)
          }
      }
  })

@markus-wa the prop m_fireXDelta has been removed in the 11/2/2023 update so we should now check if m_fireXDelta exists here. We use m_fireXDelta only when m_firePositions doesn't exist, but I can't find a demo with such a case.

Screenshot 2023-11-18 at 10 01 19

Hi akiver, You can try the following demo, it crashed on my side. http://replay233.wmsj.cn/730/003650808412331049010_0474274576.dem.bz2

akiver commented 11 months ago

I think I know why it crashes only for you - you are using the last beta.5 version but this problem has been fix on the branch master https://github.com/markus-wa/demoinfocs-golang/commit/f7db8ec3674518fb9a74ffccd19b23c13eac3bc4

If you use the master branch it should work.

markus-wa commented 11 months ago

will close this as the fix has been released with v4.0.0 - reopen if the issue persists