Applied codestyle to all .cs files

This commit is contained in:
Kwoth
2021-12-29 06:07:16 +01:00
parent 723447c7d4
commit 82000c97a4
543 changed files with 13221 additions and 14059 deletions

View File

@@ -1,8 +1,8 @@
#nullable disable
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Games.Services;
namespace NadekoBot.Modules.Gambling;
@@ -17,17 +17,22 @@ public partial class Gambling
private readonly DiscordSocketClient _client;
private readonly GamesConfigService _gamesConf;
public AnimalRacingCommands(ICurrencyService cs, DiscordSocketClient client,
GamblingConfigService gamblingConf, GamesConfigService gamesConf) : base(gamblingConf)
private IUserMessage raceMessage;
public AnimalRacingCommands(
ICurrencyService cs,
DiscordSocketClient client,
GamblingConfigService gamblingConf,
GamesConfigService gamesConf)
: base(gamblingConf)
{
_cs = cs;
_client = client;
_gamesConf = gamesConf;
}
private IUserMessage raceMessage = null;
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[NadekoOptionsAttribute(typeof(RaceOptions))]
public Task Race(params string[] args)
@@ -41,6 +46,7 @@ public partial class Gambling
ar.Initialize();
var count = 0;
Task _client_MessageReceived(SocketMessage arg)
{
var _ = Task.Run(() =>
@@ -48,12 +54,8 @@ public partial class Gambling
try
{
if (arg.Channel.Id == ctx.Channel.Id)
{
if (ar.CurrentPhase == AnimalRace.Phase.Running && ++count % 9 == 0)
{
raceMessage = null;
}
}
}
catch { }
});
@@ -66,18 +68,12 @@ public partial class Gambling
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
var winner = race.FinishedUsers[0];
if (race.FinishedUsers[0].Bet > 0)
{
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_won_money(
Format.Bold(winner.Username),
GetText(strs.animal_race_won_money(Format.Bold(winner.Username),
winner.Animal.Icon,
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)));
}
else
{
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)));
}
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)));
}
ar.OnStartingFailed += Ar_OnStartingFailed;
@@ -86,7 +82,8 @@ public partial class Gambling
ar.OnStarted += Ar_OnStarted;
_client.MessageReceived += _client_MessageReceived;
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting(options.StartTime)),
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_starting(options.StartTime)),
footer: GetText(strs.animal_race_join_instr(Prefix)));
}
@@ -94,14 +91,14 @@ public partial class Gambling
{
if (race.Users.Count == race.MaxUsers)
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full));
else
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting_with_x(race.Users.Count)));
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_starting_with_x(race.Users.Count)));
}
private async Task Ar_OnStateUpdate(AnimalRace race)
{
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
{String.Join("\n", race.Users.Select(p =>
{string.Join("\n", race.Users.Select(p =>
{
var index = race.FinishedUsers.IndexOf(p);
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
@@ -115,10 +112,10 @@ public partial class Gambling
raceMessage = await SendConfirmAsync(text);
else
await msg.ModifyAsync(x => x.Embed = _eb.Create()
.WithTitle(GetText(strs.animal_race))
.WithDescription(text)
.WithOkColor()
.Build());
.WithTitle(GetText(strs.animal_race))
.WithDescription(text)
.WithOkColor()
.Build());
}
private Task Ar_OnStartingFailed(AnimalRace race)
@@ -127,7 +124,8 @@ public partial class Gambling
return ReplyErrorLocalizedAsync(strs.animal_race_failed);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task JoinRace(ShmartNumber amount = default)
{
@@ -139,11 +137,14 @@ public partial class Gambling
await ReplyErrorLocalizedAsync(strs.race_not_exist);
return;
}
try
{
var user = await ar.JoinRace(ctx.User.Id, ctx.User.ToString(), amount);
if (amount > 0)
await SendConfirmAsync(GetText(strs.animal_race_join_bet(ctx.User.Mention, user.Animal.Icon, amount + CurrencySign)));
await SendConfirmAsync(GetText(strs.animal_race_join_bet(ctx.User.Mention,
user.Animal.Icon,
amount + CurrencySign)));
else
await SendConfirmAsync(GetText(strs.animal_race_join(ctx.User.Mention, user.Animal.Icon)));
}
@@ -169,4 +170,4 @@ public partial class Gambling
}
}
}
}
}

View File

@@ -9,25 +9,26 @@ public partial class Gambling
{
public class BlackJackCommands : GamblingSubmodule<BlackJackService>
{
private readonly ICurrencyService _cs;
private readonly DbService _db;
private IUserMessage _msg;
public enum BjAction
{
Hit = int.MinValue,
Stand,
Double,
Double
}
public BlackJackCommands(ICurrencyService cs, DbService db,
GamblingConfigService gamblingConf) : base(gamblingConf)
private readonly ICurrencyService _cs;
private readonly DbService _db;
private IUserMessage _msg;
public BlackJackCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConf)
: base(gamblingConf)
{
_cs = cs;
_db = db;
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task BlackJack(ShmartNumber amount)
{
@@ -44,6 +45,7 @@ public partial class Gambling
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
bj.StateUpdated += Bj_StateUpdated;
bj.GameEnded += Bj_GameEnded;
bj.Start();
@@ -55,9 +57,8 @@ public partial class Gambling
if (await bj.Join(ctx.User, amount))
await ReplyConfirmLocalizedAsync(strs.bj_joined);
else
{
Log.Information($"{ctx.User} can't join a blackjack game as it's in " + bj.State.ToString() + " state already.");
}
Log.Information(
$"{ctx.User} can't join a blackjack game as it's in " + bj.State + " state already.");
}
await ctx.Message.DeleteAsync();
@@ -93,14 +94,11 @@ public partial class Gambling
var cStr = string.Concat(c.Select(x => x[..^1] + " "));
cStr += "\n" + string.Concat(c.Select(x => x.Last() + " "));
var embed = _eb.Create()
.WithOkColor()
.WithTitle("BlackJack")
.AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr);
.WithOkColor()
.WithTitle("BlackJack")
.AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr);
if (bj.CurrentUser != null)
{
embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser.ToString()}");
}
if (bj.CurrentUser != null) embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser}");
foreach (var p in bj.Players)
{
@@ -111,37 +109,42 @@ public partial class Gambling
if (bj.State == Blackjack.GameState.Ended)
{
if (p.State == User.UserState.Lost)
{
full = "❌ " + full;
}
else
{
full = "✅ " + full;
}
}
else if (p == bj.CurrentUser)
{
full = "▶ " + full;
}
else if (p.State == User.UserState.Stand)
{
full = "⏹ " + full;
}
else if (p.State == User.UserState.Bust)
{
full = "💥 " + full;
}
else if (p.State == User.UserState.Blackjack)
{
full = "💰 " + full;
}
embed.AddField(full, cStr);
}
_msg = await ctx.Channel.EmbedAsync(embed);
}
catch
{
}
}
private string UserToString(User x)
{
var playerName = x.State == User.UserState.Bust ?
Format.Strikethrough(x.DiscordUser.ToString().TrimTo(30)) :
x.DiscordUser.ToString();
var playerName = x.State == User.UserState.Bust
? Format.Strikethrough(x.DiscordUser.ToString().TrimTo(30))
: x.DiscordUser.ToString();
var hand = $"{string.Concat(x.Cards.Select(y => "" + y.GetEmojiString() + ""))}";
@@ -149,17 +152,23 @@ public partial class Gambling
return $"{playerName} | Bet: {x.Bet}\n";
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public Task Hit() => InternalBlackJack(BjAction.Hit);
public Task Hit()
=> InternalBlackJack(BjAction.Hit);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public Task Stand() => InternalBlackJack(BjAction.Stand);
public Task Stand()
=> InternalBlackJack(BjAction.Stand);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public Task Double() => InternalBlackJack(BjAction.Double);
public Task Double()
=> InternalBlackJack(BjAction.Double);
public async Task InternalBlackJack(BjAction a)
{
@@ -171,14 +180,10 @@ public partial class Gambling
else if (a == BjAction.Stand)
await bj.Stand(ctx.User);
else if (a == BjAction.Double)
{
if (!await bj.Double(ctx.User))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
}
await ctx.Message.DeleteAsync();
}
}
}
}

View File

@@ -10,34 +10,36 @@ public sealed class AnimalRace : IDisposable
{
WaitingForPlayers,
Running,
Ended,
Ended
}
public Phase CurrentPhase { get; private set; } = 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 IReadOnlyCollection<AnimalRacingUser> Users => _users.ToList();
public Phase CurrentPhase { get; private set; } = Phase.WaitingForPlayers;
public IReadOnlyCollection<AnimalRacingUser> Users
=> _users.ToList();
public List<AnimalRacingUser> FinishedUsers { get; } = new();
public int MaxUsers { get; }
private readonly SemaphoreSlim _locker = new(1, 1);
private readonly HashSet<AnimalRacingUser> _users = new();
private readonly ICurrencyService _currency;
private readonly RaceOptions _options;
private readonly Queue<RaceAnimal> _animalsQueue;
public int MaxUsers { get; }
public AnimalRace(RaceOptions options, ICurrencyService currency, IEnumerable<RaceAnimal> availableAnimals)
{
this._currency = currency;
this._options = options;
this._animalsQueue = new(availableAnimals);
this.MaxUsers = _animalsQueue.Count;
_currency = currency;
_options = options;
_animalsQueue = new(availableAnimals);
MaxUsers = _animalsQueue.Count;
if (this._animalsQueue.Count == 0)
if (_animalsQueue.Count == 0)
CurrentPhase = Phase.Ended;
}
@@ -99,10 +101,8 @@ public sealed class AnimalRace : IDisposable
if (_users.Count <= 1)
{
foreach (var user in _users)
{
if (user.Bet > 0)
await _currency.AddAsync(user.UserId, "Race refund", user.Bet);
}
var _sf = OnStartingFailed?.Invoke(this);
CurrentPhase = Phase.Ended;
@@ -122,8 +122,7 @@ public sealed class AnimalRace : IDisposable
user.Progress = 60;
}
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x))
.Shuffle();
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x)).Shuffle();
FinishedUsers.AddRange(finished);
@@ -132,7 +131,9 @@ public sealed class AnimalRace : IDisposable
}
if (FinishedUsers[0].Bet > 0)
await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1));
await _currency.AddAsync(FinishedUsers[0].UserId,
"Won a Race",
FinishedUsers[0].Bet * (_users.Count - 1));
var _ended = OnEnded?.Invoke(this);
});
@@ -148,4 +149,4 @@ public sealed class AnimalRace : IDisposable
_locker.Dispose();
_users.Clear();
}
}
}

View File

@@ -13,16 +13,14 @@ public class AnimalRacingUser
public AnimalRacingUser(string username, ulong userId, long bet)
{
this.Bet = bet;
this.Username = username;
this.UserId = userId;
Bet = bet;
Username = username;
UserId = userId;
}
public override bool Equals(object obj)
=> obj is AnimalRacingUser x
? x.UserId == this.UserId
: false;
=> obj is AnimalRacingUser x ? x.UserId == UserId : false;
public override int GetHashCode()
=> this.UserId.GetHashCode();
}
=> UserId.GetHashCode();
}

View File

@@ -5,14 +5,15 @@ public class AlreadyJoinedException : Exception
{
public AlreadyJoinedException()
{
}
public AlreadyJoinedException(string message) : base(message)
public AlreadyJoinedException(string message)
: base(message)
{
}
public AlreadyJoinedException(string message, Exception innerException) : base(message, innerException)
public AlreadyJoinedException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -7,11 +7,13 @@ public class AlreadyStartedException : Exception
{
}
public AlreadyStartedException(string message) : base(message)
public AlreadyStartedException(string message)
: base(message)
{
}
public AlreadyStartedException(string message, Exception innerException) : base(message, innerException)
public AlreadyStartedException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -7,11 +7,13 @@ public class AnimalRaceFullException : Exception
{
}
public AnimalRaceFullException(string message) : base(message)
public AnimalRaceFullException(string message)
: base(message)
{
}
public AnimalRaceFullException(string message, Exception innerException) : base(message, innerException)
public AnimalRaceFullException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -7,11 +7,13 @@ public class NotEnoughFundsException : Exception
{
}
public NotEnoughFundsException(string message) : base(message)
public NotEnoughFundsException(string message)
: base(message)
{
}
public NotEnoughFundsException(string message, Exception innerException) : base(message, innerException)
public NotEnoughFundsException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -10,7 +10,7 @@ public class RaceOptions : INadekoCommandOptions
public void NormalizeOptions()
{
if (this.StartTime is < 10 or > 120)
this.StartTime = 20;
if (StartTime is < 10 or > 120)
StartTime = 20;
}
}
}

View File

@@ -3,17 +3,9 @@ namespace NadekoBot.Modules.Gambling.Common;
public class Betroll
{
public class Result
{
public int Roll { get; set; }
public float Multiplier { get; set; }
public int Threshold { get; set; }
}
private readonly IOrderedEnumerable<BetRollPair> _thresholdPairs;
private readonly Random _rng;
public Betroll(BetRollConfig settings)
{
_thresholdPairs = settings.Pairs.OrderByDescending(x => x.WhenAbove);
@@ -26,19 +18,15 @@ public class Betroll
var pair = _thresholdPairs.FirstOrDefault(x => x.WhenAbove < roll);
if (pair is null)
{
return new()
{
Multiplier = 0,
Roll = roll,
};
}
return new() { Multiplier = 0, Roll = roll };
return new()
{
Multiplier = pair.MultiplyBy,
Roll = roll,
Threshold = pair.WhenAbove,
};
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; }
}
}

View File

