314 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			314 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using Discord;
 | |
| using Discord.Commands;
 | |
| using Discord.WebSocket;
 | |
| using NadekoBot.Attributes;
 | |
| 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;
 | |
| 
 | |
| namespace NadekoBot.Modules.Gambling
 | |
| {
 | |
|     public partial class Gambling
 | |
|     {
 | |
|         [Group]
 | |
|         public class AnimalRacing : ModuleBase
 | |
|         {
 | |
|             public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
 | |
| 
 | |
|             [NadekoCommand, Usage, Description, Aliases]
 | |
|             [RequireContext(ContextType.Guild)]
 | |
|             public async Task Race()
 | |
|             {
 | |
|                 var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel);
 | |
| 
 | |
|                 if (ar.Fail)
 | |
|                     await Context.Channel.SendErrorAsync("🏁 `Failed starting a race. Another race is probably running.`").ConfigureAwait(false);
 | |
|             }
 | |
| 
 | |
|             [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))
 | |
|                 {
 | |
|                     await Context.Channel.SendErrorAsync("No race exists on this server").ConfigureAwait(false);
 | |
|                     return;
 | |
|                 }
 | |
|                 await ar.JoinRace(Context.User as IGuildUser, amount);
 | |
|             }
 | |
| 
 | |
|             public class AnimalRace
 | |
|             {
 | |
| 
 | |
|                 private ConcurrentQueue<string> animals { get; }
 | |
| 
 | |
|                 public bool Fail { get; set; }
 | |
| 
 | |
|                 public List<Participant> participants = new List<Participant>();
 | |
|                 private ulong serverId;
 | |
|                 private int messagesSinceGameStarted = 0;
 | |
|                 private Logger _log { get; }
 | |
| 
 | |
|                 public ITextChannel raceChannel { get; set; }
 | |
|                 public bool Started { get; private set; } = false;
 | |
| 
 | |
|                 public AnimalRace(ulong serverId, ITextChannel ch)
 | |
|                 {
 | |
|                     this._log = LogManager.GetCurrentClassLogger();
 | |
|                     this.serverId = serverId;
 | |
|                     this.raceChannel = ch;
 | |
|                     if (!AnimalRaces.TryAdd(serverId, this))
 | |
|                     {
 | |
|                         Fail = true;
 | |
|                         return;
 | |
|                     }
 | |
| 
 | |
|                     using (var uow = DbHandler.UnitOfWork())
 | |
|                     {
 | |
|                         animals = new ConcurrentQueue<string>(NadekoBot.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("Animal Race", $"Starting in 20 seconds or when the room is full.",
 | |
|                                     footer: $"Type {NadekoBot.ModulePrefixes[typeof(Gambling).Name]}jr to join the race.");
 | |
|                             }
 | |
|                             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("Animal Race", "Full! Starting immediately."); } catch (Exception ex) { _log.Warn(ex); }
 | |
|                             }
 | |
|                             else if (participants.Count > 1)
 | |
|                             {
 | |
|                                 try { await raceChannel.SendConfirmAsync("Animal Race", "Starting with " + participants.Count + " participants."); } catch (Exception ex) { _log.Warn(ex); }
 | |
|                             }
 | |
|                             else
 | |
|                             {
 | |
|                                 try { await raceChannel.SendErrorAsync("Animal Race", "Failed to start since there was not enough participants."); } catch (Exception ex) { _log.Warn(ex); }
 | |
|                                 var p = participants.FirstOrDefault();
 | |
| 
 | |
|                                 if (p != null && p.AmountBet > 0)
 | |
|                                     await CurrencyHandler.AddCurrencyAsync(p.User, "BetRace", p.AmountBet, false).ConfigureAwait(false);
 | |
|                                 End();
 | |
|                                 return;
 | |
|                             }
 | |
|                             await Task.Run(StartRace);
 | |
|                             End();
 | |
|                         }
 | |
|                         catch { try { End(); } catch { } }
 | |
|                     });
 | |
|                 }
 | |
| 
 | |
|                 private void End()
 | |
|                 {
 | |
|                     AnimalRace throwaway;
 | |
|                     AnimalRaces.TryRemove(serverId, out throwaway);
 | |
|                 }
 | |
| 
 | |
|                 private async Task StartRace()
 | |
