Пример #1
0
        public Task SetupTournamentAsync(string tournamentName)
        {
            // It's okay not to harden this too much, because they can retry the action, and their failure doesn't
            // make anything inconsistent.
            if (string.IsNullOrEmpty(tournamentName))
            {
                this.Logger.Debug("Couldn't setup with blank name");
                return(Task.CompletedTask);
            }

            TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager);

            // If the tournament didn't exist before, create it now. This must be an admin user who wanted to create
            // the tournament without any other directors.
            if (!manager.TryGetTournament(tournamentName, out ITournamentState state))
            {
                manager.AddOrUpdateTournament(tournamentName, new TournamentState(
                                                  this.Context.Guild.Id, tournamentName), (name, oldState) => oldState);
            }

            if (!manager.TrySetCurrentTournament(tournamentName, out string errorMessage))
            {
                this.Logger.Debug("Error when setting up tournament: {errorMessage}", errorMessage);
                return(this.SendChannelMessage(
                           BotStrings.ErrorSettingCurrentTournament(this.Context.Guild.Name, errorMessage)));
            }

            return(this.DoReadWriteActionOnCurrentTournamentAsync(
                       currentTournament => this.UpdateStageAsync(currentTournament, TournamentStage.AddReaders)));
        }
Пример #2
0
        private async Task HandleAddReadersStage(ITournamentState currentTournament, IGuild guild, SocketMessage message)
        {
            IEnumerable <Task <IGuildUser> > getReaderMembers = message.MentionedUsers
                                                                .Select(user => guild.GetUserAsync(user.Id, options: RequestOptionsSettings.Default));

            IGuildUser[] readerMembers = await Task.WhenAll(getReaderMembers);

            IEnumerable <Reader> readers = readerMembers.Select(member => new Reader()
            {
                Id   = member.Id,
                Name = member.Nickname ?? member.Username
            });

            currentTournament.AddReaders(readers);
            if (!currentTournament.Readers.Any())
            {
                this.Logger.Debug("No readers specified, so staying in the AddReaders stage");
                await message.Channel.SendMessageAsync(
                    BotStrings.NoReadersAddedMinimumReaderCount, options : RequestOptionsSettings.Default);

                return;
            }

            await message.Channel.SendMessageAsync(
                BotStrings.ReadersTotalForTournament(currentTournament.Readers.Count()));

            await this.UpdateStage(currentTournament, TournamentStage.SetRoundRobins, message.Channel);
        }
Пример #3
0
        public async Task ClearAllAsync()
        {
            await this.CleanupAllPossibleTournamentArtifactsAsync();

            this.Logger.Debug("All tournament artifacts cleared");
            await this.SendChannelMessage(BotStrings.AllPossibleTournamentArtifactsCleaned(this.Context.Guild.Name));
        }
Пример #4
0
        public async Task StartAsync()
        {
            bool startSucceeded = false;

            await this.DoReadWriteActionOnCurrentTournamentAsync(
                async currentTournament =>
            {
                if (currentTournament?.Stage != TournamentStage.AddPlayers)
                {
                    // !start only applies once we've started adding players
                    this.Logger.Debug("Start failed because we were in stage {stage}", currentTournament?.Stage);
                    await this.SendChannelMessage(BotStrings.CommandOnlyUsedTournamentReadyStart);
                    return;
                }

                await this.UpdateStageAsync(currentTournament, TournamentStage.BotSetup);

                try
                {
                    // TODO: Add more messaging around the current status
                    this.Logger.Debug("Generating tournament");
                    IScheduleFactory scheduleFactory = new RoundRobinScheduleFactory(
                        currentTournament.RoundRobinsCount);

                    currentTournament.Schedule = scheduleFactory.Generate(
                        new HashSet <Team>(currentTournament.Teams),
                        new HashSet <Reader>(currentTournament.Readers));

                    this.Logger.Debug("Tournament generated. Creating channels and roles");
                    await this.Context.Channel.SendMessageAsync(
                        BotStrings.CreatingChannelsAndRoles, options: RequestOptionsSettings.Default);
                    await this.CreateArtifactsAsync(currentTournament);

                    IUserMessage updateMessage = await this.UpdateStageAsync(
                        currentTournament, TournamentStage.RunningTournament);
                    await updateMessage.PinAsync(RequestOptionsSettings.Default);
                    currentTournament.PinnedStartMessageId = updateMessage.Id;

                    startSucceeded = true;
                }
                catch (Exception ex)
                {
                    // TODO: Make the exceptions we catch more defined.
                    // Go back to the previous stage and undo any artifacts added.
                    this.Logger.Error(ex, "Error starting the tournament. Cleaning up the tournament artifacts");
                    await this.CleanupTournamentArtifactsAsync(currentTournament);
                    await this.UpdateStageAsync(currentTournament, TournamentStage.AddPlayers);
                    throw;
                }
            });

            if (startSucceeded)
            {
                await this.Context.Channel.SendMessageAsync(
                    BotStrings.TournamentHasStarted(
                        MentionUtils.MentionChannel(this.Context.Channel.Id)),
                    options : RequestOptionsSettings.Default);
            }
        }
