added endpoint for sidebar metastats
This commit is contained in:
217
utils/utils.go
217
utils/utils.go
@@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/an0nfunc/go-steamapi"
|
||||
"github.com/markus-wa/demoinfocs-golang/v2/pkg/demoinfocs/common"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.uber.org/ratelimit"
|
||||
"io"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -137,16 +139,33 @@ type PlayerResponse struct {
|
||||
VAC bool `json:"vac"`
|
||||
VACDate int64 `json:"vac_date,omitempty"`
|
||||
GameBan bool `json:"game_ban"`
|
||||
GameBanDate int64 `json:"game_ban_date"`
|
||||
GameBanDate int64 `json:"game_ban_date,omitempty"`
|
||||
Tracked bool `json:"tracked"`
|
||||
VanityURL string `json:"vanity_url,omitempty"`
|
||||
MatchStats *MatchStats `json:"match_stats,omitempty"`
|
||||
Matches []*MatchResponse `json:"matches,omitempty"`
|
||||
}
|
||||
|
||||
type WeaponResponse struct {
|
||||
Player *PlayerResponse `json:"player"`
|
||||
Eq map[string][][]int `json:"eq,omitempty"`
|
||||
type MateResponse struct {
|
||||
Player *PlayerResponse `json:"player"`
|
||||
WinRate float32 `json:"win_rate,omitempty"`
|
||||
TieRate float32 `json:"tie_rate,omitempty"`
|
||||
Total int `json:"total,omitempty"`
|
||||
}
|
||||
|
||||
type WeaponDmg struct {
|
||||
Eq int `json:"eq"`
|
||||
Dmg uint `json:"dmg"`
|
||||
}
|
||||
|
||||
type MetaStatsResponse struct {
|
||||
Player *PlayerResponse `json:"player"`
|
||||
BestMates []*MateResponse `json:"best_mates,omitempty"`
|
||||
MostMates []*MateResponse `json:"most_mates,omitempty"`
|
||||
EqMap map[int]string `json:"eq_map,omitempty"`
|
||||
WeaponDmg []*WeaponDmg `json:"weapon_dmg,omitempty"`
|
||||
WinMaps map[string]float32 `json:"win_maps,omitempty"`
|
||||
TieMaps map[string]float32 `json:"tie_maps,omitempty"`
|
||||
}
|
||||
|
||||
type MatchResponse struct {
|
||||
@@ -177,6 +196,8 @@ type (
|
||||
|
||||
const (
|
||||
shareCodeURLEntry = "https://api.steampowered.com/ICSGOPlayers_730/GetNextMatchSharingCode/v1?key=%s&steamid=%d&steamidkey=%s&knowncode=%s"
|
||||
SideMetaCacheKey = "csgowtfd_side_meta_%d"
|
||||
MatchMetaCacheKey = "csgowtfd_match_meta_%d"
|
||||
)
|
||||
|
||||
//goland:noinspection SpellCheckingInspection
|
||||
@@ -201,6 +222,180 @@ func SendJSON(data interface{}, w http.ResponseWriter) error {
|
||||
}
|
||||
|
||||
func GetMatchStats(dbPlayer *ent.Player, lock *sync.RWMutex) (int, int, int, error) {
|
||||
wins, loss, ties, err := getWinLossTieFromPlayer(dbPlayer, lock)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
return wins, ties, loss, nil
|
||||
}
|
||||
|
||||
func GetMetaStats(dbPlayer *ent.Player, lock *sync.RWMutex, limit int) (*MetaStatsResponse, error) {
|
||||
mResponse := new(MetaStatsResponse)
|
||||
mResponse.Player = &PlayerResponse{SteamID64: dbPlayer.ID}
|
||||
|
||||
lock.RLock()
|
||||
tPlayers, err := dbPlayer.QueryMatches().QueryPlayers().All(context.Background())
|
||||
lock.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lock.RLock()
|
||||
matchIDs, err := dbPlayer.QueryMatches().IDs(context.Background())
|
||||
lock.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mapWins := map[string]int{}
|
||||
mapTies := map[string]int{}
|
||||
mapMatchTotal := map[string]int{}
|
||||
matchSeen := map[uint64]bool{}
|
||||
mResponse.EqMap = map[int]string{}
|
||||
|
||||
for _, s := range tPlayers {
|
||||
if s.ID == dbPlayer.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
mateRes := new(MateResponse)
|
||||
mostRes := new(MateResponse)
|
||||
|
||||
playerRes := &PlayerResponse{
|
||||
SteamID64: s.ID,
|
||||
Name: s.Name,
|
||||
Avatar: s.Avatar,
|
||||
Tracked: s.AuthCode != "",
|
||||
VanityURL: s.VanityURLReal,
|
||||
}
|
||||
|
||||
lock.RLock()
|
||||
pMatches, err := s.QueryMatches().Where(match.IDIn(matchIDs...)).WithStats().Where(match.HasStatsWith(stats.Or(stats.PlayerStats(dbPlayer.ID), stats.PlayerStats(s.ID)))).All(context.Background())
|
||||
lock.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mostRes.Player = playerRes
|
||||
var wins int
|
||||
var ties int
|
||||
|
||||
for _, pm := range pMatches {
|
||||
var subjectStats *ent.Stats
|
||||
var currentStats *ent.Stats
|
||||
|
||||
for _, ps := range pm.Edges.Stats {
|
||||
if ps.PlayerStats == dbPlayer.ID {
|
||||
subjectStats = ps
|
||||
} else if ps.PlayerStats == s.ID {
|
||||
currentStats = ps
|
||||
}
|
||||
}
|
||||
|
||||
win := subjectStats.TeamID == pm.MatchResult
|
||||
tie := pm.MatchResult == 0
|
||||
|
||||
if _, ok := matchSeen[pm.ID]; !ok {
|
||||
mapMatchTotal[pm.Map]++
|
||||
if win {
|
||||
mapWins[pm.Map]++
|
||||
} else if tie {
|
||||
mapTies[pm.Map]++
|
||||
}
|
||||
|
||||
lock.RLock()
|
||||
wSs, err := subjectStats.QueryWeaponStats().All(context.Background())
|
||||
lock.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, weaponStat := range wSs {
|
||||
found := false
|
||||
for _, dmgS := range mResponse.WeaponDmg {
|
||||
if dmgS.Eq == weaponStat.EqType {
|
||||
dmgS.Dmg += weaponStat.Dmg
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
mResponse.WeaponDmg = append(mResponse.WeaponDmg, &WeaponDmg{
|
||||
Eq: weaponStat.EqType,
|
||||
Dmg: weaponStat.Dmg,
|
||||
})
|
||||
}
|
||||
|
||||
if _, exist := mResponse.EqMap[weaponStat.EqType]; !exist {
|
||||
mResponse.EqMap[weaponStat.EqType] = common.EquipmentType(weaponStat.EqType).String()
|
||||
}
|
||||
}
|
||||
|
||||
matchSeen[pm.ID] = true
|
||||
}
|
||||
|
||||
// check if same team
|
||||
if subjectStats.TeamID == currentStats.TeamID {
|
||||
mostRes.Total++
|
||||
if win {
|
||||
wins++
|
||||
} else if tie {
|
||||
ties++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mostRes.Total > 0 {
|
||||
mResponse.MostMates = append(mResponse.MostMates, mostRes)
|
||||
}
|
||||
|
||||
if mostRes.Total > 0 && (wins > 0 || ties > 0) {
|
||||
mateRes.Player = playerRes
|
||||
mateRes.TieRate = float32(ties) / float32(mostRes.Total)
|
||||
mateRes.WinRate = float32(wins) / float32(mostRes.Total)
|
||||
mResponse.BestMates = append(mResponse.BestMates, mateRes)
|
||||
}
|
||||
}
|
||||
|
||||
mResponse.TieMaps = map[string]float32{}
|
||||
mResponse.WinMaps = map[string]float32{}
|
||||
|
||||
for tMap, wins := range mapWins {
|
||||
mResponse.WinMaps[tMap] = float32(wins) / float32(mapMatchTotal[tMap])
|
||||
}
|
||||
|
||||
for tMap, ties := range mapTies {
|
||||
mResponse.TieMaps[tMap] = float32(ties) / float32(mapMatchTotal[tMap])
|
||||
}
|
||||
|
||||
// sort all results
|
||||
sort.Slice(mResponse.BestMates, func(i, j int) bool {
|
||||
return mResponse.BestMates[i].WinRate > mResponse.BestMates[j].WinRate
|
||||
})
|
||||
|
||||
sort.Slice(mResponse.MostMates, func(i, j int) bool {
|
||||
return mResponse.MostMates[i].Total > mResponse.MostMates[j].Total
|
||||
})
|
||||
|
||||
sort.Slice(mResponse.WeaponDmg, func(i, j int) bool {
|
||||
return mResponse.WeaponDmg[i].Dmg > mResponse.WeaponDmg[j].Dmg
|
||||
})
|
||||
|
||||
if len(mResponse.BestMates) > limit {
|
||||
mResponse.BestMates = mResponse.BestMates[:limit]
|
||||
}
|
||||
if len(mResponse.MostMates) > limit {
|
||||
mResponse.MostMates = mResponse.MostMates[:limit]
|
||||
}
|
||||
if len(mResponse.WeaponDmg) > limit {
|
||||
mResponse.WeaponDmg = mResponse.WeaponDmg[:limit]
|
||||
}
|
||||
|
||||
return mResponse, nil
|
||||
}
|
||||
|
||||
func getWinLossTieFromPlayer(dbPlayer *ent.Player, lock *sync.RWMutex) (int, int, int, error) {
|
||||
var res []struct {
|
||||
MatchResult int `json:"match_result"`
|
||||
Count int `json:"count"`
|
||||
@@ -209,8 +404,15 @@ func GetMatchStats(dbPlayer *ent.Player, lock *sync.RWMutex) (int, int, int, err
|
||||
lock.RLock()
|
||||
err := dbPlayer.QueryMatches().GroupBy(match.FieldMatchResult).Aggregate(func(s *sql.Selector) string {
|
||||
sT := sql.Table(stats.Table)
|
||||
|
||||
s.Join(sT).On(s.C(match.FieldID), sT.C(stats.MatchesColumn))
|
||||
s.Where(sql.And(sql.Or(sql.ColumnsEQ(match.FieldMatchResult, stats.FieldTeamID), sql.EQ(s.C(match.FieldMatchResult), 0)), sql.EQ(sT.C(stats.PlayersColumn), dbPlayer.ID)))
|
||||
s.Where(sql.And(
|
||||
sql.Or(
|
||||
sql.ColumnsEQ(match.FieldMatchResult, stats.FieldTeamID),
|
||||
sql.EQ(s.C(match.FieldMatchResult), 0),
|
||||
),
|
||||
sql.EQ(sT.C(stats.PlayersColumn), dbPlayer.ID),
|
||||
))
|
||||
return sql.Count("*")
|
||||
}).Scan(context.Background(), &res)
|
||||
lock.RUnlock()
|
||||
@@ -227,9 +429,6 @@ func GetMatchStats(dbPlayer *ent.Player, lock *sync.RWMutex) (int, int, int, err
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
if len(res) < 1 {
|
||||
return 0, 0, 0, nil
|
||||
}
|
||||
var (
|
||||
wins int
|
||||
ties int
|
||||
@@ -244,7 +443,7 @@ func GetMatchStats(dbPlayer *ent.Player, lock *sync.RWMutex) (int, int, int, err
|
||||
}
|
||||
}
|
||||
|
||||
return wins, ties, total - wins - ties, nil
|
||||
return wins, total - wins - ties, ties, nil
|
||||
}
|
||||
|
||||
func IsAuthCodeValid(player *ent.Player, lock *sync.RWMutex, apiKey string, shareCode string, authCode string, rl ratelimit.Limiter) (bool, error) {
|
||||
|
Reference in New Issue
Block a user