Add event management features to API, including event cancellation, deletion, registration closure, and workflow balance generation. Introduce new roster management endpoints and enhance participant handling with avoided player preferences. Update database schema and service logic to support these changes.
This commit is contained in:
497
backend/internal/application/workflow.go
Normal file
497
backend/internal/application/workflow.go
Normal file
@@ -0,0 +1,497 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"mixmaker/backend/internal/domain"
|
||||
)
|
||||
|
||||
type BalanceWorkflow struct {
|
||||
Event domain.Event `json:"event"`
|
||||
Candidates []domain.BalanceCandidate `json:"candidates"`
|
||||
}
|
||||
|
||||
type ScrimStart struct {
|
||||
Event domain.Event `json:"event"`
|
||||
Series *domain.Series `json:"series,omitempty"`
|
||||
Tournament *domain.Tournament `json:"tournament,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Service) CancelEvent(ctx context.Context, actor domain.Account, eventID string, expectedVersion int) (domain.Event, error) {
|
||||
if !actor.IsAdmin() {
|
||||
return domain.Event{}, domain.ErrForbidden
|
||||
}
|
||||
event, err := s.Store.GetEvent(ctx, eventID)
|
||||
if err != nil {
|
||||
return domain.Event{}, err
|
||||
}
|
||||
if event.State == domain.Completed || event.State == domain.Cancelled {
|
||||
return domain.Event{}, fmt.Errorf("%w: event cannot be cancelled", domain.ErrConflict)
|
||||
}
|
||||
oldVersion := event.Version
|
||||
event.State, event.Version, event.UpdatedAt = domain.Cancelled, event.Version+1, s.Now()
|
||||
if oldVersion != expectedVersion {
|
||||
return domain.Event{}, fmt.Errorf("%w: stale event version", domain.ErrConflict)
|
||||
}
|
||||
event, err = s.Store.SaveEventWorkflow(ctx, event, oldVersion)
|
||||
if err == nil {
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, "event.cancelled", eventID, event)
|
||||
s.Bus.Publish("event:"+eventID, event)
|
||||
s.Bus.Publish("events", event)
|
||||
}
|
||||
return event, err
|
||||
}
|
||||
|
||||
func (s *Service) DeleteEvent(ctx context.Context, actor domain.Account, eventID string) error {
|
||||
if !actor.IsAdmin() {
|
||||
return domain.ErrForbidden
|
||||
}
|
||||
if err := s.Store.DeleteEvent(ctx, eventID); err != nil {
|
||||
return err
|
||||
}
|
||||
tombstone := map[string]any{"eventId": eventID, "deleted": true}
|
||||
s.Bus.Publish("event:"+eventID, tombstone)
|
||||
s.Bus.Publish("events", tombstone)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) CloseRegistration(ctx context.Context, actor domain.Account, eventID, rulesetID string, expectedVersion int) (domain.Event, error) {
|
||||
if !actor.IsAdmin() {
|
||||
return domain.Event{}, domain.ErrForbidden
|
||||
}
|
||||
event, err := s.Store.GetEvent(ctx, eventID)
|
||||
if err != nil {
|
||||
return domain.Event{}, err
|
||||
}
|
||||
if _, err = s.Store.GetRuleset(ctx, rulesetID); err != nil {
|
||||
return domain.Event{}, err
|
||||
}
|
||||
oldVersion := event.Version
|
||||
if err = event.Transition([]domain.EventState{domain.RegistrationOpen}, domain.RegistrationClosed, expectedVersion); err != nil {
|
||||
return domain.Event{}, err
|
||||
}
|
||||
event.RulesetID, event.UpdatedAt = rulesetID, s.Now()
|
||||
out, err := s.Store.SaveEventWorkflow(ctx, event, oldVersion)
|
||||
if err == nil {
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, "event.registration_closed", eventID, out)
|
||||
s.Bus.Publish("event:"+eventID, out)
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (s *Service) GenerateBalance(ctx context.Context, actor domain.Account, eventID string, expectedVersion int) (BalanceWorkflow, error) {
|
||||
if !actor.IsAdmin() {
|
||||
return BalanceWorkflow{}, domain.ErrForbidden
|
||||
}
|
||||
event, err := s.Store.GetEvent(ctx, eventID)
|
||||
if err != nil {
|
||||
return BalanceWorkflow{}, err
|
||||
}
|
||||
oldVersion := event.Version
|
||||
if err = event.Transition([]domain.EventState{domain.RegistrationClosed, domain.Balancing}, domain.Balancing, expectedVersion); err != nil {
|
||||
return BalanceWorkflow{}, err
|
||||
}
|
||||
event.UpdatedAt = s.Now()
|
||||
event, err = s.Store.SaveEventWorkflow(ctx, event, oldVersion)
|
||||
if err != nil {
|
||||
return BalanceWorkflow{}, err
|
||||
}
|
||||
candidates, err := s.Balance(ctx, actor, eventID)
|
||||
if err == nil {
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, "balance.generated", eventID, map[string]int{"count": len(candidates)})
|
||||
s.Bus.Publish("event:"+eventID, event)
|
||||
}
|
||||
return BalanceWorkflow{Event: event, Candidates: candidates}, err
|
||||
}
|
||||
|
||||
func (s *Service) SelectWorkflowBalance(ctx context.Context, actor domain.Account, eventID string, candidate domain.BalanceCandidate, expectedVersion int) (domain.RosterDraft, error) {
|
||||
if !actor.IsAdmin() {
|
||||
return domain.RosterDraft{}, domain.ErrForbidden
|
||||
}
|
||||
event, err := s.Store.GetEvent(ctx, eventID)
|
||||
if err != nil {
|
||||
return domain.RosterDraft{}, err
|
||||
}
|
||||
oldVersion := event.Version
|
||||
if err = event.Transition([]domain.EventState{domain.Balancing}, domain.RostersDraft, expectedVersion); err != nil {
|
||||
return domain.RosterDraft{}, err
|
||||
}
|
||||
roster := domain.RosterDraft{EventID: eventID, Teams: candidate.Teams, Reserve: candidate.Reserve, Version: 0}
|
||||
if err = roster.Validate(false); err != nil {
|
||||
return domain.RosterDraft{}, err
|
||||
}
|
||||
if roster, err = s.Store.SaveRoster(ctx, roster, -1); err != nil {
|
||||
return domain.RosterDraft{}, err
|
||||
}
|
||||
if err = s.Store.SaveTeams(ctx, eventID, roster.Teams); err != nil {
|
||||
return domain.RosterDraft{}, err
|
||||
}
|
||||
event.UpdatedAt = s.Now()
|
||||
if _, err = s.Store.SaveEventWorkflow(ctx, event, oldVersion); err != nil {
|
||||
return domain.RosterDraft{}, err
|
||||
}
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, "roster.created", eventID, roster)
|
||||
s.Bus.Publish("event:"+eventID, roster)
|
||||
return roster, nil
|
||||
}
|
||||
|
||||
func (s *Service) SwapRoster(ctx context.Context, actor domain.Account, eventID, teamA, playerA, teamB, playerB string, expectedVersion int) (domain.RosterDraft, error) {
|
||||
if !actor.IsAdmin() {
|
||||
return domain.RosterDraft{}, domain.ErrForbidden
|
||||
}
|
||||
roster, err := s.Store.GetRoster(ctx, eventID)
|
||||
if err != nil {
|
||||
return roster, err
|
||||
}
|
||||
if roster.Version != expectedVersion {
|
||||
return roster, fmt.Errorf("%w: stale roster version", domain.ErrConflict)
|
||||
}
|
||||
if err = roster.Swap(teamA, playerA, teamB, playerB); err != nil {
|
||||
return roster, err
|
||||
}
|
||||
roster, err = s.Store.SaveRoster(ctx, roster, expectedVersion)
|
||||
if err == nil {
|
||||
_ = s.Store.SaveTeams(ctx, eventID, roster.Teams)
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, "roster.swapped", eventID, map[string]string{"playerAId": playerA, "playerBId": playerB})
|
||||
s.Bus.Publish("event:"+eventID, roster)
|
||||
}
|
||||
return roster, err
|
||||
}
|
||||
|
||||
func (s *Service) SubstituteRoster(ctx context.Context, actor domain.Account, eventID, teamID, outgoingID, reserveID string, expectedVersion int, emergency bool) (domain.RosterDraft, error) {
|
||||
if !actor.IsAdmin() {
|
||||
return domain.RosterDraft{}, domain.ErrForbidden
|
||||
}
|
||||
event, err := s.Store.GetEvent(ctx, eventID)
|
||||
if err != nil {
|
||||
return domain.RosterDraft{}, err
|
||||
}
|
||||
if emergency != (event.State == domain.Live) {
|
||||
return domain.RosterDraft{}, fmt.Errorf("%w: substitution is unavailable in this state", domain.ErrConflict)
|
||||
}
|
||||
roster, err := s.Store.GetRoster(ctx, eventID)
|
||||
if err != nil {
|
||||
return roster, err
|
||||
}
|
||||
if roster.Version != expectedVersion {
|
||||
return roster, fmt.Errorf("%w: stale roster version", domain.ErrConflict)
|
||||
}
|
||||
players, err := s.Store.ListPlayers(ctx)
|
||||
if err != nil {
|
||||
return roster, err
|
||||
}
|
||||
rating := 0
|
||||
role := domain.Role("")
|
||||
for _, team := range roster.Teams {
|
||||
for _, slot := range team.Slots {
|
||||
if team.ID == teamID && slot.PlayerID == outgoingID {
|
||||
role = slot.Role
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, player := range players {
|
||||
if player.ID == reserveID {
|
||||
rating = ratingForRole(player, role)
|
||||
}
|
||||
}
|
||||
if rating == 0 {
|
||||
return roster, domain.ErrNotFound
|
||||
}
|
||||
if emergency {
|
||||
err = roster.EmergencySubstitute(teamID, outgoingID, reserveID, rating)
|
||||
} else {
|
||||
err = roster.Substitute(teamID, outgoingID, reserveID, rating)
|
||||
}
|
||||
if err != nil {
|
||||
return roster, err
|
||||
}
|
||||
roster, err = s.Store.SaveRoster(ctx, roster, expectedVersion)
|
||||
if err == nil {
|
||||
_ = s.Store.SaveTeams(ctx, eventID, roster.Teams)
|
||||
action := "roster.substituted"
|
||||
if emergency {
|
||||
action = "roster.emergency_substituted"
|
||||
}
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, action, eventID, map[string]string{"outgoingPlayerId": outgoingID, "incomingPlayerId": reserveID})
|
||||
s.Bus.Publish("event:"+eventID, roster)
|
||||
}
|
||||
return roster, err
|
||||
}
|
||||
|
||||
func (s *Service) SetRosterCaptain(ctx context.Context, actor domain.Account, eventID, teamID, playerID string, expectedVersion int) (domain.RosterDraft, error) {
|
||||
if !actor.IsAdmin() {
|
||||
return domain.RosterDraft{}, domain.ErrForbidden
|
||||
}
|
||||
roster, err := s.Store.GetRoster(ctx, eventID)
|
||||
if err != nil {
|
||||
return roster, err
|
||||
}
|
||||
if roster.Confirmed || roster.Version != expectedVersion {
|
||||
return roster, domain.ErrConflict
|
||||
}
|
||||
found := false
|
||||
for i := range roster.Teams {
|
||||
if roster.Teams[i].ID == teamID {
|
||||
if err = roster.Teams[i].AssignCaptain(playerID); err != nil {
|
||||
return roster, err
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return roster, domain.ErrNotFound
|
||||
}
|
||||
roster.Version++
|
||||
roster, err = s.Store.SaveRoster(ctx, roster, expectedVersion)
|
||||
if err == nil {
|
||||
_ = s.Store.SaveTeams(ctx, eventID, roster.Teams)
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, "roster.captain_assigned", eventID, map[string]string{"teamId": teamID, "playerId": playerID})
|
||||
s.Bus.Publish("event:"+eventID, roster)
|
||||
}
|
||||
return roster, err
|
||||
}
|
||||
|
||||
func (s *Service) ConfirmRosters(ctx context.Context, actor domain.Account, eventID string, expectedEventVersion, expectedRosterVersion int) (domain.Event, error) {
|
||||
if !actor.IsAdmin() {
|
||||
return domain.Event{}, domain.ErrForbidden
|
||||
}
|
||||
event, err := s.Store.GetEvent(ctx, eventID)
|
||||
if err != nil {
|
||||
return event, err
|
||||
}
|
||||
roster, err := s.Store.GetRoster(ctx, eventID)
|
||||
if err != nil {
|
||||
return event, err
|
||||
}
|
||||
if roster.Version != expectedRosterVersion {
|
||||
return event, domain.ErrConflict
|
||||
}
|
||||
if err = roster.Validate(true); err != nil {
|
||||
return event, err
|
||||
}
|
||||
oldEventVersion := event.Version
|
||||
if err = event.Transition([]domain.EventState{domain.RostersDraft}, domain.RostersConfirmed, expectedEventVersion); err != nil {
|
||||
return event, err
|
||||
}
|
||||
roster.Confirmed, roster.Version = true, roster.Version+1
|
||||
if _, err = s.Store.SaveRoster(ctx, roster, expectedRosterVersion); err != nil {
|
||||
return event, err
|
||||
}
|
||||
event.UpdatedAt = s.Now()
|
||||
event, err = s.Store.SaveEventWorkflow(ctx, event, oldEventVersion)
|
||||
if err == nil {
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, "roster.confirmed", eventID, roster)
|
||||
s.Bus.Publish("event:"+eventID, event)
|
||||
}
|
||||
return event, err
|
||||
}
|
||||
|
||||
func (s *Service) StartScrim(ctx context.Context, actor domain.Account, eventID string, expectedVersion int) (ScrimStart, error) {
|
||||
if !actor.IsAdmin() {
|
||||
return ScrimStart{}, domain.ErrForbidden
|
||||
}
|
||||
event, err := s.Store.GetEvent(ctx, eventID)
|
||||
if err != nil {
|
||||
return ScrimStart{}, err
|
||||
}
|
||||
roster, err := s.Store.GetRoster(ctx, eventID)
|
||||
if err != nil {
|
||||
return ScrimStart{}, err
|
||||
}
|
||||
if !roster.Confirmed {
|
||||
return ScrimStart{}, fmt.Errorf("%w: rosters are not confirmed", domain.ErrConflict)
|
||||
}
|
||||
rules, err := s.Store.GetRuleset(ctx, event.RulesetID)
|
||||
if err != nil {
|
||||
return ScrimStart{}, err
|
||||
}
|
||||
oldVersion := event.Version
|
||||
if err = event.Transition([]domain.EventState{domain.RostersConfirmed}, domain.Live, expectedVersion); err != nil {
|
||||
return ScrimStart{}, err
|
||||
}
|
||||
result := ScrimStart{}
|
||||
seriesToSave := make([]domain.Series, 0)
|
||||
teamIDs := make([]string, len(roster.Teams))
|
||||
for i := range roster.Teams {
|
||||
teamIDs[i] = roster.Teams[i].ID
|
||||
}
|
||||
if len(teamIDs) == 2 {
|
||||
series, createErr := domain.NewSeries(NewID(), eventID, "", [2]string{teamIDs[0], teamIDs[1]}, rules)
|
||||
if createErr != nil {
|
||||
return ScrimStart{}, createErr
|
||||
}
|
||||
seriesToSave = append(seriesToSave, *series)
|
||||
event.ActiveSeriesID = series.ID
|
||||
result.Series = series
|
||||
} else if len(teamIDs) >= 4 {
|
||||
tournament, createErr := domain.NewTournament(NewID(), eventID, event.Name, teamIDs)
|
||||
if createErr != nil {
|
||||
return ScrimStart{}, createErr
|
||||
}
|
||||
for round := range tournament.Rounds {
|
||||
for match := range tournament.Rounds[round] {
|
||||
base := tournament.Rounds[round][match]
|
||||
if base.TeamAID == "" || base.TeamBID == "" {
|
||||
continue
|
||||
}
|
||||
series, seriesErr := domain.NewSeries(base.ID, eventID, tournament.ID, [2]string{base.TeamAID, base.TeamBID}, rules)
|
||||
if seriesErr != nil {
|
||||
return ScrimStart{}, seriesErr
|
||||
}
|
||||
tournament.Rounds[round][match] = *series
|
||||
seriesToSave = append(seriesToSave, *series)
|
||||
}
|
||||
}
|
||||
event.TournamentID = tournament.ID
|
||||
result.Tournament = tournament
|
||||
} else {
|
||||
return ScrimStart{}, fmt.Errorf("%w: scrim needs two or at least four teams", domain.ErrInvalid)
|
||||
}
|
||||
event.UpdatedAt = s.Now()
|
||||
err = s.Store.StartScrim(ctx, event, oldVersion, seriesToSave, result.Tournament)
|
||||
if err == nil {
|
||||
event, err = s.Store.GetEvent(ctx, event.ID)
|
||||
}
|
||||
result.Event = event
|
||||
if err == nil {
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, "scrim.started", eventID, result)
|
||||
s.Bus.Publish("event:"+eventID, result)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *Service) TossSeriesCoin(ctx context.Context, actor domain.Account, seriesID, seed string, expectedVersion int) (domain.Series, error) {
|
||||
return s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.coin_tossed", func(series *domain.Series, rules domain.Ruleset) error {
|
||||
return series.Toss(seed, actor.ID, s.Now(), rules)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) BanSeriesMap(ctx context.Context, actor domain.Account, seriesID, teamID, name string, expectedVersion int) (domain.Series, error) {
|
||||
return s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.map_banned", func(series *domain.Series, rules domain.Ruleset) error {
|
||||
return series.BanMap(teamID, name, actor.ID, s.Now(), rules)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) PickSeriesMap(ctx context.Context, actor domain.Account, seriesID, teamID, name string, expectedVersion int) (domain.Series, error) {
|
||||
return s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.map_picked", func(series *domain.Series, rules domain.Ruleset) error {
|
||||
return series.PickMap(teamID, name, actor.ID, s.Now(), rules)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) BanSeriesHero(ctx context.Context, actor domain.Account, seriesID, teamID, hero string, expectedVersion int) (domain.Series, error) {
|
||||
return s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.hero_banned", func(series *domain.Series, _ domain.Ruleset) error {
|
||||
return series.BanHero(teamID, hero, actor.ID, s.Now())
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) RecordSeriesResult(ctx context.Context, actor domain.Account, seriesID string, outcome domain.MapOutcome, expectedVersion int) (domain.Series, error) {
|
||||
out, err := s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.map_recorded", func(series *domain.Series, rules domain.Ruleset) error {
|
||||
return series.RecordCurrentMap(outcome, actor.ID, s.Now(), rules)
|
||||
})
|
||||
if err != nil || out.WinnerTeamID == "" {
|
||||
return out, err
|
||||
}
|
||||
if out.TournamentID == "" {
|
||||
event, getErr := s.Store.GetEvent(ctx, out.EventID)
|
||||
if getErr == nil {
|
||||
old := event.Version
|
||||
event.State, event.Version, event.UpdatedAt = domain.Completed, event.Version+1, s.Now()
|
||||
_, getErr = s.Store.SaveEventWorkflow(ctx, event, old)
|
||||
}
|
||||
return out, getErr
|
||||
}
|
||||
tournament, err := s.Store.GetTournament(ctx, out.TournamentID)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
for round := range tournament.Rounds {
|
||||
for match := range tournament.Rounds[round] {
|
||||
if tournament.Rounds[round][match].ID != out.ID {
|
||||
continue
|
||||
}
|
||||
tournament.Rounds[round][match] = out
|
||||
if err = tournament.Advance(round, match); err != nil {
|
||||
return out, err
|
||||
}
|
||||
if round+1 < len(tournament.Rounds) {
|
||||
next := tournament.Rounds[round+1][match/2]
|
||||
if next.TeamAID != "" && next.TeamBID != "" {
|
||||
existing, existingErr := s.Store.GetSeries(ctx, next.ID)
|
||||
if existingErr == nil {
|
||||
tournament.Rounds[round+1][match/2] = existing
|
||||
} else if existingErr == domain.ErrNotFound {
|
||||
event, _ := s.Store.GetEvent(ctx, out.EventID)
|
||||
rules, _ := s.Store.GetRuleset(ctx, event.RulesetID)
|
||||
series, createErr := domain.NewSeries(next.ID, out.EventID, tournament.ID, [2]string{next.TeamAID, next.TeamBID}, rules)
|
||||
if createErr != nil {
|
||||
return out, createErr
|
||||
}
|
||||
tournament.Rounds[round+1][match/2] = *series
|
||||
_, err = s.Store.SaveSeries(ctx, *series)
|
||||
} else {
|
||||
err = existingErr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
_, err = s.Store.SaveTournament(ctx, tournament)
|
||||
s.Bus.Publish("tournament:"+tournament.ID, tournament)
|
||||
s.Bus.Publish("event:"+out.EventID, tournament)
|
||||
}
|
||||
if err == nil && tournament.WinnerTeamID != "" {
|
||||
event, getErr := s.Store.GetEvent(ctx, out.EventID)
|
||||
if getErr == nil {
|
||||
old := event.Version
|
||||
event.State, event.Version, event.UpdatedAt = domain.Completed, event.Version+1, s.Now()
|
||||
_, getErr = s.Store.SaveEventWorkflow(ctx, event, old)
|
||||
}
|
||||
err = getErr
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (s *Service) mutateSeries(ctx context.Context, actor domain.Account, seriesID string, expectedVersion int, action string, mutation func(*domain.Series, domain.Ruleset) error) (domain.Series, error) {
|
||||
series, err := s.Store.GetSeries(ctx, seriesID)
|
||||
if err != nil {
|
||||
return series, err
|
||||
}
|
||||
event, err := s.Store.GetEvent(ctx, series.EventID)
|
||||
if err != nil {
|
||||
return series, err
|
||||
}
|
||||
if event.State != domain.Live {
|
||||
return series, fmt.Errorf("%w: event is not live", domain.ErrConflict)
|
||||
}
|
||||
if series.Version != expectedVersion {
|
||||
return series, fmt.Errorf("%w: stale series version", domain.ErrConflict)
|
||||
}
|
||||
rules, err := s.Store.GetRuleset(ctx, series.RulesetID)
|
||||
if err != nil {
|
||||
return series, err
|
||||
}
|
||||
if err = mutation(&series, rules); err != nil {
|
||||
return series, err
|
||||
}
|
||||
series, err = s.Store.SaveSeries(ctx, series)
|
||||
if err == nil {
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, action, seriesID, series)
|
||||
s.Bus.Publish("series:"+seriesID, series)
|
||||
}
|
||||
return series, err
|
||||
}
|
||||
|
||||
func ratingForRole(player domain.Player, role domain.Role) int {
|
||||
switch role {
|
||||
case domain.Tank:
|
||||
return player.Ratings.Tank
|
||||
case domain.Damage:
|
||||
return player.Ratings.Damage
|
||||
case domain.Support:
|
||||
return player.Ratings.Support
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user