@@ -10,10 +10,13 @@ public class Blackjack
Ended
}
private Deck Deck { get; set; } = new QuadDeck();
public event Func<Blackjack, Task> StateUpdated;
public event Func<Blackjack, Task> GameEnded;
private Deck Deck { get; } = new QuadDeck();
public Dealer Dealer { get; set; }
public List<User> Players { get; set; } = new();
public GameState State { get; set; } = GameState.Starting;
public User CurrentUser { get; private set; }
@@ -22,9 +25,6 @@ public class Blackjack
private readonly ICurrencyService _cs;
private readonly DbService _db;
public event Func<Blackjack, Task> StateUpdated;
public event Func<Blackjack, Task> GameEnded;
private readonly SemaphoreSlim locker = new(1, 1);
public Blackjack(ICurrencyService cs, DbService db)
@@ -54,6 +54,7 @@ public class Blackjack
{
locker.Release();
}
await PrintState();
//if no users joined the game, end it
if (!Players.Any())
@@ -62,6 +63,7 @@ public class Blackjack
var end = GameEnded?.Invoke(this);
return;
}
//give 1 card to the dealer and 2 to each player
Dealer.Cards.Add(Deck.Draw());
foreach (var usr in Players)
@@ -72,15 +74,15 @@ public class Blackjack
if (usr.GetHandValue() == 21)
usr.State = User.UserState.Blackjack;
}
//go through all users and ask them what they want to do
foreach (var usr in Players.Where(x => !x.Done))
{
while (!usr.Done)
{
Log.Information($"Waiting for {usr.DiscordUser}'s move");
await PromptUserMove(usr);
}
}
await PrintState();
State = GameState.Ended;
await Task.Delay(2500);
@@ -106,10 +108,7 @@ public class Blackjack
// either wait for the user to make an action and
// if he doesn't - stand
var finished = await Task.WhenAny(pause, _currentUserMove.Task);
if (finished == pause)
{
await Stand(usr);
}
if (finished == pause) await Stand(usr);
CurrentUser = null;
_currentUserMove = null;
}
@@ -125,10 +124,7 @@ public class Blackjack
if (Players.Count >= 5)
return false;
if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true))
{
return false;
}
if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true)) return false;
Players.Add(new(user, bet));
var _ = PrintState();
@@ -146,7 +142,7 @@ public class Blackjack
if (cu != null && cu.DiscordUser == u)
return await Stand(cu);
return false;
}
@@ -175,7 +171,8 @@ public class Blackjack
{
var hw = Dealer.GetHandValue();
while (hw < 17
|| (hw == 17 && Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10))// hit on soft 17
|| (hw == 17
&& Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10)) // hit on soft 17
{
/* Dealer has
A 6
@@ -201,37 +198,23 @@ public class Blackjack
}
if (hw > 21)
{
foreach (var usr in Players)
{
if (usr.State is User.UserState.Stand or User.UserState.Blackjack)
usr.State = User.UserState.Won;
else
usr.State = User.UserState.Lost;
}
}
else
{
foreach (var usr in Players)
{
if (usr.State == User.UserState.Blackjack)
usr.State = User.UserState.Won;
else if (usr.State == User.UserState.Stand)
usr.State = hw < usr.GetHandValue()
? User.UserState.Won
: User.UserState.Lost;
usr.State = hw < usr.GetHandValue() ? User.UserState.Won : User.UserState.Lost;
else
usr.State = User.UserState.Lost;
}
}
foreach (var usr in Players)
{
if (usr.State is User.UserState.Won or User.UserState.Blackjack)
{
await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, gamble: true);
}
}
await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, true);
}
public async Task<bool> Double(IUser u)
@@ -240,7 +223,7 @@ public class Blackjack
if (cu != null && cu.DiscordUser == u)
return await Double(cu);
return false;
}
@@ -263,20 +246,14 @@ public class Blackjack
u.Cards.Add(Deck.Draw());
if (u.GetHandValue() == 21)
{
//blackjack
u.State = User.UserState.Blackjack;
}
else if (u.GetHandValue() > 21)
{
// user busted
u.State = User.UserState.Bust;
}
else
{
//with double you just get one card, and then you're done
u.State = User.UserState.Stand;
}
_currentUserMove.TrySetResult(true);
return true;
@@ -311,19 +288,12 @@ public class Blackjack
u.Cards.Add(Deck.Draw());
if (u.GetHandValue() == 21)
{
//blackjack
u.State = User.UserState.Blackjack;
}
else if (u.GetHandValue() > 21)
{
// user busted
u.State = User.UserState.Bust;
}
else
{
//you can hit or stand again
}
_currentUserMove.TrySetResult(true);
return true;
@@ -340,4 +310,4 @@ public class Blackjack
return Task.CompletedTask;
return StateUpdated.Invoke(this);
}
}
}

View File

@@ -13,10 +13,7 @@ public abstract class Player
// reduce the value by 10 until it drops below 22
// (emulating the fact that ace is either a 1 or a 11)
var i = Cards.Count(x => x.Number == 1);
while (val > 21 && i-- > 0)
{
val -= 10;
}
while (val > 21 && i-- > 0) val -= 10;
return val;
}
@@ -26,7 +23,6 @@ public abstract class Player
public class Dealer : Player
{
}
public class User : Player
@@ -41,17 +37,19 @@ public class User : Player
Lost
}
public UserState State { get; set; } = UserState.Waiting;
public long Bet { get; set; }
public IUser DiscordUser { get; }
public bool Done
=> State != UserState.Waiting;
public User(IUser user, long bet)
{
if (bet <= 0)
throw new ArgumentOutOfRangeException(nameof(bet));
this.Bet = bet;
this.DiscordUser = user;
Bet = bet;
DiscordUser = user;
}
public UserState State { get; set; } = UserState.Waiting;
public long Bet { get; set; }
public IUser DiscordUser { get; }
public bool Done => State != UserState.Waiting;
}
}

View File

@@ -3,28 +3,18 @@ namespace NadekoBot.Modules.Gambling.Common;
public class CurrencyRaffleGame
{
public enum Type {
public enum Type
{
Mixed,
Normal
}
public class User
{
public IUser DiscordUser { get; set; }
public long Amount { get; set; }
public IEnumerable<User> Users
=> _users;
public override int GetHashCode()
=> DiscordUser.GetHashCode();
public override bool Equals(object obj)
=> obj is User u
? u.DiscordUser == DiscordUser
: false;
}
public Type GameType { get; }
private readonly HashSet<User> _users = new();
public IEnumerable<User> Users => _users;
public Type GameType { get; }
public CurrencyRaffleGame(Type type)
=> GameType = type;
@@ -33,19 +23,12 @@ public class CurrencyRaffleGame
{
// if game type is normal, and someone already joined the game
// (that's the user who created it)
if (GameType == Type.Normal && _users.Count > 0 &&
_users.First().Amount != amount)
if (GameType == Type.Normal && _users.Count > 0 && _users.First().Amount != amount)
return false;
if (!_users.Add(new()
{
DiscordUser = usr,
Amount = amount,
}))
{
if (!_users.Add(new() { DiscordUser = usr, Amount = amount }))
return false;
}
return true;
}
@@ -67,4 +50,16 @@ public class CurrencyRaffleGame
var usrs = _users.ToArray();
return usrs[rng.Next(0, usrs.Length)];
}
}
public class User
{
public IUser DiscordUser { get; set; }
public long Amount { get; set; }
public override int GetHashCode()
=> DiscordUser.GetHashCode();
public override bool Equals(object obj)
=> obj is User u ? u.DiscordUser == DiscordUser : false;
}
}

View File

@@ -7,21 +7,28 @@ public class QuadDeck : Deck
{
CardPool = new(52 * 4);
for (var j = 1; j < 14; j++)
for (var i = 1; i < 5; i++)
{
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));
}
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
{
private static readonly Dictionary<int, string> _cardNames = new() {
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" },
@@ -36,171 +43,50 @@ public class Deck
{ 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 FullName
{
get
{
var str = string.Empty;
if (Number is <= 10 and > 1)
{
str += "_" + Number;
}
else
{
str += GetValueText().ToLowerInvariant();
}
return str + "_of_" + Suit.ToString().ToLowerInvariant();
}
}
public Card(CardSuit s, int cardNum)
{
this.Suit = s;
this.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 this.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[this.Number - 1];
str += _suitToSuitChar[this.Suit];
return str;
}
private readonly string[] _regIndicators = new[]
{
"🇦",
":two:",
":three:",
":four:",
":five:",
":six:",
":seven:",
":eight:",
":nine:",
":keycap_ten:",
"🇯",
"🇶",
"🇰"
};
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 List<Card> CardPool { get; set; }
/// <summary>
/// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time.
/// </summary>
public Deck()
=> RefillPool();
private readonly Random _r = new NadekoRandom();
static Deck()
=> 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.
/// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time.
/// </summary>
public void Restart() => RefillPool();
public Deck()
=> 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.
/// 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
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));
}
}
// 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));
}
private readonly 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.
/// 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()
@@ -221,8 +107,10 @@ public class Deck
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.
/// 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()
{
@@ -230,51 +118,79 @@ public class Deck
var orderedPool = CardPool.Shuffle();
CardPool ??= orderedPool.ToList();
}
public override string ToString() => string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine;
public override string ToString()
=> string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine;
private static void InitHandValues()
{
bool HasPair(List<Card> cards) => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 2) == 1;
bool IsPair(List<Card> cards) => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 3) == 0
&& HasPair(cards);
bool HasPair(List<Card> cards)
{
return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 1;
}
bool IsTwoPair(List<Card> cards) => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 2) == 2;
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;
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;
return newCards.Max(card => card.Number) - newCards.Min(card => card.Number) == 4;
}
bool HasThreeOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 3);
bool HasThreeOfKind(List<Card> cards)
{
return cards.GroupBy(card => card.Number).Any(group => group.Count() == 3);
}
bool IsThreeOfKind(List<Card> cards) => HasThreeOfKind(cards) && !HasPair(cards);
bool IsThreeOfKind(List<Card> cards)
{
return HasThreeOfKind(cards) && !HasPair(cards);
}
bool IsFlush(List<Card> cards) => cards.GroupBy(card => card.Suit).Count() == 1;
bool IsFlush(List<Card> cards)
{
return cards.GroupBy(card => card.Suit).Count() == 1;
}
bool IsFourOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 4);
bool IsFourOfKind(List<Card> cards)
{
return cards.GroupBy(card => card.Number).Any(group => group.Count() == 4);
}
bool IsFullHouse(List<Card> cards) => HasPair(cards) && HasThreeOfKind(cards);
bool IsFullHouse(List<Card> cards)
{
return HasPair(cards) && HasThreeOfKind(cards);
}
bool HasStraightFlush(List<Card> cards) => IsFlush(cards) && IsStraight(cards);
bool HasStraightFlush(List<Card> cards)
{
return IsFlush(cards) && IsStraight(cards);
}
bool IsRoyalFlush(List<Card> cards) => cards.Min(card => card.Number) == 1 &&
cards.Max(card => card.Number) == 13
&& HasStraightFlush(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) => HasStraightFlush(cards) && !IsRoyalFlush(cards);
bool IsStraightFlush(List<Card> cards)
{
return HasStraightFlush(cards) && !IsRoyalFlush(cards);
}
_handValues = new()
{
@@ -294,10 +210,108 @@ public class Deck
{
if (_handValues is null)
InitHandValues();
foreach (var kvp in _handValues.Where(x => x.Value(cards)))
{
return kvp.Key;
}
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;
}
}
}

View File

@@ -7,11 +7,21 @@ public class EventOptions : INadekoCommandOptions
{
[Option('a', "amount", Required = false, Default = 100, HelpText = "Amount of currency each user receives.")]
public long Amount { get; set; } = 100;
[Option('p', "pot-size", Required = false, Default = 0, HelpText = "The maximum amount of currency that can be rewarded. 0 means no limit.")]
public long PotSize { get; set; } = 0;
[Option('p',
"pot-size",
Required = false,
Default = 0,
HelpText = "The maximum amount of currency that can be rewarded. 0 means no limit.")]
public long PotSize { get; set; }
//[Option('t', "type", Required = false, Default = "reaction", HelpText = "Type of the event. reaction, gamestatus or joinserver.")]
//public string TypeString { get; set; } = "reaction";
[Option('d', "duration", Required = false, Default = 24, HelpText = "Number of hours the event should run for. Default 24.")]
[Option('d',
"duration",
Required = false,
Default = 24,
HelpText = "Number of hours the event should run for. Default 24.")]
public int Hours { get; set; } = 24;
@@ -26,4 +36,4 @@ public class EventOptions : INadekoCommandOptions
if (PotSize != 0 && PotSize < Amount)
PotSize = 0;
}
}
}

View File

@@ -5,37 +5,44 @@ namespace NadekoBot.Modules.Gambling.Common.Events;
public class GameStatusEvent : ICurrencyEvent
{
public event Func<ulong, Task> OnEnded;
private long PotSize { get; set; }
public bool Stopped { get; private set; }
public bool PotEmptied { get; private set; }
private readonly DiscordSocketClient _client;
private readonly IGuild _guild;
private IUserMessage _msg;
private readonly ICurrencyService _cs;
private readonly long _amount;
private long PotSize { get; set; }
public bool Stopped { get; private set; }
public bool PotEmptied { get; private set; } = false;
private readonly Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> _embedFunc;
private readonly bool _isPotLimited;
private readonly ITextChannel _channel;
private readonly ConcurrentHashSet<ulong> _awardedUsers = new();
private readonly ConcurrentQueue<ulong> _toAward = new();
private readonly Timer _t;
private readonly Timer _timeout = null;
private readonly Timer _timeout;
private readonly EventOptions _opts;
private readonly string _code;
public event Func<ulong, Task> OnEnded;
private readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10)
.Concat(Enumerable.Range(65, 26))
.Concat(Enumerable.Range(97, 26))
.Select(x => (char)x)
.ToArray();
.Concat(Enumerable.Range(65, 26))
.Concat(Enumerable.Range(97, 26))
.Select(x => (char)x)
.ToArray();
public GameStatusEvent(DiscordSocketClient client, ICurrencyService cs,SocketGuild g, ITextChannel ch,
EventOptions opt, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
private readonly object stopLock = new();
private readonly object potLock = new();
public GameStatusEvent(
DiscordSocketClient client,
ICurrencyService cs,
SocketGuild g,
ITextChannel ch,
EventOptions opt,
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
{
_client = client;
_guild = g;
@@ -51,9 +58,7 @@ public class GameStatusEvent : ICurrencyEvent
_t = new(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
if (_opts.Hours > 0)
{
_timeout = new(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
}
}
private void EventTimeout(object state)
@@ -65,10 +70,7 @@ public class GameStatusEvent : ICurrencyEvent
{
var potEmpty = PotEmptied;
var toAward = new List<ulong>();
while (_toAward.TryDequeue(out var x))
{
toAward.Add(x);
}
while (_toAward.TryDequeue(out var x)) toAward.Add(x);
if (!toAward.Any())
return;
@@ -78,15 +80,14 @@ public class GameStatusEvent : ICurrencyEvent
await _cs.AddBulkAsync(toAward,
toAward.Select(x => "GameStatus Event"),
toAward.Select(x => _amount),
gamble: true);
true);
if (_isPotLimited)
{
await _msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
}, new() { RetryMode = RetryMode.AlwaysRetry });
}
{
m.Embed = GetEmbed(PotSize).Build();
},
new() { RetryMode = RetryMode.AlwaysRetry });
Log.Information("Awarded {0} users {1} currency.{2}",
toAward.Count,
@@ -97,7 +98,6 @@ public class GameStatusEvent : ICurrencyEvent
{
var _ = StopEvent();
}
}
catch (Exception ex)
{
@@ -119,13 +119,9 @@ public class GameStatusEvent : ICurrencyEvent
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, Cacheable<IMessageChannel, ulong> cacheable)
{
if (msg.Id == _msg.Id)
{
await StopEvent();
}
if (msg.Id == _msg.Id) await StopEvent();
}
private readonly object stopLock = new();
public async Task StopEvent()
{
await Task.Yield();
@@ -139,7 +135,12 @@ public class GameStatusEvent : ICurrencyEvent
_client.SetGameAsync(null);
_t.Change(Timeout.Infinite, Timeout.Infinite);
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
try { var _ = _msg.DeleteAsync(); } catch { }
try
{
var _ = _msg.DeleteAsync();
}
catch { }
var os = OnEnded(_guild.Id);
}
}
@@ -152,9 +153,7 @@ public class GameStatusEvent : ICurrencyEvent
|| gu.IsBot // no bots
|| msg.Content != _code // code has to be the same
|| (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5) // no recently created accounts
{
return;
}
// there has to be money left in the pot
// and the user wasn't rewarded
if (_awardedUsers.Add(msg.Author.Id) && TryTakeFromPot())
@@ -166,21 +165,16 @@ public class GameStatusEvent : ICurrencyEvent
try
{
await msg.DeleteAsync(new()
{
RetryMode = RetryMode.AlwaysFail
});
await msg.DeleteAsync(new() { RetryMode = RetryMode.AlwaysFail });
}
catch { }
});
return Task.CompletedTask;
}
private readonly object potLock = new();
private bool TryTakeFromPot()
{
if (_isPotLimited)
{
lock (potLock)
{
if (PotSize < _amount)
@@ -189,7 +183,7 @@ public class GameStatusEvent : ICurrencyEvent
PotSize -= _amount;
return true;
}
}
return true;
}
}
}

View File

@@ -6,4 +6,4 @@ public interface ICurrencyEvent
event Func<ulong, Task> OnEnded;
Task StopEvent();
Task StartEvent();
}
}

View File

@@ -5,6 +5,10 @@ namespace NadekoBot.Modules.Gambling.Common.Events;
public class ReactionEvent : ICurrencyEvent
{
public event Func<ulong, Task> OnEnded;
private long PotSize { get; set; }
public bool Stopped { get; private set; }
public bool PotEmptied { get; private set; }
private readonly DiscordSocketClient _client;
private readonly IGuild _guild;
private IUserMessage _msg;
@@ -12,25 +16,28 @@ public class ReactionEvent : ICurrencyEvent
private readonly ICurrencyService _cs;
private readonly long _amount;
private long PotSize { get; set; }
public bool Stopped { get; private set; }
public bool PotEmptied { get; private set; } = false;
private readonly Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> _embedFunc;
private readonly bool _isPotLimited;
private readonly ITextChannel _channel;
private readonly ConcurrentHashSet<ulong> _awardedUsers = new();
private readonly ConcurrentQueue<ulong> _toAward = new();
private readonly Timer _t;
private readonly Timer _timeout = null;
private readonly Timer _timeout;
private readonly bool _noRecentlyJoinedServer;
private readonly EventOptions _opts;
private readonly GamblingConfig _config;
public event Func<ulong, Task> OnEnded;
private readonly object stopLock = new();
public ReactionEvent(DiscordSocketClient client, ICurrencyService cs,
SocketGuild g, ITextChannel ch, EventOptions opt, GamblingConfig config,
private readonly object potLock = new();
public ReactionEvent(
DiscordSocketClient client,
ICurrencyService cs,
SocketGuild g,
ITextChannel ch,
EventOptions opt,
GamblingConfig config,
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
{
_client = client;
@@ -47,9 +54,7 @@ public class ReactionEvent : ICurrencyEvent
_t = new(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
if (_opts.Hours > 0)
{
_timeout = new(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
}
}
private void EventTimeout(object state)
@@ -61,28 +66,21 @@ public class ReactionEvent : ICurrencyEvent
{
var potEmpty = PotEmptied;
var toAward = new List<ulong>();
while (_toAward.TryDequeue(out var x))
{
toAward.Add(x);
}
while (_toAward.TryDequeue(out var x)) toAward.Add(x);
if (!toAward.Any())
return;
try
{
await _cs.AddBulkAsync(toAward,
toAward.Select(x => "Reaction Event"),
toAward.Select(x => _amount),
gamble: true);
await _cs.AddBulkAsync(toAward, toAward.Select(x => "Reaction Event"), toAward.Select(x => _amount), true);
if (_isPotLimited)
{
await _msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
}, new() { RetryMode = RetryMode.AlwaysRetry });
}
{
m.Embed = GetEmbed(PotSize).Build();
},
new() { RetryMode = RetryMode.AlwaysRetry });
Log.Information("Awarded {0} users {1} currency.{2}",
toAward.Count,
@@ -103,13 +101,9 @@ public class ReactionEvent : ICurrencyEvent
public async Task StartEvent()
{
if (Emote.TryParse(_config.Currency.Sign, out var emote))
{
_emote = emote;
}
else
{
_emote = new Emoji(_config.Currency.Sign);
}
_msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize));
await _msg.AddReactionAsync(_emote);
_client.MessageDeleted += OnMessageDeleted;
@@ -122,13 +116,9 @@ public class ReactionEvent : ICurrencyEvent
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, Cacheable<IMessageChannel, ulong> cacheable)
{
if (msg.Id == _msg.Id)
{
await StopEvent();
}
if (msg.Id == _msg.Id) await StopEvent();
}
private readonly object stopLock = new();
public async Task StopEvent()
{
await Task.Yield();
@@ -141,27 +131,37 @@ public class ReactionEvent : ICurrencyEvent
_client.ReactionAdded -= HandleReaction;
_t.Change(Timeout.Infinite, Timeout.Infinite);
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
try { var _ = _msg.DeleteAsync(); } catch { }
try
{
var _ = _msg.DeleteAsync();
}
catch { }
var os = OnEnded(_guild.Id);
}
}
private Task HandleReaction(Cacheable<IUserMessage, ulong> msg,
Cacheable<IMessageChannel, ulong> cacheable, SocketReaction r)
private Task HandleReaction(
Cacheable<IUserMessage, ulong> msg,
Cacheable<IMessageChannel, ulong> cacheable,
SocketReaction r)
{
var _ = Task.Run(() =>
{
if (_emote.Name != r.Emote.Name)
return;
if ((r.User.IsSpecified ? r.User.Value : null) is not IGuildUser gu // no unknown users, as they could be bots, or alts
if ((r.User.IsSpecified
? r.User.Value
: null) is not IGuildUser gu // no unknown users, as they could be bots, or alts
|| msg.Id != _msg.Id // same message
|| gu.IsBot // no bots
|| (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5 // no recently created accounts
|| (_noRecentlyJoinedServer && // if specified, no users who joined the server in the last 24h
(gu.JoinedAt is null || (DateTime.UtcNow - gu.JoinedAt.Value).TotalDays < 1))) // and no users for who we don't know when they joined
{
|| (_noRecentlyJoinedServer
&& // if specified, no users who joined the server in the last 24h
(gu.JoinedAt is null
|| (DateTime.UtcNow - gu.JoinedAt.Value).TotalDays
< 1))) // and no users for who we don't know when they joined
return;
}
// there has to be money left in the pot
// and the user wasn't rewarded
if (_awardedUsers.Add(r.UserId) && TryTakeFromPot())
@@ -174,11 +174,9 @@ public class ReactionEvent : ICurrencyEvent
return Task.CompletedTask;
}
private readonly object potLock = new();
private bool TryTakeFromPot()
{
if (_isPotLimited)
{
lock (potLock)
{
if (PotSize < _amount)
@@ -187,7 +185,7 @@ public class ReactionEvent : ICurrencyEvent
PotSize -= _amount;
return true;
}
}
return true;
}
}
}

View File

@@ -3,25 +3,13 @@ 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>
{
public GamblingConfig()
{
BetRoll = new();
WheelOfFortune = new();
Waifu = new();
Currency = new();
BetFlip = new();
Generation = new();
Timely = new();
Decay = new();
Slots = new();
}
[Comment(@"DO NOT CHANGE")]
public int Version { get; set; } = 2;
@@ -60,13 +48,26 @@ Set 0 for unlimited")]
[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
@@ -108,8 +109,7 @@ Doesn't have to be ordered.")]
public BetRollConfig()
=> Pairs = new BetRollPair[]
{
new() { WhenAbove = 99, MultiplyBy = 10 },
new() { WhenAbove = 90, MultiplyBy = 4 },
new() { WhenAbove = 99, MultiplyBy = 10 }, new() { WhenAbove = 90, MultiplyBy = 4 },
new() { WhenAbove = 66, MultiplyBy = 2 }
};
}
@@ -162,17 +162,7 @@ public partial class WheelOfFortuneSettings
public decimal[] Multipliers { get; set; }
public WheelOfFortuneSettings()
=> Multipliers = new decimal[]
{
1.7M,
1.5M,
0.2M,
0.1M,
0.3M,
0.5M,
1.2M,
2.4M,
};
=> Multipliers = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
}
[Cloneable]
@@ -263,17 +253,17 @@ Default 1 (meaning no effect)")]
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 partial class SlotsConfig
public sealed class SlotsConfig
{
[Comment(@"Hex value of the color which the numbers on the slot image will have.")]
public Rgba32 CurrencyFontColor { get; set; } = SixLabors.ImageSharp.Color.Red;
public Rgba32 CurrencyFontColor { get; set; } = Color.Red;
}
[Cloneable]
@@ -282,16 +272,19 @@ 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)
public WaifuItemModel(
string itemEmoji,
int price,
string name,
bool negative = false)
{
ItemEmoji = itemEmoji;
Price = price;
@@ -300,13 +293,13 @@ public sealed partial class WaifuItemModel
}
public override string ToString() => Name;
public override string ToString()
=> Name;
}
[Cloneable]
public sealed partial class BetRollPair
{
public int WhenAbove { get; set; }
public float MultiplyBy { get; set; }
}
}

View File

@@ -5,4 +5,4 @@ public enum GamblingError
{
None,
NotEnough
}
}

View File

@@ -5,57 +5,55 @@ 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 GamblingConfig _config => _lazyConfig.Value;
protected string CurrencySign => _config.Currency.Sign;
protected string CurrencyName => _config.Currency.Name;
protected GamblingModule(GamblingConfigService gambService)
=> _lazyConfig = new(() => gambService.Data);
private async Task<bool> InternalCheckBet(long amount)
{
if (amount < 1)
{
return false;
}
if (amount < 1) return false;
if (amount < _config.MinBet)
{
await ReplyErrorLocalizedAsync(strs.min_bet_limit(
Format.Bold(_config.MinBet.ToString()) + CurrencySign));
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));
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);
}
if (amount < 1) return Task.FromResult(false);
return InternalCheckBet(amount);
}
protected Task<bool> CheckBetOptional(long amount)
{
if (amount == 0)
{
return Task.FromResult(true);
}
if (amount == 0) return Task.FromResult(true);
return InternalCheckBet(amount);
}
}
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
{
protected GamblingSubmodule(GamblingConfigService gamblingConfService) : base(gamblingConfService)
protected GamblingSubmodule(GamblingConfigService gamblingConfService)
: base(gamblingConfService)
{
}
}
}

View File

@@ -5,4 +5,4 @@ public class Payout
{
public string User { get; set; }
public int Amount { get; set; }
}
}

View File

@@ -3,64 +3,72 @@ namespace NadekoBot.Modules.Gambling.Common;
public class RollDuelGame
{
public ulong P1 { get; }
public ulong P2 { get; }
private readonly ulong _botId;
public long Amount { get; }
private readonly ICurrencyService _cs;
public enum Reason
{
Normal,
NoFunds,
Timeout
}
public enum State
{
Waiting,
Running,
Ended,
Ended
}
public enum Reason
{
Normal,
NoFunds,
Timeout,
}
private readonly Timer _timeoutTimer;
private readonly NadekoRandom _rng = new();
private readonly SemaphoreSlim _locker = new(1, 1);
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; }
public RollDuelGame(ICurrencyService cs, ulong botId, ulong p1, ulong p2, long amount)
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)
{
this.P1 = p1;
this.P2 = p2;
this._botId = botId;
this.Amount = 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));
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()
@@ -78,16 +86,17 @@ public class RollDuelGame
_locker.Release();
}
if(!await _cs.RemoveAsync(P1, "Roll Duel", Amount))
if (!await _cs.RemoveAsync(P1, "Roll Duel", Amount))
{
await (OnEnded?.Invoke(this, Reason.NoFunds));
await OnEnded?.Invoke(this, Reason.NoFunds);
CurrentState = State.Ended;
return;
}
if(!await _cs.RemoveAsync(P2, "Roll Duel", Amount))
if (!await _cs.RemoveAsync(P2, "Roll Duel", Amount))
{
await _cs.AddAsync(P1, "Roll Duel - refund", Amount);
await (OnEnded?.Invoke(this, Reason.NoFunds));
await OnEnded?.Invoke(this, Reason.NoFunds);
CurrentState = State.Ended;
return;
}
@@ -101,26 +110,25 @@ public class RollDuelGame
if (n1 != n2)
{
if (n1 > n2)
{
Winner = P1;
}
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 { }
try { await OnGameTick?.Invoke(this); }
catch { }
await Task.Delay(2500);
if (n1 != n2)
break;
}
while (true);
} while (true);
CurrentState = State.Ended;
await (OnEnded?.Invoke(this, Reason.Normal));
await OnEnded?.Invoke(this, Reason.Normal);
}
}
@@ -128,4 +136,4 @@ public struct RollDuelChallenge
{
public ulong Player1 { get; set; }
public ulong Player2 { get; set; }
}
}

View File

@@ -3,27 +3,11 @@ namespace NadekoBot.Modules.Gambling.Common.Slot;
public class SlotGame
{
public class Result
{
public float Multiplier { get; }
public int[] Rolls { get; }
public Result(float multiplier, int[] rolls)
{
Multiplier = multiplier;
Rolls = rolls;
}
}
private static readonly Random _rng = new NadekoRandom();
public SlotGame()
{
}
public Result Spin()
{
var rolls = new int[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) };
var rolls = new[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) };
var multi = 0;
if (rolls.All(x => x == 5))
@@ -37,4 +21,16 @@ public class SlotGame
return new(multi, rolls);
}
}
public class Result
{
public float Multiplier { get; }
public int[] Rolls { get; }
public Result(float multiplier, int[] rolls)
{
Multiplier = multiplier;
Rolls = rolls;
}
}
}

View File

@@ -7,4 +7,4 @@ public class SlotResponse
public long Won { get; set; }
public List<int> Rolls { get; set; } = new();
public GamblingError Error { get; set; }
}
}

View File

@@ -13,4 +13,4 @@ public enum AffinityTitle
Sloot,
Depraved,
Harlot
}
}

View File

@@ -14,5 +14,5 @@ public enum ClaimTitle
Veteran,
Incubis,
Harem_King,
Harem_God,
}
Harem_God
}

View File

@@ -7,4 +7,4 @@ public enum DivorceResult
SucessWithPenalty,
NotYourWife,
Cooldown
}
}

View File

@@ -6,4 +6,4 @@ public enum WaifuClaimResult
Success,
NotEnoughFunds,
InsufficientAmount
}
}

View File

@@ -3,19 +3,17 @@ namespace NadekoBot.Modules.Gambling.Common.WheelOfFortune;
public class WheelOfFortuneGame
{
public class Result
{
public int Index { get; set; }
public long Amount { get; set; }
}
private readonly NadekoRandom _rng;
private readonly ICurrencyService _cs;
private readonly long _bet;
private readonly GamblingConfig _config;
private readonly ulong _userId;
public WheelOfFortuneGame(ulong userId, long bet, GamblingConfig config, ICurrencyService cs)
public WheelOfFortuneGame(
ulong userId,
long bet,
GamblingConfig config,
ICurrencyService cs)
{
_rng = new();
_cs = cs;
@@ -31,12 +29,14 @@ public class WheelOfFortuneGame
var amount = (long)(_bet * _config.WheelOfFortune.Multipliers[result]);
if (amount > 0)
await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, gamble: true);
await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, true);
return new()
{
Index = result,
Amount = amount,
};
return new() { Index = result, Amount = amount };
}
}
public class Result
{
public int Index { get; set; }
public long Amount { get; set; }
}
}

View File

@@ -6,53 +6,54 @@ namespace NadekoBot.Modules.Gambling.Common.Connect4;
public sealed class Connect4Game : IDisposable
{
public enum Field //temporary most likely
{
Empty,
P1,
P2
}
public enum Phase
{
Joining, // waiting for second player to join
P1Move,
P2Move,
Ended,
}
public enum Field //temporary most likely
{
Empty,
P1,
P2,
Ended
}
public enum Result
{
Draw,
CurrentPlayerWon,
OtherPlayerWon,
OtherPlayerWon
}
public const int NumberOfColumns = 7;
public const int NumberOfRows = 6;
public Phase CurrentPhase { get; private set; } = Phase.Joining;
//state is bottom to top, left to right
private readonly Field[] _gameState = new Field[NumberOfRows * NumberOfColumns];
private readonly (ulong UserId, string Username)?[] _players = new(ulong, string)?[2];
public ImmutableArray<Field> GameState => _gameState.ToImmutableArray();
public ImmutableArray<(ulong UserId, string Username)?> Players => _players.ToImmutableArray();
public (ulong UserId, string Username) CurrentPlayer => CurrentPhase == Phase.P1Move
? _players[0].Value
: _players[1].Value;
public (ulong UserId, string Username) OtherPlayer => CurrentPhase == Phase.P2Move
? _players[0].Value
: _players[1].Value;
//public event Func<Connect4Game, Task> OnGameStarted;
public event Func<Connect4Game, Task> OnGameStateUpdated;
public event Func<Connect4Game, Task> OnGameFailedToStart;
public event Func<Connect4Game, Result, Task> OnGameEnded;
public Phase CurrentPhase { get; private set; } = Phase.Joining;
public ImmutableArray<Field> GameState
=> _gameState.ToImmutableArray();
public ImmutableArray<(ulong UserId, string Username)?> Players
=> _players.ToImmutableArray();
public (ulong UserId, string Username) CurrentPlayer
=> CurrentPhase == Phase.P1Move ? _players[0].Value : _players[1].Value;
public (ulong UserId, string Username) OtherPlayer
=> CurrentPhase == Phase.P2Move ? _players[0].Value : _players[1].Value;
//state is bottom to top, left to right
private readonly Field[] _gameState = new Field[NumberOfRows * NumberOfColumns];
private readonly (ulong UserId, string Username)?[] _players = new (ulong, string)?[2];
private readonly SemaphoreSlim _locker = new(1, 1);
private readonly Options _options;
private readonly ICurrencyService _cs;
@@ -69,17 +70,18 @@ public sealed class Connect4Game : IDisposable
* [ ][ ][ ][ ][ ][ ]
*/
public Connect4Game(ulong userId, string userName, Options options, ICurrencyService cs)
public Connect4Game(
ulong userId,
string userName,
Options options,
ICurrencyService cs)
{
_players[0] = (userId, userName);
_options = options;
_cs = cs;
_rng = new();
for (var i = 0; i < NumberOfColumns * NumberOfRows; i++)
{
_gameState[i] = Field.Empty;
}
for (var i = 0; i < NumberOfColumns * NumberOfRows; i++) _gameState[i] = Field.Empty;
}
public void Initialize()
@@ -97,7 +99,6 @@ public sealed class Connect4Game : IDisposable
var __ = OnGameFailedToStart?.Invoke(this);
CurrentPhase = Phase.Ended;
await _cs.AddAsync(_players[0].Value.UserId, "Connect4-refund", _options.Bet, true);
return;
}
}
finally { _locker.Release(); }
@@ -127,18 +128,23 @@ public sealed class Connect4Game : IDisposable
_players[0] = (userId, userName);
}
else //else join as a second player
{
_players[1] = (userId, userName);
}
CurrentPhase = Phase.P1Move; //start the game
_playerTimeoutTimer = new(async state =>
{
await _locker.WaitAsync();
try
{
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
}
finally { _locker.Release(); }
}, null, TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
await _locker.WaitAsync();
try
{
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
}
finally { _locker.Release(); }
},
null,
TimeSpan.FromSeconds(_options.TurnTimer),
TimeSpan.FromSeconds(_options.TurnTimer));
var __ = OnGameStateUpdated?.Invoke(this);
return true;
@@ -167,13 +173,11 @@ public sealed class Connect4Game : IDisposable
var start = NumberOfRows * inputCol;
for (var i = start; i < start + NumberOfRows; i++)
{
if (_gameState[i] == Field.Empty)
{
_gameState[i] = GetPlayerPiece(userId);
break;
}
}
//check winnning condition
// ok, i'll go from [0-2] in rows (and through all columns) and check upward if 4 are connected
@@ -190,7 +194,6 @@ public sealed class Connect4Game : IDisposable
var first = _gameState[i + (j * NumberOfRows)];
if (first != Field.Empty)
{
for (var k = 1; k < 4; k++)
{
var next = _gameState[i + k + (j * NumberOfRows)];
@@ -201,9 +204,11 @@ public sealed class Connect4Game : IDisposable
else
continue;
}
else break;
else
{
break;
}
}
}
}
}
@@ -220,7 +225,6 @@ public sealed class Connect4Game : IDisposable
var first = _gameState[j + (i * NumberOfRows)];
if (first != Field.Empty)
{
for (var k = 1; k < 4; k++)
{
var next = _gameState[j + ((i + k) * NumberOfRows)];
@@ -231,7 +235,6 @@ public sealed class Connect4Game : IDisposable
continue;
else break;
}
}
}
}
@@ -308,10 +311,7 @@ public sealed class Connect4Game : IDisposable
}
//check draw? if it's even possible
if (_gameState.All(x => x != Field.Empty))
{
EndGame(Result.Draw, null);
}
if (_gameState.All(x => x != Field.Empty)) EndGame(Result.Draw, null);
if (CurrentPhase != Phase.Ended)
{
@@ -322,6 +322,7 @@ public sealed class Connect4Game : IDisposable
ResetTimer();
}
var _ = OnGameStateUpdated?.Invoke(this);
return true;
}
@@ -329,7 +330,8 @@ public sealed class Connect4Game : IDisposable
}
private void ResetTimer()
=> _playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
=> _playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer),
TimeSpan.FromSeconds(_options.TurnTimer));
private void EndGame(Result result, ulong? winId)
{
@@ -340,27 +342,25 @@ public sealed class Connect4Game : IDisposable
if (result == Result.Draw)
{
_cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", this._options.Bet, true);
_cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", this._options.Bet, true);
_cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", _options.Bet, true);
_cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", _options.Bet, true);
return;
}
if (winId != null)
_cs.AddAsync(winId.Value, "Connnect4-win", (long)(this._options.Bet * 1.98), true);
_cs.AddAsync(winId.Value, "Connnect4-win", (long)(_options.Bet * 1.98), true);
}
private Field GetPlayerPiece(ulong userId) => _players[0].Value.UserId == userId
? Field.P1
: Field.P2;
private Field GetPlayerPiece(ulong userId)
=> _players[0].Value.UserId == userId ? Field.P1 : Field.P2;
//column is full if there are no empty fields
private bool IsColumnFull(int column)
{
var start = NumberOfRows * column;
for (var i = start; i < start + NumberOfRows; i++)
{
if (_gameState[i] == Field.Empty)
return false;
}
return true;
}
@@ -375,6 +375,16 @@ public sealed class Connect4Game : IDisposable
public class Options : INadekoCommandOptions
{
[Option('t',
"turn-timer",
Required = false,
Default = 15,
HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")]
public int TurnTimer { get; set; } = 15;
[Option('b', "bet", Required = false, Default = 0, HelpText = "Amount you bet. Default 0.")]
public int Bet { get; set; }
public void NormalizeOptions()
{
if (TurnTimer is < 5 or > 60)
@@ -383,10 +393,5 @@ public sealed class Connect4Game : IDisposable
if (Bet < 0)
Bet = 0;
}
[Option('t', "turn-timer", Required = false, Default = 15, HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")]
public int TurnTimer { get; set; } = 15;
[Option('b', "bet", Required = false, Default = 0, HelpText = "Amount you bet. Default 0.")]
public int Bet { get; set; } = 0;
}
}
}

View File

@@ -11,9 +11,28 @@ public partial class Gambling
[Group]
public class Connect4Commands : GamblingSubmodule<GamblingService>
{
private static readonly string[] numbers =
{
":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:"
};
private int RepostCounter
{
get => _repostCounter;
set
{
if (value is < 0 or > 7)
_repostCounter = 0;
else _repostCounter = value;
}
}
private readonly DiscordSocketClient _client;
private readonly ICurrencyService _cs;
private static readonly string[] numbers = new string[] { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:" };
private IUserMessage msg;
private int _repostCounter;
public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
: base(gamb)
@@ -22,7 +41,8 @@ public partial class Gambling
_cs = cs;
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[NadekoOptionsAttribute(typeof(Connect4Game.Options))]
public async Task Connect4(params string[] args)
@@ -45,7 +65,6 @@ public partial class Gambling
}
if (options.Bet > 0)
{
if (!await _cs.RemoveAsync(ctx.User.Id, "Connect4-bet", options.Bet, true))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
@@ -53,7 +72,6 @@ public partial class Gambling
game.Dispose();
return;
}
}
game.OnGameStateUpdated += Game_OnGameStateUpdated;
game.OnGameFailedToStart += Game_OnGameFailedToStart;
@@ -62,13 +80,9 @@ public partial class Gambling
game.Initialize();
if (options.Bet == 0)
{
await ReplyConfirmLocalizedAsync(strs.connect4_created);
}
else
{
await ReplyErrorLocalizedAsync(strs.connect4_created_bet(options.Bet + CurrencySign));
}
Task _client_MessageReceived(SocketMessage arg)
{
@@ -78,22 +92,20 @@ public partial class Gambling
var _ = Task.Run(async () =>
{
var success = false;
if (int.TryParse(arg.Content, out var col))
{
success = await game.Input(arg.Author.Id, col);
}
if (int.TryParse(arg.Content, out var col)) success = await game.Input(arg.Author.Id, col);
if (success)
try { await arg.DeleteAsync(); } catch { }
{
try { await arg.DeleteAsync(); }
catch { }
}
else
{
if (game.CurrentPhase is Connect4Game.Phase.Joining or Connect4Game.Phase.Ended)
{
return;
}
if (game.CurrentPhase is Connect4Game.Phase.Joining or Connect4Game.Phase.Ended) return;
RepostCounter++;
if (RepostCounter == 0)
try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()); } catch { }
try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()); }
catch { }
}
});
return Task.CompletedTask;
@@ -106,6 +118,7 @@ public partial class Gambling
_client.MessageReceived -= _client_MessageReceived;
toDispose.Dispose();
}
return ErrorLocalizedAsync(strs.connect4_failed_to_start);
}
@@ -119,44 +132,28 @@ public partial class Gambling
string title;
if (result == Connect4Game.Result.CurrentPlayerWon)
{
title = GetText(strs.connect4_won(Format.Bold(arg.CurrentPlayer.Username), Format.Bold(arg.OtherPlayer.Username)));
}
title = GetText(strs.connect4_won(Format.Bold(arg.CurrentPlayer.Username),
Format.Bold(arg.OtherPlayer.Username)));
else if (result == Connect4Game.Result.OtherPlayerWon)
{
title = GetText(strs.connect4_won(Format.Bold(arg.OtherPlayer.Username), Format.Bold(arg.CurrentPlayer.Username)));
}
title = GetText(strs.connect4_won(Format.Bold(arg.OtherPlayer.Username),
Format.Bold(arg.CurrentPlayer.Username)));
else
title = GetText(strs.connect4_draw);
return msg.ModifyAsync(x => x.Embed = _eb.Create()
.WithTitle(title)
.WithDescription(GetGameStateText(game))
.WithOkColor()
.Build());
}
}
private IUserMessage msg;
private int _repostCounter = 0;
private int RepostCounter
{
get => _repostCounter;
set
{
if (value is < 0 or > 7)
_repostCounter = 0;
else _repostCounter = value;
.WithTitle(title)
.WithDescription(GetGameStateText(game))
.WithOkColor()
.Build());
}
}
private async Task Game_OnGameStateUpdated(Connect4Game game)
{
var embed = _eb.Create()
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
.WithDescription(GetGameStateText(game))
.WithOkColor();
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
.WithDescription(GetGameStateText(game))
.WithOkColor();
if (msg is null)
@@ -185,14 +182,12 @@ public partial class Gambling
else
sb.Append("🔵"); //blue circle
}
sb.AppendLine();
}
for (var i = 0; i < Connect4Game.NumberOfColumns; i++)
{
sb.Append(numbers[i]);
}
for (var i = 0; i < Connect4Game.NumberOfColumns; i++) sb.Append(numbers[i]);
return sb.ToString();
}
}
}
}

