Removed module projects because it can't work like that atm. Commented out package commands.
This commit is contained in:
172
NadekoBot.Core/Modules/Gambling/AnimalRacingCommands.cs
Normal file
172
NadekoBot.Core/Modules/Gambling/AnimalRacingCommands.cs
Normal file
@ -0,0 +1,172 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class AnimalRacingCommands : NadekoSubmodule<AnimalRaceService>
|
||||
{
|
||||
private readonly IBotConfigProvider _bc;
|
||||
private readonly CurrencyService _cs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public AnimalRacingCommands(IBotConfigProvider bc, CurrencyService cs, DiscordSocketClient client)
|
||||
{
|
||||
_bc = bc;
|
||||
_cs = cs;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
private IUserMessage raceMessage = null;
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Race()
|
||||
{
|
||||
var ar = new AnimalRace(_cs, _bc.BotConfig.RaceAnimals.Shuffle().ToArray());
|
||||
if (!_service.AnimalRaces.TryAdd(Context.Guild.Id, ar))
|
||||
return Context.Channel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_already_started"));
|
||||
|
||||
ar.Initialize();
|
||||
|
||||
var count = 0;
|
||||
Task _client_MessageReceived(SocketMessage arg)
|
||||
{
|
||||
var _ = Task.Run(() => {
|
||||
try
|
||||
{
|
||||
if (arg.Channel.Id == Context.Channel.Id)
|
||||
{
|
||||
if (ar.CurrentPhase == AnimalRace.Phase.Running && ++count % 9 == 0)
|
||||
{
|
||||
raceMessage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task Ar_OnEnded(AnimalRace race)
|
||||
{
|
||||
_client.MessageReceived -= _client_MessageReceived;
|
||||
_service.AnimalRaces.TryRemove(Context.Guild.Id, out _);
|
||||
var winner = race.FinishedUsers[0];
|
||||
if (race.FinishedUsers[0].Bet > 0)
|
||||
{
|
||||
return Context.Channel.SendConfirmAsync(GetText("animal_race"),
|
||||
GetText("animal_race_won_money", Format.Bold(winner.Username),
|
||||
winner.Animal.Icon, (race.FinishedUsers[0].Bet * (race.Users.Length - 1)) + _bc.BotConfig.CurrencySign));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Context.Channel.SendConfirmAsync(GetText("animal_race"),
|
||||
GetText("animal_race_won", Format.Bold(winner.Username), winner.Animal.Icon));
|
||||
}
|
||||
}
|
||||
|
||||
ar.OnStartingFailed += Ar_OnStartingFailed;
|
||||
ar.OnStateUpdate += Ar_OnStateUpdate;
|
||||
ar.OnEnded += Ar_OnEnded;
|
||||
ar.OnStarted += Ar_OnStarted;
|
||||
_client.MessageReceived += _client_MessageReceived;
|
||||
|
||||
return Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting"),
|
||||
footer: GetText("animal_race_join_instr", Prefix));
|
||||
}
|
||||
|
||||
private Task Ar_OnStarted(AnimalRace race)
|
||||
{
|
||||
if(race.Users.Length == race.MaxUsers)
|
||||
return Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full"));
|
||||
else
|
||||
return Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting_with_x", race.Users.Length));
|
||||
}
|
||||
|
||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||
{
|
||||
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
|
||||
{String.Join("\n", race.Users.Select(p =>
|
||||
{
|
||||
var index = race.FinishedUsers.IndexOf(p);
|
||||
var extra = (index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}");
|
||||
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
|
||||
}))}
|
||||
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
|
||||
|
||||
var msg = raceMessage;
|
||||
|
||||
if (msg == null)
|
||||
raceMessage = await Context.Channel.SendConfirmAsync(text)
|
||||
.ConfigureAwait(false);
|
||||
else
|
||||
await msg.ModifyAsync(x => x.Embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("animal_race"))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task Ar_OnStartingFailed(AnimalRace race)
|
||||
{
|
||||
_service.AnimalRaces.TryRemove(Context.Guild.Id, out _);
|
||||
return ReplyErrorLocalized("animal_race_failed");
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task JoinRace(int amount = 0)
|
||||
{
|
||||
if (!_service.AnimalRaces.TryGetValue(Context.Guild.Id, out var ar))
|
||||
{
|
||||
await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var user = await ar.JoinRace(Context.User.Id, Context.User.ToString(), amount)
|
||||
.ConfigureAwait(false);
|
||||
if (amount > 0)
|
||||
await Context.Channel.SendConfirmAsync(GetText("animal_race_join_bet", Context.User.Mention, user.Animal.Icon, amount + _bc.BotConfig.CurrencySign)).ConfigureAwait(false);
|
||||
else
|
||||
await Context.Channel.SendConfirmAsync(GetText("animal_race_join", Context.User.Mention, user.Animal.Icon)).ConfigureAwait(false);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
//ignore if user inputed an invalid amount
|
||||
}
|
||||
catch (AlreadyJoinedException)
|
||||
{
|
||||
// just ignore this
|
||||
}
|
||||
catch (AlreadyStartedException)
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
catch (AnimalRaceFullException)
|
||||
{
|
||||
await Context.Channel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full"))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (NotEnoughFundsException)
|
||||
{
|
||||
await Context.Channel.SendErrorAsync(GetText("not_enough", _bc.BotConfig.CurrencySign)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
{
|
||||
public class AlreadyJoinedException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
{
|
||||
public class AlreadyStartedException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
{
|
||||
public class AnimalRaceFullException : Exception
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
{
|
||||
public class NotEnoughFundsException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
234
NadekoBot.Core/Modules/Gambling/Common/Cards.cs
Normal file
234
NadekoBot.Core/Modules/Gambling/Common/Cards.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
91
NadekoBot.Core/Modules/Gambling/CurrencyEventsCommands.cs
Normal file
91
NadekoBot.Core/Modules/Gambling/CurrencyEventsCommands.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Gambling.Common.CurrencyEvents;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class CurrencyEventsCommands : NadekoSubmodule<CurrencyEventsService>
|
||||
{
|
||||
public enum CurrencyEvent
|
||||
{
|
||||
Reaction,
|
||||
SneakyGameStatus
|
||||
}
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotConfigProvider _bc;
|
||||
private readonly CurrencyService _cs;
|
||||
|
||||
public CurrencyEventsCommands(DiscordSocketClient client, IBotConfigProvider bc, CurrencyService cs)
|
||||
{
|
||||
_client = client;
|
||||
_bc = bc;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task StartEvent(CurrencyEvent e, int arg = -1)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case CurrencyEvent.Reaction:
|
||||
await ReactionEvent(Context, arg).ConfigureAwait(false);
|
||||
break;
|
||||
case CurrencyEvent.SneakyGameStatus:
|
||||
await SneakyGameStatusEvent(Context, arg).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SneakyGameStatusEvent(ICommandContext context, int num)
|
||||
{
|
||||
if (num < 10 || num > 600)
|
||||
num = 60;
|
||||
|
||||
var ev = new SneakyEvent(_cs, _client, _bc, num);
|
||||
if (!await _service.StartSneakyEvent(ev, context.Message, context))
|
||||
return;
|
||||
try
|
||||
{
|
||||
var title = GetText("sneakygamestatus_title");
|
||||
var desc = GetText("sneakygamestatus_desc",
|
||||
Format.Bold(100.ToString()) + _bc.BotConfig.CurrencySign,
|
||||
Format.Bold(num.ToString()));
|
||||
await context.Channel.SendConfirmAsync(title, desc)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReactionEvent(ICommandContext context, int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
amount = 100;
|
||||
|
||||
var title = GetText("reaction_title");
|
||||
var desc = GetText("reaction_desc", _bc.BotConfig.CurrencySign, Format.Bold(amount.ToString()) + _bc.BotConfig.CurrencySign);
|
||||
var footer = GetText("reaction_footer", 24);
|
||||
var re = new ReactionEvent(_bc.BotConfig, _client, _cs, amount);
|
||||
var msg = await context.Channel.SendConfirmAsync(title,
|
||||
desc, footer: footer)
|
||||
.ConfigureAwait(false);
|
||||
await re.Start(msg, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
241
NadekoBot.Core/Modules/Gambling/DiceRollCommands.cs
Normal file
241
NadekoBot.Core/Modules/Gambling/DiceRollCommands.cs
Normal file
@ -0,0 +1,241 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using Image = ImageSharp.Image;
|
||||
using ImageSharp;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class DriceRollCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly Regex dndRegex = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
|
||||
private readonly Regex fudgeRegex = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
|
||||
|
||||
private readonly char[] _fateRolls = { '-', ' ', '+' };
|
||||
private readonly IImagesService _images;
|
||||
|
||||
public DriceRollCommands(IImagesService images)
|
||||
{
|
||||
_images = images;
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Roll()
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
var gen = rng.Next(1, 101);
|
||||
|
||||
var num1 = gen / 10;
|
||||
var num2 = gen % 10;
|
||||
var imageStream = await Task.Run(() =>
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms);
|
||||
ms.Position = 0;
|
||||
return ms;
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await Context.Channel.SendFileAsync(imageStream,
|
||||
"dice.png",
|
||||
Context.User.Mention + " " + GetText("dice_rolled", Format.Code(gen.ToString()))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public enum RollOrderType
|
||||
{
|
||||
Ordered,
|
||||
Unordered
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Roll(int num)
|
||||
{
|
||||
await InternalRoll(num, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Rolluo(int num = 1)
|
||||
{
|
||||
await InternalRoll(num, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Roll(string arg)
|
||||
{
|
||||
await InternallDndRoll(arg, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Rolluo(string arg)
|
||||
{
|
||||
await InternallDndRoll(arg, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task InternalRoll(int num, bool ordered)
|
||||
{
|
||||
if (num < 1 || num > 30)
|
||||
{
|
||||
await ReplyErrorLocalized("dice_invalid_number", 1, 30).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
var dice = new List<Image<Rgba32>>(num);
|
||||
var values = new List<int>(num);
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
var randomNumber = rng.Next(1, 7);
|
||||
var toInsert = dice.Count;
|
||||
if (ordered)
|
||||
{
|
||||
if (randomNumber == 6 || dice.Count == 0)
|
||||
toInsert = 0;
|
||||
else if (randomNumber != 1)
|
||||
for (var j = 0; j < dice.Count; j++)
|
||||
{
|
||||
if (values[j] < randomNumber)
|
||||
{
|
||||
toInsert = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
toInsert = dice.Count;
|
||||
}
|
||||
dice.Insert(toInsert, GetDice(randomNumber));
|
||||
values.Insert(toInsert, randomNumber);
|
||||
}
|
||||
|
||||
var bitmap = dice.Merge();
|
||||
var ms = new MemoryStream();
|
||||
bitmap.SaveAsPng(ms);
|
||||
ms.Position = 0;
|
||||
await Context.Channel.SendFileAsync(ms, "dice.png",
|
||||
Context.User.Mention + " " +
|
||||
GetText("dice_rolled_num", Format.Bold(values.Count.ToString())) +
|
||||
" " + GetText("total_average",
|
||||
Format.Bold(values.Sum().ToString()),
|
||||
Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2")))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task InternallDndRoll(string arg, bool ordered)
|
||||
{
|
||||
Match match;
|
||||
int n1;
|
||||
int n2;
|
||||
if ((match = fudgeRegex.Match(arg)).Length != 0 &&
|
||||
int.TryParse(match.Groups["n1"].ToString(), out n1) &&
|
||||
n1 > 0 && n1 < 500)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
var rolls = new List<char>();
|
||||
|
||||
for (int i = 0; i < n1; i++)
|
||||
{
|
||||
rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
|
||||
}
|
||||
var embed = new EmbedBuilder().WithOkColor().WithDescription(Context.User.Mention + " " + GetText("dice_rolled_num", Format.Bold(n1.ToString())))
|
||||
.AddField(efb => efb.WithName(Format.Bold("Result"))
|
||||
.WithValue(string.Join(" ", rolls.Select(c => Format.Code($"[{c}]")))));
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
else if ((match = dndRegex.Match(arg)).Length != 0)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
if (int.TryParse(match.Groups["n1"].ToString(), out n1) &&
|
||||
int.TryParse(match.Groups["n2"].ToString(), out n2) &&
|
||||
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
|
||||
{
|
||||
var add = 0;
|
||||
var sub = 0;
|
||||
int.TryParse(match.Groups["add"].Value, out add);
|
||||
int.TryParse(match.Groups["sub"].Value, out sub);
|
||||
|
||||
var arr = new int[n1];
|
||||
for (int i = 0; i < n1; i++)
|
||||
{
|
||||
arr[i] = rng.Next(1, n2 + 1);
|
||||
}
|
||||
|
||||
var sum = arr.Sum();
|
||||
var embed = new EmbedBuilder().WithOkColor().WithDescription(Context.User.Mention + " " +GetText("dice_rolled_num", n1) + $"`1 - {n2}`")
|
||||
.AddField(efb => efb.WithName(Format.Bold("Rolls"))
|
||||
.WithValue(string.Join(" ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => Format.Code(x.ToString())))))
|
||||
.AddField(efb => efb.WithName(Format.Bold("Sum"))
|
||||
.WithValue(sum + " + " + add + " - " + sub + " = " + (sum + add - sub)));
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task NRoll([Remainder] string range)
|
||||
{
|
||||
int rolled;
|
||||
if (range.Contains("-"))
|
||||
{
|
||||
var arr = range.Split('-')
|
||||
.Take(2)
|
||||
.Select(int.Parse)
|
||||
.ToArray();
|
||||
if (arr[0] > arr[1])
|
||||
{
|
||||
await ReplyErrorLocalized("second_larger_than_first").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalized("dice_rolled", Format.Bold(rolled.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Image<Rgba32> GetDice(int num)
|
||||
{
|
||||
if (num < 0 || num > 10)
|
||||
throw new ArgumentOutOfRangeException(nameof(num));
|
||||
|
||||
if (num == 10)
|
||||
{
|
||||
var images = _images.Dice;
|
||||
using (var imgOneStream = images[1].ToStream())
|
||||
using (var imgZeroStream = images[0].ToStream())
|
||||
{
|
||||
var imgOne = Image.Load(imgOneStream);
|
||||
var imgZero = Image.Load(imgZeroStream);
|
||||
|
||||
return new[] { imgOne, imgZero }.Merge();
|
||||
}
|
||||
}
|
||||
using (var die = _images.Dice[num].ToStream())
|
||||
{
|
||||
return Image.Load(die);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
106
NadekoBot.Core/Modules/Gambling/DrawCommands.cs
Normal file
106
NadekoBot.Core/Modules/Gambling/DrawCommands.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using Image = ImageSharp.Image;
|
||||
using ImageSharp;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class DrawCommands : NadekoSubmodule
|
||||
{
|
||||
private static readonly ConcurrentDictionary<IGuild, Cards> _allDecks = new ConcurrentDictionary<IGuild, Cards>();
|
||||
private const string _cardsPath = "data/images/cards";
|
||||
|
||||
|
||||
private async Task<(Stream ImageStream, string ToSend)> InternalDraw(int num, ulong? guildId = null)
|
||||
{
|
||||
if (num < 1 || num > 10)
|
||||
throw new ArgumentOutOfRangeException(nameof(num));
|
||||
|
||||
Cards cards = guildId == null ? new Cards() : _allDecks.GetOrAdd(Context.Guild, (s) => new Cards());
|
||||
var images = new List<Image<Rgba32>>();
|
||||
var cardObjects = new List<Cards.Card>();
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
if (cards.CardPool.Count == 0 && i != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ReplyErrorLocalized("no_more_cards").ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
break;
|
||||
}
|
||||
var currentCard = cards.DrawACard();
|
||||
cardObjects.Add(currentCard);
|
||||
using (var stream = File.OpenRead(Path.Combine(_cardsPath, currentCard.ToString().ToLowerInvariant() + ".jpg").Replace(' ', '_')))
|
||||
images.Add(Image.Load(stream));
|
||||
}
|
||||
MemoryStream bitmapStream = new MemoryStream();
|
||||
images.Merge().SaveAsPng(bitmapStream);
|
||||
bitmapStream.Position = 0;
|
||||
|
||||
var toSend = $"{Context.User.Mention}";
|
||||
if (cardObjects.Count == 5)
|
||||
toSend += $" drew `{Cards.GetHandValue(cardObjects)}`";
|
||||
|
||||
return (bitmapStream, toSend);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Draw(int num = 1)
|
||||
{
|
||||
if (num < 1)
|
||||
num = 1;
|
||||
if (num > 10)
|
||||
num = 10;
|
||||
|
||||
var data = await InternalDraw(num, Context.Guild.Id).ConfigureAwait(false);
|
||||
await Context.Channel.SendFileAsync(data.ImageStream, num + " cards.jpg", data.ToSend).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task DrawNew(int num = 1)
|
||||
{
|
||||
if (num < 1)
|
||||
num = 1;
|
||||
if (num > 10)
|
||||
num = 10;
|
||||
|
||||
var data = await InternalDraw(num).ConfigureAwait(false);
|
||||
await Context.Channel.SendFileAsync(data.ImageStream, num + " cards.jpg", data.ToSend).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task DeckShuffle()
|
||||
{
|
||||
//var channel = (ITextChannel)Context.Channel;
|
||||
|
||||
_allDecks.AddOrUpdate(Context.Guild,
|
||||
(g) => new Cards(),
|
||||
(g, c) =>
|
||||
{
|
||||
c.Restart();
|
||||
return c;
|
||||
});
|
||||
|
||||
await ReplyConfirmLocalized("deck_reshuffled").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
133
NadekoBot.Core/Modules/Gambling/FlipCoinCommands.cs
Normal file
133
NadekoBot.Core/Modules/Gambling/FlipCoinCommands.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using Image = ImageSharp.Image;
|
||||
using ImageSharp;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class FlipCoinCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly IImagesService _images;
|
||||
private readonly IBotConfigProvider _bc;
|
||||
private readonly CurrencyService _cs;
|
||||
|
||||
private readonly NadekoRandom rng = new NadekoRandom();
|
||||
|
||||
public FlipCoinCommands(IImagesService images, CurrencyService cs, IBotConfigProvider bc)
|
||||
{
|
||||
_images = images;
|
||||
_bc = bc;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Flip(int count = 1)
|
||||
{
|
||||
if (count == 1)
|
||||
{
|
||||
if (rng.Next(0, 2) == 1)
|
||||
{
|
||||
using (var heads = _images.Heads.ToStream())
|
||||
{
|
||||
await Context.Channel.SendFileAsync(heads, "heads.jpg", Context.User.Mention + " " + GetText("flipped", Format.Bold(GetText("heads")))).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var tails = _images.Tails.ToStream())
|
||||
{
|
||||
await Context.Channel.SendFileAsync(tails, "tails.jpg", Context.User.Mention + " " + GetText("flipped", Format.Bold(GetText("tails")))).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (count > 10 || count < 1)
|
||||
{
|
||||
await ReplyErrorLocalized("flip_invalid", 10).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var imgs = new Image<Rgba32>[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
using (var heads = _images.Heads.ToStream())
|
||||
using (var tails = _images.Tails.ToStream())
|
||||
{
|
||||
if (rng.Next(0, 10) < 5)
|
||||
{
|
||||
imgs[i] = Image.Load(heads);
|
||||
}
|
||||
else
|
||||
{
|
||||
imgs[i] = Image.Load(tails);
|
||||
}
|
||||
}
|
||||
}
|
||||
await Context.Channel.SendFileAsync(imgs.Merge().ToStream(), $"{count} coins.png").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public enum BetFlipGuess
|
||||
{
|
||||
H = 1,
|
||||
Head = 1,
|
||||
Heads = 1,
|
||||
T = 2,
|
||||
Tail = 2,
|
||||
Tails = 2
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Betflip(int amount, BetFlipGuess guess)
|
||||
{
|
||||
if (amount < _bc.BotConfig.MinimumBetAmount)
|
||||
{
|
||||
await ReplyErrorLocalized("min_bet_limit", _bc.BotConfig.MinimumBetAmount + _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var removed = await _cs.RemoveAsync(Context.User, "Betflip Gamble", amount, false).ConfigureAwait(false);
|
||||
if (!removed)
|
||||
{
|
||||
await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencyPluralName).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
BetFlipGuess result;
|
||||
IEnumerable<byte> imageToSend;
|
||||
if (rng.Next(0, 2) == 1)
|
||||
{
|
||||
imageToSend = _images.Heads;
|
||||
result = BetFlipGuess.Heads;
|
||||
}
|
||||
else
|
||||
{
|
||||
imageToSend = _images.Tails;
|
||||
result = BetFlipGuess.Tails;
|
||||
}
|
||||
|
||||
string str;
|
||||
if (guess == result)
|
||||
{
|
||||
var toWin = (int)Math.Round(amount * _bc.BotConfig.BetflipMultiplier);
|
||||
str = Context.User.Mention + " " + GetText("flip_guess", toWin + _bc.BotConfig.CurrencySign);
|
||||
await _cs.AddAsync(Context.User, "Betflip Gamble", toWin, false).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
str = Context.User.Mention + " " + GetText("better_luck");
|
||||
}
|
||||
using (var toSend = imageToSend.ToStream())
|
||||
{
|
||||
await Context.Channel.SendFileAsync(toSend, "result.png", str).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
366
NadekoBot.Core/Modules/Gambling/FlowerShopCommands.cs
Normal file
366
NadekoBot.Core/Modules/Gambling/FlowerShopCommands.cs
Normal file
@ -0,0 +1,366 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Common.Collections;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class FlowerShopCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly IBotConfigProvider _bc;
|
||||
private readonly DbService _db;
|
||||
private readonly CurrencyService _cs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public enum Role
|
||||
{
|
||||
Role
|
||||
}
|
||||
|
||||
public enum List
|
||||
{
|
||||
List
|
||||
}
|
||||
|
||||
public FlowerShopCommands(IBotConfigProvider bc, DbService db, CurrencyService cs, DiscordSocketClient client)
|
||||
{
|
||||
_db = db;
|
||||
_bc = bc;
|
||||
_cs = cs;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Shop(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
List<ShopEntry> entries;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
entries = new IndexedCollection<ShopEntry>(uow.GuildConfigs.For(Context.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries);
|
||||
}
|
||||
|
||||
await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
|
||||
{
|
||||
var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray();
|
||||
|
||||
if (!theseEntries.Any())
|
||||
return new EmbedBuilder().WithErrorColor()
|
||||
.WithDescription(GetText("shop_none"));
|
||||
var embed = new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("shop", _bc.BotConfig.CurrencySign));
|
||||
|
||||
for (int i = 0; i < theseEntries.Length; i++)
|
||||
{
|
||||
var entry = theseEntries[i];
|
||||
embed.AddField(efb => efb.WithName($"#{curPage * 9 + i + 1} - {entry.Price}{_bc.BotConfig.CurrencySign}").WithValue(EntryToString(entry)).WithIsInline(true));
|
||||
}
|
||||
return embed;
|
||||
}, entries.Count / 9, true);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Buy(int index, [Remainder]string message = null)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
ShopEntry entry;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set
|
||||
.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items));
|
||||
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
|
||||
entry = entries.ElementAtOrDefault(index);
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
await ReplyErrorLocalized("shop_item_not_found").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
{
|
||||
var guser = (IGuildUser)Context.User;
|
||||
var role = Context.Guild.GetRole(entry.RoleId);
|
||||
|
||||
if (role == null)
|
||||
{
|
||||
await ReplyErrorLocalized("shop_role_not_found").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (await _cs.RemoveAsync(Context.User.Id, $"Shop purchase - {entry.Type}", entry.Price))
|
||||
{
|
||||
try
|
||||
{
|
||||
await guser.AddRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
await _cs.AddAsync(Context.User.Id, $"Shop error refund", entry.Price);
|
||||
await ReplyErrorLocalized("shop_role_purchase_error").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", GetProfitAmount(entry.Price));
|
||||
await ReplyConfirmLocalized("shop_role_purchase", Format.Bold(role.Name)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (entry.Type == ShopEntryType.List)
|
||||
{
|
||||
if (entry.Items.Count == 0)
|
||||
{
|
||||
await ReplyErrorLocalized("out_of_stock").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var item = entry.Items.ToArray()[new NadekoRandom().Next(0, entry.Items.Count)];
|
||||
|
||||
if (await _cs.RemoveAsync(Context.User.Id, $"Shop purchase - {entry.Type}", entry.Price))
|
||||
{
|
||||
int removed;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var x = uow._context.Set<ShopEntryItem>().Remove(item);
|
||||
|
||||
removed = uow.Complete();
|
||||
}
|
||||
try
|
||||
{
|
||||
await (await Context.User.GetOrCreateDMChannelAsync())
|
||||
.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("shop_purchase", Context.Guild.Name))
|
||||
.AddField(efb => efb.WithName(GetText("item")).WithValue(item.Text).WithIsInline(false))
|
||||
.AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("name")).WithValue(entry.Name).WithIsInline(true)))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await _cs.AddAsync(entry.AuthorId,
|
||||
$"Shop sell item - {entry.Name}",
|
||||
GetProfitAmount(entry.Price)).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow._context.Set<ShopEntryItem>().Add(item);
|
||||
uow.Complete();
|
||||
|
||||
await _cs.AddAsync(Context.User.Id,
|
||||
$"Shop error refund - {entry.Name}",
|
||||
entry.Price,
|
||||
uow).ConfigureAwait(false);
|
||||
}
|
||||
await ReplyErrorLocalized("shop_buy_error").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await ReplyConfirmLocalized("shop_item_purchase").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private long GetProfitAmount(int price) =>
|
||||
(int)(Math.Ceiling(0.90 * price));
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
[RequireBotPermission(GuildPermission.ManageRoles)]
|
||||
public async Task ShopAdd(Role _, int price, [Remainder] IRole role)
|
||||
{
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = "-",
|
||||
Price = price,
|
||||
Type = ShopEntryType.Role,
|
||||
AuthorId = Context.User.Id,
|
||||
RoleId = role.Id,
|
||||
RoleName = role.Name
|
||||
};
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigs.For(Context.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries)
|
||||
{
|
||||
entry
|
||||
};
|
||||
uow.GuildConfigs.For(Context.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.Complete();
|
||||
}
|
||||
await Context.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText("shop_item_add")));
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
public async Task ShopAdd(List _, int price, [Remainder]string name)
|
||||
{
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = name.TrimTo(100),
|
||||
Price = price,
|
||||
Type = ShopEntryType.List,
|
||||
AuthorId = Context.User.Id,
|
||||
Items = new HashSet<ShopEntryItem>(),
|
||||
};
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigs.For(Context.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries)
|
||||
{
|
||||
entry
|
||||
};
|
||||
uow.GuildConfigs.For(Context.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.Complete();
|
||||
}
|
||||
await Context.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText("shop_item_add")));
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
public async Task ShopListAdd(int index, [Remainder] string itemText)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
var item = new ShopEntryItem()
|
||||
{
|
||||
Text = itemText
|
||||
};
|
||||
ShopEntry entry;
|
||||
bool rightType = false;
|
||||
bool added = false;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigs.For(Context.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries);
|
||||
entry = entries.ElementAtOrDefault(index);
|
||||
if (entry != null && (rightType = (entry.Type == ShopEntryType.List)))
|
||||
{
|
||||
if (added = entry.Items.Add(item))
|
||||
{
|
||||
uow.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entry == null)
|
||||
await ReplyErrorLocalized("shop_item_not_found").ConfigureAwait(false);
|
||||
else if (!rightType)
|
||||
await ReplyErrorLocalized("shop_item_wrong_type").ConfigureAwait(false);
|
||||
else if (added == false)
|
||||
await ReplyErrorLocalized("shop_list_item_not_unique").ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalized("shop_list_item_added").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
public async Task ShopRemove(int index)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
ShopEntry removed;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set
|
||||
.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items));
|
||||
|
||||
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
|
||||
removed = entries.ElementAtOrDefault(index);
|
||||
if (removed != null)
|
||||
{
|
||||
entries.Remove(removed);
|
||||
|
||||
config.ShopEntries = entries;
|
||||
uow.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
if (removed == null)
|
||||
await ReplyErrorLocalized("shop_item_not_found").ConfigureAwait(false);
|
||||
else
|
||||
await Context.Channel.EmbedAsync(EntryToEmbed(removed)
|
||||
.WithTitle(GetText("shop_item_rm")));
|
||||
}
|
||||
|
||||
public EmbedBuilder EntryToEmbed(ShopEntry entry)
|
||||
{
|
||||
var embed = new EmbedBuilder().WithOkColor();
|
||||
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"))).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("type")).WithValue(entry.Type.ToString()).WithIsInline(true));
|
||||
else if (entry.Type == ShopEntryType.List)
|
||||
return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(entry.Name).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("type")).WithValue(GetText("random_unique_item")).WithIsInline(true));
|
||||
//else if (entry.Type == ShopEntryType.Infinite_List)
|
||||
// return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(entry.RoleName))).WithIsInline(true))
|
||||
// .AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true))
|
||||
// .AddField(efb => efb.WithName(GetText("type")).WithValue(entry.Type.ToString()).WithIsInline(true));
|
||||
else return null;
|
||||
}
|
||||
|
||||
public string EntryToString(ShopEntry entry)
|
||||
{
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
{
|
||||
return GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"));
|
||||
}
|
||||
else if (entry.Type == ShopEntryType.List)
|
||||
{
|
||||
return GetText("unique_items_left", entry.Items.Count) + "\n" + entry.Name;
|
||||
}
|
||||
//else if (entry.Type == ShopEntryType.Infinite_List)
|
||||
//{
|
||||
|
||||
//}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
295
NadekoBot.Core/Modules/Gambling/Gambling.cs
Normal file
295
NadekoBot.Core/Modules/Gambling/Gambling.cs
Normal file
@ -0,0 +1,295 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System.Collections.Generic;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling : NadekoTopLevelModule
|
||||
{
|
||||
private readonly IBotConfigProvider _bc;
|
||||
private readonly DbService _db;
|
||||
private readonly CurrencyService _currency;
|
||||
|
||||
private string CurrencyName => _bc.BotConfig.CurrencyName;
|
||||
private string CurrencyPluralName => _bc.BotConfig.CurrencyPluralName;
|
||||
private string CurrencySign => _bc.BotConfig.CurrencySign;
|
||||
|
||||
public Gambling(IBotConfigProvider bc, DbService db, CurrencyService currency)
|
||||
{
|
||||
_bc = bc;
|
||||
_db = db;
|
||||
_currency = currency;
|
||||
}
|
||||
|
||||
public long GetCurrency(ulong id)
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
return uow.Currency.GetUserCurrency(id);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Raffle([Remainder] IRole role = null)
|
||||
{
|
||||
role = role ?? Context.Guild.EveryoneRole;
|
||||
|
||||
var members = (await role.GetMembersAsync()).Where(u => u.Status != UserStatus.Offline);
|
||||
var membersArray = members as IUser[] ?? members.ToArray();
|
||||
if (membersArray.Length == 0)
|
||||
{
|
||||
|
||||
}
|
||||
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
|
||||
await Context.Channel.SendConfirmAsync("🎟 "+ GetText("raffled_user"), $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Cash([Remainder] IUser user = null)
|
||||
{
|
||||
if(user == null)
|
||||
await ConfirmLocalized("has", Format.Bold(Context.User.ToString()), $"{GetCurrency(Context.User.Id)} {CurrencySign}").ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalized("has", Format.Bold(user.ToString()), $"{GetCurrency(user.Id)} {CurrencySign}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Cash(ulong userId)
|
||||
{
|
||||
await ReplyConfirmLocalized("has", Format.Code(userId.ToString()), $"{GetCurrency(userId)} {CurrencySign}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Give(long amount, [Remainder] IGuildUser receiver)
|
||||
{
|
||||
if (amount <= 0 || Context.User.Id == receiver.Id)
|
||||
return;
|
||||
var success = await _currency.RemoveAsync((IGuildUser)Context.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, false).ConfigureAwait(false);
|
||||
if (!success)
|
||||
{
|
||||
await ReplyErrorLocalized("not_enough", CurrencyPluralName).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await _currency.AddAsync(receiver, $"Gift from {Context.User.Username} ({Context.User.Id}).", amount, true).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalized("gifted", amount + CurrencySign, Format.Bold(receiver.ToString()))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(0)]
|
||||
public Task Award(int amount, [Remainder] IGuildUser usr) =>
|
||||
Award(amount, usr.Id);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[OwnerOnly]
|
||||
[Priority(1)]
|
||||
public async Task Award(int amount, ulong usrId)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
await _currency.AddAsync(usrId, $"Awarded by bot owner. ({Context.User.Username}/{Context.User.Id})", amount).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalized("awarded", amount + CurrencySign, $"<@{usrId}>").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(2)]
|
||||
public async Task Award(int amount, [Remainder] IRole role)
|
||||
{
|
||||
var users = (await Context.Guild.GetUsersAsync())
|
||||
.Where(u => u.GetRoles().Contains(role))
|
||||
.ToList();
|
||||
await Task.WhenAll(users.Select(u => _currency.AddAsync(u.Id,
|
||||
$"Awarded by bot owner to **{role.Name}** role. ({Context.User.Username}/{Context.User.Id})",
|
||||
amount)))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await ReplyConfirmLocalized("mass_award",
|
||||
amount + CurrencySign,
|
||||
Format.Bold(users.Count.ToString()),
|
||||
Format.Bold(role.Name)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task Take(long amount, [Remainder] IGuildUser user)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
if (await _currency.RemoveAsync(user, $"Taken by bot owner.({Context.User.Username}/{Context.User.Id})", amount, true).ConfigureAwait(false))
|
||||
await ReplyConfirmLocalized("take", amount+CurrencySign, Format.Bold(user.ToString())).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyErrorLocalized("take_fail", amount + CurrencySign, Format.Bold(user.ToString()), CurrencyPluralName).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task Take(long amount, [Remainder] ulong usrId)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
if (await _currency.RemoveAsync(usrId, $"Taken by bot owner.({Context.User.Username}/{Context.User.Id})", amount).ConfigureAwait(false))
|
||||
await ReplyConfirmLocalized("take", amount + CurrencySign, $"<@{usrId}>").ConfigureAwait(false);
|
||||
else
|
||||
await ReplyErrorLocalized("take_fail", amount + CurrencySign, Format.Code(usrId.ToString()), CurrencyPluralName).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//[NadekoCommand, Usage, Description, Aliases]
|
||||
//[OwnerOnly]
|
||||
//public Task BrTest(int tests = 1000)
|
||||
//{
|
||||
// var t = Task.Run(async () =>
|
||||
// {
|
||||
// if (tests <= 0)
|
||||
// return;
|
||||
// //multi vs how many times it occured
|
||||
// var dict = new Dictionary<int, int>();
|
||||
// var generator = new NadekoRandom();
|
||||
// for (int i = 0; i < tests; i++)
|
||||
// {
|
||||
// var rng = generator.Next(0, 101);
|
||||
// var mult = 0;
|
||||
// if (rng < 67)
|
||||
// {
|
||||
// mult = 0;
|
||||
// }
|
||||
// else if (rng < 91)
|
||||
// {
|
||||
// mult = 2;
|
||||
// }
|
||||
// else if (rng < 100)
|
||||
// {
|
||||
// mult = 4;
|
||||
// }
|
||||
// else
|
||||
// mult = 10;
|
||||
|
||||
// if (dict.ContainsKey(mult))
|
||||
// dict[mult] += 1;
|
||||
// else
|
||||
// dict.Add(mult, 1);
|
||||
// }
|
||||
|
||||
// var sb = new StringBuilder();
|
||||
// const int bet = 1;
|
||||
// int payout = 0;
|
||||
// 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];
|
||||
// }
|
||||
// try
|
||||
// {
|
||||
// await Context.Channel.SendConfirmAsync("BetRoll Test Results", sb.ToString(),
|
||||
// footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%");
|
||||
// }
|
||||
// catch { }
|
||||
|
||||
// });
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task BetRoll(long amount)
|
||||
{
|
||||
if (amount < 1)
|
||||
return;
|
||||
|
||||
if (!await _currency.RemoveAsync(Context.User, "Betroll Gamble", amount, false).ConfigureAwait(false))
|
||||
{
|
||||
await ReplyErrorLocalized("not_enough", CurrencyPluralName).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var rnd = new NadekoRandom().Next(0, 101);
|
||||
var str = Context.User.Mention + Format.Code(GetText("roll", rnd));
|
||||
if (rnd < 67)
|
||||
{
|
||||
str += GetText("better_luck");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rnd < 91)
|
||||
{
|
||||
str += GetText("br_win", (amount * _bc.BotConfig.Betroll67Multiplier) + CurrencySign, 66);
|
||||
await _currency.AddAsync(Context.User, "Betroll Gamble",
|
||||
(int) (amount * _bc.BotConfig.Betroll67Multiplier), false).ConfigureAwait(false);
|
||||
}
|
||||
else if (rnd < 100)
|
||||
{
|
||||
str += GetText("br_win", (amount * _bc.BotConfig.Betroll91Multiplier) + CurrencySign, 90);
|
||||
await _currency.AddAsync(Context.User, "Betroll Gamble",
|
||||
(int) (amount * _bc.BotConfig.Betroll91Multiplier), false).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
str += GetText("br_win", (amount * _bc.BotConfig.Betroll100Multiplier) + CurrencySign, 100) + " 👑";
|
||||
await _currency.AddAsync(Context.User, "Betroll Gamble",
|
||||
(int) (amount * _bc.BotConfig.Betroll100Multiplier), false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
await Context.Channel.SendConfirmAsync(str).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Leaderboard(int page = 1)
|
||||
{
|
||||
if (page < 1)
|
||||
return;
|
||||
|
||||
List<Currency> richest;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
richest = uow.Currency.GetTopRichest(9, 9 * (page - 1)).ToList();
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithTitle(CurrencySign +
|
||||
" " + GetText("leaderboard"))
|
||||
.WithFooter(efb => efb.WithText(GetText("page", page)));
|
||||
|
||||
if (!richest.Any())
|
||||
{
|
||||
embed.WithDescription(GetText("no_users_found"));
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < richest.Count; i++)
|
||||
{
|
||||
var x = richest[i];
|
||||
var usr = await Context.Guild.GetUserAsync(x.UserId).ConfigureAwait(false);
|
||||
var usrStr = usr == null
|
||||
? x.UserId.ToString()
|
||||
: usr.Username?.TrimTo(20, true);
|
||||
|
||||
var j = i;
|
||||
embed.AddField(efb => efb.WithName("#" + (9 * (page - 1) + j + 1) + " " + usrStr)
|
||||
.WithValue(x.Amount.ToString() + " " + CurrencySign)
|
||||
.WithIsInline(true));
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Core.Services;
|
||||
using System.Collections.Concurrent;
|
||||
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
{
|
||||
public class AnimalRaceService : INService, IUnloadableService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
|
||||
|
||||
public Task Unload()
|
||||
{
|
||||
foreach (var kvp in AnimalRaces)
|
||||
{
|
||||
try { kvp.Value.Dispose(); } catch { }
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.CurrencyEvents;
|
||||
using NadekoBot.Core.Services;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
{
|
||||
public class CurrencyEventsService : INService, IUnloadableService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, List<ReactionEvent>> ReactionEvents { get; }
|
||||
|
||||
public SneakyEvent SneakyEvent { get; private set; } = null;
|
||||
private SemaphoreSlim _sneakyLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
public CurrencyEventsService()
|
||||
{
|
||||
ReactionEvents = new ConcurrentDictionary<ulong, List<ReactionEvent>>();
|
||||
}
|
||||
|
||||
public async Task<bool> StartSneakyEvent(SneakyEvent ev, IUserMessage msg, ICommandContext ctx)
|
||||
{
|
||||
await _sneakyLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (SneakyEvent != null)
|
||||
return false;
|
||||
|
||||
SneakyEvent = ev;
|
||||
ev.OnEnded += () => SneakyEvent = null;
|
||||
var _ = SneakyEvent.Start(msg, ctx).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sneakyLock.Release();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task Unload()
|
||||
{
|
||||
foreach (var kvp in ReactionEvents)
|
||||
{
|
||||
foreach (var ev in kvp.Value)
|
||||
{
|
||||
try { await ev.Stop().ConfigureAwait(false); } catch { }
|
||||
}
|
||||
}
|
||||
ReactionEvents.Clear();
|
||||
|
||||
await _sneakyLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await SneakyEvent.Stop().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sneakyLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
NadekoBot.Core/Modules/Gambling/Services/WaifuService.cs
Normal file
12
NadekoBot.Core/Modules/Gambling/Services/WaifuService.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using NadekoBot.Core.Services;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
{
|
||||
public class WaifuService : INService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, DateTime> DivorceCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
|
||||
public ConcurrentDictionary<ulong, DateTime> AffinityCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
|
||||
}
|
||||
}
|
239
NadekoBot.Core/Modules/Gambling/SlotCommands.cs
Normal file
239
NadekoBot.Core/Modules/Gambling/SlotCommands.cs
Normal file
@ -0,0 +1,239 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using ImageSharp;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using SixLabors.Primitives;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class SlotCommands : NadekoSubmodule
|
||||
{
|
||||
private static int _totalBet;
|
||||
private static int _totalPaidOut;
|
||||
|
||||
private static readonly HashSet<ulong> _runningUsers = new HashSet<ulong>();
|
||||
private readonly IBotConfigProvider _bc;
|
||||
|
||||
private const int _alphaCutOut = byte.MaxValue / 3;
|
||||
|
||||
//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;
|
||||
private readonly CurrencyService _cs;
|
||||
|
||||
public SlotCommands(IImagesService images, IBotConfigProvider bc, CurrencyService cs)
|
||||
{
|
||||
_images = images;
|
||||
_bc = bc;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
public class SlotMachine
|
||||
{
|
||||
public const int MaxValue = 5;
|
||||
|
||||
static readonly List<Func<int[], int>> _winningCombos = new List<Func<int[], int>>()
|
||||
{
|
||||
//three flowers
|
||||
(arr) => arr.All(a=>a==MaxValue) ? 30 : 0,
|
||||
//three of the same
|
||||
(arr) => !arr.Any(a => a != arr[0]) ? 10 : 0,
|
||||
//two flowers
|
||||
(arr) => arr.Count(a => a == MaxValue) == 2 ? 4 : 0,
|
||||
//one flower
|
||||
(arr) => arr.Any(a => a == MaxValue) ? 1 : 0,
|
||||
};
|
||||
|
||||
public static SlotResult Pull()
|
||||
{
|
||||
var numbers = new int[3];
|
||||
for (var i = 0; i < numbers.Length; i++)
|
||||
{
|
||||
numbers[i] = new NadekoRandom().Next(0, MaxValue + 1);
|
||||
}
|
||||
var multi = 0;
|
||||
foreach (var t in _winningCombos)
|
||||
{
|
||||
multi = t(numbers);
|
||||
if (multi != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return new SlotResult(numbers, multi);
|
||||
}
|
||||
|
||||
public struct SlotResult
|
||||
{
|
||||
public int[] Numbers { get; }
|
||||
public int Multiplier { get; }
|
||||
public SlotResult(int[] nums, int multi)
|
||||
{
|
||||
Numbers = nums;
|
||||
Multiplier = multi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SlotStats()
|
||||
{
|
||||
//i remembered to not be a moron
|
||||
var paid = _totalPaidOut;
|
||||
var bet = _totalBet;
|
||||
|
||||
if (bet <= 0)
|
||||
bet = 1;
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithTitle("Slot Stats")
|
||||
.AddField(efb => efb.WithName("Total Bet").WithValue(bet.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("Paid Out").WithValue(paid.ToString()).WithIsInline(true))
|
||||
.WithFooter(efb => efb.WithText($"Payout Rate: {paid * 1.0 / bet * 100:f4}%"));
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SlotTest(int tests = 1000)
|
||||
{
|
||||
if (tests <= 0)
|
||||
return;
|
||||
//multi vs how many times it occured
|
||||
var dict = new Dictionary<int, int>();
|
||||
for (int i = 0; i < tests; i++)
|
||||
{
|
||||
var res = SlotMachine.Pull();
|
||||
if (dict.ContainsKey(res.Multiplier))
|
||||
dict[res.Multiplier] += 1;
|
||||
else
|
||||
dict.Add(res.Multiplier, 1);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
const int bet = 1;
|
||||
int payout = 0;
|
||||
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];
|
||||
}
|
||||
await Context.Channel.SendConfirmAsync("Slot Test Results", sb.ToString(),
|
||||
footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%");
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Slot(int amount = 0)
|
||||
{
|
||||
if (!_runningUsers.Add(Context.User.Id))
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (amount < 1)
|
||||
{
|
||||
await ReplyErrorLocalized("min_bet_limit", 1 + _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
const int maxAmount = 9999;
|
||||
if (amount > maxAmount)
|
||||
{
|
||||
GetText("slot_maxbet", maxAmount + _bc.BotConfig.CurrencySign);
|
||||
await ReplyErrorLocalized("max_bet_limit", maxAmount + _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await _cs.RemoveAsync(Context.User, "Slot Machine", amount, false))
|
||||
{
|
||||
await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
Interlocked.Add(ref _totalBet, amount);
|
||||
using (var bgFileStream = _images.SlotBackground.ToStream())
|
||||
{
|
||||
var bgImage = ImageSharp.Image.Load(bgFileStream);
|
||||
|
||||
var result = SlotMachine.Pull();
|
||||
int[] numbers = result.Numbers;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
using (var file = _images.SlotEmojis[numbers[i]].ToStream())
|
||||
using (var randomImage = ImageSharp.Image.Load(file))
|
||||
{
|
||||
bgImage.DrawImage(randomImage, 100, default, new Point(95 + 142 * i, 330));
|
||||
}
|
||||
}
|
||||
|
||||
var won = amount * result.Multiplier;
|
||||
var printWon = won;
|
||||
var n = 0;
|
||||
do
|
||||
{
|
||||
var digit = printWon % 10;
|
||||
using (var fs = _images.SlotNumbers[digit].ToStream())
|
||||
using (var img = ImageSharp.Image.Load(fs))
|
||||
{
|
||||
bgImage.DrawImage(img, 100, default, new Point(230 - n * 16, 462));
|
||||
}
|
||||
n++;
|
||||
} while ((printWon /= 10) != 0);
|
||||
|
||||
var printAmount = amount;
|
||||
n = 0;
|
||||
do
|
||||
{
|
||||
var digit = printAmount % 10;
|
||||
using (var fs = _images.SlotNumbers[digit].ToStream())
|
||||
using (var img = ImageSharp.Image.Load(fs))
|
||||
{
|
||||
bgImage.DrawImage(img, 100, default, new Point(395 - n * 16, 462));
|
||||
}
|
||||
n++;
|
||||
} while ((printAmount /= 10) != 0);
|
||||
|
||||
var msg = GetText("better_luck");
|
||||
if (result.Multiplier != 0)
|
||||
{
|
||||
await _cs.AddAsync(Context.User, $"Slot Machine x{result.Multiplier}", amount * result.Multiplier, false);
|
||||
Interlocked.Add(ref _totalPaidOut, amount * result.Multiplier);
|
||||
if (result.Multiplier == 1)
|
||||
msg = GetText("slot_single", _bc.BotConfig.CurrencySign, 1);
|
||||
else if (result.Multiplier == 4)
|
||||
msg = GetText("slot_two", _bc.BotConfig.CurrencySign, 4);
|
||||
else if (result.Multiplier == 10)
|
||||
msg = GetText("slot_three", 10);
|
||||
else if (result.Multiplier == 30)
|
||||
msg = GetText("slot_jackpot", 30);
|
||||
}
|
||||
|
||||
await Context.Channel.SendFileAsync(bgImage.ToStream(), "result.png", Context.User.Mention + " " + msg + $"\n`{GetText("slot_bet")}:`{amount} `{GetText("slot_won")}:` {amount * result.Multiplier}{_bc.BotConfig.CurrencySign}").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1500);
|
||||
_runningUsers.Remove(Context.User.Id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
617
NadekoBot.Core/Modules/Gambling/WaifuClaimCommands.cs
Normal file
617
NadekoBot.Core/Modules/Gambling/WaifuClaimCommands.cs
Normal file
@ -0,0 +1,617 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
public enum ClaimTitles
|
||||
{
|
||||
Lonely,
|
||||
Devoted,
|
||||
Rookie,
|
||||
Schemer,
|
||||
Dilettante,
|
||||
Intermediate,
|
||||
Seducer,
|
||||
Expert,
|
||||
Veteran,
|
||||
Incubis,
|
||||
Harem_King,
|
||||
Harem_God,
|
||||
}
|
||||
|
||||
public enum AffinityTitles
|
||||
{
|
||||
Pure,
|
||||
Faithful,
|
||||
Defiled,
|
||||
Cheater,
|
||||
Tainted,
|
||||
Corrupted,
|
||||
Lewd,
|
||||
Sloot,
|
||||
Depraved,
|
||||
Harlot
|
||||
}
|
||||
|
||||
[Group]
|
||||
public class WaifuClaimCommands : NadekoSubmodule<WaifuService>
|
||||
{
|
||||
enum WaifuClaimResult
|
||||
{
|
||||
Success,
|
||||
NotEnoughFunds,
|
||||
InsufficientAmount
|
||||
}
|
||||
|
||||
public WaifuClaimCommands(IBotConfigProvider bc, CurrencyService cs, DbService db)
|
||||
{
|
||||
_bc = bc;
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WaifuClaim(int amount, [Remainder]IUser target)
|
||||
{
|
||||
if (amount < 50)
|
||||
{
|
||||
await ReplyErrorLocalized("waifu_isnt_cheap", 50 + _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.Id == Context.User.Id)
|
||||
{
|
||||
await ReplyErrorLocalized("waifu_not_yourself").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
WaifuClaimResult result;
|
||||
WaifuInfo w;
|
||||
bool isAffinity;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
w = uow.Waifus.ByWaifuUserId(target.Id);
|
||||
isAffinity = (w?.Affinity?.UserId == Context.User.Id);
|
||||
if (w == null)
|
||||
{
|
||||
var claimer = uow.DiscordUsers.GetOrCreate(Context.User);
|
||||
var waifu = uow.DiscordUsers.GetOrCreate(target);
|
||||
if (!await _cs.RemoveAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
{
|
||||
uow.Waifus.Add(w = new WaifuInfo()
|
||||
{
|
||||
Waifu = waifu,
|
||||
Claimer = claimer,
|
||||
Affinity = null,
|
||||
Price = amount
|
||||
});
|
||||
uow._context.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = waifu,
|
||||
Old = null,
|
||||
New = claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
result = WaifuClaimResult.Success;
|
||||
}
|
||||
}
|
||||
else if (isAffinity && amount > w.Price * 0.88f)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
{
|
||||
var oldClaimer = w.Claimer;
|
||||
w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User);
|
||||
w.Price = amount + (amount / 4);
|
||||
result = WaifuClaimResult.Success;
|
||||
|
||||
uow._context.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (amount >= w.Price * 1.1f) // if no affinity
|
||||
{
|
||||
if (!await _cs.RemoveAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
{
|
||||
var oldClaimer = w.Claimer;
|
||||
w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User);
|
||||
w.Price = amount;
|
||||
result = WaifuClaimResult.Success;
|
||||
|
||||
uow._context.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
result = WaifuClaimResult.InsufficientAmount;
|
||||
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (result == WaifuClaimResult.InsufficientAmount)
|
||||
{
|
||||
await ReplyErrorLocalized("waifu_not_enough", Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
if (result == WaifuClaimResult.NotEnoughFunds)
|
||||
{
|
||||
await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var msg = GetText("waifu_claimed",
|
||||
Format.Bold(target.ToString()),
|
||||
amount + _bc.BotConfig.CurrencySign);
|
||||
if (w.Affinity?.UserId == Context.User.Id)
|
||||
msg += "\n" + GetText("waifu_fulfilled", target, w.Price + _bc.BotConfig.CurrencySign);
|
||||
else
|
||||
msg = " " + msg;
|
||||
await Context.Channel.SendConfirmAsync(Context.User.Mention + msg).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public enum DivorceResult
|
||||
{
|
||||
Success,
|
||||
SucessWithPenalty,
|
||||
NotYourWife,
|
||||
Cooldown
|
||||
}
|
||||
|
||||
|
||||
private static readonly TimeSpan _divorceLimit = TimeSpan.FromHours(6);
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task Divorce([Remainder]IGuildUser target) => Divorce(target.Id);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task Divorce([Remainder]ulong targetId)
|
||||
{
|
||||
if (targetId == Context.User.Id)
|
||||
return;
|
||||
|
||||
DivorceResult result;
|
||||
var difference = TimeSpan.Zero;
|
||||
var amount = 0;
|
||||
WaifuInfo w = null;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
w = uow.Waifus.ByWaifuUserId(targetId);
|
||||
var now = DateTime.UtcNow;
|
||||
if (w?.Claimer == null || w.Claimer.UserId != Context.User.Id)
|
||||
result = DivorceResult.NotYourWife;
|
||||
else if (_service.DivorceCooldowns.AddOrUpdate(Context.User.Id,
|
||||
now,
|
||||
(key, old) => ((difference = now.Subtract(old)) > _divorceLimit) ? now : old) != now)
|
||||
{
|
||||
result = DivorceResult.Cooldown;
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = w.Price / 2;
|
||||
|
||||
if (w.Affinity?.UserId == Context.User.Id)
|
||||
{
|
||||
await _cs.AddAsync(w.Waifu.UserId, "Waifu Compensation", amount, uow).ConfigureAwait(false);
|
||||
w.Price = (int)Math.Floor(w.Price * 0.75f);
|
||||
result = DivorceResult.SucessWithPenalty;
|
||||
}
|
||||
else
|
||||
{
|
||||
await _cs.AddAsync(Context.User.Id, "Waifu Refund", amount, uow).ConfigureAwait(false);
|
||||
|
||||
result = DivorceResult.Success;
|
||||
}
|
||||
var oldClaimer = w.Claimer;
|
||||
w.Claimer = null;
|
||||
|
||||
uow._context.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = null,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (result == DivorceResult.SucessWithPenalty)
|
||||
{
|
||||
await ReplyConfirmLocalized("waifu_divorced_like", Format.Bold(w.Waifu.ToString()), amount + _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
}
|
||||
else if (result == DivorceResult.Success)
|
||||
{
|
||||
await ReplyConfirmLocalized("waifu_divorced_notlike", amount + _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
}
|
||||
else if (result == DivorceResult.NotYourWife)
|
||||
{
|
||||
await ReplyErrorLocalized("waifu_not_yours").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remaining = _divorceLimit.Subtract(difference);
|
||||
await ReplyErrorLocalized("waifu_recent_divorce",
|
||||
Format.Bold(((int)remaining.TotalHours).ToString()),
|
||||
Format.Bold(remaining.Minutes.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly TimeSpan _affinityLimit = TimeSpan.FromMinutes(30);
|
||||
private readonly IBotConfigProvider _bc;
|
||||
private readonly CurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WaifuClaimerAffinity([Remainder]IGuildUser u = null)
|
||||
{
|
||||
if (u?.Id == Context.User.Id)
|
||||
{
|
||||
await ReplyErrorLocalized("waifu_egomaniac").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
DiscordUser oldAff = null;
|
||||
var sucess = false;
|
||||
var cooldown = false;
|
||||
var difference = TimeSpan.Zero;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var w = uow.Waifus.ByWaifuUserId(Context.User.Id);
|
||||
var newAff = u == null ? null : uow.DiscordUsers.GetOrCreate(u);
|
||||
var now = DateTime.UtcNow;
|
||||
if (w?.Affinity?.UserId == u?.Id)
|
||||
{
|
||||
}
|
||||
else if (_service.AffinityCooldowns.AddOrUpdate(Context.User.Id,
|
||||
now,
|
||||
(key, old) => ((difference = now.Subtract(old)) > _affinityLimit) ? now : old) != now)
|
||||
{
|
||||
cooldown = true;
|
||||
}
|
||||
else if (w == null)
|
||||
{
|
||||
var thisUser = uow.DiscordUsers.GetOrCreate(Context.User);
|
||||
uow.Waifus.Add(new WaifuInfo()
|
||||
{
|
||||
Affinity = newAff,
|
||||
Waifu = thisUser,
|
||||
Price = 1,
|
||||
Claimer = null
|
||||
});
|
||||
sucess = true;
|
||||
|
||||
uow._context.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = thisUser,
|
||||
Old = null,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (w.Affinity != null)
|
||||
oldAff = w.Affinity;
|
||||
w.Affinity = newAff;
|
||||
sucess = true;
|
||||
|
||||
uow._context.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldAff,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
}
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
if (!sucess)
|
||||
{
|
||||
if (cooldown)
|
||||
{
|
||||
var remaining = _affinityLimit.Subtract(difference);
|
||||
await ReplyErrorLocalized("waifu_affinity_cooldown",
|
||||
Format.Bold(((int)remaining.TotalHours).ToString()),
|
||||
Format.Bold(remaining.Minutes.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("waifu_affinity_already").ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (u == null)
|
||||
{
|
||||
await ReplyConfirmLocalized("waifu_affinity_reset").ConfigureAwait(false);
|
||||
}
|
||||
else if (oldAff == null)
|
||||
{
|
||||
await ReplyConfirmLocalized("waifu_affinity_set", Format.Bold(u.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalized("waifu_affinity_changed", Format.Bold(oldAff.ToString()), Format.Bold(u.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WaifuLeaderboard(int page = 1)
|
||||
{
|
||||
page--;
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
IList<WaifuInfo> waifus;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
waifus = uow.Waifus.GetTop(9, page * 9);
|
||||
}
|
||||
|
||||
if (waifus.Count == 0)
|
||||
{
|
||||
await ReplyConfirmLocalized("waifus_none").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("waifus_top_waifus"))
|
||||
.WithOkColor();
|
||||
|
||||
for (var i = 0; i < waifus.Count; i++)
|
||||
{
|
||||
var w = waifus[i];
|
||||
|
||||
var j = i;
|
||||
embed.AddField(efb => efb.WithName("#" + ((page * 9) + j + 1) + " - " + w.Price + _bc.BotConfig.CurrencySign).WithValue(w.ToString()).WithIsInline(false));
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WaifuInfo([Remainder]IGuildUser target = null)
|
||||
{
|
||||
if (target == null)
|
||||
target = (IGuildUser)Context.User;
|
||||
WaifuInfo w;
|
||||
IList<WaifuInfo> claims;
|
||||
int divorces;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
w = uow.Waifus.ByWaifuUserId(target.Id);
|
||||
claims = uow.Waifus.ByClaimerUserId(target.Id);
|
||||
divorces = uow._context.WaifuUpdates.Count(x => x.Old != null &&
|
||||
x.Old.UserId == target.Id &&
|
||||
x.UpdateType == WaifuUpdateType.Claimed &&
|
||||
x.New == null);
|
||||
if (w == null)
|
||||
{
|
||||
uow.Waifus.Add(w = new WaifuInfo()
|
||||
{
|
||||
Affinity = null,
|
||||
Claimer = null,
|
||||
Price = 1,
|
||||
Waifu = uow.DiscordUsers.GetOrCreate(target),
|
||||
});
|
||||
}
|
||||
|
||||
w.Waifu.Username = target.Username;
|
||||
w.Waifu.Discriminator = target.Discriminator;
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var claimInfo = GetClaimTitle(target.Id);
|
||||
var affInfo = GetAffinityTitle(target.Id);
|
||||
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
var nobody = GetText("nobody");
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithTitle("Waifu " + w.Waifu + " - \"the " + claimInfo.Title + "\"")
|
||||
.AddField(efb => efb.WithName(GetText("price")).WithValue(w.Price.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("claimed_by")).WithValue(w.Claimer?.ToString() ?? nobody).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("likes")).WithValue(w.Affinity?.ToString() ?? nobody).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("changes_of_heart")).WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("divorces")).WithValue(divorces.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("gifts")).WithValue(!w.Items.Any() ? "-" : string.Join("\n", w.Items.OrderBy(x => x.Price).GroupBy(x => x.ItemEmoji).Select(x => $"{x.Key} x{x.Count()}"))).WithIsInline(false))
|
||||
.AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? nobody : string.Join("\n", claims.OrderBy(x => rng.Next()).Take(30).Select(x => x.Waifu))).WithIsInline(false));
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task WaifuGift()
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(GetText("waifu_gift_shop"))
|
||||
.WithOkColor();
|
||||
|
||||
Enum.GetValues(typeof(WaifuItem.ItemName))
|
||||
.Cast<WaifuItem.ItemName>()
|
||||
.Select(x => WaifuItem.GetItem(x))
|
||||
.ForEach(x => embed.AddField(f => f.WithName(x.ItemEmoji + " " + x.Item).WithValue(x.Price).WithIsInline(true)));
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task WaifuGift(WaifuItem.ItemName item, [Remainder] IUser waifu)
|
||||
{
|
||||
if (waifu.Id == Context.User.Id)
|
||||
return;
|
||||
|
||||
var itemObj = WaifuItem.GetItem(item);
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var w = uow.Waifus.ByWaifuUserId(waifu.Id);
|
||||
|
||||
//try to buy the item first
|
||||
|
||||
if (!await _cs.RemoveAsync(Context.User.Id, "Bought waifu item", itemObj.Price, uow))
|
||||
{
|
||||
await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
if (w == null)
|
||||
{
|
||||
uow.Waifus.Add(w = new WaifuInfo()
|
||||
{
|
||||
Affinity = null,
|
||||
Claimer = null,
|
||||
Price = 1,
|
||||
Waifu = uow.DiscordUsers.GetOrCreate(waifu),
|
||||
});
|
||||
|
||||
w.Waifu.Username = waifu.Username;
|
||||
w.Waifu.Discriminator = waifu.Discriminator;
|
||||
}
|
||||
w.Items.Add(itemObj);
|
||||
if (w.Claimer?.UserId == Context.User.Id)
|
||||
{
|
||||
w.Price += itemObj.Price;
|
||||
}
|
||||
else
|
||||
w.Price += itemObj.Price / 2;
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalized("waifu_gift", Format.Bold(item.ToString() + " " +itemObj.ItemEmoji), Format.Bold(waifu.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
public struct WaifuProfileTitle
|
||||
{
|
||||
public int Count { get; }
|
||||
public string Title { get; }
|
||||
|
||||
public WaifuProfileTitle(int count, string title)
|
||||
{
|
||||
Count = count;
|
||||
Title = title;
|
||||
}
|
||||
}
|
||||
|
||||
private WaifuProfileTitle GetClaimTitle(ulong userId)
|
||||
{
|
||||
int count;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
count = uow.Waifus.ByClaimerUserId(userId).Count;
|
||||
}
|
||||
|
||||
ClaimTitles title;
|
||||
if (count == 0)
|
||||
title = ClaimTitles.Lonely;
|
||||
else if (count == 1)
|
||||
title = ClaimTitles.Devoted;
|
||||
else if (count < 4)
|
||||
title = ClaimTitles.Rookie;
|
||||
else if (count < 6)
|
||||
title = ClaimTitles.Schemer;
|
||||
else if (count < 8)
|
||||
title = ClaimTitles.Dilettante;
|
||||
else if (count < 10)
|
||||
title = ClaimTitles.Intermediate;
|
||||
else if (count < 12)
|
||||
title = ClaimTitles.Seducer;
|
||||
else if (count < 15)
|
||||
title = ClaimTitles.Expert;
|
||||
else if (count < 17)
|
||||
title = ClaimTitles.Veteran;
|
||||
else if (count < 25)
|
||||
title = ClaimTitles.Incubis;
|
||||
else if (count < 50)
|
||||
title = ClaimTitles.Harem_King;
|
||||
else
|
||||
title = ClaimTitles.Harem_God;
|
||||
|
||||
return new WaifuProfileTitle(count, title.ToString().Replace('_', ' '));
|
||||
}
|
||||
|
||||
private WaifuProfileTitle GetAffinityTitle(ulong userId)
|
||||
{
|
||||
int count;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
count = uow._context.WaifuUpdates
|
||||
.Where(w => w.User.UserId == userId && w.UpdateType == WaifuUpdateType.AffinityChanged && w.New != null)
|
||||
.GroupBy(x => x.New)
|
||||
.Count();
|
||||
}
|
||||
|
||||
AffinityTitles title;
|
||||
if (count < 1)
|
||||
title = AffinityTitles.Pure;
|
||||
else if (count < 2)
|
||||
title = AffinityTitles.Faithful;
|
||||
else if (count < 4)
|
||||
title = AffinityTitles.Defiled;
|
||||
else if (count < 7)
|
||||
title = AffinityTitles.Cheater;
|
||||
else if (count < 9)
|
||||
title = AffinityTitles.Tainted;
|
||||
else if (count < 11)
|
||||
title = AffinityTitles.Corrupted;
|
||||
else if (count < 13)
|
||||
title = AffinityTitles.Lewd;
|
||||
else if (count < 15)
|
||||
title = AffinityTitles.Sloot;
|
||||
else if (count < 17)
|
||||
title = AffinityTitles.Depraved;
|
||||
else
|
||||
title = AffinityTitles.Harlot;
|
||||
|
||||
return new WaifuProfileTitle(count, title.ToString().Replace('_', ' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
NadekoBot.Core/Modules/Gambling/WheelOfFortuneCommands.cs
Normal file
81
NadekoBot.Core/Modules/Gambling/WheelOfFortuneCommands.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
|
||||
using NadekoBot.Core.Services;
|
||||
using System.Threading.Tasks;
|
||||
using Wof = NadekoBot.Modules.Gambling.Common.WheelOfFortune.WheelOfFortune;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
public class WheelOfFortuneCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly CurrencyService _cs;
|
||||
private readonly IBotConfigProvider _bc;
|
||||
|
||||
public WheelOfFortuneCommands(CurrencyService cs, IBotConfigProvider bc)
|
||||
{
|
||||
_cs = cs;
|
||||
_bc = bc;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WheelOfFortune(int bet)
|
||||
{
|
||||
const int minBet = 10;
|
||||
if (bet < minBet)
|
||||
{
|
||||
await ReplyErrorLocalized("min_bet_limit", minBet + _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await _cs.RemoveAsync(Context.User.Id, "Wheel Of Fortune - bet", bet).ConfigureAwait(false))
|
||||
{
|
||||
await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var wof = new WheelOfFortune();
|
||||
|
||||
var amount = (int)(bet * wof.Multiplier);
|
||||
|
||||
if (amount > 0)
|
||||
await _cs.AddAsync(Context.User.Id, "Wheel Of Fortune - won", amount).ConfigureAwait(false);
|
||||
|
||||
await Context.Channel.SendConfirmAsync(
|
||||
Format.Bold($@"{Context.User.ToString()} won: {amount + _bc.BotConfig.CurrencySign}
|
||||
|
||||
『{Wof.Multipliers[1]}』 『{Wof.Multipliers[0]}』 『{Wof.Multipliers[7]}』
|
||||
|
||||
『{Wof.Multipliers[2]}』 {wof.Emoji} 『{Wof.Multipliers[6]}』
|
||||
|
||||
『{Wof.Multipliers[3]}』 『{Wof.Multipliers[4]}』 『{Wof.Multipliers[5]}』")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//[NadekoCommand, Usage, Description, Aliases]
|
||||
//[RequireContext(ContextType.Guild)]
|
||||
//public async Task WofTest(int length = 1000)
|
||||
//{
|
||||
// var mults = new Dictionary<float, int>();
|
||||
// for (int i = 0; i < length; i++)
|
||||
// {
|
||||
// var x = new Wof();
|
||||
// if (mults.ContainsKey(x.Multiplier))
|
||||
// ++mults[x.Multiplier];
|
||||
// else
|
||||
// mults.Add(x.Multiplier, 1);
|
||||
// }
|
||||
|
||||
// var payout = mults.Sum(x => x.Key * x.Value);
|
||||
// await Context.Channel.SendMessageAsync($"Total bet: {length}\n" +
|
||||
// $"Paid out: {payout}\n" +
|
||||
// $"Total Payout: {payout / length:F3}x")
|
||||
// .ConfigureAwait(false);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user