mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 09:18:27 -04:00
More work on gambling
This commit is contained in:
@@ -32,6 +32,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nadeko.Medusa", "src\Nadeko
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nadeko.Common", "src\Nadeko.Common\Nadeko.Common.csproj", "{A6022F5F-A764-4D3F-847B-36F0391FF659}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nadeko.Common", "src\Nadeko.Common\Nadeko.Common.csproj", "{A6022F5F-A764-4D3F-847B-36F0391FF659}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nadeko.Econ", "src\Nadeko.Econ\Nadeko.Econ.csproj", "{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -87,6 +89,12 @@ Global
|
|||||||
{A6022F5F-A764-4D3F-847B-36F0391FF659}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
{A6022F5F-A764-4D3F-847B-36F0391FF659}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A6022F5F-A764-4D3F-847B-36F0391FF659}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A6022F5F-A764-4D3F-847B-36F0391FF659}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A6022F5F-A764-4D3F-847B-36F0391FF659}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A6022F5F-A764-4D3F-847B-36F0391FF659}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -101,6 +109,7 @@ Global
|
|||||||
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
{E685977E-31A4-46F4-A5D7-4E3E39E82E43} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{E685977E-31A4-46F4-A5D7-4E3E39E82E43} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
{A6022F5F-A764-4D3F-847B-36F0391FF659} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{A6022F5F-A764-4D3F-847B-36F0391FF659} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
|
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace Nadeko.Common;
|
||||||
|
|
||||||
public class NadekoRandom : Random
|
public class NadekoRandom : Random
|
||||||
{
|
{
|
28
src/Nadeko.Econ/Gambling/Betflip/BetflipGame.cs
Normal file
28
src/Nadeko.Econ/Gambling/Betflip/BetflipGame.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Nadeko.Econ.Gambling;
|
||||||
|
|
||||||
|
public sealed class BetflipGame
|
||||||
|
{
|
||||||
|
private readonly decimal _winMulti;
|
||||||
|
private readonly NadekoRandom _rng;
|
||||||
|
|
||||||
|
public BetflipGame(decimal winMulti)
|
||||||
|
{
|
||||||
|
_winMulti = winMulti;
|
||||||
|
_rng = new NadekoRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BetflipResult Flip(int guess, decimal amount)
|
||||||
|
{
|
||||||
|
var side = _rng.Next(0, 1);
|
||||||
|
decimal won = 0;
|
||||||
|
|
||||||
|
if (side == guess)
|
||||||
|
won = amount * _winMulti;
|
||||||
|
|
||||||
|
return new BetflipResult()
|
||||||
|
{
|
||||||
|
Side = side,
|
||||||
|
Won = won,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
7
src/Nadeko.Econ/Gambling/Betflip/BetflipResult.cs
Normal file
7
src/Nadeko.Econ/Gambling/Betflip/BetflipResult.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Nadeko.Econ.Gambling;
|
||||||
|
|
||||||
|
public readonly struct BetflipResult
|
||||||
|
{
|
||||||
|
public decimal Won { get; init; }
|
||||||
|
public int Side { get; init; }
|
||||||
|
}
|
42
src/Nadeko.Econ/Gambling/Betroll/BetrollGame.cs
Normal file
42
src/Nadeko.Econ/Gambling/Betroll/BetrollGame.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
namespace Nadeko.Econ.Gambling;
|
||||||
|
|
||||||
|
public sealed class BetrollGame
|
||||||
|
{
|
||||||
|
private readonly (decimal WhenAbove, decimal MultiplyBy)[] _thresholdPairs;
|
||||||
|
private readonly Random _rng;
|
||||||
|
|
||||||
|
public BetrollGame(IReadOnlyList<(decimal WhenAbove, decimal MultiplyBy)> pairs)
|
||||||
|
{
|
||||||
|
_thresholdPairs = pairs.OrderByDescending(x => x.WhenAbove).ToArray();
|
||||||
|
_rng = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BetrollResult Roll(decimal amount = 0)
|
||||||
|
{
|
||||||
|
var roll = _rng.Next(0, 101);
|
||||||
|
|
||||||
|
for (var i = 0; i < _thresholdPairs.Length; i++)
|
||||||
|
{
|
||||||
|
ref var pair = ref _thresholdPairs[i];
|
||||||
|
|
||||||
|
if (pair.WhenAbove < roll)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Multiplier = pair.MultiplyBy,
|
||||||
|
Roll = roll,
|
||||||
|
Threshold = pair.WhenAbove,
|
||||||
|
Won = amount * pair.MultiplyBy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Multiplier = 0,
|
||||||
|
Roll = roll,
|
||||||
|
Threshold = -1,
|
||||||
|
Won = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
9
src/Nadeko.Econ/Gambling/Betroll/BetrollResult.cs
Normal file
9
src/Nadeko.Econ/Gambling/Betroll/BetrollResult.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Nadeko.Econ.Gambling;
|
||||||
|
|
||||||
|
public readonly struct BetrollResult
|
||||||
|
{
|
||||||
|
public int Roll { get; init; }
|
||||||
|
public decimal Multiplier { get; init; }
|
||||||
|
public decimal Threshold { get; init; }
|
||||||
|
public decimal Won { get; init; }
|
||||||
|
}
|
@@ -1,11 +1,10 @@
|
|||||||
#nullable disable
|
namespace Nadeko.Econ.Gambling;
|
||||||
namespace NadekoBot.Modules.Gambling.Common.Slot;
|
|
||||||
|
|
||||||
public class SlotGame
|
public class SlotGame
|
||||||
{
|
{
|
||||||
private static readonly Random _rng = new NadekoRandom();
|
private static readonly Random _rng = new NadekoRandom();
|
||||||
|
|
||||||
public Result Spin()
|
public SlotResult Spin(decimal bet)
|
||||||
{
|
{
|
||||||
var rolls = new[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) };
|
var rolls = new[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) };
|
||||||
var multi = 0;
|
var multi = 0;
|
||||||
@@ -19,18 +18,11 @@ public class SlotGame
|
|||||||
else if (rolls.Any(x => x == 5))
|
else if (rolls.Any(x => x == 5))
|
||||||
multi = 1;
|
multi = 1;
|
||||||
|
|
||||||
return new(multi, rolls);
|
return new()
|
||||||
}
|
|
||||||
|
|
||||||
public class Result
|
|
||||||
{
|
|
||||||
public float Multiplier { get; }
|
|
||||||
public int[] Rolls { get; }
|
|
||||||
|
|
||||||
public Result(float multiplier, int[] rolls)
|
|
||||||
{
|
{
|
||||||
Multiplier = multiplier;
|
Won = bet * multi,
|
||||||
Rolls = rolls;
|
Multiplier = multi,
|
||||||
}
|
Rolls = rolls,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
8
src/Nadeko.Econ/Gambling/Slot/SlotResult.cs
Normal file
8
src/Nadeko.Econ/Gambling/Slot/SlotResult.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Nadeko.Econ.Gambling;
|
||||||
|
|
||||||
|
public readonly struct SlotResult
|
||||||
|
{
|
||||||
|
public decimal Multiplier { get; init; }
|
||||||
|
public int[] Rolls { get; init; }
|
||||||
|
public decimal Won { get; init; }
|
||||||
|
}
|
@@ -1,20 +1,19 @@
|
|||||||
#nullable disable
|
namespace Nadeko.Econ.Gambling;
|
||||||
namespace NadekoBot.Modules.Gambling.WheelOfFortune;
|
|
||||||
|
|
||||||
public sealed class WheelOfFortuneGame
|
public sealed class WofGame
|
||||||
{
|
{
|
||||||
public static IReadOnlyList<decimal> DEFAULT_MULTIPLIERS = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
|
public static IReadOnlyList<decimal> DEFAULT_MULTIPLIERS = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
|
||||||
|
|
||||||
private readonly IReadOnlyList<decimal> _multipliers;
|
private readonly IReadOnlyList<decimal> _multipliers;
|
||||||
private readonly NadekoRandom _rng;
|
private readonly NadekoRandom _rng;
|
||||||
|
|
||||||
public WheelOfFortuneGame(IReadOnlyList<decimal> multipliers)
|
public WofGame(IReadOnlyList<decimal> multipliers)
|
||||||
{
|
{
|
||||||
_multipliers = multipliers;
|
_multipliers = multipliers;
|
||||||
_rng = new();
|
_rng = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public WheelOfFortuneGame() : this(DEFAULT_MULTIPLIERS)
|
public WofGame() : this(DEFAULT_MULTIPLIERS)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,13 +22,13 @@ public sealed class WheelOfFortuneGame
|
|||||||
var result = _rng.Next(0, _multipliers.Count);
|
var result = _rng.Next(0, _multipliers.Count);
|
||||||
|
|
||||||
var multi = _multipliers[result];
|
var multi = _multipliers[result];
|
||||||
var amount = (long)(bet * multi);
|
var amount = bet * multi;
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Index = result,
|
Index = result,
|
||||||
Multiplier = multi,
|
Multiplier = multi,
|
||||||
Amount = amount
|
Won = amount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,9 +1,8 @@
|
|||||||
#nullable disable
|
namespace Nadeko.Econ.Gambling;
|
||||||
namespace NadekoBot.Modules.Gambling.WheelOfFortune;
|
|
||||||
|
|
||||||
public readonly struct WofResult
|
public readonly struct WofResult
|
||||||
{
|
{
|
||||||
public int Index { get; init; }
|
public int Index { get; init; }
|
||||||
public decimal Multiplier { get; init; }
|
public decimal Multiplier { get; init; }
|
||||||
public long Amount { get; init; }
|
public decimal Won { get; init; }
|
||||||
}
|
}
|
1
src/Nadeko.Econ/GlobalUsings.cs
Normal file
1
src/Nadeko.Econ/GlobalUsings.cs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
global using Nadeko.Common;
|
13
src/Nadeko.Econ/Nadeko.Econ.csproj
Normal file
13
src/Nadeko.Econ/Nadeko.Econ.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nadeko.Common\Nadeko.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@@ -191,6 +191,11 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
if (conf.AutoDeleteByeMessagesTimer > 0)
|
if (conf.AutoDeleteByeMessagesTimer > 0)
|
||||||
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
||||||
}
|
}
|
||||||
|
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Missing permissions to send a bye message, the bye message will be disabled on server: {GuildId}", channel.GuildId);
|
||||||
|
await SetBye(channel.GuildId, channel.Id, false);
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Error embeding bye message");
|
Log.Warning(ex, "Error embeding bye message");
|
||||||
@@ -219,6 +224,11 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
if (conf.AutoDeleteGreetMessagesTimer > 0)
|
if (conf.AutoDeleteGreetMessagesTimer > 0)
|
||||||
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
||||||
}
|
}
|
||||||
|
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}", channel.GuildId);
|
||||||
|
await SetGreet(channel.GuildId, channel.Id, false);
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Error embeding greet message");
|
Log.Warning(ex, "Error embeding greet message");
|
||||||
|
12
src/NadekoBot/Modules/Gambling/EconomyResult.cs
Normal file
12
src/NadekoBot/Modules/Gambling/EconomyResult.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#nullable disable
|
||||||
|
namespace NadekoBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
|
public sealed class EconomyResult
|
||||||
|
{
|
||||||
|
public decimal Cash { get; init; }
|
||||||
|
public decimal Planted { get; init; }
|
||||||
|
public decimal Waifus { get; init; }
|
||||||
|
public decimal OnePercent { get; init; }
|
||||||
|
public decimal Bank { get; init; }
|
||||||
|
public long Bot { get; init; }
|
||||||
|
}
|
7
src/NadekoBot/Modules/Gambling/FlipCoin/FlipResult.cs
Normal file
7
src/NadekoBot/Modules/Gambling/FlipCoin/FlipResult.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Nadeko.Econ.Gambling;
|
||||||
|
|
||||||
|
public readonly struct FlipResult
|
||||||
|
{
|
||||||
|
public long Won { get; init; }
|
||||||
|
public int Side { get; init; }
|
||||||
|
}
|
@@ -38,6 +38,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
Draw
|
Draw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly IGamblingService _gs;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
@@ -51,6 +52,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
private IUserMessage rdMsg;
|
private IUserMessage rdMsg;
|
||||||
|
|
||||||
public Gambling(
|
public Gambling(
|
||||||
|
IGamblingService gs,
|
||||||
DbService db,
|
DbService db,
|
||||||
ICurrencyService currency,
|
ICurrencyService currency,
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
@@ -61,6 +63,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
RemindService remind)
|
RemindService remind)
|
||||||
: base(configService)
|
: base(configService)
|
||||||
{
|
{
|
||||||
|
_gs = gs;
|
||||||
_db = db;
|
_db = db;
|
||||||
_cs = currency;
|
_cs = currency;
|
||||||
_client = client;
|
_client = client;
|
||||||
@@ -657,27 +660,29 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InternallBetroll(long amount)
|
[Cmd]
|
||||||
|
public async partial Task BetRoll(ShmartNumber amount)
|
||||||
{
|
{
|
||||||
if (!await CheckBetMandatory(amount))
|
if (!await CheckBetMandatory(amount))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await _cs.RemoveAsync(ctx.User, amount, new("betroll", "bet")))
|
var result = await _gs.BetRollAsync()
|
||||||
|
if (!)
|
||||||
{
|
{
|
||||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var br = new Betroll(Config.BetRoll);
|
var br = new BetrollGame(Config.BetRoll);
|
||||||
|
|
||||||
var result = br.Roll();
|
var result = br.Roll();
|
||||||
|
|
||||||
|
var win = (long)result.Won;
|
||||||
var str = Format.Bold(ctx.User.ToString()) + Format.Code(GetText(strs.roll(result.Roll)));
|
var str = Format.Bold(ctx.User.ToString()) + Format.Code(GetText(strs.roll(result.Roll)));
|
||||||
if (result.Multiplier > 0)
|
if (win > 0)
|
||||||
{
|
{
|
||||||
var win = (long)(amount * result.Multiplier);
|
|
||||||
str += GetText(strs.br_win(N(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
|
str += GetText(strs.br_win(N(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
|
||||||
await _cs.AddAsync(ctx.User, win, new("betroll", "win"));
|
await _cs.AddAsync(ctx.User, win, new("betroll", "win"));
|
||||||
}
|
}
|
||||||
@@ -689,10 +694,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
await SendConfirmAsync(str);
|
await SendConfirmAsync(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
public partial Task BetRoll(ShmartNumber amount)
|
|
||||||
=> InternallBetroll(amount);
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[NadekoOptions(typeof(LbOpts))]
|
[NadekoOptions(typeof(LbOpts))]
|
||||||
[Priority(0)]
|
[Priority(0)]
|
||||||
|
@@ -6,8 +6,6 @@ using NadekoBot.Db;
|
|||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
using NadekoBot.Modules.Gambling.Common;
|
using NadekoBot.Modules.Gambling.Common;
|
||||||
using NadekoBot.Modules.Gambling.Common.Connect4;
|
using NadekoBot.Modules.Gambling.Common.Connect4;
|
||||||
using NadekoBot.Modules.Gambling.Common.Slot;
|
|
||||||
using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling.Services;
|
namespace NadekoBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
@@ -22,6 +20,8 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
private readonly IBotCache _cache;
|
private readonly IBotCache _cache;
|
||||||
private readonly GamblingConfigService _gss;
|
private readonly GamblingConfigService _gss;
|
||||||
|
|
||||||
|
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
||||||
|
|
||||||
public GamblingService(
|
public GamblingService(
|
||||||
DbService db,
|
DbService db,
|
||||||
Bot bot,
|
Bot bot,
|
||||||
@@ -69,8 +69,7 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
|
||||||
private async Task CurrencyDecayLoopAsync()
|
private async Task CurrencyDecayLoopAsync()
|
||||||
{
|
{
|
||||||
if (_bot.Client.ShardId != 0)
|
if (_bot.Client.ShardId != 0)
|
||||||
@@ -131,40 +130,6 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SlotResponse> SlotAsync(ulong userId, long amount)
|
|
||||||
{
|
|
||||||
var takeRes = await _cs.RemoveAsync(userId, amount, new("slot", "bet"));
|
|
||||||
|
|
||||||
if (!takeRes)
|
|
||||||
{
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
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, won, new("slot", "win", $"Slot Machine x{result.Multiplier}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
var toReturn = new SlotResponse
|
|
||||||
{
|
|
||||||
Multiplier = result.Multiplier,
|
|
||||||
Won = won
|
|
||||||
};
|
|
||||||
|
|
||||||
toReturn.Rolls.AddRange(result.Rolls);
|
|
||||||
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly TypedKey<EconomyResult> _ecoKey = new("nadeko:economy");
|
private static readonly TypedKey<EconomyResult> _ecoKey = new("nadeko:economy");
|
||||||
|
|
||||||
public async Task<EconomyResult> GetEconomyAsync()
|
public async Task<EconomyResult> GetEconomyAsync()
|
||||||
@@ -198,9 +163,6 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<WheelOfFortuneGame.Result> WheelOfFortuneSpinAsync(ulong userId, long bet)
|
|
||||||
=> new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync();
|
|
||||||
|
|
||||||
|
|
||||||
private static readonly SemaphoreSlim _timelyLock = new (1, 1);
|
private static readonly SemaphoreSlim _timelyLock = new (1, 1);
|
||||||
|
|
||||||
@@ -250,15 +212,4 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
|
|
||||||
public async Task RemoveAllTimelyClaimsAsync()
|
public async Task RemoveAllTimelyClaimsAsync()
|
||||||
=> await _cache.RemoveAsync(_timelyKey);
|
=> await _cache.RemoveAsync(_timelyKey);
|
||||||
|
|
||||||
|
|
||||||
public readonly struct EconomyResult
|
|
||||||
{
|
|
||||||
public decimal Cash { get; init; }
|
|
||||||
public decimal Planted { get; init; }
|
|
||||||
public decimal Waifus { get; init; }
|
|
||||||
public decimal OnePercent { get; init; }
|
|
||||||
public decimal Bank { get; init; }
|
|
||||||
public long Bot { get; init; }
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -8,363 +8,29 @@ using SixLabors.ImageSharp.Drawing.Processing;
|
|||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Grpc.Core;
|
using Nadeko.Econ.Gambling;
|
||||||
using NadekoBot.Modules.Gambling.WheelOfFortune;
|
|
||||||
using NadekoBot.Services.Currency;
|
|
||||||
using Color = SixLabors.ImageSharp.Color;
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
using OneOf;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling;
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
|
||||||
public enum SlotError
|
public enum GamblingError
|
||||||
{
|
{
|
||||||
InsufficientFunds,
|
InsufficientFunds,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WofError
|
// public interface ISlotService
|
||||||
{
|
// {
|
||||||
InsufficientFunds,
|
// ValueTask<OneOf<SlotResult, SlotError>> PullAsync(ulong userId, long amount);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public interface ISlotService
|
|
||||||
{
|
|
||||||
ValueTask<OneOf<SlotResult, SlotError>> PullAsync(ulong userId, long amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public record struct WofRequest(ulong UserId, long Amount);
|
|
||||||
|
|
||||||
public record struct BetrollRequest(ulong UserId, long Amount);
|
|
||||||
|
|
||||||
public sealed class DefaultSlotService : INService
|
|
||||||
{
|
|
||||||
private readonly GamblingConfigService _bcs;
|
|
||||||
private readonly ICurrencyService _cs;
|
|
||||||
// public ValueTask<OneOf<SlotResult, SlotError>> PullAsync(ulong userId, long amount)
|
|
||||||
// {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
public DefaultSlotService(GamblingConfigService bcs, ICurrencyService cs)
|
|
||||||
{
|
|
||||||
_bcs = bcs;
|
|
||||||
_cs = cs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<OneOf<WofResult, WofError>> Wof(WofRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
var isTakeSuccess = await _cs.RemoveAsync(request.UserId, request.Amount, new TxData("wof", "bet"));
|
|
||||||
|
|
||||||
if (!isTakeSuccess)
|
|
||||||
{
|
|
||||||
return WofError.InsufficientFunds;
|
|
||||||
}
|
|
||||||
|
|
||||||
var game = new WheelOfFortuneGame(_bcs.Data.WheelOfFortune.Multipliers);
|
|
||||||
var result = game.Spin(request.Amount);
|
|
||||||
|
|
||||||
if (result.Amount > 0)
|
|
||||||
{
|
|
||||||
await _cs.AddAsync(request.UserId, result.Amount, new("wof", "win"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<OneOf<>> BetRoll(BetRollRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
var takeRes = await _currency.TransferCurrencyAsync(new TransferCurrencyRequest
|
|
||||||
{
|
|
||||||
Amount = request.Amount,
|
|
||||||
Type = "bet-roll",
|
|
||||||
Subtype = "bet",
|
|
||||||
FromId = request.UserId,
|
|
||||||
ToId = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!takeRes.Success)
|
|
||||||
{
|
|
||||||
return new BetRollReply
|
|
||||||
{
|
|
||||||
Error = GamblingError.NotEnough
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var game = new Betroll(_config.Data.BetRoll);
|
|
||||||
var result = game.Roll();
|
|
||||||
|
|
||||||
if (result.Multiplier > 0)
|
|
||||||
{
|
|
||||||
var won = (long)(request.Amount * result.Multiplier);
|
|
||||||
|
|
||||||
await _currency.GrantToUserAsync(new GrantToUserRequest
|
|
||||||
{
|
|
||||||
Amount = won,
|
|
||||||
Type = "bet-roll",
|
|
||||||
Subtype = "won",
|
|
||||||
UserId = request.UserId,
|
|
||||||
GranterId = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return new BetRollReply
|
|
||||||
{
|
|
||||||
WonAmount = won,
|
|
||||||
Multiplier = result.Multiplier,
|
|
||||||
Roll = result.Roll,
|
|
||||||
Threshold = result.Threshold,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BetRollReply
|
|
||||||
{
|
|
||||||
WonAmount = 0,
|
|
||||||
Multiplier = result.Multiplier,
|
|
||||||
Roll = result.Roll,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// public override async Task<BetFlipReply> BetFlip(BetFlipRequest request, ServerCallContext context)
|
|
||||||
// {
|
|
||||||
// var takeRes = await _currency.TransferCurrencyAsync(new TransferCurrencyRequest
|
|
||||||
// {
|
|
||||||
// Amount = request.Amount,
|
|
||||||
// Type = "bet-flip",
|
|
||||||
// Subtype = "bet",
|
|
||||||
// FromId = request.UserId,
|
|
||||||
// ToId = 0,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// if (!takeRes.Success)
|
|
||||||
// {
|
|
||||||
// return new BetFlipReply
|
|
||||||
// {
|
|
||||||
// Error = GamblingError.NotEnough
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var roll = _rng.Next(0, 1000) <= 499;
|
|
||||||
// long won = 0;
|
|
||||||
//
|
|
||||||
// if (roll == request.Guess)
|
|
||||||
// {
|
|
||||||
// won = (long) (_config.Data.Multipliers.BetFlip * request.Amount);
|
|
||||||
//
|
|
||||||
// await _currency.GrantToUserAsync(new GrantToUserRequest
|
|
||||||
// {
|
|
||||||
// Amount = won,
|
|
||||||
// Type = "bet-flip",
|
|
||||||
// Subtype = "won",
|
|
||||||
// UserId = request.UserId,
|
|
||||||
// GranterId = 0,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// return new BetFlipReply
|
|
||||||
// {
|
|
||||||
// Result = roll
|
|
||||||
// ? BetFlipReply.Types.Side.Heads
|
|
||||||
// : BetFlipReply.Types.Side.Tails,
|
|
||||||
// WonAmount = won
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public override Task<FlipReply> Flip(FlipRequest request, ServerCallContext context)
|
|
||||||
// {
|
|
||||||
// if (request.Count <= 0)
|
|
||||||
// throw new RpcException(new Status(StatusCode.InvalidArgument, "Count has to be greater than 0."));
|
|
||||||
//
|
|
||||||
// var results = Enumerable.Range(0, request.Count)
|
|
||||||
// .Select(x => (FlipReply.Types.Roll) _rng.Next(0, 2));
|
|
||||||
//
|
|
||||||
// var toReturn = new FlipReply();
|
|
||||||
// toReturn.Rolls.AddRange(results);
|
|
||||||
// return Task.FromResult(toReturn);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public override async Task<SlotResponse> Slot(SlotRequest request, ServerCallContext context)
|
|
||||||
// {
|
|
||||||
// var takeRes = await _currency.TransferCurrencyAsync(new TransferCurrencyRequest
|
|
||||||
// {
|
|
||||||
// Amount = request.Amount,
|
|
||||||
// Type = "slot",
|
|
||||||
// Subtype = "bet",
|
|
||||||
// FromId = request.UserId,
|
|
||||||
// ToId = 0,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// if (!takeRes.Success)
|
|
||||||
// {
|
|
||||||
// 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 * request.Amount);
|
|
||||||
//
|
|
||||||
// await _currency.GrantToUserAsync(new GrantToUserRequest
|
|
||||||
// {
|
|
||||||
// Amount = won,
|
|
||||||
// Type = "slot",
|
|
||||||
// Subtype = "won",
|
|
||||||
// UserId = request.UserId,
|
|
||||||
// GranterId = 0,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var toReturn = new SlotResponse
|
|
||||||
// {
|
|
||||||
// Multiplier = result.Multiplier,
|
|
||||||
// Won = won,
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// toReturn.Rolls.AddRange(result.Rolls);
|
|
||||||
//
|
|
||||||
// return toReturn;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private readonly ConcurrentDictionary<ulong, Deck> _decks = new ConcurrentDictionary<ulong, Deck>();
|
|
||||||
//
|
|
||||||
// public override Task<DeckShuffleReply> DeckShuffle(DeckShuffleRequest request, ServerCallContext context)
|
|
||||||
// {
|
|
||||||
// _decks.AddOrUpdate(request.Id, new Deck(), (key, old) => new Deck());
|
|
||||||
// return Task.FromResult(new DeckShuffleReply { });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public override Task<DeckDrawReply> DeckDraw(DeckDrawRequest request, ServerCallContext context)
|
|
||||||
// {
|
|
||||||
// if (request.Count < 1 || request.Count > 10)
|
|
||||||
// throw new ArgumentOutOfRangeException(nameof(request.Id));
|
|
||||||
//
|
|
||||||
// var deck = request.UseNew
|
|
||||||
// ? new Deck()
|
|
||||||
// : _decks.GetOrAdd(request.Id, new Deck());
|
|
||||||
//
|
|
||||||
// var list = new List<Deck.Card>(request.Count);
|
|
||||||
// for (int i = 0; i < request.Count; i++)
|
|
||||||
// {
|
|
||||||
// var card = deck.DrawNoRestart();
|
|
||||||
// if (card is null)
|
|
||||||
// {
|
|
||||||
// if (i == 0)
|
|
||||||
// {
|
|
||||||
// deck.Restart();
|
|
||||||
// list.Add(deck.DrawNoRestart());
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// list.Add(card);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // todo 3.2 should replace all "placeholder" words in command strings with a link to the placeholder list explanation
|
|
||||||
// var cards = list
|
|
||||||
// .Select(x => new Card
|
|
||||||
// {
|
|
||||||
// Name = x.ToString().ToLowerInvariant().Replace(' ', '_'),
|
|
||||||
// Number = x.Number,
|
|
||||||
// Suit = (CardSuit) x.Suit
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// var toReturn = new DeckDrawReply();
|
|
||||||
// toReturn.Cards.AddRange(cards);
|
|
||||||
//
|
|
||||||
// return Task.FromResult(toReturn);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public override async Task<RpsReply> Rps(RpsRequest request, ServerCallContext context)
|
|
||||||
// {
|
|
||||||
// if (request.Amount > 0)
|
|
||||||
// {
|
|
||||||
// var res = await _currency.TransferCurrencyAsync(new TransferCurrencyRequest
|
|
||||||
// {
|
|
||||||
// Amount = request.Amount,
|
|
||||||
// FromId = request.UserId,
|
|
||||||
// Type = "rps",
|
|
||||||
// Subtype = "bet",
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// if (!res.Success)
|
|
||||||
// {
|
|
||||||
// return new RpsReply
|
|
||||||
// {
|
|
||||||
// Result = RpsReply.Types.ResultType.NotEnough
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var botPick = _rng.Next(0, 3);
|
|
||||||
// var userPick = (int) request.Pick;
|
|
||||||
//
|
|
||||||
// if (botPick == userPick)
|
|
||||||
// {
|
|
||||||
// if (request.Amount > 0)
|
|
||||||
// {
|
|
||||||
// await _currency.GrantToUserAsync(new GrantToUserRequest
|
|
||||||
// {
|
|
||||||
// Amount = request.Amount,
|
|
||||||
// GranterId = 0,
|
|
||||||
// Type = "rps",
|
|
||||||
// Subtype = "draw",
|
|
||||||
// UserId = request.UserId,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return new RpsReply
|
|
||||||
// {
|
|
||||||
// BotPick = (RpsPick) botPick,
|
|
||||||
// WonAmount = request.Amount,
|
|
||||||
// Result = RpsReply.Types.ResultType.Draw
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if ((botPick == 1 && userPick == 2) || (botPick == 2 && userPick == 0) || (botPick == 0 && userPick == 1))
|
|
||||||
// {
|
|
||||||
// if (request.Amount > 0)
|
|
||||||
// {
|
|
||||||
// await _currency.GrantToUserAsync(new GrantToUserRequest
|
|
||||||
// {
|
|
||||||
// Amount = (long) (request.Amount * 1.95f),
|
|
||||||
// GranterId = 0,
|
|
||||||
// Type = "rps",
|
|
||||||
// Subtype = "draw",
|
|
||||||
// UserId = request.UserId,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return new RpsReply
|
|
||||||
// {
|
|
||||||
// BotPick = (RpsPick) botPick,
|
|
||||||
// WonAmount = (long) (request.Amount * 1.95f),
|
|
||||||
// Result = RpsReply.Types.ResultType.Won
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return new RpsReply
|
|
||||||
// {
|
|
||||||
// BotPick = (RpsPick) botPick,
|
|
||||||
// WonAmount = 0,
|
|
||||||
// Result = RpsReply.Types.ResultType.Lost
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class SlotCommands : GamblingSubmodule<GamblingService>
|
public partial class SlotCommands : GamblingSubmodule<IGamblingService>
|
||||||
{
|
{
|
||||||
private static long totalBet;
|
private static decimal totalBet;
|
||||||
private static long totalPaidOut;
|
private static decimal totalPaidOut;
|
||||||
|
|
||||||
private static readonly HashSet<ulong> _runningUsers = new();
|
private static readonly HashSet<ulong> _runningUsers = new();
|
||||||
|
|
||||||
@@ -375,6 +41,7 @@ public partial class Gambling
|
|||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
private readonly FontProvider _fonts;
|
private readonly FontProvider _fonts;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
private object _slotStatsLock = new();
|
||||||
|
|
||||||
public SlotCommands(
|
public SlotCommands(
|
||||||
ImageCache images,
|
ImageCache images,
|
||||||
@@ -405,9 +72,9 @@ public partial class Gambling
|
|||||||
var embed = _eb.Create()
|
var embed = _eb.Create()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle("Slot Stats")
|
.WithTitle("Slot Stats")
|
||||||
.AddField("Total Bet", bet.ToString(), true)
|
.AddField("Total Bet", N(bet), true)
|
||||||
.AddField("Paid Out", paid.ToString(), true)
|
.AddField("Paid Out", N(paid), true)
|
||||||
.WithFooter($"Payout Rate: {paid * 1.0 / bet * 100:f4}%");
|
.WithFooter($"Payout Rate: {paid * 1.0M / bet * 100:f4}%");
|
||||||
|
|
||||||
await ctx.Channel.EmbedAsync(embed);
|
await ctx.Channel.EmbedAsync(embed);
|
||||||
}
|
}
|
||||||
@@ -419,10 +86,10 @@ public partial class Gambling
|
|||||||
if (tests <= 0)
|
if (tests <= 0)
|
||||||
return;
|
return;
|
||||||
//multi vs how many times it occured
|
//multi vs how many times it occured
|
||||||
var dict = new Dictionary<int, int>();
|
var dict = new Dictionary<decimal, int>();
|
||||||
for (var i = 0; i < tests; i++)
|
for (var i = 0; i < tests; i++)
|
||||||
{
|
{
|
||||||
var res = SlotMachine.Pull();
|
var res = SlotMachine.Pull(0);
|
||||||
if (dict.ContainsKey(res.Multiplier))
|
if (dict.ContainsKey(res.Multiplier))
|
||||||
dict[res.Multiplier] += 1;
|
dict[res.Multiplier] += 1;
|
||||||
else
|
else
|
||||||
@@ -430,7 +97,7 @@ public partial class Gambling
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
var payout = 0;
|
decimal payout = 0;
|
||||||
foreach (var key in dict.Keys.OrderByDescending(x => x))
|
foreach (var key in dict.Keys.OrderByDescending(x => x))
|
||||||
{
|
{
|
||||||
sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
|
sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
|
||||||
@@ -439,7 +106,7 @@ public partial class Gambling
|
|||||||
|
|
||||||
await SendConfirmAsync("Slot Test Results",
|
await SendConfirmAsync("Slot Test Results",
|
||||||
sb.ToString(),
|
sb.ToString(),
|
||||||
footer: $"Total Bet: {tests} | Payout: {payout} | {payout * 1.0f / tests * 100}%");
|
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
@@ -455,18 +122,21 @@ public partial class Gambling
|
|||||||
|
|
||||||
await ctx.Channel.TriggerTypingAsync();
|
await ctx.Channel.TriggerTypingAsync();
|
||||||
|
|
||||||
var result = await _service.SlotAsync(ctx.User.Id, amount);
|
var maybeResult = await _service.SlotAsync(ctx.User.Id, amount);
|
||||||
|
|
||||||
if (result.Error != GamblingError.None)
|
if (!maybeResult.TryPickT0(out var result, out var error))
|
||||||
{
|
{
|
||||||
if (result.Error == GamblingError.NotEnough)
|
if (error == GamblingError.InsufficientFunds)
|
||||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Add(ref totalBet, amount);
|
lock (_slotStatsLock)
|
||||||
Interlocked.Add(ref totalPaidOut, result.Won);
|
{
|
||||||
|
totalBet += amount;
|
||||||
|
totalPaidOut += result.Won;
|
||||||
|
}
|
||||||
|
|
||||||
long ownedAmount;
|
long ownedAmount;
|
||||||
await using (var uow = _db.GetDbContext())
|
await using (var uow = _db.GetDbContext())
|
||||||
@@ -537,13 +207,13 @@ public partial class Gambling
|
|||||||
var msg = GetText(strs.better_luck);
|
var msg = GetText(strs.better_luck);
|
||||||
if (result.Multiplier > 0)
|
if (result.Multiplier > 0)
|
||||||
{
|
{
|
||||||
if (Math.Abs(result.Multiplier - 1f) <= float.Epsilon)
|
if (Math.Abs(result.Multiplier - 1M) <= decimal.)
|
||||||
msg = GetText(strs.slot_single(CurrencySign, 1));
|
msg = GetText(strs.slot_single(CurrencySign, 1));
|
||||||
else if (Math.Abs(result.Multiplier - 4f) < float.Epsilon)
|
else if (Math.Abs(result.Multiplier - 4M) < float.Epsilon)
|
||||||
msg = GetText(strs.slot_two(CurrencySign, 4));
|
msg = GetText(strs.slot_two(CurrencySign, 4));
|
||||||
else if (Math.Abs(result.Multiplier - 10f) <= float.Epsilon)
|
else if (Math.Abs(result.Multiplier - 10M) <= float.Epsilon)
|
||||||
msg = GetText(strs.slot_three(10));
|
msg = GetText(strs.slot_three(10));
|
||||||
else if (Math.Abs(result.Multiplier - 30f) <= float.Epsilon)
|
else if (Math.Abs(result.Multiplier - 30M) <= float.Epsilon)
|
||||||
msg = GetText(strs.slot_jackpot(30));
|
msg = GetText(strs.slot_jackpot(30));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,19 +246,19 @@ public sealed class SlotMachine
|
|||||||
//three flowers
|
//three flowers
|
||||||
arr => arr.All(a => a == MAX_VALUE) ? 30 : 0,
|
arr => arr.All(a => a == MAX_VALUE) ? 30 : 0,
|
||||||
//three of the same
|
//three of the same
|
||||||
arr => !arr.Any(a => a != arr[0]) ? 10 : 0,
|
arr => arr.All(a => a == arr[0]) ? 10 : 0,
|
||||||
//two flowers
|
//two flowers
|
||||||
arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0,
|
arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0,
|
||||||
//one flower
|
//one flower
|
||||||
arr => arr.Any(a => a == MAX_VALUE) ? 1 : 0
|
arr => arr.Any(a => a == MAX_VALUE) ? 1 : 0
|
||||||
};
|
};
|
||||||
|
|
||||||
public static SlotResult Pull()
|
public static SlotResult Pull(decimal amount)
|
||||||
{
|
{
|
||||||
var numbers = new int[3];
|
var numbers = new int[3];
|
||||||
for (var i = 0; i < numbers.Length; i++)
|
for (var i = 0; i < numbers.Length; i++)
|
||||||
numbers[i] = new NadekoRandom().Next(0, MAX_VALUE + 1);
|
numbers[i] = new NadekoRandom().Next(0, MAX_VALUE + 1);
|
||||||
var multi = 0;
|
var multi = 0M;
|
||||||
foreach (var t in _winningCombos)
|
foreach (var t in _winningCombos)
|
||||||
{
|
{
|
||||||
multi = t(numbers);
|
multi = t(numbers);
|
||||||
@@ -596,18 +266,11 @@ public sealed class SlotMachine
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(numbers, multi);
|
return new()
|
||||||
}
|
{
|
||||||
}
|
Rolls = numbers,
|
||||||
|
Multiplier = multi,
|
||||||
public struct SlotResult
|
Won = multi * amount,
|
||||||
{
|
};
|
||||||
public int[] Numbers { get; }
|
|
||||||
public int Multiplier { get; }
|
|
||||||
|
|
||||||
public SlotResult(int[] nums, int multi)
|
|
||||||
{
|
|
||||||
Numbers = nums;
|
|
||||||
Multiplier = multi;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,10 +0,0 @@
|
|||||||
#nullable disable
|
|
||||||
namespace NadekoBot.Modules.Gambling;
|
|
||||||
|
|
||||||
public class SlotResponse
|
|
||||||
{
|
|
||||||
public float Multiplier { get; set; }
|
|
||||||
public long Won { get; set; }
|
|
||||||
public List<int> Rolls { get; set; } = new();
|
|
||||||
public GamblingError Error { get; set; }
|
|
||||||
}
|
|
@@ -1,43 +0,0 @@
|
|||||||
#nullable disable
|
|
||||||
namespace NadekoBot.Modules.Gambling.Common;
|
|
||||||
|
|
||||||
public class Betroll
|
|
||||||
{
|
|
||||||
private readonly IOrderedEnumerable<BetRollPair> _thresholdPairs;
|
|
||||||
private readonly Random _rng;
|
|
||||||
|
|
||||||
public Betroll(IReadOnlyList<long> pairs)
|
|
||||||
{
|
|
||||||
_thresholdPairs = settings.Pairs.OrderByDescending(x => x.WhenAbove);
|
|
||||||
_rng = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result Roll()
|
|
||||||
{
|
|
||||||
var roll = _rng.Next(0, 101);
|
|
||||||
|
|
||||||
var pair = _thresholdPairs.FirstOrDefault(x => x.WhenAbove < roll);
|
|
||||||
if (pair is null)
|
|
||||||
{
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Multiplier = 0,
|
|
||||||
Roll = roll
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Multiplier = pair.MultiplyBy,
|
|
||||||
Roll = roll,
|
|
||||||
Threshold = pair.WhenAbove
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Result
|
|
||||||
{
|
|
||||||
public int Roll { get; set; }
|
|
||||||
public float Multiplier { get; set; }
|
|
||||||
public int Threshold { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +1,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Modules.Gambling;
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
|
||||||
public enum GamblingError
|
public enum OldGamblingError
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
NotEnough
|
NotEnough
|
||||||
|
14
src/NadekoBot/Modules/Gambling/~Shared/IGamblingService.cs
Normal file
14
src/NadekoBot/Modules/Gambling/~Shared/IGamblingService.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#nullable disable
|
||||||
|
using Nadeko.Econ.Gambling;
|
||||||
|
using OneOf;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
|
||||||
|
public interface IGamblingService
|
||||||
|
{
|
||||||
|
Task<OneOf<WofResult, GamblingError>> WofAsync(ulong userId, long amount);
|
||||||
|
Task<OneOf<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount);
|
||||||
|
Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, int guess);
|
||||||
|
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
||||||
|
Task<FlipResult[]> FlipAsync(int count);
|
||||||
|
}
|
251
src/NadekoBot/Modules/Gambling/~Shared/NewGamblingService.cs
Normal file
251
src/NadekoBot/Modules/Gambling/~Shared/NewGamblingService.cs
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
#nullable disable
|
||||||
|
using Nadeko.Econ.Gambling;
|
||||||
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
|
using OneOf;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
|
||||||
|
public sealed class NewGamblingService : IGamblingService, INService
|
||||||
|
{
|
||||||
|
private readonly GamblingConfigService _bcs;
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
|
public NewGamblingService(GamblingConfigService bcs, ICurrencyService cs)
|
||||||
|
{
|
||||||
|
_bcs = bcs;
|
||||||
|
_cs = cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo input checks
|
||||||
|
public async Task<OneOf<WofResult, GamblingError>> WofAsync(ulong userId, long amount)
|
||||||
|
{
|
||||||
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("wof", "bet"));
|
||||||
|
|
||||||
|
if (!isTakeSuccess)
|
||||||
|
{
|
||||||
|
return GamblingError.InsufficientFunds;
|
||||||
|
}
|
||||||
|
|
||||||
|
var game = new WofGame(_bcs.Data.WheelOfFortune.Multipliers);
|
||||||
|
var result = game.Spin(amount);
|
||||||
|
|
||||||
|
var won = (long)result.Won;
|
||||||
|
if (won > 0)
|
||||||
|
{
|
||||||
|
await _cs.AddAsync(userId, won, new("wof", "win"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OneOf<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount)
|
||||||
|
{
|
||||||
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betroll", "bet"));
|
||||||
|
|
||||||
|
if (!isTakeSuccess)
|
||||||
|
{
|
||||||
|
return GamblingError.InsufficientFunds;
|
||||||
|
}
|
||||||
|
|
||||||
|
var game = new BetrollGame(_bcs.Data.BetRoll.Pairs
|
||||||
|
.Select(x => ((decimal)x.WhenAbove, (decimal)x.MultiplyBy))
|
||||||
|
.ToList());
|
||||||
|
var result = game.Roll(amount);
|
||||||
|
|
||||||
|
var won = (long)result.Won;
|
||||||
|
if (won > 0)
|
||||||
|
{
|
||||||
|
await _cs.AddAsync(userId, won, new("betroll", "win"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, int guess)
|
||||||
|
{
|
||||||
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet"));
|
||||||
|
|
||||||
|
if (!isTakeSuccess)
|
||||||
|
{
|
||||||
|
return GamblingError.InsufficientFunds;
|
||||||
|
}
|
||||||
|
|
||||||
|
var game = new BetflipGame(_bcs.Data.BetFlip.Multiplier);
|
||||||
|
var result = game.Flip(guess, amount);
|
||||||
|
|
||||||
|
var won = (long)result.Won;
|
||||||
|
if (won > 0)
|
||||||
|
{
|
||||||
|
await _cs.AddAsync(userId, won, new("betflip", "win"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount)
|
||||||
|
{
|
||||||
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("slot", "bet"));
|
||||||
|
|
||||||
|
if (!isTakeSuccess)
|
||||||
|
{
|
||||||
|
return GamblingError.InsufficientFunds;
|
||||||
|
}
|
||||||
|
|
||||||
|
var game = new SlotGame();
|
||||||
|
var result = game.Spin(amount);
|
||||||
|
|
||||||
|
var won = (long)result.Won;
|
||||||
|
if (won > 0)
|
||||||
|
{
|
||||||
|
await _cs.AddAsync(userId, won, new("slot", "won"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FlipResult[]> FlipAsync(int count)
|
||||||
|
{
|
||||||
|
var game = new BetflipGame(0);
|
||||||
|
|
||||||
|
var results = new FlipResult[count];
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
results[i] = new()
|
||||||
|
{
|
||||||
|
Side = game.Flip(0, 0).Side
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo deck draw black/white?
|
||||||
|
|
||||||
|
//
|
||||||
|
// private readonly ConcurrentDictionary<ulong, Deck> _decks = new ConcurrentDictionary<ulong, Deck>();
|
||||||
|
//
|
||||||
|
// public override Task<DeckShuffleReply> DeckShuffle(DeckShuffleRequest request, ServerCallContext context)
|
||||||
|
// {
|
||||||
|
// _decks.AddOrUpdate(request.Id, new Deck(), (key, old) => new Deck());
|
||||||
|
// return Task.FromResult(new DeckShuffleReply { });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public override Task<DeckDrawReply> DeckDraw(DeckDrawRequest request, ServerCallContext context)
|
||||||
|
// {
|
||||||
|
// if (request.Count < 1 || request.Count > 10)
|
||||||
|
// throw new ArgumentOutOfRangeException(nameof(request.Id));
|
||||||
|
//
|
||||||
|
// var deck = request.UseNew
|
||||||
|
// ? new Deck()
|
||||||
|
// : _decks.GetOrAdd(request.Id, new Deck());
|
||||||
|
//
|
||||||
|
// var list = new List<Deck.Card>(request.Count);
|
||||||
|
// for (int i = 0; i < request.Count; i++)
|
||||||
|
// {
|
||||||
|
// var card = deck.DrawNoRestart();
|
||||||
|
// if (card is null)
|
||||||
|
// {
|
||||||
|
// if (i == 0)
|
||||||
|
// {
|
||||||
|
// deck.Restart();
|
||||||
|
// list.Add(deck.DrawNoRestart());
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// list.Add(card);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // todo 3.2 should replace all "placeholder" words in command strings with a link to the placeholder list explanation
|
||||||
|
// var cards = list
|
||||||
|
// .Select(x => new Card
|
||||||
|
// {
|
||||||
|
// Name = x.ToString().ToLowerInvariant().Replace(' ', '_'),
|
||||||
|
// Number = x.Number,
|
||||||
|
// Suit = (CardSuit) x.Suit
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// var toReturn = new DeckDrawReply();
|
||||||
|
// toReturn.Cards.AddRange(cards);
|
||||||
|
//
|
||||||
|
// return Task.FromResult(toReturn);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public override async Task<RpsReply> Rps(RpsRequest request, ServerCallContext context)
|
||||||
|
// {
|
||||||
|
// if (request.Amount > 0)
|
||||||
|
// {
|
||||||
|
// var res = await _currency.TransferCurrencyAsync(new TransferCurrencyRequest
|
||||||
|
// {
|
||||||
|
// Amount = request.Amount,
|
||||||
|
// FromId = request.UserId,
|
||||||
|
// Type = "rps",
|
||||||
|
// Subtype = "bet",
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// if (!res.Success)
|
||||||
|
// {
|
||||||
|
// return new RpsReply
|
||||||
|
// {
|
||||||
|
// Result = RpsReply.Types.ResultType.NotEnough
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var botPick = _rng.Next(0, 3);
|
||||||
|
// var userPick = (int) request.Pick;
|
||||||
|
//
|
||||||
|
// if (botPick == userPick)
|
||||||
|
// {
|
||||||
|
// if (request.Amount > 0)
|
||||||
|
// {
|
||||||
|
// await _currency.GrantToUserAsync(new GrantToUserRequest
|
||||||
|
// {
|
||||||
|
// Amount = request.Amount,
|
||||||
|
// GranterId = 0,
|
||||||
|
// Type = "rps",
|
||||||
|
// Subtype = "draw",
|
||||||
|
// UserId = request.UserId,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return new RpsReply
|
||||||
|
// {
|
||||||
|
// BotPick = (RpsPick) botPick,
|
||||||
|
// WonAmount = request.Amount,
|
||||||
|
// Result = RpsReply.Types.ResultType.Draw
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if ((botPick == 1 && userPick == 2) || (botPick == 2 && userPick == 0) || (botPick == 0 && userPick == 1))
|
||||||
|
// {
|
||||||
|
// if (request.Amount > 0)
|
||||||
|
// {
|
||||||
|
// await _currency.GrantToUserAsync(new GrantToUserRequest
|
||||||
|
// {
|
||||||
|
// Amount = (long) (request.Amount * 1.95f),
|
||||||
|
// GranterId = 0,
|
||||||
|
// Type = "rps",
|
||||||
|
// Subtype = "draw",
|
||||||
|
// UserId = request.UserId,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return new RpsReply
|
||||||
|
// {
|
||||||
|
// BotPick = (RpsPick) botPick,
|
||||||
|
// WonAmount = (long) (request.Amount * 1.95f),
|
||||||
|
// Result = RpsReply.Types.ResultType.Won
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return new RpsReply
|
||||||
|
// {
|
||||||
|
// BotPick = (RpsPick) botPick,
|
||||||
|
// WonAmount = 0,
|
||||||
|
// Result = RpsReply.Types.ResultType.Lost
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
}
|
@@ -1281,10 +1281,4 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
uow.RemoveRange(guildConfig.XpSettings.CurrencyRewards);
|
uow.RemoveRange(guildConfig.XpSettings.CurrencyRewards);
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum NotifOf
|
|
||||||
{
|
|
||||||
Server,
|
|
||||||
Global
|
|
||||||
} // is it a server level-up or global level-up notification
|
|
||||||
}
|
}
|
@@ -102,6 +102,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ayu\Ayu.Discord.Voice\Ayu.Discord.Voice.csproj" />
|
<ProjectReference Include="..\ayu\Ayu.Discord.Voice\Ayu.Discord.Voice.csproj" />
|
||||||
<ProjectReference Include="..\Nadeko.Common\Nadeko.Common.csproj" />
|
<ProjectReference Include="..\Nadeko.Common\Nadeko.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Nadeko.Econ\Nadeko.Econ.csproj" />
|
||||||
<ProjectReference Include="..\Nadeko.Medusa\Nadeko.Medusa.csproj" />
|
<ProjectReference Include="..\Nadeko.Medusa\Nadeko.Medusa.csproj" />
|
||||||
<ProjectReference Include="..\NadekoBot.Generators\NadekoBot.Generators.csproj" OutputItemType="Analyzer" />
|
<ProjectReference Include="..\NadekoBot.Generators\NadekoBot.Generators.csproj" OutputItemType="Analyzer" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -125,6 +126,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Common\Collections" />
|
<Folder Include="Common\Collections" />
|
||||||
|
<Folder Include="Modules\Gambling\Wheel" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Version)' == '' ">
|
<PropertyGroup Condition=" '$(Version)' == '' ">
|
||||||
|
Reference in New Issue
Block a user