diff --git a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs index f7dcd48a..fec1a2d0 100644 --- a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs @@ -1,167 +1,219 @@ -//using Discord; -//using Discord.Commands; -//using NadekoBot.Classes; -//using NadekoBot.Extensions; -//using NadekoBot.Modules.Permissions.Classes; -//using System; -//using System.Collections.Concurrent; -//using System.Collections.Generic; -//using System.IO; -//using System.Linq; -//using System.Security.Cryptography; -//using System.Threading; -//using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; -////todo rewrite -//namespace NadekoBot.Modules.Games -//{ -// /// -// /// Flower picking/planting idea is given to me by its -// /// inceptor Violent Crumble from Game Developers League discord server -// /// (he has !cookie and !nom) Thanks a lot Violent! -// /// Check out GDL (its a growing gamedev community): -// /// https://discord.gg/0TYNJfCU4De7YIk8 -// /// -// class PlantPick : DiscordCommand -// { +//todo rewrite +namespace NadekoBot.Modules.Games +{ + public partial class Games + { + /// + /// Flower picking/planting idea is given to me by its + /// inceptor Violent Crumble from Game Developers League discord server + /// (he has !cookie and !nom) Thanks a lot Violent! + /// Check out GDL (its a growing gamedev community): + /// https://discord.gg/0TYNJfCU4De7YIk8 + /// + public class PlantPick + { -// private Random rng; -// public PlantPick(DiscordModule module) : base(module) -// { -// NadekoBot.Client.MessageReceived += PotentialFlowerGeneration; -// rng = new Random(); -// } + private Random rng; -// private static readonly ConcurrentDictionary plantpickCooldowns = new ConcurrentDictionary(); + private ConcurrentDictionary generationChannels = new ConcurrentDictionary(); + //channelid/message + private ConcurrentDictionary> plantedFlowers = new ConcurrentDictionary>(); + //channelId/last generation + private ConcurrentDictionary lastGenerations = new ConcurrentDictionary(); -// private async void PotentialFlowerGeneration(object sender, Discord.MessageEventArgs e) -// { -// try -// { -// if (e.Server == null || e.Channel.IsPrivate || e.Message.IsAuthor) -// return; -// var config = Classes.SpecificConfigurations.Default.Of(e.Server.Id); -// var now = DateTime.Now; -// int cd; -// DateTime lastSpawned; -// if (config.GenerateCurrencyChannels.TryGetValue(e.Channel.Id, out cd)) -// if (!plantpickCooldowns.TryGetValue(e.Channel.Id, out lastSpawned) || (lastSpawned + new TimeSpan(0, cd, 0)) < now) -// { -// var rnd = Math.Abs(rng.Next(0,101)); -// if (rnd == 0) -// { -// var msgs = new[] { await e.Channel.SendFile(GetRandomCurrencyImagePath()), await channel.SendMessageAsync($"❗ A random {Gambling.CurrencyName} appeared! Pick it up by typing `>pick`") }; -// plantedFlowerChannels.AddOrUpdate(e.Channel.Id, msgs, (u, m) => { m.ForEach(async msgToDelete => { try { await msgToDelete.Delete(); } catch { } }); return msgs; }); -// plantpickCooldowns.AddOrUpdate(e.Channel.Id, now, (i, d) => now); -// } -// } -// } -// catch { } -// } -// //channelid/messageid pair -// ConcurrentDictionary> plantedFlowerChannels = new ConcurrentDictionary>(); + private float chance; + private int cooldown; -// private SemaphoreSlim locker = new SemaphoreSlim(1,1); + public PlantPick(DiscordModule module) + { + NadekoBot.Client.MessageReceived += PotentialFlowerGeneration; + rng = new Random(); -// public override void Init(CommandGroupBuilder cgb) -// { -// cgb.CreateCommand(Module.Prefix + "pick") -// .Description($"Picks a flower planted in this channel. | `{Prefix}pick`") -// .Do(async e => -// { -// IEnumerable msgs; + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.BotConfig.GetOrCreate(); + var x = + generationChannels = new ConcurrentDictionary(uow.GuildConfigs.GetAll() + .Where(c => c.GenerateCurrencyChannelId != null) + .ToDictionary(c => c.GenerateCurrencyChannelId.Value, c => true)); + chance = conf.CurrencyGenerationChance; + cooldown = conf.CurrencyGenerationCooldown; + } + } -// await e.Message.Delete().ConfigureAwait(false); -// if (!plantedFlowerChannels.TryRemove(e.Channel.Id, out msgs)) -// return; + private Task PotentialFlowerGeneration(IMessage imsg) + { + var msg = imsg as IUserMessage; + if (msg == null) + return Task.CompletedTask; -// foreach(var msgToDelete in msgs) -// await msgToDelete.Delete().ConfigureAwait(false); + var channel = imsg.Channel as ITextChannel; + if (channel == null) + return Task.CompletedTask; -// await CurrencyHandler.AddFlowersAsync(umsg.Author, "Picked a flower.", 1, true).ConfigureAwait(false); -// var msg = await channel.SendMessageAsync($"**{umsg.Author.Username}** picked a {Gambling.CurrencyName}!").ConfigureAwait(false); -// ThreadPool.QueueUserWorkItem(async (state) => -// { -// try -// { -// await Task.Delay(10000).ConfigureAwait(false); -// await msg.Delete().ConfigureAwait(false); -// } -// catch { } -// }); -// }); + bool shouldGenerate; + if (!generationChannels.TryGetValue(channel.Id, out shouldGenerate) || !shouldGenerate) + return Task.CompletedTask; -// cgb.CreateCommand(Module.Prefix + "plant") -// .Description($"Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost) | `{Prefix}plant`") -// .Do(async e => -// { -// await locker.WaitAsync().ConfigureAwait(false); -// try -// { -// if (plantedFlowerChannels.ContainsKey(e.Channel.Id)) -// { -// await channel.SendMessageAsync($"There is already a {Gambling.CurrencyName} in this channel.").ConfigureAwait(false); -// return; -// } -// var removed = await CurrencyHandler.RemoveFlowers(umsg.Author, "Planted a flower.", 1, true).ConfigureAwait(false); -// if (!removed) -// { -// await channel.SendMessageAsync($"You don't have any {Gambling.CurrencyName}s.").ConfigureAwait(false); -// return; -// } + var t = Task.Run(async () => + { + var lastGeneration = lastGenerations.GetOrAdd(channel.Id, DateTime.MinValue); -// var file = GetRandomCurrencyImagePath(); -// Message msg; -// if (file == null) -// msg = await channel.SendMessageAsync(Gambling.CurrencySign).ConfigureAwait(false); -// else -// msg = await e.Channel.SendFile(file).ConfigureAwait(false); -// var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(Gambling.CurrencyName[0]); -// var msg2 = await channel.SendMessageAsync($"Oh how Nice! **{umsg.Author.Username}** planted {(vowelFirst ? "an" : "a")} {Gambling.CurrencyName}. Pick it using {Module.Prefix}pick").ConfigureAwait(false); -// plantedFlowerChannels.TryAdd(e.Channel.Id, new[] { msg, msg2 }); -// } -// finally { locker.Release(); } -// }); + if (DateTime.Now - TimeSpan.FromSeconds(cooldown) < lastGeneration) //recently generated in this channel, don't generate again + return; -// cgb.CreateCommand(Prefix + "gencurrency") -// .Alias(Prefix + "gc") -// .Description($"Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a {Gambling.CurrencyName}. Optional parameter cooldown time in minutes, 5 minutes by default. Requires Manage Messages permission. | `{Prefix}gc` or `{Prefix}gc 60`") -// .AddCheck(SimpleCheckers.ManageMessages()) -// .Parameter("cd", ParameterType.Unparsed) -// .Do(async e => -// { -// var cdStr = cd; -// int cd = 2; -// if (!int.TryParse(cdStr, out cd) || cd < 0) -// { -// cd = 2; -// } -// var config = SpecificConfigurations.Default.Of(e.Server.Id); -// int throwaway; -// if (config.GenerateCurrencyChannels.TryRemove(e.Channel.Id, out throwaway)) -// { -// await channel.SendMessageAsync("`Currency generation disabled on this channel.`").ConfigureAwait(false); -// } -// else -// { -// if (config.GenerateCurrencyChannels.TryAdd(e.Channel.Id, cd)) -// await channel.SendMessageAsync($"`Currency generation enabled on this channel. Cooldown is {cd} minutes.`").ConfigureAwait(false); -// } -// }); -// } + var num = rng.Next(1, 101) + chance * 100; -// private string GetRandomCurrencyImagePath() => -// Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault(); + if (num > 100) + { + lastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now); + //todo get prefix + try + { + var sent = await channel.SendFileAsync( + GetRandomCurrencyImagePath(), + $"❗ A random { Gambling.Gambling.CurrencyName } appeared! Pick it up by typing `>pick`") + .ConfigureAwait(false); + plantedFlowers.AddOrUpdate(channel.Id, new List() { sent }, (id, old) => { old.Add(sent); return old; }); + } + catch { } + + } + }); + return Task.CompletedTask; + } + [LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias] + [RequireContext(ContextType.Guild)] + public async Task Pick(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; -// int GetRandomNumber() -// { -// using (var rg = RandomNumberGenerator.Create()) -// { -// byte[] rno = new byte[4]; -// rg.GetBytes(rno); -// int randomvalue = BitConverter.ToInt32(rno, 0); -// return randomvalue; -// } -// } -// } -//} + if (!channel.Guild.GetCurrentUser().GetPermissions(channel).ManageMessages) + { + await channel.SendMessageAsync("`I need manage channel permissions in order to process this command.`").ConfigureAwait(false); + return; + } + + List msgs; + + await imsg.DeleteAsync().ConfigureAwait(false); + if (!plantedFlowers.TryRemove(channel.Id, out msgs)) + return; + + await Task.WhenAll(msgs.Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false); + + await CurrencyHandler.AddCurrencyAsync((IGuildUser)imsg.Author, "Picked a flower.", 1, false).ConfigureAwait(false); + var msg = await channel.SendMessageAsync($"**{imsg.Author.Username}** picked a {Gambling.Gambling.CurrencyName}!").ConfigureAwait(false); + var t = Task.Run(async () => + { + try + { + await Task.Delay(10000).ConfigureAwait(false); + await msg.DeleteAsync().ConfigureAwait(false); + } + catch { } + }); + } + + [LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias] + [RequireContext(ContextType.Guild)] + public async Task Plant(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + if (channel == null) + return; + + var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)imsg.Author, "Planted a flower.", 1, true).ConfigureAwait(false); + if (!removed) + { + await channel.SendMessageAsync($"You don't have any {Gambling.Gambling.CurrencyName}s.").ConfigureAwait(false); + return; + } + + var file = GetRandomCurrencyImagePath(); + IUserMessage msg; + var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(Gambling.Gambling.CurrencyName[0]); + var msgToSend = $"Oh how Nice! **{imsg.Author.Username}** planted {(vowelFirst ? "an" : "a")} {Gambling.Gambling.CurrencyName}. Pick it using >pick"; + if (file == null) + { + msg = await channel.SendMessageAsync(Gambling.Gambling.CurrencySign).ConfigureAwait(false); + } + else + { + //todo add prefix + msg = await channel.SendFileAsync(file, msgToSend).ConfigureAwait(false); + } + plantedFlowers.AddOrUpdate(channel.Id, new List() { msg }, (id, old) => { old.Add(msg); return old; }); + } + + [LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task Gencurrency(IUserMessage imsg) + { + var channel = imsg.Channel as ITextChannel; + if (channel == null) + return; + + bool enabled; + using (var uow = DbHandler.UnitOfWork()) + { + var guildConfig = uow.GuildConfigs.For(channel.Id); + + if (guildConfig.GenerateCurrencyChannelId == null) + { + guildConfig.GenerateCurrencyChannelId = channel.Id; + generationChannels.TryAdd(channel.Id, true); + enabled = true; + } + else + { + guildConfig.GenerateCurrencyChannelId = null; + bool throwaway; + generationChannels.TryRemove(channel.Id, out throwaway); + enabled = false; + } + + } + if (enabled) + { + await channel.SendMessageAsync("`Currency generation disabled on this channel.`").ConfigureAwait(false); + } + else + { + await channel.SendMessageAsync($"`Currency generation enabled on this channel.`").ConfigureAwait(false); + } + } + + private string GetRandomCurrencyImagePath() => + Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault(); + + int GetRandomNumber() + { + using (var rg = RandomNumberGenerator.Create()) + { + byte[] rno = new byte[4]; + rg.GetBytes(rno); + int randomvalue = BitConverter.ToInt32(rno, 0); + return randomvalue; + } + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Database/Models/BotConfig.cs b/src/NadekoBot/Services/Database/Models/BotConfig.cs index 6bb02fb3..25beddb7 100644 --- a/src/NadekoBot/Services/Database/Models/BotConfig.cs +++ b/src/NadekoBot/Services/Database/Models/BotConfig.cs @@ -15,6 +15,9 @@ namespace NadekoBot.Services.Database.Models public bool ForwardMessages { get; set; } = true; public bool ForwardToAllOwners { get; set; } = true; + public float CurrencyGenerationChance { get; set; } = 0.1f; + public int CurrencyGenerationCooldown { get; set; } = 10; + public List ModulePrefixes { get; set; } = new List() { new ModulePrefix() { ModuleName="Administration", Prefix="." }, diff --git a/src/NadekoBot/Services/Database/Models/GuildConfig.cs b/src/NadekoBot/Services/Database/Models/GuildConfig.cs index c07652e4..ada21e94 100644 --- a/src/NadekoBot/Services/Database/Models/GuildConfig.cs +++ b/src/NadekoBot/Services/Database/Models/GuildConfig.cs @@ -38,5 +38,8 @@ namespace NadekoBot.Services.Database.Models //stream notifications public List FollowedStreams { get; set; } = new List(); + + //currencyGeneration + public ulong? GenerateCurrencyChannelId { get; set; } } }