View File

@@ -1,8 +1,8 @@
#nullable disable
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Gambling.Common.Events;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.Events;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Gambling;
@@ -11,65 +11,51 @@ public partial class Gambling
[Group]
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
{
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
: base(gamblingConf)
{
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[NadekoOptionsAttribute(typeof(EventOptions))]
[OwnerOnly]
public async Task EventStart(CurrencyEvent.Type ev, params string[] options)
{
var (opts, _) = OptionsParser.ParseFrom(new EventOptions(), options);
if (!await _service.TryCreateEventAsync(ctx.Guild.Id,
ctx.Channel.Id,
ev,
opts,
GetEmbed))
{
if (!await _service.TryCreateEventAsync(ctx.Guild.Id, ctx.Channel.Id, ev, opts, GetEmbed))
await ReplyErrorLocalizedAsync(strs.start_event_fail);
}
}
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
=> type switch
{
CurrencyEvent.Type.Reaction => _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.event_title(type.ToString())))
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
.WithOkColor()
.WithTitle(GetText(strs.event_title(type.ToString())))
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
CurrencyEvent.Type.GameStatus => _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.event_title(type.ToString())))
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
.WithOkColor()
.WithTitle(GetText(strs.event_title(type.ToString())))
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
_ => throw new ArgumentOutOfRangeException(nameof(type))
};
private string GetReactionDescription(long amount, long potSize)
{
var potSizeStr = Format.Bold(potSize == 0
? "∞" + CurrencySign
: potSize + CurrencySign);
return GetText(strs.new_reaction_event(
CurrencySign,
Format.Bold(amount + CurrencySign),
potSizeStr));
var potSizeStr = Format.Bold(potSize == 0 ? "∞" + CurrencySign : potSize + CurrencySign);
return GetText(strs.new_reaction_event(CurrencySign, Format.Bold(amount + CurrencySign), potSizeStr));
}
private string GetGameStatusDescription(long amount, long potSize)
{
var potSizeStr = Format.Bold(potSize == 0
? "∞" + CurrencySign
: potSize + CurrencySign);
return GetText(strs.new_gamestatus_event(
CurrencySign,
Format.Bold(amount + CurrencySign),
potSizeStr));
var potSizeStr = Format.Bold(potSize == 0 ? "∞" + CurrencySign : potSize + CurrencySign);
return GetText(strs.new_gamestatus_event(CurrencySign, Format.Bold(amount + CurrencySign), potSizeStr));
}
}
}
}

View File