Пример #5
0
 public Task GetCurrentTournamentAsync()
 {
     // DoReadActionOnCurrentTournament will not run the action if the tournament is null. It'll send an
     // error message to the user instead.
     return(this.DoReadActionOnCurrentTournamentAsync(
                currentTournament => this.SendUserMessageAsync(
                    BotStrings.CurrentTournamentInGuild(this.Context.Guild.Name, currentTournament.Name))));
 }
Пример #6
0
        internal async Task HandleOnReactionRemoved(
            Cacheable <IUserMessage, ulong> cachedMessage, ISocketMessageChannel messageChannel, SocketReaction reaction)
        {
            if (!cachedMessage.HasValue || reaction.UserId == this.Client.CurrentUser.Id)
            {
                // Ignore the bot's own additions.
                return;
            }

            if (!(reaction.Channel is IGuildChannel guildChannel))
            {
                // Only listen to guild reacts.
                this.Logger.Verbose("Reaction removal ignored because it wasn't on a guild channel");
                return;
            }

            if (!(reaction.User.IsSpecified && reaction.User.Value is IGuildUser guildUser))
            {
                this.Logger.Verbose("Reaction removal ignored because it wasn't by a guild user");
                return;
            }

            // Issue: when we remove a reaction, this will remove the team they were on. How would we know this, outside
            // of keeping state on this?
            // When the user chooses two emojis, and the bot removes the second one, the event handler is called. We
            // need to make sure that the user initiated this removal, which we can do by making sure that the player
            // we're removing from the set has the same team as the emoji maps to.
            // TODO: We may want to make this a dictionary of IDs to Players to make this operation efficient. We could
            // use ContainsKey to do the contains checks efficiently.
            await this.GetTournamentsManager(guildChannel.Guild).DoReadWriteActionOnCurrentTournamentForMemberAsync(
                guildUser,
                async currentTournament =>
            {
                Player player = this.GetPlayerFromReactionEventOrNull(
                    currentTournament, guildUser, cachedMessage.Id, reaction.Emote.Name, out string errorMessage);
                if (player == null)
                {
                    // User should have already received an error message from the add if it was relevant.
                    this.Logger.Verbose(
                        "Reaction addition ignored because it wasn't by a player. Message: {errorMessage}",
                        errorMessage);
                    return;
                }

                if (currentTournament.TryGetPlayerTeam(guildUser.Id, out Team storedTeam) &&
                    storedTeam == player.Team &&
                    currentTournament.TryRemovePlayer(guildUser.Id))
                {
                    this.Logger.Debug("Player {id} left team {name}", player.Id, player.Team.Name);
                    await guildUser.SendMessageAsync(
                        BotStrings.YouHaveLeftTeam(player.Team.Name), options: RequestOptionsSettings.Default);
                }
            });
        }
