-
Notifications
You must be signed in to change notification settings - Fork 0
/
matches.go
155 lines (139 loc) · 3.8 KB
/
matches.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package tetrio
import (
"context"
"encoding/json"
"errors"
"net/url"
"time"
)
// Handling is the in game handling settings.
type Handling struct {
ARR float64 `json:"arr"`
DAS float64 `json:"das"`
DCD float64 `json:"dcd"`
SDF int `json:"sdf"`
SafeLock bool `json:"safelock"`
Cancel bool `json:"cancel"`
}
// VersusStats is the statistics for multiplayer games.
type VersusStats struct {
APM float64 `json:"apm"` // Attacks per minute
PPS float64 `json:"pps"` // Pieces per second
VS float64 `json:"vs"` // Versus score
}
var ErrNonMultiRecord = errors.New("tetrio: not a multiplayer record")
var ErrAmbiguousRecord = errors.New("tetrio: record has no winner or loser")
type LeagueRecord struct {
ReplayID string
TS time.Time
IsForfeit bool
Winner LeaguePlayer
Loser LeaguePlayer
}
type LeaguePlayer struct {
User PartialUser
Wins int
Inputs int
PiecesPlaced int
Handling Handling
Stats VersusStats
RoundStats []VersusStats
}
func (s Session) GetMatches(ctx context.Context, userID string) ([]LeagueRecord, error) {
return send[[]LeagueRecord](ctx, s, "/streams/league_userrecent_"+url.PathEscape(userID))
}
func (g *LeagueRecord) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
return nil
}
var rec gameRecord
err := json.Unmarshal(data, &rec)
if err != nil {
return err
}
if !rec.IsMulti {
return ErrNonMultiRecord
}
var cs [2]leagueEndCtx
err = json.Unmarshal(rec.EndContext, &cs)
if err != nil {
return err
}
if cs[0].Success == cs[1].Success {
return ErrAmbiguousRecord
}
g.ReplayID = rec.ReplayID
g.TS = rec.TS
g.IsForfeit = cs[0].Active != cs[1].Active
for i := 0; i < 2; i++ {
apms := cs[i].Points.SecondaryAvgTracking
ppss := cs[i].Points.TertiaryAvgTracking
vss := cs[i].Points.ExtraAvgTracking.AggregateStatsVSScore
roundCount := max(len(apms), len(ppss), len(vss))
rs := make([]VersusStats, roundCount)
for j, apm := range apms {
rs[j].APM = apm
}
for j, pps := range ppss {
rs[j].PPS = pps
}
for j, vs := range vss {
rs[j].VS = vs
}
p := LeaguePlayer{
User: PartialUser{
ID: cs[i].ID,
Username: cs[i].Username,
},
Handling: cs[i].Handling,
Inputs: cs[i].Inputs,
PiecesPlaced: cs[i].PiecesPlaced,
Wins: cs[i].Wins,
Stats: VersusStats{
APM: cs[i].Points.Secondary,
PPS: cs[i].Points.Tertiary,
VS: cs[i].Points.Extra.VS,
},
RoundStats: rs,
}
if cs[i].Success {
g.Winner = p
} else {
g.Loser = p
}
}
return nil
}
type gameRecord struct {
ID string `json:"_id"`
Stream string `json:"stream"`
ReplayID string `json:"replayid"`
User PartialUser `json:"user"`
TS time.Time `json:"ts"`
IsMulti bool `json:"ismulti"`
EndContext json.RawMessage `json:"endcontext"`
}
type leagueEndCtx struct {
ID string `json:"id"`
Username string `json:"username"`
Handling Handling `json:"handling"`
Active bool `json:"active"`
Success bool `json:"success"`
Inputs int `json:"inputs"`
PiecesPlaced int `json:"piecesplaced"`
Wins int `json:"wins"`
Points leaguePoints `json:"points"`
}
type leaguePoints struct {
Primary int `json:"primary"` // Wins
Secondary float64 `json:"secondary"` // APM
Tertiary float64 `json:"tertiary"` // PPS
Extra struct {
VS float64 `json:"vs"`
} `json:"extra"`
SecondaryAvgTracking []float64 `json:"secondaryAvgTracking"` // APM of each round
TertiaryAvgTracking []float64 `json:"tertiaryAvgTracking"` // PPS of each round
ExtraAvgTracking struct {
AggregateStatsVSScore []float64 `json:"aggregatestats___vsscore"`
} `json:"extraAvgTracking"`
}