@@ -1,6 +1,6 @@
#nullable disable
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
namespace NadekoBot.Modules.Gambling;
@@ -10,29 +10,35 @@ public partial class Gambling
{
public enum Mixed { Mixed }
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService)
: base(gamblingConfService)
{
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task RaffleCur(Mixed _, ShmartNumber amount) =>
RaffleCur(amount, true);
public Task RaffleCur(Mixed _, ShmartNumber amount)
=> RaffleCur(amount, true);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task RaffleCur(ShmartNumber amount, bool mixed = false)
{
if (!await CheckBetMandatory(amount))
return;
async Task OnEnded(IUser arg, long won)
{
await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName, Format.Bold(arg.ToString()), won + CurrencySign)));
await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName,
Format.Bold(arg.ToString()),
won + CurrencySign)));
}
var res = await _service.JoinOrCreateGame(ctx.Channel.Id,
ctx.User, amount, mixed, OnEnded);
var res = await _service.JoinOrCreateGame(ctx.Channel.Id, ctx.User, amount, mixed, OnEnded);
if (res.Item1 != null)
{
@@ -49,4 +55,4 @@ public partial class Gambling
}
}
}
}
}

View File

@@ -11,7 +11,9 @@ public partial class Gambling
[Group]
public class DiceRollCommands : NadekoSubmodule
{
private static readonly Regex dndRegex = new(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
private static readonly Regex dndRegex = new(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$",
RegexOptions.Compiled);
private static readonly Regex fudgeRegex = new(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
private static readonly char[] _fateRolls = { '-', ' ', '+' };
@@ -20,7 +22,8 @@ public partial class Gambling
public DiceRollCommands(IDataCache data)
=> _images = data.LocalImages;
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task Roll()
{
var rng = new NadekoRandom();
@@ -38,23 +41,27 @@ public partial class Gambling
Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[Priority(1)]
public async Task Roll(int num)
=> await InternalRoll(num, true);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[Priority(1)]
public async Task Rolluo(int num = 1)
=> await InternalRoll(num, false);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[Priority(0)]
public async Task Roll(string arg)
=> await InternallDndRoll(arg, true);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[Priority(0)]
public async Task Rolluo(string arg)
=> await InternallDndRoll(arg, false);
@@ -81,65 +88,66 @@ public partial class Gambling
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);
}
using var bitmap = dice.Merge(out var format);
await using var ms = bitmap.ToStream(format);
foreach (var d in dice)
{
d.Dispose();
}
foreach (var d in dice) d.Dispose();
await ctx.Channel.SendFileAsync(ms, $"dice.{format.FileExtensions.First()}",
Format.Bold(ctx.User.ToString()) + " " +
GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString()))) +
" " + GetText(strs.total_average(
Format.Bold(values.Sum().ToString()),
await ctx.Channel.SendFileAsync(ms,
$"dice.{format.FileExtensions.First()}",
Format.Bold(ctx.User.ToString())
+ " "
+ GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString())))
+ " "
+ GetText(strs.total_average(Format.Bold(values.Sum().ToString()),
Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2")))));
}
private async Task InternallDndRoll(string arg, bool ordered)
{
Match match;
if ((match = fudgeRegex.Match(arg)).Length != 0 &&
int.TryParse(match.Groups["n1"].ToString(), out var n1) &&
n1 is > 0 and < 500)
if ((match = fudgeRegex.Match(arg)).Length != 0
&& int.TryParse(match.Groups["n1"].ToString(), out var n1)
&& n1 is > 0 and < 500)
{
var rng = new NadekoRandom();
var rolls = new List<char>();
for (var i = 0; i < n1; i++)
{
rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
}
for (var i = 0; i < n1; i++) rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
var embed = _eb.Create()
.WithOkColor()
.WithDescription(ctx.User.Mention + " " + GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
.AddField(Format.Bold("Result"), string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))));
.WithOkColor()
.WithDescription(ctx.User.Mention
+ " "
+ GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
.AddField(Format.Bold("Result"),
string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))));
await ctx.Channel.EmbedAsync(embed);
}
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 var n2) &&
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
if (int.TryParse(match.Groups["n1"].ToString(), out n1)
&& int.TryParse(match.Groups["n2"].ToString(), out var n2)
&& n1 <= 50
&& n2 <= 100000
&& n1 > 0
&& n2 > 0)
{
if (!int.TryParse(match.Groups["add"].Value, out var add))
add = 0;
@@ -147,39 +155,39 @@ public partial class Gambling
sub = 0;
var arr = new int[n1];
for (var i = 0; i < n1; i++)
{
arr[i] = rng.Next(1, n2 + 1);
}
for (var i = 0; i < n1; i++) arr[i] = rng.Next(1, n2 + 1);
var sum = arr.Sum();
var embed = _eb.Create().WithOkColor()
.WithDescription(ctx.User.Mention + " " + GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
.AddField(Format.Bold("Rolls"), string.Join(" ",
(ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x =>
Format.Code(x.ToString()))))
.AddField(Format.Bold("Sum"),
sum + " + " + add + " - " + sub + " = " + (sum + add - sub));
var embed = _eb.Create()
.WithOkColor()
.WithDescription(ctx.User.Mention
+ " "
+ GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
.AddField(Format.Bold("Rolls"),
string.Join(" ",
(ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x
=> Format.Code(x.ToString()))))
.AddField(Format.Bold("Sum"),
sum + " + " + add + " - " + sub + " = " + (sum + add - sub));
await ctx.Channel.EmbedAsync(embed);
}
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task NRoll([Leftover] string range)
{
int rolled;
if (range.Contains("-"))
{
var arr = range.Split('-')
.Take(2)
.Select(int.Parse)
.ToArray();
var arr = range.Split('-').Take(2).Select(int.Parse).ToArray();
if (arr[0] > arr[1])
{
await ReplyErrorLocalizedAsync(strs.second_larger_than_first);
return;
}
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
}
else
@@ -202,7 +210,8 @@ public partial class Gambling
using var imgZero = Image.Load(images[0]);
return new[] { imgOne, imgZero }.Merge();
}
return Image.Load(_images.Dice[num]);
}
}
}
}

View File

@@ -1,8 +1,8 @@
#nullable disable
using NadekoBot.Modules.Gambling.Common;
using Image = SixLabors.ImageSharp.Image;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Image = SixLabors.ImageSharp.Image;
namespace NadekoBot.Modules.Gambling;
@@ -37,18 +37,17 @@ public partial class Gambling
{
// ignored
}
break;
}
var currentCard = cards.Draw();
cardObjects.Add(currentCard);
images.Add(Image.Load(_images.GetCard(currentCard.ToString().ToLowerInvariant().Replace(' ', '_'))));
}
using var img = images.Merge();
foreach (var i in images)
{
i.Dispose();
}
foreach (var i in images) i.Dispose();
var toSend = $"{Format.Bold(ctx.User.ToString())}";
if (cardObjects.Count == 5)
@@ -60,7 +59,8 @@ public partial class Gambling
return (img.ToStream(), toSend);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task Draw(int num = 1)
{
@@ -76,7 +76,8 @@ public partial class Gambling
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task DrawNew(int num = 1)
{
if (num < 1)
@@ -91,7 +92,8 @@ public partial class Gambling
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task DeckShuffle()
{
@@ -108,4 +110,4 @@ public partial class Gambling
await ReplyConfirmLocalizedAsync(strs.deck_reshuffled);
}
}
}
}

View File

@@ -12,17 +12,29 @@ public partial class Gambling
[Group]
public class FlipCoinCommands : GamblingSubmodule<GamblingService>
{
public enum BetFlipGuess
{
H = 1,
Head = 1,
Heads = 1,
T = 2,
Tail = 2,
Tails = 2
}
private static readonly NadekoRandom rng = new();
private readonly IImageCache _images;
private readonly ICurrencyService _cs;
private static readonly NadekoRandom rng = new();
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss)
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss)
: base(gss)
{
_images = data.LocalImages;
_cs = cs;
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task Flip(int count = 1)
{
if (count is > 10 or < 1)
@@ -30,6 +42,7 @@ public partial class Gambling
await ReplyErrorLocalizedAsync(strs.flip_invalid(10));
return;
}
var headCount = 0;
var tailCount = 0;
var imgs = new Image<Rgba32>[count];
@@ -51,40 +64,31 @@ public partial class Gambling
using var img = imgs.Merge(out var format);
await using var stream = img.ToStream(format);
foreach (var i in imgs)
{
i.Dispose();
}
foreach (var i in imgs) i.Dispose();
var msg = count != 1
? Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_results(count, headCount, tailCount))
: Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flipped(headCount > 0
? Format.Bold(GetText(strs.heads))
: Format.Bold(GetText(strs.tails))));
: Format.Bold(ctx.User.ToString())
+ " "
+ GetText(strs.flipped(headCount > 0
? Format.Bold(GetText(strs.heads))
: Format.Bold(GetText(strs.tails))));
await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg);
}
public enum BetFlipGuess
{
H = 1,
Head = 1,
Heads = 1,
T = 2,
Tail = 2,
Tails = 2
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task Betflip(ShmartNumber amount, BetFlipGuess guess)
{
if (!await CheckBetMandatory(amount) || amount == 1)
return;
var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, gamble: true);
var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, true);
if (!removed)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
BetFlipGuess result;
Uri imageToSend;
var coins = _images.ImageUrls.Coins;
@@ -104,17 +108,17 @@ public partial class Gambling
{
var toWin = (long)(amount * _config.BetFlip.Multiplier);
str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_guess(toWin + CurrencySign));
await _cs.AddAsync(ctx.User, "Betflip Gamble", toWin, false, gamble: true);
await _cs.AddAsync(ctx.User, "Betflip Gamble", toWin, false, true);
}
else
{
str = ctx.User.ToString() + " " + GetText(strs.better_luck);
str = ctx.User + " " + GetText(strs.better_luck);
}
await ctx.Channel.EmbedAsync(_eb.Create()
.WithDescription(str)
.WithOkColor()
.WithImageUrl(imageToSend.ToString()));
.WithDescription(str)
.WithOkColor()
.WithImageUrl(imageToSend.ToString()));
}
}
}
}

View File

@@ -1,16 +1,35 @@
#nullable disable
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Db;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Services.Database.Models;
using System.Globalization;
using System.Numerics;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
namespace NadekoBot.Modules.Gambling;
public partial class Gambling : GamblingModule<GamblingService>
{
public enum RpsPick
{
R = 0,
Rock = 0,
Rocket = 0,
P = 1,
Paper = 1,
Paperclip = 1,
S = 2,
Scissors = 2
}
public enum RpsResult
{
Win,
Loss,
Draw
}
private readonly DbService _db;
private readonly ICurrencyService _cs;
private readonly IDataCache _cache;
@@ -19,9 +38,16 @@ public partial class Gambling : GamblingModule<GamblingService>
private readonly DownloadTracker _tracker;
private readonly GamblingConfigService _configService;
public Gambling(DbService db, ICurrencyService currency,
IDataCache cache, DiscordSocketClient client,
DownloadTracker tracker, GamblingConfigService configService) : base(configService)
private IUserMessage rdMsg;
public Gambling(
DbService db,
ICurrencyService currency,
IDataCache cache,
DiscordSocketClient client,
DownloadTracker tracker,
GamblingConfigService configService)
: base(configService)
{
_db = db;
_cs = currency;
@@ -48,30 +74,33 @@ public partial class Gambling : GamblingModule<GamblingService>
return n(uow.DiscordUser.GetUserCurrency(id));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task Economy()
{
var ec = _service.GetEconomy();
decimal onePercent = 0;
if (ec.Cash > 0)
{
onePercent = ec.OnePercent / (ec.Cash-ec.Bot); // This stops the top 1% from owning more than 100% of the money
// [21:03] Bob Page: Kinda remids me of US economy
}
onePercent =
ec.OnePercent / (ec.Cash - ec.Bot); // This stops the top 1% from owning more than 100% of the money
// [21:03] Bob Page: Kinda remids me of US economy
var embed = _eb.Create()
.WithTitle(GetText(strs.economy_state))
.AddField(GetText(strs.currency_owned), ((BigInteger)(ec.Cash - ec.Bot)).ToString("N", Culture) + CurrencySign)
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
.AddField(GetText(strs.currency_planted), (BigInteger)ec.Planted)
.AddField(GetText(strs.owned_waifus_total), (BigInteger)ec.Waifus + CurrencySign)
.AddField(GetText(strs.bot_currency), n(ec.Bot))
.AddField(GetText(strs.total), ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", Culture) + CurrencySign)
.WithOkColor();
.WithTitle(GetText(strs.economy_state))
.AddField(GetText(strs.currency_owned),
((BigInteger)(ec.Cash - ec.Bot)).ToString("N", Culture) + CurrencySign)
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
.AddField(GetText(strs.currency_planted), (BigInteger)ec.Planted)
.AddField(GetText(strs.owned_waifus_total), (BigInteger)ec.Waifus + CurrencySign)
.AddField(GetText(strs.bot_currency), n(ec.Bot))
.AddField(GetText(strs.total),
((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", Culture) + CurrencySign)
.WithOkColor();
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
await ctx.Channel.EmbedAsync(embed);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task Timely()
{
var val = _config.Timely.Amount;
@@ -94,7 +123,8 @@ public partial class Gambling : GamblingModule<GamblingService>
await ReplyConfirmLocalizedAsync(strs.timely(n(val), period));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[OwnerOnly]
public async Task TimelyReset()
{
@@ -102,26 +132,28 @@ public partial class Gambling : GamblingModule<GamblingService>
await ReplyConfirmLocalizedAsync(strs.timely_reset);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[OwnerOnly]
public async Task TimelySet(int amount, int period = 24)
{
if (amount < 0 || period < 0)
return;
_configService.ModifyConfig(gs =>
{
gs.Timely.Amount = amount;
gs.Timely.Cooldown = period;
});
if (amount == 0)
await ReplyConfirmLocalizedAsync(strs.timely_set_none);
else
await ReplyConfirmLocalizedAsync(strs.timely_set(Format.Bold(n(amount)), Format.Bold(period.ToString())));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task Raffle([Leftover] IRole role = null)
{
@@ -129,15 +161,15 @@ public partial class Gambling : GamblingModule<GamblingService>
var members = (await role.GetMembersAsync()).Where(u => u.Status != UserStatus.Offline);
var membersArray = members as IUser[] ?? members.ToArray();
if (membersArray.Length == 0)
{
return;
}
if (membersArray.Length == 0) return;
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user), $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}");
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user),
$"**{usr.Username}#{usr.Discriminator}**",
footer: $"ID: {usr.Id}");
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task RaffleAny([Leftover] IRole role = null)
{
@@ -145,30 +177,32 @@ public partial class Gambling : GamblingModule<GamblingService>
var members = await role.GetMembersAsync();
var membersArray = members as IUser[] ?? members.ToArray();
if (membersArray.Length == 0)
{
return;
}
if (membersArray.Length == 0) return;
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user), $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}");
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user),
$"**{usr.Username}#{usr.Discriminator}**",
footer: $"ID: {usr.Id}");
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[Priority(2)]
public Task CurrencyTransactions(int page = 1) =>
InternalCurrencyTransactions(ctx.User.Id, page);
public Task CurrencyTransactions(int page = 1)
=> InternalCurrencyTransactions(ctx.User.Id, page);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[OwnerOnly]
[Priority(0)]
public Task CurrencyTransactions([Leftover] IUser usr) =>
InternalCurrencyTransactions(usr.Id, 1);
public Task CurrencyTransactions([Leftover] IUser usr)
=> InternalCurrencyTransactions(usr.Id, 1);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[OwnerOnly]
[Priority(1)]
public Task CurrencyTransactions(IUser usr, int page) =>
InternalCurrencyTransactions(usr.Id, page);
public Task CurrencyTransactions(IUser usr, int page)
=> InternalCurrencyTransactions(usr.Id, page);
private async Task InternalCurrencyTransactions(ulong userId, int page)
{
@@ -182,9 +216,9 @@ public partial class Gambling : GamblingModule<GamblingService>
}
var embed = _eb.Create()
.WithTitle(GetText(strs.transactions(
((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString() ?? $"{userId}")))
.WithOkColor();
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
?? $"{userId}")))
.WithOkColor();
var desc = string.Empty;
foreach (var tr in trs)
@@ -199,12 +233,14 @@ public partial class Gambling : GamblingModule<GamblingService>
await ctx.Channel.EmbedAsync(embed);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[Priority(0)]
public async Task Cash(ulong userId)
=> await ReplyConfirmLocalizedAsync(strs.has(Format.Code(userId.ToString()), $"{GetCurrency(userId)}"));
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[Priority(1)]
public async Task Cash([Leftover] IUser user = null)
{
@@ -212,44 +248,51 @@ public partial class Gambling : GamblingModule<GamblingService>
await ConfirmLocalizedAsync(strs.has(Format.Bold(user.ToString()), $"{GetCurrency(user.Id)}"));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task Give(ShmartNumber amount, IGuildUser receiver, [Leftover] string msg = null)
{
if (amount <= 0 || ctx.User.Id == receiver.Id || receiver.IsBot)
return;
var success = await _cs.RemoveAsync((IGuildUser)ctx.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, false);
var success =
await _cs.RemoveAsync((IGuildUser)ctx.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
await _cs.AddAsync(receiver, $"Gift from {ctx.User.Username} ({ctx.User.Id}) - {msg}.", amount, true);
await ReplyConfirmLocalizedAsync(strs.gifted(n(amount), Format.Bold(receiver.ToString())));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public Task Give(ShmartNumber amount, [Leftover] IGuildUser receiver)
=> Give(amount, receiver, null);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(0)]
public Task Award(long amount, IGuildUser usr, [Leftover] string msg) =>
Award(amount, usr.Id, msg);
public Task Award(long amount, IGuildUser usr, [Leftover] string msg)
=> Award(amount, usr.Id, msg);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(1)]
public Task Award(long amount, [Leftover] IGuildUser usr) =>
Award(amount, usr.Id);
public Task Award(long amount, [Leftover] IGuildUser usr)
=> Award(amount, usr.Id);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[OwnerOnly]
[Priority(2)]
public async Task Award(long amount, ulong usrId, [Leftover] string msg = null)
@@ -259,7 +302,7 @@ public partial class Gambling : GamblingModule<GamblingService>
var usr = await ((DiscordSocketClient)Context.Client).Rest.GetUserAsync(usrId);
if(usr is null)
if (usr is null)
{
await ReplyErrorLocalizedAsync(strs.user_not_found);
return;
@@ -272,28 +315,27 @@ public partial class Gambling : GamblingModule<GamblingService>
await ReplyConfirmLocalizedAsync(strs.awarded(n(amount), $"<@{usrId}>"));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(3)]
public async Task Award(long amount, [Leftover] IRole role)
{
var users = (await ctx.Guild.GetUsersAsync())
.Where(u => u.GetRoles().Contains(role))
.ToList();
var users = (await ctx.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).ToList();
await _cs.AddBulkAsync(users.Select(x => x.Id),
users.Select(x => $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
users.Select(x => amount),
gamble: true);
users.Select(x => $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
users.Select(x => amount),
true);
await ReplyConfirmLocalizedAsync(strs.mass_award(
n(amount),
await ReplyConfirmLocalizedAsync(strs.mass_award(n(amount),
Format.Bold(users.Count.ToString()),
Format.Bold(role.Name)));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(0)]
@@ -302,17 +344,17 @@ public partial class Gambling : GamblingModule<GamblingService>
var users = (await role.GetMembersAsync()).ToList();
await _cs.RemoveBulkAsync(users.Select(x => x.Id),
users.Select(x => $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
users.Select(x => amount),
gamble: true);
users.Select(x => $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
users.Select(x => amount),
true);
await ReplyConfirmLocalizedAsync(strs.mass_take(
n(amount),
await ReplyConfirmLocalizedAsync(strs.mass_take(n(amount),
Format.Bold(users.Count.ToString()),
Format.Bold(role.Name)));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(1)]
@@ -321,7 +363,9 @@ public partial class Gambling : GamblingModule<GamblingService>
if (amount <= 0)
return;
if (await _cs.RemoveAsync(user, $"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})", amount,
if (await _cs.RemoveAsync(user,
$"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})",
amount,
gamble: ctx.Client.CurrentUser.Id != user.Id))
await ReplyConfirmLocalizedAsync(strs.take(n(amount), Format.Bold(user.ToString())));
else
@@ -329,23 +373,25 @@ public partial class Gambling : GamblingModule<GamblingService>
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[OwnerOnly]
public async Task Take(long amount, [Leftover] ulong usrId)
{
if (amount <= 0)
return;
if (await _cs.RemoveAsync(usrId, $"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})", amount,
gamble: ctx.Client.CurrentUser.Id != usrId))
if (await _cs.RemoveAsync(usrId,
$"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})",
amount,
ctx.Client.CurrentUser.Id != usrId))
await ReplyConfirmLocalizedAsync(strs.take(n(amount), $"<@{usrId}>"));
else
await ReplyErrorLocalizedAsync(strs.take_fail(n(amount), Format.Code(usrId.ToString()), CurrencySign));
}
private IUserMessage rdMsg = null;
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task RollDuel(IUser u)
{
@@ -354,13 +400,11 @@ public partial class Gambling : GamblingModule<GamblingService>
//since the challenge is created by another user, we need to reverse the ids
//if it gets removed, means challenge is accepted
if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game))
{
await game.StartGame();
}
if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game)) await game.StartGame();
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task RollDuel(ShmartNumber amount, IUser u)
{
@@ -370,9 +414,7 @@ public partial class Gambling : GamblingModule<GamblingService>
if (amount <= 0)
return;
var embed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.roll_duel));
var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.roll_duel));
var description = string.Empty;
@@ -381,22 +423,18 @@ public partial class Gambling : GamblingModule<GamblingService>
if (_service.Duels.TryGetValue((ctx.User.Id, u.Id), out var other))
{
if (other.Amount != amount)
{
await ReplyErrorLocalizedAsync(strs.roll_duel_already_challenged);
}
else
{
await RollDuel(u);
}
return;
}
if (_service.Duels.TryAdd((u.Id, ctx.User.Id), game))
{
game.OnGameTick += Game_OnGameTick;
game.OnEnded += Game_OnEnded;
await ReplyConfirmLocalizedAsync(strs.roll_duel_challenge(
Format.Bold(ctx.User.ToString()),
await ReplyConfirmLocalizedAsync(strs.roll_duel_challenge(Format.Bold(ctx.User.ToString()),
Format.Bold(u.ToString()),
Format.Bold(n(amount))));
}
@@ -411,16 +449,12 @@ public partial class Gambling : GamblingModule<GamblingService>
embed = embed.WithDescription(description);
if (rdMsg is null)
{
rdMsg = await ctx.Channel.EmbedAsync(embed);
}
else
{
await rdMsg.ModifyAsync(x =>
{
x.Embed = embed.Build();
});
}
}
async Task Game_OnEnded(RollDuelGame rdGame, RollDuelGame.Reason reason)
@@ -429,13 +463,11 @@ public partial class Gambling : GamblingModule<GamblingService>
{
if (reason == RollDuelGame.Reason.Normal)
{
var winner = rdGame.Winner == rdGame.P1
? ctx.User
: u;
var winner = rdGame.Winner == rdGame.P1 ? ctx.User : u;
description += $"\n**{winner}** Won {n((long)(rdGame.Amount * 2 * 0.98))}";
embed = embed.WithDescription(description);
await rdMsg.ModifyAsync(x => x.Embed = embed.Build());
}
else if (reason == RollDuelGame.Reason.Timeout)
@@ -459,13 +491,13 @@ public partial class Gambling : GamblingModule<GamblingService>
if (!await CheckBetMandatory(amount))
return;
if (!await _cs.RemoveAsync(ctx.User, "Betroll Gamble", amount, false, gamble: true))
if (!await _cs.RemoveAsync(ctx.User, "Betroll Gamble", amount, false, true))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
var br = new Betroll(base._config.BetRoll);
var br = new Betroll(_config.BetRoll);
var result = br.Roll();
@@ -474,31 +506,31 @@ public partial class Gambling : GamblingModule<GamblingService>
if (result.Multiplier > 0)
{
var win = (long)(amount * result.Multiplier);
str += GetText(strs.br_win(
n(win),
result.Threshold + (result.Roll == 100 ? " 👑" : "")));
await _cs.AddAsync(ctx.User, "Betroll Gamble",
win, false, gamble: true);
str += GetText(strs.br_win(n(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
await _cs.AddAsync(ctx.User, "Betroll Gamble", win, false, true);
}
else
{
str += GetText(strs.better_luck);
}
await SendConfirmAsync(str);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public Task BetRoll(ShmartNumber amount)
=> InternallBetroll(amount);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[NadekoOptions(typeof(LbOpts))]
[Priority(0)]
public Task Leaderboard(params string[] args)
=> Leaderboard(1, args);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[NadekoOptions(typeof(LbOpts))]
[Priority(1)]
public async Task Leaderboard(int page = 1, params string[] args)
@@ -510,10 +542,7 @@ public partial class Gambling : GamblingModule<GamblingService>
var cleanRichest = new List<DiscordUser>();
// it's pointless to have clean on dm context
if (ctx.Guild is null)
{
opts.Clean = false;
}
if (ctx.Guild is null) opts.Clean = false;
if (opts.Clean)
{
@@ -523,13 +552,12 @@ public partial class Gambling : GamblingModule<GamblingService>
{
cleanRichest = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 10_000);
}
await ctx.Channel.TriggerTypingAsync();
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
var sg = (SocketGuild)ctx.Guild;
cleanRichest = cleanRichest.Where(x => sg.GetUser(x.UserId) != null)
.ToList();
cleanRichest = cleanRichest.Where(x => sg.GetUser(x.UserId) != null).ToList();
}
else
{
@@ -537,62 +565,46 @@ public partial class Gambling : GamblingModule<GamblingService>
cleanRichest = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, page).ToList();
}
await ctx.SendPaginatedConfirmAsync(page, curPage =>
{
var embed = _eb.Create()
.WithOkColor()
.WithTitle(CurrencySign + " " + GetText(strs.leaderboard));
await ctx.SendPaginatedConfirmAsync(page,
curPage =>
{
var embed = _eb.Create().WithOkColor().WithTitle(CurrencySign + " " + GetText(strs.leaderboard));
List<DiscordUser> toSend;
if (!opts.Clean)
{
using var uow = _db.GetDbContext();
toSend = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, curPage);
}
else
{
toSend = cleanRichest.Skip(curPage * 9).Take(9).ToList();
}
if (!toSend.Any())
{
embed.WithDescription(GetText(strs.no_user_on_this_page));
return embed;
}
for (var i = 0; i < toSend.Count; i++)
{
var x = toSend[i];
var usrStr = x.ToString().TrimTo(20, true);
var j = i;
embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, n(x.CurrencyAmount), true);
}
List<DiscordUser> toSend;
if (!opts.Clean)
{
using var uow = _db.GetDbContext();
toSend = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, curPage);
}
else
{
toSend = cleanRichest.Skip(curPage * 9).Take(9).ToList();
}
if (!toSend.Any())
{
embed.WithDescription(GetText(strs.no_user_on_this_page));
return embed;
}
for (var i = 0; i < toSend.Count; i++)
{
var x = toSend[i];
var usrStr = x.ToString().TrimTo(20, true);
var j = i;
embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, n(x.CurrencyAmount), true);
}
return embed;
}, opts.Clean ? cleanRichest.Count() : 9000, 9, opts.Clean);
},
opts.Clean ? cleanRichest.Count() : 9000,
9,
opts.Clean);
}
public enum RpsPick
{
R = 0,
Rock = 0,
Rocket = 0,
P = 1,
Paper = 1,
Paperclip = 1,
S = 2,
Scissors = 2
}
public enum RpsResult
{
Win,
Loss,
Draw,
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task Rps(RpsPick pick, ShmartNumber amount = default)
{
long oldAmount = amount;
@@ -611,35 +623,31 @@ public partial class Gambling : GamblingModule<GamblingService>
return "✂️";
}
}
var embed = _eb.Create();
var nadekoPick = (RpsPick)new NadekoRandom().Next(0, 3);
if (amount > 0)
{
if (!await _cs.RemoveAsync(ctx.User.Id,
"Rps-bet", amount, gamble: true))
if (!await _cs.RemoveAsync(ctx.User.Id, "Rps-bet", amount, true))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
}
string msg;
if (pick == nadekoPick)
{
await _cs.AddAsync(ctx.User.Id,
"Rps-draw", amount, gamble: true);
await _cs.AddAsync(ctx.User.Id, "Rps-draw", amount, true);
embed.WithOkColor();
msg = GetText(strs.rps_draw(getRpsPick(pick)));
}
else if ((pick == RpsPick.Paper && nadekoPick == RpsPick.Rock) ||
(pick == RpsPick.Rock && nadekoPick == RpsPick.Scissors) ||
(pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper))
else if ((pick == RpsPick.Paper && nadekoPick == RpsPick.Rock)
|| (pick == RpsPick.Rock && nadekoPick == RpsPick.Scissors)
|| (pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper))
{
amount = (long)(amount * base._config.BetFlip.Multiplier);
await _cs.AddAsync(ctx.User.Id,
"Rps-win", amount, gamble: true);
amount = (long)(amount * _config.BetFlip.Multiplier);
await _cs.AddAsync(ctx.User.Id, "Rps-win", amount, true);
embed.WithOkColor();
embed.AddField(GetText(strs.won), n(amount));
msg = GetText(strs.rps_win(ctx.User.Mention, getRpsPick(pick), getRpsPick(nadekoPick)));
@@ -651,9 +659,8 @@ public partial class Gambling : GamblingModule<GamblingService>
msg = GetText(strs.rps_win(ctx.Client.CurrentUser.Mention, getRpsPick(nadekoPick), getRpsPick(pick)));
}
embed
.WithDescription(msg);
embed.WithDescription(msg);
await ctx.Channel.EmbedAsync(embed);
}
}
}

