Removed module projects because it can't work like that atm. Commented out package commands.

This commit is contained in:
Master Kwoth
2017-10-15 09:39:46 +02:00
parent 90e71a3a30
commit 696a0eb2a7
180 changed files with 21625 additions and 1058 deletions

View File

@ -0,0 +1,161 @@
using NadekoBot.Common;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing
{
public class AnimalRace : IDisposable
{
public enum Phase
{
WaitingForPlayers,
Running,
Ended,
}
private const int _startingDelayMiliseconds = 20_000;
public Phase CurrentPhase = Phase.WaitingForPlayers;
public event Func<AnimalRace, Task> OnStarted = delegate { return Task.CompletedTask; };
public event Func<AnimalRace, Task> OnStartingFailed = delegate { return Task.CompletedTask; };
public event Func<AnimalRace, Task> OnStateUpdate = delegate { return Task.CompletedTask; };
public event Func<AnimalRace, Task> OnEnded = delegate { return Task.CompletedTask; };
public ImmutableArray<AnimalRacingUser> Users => _users.ToImmutableArray();
public List<AnimalRacingUser> FinishedUsers { get; } = new List<AnimalRacingUser>();
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly HashSet<AnimalRacingUser> _users = new HashSet<AnimalRacingUser>();
private readonly CurrencyService _currency;
private readonly Queue<RaceAnimal> _animalsQueue;
public int MaxUsers { get; }
public AnimalRace(CurrencyService currency, RaceAnimal[] availableAnimals)
{
this._currency = currency;
this._animalsQueue = new Queue<RaceAnimal>(availableAnimals);
this.MaxUsers = availableAnimals.Length;
if (this._animalsQueue.Count == 0)
CurrentPhase = Phase.Ended;
}
public void Initialize() //lame name
{
var _t = Task.Run(async () =>
{
await Task.Delay(_startingDelayMiliseconds).ConfigureAwait(false);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (CurrentPhase != Phase.WaitingForPlayers)
return;
await Start().ConfigureAwait(false);
}
finally { _locker.Release(); }
});
}
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, int bet = 0)
{
if (bet < 0)
throw new ArgumentOutOfRangeException(nameof(bet));
var user = new AnimalRacingUser(userName, userId, bet);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (_users.Count == MaxUsers)
throw new AnimalRaceFullException();
if (CurrentPhase != Phase.WaitingForPlayers)
throw new AlreadyStartedException();
if (!await _currency.RemoveAsync(userId, "BetRace", bet).ConfigureAwait(false))
throw new NotEnoughFundsException();
if (_users.Contains(user))
throw new AlreadyJoinedException();
var animal = _animalsQueue.Dequeue();
user.Animal = animal;
_users.Add(user);
if (_animalsQueue.Count == 0) //start if no more spots left
await Start().ConfigureAwait(false);
return user;
}
finally { _locker.Release(); }
}
private async Task Start()
{
CurrentPhase = Phase.Running;
if (_users.Count <= 1)
{
foreach (var user in _users)
{
if(user.Bet > 0)
await _currency.AddAsync(user.UserId, "Race refund", user.Bet).ConfigureAwait(false);
}
var _sf = OnStartingFailed?.Invoke(this);
CurrentPhase = Phase.Ended;
return;
}
var _ = OnStarted?.Invoke(this);
var _t = Task.Run(async () =>
{
var rng = new NadekoRandom();
while (!_users.All(x => x.Progress >= 60))
{
foreach (var user in _users)
{
user.Progress += rng.Next(1, 11);
if (user.Progress >= 60)
user.Progress = 60;
}
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x))
.Shuffle();
FinishedUsers.AddRange(finished);
var _ignore = OnStateUpdate?.Invoke(this);
await Task.Delay(2500).ConfigureAwait(false);
}
if (FinishedUsers[0].Bet > 0)
await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1))
.ConfigureAwait(false);
var _ended = OnEnded?.Invoke(this);
});
}
public void Dispose()
{
CurrentPhase = Phase.Ended;
OnStarted = null;
OnEnded = null;
OnStartingFailed = null;
OnStateUpdate = null;
_locker.Dispose();
_users.Clear();
}
}
}

View File