Пример #7
0
        public Task GoBackAsync()
        {
            return(this.DoReadWriteActionOnCurrentTournamentAsync(
                       async currentTournament =>
            {
                bool sendMessage = true;
                switch (currentTournament.Stage)
                {
                case TournamentStage.SetRoundRobins:
                    currentTournament.TryClearReaders();
                    break;

                case TournamentStage.AddTeams:
                    currentTournament.RoundRobinsCount = 0;
                    break;

                case TournamentStage.AddPlayers:
                    currentTournament.TryClearTeams();
                    currentTournament.ClearSymbolsToTeam();

                    List <Task <IMessage> > getJoinTeamMessagesTasks = new List <Task <IMessage> >();
                    foreach (ulong id in currentTournament.JoinTeamMessageIds)
                    {
                        getJoinTeamMessagesTasks.Add(this.Context.Channel.GetMessageAsync(id));
                    }

                    IMessage[] joinTeamMessages = await Task.WhenAll(getJoinTeamMessagesTasks);
                    await Task.WhenAll(joinTeamMessages
                                       .Select(message => this.Context.Channel.DeleteMessageAsync(message)));

                    currentTournament.ClearJoinTeamMessageIds();
                    break;

                case TournamentStage.Rebracketing:
                    // Don't send the message about the tournament starting again
                    sendMessage = false;
                    break;

                default:
                    // Nothing to go back to, so do nothing.
                    this.Logger.Debug("Could not go back on stage {stage}", currentTournament.Stage);
                    await this.SendChannelMessage(BotStrings.CannotGoBack(currentTournament.Stage));
                    return;
                }

                TournamentStage previousStage = currentTournament.Stage == TournamentStage.Finals ?
                                                TournamentStage.RunningTournament :
                                                currentTournament.Stage - 1;
                await this.UpdateStageAsync(currentTournament, previousStage, sendMessage);
            }));
        }
Пример #8
0
        public static async Task DoReadActionOnCurrentTournamentForMemberAsync(
            this TournamentsManager manager, IUser user, Func <IReadOnlyTournamentState, Task> action)
        {
            Result <Task> result = manager.TryReadActionOnCurrentTournament(action);

            if (result.Success)
            {
                await result.Value;
                return;
            }

            IDMChannel channel = await user.GetOrCreateDMChannelAsync();

            await channel.SendMessageAsync(BotStrings.UnableToPerformCommand(result.ErrorMessage));
        }
Пример #9
0
        public async Task RemoveTournamentDirectorAsync(IGuildUser oldDirector, string tournamentName)
        {
            Verify.IsNotNull(oldDirector, nameof(oldDirector));

            if (string.IsNullOrWhiteSpace(tournamentName))
            {
                this.Logger.Debug("Couldn't remove director {id} for tournament with blank name", oldDirector.Id);
                return;
            }

            tournamentName = tournamentName.Trim();
            TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager);

            // TODO: Harden this. Since it's not guaranteed to be the current tournament, we can't use the helper
            // methods
            if (!manager.TryGetTournament(tournamentName, out ITournamentState state))
            {
                this.Logger.Debug(
                    "Couldn't remove director {id} for nonexistent tournament {tournamentName}",
                    oldDirector.Id,
                    tournamentName);
                await this.Context.Channel.SendMessageAsync(
                    BotStrings.TournamentDoesNotExist(tournamentName, this.Context.Guild.Name),
                    options : RequestOptionsSettings.Default);

                return;
            }

            if (state.TryRemoveDirector(oldDirector.Id))
            {
                this.Logger.Debug(
                    "Removed {id} as a tournament director for {tournamentName}", oldDirector.Id, tournamentName);
                await this.Context.Channel.SendMessageAsync(
                    BotStrings.RemovedTournamentDirector(tournamentName, this.Context.Guild.Name),
                    options : RequestOptionsSettings.Default);

                return;
            }

            this.Logger.Debug(
                "User {id} is not a director for {tournamentName}, so could not be removed",
                oldDirector.Id,
                tournamentName);
            await this.Context.Channel.SendMessageAsync(
                BotStrings.UserNotTournamentDirector(tournamentName, this.Context.Guild.Name), options : RequestOptionsSettings.Default);
        }
