Coder Social home page Coder Social logo

markus-wa / demoinfocs-golang Goto Github PK

View Code? Open in Web Editor NEW
641.0 641.0 89.0 11.92 MB

A Counter-Strike 2 & CS:GO demo parser for Go (demoinfo)

Home Page: https://pkg.go.dev/github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs?tab=doc

License: MIT License

Go 98.46% Shell 1.54%
counter-strike csgo demo demo-parser demoinfo esports go golang hacktoberfest parser replays

demoinfocs-golang's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

demoinfocs-golang's Issues

MatchEnded event?

How can I know the match is ended so I can discard any event happened after the match ended?

Maps metadata outdated

Describe the bug
The metadata related to radar images seem outdated and should be updated according to the information in SteamApps/common/Counter-Strike Global Offensive/csgo/resource/overviews/*.txt

Using this outdated metadata will cause incorrect translation functionality in the metadata functions.

To Reproduce
Use the TranslateScale function from metadata to translate positions for example de_dust2 (which seem broken). Get a ingame coordinate from a corner somewhere on the map and then try to draw it on the radar image (like in the heatmap example).

Expected behavior
The position on the radar image should be the same as the ingame position.

Library version
Don't know. Latest and greatest.

Parsing very slow due to Entity.BindPosition()

Describe the bug
#44 introduced Entity.BindPosition() for players which the parser uses internally to keep track of the players positions. This function performs very badly since it calls Entity.FindProperty() twice on every update.

     10ms       10ms    198:func (e *Entity) positionPlayer() r3.Vector {
     110ms     42.73s    199:   xy := e.FindProperty(propVecOriginPlayerXY).value.VectorVal
      10ms     35.67s    200:   z := float64(e.FindProperty(propVecOriginPlayerZ).value.FloatVal)
      90ms       90ms    201:   return r3.Vector{
         .          .    202:           X: xy.X,
         .          .    203:           Y: xy.Y,
         .          .    204:           Z: z,
         .          .    205:   }
         .          .    206:}

To Reproduce

git checkout 95349a9
# Fast
go test -run _NONE_ -bench .

git checkout 4b006c8
# Slow
go test -run _NONE_ -bench .

Expected behavior
As fast as before merging #44

Library version
4b006c8

Additional context
Discovered by @micvbang

Entity.Position() not working for player entities

Describe the bug
Entity.Position() and by extension Entity.OnPositionUpdate() aren't working for Player entities due to the position being networked differently from other entities.

Calling the function results in a nil pointer dereference panic because the m_cellbits property doesn't seem to exist.

panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x38 pc=0x78f250]

goroutine 19 [running]:
github.com/markus-wa/demoinfocs-golang.recoverFromUnexpectedEOF(0x81dde0, 0xb519b0, 0xb519b0, 0x0)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/parsing.go:110 +0xbd
github.com/markus-wa/demoinfocs-golang.(*Parser).handleGameEvent.func1(0xc04210a000)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/game_events.go:27 +0x4a
panic(0x81dde0, 0xb519b0)
        C:/Go/src/runtime/panic.go:502 +0x237
github.com/markus-wa/demoinfocs-golang/sendtables.(*Entity).Position(0xc04216a780, 0xc042089020, 0xc, 0xc0427c5208)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/sendtables/entity.go:167 +0x50

To Reproduce
Use Entity.Position() on any player and check agains Player.Position.

Code:

func x() {
    pl := parser.GameState().Participants()[0]
    fmt.Println(parser.GameState().Entities()[pl.EntityID].Position(), pl.Position)
}

Expected behavior
It should print the same (or at least a very similar) value twice.

Library version
v1.0.0-beta.2

Team info from player

Higher goal
It is better to reach team info of a player directly from player struct rather than extraneous method callings. Therefore, we can reach score, team name etc directly using player itself.

Describe the solution you'd like
Probably changing theTeam field to TeamState in Player struct and maintaining this field should work.

team state and team

Higher goal
Getting complete team information.

Describe the solution you'd like
The problem is that some events return team state which has nothing about the side of the team which is expected. However, there is no connection between team states and team sides (Team). For example, if I trigger scoreUpdated event, it returns me teamstate which I have no idea whether it is t or ct team. Fast solution would be adding a method in gamestate to retrieve team side by using team id.

Panic "Failed to unmarshal cmd"

With defaultDemName = "/broken/2014-11-01-ESWC2014-fnatic-vs-hellraisers-de_inferno.dem"

The demo appears to work without problems in GOTV.

go test -run TestDemoInfoCs
Parsing header
Registering handlers
Parsing to end
T-side score:  1
T-side score:  2
T-side score:  3
T-side score:  12
T-side score:  13
T-side score:  14
T-side score:  15
T-side score:  16
T-side score:  0
T-side score:  15
T-side score:  18
T-side score:  21
T-side score:  24
T-side score:  27
T-side score:  30
T-side score:  33
--- FAIL: TestDemoInfoCs (7.44s)
panic: Failed to unmarshal cmd 26 [recovered]
        panic: Failed to unmarshal cmd 26

goroutine 18 [running]:
testing.tRunner.func1(0xc04204c820)
        C:/Go/src/testing/testing.go:622 +0x2a4
panic(0x79fc00, 0xc042af2e60)
        C:/Go/src/runtime/panic.go:489 +0x2dd
github.com/markus-wa/demoinfocs-golang.(*Parser).parsePacket(0xc0420ec000)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/demopacket.go:78 +0x380
github.com/markus-wa/demoinfocs-golang.(*Parser).parseTick(0xc0420ec000, 0x7bf960)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/parsing.go:160 +0x1bc
github.com/markus-wa/demoinfocs-golang.(*Parser).ParseNextTick(0xc0420ec000, 0x1)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/parsing.go:72 +0x5a
github.com/markus-wa/demoinfocs-golang.(*Parser).ParseToEnd(0xc0420ec000, 0xc0420410f8)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/parsing.go:56 +0x47
github.com/markus-wa/demoinfocs-golang_test.TestDemoInfoCs(0xc04204c820)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/demoinfocs_test.go:65 +0x486
testing.tRunner(0xc04204c820, 0x850d08)
        C:/Go/src/testing/testing.go:657 +0x9d
created by testing.(*T).Run
        C:/Go/src/testing/testing.go:697 +0x2d1
exit status 2
FAIL    github.com/markus-wa/demoinfocs-golang  7.595s

Unexpected `EqUnknown`

Describe the bug
Hey!

I unfortunately haven't had time to investigate this myself. I thought that I'd report it so that I don't forget ๐Ÿ™‚

I've found that in some demos I get EqUnknown (i.e. uninitialized) for some player's weapons.

My gut tells me that this happens in the weapons mapping in datatables.go, but I have nothing else to base this on.

To Reproduce
Run the following code on dust2 from this match: https://www.hltv.org/matches/2322780/liquid-vs-astralis-esl-pro-league-season-7-finals

In round 2 we see that device has bought a scout that is being returned as EqUnknown.

Code:

package main

import (
	"log"
	"os"

	"github.com/markus-wa/demoinfocs-golang/common"

	dem "github.com/markus-wa/demoinfocs-golang"
	"github.com/markus-wa/demoinfocs-golang/events"
	ex "github.com/markus-wa/demoinfocs-golang/examples"
)

func main() {
	f, err := os.Open(ex.DemoPathFromArgs())
	checkError(err)
	defer f.Close()

	p := dem.NewParser(f)

	_, err = p.ParseHeader()
	checkError(err)

	roundNum := 1
	const playerName = "device"

	p.RegisterEventHandler(func(ev events.RoundEnd) {
		roundNum++
	})

	// Log playerName's weapons
	p.RegisterEventHandler(func(ev events.RoundFreezetimeEnd) {
		if roundNum == 2 {
			for _, p := range p.GameState().Participants().TeamMembers(common.TeamCounterTerrorists) {
				if p.Name != playerName {
					continue
				}
				for i, wep := range p.Weapons() {
					log.Printf("%s weapon[%d]: %+v\n", playerName, i, wep.Weapon)
				}
			}
			p.Cancel()
		}
	})

	err = p.ParseToEnd()
	checkError(err)
}

func checkError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

Expected behavior
Expected to not get EqUnknown for known weapons.

Library version
I'm getting these results on 13db521

roundEnd vs roundOfficiallyEnd

  1. What is all of the differences of round end and round officially ended event?
  2. As far as I read, these events could be in deficit for some of the demo files but Are we sure that on of events will be definitely called to identify a round has been ended such as round end, round officially end or score updated?

events: Is player blinded during Kill event?

Higher goal
It would be wonderful to know whether a player has been killed in a blinded situation or not. Therefore, I can update the blinded kill features.

Describe the solution you'd like
Probably, there should be an addition in event of Kill like:

type Kill struct {
    Weapon            *common.Equipment
    Victim            *common.Player
    Killer            *common.Player
    Assister          *common.Player
    PenetratedObjects int
    IsHeadshot        bool
    IsBlinded         bool
}

player reconnect

When a player connects/disconnect, which fields get reset and which fields get a newly assigned value? I collect information about players and I need to transmit info to a player who reconnected. I am using SteamID to identify players. Any suggestions to do this task in an easy way?
And also it would be wonderful to the creation of playerReconnect event.

Player additional information

Higher goal
There are several fields for players can easy being retrieved from a demo file. My interest is: whether a player is in a buy zone, whether a player in a bomb zone (and the name of the bomb zone), how many seconds alive in this round, whether he is walking, whether he is jumping (I do not know it is possible).
Describe the solution you'd like
Most of the fields are in a demo file and implemented here: https://github.com/saul/demofile/blob/c6d6555/src/entities/player.ts

base class for events

I decided to store all events in an array per round before dispatching them directly with a registered event handler, therefore, after I checked several parameters for the current round, I can decide to discard or dispatch events. However, I do not see a base interface implementation for all events. My question is that how can I store all events in a single event container for delayed dispatch?

Exposing vendored types to user, e.g. in OnPositionUpdate(r3.Vector)

It may be that I'm just unaware of how this can be circumvented, but I'm having trouble using the callbacks that require the usage of vendored types, e.g. Entity.OnPositionUpdate(r3.Vector).

It makes sense that I can't just use the type as r3.Vector in my source, as it may be different from the vendored version. And the compiler won't let me import vendored packages, which I'm not absolutely certain that I understand why wouldn't work. Anyway, unless there's some way out of this that I can't see, I think we need to look at the types from vendored code used in callbacks to publicly exported code ๐Ÿ™‚

The most obvious solution that I can come up with is to create the simplest possible wrappers for these types, e.g. where r3.Vector is used, one could create the type Vector and use it like so:

type Vector struct {
    r3.Vector
}

// example of internal use in demoinfocs-golang
func test(v r3.Vector) Vector {
    return Vector{v}
}

Unexpected player appears in a team

Describe the bug
Non-player persons appear on a team. Exact definition: There is a player called Zim in this dem file, which is neither connected nor team changed. I do not know where he is coming from.
To Reproduce
demo link: https://www.hltv.org/download/demo/47811
matchmaking page: https://www.hltv.org/matches/2331369/izako-boars-vs-havu-lantrek-2019
Code:

package testParser

import (
    "fmt"
    "os"
    "testing"

    dem "github.com/markus-wa/demoinfocs-golang"
    p_common "github.com/markus-wa/demoinfocs-golang/common"
    events "github.com/markus-wa/demoinfocs-golang/events"
)

//TestNonExistCOnnect test non exist player situation
func TestNonExistCOnnect(t *testing.T) {
    f, err := os.Open("/home/baran/Desktop/demo_files/test_demos/izako-boars-vs-havu-inferno.dem")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    p := dem.NewParser(f)

    // gs := p.GameState()

    p.RegisterEventHandler(func(e events.PlayerDisconnected) {
        gs := p.GameState()

        if e.Player != nil {
            fmt.Printf("Player has been disconnected: name:%s team:%s tick:%d\n", e.Player.Name, e.Player.TeamState.ClanName, gs.IngameTick())
        }
    })

    p.RegisterEventHandler(func(e events.PlayerTeamChange) {
        gs := p.GameState()
        participants := gs.Participants()
        teamTerrorist := participants.TeamMembers(p_common.TeamTerrorists)
        if e.Player != nil {
            fmt.Printf("Player team changed: name:%s old team:%d new team:%d tick:%d\n", e.Player.Name, e.OldTeam, e.NewTeam, gs.IngameTick())
            for _, t := range teamTerrorist {
                if t != nil {
                    fmt.Printf("t player name:%s team:%d\n", t.Name, t.Team)

                }
            }
        }
    })

    // p.RegisterEventHandler(func(e events.Kill) {
    //     fmt.Printf("Player killed a player: attacker: %s team:%d tick:%d\n", e.Killer.Name, e.Killer.Team, gs.IngameTick())
    //     if e.Killer != nil && reconnectedPlayerID == e.Killer.SteamID {
    //         fmt.Printf("Player killed a player: attacker: %s team:%s tick:%d\n", e.Killer.Name, e.Killer.TeamState.ClanName, gs.IngameTick())
    //     }
    // })

    p.RegisterEventHandler(func(e events.PlayerConnect) {
        gs := p.GameState()

        // teamCT := participants.TeamMembers(p_common.TeamCounterTerrorists)
        if e.Player != nil {
            fmt.Printf("Player has been connected: name:%s team:%d tick:%d\n", e.Player.Name, e.Player.Team, gs.IngameTick())

        }
    })

    // Parse to end
    err = p.ParseToEnd()
    if err != nil {
        panic(err)
    }
}

Expected behavior
No player other than expected ones.

Library version
v1.0.1

I want to contribute

Hey Markus!
First of all, congrats for this excellent project and the good code you're writing.
I would love to help on it and discuss few ideas, can you send me an email or gimme yours?
Salut !

events: PlayerFlashed lack of info (Attacker / FlashDuration)

Higher goal
I need to know who is flashed, who did it and the duration of being blind. It is an important feature for my machine learning model.

Describe the solution you'd like
Probably, there should be alteration for event of PlayerFlashed like:

type PlayerFlashed struct {
    Attacker *common.Player
    Defender *common.Player
    duration int
}

v1.0.0 Event naming

I was just going through some code and realized that the Event-suffix on all events in the events package is unnecessary and somewhat stuttery since it's already specified by the package name.

What do you think?

User defined events

Higher goal
As a part of my analyzer, I would like to check some pre and post status of the game related an event. For example, I need to check whether player C still alive 2 seconds after player B has been killed. Or Is there any team member near the place just 1 second before player C has been hurt.

Describe the solution you'd like
A very naive way to implement the need is that every pre and post-event entity can be stored in a list and every time tickEnd event triggered, we can check the list whether there is any post/pre entity to proses. This solution will be very slow.
Describe alternatives you've considered
Instead, providing a way to dispatch this user-defined pre/post entities (can be formed as custom events as well) like other events such as playerDeath, kill, etc. Here, I am assuming that I already collected the exact tick number of pre/post events, so the parser only emits these pre/post events like other default events when the tick number has occurred.

Persistent team id

Currently, since CCSTeam entities don't switch on team-switches (players switch instead), the TeamState id field stays the same.

Id should stay consistent on team-switches, and then we can instead have a side field in TeamState to display which side a team is on (which is in effect what TeamState.id tells us currently).

A potential solution is to create logic on player switches. This logic detects team-switches:

	type TeamSwitch struct {
		tickPlayerSwitch int
		numPlayerSwitch  int
		teamSwitchDone   bool
	}

	ts := TeamSwitch{tickPlayerSwitch: -1}

	p.RegisterEventHandler(func(e events.PlayerTeamChangeEvent) {
		tick := p.GameState().IngameTick()
		numPlayers := len(p.GameState().PlayingParticipants())
		if tick > ts.tickPlayerSwitch {
			// First player switch, fix ts
			ts.numPlayerSwitch = 1
			ts.tickPlayerSwitch = tick
			ts.teamSwitchDone = false
		} else if tick == ts.tickPlayerSwitch {
			ts.numPlayerSwitch++
			if (ts.teamSwitchDone == false) && (ts.numPlayerSwitch >= numPlayers-1) {
				// We now assume we have a change of sides.
				// Creates a margin of one in case of missing event
				ts.teamSwitchDone = true
				fmt.Println("Change of sides")
			}
		}
	})

Fully Support Parsing of POV Demos

When adding a POV demo to the test demo-set in test/cs-demos the parser crashes.

Parsing header
Registering handlers
Parsing to end
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x18 pc=0x744af6]

goroutine 19 [running]:
github.com/markus-wa/demoinfocs-golang/st.(*Entity).ApplyUpdate(0x0, 0xc0428a8000)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/st/entity.go:53 +0xc6
github.com/markus-wa/demoinfocs-golang.(*Parser).handlePacketEntities(0xc0420ec000, 0xc0425ea0c0)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/packet_handlers.go:47 +0x2d0
github.com/markus-wa/demoinfocs-golang.(*Parser).(github.com/markus-wa/demoinfocs-golang.handlePacketEntities)-fm(0xc0425ea0c0)
        C:/Dev/GoPath/src/github.com/markus-wa/demoinfocs-golang/parser.go:120 +0x3b
reflect.Value.call(0x79e7e0, 0xc042041040, 0x13, 0x829e20, 0x4, 0xc042c51f60, 0x1, 0x1, 0xc042c51e78, 0xc042c51e70, ...)
        C:/Go/src/reflect/value.go:434 +0x926
reflect.Value.Call(0x79e7e0, 0xc042041040, 0x13, 0xc042c51f60, 0x1, 0x1, 0x4, 0xc04213e000, 0xa53e00)
        C:/Go/src/reflect/value.go:302 +0xab
github.com/markus-wa/godispatch.(*Dispatcher).Dispatch.func1(0xc0420ec068, 0xc042c51f48, 0xc042c51f60, 0x1, 0x1)
        C:/Dev/GoPath/src/github.com/markus-wa/godispatch/dispatch.go:49 +0xa1
github.com/markus-wa/godispatch.(*Dispatcher).Dispatch(0xc0420ec068, 0x811500, 0xc0425ea0c0)
        C:/Dev/GoPath/src/github.com/markus-wa/godispatch/dispatch.go:50 +0x220
github.com/markus-wa/godispatch.(*Dispatcher).dispatchQueue(0xc0420ec068, 0xc04203eb40)
        C:/Dev/GoPath/src/github.com/markus-wa/godispatch/dispatch.go:93 +0x11e
created by github.com/markus-wa/godispatch.(*Dispatcher).AddQueues
        C:/Dev/GoPath/src/github.com/markus-wa/godispatch/dispatch.go:79 +0x189
exit status 2
FAIL    github.com/markus-wa/demoinfocs-golang  0.245s

nill references for events

I am observing nill players for several events such as victim in kill event etc. Is it a bug or normal? If normal how should be treated for this event?

Improve documentation related to GameState

If references to gamestate data is saved in an event, then it is not clear if the data is still valid in coming events. Documentation could be updated to clarify this use case.

Warm up event ignore

Higher goal
An option for a parser to event trigger during warm-up sessions.

Describe the solution you'd like
Probably a bool var. to indicate whether warm-up will be ignored or not in parser conf.

consistency in player ids

I there a consistency in player ids or it is changing on connect/disconnect event? If no consistency, how can I identify this is the same player with the previous player?

WeaponFiredEvent e.Weapon always default

hi ;)
first of all very nice library! love it
have not done much in go and playing around with it at the moment
when i register on for example the WeaponFiredEvent the event variable Weapon as well as Player.ActiveWeapon is set to default Equipment ( if i'm correct its because they are both eventually coming from Parser.weapons)

dont know for sure but i think its because in
https://github.com/markus-wa/demoinfocs-golang/blob/4080117fb1ba6a5782e432cb2b82b594653410f5/datatables.go#L301

eq := p.weapons[event.Entity.ID]

shouldnt it be

eq := &p.weapons[event.Entity.ID]

because you are filling eq with values afterwards but p.weapons never gets the value back

thank you
lukas

divide by zero in demoinfocs-golang/common.DemoHeader.FrameTime

Describe the bug
Running the code below on the demo file linked results in a panic (runtime error) because it's dividing by zero in common.DemoHeader.FrameTime.

To Reproduce
https://www.dropbox.com/s/z0xjroygkdec6l6/742301.dem?dl=0

Code:

		f, err := os.Open("742301.dem")
		if err != nil {
			panic(err)
		}
		defer f.Close()

		p := dem.NewParser(f)
		p.RegisterEventHandler(func(e events.SayText2) {
			 p.CurrentTime()
		})
		p.ParseToEnd()

Library version
v1.1.0

Equipment.Class() returns wrong value

Equipment.Class() currently:

EquipmentClass(int(e.Weapon) / 100)

This returns the wrong value (actual value - 1) as e.Weapon, or EquipmentElement, is rounded to the closest value.

E.g. glock has EquipmentElement value of 2. It should have an EquipmentClass of 1 (pistols), but the returned value is 0.

Naturally, as 2/100 = .02 ~ 0

Solution could be,
i) Add 100 to numerator:
EquipmentClass(int(e.Weapon)+100 / 100)
ii) Round to next integer:
EquipmentClass(math.Ceil(int(e.Weapon) / 100))

round end loser state

Describe the bug
When round end has been called, getting score by using loser team state return always 0.

To Reproduce
I think it will be produced for all demo files

Code:
Initiate round end event and get loser score by e.LoserState.Score

Expected behavior
Correct score for losing team as well.
Library version
Latest

Improve Documentation

  • Complete basic GoDoc
  • Improve README.md
  • Add more examples
    • AdditionalEventEmitters
    • ServerClasses
  • Explain some lingo & quirks of CS:GO -> will be done on a per-question basis
  • Document more fields that aren't immediately obvious
  • Maybe document #64 a bit more in depth

Notes by @micvbang:

I think it would be nice to see some general documentation on some of the lingo and quirks of the game on the roadmap
One example could be the life-cycle of grenades (e.g. for fire nades: weapon entity -> projectile entity -> inferno entity)
Another example could be what entity ids are, and why (id reuse) regular users of the library probably want to use the UniqueID instead
(This probably is an extension of bullet 1) documentation on some of the fields that are not immediately, e.g. Player.UserID. I think it would be nice to explain what this is, when compared to e.g. EntityID and SteamID

RoundEnd event not called every round

Describe the bug

Hey Markus!

This is yet another I-found-a-bug-but-did-not-yet-investigate situation ๐Ÿ™‚

I've found that in some demos the RoundEnd event is not triggered every round.

Since the internal score reported by the library is correct, at least m_scoreTotal is being called as we would expect.

To Reproduce

Run the following code on this match: https://www.hltv.org/matches/2327343/mibr-vs-azio-ecs-season-6-north-america

package main

import (
	"fmt"
	"log"
	"os"

	dem "github.com/markus-wa/demoinfocs-golang"
	"github.com/markus-wa/demoinfocs-golang/events"
	ex "github.com/markus-wa/demoinfocs-golang/examples"
)

func main() {
	f, err := os.Open(ex.DemoPathFromArgs())
	checkError(err)
	defer f.Close()

	p := dem.NewParser(f)

	_, err = p.ParseHeader()
	checkError(err)

	numRoundEnd := 1
	p.RegisterEventHandler(func(ev events.RoundEnd) {
		numRoundEnd++
	})

	numRoundEndOfficial := 1
	p.RegisterEventHandler(func(ev events.RoundEndOfficial) {
		numRoundEndOfficial++
	})

	err = p.ParseToEnd()
	checkError(err)

	fmt.Println("")
	fmt.Printf("Number of 'round end' events: %d\n", numRoundEnd)
	fmt.Printf("Number of 'round official end' events: %d\n", numRoundEndOfficial)
	fmt.Printf("Final score: %d:%d\n", p.GameState().TeamCounterTerrorists().Score(), p.GameState().TeamTerrorists().Score())
}

func checkError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

Expected behavior

I would expect to see 26 RoundEnd events, as reported by HLTV

Library version

I'm getting these results on 9916e2b4

Wrong prop values

Prop values seem to be outputting some wrong values. I have tried a couple of props that typically have two different outputs. After the first FireEntityCreatedEvent happens, both values are printed (e.g. 3 and 2 for m_iTeamNum) on the same tick. Then for the rest of the entities created, only value of 2 is printed.

	stp.FindServerClassByName("CBaseCSGrenadeProjectile").RegisterEntityCreatedHandler(func(pr st.EntityCreatedEvent) {
		pr.Entity.FindProperty("m_iTeamNum").RegisterPropertyUpdateHandler(func(val st.PropValue) {
			fmt.Println("VAL", val.IntVal)
		})
	})

This behavior happens on one of two demos I tried.

The same issue happens on m_nModelIndex on the same type of entity, however, then both demos display the behavior (both values printed on first entity created, only one value for the rest).

This is not a issue with the demos, as this does not happen using a different parser.

Example demos:
https://www.hltv.org/matches/2322565/astralis-vs-gambit-ecs-season-5-europe
https://www.hltv.org/matches/2296569/envyus-vs-natus-vincere-sltv-starseries-xiii-finals

Issues with how prop values are currently handled

Currently, there are limitations on how one can use prop values.

1. Ability to get current prop value

Currently we can only get prop value on updates. One can solve this by storing the value in a local variable, but this should perhaps be stored on the entity.

Even if we store the value locally, we face an issue: Let's say we want to store prop values when a FireEntityCreatedEvent happens. FirePropertyUpdate has not been fired yet, so we cannot store this data.

2. Multiple values fired at same tick if value differing for default(?) value on FirePropertyUpdate after a FireEntityCreatedEvent

E.g. "m_iTeamNum" on "CBaseCSGrenadeProjectile" seems to default to 3, so if the value actually is 3, FirePropertyUpdate fires once with this value. If this value is actually 2, FirePropertyUpdate is fired twice on the same tick (first with value 3, then 2).

Broad overview of what I think needs to be done for 1.:

  • Specify props one want stored on a type of entity
  • On FirePropertyUpdate for these props, store the data to Entity
  • Have a handler on the entity that is fired when the data is populated (e.g. at end of tick, as we then should get the latest, valid value from FirePropertyUpdate re: 2.)

Molotov and Incendiary grenade projectile positioning bug

We use the entity id of grenade projectiles when tracking them. This works well for non-fire grenades. It seems that new entities are created for fire grenades they explode, meaning that we can't use the entity id for tracking them and keeping GameState.grenadeProjectiles up to date.

Currently, this means that fire grenades will not get removed from GameState.grenadeProjectiles once they explode, meaning that they will accumulate through the demo.

Clean up API for v1.0.0

See v1.0.0 branch

  • Put 'game state' into a separate struct from Parser
  • Replace current warning system with a ParserWarnEvent
  • Clean up the NewParser() / NewParserWithBufferSize() (remove warnHandler from the former and rename the latter)
  • Rename RegisterPropertyUpdateHandler (remove 'Property' because it's stuttery)
  • Remove anything deprecated (marked via comment)
  • Change inconsistent / strange type names in sendtables (PropValue -> PropertyValue?, FlattenedPropEntry -> PropertyEntryMetadata? and many more, allowed since Parser.SendTableParser() is in beta)
  • Move HitGroup, RoundEndReason & RoundMVPReason constants to events package
  • Consider changing beta features / removing beta labels
  • Move Parser.entities to GameState

Smoke grenade OnDestroy event not triggered as expected

Describe the bug
In datatables.bindGrenadeProjectiles we assume that the projectile entity destroy-event is triggered every time the entity no longer appears in the game. I still haven't found out why, but this does not seem to be the case. It looks to me like projectile destruction event is not triggered on smokes (more entities?) that do not expire normally, but are "removed" because the round has ended.

To Reproduce
I found the bug in round 4 of de_nuke in the following game: https://www.hltv.org/matches/2322780/liquid-vs-astralis-esl-pro-league-season-7-finals

package main

import (
	"log"
	"os"
	"sync"

	dem "github.com/markus-wa/demoinfocs-golang"
	"github.com/markus-wa/demoinfocs-golang/events"
)

func main() {
	f, err := os.Open("liquid-vs-astralis-m2-nuke.dem")
	if err != nil {
		log.Fatal(err)
	}

	p := dem.NewParser(f)
	_, err = p.ParseHeader()
	if err != nil {
		log.Fatal(err)
	}

	wg := &sync.WaitGroup{}

	roundNum := 0
	p.RegisterEventHandler(func(_ events.RoundOfficiallyEndedEvent) {
		roundNum++
	})

	p.RegisterEventHandler(func(_ events.RoundStartedEvent) {
		log.Printf("Round %+v just started. We expect there to be no projectiles. There are: %+v\n", roundNum, len(p.GameState().GrenadeProjectiles()))
	})

	err = p.ParseToEnd()
	if err != nil {
		log.Fatal(err)
	}

	wg.Wait()
}

Expected behavior
I expect GameState().GrenadeProjectiles() to only contain active grenade projectiles, even those that did not expire normally

Library version
v1.0.0

A quick (and dirty) way to solve the problem is to simply reset the gameState.grenadeProjectiles map on RoundOfficiallyEnded event, like so (game_events.go):

...
case "round_officially_ended": // Round ended. . . probably the event where you get teleported to the spawn (=> You can still walk around between round_end and this?)
	for _, proj := range p.gameState.grenadeProjectiles {
		delete(p.gameState.grenadeProjectiles, proj.EntityID)
	}
	p.eventDispatcher.Dispatch(events.RoundOfficiallyEndedEvent{})
case ...

Question: yaw, pitch

I could not see in player struct yaw and pitch angles. However, there are viewangle variables in the struct. Are there correspond to yaw and pitch? If yes, what is exactly the view angles? I need to understand because I need to calculate crosshair replacement.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.