|                 {
 | |
|                     var rng = new NadekoRandom();
 | |
|                     Participant winner = null;
 | |
|                     IUserMessage msg = null;
 | |
|                     int place = 1;
 | |
|                     try
 | |
|                     {
 | |
|                         NadekoBot.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 { }
 | |
|                     finally
 | |
|                     {
 | |
|                         NadekoBot.Client.MessageReceived -= Client_MessageReceived;
 | |
|                     }
 | |
| 
 | |
|                     if (winner.AmountBet > 0)
 | |
|                     {
 | |
|                         var wonAmount = winner.AmountBet * (participants.Count - 1);
 | |
| 
 | |
|                         await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, true).ConfigureAwait(false);
 | |
|                         await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race and {wonAmount}{CurrencySign}!**").ConfigureAwait(false);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race!**").ConfigureAwait(false);
 | |
|                     }
 | |
| 
 | |
|                 }
 | |
| 
 | |
|                 private Task Client_MessageReceived(SocketMessage imsg)
 | |
|                 {
 | |
|                     var msg = imsg as SocketUserMessage;
 | |
|                     if (msg == null)
 | |
|                         return Task.CompletedTask;
 | |
|                     if (msg.IsAuthor() || !(imsg.Channel is ITextChannel) || imsg.Channel != raceChannel)
 | |
|                         return Task.CompletedTask;
 | |
|                     messagesSinceGameStarted++;
 | |
|                     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)
 | |
|                 {
 | |
|                     var animal = "";
 | |
|                     if (!animals.TryDequeue(out animal))
 | |
|                     {
 | |
|                         await raceChannel.SendErrorAsync($"{u.Mention} `There is no running race on this server.`").ConfigureAwait(false);
 | |
|                         return;
 | |
|                     }
 | |
|                     var p = new Participant(u, animal, amount);
 | |
|                     if (participants.Contains(p))
 | |
|                     {
 | |
|                         await raceChannel.SendErrorAsync($"{u.Mention} `You already joined this race.`").ConfigureAwait(false);
 | |
|                         return;
 | |
|                     }
 | |
|                     if (Started)
 | |
|                     {
 | |
|                         await raceChannel.SendErrorAsync($"{u.Mention} `Race is already started`").ConfigureAwait(false);
 | |
|                         return;
 | |
|                     }
 | |
|                     if (amount > 0)
 | |
|                         if (!await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)u, "BetRace", amount, false).ConfigureAwait(false))
 | |
|                         {
 | |
|                             try { await raceChannel.SendErrorAsync($"{u.Mention} You don't have enough {NadekoBot.BotConfig.CurrencyPluralName}.").ConfigureAwait(false); } catch { }
 | |
|                             return;
 | |
|                         }
 | |
|                     participants.Add(p);
 | |
|                     await raceChannel.SendConfirmAsync("Animal Race", $"{u.Mention} **joined as a {p.Animal}" + (amount > 0 ? $" and bet {amount} {CurrencySign}!**" : "**"))
 | |
|                         .ConfigureAwait(false);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             public class Participant
 | |
|             {
 | |
|                 public IGuildUser User { get; set; }
 | |
|                 public string Animal { get; set; }
 | |
|                 public int AmountBet { get; set; }
 | |
| 
 | |
|                 public float Coeff { get; set; }
 | |
|                 public int Total { get; set; }
 | |
| 
 | |
|                 public int Place { get; set; } = 0;
 | |
| 
 | |
|                 public Participant(IGuildUser u, string a, int amount)
 | |
|                 {
 | |
|                     this.User = u;
 | |
|                     this.Animal = a;
 | |
|                     this.AmountBet = amount;
 | |
|                 }
 | |
| 
 | |
|                 public override int GetHashCode() => User.GetHashCode();
 | |
| 
 | |
|                 public override bool Equals(object obj)
 | |
|                 {
 | |
|                     var p = obj as Participant;
 | |
|                     return p == null ?
 | |
|                         false :
 | |
|                         p.User == User;
 | |
|                 }
 | |
| 
 | |
|                 public override string ToString()
 | |
|                 {
 | |
|                     var str = new string('‣', Total) + Animal;
 | |
|                     if (Place == 0)
 | |
|                         return str;
 | |
|                     if (Place == 1)
 | |
|                     {
 | |
|                         return str + "🏆";
 | |
|                     }
 | |
|                     else if (Place == 2)
 | |
|                     {
 | |
|                         return str + "`2nd`";
 | |
|                     }
 | |
|                     else if (Place == 3)
 | |
|                     {
 | |
|                         return str + "`3rd`";
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         return str + $"`{Place}th`";
 | |
|                     }
 | |
| 
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| } |