Пример #10
0
        public async Task RemovePlayerAsync(IGuildUser user)
        {
            Verify.IsNotNull(user, nameof(user));

            bool removedSuccessfully        = false;
            IEnumerable <ulong> teamRoleIds = null;

            await this.DoReadWriteActionOnCurrentTournamentAsync(
                currentTournament =>
            {
                if (!currentTournament.TryRemovePlayer(user.Id))
                {
                    this.Logger.Debug("Player {id} wasn't on any team", user.Id);
                    return(this.SendChannelMessage(BotStrings.PlayerIsNotOnAnyTeam(user.Mention)));
                }

                if (currentTournament.IsTorunamentInPlayStage())
                {
                    teamRoleIds = currentTournament.TournamentRoles.TeamRoleIds.Select(kvp => kvp.Value);
                }

                removedSuccessfully = true;
                return(Task.CompletedTask);
            });

            if (!removedSuccessfully)
            {
                return;
            }

            // Get the team role from the player and remove it.
            if (teamRoleIds != null)
            {
                IEnumerable <IRole> teamRoles = user.RoleIds.Join(
                    teamRoleIds,
                    id => id,
                    id => id,
                    (userRoleId, teamId) => this.Context.Guild.GetRole(userRoleId));
                await user.RemoveRolesAsync(teamRoles, RequestOptionsSettings.Default);
            }

            this.Logger.Debug(
                "Player {0} was removed from the tournament. Role removed: {2}", user.Id, teamRoleIds != null);
            await this.SendChannelMessage(BotStrings.PlayerRemoved(user.Mention));
        }
Пример #11
0
        public async Task GetScheduleAsync(string teamName = null)
        {
            // We may also want to expand schedule, so you show it just for a team, or for a particular round
            await this.DoReadActionOnCurrentTournamentAsync(
                currentTournament =>
            {
                Team team = teamName == null ?
                            null :
                            new Team()
                {
                    Name = teamName
                };

                // TODO: Need to do more investigation on if this should be sent to the channel or to the user.
                return(this.Context.Channel.SendAllEmbeds(
                           currentTournament.Schedule.Rounds,
                           () => new EmbedBuilder()
                {
                    Title = BotStrings.Schedule
                },
                           (round, roundIndex) =>
                {
                    EmbedFieldBuilder fieldBuilder = new EmbedFieldBuilder
                    {
                        Name = BotStrings.RoundNumber(roundIndex + 1)
                    };
                    IEnumerable <Game> games = round.Games
                                               .Where(game => game.Teams != null && game.Reader != null);
                    if (team != null)
                    {
                        games = games.Where(game => game.Teams.Contains(team));
                    }

                    IEnumerable <string> lines = games
                                                 .Select(game => BotStrings.ScheduleLine(
                                                             game.Reader.Name, game.Teams.Select(team => team.Name).ToArray()));

                    fieldBuilder.Value = string.Join("\n", lines);
                    return fieldBuilder;
                }));
            }
                );
        }
Пример #12
0
        private async Task HandleSetRoundRobinsStage(ITournamentState currentTournament, SocketMessage message)
        {
            if (!int.TryParse(message.Content, out int rounds))
            {
                this.Logger.Debug("Round robin count specified couldn't be parsed as an int");
                return;
            }
            else if (rounds <= 0 || rounds > TournamentState.MaxRoundRobins)
            {
                this.Logger.Debug("Round robin count ({0}) specified is invalid", rounds);
                await message.Channel.SendMessageAsync(
                    BotStrings.InvalidNumberOfRoundRobins(TournamentState.MaxRoundRobins),
                    options : RequestOptionsSettings.Default);

                return;
            }

            currentTournament.RoundRobinsCount = rounds;
            await this.UpdateStage(currentTournament, TournamentStage.AddTeams, message.Channel);
        }
