using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Discord; using Discord.WebSocket; using NadekoBot.Common; using NadekoBot.Common.Collections; using NadekoBot.Extensions; using NadekoBot.Modules.Games.Common; using NadekoBot.Core.Services; using NadekoBot.Core.Services.Database.Models; using NadekoBot.Core.Services.Impl; using Newtonsoft.Json; using NLog; using NadekoBot.Modules.Games.Common.Acrophobia; using NadekoBot.Modules.Games.Common.Connect4; using NadekoBot.Modules.Games.Common.Hangman; using NadekoBot.Modules.Games.Common.Trivia; using NadekoBot.Modules.Games.Common.Nunchi; namespace NadekoBot.Modules.Games.Services { public class GamesService : INService, IUnloadableService { private readonly IBotConfigProvider _bc; public readonly ConcurrentDictionary GirlRatings = new ConcurrentDictionary(); public readonly ImmutableArray EightBallResponses; private readonly Timer _t; private readonly CommandHandler _cmd; private readonly NadekoStrings _strings; private readonly IImagesService _images; private readonly Logger _log; public readonly string TypingArticlesPath = "data/typing_articles2.json"; private readonly CommandHandler _cmdHandler; public List TypingArticles { get; } = new List(); //channelId, game public ConcurrentDictionary AcrophobiaGames { get; } = new ConcurrentDictionary(); public ConcurrentDictionary Connect4Games { get; } = new ConcurrentDictionary(); public ConcurrentDictionary HangmanGames { get; } = new ConcurrentDictionary(); public TermPool TermPool { get; } = new TermPool(); public ConcurrentDictionary RunningTrivias { get; } = new ConcurrentDictionary(); public Dictionary TicTacToeGames { get; } = new Dictionary(); public ConcurrentDictionary RunningContests { get; } = new ConcurrentDictionary(); public ConcurrentDictionary NunchiGames { get; } = new ConcurrentDictionary(); public GamesService(CommandHandler cmd, IBotConfigProvider bc, NadekoBot bot, NadekoStrings strings, IImagesService images, CommandHandler cmdHandler) { _bc = bc; _cmd = cmd; _strings = strings; _images = images; _cmdHandler = cmdHandler; _log = LogManager.GetCurrentClassLogger(); //8ball EightBallResponses = _bc.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToImmutableArray(); //girl ratings _t = new Timer((_) => { GirlRatings.Clear(); }, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1)); //plantpick _cmd.OnMessageNoTrigger += PotentialFlowerGeneration; GenerationChannels = new ConcurrentHashSet(bot .AllGuildConfigs .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId))); try { TypingArticles = JsonConvert.DeserializeObject>(File.ReadAllText(TypingArticlesPath)); } catch (Exception ex) { _log.Warn("Error while loading typing articles {0}", ex.ToString()); TypingArticles = new List(); } } public async Task Unload() { _t.Change(Timeout.Infinite, Timeout.Infinite); _cmd.OnMessageNoTrigger -= PotentialFlowerGeneration; AcrophobiaGames.ForEach(x => x.Value.Dispose()); AcrophobiaGames.Clear(); Connect4Games.ForEach(x => x.Value.Dispose()); Connect4Games.Clear(); HangmanGames.ForEach(x => x.Value.Dispose()); HangmanGames.Clear(); await Task.WhenAll(RunningTrivias.Select(x => x.Value.StopGame())); RunningTrivias.Clear(); TicTacToeGames.Clear(); await Task.WhenAll(RunningContests.Select(x => x.Value.Stop())) .ConfigureAwait(false); RunningContests.Clear(); NunchiGames.ForEach(x => x.Value.Dispose()); NunchiGames.Clear(); } private void DisposeElems(IEnumerable xs) { xs.ForEach(x => x.Dispose()); } public void AddTypingArticle(IUser user, string text) { TypingArticles.Add(new TypingArticle { Title = $"Text added on {DateTime.UtcNow} by {user}", Text = text.SanitizeMentions(), }); File.WriteAllText(TypingArticlesPath, JsonConvert.SerializeObject(TypingArticles)); } public ConcurrentHashSet GenerationChannels { get; } //channelid/message public ConcurrentDictionary> PlantedFlowers { get; } = new ConcurrentDictionary>(); //channelId/last generation public ConcurrentDictionary LastGenerations { get; } = new ConcurrentDictionary(); private ConcurrentDictionary _locks { get; } = new ConcurrentDictionary(); public (string Name, ImmutableArray Data) GetRandomCurrencyImage() { var rng = new NadekoRandom(); return _images.Currency[rng.Next(0, _images.Currency.Length)]; } private string GetText(ITextChannel ch, string key, params object[] rep) => _strings.GetText(key, ch.GuildId, "Games".ToLowerInvariant(), rep); private Task PotentialFlowerGeneration(IUserMessage imsg) { var msg = imsg as SocketUserMessage; if (msg == null || msg.Author.IsBot) return Task.CompletedTask; var channel = imsg.Channel as ITextChannel; if (channel == null) return Task.CompletedTask; if (!GenerationChannels.Contains(channel.Id)) return Task.CompletedTask; var _ = Task.Run(async () => { try { var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue); var rng = new NadekoRandom(); if (DateTime.UtcNow - TimeSpan.FromSeconds(_bc.BotConfig.CurrencyGenerationCooldown) < lastGeneration) //recently generated in this channel, don't generate again return; var num = rng.Next(1, 101) + _bc.BotConfig.CurrencyGenerationChance * 100; if (num > 100 && LastGenerations.TryUpdate(channel.Id, DateTime.UtcNow, lastGeneration)) { var dropAmount = _bc.BotConfig.CurrencyDropAmount; var dropAmountMax = _bc.BotConfig.CurrencyDropAmountMax; if (dropAmountMax != null && dropAmountMax > dropAmount) dropAmount = new NadekoRandom().Next(dropAmount, dropAmountMax.Value + 1); if (dropAmount > 0) { var msgs = new IUserMessage[dropAmount]; var prefix = _cmdHandler.GetPrefix(channel.Guild.Id); var toSend = dropAmount == 1 ? GetText(channel, "curgen_sn", _bc.BotConfig.CurrencySign) + " " + GetText(channel, "pick_sn", prefix) : GetText(channel, "curgen_pl", dropAmount, _bc.BotConfig.CurrencySign) + " " + GetText(channel, "pick_pl", prefix); var file = GetRandomCurrencyImage(); using (var fileStream = file.Data.ToStream()) { var sent = await channel.SendFileAsync( fileStream, file.Name, toSend).ConfigureAwait(false); msgs[0] = sent; } PlantedFlowers.AddOrUpdate(channel.Id, msgs.ToList(), (id, old) => { old.AddRange(msgs); return old; }); } } } catch (Exception ex) { LogManager.GetCurrentClassLogger().Warn(ex); } }); return Task.CompletedTask; } } }