animal racing rewritten to be isolated. Please hunt bugs.
This commit is contained in:
parent
3097ef88a7
commit
82aac891dd
@ -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
|
||||
{
|
||||
@ -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<string> animals { get; }
|
||||
|
||||
public bool Fail { get; set; }
|
||||
|
||||
private readonly List<Participant> _participants = new List<Participant>();
|
||||
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)
|
||||
{
|
||||
_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<string>(_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)
|
||||
var user = await ar.JoinRace(Context.User.Id, Context.User.ToString(), amount)
|
||||
.ConfigureAwait(false);
|
||||
await _raceChannel.SendConfirmAsync(GetText("animal_race"),
|
||||
Format.Bold(GetText("animal_race_won_money", winner.User.Mention,
|
||||
winner.Animal, wonAmount + _bc.BotConfig.CurrencySign)))
|
||||
if (amount > 0)
|
||||
await Context.Channel.SendConfirmAsync(GetText("animal_race_join_bet", Context.User.Mention, user.Animal.Icon, amount + _bc.BotConfig.CurrencySign)).ConfigureAwait(false);
|
||||
else
|
||||
await Context.Channel.SendConfirmAsync(GetText("animal_race_join", Context.User.Mention, user.Animal.Icon)).ConfigureAwait(false);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
//ignore if user inputed an invalid amount
|
||||
}
|
||||
catch (AlreadyJoinedException)
|
||||
{
|
||||
// just ignore this
|
||||
}
|
||||
catch (AlreadyStartedException)
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
catch (AnimalRaceFullException)
|
||||
{
|
||||
await Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full"))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
catch (NotEnoughFundsException)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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);
|
||||
else
|
||||
confStr = GetText("animal_race_join", u.Mention, p.Animal);
|
||||
await _raceChannel.SendConfirmAsync(GetText("animal_race"), Format.Bold(confStr)).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)
|
||||
{
|
||||
User = u;
|
||||
Animal = a;
|
||||
AmountBet = amount;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => User.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var p = obj as Participant;
|
||||
return p != null && p.User == User;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var str = new string('‣', Total) + Animal;
|
||||
if (Place == 0)
|
||||
return str;
|
||||
|
||||
str += $"`#{Place}`";
|
||||
|
||||
if (Place == 1)
|
||||
str += "🏆";
|
||||
|
||||
return str;
|
||||
await Context.Channel.SendErrorAsync(GetText("not_enough", _bc.BotConfig.CurrencySign)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
161
src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs
Normal file
161
src/NadekoBot/Modules/Gambling/Common/AnimalRacing/AnimalRace.cs
Normal file
@ -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<AnimalRace, Task> OnStarted = delegate { return Task.CompletedTask; };
|
||||
public event Func<AnimalRace, Task> OnStartingFailed = delegate { return Task.CompletedTask; };
|
||||
public event Func<AnimalRace, Task> OnStateUpdate = delegate { return Task.CompletedTask; };
|
||||
public event Func<AnimalRace, Task> OnEnded = delegate { return Task.CompletedTask; };
|
||||
|
||||
public ImmutableArray<AnimalRacingUser> Users => _users.ToImmutableArray();
|
||||
public List<AnimalRacingUser> FinishedUsers { get; } = new List<AnimalRacingUser>();
|
||||
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
private readonly HashSet<AnimalRacingUser> _users = new HashSet<AnimalRacingUser>();
|
||||
private readonly CurrencyService _currency;
|
||||
private readonly Queue<RaceAnimal> _animalsQueue;
|
||||
public int MaxUsers { get; }
|
||||
|
||||
public AnimalRace(CurrencyService currency, RaceAnimal[] availableAnimals)
|
||||
{
|
||||
this._currency = currency;
|
||||
this._animalsQueue = new Queue<RaceAnimal>(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<AnimalRacingUser> 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
{
|
||||
public class AlreadyJoinedException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
{
|
||||
public class AlreadyStartedException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
{
|
||||
public class AnimalRaceFullException : Exception
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
{
|
||||
public class NotEnoughFundsException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -12,12 +12,12 @@ namespace NadekoBot.Modules.Games.Common.Hangman
|
||||
public class TermPool
|
||||
{
|
||||
const string termsPath = "data/hangman3.json";
|
||||
public static IReadOnlyDictionary<string, HangmanObject[]> data { get; } = new Dictionary<string, HangmanObject[]>();
|
||||
public static IReadOnlyDictionary<string, HangmanObject[]> Data { get; } = new Dictionary<string, HangmanObject[]>();
|
||||
static TermPool()
|
||||
{
|
||||
try
|
||||
{
|
||||
data = JsonConvert.DeserializeObject<Dictionary<string, HangmanObject[]>>(File.ReadAllText(termsPath));
|
||||
Data = JsonConvert.DeserializeObject<Dictionary<string, HangmanObject[]>>(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)];
|
||||
|
@ -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<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
|
||||
private readonly string _question;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
@ -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]
|
||||
|
@ -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.",
|
||||
|
Loading…
Reference in New Issue
Block a user