View File

@@ -1,7 +1,7 @@
#nullable disable
using NadekoBot.Modules.Administration.Services;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
namespace NadekoBot.Modules.Gambling;
@@ -12,17 +12,16 @@ public partial class Gambling
{
private readonly ILogCommandService logService;
public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss) : base(gss)
public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss)
: base(gss)
=> this.logService = logService;
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task Pick(string pass = null)
{
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
{
return;
}
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric()) return;
var picked = await _service.PickAsync(ctx.Guild.Id, (ITextChannel)ctx.Channel, ctx.User.Id, pass);
@@ -33,27 +32,23 @@ public partial class Gambling
}
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
{
try
{
logService.AddDeleteIgnore(ctx.Message.Id);
await ctx.Message.DeleteAsync();
}
catch { }
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task Plant(ShmartNumber amount, string pass = null)
{
if (amount < 1)
return;
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
{
return;
}
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric()) return;
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
{
@@ -61,14 +56,17 @@ public partial class Gambling
await ctx.Message.DeleteAsync();
}
var success = await _service.PlantAsync(ctx.Guild.Id, ctx.Channel, ctx.User.Id, ctx.User.ToString(), amount, pass);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.not_enough( CurrencySign));
}
var success = await _service.PlantAsync(ctx.Guild.Id,
ctx.Channel,
ctx.User.Id,
ctx.User.ToString(),
amount,
pass);
if (!success) await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
#if GLOBAL_NADEKO
@@ -78,16 +76,13 @@ public partial class Gambling
{
var enabled = _service.ToggleCurrencyGeneration(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
{
await ReplyConfirmLocalizedAsync(strs.curgen_enabled);
}
else
{
await ReplyConfirmLocalizedAsync(strs.curgen_disabled);
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[OwnerOnly]
@@ -97,19 +92,19 @@ public partial class Gambling
return Task.CompletedTask;
var enabledIn = _service.GetAllGeneratingChannels();
return ctx.SendPaginatedConfirmAsync(page, cur =>
{
var items = enabledIn.Skip(page * 9).Take(9);
if (!items.Any())
return ctx.SendPaginatedConfirmAsync(page,
cur =>
{
return _eb.Create().WithErrorColor()
.WithDescription("-");
}
var items = enabledIn.Skip(page * 9).Take(9);
return items.Aggregate(_eb.Create().WithOkColor(),
(eb, i) => eb.AddField(i.GuildId.ToString(), i.ChannelId));
}, enabledIn.Count(), 9);
if (!items.Any())
return _eb.Create().WithErrorColor().WithDescription("-");
return items.Aggregate(_eb.Create().WithOkColor(),
(eb, i) => eb.AddField(i.GuildId.ToString(), i.ChannelId));
},
enabledIn.Count(),
9);
}
}
}
}

View File

@@ -6,4 +6,4 @@ namespace NadekoBot.Modules.Gambling.Services;
public class AnimalRaceService : INService
{
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new();
}
}

View File

@@ -6,4 +6,4 @@ namespace NadekoBot.Modules.Gambling.Services;
public class BlackJackService : INService
{
public ConcurrentDictionary<ulong, Blackjack> Games { get; } = new();
}
}

View File

@@ -1,6 +1,6 @@
#nullable disable
using NadekoBot.Modules.Gambling.Common.Events;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.Events;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Gambling.Services;
@@ -14,18 +14,19 @@ public class CurrencyEventsService : INService
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events = new();
public CurrencyEventsService(
DiscordSocketClient client,
ICurrencyService cs,
GamblingConfigService configService)
public CurrencyEventsService(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService configService)
{
_client = client;
_cs = cs;
_configService = configService;
}
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
EventOptions opts, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embed)
public async Task<bool> TryCreateEventAsync(
ulong guildId,
ulong channelId,
CurrencyEvent.Type type,
EventOptions opts,
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embed)
{
var g = _client.GetGuild(guildId);
if (g?.GetChannel(channelId) is not SocketTextChannel ch)
@@ -34,21 +35,14 @@ public class CurrencyEventsService : INService
ICurrencyEvent ce;
if (type == CurrencyEvent.Type.Reaction)
{
ce = new ReactionEvent(_client, _cs, g, ch, opts, _configService.Data, embed);
}
else if (type == CurrencyEvent.Type.GameStatus)
{
ce = new GameStatusEvent(_client, _cs, g, ch, opts, embed);
}
else
{
return false;
}
var added = _events.TryAdd(guildId, ce);
if (added)
{
try
{
ce.OnEnded += OnEventEnded;
@@ -60,7 +54,6 @@ public class CurrencyEventsService : INService
_events.TryRemove(guildId, out ce);
return false;
}
}
return added;
}
@@ -70,4 +63,4 @@ public class CurrencyEventsService : INService
_events.TryRemove(gid, out _);
return Task.CompletedTask;
}
}
}

View File

@@ -10,19 +10,24 @@ public class CurrencyRaffleService : INService
NotEnoughCurrency,
AlreadyJoinedOrInvalidAmount
}
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new();
private readonly SemaphoreSlim _locker = new(1, 1);
private readonly DbService _db;
private readonly ICurrencyService _cs;
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new();
public CurrencyRaffleService(DbService db, ICurrencyService cs)
{
_db = db;
_cs = cs;
}
public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(ulong channelId, IUser user, long amount, bool mixed, Func<IUser, long, Task> onEnded)
public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(
ulong channelId,
IUser user,
long amount,
bool mixed,
Func<IUser, long, Task> onEnded)
{
await _locker.WaitAsync();
try
@@ -31,9 +36,7 @@ public class CurrencyRaffleService : INService
if (!Games.TryGetValue(channelId, out var crg))
{
newGame = true;
crg = new(mixed
? CurrencyRaffleGame.Type.Mixed
: CurrencyRaffleGame.Type.Normal);
crg = new(mixed ? CurrencyRaffleGame.Type.Mixed : CurrencyRaffleGame.Type.Normal);
Games.Add(channelId, crg);
}
@@ -51,6 +54,7 @@ public class CurrencyRaffleService : INService
await _cs.AddAsync(user.Id, "Curency Raffle Refund", amount);
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
}
if (newGame)
{
var _t = Task.Run(async () =>
@@ -62,8 +66,7 @@ public class CurrencyRaffleService : INService
var winner = crg.GetWinner();
var won = crg.Users.Sum(x => x.Amount);
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win",
won);
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win", won);
Games.Remove(channelId, out _);
var oe = onEnded(winner.DiscordUser, won);
}
@@ -71,6 +74,7 @@ public class CurrencyRaffleService : INService
finally { _locker.Release(); }
});
}
return (crg, null);
}
finally
@@ -78,4 +82,4 @@ public class CurrencyRaffleService : INService
_locker.Release();
}
}
}
}

View File

