NadekoBot/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs

362 lines
15 KiB
C#
Raw Normal View History

using Discord;
using Discord.Commands;
2016-12-17 00:16:14 +00:00
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
2017-05-24 20:28:16 +00:00
using NadekoBot.Services.Database.Models;
2016-10-05 05:01:19 +00:00
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
2017-02-14 13:30:21 +00:00
public class AnimalRacing : NadekoSubmodule
{
2017-05-24 20:28:16 +00:00
private readonly BotConfig _bc;
private readonly CurrencyService _cs;
2017-05-24 20:28:16 +00:00
private readonly DiscordShardedClient _client;
2016-12-08 17:35:34 +00:00
public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
public AnimalRacing(BotConfig bc, CurrencyService cs, DiscordShardedClient client)
2017-05-24 20:28:16 +00:00
{
_bc = bc;
_cs = cs;
2017-05-24 20:28:16 +00:00
_client = client;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
2016-12-16 18:43:57 +00:00
public async Task Race()
{
2017-05-24 20:28:16 +00:00
var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel, Prefix,
_bc, _cs, _client,_localization, _strings);
if (ar.Fail)
2017-02-18 20:07:36 +00:00
await ReplyErrorLocalized("race_failed_starting").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task JoinRace(int amount = 0)
{
2016-07-25 23:00:55 +00:00
if (amount < 0)
amount = 0;
2016-07-25 23:00:55 +00:00
AnimalRace ar;
2016-12-16 21:44:26 +00:00
if (!AnimalRaces.TryGetValue(Context.Guild.Id, out ar))
{
2017-02-18 20:07:36 +00:00
await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false);
return;
}
2016-12-16 18:43:57 +00:00
await ar.JoinRace(Context.User as IGuildUser, amount);
}
//todo 85 needs to be completely isolated, shouldn't use any services in the constructor,
2017-05-24 20:28:16 +00:00
//then move the rest either to the module itself, or the service
public class AnimalRace
{
private ConcurrentQueue<string> animals { get; }
2016-09-01 01:12:08 +00:00
public bool Fail { get; set; }
2017-02-18 20:07:36 +00:00
private readonly List<Participant> _participants = new List<Participant>();
private readonly ulong _serverId;
private int _messagesSinceGameStarted;
2017-02-14 13:30:21 +00:00
private readonly string _prefix;
2017-02-18 20:07:36 +00:00
private readonly Logger _log;
2017-02-18 20:07:36 +00:00
private readonly ITextChannel _raceChannel;
2017-05-24 20:28:16 +00:00
private readonly BotConfig _bc;
private readonly CurrencyService _cs;
2017-05-24 20:28:16 +00:00
private readonly DiscordShardedClient _client;
private readonly ILocalization _localization;
private readonly NadekoStrings _strings;
2017-02-18 20:07:36 +00:00
public bool Started { get; private set; }
2017-05-24 20:28:16 +00:00
public AnimalRace(ulong serverId, ITextChannel channel, string prefix, BotConfig bc,
CurrencyService cs, DiscordShardedClient client, ILocalization localization,
2017-05-24 20:28:16 +00:00
NadekoStrings strings)
{
2017-02-18 20:07:36 +00:00
_prefix = prefix;
2017-05-24 20:28:16 +00:00
_bc = bc;
_cs = cs;
2017-02-18 20:07:36 +00:00
_log = LogManager.GetCurrentClassLogger();
_serverId = serverId;
2017-05-24 20:28:16 +00:00
_raceChannel = channel;
_client = client;
_localization = localization;
_strings = strings;
if (!AnimalRaces.TryAdd(serverId, this))
{
Fail = true;
return;
}
2017-02-14 13:30:21 +00:00
2017-05-24 20:28:16 +00:00
animals = new ConcurrentQueue<string>(_bc.RaceAnimals.Select(ra => ra.Icon).Shuffle());
var cancelSource = new CancellationTokenSource();
var token = cancelSource.Token;
var fullgame = CheckForFullGameAsync(token);
Task.Run(async () =>
{
try
{
2016-12-25 12:35:59 +00:00
try
{
2017-02-18 20:07:36 +00:00
await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting"),
footer: GetText("animal_race_join_instr", _prefix));
2016-12-25 12:35:59 +00:00
}
catch (Exception ex)
{
_log.Warn(ex);
}
var t = await Task.WhenAny(Task.Delay(20000, token), fullgame);
Started = true;
cancelSource.Cancel();
if (t == fullgame)
{
2017-02-18 20:07:36 +00:00
try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full") ); } catch (Exception ex) { _log.Warn(ex); }
}
2017-02-18 20:07:36 +00:00
else if (_participants.Count > 1)
{
2017-02-18 20:07:36 +00:00
try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting_with_x", _participants.Count)); } catch (Exception ex) { _log.Warn(ex); }
}
else
{
2017-02-18 20:07:36 +00:00
try { await _raceChannel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_failed")); } catch (Exception ex) { _log.Warn(ex); }
var p = _participants.FirstOrDefault();
2016-10-05 05:01:19 +00:00
if (p != null && p.AmountBet > 0)
await _cs.AddAsync(p.User, "BetRace", p.AmountBet, false).ConfigureAwait(false);
End();
return;
}
await Task.Run(StartRace);
End();
}
2016-12-08 17:35:34 +00:00
catch { try { End(); } catch { } }
});
}
private void End()
{
AnimalRace throwaway;
2017-02-18 20:07:36 +00:00
AnimalRaces.TryRemove(_serverId, out throwaway);
}
private async Task StartRace()
{
var rng = new NadekoRandom();
Participant winner = null;
IUserMessage msg = null;
2017-02-18 20:07:36 +00:00
var place = 1;
try
{
2017-05-24 20:28:16 +00:00
_client.MessageReceived += Client_MessageReceived;
2017-02-18 20:07:36 +00:00
while (!_participants.All(p => p.Total >= 60))
{
//update the state
2017-02-18 20:07:36 +00:00
_participants.ForEach(p =>
{
p.Total += 1 + rng.Next(0, 10);
2016-12-25 12:35:59 +00:00
});
2017-02-18 20:07:36 +00:00
_participants
2016-12-25 12:35:59 +00:00
.OrderByDescending(p => p.Total)
.ForEach(p =>
{
2016-12-25 12:35:59 +00:00
if (p.Total > 60)
{
2016-12-25 12:35:59 +00:00
if (winner == null)
{
winner = p;
}
p.Total = 60;
if (p.Place == 0)
p.Place = place++;
}
2016-12-25 12:35:59 +00:00
});
//draw the state
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
2017-02-18 20:07:36 +00:00
{String.Join("\n", _participants.Select(p => $"{(int)(p.Total / 60f * 100),-2}%|{p.ToString()}"))}
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
2017-02-18 20:07:36 +00:00
if (msg == null || _messagesSinceGameStarted >= 10) // also resend the message if channel was spammed
{
if (msg != null)
try { await msg.DeleteAsync(); } catch { }
2017-02-18 20:07:36 +00:00
_messagesSinceGameStarted = 0;
try { msg = await _raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
else
2016-10-05 05:01:19 +00:00
{
try { await msg.ModifyAsync(m => m.Content = text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
await Task.Delay(2500);
}
}
2017-02-18 20:07:36 +00:00
catch
{
// ignored
}
finally
{
2017-05-24 20:28:16 +00:00
_client.MessageReceived -= Client_MessageReceived;
}
2017-02-18 20:07:36 +00:00
if (winner != null)
{
2017-02-18 20:07:36 +00:00
if (winner.AmountBet > 0)
{
var wonAmount = winner.AmountBet * (_participants.Count - 1);
await _cs.AddAsync(winner.User, "Won a Race", wonAmount, true)
2017-02-18 20:07:36 +00:00
.ConfigureAwait(false);
await _raceChannel.SendConfirmAsync(GetText("animal_race"),
Format.Bold(GetText("animal_race_won_money", winner.User.Mention,
2017-05-24 20:28:16 +00:00
winner.Animal, wonAmount + _bc.CurrencySign)))
2017-02-18 20:07:36 +00:00
.ConfigureAwait(false);
}
else
{
await _raceChannel.SendConfirmAsync(GetText("animal_race"),
Format.Bold(GetText("animal_race_won", winner.User.Mention, winner.Animal))).ConfigureAwait(false);
}
}
2016-07-25 23:00:55 +00:00
}
2017-01-15 01:28:33 +00:00
private Task Client_MessageReceived(SocketMessage imsg)
{
2017-06-15 23:55:14 +00:00
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);
2017-01-15 01:28:33 +00:00
return Task.CompletedTask;
2017-06-15 23:55:14 +00:00
});
2017-01-15 01:28:33 +00:00
return Task.CompletedTask;
}
private async Task CheckForFullGameAsync(CancellationToken cancelToken)
{
while (animals.Count > 0)
{
await Task.Delay(100, cancelToken);
}
}
2016-10-26 16:17:40 +00:00
public async Task JoinRace(IGuildUser u, int amount = 0)
{
2017-02-18 20:07:36 +00:00
string animal;
if (!animals.TryDequeue(out animal))
{
2017-02-18 20:07:36 +00:00
await _raceChannel.SendErrorAsync(GetText("animal_race_no_race")).ConfigureAwait(false);
2016-10-26 16:17:40 +00:00
return;
}
var p = new Participant(u, animal, amount);
2017-02-18 20:07:36 +00:00
if (_participants.Contains(p))
{
2017-02-18 20:07:36 +00:00
await _raceChannel.SendErrorAsync(GetText("animal_race_already_in")).ConfigureAwait(false);
2016-10-26 16:17:40 +00:00
return;
}
if (Started)
{
2017-02-18 20:07:36 +00:00
await _raceChannel.SendErrorAsync(GetText("animal_race_already_started")).ConfigureAwait(false);
2016-10-26 16:17:40 +00:00
return;
}
2016-10-26 16:17:40 +00:00
if (amount > 0)
if (!await _cs.RemoveAsync(u, "BetRace", amount, false).ConfigureAwait(false))
2016-10-26 16:17:40 +00:00
{
2017-05-24 20:28:16 +00:00
await _raceChannel.SendErrorAsync(GetText("not_enough", _bc.CurrencySign)).ConfigureAwait(false);
2016-10-26 16:17:40 +00:00
return;
}
2017-02-18 20:07:36 +00:00
_participants.Add(p);
string confStr;
if (amount > 0)
2017-05-24 20:28:16 +00:00
confStr = GetText("animal_race_join_bet", u.Mention, p.Animal, amount + _bc.CurrencySign);
2017-02-18 20:07:36 +00:00
else
confStr = GetText("animal_race_join", u.Mention, p.Animal);
await _raceChannel.SendConfirmAsync(GetText("animal_race"), Format.Bold(confStr)).ConfigureAwait(false);
}
2017-02-18 20:07:36 +00:00
private string GetText(string text)
2017-05-24 20:28:16 +00:00
=> _strings.GetText(text,
_localization.GetCultureInfo(_raceChannel.Guild),
2017-02-18 20:07:36 +00:00
typeof(Gambling).Name.ToLowerInvariant());
private string GetText(string text, params object[] replacements)
2017-05-24 20:28:16 +00:00
=> _strings.GetText(text,
_localization.GetCultureInfo(_raceChannel.Guild),
2017-02-18 20:07:36 +00:00
typeof(Gambling).Name.ToLowerInvariant(),
replacements);
}
public class Participant
{
2017-02-18 20:07:36 +00:00
public IGuildUser User { get; }
public string Animal { get; }
public int AmountBet { get; }
public float Coeff { get; set; }
public int Total { get; set; }
2017-02-18 20:07:36 +00:00
public int Place { get; set; }
public Participant(IGuildUser u, string a, int amount)
{
2017-02-18 20:07:36 +00:00
User = u;
Animal = a;
AmountBet = amount;
}
public override int GetHashCode() => User.GetHashCode();
public override bool Equals(object obj)
{
var p = obj as Participant;
2017-02-14 13:30:21 +00:00
return p != null && p.User == User;
}
public override string ToString()
{
var str = new string('‣', Total) + Animal;
if (Place == 0)
return str;
2017-02-18 20:07:36 +00:00
str += $"`#{Place}`";
if (Place == 1)
2017-02-18 20:07:36 +00:00
str += "🏆";
2017-02-18 20:07:36 +00:00
return str;
}
}
}
}
}