Пример #13
0
        private static bool TryGetEmojis(int count, out IEmote[] emotes, out string errorMessage)
        {
            emotes       = null;
            errorMessage = null;

            const int emojiLimit = 9 + 26;

            if (count < 0)
            {
                errorMessage = BotStrings.NumberOfTeamsMustBeGreaterThanZero;
                return(false);
            }
            else if (count > emojiLimit)
            {
                errorMessage = BotStrings.TooManyTeams(emojiLimit);
                return(false);
            }

            emotes = new IEmote[count];
            int numberLoopLimit = Math.Min(9, count);

            // Encoding.Unicode.GetString(new byte[] { 49, 0, 227, 32 }) to Encoding.Unicode.GetString(new byte[] { 58, 0, 227, 32 }) is 1-9
            byte[] numberBlock = { 49, 0, 227, 32 };
            int    i           = 0;

            for (; i < numberLoopLimit; i++)
            {
                emotes[i]       = new Emoji(Encoding.Unicode.GetString(numberBlock));
                numberBlock[0] += 1;
            }

            // Encoding.Unicode.GetString(new byte[] { 60, 216, 230, 221 }) to Encoding.Unicode.GetString(new byte[] { 60, 216, 255, 221 }) is A-Z
            byte[] letterBlock = new byte[] { 60, 216, 230, 221 };
            for (; i < count; i++)
            {
                emotes[i]       = new Emoji(Encoding.Unicode.GetString(letterBlock));
                letterBlock[2] += 1;
            }

            return(true);
        }
Пример #14
0
        public Task AddTournamentDirectorAsync(IGuildUser newDirector, string tournamentName)
        {
            Verify.IsNotNull(newDirector, nameof(newDirector));

            if (string.IsNullOrWhiteSpace(tournamentName))
            {
                this.Logger.Debug("Did not add {id} to tournament with blank name", newDirector.Id);
                return(Task.CompletedTask);
            }

            TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager);

            ITournamentState state            = new TournamentState(this.Context.Guild.Id, tournamentName.Trim());
            bool             updateSuccessful = state.TryAddDirector(newDirector.Id);

            manager.AddOrUpdateTournament(
                tournamentName,
                state,
                (name, tournamentState) =>
            {
                updateSuccessful = tournamentState.TryAddDirector(newDirector.Id);
                return(tournamentState);
            });

            // TODO: Need to handle this differently depending on the stage. Completed shouldn't do anything, and
            // after RoleSetup we should give them the TD role.
            if (updateSuccessful)
            {
                this.Logger.Debug(
                    "Added {id} as a tournament director for {tournamentName}", newDirector.Id, tournamentName);
                return(this.SendChannelMessage(
                           BotStrings.AddTournamentDirectorSuccessful(tournamentName, this.Context.Guild.Name)));
            }

            this.Logger.Debug(
                "{id} already a tournament director for {tournamentName}", newDirector.Id, tournamentName);
            return(this.SendChannelMessage(
                       BotStrings.UserAlreadyTournamentDirector(tournamentName, this.Context.Guild.Name)));
        }