@@ -6,74 +6,119 @@ namespace NadekoBot.Modules.Gambling.Services;
public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
{
public override string Name { get; } = "gambling";
private const string FilePath = "data/gambling.yml";
private static readonly TypedKey<GamblingConfig> changeKey = new("config.gambling.updated");
public override string Name { get; } = "gambling";
private readonly IEnumerable<WaifuItemModel> antiGiftSeed = new[]
{
new WaifuItemModel("🥀", 100, "WiltedRose", true), new WaifuItemModel("✂️", 1000, "Haircut", true),
new WaifuItemModel("🧻", 10000, "ToiletPaper", true)
};
public GamblingConfigService(IConfigSeria serializer, IPubSub pubSub)
: base(FilePath, serializer, pubSub, changeKey)
{
AddParsedProp("currency.name", gs => gs.Currency.Name, ConfigParsers.String, ConfigPrinters.ToString);
AddParsedProp("currency.sign", gs => gs.Currency.Sign, ConfigParsers.String, ConfigPrinters.ToString);
AddParsedProp("minbet", gs => gs.MinBet, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("maxbet", gs => gs.MaxBet, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("gen.min", gs => gs.Generation.MinAmount, int.TryParse, ConfigPrinters.ToString, val => val >= 1);
AddParsedProp("gen.max", gs => gs.Generation.MaxAmount, int.TryParse, ConfigPrinters.ToString, val => val >= 1);
AddParsedProp("gen.cd", gs => gs.Generation.GenCooldown, int.TryParse, ConfigPrinters.ToString, val => val > 0);
AddParsedProp("gen.chance", gs => gs.Generation.Chance, decimal.TryParse, ConfigPrinters.ToString, val => val is >= 0 and <= 1);
AddParsedProp("gen.chance",
gs => gs.Generation.Chance,
decimal.TryParse,
ConfigPrinters.ToString,
val => val is >= 0 and <= 1);
AddParsedProp("gen.has_pw", gs => gs.Generation.HasPassword, bool.TryParse, ConfigPrinters.ToString);
AddParsedProp("bf.multi", gs => gs.BetFlip.Multiplier, decimal.TryParse, ConfigPrinters.ToString, val => val >= 1);
AddParsedProp("waifu.min_price", gs => gs.Waifu.MinPrice, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("waifu.multi.reset", gs => gs.Waifu.Multipliers.WaifuReset, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("waifu.multi.crush_claim", gs => gs.Waifu.Multipliers.CrushClaim, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("waifu.multi.normal_claim", gs => gs.Waifu.Multipliers.NormalClaim, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
AddParsedProp("waifu.multi.divorce_value", gs => gs.Waifu.Multipliers.DivorceNewValue, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
AddParsedProp("waifu.multi.all_gifts", gs => gs.Waifu.Multipliers.AllGiftPrices, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
AddParsedProp("waifu.multi.gift_effect", gs => gs.Waifu.Multipliers.GiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("waifu.multi.negative_gift_effect", gs => gs.Waifu.Multipliers.NegativeGiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("decay.percent", gs => gs.Decay.Percent, decimal.TryParse, ConfigPrinters.ToString, val => val is >= 0 and <= 1);
AddParsedProp("decay.maxdecay", gs => gs.Decay.MaxDecay, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("decay.threshold", gs => gs.Decay.MinThreshold, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("bf.multi",
gs => gs.BetFlip.Multiplier,
decimal.TryParse,
ConfigPrinters.ToString,
val => val >= 1);
AddParsedProp("waifu.min_price",
gs => gs.Waifu.MinPrice,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("waifu.multi.reset",
gs => gs.Waifu.Multipliers.WaifuReset,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("waifu.multi.crush_claim",
gs => gs.Waifu.Multipliers.CrushClaim,
decimal.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("waifu.multi.normal_claim",
gs => gs.Waifu.Multipliers.NormalClaim,
decimal.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("waifu.multi.divorce_value",
gs => gs.Waifu.Multipliers.DivorceNewValue,
decimal.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("waifu.multi.all_gifts",
gs => gs.Waifu.Multipliers.AllGiftPrices,
decimal.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("waifu.multi.gift_effect",
gs => gs.Waifu.Multipliers.GiftEffect,
decimal.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("waifu.multi.negative_gift_effect",
gs => gs.Waifu.Multipliers.NegativeGiftEffect,
decimal.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("decay.percent",
gs => gs.Decay.Percent,
decimal.TryParse,
ConfigPrinters.ToString,
val => val is >= 0 and <= 1);
AddParsedProp("decay.maxdecay",
gs => gs.Decay.MaxDecay,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("decay.threshold",
gs => gs.Decay.MinThreshold,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
Migrate();
}
private readonly IEnumerable<WaifuItemModel> antiGiftSeed = new[]
{
new WaifuItemModel("🥀", 100, "WiltedRose", true),
new WaifuItemModel("✂️", 1000, "Haircut", true),
new WaifuItemModel("🧻", 10000, "ToiletPaper", true),
};
public void Migrate()
{
if (data.Version < 2)
{
ModifyConfig(c =>
{
c.Waifu.Items = c.Waifu.Items.Concat(antiGiftSeed).ToList();
c.Version = 2;
});
}
if (data.Version < 3)
{
ModifyConfig(c =>
{
c.Version = 3;
c.VoteReward = 100;
});
}
if (data.Version < 4)
{
ModifyConfig(c =>
{
c.Version = 4;
});
}
}
}
}

View File

@@ -1,16 +1,18 @@
#nullable disable
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.Connect4;
using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
using Newtonsoft.Json;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.Connect4;
using NadekoBot.Modules.Gambling.Common.Slot;
using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
using Newtonsoft.Json;
namespace NadekoBot.Modules.Gambling.Services;
public class GamblingService : INService
{
public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new();
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new();
private readonly DbService _db;
private readonly ICurrencyService _cs;
private readonly Bot _bot;
@@ -18,13 +20,15 @@ public class GamblingService : INService
private readonly IDataCache _cache;
private readonly GamblingConfigService _gss;
public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new();
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new();
private readonly Timer _decayTimer;
public GamblingService(DbService db, Bot bot, ICurrencyService cs,
DiscordSocketClient client, IDataCache cache, GamblingConfigService gss)
public GamblingService(
DbService db,
Bot bot,
ICurrencyService cs,
DiscordSocketClient client,
IDataCache cache,
GamblingConfigService gss)
{
_db = db;
_cs = cs;
@@ -32,30 +36,29 @@ public class GamblingService : INService
_client = client;
_cache = cache;
_gss = gss;
if (_bot.Client.ShardId == 0)
{
_decayTimer = new(_ =>
{
var config = _gss.Data;
var maxDecay = config.Decay.MaxDecay;
if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0)
return;
using var uow = _db.GetDbContext();
var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval))
return;
Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% " +
$"| max: {maxDecay} " +
$"| threshold: {config.Decay.MinThreshold}");
if (maxDecay == 0)
maxDecay = int.MaxValue;
uow.Database.ExecuteSqlInterpolated($@"
if (_bot.Client.ShardId == 0)
_decayTimer = new(_ =>
{
var config = _gss.Data;
var maxDecay = config.Decay.MaxDecay;
if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0)
return;
using var uow = _db.GetDbContext();
var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval))
return;
Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% "
+ $"| max: {maxDecay} "
+ $"| threshold: {config.Decay.MinThreshold}");
if (maxDecay == 0)
maxDecay = int.MaxValue;
uow.Database.ExecuteSqlInterpolated($@"
UPDATE DiscordUser
SET CurrencyAmount=
CASE WHEN
@@ -67,23 +70,20 @@ SET CurrencyAmount=
END
WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentUser.Id};");
_cache.SetLastCurrencyDecay();
uow.SaveChanges();
}, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
}
_cache.SetLastCurrencyDecay();
uow.SaveChanges();
},
null,
TimeSpan.FromMinutes(5),
TimeSpan.FromMinutes(5));
}
public async Task<SlotResponse> SlotAsync(ulong userId, long amount)
{
var takeRes = await _cs.RemoveAsync(userId, "Slot Machine", amount, true);
if (!takeRes)
{
return new()
{
Error = GamblingError.NotEnough
};
}
return new() { Error = GamblingError.NotEnough };
var game = new SlotGame();
var result = game.Spin();
@@ -96,37 +96,21 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true);
}
var toReturn = new SlotResponse
{
Multiplier = result.Multiplier,
Won = won,
};
var toReturn = new SlotResponse { Multiplier = result.Multiplier, Won = won };
toReturn.Rolls.AddRange(result.Rolls);
return toReturn;
}
public struct EconomyResult
{
public decimal Cash { get; set; }
public decimal Planted { get; set; }
public decimal Waifus { get; set; }
public decimal OnePercent { get; set; }
public long Bot { get; set; }
}
public EconomyResult GetEconomy()
{
if (_cache.TryGetEconomy(out var data))
{
try
{
return JsonConvert.DeserializeObject<EconomyResult>(data);
}
catch { }
}
decimal cash;
decimal onePercent;
@@ -149,7 +133,7 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
Planted = planted,
Bot = bot,
Waifus = waifus,
OnePercent = onePercent,
OnePercent = onePercent
};
_cache.SetEconomy(JsonConvert.SerializeObject(result));
@@ -158,4 +142,14 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
public Task<WheelOfFortuneGame.Result> WheelOfFortuneSpinAsync(ulong userId, long bet)
=> new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync();
}
public struct EconomyResult
{
public decimal Cash { get; set; }
public decimal Planted { get; set; }
public decimal Waifus { get; set; }
public decimal OnePercent { get; set; }
public long Bot { get; set; }
}
}

View File

@@ -4,7 +4,7 @@ namespace NadekoBot.Modules.Gambling.Services;
public interface IShopService
{
/// <summary>
/// Changes the price of a shop item
/// Changes the price of a shop item
/// </summary>
/// <param name="guildId">Id of the guild in which the shop is</param>
/// <param name="index">Index of the item</param>
@@ -13,7 +13,7 @@ public interface IShopService
Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice);
/// <summary>
/// Changes the name of a shop item
/// Changes the name of a shop item
/// </summary>
/// <param name="guildId">Id of the guild in which the shop is</param>
/// <param name="index">Index of the item</param>
@@ -22,7 +22,7 @@ public interface IShopService
Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName);
/// <summary>
/// Swaps indexes of 2 items in the shop
/// Swaps indexes of 2 items in the shop
/// </summary>
/// <param name="guildId">Id of the guild in which the shop is</param>
/// <param name="index1">First entry's index</param>
@@ -31,11 +31,11 @@ public interface IShopService
Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2);
/// <summary>
/// Swaps indexes of 2 items in the shop
/// Swaps indexes of 2 items in the shop
/// </summary>
/// <param name="guildId">Id of the guild in which the shop is</param>
/// <param name="fromIndex">Current index of the entry to move</param>
/// <param name="toIndex">Destination index of the entry</param>
/// <returns>Whether swap was successful</returns>
Task<bool> MoveEntryAsync(ulong guildId, int fromIndex, int toIndex);
}
}

View File

@@ -1,9 +1,9 @@
#nullable disable
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.Collections;
using NadekoBot.Db;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
namespace NadekoBot.Modules.Gambling.Services;
@@ -14,13 +14,9 @@ public class ShopService : IShopService, INService
public ShopService(DbService db)
=> _db = db;
private IndexedCollection<ShopEntry> GetEntriesInternal(NadekoContext uow, ulong guildId) =>
uow.GuildConfigsForId(
guildId,
set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items)
)
.ShopEntries
.ToIndexed();
private IndexedCollection<ShopEntry> GetEntriesInternal(NadekoContext uow, ulong guildId)
=> uow.GuildConfigsForId(guildId, set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items))
.ShopEntries.ToIndexed();
public async Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice)
{
@@ -98,4 +94,4 @@ public class ShopService : IShopService, INService
await uow.SaveChangesAsync();
return true;
}
}
}

View File

@@ -1,19 +1,21 @@
#nullable disable
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db;
using NadekoBot.Services.Database.Models;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using NadekoBot.Db;
using Image = SixLabors.ImageSharp.Image;
using Color = SixLabors.ImageSharp.Color;
using Image = SixLabors.ImageSharp.Image;
namespace NadekoBot.Modules.Gambling.Services;
public class PlantPickService : INService
{
//channelId/last generation
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new();
private readonly DbService _db;
private readonly IBotStrings _strings;
private readonly IImageCache _images;
@@ -25,13 +27,18 @@ public class PlantPickService : INService
private readonly GamblingConfigService _gss;
public readonly ConcurrentHashSet<ulong> _generationChannels = new();
//channelId/last generation
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new();
private readonly SemaphoreSlim pickLock = new(1, 1);
public PlantPickService(DbService db, CommandHandler cmd, IBotStrings strings,
IDataCache cache, FontProvider fonts, ICurrencyService cs,
CommandHandler cmdHandler, DiscordSocketClient client, GamblingConfigService gss)
public PlantPickService(
DbService db,
CommandHandler cmd,
IBotStrings strings,
IDataCache cache,
FontProvider fonts,
ICurrencyService cs,
CommandHandler cmdHandler,
DiscordSocketClient client,
GamblingConfigService gss)
{
_db = db;
_strings = strings;
@@ -47,13 +54,12 @@ public class PlantPickService : INService
using var uow = db.GetDbContext();
var guildIds = client.Guilds.Select(x => x.Id).ToList();
var configs = uow.Set<GuildConfig>()
.AsQueryable()
.Include(x => x.GenerateCurrencyChannelIds)
.Where(x => guildIds.Contains(x.GuildId))
.ToList();
_generationChannels = new(configs
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
.AsQueryable()
.Include(x => x.GenerateCurrencyChannelIds)
.Where(x => guildIds.Contains(x.GuildId))
.ToList();
_generationChannels = new(configs.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
}
private string GetText(ulong gid, LocStr str)
@@ -65,7 +71,7 @@ public class PlantPickService : INService
using var uow = _db.GetDbContext();
var guildConfig = uow.GuildConfigsForId(gid, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
var toAdd = new GCChannelId() { ChannelId = cid };
var toAdd = new GCChannelId { ChannelId = cid };
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
{
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
@@ -75,13 +81,11 @@ public class PlantPickService : INService
else
{
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
if (toDelete != null)
{
uow.Remove(toDelete);
}
if (toDelete != null) uow.Remove(toDelete);
_generationChannels.TryRemove(cid);
enabled = false;
}
uow.SaveChanges();
return enabled;
}
@@ -94,7 +98,7 @@ public class PlantPickService : INService
}
/// <summary>
/// Get a random currency image stream, with an optional password sticked onto it.
/// Get a random currency image stream, with an optional password sticked onto it.
/// </summary>
/// <param name="pass">Optional password to add to top left corner.</param>
/// <returns>Stream of the currency image</returns>
@@ -111,6 +115,7 @@ public class PlantPickService : INService
{
extension = format.FileExtensions.FirstOrDefault() ?? "png";
}
// return the image
return curImg.ToStream();
}
@@ -124,7 +129,7 @@ public class PlantPickService : INService
}
/// <summary>
/// Add a password to the image.
/// Add a password to the image.
/// </summary>
/// <param name="curImg">Image to add password to.</param>
/// <param name="pass">Password to add to top left corner.</param>
@@ -149,10 +154,7 @@ public class PlantPickService : INService
new PointF(0, size.Height + 10));
// draw the password over the background
x.DrawText(pass,
font,
SixLabors.ImageSharp.Color.White,
new(0, 0));
x.DrawText(pass, font, Color.White, new(0, 0));
});
// return image as a stream for easy sending
return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png");
@@ -177,7 +179,8 @@ public class PlantPickService : INService
var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue);
var rng = new NadekoRandom();
if (DateTime.UtcNow - TimeSpan.FromSeconds(config.Generation.GenCooldown) < lastGeneration) //recently generated in this channel, don't generate again
if (DateTime.UtcNow - TimeSpan.FromSeconds(config.Generation.GenCooldown)
< lastGeneration) //recently generated in this channel, don't generate again
return;
var num = rng.Next(1, 101) + (config.Generation.Chance * 100);
@@ -194,9 +197,11 @@ public class PlantPickService : INService
var prefix = _cmdHandler.GetPrefix(channel.Guild.Id);
var toSend = dropAmount == 1
? GetText(channel.GuildId, strs.curgen_sn(config.Currency.Sign))
+ " " + GetText(channel.GuildId, strs.pick_sn(prefix))
+ " "
+ GetText(channel.GuildId, strs.pick_sn(prefix))
: GetText(channel.GuildId, strs.curgen_pl(dropAmount, config.Currency.Sign))
+ " " + GetText(channel.GuildId, strs.pick_pl(prefix));
+ " "
+ GetText(channel.GuildId, strs.pick_pl(prefix));
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
@@ -223,7 +228,7 @@ public class PlantPickService : INService
}
/// <summary>
/// Generate a hexadecimal string from 1000 to ffff.
/// Generate a hexadecimal string from 1000 to ffff.
/// </summary>
/// <returns>A hexadecimal string from 1000 to ffff</returns>
private string GenerateCurrencyPassword()
@@ -234,7 +239,11 @@ public class PlantPickService : INService
return num.ToString("x4");
}
public async Task<long> PickAsync(ulong gid, ITextChannel ch, ulong uid, string pass)
public async Task<long> PickAsync(
ulong gid,
ITextChannel ch,
ulong uid,
string pass)
{
await pickLock.WaitAsync();
try
@@ -246,12 +255,11 @@ public class PlantPickService : INService
// this method will sum all plants with that password,
// remove them, and get messageids of the removed plants
pass = pass?.Trim().TrimTo(10, hideDots: true).ToUpperInvariant();
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
// gets all plants in this channel with the same password
var entries = uow.PlantedCurrency
.AsQueryable()
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
.ToList();
var entries = uow.PlantedCurrency.AsQueryable()
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
.ToList();
// sum how much currency that is, and get all of the message ids (so that i can delete them)
amount = entries.Sum(x => x.Amount);
ids = entries.Select(x => x.MessageId).ToArray();
@@ -260,10 +268,8 @@ public class PlantPickService : INService
if (amount > 0)
{
// give the picked currency to the user
await _cs.AddAsync(uid, "Picked currency", amount, gamble: false);
}
await _cs.AddAsync(uid, "Picked currency", amount);
uow.SaveChanges();
}
@@ -283,16 +289,18 @@ public class PlantPickService : INService
}
}
public async Task<ulong?> SendPlantMessageAsync(ulong gid, IMessageChannel ch, string user, long amount, string pass)
public async Task<ulong?> SendPlantMessageAsync(
ulong gid,
IMessageChannel ch,
string user,
long amount,
string pass)
{
try
{
// get the text
var prefix = _cmdHandler.GetPrefix(gid);
var msgToSend = GetText(gid,
strs.planted(
Format.Bold(user),
amount + _gss.Data.Currency.Sign));
var msgToSend = GetText(gid, strs.planted(Format.Bold(user), amount + _gss.Data.Currency.Sign));
if (amount > 1)
msgToSend += " " + GetText(gid, strs.pick_pl(prefix));
@@ -313,34 +321,48 @@ public class PlantPickService : INService
}
}
public async Task<bool> PlantAsync(ulong gid, IMessageChannel ch, ulong uid, string user, long amount, string pass)
public async Task<bool> PlantAsync(
ulong gid,
IMessageChannel ch,
ulong uid,
string user,
long amount,
string pass)
{
// normalize it - no more than 10 chars, uppercase
pass = pass?.Trim().TrimTo(10, hideDots: true).ToUpperInvariant();
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
// has to be either null or alphanumeric
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
return false;
// remove currency from the user who's planting
if (await _cs.RemoveAsync(uid, "Planted currency", amount, gamble: false))
if (await _cs.RemoveAsync(uid, "Planted currency", amount))
{
// try to send the message with the currency image
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass);
if (msgId is null)
{
// if it fails it will return null, if it returns null, refund
await _cs.AddAsync(uid, "Planted currency refund", amount, gamble: false);
await _cs.AddAsync(uid, "Planted currency refund", amount);
return false;
}
// if it doesn't fail, put the plant in the database for other people to pick
await AddPlantToDatabase(gid, ch.Id, uid, msgId.Value, amount, pass);
return true;
}
// if user doesn't have enough currency, fail
return false;
}
private async Task AddPlantToDatabase(ulong gid, ulong cid, ulong uid, ulong mid, long amount, string pass)
private async Task AddPlantToDatabase(
ulong gid,
ulong cid,
ulong uid,
ulong mid,
long amount,
string pass)
{
await using var uow = _db.GetDbContext();
uow.PlantedCurrency.Add(new()
@@ -350,8 +372,8 @@ public class PlantPickService : INService
ChannelId = cid,
Password = pass,
UserId = uid,
MessageId = mid,
MessageId = mid
});
await uow.SaveChangesAsync();
}
}
}

View File