@ -0,0 +1,32 @@
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing
{
public class AnimalRacingUser
{
public int Bet { get; }
public string Username { get; }
public ulong UserId { get; }
public RaceAnimal Animal { get; set; }
public int Progress { get; set; }
public AnimalRacingUser(string username, ulong userId, int bet)
{
this.Bet = bet;
this.Username = username;
this.UserId = userId;
}
public override bool Equals(object obj)
{
return obj is AnimalRacingUser x
? x.UserId == this.UserId
: false;
}
public override int GetHashCode()
{
return this.UserId.GetHashCode();
}
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
{
public class AlreadyJoinedException : Exception
{
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
{
public class AlreadyStartedException : Exception
{
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
{
public class AnimalRaceFullException : Exception
{
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
{
public class NotEnoughFundsException : Exception
{
}
}

View File

@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NadekoBot.Common;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Gambling.Common
{
public class Cards
{
private static readonly Dictionary<int, string> cardNames = new Dictionary<int, string>() {
{ 1, "Ace" },
{ 2, "Two" },
{ 3, "Three" },
{ 4, "Four" },
{ 5, "Five" },
{ 6, "Six" },
{ 7, "Seven" },
{ 8, "Eight" },
{ 9, "Nine" },
{ 10, "Ten" },
{ 11, "Jack" },
{ 12, "Queen" },
{ 13, "King" }
};
private static Dictionary<string, Func<List<Card>, bool>> handValues;
public enum CardSuit
{
Spades = 1,
Hearts = 2,
Diamonds = 3,
Clubs = 4
}
public class Card : IComparable
{
public CardSuit Suit { get; }
public int Number { get; }
public string Name
{
get
{
var str = "";
if (Number <= 10 && Number > 1)
{
str += "_" + Number;
}
else {
str += GetName().ToLower();
}
return str + "_of_" + Suit.ToString().ToLower();
}
}
public Card(CardSuit s, int cardNum)
{
this.Suit = s;
this.Number = cardNum;
}
public string GetName() => cardNames[Number];
public override string ToString() => cardNames[Number] + " Of " + Suit;
public int CompareTo(object obj)
{
if (!(obj is Card)) return 0;
var c = (Card)obj;
return this.Number - c.Number;
}
}
private List<Card> cardPool;
public List<Card> CardPool
{
get { return cardPool; }
set { cardPool = value; }
}
/// <summary>
/// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time.
/// </summary>
public Cards()
{
cardPool = new List<Card>(52);
RefillPool();
InitHandValues();
}
/// <summary>
/// Restart the game of blackjack. It will only refill the pool for now. Probably wont be used, unless you want to have only 1 bjg running at one time,
/// then you will restart the same game every time.
/// </summary>
public void Restart() => RefillPool();
/// <summary>
/// Removes all cards from the pool and refills the pool with all of the possible cards. NOTE: I think this is too expensive.
/// We should probably make it so it copies another premade list with all the cards, or something.
/// </summary>
private void RefillPool()
{
cardPool.Clear();
//foreach suit
for (var j = 1; j < 14; j++)
{
// and number
for (var i = 1; i < 5; i++)
{
//generate a card of that suit and number and add it to the pool
// the pool will go from ace of spades,hears,diamonds,clubs all the way to the king of spades. hearts, ...
cardPool.Add(new Card((CardSuit)i, j));
}
}
}
private Random r = new NadekoRandom();
/// <summary>
/// Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the deck is in the default order.
/// </summary>
/// <returns>A card from the pool</returns>
public Card DrawACard()
{
if (CardPool.Count == 0)
Restart();
//you can either do this if your deck is not shuffled
var num = r.Next(0, cardPool.Count);
var c = cardPool[num];
cardPool.RemoveAt(num);
return c;
// if you want to shuffle when you fill, then take the first one
/*
Card c = cardPool[0];
cardPool.RemoveAt(0);
return c;
*/
}
/// <summary>
/// Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard method.
/// </summary>
private void Shuffle()
{
if (cardPool.Count <= 1) return;
var orderedPool = cardPool.Shuffle();
cardPool = cardPool as List<Card> ?? orderedPool.ToList();
}
public override string ToString() => string.Concat(cardPool.Select(c => c.ToString())) + Environment.NewLine;
private static void InitHandValues()
{
Func<List<Card>, bool> hasPair =
cards => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 2) == 1;
Func<List<Card>, bool> isPair =
cards => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 3) == 0
&& hasPair(cards);
Func<List<Card>, bool> isTwoPair =
cards => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 2) == 2;
Func<List<Card>, bool> isStraight =
cards =>
{
if (cards.GroupBy(card => card.Number).Count() != cards.Count())
return false;
var toReturn = (cards.Max(card => (int)card.Number)
- cards.Min(card => (int)card.Number) == 4);
if (toReturn || cards.All(c => c.Number != 1)) return toReturn;
var newCards = cards.Select(c => c.Number == 1 ? new Card(c.Suit, 14) : c);
return (newCards.Max(card => (int)card.Number)
- newCards.Min(card => (int)card.Number) == 4);
};
Func<List<Card>, bool> hasThreeOfKind =
cards => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 3);
Func<List<Card>, bool> isThreeOfKind =
cards => hasThreeOfKind(cards) && !hasPair(cards);
Func<List<Card>, bool> isFlush =
cards => cards.GroupBy(card => card.Suit).Count() == 1;
Func<List<Card>, bool> isFourOfKind =
cards => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 4);
Func<List<Card>, bool> isFullHouse =
cards => hasPair(cards) && hasThreeOfKind(cards);
Func<List<Card>, bool> hasStraightFlush =
cards => isFlush(cards) && isStraight(cards);
Func<List<Card>, bool> isRoyalFlush =
cards => cards.Min(card => card.Number) == 1 &&
cards.Max(card => card.Number) == 13
&& hasStraightFlush(cards);
Func<List<Card>, bool> isStraightFlush =
cards => hasStraightFlush(cards) && !isRoyalFlush(cards);
handValues = new Dictionary<string, Func<List<Card>, bool>>
{
{ "Royal Flush", isRoyalFlush },
{ "Straight Flush", isStraightFlush },
{ "Four Of A Kind", isFourOfKind },
{ "Full House", isFullHouse },
{ "Flush", isFlush },
{ "Straight", isStraight },
{ "Three Of A Kind", isThreeOfKind },
{ "Two Pairs", isTwoPair },
{ "A Pair", isPair }
};
}
public static string GetHandValue(List<Card> cards)
{
if (handValues == null)
InitHandValues();
foreach (var kvp in handValues.Where(x => x.Value(cards)))
{
return kvp.Key;
}
return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetName() ?? cards.Max().GetName());
}
}
}

View File

@ -0,0 +1,12 @@
using Discord;
using Discord.Commands;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common
{
public abstract class CurrencyEvent
{
public abstract Task Stop();
public abstract Task Start(IUserMessage msg, ICommandContext channel);
}
}

View File

@ -0,0 +1,144 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common
{
public class ReactionEvent : CurrencyEvent
{
private readonly ConcurrentHashSet<ulong> _reactionAwardedUsers = new ConcurrentHashSet<ulong>();
private readonly BotConfig _bc;
private readonly Logger _log;
private readonly DiscordSocketClient _client;
private readonly CurrencyService _cs;
private readonly SocketSelfUser _botUser;
private IUserMessage StartingMessage { get; set; }
private CancellationTokenSource Source { get; }
private CancellationToken CancelToken { get; }
private readonly ConcurrentQueue<ulong> _toGiveTo = new ConcurrentQueue<ulong>();
private readonly int _amount;
public ReactionEvent(BotConfig bc, DiscordSocketClient client, CurrencyService cs, int amount)
{
_bc = bc;
_log = LogManager.GetCurrentClassLogger();
_client = client;
_cs = cs;
_botUser = client.CurrentUser;
_amount = amount;
Source = new CancellationTokenSource();
CancelToken = Source.Token;
var _ = Task.Run(async () =>
{
var users = new List<ulong>();
while (!CancelToken.IsCancellationRequested)
{
await Task.Delay(1000).ConfigureAwait(false);
while (_toGiveTo.TryDequeue(out var usrId))
{
users.Add(usrId);
}
if (users.Count > 0)
{
await _cs.AddToManyAsync("Reaction Event", _amount, users.ToArray()).ConfigureAwait(false);
}
users.Clear();
}
}, CancelToken);
}
public override async Task Stop()
{
if (StartingMessage != null)
await StartingMessage.DeleteAsync().ConfigureAwait(false);
if (!Source.IsCancellationRequested)
Source.Cancel();
_client.MessageDeleted -= MessageDeletedEventHandler;
}
private Task MessageDeletedEventHandler(Cacheable<IMessage, ulong> msg, ISocketMessageChannel channel)
{
if (StartingMessage?.Id == msg.Id)
{
_log.Warn("Stopping flower reaction event because message is deleted.");
var __ = Task.Run(Stop);
}
return Task.CompletedTask;
}
public override async Task Start(IUserMessage umsg, ICommandContext context)
{
StartingMessage = umsg;
_client.MessageDeleted += MessageDeletedEventHandler;
IEmote iemote;
if (Emote.TryParse(_bc.CurrencySign, out var emote))
{
iemote = emote;
}
else
iemote = new Emoji(_bc.CurrencySign);
try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); }
catch
{
try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); }
catch
{
try { await StartingMessage.DeleteAsync().ConfigureAwait(false); }
catch { return; }
}
}
using (StartingMessage.OnReaction(_client, (r) =>
{
try
{
if (r.UserId == _botUser.Id)
return;
if (r.Emote.Name == iemote.Name && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _reactionAwardedUsers.Add(r.User.Value.Id))
{
_toGiveTo.Enqueue(r.UserId);
}
}
catch
{
// ignored
}
}))
{
try
{
await Task.Delay(TimeSpan.FromHours(24), CancelToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
if (CancelToken.IsCancellationRequested)
return;
_log.Warn("Stopping flower reaction event because it expired.");
await Stop();
}
}
}
}

