mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-12 02:08:27 -04:00
Reorganizing module and submodule folders
This commit is contained in:
32
src/NadekoBot/Modules/Gambling/~Shared/BetRoll.cs
Normal file
32
src/NadekoBot/Modules/Gambling/~Shared/BetRoll.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class Betroll
|
||||
{
|
||||
private readonly IOrderedEnumerable<BetRollPair> _thresholdPairs;
|
||||
private readonly Random _rng;
|
||||
|
||||
public Betroll(BetRollConfig settings)
|
||||
{
|
||||
_thresholdPairs = settings.Pairs.OrderByDescending(x => x.WhenAbove);
|
||||
_rng = new();
|
||||
}
|
||||
|
||||
public Result Roll()
|
||||
{
|
||||
var roll = _rng.Next(0, 101);
|
||||
|
||||
var pair = _thresholdPairs.FirstOrDefault(x => x.WhenAbove < roll);
|
||||
if (pair is null)
|
||||
return new() { Multiplier = 0, Roll = roll };
|
||||
|
||||
return new() { Multiplier = pair.MultiplyBy, Roll = roll, Threshold = pair.WhenAbove };
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
public int Roll { get; set; }
|
||||
public float Multiplier { get; set; }
|
||||
public int Threshold { get; set; }
|
||||
}
|
||||
}
|
317
src/NadekoBot/Modules/Gambling/~Shared/Deck.cs
Normal file
317
src/NadekoBot/Modules/Gambling/~Shared/Deck.cs
Normal file
@@ -0,0 +1,317 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class QuadDeck : Deck
|
||||
{
|
||||
protected override void RefillPool()
|
||||
{
|
||||
CardPool = new(52 * 4);
|
||||
for (var j = 1; j < 14; j++)
|
||||
for (var i = 1; i < 5; i++)
|
||||
{
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Deck
|
||||
{
|
||||
public enum CardSuit
|
||||
{
|
||||
Spades = 1,
|
||||
Hearts = 2,
|
||||
Diamonds = 3,
|
||||
Clubs = 4
|
||||
}
|
||||
|
||||
private static readonly Dictionary<int, string> _cardNames = new()
|
||||
{
|
||||
{ 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 List<Card> CardPool { get; set; }
|
||||
private readonly Random _r = new NadekoRandom();
|
||||
|
||||
static Deck()
|
||||
=> InitHandValues();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time.
|
||||
/// </summary>
|
||||
public Deck()
|
||||
=> RefillPool();
|
||||
|
||||
/// <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>
|
||||
protected virtual void RefillPool()
|
||||
{
|
||||
CardPool = new(52);
|
||||
//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((CardSuit)i, j));
|
||||
}
|
||||
|
||||
/// <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 Draw()
|
||||
{
|
||||
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 ??= orderedPool.ToList();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine;
|
||||
|
||||
private static void InitHandValues()
|
||||
{
|
||||
bool HasPair(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 1;
|
||||
}
|
||||
|
||||
bool IsPair(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Count(group => group.Count() == 3) == 0 && HasPair(cards);
|
||||
}
|
||||
|
||||
bool IsTwoPair(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 2;
|
||||
}
|
||||
|
||||
bool IsStraight(List<Card> cards)
|
||||
{
|
||||
if (cards.GroupBy(card => card.Number).Count() != cards.Count())
|
||||
return false;
|
||||
var toReturn = cards.Max(card => card.Number) - cards.Min(card => card.Number) == 4;
|
||||
if (toReturn || cards.All(c => c.Number != 1)) return toReturn;
|
||||
|
||||
var newCards = cards.Select(c => c.Number == 1 ? new(c.Suit, 14) : c);
|
||||
return newCards.Max(card => card.Number) - newCards.Min(card => card.Number) == 4;
|
||||
}
|
||||
|
||||
bool HasThreeOfKind(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Any(group => group.Count() == 3);
|
||||
}
|
||||
|
||||
bool IsThreeOfKind(List<Card> cards)
|
||||
{
|
||||
return HasThreeOfKind(cards) && !HasPair(cards);
|
||||
}
|
||||
|
||||
bool IsFlush(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Suit).Count() == 1;
|
||||
}
|
||||
|
||||
bool IsFourOfKind(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Any(group => group.Count() == 4);
|
||||
}
|
||||
|
||||
bool IsFullHouse(List<Card> cards)
|
||||
{
|
||||
return HasPair(cards) && HasThreeOfKind(cards);
|
||||
}
|
||||
|
||||
bool HasStraightFlush(List<Card> cards)
|
||||
{
|
||||
return IsFlush(cards) && IsStraight(cards);
|
||||
}
|
||||
|
||||
bool IsRoyalFlush(List<Card> cards)
|
||||
{
|
||||
return cards.Min(card => card.Number) == 1
|
||||
&& cards.Max(card => card.Number) == 13
|
||||
&& HasStraightFlush(cards);
|
||||
}
|
||||
|
||||
bool IsStraightFlush(List<Card> cards)
|
||||
{
|
||||
return HasStraightFlush(cards) && !IsRoyalFlush(cards);
|
||||
}
|
||||
|
||||
_handValues = new()
|
||||
{
|
||||
{ "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 is null)
|
||||
InitHandValues();
|
||||
foreach (var kvp in _handValues.Where(x => x.Value(cards))) return kvp.Key;
|
||||
return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetValueText() ?? cards.Max().GetValueText());
|
||||
}
|
||||
|
||||
public class Card : IComparable
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<CardSuit, string> _suitToSuitChar = new Dictionary<CardSuit, string>
|
||||
{
|
||||
{ CardSuit.Diamonds, "♦" }, { CardSuit.Clubs, "♣" }, { CardSuit.Spades, "♠" }, { CardSuit.Hearts, "♥" }
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyDictionary<string, CardSuit> _suitCharToSuit = new Dictionary<string, CardSuit>
|
||||
{
|
||||
{ "♦", CardSuit.Diamonds },
|
||||
{ "d", CardSuit.Diamonds },
|
||||
{ "♣", CardSuit.Clubs },
|
||||
{ "c", CardSuit.Clubs },
|
||||
{ "♠", CardSuit.Spades },
|
||||
{ "s", CardSuit.Spades },
|
||||
{ "♥", CardSuit.Hearts },
|
||||
{ "h", CardSuit.Hearts }
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyDictionary<char, int> _numberCharToNumber = new Dictionary<char, int>
|
||||
{
|
||||
{ 'a', 1 },
|
||||
{ '2', 2 },
|
||||
{ '3', 3 },
|
||||
{ '4', 4 },
|
||||
{ '5', 5 },
|
||||
{ '6', 6 },
|
||||
{ '7', 7 },
|
||||
{ '8', 8 },
|
||||
{ '9', 9 },
|
||||
{ 't', 10 },
|
||||
{ 'j', 11 },
|
||||
{ 'q', 12 },
|
||||
{ 'k', 13 }
|
||||
};
|
||||
|
||||
public CardSuit Suit { get; }
|
||||
public int Number { get; }
|
||||
|
||||
public string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
var str = string.Empty;
|
||||
|
||||
if (Number is <= 10 and > 1)
|
||||
str += "_" + Number;
|
||||
else
|
||||
str += GetValueText().ToLowerInvariant();
|
||||
return str + "_of_" + Suit.ToString().ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly string[] _regIndicators =
|
||||
{
|
||||
"🇦", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:", ":keycap_ten:",
|
||||
"🇯", "🇶", "🇰"
|
||||
};
|
||||
|
||||
public Card(CardSuit s, int cardNum)
|
||||
{
|
||||
Suit = s;
|
||||
Number = cardNum;
|
||||
}
|
||||
|
||||
public string GetValueText()
|
||||
=> _cardNames[Number];
|
||||
|
||||
public override string ToString()
|
||||
=> _cardNames[Number] + " Of " + Suit;
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is not Card card) return 0;
|
||||
return Number - card.Number;
|
||||
}
|
||||
|
||||
public static Card Parse(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
if (input.Length != 2
|
||||
|| !_numberCharToNumber.TryGetValue(input[0], out var n)
|
||||
|| !_suitCharToSuit.TryGetValue(input[1].ToString(), out var s))
|
||||
throw new ArgumentException("Invalid input", nameof(input));
|
||||
|
||||
return new(s, n);
|
||||
}
|
||||
|
||||
public string GetEmojiString()
|
||||
{
|
||||
var str = string.Empty;
|
||||
|
||||
str += _regIndicators[Number - 1];
|
||||
str += _suitToSuitChar[Suit];
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
305
src/NadekoBot/Modules/Gambling/~Shared/GamblingConfig.cs
Normal file
305
src/NadekoBot/Modules/Gambling/~Shared/GamblingConfig.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
#nullable disable
|
||||
using Cloneable;
|
||||
using NadekoBot.Common.Yml;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using YamlDotNet.Serialization;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||
{
|
||||
[Comment(@"DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 2;
|
||||
|
||||
[Comment(@"Currency settings")]
|
||||
public CurrencyConfig Currency { get; set; }
|
||||
|
||||
[Comment(@"Minimum amount users can bet (>=0)")]
|
||||
public int MinBet { get; set; } = 0;
|
||||
|
||||
[Comment(@"Maximum amount users can bet
|
||||
Set 0 for unlimited")]
|
||||
public int MaxBet { get; set; } = 0;
|
||||
|
||||
[Comment(@"Settings for betflip command")]
|
||||
public BetFlipConfig BetFlip { get; set; }
|
||||
|
||||
[Comment(@"Settings for betroll command")]
|
||||
public BetRollConfig BetRoll { get; set; }
|
||||
|
||||
[Comment(@"Automatic currency generation settings.")]
|
||||
public GenerationConfig Generation { get; set; }
|
||||
|
||||
[Comment(@"Settings for timely command
|
||||
(letting people claim X amount of currency every Y hours)")]
|
||||
public TimelyConfig Timely { get; set; }
|
||||
|
||||
[Comment(@"How much will each user's owned currency decay over time.")]
|
||||
public DecayConfig Decay { get; set; }
|
||||
|
||||
[Comment(@"Settings for Wheel Of Fortune command.")]
|
||||
public WheelOfFortuneSettings WheelOfFortune { get; set; }
|
||||
|
||||
[Comment(@"Settings related to waifus")]
|
||||
public WaifuConfig Waifu { get; set; }
|
||||
|
||||
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
|
||||
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
|
||||
public decimal PatreonCurrencyPerCent { get; set; } = 1;
|
||||
|
||||
[Comment(@"Currency reward per vote.
|
||||
This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting")]
|
||||
public long VoteReward { get; set; } = 100;
|
||||
|
||||
[Comment(@"Slot config")]
|
||||
public SlotsConfig Slots { get; set; }
|
||||
|
||||
public GamblingConfig()
|
||||
{
|
||||
BetRoll = new();
|
||||
WheelOfFortune = new();
|
||||
Waifu = new();
|
||||
Currency = new();
|
||||
BetFlip = new();
|
||||
Generation = new();
|
||||
Timely = new();
|
||||
Decay = new();
|
||||
Slots = new();
|
||||
}
|
||||
}
|
||||
|
||||
public class CurrencyConfig
|
||||
{
|
||||
[Comment(@"What is the emoji/character which represents the currency")]
|
||||
public string Sign { get; set; } = "🌸";
|
||||
|
||||
[Comment(@"What is the name of the currency")]
|
||||
public string Name { get; set; } = "Nadeko Flower";
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class TimelyConfig
|
||||
{
|
||||
[Comment(@"How much currency will the users get every time they run .timely command
|
||||
setting to 0 or less will disable this feature")]
|
||||
public int Amount { get; set; } = 0;
|
||||
|
||||
[Comment(@"How often (in hours) can users claim currency with .timely command
|
||||
setting to 0 or less will disable this feature")]
|
||||
public int Cooldown { get; set; } = 24;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class BetFlipConfig
|
||||
{
|
||||
[Comment(@"Bet multiplier if user guesses correctly")]
|
||||
public decimal Multiplier { get; set; } = 1.95M;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class BetRollConfig
|
||||
{
|
||||
[Comment(@"When betroll is played, user will roll a number 0-100.
|
||||
This setting will describe which multiplier is used for when the roll is higher than the given number.
|
||||
Doesn't have to be ordered.")]
|
||||
public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
|
||||
|
||||
public BetRollConfig()
|
||||
=> Pairs = new BetRollPair[]
|
||||
{
|
||||
new() { WhenAbove = 99, MultiplyBy = 10 }, new() { WhenAbove = 90, MultiplyBy = 4 },
|
||||
new() { WhenAbove = 66, MultiplyBy = 2 }
|
||||
};
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class GenerationConfig
|
||||
{
|
||||
[Comment(@"when currency is generated, should it also have a random password
|
||||
associated with it which users have to type after the .pick command
|
||||
in order to get it")]
|
||||
public bool HasPassword { get; set; } = true;
|
||||
|
||||
[Comment(@"Every message sent has a certain % chance to generate the currency
|
||||
specify the percentage here (1 being 100%, 0 being 0% - for example
|
||||
default is 0.02, which is 2%")]
|
||||
public decimal Chance { get; set; } = 0.02M;
|
||||
|
||||
[Comment(@"How many seconds have to pass for the next message to have a chance to spawn currency")]
|
||||
public int GenCooldown { get; set; } = 10;
|
||||
|
||||
[Comment(@"Minimum amount of currency that can spawn")]
|
||||
public int MinAmount { get; set; } = 1;
|
||||
|
||||
[Comment(@"Maximum amount of currency that can spawn.
|
||||
Set to the same value as MinAmount to always spawn the same amount")]
|
||||
public int MaxAmount { get; set; } = 1;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class DecayConfig
|
||||
{
|
||||
[Comment(@"Percentage of user's current currency which will be deducted every 24h.
|
||||
0 - 1 (1 is 100%, 0.5 50%, 0 disabled)")]
|
||||
public decimal Percent { get; set; } = 0;
|
||||
|
||||
[Comment(@"Maximum amount of user's currency that can decay at each interval. 0 for unlimited.")]
|
||||
public int MaxDecay { get; set; } = 0;
|
||||
|
||||
[Comment(@"Only users who have more than this amount will have their currency decay.")]
|
||||
public int MinThreshold { get; set; } = 99;
|
||||
|
||||
[Comment(@"How often, in hours, does the decay run. Default is 24 hours")]
|
||||
public int HourInterval { get; set; } = 24;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class WheelOfFortuneSettings
|
||||
{
|
||||
[Comment(@"Self-Explanatory. Has to have 8 values, otherwise the command won't work.")]
|
||||
public decimal[] Multipliers { get; set; }
|
||||
|
||||
public WheelOfFortuneSettings()
|
||||
=> Multipliers = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class WaifuConfig
|
||||
{
|
||||
[Comment(@"Minimum price a waifu can have")]
|
||||
public int MinPrice { get; set; } = 50;
|
||||
|
||||
public MultipliersData Multipliers { get; set; } = new();
|
||||
|
||||
[Comment(@"List of items available for gifting.
|
||||
If negative is true, gift will instead reduce waifu value.")]
|
||||
public List<WaifuItemModel> Items { get; set; } = new();
|
||||
|
||||
public WaifuConfig()
|
||||
=> Items = new()
|
||||
{
|
||||
new("🥔", 5, "Potato"),
|
||||
new("🍪", 10, "Cookie"),
|
||||
new("🥖", 20, "Bread"),
|
||||
new("🍭", 30, "Lollipop"),
|
||||
new("🌹", 50, "Rose"),
|
||||
new("🍺", 70, "Beer"),
|
||||
new("🌮", 85, "Taco"),
|
||||
new("💌", 100, "LoveLetter"),
|
||||
new("🥛", 125, "Milk"),
|
||||
new("🍕", 150, "Pizza"),
|
||||
new("🍫", 200, "Chocolate"),
|
||||
new("🍦", 250, "Icecream"),
|
||||
new("🍣", 300, "Sushi"),
|
||||
new("🍚", 400, "Rice"),
|
||||
new("🍉", 500, "Watermelon"),
|
||||
new("🍱", 600, "Bento"),
|
||||
new("🎟", 800, "MovieTicket"),
|
||||
new("🍰", 1000, "Cake"),
|
||||
new("📔", 1500, "Book"),
|
||||
new("🐱", 2000, "Cat"),
|
||||
new("🐶", 2001, "Dog"),
|
||||
new("🐼", 2500, "Panda"),
|
||||
new("💄", 3000, "Lipstick"),
|
||||
new("👛", 3500, "Purse"),
|
||||
new("📱", 4000, "iPhone"),
|
||||
new("👗", 4500, "Dress"),
|
||||
new("💻", 5000, "Laptop"),
|
||||
new("🎻", 7500, "Violin"),
|
||||
new("🎹", 8000, "Piano"),
|
||||
new("🚗", 9000, "Car"),
|
||||
new("💍", 10000, "Ring"),
|
||||
new("🛳", 12000, "Ship"),
|
||||
new("🏠", 15000, "House"),
|
||||
new("🚁", 20000, "Helicopter"),
|
||||
new("🚀", 30000, "Spaceship"),
|
||||
new("🌕", 50000, "Moon")
|
||||
};
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class MultipliersData
|
||||
{
|
||||
[Comment(@"Multiplier for waifureset. Default 150.
|
||||
Formula (at the time of writing this):
|
||||
price = (waifu_price * 1.25f) + ((number_of_divorces + changes_of_heart + 2) * WaifuReset) rounded up")]
|
||||
public int WaifuReset { get; set; } = 150;
|
||||
|
||||
[Comment(@"The minimum amount of currency that you have to pay
|
||||
in order to buy a waifu who doesn't have a crush on you.
|
||||
Default is 1.1
|
||||
Example: If a waifu is worth 100, you will have to pay at least 100 * NormalClaim currency to claim her.
|
||||
(100 * 1.1 = 110)")]
|
||||
public decimal NormalClaim { get; set; } = 1.1m;
|
||||
|
||||
[Comment(@"The minimum amount of currency that you have to pay
|
||||
in order to buy a waifu that has a crush on you.
|
||||
Default is 0.88
|
||||
Example: If a waifu is worth 100, you will have to pay at least 100 * CrushClaim currency to claim her.
|
||||
(100 * 0.88 = 88)")]
|
||||
public decimal CrushClaim { get; set; } = 0.88M;
|
||||
|
||||
[Comment(@"When divorcing a waifu, her new value will be her current value multiplied by this number.
|
||||
Default 0.75 (meaning will lose 25% of her value)")]
|
||||
public decimal DivorceNewValue { get; set; } = 0.75M;
|
||||
|
||||
[Comment(@"All gift prices will be multiplied by this number.
|
||||
Default 1 (meaning no effect)")]
|
||||
public decimal AllGiftPrices { get; set; } = 1.0M;
|
||||
|
||||
[Comment(@"What percentage of the value of the gift will a waifu gain when she's gifted.
|
||||
Default 0.95 (meaning 95%)
|
||||
Example: If a waifu is worth 1000, and she receives a gift worth 100, her new value will be 1095)")]
|
||||
public decimal GiftEffect { get; set; } = 0.95M;
|
||||
|
||||
[Comment(@"What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'.
|
||||
Default 0.5 (meaning 50%)
|
||||
Example: If a waifu is worth 1000, and she receives a negative gift worth 100, her new value will be 950)")]
|
||||
public decimal NegativeGiftEffect { get; set; } = 0.50M;
|
||||
}
|
||||
|
||||
public sealed class SlotsConfig
|
||||
{
|
||||
[Comment(@"Hex value of the color which the numbers on the slot image will have.")]
|
||||
public Rgba32 CurrencyFontColor { get; set; } = Color.Red;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class WaifuItemModel
|
||||
{
|
||||
public string ItemEmoji { get; set; }
|
||||
public int Price { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)]
|
||||
public bool Negative { get; set; }
|
||||
|
||||
public WaifuItemModel()
|
||||
{
|
||||
}
|
||||
|
||||
public WaifuItemModel(
|
||||
string itemEmoji,
|
||||
int price,
|
||||
string name,
|
||||
bool negative = false)
|
||||
{
|
||||
ItemEmoji = itemEmoji;
|
||||
Price = price;
|
||||
Name = name;
|
||||
Negative = negative;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class BetRollPair
|
||||
{
|
||||
public int WhenAbove { get; set; }
|
||||
public float MultiplyBy { get; set; }
|
||||
}
|
8
src/NadekoBot/Modules/Gambling/~Shared/GamblingError.cs
Normal file
8
src/NadekoBot/Modules/Gambling/~Shared/GamblingError.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public enum GamblingError
|
||||
{
|
||||
None,
|
||||
NotEnough
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public abstract class GamblingModule<TService> : NadekoModule<TService>
|
||||
{
|
||||
protected GamblingConfig _config
|
||||
=> _lazyConfig.Value;
|
||||
|
||||
protected string CurrencySign
|
||||
=> _config.Currency.Sign;
|
||||
|
||||
protected string CurrencyName
|
||||
=> _config.Currency.Name;
|
||||
|
||||
private readonly Lazy<GamblingConfig> _lazyConfig;
|
||||
|
||||
protected GamblingModule(GamblingConfigService gambService)
|
||||
=> _lazyConfig = new(() => gambService.Data);
|
||||
|
||||
private async Task<bool> InternalCheckBet(long amount)
|
||||
{
|
||||
if (amount < 1) return false;
|
||||
if (amount < _config.MinBet)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.min_bet_limit(Format.Bold(_config.MinBet.ToString()) + CurrencySign));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_config.MaxBet > 0 && amount > _config.MaxBet)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.max_bet_limit(Format.Bold(_config.MaxBet.ToString()) + CurrencySign));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Task<bool> CheckBetMandatory(long amount)
|
||||
{
|
||||
if (amount < 1) return Task.FromResult(false);
|
||||
return InternalCheckBet(amount);
|
||||
}
|
||||
|
||||
protected Task<bool> CheckBetOptional(long amount)
|
||||
{
|
||||
if (amount == 0) return Task.FromResult(true);
|
||||
return InternalCheckBet(amount);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
|
||||
{
|
||||
protected GamblingSubmodule(GamblingConfigService gamblingConfService)
|
||||
: base(gamblingConfService)
|
||||
{
|
||||
}
|
||||
}
|
139
src/NadekoBot/Modules/Gambling/~Shared/RollDuelGame.cs
Normal file
139
src/NadekoBot/Modules/Gambling/~Shared/RollDuelGame.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class RollDuelGame
|
||||
{
|
||||
public enum Reason
|
||||
{
|
||||
Normal,
|
||||
NoFunds,
|
||||
Timeout
|
||||
}
|
||||
|
||||
public enum State
|
||||
{
|
||||
Waiting,
|
||||
Running,
|
||||
Ended
|
||||
}
|
||||
|
||||
public event Func<RollDuelGame, Task> OnGameTick;
|
||||
public event Func<RollDuelGame, Reason, Task> OnEnded;
|
||||
|
||||
public ulong P1 { get; }
|
||||
public ulong P2 { get; }
|
||||
|
||||
public long Amount { get; }
|
||||
|
||||
public List<(int, int)> Rolls { get; } = new();
|
||||
public State CurrentState { get; private set; }
|
||||
public ulong Winner { get; private set; }
|
||||
|
||||
private readonly ulong _botId;
|
||||
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
private readonly Timer _timeoutTimer;
|
||||
private readonly NadekoRandom _rng = new();
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
|
||||
public RollDuelGame(
|
||||
ICurrencyService cs,
|
||||
ulong botId,
|
||||
ulong p1,
|
||||
ulong p2,
|
||||
long amount)
|
||||
{
|
||||
P1 = p1;
|
||||
P2 = p2;
|
||||
_botId = botId;
|
||||
Amount = amount;
|
||||
_cs = cs;
|
||||
|
||||
_timeoutTimer = new(async delegate
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (CurrentState != State.Waiting)
|
||||
return;
|
||||
CurrentState = State.Ended;
|
||||
await OnEnded?.Invoke(this, Reason.Timeout);
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromSeconds(15),
|
||||
TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
|
||||
public async Task StartGame()
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (CurrentState != State.Waiting)
|
||||
return;
|
||||
_timeoutTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
CurrentState = State.Running;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
|
||||
if (!await _cs.RemoveAsync(P1, "Roll Duel", Amount))
|
||||
{
|
||||
await OnEnded?.Invoke(this, Reason.NoFunds);
|
||||
CurrentState = State.Ended;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await _cs.RemoveAsync(P2, "Roll Duel", Amount))
|
||||
{
|
||||
await _cs.AddAsync(P1, "Roll Duel - refund", Amount);
|
||||
await OnEnded?.Invoke(this, Reason.NoFunds);
|
||||
CurrentState = State.Ended;
|
||||
return;
|
||||
}
|
||||
|
||||
int n1, n2;
|
||||
do
|
||||
{
|
||||
n1 = _rng.Next(0, 5);
|
||||
n2 = _rng.Next(0, 5);
|
||||
Rolls.Add((n1, n2));
|
||||
if (n1 != n2)
|
||||
{
|
||||
if (n1 > n2)
|
||||
Winner = P1;
|
||||
else
|
||||
Winner = P2;
|
||||
var won = (long)(Amount * 2 * 0.98f);
|
||||
await _cs.AddAsync(Winner, "Roll Duel win", won);
|
||||
|
||||
await _cs.AddAsync(_botId, "Roll Duel fee", (Amount * 2) - won);
|
||||
}
|
||||
|
||||
try { await OnGameTick?.Invoke(this); }
|
||||
catch { }
|
||||
|
||||
await Task.Delay(2500);
|
||||
if (n1 != n2)
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
CurrentState = State.Ended;
|
||||
await OnEnded?.Invoke(this, Reason.Normal);
|
||||
}
|
||||
}
|
||||
|
||||
public struct RollDuelChallenge
|
||||
{
|
||||
public ulong Player1 { get; set; }
|
||||
public ulong Player2 { get; set; }
|
||||
}
|
10
src/NadekoBot/Modules/Gambling/~Shared/SlotResponse.cs
Normal file
10
src/NadekoBot/Modules/Gambling/~Shared/SlotResponse.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public class SlotResponse
|
||||
{
|
||||
public float Multiplier { get; set; }
|
||||
public long Won { get; set; }
|
||||
public List<int> Rolls { get; set; } = new();
|
||||
public GamblingError Error { get; set; }
|
||||
}
|
Reference in New Issue
Block a user