Images service almost done. Slots images moved to /data/images/slots currency_images renamed to currency and moved to data/images/
							
								
								
									
										82
									
								
								src/NadekoBot/DataStructures/DisposableImutableList.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,82 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.DataStructures
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public static class DisposableReadOnlyListExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IDisposableReadOnlyList<T> AsDisposable<T>(this IReadOnlyList<T> arr) where T : IDisposable
 | 
			
		||||
            => new DisposableReadOnlyList<T>(arr);
 | 
			
		||||
 | 
			
		||||
        public static IDisposableReadOnlyList<KeyValuePair<TKey, TValue>> AsDisposable<TKey, TValue>(this IReadOnlyList<KeyValuePair<TKey, TValue>> arr) where TValue : IDisposable
 | 
			
		||||
            => new DisposableReadOnlyList<TKey, TValue>(arr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface IDisposableReadOnlyList<T> : IReadOnlyList<T>, IDisposable
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class DisposableReadOnlyList<T> : IDisposableReadOnlyList<T>
 | 
			
		||||
        where T : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IReadOnlyList<T> _arr;
 | 
			
		||||
 | 
			
		||||
        public int Count => _arr.Count;
 | 
			
		||||
 | 
			
		||||
        public T this[int index] => _arr[index];
 | 
			
		||||
 | 
			
		||||
        public DisposableReadOnlyList(IReadOnlyList<T> arr)
 | 
			
		||||
        {
 | 
			
		||||
            this._arr = arr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerator<T> GetEnumerator()
 | 
			
		||||
            => _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        IEnumerator IEnumerable.GetEnumerator()
 | 
			
		||||
            => _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in _arr)
 | 
			
		||||
            {
 | 
			
		||||
                item.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class DisposableReadOnlyList<T, U> : IDisposableReadOnlyList<KeyValuePair<T, U>>
 | 
			
		||||
        where U : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IReadOnlyList<KeyValuePair<T, U>> _arr;
 | 
			
		||||
 | 
			
		||||
        public int Count => _arr.Count;
 | 
			
		||||
 | 
			
		||||
        KeyValuePair<T, U> IReadOnlyList<KeyValuePair<T, U>>.this[int index] => _arr[index];
 | 
			
		||||
 | 
			
		||||
        public DisposableReadOnlyList(IReadOnlyList<KeyValuePair<T, U>> arr)
 | 
			
		||||
        {
 | 
			
		||||
            this._arr = arr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerator<KeyValuePair<T, U>> GetEnumerator() =>
 | 
			
		||||
            _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        IEnumerator IEnumerable.GetEnumerator() =>
 | 
			
		||||
            _arr.GetEnumerator();
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in _arr)
 | 
			
		||||
            {
 | 
			
		||||
                item.Value.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -173,10 +173,8 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task ReloadImages()
 | 
			
		||||
            {
 | 
			
		||||
                var sw = Stopwatch.StartNew();
 | 
			
		||||
                await NadekoBot.Images.Reload().ConfigureAwait(false);
 | 
			
		||||
                sw.Stop();
 | 
			
		||||
                await Context.Channel.SendConfirmAsync("Images Reloaded").ConfigureAwait(false);
 | 
			
		||||
                var time = await NadekoBot.Images.Reload().ConfigureAwait(false);
 | 
			
		||||
                await Context.Channel.SendConfirmAsync($"Images loaded after {time.TotalSeconds:F3}s!").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)
 | 
			
		||||
 
 | 
			
		||||
@@ -33,16 +33,12 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                var num1 = gen / 10;
 | 
			
		||||
                var num2 = gen % 10;
 | 
			
		||||
                var imageStream = await Task.Run(() =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                {
 | 
			
		||||
                    var ms = new MemoryStream();
 | 
			
		||||
                    new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms);
 | 
			
		||||
                    ms.Position = 0;
 | 
			
		||||
                    return ms;
 | 
			
		||||
                    }
 | 
			
		||||
                    catch { return new MemoryStream(); }
 | 
			
		||||
                });
 | 
			
		||||
                }).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await Context.Channel.SendFileAsync(imageStream, "dice.png", $"{Context.User.Mention} rolled " + Format.Code(gen.ToString())).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -82,7 +78,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                await InternallDndRoll(arg, false).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private async Task InternalRoll( int num, bool ordered)
 | 
			
		||||
            private async Task InternalRoll(int num, bool ordered)
 | 
			
		||||
            {
 | 
			
		||||
                if (num < 1 || num > 30)
 | 
			
		||||
                {
 | 
			
		||||
@@ -209,22 +205,26 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
 | 
			
		||||
            private Image GetDice(int num)
 | 
			
		||||
            {
 | 
			
		||||
                const string pathToImage = "data/images/dice";
 | 
			
		||||
                if (num != 10)
 | 
			
		||||
                {
 | 
			
		||||
                    using (var stream = File.OpenRead(Path.Combine(pathToImage, $"{num}.png")))
 | 
			
		||||
                        return new Image(stream);
 | 
			
		||||
                }
 | 
			
		||||
                if (num < 0 || num > 10)
 | 
			
		||||
                    throw new ArgumentOutOfRangeException(nameof(num));
 | 
			
		||||
 | 
			
		||||
                using (var one = File.OpenRead(Path.Combine(pathToImage, "1.png")))
 | 
			
		||||
                using (var zero = File.OpenRead(Path.Combine(pathToImage, "0.png")))
 | 
			
		||||
                if (num == 10)
 | 
			
		||||
                {
 | 
			
		||||
                    Image imgOne = new Image(one);
 | 
			
		||||
                    Image imgZero = new Image(zero);
 | 
			
		||||
                    var images = NadekoBot.Images.Dice;
 | 
			
		||||
                    using (var imgOneStream = images[1].Value.ToStream())
 | 
			
		||||
                    using (var imgZeroStream = images[0].Value.ToStream())
 | 
			
		||||
                    {
 | 
			
		||||
                        Image imgOne = new Image(imgOneStream);
 | 
			
		||||
                        Image imgZero = new Image(imgZeroStream);
 | 
			
		||||
 | 
			
		||||
                        return new[] { imgOne, imgZero }.Merge();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                using (var die = NadekoBot.Images.Dice[num].Value.ToStream())
 | 
			
		||||
                {
 | 
			
		||||
                    return new Image(die);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,6 +5,7 @@ using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Image = ImageSharp.Image;
 | 
			
		||||
@@ -32,9 +33,19 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                if (count == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    if (rng.Next(0, 2) == 1)
 | 
			
		||||
                        await Context.Channel.SendFileAsync(_images.Heads, "heads.jpg", $"{Context.User.Mention} flipped " + Format.Code("Heads") + ".").ConfigureAwait(false);
 | 
			
		||||
                    {
 | 
			
		||||
                        using (var heads = _images.Heads.ToStream())
 | 
			
		||||
                        {
 | 
			
		||||
                            await Context.Channel.SendFileAsync(heads, "heads.jpg", $"{Context.User.Mention} flipped " + Format.Code("Heads") + ".").ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                        await Context.Channel.SendFileAsync(_images.Tails, "tails.jpg", $"{Context.User.Mention} flipped " + Format.Code("Tails") + ".").ConfigureAwait(false);
 | 
			
		||||
                    {
 | 
			
		||||
                        using (var tails = _images.Tails.ToStream())
 | 
			
		||||
                        {
 | 
			
		||||
                            await Context.Channel.SendFileAsync(tails, "tails.jpg", $"{Context.User.Mention} flipped " + Format.Code("Tails") + ".").ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (count > 10 || count < 1)
 | 
			
		||||
@@ -43,14 +54,18 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var imgs = new Image[count];
 | 
			
		||||
                using (var heads = _images.Heads.ToStream())
 | 
			
		||||
                using(var tails = _images.Tails.ToStream())
 | 
			
		||||
                {
 | 
			
		||||
                    for (var i = 0; i < count; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        imgs[i] = rng.Next(0, 10) < 5 ?
 | 
			
		||||
                                new Image(_images.Heads) :
 | 
			
		||||
                                new Image(_images.Tails);
 | 
			
		||||
                                    new Image(heads) :
 | 
			
		||||
                                    new Image(tails);
 | 
			
		||||
                    }
 | 
			
		||||
                    await Context.Channel.SendFileAsync(imgs.Merge().ToStream(), $"{count} coins.png").ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task Betflip(int amount, string guess)
 | 
			
		||||
@@ -74,9 +89,10 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                //heads = true
 | 
			
		||||
                //tails = false
 | 
			
		||||
 | 
			
		||||
                //todo this seems stinky, no time to look at it right now
 | 
			
		||||
                var isHeads = guessStr == "HEADS" || guessStr == "H";
 | 
			
		||||
                bool result = false;
 | 
			
		||||
                Stream imageToSend;
 | 
			
		||||
                IEnumerable<byte> imageToSend;
 | 
			
		||||
                if (rng.Next(0, 2) == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    imageToSend = _images.Heads;
 | 
			
		||||
@@ -98,8 +114,10 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                {
 | 
			
		||||
                    str = $"{Context.User.Mention}`Better luck next time.`";
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await Context.Channel.SendFileAsync(imageToSend, "result.png", str).ConfigureAwait(false);
 | 
			
		||||
                using (var toSend = imageToSend.ToStream())
 | 
			
		||||
                {
 | 
			
		||||
                    await Context.Channel.SendFileAsync(toSend, "result.png", str).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -21,52 +21,19 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
            private static int totalBet = 0;
 | 
			
		||||
            private static int totalPaidOut = 0;
 | 
			
		||||
 | 
			
		||||
            private const string backgroundPath = "data/slots/background.png";
 | 
			
		||||
 | 
			
		||||
            private static readonly byte[] backgroundBuffer;
 | 
			
		||||
            private static readonly byte[][] numbersBuffer = new byte[10][];
 | 
			
		||||
            private static readonly byte[][] emojiBuffer;
 | 
			
		||||
 | 
			
		||||
            const int alphaCutOut = byte.MaxValue / 3;
 | 
			
		||||
 | 
			
		||||
            static Slots()
 | 
			
		||||
            {
 | 
			
		||||
                backgroundBuffer = File.ReadAllBytes(backgroundPath);
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < 10; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    numbersBuffer[i] = File.ReadAllBytes("data/slots/" + i + ".png");
 | 
			
		||||
                }
 | 
			
		||||
                int throwaway;
 | 
			
		||||
                var emojiFiles = Directory.GetFiles("data/slots/emojis/", "*.png")
 | 
			
		||||
                    .Where(f => int.TryParse(Path.GetFileNameWithoutExtension(f), out throwaway))
 | 
			
		||||
                    .OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
 | 
			
		||||
                    .ToArray();
 | 
			
		||||
 | 
			
		||||
                emojiBuffer = new byte[emojiFiles.Length][];
 | 
			
		||||
                for (int i = 0; i < emojiFiles.Length; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    emojiBuffer[i] = File.ReadAllBytes(emojiFiles[i]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            private static MemoryStream InternalGetStream(string path)
 | 
			
		||||
            {
 | 
			
		||||
                var ms = new MemoryStream();
 | 
			
		||||
                using (var fs = File.Open(path, FileMode.Open))
 | 
			
		||||
                {
 | 
			
		||||
                    fs.CopyTo(ms);
 | 
			
		||||
                    fs.Flush();
 | 
			
		||||
                }
 | 
			
		||||
                ms.Position = 0;
 | 
			
		||||
                return ms;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //here is a payout chart
 | 
			
		||||
            //https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
 | 
			
		||||
            //thanks to judge for helping me with this
 | 
			
		||||
 | 
			
		||||
            private readonly IImagesService _images;
 | 
			
		||||
 | 
			
		||||
            public Slots()
 | 
			
		||||
            {
 | 
			
		||||
                this._images = NadekoBot.Images;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public class SlotMachine
 | 
			
		||||
            {
 | 
			
		||||
                public const int MaxValue = 5;
 | 
			
		||||
@@ -154,7 +121,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                var sb = new StringBuilder();
 | 
			
		||||
                const int bet = 1;
 | 
			
		||||
                int payout = 0;
 | 
			
		||||
                foreach (var key in dict.Keys.OrderByDescending(x=>x))
 | 
			
		||||
                foreach (var key in dict.Keys.OrderByDescending(x => x))
 | 
			
		||||
                {
 | 
			
		||||
                    sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
 | 
			
		||||
                    payout += key * dict[key];
 | 
			
		||||
@@ -164,6 +131,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            static HashSet<ulong> runningUsers = new HashSet<ulong>();
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task Slot(int amount = 0)
 | 
			
		||||
            {
 | 
			
		||||
@@ -189,7 +157,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    Interlocked.Add(ref totalBet, amount);
 | 
			
		||||
                    using (var bgFileStream = new MemoryStream(backgroundBuffer))
 | 
			
		||||
                    using (var bgFileStream = NadekoBot.Images.SlotBackground.ToStream())
 | 
			
		||||
                    {
 | 
			
		||||
                        var bgImage = new ImageSharp.Image(bgFileStream);
 | 
			
		||||
 | 
			
		||||
@@ -199,7 +167,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                        {
 | 
			
		||||
                            for (int i = 0; i < 3; i++)
 | 
			
		||||
                            {
 | 
			
		||||
                                using (var file = new MemoryStream(emojiBuffer[numbers[i]]))
 | 
			
		||||
                                using (var file = _images.SlotEmojis[numbers[i]].ToStream())
 | 
			
		||||
                                {
 | 
			
		||||
                                    var randomImage = new ImageSharp.Image(file);
 | 
			
		||||
                                    using (var toAdd = randomImage.Lock())
 | 
			
		||||
@@ -226,7 +194,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                            do
 | 
			
		||||
                            {
 | 
			
		||||
                                var digit = printWon % 10;
 | 
			
		||||
                                using (var fs = new MemoryStream(numbersBuffer[digit]))
 | 
			
		||||
                                using (var fs = NadekoBot.Images.SlotNumbers[digit].ToStream())
 | 
			
		||||
                                {
 | 
			
		||||
                                    var img = new ImageSharp.Image(fs);
 | 
			
		||||
                                    using (var pixels = img.Lock())
 | 
			
		||||
@@ -251,7 +219,7 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
                            do
 | 
			
		||||
                            {
 | 
			
		||||
                                var digit = printAmount % 10;
 | 
			
		||||
                                using (var fs = new MemoryStream(numbersBuffer[digit]))
 | 
			
		||||
                                using (var fs = _images.SlotNumbers[digit].ToStream())
 | 
			
		||||
                                {
 | 
			
		||||
                                    var img = new ImageSharp.Image(fs);
 | 
			
		||||
                                    using (var pixels = img.Lock())
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
@@ -97,13 +98,16 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                                    firstPart = $"{dropAmount} random { NadekoBot.BotConfig.CurrencyPluralName } appeared!";
 | 
			
		||||
                                }
 | 
			
		||||
                                var file = GetRandomCurrencyImage();
 | 
			
		||||
                                using (var fileStream = file.Value.ToStream())
 | 
			
		||||
                                {
 | 
			
		||||
                                    var sent = await channel.SendFileAsync(
 | 
			
		||||
                                    file.Item2,
 | 
			
		||||
                                    file.Item1,
 | 
			
		||||
                                        fileStream,
 | 
			
		||||
                                        file.Key,
 | 
			
		||||
                                        $"❗ {firstPart} Pick it up by typing `{NadekoBot.ModulePrefixes[typeof(Games).Name]}pick`")
 | 
			
		||||
                                            .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                                    msgs[0] = sent;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                plantedFlowers.AddOrUpdate(channel.Id, msgs.ToList(), (id, old) => { old.AddRange(msgs); return old; });
 | 
			
		||||
                            }
 | 
			
		||||
@@ -167,18 +171,15 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var file = GetRandomCurrencyImage();
 | 
			
		||||
                IUserMessage msg;
 | 
			
		||||
                var imgData = GetRandomCurrencyImage();
 | 
			
		||||
                var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.BotConfig.CurrencyName[0]);
 | 
			
		||||
 | 
			
		||||
                var msgToSend = $"Oh how Nice! **{Context.User.Username}** planted {(amount == 1 ? (vowelFirst ? "an" : "a") : amount.ToString())} {(amount > 1 ? NadekoBot.BotConfig.CurrencyPluralName : NadekoBot.BotConfig.CurrencyName)}. Pick it using {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick";
 | 
			
		||||
                if (file == null)
 | 
			
		||||
 | 
			
		||||
                IUserMessage msg;
 | 
			
		||||
                using (var toSend = imgData.Value.ToStream())
 | 
			
		||||
                {
 | 
			
		||||
                    msg = await Context.Channel.SendConfirmAsync(NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    msg = await Context.Channel.SendFileAsync(file.Item2, file.Item1, msgToSend).ConfigureAwait(false);
 | 
			
		||||
                    msg = await Context.Channel.SendFileAsync(toSend, imgData.Key, msgToSend).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var msgs = new IUserMessage[amount];
 | 
			
		||||
@@ -228,12 +229,12 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static Tuple<string, Stream> GetRandomCurrencyImage()
 | 
			
		||||
            private static KeyValuePair<string, ImmutableArray<byte>> GetRandomCurrencyImage()
 | 
			
		||||
            {
 | 
			
		||||
                var rng = new NadekoRandom();
 | 
			
		||||
                var images = NadekoBot.Images.Currency;
 | 
			
		||||
 | 
			
		||||
                return images[rng.Next(0, images.Count)];
 | 
			
		||||
                return images[rng.Next(0, images.Length)];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            int GetRandomNumber()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using System;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.IO;
 | 
			
		||||
@@ -10,12 +11,16 @@ namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public interface IImagesService
 | 
			
		||||
    {
 | 
			
		||||
        Stream Heads { get; }
 | 
			
		||||
        Stream Tails { get; }
 | 
			
		||||
        ImmutableArray<byte> Heads { get; }
 | 
			
		||||
        ImmutableArray<byte> Tails { get; }
 | 
			
		||||
 | 
			
		||||
        IImmutableList<Tuple<string, Stream>> Currency { get; }
 | 
			
		||||
        IImmutableList<Tuple<string, Stream>> Dice { get; }
 | 
			
		||||
        ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Currency { get; }
 | 
			
		||||
        ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Dice { get; }
 | 
			
		||||
 | 
			
		||||
        Task Reload();
 | 
			
		||||
        ImmutableArray<byte> SlotBackground { get; }
 | 
			
		||||
        ImmutableArray<ImmutableArray<byte>> SlotEmojis { get; }
 | 
			
		||||
        ImmutableArray<ImmutableArray<byte>> SlotNumbers { get; }
 | 
			
		||||
 | 
			
		||||
        Task<TimeSpan> Reload();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using NLog;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
@@ -15,27 +16,30 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
        private const string headsPath = "data/images/coins/heads.png";
 | 
			
		||||
        private const string tailsPath = "data/images/coins/tails.png";
 | 
			
		||||
        private const string basePath = "data/images/";
 | 
			
		||||
 | 
			
		||||
        private const string currencyImagesPath = "data/currency_images";
 | 
			
		||||
        private const string diceImagesPath = "data/images/dice";
 | 
			
		||||
        private const string headsPath = basePath + "coins/heads.png";
 | 
			
		||||
        private const string tailsPath = basePath + "coins/tails.png";
 | 
			
		||||
 | 
			
		||||
        private byte[] heads;
 | 
			
		||||
        public Stream Heads => new MemoryStream(heads, false);
 | 
			
		||||
        private const string currencyImagesPath = basePath + "currency";
 | 
			
		||||
        private const string diceImagesPath = basePath + "dice";
 | 
			
		||||
 | 
			
		||||
        private const string slotBackgroundPath = basePath + "slots/background.png";
 | 
			
		||||
        private const string slotNumbersPath = basePath + "slots/numbers/";
 | 
			
		||||
        private const string slotEmojisPath = basePath + "slots/emojis/";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public ImmutableArray<byte> Heads { get; private set; }
 | 
			
		||||
        public ImmutableArray<byte> Tails { get; private set; }
 | 
			
		||||
 | 
			
		||||
        private byte[] tails;
 | 
			
		||||
        public Stream Tails => new MemoryStream(tails, false);
 | 
			
		||||
        //todo C#7
 | 
			
		||||
        private IReadOnlyDictionary<string, byte[]> currency;
 | 
			
		||||
        public IImmutableList<Tuple<string, Stream>> Currency =>
 | 
			
		||||
            currency.Select(x => new Tuple<string, Stream>(x.Key, new MemoryStream(x.Value, false)))
 | 
			
		||||
                          .ToImmutableArray();
 | 
			
		||||
        public ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Currency { get; private set; }
 | 
			
		||||
 | 
			
		||||
        private IReadOnlyDictionary<string, byte[]> dice;
 | 
			
		||||
        public IImmutableList<Tuple<string, Stream>> Dice =>
 | 
			
		||||
            dice.Select(x => new Tuple<string, Stream>(x.Key, new MemoryStream(x.Value, false)))
 | 
			
		||||
                          .ToImmutableArray();
 | 
			
		||||
        public ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Dice { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public ImmutableArray<byte> SlotBackground { get; private set; }
 | 
			
		||||
        public ImmutableArray<ImmutableArray<byte>> SlotNumbers { get; private set; }
 | 
			
		||||
        public ImmutableArray<ImmutableArray<byte>> SlotEmojis { get; private set; }
 | 
			
		||||
 | 
			
		||||
        private ImagesService()
 | 
			
		||||
        {
 | 
			
		||||
@@ -49,18 +53,41 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
            return srvc;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task Reload() => Task.Run(() =>
 | 
			
		||||
        public Task<TimeSpan> Reload() => Task.Run(() =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                _log.Info("Loading images...");
 | 
			
		||||
                var sw = Stopwatch.StartNew();
 | 
			
		||||
                heads = File.ReadAllBytes(headsPath);
 | 
			
		||||
                tails = File.ReadAllBytes(tailsPath);
 | 
			
		||||
                Heads = File.ReadAllBytes(headsPath).ToImmutableArray();
 | 
			
		||||
                Tails = File.ReadAllBytes(tailsPath).ToImmutableArray();
 | 
			
		||||
 | 
			
		||||
                currency = Directory.GetFiles(currencyImagesPath).ToDictionary(x => Path.GetFileName(x), x => File.ReadAllBytes(x));
 | 
			
		||||
                dice = Directory.GetFiles(diceImagesPath).ToDictionary(x => Path.GetFileName(x), x => File.ReadAllBytes(x));
 | 
			
		||||
                Currency = Directory.GetFiles(currencyImagesPath)
 | 
			
		||||
                    .Select(x => new KeyValuePair<string, ImmutableArray<byte>>(
 | 
			
		||||
                                        Path.GetFileName(x), 
 | 
			
		||||
                                        File.ReadAllBytes(x).ToImmutableArray()))
 | 
			
		||||
                    .ToImmutableArray();
 | 
			
		||||
 | 
			
		||||
                Dice = Directory.GetFiles(diceImagesPath)
 | 
			
		||||
                                .OrderBy(x => int.Parse(Path.GetFileNameWithoutExtension(x)))
 | 
			
		||||
                                .Select(x => new KeyValuePair<string, ImmutableArray<byte>>(x, 
 | 
			
		||||
                                                    File.ReadAllBytes(x).ToImmutableArray()))
 | 
			
		||||
                                .ToImmutableArray();
 | 
			
		||||
                
 | 
			
		||||
                SlotBackground = File.ReadAllBytes(slotBackgroundPath).ToImmutableArray();
 | 
			
		||||
 | 
			
		||||
                SlotNumbers = Directory.GetFiles(slotNumbersPath)
 | 
			
		||||
                    .OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
 | 
			
		||||
                    .Select(x => File.ReadAllBytes(x).ToImmutableArray())
 | 
			
		||||
                    .ToImmutableArray();
 | 
			
		||||
 | 
			
		||||
                SlotEmojis = Directory.GetFiles(slotEmojisPath)
 | 
			
		||||
                    .Select(x => File.ReadAllBytes(x).ToImmutableArray())
 | 
			
		||||
                    .ToImmutableArray();
 | 
			
		||||
 | 
			
		||||
                sw.Stop();
 | 
			
		||||
                _log.Info($"Images loaded after {sw.Elapsed.TotalSeconds:F2}s!");
 | 
			
		||||
                return sw.Elapsed;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,9 @@ namespace NadekoBot.Extensions
 | 
			
		||||
        private const string arrow_left = "⬅";
 | 
			
		||||
        private const string arrow_right = "➡";
 | 
			
		||||
 | 
			
		||||
        public static Stream ToStream(this IEnumerable<byte> bytes, bool canWrite = false)
 | 
			
		||||
            => new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// danny kamisama
 | 
			
		||||
        /// </summary>
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB  | 
| 
		 Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB  | 
| 
		 Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB  | 
| 
		 Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB  | 
| 
		 Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB  | 
| 
		 Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB  | 
| 
		 Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB  | 
| 
		 Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB  | 
| 
		 Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB  | 
| 
		 Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB  | 
| 
		 Before Width: | Height: | Size: 551 B After Width: | Height: | Size: 551 B  | 
| 
		 Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B  | 
| 
		 Before Width: | Height: | Size: 553 B After Width: | Height: | Size: 553 B  | 
| 
		 Before Width: | Height: | Size: 539 B After Width: | Height: | Size: 539 B  | 
| 
		 Before Width: | Height: | Size: 471 B After Width: | Height: | Size: 471 B  | 
| 
		 Before Width: | Height: | Size: 542 B After Width: | Height: | Size: 542 B  | 
| 
		 Before Width: | Height: | Size: 562 B After Width: | Height: | Size: 562 B  | 
| 
		 Before Width: | Height: | Size: 417 B After Width: | Height: | Size: 417 B  | 
| 
		 Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 597 B  | 
| 
		 Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 578 B  |