170 lines
5.2 KiB
Go
170 lines
5.2 KiB
Go
package domain
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type PublicPlayer struct {
|
|
ID string `json:"id"`
|
|
DisplayName string `json:"displayName"`
|
|
BattleTag string `json:"battleTag"`
|
|
Ratings Ratings `json:"ratings"`
|
|
IsGuest bool `json:"isGuest"`
|
|
}
|
|
|
|
type PlayerStatsSummary struct {
|
|
SeriesPlayed int `json:"seriesPlayed"`
|
|
SeriesWon int `json:"seriesWon"`
|
|
SeriesLost int `json:"seriesLost"`
|
|
WinRate float64 `json:"winRate"`
|
|
MapsPlayed int `json:"mapsPlayed"`
|
|
MapsWon int `json:"mapsWon"`
|
|
MapsLost int `json:"mapsLost"`
|
|
MapsDrawn int `json:"mapsDrawn"`
|
|
}
|
|
|
|
type PlayerMatchHistory struct {
|
|
SeriesID string `json:"seriesId"`
|
|
EventID string `json:"eventId"`
|
|
EventName string `json:"eventName"`
|
|
EventDate time.Time `json:"eventDate"`
|
|
OwnTeamID string `json:"ownTeamId"`
|
|
OwnTeamName string `json:"ownTeamName"`
|
|
OpponentTeamID string `json:"opponentTeamId"`
|
|
OpponentTeamName string `json:"opponentTeamName"`
|
|
OwnScore int `json:"ownScore"`
|
|
OpponentScore int `json:"opponentScore"`
|
|
Outcome string `json:"outcome"`
|
|
}
|
|
|
|
type PlayerProfile struct {
|
|
Player PublicPlayer `json:"player"`
|
|
Summary PlayerStatsSummary `json:"summary"`
|
|
Recent []PlayerMatchHistory `json:"recentMatches"`
|
|
}
|
|
|
|
type PlayerSeriesContext struct {
|
|
Event Event
|
|
Series Series
|
|
TeamA Team
|
|
TeamB Team
|
|
}
|
|
|
|
func BuildPlayerProfile(player Player, matches []PlayerSeriesContext, recentLimit int) PlayerProfile {
|
|
profile := PlayerProfile{
|
|
Player: PublicPlayer{
|
|
ID: player.ID, DisplayName: player.DisplayName, BattleTag: player.BattleTag,
|
|
Ratings: player.Ratings, IsGuest: player.AccountID == "",
|
|
},
|
|
Recent: []PlayerMatchHistory{},
|
|
}
|
|
for _, match := range matches {
|
|
if match.Event.State != Completed || match.Series.Phase != SeriesComplete || match.Series.WinnerTeamID == "" {
|
|
continue
|
|
}
|
|
own, opponent, ownIsA := teamContaining(match.TeamA, player.ID), match.TeamB, true
|
|
if own.ID == "" {
|
|
own, opponent, ownIsA = teamContaining(match.TeamB, player.ID), match.TeamA, false
|
|
}
|
|
if own.ID == "" || opponent.ID == "" {
|
|
continue
|
|
}
|
|
ownScore, opponentScore, mapsWon, mapsLost, mapsDrawn := scoreEffectiveResults(match.Series, ownIsA)
|
|
profile.Summary.SeriesPlayed++
|
|
profile.Summary.MapsPlayed += mapsWon + mapsLost + mapsDrawn
|
|
profile.Summary.MapsWon += mapsWon
|
|
profile.Summary.MapsLost += mapsLost
|
|
profile.Summary.MapsDrawn += mapsDrawn
|
|
outcome := "loss"
|
|
if match.Series.WinnerTeamID == own.ID {
|
|
outcome = "win"
|
|
profile.Summary.SeriesWon++
|
|
} else {
|
|
profile.Summary.SeriesLost++
|
|
}
|
|
profile.Recent = append(profile.Recent, PlayerMatchHistory{
|
|
SeriesID: match.Series.ID, EventID: match.Event.ID, EventName: match.Event.Name, EventDate: match.Event.StartsAt,
|
|
OwnTeamID: own.ID, OwnTeamName: own.Name, OpponentTeamID: opponent.ID, OpponentTeamName: opponent.Name,
|
|
OwnScore: ownScore, OpponentScore: opponentScore, Outcome: outcome,
|
|
})
|
|
}
|
|
if profile.Summary.SeriesPlayed > 0 {
|
|
profile.Summary.WinRate = float64(profile.Summary.SeriesWon) / float64(profile.Summary.SeriesPlayed)
|
|
}
|
|
sort.SliceStable(profile.Recent, func(i, j int) bool {
|
|
if profile.Recent[i].EventDate.Equal(profile.Recent[j].EventDate) {
|
|
return profile.Recent[i].SeriesID > profile.Recent[j].SeriesID
|
|
}
|
|
return profile.Recent[i].EventDate.After(profile.Recent[j].EventDate)
|
|
})
|
|
if recentLimit >= 0 && len(profile.Recent) > recentLimit {
|
|
profile.Recent = profile.Recent[:recentLimit]
|
|
}
|
|
return profile
|
|
}
|
|
|
|
func teamContaining(team Team, playerID string) Team {
|
|
for _, slot := range team.Slots {
|
|
if slot.PlayerID == playerID {
|
|
return team
|
|
}
|
|
}
|
|
return Team{}
|
|
}
|
|
|
|
func scoreEffectiveResults(series Series, ownIsA bool) (ownScore, opponentScore, won, lost, drawn int) {
|
|
superseded := make(map[int]bool)
|
|
for _, result := range series.Results {
|
|
if result.CorrectionOf > 0 {
|
|
superseded[result.CorrectionOf] = true
|
|
}
|
|
}
|
|
for index, result := range series.Results {
|
|
if superseded[index+1] {
|
|
continue
|
|
}
|
|
switch result.Outcome {
|
|
case Draw:
|
|
drawn++
|
|
case TeamAWin:
|
|
if ownIsA {
|
|
ownScore, won = ownScore+1, won+1
|
|
} else {
|
|
opponentScore, lost = opponentScore+1, lost+1
|
|
}
|
|
case TeamBWin:
|
|
if ownIsA {
|
|
opponentScore, lost = opponentScore+1, lost+1
|
|
} else {
|
|
ownScore, won = ownScore+1, won+1
|
|
}
|
|
}
|
|
}
|
|
return ownScore, opponentScore, won, lost, drawn
|
|
}
|
|
|
|
type CommunitySettings struct {
|
|
DiscordInviteURL string `json:"discordInviteUrl"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
UpdatedBy string `json:"updatedBy"`
|
|
}
|
|
|
|
func (settings *CommunitySettings) Validate() error {
|
|
settings.DiscordInviteURL = strings.TrimSpace(settings.DiscordInviteURL)
|
|
if settings.DiscordInviteURL == "" {
|
|
return nil
|
|
}
|
|
if !strings.HasPrefix(settings.DiscordInviteURL, "https://discord.gg/") &&
|
|
!strings.HasPrefix(settings.DiscordInviteURL, "https://discord.com/invite/") {
|
|
return ErrInvalid
|
|
}
|
|
code := strings.TrimPrefix(settings.DiscordInviteURL, "https://discord.gg/")
|
|
code = strings.TrimPrefix(code, "https://discord.com/invite/")
|
|
if code == "" || strings.ContainsAny(code, "/?# \t\r\n") {
|
|
return ErrInvalid
|
|
}
|
|
return nil
|
|
}
|