Пример #15
0
        // TODO: Make string message and IGuildChannel?
        private async Task HandleAddTeamsStage(ITournamentState currentTournament, SocketMessage message)
        {
            if (!TeamsParser.TryParseTeams(message.Content, out IEnumerable <Team> teams, out string errorMessage))
            {
                await message.Channel.SendMessageAsync(errorMessage, options : RequestOptionsSettings.Default);

                this.Logger.Debug("Team names could not be parsed. Error message: {errorMessage}", errorMessage);
                return;
            }

            currentTournament.AddTeams(teams);

            int teamsCount = currentTournament.Teams.Count();

            if (teamsCount < 2)
            {
                await message.Channel.SendMessageAsync(
                    BotStrings.MustBeTwoTeamsPerTournament, options : RequestOptionsSettings.Default);

                currentTournament.RemoveTeams(teams);
                this.Logger.Debug("Too few teams specified in AddTeams stage ({0})", teamsCount);
                return;
            }

            int maxTeamsCount = GetMaximumTeamCount(currentTournament);

            if (teamsCount > maxTeamsCount)
            {
                currentTournament.TryClearTeams();
                await message.Channel.SendMessageAsync(
                    BotStrings.TooManyTeams(maxTeamsCount), options : RequestOptionsSettings.Default);

                this.Logger.Debug("Too many teams specified in AddTeams stage ({0})", teamsCount);
                return;
            }

            if (!TryGetEmojis(teamsCount, out IEmote[] emotes, out errorMessage))
Пример #16
0
        public async Task SwitchReaderAsync(IGuildUser oldReaderUser, IGuildUser newReaderUser)
        {
            Verify.IsNotNull(oldReaderUser, nameof(oldReaderUser));
            Verify.IsNotNull(newReaderUser, nameof(newReaderUser));

            bool  switchSuccessful = false;
            ulong oldReaderRoleId  = 0;

            await this.DoReadWriteActionOnCurrentTournamentAsync(
                async currentTournament =>
            {
                // Only allow this after the tournament has started running
                if (!currentTournament.IsTorunamentInPlayStage())
                {
                    await this.SendChannelMessage(BotStrings.CommandOnlyUsedWhileTournamentRunning);
                    return;
                }

                oldReaderRoleId = currentTournament.TournamentRoles.ReaderRoomRoleIds.Select(kvp => kvp.Value)
                                  .Join(oldReaderUser.RoleIds, id => id, id => id, (roomRoleId, readerRoleId) => roomRoleId)
                                  .FirstOrDefault();
                if (oldReaderRoleId == default(ulong))
                {
                    await this.SendChannelMessage(BotStrings.CouldntGetRoleForTheOldReader);
                    return;
                }

                if (!currentTournament.IsReader(oldReaderUser.Id))
                {
                    await this.SendChannelMessage(BotStrings.NotACurrentReader(oldReaderUser.Mention));
                    return;
                }
                else if (currentTournament.IsReader(newReaderUser.Id))
                {
                    await this.SendChannelMessage(BotStrings.IsAlreadyReader(newReaderUser.Mention));
                    return;
                }

                if (!currentTournament.TryRemoveReader(oldReaderUser.Id))
                {
                    await this.SendChannelMessage(BotStrings.UnknownErrorRemovingOldReader);
                    return;
                }

                Reader newReader = new Reader()
                {
                    Id   = newReaderUser.Id,
                    Name = newReaderUser.Nickname ?? newReaderUser.Username
                };
                currentTournament.AddReaders(new Reader[] { newReader });

                switchSuccessful = true;
            });

            if (!switchSuccessful)
            {
                return;
            }

            IRole oldReaderRole = this.Context.Guild.GetRole(oldReaderRoleId);

            List <Task> roleChangeTasks = new List <Task>
            {
                newReaderUser.AddRoleAsync(oldReaderRole, RequestOptionsSettings.Default),
                oldReaderUser.RemoveRoleAsync(oldReaderRole, RequestOptionsSettings.Default)
            };
            await Task.WhenAll(roleChangeTasks);

            await this.SendChannelMessage(BotStrings.ReadersSwitchedSuccessfully);
        }
Пример #17
0
        public async Task AddPlayerAsync(IGuildUser user, string teamName)
        {
            Verify.IsNotNull(user, nameof(user));

            bool  addPlayerSuccessful = false;
            ulong teamRoleId          = default(ulong);

            await this.DoReadWriteActionOnCurrentTournamentAsync(
                currentTournament =>
            {
                // Rewrite exit-early, then choose whether we add the role or not
                if (!currentTournament.TryGetTeamFromName(teamName, out Team team))
                {
                    this.Logger.Debug("Player {id} could not be added to nonexistent {teamName}", user.Id, teamName);
                    return(this.SendChannelMessage(BotStrings.TeamDoesNotExist(teamName)));
                }

                Player player = new Player()
                {
                    Id   = user.Id,
                    Team = team
                };
                if (!currentTournament.TryAddPlayer(player))
                {
                    this.Logger.Debug("Player {id} already on team; not added to {teamName}", user.Id, teamName);
                    return(this.SendChannelMessage(BotStrings.PlayerIsAlreadyOnTeam(user.Mention)));
                }

                if (!currentTournament.IsTorunamentInPlayStage())
                {
                    addPlayerSuccessful = true;
                    return(Task.CompletedTask);
                }

                KeyValuePair <Team, ulong> teamRoleIdPair = currentTournament.TournamentRoles.TeamRoleIds
                                                            .FirstOrDefault(kvp => kvp.Key == team);
                if (teamRoleIdPair.Key != team)
                {
                    // We can't add the role to the player. So undo adding the player to the tournament
                    this.Logger.Debug(
                        "Player {id} could not be added because role for {teamName} does not exist",
                        user.Id,
                        teamName);
                    currentTournament.TryRemovePlayer(player.Id);
                    return(this.SendChannelMessage(
                               BotStrings.PlayerCannotBeAddedToTeamWithNoRole(user.Mention, teamName)));
                }

                teamRoleId          = teamRoleIdPair.Value;
                addPlayerSuccessful = true;
                return(Task.CompletedTask);
            });

            if (!addPlayerSuccessful)
            {
                return;
            }

            IRole teamRole = null;

            if (teamRoleId != default(ulong))
            {
                teamRole = this.Context.Guild.GetRole(teamRoleId);
                await user.AddRoleAsync(teamRole, RequestOptionsSettings.Default);
            }

            this.Logger.Debug(
                "Player {0} successfully added to team {1}. Role added: {2}",
                user.Id,
                teamName,
                teamRole != null);
            await this.SendChannelMessage(BotStrings.AddPlayerSuccessful(user.Mention, teamName));
        }
Пример #18
0
        public async Task SetupFinalsAsync(IGuildUser readerUser, string rawTeamNameParts)
        {
            ITextChannel channel = null;

            await this.DoReadWriteActionOnCurrentTournamentAsync(
                async currentTournament =>
            {
                if (currentTournament?.Stage != TournamentStage.RunningTournament)
                {
                    this.Logger.Debug("Could not start finals in stage {stage}", currentTournament?.Stage);
                    await this.SendChannelMessage(BotStrings.ErrorFinalsOnlySetDuringPrelimsOrPlayoffs);
                    return;
                }

                if (!currentTournament.TryGetReader(readerUser.Id, out Reader reader))
                {
                    this.Logger.Debug("Could not start finals because {1} is not a reader", readerUser.Id);
                    await this.SendChannelMessage(BotStrings.ErrorGivenUserIsntAReader);
                    return;
                }

                if (rawTeamNameParts == null)
                {
                    this.Logger.Debug(
                        "Could not start finals because no teams were specified");
                    await this.SendChannelMessage(BotStrings.ErrorNoTeamsSpecified);
                    return;
                }

                string combinedTeamNames = string.Join(" ", rawTeamNameParts).Trim();
                if (!TeamNameParser.TryGetTeamNamesFromParts(
                        combinedTeamNames, out IList <string> teamNames, out string errorMessage))
                {
                    this.Logger.Debug(
                        "Could not start finals because of this error: {errorMessage}", errorMessage);
                    await this.SendChannelMessage(BotStrings.ErrorGenericMessage(errorMessage));
                    return;
                }

                if (teamNames.Count != 2)
                {
                    this.Logger.Debug(
                        "Could not start finals because {count} teams were specified", teamNames.Count);
                    await this.SendChannelMessage(BotStrings.ErrorTwoTeamsMustBeSpecifiedFinals(teamNames.Count));
                    return;
                }

                Team[] teams = teamNames.Select(name => new Team()
                {
                    Name = name
                })
                               .ToArray();
                if (currentTournament.Teams.Intersect(teams).Count() != teams.Length)
                {
                    this.Logger.Debug(
                        "Could not start finals because some teams were not in the tournament", teamNames.Count);
                    await this.SendChannelMessage(
                        BotStrings.ErrorAtLeastOneTeamNotInTournament(string.Join(", ", teamNames)));
                    return;
                }

                // Create a finals channel and give access to the teams and readers
                Game finalsGame = new Game()
                {
                    Reader = reader,
                    Teams  = teams
                };

                int finalsRoundNumber   = currentTournament.Schedule.Rounds.Count + 1;
                IList <Game> finalGames =
                    currentTournament.Schedule.Rounds[currentTournament.Schedule.Rounds.Count - 1].Games;
                int roomIndex = 0;
                foreach (Game game in finalGames)
                {
                    if (game.Reader.Equals(reader))
                    {
                        break;
                    }

                    roomIndex++;
                }

                if (roomIndex >= finalGames.Count)
                {
                    // Need to have a fall-back somehow. For now default to the first room.
                    roomIndex = 0;
                }

                // TODO: Look into creating the channels after the update stage so we can release the lock
                // sooner. However, this does mean that a failure to create channels will leave us in a bad
                // state.
                channel = await this.ChannelManager.CreateChannelsForFinals(
                    this.Context.Client.CurrentUser, currentTournament, finalsGame, finalsRoundNumber, roomIndex);

                currentTournament.UpdateStage(
                    TournamentStage.Finals, out string nextStageTitle, out string nextStageInstructions);
            });

            if (channel != null)
            {
                this.Logger.Debug("Finals started successfully");
                await this.Context.Channel.SendMessageAsync(
                    BotStrings.FinalsParticipantsPleaseJoin(channel.Mention),
                    options : RequestOptionsSettings.Default);
            }
        }
Пример #19
0
        internal async Task HandleOnReactionAdded(
            Cacheable <IUserMessage, ulong> cachedMessage, ISocketMessageChannel messageChannel, SocketReaction reaction)
        {
            // TODO: Combine checks for reaction add/remove handler into one method
            if (!cachedMessage.HasValue || reaction.UserId == this.Client.CurrentUser.Id)
            {
                // Ignore the bot's own additions.
                return;
            }

            if (!(reaction.Channel is IGuildChannel guildChannel))
            {
                // Only listen to guild reacts.
                this.Logger.Verbose("Reaction addition ignored because it wasn't on a guild channel");
                return;
            }

            if (!(reaction.User.IsSpecified && reaction.User.Value is IGuildUser guildUser))
            {
                this.Logger.Verbose("Reaction addition ignored because it wasn't by a guild user");
                return;
            }

            IUserMessage message      = cachedMessage.Value;
            Player       player       = null;
            bool         playerAdded  = false;
            string       errorMessage = null;
            bool         attempt      = this
                                        .GetTournamentsManager(guildChannel.Guild)
                                        .TryReadWriteActionOnCurrentTournament(currentTournament =>
            {
                player = this.GetPlayerFromReactionEventOrNull(
                    currentTournament, guildUser, message.Id, reaction.Emote.Name, out errorMessage);
                if (player == null)
                {
                    // TODO: we may want to remove the reaction if it's on our team-join messages.
                    this.Logger.Verbose(
                        "Reaction addition ignored because it wasn't by a player. Message: {errorMessage}",
                        errorMessage);
                    return;
                }

                // Because player equality/hashing is only based on the ID, we can check if the player is in the set with
                // the new instance.
                playerAdded = currentTournament.TryAddPlayer(player);
            });

            if (!(attempt && playerAdded) && errorMessage != null)
            {
                // TODO: We should remove the reaction they gave instead of this one (this may also require the manage
                // emojis permission?). This would also require a map from userIds/Players to emojis in the tournament
                // state. The Reactions collection doesn't have information on who added it, and iterating through each
                // emoji to see if the user was there would be slow.
                Task deleteReactionTask = message.RemoveReactionAsync(
                    reaction.Emote, guildUser, RequestOptionsSettings.Default);
                Task sendMessageTask = guildUser.SendMessageAsync(errorMessage);
                await Task.WhenAll(deleteReactionTask, sendMessageTask);

                this.Logger.Verbose("Reaction removed. Message: {errorMessage}", errorMessage);
                return;
            }
            else if (player != null)
            {
                this.Logger.Debug("Player {id} joined team {name}", player.Id, player.Team.Name);
                await guildUser.SendMessageAsync(BotStrings.YouHaveJoinedTeam(player.Team.Name));
            }
        }