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 Discord.WebSocket;
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NLog;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NadekoBot.Common;
|
|
||||||
using NadekoBot.Common.Attributes;
|
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
|
namespace NadekoBot.Modules.Gambling
|
||||||
{
|
{
|
||||||
@ -35,326 +32,118 @@ namespace NadekoBot.Modules.Gambling
|
|||||||
_client = client;
|
_client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUserMessage raceMessage = null;
|
||||||
|
|
||||||
[NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task Race()
|
public Task Race()
|
||||||
{
|
{
|
||||||
var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel, Prefix,
|
var ar = new AnimalRace(_cs, _bc.BotConfig.RaceAnimals.Shuffle().ToArray());
|
||||||
_bc, _cs, _client,_localization, _strings);
|
if (!AnimalRaces.TryAdd(Context.Guild.Id, ar))
|
||||||
|
return Context.Channel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_already_started"));
|
||||||
|
ar.Initialize();
|
||||||
|
|
||||||
if (ar.Fail)
|
ar.OnStartingFailed += Ar_OnStartingFailed;
|
||||||
await ReplyErrorLocalized("race_failed_starting").ConfigureAwait(false);
|
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]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task JoinRace(int amount = 0)
|
public async Task JoinRace(int amount = 0)
|
||||||
{
|
{
|
||||||
|
if (!AnimalRaces.TryGetValue(Context.Guild.Id, out var ar))
|
||||||
if (amount < 0)
|
|
||||||
amount = 0;
|
|
||||||
|
|
||||||
|
|
||||||
AnimalRace ar;
|
|
||||||
if (!AnimalRaces.TryGetValue(Context.Guild.Id, out ar))
|
|
||||||
{
|
{
|
||||||
await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false);
|
await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false);
|
||||||
return;
|
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
|
||||||
{
|
{
|
||||||
try
|
var user = await ar.JoinRace(Context.User.Id, Context.User.ToString(), amount)
|
||||||
{
|
|
||||||
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);
|
.ConfigureAwait(false);
|
||||||
await _raceChannel.SendConfirmAsync(GetText("animal_race"),
|
if (amount > 0)
|
||||||
Format.Bold(GetText("animal_race_won_money", winner.User.Mention,
|
await Context.Channel.SendConfirmAsync(GetText("animal_race_join_bet", Context.User.Mention, user.Animal.Icon, amount + _bc.BotConfig.CurrencySign)).ConfigureAwait(false);
|
||||||
winner.Animal, wonAmount + _bc.BotConfig.CurrencySign)))
|
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);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
catch (NotEnoughFundsException)
|
||||||
{
|
{
|
||||||
await _raceChannel.SendConfirmAsync(GetText("animal_race"),
|
await Context.Channel.SendErrorAsync(GetText("not_enough", _bc.BotConfig.CurrencySign)).ConfigureAwait(false);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
public class TermPool
|
||||||
{
|
{
|
||||||
const string termsPath = "data/hangman3.json";
|
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()
|
static TermPool()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
data = JsonConvert.DeserializeObject<Dictionary<string, HangmanObject[]>>(File.ReadAllText(termsPath));
|
Data = JsonConvert.DeserializeObject<Dictionary<string, HangmanObject[]>>(File.ReadAllText(termsPath));
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@ -35,11 +35,11 @@ namespace NadekoBot.Modules.Games.Common.Hangman
|
|||||||
|
|
||||||
if (type == TermType.Random)
|
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'
|
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();
|
throw new TermNotFoundException();
|
||||||
|
|
||||||
var obj = termTypes[rng.Next(0, termTypes.Length)];
|
var obj = termTypes[rng.Next(0, termTypes.Length)];
|
||||||
|
@ -11,12 +11,11 @@ using NadekoBot.Services.Impl;
|
|||||||
|
|
||||||
namespace NadekoBot.Modules.Games.Common
|
namespace NadekoBot.Modules.Games.Common
|
||||||
{
|
{
|
||||||
//todo 75 rewrite
|
|
||||||
public class Poll
|
public class Poll
|
||||||
{
|
{
|
||||||
private readonly IUserMessage _originalMessage;
|
private readonly IUserMessage _originalMessage;
|
||||||
private readonly IGuild _guild;
|
private readonly IGuild _guild;
|
||||||
private string[] answers { get; }
|
private readonly string[] answers;
|
||||||
private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
|
private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
|
||||||
private readonly string _question;
|
private readonly string _question;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Games
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task Hangmanlist()
|
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]
|
[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_all_stats_cleared": "All custom reaction stats cleared.",
|
||||||
"customreactions_deleted": "Custom Reaction deleted",
|
"customreactions_deleted": "Custom Reaction deleted",
|
||||||
"customreactions_insuff_perms": "Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for server custom reactions.",
|
"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