From 82aac891dd7f45ed8000c8f58b3e422ba605fd05 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Tue, 1 Aug 2017 00:11:36 +0200 Subject: [PATCH] animal racing rewritten to be isolated. Please hunt bugs. --- .../Modules/Gambling/AnimalRacingCommands.cs | 395 ++++-------------- .../Common/AnimalRacing/AnimalRace.cs | 161 +++++++ .../Common/AnimalRacing/AnimalRacingUser.cs | 32 ++ .../Exceptions/AlreadyJoinedException.cs | 9 + .../Exceptions/AlreadyStartedException.cs | 9 + .../Exceptions/AnimalRaceFullException.cs | 8 + .../Exceptions/NotEnoughFundsException.cs | 9 + .../Modules/Games/Common/Hangman/TermPool.cs | 8 +- src/NadekoBot/Modules/Games/Common/Poll.cs | 3 +- .../Modules/Games/HangmanCommands.cs | 2 +- .../_strings/ResponseStrings.en-US.json | 23 - 11 files changed, 326 insertions(+), 333 deletions(-) create mode 100644 src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs create mode 100644 src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs create mode 100644 src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs create mode 100644 src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs create mode 100644 src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs create mode 100644 src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs diff --git a/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs b/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs index f4777bef..2b9ee3ce 100644 --- a/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs +++ b/src/NadekoBot/Modules/Gambling/AnimalRacingCommands.cs @@ -3,16 +3,13 @@ using Discord.Commands; using Discord.WebSocket; using NadekoBot.Extensions; using NadekoBot.Services; -using NLog; using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; -using NadekoBot.Common; using NadekoBot.Common.Attributes; -using NadekoBot.Services.Impl; +using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions; +using NadekoBot.Modules.Gambling.Common.AnimalRacing; namespace NadekoBot.Modules.Gambling { @@ -24,7 +21,7 @@ namespace NadekoBot.Modules.Gambling private readonly IBotConfigProvider _bc; private readonly CurrencyService _cs; private readonly DiscordSocketClient _client; - + public static ConcurrentDictionary AnimalRaces { get; } = new ConcurrentDictionary(); @@ -35,326 +32,118 @@ namespace NadekoBot.Modules.Gambling _client = client; } + private IUserMessage raceMessage = null; + [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - public async Task Race() + public Task Race() { - var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel, Prefix, - _bc, _cs, _client,_localization, _strings); + var ar = new AnimalRace(_cs, _bc.BotConfig.RaceAnimals.Shuffle().ToArray()); + if (!AnimalRaces.TryAdd(Context.Guild.Id, ar)) + return Context.Channel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_already_started")); + ar.Initialize(); - if (ar.Fail) - await ReplyErrorLocalized("race_failed_starting").ConfigureAwait(false); + ar.OnStartingFailed += Ar_OnStartingFailed; + ar.OnStateUpdate += Ar_OnStateUpdate; + ar.OnEnded += Ar_OnEnded; + ar.OnStarted += Ar_OnStarted; + + return Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting"), + footer: GetText("animal_race_join_instr", Prefix)); + } + + private Task Ar_OnStarted(AnimalRace race) + { + if(race.Users.Length == race.MaxUsers) + return Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full")); + else + return Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting_with_x", race.Users.Length)); + } + + private Task Ar_OnEnded(AnimalRace race) + { + AnimalRaces.TryRemove(Context.Guild.Id, out _); + var winner = race.FinishedUsers[0]; + if (race.FinishedUsers[0].Bet > 0) + { + return Context.Channel.SendConfirmAsync(GetText("animal_race"), + GetText("animal_race_won_money", Format.Bold(winner.Username), + winner.Animal.Icon, (race.FinishedUsers[0].Bet * (race.Users.Length - 1)) + _bc.BotConfig.CurrencySign)); + } + else + { + return Context.Channel.SendConfirmAsync(GetText("animal_race"), + GetText("animal_race_won", Format.Bold(winner.Username), winner.Animal.Icon)); + } + } + + private async Task Ar_OnStateUpdate(AnimalRace race) + { + var text = $@"|πŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸ”š| +{String.Join("\n", race.Users.Select(p => + { + var index = race.FinishedUsers.IndexOf(p); + var extra = (index == -1 ? "" : $"#{index + 1} {(index == 0 ? "πŸ†" : "")}"); + return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('β€£', p.Progress) + p.Animal.Icon + extra}"; + }))} +|πŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸ”š|"; + + if (raceMessage == null) + raceMessage = await Context.Channel.SendConfirmAsync(text) + .ConfigureAwait(false); + else + await raceMessage.ModifyAsync(x => x.Embed = new EmbedBuilder() + .WithTitle(GetText("animal_race")) + .WithDescription(text) + .WithOkColor() + .Build()) + .ConfigureAwait(false); + } + + private Task Ar_OnStartingFailed(AnimalRace race) + { + AnimalRaces.TryRemove(Context.Guild.Id, out _); + return ReplyErrorLocalized("animal_race_failed"); } [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] public async Task JoinRace(int amount = 0) { - - if (amount < 0) - amount = 0; - - - AnimalRace ar; - if (!AnimalRaces.TryGetValue(Context.Guild.Id, out ar)) + if (!AnimalRaces.TryGetValue(Context.Guild.Id, out var ar)) { await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false); return; } - await ar.JoinRace(Context.User as IGuildUser, amount); - } - - //todo 85 needs to be completely isolated, shouldn't use any services in the constructor, - //then move the rest either to the module itself, or the service - public class AnimalRace - { - - private ConcurrentQueue animals { get; } - - public bool Fail { get; set; } - - private readonly List _participants = new List(); - private readonly ulong _serverId; - private int _messagesSinceGameStarted; - private readonly string _prefix; - - private readonly Logger _log; - - private readonly ITextChannel _raceChannel; - private readonly IBotConfigProvider _bc; - private readonly CurrencyService _cs; - private readonly DiscordSocketClient _client; - private readonly ILocalization _localization; - private readonly NadekoStrings _strings; - - public bool Started { get; private set; } - - public AnimalRace(ulong serverId, ITextChannel channel, string prefix, IBotConfigProvider bc, - CurrencyService cs, DiscordSocketClient client, ILocalization localization, - NadekoStrings strings) + try { - _prefix = prefix; - _bc = bc; - _cs = cs; - _log = LogManager.GetCurrentClassLogger(); - _serverId = serverId; - _raceChannel = channel; - _client = client; - _localization = localization; - _strings = strings; - - if (!AnimalRaces.TryAdd(serverId, this)) - { - Fail = true; - return; - } - - animals = new ConcurrentQueue(_bc.BotConfig.RaceAnimals.Select(ra => ra.Icon).Shuffle()); - - - var cancelSource = new CancellationTokenSource(); - var token = cancelSource.Token; - var fullgame = CheckForFullGameAsync(token); - Task.Run(async () => - { - try - { - try - { - await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting"), - footer: GetText("animal_race_join_instr", _prefix)); - } - catch (Exception ex) - { - _log.Warn(ex); - } - var t = await Task.WhenAny(Task.Delay(20000, token), fullgame); - Started = true; - cancelSource.Cancel(); - if (t == fullgame) - { - try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full") ); } catch (Exception ex) { _log.Warn(ex); } - } - else if (_participants.Count > 1) - { - try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting_with_x", _participants.Count)); } catch (Exception ex) { _log.Warn(ex); } - } - else - { - try { await _raceChannel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_failed")); } catch (Exception ex) { _log.Warn(ex); } - var p = _participants.FirstOrDefault(); - - if (p != null && p.AmountBet > 0) - await _cs.AddAsync(p.User, "BetRace", p.AmountBet, false).ConfigureAwait(false); - End(); - return; - } - await Task.Run(StartRace); - End(); - } - catch { try { End(); } catch { } } - }); - } - - private void End() - { - AnimalRaces.TryRemove(_serverId, out _); - } - - private async Task StartRace() - { - var rng = new NadekoRandom(); - Participant winner = null; - IUserMessage msg = null; - var place = 1; - try - { - _client.MessageReceived += Client_MessageReceived; - - while (!_participants.All(p => p.Total >= 60)) - { - //update the state - _participants.ForEach(p => - { - p.Total += 1 + rng.Next(0, 10); - }); - - - _participants - .OrderByDescending(p => p.Total) - .ForEach(p => - { - if (p.Total > 60) - { - if (winner == null) - { - winner = p; - } - p.Total = 60; - if (p.Place == 0) - p.Place = place++; - } - }); - - - //draw the state - - var text = $@"|πŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸ”š| -{String.Join("\n", _participants.Select(p => $"{(int)(p.Total / 60f * 100),-2}%|{p.ToString()}"))} -|πŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸ”š|"; - if (msg == null || _messagesSinceGameStarted >= 10) // also resend the message if channel was spammed - { - if (msg != null) - try { await msg.DeleteAsync(); } catch { } - _messagesSinceGameStarted = 0; - try { msg = await _raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } - } - else - { - try { await msg.ModifyAsync(m => m.Content = text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } - } - - await Task.Delay(2500); - } - } - catch - { - // ignored - } - finally - { - _client.MessageReceived -= Client_MessageReceived; - } - - if (winner != null) - { - if (winner.AmountBet > 0) - { - var wonAmount = winner.AmountBet * (_participants.Count - 1); - - await _cs.AddAsync(winner.User, "Won a Race", wonAmount, true) - .ConfigureAwait(false); - await _raceChannel.SendConfirmAsync(GetText("animal_race"), - Format.Bold(GetText("animal_race_won_money", winner.User.Mention, - winner.Animal, wonAmount + _bc.BotConfig.CurrencySign))) - .ConfigureAwait(false); - } - else - { - await _raceChannel.SendConfirmAsync(GetText("animal_race"), - Format.Bold(GetText("animal_race_won", winner.User.Mention, winner.Animal))).ConfigureAwait(false); - } - } - - } - - private Task Client_MessageReceived(SocketMessage imsg) - { - var _ = Task.Run(() => - { - var msg = imsg as SocketUserMessage; - if (msg == null) - return Task.CompletedTask; - if ((msg.Author.Id == _client.CurrentUser.Id) || !(imsg.Channel is ITextChannel) || imsg.Channel != _raceChannel) - return Task.CompletedTask; - Interlocked.Increment(ref _messagesSinceGameStarted); - return Task.CompletedTask; - }); - return Task.CompletedTask; - } - - private async Task CheckForFullGameAsync(CancellationToken cancelToken) - { - while (animals.Count > 0) - { - await Task.Delay(100, cancelToken); - } - } - - public async Task JoinRace(IGuildUser u, int amount = 0) - { - string animal; - if (!animals.TryDequeue(out animal)) - { - await _raceChannel.SendErrorAsync(GetText("animal_race_no_race")).ConfigureAwait(false); - return; - } - var p = new Participant(u, animal, amount); - if (_participants.Contains(p)) - { - await _raceChannel.SendErrorAsync(GetText("animal_race_already_in")).ConfigureAwait(false); - return; - } - if (Started) - { - await _raceChannel.SendErrorAsync(GetText("animal_race_already_started")).ConfigureAwait(false); - return; - } + var user = await ar.JoinRace(Context.User.Id, Context.User.ToString(), amount) + .ConfigureAwait(false); if (amount > 0) - if (!await _cs.RemoveAsync(u, "BetRace", amount, false).ConfigureAwait(false)) - { - await _raceChannel.SendErrorAsync(GetText("not_enough", _bc.BotConfig.CurrencySign)).ConfigureAwait(false); - return; - } - _participants.Add(p); - string confStr; - if (amount > 0) - confStr = GetText("animal_race_join_bet", u.Mention, p.Animal, amount + _bc.BotConfig.CurrencySign); + await Context.Channel.SendConfirmAsync(GetText("animal_race_join_bet", Context.User.Mention, user.Animal.Icon, amount + _bc.BotConfig.CurrencySign)).ConfigureAwait(false); else - confStr = GetText("animal_race_join", u.Mention, p.Animal); - await _raceChannel.SendConfirmAsync(GetText("animal_race"), Format.Bold(confStr)).ConfigureAwait(false); + await Context.Channel.SendConfirmAsync(GetText("animal_race_join", Context.User.Mention, user.Animal.Icon)).ConfigureAwait(false); } - - private string GetText(string text) - => _strings.GetText(text, - _localization.GetCultureInfo(_raceChannel.Guild), - typeof(Gambling).Name.ToLowerInvariant()); - - private string GetText(string text, params object[] replacements) - => _strings.GetText(text, - _localization.GetCultureInfo(_raceChannel.Guild), - typeof(Gambling).Name.ToLowerInvariant(), - replacements); - } - - public class Participant - { - public IGuildUser User { get; } - public string Animal { get; } - public int AmountBet { get; } - - public float Coeff { get; set; } - public int Total { get; set; } - - public int Place { get; set; } - - public Participant(IGuildUser u, string a, int amount) + catch (ArgumentOutOfRangeException) { - User = u; - Animal = a; - AmountBet = amount; + //ignore if user inputed an invalid amount } - - public override int GetHashCode() => User.GetHashCode(); - - public override bool Equals(object obj) + catch (AlreadyJoinedException) { - var p = obj as Participant; - return p != null && p.User == User; + // just ignore this } - - public override string ToString() + catch (AlreadyStartedException) { - var str = new string('β€£', Total) + Animal; - if (Place == 0) - return str; - - str += $"`#{Place}`"; - - if (Place == 1) - str += "πŸ†"; - - return str; + //ignore + } + catch (AnimalRaceFullException) + { + await Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full")) + .ConfigureAwait(false); + } + catch (NotEnoughFundsException) + { + await Context.Channel.SendErrorAsync(GetText("not_enough", _bc.BotConfig.CurrencySign)).ConfigureAwait(false); } } } diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs new file mode 100644 index 00000000..bf1c51ac --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs @@ -0,0 +1,161 @@ +ο»Ώusing NadekoBot.Common; +using NadekoBot.Extensions; +using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing +{ + public class AnimalRace : IDisposable + { + public enum Phase + { + WaitingForPlayers, + Running, + Ended, + } + + private const int _startingDelayMiliseconds = 20_000; + + public Phase CurrentPhase = Phase.WaitingForPlayers; + + public event Func OnStarted = delegate { return Task.CompletedTask; }; + public event Func OnStartingFailed = delegate { return Task.CompletedTask; }; + public event Func OnStateUpdate = delegate { return Task.CompletedTask; }; + public event Func OnEnded = delegate { return Task.CompletedTask; }; + + public ImmutableArray Users => _users.ToImmutableArray(); + public List FinishedUsers { get; } = new List(); + + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1); + private readonly HashSet _users = new HashSet(); + private readonly CurrencyService _currency; + private readonly Queue _animalsQueue; + public int MaxUsers { get; } + + public AnimalRace(CurrencyService currency, RaceAnimal[] availableAnimals) + { + this._currency = currency; + this._animalsQueue = new Queue(availableAnimals); + this.MaxUsers = availableAnimals.Length; + + if (this._animalsQueue.Count == 0) + CurrentPhase = Phase.Ended; + } + + public void Initialize() //lame name + { + var _t = Task.Run(async () => + { + await Task.Delay(_startingDelayMiliseconds).ConfigureAwait(false); + + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (CurrentPhase != Phase.WaitingForPlayers) + return; + + await Start().ConfigureAwait(false); + } + finally { _locker.Release(); } + }); + } + + public async Task JoinRace(ulong userId, string userName, int bet = 0) + { + if (bet < 0) + throw new ArgumentOutOfRangeException(nameof(bet)); + + var user = new AnimalRacingUser(userName, userId, bet); + + await _locker.WaitAsync().ConfigureAwait(false); + try + { + if (_users.Count == MaxUsers) + throw new AnimalRaceFullException(); + + if (CurrentPhase != Phase.WaitingForPlayers) + throw new AlreadyStartedException(); + + if (!await _currency.RemoveAsync(userId, "BetRace", bet).ConfigureAwait(false)) + throw new NotEnoughFundsException(); + + if (_users.Contains(user)) + throw new AlreadyJoinedException(); + + var animal = _animalsQueue.Dequeue(); + user.Animal = animal; + _users.Add(user); + + if (_animalsQueue.Count == 0) //start if no more spots left + await Start().ConfigureAwait(false); + + return user; + } + finally { _locker.Release(); } + } + + private async Task Start() + { + CurrentPhase = Phase.Running; + if (_users.Count <= 1) + { + foreach (var user in _users) + { + if(user.Bet > 0) + await _currency.AddAsync(user.UserId, "Race refund", user.Bet).ConfigureAwait(false); + } + + var _sf = OnStartingFailed?.Invoke(this); + CurrentPhase = Phase.Ended; + return; + } + + var _ = OnStarted?.Invoke(this); + var _t = Task.Run(async () => + { + var rng = new NadekoRandom(); + while (!_users.All(x => x.Progress >= 60)) + { + foreach (var user in _users) + { + user.Progress += rng.Next(1, 11); + if (user.Progress >= 60) + user.Progress = 60; + } + + var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x)) + .Shuffle(); + + FinishedUsers.AddRange(finished); + + var _ignore = OnStateUpdate?.Invoke(this); + await Task.Delay(2500).ConfigureAwait(false); + } + + if (FinishedUsers[0].Bet > 0) + await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1)) + .ConfigureAwait(false); + + var _ended = OnEnded?.Invoke(this); + }); + } + + public void Dispose() + { + CurrentPhase = Phase.Ended; + OnStarted = null; + OnEnded = null; + OnStartingFailed = null; + OnStateUpdate = null; + _locker.Dispose(); + _users.Clear(); + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs new file mode 100644 index 00000000..ea9bc453 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRacingUser.cs @@ -0,0 +1,32 @@ +ο»Ώusing NadekoBot.Services.Database.Models; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing +{ + public class AnimalRacingUser + { + public int Bet { get; } + public string Username { get; } + public ulong UserId { get; } + public RaceAnimal Animal { get; set; } + public int Progress { get; set; } + + public AnimalRacingUser(string username, ulong userId, int bet) + { + this.Bet = bet; + this.Username = username; + this.UserId = userId; + } + + public override bool Equals(object obj) + { + return obj is AnimalRacingUser x + ? x.UserId == this.UserId + : false; + } + + public override int GetHashCode() + { + return this.UserId.GetHashCode(); + } + } +} diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs new file mode 100644 index 00000000..56469bf8 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyJoinedException.cs @@ -0,0 +1,9 @@ +ο»Ώusing System; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions +{ + public class AlreadyJoinedException : Exception + { + + } +} diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs new file mode 100644 index 00000000..ab54a87a --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AlreadyStartedException.cs @@ -0,0 +1,9 @@ +ο»Ώusing System; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions +{ + public class AlreadyStartedException : Exception + { + + } +} diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs new file mode 100644 index 00000000..9c3e8f69 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/AnimalRaceFullException.cs @@ -0,0 +1,8 @@ +ο»Ώusing System; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions +{ + public class AnimalRaceFullException : Exception + { + } +} diff --git a/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs new file mode 100644 index 00000000..1fb01093 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Common/AnimalRacing/Exceptions/NotEnoughFundsException.cs @@ -0,0 +1,9 @@ +ο»Ώusing System; + +namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions +{ + public class NotEnoughFundsException : Exception + { + + } +} diff --git a/src/NadekoBot/Modules/Games/Common/Hangman/TermPool.cs b/src/NadekoBot/Modules/Games/Common/Hangman/TermPool.cs index 3074aef7..94423b54 100644 --- a/src/NadekoBot/Modules/Games/Common/Hangman/TermPool.cs +++ b/src/NadekoBot/Modules/Games/Common/Hangman/TermPool.cs @@ -12,12 +12,12 @@ namespace NadekoBot.Modules.Games.Common.Hangman public class TermPool { const string termsPath = "data/hangman3.json"; - public static IReadOnlyDictionary data { get; } = new Dictionary(); + public static IReadOnlyDictionary Data { get; } = new Dictionary(); static TermPool() { try { - data = JsonConvert.DeserializeObject>(File.ReadAllText(termsPath)); + Data = JsonConvert.DeserializeObject>(File.ReadAllText(termsPath)); } catch (Exception) { @@ -35,11 +35,11 @@ namespace NadekoBot.Modules.Games.Common.Hangman if (type == TermType.Random) { - var keys = data.Keys.ToArray(); + var keys = Data.Keys.ToArray(); type = _termTypes[rng.Next(0, _termTypes.Length - 1)]; // - 1 because last one is 'all' } - if (!data.TryGetValue(type.ToString(), out var termTypes) || termTypes.Length == 0) + if (!Data.TryGetValue(type.ToString(), out var termTypes) || termTypes.Length == 0) throw new TermNotFoundException(); var obj = termTypes[rng.Next(0, termTypes.Length)]; diff --git a/src/NadekoBot/Modules/Games/Common/Poll.cs b/src/NadekoBot/Modules/Games/Common/Poll.cs index 816e2101..cf051a07 100644 --- a/src/NadekoBot/Modules/Games/Common/Poll.cs +++ b/src/NadekoBot/Modules/Games/Common/Poll.cs @@ -11,12 +11,11 @@ using NadekoBot.Services.Impl; namespace NadekoBot.Modules.Games.Common { - //todo 75 rewrite public class Poll { private readonly IUserMessage _originalMessage; private readonly IGuild _guild; - private string[] answers { get; } + private readonly string[] answers; private readonly ConcurrentDictionary _participants = new ConcurrentDictionary(); private readonly string _question; private readonly DiscordSocketClient _client; diff --git a/src/NadekoBot/Modules/Games/HangmanCommands.cs b/src/NadekoBot/Modules/Games/HangmanCommands.cs index ce76cc37..f7098f8e 100644 --- a/src/NadekoBot/Modules/Games/HangmanCommands.cs +++ b/src/NadekoBot/Modules/Games/HangmanCommands.cs @@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Games [RequireContext(ContextType.Guild)] public async Task Hangmanlist() { - await Context.Channel.SendConfirmAsync(Format.Code(GetText("hangman_types", Prefix)) + "\n" + string.Join(", ", TermPool.data.Keys)); + await Context.Channel.SendConfirmAsync(Format.Code(GetText("hangman_types", Prefix)) + "\n" + string.Join(", ", TermPool.Data.Keys)); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/_strings/ResponseStrings.en-US.json b/src/NadekoBot/_strings/ResponseStrings.en-US.json index 30a1d23f..5e1b3ead 100644 --- a/src/NadekoBot/_strings/ResponseStrings.en-US.json +++ b/src/NadekoBot/_strings/ResponseStrings.en-US.json @@ -1,27 +1,4 @@ { - "clashofclans_base_already_claimed": "That base is already claimed or destroyed.", - "clashofclans_base_already_destroyed": "That base is already destroyed.", - "clashofclans_base_already_unclaimed": "That base is not claimed.", - "clashofclans_base_destroyed": "**DESTROYED** base #{0} in a war against {1}", - "clashofclans_base_unclaimed": "{0} has **UNCLAIMED** base #{1} in a war against {2}", - "clashofclans_claimed_base": "{0} claimed a base #{1} in a war against {2}", - "clashofclans_claimed_other": "@{0} You already claimed base #{1}. You can't claim a new one.", - "clashofclans_claim_expired": "Claim from @{0} in a war against {1} has expired.", - "clashofclans_enemy": "Enemy", - "clashofclans_info_about_war": "Info about war against {0}", - "clashofclans_invalid_base_number": "Invalid base number.", - "clashofclans_invalid_size": "Not a valid war size.", - "clashofclans_list_active_wars": "List of active wars", - "clashofclans_not_claimed": "not claimed", - "clashofclans_not_partic": "You are not participating in that war.", - "clashofclans_not_partic_or_destroyed": "@{0} You are either not participating in that war, or that base is already destroyed.", - "clashofclans_no_active_wars": "No active war.", - "clashofclans_size": "Size", - "clashofclans_war_already_started": "War against {0} has already started.", - "clashofclans_war_created": "War against {0} created.", - "clashofclans_war_ended": "War against {0} ended.", - "clashofclans_war_not_exist": "That war does not exist.", - "clashofclans_war_started": "War against {0} started!", "customreactions_all_stats_cleared": "All custom reaction stats cleared.", "customreactions_deleted": "Custom Reaction deleted", "customreactions_insuff_perms": "Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for server custom reactions.",