mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-11-04 16:44:28 -05:00
Global usings and file scoped namespaces
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
@@ -14,172 +12,171 @@ using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
using NadekoBot.Modules.Games.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
// wth is this, needs full rewrite
|
||||
public partial class Gambling
|
||||
{
|
||||
// wth is this, needs full rewrite
|
||||
public partial class Gambling
|
||||
[Group]
|
||||
public class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
|
||||
{
|
||||
[Group]
|
||||
public class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly GamesConfigService _gamesConf;
|
||||
|
||||
public AnimalRacingCommands(ICurrencyService cs, DiscordSocketClient client,
|
||||
GamblingConfigService gamblingConf, GamesConfigService gamesConf) : base(gamblingConf)
|
||||
{
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly GamesConfigService _gamesConf;
|
||||
_cs = cs;
|
||||
_client = client;
|
||||
_gamesConf = gamesConf;
|
||||
}
|
||||
|
||||
public AnimalRacingCommands(ICurrencyService cs, DiscordSocketClient client,
|
||||
GamblingConfigService gamblingConf, GamesConfigService gamesConf) : base(gamblingConf)
|
||||
private IUserMessage raceMessage = null;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptionsAttribute(typeof(RaceOptions))]
|
||||
public Task Race(params string[] args)
|
||||
{
|
||||
var (options, success) = OptionsParser.ParseFrom(new RaceOptions(), args);
|
||||
|
||||
var ar = new AnimalRace(options, _cs, _gamesConf.Data.RaceAnimals.Shuffle());
|
||||
if (!_service.AnimalRaces.TryAdd(ctx.Guild.Id, ar))
|
||||
return SendErrorAsync(GetText(strs.animal_race), GetText(strs.animal_race_already_started));
|
||||
|
||||
ar.Initialize();
|
||||
|
||||
var count = 0;
|
||||
Task _client_MessageReceived(SocketMessage arg)
|
||||
{
|
||||
_cs = cs;
|
||||
_client = client;
|
||||
_gamesConf = gamesConf;
|
||||
}
|
||||
|
||||
private IUserMessage raceMessage = null;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptionsAttribute(typeof(RaceOptions))]
|
||||
public Task Race(params string[] args)
|
||||
{
|
||||
var (options, success) = OptionsParser.ParseFrom(new RaceOptions(), args);
|
||||
|
||||
var ar = new AnimalRace(options, _cs, _gamesConf.Data.RaceAnimals.Shuffle());
|
||||
if (!_service.AnimalRaces.TryAdd(ctx.Guild.Id, ar))
|
||||
return SendErrorAsync(GetText(strs.animal_race), GetText(strs.animal_race_already_started));
|
||||
|
||||
ar.Initialize();
|
||||
|
||||
var count = 0;
|
||||
Task _client_MessageReceived(SocketMessage arg)
|
||||
var _ = Task.Run(() =>
|
||||
{
|
||||
var _ = Task.Run(() =>
|
||||
try
|
||||
{
|
||||
try
|
||||
if (arg.Channel.Id == ctx.Channel.Id)
|
||||
{
|
||||
if (arg.Channel.Id == ctx.Channel.Id)
|
||||
if (ar.CurrentPhase == AnimalRace.Phase.Running && ++count % 9 == 0)
|
||||
{
|
||||
if (ar.CurrentPhase == AnimalRace.Phase.Running && ++count % 9 == 0)
|
||||
{
|
||||
raceMessage = null;
|
||||
}
|
||||
raceMessage = null;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task Ar_OnEnded(AnimalRace race)
|
||||
{
|
||||
_client.MessageReceived -= _client_MessageReceived;
|
||||
_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),
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
ar.OnStartingFailed += Ar_OnStartingFailed;
|
||||
ar.OnStateUpdate += Ar_OnStateUpdate;
|
||||
ar.OnEnded += Ar_OnEnded;
|
||||
ar.OnStarted += Ar_OnStarted;
|
||||
_client.MessageReceived += _client_MessageReceived;
|
||||
|
||||
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting(options.StartTime)),
|
||||
footer: GetText(strs.animal_race_join_instr(Prefix)));
|
||||
catch { }
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Ar_OnStarted(AnimalRace race)
|
||||
Task Ar_OnEnded(AnimalRace race)
|
||||
{
|
||||
if (race.Users.Count == race.MaxUsers)
|
||||
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full));
|
||||
_client.MessageReceived -= _client_MessageReceived;
|
||||
_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),
|
||||
winner.Animal.Icon,
|
||||
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)));
|
||||
}
|
||||
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_won(Format.Bold(winner.Username), winner.Animal.Icon)));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||
{
|
||||
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
|
||||
ar.OnStartingFailed += Ar_OnStartingFailed;
|
||||
ar.OnStateUpdate += Ar_OnStateUpdate;
|
||||
ar.OnEnded += Ar_OnEnded;
|
||||
ar.OnStarted += Ar_OnStarted;
|
||||
_client.MessageReceived += _client_MessageReceived;
|
||||
|
||||
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting(options.StartTime)),
|
||||
footer: GetText(strs.animal_race_join_instr(Prefix)));
|
||||
}
|
||||
|
||||
private Task Ar_OnStarted(AnimalRace race)
|
||||
{
|
||||
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)));
|
||||
}
|
||||
|
||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||
{
|
||||
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
|
||||
{String.Join("\n", race.Users.Select(p =>
|
||||
{
|
||||
var index = race.FinishedUsers.IndexOf(p);
|
||||
var extra = (index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}");
|
||||
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
|
||||
}))}
|
||||
{
|
||||
var index = race.FinishedUsers.IndexOf(p);
|
||||
var extra = (index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}");
|
||||
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
|
||||
}))}
|
||||
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
|
||||
|
||||
var msg = raceMessage;
|
||||
var msg = raceMessage;
|
||||
|
||||
if (msg is null)
|
||||
raceMessage = await SendConfirmAsync(text)
|
||||
.ConfigureAwait(false);
|
||||
else
|
||||
await msg.ModifyAsync(x => x.Embed = _eb.Create()
|
||||
if (msg is null)
|
||||
raceMessage = await SendConfirmAsync(text)
|
||||
.ConfigureAwait(false);
|
||||
else
|
||||
await msg.ModifyAsync(x => x.Embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task Ar_OnStartingFailed(AnimalRace race)
|
||||
private Task Ar_OnStartingFailed(AnimalRace race)
|
||||
{
|
||||
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
|
||||
return ReplyErrorLocalizedAsync(strs.animal_race_failed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task JoinRace(ShmartNumber amount = default)
|
||||
{
|
||||
if (!await CheckBetOptional(amount).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
if (!_service.AnimalRaces.TryGetValue(ctx.Guild.Id, out var ar))
|
||||
{
|
||||
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
|
||||
return ReplyErrorLocalizedAsync(strs.animal_race_failed);
|
||||
await ReplyErrorLocalizedAsync(strs.race_not_exist).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task JoinRace(ShmartNumber amount = default)
|
||||
try
|
||||
{
|
||||
if (!await CheckBetOptional(amount).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
if (!_service.AnimalRaces.TryGetValue(ctx.Guild.Id, out var ar))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.race_not_exist).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var user = await ar.JoinRace(ctx.User.Id, ctx.User.ToString(), amount)
|
||||
.ConfigureAwait(false);
|
||||
if (amount > 0)
|
||||
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)));
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
//ignore if user inputed an invalid amount
|
||||
}
|
||||
catch (AlreadyJoinedException)
|
||||
{
|
||||
// just ignore this
|
||||
}
|
||||
catch (AlreadyStartedException)
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
catch (AnimalRaceFullException)
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (NotEnoughFundsException)
|
||||
{
|
||||
await SendErrorAsync(GetText(strs.not_enough(CurrencySign)));
|
||||
}
|
||||
var user = await ar.JoinRace(ctx.User.Id, ctx.User.ToString(), amount)
|
||||
.ConfigureAwait(false);
|
||||
if (amount > 0)
|
||||
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)));
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
//ignore if user inputed an invalid amount
|
||||
}
|
||||
catch (AlreadyJoinedException)
|
||||
{
|
||||
// just ignore this
|
||||
}
|
||||
catch (AlreadyStartedException)
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
catch (AnimalRaceFullException)
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (NotEnoughFundsException)
|
||||
{
|
||||
await SendErrorAsync(GetText(strs.not_enough(CurrencySign)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,188 +6,185 @@ using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Blackjack;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
public class BlackJackCommands : GamblingSubmodule<BlackJackService>
|
||||
{
|
||||
public class BlackJackCommands : GamblingSubmodule<BlackJackService>
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
private IUserMessage _msg;
|
||||
|
||||
public enum BjAction
|
||||
{
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
private IUserMessage _msg;
|
||||
Hit = int.MinValue,
|
||||
Stand,
|
||||
Double,
|
||||
}
|
||||
|
||||
public enum BjAction
|
||||
public BlackJackCommands(ICurrencyService cs, DbService db,
|
||||
GamblingConfigService gamblingConf) : base(gamblingConf)
|
||||
{
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task BlackJack(ShmartNumber amount)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
var newBj = new Blackjack(_cs, _db);
|
||||
Blackjack bj;
|
||||
if (newBj == (bj = _service.Games.GetOrAdd(ctx.Channel.Id, newBj)))
|
||||
{
|
||||
Hit = int.MinValue,
|
||||
Stand,
|
||||
Double,
|
||||
}
|
||||
|
||||
public BlackJackCommands(ICurrencyService cs, DbService db,
|
||||
GamblingConfigService gamblingConf) : base(gamblingConf)
|
||||
{
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task BlackJack(ShmartNumber amount)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
var newBj = new Blackjack(_cs, _db);
|
||||
Blackjack bj;
|
||||
if (newBj == (bj = _service.Games.GetOrAdd(ctx.Channel.Id, newBj)))
|
||||
if (!await bj.Join(ctx.User, amount).ConfigureAwait(false))
|
||||
{
|
||||
if (!await bj.Join(ctx.User, amount).ConfigureAwait(false))
|
||||
{
|
||||
_service.Games.TryRemove(ctx.Channel.Id, out _);
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
bj.StateUpdated += Bj_StateUpdated;
|
||||
bj.GameEnded += Bj_GameEnded;
|
||||
bj.Start();
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.bj_created).ConfigureAwait(false);
|
||||
_service.Games.TryRemove(ctx.Channel.Id, out _);
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
bj.StateUpdated += Bj_StateUpdated;
|
||||
bj.GameEnded += Bj_GameEnded;
|
||||
bj.Start();
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.bj_created).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await bj.Join(ctx.User, amount).ConfigureAwait(false))
|
||||
await ReplyConfirmLocalizedAsync(strs.bj_joined).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
if (await bj.Join(ctx.User, amount).ConfigureAwait(false))
|
||||
await ReplyConfirmLocalizedAsync(strs.bj_joined).ConfigureAwait(false);
|
||||
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.ToString() + " state already.");
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.Message.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task Bj_GameEnded(Blackjack arg)
|
||||
{
|
||||
_service.Games.TryRemove(ctx.Channel.Id, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Bj_StateUpdated(Blackjack bj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_msg != null)
|
||||
{
|
||||
var _ = _msg.DeleteAsync();
|
||||
}
|
||||
|
||||
await ctx.Message.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task Bj_GameEnded(Blackjack arg)
|
||||
{
|
||||
_service.Games.TryRemove(ctx.Channel.Id, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Bj_StateUpdated(Blackjack bj)
|
||||
{
|
||||
try
|
||||
var c = bj.Dealer.Cards.Select(x => x.GetEmojiString());
|
||||
var dealerIcon = "❔ ";
|
||||
if (bj.State == Blackjack.GameState.Ended)
|
||||
{
|
||||
if (_msg != null)
|
||||
{
|
||||
var _ = _msg.DeleteAsync();
|
||||
}
|
||||
if (bj.Dealer.GetHandValue() == 21)
|
||||
dealerIcon = "💰 ";
|
||||
else if (bj.Dealer.GetHandValue() > 21)
|
||||
dealerIcon = "💥 ";
|
||||
else
|
||||
dealerIcon = "🏁 ";
|
||||
}
|
||||
|
||||
var c = bj.Dealer.Cards.Select(x => x.GetEmojiString());
|
||||
var dealerIcon = "❔ ";
|
||||
var cStr = string.Concat(c.Select(x => x.Substring(0, x.Length - 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);
|
||||
|
||||
if (bj.CurrentUser != null)
|
||||
{
|
||||
embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser.ToString()}");
|
||||
}
|
||||
|
||||
foreach (var p in bj.Players)
|
||||
{
|
||||
c = p.Cards.Select(x => x.GetEmojiString());
|
||||
cStr = "-\t" + string.Concat(c.Select(x => x.Substring(0, x.Length - 1) + " "));
|
||||
cStr += "\n-\t" + string.Concat(c.Select(x => x.Last() + " "));
|
||||
var full = $"{p.DiscordUser.ToString().TrimTo(20)} | Bet: {p.Bet} | Value: {p.GetHandValue()}";
|
||||
if (bj.State == Blackjack.GameState.Ended)
|
||||
{
|
||||
if (bj.Dealer.GetHandValue() == 21)
|
||||
dealerIcon = "💰 ";
|
||||
else if (bj.Dealer.GetHandValue() > 21)
|
||||
dealerIcon = "💥 ";
|
||||
else
|
||||
dealerIcon = "🏁 ";
|
||||
}
|
||||
|
||||
var cStr = string.Concat(c.Select(x => x.Substring(0, x.Length - 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);
|
||||
|
||||
if (bj.CurrentUser != null)
|
||||
{
|
||||
embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser.ToString()}");
|
||||
}
|
||||
|
||||
foreach (var p in bj.Players)
|
||||
{
|
||||
c = p.Cards.Select(x => x.GetEmojiString());
|
||||
cStr = "-\t" + string.Concat(c.Select(x => x.Substring(0, x.Length - 1) + " "));
|
||||
cStr += "\n-\t" + string.Concat(c.Select(x => x.Last() + " "));
|
||||
var full = $"{p.DiscordUser.ToString().TrimTo(20)} | Bet: {p.Bet} | Value: {p.GetHandValue()}";
|
||||
if (bj.State == Blackjack.GameState.Ended)
|
||||
if (p.State == User.UserState.Lost)
|
||||
{
|
||||
if (p.State == User.UserState.Lost)
|
||||
{
|
||||
full = "❌ " + full;
|
||||
}
|
||||
else
|
||||
{
|
||||
full = "✅ " + full;
|
||||
}
|
||||
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).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
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).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string UserToString(User x)
|
||||
catch
|
||||
{
|
||||
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() + "〗"))}";
|
||||
|
||||
|
||||
return $"{playerName} | Bet: {x.Bet}\n";
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Hit() => InternalBlackJack(BjAction.Hit);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Stand() => InternalBlackJack(BjAction.Stand);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Double() => InternalBlackJack(BjAction.Double);
|
||||
|
||||
public async Task InternalBlackJack(BjAction a)
|
||||
{
|
||||
if (!_service.Games.TryGetValue(ctx.Channel.Id, out var bj))
|
||||
return;
|
||||
|
||||
if (a == BjAction.Hit)
|
||||
await bj.Hit(ctx.User).ConfigureAwait(false);
|
||||
else if (a == BjAction.Stand)
|
||||
await bj.Stand(ctx.User).ConfigureAwait(false);
|
||||
else if (a == BjAction.Double)
|
||||
{
|
||||
if (!await bj.Double(ctx.User).ConfigureAwait(false))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.Message.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private string UserToString(User x)
|
||||
{
|
||||
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() + "〗"))}";
|
||||
|
||||
|
||||
return $"{playerName} | Bet: {x.Bet}\n";
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Hit() => InternalBlackJack(BjAction.Hit);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Stand() => InternalBlackJack(BjAction.Stand);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Double() => InternalBlackJack(BjAction.Double);
|
||||
|
||||
public async Task InternalBlackJack(BjAction a)
|
||||
{
|
||||
if (!_service.Games.TryGetValue(ctx.Channel.Id, out var bj))
|
||||
return;
|
||||
|
||||
if (a == BjAction.Hit)
|
||||
await bj.Hit(ctx.User).ConfigureAwait(false);
|
||||
else if (a == BjAction.Stand)
|
||||
await bj.Stand(ctx.User).ConfigureAwait(false);
|
||||
else if (a == BjAction.Double)
|
||||
{
|
||||
if (!await bj.Double(ctx.User).ConfigureAwait(false))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.Message.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
using NadekoBot.Modules.Games.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
|
||||
|
||||
public sealed class AnimalRace : IDisposable
|
||||
{
|
||||
public sealed class AnimalRace : IDisposable
|
||||
public enum Phase
|
||||
{
|
||||
public enum Phase
|
||||
WaitingForPlayers,
|
||||
Running,
|
||||
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 List<AnimalRacingUser> FinishedUsers { get; } = new List<AnimalRacingUser>();
|
||||
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
private readonly HashSet<AnimalRacingUser> _users = new HashSet<AnimalRacingUser>();
|
||||
private readonly 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 Queue<RaceAnimal>(availableAnimals);
|
||||
this.MaxUsers = _animalsQueue.Count;
|
||||
|
||||
if (this._animalsQueue.Count == 0)
|
||||
CurrentPhase = Phase.Ended;
|
||||
}
|
||||
|
||||
public void Initialize() //lame name
|
||||
{
|
||||
var _t = Task.Run(async () =>
|
||||
{
|
||||
WaitingForPlayers,
|
||||
Running,
|
||||
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 List<AnimalRacingUser> FinishedUsers { get; } = new List<AnimalRacingUser>();
|
||||
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
private readonly HashSet<AnimalRacingUser> _users = new HashSet<AnimalRacingUser>();
|
||||
private readonly 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 Queue<RaceAnimal>(availableAnimals);
|
||||
this.MaxUsers = _animalsQueue.Count;
|
||||
|
||||
if (this._animalsQueue.Count == 0)
|
||||
CurrentPhase = Phase.Ended;
|
||||
}
|
||||
|
||||
public void Initialize() //lame name
|
||||
{
|
||||
var _t = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(_options.StartTime * 1000).ConfigureAwait(false);
|
||||
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (CurrentPhase != Phase.WaitingForPlayers)
|
||||
return;
|
||||
|
||||
await Start().ConfigureAwait(false);
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
|
||||
{
|
||||
if (bet < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bet));
|
||||
|
||||
var user = new AnimalRacingUser(userName, userId, bet);
|
||||
await Task.Delay(_options.StartTime * 1000).ConfigureAwait(false);
|
||||
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_users.Count == MaxUsers)
|
||||
throw new AnimalRaceFullException();
|
||||
|
||||
if (CurrentPhase != Phase.WaitingForPlayers)
|
||||
throw new AlreadyStartedException();
|
||||
return;
|
||||
|
||||
if (!await _currency.RemoveAsync(userId, "BetRace", bet).ConfigureAwait(false))
|
||||
throw new NotEnoughFundsException();
|
||||
|
||||
if (_users.Contains(user))
|
||||
throw new AlreadyJoinedException();
|
||||
|
||||
var animal = _animalsQueue.Dequeue();
|
||||
user.Animal = animal;
|
||||
_users.Add(user);
|
||||
|
||||
if (_animalsQueue.Count == 0) //start if no more spots left
|
||||
await Start().ConfigureAwait(false);
|
||||
|
||||
return user;
|
||||
await Start().ConfigureAwait(false);
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
|
||||
{
|
||||
if (bet < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bet));
|
||||
|
||||
var user = new AnimalRacingUser(userName, userId, bet);
|
||||
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_users.Count == MaxUsers)
|
||||
throw new AnimalRaceFullException();
|
||||
|
||||
if (CurrentPhase != Phase.WaitingForPlayers)
|
||||
throw new AlreadyStartedException();
|
||||
|
||||
if (!await _currency.RemoveAsync(userId, "BetRace", bet).ConfigureAwait(false))
|
||||
throw new NotEnoughFundsException();
|
||||
|
||||
if (_users.Contains(user))
|
||||
throw new AlreadyJoinedException();
|
||||
|
||||
var animal = _animalsQueue.Dequeue();
|
||||
user.Animal = animal;
|
||||
_users.Add(user);
|
||||
|
||||
if (_animalsQueue.Count == 0) //start if no more spots left
|
||||
await Start().ConfigureAwait(false);
|
||||
|
||||
return user;
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
}
|
||||
|
||||
private async Task Start()
|
||||
{
|
||||
CurrentPhase = Phase.Running;
|
||||
if (_users.Count <= 1)
|
||||
{
|
||||
foreach (var user in _users)
|
||||
{
|
||||
if (user.Bet > 0)
|
||||
await _currency.AddAsync(user.UserId, "Race refund", user.Bet).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var _sf = OnStartingFailed?.Invoke(this);
|
||||
CurrentPhase = Phase.Ended;
|
||||
return;
|
||||
}
|
||||
|
||||
private async Task Start()
|
||||
var _ = OnStarted?.Invoke(this);
|
||||
var _t = Task.Run(async () =>
|
||||
{
|
||||
CurrentPhase = Phase.Running;
|
||||
if (_users.Count <= 1)
|
||||
var rng = new NadekoRandom();
|
||||
while (!_users.All(x => x.Progress >= 60))
|
||||
{
|
||||
foreach (var user in _users)
|
||||
{
|
||||
if (user.Bet > 0)
|
||||
await _currency.AddAsync(user.UserId, "Race refund", user.Bet).ConfigureAwait(false);
|
||||
user.Progress += rng.Next(1, 11);
|
||||
if (user.Progress >= 60)
|
||||
user.Progress = 60;
|
||||
}
|
||||
|
||||
var _sf = OnStartingFailed?.Invoke(this);
|
||||
CurrentPhase = Phase.Ended;
|
||||
return;
|
||||
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x))
|
||||
.Shuffle();
|
||||
|
||||
FinishedUsers.AddRange(finished);
|
||||
|
||||
var _ignore = OnStateUpdate?.Invoke(this);
|
||||
await Task.Delay(2500).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var _ = OnStarted?.Invoke(this);
|
||||
var _t = Task.Run(async () =>
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
while (!_users.All(x => x.Progress >= 60))
|
||||
{
|
||||
foreach (var user in _users)
|
||||
{
|
||||
user.Progress += rng.Next(1, 11);
|
||||
if (user.Progress >= 60)
|
||||
user.Progress = 60;
|
||||
}
|
||||
if (FinishedUsers[0].Bet > 0)
|
||||
await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x))
|
||||
.Shuffle();
|
||||
var _ended = OnEnded?.Invoke(this);
|
||||
});
|
||||
}
|
||||
|
||||
FinishedUsers.AddRange(finished);
|
||||
|
||||
var _ignore = OnStateUpdate?.Invoke(this);
|
||||
await Task.Delay(2500).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (FinishedUsers[0].Bet > 0)
|
||||
await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var _ended = OnEnded?.Invoke(this);
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CurrentPhase = Phase.Ended;
|
||||
OnStarted = null;
|
||||
OnEnded = null;
|
||||
OnStartingFailed = null;
|
||||
OnStateUpdate = null;
|
||||
_locker.Dispose();
|
||||
_users.Clear();
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
CurrentPhase = Phase.Ended;
|
||||
OnStarted = null;
|
||||
OnEnded = null;
|
||||
OnStartingFailed = null;
|
||||
OnStateUpdate = null;
|
||||
_locker.Dispose();
|
||||
_users.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,31 @@
|
||||
using NadekoBot.Modules.Games.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
|
||||
|
||||
public class AnimalRacingUser
|
||||
{
|
||||
public class AnimalRacingUser
|
||||
public long Bet { get; }
|
||||
public string Username { get; }
|
||||
public ulong UserId { get; }
|
||||
public RaceAnimal Animal { get; set; }
|
||||
public int Progress { get; set; }
|
||||
|
||||
public AnimalRacingUser(string username, ulong userId, long bet)
|
||||
{
|
||||
public long Bet { get; }
|
||||
public string Username { get; }
|
||||
public ulong UserId { get; }
|
||||
public RaceAnimal Animal { get; set; }
|
||||
public int Progress { get; set; }
|
||||
|
||||
public AnimalRacingUser(string username, ulong userId, long bet)
|
||||
{
|
||||
this.Bet = bet;
|
||||
this.Username = username;
|
||||
this.UserId = userId;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is AnimalRacingUser x
|
||||
? x.UserId == this.UserId
|
||||
: false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.UserId.GetHashCode();
|
||||
}
|
||||
this.Bet = bet;
|
||||
this.Username = username;
|
||||
this.UserId = userId;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is AnimalRacingUser x
|
||||
? x.UserId == this.UserId
|
||||
: false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.UserId.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
using System;
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
public class AlreadyJoinedException : Exception
|
||||
{
|
||||
public class AlreadyJoinedException : Exception
|
||||
public AlreadyJoinedException()
|
||||
{
|
||||
public AlreadyJoinedException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AlreadyJoinedException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AlreadyJoinedException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AlreadyJoinedException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AlreadyJoinedException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
using System;
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
public class AlreadyStartedException : Exception
|
||||
{
|
||||
public class AlreadyStartedException : Exception
|
||||
public AlreadyStartedException()
|
||||
{
|
||||
public AlreadyStartedException()
|
||||
{
|
||||
}
|
||||
|
||||
public AlreadyStartedException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AlreadyStartedException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AlreadyStartedException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AlreadyStartedException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
using System;
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
public class AnimalRaceFullException : Exception
|
||||
{
|
||||
public class AnimalRaceFullException : Exception
|
||||
public AnimalRaceFullException()
|
||||
{
|
||||
public AnimalRaceFullException()
|
||||
{
|
||||
}
|
||||
|
||||
public AnimalRaceFullException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AnimalRaceFullException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AnimalRaceFullException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AnimalRaceFullException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
using System;
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
|
||||
public class NotEnoughFundsException : Exception
|
||||
{
|
||||
public class NotEnoughFundsException : Exception
|
||||
public NotEnoughFundsException()
|
||||
{
|
||||
public NotEnoughFundsException()
|
||||
{
|
||||
}
|
||||
|
||||
public NotEnoughFundsException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public NotEnoughFundsException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NotEnoughFundsException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public NotEnoughFundsException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
using CommandLine;
|
||||
using NadekoBot.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing
|
||||
{
|
||||
public class RaceOptions : INadekoCommandOptions
|
||||
{
|
||||
[Option('s', "start-time", Default = 20, Required = false)]
|
||||
public int StartTime { get; set; } = 20;
|
||||
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (this.StartTime < 10 || this.StartTime > 120)
|
||||
this.StartTime = 20;
|
||||
}
|
||||
public class RaceOptions : INadekoCommandOptions
|
||||
{
|
||||
[Option('s', "start-time", Default = 20, Required = false)]
|
||||
public int StartTime { get; set; } = 20;
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (this.StartTime < 10 || this.StartTime > 120)
|
||||
this.StartTime = 20;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,43 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
public class Betroll
|
||||
{
|
||||
public class Betroll
|
||||
public class Result
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
public int Roll { get; set; }
|
||||
public float Multiplier { get; set; }
|
||||
public int Threshold { get; set; }
|
||||
}
|
||||
public int Roll { get; set; }
|
||||
public float Multiplier { get; set; }
|
||||
public int Threshold { get; set; }
|
||||
}
|
||||
|
||||
|
||||
private readonly IOrderedEnumerable<BetRollPair> _thresholdPairs;
|
||||
private readonly Random _rng;
|
||||
private readonly IOrderedEnumerable<BetRollPair> _thresholdPairs;
|
||||
private readonly Random _rng;
|
||||
|
||||
public Betroll(BetRollConfig settings)
|
||||
public Betroll(BetRollConfig settings)
|
||||
{
|
||||
_thresholdPairs = settings.Pairs.OrderByDescending(x => x.WhenAbove);
|
||||
_rng = new Random();
|
||||
}
|
||||
|
||||
public Result Roll()
|
||||
{
|
||||
var roll = _rng.Next(0, 101);
|
||||
|
||||
var pair = _thresholdPairs.FirstOrDefault(x => x.WhenAbove < roll);
|
||||
if (pair is null)
|
||||
{
|
||||
_thresholdPairs = settings.Pairs.OrderByDescending(x => x.WhenAbove);
|
||||
_rng = new Random();
|
||||
}
|
||||
|
||||
public Result Roll()
|
||||
{
|
||||
var roll = _rng.Next(0, 101);
|
||||
|
||||
var pair = _thresholdPairs.FirstOrDefault(x => x.WhenAbove < roll);
|
||||
if (pair is null)
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
Multiplier = 0,
|
||||
Roll = roll,
|
||||
};
|
||||
}
|
||||
|
||||
return new Result
|
||||
{
|
||||
Multiplier = pair.MultiplyBy,
|
||||
Multiplier = 0,
|
||||
Roll = roll,
|
||||
Threshold = pair.WhenAbove,
|
||||
};
|
||||
}
|
||||
|
||||
return new Result
|
||||
{
|
||||
Multiplier = pair.MultiplyBy,
|
||||
Roll = roll,
|
||||
Threshold = pair.WhenAbove,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,353 +1,347 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.Blackjack
|
||||
namespace NadekoBot.Modules.Gambling.Common.Blackjack;
|
||||
|
||||
public class Blackjack
|
||||
{
|
||||
public class Blackjack
|
||||
public enum GameState
|
||||
{
|
||||
public enum GameState
|
||||
{
|
||||
Starting,
|
||||
Playing,
|
||||
Ended
|
||||
}
|
||||
Starting,
|
||||
Playing,
|
||||
Ended
|
||||
}
|
||||
|
||||
private Deck Deck { get; set; } = new QuadDeck();
|
||||
public Dealer Dealer { get; set; }
|
||||
private Deck Deck { get; set; } = new QuadDeck();
|
||||
public Dealer Dealer { get; set; }
|
||||
|
||||
|
||||
public List<User> Players { get; set; } = new List<User>();
|
||||
public GameState State { get; set; } = GameState.Starting;
|
||||
public User CurrentUser { get; private set; }
|
||||
public List<User> Players { get; set; } = new List<User>();
|
||||
public GameState State { get; set; } = GameState.Starting;
|
||||
public User CurrentUser { get; private set; }
|
||||
|
||||
private TaskCompletionSource<bool> _currentUserMove;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
private TaskCompletionSource<bool> _currentUserMove;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
|
||||
public event Func<Blackjack, Task> StateUpdated;
|
||||
public event Func<Blackjack, Task> GameEnded;
|
||||
public event Func<Blackjack, Task> StateUpdated;
|
||||
public event Func<Blackjack, Task> GameEnded;
|
||||
|
||||
private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1);
|
||||
|
||||
public Blackjack(ICurrencyService cs, DbService db)
|
||||
{
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
Dealer = new Dealer();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
var _ = GameLoop();
|
||||
}
|
||||
|
||||
public async Task GameLoop()
|
||||
public Blackjack(ICurrencyService cs, DbService db)
|
||||
{
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
Dealer = new Dealer();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
var _ = GameLoop();
|
||||
}
|
||||
|
||||
public async Task GameLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
//wait for players to join
|
||||
await Task.Delay(20000).ConfigureAwait(false);
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
//wait for players to join
|
||||
await Task.Delay(20000).ConfigureAwait(false);
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
State = GameState.Playing;
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
await PrintState().ConfigureAwait(false);
|
||||
//if no users joined the game, end it
|
||||
if (!Players.Any())
|
||||
{
|
||||
State = GameState.Ended;
|
||||
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)
|
||||
{
|
||||
usr.Cards.Add(Deck.Draw());
|
||||
usr.Cards.Add(Deck.Draw());
|
||||
|
||||
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).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
await PrintState().ConfigureAwait(false);
|
||||
State = GameState.Ended;
|
||||
await Task.Delay(2500).ConfigureAwait(false);
|
||||
Log.Information("Dealer moves");
|
||||
await DealerMoves().ConfigureAwait(false);
|
||||
await PrintState().ConfigureAwait(false);
|
||||
var _ = GameEnded?.Invoke(this);
|
||||
State = GameState.Playing;
|
||||
}
|
||||
catch (Exception ex)
|
||||
finally
|
||||
{
|
||||
Log.Error(ex, "REPORT THE MESSAGE BELOW IN #NadekoLog SERVER PLEASE");
|
||||
State = GameState.Ended;
|
||||
var _ = GameEnded?.Invoke(this);
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PromptUserMove(User usr)
|
||||
{
|
||||
var pause = Task.Delay(20000); //10 seconds to decide
|
||||
CurrentUser = usr;
|
||||
_currentUserMove = new TaskCompletionSource<bool>();
|
||||
await PrintState().ConfigureAwait(false);
|
||||
// either wait for the user to make an action and
|
||||
// if he doesn't - stand
|
||||
var finished = await Task.WhenAny(pause, _currentUserMove.Task).ConfigureAwait(false);
|
||||
if (finished == pause)
|
||||
//if no users joined the game, end it
|
||||
if (!Players.Any())
|
||||
{
|
||||
await Stand(usr).ConfigureAwait(false);
|
||||
State = GameState.Ended;
|
||||
var end = GameEnded?.Invoke(this);
|
||||
return;
|
||||
}
|
||||
CurrentUser = null;
|
||||
_currentUserMove = null;
|
||||
}
|
||||
|
||||
public async Task<bool> Join(IUser user, long bet)
|
||||
{
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
//give 1 card to the dealer and 2 to each player
|
||||
Dealer.Cards.Add(Deck.Draw());
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (State != GameState.Starting)
|
||||
return false;
|
||||
usr.Cards.Add(Deck.Draw());
|
||||
usr.Cards.Add(Deck.Draw());
|
||||
|
||||
if (Players.Count >= 5)
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true).ConfigureAwait(false))
|
||||
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)
|
||||
{
|
||||
return false;
|
||||
Log.Information($"Waiting for {usr.DiscordUser}'s move");
|
||||
await PromptUserMove(usr).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Players.Add(new User(user, bet));
|
||||
var _ = PrintState();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
await PrintState().ConfigureAwait(false);
|
||||
State = GameState.Ended;
|
||||
await Task.Delay(2500).ConfigureAwait(false);
|
||||
Log.Information("Dealer moves");
|
||||
await DealerMoves().ConfigureAwait(false);
|
||||
await PrintState().ConfigureAwait(false);
|
||||
var _ = GameEnded?.Invoke(this);
|
||||
}
|
||||
|
||||
public async Task<bool> Stand(IUser u)
|
||||
catch (Exception ex)
|
||||
{
|
||||
var cu = CurrentUser;
|
||||
Log.Error(ex, "REPORT THE MESSAGE BELOW IN #NadekoLog SERVER PLEASE");
|
||||
State = GameState.Ended;
|
||||
var _ = GameEnded?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (cu != null && cu.DiscordUser == u)
|
||||
return await Stand(cu).ConfigureAwait(false);
|
||||
private async Task PromptUserMove(User usr)
|
||||
{
|
||||
var pause = Task.Delay(20000); //10 seconds to decide
|
||||
CurrentUser = usr;
|
||||
_currentUserMove = new TaskCompletionSource<bool>();
|
||||
await PrintState().ConfigureAwait(false);
|
||||
// either wait for the user to make an action and
|
||||
// if he doesn't - stand
|
||||
var finished = await Task.WhenAny(pause, _currentUserMove.Task).ConfigureAwait(false);
|
||||
if (finished == pause)
|
||||
{
|
||||
await Stand(usr).ConfigureAwait(false);
|
||||
}
|
||||
CurrentUser = null;
|
||||
_currentUserMove = null;
|
||||
}
|
||||
|
||||
public async Task<bool> Join(IUser user, long bet)
|
||||
{
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (State != GameState.Starting)
|
||||
return false;
|
||||
|
||||
if (Players.Count >= 5)
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true).ConfigureAwait(false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Players.Add(new User(user, bet));
|
||||
var _ = PrintState();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Stand(IUser u)
|
||||
{
|
||||
var cu = CurrentUser;
|
||||
|
||||
if (cu != null && cu.DiscordUser == u)
|
||||
return await Stand(cu).ConfigureAwait(false);
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> Stand(User u)
|
||||
{
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (State != GameState.Playing)
|
||||
return false;
|
||||
|
||||
if (CurrentUser != u)
|
||||
return false;
|
||||
|
||||
u.State = User.UserState.Stand;
|
||||
_currentUserMove.TrySetResult(true);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DealerMoves()
|
||||
{
|
||||
var hw = Dealer.GetHandValue();
|
||||
while (hw < 17
|
||||
|| (hw == 17 && Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10))// hit on soft 17
|
||||
{
|
||||
/* Dealer has
|
||||
A 6
|
||||
That's 17, soft
|
||||
hw == 17 => true
|
||||
number of aces = 1
|
||||
1 > 17-17 /10 => true
|
||||
|
||||
AA 5
|
||||
That's 17, again soft, since one ace is worth 11, even though another one is 1
|
||||
hw == 17 => true
|
||||
number of aces = 2
|
||||
2 > 27 - 17 / 10 => true
|
||||
|
||||
AA Q 5
|
||||
That's 17, but not soft, since both aces are worth 1
|
||||
hw == 17 => true
|
||||
number of aces = 2
|
||||
2 > 37 - 17 / 10 => false
|
||||
* */
|
||||
Dealer.Cards.Add(Deck.Draw());
|
||||
hw = Dealer.GetHandValue();
|
||||
}
|
||||
|
||||
public async Task<bool> Stand(User u)
|
||||
if (hw > 21)
|
||||
{
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (State != GameState.Playing)
|
||||
return false;
|
||||
|
||||
if (CurrentUser != u)
|
||||
return false;
|
||||
|
||||
u.State = User.UserState.Stand;
|
||||
_currentUserMove.TrySetResult(true);
|
||||
return true;
|
||||
if (usr.State == User.UserState.Stand || usr.State == User.UserState.Blackjack)
|
||||
usr.State = User.UserState.Won;
|
||||
else
|
||||
usr.State = User.UserState.Lost;
|
||||
}
|
||||
finally
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
locker.Release();
|
||||
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;
|
||||
else
|
||||
usr.State = User.UserState.Lost;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DealerMoves()
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
var hw = Dealer.GetHandValue();
|
||||
while (hw < 17
|
||||
|| (hw == 17 && Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10))// hit on soft 17
|
||||
if (usr.State == User.UserState.Won || usr.State == User.UserState.Blackjack)
|
||||
{
|
||||
/* Dealer has
|
||||
A 6
|
||||
That's 17, soft
|
||||
hw == 17 => true
|
||||
number of aces = 1
|
||||
1 > 17-17 /10 => true
|
||||
|
||||
AA 5
|
||||
That's 17, again soft, since one ace is worth 11, even though another one is 1
|
||||
hw == 17 => true
|
||||
number of aces = 2
|
||||
2 > 27 - 17 / 10 => true
|
||||
|
||||
AA Q 5
|
||||
That's 17, but not soft, since both aces are worth 1
|
||||
hw == 17 => true
|
||||
number of aces = 2
|
||||
2 > 37 - 17 / 10 => false
|
||||
* */
|
||||
Dealer.Cards.Add(Deck.Draw());
|
||||
hw = Dealer.GetHandValue();
|
||||
await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, gamble: true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hw > 21)
|
||||
public async Task<bool> Double(IUser u)
|
||||
{
|
||||
var cu = CurrentUser;
|
||||
|
||||
if (cu != null && cu.DiscordUser == u)
|
||||
return await Double(cu).ConfigureAwait(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> Double(User u)
|
||||
{
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (State != GameState.Playing)
|
||||
return false;
|
||||
|
||||
if (CurrentUser != u)
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(u.DiscordUser.Id, "Blackjack-double", u.Bet).ConfigureAwait(false))
|
||||
return false;
|
||||
|
||||
u.Bet *= 2;
|
||||
|
||||
u.Cards.Add(Deck.Draw());
|
||||
|
||||
if (u.GetHandValue() == 21)
|
||||
{
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (usr.State == User.UserState.Stand || usr.State == User.UserState.Blackjack)
|
||||
usr.State = User.UserState.Won;
|
||||
else
|
||||
usr.State = User.UserState.Lost;
|
||||
}
|
||||
//blackjack
|
||||
u.State = User.UserState.Blackjack;
|
||||
}
|
||||
else if (u.GetHandValue() > 21)
|
||||
{
|
||||
// user busted
|
||||
u.State = User.UserState.Bust;
|
||||
}
|
||||
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;
|
||||
else
|
||||
usr.State = User.UserState.Lost;
|
||||
}
|
||||
//with double you just get one card, and then you're done
|
||||
u.State = User.UserState.Stand;
|
||||
}
|
||||
_currentUserMove.TrySetResult(true);
|
||||
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (usr.State == User.UserState.Won || usr.State == User.UserState.Blackjack)
|
||||
{
|
||||
await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, gamble: true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> Double(IUser u)
|
||||
finally
|
||||
{
|
||||
var cu = CurrentUser;
|
||||
|
||||
if (cu != null && cu.DiscordUser == u)
|
||||
return await Double(cu).ConfigureAwait(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> Double(User u)
|
||||
{
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (State != GameState.Playing)
|
||||
return false;
|
||||
|
||||
if (CurrentUser != u)
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(u.DiscordUser.Id, "Blackjack-double", u.Bet).ConfigureAwait(false))
|
||||
return false;
|
||||
|
||||
u.Bet *= 2;
|
||||
|
||||
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;
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Hit(IUser u)
|
||||
{
|
||||
var cu = CurrentUser;
|
||||
|
||||
if (cu != null && cu.DiscordUser == u)
|
||||
return await Hit(cu).ConfigureAwait(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> Hit(User u)
|
||||
{
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (State != GameState.Playing)
|
||||
return false;
|
||||
|
||||
if (CurrentUser != u)
|
||||
return false;
|
||||
|
||||
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;
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public Task PrintState()
|
||||
{
|
||||
if (StateUpdated is null)
|
||||
return Task.CompletedTask;
|
||||
return StateUpdated.Invoke(this);
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Hit(IUser u)
|
||||
{
|
||||
var cu = CurrentUser;
|
||||
|
||||
if (cu != null && cu.DiscordUser == u)
|
||||
return await Hit(cu).ConfigureAwait(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> Hit(User u)
|
||||
{
|
||||
await locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (State != GameState.Playing)
|
||||
return false;
|
||||
|
||||
if (CurrentUser != u)
|
||||
return false;
|
||||
|
||||
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;
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public Task PrintState()
|
||||
{
|
||||
if (StateUpdated is null)
|
||||
return Task.CompletedTask;
|
||||
return StateUpdated.Invoke(this);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,60 @@
|
||||
using Discord;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.Blackjack
|
||||
namespace NadekoBot.Modules.Gambling.Common.Blackjack;
|
||||
|
||||
public abstract class Player
|
||||
{
|
||||
public abstract class Player
|
||||
public List<Deck.Card> Cards { get; } = new List<Deck.Card>();
|
||||
|
||||
public int GetHandValue()
|
||||
{
|
||||
public List<Deck.Card> Cards { get; } = new List<Deck.Card>();
|
||||
var val = GetRawHandValue();
|
||||
|
||||
public int GetHandValue()
|
||||
// while the hand value is greater than 21, for each ace you have in the deck
|
||||
// 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)
|
||||
{
|
||||
var val = GetRawHandValue();
|
||||
|
||||
// while the hand value is greater than 21, for each ace you have in the deck
|
||||
// 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;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public int GetRawHandValue()
|
||||
{
|
||||
return Cards.Sum(x => x.Number == 1 ? 11 : x.Number >= 10 ? 10 : x.Number);
|
||||
val -= 10;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public class Dealer : Player
|
||||
public int GetRawHandValue()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class User : Player
|
||||
{
|
||||
public enum UserState
|
||||
{
|
||||
Waiting,
|
||||
Stand,
|
||||
Bust,
|
||||
Blackjack,
|
||||
Won,
|
||||
Lost
|
||||
}
|
||||
|
||||
public User(IUser user, long bet)
|
||||
{
|
||||
if (bet <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bet));
|
||||
|
||||
this.Bet = bet;
|
||||
this.DiscordUser = user;
|
||||
}
|
||||
|
||||
public UserState State { get; set; } = UserState.Waiting;
|
||||
public long Bet { get; set; }
|
||||
public IUser DiscordUser { get; }
|
||||
public bool Done => State != UserState.Waiting;
|
||||
return Cards.Sum(x => x.Number == 1 ? 11 : x.Number >= 10 ? 10 : x.Number);
|
||||
}
|
||||
}
|
||||
|
||||
public class Dealer : Player
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class User : Player
|
||||
{
|
||||
public enum UserState
|
||||
{
|
||||
Waiting,
|
||||
Stand,
|
||||
Bust,
|
||||
Blackjack,
|
||||
Won,
|
||||
Lost
|
||||
}
|
||||
|
||||
public User(IUser user, long bet)
|
||||
{
|
||||
if (bet <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bet));
|
||||
|
||||
this.Bet = bet;
|
||||
this.DiscordUser = user;
|
||||
}
|
||||
|
||||
public UserState State { get; set; } = UserState.Waiting;
|
||||
public long Bet { get; set; }
|
||||
public IUser DiscordUser { get; }
|
||||
public bool Done => State != UserState.Waiting;
|
||||
}
|
||||
@@ -1,81 +1,78 @@
|
||||
using Discord;
|
||||
using NadekoBot.Common;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class CurrencyRaffleGame
|
||||
{
|
||||
public class CurrencyRaffleGame
|
||||
public enum Type {
|
||||
Mixed,
|
||||
Normal
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public enum Type {
|
||||
Mixed,
|
||||
Normal
|
||||
public IUser DiscordUser { get; set; }
|
||||
public long Amount { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return DiscordUser.GetHashCode();
|
||||
}
|
||||
|
||||
public class User
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
public IUser DiscordUser { get; set; }
|
||||
public long Amount { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return DiscordUser.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is User u
|
||||
? u.DiscordUser == DiscordUser
|
||||
: false;
|
||||
}
|
||||
return obj is User u
|
||||
? u.DiscordUser == DiscordUser
|
||||
: false;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet<User> _users = new HashSet<User>();
|
||||
public IEnumerable<User> Users => _users;
|
||||
public Type GameType { get; }
|
||||
private readonly HashSet<User> _users = new HashSet<User>();
|
||||
public IEnumerable<User> Users => _users;
|
||||
public Type GameType { get; }
|
||||
|
||||
public CurrencyRaffleGame(Type type)
|
||||
{
|
||||
GameType = type;
|
||||
}
|
||||
public CurrencyRaffleGame(Type type)
|
||||
{
|
||||
GameType = type;
|
||||
}
|
||||
|
||||
public bool AddUser(IUser usr, long amount)
|
||||
{
|
||||
// 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)
|
||||
return false;
|
||||
public bool AddUser(IUser usr, long amount)
|
||||
{
|
||||
// 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)
|
||||
return false;
|
||||
|
||||
if (!_users.Add(new User
|
||||
if (!_users.Add(new User
|
||||
{
|
||||
DiscordUser = usr,
|
||||
Amount = amount,
|
||||
}))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public User GetWinner()
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
if (GameType == Type.Mixed)
|
||||
{
|
||||
var num = rng.NextLong(0L, Users.Sum(x => x.Amount));
|
||||
var sum = 0L;
|
||||
foreach (var u in Users)
|
||||
{
|
||||
sum += u.Amount;
|
||||
if (sum > num)
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
var usrs = _users.ToArray();
|
||||
return usrs[rng.Next(0, usrs.Length)];
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public User GetWinner()
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
if (GameType == Type.Mixed)
|
||||
{
|
||||
var num = rng.NextLong(0L, Users.Sum(x => x.Amount));
|
||||
var sum = 0L;
|
||||
foreach (var u in Users)
|
||||
{
|
||||
sum += u.Amount;
|
||||
if (sum > num)
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
var usrs = _users.ToArray();
|
||||
return usrs[rng.Next(0, usrs.Length)];
|
||||
}
|
||||
}
|
||||
@@ -1,312 +1,308 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Extensions;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class QuadDeck : Deck
|
||||
{
|
||||
public class QuadDeck : Deck
|
||||
protected override void RefillPool()
|
||||
{
|
||||
protected override void RefillPool()
|
||||
CardPool = new List<Card>(52 * 4);
|
||||
for (var j = 1; j < 14; j++)
|
||||
{
|
||||
CardPool = new List<Card>(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 Card((CardSuit)i, j));
|
||||
CardPool.Add(new Card((CardSuit)i, j));
|
||||
CardPool.Add(new Card((CardSuit)i, j));
|
||||
CardPool.Add(new Card((CardSuit)i, j));
|
||||
}
|
||||
CardPool.Add(new Card((CardSuit)i, j));
|
||||
CardPool.Add(new Card((CardSuit)i, j));
|
||||
CardPool.Add(new Card((CardSuit)i, j));
|
||||
CardPool.Add(new Card((CardSuit)i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Deck
|
||||
public class Deck
|
||||
{
|
||||
private static readonly Dictionary<int, string> cardNames = new Dictionary<int, string>() {
|
||||
{ 1, "Ace" },
|
||||
{ 2, "Two" },
|
||||
{ 3, "Three" },
|
||||
{ 4, "Four" },
|
||||
{ 5, "Five" },
|
||||
{ 6, "Six" },
|
||||
{ 7, "Seven" },
|
||||
{ 8, "Eight" },
|
||||
{ 9, "Nine" },
|
||||
{ 10, "Ten" },
|
||||
{ 11, "Jack" },
|
||||
{ 12, "Queen" },
|
||||
{ 13, "King" }
|
||||
};
|
||||
private static Dictionary<string, Func<List<Card>, bool>> handValues;
|
||||
|
||||
|
||||
public enum CardSuit
|
||||
{
|
||||
private static readonly Dictionary<int, string> cardNames = new Dictionary<int, string>() {
|
||||
{ 1, "Ace" },
|
||||
{ 2, "Two" },
|
||||
{ 3, "Three" },
|
||||
{ 4, "Four" },
|
||||
{ 5, "Five" },
|
||||
{ 6, "Six" },
|
||||
{ 7, "Seven" },
|
||||
{ 8, "Eight" },
|
||||
{ 9, "Nine" },
|
||||
{ 10, "Ten" },
|
||||
{ 11, "Jack" },
|
||||
{ 12, "Queen" },
|
||||
{ 13, "King" }
|
||||
};
|
||||
private static Dictionary<string, Func<List<Card>, bool>> handValues;
|
||||
Spades = 1,
|
||||
Hearts = 2,
|
||||
Diamonds = 3,
|
||||
Clubs = 4
|
||||
}
|
||||
|
||||
public class Card : IComparable
|
||||
{
|
||||
public CardSuit Suit { get; }
|
||||
public int Number { get; }
|
||||
|
||||
public enum CardSuit
|
||||
public string FullName
|
||||
{
|
||||
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 = "";
|
||||
|
||||
if (Number <= 10 && Number > 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 Card)) return 0;
|
||||
var c = (Card)obj;
|
||||
return this.Number - c.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 Card(s, n);
|
||||
}
|
||||
|
||||
public string GetEmojiString()
|
||||
get
|
||||
{
|
||||
var str = "";
|
||||
|
||||
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 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 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();
|
||||
}
|
||||
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.
|
||||
/// </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 List<Card>(52);
|
||||
//foreach suit
|
||||
for (var j = 1; j < 14; j++)
|
||||
{
|
||||
// and number
|
||||
for (var i = 1; i < 5; i++)
|
||||
if (Number <= 10 && Number > 1)
|
||||
{
|
||||
//generate a card of that suit and number and add it to the pool
|
||||
|
||||
// the pool will go from ace of spades,hears,diamonds,clubs all the way to the king of spades. hearts, ...
|
||||
CardPool.Add(new Card((CardSuit)i, j));
|
||||
str += "_" + Number;
|
||||
}
|
||||
else
|
||||
{
|
||||
str += GetValueText().ToLowerInvariant();
|
||||
}
|
||||
return str + "_of_" + Suit.ToString().ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
private Random r = new NadekoRandom();
|
||||
/// <summary>
|
||||
/// Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the deck is in the default order.
|
||||
/// </summary>
|
||||
/// <returns>A card from the pool</returns>
|
||||
public Card Draw()
|
||||
|
||||
public Card(CardSuit s, int cardNum)
|
||||
{
|
||||
if (CardPool.Count == 0)
|
||||
Restart();
|
||||
//you can either do this if your deck is not shuffled
|
||||
|
||||
var num = r.Next(0, CardPool.Count);
|
||||
var c = CardPool[num];
|
||||
CardPool.RemoveAt(num);
|
||||
return c;
|
||||
|
||||
// if you want to shuffle when you fill, then take the first one
|
||||
/*
|
||||
Card c = cardPool[0];
|
||||
cardPool.RemoveAt(0);
|
||||
return c;
|
||||
*/
|
||||
this.Suit = s;
|
||||
this.Number = cardNum;
|
||||
}
|
||||
/// <summary>
|
||||
/// Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard method.
|
||||
/// </summary>
|
||||
private void Shuffle()
|
||||
|
||||
public string GetValueText() => cardNames[Number];
|
||||
|
||||
public override string ToString() => cardNames[Number] + " Of " + Suit;
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (CardPool.Count <= 1) return;
|
||||
var orderedPool = CardPool.Shuffle();
|
||||
CardPool = CardPool as List<Card> ?? orderedPool.ToList();
|
||||
if (!(obj is Card)) return 0;
|
||||
var c = (Card)obj;
|
||||
return this.Number - c.Number;
|
||||
}
|
||||
public override string ToString() => string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine;
|
||||
|
||||
private static void InitHandValues()
|
||||
public static Card Parse(string input)
|
||||
{
|
||||
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);
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
bool isTwoPair(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Count(group => group.Count() == 2) == 2;
|
||||
|
||||
bool isStraight(List<Card> cards)
|
||||
if (input.Length != 2
|
||||
|| !_numberCharToNumber.TryGetValue(input[0], out var n)
|
||||
|| !_suitCharToSuit.TryGetValue(input[1].ToString(), out var s))
|
||||
{
|
||||
if (cards.GroupBy(card => card.Number).Count() != cards.Count())
|
||||
return false;
|
||||
var toReturn = (cards.Max(card => (int)card.Number)
|
||||
- cards.Min(card => (int)card.Number) == 4);
|
||||
if (toReturn || cards.All(c => c.Number != 1)) return toReturn;
|
||||
|
||||
var newCards = cards.Select(c => c.Number == 1 ? new Card(c.Suit, 14) : c);
|
||||
return (newCards.Max(card => (int)card.Number)
|
||||
- newCards.Min(card => (int)card.Number) == 4);
|
||||
throw new ArgumentException("Invalid input", nameof(input));
|
||||
}
|
||||
|
||||
bool hasThreeOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Any(group => group.Count() == 3);
|
||||
|
||||
bool isThreeOfKind(List<Card> cards) => hasThreeOfKind(cards) && !hasPair(cards);
|
||||
|
||||
bool isFlush(List<Card> cards) => cards.GroupBy(card => card.Suit).Count() == 1;
|
||||
|
||||
bool isFourOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Any(group => group.Count() == 4);
|
||||
|
||||
bool isFullHouse(List<Card> cards) => hasPair(cards) && hasThreeOfKind(cards);
|
||||
|
||||
bool hasStraightFlush(List<Card> cards) => isFlush(cards) && isStraight(cards);
|
||||
|
||||
bool isRoyalFlush(List<Card> cards) => cards.Min(card => card.Number) == 1 &&
|
||||
cards.Max(card => card.Number) == 13
|
||||
&& hasStraightFlush(cards);
|
||||
|
||||
bool isStraightFlush(List<Card> cards) => hasStraightFlush(cards) && !isRoyalFlush(cards);
|
||||
|
||||
handValues = new Dictionary<string, Func<List<Card>, bool>>
|
||||
{
|
||||
{ "Royal Flush", isRoyalFlush },
|
||||
{ "Straight Flush", isStraightFlush },
|
||||
{ "Four Of A Kind", isFourOfKind },
|
||||
{ "Full House", isFullHouse },
|
||||
{ "Flush", isFlush },
|
||||
{ "Straight", isStraight },
|
||||
{ "Three Of A Kind", isThreeOfKind },
|
||||
{ "Two Pairs", isTwoPair },
|
||||
{ "A Pair", isPair }
|
||||
};
|
||||
return new Card(s, n);
|
||||
}
|
||||
|
||||
public static string GetHandValue(List<Card> cards)
|
||||
public string GetEmojiString()
|
||||
{
|
||||
if (handValues is null)
|
||||
InitHandValues();
|
||||
foreach (var kvp in handValues.Where(x => x.Value(cards)))
|
||||
var str = "";
|
||||
|
||||
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 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 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();
|
||||
}
|
||||
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.
|
||||
/// </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 List<Card>(52);
|
||||
//foreach suit
|
||||
for (var j = 1; j < 14; j++)
|
||||
{
|
||||
// and number
|
||||
for (var i = 1; i < 5; i++)
|
||||
{
|
||||
return kvp.Key;
|
||||
//generate a card of that suit and number and add it to the pool
|
||||
|
||||
// the pool will go from ace of spades,hears,diamonds,clubs all the way to the king of spades. hearts, ...
|
||||
CardPool.Add(new Card((CardSuit)i, j));
|
||||
}
|
||||
return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetValueText() ?? cards.Max().GetValueText());
|
||||
}
|
||||
}
|
||||
private Random r = new NadekoRandom();
|
||||
/// <summary>
|
||||
/// Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the deck is in the default order.
|
||||
/// </summary>
|
||||
/// <returns>A card from the pool</returns>
|
||||
public Card Draw()
|
||||
{
|
||||
if (CardPool.Count == 0)
|
||||
Restart();
|
||||
//you can either do this if your deck is not shuffled
|
||||
|
||||
var num = r.Next(0, CardPool.Count);
|
||||
var c = CardPool[num];
|
||||
CardPool.RemoveAt(num);
|
||||
return c;
|
||||
|
||||
// if you want to shuffle when you fill, then take the first one
|
||||
/*
|
||||
Card c = cardPool[0];
|
||||
cardPool.RemoveAt(0);
|
||||
return c;
|
||||
*/
|
||||
}
|
||||
/// <summary>
|
||||
/// Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard method.
|
||||
/// </summary>
|
||||
private void Shuffle()
|
||||
{
|
||||
if (CardPool.Count <= 1) return;
|
||||
var orderedPool = CardPool.Shuffle();
|
||||
CardPool = CardPool as List<Card> ?? orderedPool.ToList();
|
||||
}
|
||||
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 isTwoPair(List<Card> cards) => 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 => (int)card.Number)
|
||||
- cards.Min(card => (int)card.Number) == 4);
|
||||
if (toReturn || cards.All(c => c.Number != 1)) return toReturn;
|
||||
|
||||
var newCards = cards.Select(c => c.Number == 1 ? new Card(c.Suit, 14) : c);
|
||||
return (newCards.Max(card => (int)card.Number)
|
||||
- newCards.Min(card => (int)card.Number) == 4);
|
||||
}
|
||||
|
||||
bool hasThreeOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Any(group => group.Count() == 3);
|
||||
|
||||
bool isThreeOfKind(List<Card> cards) => hasThreeOfKind(cards) && !hasPair(cards);
|
||||
|
||||
bool isFlush(List<Card> cards) => cards.GroupBy(card => card.Suit).Count() == 1;
|
||||
|
||||
bool isFourOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Any(group => group.Count() == 4);
|
||||
|
||||
bool isFullHouse(List<Card> cards) => hasPair(cards) && hasThreeOfKind(cards);
|
||||
|
||||
bool hasStraightFlush(List<Card> cards) => isFlush(cards) && isStraight(cards);
|
||||
|
||||
bool isRoyalFlush(List<Card> cards) => cards.Min(card => card.Number) == 1 &&
|
||||
cards.Max(card => card.Number) == 13
|
||||
&& hasStraightFlush(cards);
|
||||
|
||||
bool isStraightFlush(List<Card> cards) => hasStraightFlush(cards) && !isRoyalFlush(cards);
|
||||
|
||||
handValues = new Dictionary<string, Func<List<Card>, bool>>
|
||||
{
|
||||
{ "Royal Flush", isRoyalFlush },
|
||||
{ "Straight Flush", isStraightFlush },
|
||||
{ "Four Of A Kind", isFourOfKind },
|
||||
{ "Full House", isFullHouse },
|
||||
{ "Flush", isFlush },
|
||||
{ "Straight", isStraight },
|
||||
{ "Three Of A Kind", isThreeOfKind },
|
||||
{ "Two Pairs", isTwoPair },
|
||||
{ "A Pair", isPair }
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetHandValue(List<Card> cards)
|
||||
{
|
||||
if (handValues is null)
|
||||
InitHandValues();
|
||||
foreach (var kvp in handValues.Where(x => x.Value(cards)))
|
||||
{
|
||||
return kvp.Key;
|
||||
}
|
||||
return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetValueText() ?? cards.Max().GetValueText());
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,29 @@
|
||||
using CommandLine;
|
||||
using NadekoBot.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.Events
|
||||
namespace NadekoBot.Modules.Gambling.Common.Events;
|
||||
|
||||
public class EventOptions : INadekoCommandOptions
|
||||
{
|
||||
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('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.")]
|
||||
public int Hours { get; set; } = 24;
|
||||
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
[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('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.")]
|
||||
public int Hours { get; set; } = 24;
|
||||
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (Amount < 0)
|
||||
Amount = 100;
|
||||
if (PotSize < 0)
|
||||
PotSize = 0;
|
||||
if (Hours <= 0)
|
||||
Hours = 24;
|
||||
if (PotSize != 0 && PotSize < Amount)
|
||||
PotSize = 0;
|
||||
}
|
||||
if (Amount < 0)
|
||||
Amount = 100;
|
||||
if (PotSize < 0)
|
||||
PotSize = 0;
|
||||
if (Hours <= 0)
|
||||
Hours = 24;
|
||||
if (PotSize != 0 && PotSize < Amount)
|
||||
PotSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,207 +4,201 @@ using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.Events
|
||||
namespace NadekoBot.Modules.Gambling.Common.Events;
|
||||
|
||||
public class GameStatusEvent : ICurrencyEvent
|
||||
{
|
||||
public class GameStatusEvent : ICurrencyEvent
|
||||
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 ConcurrentHashSet<ulong>();
|
||||
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
|
||||
private readonly Timer _t;
|
||||
private readonly Timer _timeout = null;
|
||||
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();
|
||||
|
||||
public GameStatusEvent(DiscordSocketClient client, ICurrencyService cs,SocketGuild g, ITextChannel ch,
|
||||
EventOptions opt, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IGuild _guild;
|
||||
private IUserMessage _msg;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly long _amount;
|
||||
_client = client;
|
||||
_guild = g;
|
||||
_cs = cs;
|
||||
_amount = opt.Amount;
|
||||
PotSize = opt.PotSize;
|
||||
_embedFunc = embedFunc;
|
||||
_isPotLimited = PotSize > 0;
|
||||
_channel = ch;
|
||||
_opts = opt;
|
||||
// generate code
|
||||
_code = new string(_sneakyGameStatusChars.Shuffle().Take(5).ToArray());
|
||||
|
||||
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 ConcurrentHashSet<ulong>();
|
||||
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
|
||||
private readonly Timer _t;
|
||||
private readonly Timer _timeout = null;
|
||||
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();
|
||||
|
||||
public GameStatusEvent(DiscordSocketClient client, ICurrencyService cs,SocketGuild g, ITextChannel ch,
|
||||
EventOptions opt, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
|
||||
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
|
||||
if (_opts.Hours > 0)
|
||||
{
|
||||
_client = client;
|
||||
_guild = g;
|
||||
_cs = cs;
|
||||
_amount = opt.Amount;
|
||||
PotSize = opt.PotSize;
|
||||
_embedFunc = embedFunc;
|
||||
_isPotLimited = PotSize > 0;
|
||||
_channel = ch;
|
||||
_opts = opt;
|
||||
// generate code
|
||||
_code = new string(_sneakyGameStatusChars.Shuffle().Take(5).ToArray());
|
||||
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
|
||||
if (_opts.Hours > 0)
|
||||
{
|
||||
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
private void EventTimeout(object state)
|
||||
{
|
||||
var _ = StopEvent();
|
||||
}
|
||||
|
||||
private async void OnTimerTick(object state)
|
||||
{
|
||||
var potEmpty = PotEmptied;
|
||||
List<ulong> toAward = new List<ulong>();
|
||||
while (_toAward.TryDequeue(out var x))
|
||||
{
|
||||
toAward.Add(x);
|
||||
}
|
||||
|
||||
private void EventTimeout(object state)
|
||||
{
|
||||
var _ = StopEvent();
|
||||
}
|
||||
if (!toAward.Any())
|
||||
return;
|
||||
|
||||
private async void OnTimerTick(object state)
|
||||
try
|
||||
{
|
||||
var potEmpty = PotEmptied;
|
||||
List<ulong> toAward = new List<ulong>();
|
||||
while (_toAward.TryDequeue(out var x))
|
||||
await _cs.AddBulkAsync(toAward,
|
||||
toAward.Select(x => "GameStatus Event"),
|
||||
toAward.Select(x => _amount),
|
||||
gamble: true).ConfigureAwait(false);
|
||||
|
||||
if (_isPotLimited)
|
||||
{
|
||||
toAward.Add(x);
|
||||
await _msg.ModifyAsync(m =>
|
||||
{
|
||||
m.Embed = GetEmbed(PotSize).Build();
|
||||
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!toAward.Any())
|
||||
Log.Information("Awarded {0} users {1} currency.{2}",
|
||||
toAward.Count,
|
||||
_amount,
|
||||
_isPotLimited ? $" {PotSize} left." : "");
|
||||
|
||||
if (potEmpty)
|
||||
{
|
||||
var _ = StopEvent();
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error in OnTimerTick in gamestatusevent");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartEvent()
|
||||
{
|
||||
_msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize)).ConfigureAwait(false);
|
||||
await _client.SetGameAsync(_code).ConfigureAwait(false);
|
||||
_client.MessageDeleted += OnMessageDeleted;
|
||||
_client.MessageReceived += HandleMessage;
|
||||
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
private IEmbedBuilder GetEmbed(long pot)
|
||||
{
|
||||
return _embedFunc(CurrencyEvent.Type.GameStatus, _opts, pot);
|
||||
}
|
||||
|
||||
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
|
||||
{
|
||||
if (msg.Id == _msg.Id)
|
||||
{
|
||||
await StopEvent().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object stopLock = new object();
|
||||
public async Task StopEvent()
|
||||
{
|
||||
await Task.Yield();
|
||||
lock (stopLock)
|
||||
{
|
||||
if (Stopped)
|
||||
return;
|
||||
Stopped = true;
|
||||
_client.MessageDeleted -= OnMessageDeleted;
|
||||
_client.MessageReceived -= HandleMessage;
|
||||
_client.SetGameAsync(null);
|
||||
_t.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
try { var _ = _msg.DeleteAsync(); } catch { }
|
||||
var os = OnEnded(_guild.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleMessage(SocketMessage msg)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
if (!(msg.Author is IGuildUser gu) // no unknown users, as they could be bots, or alts
|
||||
|| 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())
|
||||
{
|
||||
_toAward.Enqueue(msg.Author.Id);
|
||||
if (_isPotLimited && PotSize < _amount)
|
||||
PotEmptied = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _cs.AddBulkAsync(toAward,
|
||||
toAward.Select(x => "GameStatus Event"),
|
||||
toAward.Select(x => _amount),
|
||||
gamble: true).ConfigureAwait(false);
|
||||
|
||||
if (_isPotLimited)
|
||||
await msg.DeleteAsync(new RequestOptions()
|
||||
{
|
||||
await _msg.ModifyAsync(m =>
|
||||
{
|
||||
m.Embed = GetEmbed(PotSize).Build();
|
||||
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Log.Information("Awarded {0} users {1} currency.{2}",
|
||||
toAward.Count,
|
||||
_amount,
|
||||
_isPotLimited ? $" {PotSize} left." : "");
|
||||
|
||||
if (potEmpty)
|
||||
{
|
||||
var _ = StopEvent();
|
||||
}
|
||||
|
||||
RetryMode = RetryMode.AlwaysFail
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error in OnTimerTick in gamestatusevent");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartEvent()
|
||||
{
|
||||
_msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize)).ConfigureAwait(false);
|
||||
await _client.SetGameAsync(_code).ConfigureAwait(false);
|
||||
_client.MessageDeleted += OnMessageDeleted;
|
||||
_client.MessageReceived += HandleMessage;
|
||||
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
private IEmbedBuilder GetEmbed(long pot)
|
||||
{
|
||||
return _embedFunc(CurrencyEvent.Type.GameStatus, _opts, pot);
|
||||
}
|
||||
|
||||
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
|
||||
{
|
||||
if (msg.Id == _msg.Id)
|
||||
{
|
||||
await StopEvent().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object stopLock = new object();
|
||||
public async Task StopEvent()
|
||||
{
|
||||
await Task.Yield();
|
||||
lock (stopLock)
|
||||
{
|
||||
if (Stopped)
|
||||
return;
|
||||
Stopped = true;
|
||||
_client.MessageDeleted -= OnMessageDeleted;
|
||||
_client.MessageReceived -= HandleMessage;
|
||||
_client.SetGameAsync(null);
|
||||
_t.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
try { var _ = _msg.DeleteAsync(); } catch { }
|
||||
var os = OnEnded(_guild.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleMessage(SocketMessage msg)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
if (!(msg.Author is IGuildUser gu) // no unknown users, as they could be bots, or alts
|
||||
|| 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())
|
||||
{
|
||||
_toAward.Enqueue(msg.Author.Id);
|
||||
if (_isPotLimited && PotSize < _amount)
|
||||
PotEmptied = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await msg.DeleteAsync(new RequestOptions()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysFail
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private readonly object potLock = new object();
|
||||
private bool TryTakeFromPot()
|
||||
{
|
||||
if (_isPotLimited)
|
||||
{
|
||||
lock (potLock)
|
||||
{
|
||||
if (PotSize < _amount)
|
||||
return false;
|
||||
|
||||
PotSize -= _amount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object potLock = new object();
|
||||
private bool TryTakeFromPot()
|
||||
{
|
||||
if (_isPotLimited)
|
||||
{
|
||||
lock (potLock)
|
||||
{
|
||||
if (PotSize < _amount)
|
||||
return false;
|
||||
|
||||
PotSize -= _amount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public interface ICurrencyEvent
|
||||
{
|
||||
public interface ICurrencyEvent
|
||||
{
|
||||
event Func<ulong, Task> OnEnded;
|
||||
Task StopEvent();
|
||||
Task StartEvent();
|
||||
}
|
||||
}
|
||||
event Func<ulong, Task> OnEnded;
|
||||
Task StopEvent();
|
||||
Task StartEvent();
|
||||
}
|
||||
@@ -4,207 +4,200 @@ using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.Events
|
||||
namespace NadekoBot.Modules.Gambling.Common.Events;
|
||||
|
||||
public class ReactionEvent : ICurrencyEvent
|
||||
{
|
||||
public class ReactionEvent : ICurrencyEvent
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IGuild _guild;
|
||||
private IUserMessage _msg;
|
||||
private IEmote _emote;
|
||||
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 ConcurrentHashSet<ulong>();
|
||||
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
|
||||
private readonly Timer _t;
|
||||
private readonly Timer _timeout = null;
|
||||
private readonly bool _noRecentlyJoinedServer;
|
||||
private readonly EventOptions _opts;
|
||||
private readonly GamblingConfig _config;
|
||||
|
||||
public event Func<ulong, Task> OnEnded;
|
||||
|
||||
public ReactionEvent(DiscordSocketClient client, ICurrencyService cs,
|
||||
SocketGuild g, ITextChannel ch, EventOptions opt, GamblingConfig config,
|
||||
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IGuild _guild;
|
||||
private IUserMessage _msg;
|
||||
private IEmote _emote;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly long _amount;
|
||||
_client = client;
|
||||
_guild = g;
|
||||
_cs = cs;
|
||||
_amount = opt.Amount;
|
||||
PotSize = opt.PotSize;
|
||||
_embedFunc = embedFunc;
|
||||
_isPotLimited = PotSize > 0;
|
||||
_channel = ch;
|
||||
_noRecentlyJoinedServer = false;
|
||||
_opts = opt;
|
||||
_config = config;
|
||||
|
||||
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 ConcurrentHashSet<ulong>();
|
||||
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
|
||||
private readonly Timer _t;
|
||||
private readonly Timer _timeout = null;
|
||||
private readonly bool _noRecentlyJoinedServer;
|
||||
private readonly EventOptions _opts;
|
||||
private readonly GamblingConfig _config;
|
||||
|
||||
public event Func<ulong, Task> OnEnded;
|
||||
|
||||
public ReactionEvent(DiscordSocketClient client, ICurrencyService cs,
|
||||
SocketGuild g, ITextChannel ch, EventOptions opt, GamblingConfig config,
|
||||
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
|
||||
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
|
||||
if (_opts.Hours > 0)
|
||||
{
|
||||
_client = client;
|
||||
_guild = g;
|
||||
_cs = cs;
|
||||
_amount = opt.Amount;
|
||||
PotSize = opt.PotSize;
|
||||
_embedFunc = embedFunc;
|
||||
_isPotLimited = PotSize > 0;
|
||||
_channel = ch;
|
||||
_noRecentlyJoinedServer = false;
|
||||
_opts = opt;
|
||||
_config = config;
|
||||
|
||||
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
|
||||
if (_opts.Hours > 0)
|
||||
{
|
||||
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private void EventTimeout(object state)
|
||||
{
|
||||
var _ = StopEvent();
|
||||
}
|
||||
|
||||
private async void OnTimerTick(object state)
|
||||
{
|
||||
var potEmpty = PotEmptied;
|
||||
List<ulong> toAward = new List<ulong>();
|
||||
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).ConfigureAwait(false);
|
||||
|
||||
if (_isPotLimited)
|
||||
{
|
||||
await _msg.ModifyAsync(m =>
|
||||
{
|
||||
m.Embed = GetEmbed(PotSize).Build();
|
||||
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Log.Information("Awarded {0} users {1} currency.{2}",
|
||||
toAward.Count,
|
||||
_amount,
|
||||
_isPotLimited ? $" {PotSize} left." : "");
|
||||
|
||||
if (potEmpty)
|
||||
{
|
||||
var _ = StopEvent();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error adding bulk currency to users");
|
||||
}
|
||||
}
|
||||
|
||||
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)).ConfigureAwait(false);
|
||||
await _msg.AddReactionAsync(_emote).ConfigureAwait(false);
|
||||
_client.MessageDeleted += OnMessageDeleted;
|
||||
_client.ReactionAdded += HandleReaction;
|
||||
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
private IEmbedBuilder GetEmbed(long pot)
|
||||
{
|
||||
return _embedFunc(CurrencyEvent.Type.Reaction, _opts, pot);
|
||||
}
|
||||
|
||||
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
|
||||
{
|
||||
if (msg.Id == _msg.Id)
|
||||
{
|
||||
await StopEvent().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object stopLock = new object();
|
||||
public async Task StopEvent()
|
||||
{
|
||||
await Task.Yield();
|
||||
lock (stopLock)
|
||||
{
|
||||
if (Stopped)
|
||||
return;
|
||||
Stopped = true;
|
||||
_client.MessageDeleted -= OnMessageDeleted;
|
||||
_client.ReactionAdded -= HandleReaction;
|
||||
_t.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
try { var _ = _msg.DeleteAsync(); } catch { }
|
||||
var os = OnEnded(_guild.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleReaction(Cacheable<IUserMessage, ulong> msg,
|
||||
ISocketMessageChannel ch, SocketReaction r)
|
||||
{
|
||||
var _ = Task.Run(() =>
|
||||
{
|
||||
if (_emote.Name != r.Emote.Name)
|
||||
return;
|
||||
var gu = (r.User.IsSpecified ? r.User.Value : null) as IGuildUser;
|
||||
if (gu is null // 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
|
||||
{
|
||||
return;
|
||||
}
|
||||
// there has to be money left in the pot
|
||||
// and the user wasn't rewarded
|
||||
if (_awardedUsers.Add(r.UserId) && TryTakeFromPot())
|
||||
{
|
||||
_toAward.Enqueue(r.UserId);
|
||||
if (_isPotLimited && PotSize < _amount)
|
||||
PotEmptied = true;
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private readonly object potLock = new object();
|
||||
private bool TryTakeFromPot()
|
||||
{
|
||||
if (_isPotLimited)
|
||||
{
|
||||
lock (potLock)
|
||||
{
|
||||
if (PotSize < _amount)
|
||||
return false;
|
||||
|
||||
PotSize -= _amount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EventTimeout(object state)
|
||||
{
|
||||
var _ = StopEvent();
|
||||
}
|
||||
|
||||
private async void OnTimerTick(object state)
|
||||
{
|
||||
var potEmpty = PotEmptied;
|
||||
List<ulong> toAward = new List<ulong>();
|
||||
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).ConfigureAwait(false);
|
||||
|
||||
if (_isPotLimited)
|
||||
{
|
||||
await _msg.ModifyAsync(m =>
|
||||
{
|
||||
m.Embed = GetEmbed(PotSize).Build();
|
||||
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Log.Information("Awarded {0} users {1} currency.{2}",
|
||||
toAward.Count,
|
||||
_amount,
|
||||
_isPotLimited ? $" {PotSize} left." : "");
|
||||
|
||||
if (potEmpty)
|
||||
{
|
||||
var _ = StopEvent();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error adding bulk currency to users");
|
||||
}
|
||||
}
|
||||
|
||||
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)).ConfigureAwait(false);
|
||||
await _msg.AddReactionAsync(_emote).ConfigureAwait(false);
|
||||
_client.MessageDeleted += OnMessageDeleted;
|
||||
_client.ReactionAdded += HandleReaction;
|
||||
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
private IEmbedBuilder GetEmbed(long pot)
|
||||
{
|
||||
return _embedFunc(CurrencyEvent.Type.Reaction, _opts, pot);
|
||||
}
|
||||
|
||||
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
|
||||
{
|
||||
if (msg.Id == _msg.Id)
|
||||
{
|
||||
await StopEvent().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object stopLock = new object();
|
||||
public async Task StopEvent()
|
||||
{
|
||||
await Task.Yield();
|
||||
lock (stopLock)
|
||||
{
|
||||
if (Stopped)
|
||||
return;
|
||||
Stopped = true;
|
||||
_client.MessageDeleted -= OnMessageDeleted;
|
||||
_client.ReactionAdded -= HandleReaction;
|
||||
_t.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
try { var _ = _msg.DeleteAsync(); } catch { }
|
||||
var os = OnEnded(_guild.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleReaction(Cacheable<IUserMessage, ulong> msg,
|
||||
ISocketMessageChannel ch, SocketReaction r)
|
||||
{
|
||||
var _ = Task.Run(() =>
|
||||
{
|
||||
if (_emote.Name != r.Emote.Name)
|
||||
return;
|
||||
var gu = (r.User.IsSpecified ? r.User.Value : null) as IGuildUser;
|
||||
if (gu is null // 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
|
||||
{
|
||||
return;
|
||||
}
|
||||
// there has to be money left in the pot
|
||||
// and the user wasn't rewarded
|
||||
if (_awardedUsers.Add(r.UserId) && TryTakeFromPot())
|
||||
{
|
||||
_toAward.Enqueue(r.UserId);
|
||||
if (_isPotLimited && PotSize < _amount)
|
||||
PotEmptied = true;
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private readonly object potLock = new object();
|
||||
private bool TryTakeFromPot()
|
||||
{
|
||||
if (_isPotLimited)
|
||||
{
|
||||
lock (potLock)
|
||||
{
|
||||
if (PotSize < _amount)
|
||||
return false;
|
||||
|
||||
PotSize -= _amount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,321 +1,318 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cloneable;
|
||||
using Cloneable;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Yml;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||
{
|
||||
[Cloneable]
|
||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||
public GamblingConfig()
|
||||
{
|
||||
public GamblingConfig()
|
||||
{
|
||||
BetRoll = new BetRollConfig();
|
||||
WheelOfFortune = new WheelOfFortuneSettings();
|
||||
Waifu = new WaifuConfig();
|
||||
Currency = new CurrencyConfig();
|
||||
BetFlip = new BetFlipConfig();
|
||||
Generation = new GenerationConfig();
|
||||
Timely = new TimelyConfig();
|
||||
Decay = new DecayConfig();
|
||||
Slots = new SlotsConfig();
|
||||
}
|
||||
BetRoll = new BetRollConfig();
|
||||
WheelOfFortune = new WheelOfFortuneSettings();
|
||||
Waifu = new WaifuConfig();
|
||||
Currency = new CurrencyConfig();
|
||||
BetFlip = new BetFlipConfig();
|
||||
Generation = new GenerationConfig();
|
||||
Timely = new TimelyConfig();
|
||||
Decay = new DecayConfig();
|
||||
Slots = new SlotsConfig();
|
||||
}
|
||||
|
||||
[Comment(@"DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 2;
|
||||
[Comment(@"DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 2;
|
||||
|
||||
[Comment(@"Currency settings")]
|
||||
public CurrencyConfig Currency { get; set; }
|
||||
[Comment(@"Currency settings")]
|
||||
public CurrencyConfig Currency { get; set; }
|
||||
|
||||
[Comment(@"Minimum amount users can bet (>=0)")]
|
||||
public int MinBet { get; set; } = 0;
|
||||
[Comment(@"Minimum amount users can bet (>=0)")]
|
||||
public int MinBet { get; set; } = 0;
|
||||
|
||||
[Comment(@"Maximum amount users can bet
|
||||
[Comment(@"Maximum amount users can bet
|
||||
Set 0 for unlimited")]
|
||||
public int MaxBet { get; set; } = 0;
|
||||
public int MaxBet { get; set; } = 0;
|
||||
|
||||
[Comment(@"Settings for betflip command")]
|
||||
public BetFlipConfig BetFlip { get; set; }
|
||||
[Comment(@"Settings for betflip command")]
|
||||
public BetFlipConfig BetFlip { get; set; }
|
||||
|
||||
[Comment(@"Settings for betroll command")]
|
||||
public BetRollConfig BetRoll { get; set; }
|
||||
[Comment(@"Settings for betroll command")]
|
||||
public BetRollConfig BetRoll { get; set; }
|
||||
|
||||
[Comment(@"Automatic currency generation settings.")]
|
||||
public GenerationConfig Generation { get; set; }
|
||||
[Comment(@"Automatic currency generation settings.")]
|
||||
public GenerationConfig Generation { get; set; }
|
||||
|
||||
[Comment(@"Settings for timely command
|
||||
[Comment(@"Settings for timely command
|
||||
(letting people claim X amount of currency every Y hours)")]
|
||||
public TimelyConfig Timely { get; set; }
|
||||
public TimelyConfig Timely { get; set; }
|
||||
|
||||
[Comment(@"How much will each user's owned currency decay over time.")]
|
||||
public DecayConfig Decay { get; set; }
|
||||
[Comment(@"How much will each user's owned currency decay over time.")]
|
||||
public DecayConfig Decay { get; set; }
|
||||
|
||||
[Comment(@"Settings for Wheel Of Fortune command.")]
|
||||
public WheelOfFortuneSettings WheelOfFortune { get; set; }
|
||||
[Comment(@"Settings for Wheel Of Fortune command.")]
|
||||
public WheelOfFortuneSettings WheelOfFortune { get; set; }
|
||||
|
||||
[Comment(@"Settings related to waifus")]
|
||||
public WaifuConfig Waifu { get; set; }
|
||||
[Comment(@"Settings related to waifus")]
|
||||
public WaifuConfig Waifu { get; set; }
|
||||
|
||||
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
|
||||
[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;
|
||||
public decimal PatreonCurrencyPerCent { get; set; } = 1;
|
||||
|
||||
[Comment(@"Currency reward per vote.
|
||||
[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;
|
||||
public long VoteReward { get; set; } = 100;
|
||||
|
||||
[Comment(@"Slot config")]
|
||||
public SlotsConfig Slots { get; set; }
|
||||
}
|
||||
[Comment(@"Slot config")]
|
||||
public SlotsConfig Slots { get; set; }
|
||||
}
|
||||
|
||||
public class CurrencyConfig
|
||||
{
|
||||
[Comment(@"What is the emoji/character which represents the currency")]
|
||||
public string Sign { get; set; } = "🌸";
|
||||
public class CurrencyConfig
|
||||
{
|
||||
[Comment(@"What is the emoji/character which represents the currency")]
|
||||
public string Sign { get; set; } = "🌸";
|
||||
|
||||
[Comment(@"What is the name of the currency")]
|
||||
public string Name { get; set; } = "Nadeko Flower";
|
||||
}
|
||||
[Comment(@"What is the name of the currency")]
|
||||
public string Name { get; set; } = "Nadeko Flower";
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class TimelyConfig
|
||||
{
|
||||
[Comment(@"How much currency will the users get every time they run .timely command
|
||||
[Cloneable]
|
||||
public partial class TimelyConfig
|
||||
{
|
||||
[Comment(@"How much currency will the users get every time they run .timely command
|
||||
setting to 0 or less will disable this feature")]
|
||||
public int Amount { get; set; } = 0;
|
||||
public int Amount { get; set; } = 0;
|
||||
|
||||
[Comment(@"How often (in hours) can users claim currency with .timely command
|
||||
[Comment(@"How often (in hours) can users claim currency with .timely command
|
||||
setting to 0 or less will disable this feature")]
|
||||
public int Cooldown { get; set; } = 24;
|
||||
}
|
||||
public int Cooldown { get; set; } = 24;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class BetFlipConfig
|
||||
{
|
||||
[Comment(@"Bet multiplier if user guesses correctly")]
|
||||
public decimal Multiplier { get; set; } = 1.95M;
|
||||
}
|
||||
[Cloneable]
|
||||
public partial class BetFlipConfig
|
||||
{
|
||||
[Comment(@"Bet multiplier if user guesses correctly")]
|
||||
public decimal Multiplier { get; set; } = 1.95M;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class BetRollConfig
|
||||
{
|
||||
[Comment(@"When betroll is played, user will roll a number 0-100.
|
||||
[Cloneable]
|
||||
public partial class BetRollConfig
|
||||
{
|
||||
[Comment(@"When betroll is played, user will roll a number 0-100.
|
||||
This setting will describe which multiplier is used for when the roll is higher than the given number.
|
||||
Doesn't have to be ordered.")]
|
||||
public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
|
||||
public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
|
||||
|
||||
public BetRollConfig()
|
||||
{
|
||||
Pairs = new BetRollPair[]
|
||||
{
|
||||
new BetRollPair { WhenAbove = 99, MultiplyBy = 10 },
|
||||
new BetRollPair { WhenAbove = 90, MultiplyBy = 4 },
|
||||
new BetRollPair { WhenAbove = 66, MultiplyBy = 2 }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class GenerationConfig
|
||||
public BetRollConfig()
|
||||
{
|
||||
[Comment(@"when currency is generated, should it also have a random password
|
||||
Pairs = new BetRollPair[]
|
||||
{
|
||||
new BetRollPair { WhenAbove = 99, MultiplyBy = 10 },
|
||||
new BetRollPair { WhenAbove = 90, MultiplyBy = 4 },
|
||||
new BetRollPair { WhenAbove = 66, MultiplyBy = 2 }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class GenerationConfig
|
||||
{
|
||||
[Comment(@"when currency is generated, should it also have a random password
|
||||
associated with it which users have to type after the .pick command
|
||||
in order to get it")]
|
||||
public bool HasPassword { get; set; } = true;
|
||||
public bool HasPassword { get; set; } = true;
|
||||
|
||||
[Comment(@"Every message sent has a certain % chance to generate the currency
|
||||
[Comment(@"Every message sent has a certain % chance to generate the currency
|
||||
specify the percentage here (1 being 100%, 0 being 0% - for example
|
||||
default is 0.02, which is 2%")]
|
||||
public decimal Chance { get; set; } = 0.02M;
|
||||
public decimal Chance { get; set; } = 0.02M;
|
||||
|
||||
[Comment(@"How many seconds have to pass for the next message to have a chance to spawn currency")]
|
||||
public int GenCooldown { get; set; } = 10;
|
||||
[Comment(@"How many seconds have to pass for the next message to have a chance to spawn currency")]
|
||||
public int GenCooldown { get; set; } = 10;
|
||||
|
||||
[Comment(@"Minimum amount of currency that can spawn")]
|
||||
public int MinAmount { get; set; } = 1;
|
||||
[Comment(@"Minimum amount of currency that can spawn")]
|
||||
public int MinAmount { get; set; } = 1;
|
||||
|
||||
[Comment(@"Maximum amount of currency that can spawn.
|
||||
[Comment(@"Maximum amount of currency that can spawn.
|
||||
Set to the same value as MinAmount to always spawn the same amount")]
|
||||
public int MaxAmount { get; set; } = 1;
|
||||
}
|
||||
public int MaxAmount { get; set; } = 1;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class DecayConfig
|
||||
{
|
||||
[Comment(@"Percentage of user's current currency which will be deducted every 24h.
|
||||
[Cloneable]
|
||||
public partial class DecayConfig
|
||||
{
|
||||
[Comment(@"Percentage of user's current currency which will be deducted every 24h.
|
||||
0 - 1 (1 is 100%, 0.5 50%, 0 disabled)")]
|
||||
public decimal Percent { get; set; } = 0;
|
||||
public decimal Percent { get; set; } = 0;
|
||||
|
||||
[Comment(@"Maximum amount of user's currency that can decay at each interval. 0 for unlimited.")]
|
||||
public int MaxDecay { get; set; } = 0;
|
||||
[Comment(@"Maximum amount of user's currency that can decay at each interval. 0 for unlimited.")]
|
||||
public int MaxDecay { get; set; } = 0;
|
||||
|
||||
[Comment(@"Only users who have more than this amount will have their currency decay.")]
|
||||
public int MinThreshold { get; set; } = 99;
|
||||
[Comment(@"Only users who have more than this amount will have their currency decay.")]
|
||||
public int MinThreshold { get; set; } = 99;
|
||||
|
||||
[Comment(@"How often, in hours, does the decay run. Default is 24 hours")]
|
||||
public int HourInterval { get; set; } = 24;
|
||||
}
|
||||
[Comment(@"How often, in hours, does the decay run. Default is 24 hours")]
|
||||
public int HourInterval { get; set; } = 24;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public partial class WheelOfFortuneSettings
|
||||
[Cloneable]
|
||||
public partial class WheelOfFortuneSettings
|
||||
{
|
||||
[Comment(@"Self-Explanatory. Has to have 8 values, otherwise the command won't work.")]
|
||||
public decimal[] Multipliers { get; set; }
|
||||
|
||||
public WheelOfFortuneSettings()
|
||||
{
|
||||
[Comment(@"Self-Explanatory. Has to have 8 values, otherwise the command won't work.")]
|
||||
public decimal[] Multipliers { get; set; }
|
||||
|
||||
public WheelOfFortuneSettings()
|
||||
Multipliers = new decimal[]
|
||||
{
|
||||
Multipliers = new decimal[]
|
||||
{
|
||||
1.7M,
|
||||
1.5M,
|
||||
0.2M,
|
||||
0.1M,
|
||||
0.3M,
|
||||
0.5M,
|
||||
1.2M,
|
||||
2.4M,
|
||||
};
|
||||
}
|
||||
1.7M,
|
||||
1.5M,
|
||||
0.2M,
|
||||
0.1M,
|
||||
0.3M,
|
||||
0.5M,
|
||||
1.2M,
|
||||
2.4M,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class WaifuConfig
|
||||
{
|
||||
[Comment(@"Minimum price a waifu can have")]
|
||||
public int MinPrice { get; set; } = 50;
|
||||
[Cloneable]
|
||||
public sealed partial class WaifuConfig
|
||||
{
|
||||
[Comment(@"Minimum price a waifu can have")]
|
||||
public int MinPrice { get; set; } = 50;
|
||||
|
||||
public MultipliersData Multipliers { get; set; } = new MultipliersData();
|
||||
public MultipliersData Multipliers { get; set; } = new MultipliersData();
|
||||
|
||||
[Comment(@"List of items available for gifting.
|
||||
[Comment(@"List of items available for gifting.
|
||||
If negative is true, gift will instead reduce waifu value.")]
|
||||
public List<WaifuItemModel> Items { get; set; } = new List<WaifuItemModel>();
|
||||
public List<WaifuItemModel> Items { get; set; } = new List<WaifuItemModel>();
|
||||
|
||||
public WaifuConfig()
|
||||
{
|
||||
Items = new()
|
||||
{
|
||||
new("🥔", 5, "Potato"),
|
||||
new("🍪", 10, "Cookie"),
|
||||
new("🥖", 20, "Bread"),
|
||||
new("🍭", 30, "Lollipop"),
|
||||
new("🌹", 50, "Rose"),
|
||||
new("🍺", 70, "Beer"),
|
||||
new("🌮", 85, "Taco"),
|
||||
new("💌", 100, "LoveLetter"),
|
||||
new("🥛", 125, "Milk"),
|
||||
new("🍕", 150, "Pizza"),
|
||||
new("🍫", 200, "Chocolate"),
|
||||
new("🍦", 250, "Icecream"),
|
||||
new("🍣", 300, "Sushi"),
|
||||
new("🍚", 400, "Rice"),
|
||||
new("🍉", 500, "Watermelon"),
|
||||
new("🍱", 600, "Bento"),
|
||||
new("🎟", 800, "MovieTicket"),
|
||||
new("🍰", 1000, "Cake"),
|
||||
new("📔", 1500, "Book"),
|
||||
new("🐱", 2000, "Cat"),
|
||||
new("🐶", 2001, "Dog"),
|
||||
new("🐼", 2500, "Panda"),
|
||||
new("💄", 3000, "Lipstick"),
|
||||
new("👛", 3500, "Purse"),
|
||||
new("📱", 4000, "iPhone"),
|
||||
new("👗", 4500, "Dress"),
|
||||
new("💻", 5000, "Laptop"),
|
||||
new("🎻", 7500, "Violin"),
|
||||
new("🎹", 8000, "Piano"),
|
||||
new("🚗", 9000, "Car"),
|
||||
new("💍", 10000, "Ring"),
|
||||
new("🛳", 12000, "Ship"),
|
||||
new("🏠", 15000, "House"),
|
||||
new("🚁", 20000, "Helicopter"),
|
||||
new("🚀", 30000, "Spaceship"),
|
||||
new("🌕", 50000, "Moon")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class MultipliersData
|
||||
public WaifuConfig()
|
||||
{
|
||||
[Comment(@"Multiplier for waifureset. Default 150.
|
||||
Items = new()
|
||||
{
|
||||
new("🥔", 5, "Potato"),
|
||||
new("🍪", 10, "Cookie"),
|
||||
new("🥖", 20, "Bread"),
|
||||
new("🍭", 30, "Lollipop"),
|
||||
new("🌹", 50, "Rose"),
|
||||
new("🍺", 70, "Beer"),
|
||||
new("🌮", 85, "Taco"),
|
||||
new("💌", 100, "LoveLetter"),
|
||||
new("🥛", 125, "Milk"),
|
||||
new("🍕", 150, "Pizza"),
|
||||
new("🍫", 200, "Chocolate"),
|
||||
new("🍦", 250, "Icecream"),
|
||||
new("🍣", 300, "Sushi"),
|
||||
new("🍚", 400, "Rice"),
|
||||
new("🍉", 500, "Watermelon"),
|
||||
new("🍱", 600, "Bento"),
|
||||
new("🎟", 800, "MovieTicket"),
|
||||
new("🍰", 1000, "Cake"),
|
||||
new("📔", 1500, "Book"),
|
||||
new("🐱", 2000, "Cat"),
|
||||
new("🐶", 2001, "Dog"),
|
||||
new("🐼", 2500, "Panda"),
|
||||
new("💄", 3000, "Lipstick"),
|
||||
new("👛", 3500, "Purse"),
|
||||
new("📱", 4000, "iPhone"),
|
||||
new("👗", 4500, "Dress"),
|
||||
new("💻", 5000, "Laptop"),
|
||||
new("🎻", 7500, "Violin"),
|
||||
new("🎹", 8000, "Piano"),
|
||||
new("🚗", 9000, "Car"),
|
||||
new("💍", 10000, "Ring"),
|
||||
new("🛳", 12000, "Ship"),
|
||||
new("🏠", 15000, "House"),
|
||||
new("🚁", 20000, "Helicopter"),
|
||||
new("🚀", 30000, "Spaceship"),
|
||||
new("🌕", 50000, "Moon")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class MultipliersData
|
||||
{
|
||||
[Comment(@"Multiplier for waifureset. Default 150.
|
||||
Formula (at the time of writing this):
|
||||
price = (waifu_price * 1.25f) + ((number_of_divorces + changes_of_heart + 2) * WaifuReset) rounded up")]
|
||||
public int WaifuReset { get; set; } = 150;
|
||||
public int WaifuReset { get; set; } = 150;
|
||||
|
||||
[Comment(@"The minimum amount of currency that you have to pay
|
||||
[Comment(@"The minimum amount of currency that you have to pay
|
||||
in order to buy a waifu who doesn't have a crush on you.
|
||||
Default is 1.1
|
||||
Example: If a waifu is worth 100, you will have to pay at least 100 * NormalClaim currency to claim her.
|
||||
(100 * 1.1 = 110)")]
|
||||
public decimal NormalClaim { get; set; } = 1.1m;
|
||||
public decimal NormalClaim { get; set; } = 1.1m;
|
||||
|
||||
[Comment(@"The minimum amount of currency that you have to pay
|
||||
[Comment(@"The minimum amount of currency that you have to pay
|
||||
in order to buy a waifu that has a crush on you.
|
||||
Default is 0.88
|
||||
Example: If a waifu is worth 100, you will have to pay at least 100 * CrushClaim currency to claim her.
|
||||
(100 * 0.88 = 88)")]
|
||||
public decimal CrushClaim { get; set; } = 0.88M;
|
||||
public decimal CrushClaim { get; set; } = 0.88M;
|
||||
|
||||
[Comment(@"When divorcing a waifu, her new value will be her current value multiplied by this number.
|
||||
[Comment(@"When divorcing a waifu, her new value will be her current value multiplied by this number.
|
||||
Default 0.75 (meaning will lose 25% of her value)")]
|
||||
public decimal DivorceNewValue { get; set; } = 0.75M;
|
||||
public decimal DivorceNewValue { get; set; } = 0.75M;
|
||||
|
||||
[Comment(@"All gift prices will be multiplied by this number.
|
||||
[Comment(@"All gift prices will be multiplied by this number.
|
||||
Default 1 (meaning no effect)")]
|
||||
public decimal AllGiftPrices { get; set; } = 1.0M;
|
||||
public decimal AllGiftPrices { get; set; } = 1.0M;
|
||||
|
||||
[Comment(@"What percentage of the value of the gift will a waifu gain when she's gifted.
|
||||
[Comment(@"What percentage of the value of the gift will a waifu gain when she's gifted.
|
||||
Default 0.95 (meaning 95%)
|
||||
Example: If a waifu is worth 1000, and she receives a gift worth 100, her new value will be 1095)")]
|
||||
public decimal GiftEffect { get; set; } = 0.95M;
|
||||
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'.
|
||||
[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 decimal NegativeGiftEffect { get; set; } = 0.50M;
|
||||
}
|
||||
|
||||
public sealed partial 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 sealed partial 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;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class WaifuItemModel
|
||||
{
|
||||
public string ItemEmoji { get; set; }
|
||||
public int Price { get; set; }
|
||||
public string Name { get; set; }
|
||||
[Cloneable]
|
||||
public sealed partial class WaifuItemModel
|
||||
{
|
||||
public string ItemEmoji { get; set; }
|
||||
public int Price { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)]
|
||||
public bool Negative { get; set; }
|
||||
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)]
|
||||
public bool Negative { get; set; }
|
||||
|
||||
public WaifuItemModel()
|
||||
{
|
||||
public WaifuItemModel()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public WaifuItemModel(string itemEmoji, int price, string name, bool negative = false)
|
||||
{
|
||||
ItemEmoji = itemEmoji;
|
||||
Price = price;
|
||||
Name = name;
|
||||
Negative = negative;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class BetRollPair
|
||||
public WaifuItemModel(string itemEmoji, int price, string name, bool negative = false)
|
||||
{
|
||||
public int WhenAbove { get; set; }
|
||||
public float MultiplyBy { get; set; }
|
||||
ItemEmoji = itemEmoji;
|
||||
Price = price;
|
||||
Name = name;
|
||||
Negative = negative;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class BetRollPair
|
||||
{
|
||||
public int WhenAbove { get; set; }
|
||||
public float MultiplyBy { get; set; }
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public enum GamblingError
|
||||
{
|
||||
public enum GamblingError
|
||||
{
|
||||
None,
|
||||
NotEnough
|
||||
}
|
||||
None,
|
||||
NotEnough
|
||||
}
|
||||
@@ -1,68 +1,64 @@
|
||||
using System;
|
||||
using Discord;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Modules;
|
||||
using Discord;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public abstract class GamblingModule<TService> : NadekoModule<TService>
|
||||
{
|
||||
public abstract class GamblingModule<TService> : NadekoModule<TService>
|
||||
{
|
||||
private readonly Lazy<GamblingConfig> _lazyConfig;
|
||||
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 Lazy<GamblingConfig>(() => gambService.Data);
|
||||
}
|
||||
|
||||
private async Task<bool> InternalCheckBet(long amount)
|
||||
{
|
||||
if (amount < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (amount < _config.MinBet)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.min_bet_limit(
|
||||
Format.Bold(_config.MinBet.ToString()) + CurrencySign));
|
||||
return false;
|
||||
}
|
||||
if (_config.MaxBet > 0 && amount > _config.MaxBet)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.max_bet_limit(
|
||||
Format.Bold(_config.MaxBet.ToString()) + CurrencySign));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Task<bool> CheckBetMandatory(long amount)
|
||||
{
|
||||
if (amount < 1)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
return InternalCheckBet(amount);
|
||||
}
|
||||
|
||||
protected Task<bool> CheckBetOptional(long amount)
|
||||
{
|
||||
if (amount == 0)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
return InternalCheckBet(amount);
|
||||
}
|
||||
protected GamblingModule(GamblingConfigService gambService)
|
||||
{
|
||||
_lazyConfig = new Lazy<GamblingConfig>(() => gambService.Data);
|
||||
}
|
||||
|
||||
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
|
||||
private async Task<bool> InternalCheckBet(long amount)
|
||||
{
|
||||
protected GamblingSubmodule(GamblingConfigService gamblingConfService) : base(gamblingConfService)
|
||||
if (amount < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (amount < _config.MinBet)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.min_bet_limit(
|
||||
Format.Bold(_config.MinBet.ToString()) + CurrencySign));
|
||||
return false;
|
||||
}
|
||||
if (_config.MaxBet > 0 && amount > _config.MaxBet)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.max_bet_limit(
|
||||
Format.Bold(_config.MaxBet.ToString()) + CurrencySign));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Task<bool> CheckBetMandatory(long amount)
|
||||
{
|
||||
if (amount < 1)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
return InternalCheckBet(amount);
|
||||
}
|
||||
|
||||
protected Task<bool> CheckBetOptional(long amount)
|
||||
{
|
||||
if (amount == 0)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
return InternalCheckBet(amount);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
|
||||
{
|
||||
protected GamblingSubmodule(GamblingConfigService gamblingConfService) : base(gamblingConfService)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class Payout
|
||||
{
|
||||
public class Payout
|
||||
{
|
||||
public string User { get; set; }
|
||||
public int Amount { get; set; }
|
||||
}
|
||||
}
|
||||
public string User { get; set; }
|
||||
public int Amount { get; set; }
|
||||
}
|
||||
@@ -1,140 +1,137 @@
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class RollDuelGame
|
||||
{
|
||||
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 State
|
||||
{
|
||||
public ulong P1 { get; }
|
||||
public ulong P2 { get; }
|
||||
Waiting,
|
||||
Running,
|
||||
Ended,
|
||||
}
|
||||
|
||||
private readonly ulong _botId;
|
||||
|
||||
public long Amount { get; }
|
||||
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
public enum State
|
||||
{
|
||||
Waiting,
|
||||
Running,
|
||||
Ended,
|
||||
}
|
||||
|
||||
public enum Reason
|
||||
{
|
||||
Normal,
|
||||
NoFunds,
|
||||
Timeout,
|
||||
}
|
||||
public enum Reason
|
||||
{
|
||||
Normal,
|
||||
NoFunds,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
private readonly Timer _timeoutTimer;
|
||||
private readonly NadekoRandom _rng = new NadekoRandom();
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
private readonly Timer _timeoutTimer;
|
||||
private readonly NadekoRandom _rng = new NadekoRandom();
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
|
||||
public event Func<RollDuelGame, Task> OnGameTick;
|
||||
public event Func<RollDuelGame, Reason, Task> OnEnded;
|
||||
public event Func<RollDuelGame, Task> OnGameTick;
|
||||
public event Func<RollDuelGame, Reason, Task> OnEnded;
|
||||
|
||||
public List<(int, int)> Rolls { get; } = new List<(int, int)>();
|
||||
public State CurrentState { get; private set; }
|
||||
public ulong Winner { get; private set; }
|
||||
public List<(int, int)> Rolls { get; } = new List<(int, int)>();
|
||||
public State CurrentState { get; private set; }
|
||||
public ulong Winner { get; private set; }
|
||||
|
||||
public RollDuelGame(ICurrencyService cs, ulong botId, ulong p1, ulong p2, long amount)
|
||||
{
|
||||
this.P1 = p1;
|
||||
this.P2 = p2;
|
||||
this._botId = botId;
|
||||
this.Amount = amount;
|
||||
_cs = cs;
|
||||
public RollDuelGame(ICurrencyService cs, ulong botId, ulong p1, ulong p2, long amount)
|
||||
{
|
||||
this.P1 = p1;
|
||||
this.P2 = p2;
|
||||
this._botId = botId;
|
||||
this.Amount = amount;
|
||||
_cs = cs;
|
||||
|
||||
_timeoutTimer = new Timer(async delegate
|
||||
{
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (CurrentState != State.Waiting)
|
||||
return;
|
||||
CurrentState = State.Ended;
|
||||
await (OnEnded?.Invoke(this, Reason.Timeout)).ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}, null, TimeSpan.FromSeconds(15), TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
|
||||
public async Task StartGame()
|
||||
_timeoutTimer = new Timer(async delegate
|
||||
{
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (CurrentState != State.Waiting)
|
||||
return;
|
||||
_timeoutTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
CurrentState = State.Running;
|
||||
CurrentState = State.Ended;
|
||||
await (OnEnded?.Invoke(this, Reason.Timeout)).ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
|
||||
if(!await _cs.RemoveAsync(P1, "Roll Duel", Amount).ConfigureAwait(false))
|
||||
{
|
||||
await (OnEnded?.Invoke(this, Reason.NoFunds)).ConfigureAwait(false);
|
||||
CurrentState = State.Ended;
|
||||
return;
|
||||
}
|
||||
if(!await _cs.RemoveAsync(P2, "Roll Duel", Amount).ConfigureAwait(false))
|
||||
{
|
||||
await _cs.AddAsync(P1, "Roll Duel - refund", Amount).ConfigureAwait(false);
|
||||
await (OnEnded?.Invoke(this, Reason.NoFunds)).ConfigureAwait(false);
|
||||
CurrentState = State.Ended;
|
||||
return;
|
||||
}
|
||||
|
||||
int n1, n2;
|
||||
do
|
||||
{
|
||||
n1 = _rng.Next(0, 5);
|
||||
n2 = _rng.Next(0, 5);
|
||||
Rolls.Add((n1, n2));
|
||||
if (n1 != n2)
|
||||
{
|
||||
if (n1 > n2)
|
||||
{
|
||||
Winner = P1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Winner = P2;
|
||||
}
|
||||
var won = (long)(Amount * 2 * 0.98f);
|
||||
await _cs.AddAsync(Winner, "Roll Duel win", won)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await _cs.AddAsync(_botId, "Roll Duel fee", Amount * 2 - won)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
try { await (OnGameTick?.Invoke(this)).ConfigureAwait(false); } catch { }
|
||||
await Task.Delay(2500).ConfigureAwait(false);
|
||||
if (n1 != n2)
|
||||
break;
|
||||
}
|
||||
while (true);
|
||||
CurrentState = State.Ended;
|
||||
await (OnEnded?.Invoke(this, Reason.Normal)).ConfigureAwait(false);
|
||||
}
|
||||
}, null, TimeSpan.FromSeconds(15), TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
|
||||
public struct RollDuelChallenge
|
||||
public async Task StartGame()
|
||||
{
|
||||
public ulong Player1 { get; set; }
|
||||
public ulong Player2 { get; set; }
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (CurrentState != State.Waiting)
|
||||
return;
|
||||
_timeoutTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
CurrentState = State.Running;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
|
||||
if(!await _cs.RemoveAsync(P1, "Roll Duel", Amount).ConfigureAwait(false))
|
||||
{
|
||||
await (OnEnded?.Invoke(this, Reason.NoFunds)).ConfigureAwait(false);
|
||||
CurrentState = State.Ended;
|
||||
return;
|
||||
}
|
||||
if(!await _cs.RemoveAsync(P2, "Roll Duel", Amount).ConfigureAwait(false))
|
||||
{
|
||||
await _cs.AddAsync(P1, "Roll Duel - refund", Amount).ConfigureAwait(false);
|
||||
await (OnEnded?.Invoke(this, Reason.NoFunds)).ConfigureAwait(false);
|
||||
CurrentState = State.Ended;
|
||||
return;
|
||||
}
|
||||
|
||||
int n1, n2;
|
||||
do
|
||||
{
|
||||
n1 = _rng.Next(0, 5);
|
||||
n2 = _rng.Next(0, 5);
|
||||
Rolls.Add((n1, n2));
|
||||
if (n1 != n2)
|
||||
{
|
||||
if (n1 > n2)
|
||||
{
|
||||
Winner = P1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Winner = P2;
|
||||
}
|
||||
var won = (long)(Amount * 2 * 0.98f);
|
||||
await _cs.AddAsync(Winner, "Roll Duel win", won)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await _cs.AddAsync(_botId, "Roll Duel fee", Amount * 2 - won)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
try { await (OnGameTick?.Invoke(this)).ConfigureAwait(false); } catch { }
|
||||
await Task.Delay(2500).ConfigureAwait(false);
|
||||
if (n1 != n2)
|
||||
break;
|
||||
}
|
||||
while (true);
|
||||
CurrentState = State.Ended;
|
||||
await (OnEnded?.Invoke(this, Reason.Normal)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public struct RollDuelChallenge
|
||||
{
|
||||
public ulong Player1 { get; set; }
|
||||
public ulong Player2 { get; set; }
|
||||
}
|
||||
@@ -1,44 +1,41 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.Slot
|
||||
namespace NadekoBot.Modules.Gambling.Common.Slot;
|
||||
|
||||
public class SlotGame
|
||||
{
|
||||
public class SlotGame
|
||||
public class Result
|
||||
{
|
||||
public class Result
|
||||
public float Multiplier { get; }
|
||||
public int[] Rolls { get; }
|
||||
|
||||
public Result(float multiplier, int[] rolls)
|
||||
{
|
||||
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 multi = 0;
|
||||
|
||||
if (rolls.All(x => x == 5))
|
||||
multi = 30;
|
||||
else if (rolls.All(x => x == rolls[0]))
|
||||
multi = 10;
|
||||
else if (rolls.Count(x => x == 5) == 2)
|
||||
multi = 4;
|
||||
else if (rolls.Any(x => x == 5))
|
||||
multi = 1;
|
||||
|
||||
return new Result(multi, 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 multi = 0;
|
||||
|
||||
if (rolls.All(x => x == 5))
|
||||
multi = 30;
|
||||
else if (rolls.All(x => x == rolls[0]))
|
||||
multi = 10;
|
||||
else if (rolls.Count(x => x == 5) == 2)
|
||||
multi = 4;
|
||||
else if (rolls.Any(x => x == 5))
|
||||
multi = 1;
|
||||
|
||||
return new Result(multi, rolls);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
public class SlotResponse
|
||||
{
|
||||
public class SlotResponse
|
||||
{
|
||||
public float Multiplier { get; set; }
|
||||
public long Won { get; set; }
|
||||
public List<int> Rolls { get; set; } = new List<int>();
|
||||
public GamblingError Error { get; set; }
|
||||
}
|
||||
public float Multiplier { get; set; }
|
||||
public long Won { get; set; }
|
||||
public List<int> Rolls { get; set; } = new List<int>();
|
||||
public GamblingError Error { get; set; }
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
|
||||
public enum AffinityTitle
|
||||
{
|
||||
public enum AffinityTitle
|
||||
{
|
||||
Pure,
|
||||
Faithful,
|
||||
Playful,
|
||||
Cheater,
|
||||
Tainted,
|
||||
Corrupted,
|
||||
Lewd,
|
||||
Sloot,
|
||||
Depraved,
|
||||
Harlot
|
||||
}
|
||||
}
|
||||
Pure,
|
||||
Faithful,
|
||||
Playful,
|
||||
Cheater,
|
||||
Tainted,
|
||||
Corrupted,
|
||||
Lewd,
|
||||
Sloot,
|
||||
Depraved,
|
||||
Harlot
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
|
||||
public enum ClaimTitle
|
||||
{
|
||||
public enum ClaimTitle
|
||||
{
|
||||
Lonely,
|
||||
Devoted,
|
||||
Rookie,
|
||||
Schemer,
|
||||
Dilettante,
|
||||
Intermediate,
|
||||
Seducer,
|
||||
Expert,
|
||||
Veteran,
|
||||
Incubis,
|
||||
Harem_King,
|
||||
Harem_God,
|
||||
}
|
||||
}
|
||||
Lonely,
|
||||
Devoted,
|
||||
Rookie,
|
||||
Schemer,
|
||||
Dilettante,
|
||||
Intermediate,
|
||||
Seducer,
|
||||
Expert,
|
||||
Veteran,
|
||||
Incubis,
|
||||
Harem_King,
|
||||
Harem_God,
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
|
||||
public enum DivorceResult
|
||||
{
|
||||
public enum DivorceResult
|
||||
{
|
||||
Success,
|
||||
SucessWithPenalty,
|
||||
NotYourWife,
|
||||
Cooldown
|
||||
}
|
||||
}
|
||||
Success,
|
||||
SucessWithPenalty,
|
||||
NotYourWife,
|
||||
Cooldown
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu
|
||||
namespace NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
|
||||
public enum WaifuClaimResult
|
||||
{
|
||||
public enum WaifuClaimResult
|
||||
{
|
||||
Success,
|
||||
NotEnoughFunds,
|
||||
InsufficientAmount
|
||||
}
|
||||
}
|
||||
Success,
|
||||
NotEnoughFunds,
|
||||
InsufficientAmount
|
||||
}
|
||||
@@ -1,47 +1,45 @@
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.WheelOfFortune
|
||||
namespace NadekoBot.Modules.Gambling.Common.WheelOfFortune;
|
||||
|
||||
public class WheelOfFortuneGame
|
||||
{
|
||||
public class WheelOfFortuneGame
|
||||
public class Result
|
||||
{
|
||||
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)
|
||||
{
|
||||
_rng = new NadekoRandom();
|
||||
_cs = cs;
|
||||
_bet = bet;
|
||||
_config = config;
|
||||
_userId = userId;
|
||||
}
|
||||
|
||||
public async Task<Result> SpinAsync()
|
||||
{
|
||||
var result = _rng.Next(0, _config.WheelOfFortune.Multipliers.Length);
|
||||
|
||||
var amount = (long)(_bet * _config.WheelOfFortune.Multipliers[result]);
|
||||
|
||||
if (amount > 0)
|
||||
await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, gamble: true).ConfigureAwait(false);
|
||||
|
||||
return new Result
|
||||
{
|
||||
Index = result,
|
||||
Amount = amount,
|
||||
};
|
||||
}
|
||||
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)
|
||||
{
|
||||
_rng = new NadekoRandom();
|
||||
_cs = cs;
|
||||
_bet = bet;
|
||||
_config = config;
|
||||
_userId = userId;
|
||||
}
|
||||
|
||||
public async Task<Result> SpinAsync()
|
||||
{
|
||||
var result = _rng.Next(0, _config.WheelOfFortune.Multipliers.Length);
|
||||
|
||||
var amount = (long)(_bet * _config.WheelOfFortune.Multipliers[result]);
|
||||
|
||||
if (amount > 0)
|
||||
await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, gamble: true).ConfigureAwait(false);
|
||||
|
||||
return new Result
|
||||
{
|
||||
Index = result,
|
||||
Amount = amount,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,400 +1,397 @@
|
||||
using CommandLine;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Services;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common.Connect4
|
||||
namespace NadekoBot.Modules.Gambling.Common.Connect4;
|
||||
|
||||
public sealed class Connect4Game : IDisposable
|
||||
{
|
||||
public sealed class Connect4Game : IDisposable
|
||||
public enum Phase
|
||||
{
|
||||
public enum Phase
|
||||
Joining, // waiting for second player to join
|
||||
P1Move,
|
||||
P2Move,
|
||||
Ended,
|
||||
}
|
||||
|
||||
public enum Field //temporary most likely
|
||||
{
|
||||
Empty,
|
||||
P1,
|
||||
P2,
|
||||
}
|
||||
|
||||
public enum Result
|
||||
{
|
||||
Draw,
|
||||
CurrentPlayerWon,
|
||||
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;
|
||||
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
private readonly Options _options;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly NadekoRandom _rng;
|
||||
|
||||
private Timer _playerTimeoutTimer;
|
||||
|
||||
/* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
*/
|
||||
|
||||
public Connect4Game(ulong userId, string userName, Options options, ICurrencyService cs)
|
||||
{
|
||||
_players[0] = (userId, userName);
|
||||
_options = options;
|
||||
_cs = cs;
|
||||
|
||||
_rng = new NadekoRandom();
|
||||
for (int i = 0; i < NumberOfColumns * NumberOfRows; i++)
|
||||
{
|
||||
Joining, // waiting for second player to join
|
||||
P1Move,
|
||||
P2Move,
|
||||
Ended,
|
||||
_gameState[i] = Field.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Field //temporary most likely
|
||||
public void Initialize()
|
||||
{
|
||||
if (CurrentPhase != Phase.Joining)
|
||||
return;
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
Empty,
|
||||
P1,
|
||||
P2,
|
||||
}
|
||||
|
||||
public enum Result
|
||||
{
|
||||
Draw,
|
||||
CurrentPlayerWon,
|
||||
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;
|
||||
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
private readonly Options _options;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly NadekoRandom _rng;
|
||||
|
||||
private Timer _playerTimeoutTimer;
|
||||
|
||||
/* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
*/
|
||||
|
||||
public Connect4Game(ulong userId, string userName, Options options, ICurrencyService cs)
|
||||
{
|
||||
_players[0] = (userId, userName);
|
||||
_options = options;
|
||||
_cs = cs;
|
||||
|
||||
_rng = new NadekoRandom();
|
||||
for (int i = 0; i < NumberOfColumns * NumberOfRows; i++)
|
||||
await Task.Delay(15000).ConfigureAwait(false);
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_gameState[i] = Field.Empty;
|
||||
if (_players[1] is null)
|
||||
{
|
||||
var __ = OnGameFailedToStart?.Invoke(this);
|
||||
CurrentPhase = Phase.Ended;
|
||||
await _cs.AddAsync(_players[0].Value.UserId, "Connect4-refund", _options.Bet, true).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
public async Task<bool> Join(ulong userId, string userName, int bet)
|
||||
{
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (CurrentPhase != Phase.Joining)
|
||||
return;
|
||||
var _ = Task.Run(async () =>
|
||||
if (CurrentPhase != Phase.Joining) //can't join if its not a joining phase
|
||||
return false;
|
||||
|
||||
if (_players[0].Value.UserId == userId) // same user can't join own game
|
||||
return false;
|
||||
|
||||
if (bet != _options.Bet) // can't join if bet amount is not the same
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(userId, "Connect4-bet", bet, true).ConfigureAwait(false)) // user doesn't have enough money to gamble
|
||||
return false;
|
||||
|
||||
if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player
|
||||
{
|
||||
_players[1] = _players[0];
|
||||
_players[0] = (userId, userName);
|
||||
}
|
||||
else //else join as a second player
|
||||
_players[1] = (userId, userName);
|
||||
|
||||
CurrentPhase = Phase.P1Move; //start the game
|
||||
_playerTimeoutTimer = new Timer(async state =>
|
||||
{
|
||||
await Task.Delay(15000).ConfigureAwait(false);
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_players[1] is null)
|
||||
{
|
||||
var __ = OnGameFailedToStart?.Invoke(this);
|
||||
CurrentPhase = Phase.Ended;
|
||||
await _cs.AddAsync(_players[0].Value.UserId, "Connect4-refund", _options.Bet, true).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}, null, TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
|
||||
var __ = OnGameStateUpdated?.Invoke(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
}
|
||||
|
||||
public async Task<bool> Join(ulong userId, string userName, int bet)
|
||||
public async Task<bool> Input(ulong userId, int inputCol)
|
||||
{
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (CurrentPhase != Phase.Joining) //can't join if its not a joining phase
|
||||
return false;
|
||||
inputCol -= 1;
|
||||
if (CurrentPhase == Phase.Ended || CurrentPhase == Phase.Joining)
|
||||
return false;
|
||||
|
||||
if (_players[0].Value.UserId == userId) // same user can't join own game
|
||||
return false;
|
||||
if (!((_players[0].Value.UserId == userId && CurrentPhase == Phase.P1Move)
|
||||
|| (_players[1].Value.UserId == userId && CurrentPhase == Phase.P2Move)))
|
||||
return false;
|
||||
|
||||
if (bet != _options.Bet) // can't join if bet amount is not the same
|
||||
return false;
|
||||
if (inputCol < 0 || inputCol > NumberOfColumns) //invalid input
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(userId, "Connect4-bet", bet, true).ConfigureAwait(false)) // user doesn't have enough money to gamble
|
||||
return false;
|
||||
if (IsColumnFull(inputCol)) //can't play there event?
|
||||
return false;
|
||||
|
||||
if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player
|
||||
{
|
||||
_players[1] = _players[0];
|
||||
_players[0] = (userId, userName);
|
||||
}
|
||||
else //else join as a second player
|
||||
_players[1] = (userId, userName);
|
||||
|
||||
CurrentPhase = Phase.P1Move; //start the game
|
||||
_playerTimeoutTimer = new Timer(async state =>
|
||||
{
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
}, null, TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
|
||||
var __ = OnGameStateUpdated?.Invoke(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
}
|
||||
|
||||
public async Task<bool> Input(ulong userId, int inputCol)
|
||||
{
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
inputCol -= 1;
|
||||
if (CurrentPhase == Phase.Ended || CurrentPhase == Phase.Joining)
|
||||
return false;
|
||||
|
||||
if (!((_players[0].Value.UserId == userId && CurrentPhase == Phase.P1Move)
|
||||
|| (_players[1].Value.UserId == userId && CurrentPhase == Phase.P2Move)))
|
||||
return false;
|
||||
|
||||
if (inputCol < 0 || inputCol > NumberOfColumns) //invalid input
|
||||
return false;
|
||||
|
||||
if (IsColumnFull(inputCol)) //can't play there event?
|
||||
return false;
|
||||
|
||||
var start = NumberOfRows * inputCol;
|
||||
for (int 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
|
||||
|
||||
for (int i = 0; i < NumberOfRows - 3; i++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
for (int j = 0; j < NumberOfColumns; j++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
var first = _gameState[i + j * NumberOfRows];
|
||||
if (first != Field.Empty)
|
||||
{
|
||||
for (int k = 1; k < 4; k++)
|
||||
{
|
||||
var next = _gameState[i + k + j * NumberOfRows];
|
||||
if (next == first)
|
||||
{
|
||||
if (k == 3)
|
||||
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
|
||||
else
|
||||
continue;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// i'll go [0-1] in columns (and through all rows) and check to the right if 4 are connected
|
||||
for (int i = 0; i < NumberOfColumns - 3; i++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
for (int j = 0; j < NumberOfRows; j++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
var first = _gameState[j + i * NumberOfRows];
|
||||
if (first != Field.Empty)
|
||||
{
|
||||
for (int k = 1; k < 4; k++)
|
||||
{
|
||||
var next = _gameState[j + (i + k) * NumberOfRows];
|
||||
if (next == first)
|
||||
if (k == 3)
|
||||
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
|
||||
else
|
||||
continue;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//need to check diagonal now
|
||||
for (int col = 0; col < NumberOfColumns; col++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
for (int row = 0; row < NumberOfRows; row++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
var first = _gameState[row + col * NumberOfRows];
|
||||
|
||||
if (first != Field.Empty)
|
||||
{
|
||||
var same = 1;
|
||||
|
||||
//top left
|
||||
for (int i = 1; i < 4; i++)
|
||||
{
|
||||
//while going top left, rows are increasing, columns are decreasing
|
||||
var curRow = row + i;
|
||||
var curCol = col - i;
|
||||
|
||||
//check if current values are in range
|
||||
if (curRow >= NumberOfRows || curRow < 0)
|
||||
break;
|
||||
if (curCol < 0 || curCol >= NumberOfColumns)
|
||||
break;
|
||||
|
||||
var cur = _gameState[curRow + curCol * NumberOfRows];
|
||||
if (cur == first)
|
||||
same++;
|
||||
else break;
|
||||
}
|
||||
|
||||
if (same == 4)
|
||||
{
|
||||
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
|
||||
break;
|
||||
}
|
||||
|
||||
same = 1;
|
||||
|
||||
//top right
|
||||
for (int i = 1; i < 4; i++)
|
||||
{
|
||||
//while going top right, rows are increasing, columns are increasing
|
||||
var curRow = row + i;
|
||||
var curCol = col + i;
|
||||
|
||||
//check if current values are in range
|
||||
if (curRow >= NumberOfRows || curRow < 0)
|
||||
break;
|
||||
if (curCol < 0 || curCol >= NumberOfColumns)
|
||||
break;
|
||||
|
||||
var cur = _gameState[curRow + curCol * NumberOfRows];
|
||||
if (cur == first)
|
||||
same++;
|
||||
else break;
|
||||
}
|
||||
|
||||
if (same == 4)
|
||||
{
|
||||
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check draw? if it's even possible
|
||||
if (_gameState.All(x => x != Field.Empty))
|
||||
{
|
||||
EndGame(Result.Draw, null);
|
||||
}
|
||||
|
||||
if (CurrentPhase != Phase.Ended)
|
||||
{
|
||||
if (CurrentPhase == Phase.P1Move)
|
||||
CurrentPhase = Phase.P2Move;
|
||||
else
|
||||
CurrentPhase = Phase.P1Move;
|
||||
|
||||
ResetTimer();
|
||||
}
|
||||
var _ = OnGameStateUpdated?.Invoke(this);
|
||||
return true;
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
}
|
||||
|
||||
private void ResetTimer()
|
||||
{
|
||||
_playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
|
||||
}
|
||||
|
||||
private void EndGame(Result result, ulong? winId)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
return;
|
||||
var _ = OnGameEnded?.Invoke(this, result);
|
||||
CurrentPhase = Phase.Ended;
|
||||
|
||||
if (result == Result.Draw)
|
||||
{
|
||||
_cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", this._options.Bet, true);
|
||||
_cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", this._options.Bet, true);
|
||||
return;
|
||||
}
|
||||
if (winId != null)
|
||||
_cs.AddAsync(winId.Value, "Connnect4-win", (long)(this._options.Bet * 1.98), true);
|
||||
}
|
||||
|
||||
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;
|
||||
var start = NumberOfRows * inputCol;
|
||||
for (int i = start; i < start + NumberOfRows; i++)
|
||||
{
|
||||
if (_gameState[i] == Field.Empty)
|
||||
return false;
|
||||
{
|
||||
_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
|
||||
|
||||
for (int i = 0; i < NumberOfRows - 3; i++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
for (int j = 0; j < NumberOfColumns; j++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
var first = _gameState[i + j * NumberOfRows];
|
||||
if (first != Field.Empty)
|
||||
{
|
||||
for (int k = 1; k < 4; k++)
|
||||
{
|
||||
var next = _gameState[i + k + j * NumberOfRows];
|
||||
if (next == first)
|
||||
{
|
||||
if (k == 3)
|
||||
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
|
||||
else
|
||||
continue;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// i'll go [0-1] in columns (and through all rows) and check to the right if 4 are connected
|
||||
for (int i = 0; i < NumberOfColumns - 3; i++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
for (int j = 0; j < NumberOfRows; j++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
var first = _gameState[j + i * NumberOfRows];
|
||||
if (first != Field.Empty)
|
||||
{
|
||||
for (int k = 1; k < 4; k++)
|
||||
{
|
||||
var next = _gameState[j + (i + k) * NumberOfRows];
|
||||
if (next == first)
|
||||
if (k == 3)
|
||||
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
|
||||
else
|
||||
continue;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//need to check diagonal now
|
||||
for (int col = 0; col < NumberOfColumns; col++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
for (int row = 0; row < NumberOfRows; row++)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
break;
|
||||
|
||||
var first = _gameState[row + col * NumberOfRows];
|
||||
|
||||
if (first != Field.Empty)
|
||||
{
|
||||
var same = 1;
|
||||
|
||||
//top left
|
||||
for (int i = 1; i < 4; i++)
|
||||
{
|
||||
//while going top left, rows are increasing, columns are decreasing
|
||||
var curRow = row + i;
|
||||
var curCol = col - i;
|
||||
|
||||
//check if current values are in range
|
||||
if (curRow >= NumberOfRows || curRow < 0)
|
||||
break;
|
||||
if (curCol < 0 || curCol >= NumberOfColumns)
|
||||
break;
|
||||
|
||||
var cur = _gameState[curRow + curCol * NumberOfRows];
|
||||
if (cur == first)
|
||||
same++;
|
||||
else break;
|
||||
}
|
||||
|
||||
if (same == 4)
|
||||
{
|
||||
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
|
||||
break;
|
||||
}
|
||||
|
||||
same = 1;
|
||||
|
||||
//top right
|
||||
for (int i = 1; i < 4; i++)
|
||||
{
|
||||
//while going top right, rows are increasing, columns are increasing
|
||||
var curRow = row + i;
|
||||
var curCol = col + i;
|
||||
|
||||
//check if current values are in range
|
||||
if (curRow >= NumberOfRows || curRow < 0)
|
||||
break;
|
||||
if (curCol < 0 || curCol >= NumberOfColumns)
|
||||
break;
|
||||
|
||||
var cur = _gameState[curRow + curCol * NumberOfRows];
|
||||
if (cur == first)
|
||||
same++;
|
||||
else break;
|
||||
}
|
||||
|
||||
if (same == 4)
|
||||
{
|
||||
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check draw? if it's even possible
|
||||
if (_gameState.All(x => x != Field.Empty))
|
||||
{
|
||||
EndGame(Result.Draw, null);
|
||||
}
|
||||
|
||||
if (CurrentPhase != Phase.Ended)
|
||||
{
|
||||
if (CurrentPhase == Phase.P1Move)
|
||||
CurrentPhase = Phase.P2Move;
|
||||
else
|
||||
CurrentPhase = Phase.P1Move;
|
||||
|
||||
ResetTimer();
|
||||
}
|
||||
var _ = OnGameStateUpdated?.Invoke(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
OnGameFailedToStart = null;
|
||||
OnGameStateUpdated = null;
|
||||
OnGameEnded = null;
|
||||
_playerTimeoutTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
|
||||
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (TurnTimer < 5 || TurnTimer > 60)
|
||||
TurnTimer = 15;
|
||||
|
||||
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;
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetTimer()
|
||||
{
|
||||
_playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
|
||||
}
|
||||
|
||||
private void EndGame(Result result, ulong? winId)
|
||||
{
|
||||
if (CurrentPhase == Phase.Ended)
|
||||
return;
|
||||
var _ = OnGameEnded?.Invoke(this, result);
|
||||
CurrentPhase = Phase.Ended;
|
||||
|
||||
if (result == Result.Draw)
|
||||
{
|
||||
_cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", this._options.Bet, true);
|
||||
_cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", this._options.Bet, true);
|
||||
return;
|
||||
}
|
||||
if (winId != null)
|
||||
_cs.AddAsync(winId.Value, "Connnect4-win", (long)(this._options.Bet * 1.98), true);
|
||||
}
|
||||
|
||||
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 (int i = start; i < start + NumberOfRows; i++)
|
||||
{
|
||||
if (_gameState[i] == Field.Empty)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
OnGameFailedToStart = null;
|
||||
OnGameStateUpdated = null;
|
||||
OnGameEnded = null;
|
||||
_playerTimeoutTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
|
||||
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (TurnTimer < 5 || TurnTimer > 60)
|
||||
TurnTimer = 15;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -6,204 +6,202 @@ using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Gambling.Common.Connect4;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
[Group]
|
||||
public class Connect4Commands : GamblingSubmodule<GamblingService>
|
||||
{
|
||||
[Group]
|
||||
public class Connect4Commands : GamblingSubmodule<GamblingService>
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICurrencyService _cs;
|
||||
private static readonly string[] numbers = new string[] { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:" };
|
||||
|
||||
public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
|
||||
: base(gamb)
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICurrencyService _cs;
|
||||
private static readonly string[] numbers = new string[] { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:" };
|
||||
_client = client;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
|
||||
: base(gamb)
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptionsAttribute(typeof(Connect4Game.Options))]
|
||||
public async Task Connect4(params string[] args)
|
||||
{
|
||||
var (options, _) = OptionsParser.ParseFrom(new Connect4Game.Options(), args);
|
||||
if (!await CheckBetOptional(options.Bet).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options, _cs);
|
||||
Connect4Game game;
|
||||
if ((game = _service.Connect4Games.GetOrAdd(ctx.Channel.Id, newGame)) != newGame)
|
||||
{
|
||||
_client = client;
|
||||
_cs = cs;
|
||||
if (game.CurrentPhase != Connect4Game.Phase.Joining)
|
||||
return;
|
||||
|
||||
newGame.Dispose();
|
||||
//means game already exists, try to join
|
||||
var joined = await game.Join(ctx.User.Id, ctx.User.ToString(), options.Bet).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptionsAttribute(typeof(Connect4Game.Options))]
|
||||
public async Task Connect4(params string[] args)
|
||||
if (options.Bet > 0)
|
||||
{
|
||||
var (options, _) = OptionsParser.ParseFrom(new Connect4Game.Options(), args);
|
||||
if (!await CheckBetOptional(options.Bet).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options, _cs);
|
||||
Connect4Game game;
|
||||
if ((game = _service.Connect4Games.GetOrAdd(ctx.Channel.Id, newGame)) != newGame)
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id, "Connect4-bet", options.Bet, true).ConfigureAwait(false))
|
||||
{
|
||||
if (game.CurrentPhase != Connect4Game.Phase.Joining)
|
||||
return;
|
||||
|
||||
newGame.Dispose();
|
||||
//means game already exists, try to join
|
||||
var joined = await game.Join(ctx.User.Id, ctx.User.ToString(), options.Bet).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
_service.Connect4Games.TryRemove(ctx.Channel.Id, out _);
|
||||
game.Dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.Bet > 0)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id, "Connect4-bet", options.Bet, true).ConfigureAwait(false))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
_service.Connect4Games.TryRemove(ctx.Channel.Id, out _);
|
||||
game.Dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
game.OnGameStateUpdated += Game_OnGameStateUpdated;
|
||||
game.OnGameFailedToStart += Game_OnGameFailedToStart;
|
||||
game.OnGameEnded += Game_OnGameEnded;
|
||||
_client.MessageReceived += _client_MessageReceived;
|
||||
|
||||
game.OnGameStateUpdated += Game_OnGameStateUpdated;
|
||||
game.OnGameFailedToStart += Game_OnGameFailedToStart;
|
||||
game.OnGameEnded += Game_OnGameEnded;
|
||||
_client.MessageReceived += _client_MessageReceived;
|
||||
game.Initialize();
|
||||
if (options.Bet == 0)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.connect4_created).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.connect4_created_bet(options.Bet + CurrencySign));
|
||||
}
|
||||
|
||||
game.Initialize();
|
||||
if (options.Bet == 0)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.connect4_created).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.connect4_created_bet(options.Bet + CurrencySign));
|
||||
}
|
||||
|
||||
Task _client_MessageReceived(SocketMessage arg)
|
||||
{
|
||||
if (ctx.Channel.Id != arg.Channel.Id)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
bool success = false;
|
||||
if (int.TryParse(arg.Content, out var col))
|
||||
{
|
||||
success = await game.Input(arg.Author.Id, col).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (success)
|
||||
try { await arg.DeleteAsync().ConfigureAwait(false); } catch { }
|
||||
else
|
||||
{
|
||||
if (game.CurrentPhase == Connect4Game.Phase.Joining
|
||||
|| game.CurrentPhase == Connect4Game.Phase.Ended)
|
||||
{
|
||||
return;
|
||||
}
|
||||
RepostCounter++;
|
||||
if (RepostCounter == 0)
|
||||
try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()).ConfigureAwait(false); } catch { }
|
||||
}
|
||||
});
|
||||
Task _client_MessageReceived(SocketMessage arg)
|
||||
{
|
||||
if (ctx.Channel.Id != arg.Channel.Id)
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task Game_OnGameFailedToStart(Connect4Game arg)
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
|
||||
bool success = false;
|
||||
if (int.TryParse(arg.Content, out var col))
|
||||
{
|
||||
_client.MessageReceived -= _client_MessageReceived;
|
||||
toDispose.Dispose();
|
||||
}
|
||||
return ErrorLocalizedAsync(strs.connect4_failed_to_start);
|
||||
}
|
||||
|
||||
Task Game_OnGameEnded(Connect4Game arg, Connect4Game.Result result)
|
||||
{
|
||||
if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
|
||||
{
|
||||
_client.MessageReceived -= _client_MessageReceived;
|
||||
toDispose.Dispose();
|
||||
success = await game.Input(arg.Author.Id, col).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
string title;
|
||||
if (result == Connect4Game.Result.CurrentPlayerWon)
|
||||
{
|
||||
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)));
|
||||
}
|
||||
if (success)
|
||||
try { await arg.DeleteAsync().ConfigureAwait(false); } catch { }
|
||||
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 < 0 || value > 7)
|
||||
_repostCounter = 0;
|
||||
else _repostCounter = value;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Game_OnGameStateUpdated(Connect4Game game)
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor();
|
||||
|
||||
|
||||
if (msg is null)
|
||||
msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
else
|
||||
await msg.ModifyAsync(x => x.Embed = embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string GetGameStateText(Connect4Game game)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (game.CurrentPhase == Connect4Game.Phase.P1Move ||
|
||||
game.CurrentPhase == Connect4Game.Phase.P2Move)
|
||||
sb.AppendLine(GetText(strs.connect4_player_to_move(Format.Bold(game.CurrentPlayer.Username))));
|
||||
|
||||
for (int i = Connect4Game.NumberOfRows; i > 0; i--)
|
||||
{
|
||||
for (int j = 0; j < Connect4Game.NumberOfColumns; j++)
|
||||
{
|
||||
var cur = game.GameState[i + (j * Connect4Game.NumberOfRows) - 1];
|
||||
|
||||
if (cur == Connect4Game.Field.Empty)
|
||||
sb.Append("⚫"); //black circle
|
||||
else if (cur == Connect4Game.Field.P1)
|
||||
sb.Append("🔴"); //red circle
|
||||
else
|
||||
sb.Append("🔵"); //blue circle
|
||||
if (game.CurrentPhase == Connect4Game.Phase.Joining
|
||||
|| game.CurrentPhase == Connect4Game.Phase.Ended)
|
||||
{
|
||||
return;
|
||||
}
|
||||
RepostCounter++;
|
||||
if (RepostCounter == 0)
|
||||
try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()).ConfigureAwait(false); } catch { }
|
||||
}
|
||||
sb.AppendLine();
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task Game_OnGameFailedToStart(Connect4Game arg)
|
||||
{
|
||||
if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
|
||||
{
|
||||
_client.MessageReceived -= _client_MessageReceived;
|
||||
toDispose.Dispose();
|
||||
}
|
||||
return ErrorLocalizedAsync(strs.connect4_failed_to_start);
|
||||
}
|
||||
|
||||
Task Game_OnGameEnded(Connect4Game arg, Connect4Game.Result result)
|
||||
{
|
||||
if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
|
||||
{
|
||||
_client.MessageReceived -= _client_MessageReceived;
|
||||
toDispose.Dispose();
|
||||
}
|
||||
|
||||
for (int i = 0; i < Connect4Game.NumberOfColumns; i++)
|
||||
string title;
|
||||
if (result == Connect4Game.Result.CurrentPlayerWon)
|
||||
{
|
||||
sb.Append(numbers[i]);
|
||||
title = GetText(strs.connect4_won(Format.Bold(arg.CurrentPlayer.Username), Format.Bold(arg.OtherPlayer.Username)));
|
||||
}
|
||||
return sb.ToString();
|
||||
else if (result == Connect4Game.Result.OtherPlayerWon)
|
||||
{
|
||||
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 < 0 || value > 7)
|
||||
_repostCounter = 0;
|
||||
else _repostCounter = value;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Game_OnGameStateUpdated(Connect4Game game)
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor();
|
||||
|
||||
|
||||
if (msg is null)
|
||||
msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
else
|
||||
await msg.ModifyAsync(x => x.Embed = embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string GetGameStateText(Connect4Game game)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (game.CurrentPhase == Connect4Game.Phase.P1Move ||
|
||||
game.CurrentPhase == Connect4Game.Phase.P2Move)
|
||||
sb.AppendLine(GetText(strs.connect4_player_to_move(Format.Bold(game.CurrentPlayer.Username))));
|
||||
|
||||
for (int i = Connect4Game.NumberOfRows; i > 0; i--)
|
||||
{
|
||||
for (int j = 0; j < Connect4Game.NumberOfColumns; j++)
|
||||
{
|
||||
var cur = game.GameState[i + (j * Connect4Game.NumberOfRows) - 1];
|
||||
|
||||
if (cur == Connect4Game.Field.Empty)
|
||||
sb.Append("⚫"); //black circle
|
||||
else if (cur == Connect4Game.Field.P1)
|
||||
sb.Append("🔴"); //red circle
|
||||
else
|
||||
sb.Append("🔵"); //blue circle
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
for (int i = 0; i < Connect4Game.NumberOfColumns; i++)
|
||||
{
|
||||
sb.Append(numbers[i]);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,80 +5,78 @@ using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Gambling.Common.Events;
|
||||
using System;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
||||
{
|
||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
|
||||
{
|
||||
}
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
[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,
|
||||
public partial class Gambling
|
||||
{
|
||||
[Group]
|
||||
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
||||
{
|
||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
|
||||
{
|
||||
}
|
||||
|
||||
[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))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.start_event_fail).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
|
||||
{
|
||||
return 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))),
|
||||
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))),
|
||||
_ => 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));
|
||||
}
|
||||
|
||||
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));
|
||||
await ReplyErrorLocalizedAsync(strs.start_event_fail).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
|
||||
{
|
||||
return 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))),
|
||||
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))),
|
||||
_ => 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));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,59 +2,56 @@
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Linq;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
public class CurrencyRaffleCommands : GamblingSubmodule<CurrencyRaffleService>
|
||||
{
|
||||
public class CurrencyRaffleCommands : GamblingSubmodule<CurrencyRaffleService>
|
||||
public enum Mixed { Mixed }
|
||||
|
||||
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
|
||||
{
|
||||
public enum Mixed { Mixed }
|
||||
}
|
||||
|
||||
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task RaffleCur(Mixed _, ShmartNumber amount) =>
|
||||
RaffleCur(amount, true);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task RaffleCur(ShmartNumber amount, bool mixed = false)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
async Task OnEnded(IUser arg, long won)
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName, Format.Bold(arg.ToString()), won + CurrencySign)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task RaffleCur(Mixed _, ShmartNumber amount) =>
|
||||
RaffleCur(amount, true);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task RaffleCur(ShmartNumber amount, bool mixed = false)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
async Task OnEnded(IUser arg, long won)
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName, Format.Bold(arg.ToString()), won + CurrencySign)));
|
||||
}
|
||||
var res = await _service.JoinOrCreateGame(ctx.Channel.Id,
|
||||
var res = await _service.JoinOrCreateGame(ctx.Channel.Id,
|
||||
ctx.User, amount, mixed, OnEnded)
|
||||
.ConfigureAwait(false);
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (res.Item1 != null)
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.rafflecur(res.Item1.GameType.ToString())),
|
||||
string.Join("\n", res.Item1.Users.Select(x => $"{x.DiscordUser} ({x.Amount})")),
|
||||
footer: GetText(strs.rafflecur_joined(ctx.User.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (res.Item2 == CurrencyRaffleService.JoinErrorType.AlreadyJoinedOrInvalidAmount)
|
||||
await ReplyErrorLocalizedAsync(strs.rafflecur_already_joined).ConfigureAwait(false);
|
||||
else if (res.Item2 == CurrencyRaffleService.JoinErrorType.NotEnoughCurrency)
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
if (res.Item1 != null)
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.rafflecur(res.Item1.GameType.ToString())),
|
||||
string.Join("\n", res.Item1.Users.Select(x => $"{x.DiscordUser} ({x.Amount})")),
|
||||
footer: GetText(strs.rafflecur_joined(ctx.User.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (res.Item2 == CurrencyRaffleService.JoinErrorType.AlreadyJoinedOrInvalidAmount)
|
||||
await ReplyErrorLocalizedAsync(strs.rafflecur_already_joined).ConfigureAwait(false);
|
||||
else if (res.Item2 == CurrencyRaffleService.JoinErrorType.NotEnoughCurrency)
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,230 +5,226 @@ using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Services;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
[Group]
|
||||
public class DiceRollCommands : NadekoSubmodule
|
||||
{
|
||||
[Group]
|
||||
public class DiceRollCommands : NadekoSubmodule
|
||||
private static readonly Regex dndRegex = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
|
||||
private static readonly Regex fudgeRegex = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly char[] _fateRolls = { '-', ' ', '+' };
|
||||
private readonly IImageCache _images;
|
||||
|
||||
public DiceRollCommands(IDataCache data)
|
||||
{
|
||||
private static readonly Regex dndRegex = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
|
||||
private static readonly Regex fudgeRegex = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
|
||||
_images = data.LocalImages;
|
||||
}
|
||||
|
||||
private static readonly char[] _fateRolls = { '-', ' ', '+' };
|
||||
private readonly IImageCache _images;
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Roll()
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
var gen = rng.Next(1, 101);
|
||||
|
||||
public DiceRollCommands(IDataCache data)
|
||||
var num1 = gen / 10;
|
||||
var num2 = gen % 10;
|
||||
|
||||
using (var img1 = GetDice(num1))
|
||||
using (var img2 = GetDice(num2))
|
||||
using (var img = new[] { img1, img2 }.Merge(out var format))
|
||||
using (var ms = img.ToStream(format))
|
||||
{
|
||||
_images = data.LocalImages;
|
||||
await ctx.Channel.SendFileAsync(ms,
|
||||
$"dice.{format.FileExtensions.First()}",
|
||||
Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Roll(int num)
|
||||
{
|
||||
await InternalRoll(num, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Rolluo(int num = 1)
|
||||
{
|
||||
await InternalRoll(num, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Roll(string arg)
|
||||
{
|
||||
await InternallDndRoll(arg, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Rolluo(string arg)
|
||||
{
|
||||
await InternallDndRoll(arg, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task InternalRoll(int num, bool ordered)
|
||||
{
|
||||
if (num < 1 || num > 30)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.dice_invalid_number(1, 30));
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Roll()
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
var dice = new List<Image<Rgba32>>(num);
|
||||
var values = new List<int>(num);
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
var gen = rng.Next(1, 101);
|
||||
|
||||
var num1 = gen / 10;
|
||||
var num2 = gen % 10;
|
||||
|
||||
using (var img1 = GetDice(num1))
|
||||
using (var img2 = GetDice(num2))
|
||||
using (var img = new[] { img1, img2 }.Merge(out var format))
|
||||
using (var ms = img.ToStream(format))
|
||||
var randomNumber = rng.Next(1, 7);
|
||||
var toInsert = dice.Count;
|
||||
if (ordered)
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(ms,
|
||||
$"dice.{format.FileExtensions.First()}",
|
||||
Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Roll(int num)
|
||||
{
|
||||
await InternalRoll(num, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Rolluo(int num = 1)
|
||||
{
|
||||
await InternalRoll(num, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Roll(string arg)
|
||||
{
|
||||
await InternallDndRoll(arg, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Rolluo(string arg)
|
||||
{
|
||||
await InternallDndRoll(arg, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task InternalRoll(int num, bool ordered)
|
||||
{
|
||||
if (num < 1 || num > 30)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.dice_invalid_number(1, 30));
|
||||
return;
|
||||
}
|
||||
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
var dice = new List<Image<Rgba32>>(num);
|
||||
var values = new List<int>(num);
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
var randomNumber = rng.Next(1, 7);
|
||||
var toInsert = dice.Count;
|
||||
if (ordered)
|
||||
{
|
||||
if (randomNumber == 6 || dice.Count == 0)
|
||||
toInsert = 0;
|
||||
else if (randomNumber != 1)
|
||||
for (var j = 0; j < dice.Count; j++)
|
||||
{
|
||||
if (values[j] < randomNumber)
|
||||
{
|
||||
toInsert = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
toInsert = dice.Count;
|
||||
}
|
||||
dice.Insert(toInsert, GetDice(randomNumber));
|
||||
values.Insert(toInsert, randomNumber);
|
||||
}
|
||||
|
||||
using (var bitmap = dice.Merge(out var format))
|
||||
using (var ms = bitmap.ToStream(format))
|
||||
{
|
||||
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()),
|
||||
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 int n1) &&
|
||||
n1 > 0 && n1 < 500)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
var rolls = new List<char>();
|
||||
|
||||
for (int i = 0; i < n1; i++)
|
||||
{
|
||||
rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
|
||||
}
|
||||
var embed = _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}]"))));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
else if ((match = dndRegex.Match(arg)).Length != 0)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
if (int.TryParse(match.Groups["n1"].ToString(), out n1) &&
|
||||
int.TryParse(match.Groups["n2"].ToString(), out int n2) &&
|
||||
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
|
||||
{
|
||||
if (!int.TryParse(match.Groups["add"].Value, out int add))
|
||||
add = 0;
|
||||
if (!int.TryParse(match.Groups["sub"].Value, out int sub))
|
||||
sub = 0;
|
||||
|
||||
var arr = new int[n1];
|
||||
for (int i = 0; i < n1; i++)
|
||||
if (randomNumber == 6 || dice.Count == 0)
|
||||
toInsert = 0;
|
||||
else if (randomNumber != 1)
|
||||
for (var j = 0; j < dice.Count; j++)
|
||||
{
|
||||
arr[i] = rng.Next(1, n2 + 1);
|
||||
if (values[j] < randomNumber)
|
||||
{
|
||||
toInsert = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task NRoll([Leftover] string range)
|
||||
{
|
||||
int rolled;
|
||||
if (range.Contains("-"))
|
||||
{
|
||||
var arr = range.Split('-')
|
||||
.Take(2)
|
||||
.Select(int.Parse)
|
||||
.ToArray();
|
||||
if (arr[0] > arr[1])
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.second_larger_than_first).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
|
||||
toInsert = dice.Count;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.dice_rolled(Format.Bold(rolled.ToString()))).ConfigureAwait(false);
|
||||
dice.Insert(toInsert, GetDice(randomNumber));
|
||||
values.Insert(toInsert, randomNumber);
|
||||
}
|
||||
|
||||
private Image<Rgba32> GetDice(int num)
|
||||
using (var bitmap = dice.Merge(out var format))
|
||||
using (var ms = bitmap.ToStream(format))
|
||||
{
|
||||
if (num < 0 || num > 10)
|
||||
throw new ArgumentOutOfRangeException(nameof(num));
|
||||
|
||||
if (num == 10)
|
||||
foreach (var d in dice)
|
||||
{
|
||||
var images = _images.Dice;
|
||||
using (var imgOne = Image.Load(images[1]))
|
||||
using (var imgZero = Image.Load(images[0]))
|
||||
{
|
||||
return new[] { imgOne, imgZero }.Merge();
|
||||
}
|
||||
d.Dispose();
|
||||
}
|
||||
return Image.Load(_images.Dice[num]);
|
||||
|
||||
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 int n1) &&
|
||||
n1 > 0 && n1 < 500)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
var rolls = new List<char>();
|
||||
|
||||
for (int i = 0; i < n1; i++)
|
||||
{
|
||||
rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
|
||||
}
|
||||
var embed = _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}]"))));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
else if ((match = dndRegex.Match(arg)).Length != 0)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
if (int.TryParse(match.Groups["n1"].ToString(), out n1) &&
|
||||
int.TryParse(match.Groups["n2"].ToString(), out int n2) &&
|
||||
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
|
||||
{
|
||||
if (!int.TryParse(match.Groups["add"].Value, out int add))
|
||||
add = 0;
|
||||
if (!int.TryParse(match.Groups["sub"].Value, out int sub))
|
||||
sub = 0;
|
||||
|
||||
var arr = new int[n1];
|
||||
for (int i = 0; i < n1; i++)
|
||||
{
|
||||
arr[i] = rng.Next(1, n2 + 1);
|
||||
}
|
||||
|
||||
var sum = arr.Sum();
|
||||
var embed = _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).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task NRoll([Leftover] string range)
|
||||
{
|
||||
int rolled;
|
||||
if (range.Contains("-"))
|
||||
{
|
||||
var arr = range.Split('-')
|
||||
.Take(2)
|
||||
.Select(int.Parse)
|
||||
.ToArray();
|
||||
if (arr[0] > arr[1])
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.second_larger_than_first).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.dice_rolled(Format.Bold(rolled.ToString()))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Image<Rgba32> GetDice(int num)
|
||||
{
|
||||
if (num < 0 || num > 10)
|
||||
throw new ArgumentOutOfRangeException(nameof(num));
|
||||
|
||||
if (num == 10)
|
||||
{
|
||||
var images = _images.Dice;
|
||||
using (var imgOne = Image.Load(images[1]))
|
||||
using (var imgZero = Image.Load(images[0]))
|
||||
{
|
||||
return new[] { imgOne, imgZero }.Merge();
|
||||
}
|
||||
}
|
||||
return Image.Load(_images.Dice[num]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
@@ -13,112 +11,111 @@ using Image = SixLabors.ImageSharp.Image;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
[Group]
|
||||
public class DrawCommands : NadekoSubmodule
|
||||
{
|
||||
[Group]
|
||||
public class DrawCommands : NadekoSubmodule
|
||||
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new ConcurrentDictionary<IGuild, Deck>();
|
||||
private readonly IImageCache _images;
|
||||
|
||||
public DrawCommands(IDataCache data)
|
||||
{
|
||||
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new ConcurrentDictionary<IGuild, Deck>();
|
||||
private readonly IImageCache _images;
|
||||
_images = data.LocalImages;
|
||||
}
|
||||
|
||||
public DrawCommands(IDataCache data)
|
||||
private async Task<(Stream ImageStream, string ToSend)> InternalDraw(int num, ulong? guildId = null)
|
||||
{
|
||||
if (num < 1 || num > 10)
|
||||
throw new ArgumentOutOfRangeException(nameof(num));
|
||||
|
||||
Deck cards = guildId is null ? new Deck() : _allDecks.GetOrAdd(ctx.Guild, (s) => new Deck());
|
||||
var images = new List<Image<Rgba32>>();
|
||||
var cardObjects = new List<Deck.Card>();
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
_images = data.LocalImages;
|
||||
}
|
||||
|
||||
private async Task<(Stream ImageStream, string ToSend)> InternalDraw(int num, ulong? guildId = null)
|
||||
{
|
||||
if (num < 1 || num > 10)
|
||||
throw new ArgumentOutOfRangeException(nameof(num));
|
||||
|
||||
Deck cards = guildId is null ? new Deck() : _allDecks.GetOrAdd(ctx.Guild, (s) => new Deck());
|
||||
var images = new List<Image<Rgba32>>();
|
||||
var cardObjects = new List<Deck.Card>();
|
||||
for (var i = 0; i < num; i++)
|
||||
if (cards.CardPool.Count == 0 && i != 0)
|
||||
{
|
||||
if (cards.CardPool.Count == 0 && i != 0)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_more_cards).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
break;
|
||||
await ReplyErrorLocalizedAsync(strs.no_more_cards).ConfigureAwait(false);
|
||||
}
|
||||
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)
|
||||
catch
|
||||
{
|
||||
i.Dispose();
|
||||
// ignored
|
||||
}
|
||||
|
||||
var toSend = $"{Format.Bold(ctx.User.ToString())}";
|
||||
if (cardObjects.Count == 5)
|
||||
toSend += $" drew `{Deck.GetHandValue(cardObjects)}`";
|
||||
|
||||
if (guildId != null)
|
||||
toSend += "\n" + GetText(strs.cards_left(Format.Bold(cards.CardPool.Count.ToString())));
|
||||
|
||||
return (img.ToStream(), toSend);
|
||||
break;
|
||||
}
|
||||
var currentCard = cards.Draw();
|
||||
cardObjects.Add(currentCard);
|
||||
images.Add(Image.Load(_images.GetCard(currentCard.ToString().ToLowerInvariant().Replace(' ', '_'))));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Draw(int num = 1)
|
||||
using (var img = images.Merge())
|
||||
{
|
||||
if (num < 1)
|
||||
num = 1;
|
||||
if (num > 10)
|
||||
num = 10;
|
||||
|
||||
var (ImageStream, ToSend) = await InternalDraw(num, ctx.Guild.Id).ConfigureAwait(false);
|
||||
using (ImageStream)
|
||||
foreach (var i in images)
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend).ConfigureAwait(false);
|
||||
i.Dispose();
|
||||
}
|
||||
|
||||
var toSend = $"{Format.Bold(ctx.User.ToString())}";
|
||||
if (cardObjects.Count == 5)
|
||||
toSend += $" drew `{Deck.GetHandValue(cardObjects)}`";
|
||||
|
||||
if (guildId != null)
|
||||
toSend += "\n" + GetText(strs.cards_left(Format.Bold(cards.CardPool.Count.ToString())));
|
||||
|
||||
return (img.ToStream(), toSend);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task DrawNew(int num = 1)
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Draw(int num = 1)
|
||||
{
|
||||
if (num < 1)
|
||||
num = 1;
|
||||
if (num > 10)
|
||||
num = 10;
|
||||
|
||||
var (ImageStream, ToSend) = await InternalDraw(num, ctx.Guild.Id).ConfigureAwait(false);
|
||||
using (ImageStream)
|
||||
{
|
||||
if (num < 1)
|
||||
num = 1;
|
||||
if (num > 10)
|
||||
num = 10;
|
||||
await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
var (ImageStream, ToSend) = await InternalDraw(num).ConfigureAwait(false);
|
||||
using (ImageStream)
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task DrawNew(int num = 1)
|
||||
{
|
||||
if (num < 1)
|
||||
num = 1;
|
||||
if (num > 10)
|
||||
num = 10;
|
||||
|
||||
var (ImageStream, ToSend) = await InternalDraw(num).ConfigureAwait(false);
|
||||
using (ImageStream)
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task DeckShuffle()
|
||||
{
|
||||
//var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
_allDecks.AddOrUpdate(ctx.Guild,
|
||||
(g) => new Deck(),
|
||||
(g, c) =>
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
c.Restart();
|
||||
return c;
|
||||
});
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task DeckShuffle()
|
||||
{
|
||||
//var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
_allDecks.AddOrUpdate(ctx.Guild,
|
||||
(g) => new Deck(),
|
||||
(g, c) =>
|
||||
{
|
||||
c.Restart();
|
||||
return c;
|
||||
});
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.deck_reshuffled).ConfigureAwait(false);
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(strs.deck_reshuffled).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,124 +7,121 @@ using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Services;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
[Group]
|
||||
public class FlipCoinCommands : GamblingSubmodule<GamblingService>
|
||||
{
|
||||
[Group]
|
||||
public class FlipCoinCommands : GamblingSubmodule<GamblingService>
|
||||
private readonly IImageCache _images;
|
||||
private readonly ICurrencyService _cs;
|
||||
private static readonly NadekoRandom rng = new NadekoRandom();
|
||||
|
||||
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss)
|
||||
{
|
||||
private readonly IImageCache _images;
|
||||
private readonly ICurrencyService _cs;
|
||||
private static readonly NadekoRandom rng = new NadekoRandom();
|
||||
_images = data.LocalImages;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss)
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Flip(int count = 1)
|
||||
{
|
||||
if (count > 10 || count < 1)
|
||||
{
|
||||
_images = data.LocalImages;
|
||||
_cs = cs;
|
||||
await ReplyErrorLocalizedAsync(strs.flip_invalid(10));
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Flip(int count = 1)
|
||||
var headCount = 0;
|
||||
var tailCount = 0;
|
||||
var imgs = new Image<Rgba32>[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (count > 10 || count < 1)
|
||||
var headsArr = _images.Heads[rng.Next(0, _images.Heads.Count)];
|
||||
var tailsArr = _images.Tails[rng.Next(0, _images.Tails.Count)];
|
||||
if (rng.Next(0, 10) < 5)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.flip_invalid(10));
|
||||
return;
|
||||
}
|
||||
var headCount = 0;
|
||||
var tailCount = 0;
|
||||
var imgs = new Image<Rgba32>[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var headsArr = _images.Heads[rng.Next(0, _images.Heads.Count)];
|
||||
var tailsArr = _images.Tails[rng.Next(0, _images.Tails.Count)];
|
||||
if (rng.Next(0, 10) < 5)
|
||||
{
|
||||
imgs[i] = Image.Load(headsArr);
|
||||
headCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
imgs[i] = Image.Load(tailsArr);
|
||||
tailCount++;
|
||||
}
|
||||
}
|
||||
using (var img = imgs.Merge(out var format))
|
||||
using (var stream = img.ToStream(format))
|
||||
{
|
||||
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))));
|
||||
await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public enum BetFlipGuess
|
||||
{
|
||||
H = 1,
|
||||
Head = 1,
|
||||
Heads = 1,
|
||||
T = 2,
|
||||
Tail = 2,
|
||||
Tails = 2
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Betflip(ShmartNumber amount, BetFlipGuess guess)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false) || amount == 1)
|
||||
return;
|
||||
|
||||
var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, gamble: true).ConfigureAwait(false);
|
||||
if (!removed)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
BetFlipGuess result;
|
||||
Uri imageToSend;
|
||||
var coins = _images.ImageUrls.Coins;
|
||||
if (rng.Next(0, 1000) <= 499)
|
||||
{
|
||||
imageToSend = coins.Heads[rng.Next(0, coins.Heads.Length)];
|
||||
result = BetFlipGuess.Heads;
|
||||
imgs[i] = Image.Load(headsArr);
|
||||
headCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
imageToSend = coins.Tails[rng.Next(0, coins.Tails.Length)];
|
||||
result = BetFlipGuess.Tails;
|
||||
imgs[i] = Image.Load(tailsArr);
|
||||
tailCount++;
|
||||
}
|
||||
|
||||
string str;
|
||||
if (guess == result)
|
||||
{
|
||||
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).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
str = ctx.User.ToString() + " " + GetText(strs.better_luck);
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithDescription(str)
|
||||
.WithOkColor()
|
||||
.WithImageUrl(imageToSend.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
using (var img = imgs.Merge(out var format))
|
||||
using (var stream = img.ToStream(format))
|
||||
{
|
||||
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))));
|
||||
await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public enum BetFlipGuess
|
||||
{
|
||||
H = 1,
|
||||
Head = 1,
|
||||
Heads = 1,
|
||||
T = 2,
|
||||
Tail = 2,
|
||||
Tails = 2
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Betflip(ShmartNumber amount, BetFlipGuess guess)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false) || amount == 1)
|
||||
return;
|
||||
|
||||
var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, gamble: true).ConfigureAwait(false);
|
||||
if (!removed)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
BetFlipGuess result;
|
||||
Uri imageToSend;
|
||||
var coins = _images.ImageUrls.Coins;
|
||||
if (rng.Next(0, 1000) <= 499)
|
||||
{
|
||||
imageToSend = coins.Heads[rng.Next(0, coins.Heads.Length)];
|
||||
result = BetFlipGuess.Heads;
|
||||
}
|
||||
else
|
||||
{
|
||||
imageToSend = coins.Tails[rng.Next(0, coins.Tails.Length)];
|
||||
result = BetFlipGuess.Tails;
|
||||
}
|
||||
|
||||
string str;
|
||||
if (guess == result)
|
||||
{
|
||||
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).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
str = ctx.User.ToString() + " " + GetText(strs.better_luck);
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithDescription(str)
|
||||
.WithOkColor()
|
||||
.WithImageUrl(imageToSend.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,121 +5,119 @@ using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
[Group]
|
||||
public class PlantPickCommands : GamblingSubmodule<PlantPickService>
|
||||
{
|
||||
[Group]
|
||||
public class PlantPickCommands : GamblingSubmodule<PlantPickService>
|
||||
private readonly ILogCommandService logService;
|
||||
|
||||
public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss) : base(gss)
|
||||
{
|
||||
private readonly ILogCommandService logService;
|
||||
this.logService = logService;
|
||||
}
|
||||
|
||||
public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss) : base(gss)
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Pick(string pass = null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
|
||||
{
|
||||
this.logService = logService;
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Pick(string pass = null)
|
||||
var picked = await _service.PickAsync(ctx.Guild.Id, (ITextChannel)ctx.Channel, ctx.User.Id, pass);
|
||||
|
||||
if (picked > 0)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var picked = await _service.PickAsync(ctx.Guild.Id, (ITextChannel)ctx.Channel, ctx.User.Id, pass);
|
||||
|
||||
if (picked > 0)
|
||||
{
|
||||
var msg = await ReplyConfirmLocalizedAsync(strs.picked(picked + CurrencySign));
|
||||
msg.DeleteAfter(10);
|
||||
}
|
||||
|
||||
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
|
||||
{
|
||||
try
|
||||
{
|
||||
logService.AddDeleteIgnore(ctx.Message.Id);
|
||||
await ctx.Message.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
var msg = await ReplyConfirmLocalizedAsync(strs.picked(picked + CurrencySign));
|
||||
msg.DeleteAfter(10);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Plant(ShmartNumber amount, string pass = null)
|
||||
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
|
||||
{
|
||||
if (amount < 1)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
|
||||
try
|
||||
{
|
||||
logService.AddDeleteIgnore(ctx.Message.Id);
|
||||
await ctx.Message.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Plant(ShmartNumber amount, string pass = null)
|
||||
{
|
||||
if (amount < 1)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
|
||||
{
|
||||
logService.AddDeleteIgnore(ctx.Message.Id);
|
||||
await ctx.Message.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
#if GLOBAL_NADEKO
|
||||
[OwnerOnly]
|
||||
#endif
|
||||
public async Task GenCurrency()
|
||||
public async Task GenCurrency()
|
||||
{
|
||||
bool enabled = _service.ToggleCurrencyGeneration(ctx.Guild.Id, ctx.Channel.Id);
|
||||
if (enabled)
|
||||
{
|
||||
bool enabled = _service.ToggleCurrencyGeneration(ctx.Guild.Id, ctx.Channel.Id);
|
||||
if (enabled)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.curgen_enabled).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.curgen_disabled).ConfigureAwait(false);
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(strs.curgen_enabled).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[OwnerOnly]
|
||||
public Task GenCurList(int page = 1)
|
||||
else
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
var enabledIn = _service.GetAllGeneratingChannels();
|
||||
|
||||
return ctx.SendPaginatedConfirmAsync(page, (cur) =>
|
||||
{
|
||||
var items = enabledIn.Skip(page * 9).Take(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);
|
||||
await ReplyConfirmLocalizedAsync(strs.curgen_disabled).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[OwnerOnly]
|
||||
public Task GenCurList(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
var enabledIn = _service.GetAllGeneratingChannels();
|
||||
|
||||
return ctx.SendPaginatedConfirmAsync(page, (cur) =>
|
||||
{
|
||||
var items = enabledIn.Skip(page * 9).Take(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services;
|
||||
using System.Collections.Concurrent;
|
||||
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class AnimalRaceService : INService
|
||||
{
|
||||
public class AnimalRaceService : INService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
|
||||
}
|
||||
}
|
||||
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
|
||||
}
|
||||
@@ -2,10 +2,9 @@
|
||||
using NadekoBot.Services;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class BlackJackService : INService
|
||||
{
|
||||
public class BlackJackService : INService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, Blackjack> Games { get; } = new ConcurrentDictionary<ulong, Blackjack>();
|
||||
}
|
||||
}
|
||||
public ConcurrentDictionary<ulong, Blackjack> Games { get; } = new ConcurrentDictionary<ulong, Blackjack>();
|
||||
}
|
||||
@@ -2,83 +2,77 @@
|
||||
using NadekoBot.Modules.Gambling.Common.Events;
|
||||
using System.Collections.Concurrent;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Net.Http;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class CurrencyEventsService : INService
|
||||
{
|
||||
public class CurrencyEventsService : INService
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly GamblingConfigService _configService;
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
|
||||
new ConcurrentDictionary<ulong, ICurrencyEvent>();
|
||||
|
||||
|
||||
public CurrencyEventsService(
|
||||
DiscordSocketClient client,
|
||||
ICurrencyService cs,
|
||||
GamblingConfigService configService)
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly GamblingConfigService _configService;
|
||||
_client = client;
|
||||
_cs = cs;
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
|
||||
new ConcurrentDictionary<ulong, ICurrencyEvent>();
|
||||
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
|
||||
EventOptions opts, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embed)
|
||||
{
|
||||
SocketGuild g = _client.GetGuild(guildId);
|
||||
SocketTextChannel ch = g?.GetChannel(channelId) as SocketTextChannel;
|
||||
if (ch is null)
|
||||
return false;
|
||||
|
||||
ICurrencyEvent ce;
|
||||
|
||||
public CurrencyEventsService(
|
||||
DiscordSocketClient client,
|
||||
ICurrencyService cs,
|
||||
GamblingConfigService configService)
|
||||
if (type == CurrencyEvent.Type.Reaction)
|
||||
{
|
||||
_client = client;
|
||||
_cs = cs;
|
||||
_configService = configService;
|
||||
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;
|
||||
}
|
||||
|
||||
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
|
||||
EventOptions opts, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embed)
|
||||
var added = _events.TryAdd(guildId, ce);
|
||||
if (added)
|
||||
{
|
||||
SocketGuild g = _client.GetGuild(guildId);
|
||||
SocketTextChannel ch = g?.GetChannel(channelId) as SocketTextChannel;
|
||||
if (ch is null)
|
||||
return false;
|
||||
|
||||
ICurrencyEvent ce;
|
||||
|
||||
if (type == CurrencyEvent.Type.Reaction)
|
||||
try
|
||||
{
|
||||
ce = new ReactionEvent(_client, _cs, g, ch, opts, _configService.Data, embed);
|
||||
ce.OnEnded += OnEventEnded;
|
||||
await ce.StartEvent().ConfigureAwait(false);
|
||||
}
|
||||
else if (type == CurrencyEvent.Type.GameStatus)
|
||||
{
|
||||
ce = new GameStatusEvent(_client, _cs, g, ch, opts, embed);
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error starting event");
|
||||
_events.TryRemove(guildId, out ce);
|
||||
return false;
|
||||
}
|
||||
|
||||
var added = _events.TryAdd(guildId, ce);
|
||||
if (added)
|
||||
{
|
||||
try
|
||||
{
|
||||
ce.OnEnded += OnEventEnded;
|
||||
await ce.StartEvent().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error starting event");
|
||||
_events.TryRemove(guildId, out ce);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
private Task OnEventEnded(ulong gid)
|
||||
{
|
||||
_events.TryRemove(gid, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
private Task OnEventEnded(ulong gid)
|
||||
{
|
||||
_events.TryRemove(gid, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -2,87 +2,83 @@
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Discord;
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class CurrencyRaffleService : INService
|
||||
{
|
||||
public class CurrencyRaffleService : INService
|
||||
public enum JoinErrorType
|
||||
{
|
||||
public enum JoinErrorType
|
||||
{
|
||||
NotEnoughCurrency,
|
||||
AlreadyJoinedOrInvalidAmount
|
||||
}
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
NotEnoughCurrency,
|
||||
AlreadyJoinedOrInvalidAmount
|
||||
}
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new Dictionary<ulong, CurrencyRaffleGame>();
|
||||
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new Dictionary<ulong, CurrencyRaffleGame>();
|
||||
|
||||
public CurrencyRaffleService(DbService db, ICurrencyService cs)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
}
|
||||
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().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
var newGame = false;
|
||||
if (!Games.TryGetValue(channelId, out var crg))
|
||||
{
|
||||
var newGame = false;
|
||||
if (!Games.TryGetValue(channelId, out var crg))
|
||||
{
|
||||
newGame = true;
|
||||
crg = new CurrencyRaffleGame(mixed
|
||||
? CurrencyRaffleGame.Type.Mixed
|
||||
: CurrencyRaffleGame.Type.Normal);
|
||||
Games.Add(channelId, crg);
|
||||
}
|
||||
newGame = true;
|
||||
crg = new CurrencyRaffleGame(mixed
|
||||
? CurrencyRaffleGame.Type.Mixed
|
||||
: CurrencyRaffleGame.Type.Normal);
|
||||
Games.Add(channelId, crg);
|
||||
}
|
||||
|
||||
//remove money, and stop the game if this
|
||||
// user created it and doesn't have the money
|
||||
if (!await _cs.RemoveAsync(user.Id, "Currency Raffle Join", amount).ConfigureAwait(false))
|
||||
{
|
||||
if (newGame)
|
||||
Games.Remove(channelId);
|
||||
return (null, JoinErrorType.NotEnoughCurrency);
|
||||
}
|
||||
|
||||
if (!crg.AddUser(user, amount))
|
||||
{
|
||||
await _cs.AddAsync(user.Id, "Curency Raffle Refund", amount).ConfigureAwait(false);
|
||||
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
|
||||
}
|
||||
//remove money, and stop the game if this
|
||||
// user created it and doesn't have the money
|
||||
if (!await _cs.RemoveAsync(user.Id, "Currency Raffle Join", amount).ConfigureAwait(false))
|
||||
{
|
||||
if (newGame)
|
||||
{
|
||||
var _t = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(60000).ConfigureAwait(false);
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var winner = crg.GetWinner();
|
||||
var won = crg.Users.Sum(x => x.Amount);
|
||||
Games.Remove(channelId);
|
||||
return (null, JoinErrorType.NotEnoughCurrency);
|
||||
}
|
||||
|
||||
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win",
|
||||
won).ConfigureAwait(false);
|
||||
Games.Remove(channelId, out _);
|
||||
var oe = onEnded(winner.DiscordUser, won);
|
||||
}
|
||||
catch { }
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
return (crg, null);
|
||||
}
|
||||
finally
|
||||
if (!crg.AddUser(user, amount))
|
||||
{
|
||||
_locker.Release();
|
||||
await _cs.AddAsync(user.Id, "Curency Raffle Refund", amount).ConfigureAwait(false);
|
||||
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
|
||||
}
|
||||
if (newGame)
|
||||
{
|
||||
var _t = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(60000).ConfigureAwait(false);
|
||||
await _locker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var winner = crg.GetWinner();
|
||||
var won = crg.Users.Sum(x => x.Amount);
|
||||
|
||||
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win",
|
||||
won).ConfigureAwait(false);
|
||||
Games.Remove(channelId, out _);
|
||||
var oe = onEnded(winner.DiscordUser, won);
|
||||
}
|
||||
catch { }
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
return (crg, null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Configs;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||
{
|
||||
public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||
{
|
||||
public override string Name { get; } = "gambling";
|
||||
private const string FilePath = "data/gambling.yml";
|
||||
private static TypedKey<GamblingConfig> changeKey = new TypedKey<GamblingConfig>("config.gambling.updated");
|
||||
public override string Name { get; } = "gambling";
|
||||
private const string FilePath = "data/gambling.yml";
|
||||
private static TypedKey<GamblingConfig> changeKey = new TypedKey<GamblingConfig>("config.gambling.updated");
|
||||
|
||||
|
||||
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);
|
||||
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("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 >= 0 && val <= 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 >= 0 && val <= 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("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 >= 0 && val <= 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 >= 0 && val <= 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();
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
private readonly IEnumerable<WaifuItemModel> antiGiftSeed = new[]
|
||||
if (_data.Version < 3)
|
||||
{
|
||||
new WaifuItemModel("🥀", 100, "WiltedRose", true),
|
||||
new WaifuItemModel("✂️", 1000, "Haircut", true),
|
||||
new WaifuItemModel("🧻", 10000, "ToiletPaper", true),
|
||||
};
|
||||
|
||||
public void Migrate()
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 3;
|
||||
c.VoteReward = 100;
|
||||
});
|
||||
}
|
||||
|
||||
if (_data.Version < 4)
|
||||
{
|
||||
if (_data.Version < 2)
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
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;
|
||||
});
|
||||
}
|
||||
c.Version = 4;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,69 +4,63 @@ using NadekoBot.Services;
|
||||
using NadekoBot.Modules.Gambling.Common.Connect4;
|
||||
using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Gambling.Common.Slot;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class GamblingService : INService
|
||||
{
|
||||
public class GamblingService : INService
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly Bot _bot;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IDataCache _cache;
|
||||
private readonly GamblingConfigService _gss;
|
||||
|
||||
public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new ConcurrentDictionary<(ulong, ulong), RollDuelGame>();
|
||||
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new ConcurrentDictionary<ulong, Connect4Game>();
|
||||
|
||||
private readonly Timer _decayTimer;
|
||||
|
||||
public GamblingService(DbService db, Bot bot, ICurrencyService cs,
|
||||
DiscordSocketClient client, IDataCache cache, GamblingConfigService gss)
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly Bot _bot;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IDataCache _cache;
|
||||
private readonly GamblingConfigService _gss;
|
||||
|
||||
public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new ConcurrentDictionary<(ulong, ulong), RollDuelGame>();
|
||||
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new ConcurrentDictionary<ulong, Connect4Game>();
|
||||
|
||||
private readonly Timer _decayTimer;
|
||||
|
||||
public GamblingService(DbService db, Bot bot, ICurrencyService cs,
|
||||
DiscordSocketClient client, IDataCache cache, GamblingConfigService gss)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
_bot = bot;
|
||||
_client = client;
|
||||
_cache = cache;
|
||||
_gss = gss;
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
_bot = bot;
|
||||
_client = client;
|
||||
_cache = cache;
|
||||
_gss = gss;
|
||||
|
||||
if (_bot.Client.ShardId == 0)
|
||||
if (_bot.Client.ShardId == 0)
|
||||
{
|
||||
_decayTimer = new Timer(_ =>
|
||||
{
|
||||
_decayTimer = new Timer(_ =>
|
||||
{
|
||||
var config = _gss.Data;
|
||||
var maxDecay = config.Decay.MaxDecay;
|
||||
if (config.Decay.Percent <= 0 || config.Decay.Percent > 1 || maxDecay < 0)
|
||||
return;
|
||||
var config = _gss.Data;
|
||||
var maxDecay = config.Decay.MaxDecay;
|
||||
if (config.Decay.Percent <= 0 || config.Decay.Percent > 1 || maxDecay < 0)
|
||||
return;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
|
||||
|
||||
if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval))
|
||||
return;
|
||||
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}");
|
||||
Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% " +
|
||||
$"| max: {maxDecay} " +
|
||||
$"| threshold: {config.Decay.MinThreshold}");
|
||||
|
||||
if (maxDecay == 0)
|
||||
maxDecay = int.MaxValue;
|
||||
if (maxDecay == 0)
|
||||
maxDecay = int.MaxValue;
|
||||
|
||||
uow.Database.ExecuteSqlInterpolated($@"
|
||||
uow.Database.ExecuteSqlInterpolated($@"
|
||||
UPDATE DiscordUser
|
||||
SET CurrencyAmount=
|
||||
CASE WHEN
|
||||
@@ -78,99 +72,98 @@ SET CurrencyAmount=
|
||||
END
|
||||
WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentUser.Id};");
|
||||
|
||||
_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 SlotResponse
|
||||
{
|
||||
Error = GamblingError.NotEnough
|
||||
};
|
||||
}
|
||||
|
||||
var game = new SlotGame();
|
||||
var result = game.Spin();
|
||||
long won = 0;
|
||||
|
||||
if (result.Multiplier > 0)
|
||||
{
|
||||
won = (long)(result.Multiplier * amount);
|
||||
|
||||
await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true);
|
||||
}
|
||||
|
||||
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);
|
||||
_cache.SetLastCurrencyDecay();
|
||||
uow.SaveChanges();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
decimal cash;
|
||||
decimal onePercent;
|
||||
decimal planted;
|
||||
decimal waifus;
|
||||
long bot;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
cash = uow.DiscordUser.GetTotalCurrency();
|
||||
onePercent = uow.DiscordUser.GetTopOnePercentCurrency(_client.CurrentUser.Id);
|
||||
planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount);
|
||||
waifus = uow.WaifuInfo.GetTotalValue();
|
||||
bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id);
|
||||
}
|
||||
|
||||
var result = new EconomyResult
|
||||
{
|
||||
Cash = cash,
|
||||
Planted = planted,
|
||||
Bot = bot,
|
||||
Waifus = waifus,
|
||||
OnePercent = onePercent,
|
||||
};
|
||||
|
||||
_cache.SetEconomy(JsonConvert.SerializeObject(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<WheelOfFortuneGame.Result> WheelOfFortuneSpinAsync(ulong userId, long bet)
|
||||
{
|
||||
return new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync();
|
||||
}, 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 SlotResponse
|
||||
{
|
||||
Error = GamblingError.NotEnough
|
||||
};
|
||||
}
|
||||
|
||||
var game = new SlotGame();
|
||||
var result = game.Spin();
|
||||
long won = 0;
|
||||
|
||||
if (result.Multiplier > 0)
|
||||
{
|
||||
won = (long)(result.Multiplier * amount);
|
||||
|
||||
await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true);
|
||||
}
|
||||
|
||||
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;
|
||||
decimal planted;
|
||||
decimal waifus;
|
||||
long bot;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
cash = uow.DiscordUser.GetTotalCurrency();
|
||||
onePercent = uow.DiscordUser.GetTopOnePercentCurrency(_client.CurrentUser.Id);
|
||||
planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount);
|
||||
waifus = uow.WaifuInfo.GetTotalValue();
|
||||
bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id);
|
||||
}
|
||||
|
||||
var result = new EconomyResult
|
||||
{
|
||||
Cash = cash,
|
||||
Planted = planted,
|
||||
Bot = bot,
|
||||
Waifus = waifus,
|
||||
OnePercent = onePercent,
|
||||
};
|
||||
|
||||
_cache.SetEconomy(JsonConvert.SerializeObject(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<WheelOfFortuneGame.Result> WheelOfFortuneSpinAsync(ulong userId, long bet)
|
||||
{
|
||||
return new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,42 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public interface IShopService
|
||||
{
|
||||
public interface IShopService
|
||||
{
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="newPrice">New item price</param>
|
||||
/// <returns>Success status</returns>
|
||||
Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice);
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="newPrice">New item price</param>
|
||||
/// <returns>Success status</returns>
|
||||
Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice);
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="newName">New item name</param>
|
||||
/// <returns>Success status</returns>
|
||||
Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName);
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="newName">New item name</param>
|
||||
/// <returns>Success status</returns>
|
||||
Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName);
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="index2">Second entry's index</param>
|
||||
/// <returns>Whether swap was successful</returns>
|
||||
Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2);
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="index2">Second entry's index</param>
|
||||
/// <returns>Whether swap was successful</returns>
|
||||
Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2);
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Services;
|
||||
@@ -7,102 +6,100 @@ using NadekoBot.Services.Database;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Administration;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class ShopService : IShopService, INService
|
||||
{
|
||||
public class ShopService : IShopService, INService
|
||||
private readonly DbService _db;
|
||||
|
||||
public ShopService(DbService db)
|
||||
{
|
||||
private readonly DbService _db;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if (newPrice <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(newPrice));
|
||||
|
||||
public async Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice)
|
||||
{
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if (newPrice <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(newPrice));
|
||||
using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
if (index >= entries.Count)
|
||||
return false;
|
||||
|
||||
if (index >= entries.Count)
|
||||
return false;
|
||||
entries[index].Price = newPrice;
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
entries[index].Price = newPrice;
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
public async Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName)
|
||||
{
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if (string.IsNullOrWhiteSpace(newName))
|
||||
throw new ArgumentNullException(nameof(newName));
|
||||
|
||||
public async Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName)
|
||||
{
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if (string.IsNullOrWhiteSpace(newName))
|
||||
throw new ArgumentNullException(nameof(newName));
|
||||
using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
if (index >= entries.Count)
|
||||
return false;
|
||||
|
||||
if (index >= entries.Count)
|
||||
return false;
|
||||
entries[index].Name = newName.TrimTo(100);
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
entries[index].Name = newName.TrimTo(100);
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
public async Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2)
|
||||
{
|
||||
if (index1 < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index1));
|
||||
if (index2 < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index2));
|
||||
|
||||
public async Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2)
|
||||
{
|
||||
if (index1 < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index1));
|
||||
if (index2 < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index2));
|
||||
using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
if (index1 >= entries.Count || index2 >= entries.Count || index1 == index2)
|
||||
return false;
|
||||
|
||||
if (index1 >= entries.Count || index2 >= entries.Count || index1 == index2)
|
||||
return false;
|
||||
entries[index1].Index = index2;
|
||||
entries[index2].Index = index1;
|
||||
|
||||
entries[index1].Index = index2;
|
||||
entries[index2].Index = index1;
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
public async Task<bool> MoveEntryAsync(ulong guildId, int fromIndex, int toIndex)
|
||||
{
|
||||
if (fromIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(fromIndex));
|
||||
if (toIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(toIndex));
|
||||
|
||||
public async Task<bool> MoveEntryAsync(ulong guildId, int fromIndex, int toIndex)
|
||||
{
|
||||
if (fromIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(fromIndex));
|
||||
if (toIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(toIndex));
|
||||
using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
if (fromIndex >= entries.Count || toIndex >= entries.Count || fromIndex == toIndex)
|
||||
return false;
|
||||
|
||||
if (fromIndex >= entries.Count || toIndex >= entries.Count || fromIndex == toIndex)
|
||||
return false;
|
||||
var entry = entries[fromIndex];
|
||||
entries.RemoveAt(fromIndex);
|
||||
entries.Insert(toIndex, entry);
|
||||
|
||||
var entry = entries[fromIndex];
|
||||
entries.RemoveAt(fromIndex);
|
||||
entries.Insert(toIndex, entry);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -11,373 +11,369 @@ using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Db;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class PlantPickService : INService
|
||||
{
|
||||
public class PlantPickService : INService
|
||||
private readonly DbService _db;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly IImageCache _images;
|
||||
private readonly FontProvider _fonts;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly CommandHandler _cmdHandler;
|
||||
private readonly NadekoRandom _rng;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly GamblingConfigService _gss;
|
||||
|
||||
public readonly ConcurrentHashSet<ulong> _generationChannels = new ConcurrentHashSet<ulong>();
|
||||
//channelId/last generation
|
||||
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
|
||||
private readonly SemaphoreSlim pickLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
public PlantPickService(DbService db, CommandHandler cmd, IBotStrings strings,
|
||||
IDataCache cache, FontProvider fonts, ICurrencyService cs,
|
||||
CommandHandler cmdHandler, DiscordSocketClient client, GamblingConfigService gss)
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly IImageCache _images;
|
||||
private readonly FontProvider _fonts;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly CommandHandler _cmdHandler;
|
||||
private readonly NadekoRandom _rng;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly GamblingConfigService _gss;
|
||||
_db = db;
|
||||
_strings = strings;
|
||||
_images = cache.LocalImages;
|
||||
_fonts = fonts;
|
||||
_cs = cs;
|
||||
_cmdHandler = cmdHandler;
|
||||
_rng = new NadekoRandom();
|
||||
_client = client;
|
||||
_gss = gss;
|
||||
|
||||
public readonly ConcurrentHashSet<ulong> _generationChannels = new ConcurrentHashSet<ulong>();
|
||||
//channelId/last generation
|
||||
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
|
||||
private readonly SemaphoreSlim pickLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
public PlantPickService(DbService db, CommandHandler cmd, IBotStrings strings,
|
||||
IDataCache cache, FontProvider fonts, ICurrencyService cs,
|
||||
CommandHandler cmdHandler, DiscordSocketClient client, GamblingConfigService gss)
|
||||
cmd.OnMessageNoTrigger += PotentialFlowerGeneration;
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
_db = db;
|
||||
_strings = strings;
|
||||
_images = cache.LocalImages;
|
||||
_fonts = fonts;
|
||||
_cs = cs;
|
||||
_cmdHandler = cmdHandler;
|
||||
_rng = new NadekoRandom();
|
||||
_client = client;
|
||||
_gss = gss;
|
||||
|
||||
cmd.OnMessageNoTrigger += PotentialFlowerGeneration;
|
||||
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();
|
||||
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 ConcurrentHashSet<ulong>(configs
|
||||
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
|
||||
}
|
||||
_generationChannels = new ConcurrentHashSet<ulong>(configs
|
||||
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
|
||||
}
|
||||
}
|
||||
|
||||
private string GetText(ulong gid, LocStr str)
|
||||
=> _strings.GetText(str, gid);
|
||||
private string GetText(ulong gid, LocStr str)
|
||||
=> _strings.GetText(str, gid);
|
||||
|
||||
public bool ToggleCurrencyGeneration(ulong gid, ulong cid)
|
||||
public bool ToggleCurrencyGeneration(ulong gid, ulong cid)
|
||||
{
|
||||
bool enabled;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
bool enabled;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var guildConfig = uow.GuildConfigsForId(gid, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
|
||||
var guildConfig = uow.GuildConfigsForId(gid, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
|
||||
|
||||
var toAdd = new GCChannelId() { ChannelId = cid };
|
||||
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
|
||||
{
|
||||
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
|
||||
_generationChannels.Add(cid);
|
||||
enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
|
||||
if (toDelete != null)
|
||||
{
|
||||
uow.Remove(toDelete);
|
||||
}
|
||||
_generationChannels.TryRemove(cid);
|
||||
enabled = false;
|
||||
}
|
||||
uow.SaveChanges();
|
||||
var toAdd = new GCChannelId() { ChannelId = cid };
|
||||
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
|
||||
{
|
||||
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
|
||||
_generationChannels.Add(cid);
|
||||
enabled = true;
|
||||
}
|
||||
return enabled;
|
||||
else
|
||||
{
|
||||
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
|
||||
if (toDelete != null)
|
||||
{
|
||||
uow.Remove(toDelete);
|
||||
}
|
||||
_generationChannels.TryRemove(cid);
|
||||
enabled = false;
|
||||
}
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public IEnumerable<GuildConfigExtensions.GeneratingChannel> GetAllGeneratingChannels()
|
||||
public IEnumerable<GuildConfigExtensions.GeneratingChannel> GetAllGeneratingChannels()
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var chs = uow.GuildConfigs.GetGeneratingChannels();
|
||||
return chs;
|
||||
}
|
||||
var chs = uow.GuildConfigs.GetGeneratingChannels();
|
||||
return chs;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public Stream GetRandomCurrencyImage(string pass, out string extension)
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public Stream GetRandomCurrencyImage(string pass, out string extension)
|
||||
{
|
||||
// get a random currency image bytes
|
||||
var rng = new NadekoRandom();
|
||||
var curImg = _images.Currency[rng.Next(0, _images.Currency.Count)];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pass))
|
||||
{
|
||||
// get a random currency image bytes
|
||||
var rng = new NadekoRandom();
|
||||
var curImg = _images.Currency[rng.Next(0, _images.Currency.Count)];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pass))
|
||||
// determine the extension
|
||||
using (var img = Image.Load(curImg, out var format))
|
||||
{
|
||||
// determine the extension
|
||||
using (var img = Image.Load(curImg, out var format))
|
||||
{
|
||||
extension = format.FileExtensions.FirstOrDefault() ?? "png";
|
||||
}
|
||||
// return the image
|
||||
return curImg.ToStream();
|
||||
extension = format.FileExtensions.FirstOrDefault() ?? "png";
|
||||
}
|
||||
|
||||
// get the image stream and extension
|
||||
var (s, ext) = AddPassword(curImg, pass);
|
||||
// set the out extension parameter to the extension we've got
|
||||
extension = ext;
|
||||
// return the image
|
||||
return s;
|
||||
return curImg.ToStream();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <returns>Image with the password in the top left corner.</returns>
|
||||
private (Stream, string) AddPassword(byte[] curImg, string pass)
|
||||
// get the image stream and extension
|
||||
var (s, ext) = AddPassword(curImg, pass);
|
||||
// set the out extension parameter to the extension we've got
|
||||
extension = ext;
|
||||
// return the image
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <returns>Image with the password in the top left corner.</returns>
|
||||
private (Stream, string) AddPassword(byte[] curImg, string pass)
|
||||
{
|
||||
// draw lower, it looks better
|
||||
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
||||
using (var img = Image.Load<Rgba32>(curImg, out var format))
|
||||
{
|
||||
// draw lower, it looks better
|
||||
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
||||
using (var img = Image.Load<Rgba32>(curImg, out var format))
|
||||
// choose font size based on the image height, so that it's visible
|
||||
var font = _fonts.NotoSans.CreateFont(img.Height / 12, FontStyle.Bold);
|
||||
img.Mutate(x =>
|
||||
{
|
||||
// choose font size based on the image height, so that it's visible
|
||||
var font = _fonts.NotoSans.CreateFont(img.Height / 12, FontStyle.Bold);
|
||||
img.Mutate(x =>
|
||||
{
|
||||
// measure the size of the text to be drawing
|
||||
var size = TextMeasurer.Measure(pass, new RendererOptions(font, new PointF(0, 0)));
|
||||
// measure the size of the text to be drawing
|
||||
var size = TextMeasurer.Measure(pass, new RendererOptions(font, new PointF(0, 0)));
|
||||
|
||||
// fill the background with black, add 5 pixels on each side to make it look better
|
||||
x.FillPolygon(Color.ParseHex("00000080"),
|
||||
new PointF(0, 0),
|
||||
new PointF(size.Width + 5, 0),
|
||||
new PointF(size.Width + 5, size.Height + 10),
|
||||
new PointF(0, size.Height + 10));
|
||||
// fill the background with black, add 5 pixels on each side to make it look better
|
||||
x.FillPolygon(Color.ParseHex("00000080"),
|
||||
new PointF(0, 0),
|
||||
new PointF(size.Width + 5, 0),
|
||||
new PointF(size.Width + 5, size.Height + 10),
|
||||
new PointF(0, size.Height + 10));
|
||||
|
||||
// draw the password over the background
|
||||
x.DrawText(pass,
|
||||
font,
|
||||
SixLabors.ImageSharp.Color.White,
|
||||
new PointF(0, 0));
|
||||
});
|
||||
// return image as a stream for easy sending
|
||||
return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png");
|
||||
}
|
||||
}
|
||||
|
||||
private Task PotentialFlowerGeneration(IUserMessage imsg)
|
||||
{
|
||||
var msg = imsg as SocketUserMessage;
|
||||
if (msg is null || msg.Author.IsBot)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!(imsg.Channel is ITextChannel channel))
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!_generationChannels.Contains(channel.Id))
|
||||
return Task.CompletedTask;
|
||||
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = _gss.Data;
|
||||
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
|
||||
return;
|
||||
|
||||
var num = rng.Next(1, 101) + config.Generation.Chance * 100;
|
||||
if (num > 100 && LastGenerations.TryUpdate(channel.Id, DateTime.UtcNow, lastGeneration))
|
||||
{
|
||||
var dropAmount = config.Generation.MinAmount;
|
||||
var dropAmountMax = config.Generation.MaxAmount;
|
||||
|
||||
if (dropAmountMax > dropAmount)
|
||||
dropAmount = new NadekoRandom().Next(dropAmount, dropAmountMax + 1);
|
||||
|
||||
if (dropAmount > 0)
|
||||
{
|
||||
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.curgen_pl(dropAmount, config.Currency.Sign))
|
||||
+ " " + GetText(channel.GuildId, strs.pick_pl(prefix));
|
||||
|
||||
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
|
||||
|
||||
IUserMessage sent;
|
||||
using (var stream = GetRandomCurrencyImage(pw, out var ext))
|
||||
{
|
||||
sent = await channel.SendFileAsync(stream, $"currency_image.{ext}", toSend).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await AddPlantToDatabase(channel.GuildId,
|
||||
channel.Id,
|
||||
_client.CurrentUser.Id,
|
||||
sent.Id,
|
||||
dropAmount,
|
||||
pw).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
// draw the password over the background
|
||||
x.DrawText(pass,
|
||||
font,
|
||||
SixLabors.ImageSharp.Color.White,
|
||||
new PointF(0, 0));
|
||||
});
|
||||
// return image as a stream for easy sending
|
||||
return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png");
|
||||
}
|
||||
}
|
||||
|
||||
private Task PotentialFlowerGeneration(IUserMessage imsg)
|
||||
{
|
||||
var msg = imsg as SocketUserMessage;
|
||||
if (msg is null || msg.Author.IsBot)
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a hexadecimal string from 1000 to ffff.
|
||||
/// </summary>
|
||||
/// <returns>A hexadecimal string from 1000 to ffff</returns>
|
||||
private string GenerateCurrencyPassword()
|
||||
{
|
||||
// generate a number from 1000 to ffff
|
||||
var num = _rng.Next(4096, 65536);
|
||||
// convert it to hexadecimal
|
||||
return num.ToString("x4");
|
||||
}
|
||||
if (!(imsg.Channel is ITextChannel channel))
|
||||
return Task.CompletedTask;
|
||||
|
||||
public async Task<long> PickAsync(ulong gid, ITextChannel ch, ulong uid, string pass)
|
||||
if (!_generationChannels.Contains(channel.Id))
|
||||
return Task.CompletedTask;
|
||||
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await pickLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
long amount;
|
||||
ulong[] ids;
|
||||
using (var uow = _db.GetDbContext())
|
||||
var config = _gss.Data;
|
||||
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
|
||||
return;
|
||||
|
||||
var num = rng.Next(1, 101) + config.Generation.Chance * 100;
|
||||
if (num > 100 && LastGenerations.TryUpdate(channel.Id, DateTime.UtcNow, lastGeneration))
|
||||
{
|
||||
// this method will sum all plants with that password,
|
||||
// remove them, and get messageids of the removed plants
|
||||
var dropAmount = config.Generation.MinAmount;
|
||||
var dropAmountMax = config.Generation.MaxAmount;
|
||||
|
||||
pass = pass?.Trim().TrimTo(10, hideDots: 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();
|
||||
// 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();
|
||||
// remove them from the database
|
||||
uow.RemoveRange(entries);
|
||||
if (dropAmountMax > dropAmount)
|
||||
dropAmount = new NadekoRandom().Next(dropAmount, dropAmountMax + 1);
|
||||
|
||||
|
||||
if (amount > 0)
|
||||
if (dropAmount > 0)
|
||||
{
|
||||
// give the picked currency to the user
|
||||
await _cs.AddAsync(uid, "Picked currency", amount, gamble: false);
|
||||
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.curgen_pl(dropAmount, config.Currency.Sign))
|
||||
+ " " + GetText(channel.GuildId, strs.pick_pl(prefix));
|
||||
|
||||
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
|
||||
|
||||
IUserMessage sent;
|
||||
using (var stream = GetRandomCurrencyImage(pw, out var ext))
|
||||
{
|
||||
sent = await channel.SendFileAsync(stream, $"currency_image.{ext}", toSend).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await AddPlantToDatabase(channel.GuildId,
|
||||
channel.Id,
|
||||
_client.CurrentUser.Id,
|
||||
sent.Id,
|
||||
dropAmount,
|
||||
pw).ConfigureAwait(false);
|
||||
}
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// delete all of the plant messages which have just been picked
|
||||
var _ = ch.DeleteMessagesAsync(ids);
|
||||
}
|
||||
catch { }
|
||||
|
||||
// return the amount of currency the user picked
|
||||
return amount;
|
||||
}
|
||||
finally
|
||||
{
|
||||
pickLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (amount > 1)
|
||||
msgToSend += " " + GetText(gid, strs.pick_pl(prefix));
|
||||
else
|
||||
msgToSend += " " + GetText(gid, strs.pick_sn(prefix));
|
||||
|
||||
//get the image
|
||||
using (var stream = GetRandomCurrencyImage(pass, out var ext))
|
||||
{
|
||||
// send it
|
||||
var msg = await ch.SendFileAsync(stream, $"img.{ext}", msgToSend).ConfigureAwait(false);
|
||||
// return sent message's id (in order to be able to delete it when it's picked)
|
||||
return msg.Id;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// if sending fails, return null as message id
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
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();
|
||||
// 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))
|
||||
{
|
||||
// try to send the message with the currency image
|
||||
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass).ConfigureAwait(false);
|
||||
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);
|
||||
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).ConfigureAwait(false);
|
||||
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)
|
||||
/// <summary>
|
||||
/// Generate a hexadecimal string from 1000 to ffff.
|
||||
/// </summary>
|
||||
/// <returns>A hexadecimal string from 1000 to ffff</returns>
|
||||
private string GenerateCurrencyPassword()
|
||||
{
|
||||
// generate a number from 1000 to ffff
|
||||
var num = _rng.Next(4096, 65536);
|
||||
// convert it to hexadecimal
|
||||
return num.ToString("x4");
|
||||
}
|
||||
|
||||
public async Task<long> PickAsync(ulong gid, ITextChannel ch, ulong uid, string pass)
|
||||
{
|
||||
await pickLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
long amount;
|
||||
ulong[] ids;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow.PlantedCurrency.Add(new PlantedCurrency
|
||||
// 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();
|
||||
// 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();
|
||||
// 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();
|
||||
// remove them from the database
|
||||
uow.RemoveRange(entries);
|
||||
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
Amount = amount,
|
||||
GuildId = gid,
|
||||
ChannelId = cid,
|
||||
Password = pass,
|
||||
UserId = uid,
|
||||
MessageId = mid,
|
||||
});
|
||||
await uow.SaveChangesAsync();
|
||||
// give the picked currency to the user
|
||||
await _cs.AddAsync(uid, "Picked currency", amount, gamble: false);
|
||||
}
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// delete all of the plant messages which have just been picked
|
||||
var _ = ch.DeleteMessagesAsync(ids);
|
||||
}
|
||||
catch { }
|
||||
|
||||
// return the amount of currency the user picked
|
||||
return amount;
|
||||
}
|
||||
finally
|
||||
{
|
||||
pickLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (amount > 1)
|
||||
msgToSend += " " + GetText(gid, strs.pick_pl(prefix));
|
||||
else
|
||||
msgToSend += " " + GetText(gid, strs.pick_sn(prefix));
|
||||
|
||||
//get the image
|
||||
using (var stream = GetRandomCurrencyImage(pass, out var ext))
|
||||
{
|
||||
// send it
|
||||
var msg = await ch.SendFileAsync(stream, $"img.{ext}", msgToSend).ConfigureAwait(false);
|
||||
// return sent message's id (in order to be able to delete it when it's picked)
|
||||
return msg.Id;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// if sending fails, return null as message id
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
// 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))
|
||||
{
|
||||
// try to send the message with the currency image
|
||||
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass).ConfigureAwait(false);
|
||||
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);
|
||||
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).ConfigureAwait(false);
|
||||
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)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow.PlantedCurrency.Add(new PlantedCurrency
|
||||
{
|
||||
Amount = amount,
|
||||
GuildId = gid,
|
||||
ChannelId = cid,
|
||||
Password = pass,
|
||||
UserId = uid,
|
||||
MessageId = mid,
|
||||
});
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Services;
|
||||
using Discord.WebSocket;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class VoteModel
|
||||
{
|
||||
public class VoteModel
|
||||
{
|
||||
[JsonPropertyName("userId")]
|
||||
public ulong UserId { get; set; }
|
||||
}
|
||||
[JsonPropertyName("userId")]
|
||||
public ulong UserId { get; set; }
|
||||
}
|
||||
|
||||
public class VoteRewardService : INService, IReadyExecutor
|
||||
public class VoteRewardService : INService, IReadyExecutor
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ICurrencyService _currencyService;
|
||||
private readonly GamblingConfigService _gamb;
|
||||
private HttpClient _http;
|
||||
|
||||
public VoteRewardService(
|
||||
DiscordSocketClient client,
|
||||
IBotCredentials creds,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ICurrencyService currencyService,
|
||||
GamblingConfigService gamb)
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ICurrencyService _currencyService;
|
||||
private readonly GamblingConfigService _gamb;
|
||||
private HttpClient _http;
|
||||
|
||||
public VoteRewardService(
|
||||
DiscordSocketClient client,
|
||||
IBotCredentials creds,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ICurrencyService currencyService,
|
||||
GamblingConfigService gamb)
|
||||
{
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_currencyService = currencyService;
|
||||
_gamb = gamb;
|
||||
}
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_currencyService = currencyService;
|
||||
_gamb = gamb;
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
if (_client.ShardId != 0)
|
||||
return;
|
||||
|
||||
_http = new HttpClient(new HttpClientHandler()
|
||||
{
|
||||
if (_client.ShardId != 0)
|
||||
return;
|
||||
AllowAutoRedirect = false,
|
||||
ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||
});
|
||||
|
||||
_http = new HttpClient(new HttpClientHandler()
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||
});
|
||||
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(30000);
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(30000);
|
||||
|
||||
var topggKey = _creds.Votes?.TopggKey;
|
||||
var topggServiceUrl = _creds.Votes?.TopggServiceUrl;
|
||||
var topggKey = _creds.Votes?.TopggKey;
|
||||
var topggServiceUrl = _creds.Votes?.TopggServiceUrl;
|
||||
|
||||
try
|
||||
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");
|
||||
var res = await _http.GetStringAsync(uri);
|
||||
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
|
||||
|
||||
if (data is { Count: > 0 })
|
||||
{
|
||||
_http.DefaultRequestHeaders.Authorization = new(topggKey);
|
||||
var uri = new Uri(new(topggServiceUrl), "topgg/new");
|
||||
var res = await _http.GetStringAsync(uri);
|
||||
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
|
||||
var ids = data.Select(x => x.UserId).ToList();
|
||||
|
||||
if (data is { Count: > 0 })
|
||||
{
|
||||
var ids = data.Select(x => x.UserId).ToList();
|
||||
await _currencyService.AddBulkAsync(ids,
|
||||
data.Select(_ => "top.gg vote reward"),
|
||||
data.Select(x => _gamb.Data.VoteReward),
|
||||
true);
|
||||
|
||||
await _currencyService.AddBulkAsync(ids,
|
||||
data.Select(_ => "top.gg vote reward"),
|
||||
data.Select(x => _gamb.Data.VoteReward),
|
||||
true);
|
||||
|
||||
Log.Information("Rewarding {Count} top.gg voters", ids.Count());
|
||||
}
|
||||
Log.Information("Rewarding {Count} top.gg voters", ids.Count());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Critical error loading top.gg vote rewards.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Critical error loading top.gg vote rewards.");
|
||||
}
|
||||
|
||||
var discordsKey = _creds.Votes?.DiscordsKey;
|
||||
var discordsServiceUrl = _creds.Votes?.DiscordsServiceUrl;
|
||||
var discordsKey = _creds.Votes?.DiscordsKey;
|
||||
var discordsServiceUrl = _creds.Votes?.DiscordsServiceUrl;
|
||||
|
||||
try
|
||||
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"));
|
||||
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
|
||||
|
||||
if (data is { Count: > 0 })
|
||||
{
|
||||
_http.DefaultRequestHeaders.Authorization = new(discordsKey);
|
||||
var res = await _http.GetStringAsync(new Uri(new(discordsServiceUrl), "discords/new"));
|
||||
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
|
||||
|
||||
if (data is { Count: > 0 })
|
||||
{
|
||||
var ids = data.Select(x => x.UserId).ToList();
|
||||
var ids = data.Select(x => x.UserId).ToList();
|
||||
|
||||
await _currencyService.AddBulkAsync(ids,
|
||||
data.Select(_ => "discords.com vote reward"),
|
||||
data.Select(x => _gamb.Data.VoteReward),
|
||||
true);
|
||||
await _currencyService.AddBulkAsync(ids,
|
||||
data.Select(_ => "discords.com vote reward"),
|
||||
data.Select(x => _gamb.Data.VoteReward),
|
||||
true);
|
||||
|
||||
Log.Information("Rewarding {Count} discords.com voters", ids.Count());
|
||||
}
|
||||
Log.Information("Rewarding {Count} discords.com voters", ids.Count());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Critical error loading discords.com vote rewards.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Critical error loading discords.com vote rewards.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,527 +2,523 @@
|
||||
using NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class WaifuService : INService
|
||||
{
|
||||
public class WaifuService : INService
|
||||
public class FullWaifuInfo
|
||||
{
|
||||
public class FullWaifuInfo
|
||||
{
|
||||
public WaifuInfo Waifu { get; set; }
|
||||
public IEnumerable<string> Claims { get; set; }
|
||||
public int Divorces { get; set; }
|
||||
}
|
||||
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;
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly IDataCache _cache;
|
||||
private readonly GamblingConfigService _gss;
|
||||
|
||||
public WaifuService(DbService db, ICurrencyService cs, IDataCache cache,
|
||||
GamblingConfigService gss)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
_cache = cache;
|
||||
_gss = gss;
|
||||
}
|
||||
public WaifuService(DbService db, ICurrencyService cs, IDataCache cache,
|
||||
GamblingConfigService gss)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
_cache = cache;
|
||||
_gss = gss;
|
||||
}
|
||||
|
||||
public async Task<bool> WaifuTransfer(IUser owner, ulong waifuId, IUser newOwner)
|
||||
{
|
||||
if (owner.Id == newOwner.Id || waifuId == newOwner.Id)
|
||||
return false;
|
||||
public async Task<bool> WaifuTransfer(IUser owner, ulong waifuId, IUser newOwner)
|
||||
{
|
||||
if (owner.Id == newOwner.Id || waifuId == newOwner.Id)
|
||||
return false;
|
||||
|
||||
var settings = _gss.Data;
|
||||
var settings = _gss.Data;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var waifu = uow.WaifuInfo.ByWaifuUserId(waifuId);
|
||||
var ownerUser = uow.GetOrCreateUser(owner);
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var waifu = uow.WaifuInfo.ByWaifuUserId(waifuId);
|
||||
var ownerUser = uow.GetOrCreateUser(owner);
|
||||
|
||||
// owner has to be the owner of the waifu
|
||||
if (waifu is null || waifu.ClaimerId != ownerUser.Id)
|
||||
return false;
|
||||
// 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,
|
||||
// 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))
|
||||
{
|
||||
// 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)
|
||||
waifu.Price = settings.Waifu.MinPrice;
|
||||
}
|
||||
else // if not, pay 10% fee
|
||||
{
|
||||
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, gamble: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
waifu.Price = (int) (waifu.Price * 0.95); // half of 10% = 5% price reduction
|
||||
if (waifu.Price < settings.Waifu.MinPrice)
|
||||
waifu.Price = settings.Waifu.MinPrice;
|
||||
}
|
||||
|
||||
//new claimerId is the id of the new owner
|
||||
var newOwnerUser = uow.GetOrCreateUser(newOwner);
|
||||
waifu.ClaimerId = newOwnerUser.Id;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetResetPrice(IUser user)
|
||||
{
|
||||
var settings = _gss.Data;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
|
||||
|
||||
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();
|
||||
|
||||
return (int) Math.Ceiling(waifu.Price * 1.25f) +
|
||||
((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TryReset(IUser user)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var price = GetResetPrice(user);
|
||||
if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, gamble: true))
|
||||
// unable to pay 60% penalty
|
||||
return false;
|
||||
}
|
||||
|
||||
var affs = uow.WaifuUpdates
|
||||
.AsQueryable()
|
||||
.Where(w => w.User.UserId == user.Id
|
||||
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
||||
&& w.New != null);
|
||||
waifu.Price = (int)(waifu.Price * 0.7); // half of 60% = 30% price reduction
|
||||
if (waifu.Price < settings.Waifu.MinPrice)
|
||||
waifu.Price = settings.Waifu.MinPrice;
|
||||
}
|
||||
else // if not, pay 10% fee
|
||||
{
|
||||
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, gamble: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
//reset divorces to 0
|
||||
uow.WaifuUpdates.RemoveRange(divorces);
|
||||
var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
|
||||
//reset price, remove items
|
||||
//remove owner, remove affinity
|
||||
waifu.Price = 50;
|
||||
waifu.Items.Clear();
|
||||
waifu.ClaimerId = null;
|
||||
waifu.AffinityId = null;
|
||||
|
||||
//wives stay though
|
||||
|
||||
uow.SaveChanges();
|
||||
waifu.Price = (int) (waifu.Price * 0.95); // half of 10% = 5% price reduction
|
||||
if (waifu.Price < settings.Waifu.MinPrice)
|
||||
waifu.Price = settings.Waifu.MinPrice;
|
||||
}
|
||||
|
||||
return true;
|
||||
//new claimerId is the id of the new owner
|
||||
var newOwnerUser = uow.GetOrCreateUser(newOwner);
|
||||
waifu.ClaimerId = newOwnerUser.Id;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<(WaifuInfo, bool, WaifuClaimResult)> ClaimWaifuAsync(IUser user, IUser target, int amount)
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetResetPrice(IUser user)
|
||||
{
|
||||
var settings = _gss.Data;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var settings = _gss.Data;
|
||||
WaifuClaimResult result;
|
||||
WaifuInfo w;
|
||||
bool isAffinity;
|
||||
using (var uow = _db.GetDbContext())
|
||||
var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
|
||||
|
||||
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();
|
||||
|
||||
return (int) Math.Ceiling(waifu.Price * 1.25f) +
|
||||
((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TryReset(IUser user)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var price = GetResetPrice(user);
|
||||
if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, gamble: true))
|
||||
return false;
|
||||
|
||||
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);
|
||||
|
||||
//reset changes of heart to 0
|
||||
uow.WaifuUpdates.RemoveRange(affs);
|
||||
//reset divorces to 0
|
||||
uow.WaifuUpdates.RemoveRange(divorces);
|
||||
var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
|
||||
//reset price, remove items
|
||||
//remove owner, remove affinity
|
||||
waifu.Price = 50;
|
||||
waifu.Items.Clear();
|
||||
waifu.ClaimerId = null;
|
||||
waifu.AffinityId = null;
|
||||
|
||||
//wives stay though
|
||||
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<(WaifuInfo, bool, WaifuClaimResult)> ClaimWaifuAsync(IUser user, IUser target, int amount)
|
||||
{
|
||||
var settings = _gss.Data;
|
||||
WaifuClaimResult result;
|
||||
WaifuInfo w;
|
||||
bool isAffinity;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
w = uow.WaifuInfo.ByWaifuUserId(target.Id);
|
||||
isAffinity = (w?.Affinity?.UserId == user.Id);
|
||||
if (w is null)
|
||||
{
|
||||
w = uow.WaifuInfo.ByWaifuUserId(target.Id);
|
||||
isAffinity = (w?.Affinity?.UserId == user.Id);
|
||||
if (w is null)
|
||||
var claimer = uow.GetOrCreateUser(user);
|
||||
var waifu = uow.GetOrCreateUser(target);
|
||||
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
|
||||
{
|
||||
var claimer = uow.GetOrCreateUser(user);
|
||||
var waifu = uow.GetOrCreateUser(target);
|
||||
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
{
|
||||
uow.WaifuInfo.Add(w = new WaifuInfo()
|
||||
{
|
||||
Waifu = waifu,
|
||||
Claimer = claimer,
|
||||
Affinity = null,
|
||||
Price = amount
|
||||
});
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
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))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
{
|
||||
var oldClaimer = w.Claimer;
|
||||
w.Claimer = uow.GetOrCreateUser(user);
|
||||
w.Price = amount + (amount / 4);
|
||||
result = WaifuClaimResult.Success;
|
||||
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
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))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
{
|
||||
var oldClaimer = w.Claimer;
|
||||
w.Claimer = uow.GetOrCreateUser(user);
|
||||
w.Price = amount;
|
||||
result = WaifuClaimResult.Success;
|
||||
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
result = WaifuClaimResult.InsufficientAmount;
|
||||
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return (w, isAffinity, result);
|
||||
}
|
||||
|
||||
public async Task<(DiscordUser, bool, TimeSpan?)> ChangeAffinityAsync(IUser user, IGuildUser target)
|
||||
{
|
||||
DiscordUser oldAff = null;
|
||||
var success = false;
|
||||
TimeSpan? remaining = null;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var w = uow.WaifuInfo.ByWaifuUserId(user.Id);
|
||||
var newAff = target is null ? null : uow.GetOrCreateUser(target);
|
||||
if (w?.Affinity?.UserId == target?.Id)
|
||||
{
|
||||
}
|
||||
else if (!_cache.TryAddAffinityCooldown(user.Id, out remaining))
|
||||
{
|
||||
}
|
||||
else if (w is null)
|
||||
{
|
||||
var thisUser = uow.GetOrCreateUser(user);
|
||||
uow.WaifuInfo.Add(new WaifuInfo()
|
||||
uow.WaifuInfo.Add(w = new WaifuInfo()
|
||||
{
|
||||
Affinity = newAff,
|
||||
Waifu = thisUser,
|
||||
Price = 1,
|
||||
Claimer = null
|
||||
Waifu = waifu,
|
||||
Claimer = claimer,
|
||||
Affinity = null,
|
||||
Price = amount
|
||||
});
|
||||
success = true;
|
||||
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = thisUser,
|
||||
User = waifu,
|
||||
Old = null,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
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))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (w.Affinity != null)
|
||||
oldAff = w.Affinity;
|
||||
w.Affinity = newAff;
|
||||
success = true;
|
||||
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldAff,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return (oldAff, success, remaining);
|
||||
}
|
||||
|
||||
public IEnumerable<WaifuLbResult> GetTopWaifusAtPage(int page)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.WaifuInfo.GetTop(9, page * 9);
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetWaifuUserId(ulong ownerId, string name)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.WaifuInfo.GetWaifuUserId(ownerId, name);
|
||||
}
|
||||
|
||||
public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId)
|
||||
{
|
||||
DivorceResult result;
|
||||
TimeSpan? remaining = null;
|
||||
long amount = 0;
|
||||
WaifuInfo w = null;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = w.Price / 2;
|
||||
|
||||
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);
|
||||
result = DivorceResult.SucessWithPenalty;
|
||||
}
|
||||
else
|
||||
{
|
||||
await _cs.AddAsync(user.Id, "Waifu Refund", amount, gamble: true);
|
||||
|
||||
result = DivorceResult.Success;
|
||||
}
|
||||
|
||||
var oldClaimer = w.Claimer;
|
||||
w.Claimer = null;
|
||||
w.Claimer = uow.GetOrCreateUser(user);
|
||||
w.Price = amount + (amount / 4);
|
||||
result = WaifuClaimResult.Success;
|
||||
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = null,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return (w, result, amount, remaining);
|
||||
}
|
||||
|
||||
public async Task<bool> GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(from, "Bought waifu item", itemObj.Price, gamble: true))
|
||||
else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id,
|
||||
set => set.Include(x => x.Items)
|
||||
.Include(x => x.Claimer));
|
||||
if (w is null)
|
||||
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
|
||||
{
|
||||
uow.WaifuInfo.Add(w = new WaifuInfo()
|
||||
{
|
||||
Affinity = null,
|
||||
Claimer = null,
|
||||
Price = 1,
|
||||
Waifu = uow.GetOrCreateUser(giftedWaifu),
|
||||
});
|
||||
}
|
||||
|
||||
if (!itemObj.Negative)
|
||||
{
|
||||
w.Items.Add(new WaifuItem()
|
||||
{
|
||||
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;
|
||||
}
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
{
|
||||
w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
|
||||
if (w.Price < 1)
|
||||
w.Price = 1;
|
||||
}
|
||||
var oldClaimer = w.Claimer;
|
||||
w.Claimer = uow.GetOrCreateUser(user);
|
||||
w.Price = amount;
|
||||
result = WaifuClaimResult.Success;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public WaifuInfoStats GetFullWaifuInfoAsync(ulong targetId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var wi = uow.GetWaifuInfo(targetId);
|
||||
if (wi is null)
|
||||
{
|
||||
wi = new WaifuInfoStats
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
AffinityCount = 0,
|
||||
AffinityName = null,
|
||||
ClaimCount = 0,
|
||||
ClaimerName = null,
|
||||
Claims = new List<string>(),
|
||||
Fans = new List<string>(),
|
||||
DivorceCount = 0,
|
||||
FullName = null,
|
||||
Items = new List<WaifuItem>(),
|
||||
Price = 1
|
||||
};
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
|
||||
return wi;
|
||||
}
|
||||
else
|
||||
result = WaifuClaimResult.InsufficientAmount;
|
||||
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
public WaifuInfoStats GetFullWaifuInfoAsync(IGuildUser target)
|
||||
|
||||
return (w, isAffinity, result);
|
||||
}
|
||||
|
||||
public async Task<(DiscordUser, bool, TimeSpan?)> ChangeAffinityAsync(IUser user, IGuildUser target)
|
||||
{
|
||||
DiscordUser oldAff = null;
|
||||
var success = false;
|
||||
TimeSpan? remaining = null;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
var w = uow.WaifuInfo.ByWaifuUserId(user.Id);
|
||||
var newAff = target is null ? null : uow.GetOrCreateUser(target);
|
||||
if (w?.Affinity?.UserId == target?.Id)
|
||||
{
|
||||
var du = uow.GetOrCreateUser(target);
|
||||
|
||||
return GetFullWaifuInfoAsync(target.Id);
|
||||
}
|
||||
}
|
||||
else if (!_cache.TryAddAffinityCooldown(user.Id, out remaining))
|
||||
{
|
||||
}
|
||||
else if (w is null)
|
||||
{
|
||||
var thisUser = uow.GetOrCreateUser(user);
|
||||
uow.WaifuInfo.Add(new WaifuInfo()
|
||||
{
|
||||
Affinity = newAff,
|
||||
Waifu = thisUser,
|
||||
Price = 1,
|
||||
Claimer = null
|
||||
});
|
||||
success = true;
|
||||
|
||||
public string GetClaimTitle(int count)
|
||||
{
|
||||
ClaimTitle title;
|
||||
if (count == 0)
|
||||
title = ClaimTitle.Lonely;
|
||||
else if (count == 1)
|
||||
title = ClaimTitle.Devoted;
|
||||
else if (count < 3)
|
||||
title = ClaimTitle.Rookie;
|
||||
else if (count < 6)
|
||||
title = ClaimTitle.Schemer;
|
||||
else if (count < 10)
|
||||
title = ClaimTitle.Dilettante;
|
||||
else if (count < 17)
|
||||
title = ClaimTitle.Intermediate;
|
||||
else if (count < 25)
|
||||
title = ClaimTitle.Seducer;
|
||||
else if (count < 35)
|
||||
title = ClaimTitle.Expert;
|
||||
else if (count < 50)
|
||||
title = ClaimTitle.Veteran;
|
||||
else if (count < 75)
|
||||
title = ClaimTitle.Incubis;
|
||||
else if (count < 100)
|
||||
title = ClaimTitle.Harem_King;
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = thisUser,
|
||||
Old = null,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
}
|
||||
else
|
||||
title = ClaimTitle.Harem_God;
|
||||
{
|
||||
if (w.Affinity != null)
|
||||
oldAff = w.Affinity;
|
||||
w.Affinity = newAff;
|
||||
success = true;
|
||||
|
||||
return title.ToString().Replace('_', ' ');
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldAff,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public string GetAffinityTitle(int count)
|
||||
{
|
||||
AffinityTitle title;
|
||||
if (count < 1)
|
||||
title = AffinityTitle.Pure;
|
||||
else if (count < 2)
|
||||
title = AffinityTitle.Faithful;
|
||||
else if (count < 4)
|
||||
title = AffinityTitle.Playful;
|
||||
else if (count < 8)
|
||||
title = AffinityTitle.Cheater;
|
||||
else if (count < 11)
|
||||
title = AffinityTitle.Tainted;
|
||||
else if (count < 15)
|
||||
title = AffinityTitle.Corrupted;
|
||||
else if (count < 20)
|
||||
title = AffinityTitle.Lewd;
|
||||
else if (count < 25)
|
||||
title = AffinityTitle.Sloot;
|
||||
else if (count < 35)
|
||||
title = AffinityTitle.Depraved;
|
||||
else
|
||||
title = AffinityTitle.Harlot;
|
||||
return (oldAff, success, remaining);
|
||||
}
|
||||
|
||||
return title.ToString().Replace('_', ' ');
|
||||
}
|
||||
|
||||
public IReadOnlyList<WaifuItemModel> GetWaifuItems()
|
||||
public IEnumerable<WaifuLbResult> GetTopWaifusAtPage(int page)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
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 uow.WaifuInfo.GetTop(9, page * 9);
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetWaifuUserId(ulong ownerId, string name)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.WaifuInfo.GetWaifuUserId(ownerId, name);
|
||||
}
|
||||
|
||||
public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId)
|
||||
{
|
||||
DivorceResult result;
|
||||
TimeSpan? remaining = null;
|
||||
long amount = 0;
|
||||
WaifuInfo w = null;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = w.Price / 2;
|
||||
|
||||
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);
|
||||
result = DivorceResult.SucessWithPenalty;
|
||||
}
|
||||
else
|
||||
{
|
||||
await _cs.AddAsync(user.Id, "Waifu Refund", amount, gamble: true);
|
||||
|
||||
result = DivorceResult.Success;
|
||||
}
|
||||
|
||||
var oldClaimer = w.Claimer;
|
||||
w.Claimer = null;
|
||||
|
||||
uow.WaifuUpdates.Add(new WaifuUpdate()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = null,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return (w, result, amount, remaining);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
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 WaifuInfo()
|
||||
{
|
||||
Affinity = null,
|
||||
Claimer = null,
|
||||
Price = 1,
|
||||
Waifu = uow.GetOrCreateUser(giftedWaifu),
|
||||
});
|
||||
}
|
||||
|
||||
if (!itemObj.Negative)
|
||||
{
|
||||
w.Items.Add(new WaifuItem()
|
||||
{
|
||||
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
|
||||
{
|
||||
w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
|
||||
if (w.Price < 1)
|
||||
w.Price = 1;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public WaifuInfoStats GetFullWaifuInfoAsync(ulong targetId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var wi = uow.GetWaifuInfo(targetId);
|
||||
if (wi is null)
|
||||
{
|
||||
wi = new WaifuInfoStats
|
||||
{
|
||||
AffinityCount = 0,
|
||||
AffinityName = null,
|
||||
ClaimCount = 0,
|
||||
ClaimerName = null,
|
||||
Claims = new List<string>(),
|
||||
Fans = new List<string>(),
|
||||
DivorceCount = 0,
|
||||
FullName = null,
|
||||
Items = new List<WaifuItem>(),
|
||||
Price = 1
|
||||
};
|
||||
}
|
||||
|
||||
return wi;
|
||||
}
|
||||
}
|
||||
public WaifuInfoStats GetFullWaifuInfoAsync(IGuildUser target)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var du = uow.GetOrCreateUser(target);
|
||||
|
||||
return GetFullWaifuInfoAsync(target.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetClaimTitle(int count)
|
||||
{
|
||||
ClaimTitle title;
|
||||
if (count == 0)
|
||||
title = ClaimTitle.Lonely;
|
||||
else if (count == 1)
|
||||
title = ClaimTitle.Devoted;
|
||||
else if (count < 3)
|
||||
title = ClaimTitle.Rookie;
|
||||
else if (count < 6)
|
||||
title = ClaimTitle.Schemer;
|
||||
else if (count < 10)
|
||||
title = ClaimTitle.Dilettante;
|
||||
else if (count < 17)
|
||||
title = ClaimTitle.Intermediate;
|
||||
else if (count < 25)
|
||||
title = ClaimTitle.Seducer;
|
||||
else if (count < 35)
|
||||
title = ClaimTitle.Expert;
|
||||
else if (count < 50)
|
||||
title = ClaimTitle.Veteran;
|
||||
else if (count < 75)
|
||||
title = ClaimTitle.Incubis;
|
||||
else if (count < 100)
|
||||
title = ClaimTitle.Harem_King;
|
||||
else
|
||||
title = ClaimTitle.Harem_God;
|
||||
|
||||
return title.ToString().Replace('_', ' ');
|
||||
}
|
||||
|
||||
public string GetAffinityTitle(int count)
|
||||
{
|
||||
AffinityTitle title;
|
||||
if (count < 1)
|
||||
title = AffinityTitle.Pure;
|
||||
else if (count < 2)
|
||||
title = AffinityTitle.Faithful;
|
||||
else if (count < 4)
|
||||
title = AffinityTitle.Playful;
|
||||
else if (count < 8)
|
||||
title = AffinityTitle.Cheater;
|
||||
else if (count < 11)
|
||||
title = AffinityTitle.Tainted;
|
||||
else if (count < 15)
|
||||
title = AffinityTitle.Corrupted;
|
||||
else if (count < 20)
|
||||
title = AffinityTitle.Lewd;
|
||||
else if (count < 25)
|
||||
title = AffinityTitle.Sloot;
|
||||
else if (count < 35)
|
||||
title = AffinityTitle.Depraved;
|
||||
else
|
||||
title = AffinityTitle.Harlot;
|
||||
|
||||
return title.ToString().Replace('_', ' ');
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -14,462 +11,459 @@ using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Administration;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
[Group]
|
||||
public class ShopCommands : GamblingSubmodule<IShopService>
|
||||
{
|
||||
[Group]
|
||||
public class ShopCommands : GamblingSubmodule<IShopService>
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
public enum Role
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
Role
|
||||
}
|
||||
|
||||
public enum Role
|
||||
{
|
||||
Role
|
||||
}
|
||||
public enum List
|
||||
{
|
||||
List
|
||||
}
|
||||
|
||||
public enum List
|
||||
{
|
||||
List
|
||||
}
|
||||
|
||||
public ShopCommands(DbService db, ICurrencyService cs, GamblingConfigService gamblingConf)
|
||||
: base(gamblingConf)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
}
|
||||
public ShopCommands(DbService db, ICurrencyService cs, GamblingConfigService gamblingConf)
|
||||
: base(gamblingConf)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
private Task ShopInternalAsync(int page = 0)
|
||||
{
|
||||
if (page < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(page));
|
||||
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) =>
|
||||
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 (int i = 0; i < theseEntries.Length; i++)
|
||||
{
|
||||
var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray();
|
||||
var entry = theseEntries[i];
|
||||
embed.AddField(
|
||||
$"#{curPage * 9 + i + 1} - {entry.Price}{CurrencySign}",
|
||||
EntryToString(entry),
|
||||
true);
|
||||
}
|
||||
return embed;
|
||||
}, entries.Count, 9, true);
|
||||
}
|
||||
|
||||
if (!theseEntries.Any())
|
||||
return _eb.Create().WithErrorColor()
|
||||
.WithDescription(GetText(strs.shop_none));
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.shop));
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Shop(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return ShopInternalAsync(page);
|
||||
}
|
||||
|
||||
for (int 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, true);
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Buy(int index)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
ShopEntry entry;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Shop(int page = 1)
|
||||
if (entry is null)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return ShopInternalAsync(page);
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Buy(int index)
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
ShopEntry entry;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
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();
|
||||
}
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
var role = ctx.Guild.GetRole(entry.RoleId);
|
||||
|
||||
if (entry is null)
|
||||
if (role is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.shop_role_not_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
{
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
var role = ctx.Guild.GetRole(entry.RoleId);
|
||||
|
||||
if (role is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.shop_role_not_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (guser.RoleIds.Any(id => id == role.Id))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.shop_role_already_bought).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
await guser.AddRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error adding shop role");
|
||||
await _cs.AddAsync(ctx.User.Id, $"Shop error refund", entry.Price).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.shop_role_purchase_error).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var profit = GetProfitAmount(entry.Price);
|
||||
await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", profit).ConfigureAwait(false);
|
||||
await _cs.AddAsync(ctx.Client.CurrentUser.Id, $"Shop sell item - cut", entry.Price - profit).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_role_purchase(Format.Bold(role.Name))).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (entry.Type == ShopEntryType.List)
|
||||
if (guser.RoleIds.Any(id => id == role.Id))
|
||||
{
|
||||
if (entry.Items.Count == 0)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_role_already_bought).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.out_of_stock).ConfigureAwait(false);
|
||||
await guser.AddRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error adding shop role");
|
||||
await _cs.AddAsync(ctx.User.Id, $"Shop error refund", entry.Price).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.shop_role_purchase_error).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var profit = GetProfitAmount(entry.Price);
|
||||
await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", profit).ConfigureAwait(false);
|
||||
await _cs.AddAsync(ctx.Client.CurrentUser.Id, $"Shop sell item - cut", entry.Price - profit).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_role_purchase(Format.Bold(role.Name))).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (entry.Type == ShopEntryType.List)
|
||||
{
|
||||
if (entry.Items.Count == 0)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.out_of_stock).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var item = entry.Items.ToArray()[new NadekoRandom().Next(0, entry.Items.Count)];
|
||||
var item = entry.Items.ToArray()[new NadekoRandom().Next(0, entry.Items.Count)];
|
||||
|
||||
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price).ConfigureAwait(false))
|
||||
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price).ConfigureAwait(false))
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var x = uow.Set<ShopEntryItem>().Remove(item);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
try
|
||||
{
|
||||
await (await ctx.User.GetOrCreateDMChannelAsync().ConfigureAwait(false))
|
||||
.EmbedAsync(_eb.Create().WithOkColor()
|
||||
var x = uow.Set<ShopEntryItem>().Remove(item);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
try
|
||||
{
|
||||
await (await ctx.User.GetOrCreateDMChannelAsync().ConfigureAwait(false))
|
||||
.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))
|
||||
.ConfigureAwait(false);
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await _cs.AddAsync(entry.AuthorId,
|
||||
$"Shop sell item - {entry.Name}",
|
||||
GetProfitAmount(entry.Price)).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
await _cs.AddAsync(entry.AuthorId,
|
||||
$"Shop sell item - {entry.Name}",
|
||||
GetProfitAmount(entry.Price)).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _cs.AddAsync(ctx.User.Id,
|
||||
$"Shop error refund - {entry.Name}",
|
||||
entry.Price).ConfigureAwait(false);
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
await _cs.AddAsync(ctx.User.Id,
|
||||
$"Shop error refund - {entry.Name}",
|
||||
entry.Price).ConfigureAwait(false);
|
||||
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 = entries.ElementAtOrDefault(index);
|
||||
if (entry != null)
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries);
|
||||
entry = entries.ElementAtOrDefault(index);
|
||||
if (entry != null)
|
||||
if (entry.Items.Add(item))
|
||||
{
|
||||
if (entry.Items.Add(item))
|
||||
{
|
||||
uow.SaveChanges();
|
||||
}
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.shop_buy_error).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_item_purchase).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
await ReplyErrorLocalizedAsync(strs.shop_buy_error).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_item_purchase).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static long GetProfitAmount(int price) =>
|
||||
(int)(Math.Ceiling(0.90 * price));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task ShopAdd(Role _, int price, [Leftover] IRole role)
|
||||
{
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = "-",
|
||||
Price = price,
|
||||
Type = ShopEntryType.Role,
|
||||
AuthorId = ctx.User.Id,
|
||||
RoleId = role.Id,
|
||||
RoleName = role.Name
|
||||
};
|
||||
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
|
||||
};
|
||||
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopAdd(List _, int price, [Leftover] string name)
|
||||
{
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = name.TrimTo(100),
|
||||
Price = price,
|
||||
Type = ShopEntryType.List,
|
||||
AuthorId = ctx.User.Id,
|
||||
Items = new HashSet<ShopEntryItem>(),
|
||||
};
|
||||
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
|
||||
};
|
||||
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopListAdd(int index, [Leftover] string itemText)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
var item = new ShopEntryItem()
|
||||
{
|
||||
Text = itemText
|
||||
};
|
||||
ShopEntry entry;
|
||||
bool rightType = false;
|
||||
bool added = false;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries);
|
||||
entry = entries.ElementAtOrDefault(index);
|
||||
if (entry != null && (rightType = (entry.Type == ShopEntryType.List)))
|
||||
{
|
||||
if (added = entry.Items.Add(item))
|
||||
{
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entry is null)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
|
||||
else if (!rightType)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_wrong_type).ConfigureAwait(false);
|
||||
else if (added == false)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_list_item_not_unique).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_list_item_added).ConfigureAwait(false);
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopRemove(int index)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
ShopEntry removed;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
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);
|
||||
if (removed != null)
|
||||
private static long GetProfitAmount(int price) =>
|
||||
(int)(Math.Ceiling(0.90 * price));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task ShopAdd(Role _, int price, [Leftover] IRole role)
|
||||
{
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = "-",
|
||||
Price = price,
|
||||
Type = ShopEntryType.Role,
|
||||
AuthorId = ctx.User.Id,
|
||||
RoleId = role.Id,
|
||||
RoleName = role.Name
|
||||
};
|
||||
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
|
||||
};
|
||||
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopAdd(List _, int price, [Leftover] string name)
|
||||
{
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
{
|
||||
Name = name.TrimTo(100),
|
||||
Price = price,
|
||||
Type = ShopEntryType.List,
|
||||
AuthorId = ctx.User.Id,
|
||||
Items = new HashSet<ShopEntryItem>(),
|
||||
};
|
||||
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
|
||||
};
|
||||
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopListAdd(int index, [Leftover] string itemText)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
var item = new ShopEntryItem()
|
||||
{
|
||||
Text = itemText
|
||||
};
|
||||
ShopEntry entry;
|
||||
bool rightType = false;
|
||||
bool added = false;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries);
|
||||
entry = entries.ElementAtOrDefault(index);
|
||||
if (entry != null && (rightType = (entry.Type == ShopEntryType.List)))
|
||||
{
|
||||
if (added = entry.Items.Add(item))
|
||||
{
|
||||
uow.RemoveRange(removed.Items);
|
||||
uow.Remove(removed);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
if (removed is null)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(removed)
|
||||
.WithTitle(GetText(strs.shop_item_rm))).ConfigureAwait(false);
|
||||
}
|
||||
if (entry is null)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
|
||||
else if (!rightType)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_wrong_type).ConfigureAwait(false);
|
||||
else if (added == false)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_list_item_not_unique).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_list_item_added).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopChangePrice(int index, int price)
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopRemove(int index)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
ShopEntry removed;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
if (--index < 0 || price <= 0)
|
||||
return;
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set
|
||||
.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items));
|
||||
|
||||
var succ = await _service.ChangeEntryPriceAsync(ctx.Guild.Id, index, price);
|
||||
if (succ)
|
||||
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
|
||||
removed = entries.ElementAtOrDefault(index);
|
||||
if (removed != null)
|
||||
{
|
||||
await ShopInternalAsync(index / 9);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
uow.RemoveRange(removed.Items);
|
||||
uow.Remove(removed);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopChangeName(int index, [Leftover] string newName)
|
||||
if (removed is null)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(removed)
|
||||
.WithTitle(GetText(strs.shop_item_rm))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopChangePrice(int index, int price)
|
||||
{
|
||||
if (--index < 0 || price <= 0)
|
||||
return;
|
||||
|
||||
var succ = await _service.ChangeEntryPriceAsync(ctx.Guild.Id, index, price);
|
||||
if (succ)
|
||||
{
|
||||
if (--index < 0 || string.IsNullOrWhiteSpace(newName))
|
||||
return;
|
||||
await ShopInternalAsync(index / 9);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[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)
|
||||
{
|
||||
await ShopInternalAsync(index / 9);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopSwap(int index1, int index2)
|
||||
var succ = await _service.ChangeEntryNameAsync(ctx.Guild.Id, index, newName);
|
||||
if (succ)
|
||||
{
|
||||
if (--index1 < 0 || --index2 < 0 || index1 == index2)
|
||||
return;
|
||||
await ShopInternalAsync(index / 9);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[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)
|
||||
{
|
||||
await ShopInternalAsync(index1 / 9);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
var succ = await _service.SwapEntriesAsync(ctx.Guild.Id, index1, index2);
|
||||
if (succ)
|
||||
{
|
||||
await ShopInternalAsync(index1 / 9);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopMove(int fromIndex, int toIndex)
|
||||
{
|
||||
if (--fromIndex < 0 || --toIndex < 0 || fromIndex == toIndex)
|
||||
return;
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopMove(int fromIndex, int toIndex)
|
||||
{
|
||||
if (--fromIndex < 0 || --toIndex < 0 || fromIndex == toIndex)
|
||||
return;
|
||||
|
||||
var succ = await _service.MoveEntryAsync(ctx.Guild.Id, fromIndex, toIndex);
|
||||
if (succ)
|
||||
{
|
||||
await ShopInternalAsync(toIndex / 9);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
var succ = await _service.MoveEntryAsync(ctx.Guild.Id, fromIndex, toIndex);
|
||||
if (succ)
|
||||
{
|
||||
await ShopInternalAsync(toIndex / 9);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public IEmbedBuilder EntryToEmbed(ShopEntry entry)
|
||||
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), entry.Name, 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;
|
||||
}
|
||||
|
||||
public string EntryToString(ShopEntry entry)
|
||||
{
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
{
|
||||
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), entry.Name, 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 GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE")));
|
||||
}
|
||||
|
||||
public string EntryToString(ShopEntry entry)
|
||||
else if (entry.Type == ShopEntryType.List)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return GetText(strs.unique_items_left(entry.Items.Count)) + "\n" + entry.Name;
|
||||
}
|
||||
//else if (entry.Type == ShopEntryType.Infinite_List)
|
||||
//{
|
||||
|
||||
//}
|
||||
return "";
|
||||
return GetText(strs.unique_items_left(entry.Items.Count)) + "\n" + entry.Name;
|
||||
}
|
||||
//else if (entry.Type == ShopEntryType.Infinite_List)
|
||||
//{
|
||||
|
||||
//}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -21,248 +18,247 @@ using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
[Group]
|
||||
public class SlotCommands : GamblingSubmodule<GamblingService>
|
||||
{
|
||||
[Group]
|
||||
public class SlotCommands : GamblingSubmodule<GamblingService>
|
||||
private static long _totalBet;
|
||||
private static long _totalPaidOut;
|
||||
|
||||
private static readonly HashSet<ulong> _runningUsers = new HashSet<ulong>();
|
||||
|
||||
//here is a payout chart
|
||||
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
|
||||
//thanks to judge for helping me with this
|
||||
|
||||
private readonly IImageCache _images;
|
||||
private FontProvider _fonts;
|
||||
private readonly DbService _db;
|
||||
|
||||
public SlotCommands(IDataCache data,
|
||||
FontProvider fonts, DbService db,
|
||||
GamblingConfigService gamb) : base(gamb)
|
||||
{
|
||||
private static long _totalBet;
|
||||
private static long _totalPaidOut;
|
||||
_images = data.LocalImages;
|
||||
_fonts = fonts;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
private static readonly HashSet<ulong> _runningUsers = new HashSet<ulong>();
|
||||
public sealed class SlotMachine
|
||||
{
|
||||
public const int MaxValue = 5;
|
||||
|
||||
//here is a payout chart
|
||||
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
|
||||
//thanks to judge for helping me with this
|
||||
|
||||
private readonly IImageCache _images;
|
||||
private FontProvider _fonts;
|
||||
private readonly DbService _db;
|
||||
|
||||
public SlotCommands(IDataCache data,
|
||||
FontProvider fonts, DbService db,
|
||||
GamblingConfigService gamb) : base(gamb)
|
||||
static readonly List<Func<int[], int>> _winningCombos = new List<Func<int[], int>>()
|
||||
{
|
||||
_images = data.LocalImages;
|
||||
_fonts = fonts;
|
||||
_db = db;
|
||||
//three flowers
|
||||
(arr) => arr.All(a=>a==MaxValue) ? 30 : 0,
|
||||
//three of the same
|
||||
(arr) => !arr.Any(a => a != arr[0]) ? 10 : 0,
|
||||
//two flowers
|
||||
(arr) => arr.Count(a => a == MaxValue) == 2 ? 4 : 0,
|
||||
//one flower
|
||||
(arr) => arr.Any(a => a == MaxValue) ? 1 : 0,
|
||||
};
|
||||
|
||||
public static SlotResult Pull()
|
||||
{
|
||||
var numbers = new int[3];
|
||||
for (var i = 0; i < numbers.Length; i++)
|
||||
{
|
||||
numbers[i] = new NadekoRandom().Next(0, MaxValue + 1);
|
||||
}
|
||||
var multi = 0;
|
||||
foreach (var t in _winningCombos)
|
||||
{
|
||||
multi = t(numbers);
|
||||
if (multi != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return new SlotResult(numbers, multi);
|
||||
}
|
||||
|
||||
public sealed class SlotMachine
|
||||
public struct SlotResult
|
||||
{
|
||||
public const int MaxValue = 5;
|
||||
|
||||
static readonly List<Func<int[], int>> _winningCombos = new List<Func<int[], int>>()
|
||||
public int[] Numbers { get; }
|
||||
public int Multiplier { get; }
|
||||
public SlotResult(int[] nums, int multi)
|
||||
{
|
||||
//three flowers
|
||||
(arr) => arr.All(a=>a==MaxValue) ? 30 : 0,
|
||||
//three of the same
|
||||
(arr) => !arr.Any(a => a != arr[0]) ? 10 : 0,
|
||||
//two flowers
|
||||
(arr) => arr.Count(a => a == MaxValue) == 2 ? 4 : 0,
|
||||
//one flower
|
||||
(arr) => arr.Any(a => a == MaxValue) ? 1 : 0,
|
||||
};
|
||||
Numbers = nums;
|
||||
Multiplier = multi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static SlotResult Pull()
|
||||
[NadekoCommand, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SlotStats()
|
||||
{
|
||||
//i remembered to not be a moron
|
||||
var paid = _totalPaidOut;
|
||||
var bet = _totalBet;
|
||||
|
||||
if (bet <= 0)
|
||||
bet = 1;
|
||||
|
||||
var embed = _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}%");
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SlotTest(int tests = 1000)
|
||||
{
|
||||
if (tests <= 0)
|
||||
return;
|
||||
//multi vs how many times it occured
|
||||
var dict = new Dictionary<int, int>();
|
||||
for (int i = 0; i < tests; i++)
|
||||
{
|
||||
var res = SlotMachine.Pull();
|
||||
if (dict.ContainsKey(res.Multiplier))
|
||||
dict[res.Multiplier] += 1;
|
||||
else
|
||||
dict.Add(res.Multiplier, 1);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
const int bet = 1;
|
||||
int payout = 0;
|
||||
foreach (var key in dict.Keys.OrderByDescending(x => x))
|
||||
{
|
||||
sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
|
||||
payout += key * dict[key];
|
||||
}
|
||||
await SendConfirmAsync("Slot Test Results", sb.ToString(),
|
||||
footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Slot(ShmartNumber amount)
|
||||
{
|
||||
if (!_runningUsers.Add(ctx.User.Id))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
|
||||
var result = await _service.SlotAsync(ctx.User.Id, amount);
|
||||
|
||||
if (result.Error != GamblingError.None)
|
||||
{
|
||||
if (result.Error == GamblingError.NotEnough)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _totalBet, amount);
|
||||
Interlocked.Add(ref _totalPaidOut, result.Won);
|
||||
|
||||
long ownedAmount;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
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];
|
||||
for (var i = 0; i < numbers.Length; i++)
|
||||
{
|
||||
numbers[i] = new NadekoRandom().Next(0, MaxValue + 1);
|
||||
}
|
||||
var multi = 0;
|
||||
foreach (var t in _winningCombos)
|
||||
{
|
||||
multi = t(numbers);
|
||||
if (multi != 0)
|
||||
break;
|
||||
}
|
||||
result.Rolls.CopyTo(numbers, 0);
|
||||
|
||||
return new SlotResult(numbers, multi);
|
||||
}
|
||||
|
||||
public struct SlotResult
|
||||
{
|
||||
public int[] Numbers { get; }
|
||||
public int Multiplier { get; }
|
||||
public SlotResult(int[] nums, int multi)
|
||||
{
|
||||
Numbers = nums;
|
||||
Multiplier = multi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SlotStats()
|
||||
{
|
||||
//i remembered to not be a moron
|
||||
var paid = _totalPaidOut;
|
||||
var bet = _totalBet;
|
||||
|
||||
if (bet <= 0)
|
||||
bet = 1;
|
||||
|
||||
var embed = _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}%");
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SlotTest(int tests = 1000)
|
||||
{
|
||||
if (tests <= 0)
|
||||
return;
|
||||
//multi vs how many times it occured
|
||||
var dict = new Dictionary<int, int>();
|
||||
for (int i = 0; i < tests; i++)
|
||||
{
|
||||
var res = SlotMachine.Pull();
|
||||
if (dict.ContainsKey(res.Multiplier))
|
||||
dict[res.Multiplier] += 1;
|
||||
else
|
||||
dict.Add(res.Multiplier, 1);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
const int bet = 1;
|
||||
int payout = 0;
|
||||
foreach (var key in dict.Keys.OrderByDescending(x => x))
|
||||
{
|
||||
sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
|
||||
payout += key * dict[key];
|
||||
}
|
||||
await SendConfirmAsync("Slot Test Results", sb.ToString(),
|
||||
footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Slot(ShmartNumber amount)
|
||||
{
|
||||
if (!_runningUsers.Add(ctx.User.Id))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
|
||||
var result = await _service.SlotAsync(ctx.User.Id, amount);
|
||||
|
||||
if (result.Error != GamblingError.None)
|
||||
{
|
||||
if (result.Error == GamblingError.NotEnough)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _totalBet, amount);
|
||||
Interlocked.Add(ref _totalPaidOut, result.Won);
|
||||
|
||||
long ownedAmount;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
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;
|
||||
Color fontColor = _config.Slots.CurrencyFontColor;
|
||||
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 140,
|
||||
}
|
||||
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), fontColor,
|
||||
new PointF(227, 92)));
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 140,
|
||||
}
|
||||
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), fontColor,
|
||||
new PointF(227, 92)));
|
||||
|
||||
var bottomFont = _fonts.DottyFont.CreateFont(50);
|
||||
var bottomFont = _fonts.DottyFont.CreateFont(50);
|
||||
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 135,
|
||||
}
|
||||
}, amount.ToString(), bottomFont, fontColor,
|
||||
new PointF(129, 472)));
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 135,
|
||||
}
|
||||
}, amount.ToString(), bottomFont, fontColor,
|
||||
new PointF(129, 472)));
|
||||
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 135,
|
||||
}
|
||||
}, ownedAmount.ToString(), bottomFont, fontColor,
|
||||
new PointF(325, 472)));
|
||||
//sw.PrintLap("drew red text");
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 135,
|
||||
}
|
||||
}, ownedAmount.ToString(), bottomFont, fontColor,
|
||||
new PointF(325, 472)));
|
||||
//sw.PrintLap("drew red text");
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
using (var img = Image.Load(_images.SlotEmojis[numbers[i]]))
|
||||
{
|
||||
bgImage.Mutate(x => x.DrawImage(img, new Point(148 + 105 * i, 217), 1f));
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
using (var img = Image.Load(_images.SlotEmojis[numbers[i]]))
|
||||
{
|
||||
bgImage.Mutate(x => x.DrawImage(img, new Point(148 + 105 * i, 217), 1f));
|
||||
}
|
||||
}
|
||||
|
||||
var msg = GetText(strs.better_luck);
|
||||
if (result.Multiplier > 0)
|
||||
{
|
||||
if (result.Multiplier == 1f)
|
||||
msg = GetText(strs.slot_single(CurrencySign, 1));
|
||||
else if (result.Multiplier == 4f)
|
||||
msg = GetText(strs.slot_two(CurrencySign, 4));
|
||||
else if (result.Multiplier == 10f)
|
||||
msg = GetText(strs.slot_three(10));
|
||||
else if (result.Multiplier == 30f)
|
||||
msg = GetText(strs.slot_jackpot(30));
|
||||
}
|
||||
var msg = GetText(strs.better_luck);
|
||||
if (result.Multiplier > 0)
|
||||
{
|
||||
if (result.Multiplier == 1f)
|
||||
msg = GetText(strs.slot_single(CurrencySign, 1));
|
||||
else if (result.Multiplier == 4f)
|
||||
msg = GetText(strs.slot_two(CurrencySign, 4));
|
||||
else if (result.Multiplier == 10f)
|
||||
msg = GetText(strs.slot_three(10));
|
||||
else if (result.Multiplier == 30f)
|
||||
msg = GetText(strs.slot_jackpot(30));
|
||||
}
|
||||
|
||||
using (var imgStream = bgImage.ToStream())
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(imgStream,
|
||||
filename: "result.png",
|
||||
text: Format.Bold(ctx.User.ToString()) + " " + msg).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
_runningUsers.Remove(ctx.User.Id);
|
||||
});
|
||||
}
|
||||
using (var imgStream = bgImage.ToStream())
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(imgStream,
|
||||
filename: "result.png",
|
||||
text: Format.Bold(ctx.User.ToString()) + " " + msg).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
_runningUsers.Remove(ctx.User.Id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,359 +4,356 @@ using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
[Group]
|
||||
public class WaifuClaimCommands : GamblingSubmodule<WaifuService>
|
||||
{
|
||||
[Group]
|
||||
public class WaifuClaimCommands : GamblingSubmodule<WaifuService>
|
||||
|
||||
public WaifuClaimCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
|
||||
{
|
||||
}
|
||||
|
||||
public WaifuClaimCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
|
||||
[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))));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed))
|
||||
return;
|
||||
|
||||
if (await _service.TryReset(ctx.User))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.waifu_reset);
|
||||
return;
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_reset_fail);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WaifuClaim(int amount, [Leftover]IUser target)
|
||||
{
|
||||
if (amount < _config.Waifu.MinPrice)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_isnt_cheap(_config.Waifu.MinPrice + CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task WaifuReset()
|
||||
if (target.Id == ctx.User.Id)
|
||||
{
|
||||
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))));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed))
|
||||
return;
|
||||
|
||||
if (await _service.TryReset(ctx.User))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.waifu_reset);
|
||||
return;
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_reset_fail);
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_not_yourself);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WaifuClaim(int amount, [Leftover]IUser target)
|
||||
var (w, isAffinity, result) = await _service.ClaimWaifuAsync(ctx.User, target, amount);
|
||||
|
||||
if (result == WaifuClaimResult.InsufficientAmount)
|
||||
{
|
||||
if (amount < _config.Waifu.MinPrice)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_isnt_cheap(_config.Waifu.MinPrice + CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.Id == ctx.User.Id)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_not_yourself);
|
||||
return;
|
||||
}
|
||||
|
||||
var (w, isAffinity, result) = await _service.ClaimWaifuAsync(ctx.User, target, amount);
|
||||
|
||||
if (result == WaifuClaimResult.InsufficientAmount)
|
||||
{
|
||||
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));
|
||||
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);
|
||||
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));
|
||||
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]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task WaifuTransfer(ulong waifuId, IUser newOwner)
|
||||
[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()),
|
||||
Format.Bold(ctx.User.ToString()),
|
||||
Format.Bold(newOwner.ToString())));
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_transfer_fail);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task WaifuTransfer(IUser waifu, IUser newOwner)
|
||||
{
|
||||
if (!await _service.WaifuTransfer(ctx.User, waifu.Id, newOwner))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_transfer_fail);
|
||||
return;
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(
|
||||
Format.Bold(waifuId.ToString()),
|
||||
Format.Bold(ctx.User.ToString()),
|
||||
Format.Bold(newOwner.ToString())));
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(
|
||||
Format.Bold(waifu.ToString()),
|
||||
Format.Bold(ctx.User.ToString()),
|
||||
Format.Bold(newOwner.ToString())));
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task WaifuTransfer(IUser waifu, IUser newOwner)
|
||||
{
|
||||
if (!await _service.WaifuTransfer(ctx.User, waifu.Id, newOwner))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_transfer_fail);
|
||||
return;
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(
|
||||
Format.Bold(waifu.ToString()),
|
||||
Format.Bold(ctx.User.ToString()),
|
||||
Format.Bold(newOwner.ToString())));
|
||||
}
|
||||
|
||||
return Divorce(waifuUserId);
|
||||
[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);
|
||||
}
|
||||
|
||||
return Divorce(waifuUserId);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task Divorce([Leftover]IGuildUser target)
|
||||
=> Divorce(target.Id);
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task Divorce([Leftover]IGuildUser target)
|
||||
=> Divorce(target.Id);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task Divorce([Leftover]ulong targetId)
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task Divorce([Leftover]ulong targetId)
|
||||
{
|
||||
if (targetId == ctx.User.Id)
|
||||
return;
|
||||
|
||||
var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId);
|
||||
|
||||
if (result == DivorceResult.SucessWithPenalty)
|
||||
{
|
||||
if (targetId == ctx.User.Id)
|
||||
return;
|
||||
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())));
|
||||
}
|
||||
}
|
||||
|
||||
var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId);
|
||||
|
||||
if (result == DivorceResult.SucessWithPenalty)
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
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 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(
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_affinity_cooldown(
|
||||
Format.Bold(((int)remaining?.TotalHours).ToString()),
|
||||
Format.Bold(remaining?.Minutes.ToString())));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
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 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())));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WaifuLb(int page = 1)
|
||||
{
|
||||
page--;
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
if (page > 100)
|
||||
page = 100;
|
||||
|
||||
var waifus = _service.GetTopWaifusAtPage(page);
|
||||
|
||||
if (waifus.Count() == 0)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.waifus_none);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WaifuLb(int page = 1)
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.waifus_top_waifus))
|
||||
.WithOkColor();
|
||||
|
||||
var i = 0;
|
||||
foreach (var w in waifus)
|
||||
{
|
||||
page--;
|
||||
var j = i++;
|
||||
embed.AddField("#" + ((page * 9) + j + 1) + " - " + w.Price + CurrencySign, w.ToString(), false);
|
||||
}
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
if (page > 100)
|
||||
page = 100;
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task WaifuInfo([Leftover]IUser target = null)
|
||||
{
|
||||
if (target is null)
|
||||
target = ctx.User;
|
||||
|
||||
var waifus = _service.GetTopWaifusAtPage(page);
|
||||
return InternalWaifuInfo(target.Id, target.ToString());
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task WaifuInfo(ulong targetId)
|
||||
=> InternalWaifuInfo(targetId);
|
||||
|
||||
if (waifus.Count() == 0)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.waifus_none);
|
||||
return;
|
||||
}
|
||||
private Task InternalWaifuInfo(ulong targetId, string name = null)
|
||||
{
|
||||
var wi = _service.GetFullWaifuInfoAsync(targetId);
|
||||
var affInfo = _service.GetAffinityTitle(wi.AffinityCount);
|
||||
|
||||
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)));
|
||||
|
||||
var fansStr = wi
|
||||
.Fans
|
||||
.Shuffle()
|
||||
.Take(30)
|
||||
.Select(x => wi.Claims.Contains(x) ? $"{x} 💞" : x)
|
||||
.JoinWith('\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);
|
||||
|
||||
return ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task WaifuGift(int page = 1)
|
||||
{
|
||||
if (--page < 0 || page > (_config.Waifu.Items.Count - 1) / 9)
|
||||
return;
|
||||
|
||||
var waifuItems = _service.GetWaifuItems();
|
||||
await ctx.SendPaginatedConfirmAsync(page, (cur) =>
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.waifus_top_waifus))
|
||||
.WithTitle(GetText(strs.waifu_gift_shop))
|
||||
.WithOkColor();
|
||||
|
||||
var i = 0;
|
||||
foreach (var w in waifus)
|
||||
{
|
||||
var j = i++;
|
||||
embed.AddField("#" + ((page * 9) + j + 1) + " - " + w.Price + CurrencySign, w.ToString(), false);
|
||||
}
|
||||
waifuItems
|
||||
.OrderBy(x => x.Negative)
|
||||
.ThenBy(x => x.Price)
|
||||
.Skip(9 * cur)
|
||||
.Take(9)
|
||||
.ForEach(x => embed
|
||||
.AddField($"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
|
||||
Format.Bold(x.Price.ToString()) + _config.Currency.Sign,
|
||||
true));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
return embed;
|
||||
}, waifuItems.Count, 9);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task WaifuInfo([Leftover]IUser target = null)
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task WaifuGift(string itemName, [Leftover] IUser waifu)
|
||||
{
|
||||
if (waifu.Id == ctx.User.Id)
|
||||
return;
|
||||
|
||||
var allItems = _service.GetWaifuItems();
|
||||
var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName.ToLowerInvariant());
|
||||
if (item is null)
|
||||
{
|
||||
if (target is null)
|
||||
target = ctx.User;
|
||||
|
||||
return InternalWaifuInfo(target.Id, target.ToString());
|
||||
await ReplyErrorLocalizedAsync(strs.waifu_gift_not_exist);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task WaifuInfo(ulong targetId)
|
||||
=> InternalWaifuInfo(targetId);
|
||||
var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, item);
|
||||
|
||||
private Task InternalWaifuInfo(ulong targetId, string name = null)
|
||||
if (sucess)
|
||||
{
|
||||
var wi = _service.GetFullWaifuInfoAsync(targetId);
|
||||
var affInfo = _service.GetAffinityTitle(wi.AffinityCount);
|
||||
|
||||
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)));
|
||||
|
||||
var fansStr = wi
|
||||
.Fans
|
||||
.Shuffle()
|
||||
.Take(30)
|
||||
.Select(x => wi.Claims.Contains(x) ? $"{x} 💞" : x)
|
||||
.JoinWith('\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);
|
||||
|
||||
return ctx.Channel.EmbedAsync(embed);
|
||||
await ReplyConfirmLocalizedAsync(strs.waifu_gift(
|
||||
Format.Bold(item.ToString() + " " + item.ItemEmoji),
|
||||
Format.Bold(waifu.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task WaifuGift(int page = 1)
|
||||
else
|
||||
{
|
||||
if (--page < 0 || page > (_config.Waifu.Items.Count - 1) / 9)
|
||||
return;
|
||||
|
||||
var waifuItems = _service.GetWaifuItems();
|
||||
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)
|
||||
.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);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task WaifuGift(string itemName, [Leftover] IUser waifu)
|
||||
{
|
||||
if (waifu.Id == ctx.User.Id)
|
||||
return;
|
||||
|
||||
var allItems = _service.GetWaifuItems();
|
||||
var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName.ToLowerInvariant());
|
||||
if (item is null)
|
||||
{
|
||||
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),
|
||||
Format.Bold(waifu.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Discord;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Threading.Tasks;
|
||||
using Wof = NadekoBot.Modules.Gambling.Common.WheelOfFortune.WheelOfFortuneGame;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
@@ -9,13 +8,13 @@ using System.Collections.Immutable;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class Gambling
|
||||
public class WheelOfFortuneCommands : GamblingSubmodule<GamblingService>
|
||||
{
|
||||
public class WheelOfFortuneCommands : GamblingSubmodule<GamblingService>
|
||||
{
|
||||
private static readonly ImmutableArray<string> _emojis = new string[] {
|
||||
private static readonly ImmutableArray<string> _emojis = new string[] {
|
||||
"⬆",
|
||||
"↖",
|
||||
"⬅",
|
||||
@@ -25,40 +24,39 @@ namespace NadekoBot.Modules.Gambling
|
||||
"➡",
|
||||
"↗" }.ToImmutableArray();
|
||||
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
|
||||
public WheelOfFortuneCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConfService)
|
||||
: base(gamblingConfService)
|
||||
public WheelOfFortuneCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConfService)
|
||||
: base(gamblingConfService)
|
||||
{
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task WheelOfFortune(ShmartNumber amount)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id, "Wheel Of Fortune - bet", amount, gamble: true).ConfigureAwait(false))
|
||||
{
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task WheelOfFortune(ShmartNumber amount)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
|
||||
return;
|
||||
var result = await _service.WheelOfFortuneSpinAsync(ctx.User.Id, amount).ConfigureAwait(false);
|
||||
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id, "Wheel Of Fortune - bet", amount, gamble: true).ConfigureAwait(false))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _service.WheelOfFortuneSpinAsync(ctx.User.Id, amount).ConfigureAwait(false);
|
||||
|
||||
var wofMultipliers = _config.WheelOfFortune.Multipliers;
|
||||
await SendConfirmAsync(
|
||||
Format.Bold($@"{ctx.User.ToString()} won: {result.Amount + CurrencySign}
|
||||
var wofMultipliers = _config.WheelOfFortune.Multipliers;
|
||||
await SendConfirmAsync(
|
||||
Format.Bold($@"{ctx.User.ToString()} won: {result.Amount + CurrencySign}
|
||||
|
||||
『{wofMultipliers[1]}』 『{wofMultipliers[0]}』 『{wofMultipliers[7]}』
|
||||
|
||||
『{wofMultipliers[2]}』 {_emojis[result.Index]} 『{wofMultipliers[6]}』
|
||||
|
||||
『{wofMultipliers[3]}』 『{wofMultipliers[4]}』 『{wofMultipliers[5]}』")).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user