From d74b8e3447db25fdefedbfc81c1ae724ed0e3ba8 Mon Sep 17 00:00:00 2001 From: Giovanni Harting <539@idlegandalf.com> Date: Wed, 13 Oct 2021 17:30:17 +0200 Subject: [PATCH] added weapon-stats endpoint --- csgo/demo_parser.go | 4 + main.go | 236 +++++++++++++++++++++++++------------------- utils/utils.go | 67 +++++++++++++ 3 files changed, 207 insertions(+), 100 deletions(-) diff --git a/csgo/demo_parser.go b/csgo/demo_parser.go index 9085210..ab621a2 100644 --- a/csgo/demo_parser.go +++ b/csgo/demo_parser.go @@ -317,6 +317,10 @@ func (p *DemoParser) parseWorker() { } for _, tMatchPlayer := range tStats { + if tMatchPlayer.Color == "" { + tMatchPlayer.Color = stats.ColorGrey + } + p.lock.Lock() nMatchPLayer, err := tMatchPlayer.Update(). SetDmgTeam(tMatchPlayer.DmgTeam). diff --git a/main.go b/main.go index 756d21c..c8d289d 100644 --- a/main.go +++ b/main.go @@ -57,6 +57,18 @@ type PlayerResponse struct { Matches []*MatchResponse `json:"matches,omitempty"` } +type EqResponse struct { + Victim uint64 `json:"victim"` + Type int `json:"type"` + HitGroup int `json:"hit_group"` + Dmg uint `json:"dmg"` +} + +type WeaponResponse struct { + Player *PlayerResponse `json:"player"` + Eq []*EqResponse `json:"eq,omitempty"` +} + type MatchResponse struct { MatchId uint64 `json:"match_id,string"` ShareCode string `json:"share_code,omitempty"` @@ -70,63 +82,6 @@ type MatchResponse struct { Stats interface{} `json:"stats,omitempty"` } -type StatsResponse struct { - TeamID int `json:"team_id"` - Kills int `json:"kills"` - Deaths int `json:"deaths"` - Assists int `json:"assists"` - Headshot int `json:"headshot"` - MVP uint `json:"mvp"` - Score int `json:"score"` - Player interface{} `json:"player,omitempty"` - Rank struct { - Old int `json:"old,omitempty"` - New int `json:"new,omitempty"` - } `json:"rank,omitempty"` - MultiKills struct { - Duo uint `json:"duo,omitempty"` - Triple uint `json:"triple,omitempty"` - Quad uint `json:"quad,omitempty"` - Pent uint `json:"pent,omitempty"` - } `json:"multi_kills,omitempty"` - Dmg struct { - Enemy uint `json:"enemy,omitempty"` - Team uint `json:"team,omitempty"` - UD struct { - HE uint `json:"he,omitempty"` - Flames uint `json:"flames,omitempty"` - Flash uint `json:"flash,omitempty"` - Decoy uint `json:"decoy,omitempty"` - Smoke uint `json:"smoke,omitempty"` - } `json:"ud,omitempty"` - HitGroup struct { - Head uint `json:"head,omitempty"` - Chest uint `json:"chest,omitempty"` - Stomach uint `json:"stomach,omitempty"` - LeftArm uint `json:"left_arm,omitempty"` - RightArm uint `json:"right_arm,omitempty"` - LeftLeg uint `json:"left_leg,omitempty"` - RightLeg uint `json:"right_leg,omitempty"` - Gear uint `json:"gear,omitempty"` - } `json:"hit_group,omitempty"` - } `json:"dmg,omitempty"` - Flash struct { - Duration struct { - Self float32 `json:"self,omitempty"` - Team float32 `json:"team,omitempty"` - Enemy float32 `json:"enemy,omitempty"` - } `json:"duration,omitempty"` - Total struct { - Team uint `json:"team,omitempty"` - Enemy uint `json:"enemy,omitempty"` - Self uint `json:"self,omitempty"` - } `json:"total,omitempty"` - } `json:"flash,omitempty"` - Crosshair string `json:"crosshair,omitempty"` - Color string `json:"color,omitempty"` - KAST int `json:"kast,omitempty"` -} - func housekeeping() { for { if !firstHK { @@ -301,7 +256,7 @@ func getPlayer(w http.ResponseWriter, r *http.Request) { continue } - sResponse := &StatsResponse{ + sResponse := &utils.StatsResponse{ TeamID: tStats.TeamID, Kills: tStats.Kills, Deaths: tStats.Deaths, @@ -311,16 +266,22 @@ func getPlayer(w http.ResponseWriter, r *http.Request) { Score: tStats.Score, } - sResponse.MultiKills.Duo = tStats.Mk2 - sResponse.MultiKills.Triple = tStats.Mk3 - sResponse.MultiKills.Quad = tStats.Mk4 - sResponse.MultiKills.Pent = tStats.Mk5 + sResponse.MultiKills = &utils.MultiKills{ + Duo: tStats.Mk2, + Triple: tStats.Mk3, + Quad: tStats.Mk4, + Pent: tStats.Mk5, + } - sResponse.Rank.Old = tStats.RankOld - sResponse.Rank.New = tStats.RankNew + sResponse.Rank = &utils.Rank{ + Old: tStats.RankOld, + New: tStats.RankNew, + } - sResponse.Dmg.Enemy = tStats.DmgEnemy - sResponse.Dmg.Team = tStats.DmgTeam + sResponse.Dmg = &utils.Damage{ + Enemy: tStats.DmgEnemy, + Team: tStats.DmgTeam, + } mResponse.Stats = sResponse response.Matches = append(response.Matches, mResponse) @@ -409,6 +370,64 @@ func getMatchParse(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusAccepted) } +func getMatchWeapons(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", conf.Httpd.CORSAllowDomains) + id := mux.Vars(r)["id"] + + if id == "" { + w.WriteHeader(http.StatusBadRequest) + return + } + + matchId, err := strconv.ParseUint(id, 10, 64) + if err != nil { + log.Warningf("[GM] Error parsing matchID %s: %v", id, err) + w.WriteHeader(http.StatusBadRequest) + return + } + + mResponse := make([]*WeaponResponse, 0) + + db.Lock.RLock() + tStats, err := db.Client.Stats.Query().Where(stats.HasMatchesWith(match.ID(matchId))).All(context.Background()) + db.Lock.RUnlock() + if err != nil { + log.Warningf("[GMW] match %d not found: %+v", matchId, err) + w.WriteHeader(http.StatusNotFound) + return + } + + for _, stat := range tStats { + db.Lock.RLock() + mWs, err := stat.QueryWeaponStats().All(context.Background()) + db.Lock.RUnlock() + if err != nil { + log.Warningf("[GMW] Unbale to get WeaponStats for player %d: %v", stat.PlayerStats, err) + continue + } + + mWr := &WeaponResponse{ + Player: &PlayerResponse{SteamID64: stat.PlayerStats}, + } + + for _, wr := range mWs { + mWr.Eq = append(mWr.Eq, &EqResponse{ + Victim: wr.Victim, + Type: wr.EqType, + HitGroup: wr.HitGroup, + Dmg: wr.Dmg, + }) + } + mResponse = append(mResponse, mWr) + } + + err = utils.SendJSON(mResponse, w) + if err != nil { + log.Errorf("[GM] JSON: %+v", err) + w.WriteHeader(http.StatusInternalServerError) + } +} + func getMatch(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", conf.Httpd.CORSAllowDomains) id := mux.Vars(r)["id"] @@ -444,7 +463,7 @@ func getMatch(w http.ResponseWriter, r *http.Request) { MatchResult: tMatch.MatchResult, MaxRounds: tMatch.MaxRounds, Parsed: tMatch.DemoParsed, - Stats: []*StatsResponse{}, + Stats: []*utils.StatsResponse{}, } db.Lock.RLock() @@ -456,10 +475,10 @@ func getMatch(w http.ResponseWriter, r *http.Request) { return } - tmpStats := make([]*StatsResponse, 0) + tmpStats := make([]*utils.StatsResponse, 0) for _, iStats := range tStats { - sResponse := &StatsResponse{ + sResponse := &utils.StatsResponse{ Player: PlayerResponse{ SteamID64: iStats.Edges.Players.ID, Name: iStats.Edges.Players.Name, @@ -475,38 +494,54 @@ func getMatch(w http.ResponseWriter, r *http.Request) { Headshot: iStats.Headshot, MVP: iStats.Mvp, Score: iStats.Score, - } - sResponse.Color = iStats.Color.String() - sResponse.Crosshair = iStats.Crosshair - sResponse.KAST = iStats.Kast - sResponse.Dmg.Team = iStats.DmgTeam - sResponse.Dmg.Enemy = iStats.DmgEnemy - sResponse.Dmg.UD.HE = iStats.UdHe - sResponse.Dmg.UD.Smoke = iStats.UdSmoke - sResponse.Dmg.UD.Flash = iStats.UdFlash - sResponse.Dmg.UD.Decoy = iStats.UdDecoy - sResponse.Dmg.UD.Flames = iStats.UdFlames - sResponse.Dmg.HitGroup.Gear = iStats.HitGroupGear - sResponse.Dmg.HitGroup.LeftLeg = iStats.HitGroupLeftLeg - sResponse.Dmg.HitGroup.RightLeg = iStats.HitGroupRightLeg - sResponse.Dmg.HitGroup.RightArm = iStats.HitGroupRightArm - sResponse.Dmg.HitGroup.LeftArm = iStats.HitGroupLeftArm - sResponse.Dmg.HitGroup.Stomach = iStats.HitGroupStomach - sResponse.Dmg.HitGroup.Chest = iStats.HitGroupChest - sResponse.Dmg.HitGroup.Head = iStats.HitGroupHead - sResponse.Rank.Old = iStats.RankOld - sResponse.Rank.New = iStats.RankNew - sResponse.Flash.Total.Enemy = iStats.FlashTotalEnemy - sResponse.Flash.Total.Team = iStats.FlashTotalTeam - sResponse.Flash.Total.Self = iStats.FlashTotalSelf - sResponse.Flash.Duration.Enemy = iStats.FlashDurationEnemy - sResponse.Flash.Duration.Team = iStats.FlashDurationTeam - sResponse.Flash.Duration.Self = iStats.FlashDurationSelf - sResponse.MultiKills.Duo = iStats.Mk2 - sResponse.MultiKills.Triple = iStats.Mk3 - sResponse.MultiKills.Quad = iStats.Mk4 - sResponse.MultiKills.Pent = iStats.Mk5 + Dmg: &utils.Damage{ + Team: iStats.DmgTeam, + Enemy: iStats.DmgEnemy, + UD: &utils.UD{ + HE: iStats.UdHe, + Smoke: iStats.UdSmoke, + Flash: iStats.UdFlash, + Decoy: iStats.UdDecoy, + Flames: iStats.UdFlames, + }, + HitGroup: &utils.HitGroup{ + Gear: iStats.HitGroupGear, + LeftLeg: iStats.HitGroupLeftLeg, + RightLeg: iStats.HitGroupRightLeg, + RightArm: iStats.HitGroupRightArm, + LeftArm: iStats.HitGroupLeftArm, + Stomach: iStats.HitGroupStomach, + Chest: iStats.HitGroupChest, + Head: iStats.HitGroupHead, + }, + }, + Color: iStats.Color.String(), + Crosshair: iStats.Crosshair, + KAST: iStats.Kast, + Rank: &utils.Rank{ + Old: iStats.RankOld, + New: iStats.RankNew, + }, + Flash: &utils.Flash{ + Total: &utils.SelfTeamEnemy{ + Enemy: iStats.FlashTotalEnemy, + Team: iStats.FlashTotalTeam, + Self: iStats.FlashTotalSelf, + }, + Duration: &utils.SelfTeamEnemy{ + Enemy: iStats.FlashDurationEnemy, + Team: iStats.FlashDurationTeam, + Self: iStats.FlashDurationSelf, + }, + }, + MultiKills: &utils.MultiKills{ + Duo: iStats.Mk2, + Triple: iStats.Mk3, + Quad: iStats.Mk4, + Pent: iStats.Mk5, + }, + } if !iStats.Edges.Players.VacDate.IsZero() { switch s := sResponse.Player.(type) { @@ -622,6 +657,7 @@ func main() { router.HandleFunc("/player/trackme", postPlayerTrackMe).Methods(http.MethodPost, http.MethodOptions) router.HandleFunc("/match/parse/{sharecode}", getMatchParse).Methods(http.MethodGet, http.MethodOptions) router.HandleFunc("/match/{id:[0-9]{19}}", getMatch).Methods(http.MethodGet, http.MethodOptions) + router.HandleFunc("/match/{id:[0-9]{19}}/weapons", getMatchWeapons).Methods(http.MethodGet, http.MethodOptions) router.Use(mux.CORSMethodMiddleware(router)) loggedRouter := handlers.LoggingHandler(os.Stdout, router) proxyRouter := handlers.ProxyHeaders(loggedRouter) diff --git a/utils/utils.go b/utils/utils.go index 363c405..68d79d4 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -83,6 +83,73 @@ type MatchStats struct { Loss int `json:"loss,omitempty"` } +type MultiKills struct { + Duo uint `json:"duo,omitempty"` + Triple uint `json:"triple,omitempty"` + Quad uint `json:"quad,omitempty"` + Pent uint `json:"pent,omitempty"` +} + +type Rank struct { + Old int `json:"old,omitempty"` + New int `json:"new,omitempty"` +} + +type HitGroup struct { + Head uint `json:"head,omitempty"` + Chest uint `json:"chest,omitempty"` + Stomach uint `json:"stomach,omitempty"` + LeftArm uint `json:"left_arm,omitempty"` + RightArm uint `json:"right_arm,omitempty"` + LeftLeg uint `json:"left_leg,omitempty"` + RightLeg uint `json:"right_leg,omitempty"` + Gear uint `json:"gear,omitempty"` +} + +type UD struct { + HE uint `json:"he,omitempty"` + Flames uint `json:"flames,omitempty"` + Flash uint `json:"flash,omitempty"` + Decoy uint `json:"decoy,omitempty"` + Smoke uint `json:"smoke,omitempty"` +} + +type Damage struct { + Enemy uint `json:"enemy,omitempty"` + Team uint `json:"team,omitempty"` + UD *UD `json:"ud,omitempty"` + HitGroup *HitGroup `json:"hit_group,omitempty"` +} + +type SelfTeamEnemy struct { + Self interface{} `json:"self,omitempty"` + Team interface{} `json:"team,omitempty"` + Enemy interface{} `json:"enemy,omitempty"` +} + +type Flash struct { + Duration *SelfTeamEnemy `json:"duration,omitempty"` + Total *SelfTeamEnemy `json:"total,omitempty"` +} + +type StatsResponse struct { + TeamID int `json:"team_id"` + Kills int `json:"kills"` + Deaths int `json:"deaths"` + Assists int `json:"assists"` + Headshot int `json:"headshot"` + MVP uint `json:"mvp"` + Score int `json:"score"` + Player interface{} `json:"player,omitempty"` + Rank *Rank `json:"rank,omitempty"` + MultiKills *MultiKills `json:"multi_kills,omitempty"` + Dmg *Damage `json:"dmg,omitempty"` + Flash *Flash `json:"flash,omitempty"` + Crosshair string `json:"crosshair,omitempty"` + Color string `json:"color,omitempty"` + KAST int `json:"kast,omitempty"` +} + const ( steamID64Entry = "https://steamcommunity.com/profiles/%d?xml=1" steamVanityURLEntry = "https://steamcommunity.com/id/%s?xml=1"