@@ -1,7 +1,7 @@
#nullable disable
using NadekoBot.Common.ModuleBehaviors;
using System.Text.Json;
using System.Text.Json.Serialization;
using NadekoBot.Common.ModuleBehaviors;
namespace NadekoBot.Modules.Gambling.Services;
@@ -10,7 +10,7 @@ public class VoteModel
[JsonPropertyName("userId")]
public ulong UserId { get; set; }
}
public class VoteRewardService : INService, IReadyExecutor
{
private readonly DiscordSocketClient _client;
@@ -33,18 +33,17 @@ public class VoteRewardService : INService, IReadyExecutor
_currencyService = currencyService;
_gamb = gamb;
}
public async Task OnReadyAsync()
{
if (_client.ShardId != 0)
return;
_http = new(new HttpClientHandler()
_http = new(new HttpClientHandler
{
AllowAutoRedirect = false,
ServerCertificateCustomValidationCallback = delegate { return true; }
AllowAutoRedirect = false, ServerCertificateCustomValidationCallback = delegate { return true; }
});
while (true)
{
await Task.Delay(30000);
@@ -54,8 +53,7 @@ public class VoteRewardService : INService, IReadyExecutor
try
{
if (!string.IsNullOrWhiteSpace(topggKey)
&& !string.IsNullOrWhiteSpace(topggServiceUrl))
if (!string.IsNullOrWhiteSpace(topggKey) && !string.IsNullOrWhiteSpace(topggServiceUrl))
{
_http.DefaultRequestHeaders.Authorization = new(topggKey);
var uri = new Uri(new(topggServiceUrl), "topgg/new");
@@ -82,11 +80,10 @@ public class VoteRewardService : INService, IReadyExecutor
var discordsKey = _creds.Votes?.DiscordsKey;
var discordsServiceUrl = _creds.Votes?.DiscordsServiceUrl;
try
{
if (!string.IsNullOrWhiteSpace(discordsKey)
&& !string.IsNullOrWhiteSpace(discordsServiceUrl))
if (!string.IsNullOrWhiteSpace(discordsKey) && !string.IsNullOrWhiteSpace(discordsServiceUrl))
{
_http.DefaultRequestHeaders.Authorization = new(discordsKey);
var res = await _http.GetStringAsync(new Uri(new(discordsServiceUrl), "discords/new"));
@@ -95,7 +92,7 @@ public class VoteRewardService : INService, IReadyExecutor
if (data is { Count: > 0 })
{
var ids = data.Select(x => x.UserId).ToList();
await _currencyService.AddBulkAsync(ids,
data.Select(_ => "discords.com vote reward"),
data.Select(x => _gamb.Data.VoteReward),
@@ -111,4 +108,4 @@ public class VoteRewardService : INService, IReadyExecutor
}
}
}
}
}

View File

@@ -1,28 +1,24 @@
#nullable disable
using NadekoBot.Modules.Gambling.Common.Waifu;
using NadekoBot.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.Waifu;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Gambling.Services;
public class WaifuService : INService
{
public class FullWaifuInfo
{
public WaifuInfo Waifu { get; set; }
public IEnumerable<string> Claims { get; set; }
public int Divorces { get; set; }
}
private readonly DbService _db;
private readonly ICurrencyService _cs;
private readonly IDataCache _cache;
private readonly GamblingConfigService _gss;
public WaifuService(DbService db, ICurrencyService cs, IDataCache cache,
public WaifuService(
DbService db,
ICurrencyService cs,
IDataCache cache,
GamblingConfigService gss)
{
_db = db;
@@ -45,18 +41,13 @@ public class WaifuService : INService
// owner has to be the owner of the waifu
if (waifu is null || waifu.ClaimerId != ownerUser.Id)
return false;
// if waifu likes the person, gotta pay the penalty
if (waifu.AffinityId == ownerUser.Id)
{
if (!await _cs.RemoveAsync(owner.Id,
"Waifu Transfer - affinity penalty",
(int)(waifu.Price * 0.6),
true))
{
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer - affinity penalty", (int)(waifu.Price * 0.6), true))
// unable to pay 60% penalty
return false;
}
waifu.Price = (int)(waifu.Price * 0.7); // half of 60% = 30% price reduction
if (waifu.Price < settings.Waifu.MinPrice)
@@ -64,12 +55,9 @@ public class WaifuService : INService
}
else // if not, pay 10% fee
{
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, gamble: true))
{
return false;
}
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, true)) return false;
waifu.Price = (int) (waifu.Price * 0.95); // half of 10% = 5% price reduction
waifu.Price = (int)(waifu.Price * 0.95); // half of 10% = 5% price reduction
if (waifu.Price < settings.Waifu.MinPrice)
waifu.Price = settings.Waifu.MinPrice;
}
@@ -92,41 +80,36 @@ public class WaifuService : INService
if (waifu is null)
return settings.Waifu.MinPrice;
var divorces = uow.WaifuUpdates.Count(x => x.Old != null &&
x.Old.UserId == user.Id &&
x.UpdateType == WaifuUpdateType.Claimed &&
x.New == null);
var affs = uow.WaifuUpdates
.AsQueryable()
.Where(w => w.User.UserId == user.Id && w.UpdateType == WaifuUpdateType.AffinityChanged &&
w.New != null)
.ToList()
.GroupBy(x => x.New)
.Count();
var divorces = uow.WaifuUpdates.Count(x
=> x.Old != null && x.Old.UserId == user.Id && x.UpdateType == WaifuUpdateType.Claimed && x.New == null);
var affs = uow.WaifuUpdates.AsQueryable()
.Where(w => w.User.UserId == user.Id
&& w.UpdateType == WaifuUpdateType.AffinityChanged
&& w.New != null)
.ToList()
.GroupBy(x => x.New)
.Count();
return (int) Math.Ceiling(waifu.Price * 1.25f) +
((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
return (int)Math.Ceiling(waifu.Price * 1.25f) + ((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
}
public async Task<bool> TryReset(IUser user)
{
await using var uow = _db.GetDbContext();
var price = GetResetPrice(user);
if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, gamble: true))
if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, true))
return false;
var affs = uow.WaifuUpdates
.AsQueryable()
.Where(w => w.User.UserId == user.Id
&& w.UpdateType == WaifuUpdateType.AffinityChanged
&& w.New != null);
var affs = uow.WaifuUpdates.AsQueryable()
.Where(w => w.User.UserId == user.Id
&& w.UpdateType == WaifuUpdateType.AffinityChanged
&& w.New != null);
var divorces = uow.WaifuUpdates
.AsQueryable()
.Where(x => x.Old != null &&
x.Old.UserId == user.Id &&
x.UpdateType == WaifuUpdateType.Claimed &&
x.New == null);
var divorces = uow.WaifuUpdates.AsQueryable()
.Where(x => x.Old != null
&& x.Old.UserId == user.Id
&& x.UpdateType == WaifuUpdateType.Claimed
&& x.New == null);
//reset changes of heart to 0
uow.WaifuUpdates.RemoveRange(affs);
@@ -161,32 +144,23 @@ public class WaifuService : INService
{
var claimer = uow.GetOrCreateUser(user);
var waifu = uow.GetOrCreateUser(target);
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true))
{
result = WaifuClaimResult.NotEnoughFunds;
}
else
{
uow.WaifuInfo.Add(w = new()
{
Waifu = waifu,
Claimer = claimer,
Affinity = null,
Price = amount
});
uow.WaifuInfo.Add(w = new() { Waifu = waifu, Claimer = claimer, Affinity = null, Price = amount });
uow.WaifuUpdates.Add(new()
{
User = waifu,
Old = null,
New = claimer,
UpdateType = WaifuUpdateType.Claimed
User = waifu, Old = null, New = claimer, UpdateType = WaifuUpdateType.Claimed
});
result = WaifuClaimResult.Success;
}
}
else if (isAffinity && amount > w.Price * settings.Waifu.Multipliers.CrushClaim)
{
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true))
{
result = WaifuClaimResult.NotEnoughFunds;
}
@@ -199,16 +173,13 @@ public class WaifuService : INService
uow.WaifuUpdates.Add(new()
{
User = w.Waifu,
Old = oldClaimer,
New = w.Claimer,
UpdateType = WaifuUpdateType.Claimed
User = w.Waifu, Old = oldClaimer, New = w.Claimer, UpdateType = WaifuUpdateType.Claimed
});
}
}
else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity
{
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true))
{
result = WaifuClaimResult.NotEnoughFunds;
}
@@ -221,15 +192,14 @@ public class WaifuService : INService
uow.WaifuUpdates.Add(new()
{
User = w.Waifu,
Old = oldClaimer,
New = w.Claimer,
UpdateType = WaifuUpdateType.Claimed
User = w.Waifu, Old = oldClaimer, New = w.Claimer, UpdateType = WaifuUpdateType.Claimed
});
}
}
else
{
result = WaifuClaimResult.InsufficientAmount;
}
await uow.SaveChangesAsync();
@@ -256,21 +226,12 @@ public class WaifuService : INService
else if (w is null)
{
var thisUser = uow.GetOrCreateUser(user);
uow.WaifuInfo.Add(new()
{
Affinity = newAff,
Waifu = thisUser,
Price = 1,
Claimer = null
});
uow.WaifuInfo.Add(new() { Affinity = newAff, Waifu = thisUser, Price = 1, Claimer = null });
success = true;
uow.WaifuUpdates.Add(new()
{
User = thisUser,
Old = null,
New = newAff,
UpdateType = WaifuUpdateType.AffinityChanged
User = thisUser, Old = null, New = newAff, UpdateType = WaifuUpdateType.AffinityChanged
});
}
else
@@ -282,10 +243,7 @@ public class WaifuService : INService
uow.WaifuUpdates.Add(new()
{
User = w.Waifu,
Old = oldAff,
New = newAff,
UpdateType = WaifuUpdateType.AffinityChanged
User = w.Waifu, Old = oldAff, New = newAff, UpdateType = WaifuUpdateType.AffinityChanged
});
}
@@ -318,7 +276,9 @@ public class WaifuService : INService
w = uow.WaifuInfo.ByWaifuUserId(targetId);
var now = DateTime.UtcNow;
if (w?.Claimer is null || w.Claimer.UserId != user.Id)
{
result = DivorceResult.NotYourWife;
}
else if (!_cache.TryAddDivorceCooldown(user.Id, out remaining))
{
result = DivorceResult.Cooldown;
@@ -329,13 +289,13 @@ public class WaifuService : INService
if (w.Affinity?.UserId == user.Id)
{
await _cs.AddAsync(w.Waifu.UserId, "Waifu Compensation", amount, gamble: true);
w.Price = (int) Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue);
await _cs.AddAsync(w.Waifu.UserId, "Waifu Compensation", amount, true);
w.Price = (int)Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue);
result = DivorceResult.SucessWithPenalty;
}
else
{
await _cs.AddAsync(user.Id, "Waifu Refund", amount, gamble: true);
await _cs.AddAsync(user.Id, "Waifu Refund", amount, true);
result = DivorceResult.Success;
}
@@ -345,10 +305,7 @@ public class WaifuService : INService
uow.WaifuUpdates.Add(new()
{
User = w.Waifu,
Old = oldClaimer,
New = null,
UpdateType = WaifuUpdateType.Claimed
User = w.Waifu, Old = oldClaimer, New = null, UpdateType = WaifuUpdateType.Claimed
});
}
@@ -360,42 +317,24 @@ public class WaifuService : INService
public async Task<bool> GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj)
{
if (!await _cs.RemoveAsync(from, "Bought waifu item", itemObj.Price, gamble: true))
{
return false;
}
if (!await _cs.RemoveAsync(from, "Bought waifu item", itemObj.Price, gamble: true)) return false;
await using var uow = _db.GetDbContext();
var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id,
set => set.Include(x => x.Items)
.Include(x => x.Claimer));
var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id, set => set.Include(x => x.Items).Include(x => x.Claimer));
if (w is null)
{
uow.WaifuInfo.Add(w = new()
{
Affinity = null,
Claimer = null,
Price = 1,
Waifu = uow.GetOrCreateUser(giftedWaifu),
Affinity = null, Claimer = null, Price = 1, Waifu = uow.GetOrCreateUser(giftedWaifu)
});
}
if (!itemObj.Negative)
{
w.Items.Add(new()
{
Name = itemObj.Name.ToLowerInvariant(),
ItemEmoji = itemObj.ItemEmoji,
});
if (w.Claimer?.UserId == @from.Id)
{
w.Items.Add(new() { Name = itemObj.Name.ToLowerInvariant(), ItemEmoji = itemObj.ItemEmoji });
if (w.Claimer?.UserId == from.Id)
w.Price += (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
}
else
{
w.Price += itemObj.Price / 2;
}
}
else
{
@@ -414,7 +353,6 @@ public class WaifuService : INService
using var uow = _db.GetDbContext();
var wi = uow.GetWaifuInfo(targetId);
if (wi is null)
{
wi = new()
{
AffinityCount = 0,
@@ -428,15 +366,15 @@ public class WaifuService : INService
Items = new(),
Price = 1
};
}
return wi;
}
public WaifuInfoStats GetFullWaifuInfoAsync(IGuildUser target)
{
using var uow = _db.GetDbContext();
var du = uow.GetOrCreateUser(target);
return GetFullWaifuInfoAsync(target.Id);
}
@@ -501,8 +439,18 @@ public class WaifuService : INService
public IReadOnlyList<WaifuItemModel> GetWaifuItems()
{
var conf = _gss.Data;
return conf.Waifu.Items
.Select(x => new WaifuItemModel(x.ItemEmoji, (int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices), x.Name, x.Negative))
.ToList();
return conf.Waifu.Items.Select(x
=> new WaifuItemModel(x.ItemEmoji,
(int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices),
x.Name,
x.Negative))
.ToList();
}
}
public class FullWaifuInfo
{
public WaifuInfo Waifu { get; set; }
public IEnumerable<string> Claims { get; set; }
public int Divorces { get; set; }
}
}

View File

@@ -1,10 +1,10 @@
#nullable disable
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.Collections;
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
namespace NadekoBot.Modules.Gambling;
@@ -13,69 +13,71 @@ public partial class Gambling
[Group]
public class ShopCommands : GamblingSubmodule<IShopService>
{
private readonly DbService _db;
private readonly ICurrencyService _cs;
public enum List
{
List
}
public enum Role
{
Role
}
public enum List
{
List
}
private readonly DbService _db;
private readonly ICurrencyService _cs;
public ShopCommands(DbService db, ICurrencyService cs, GamblingConfigService gamblingConf)
: base(gamblingConf)
: base(gamblingConf)
{
_db = db;
_cs = cs;
}
private Task ShopInternalAsync(int page = 0)
{
if (page < 0)
throw new ArgumentOutOfRangeException(nameof(page));
using var uow = _db.GetDbContext();
var entries = uow.GuildConfigsForId(ctx.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries
.ToIndexed();
return ctx.SendPaginatedConfirmAsync(page, curPage =>
{
var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray();
if (!theseEntries.Any())
return _eb.Create().WithErrorColor()
.WithDescription(GetText(strs.shop_none));
var embed = _eb.Create().WithOkColor()
.WithTitle(GetText(strs.shop));
for (var i = 0; i < theseEntries.Length; i++)
set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items))
.ShopEntries.ToIndexed();
return ctx.SendPaginatedConfirmAsync(page,
curPage =>
{
var entry = theseEntries[i];
embed.AddField(
$"#{(curPage * 9) + i + 1} - {entry.Price}{CurrencySign}",
EntryToString(entry),
true);
}
return embed;
}, entries.Count, 9, true);
var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray();
if (!theseEntries.Any())
return _eb.Create().WithErrorColor().WithDescription(GetText(strs.shop_none));
var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.shop));
for (var i = 0; i < theseEntries.Length; i++)
{
var entry = theseEntries[i];
embed.AddField($"#{(curPage * 9) + i + 1} - {entry.Price}{CurrencySign}",
EntryToString(entry),
true);
}
return embed;
},
entries.Count,
9);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public Task Shop(int page = 1)
{
if (--page < 0)
return Task.CompletedTask;
return ShopInternalAsync(page);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task Buy(int index)
{
@@ -85,9 +87,8 @@ public partial class Gambling
ShopEntry entry;
await using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set
.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items));
var config = uow.GuildConfigsForId(ctx.Guild.Id,
set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items));
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
entry = entries.ElementAtOrDefault(index);
uow.SaveChanges();
@@ -109,7 +110,7 @@ public partial class Gambling
await ReplyErrorLocalizedAsync(strs.shop_role_not_found);
return;
}
if (guser.RoleIds.Any(id => id == role.Id))
{
await ReplyErrorLocalizedAsync(strs.shop_role_already_bought);
@@ -125,23 +126,23 @@ public partial class Gambling
catch (Exception ex)
{
Log.Warning(ex, "Error adding shop role");
await _cs.AddAsync(ctx.User.Id, $"Shop error refund", entry.Price);
await _cs.AddAsync(ctx.User.Id, "Shop error refund", entry.Price);
await ReplyErrorLocalizedAsync(strs.shop_role_purchase_error);
return;
}
var profit = GetProfitAmount(entry.Price);
await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", profit);
await _cs.AddAsync(ctx.Client.CurrentUser.Id, $"Shop sell item - cut", entry.Price - profit);
await _cs.AddAsync(ctx.Client.CurrentUser.Id, "Shop sell item - cut", entry.Price - profit);
await ReplyConfirmLocalizedAsync(strs.shop_role_purchase(Format.Bold(role.Name)));
return;
}
else
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
else if (entry.Type == ShopEntryType.List)
if (entry.Type == ShopEntryType.List)
{
if (entry.Items.Count == 0)
{
@@ -158,14 +159,15 @@ public partial class Gambling
var x = uow.Set<ShopEntryItem>().Remove(item);
uow.SaveChanges();
}
try
{
await ctx.User
.EmbedAsync(_eb.Create().WithOkColor()
.WithTitle(GetText(strs.shop_purchase(ctx.Guild.Name)))
.AddField(GetText(strs.item), item.Text, false)
.AddField(GetText(strs.price), entry.Price.ToString(), true)
.AddField(GetText(strs.name), entry.Name, true));
await ctx.User.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.shop_purchase(ctx.Guild.Name)))
.AddField(GetText(strs.item), item.Text)
.AddField(GetText(strs.price), entry.Price.ToString(), true)
.AddField(GetText(strs.name), entry.Name, true));
await _cs.AddAsync(entry.AuthorId,
$"Shop sell item - {entry.Name}",
@@ -173,41 +175,37 @@ public partial class Gambling
}
catch
{
await _cs.AddAsync(ctx.User.Id,
$"Shop error refund - {entry.Name}",
entry.Price);
await _cs.AddAsync(ctx.User.Id, $"Shop error refund - {entry.Name}", entry.Price);
await using (var uow = _db.GetDbContext())
{
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries);
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items))
.ShopEntries);
entry = entries.ElementAtOrDefault(index);
if (entry != null)
{
if (entry.Items.Add(item))
{
uow.SaveChanges();
}
}
}
await ReplyErrorLocalizedAsync(strs.shop_buy_error);
return;
}
await ReplyConfirmLocalizedAsync(strs.shop_item_purchase);
}
else
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
}
}
private static long GetProfitAmount(int price) =>
(int)Math.Ceiling(0.90 * price);
private static long GetProfitAmount(int price)
=> (int)Math.Ceiling(0.90 * price);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageRoles)]
@@ -215,8 +213,8 @@ public partial class Gambling
{
if (price < 1)
return;
var entry = new ShopEntry()
var entry = new ShopEntry
{
Name = "-",
Price = price,
@@ -228,19 +226,18 @@ public partial class Gambling
await using (var uow = _db.GetDbContext())
{
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries)
{
entry
};
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items))
.ShopEntries) { entry };
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
uow.SaveChanges();
}
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
.WithTitle(GetText(strs.shop_item_add)));
await ctx.Channel.EmbedAsync(EntryToEmbed(entry).WithTitle(GetText(strs.shop_item_add)));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopAdd(List _, int price, [Leftover] string name)
@@ -248,31 +245,29 @@ public partial class Gambling
if (price < 1)
return;
var entry = new ShopEntry()
var entry = new ShopEntry
{
Name = name.TrimTo(100),
Price = price,
Type = ShopEntryType.List,
AuthorId = ctx.User.Id,
Items = new(),
Items = new()
};
await using (var uow = _db.GetDbContext())
{
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries)
{
entry
};
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items))
.ShopEntries) { entry };
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
uow.SaveChanges();
}
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
.WithTitle(GetText(strs.shop_item_add)));
await ctx.Channel.EmbedAsync(EntryToEmbed(entry).WithTitle(GetText(strs.shop_item_add)));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopListAdd(int index, [Leftover] string itemText)
@@ -280,27 +275,22 @@ public partial class Gambling
index -= 1;
if (index < 0)
return;
var item = new ShopEntryItem()
{
Text = itemText
};
var item = new ShopEntryItem { Text = itemText };
ShopEntry entry;
var rightType = false;
var added = false;
await using (var uow = _db.GetDbContext())
{
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries);
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.SaveChanges();
}
}
}
if (entry is null)
await ReplyErrorLocalizedAsync(strs.shop_item_not_found);
else if (!rightType)
@@ -311,7 +301,8 @@ public partial class Gambling
await ReplyConfirmLocalizedAsync(strs.shop_list_item_added);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopRemove(int index)
@@ -322,9 +313,8 @@ public partial class Gambling
ShopEntry removed;
await using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set
.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items));
var config = uow.GuildConfigsForId(ctx.Guild.Id,
set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items));
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
removed = entries.ElementAtOrDefault(index);
@@ -339,11 +329,11 @@ public partial class Gambling
if (removed is null)
await ReplyErrorLocalizedAsync(strs.shop_item_not_found);
else
await ctx.Channel.EmbedAsync(EntryToEmbed(removed)
.WithTitle(GetText(strs.shop_item_rm)));
await ctx.Channel.EmbedAsync(EntryToEmbed(removed).WithTitle(GetText(strs.shop_item_rm)));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopChangePrice(int index, int price)
@@ -363,14 +353,15 @@ public partial class Gambling
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopChangeName(int index, [Leftover] string newName)
{
if (--index < 0 || string.IsNullOrWhiteSpace(newName))
return;
var succ = await _service.ChangeEntryNameAsync(ctx.Guild.Id, index, newName);
if (succ)
{
@@ -383,14 +374,15 @@ public partial class Gambling
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopSwap(int index1, int index2)
{
if (--index1 < 0 || --index2 < 0 || index1 == index2)
return;
var succ = await _service.SwapEntriesAsync(ctx.Guild.Id, index1, index2);
if (succ)
{
@@ -402,8 +394,9 @@ public partial class Gambling
await ctx.ErrorAsync();
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopMove(int fromIndex, int toIndex)
@@ -422,36 +415,36 @@ public partial class Gambling
await ctx.ErrorAsync();
}
}
public IEmbedBuilder EntryToEmbed(ShopEntry entry)
{
var embed = _eb.Create().WithOkColor();
if (entry.Type == ShopEntryType.Role)
return embed.AddField(GetText(strs.name), GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"))), true)
.AddField(GetText(strs.price), entry.Price.ToString(), true)
.AddField(GetText(strs.type), entry.Type.ToString(), true);
else if (entry.Type == ShopEntryType.List)
return embed
.AddField(GetText(strs.name),
GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name
?? "MISSING_ROLE"))),
true)
.AddField(GetText(strs.price), entry.Price.ToString(), true)
.AddField(GetText(strs.type), entry.Type.ToString(), true);
if (entry.Type == ShopEntryType.List)
return embed.AddField(GetText(strs.name), entry.Name, true)
.AddField(GetText(strs.price), entry.Price.ToString(), true)
.AddField(GetText(strs.type), GetText(strs.random_unique_item), true);
.AddField(GetText(strs.price), entry.Price.ToString(), true)
.AddField(GetText(strs.type), GetText(strs.random_unique_item), true);
//else if (entry.Type == ShopEntryType.Infinite_List)
// return embed.AddField(GetText(strs.name), GetText(strs.shop_role(Format.Bold(entry.RoleName)), true))
// .AddField(GetText(strs.price), entry.Price.ToString(), true)
// .AddField(GetText(strs.type), entry.Type.ToString(), true);
else return null;
return null;
}
public string EntryToString(ShopEntry entry)
{
if (entry.Type == ShopEntryType.Role)
{
return GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE")));
}
else if (entry.Type == ShopEntryType.List)
{
if (entry.Type == ShopEntryType.List)
return GetText(strs.unique_items_left(entry.Items.Count)) + "\n" + entry.Name;
}
//else if (entry.Type == ShopEntryType.Infinite_List)
//{
@@ -459,4 +452,4 @@ public partial class Gambling
return "";
}
}
}
}

