[FEATURE] Detailed stats about player weapon usage and hitgroups (#1)
Reviewed-on: https://git.harting.dev/CSGOWTF/csgowtfd/pulls/1 Co-authored-by: Giovanni Harting <539@idlegandalf.com> Co-committed-by: Giovanni Harting <539@idlegandalf.com>
This commit is contained in:
242
main.go
242
main.go
@@ -9,12 +9,14 @@ import (
|
||||
"csgowtfd/ent/player"
|
||||
"csgowtfd/ent/stats"
|
||||
"csgowtfd/utils"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/go-redis/cache/v8"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wercker/journalhook"
|
||||
@@ -44,42 +46,6 @@ var (
|
||||
journalLogFlag = flag.Bool("journal", false, "Log to systemd journal instead of stdout")
|
||||
)
|
||||
|
||||
type PlayerResponse struct {
|
||||
SteamID64 uint64 `json:"steamid64,string"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"avatar"`
|
||||
VAC bool `json:"vac"`
|
||||
Tracked bool `json:"tracked"`
|
||||
VanityURL string `json:"vanity_url,omitempty"`
|
||||
MatchStats utils.MatchStats `json:"match_stats,omitempty"`
|
||||
Matches []*MatchResponse `json:"matches,omitempty"`
|
||||
}
|
||||
|
||||
type MatchResponse struct {
|
||||
MatchId uint64 `json:"match_id,string"`
|
||||
ShareCode string `json:"share_code"`
|
||||
Map string `json:"map"`
|
||||
Date time.Time `json:"date"`
|
||||
Score [2]int `json:"score"`
|
||||
Duration int `json:"duration"`
|
||||
MatchResult int `json:"match_result"`
|
||||
MaxRounds int `json:"max_rounds,omitempty"`
|
||||
Parsed bool `json:"parsed"`
|
||||
Stats []*StatsResponse `json:"stats"`
|
||||
}
|
||||
|
||||
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 int `json:"mvp"`
|
||||
Score int `json:"score"`
|
||||
Player PlayerResponse `json:"player"`
|
||||
Extended interface{} `json:"extended,omitempty"`
|
||||
}
|
||||
|
||||
func housekeeping() {
|
||||
for {
|
||||
if !firstHK {
|
||||
@@ -98,9 +64,7 @@ func housekeeping() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tPlayer := range tPlayerNeedSteamUpdate {
|
||||
_, err = utils.UpdatePlayerFromSteam(tPlayer, conf.Steam.APIKey, db.Lock, rL)
|
||||
}
|
||||
_, err = utils.UpdatePlayerFromSteam(tPlayerNeedSteamUpdate, db.Client, conf.Steam.APIKey, db.Lock, rL)
|
||||
|
||||
// getting new sharecodes
|
||||
if !demoLoader.GCReady {
|
||||
@@ -169,18 +133,22 @@ func getPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
response := PlayerResponse{
|
||||
response := utils.PlayerResponse{
|
||||
SteamID64: tPlayer.ID,
|
||||
Name: tPlayer.Name,
|
||||
Avatar: tPlayer.AvatarURL,
|
||||
VAC: tPlayer.Vac,
|
||||
VanityURL: tPlayer.VanityURLReal,
|
||||
Tracked: tPlayer.AuthCode != "",
|
||||
Matches: []*MatchResponse{},
|
||||
Matches: []*utils.MatchResponse{},
|
||||
}
|
||||
|
||||
if !tPlayer.VacDate.IsZero() {
|
||||
response.VACDate = &tPlayer.VacDate
|
||||
}
|
||||
|
||||
db.Lock.RLock()
|
||||
tMatches, err := tPlayer.QueryMatches().Order(ent.Desc(match.FieldDate)).Limit(20).All(context.Background())
|
||||
tMatches, err := tPlayer.QueryMatches().Order(ent.Desc(match.FieldDate)).Limit(10).All(context.Background())
|
||||
db.Lock.RUnlock()
|
||||
if err != nil {
|
||||
log.Debugf("[GP] No matches found for player %s", id)
|
||||
@@ -226,9 +194,8 @@ func getPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
for _, iMatch := range tMatches {
|
||||
mResponse := &MatchResponse{
|
||||
mResponse := &utils.MatchResponse{
|
||||
MatchId: iMatch.ID,
|
||||
ShareCode: iMatch.ShareCode,
|
||||
Map: iMatch.Map,
|
||||
Date: iMatch.Date,
|
||||
Score: [2]int{iMatch.ScoreTeamA, iMatch.ScoreTeamB},
|
||||
@@ -236,31 +203,49 @@ func getPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
MatchResult: iMatch.MatchResult,
|
||||
MaxRounds: iMatch.MaxRounds,
|
||||
Parsed: iMatch.DemoParsed,
|
||||
Stats: []*StatsResponse{},
|
||||
}
|
||||
|
||||
db.Lock.RLock()
|
||||
tStats, err := iMatch.QueryStats().Where(stats.HasPlayersWith(player.ID(tPlayer.ID))).WithPlayers().All(context.Background())
|
||||
tStats, err := iMatch.QueryStats().Modify(func(s *sql.Selector) {
|
||||
s.Select(stats.FieldTeamID, stats.FieldKills, stats.FieldDeaths, stats.FieldAssists, stats.FieldHeadshot,
|
||||
stats.FieldMvp, stats.FieldScore, stats.FieldMk2, stats.FieldMk3, stats.FieldMk4, stats.FieldMk5,
|
||||
stats.FieldRankOld, stats.FieldRankNew, stats.FieldDmgTeam, stats.FieldDmgEnemy)
|
||||
s.Where(sql.EQ(s.C(stats.PlayersColumn), tPlayer.ID))
|
||||
}).Only(context.Background())
|
||||
db.Lock.RUnlock()
|
||||
if err != nil {
|
||||
response.Matches = append(response.Matches, mResponse)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, iStats := range tStats {
|
||||
sResponse := &StatsResponse{
|
||||
TeamID: iStats.TeamID,
|
||||
Kills: iStats.Kills,
|
||||
Deaths: iStats.Deaths,
|
||||
Assists: iStats.Assists,
|
||||
Headshot: iStats.Headshot,
|
||||
MVP: iStats.Mvp,
|
||||
Score: iStats.Score,
|
||||
Extended: iStats.Extended,
|
||||
}
|
||||
|
||||
mResponse.Stats = append(mResponse.Stats, sResponse)
|
||||
sResponse := &utils.StatsResponse{
|
||||
TeamID: tStats.TeamID,
|
||||
Kills: tStats.Kills,
|
||||
Deaths: tStats.Deaths,
|
||||
Assists: tStats.Assists,
|
||||
Headshot: tStats.Headshot,
|
||||
MVP: tStats.Mvp,
|
||||
Score: tStats.Score,
|
||||
}
|
||||
|
||||
sResponse.MultiKills = &utils.MultiKills{
|
||||
Duo: tStats.Mk2,
|
||||
Triple: tStats.Mk3,
|
||||
Quad: tStats.Mk4,
|
||||
Pent: tStats.Mk5,
|
||||
}
|
||||
|
||||
sResponse.Rank = &utils.Rank{
|
||||
Old: tStats.RankOld,
|
||||
New: tStats.RankNew,
|
||||
}
|
||||
|
||||
sResponse.Dmg = &utils.Damage{
|
||||
Enemy: tStats.DmgEnemy,
|
||||
Team: tStats.DmgTeam,
|
||||
}
|
||||
|
||||
mResponse.Stats = sResponse
|
||||
response.Matches = append(response.Matches, mResponse)
|
||||
}
|
||||
|
||||
@@ -322,7 +307,7 @@ func postPlayerTrackMe(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func getMatchParse(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -344,7 +329,65 @@ func getMatchParse(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
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([]*utils.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 := &utils.WeaponResponse{
|
||||
Player: &utils.PlayerResponse{SteamID64: stat.PlayerStats},
|
||||
}
|
||||
|
||||
for _, wr := range mWs {
|
||||
mWr.Eq = append(mWr.Eq, &utils.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) {
|
||||
@@ -372,7 +415,7 @@ func getMatch(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
mResponse := &MatchResponse{
|
||||
mResponse := &utils.MatchResponse{
|
||||
MatchId: tMatch.ID,
|
||||
ShareCode: tMatch.ShareCode,
|
||||
Map: tMatch.Map,
|
||||
@@ -382,7 +425,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()
|
||||
@@ -394,9 +437,11 @@ func getMatch(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
tmpStats := make([]*utils.StatsResponse, 0)
|
||||
|
||||
for _, iStats := range tStats {
|
||||
sResponse := &StatsResponse{
|
||||
Player: PlayerResponse{
|
||||
sResponse := &utils.StatsResponse{
|
||||
Player: utils.PlayerResponse{
|
||||
SteamID64: iStats.Edges.Players.ID,
|
||||
Name: iStats.Edges.Players.Name,
|
||||
Avatar: iStats.Edges.Players.AvatarURL,
|
||||
@@ -411,11 +456,65 @@ func getMatch(w http.ResponseWriter, r *http.Request) {
|
||||
Headshot: iStats.Headshot,
|
||||
MVP: iStats.Mvp,
|
||||
Score: iStats.Score,
|
||||
Extended: iStats.Extended,
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
mResponse.Stats = append(mResponse.Stats, sResponse)
|
||||
if !iStats.Edges.Players.VacDate.IsZero() {
|
||||
switch s := sResponse.Player.(type) {
|
||||
case utils.PlayerResponse:
|
||||
s.VACDate = &iStats.Edges.Players.VacDate
|
||||
}
|
||||
}
|
||||
|
||||
tmpStats = append(tmpStats, sResponse)
|
||||
}
|
||||
mResponse.Stats = tmpStats
|
||||
|
||||
err = utils.SendJSON(mResponse, w)
|
||||
if err != nil {
|
||||
@@ -461,13 +560,17 @@ func main() {
|
||||
|
||||
db.Client, err = ent.Open(conf.Db.Driver, conf.Db.ConnectTo)
|
||||
if err != nil {
|
||||
log.Panicf("Failed to open database %s: %v", "opencsgo.db", err)
|
||||
log.Panicf("Failed to open database %s: %v", conf.Db.ConnectTo, err)
|
||||
}
|
||||
defer func(dbSQLite *ent.Client) {
|
||||
_ = dbSQLite.Close()
|
||||
}(db.Client)
|
||||
|
||||
if err := db.Client.Schema.Create(context.Background(), migrate.WithDropIndex(true), migrate.WithDropColumn(true)); err != nil {
|
||||
if err := db.Client.Schema.Create(
|
||||
context.Background(),
|
||||
migrate.WithDropIndex(true),
|
||||
migrate.WithDropColumn(true),
|
||||
); err != nil {
|
||||
log.Panicf("Automigrate failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -516,6 +619,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)
|
||||
|
Reference in New Issue
Block a user