View File

@ -0,0 +1,99 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common;
using NadekoBot.Common.Collections;
using NadekoBot.Core.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common.CurrencyEvents
{
public class SneakyEvent : CurrencyEvent
{
public event Action OnEnded;
public string Code { get; private set; } = string.Empty;
private readonly ConcurrentHashSet<ulong> _sneakyGameAwardedUsers = new ConcurrentHashSet<ulong>();
private readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10)
.Concat(Enumerable.Range(65, 26))
.Concat(Enumerable.Range(97, 26))
.Select(x => (char)x)
.ToArray();
private readonly CurrencyService _cs;
private readonly DiscordSocketClient _client;
private readonly IBotConfigProvider _bc;
private readonly int _length;
public SneakyEvent(CurrencyService cs, DiscordSocketClient client,
IBotConfigProvider bc, int len)
{
_cs = cs;
_client = client;
_bc = bc;
_length = len;
}
public override async Task Start(IUserMessage msg, ICommandContext channel)
{
GenerateCode();
//start the event
_client.MessageReceived += SneakyGameMessageReceivedEventHandler;
await _client.SetGameAsync($"type {Code} for " + _bc.BotConfig.CurrencyPluralName)
.ConfigureAwait(false);
await Task.Delay(_length * 1000).ConfigureAwait(false);
await Stop().ConfigureAwait(false);
}
private void GenerateCode()
{
var rng = new NadekoRandom();
for (var i = 0; i < 5; i++)
{
Code += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)];
}
}
public override async Task Stop()
{
try
{
_client.MessageReceived -= SneakyGameMessageReceivedEventHandler;
Code = string.Empty;
_sneakyGameAwardedUsers.Clear();
await _client.SetGameAsync(null).ConfigureAwait(false);
}
catch { }
finally
{
OnEnded?.Invoke();
}
}
private Task SneakyGameMessageReceivedEventHandler(SocketMessage arg)
{
if (arg.Content == Code &&
_sneakyGameAwardedUsers.Add(arg.Author.Id))
{
var _ = Task.Run(async () =>
{
await _cs.AddAsync(arg.Author, "Sneaky Game Event", 100, false)
.ConfigureAwait(false);
try { await arg.DeleteAsync(new RequestOptions() { RetryMode = RetryMode.AlwaysFail }).ConfigureAwait(false); }
catch
{
// ignored
}
});
}
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,45 @@
using NadekoBot.Common;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common.WheelOfFortune
{
public class WheelOfFortune
{
private static readonly NadekoRandom _rng = new NadekoRandom();
private static readonly ImmutableArray<string> _emojis = new string[] {
"⬆",
"↖",
"⬅",
"↙",
"⬇",
"↘",
"➡",
"↗" }.ToImmutableArray();
public static readonly ImmutableArray<float> Multipliers = new float[] {
1.7f,
1.5f,
0.2f,
0.1f,
0.3f,
0.5f,
1.2f,
2.4f,
}.ToImmutableArray();
public int Result { get; }
public string Emoji => _emojis[Result];
public float Multiplier => Multipliers[Result];
public WheelOfFortune()
{
this.Result = _rng.Next(0, 8);
}
}
}