View File

@@ -1,15 +1,15 @@
#nullable disable
using System.Text;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
using SixLabors.Fonts;
using Image = SixLabors.ImageSharp.Image;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.Text;
using Color = SixLabors.ImageSharp.Color;
using Image = SixLabors.ImageSharp.Image;
namespace NadekoBot.Modules.Gambling;
@@ -31,64 +31,23 @@ public partial class Gambling
private FontProvider _fonts;
private readonly DbService _db;
public SlotCommands(IDataCache data,
FontProvider fonts, DbService db,
GamblingConfigService gamb) : base(gamb)
public SlotCommands(
IDataCache data,
FontProvider fonts,
DbService db,
GamblingConfigService gamb)
: base(gamb)
{
_images = data.LocalImages;
_fonts = fonts;
_db = db;
}
public sealed class SlotMachine
{
public const int MAX_VALUE = 5;
public Task Test()
=> Task.CompletedTask;
private static readonly List<Func<int[], int>> _winningCombos = new()
{
//three flowers
arr => arr.All(a=>a==MAX_VALUE) ? 30 : 0,
//three of the same
arr => !arr.Any(a => a != arr[0]) ? 10 : 0,
//two flowers
arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0,
//one flower
arr => arr.Any(a => a == MAX_VALUE) ? 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, MAX_VALUE + 1);
}
var multi = 0;
foreach (var t in _winningCombos)
{
multi = t(numbers);
if (multi != 0)
break;
}
return new(numbers, multi);
}
public struct SlotResult
{
public int[] Numbers { get; }
public int Multiplier { get; }
public SlotResult(int[] nums, int multi)
{
Numbers = nums;
Multiplier = multi;
}
}
}
public Task Test() => Task.CompletedTask;
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[OwnerOnly]
public async Task SlotStats()
{
@@ -100,16 +59,17 @@ public partial class Gambling
bet = 1;
var embed = _eb.Create()
.WithOkColor()
.WithTitle("Slot Stats")
.AddField("Total Bet", bet.ToString(), true)
.AddField("Paid Out", paid.ToString(), true)
.WithFooter($"Payout Rate: {paid * 1.0 / bet * 100:f4}%");
.WithOkColor()
.WithTitle("Slot Stats")
.AddField("Total Bet", bet.ToString(), true)
.AddField("Paid Out", paid.ToString(), true)
.WithFooter($"Payout Rate: {paid * 1.0 / bet * 100:f4}%");
await ctx.Channel.EmbedAsync(embed);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[OwnerOnly]
public async Task SlotTest(int tests = 1000)
{
@@ -133,21 +93,24 @@ public partial class Gambling
sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
payout += key * dict[key];
}
await SendConfirmAsync("Slot Test Results", sb.ToString(),
await SendConfirmAsync("Slot Test Results",
sb.ToString(),
footer: $"Total Bet: {tests} | Payout: {payout} | {payout * 1.0f / tests * 100}%");
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task Slot(ShmartNumber amount)
{
if (!_runningUsers.Add(ctx.User.Id))
return;
try
{
if (!await CheckBetMandatory(amount))
return;
await ctx.Channel.TriggerTypingAsync();
var result = await _service.SlotAsync(ctx.User.Id, amount);
@@ -155,9 +118,7 @@ public partial class Gambling
if (result.Error != GamblingError.None)
{
if (result.Error == GamblingError.NotEnough)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
return;
}
@@ -168,40 +129,45 @@ public partial class Gambling
long ownedAmount;
await using (var uow = _db.GetDbContext())
{
ownedAmount = uow.Set<DiscordUser>()
.FirstOrDefault(x => x.UserId == ctx.User.Id)
?.CurrencyAmount ?? 0;
ownedAmount = uow.Set<DiscordUser>().FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount
?? 0;
}
using (var bgImage = Image.Load<Rgba32>(_images.SlotBackground, out var format))
{
var numbers = new int[3];
result.Rolls.CopyTo(numbers, 0);
Color fontColor = _config.Slots.CurrencyFontColor;
bgImage.Mutate(x => x.DrawText(new()
{
TextOptions = new()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 140,
WrapTextWidth = 140
}
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), fontColor,
},
result.Won.ToString(),
_fonts.DottyFont.CreateFont(65),
fontColor,
new(227, 92)));
var bottomFont = _fonts.DottyFont.CreateFont(50);
bgImage.Mutate(x => x.DrawText(new()
{
TextOptions = new()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 135,
WrapTextWidth = 135
}
}, amount.ToString(), bottomFont, fontColor,
},
amount.ToString(),
bottomFont,
fontColor,
new(129, 472)));
bgImage.Mutate(x => x.DrawText(new()
@@ -210,9 +176,12 @@ public partial class Gambling
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 135,
WrapTextWidth = 135
}
}, ownedAmount.ToString(), bottomFont, fontColor,
},
ownedAmount.ToString(),
bottomFont,
fontColor,
new(325, 472)));
//sw.PrintLap("drew red text");
@@ -238,8 +207,8 @@ public partial class Gambling
await using (var imgStream = bgImage.ToStream())
{
await ctx.Channel.SendFileAsync(imgStream,
filename: "result.png",
text: Format.Bold(ctx.User.ToString()) + " " + msg);
"result.png",
Format.Bold(ctx.User.ToString()) + " " + msg);
}
}
}
@@ -252,5 +221,49 @@ public partial class Gambling
});
}
}
public sealed class SlotMachine
{
public const int MAX_VALUE = 5;
private static readonly List<Func<int[], int>> _winningCombos = new()
{
//three flowers
arr => arr.All(a => a == MAX_VALUE) ? 30 : 0,
//three of the same
arr => !arr.Any(a => a != arr[0]) ? 10 : 0,
//two flowers
arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0,
//one flower
arr => arr.Any(a => a == MAX_VALUE) ? 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, MAX_VALUE + 1);
var multi = 0;
foreach (var t in _winningCombos)
{
multi = t(numbers);
if (multi != 0)
break;
}
return new(numbers, multi);
}
public struct SlotResult
{
public int[] Numbers { get; }
public int Multiplier { get; }
public SlotResult(int[] nums, int multi)
{
Numbers = nums;
Multiplier = multi;
}
}
}
}
}
}

View File

@@ -8,4 +8,4 @@ public class TestGamblingService : InteractionModuleBase
[SlashCommand("test", "uwu")]
public async Task Test(string input1, int input2)
=> await RespondAsync("Bravo " + input1 + input2);
}
}

View File

@@ -1,7 +1,7 @@
#nullable disable
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.Waifu;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Gambling.Common;
namespace NadekoBot.Modules.Gambling;
@@ -10,18 +10,19 @@ public partial class Gambling
[Group]
public class WaifuClaimCommands : GamblingSubmodule<WaifuService>
{
public WaifuClaimCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
: base(gamblingConfService)
{
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task WaifuReset()
{
var price = _service.GetResetPrice(ctx.User);
var embed = _eb.Create()
.WithTitle(GetText(strs.waifu_reset_confirm))
.WithDescription(GetText(strs.waifu_reset_price(Format.Bold(price + CurrencySign))));
.WithTitle(GetText(strs.waifu_reset_confirm))
.WithDescription(GetText(strs.waifu_reset_price(Format.Bold(price + CurrencySign))));
if (!await PromptUserConfirmAsync(embed))
return;
@@ -31,12 +32,14 @@ public partial class Gambling
await ReplyConfirmLocalizedAsync(strs.waifu_reset);
return;
}
await ReplyErrorLocalizedAsync(strs.waifu_reset_fail);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuClaim(int amount, [Leftover]IUser target)
public async Task WaifuClaim(int amount, [Leftover] IUser target)
{
if (amount < _config.Waifu.MinPrice)
{
@@ -54,43 +57,44 @@ public partial class Gambling
if (result == WaifuClaimResult.InsufficientAmount)
{
await ReplyErrorLocalizedAsync(strs.waifu_not_enough(Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))));
await ReplyErrorLocalizedAsync(
strs.waifu_not_enough(Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))));
return;
}
if (result == WaifuClaimResult.NotEnoughFunds)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
var msg = GetText(strs.waifu_claimed(
Format.Bold(target.ToString()),
amount + CurrencySign));
var msg = GetText(strs.waifu_claimed(Format.Bold(target.ToString()), amount + CurrencySign));
if (w.Affinity?.UserId == ctx.User.Id)
msg += "\n" + GetText(strs.waifu_fulfilled(target, w.Price + CurrencySign));
else
msg = " " + msg;
await SendConfirmAsync(ctx.User.Mention + msg);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task WaifuTransfer(ulong waifuId, IUser newOwner)
{
if (!await _service.WaifuTransfer(ctx.User, waifuId, newOwner)
)
if (!await _service.WaifuTransfer(ctx.User, waifuId, newOwner))
{
await ReplyErrorLocalizedAsync(strs.waifu_transfer_fail);
return;
}
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(
Format.Bold(waifuId.ToString()),
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(Format.Bold(waifuId.ToString()),
Format.Bold(ctx.User.ToString()),
Format.Bold(newOwner.ToString())));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task WaifuTransfer(IUser waifu, IUser newOwner)
@@ -101,36 +105,35 @@ public partial class Gambling
return;
}
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(
Format.Bold(waifu.ToString()),
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(Format.Bold(waifu.ToString()),
Format.Bold(ctx.User.ToString()),
Format.Bold(newOwner.ToString())));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(-1)]
public Task Divorce([Leftover] string target)
{
var waifuUserId = _service.GetWaifuUserId(ctx.User.Id, target);
if (waifuUserId == default)
{
return ReplyErrorLocalizedAsync(strs.waifu_not_yours);
}
if (waifuUserId == default) return ReplyErrorLocalizedAsync(strs.waifu_not_yours);
return Divorce(waifuUserId);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task Divorce([Leftover]IGuildUser target)
public Task Divorce([Leftover] IGuildUser target)
=> Divorce(target.Id);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task Divorce([Leftover]ulong targetId)
public async Task Divorce([Leftover] ulong targetId)
{
if (targetId == ctx.User.Id)
return;
@@ -138,64 +141,52 @@ public partial class Gambling
var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId);
if (result == DivorceResult.SucessWithPenalty)
{
await ReplyConfirmLocalizedAsync(strs.waifu_divorced_like(Format.Bold(w.Waifu.ToString()), amount + CurrencySign));
}
await ReplyConfirmLocalizedAsync(strs.waifu_divorced_like(Format.Bold(w.Waifu.ToString()),
amount + CurrencySign));
else if (result == DivorceResult.Success)
{
await ReplyConfirmLocalizedAsync(strs.waifu_divorced_notlike(amount + CurrencySign));
}
else if (result == DivorceResult.NotYourWife)
{
await ReplyErrorLocalizedAsync(strs.waifu_not_yours);
}
else
{
await ReplyErrorLocalizedAsync(strs.waifu_recent_divorce(
Format.Bold(((int)remaining?.TotalHours).ToString()),
Format.Bold(remaining?.Minutes.ToString())));
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task Affinity([Leftover]IGuildUser u = null)
public async Task Affinity([Leftover] IGuildUser u = null)
{
if (u?.Id == ctx.User.Id)
{
await ReplyErrorLocalizedAsync(strs.waifu_egomaniac);
return;
}
var (oldAff, sucess, remaining) = await _service.ChangeAffinityAsync(ctx.User, u);
if (!sucess)
{
if (remaining != null)
{
await ReplyErrorLocalizedAsync(strs.waifu_affinity_cooldown(
Format.Bold(((int)remaining?.TotalHours).ToString()),
Format.Bold(remaining?.Minutes.ToString())));
}
else
{
await ReplyErrorLocalizedAsync(strs.waifu_affinity_already);
}
return;
}
if (u is null)
{
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_reset);
}
else if (oldAff is null)
{
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_set(Format.Bold(u.ToString())));
}
else
{
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_changed(Format.Bold(oldAff.ToString()), Format.Bold(u.ToString())));
}
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_changed(Format.Bold(oldAff.ToString()),
Format.Bold(u.ToString())));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuLb(int page = 1)
{
@@ -215,32 +206,32 @@ public partial class Gambling
return;
}
var embed = _eb.Create()
.WithTitle(GetText(strs.waifus_top_waifus))
.WithOkColor();
var embed = _eb.Create().WithTitle(GetText(strs.waifus_top_waifus)).WithOkColor();
var i = 0;
foreach (var w in waifus)
{
var j = i++;
embed.AddField("#" + ((page * 9) + j + 1) + " - " + w.Price + CurrencySign, w.ToString(), false);
embed.AddField("#" + ((page * 9) + j + 1) + " - " + w.Price + CurrencySign, w.ToString());
}
await ctx.Channel.EmbedAsync(embed);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public Task WaifuInfo([Leftover]IUser target = null)
public Task WaifuInfo([Leftover] IUser target = null)
{
if (target is null)
target = ctx.User;
return InternalWaifuInfo(target.Id, target.ToString());
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task WaifuInfo(ulong targetId)
@@ -251,52 +242,50 @@ public partial class Gambling
var wi = _service.GetFullWaifuInfoAsync(targetId);
var affInfo = _service.GetAffinityTitle(wi.AffinityCount);
var waifuItems = _service.GetWaifuItems()
.ToDictionary(x => x.ItemEmoji, x => x);
var waifuItems = _service.GetWaifuItems().ToDictionary(x => x.ItemEmoji, x => x);
var nobody = GetText(strs.nobody);
var itemsStr = !wi.Items.Any()
? "-"
: string.Join("\n",
wi.Items.Where(x => waifuItems.TryGetValue(x.ItemEmoji, out _))
.OrderBy(x => waifuItems[x.ItemEmoji].Price)
.GroupBy(x => x.ItemEmoji)
.Select(x => $"{x.Key} x{x.Count(),-3}")
.Chunk(2)
.Select(x => string.Join(" ", x))
);
.OrderBy(x => waifuItems[x.ItemEmoji].Price)
.GroupBy(x => x.ItemEmoji)
.Select(x => $"{x.Key} x{x.Count(),-3}")
.Chunk(2)
.Select(x => string.Join(" ", x)));
var fansStr = wi.Fans.Shuffle().Take(30).Select(x => wi.Claims.Contains(x) ? $"{x} 💞" : x).Join('\n');
var fansStr = wi
.Fans
.Shuffle()
.Take(30)
.Select(x => wi.Claims.Contains(x) ? $"{x} 💞" : x)
.Join('\n');
if (string.IsNullOrWhiteSpace(fansStr))
fansStr = "-";
var embed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.waifu) + " " + (wi.FullName ?? name ?? targetId.ToString()) + " - \"the " +
_service.GetClaimTitle(wi.ClaimCount) + "\"")
.AddField(GetText(strs.price), wi.Price.ToString(), true)
.AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
.AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
.AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true)
.AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
.AddField("\u200B", "\u200B", true)
.AddField(GetText(strs.fans(wi.Fans.Count)), fansStr, true)
.AddField($"Waifus ({wi.ClaimCount})", wi.ClaimCount == 0
? nobody
: string.Join("\n", wi.Claims.Shuffle().Take(30)), true)
.AddField(GetText(strs.gifts), itemsStr, true);
.WithOkColor()
.WithTitle(GetText(strs.waifu)
+ " "
+ (wi.FullName ?? name ?? targetId.ToString())
+ " - \"the "
+ _service.GetClaimTitle(wi.ClaimCount)
+ "\"")
.AddField(GetText(strs.price), wi.Price.ToString(), true)
.AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
.AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
.AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true)
.AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
.AddField("\u200B", "\u200B", true)
.AddField(GetText(strs.fans(wi.Fans.Count)), fansStr, true)
.AddField($"Waifus ({wi.ClaimCount})",
wi.ClaimCount == 0 ? nobody : string.Join("\n", wi.Claims.Shuffle().Take(30)),
true)
.AddField(GetText(strs.gifts), itemsStr, true);
return ctx.Channel.EmbedAsync(embed);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task WaifuGift(int page = 1)
@@ -305,28 +294,29 @@ public partial class Gambling
return;
var waifuItems = _service.GetWaifuItems();
await ctx.SendPaginatedConfirmAsync(page, cur =>
{
var embed = _eb.Create()
.WithTitle(GetText(strs.waifu_gift_shop))
.WithOkColor();
await ctx.SendPaginatedConfirmAsync(page,
cur =>
{
var embed = _eb.Create().WithTitle(GetText(strs.waifu_gift_shop)).WithOkColor();
waifuItems.OrderBy(x => x.Negative)
.ThenBy(x => x.Price)
.Skip(9 * cur)
.Take(9)
.ToList()
.ForEach(x => embed.AddField($"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
Format.Bold(x.Price.ToString()) + _config.Currency.Sign,
true
)
);
waifuItems.OrderBy(x => x.Negative)
.ThenBy(x => x.Price)
.Skip(9 * cur)
.Take(9)
.ToList()
.ForEach(x => embed.AddField(
$"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
Format.Bold(x.Price.ToString()) + _config.Currency.Sign,
true));
return embed;
}, waifuItems.Count, 9);
return embed;
},
waifuItems.Count,
9);
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task WaifuGift(string itemName, [Leftover] IUser waifu)
@@ -341,18 +331,14 @@ public partial class Gambling
await ReplyErrorLocalizedAsync(strs.waifu_gift_not_exist);
return;
}
var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, item);
if (sucess)
{
await ReplyConfirmLocalizedAsync(strs.waifu_gift(
Format.Bold(item.ToString() + " " + item.ItemEmoji),
await ReplyConfirmLocalizedAsync(strs.waifu_gift(Format.Bold(item + " " + item.ItemEmoji),
Format.Bold(waifu.ToString())));
}
else
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
}
}
}
}

View File

@@ -1,8 +1,8 @@
#nullable disable
using Wof = NadekoBot.Modules.Gambling.Common.WheelOfFortune.WheelOfFortuneGame;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
using System.Collections.Immutable;
using Wof = NadekoBot.Modules.Gambling.Common.WheelOfFortune.WheelOfFortuneGame;
namespace NadekoBot.Modules.Gambling;
@@ -10,15 +10,8 @@ public partial class Gambling
{
public class WheelOfFortuneCommands : GamblingSubmodule<GamblingService>
{
private static readonly ImmutableArray<string> _emojis = new string[] {
"⬆",
"↖",
"⬅",
"↙",
"⬇",
"↘",
"➡",
"↗" }.ToImmutableArray();
private static readonly ImmutableArray<string> _emojis =
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
private readonly ICurrencyService _cs;
private readonly DbService _db;
@@ -30,13 +23,14 @@ public partial class Gambling
_db = db;
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task WheelOfFortune(ShmartNumber amount)
{
if (!await CheckBetMandatory(amount))
return;
if (!await _cs.RemoveAsync(ctx.User.Id, "Wheel Of Fortune - bet", amount, gamble: true))
if (!await _cs.RemoveAsync(ctx.User.Id, "Wheel Of Fortune - bet", amount, true))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
@@ -45,8 +39,7 @@ public partial class Gambling
var result = await _service.WheelOfFortuneSpinAsync(ctx.User.Id, amount);
var wofMultipliers = _config.WheelOfFortune.Multipliers;
await SendConfirmAsync(
Format.Bold($@"{ctx.User.ToString()} won: {result.Amount + CurrencySign}
await SendConfirmAsync(Format.Bold($@"{ctx.User.ToString()} won: {result.Amount + CurrencySign}
『{wofMultipliers[1]}』 『{wofMultipliers[0]}』 『{wofMultipliers[7]}』
@@ -55,4 +48,4 @@ public partial class Gambling
『{wofMultipliers[3]}』 『{wofMultipliers[4]}』 『{wofMultipliers[5]}』"));
}
}
}
}