added .rakeback to get a part of the house edge back. Rakeback is accumulated by betting (not winning or losing in particular). All games have manually specified rakeback values
slot now has 1 more icon (wheat!), and multipliers have been modified to even out the gains betroll is improved (around 2% better payout), as 66 is now a winning number, not a losing one
This commit is contained in:
parent
14ac3c92bb
commit
66870f6859
21 changed files with 7282 additions and 55 deletions
|
@ -74,6 +74,13 @@ public abstract class EllieContext : DbContext
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
#region Rakeback
|
||||||
|
|
||||||
|
modelBuilder.Entity<Rakeback>()
|
||||||
|
.HasKey(x => x.UserId);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region UserBetStats
|
#region UserBetStats
|
||||||
|
|
||||||
modelBuilder.Entity<UserBetStats>()
|
modelBuilder.Entity<UserBetStats>()
|
||||||
|
|
3919
src/EllieBot/Migrations/PostgreSql/20241107051622_rakeback.Designer.cs
generated
Normal file
3919
src/EllieBot/Migrations/PostgreSql/20241107051622_rakeback.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,33 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace EllieBot.Migrations.PostgreSql
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class rakeback : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "rakeback",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
amount = table.Column<decimal>(type: "numeric", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_rakeback", x => x.userid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "rakeback");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3227,6 +3227,23 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("greetsettings", (string)null);
|
b.ToTable("greetsettings", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Services.Rakeback", b =>
|
||||||
|
{
|
||||||
|
b.Property<decimal>("UserId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("userid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("amount");
|
||||||
|
|
||||||
|
b.HasKey("UserId")
|
||||||
|
.HasName("pk_rakeback");
|
||||||
|
|
||||||
|
b.ToTable("rakeback", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Services.UserBetStats", b =>
|
modelBuilder.Entity("EllieBot.Services.UserBetStats", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
3025
src/EllieBot/Migrations/Sqlite/20241107051525_rakeback.Designer.cs
generated
Normal file
3025
src/EllieBot/Migrations/Sqlite/20241107051525_rakeback.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
34
src/EllieBot/Migrations/Sqlite/20241107051525_rakeback.cs
Normal file
34
src/EllieBot/Migrations/Sqlite/20241107051525_rakeback.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace EllieBot.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class rakeback : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Rakeback",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
UserId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Amount = table.Column<decimal>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Rakeback", x => x.UserId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Rakeback");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2399,6 +2399,20 @@ namespace EllieBot.Migrations
|
||||||
b.ToTable("GreetSettings");
|
b.ToTable("GreetSettings");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Services.Rakeback", b =>
|
||||||
|
{
|
||||||
|
b.Property<ulong>("UserId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Rakeback");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Services.UserBetStats", b =>
|
modelBuilder.Entity("EllieBot.Services.UserBetStats", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
|
@ -38,6 +38,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
private readonly IRemindService _remind;
|
private readonly IRemindService _remind;
|
||||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||||
private readonly IPatronageService _ps;
|
private readonly IPatronageService _ps;
|
||||||
|
private readonly RakebackService _rb;
|
||||||
|
|
||||||
public Gambling(
|
public Gambling(
|
||||||
IGamblingService gs,
|
IGamblingService gs,
|
||||||
|
@ -50,7 +51,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
IBankService bank,
|
IBankService bank,
|
||||||
IRemindService remind,
|
IRemindService remind,
|
||||||
IPatronageService patronage,
|
IPatronageService patronage,
|
||||||
GamblingTxTracker gamblingTxTracker)
|
GamblingTxTracker gamblingTxTracker,
|
||||||
|
RakebackService rb)
|
||||||
: base(configService)
|
: base(configService)
|
||||||
{
|
{
|
||||||
_gs = gs;
|
_gs = gs;
|
||||||
|
@ -60,6 +62,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
_bank = bank;
|
_bank = bank;
|
||||||
_remind = remind;
|
_remind = remind;
|
||||||
_gamblingTxTracker = gamblingTxTracker;
|
_gamblingTxTracker = gamblingTxTracker;
|
||||||
|
_rb = rb;
|
||||||
_ps = patronage;
|
_ps = patronage;
|
||||||
_rng = new EllieRandom();
|
_rng = new EllieRandom();
|
||||||
|
|
||||||
|
@ -318,7 +321,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
|
|
||||||
|
|
||||||
var val = Config.Timely.Amount;
|
var val = Config.Timely.Amount;
|
||||||
|
|
||||||
var boostGuilds = Config.BoostBonus.GuildIds ?? new();
|
var boostGuilds = Config.BoostBonus.GuildIds ?? new();
|
||||||
var guildUsers = await boostGuilds
|
var guildUsers = await boostGuilds
|
||||||
.Select(async gid =>
|
.Select(async gid =>
|
||||||
|
@ -1089,4 +1091,45 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%")
|
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%")
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EllieInteractionBase CreateRakebackInteraction()
|
||||||
|
=> _inter.Create(ctx.User.Id,
|
||||||
|
new ButtonBuilder(
|
||||||
|
customId: "cash:rakeback",
|
||||||
|
emote: new Emoji("💸")),
|
||||||
|
RakebackAction);
|
||||||
|
|
||||||
|
private async Task RakebackAction(SocketMessageComponent arg)
|
||||||
|
{
|
||||||
|
var rb = await _rb.ClaimRakebackAsync(ctx.User.Id);
|
||||||
|
|
||||||
|
if (rb == 0)
|
||||||
|
{
|
||||||
|
await arg.DeferAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await arg.RespondAsync(_sender, GetText(strs.rakeback_claimed(N(rb))), MsgType.Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task Rakeback()
|
||||||
|
{
|
||||||
|
var rb = await _rb.GetRakebackAsync(ctx.User.Id);
|
||||||
|
|
||||||
|
if (rb < 1)
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Error(strs.rakeback_none)
|
||||||
|
.SendAsync();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inter = CreateRakebackInteraction();
|
||||||
|
await Response()
|
||||||
|
.Pending(strs.rakeback_available(N(rb)))
|
||||||
|
.Interaction(inter)
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ namespace EllieBot.Modules.Gambling.Common;
|
||||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||||
{
|
{
|
||||||
[Comment("""DO NOT CHANGE""")]
|
[Comment("""DO NOT CHANGE""")]
|
||||||
public int Version { get; set; } = 11;
|
public int Version { get; set; } = 12;
|
||||||
|
|
||||||
[Comment("""Currency settings""")]
|
[Comment("""Currency settings""")]
|
||||||
public CurrencyConfig Currency { get; set; }
|
public CurrencyConfig Currency { get; set; }
|
||||||
|
@ -164,7 +164,7 @@ public partial class BetRollConfig
|
||||||
},
|
},
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
WhenAbove = 66,
|
WhenAbove = 65,
|
||||||
MultiplyBy = 2
|
MultiplyBy = 2
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -226,7 +226,7 @@ public partial class LuckyLadderSettings
|
||||||
public decimal[] Multipliers { get; set; }
|
public decimal[] Multipliers { get; set; }
|
||||||
|
|
||||||
public LuckyLadderSettings()
|
public LuckyLadderSettings()
|
||||||
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.2M, 0.5M, 0.3M, 0.2M, 0.1M];
|
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.1M, 0.5M, 0.3M, 0.2M, 0.1M];
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
|
|
|
@ -189,11 +189,16 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.Version < 11)
|
if (data.Version < 12)
|
||||||
{
|
{
|
||||||
ModifyConfig(c =>
|
ModifyConfig(c =>
|
||||||
{
|
{
|
||||||
c.Version = 11;
|
c.Version = 12;
|
||||||
|
|
||||||
|
if (c.BetRoll.Pairs.Length == 3 && c.BetRoll.Pairs[2].WhenAbove == 66)
|
||||||
|
{
|
||||||
|
c.BetRoll.Pairs[2].WhenAbove = 65;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,5 +13,10 @@ public interface IGamblingService
|
||||||
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
||||||
Task<FlipResult[]> FlipAsync(int count);
|
Task<FlipResult[]> FlipAsync(int count);
|
||||||
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
|
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
|
||||||
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor);
|
|
||||||
|
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(
|
||||||
|
ulong userId,
|
||||||
|
long amount,
|
||||||
|
byte? maybeGuessValue,
|
||||||
|
byte? maybeGuessColor);
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Modules.Gambling.Betdraw;
|
using EllieBot.Modules.Gambling.Betdraw;
|
||||||
using EllieBot.Modules.Gambling.Rps;
|
using EllieBot.Modules.Gambling.Rps;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
|
@ -8,12 +10,12 @@ namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
public sealed class NewGamblingService : IGamblingService, IEService
|
public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
{
|
{
|
||||||
private readonly GamblingConfigService _bcs;
|
private readonly GamblingConfigService _gcs;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
public NewGamblingService(GamblingConfigService bcs, ICurrencyService cs)
|
public NewGamblingService(GamblingConfigService gcs, ICurrencyService cs)
|
||||||
{
|
{
|
||||||
_bcs = bcs;
|
_gcs = gcs;
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +33,7 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new LulaGame(_bcs.Data.LuckyLadder.Multipliers);
|
var game = new LulaGame(_gcs.Data.LuckyLadder.Multipliers);
|
||||||
var result = game.Spin(amount);
|
var result = game.Spin(amount);
|
||||||
|
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
|
@ -57,9 +59,9 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new BetrollGame(_bcs.Data.BetRoll.Pairs
|
var game = new BetrollGame(_gcs.Data.BetRoll.Pairs
|
||||||
.Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
|
.Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
|
||||||
.ToList());
|
.ToList());
|
||||||
|
|
||||||
var result = game.Roll(amount);
|
var result = game.Roll(amount);
|
||||||
|
|
||||||
|
@ -88,7 +90,7 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new BetflipGame(_bcs.Data.BetFlip.Multiplier);
|
var game = new BetflipGame(_gcs.Data.BetFlip.Multiplier);
|
||||||
var result = game.Flip(guess, amount);
|
var result = game.Flip(guess, amount);
|
||||||
|
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
|
@ -100,7 +102,11 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor)
|
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(
|
||||||
|
ulong userId,
|
||||||
|
long amount,
|
||||||
|
byte? maybeGuessValue,
|
||||||
|
byte? maybeGuessColor)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
|
|
||||||
|
@ -266,3 +272,45 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class RakebackService : IEService
|
||||||
|
{
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
|
public RakebackService(DbService db, ICurrencyService cs)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_cs = cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetRakebackAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
var rb = uow.GetTable<Rakeback>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.Select(x => x.Amount)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return (long)rb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> ClaimRakebackAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
var rbs = await uow.GetTable<Rakeback>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.DeleteWithOutputAsync((x) => x.Amount);
|
||||||
|
|
||||||
|
if (rbs.Length == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var rb = (long)rbs[0];
|
||||||
|
|
||||||
|
await _cs.AddAsync(userId, rb, new("rakeback", "claim"));
|
||||||
|
|
||||||
|
return rb;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,9 +11,9 @@ public class SlotGame
|
||||||
{
|
{
|
||||||
var rolls = new[]
|
var rolls = new[]
|
||||||
{
|
{
|
||||||
(byte)_rng.Next(0, 6),
|
(byte)_rng.Next(0, 7),
|
||||||
(byte)_rng.Next(0, 6),
|
(byte)_rng.Next(0, 7),
|
||||||
(byte)_rng.Next(0, 6)
|
(byte)_rng.Next(0, 7)
|
||||||
};
|
};
|
||||||
|
|
||||||
ref var a = ref rolls[0];
|
ref var a = ref rolls[0];
|
||||||
|
@ -24,24 +24,24 @@ public class SlotGame
|
||||||
var winType = SlotWinType.None;
|
var winType = SlotWinType.None;
|
||||||
if (a == b && b == c)
|
if (a == b && b == c)
|
||||||
{
|
{
|
||||||
if (a == 5)
|
if (a == 6)
|
||||||
{
|
{
|
||||||
winType = SlotWinType.TrippleJoker;
|
winType = SlotWinType.TrippleJoker;
|
||||||
multi = 30;
|
multi = 25;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
winType = SlotWinType.TrippleNormal;
|
winType = SlotWinType.TrippleNormal;
|
||||||
multi = 10;
|
multi = 15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (a == 5 && (b == 5 || c == 5)
|
else if (a == 6 && (b == 6 || c == 6)
|
||||||
|| (b == 5 && c == 5))
|
|| (b == 6 && c == 6))
|
||||||
{
|
{
|
||||||
winType = SlotWinType.DoubleJoker;
|
winType = SlotWinType.DoubleJoker;
|
||||||
multi = 4;
|
multi = 6;
|
||||||
}
|
}
|
||||||
else if (a == 5 || b == 5 || c == 5)
|
else if (a == 6 || b == 6 || c == 6)
|
||||||
{
|
{
|
||||||
winType = SlotWinType.SingleJoker;
|
winType = SlotWinType.SingleJoker;
|
||||||
multi = 1;
|
multi = 1;
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Common;
|
||||||
public partial class ImageUrls : ICloneable<ImageUrls>
|
public partial class ImageUrls : ICloneable<ImageUrls>
|
||||||
{
|
{
|
||||||
[Comment("DO NOT CHANGE")]
|
[Comment("DO NOT CHANGE")]
|
||||||
public int Version { get; set; } = 5;
|
public int Version { get; set; } = 6;
|
||||||
|
|
||||||
public CoinData Coins { get; set; }
|
public CoinData Coins { get; set; }
|
||||||
public Uri[] Currency { get; set; }
|
public Uri[] Currency { get; set; }
|
||||||
|
|
|
@ -88,6 +88,10 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
if (users.Count == 0)
|
if (users.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// rakeback
|
||||||
|
var rakebacks = new Dictionary<ulong, decimal>();
|
||||||
|
|
||||||
|
// update userstats
|
||||||
foreach (var (k, x) in users.GroupBy(x => (x.UserId, x.Game))
|
foreach (var (k, x) in users.GroupBy(x => (x.UserId, x.Game))
|
||||||
.ToDictionary(x => x.Key,
|
.ToDictionary(x => x.Key,
|
||||||
x => x.Aggregate((a, b) => new()
|
x => x.Aggregate((a, b) => new()
|
||||||
|
@ -100,6 +104,10 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
MaxWin = Math.Max(a.MaxWin, b.MaxWin),
|
MaxWin = Math.Max(a.MaxWin, b.MaxWin),
|
||||||
})))
|
})))
|
||||||
{
|
{
|
||||||
|
rakebacks.TryAdd(k.UserId, 0m);
|
||||||
|
rakebacks[k.UserId] += x.TotalBet * GetHouseEdge(k.Game) * BASE_RAKEBACK;
|
||||||
|
|
||||||
|
|
||||||
// bulk upsert in the future
|
// bulk upsert in the future
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.GetTable<UserBetStats>()
|
await uow.GetTable<UserBetStats>()
|
||||||
|
@ -129,6 +137,25 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
Game = k.Game
|
Game = k.Game
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var (k, v) in rakebacks)
|
||||||
|
{
|
||||||
|
await _db.GetDbContext()
|
||||||
|
.GetTable<Rakeback>()
|
||||||
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
{
|
||||||
|
UserId = k,
|
||||||
|
Amount = v
|
||||||
|
},
|
||||||
|
(old) => new()
|
||||||
|
{
|
||||||
|
Amount = old.Amount + v
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
UserId = k
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -137,6 +164,8 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const decimal BASE_RAKEBACK = 0.05m;
|
||||||
|
|
||||||
public Task TrackAdd(ulong userId, long amount, TxData? txData)
|
public Task TrackAdd(ulong userId, long amount, TxData? txData)
|
||||||
{
|
{
|
||||||
if (txData is null)
|
if (txData is null)
|
||||||
|
@ -275,6 +304,19 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
.Where(x => x.UserId == userId && x.Game == game)
|
.Where(x => x.UserId == userId && x.Game == game)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public decimal GetHouseEdge(GamblingGame game)
|
||||||
|
=> game switch
|
||||||
|
{
|
||||||
|
GamblingGame.Betflip => 0.025m,
|
||||||
|
GamblingGame.Betroll => 0.04m,
|
||||||
|
GamblingGame.Betdraw => 0.04m,
|
||||||
|
GamblingGame.Slots => 0.034m,
|
||||||
|
GamblingGame.Blackjack => 0.02m,
|
||||||
|
GamblingGame.Lula => 0.025m,
|
||||||
|
GamblingGame.Race => 0.06m,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class UserBetStats
|
public sealed class UserBetStats
|
||||||
|
@ -306,3 +348,9 @@ public enum GamblingGame
|
||||||
Race = 6,
|
Race = 6,
|
||||||
AnimalRace = 6
|
AnimalRace = 6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class Rakeback
|
||||||
|
{
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
}
|
|
@ -27,5 +27,22 @@ public sealed class ImagesConfig : ConfigServiceBase<ImageUrls>
|
||||||
c.Version = 5;
|
c.Version = 5;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.Version < 6)
|
||||||
|
{
|
||||||
|
ModifyConfig(c =>
|
||||||
|
{
|
||||||
|
if (c.Slots.Emojis?.Length == 6)
|
||||||
|
{
|
||||||
|
c.Slots.Emojis =
|
||||||
|
[
|
||||||
|
new("https://cdn.nadeko.bot/slots/15.png"),
|
||||||
|
..c.Slots.Emojis
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Version = 6;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1454,3 +1454,6 @@ translateflags:
|
||||||
- trfl
|
- trfl
|
||||||
- fltr
|
- fltr
|
||||||
- transflags
|
- transflags
|
||||||
|
rakeback:
|
||||||
|
- rakeback
|
||||||
|
- rb
|
|
@ -1,5 +1,5 @@
|
||||||
# DO NOT CHANGE
|
# DO NOT CHANGE
|
||||||
version: 11
|
version: 12
|
||||||
# Currency settings
|
# Currency settings
|
||||||
currency:
|
currency:
|
||||||
# What is the emoji/character which represents the currency
|
# What is the emoji/character which represents the currency
|
||||||
|
@ -28,7 +28,7 @@ betRoll:
|
||||||
multiplyBy: 10
|
multiplyBy: 10
|
||||||
- whenAbove: 90
|
- whenAbove: 90
|
||||||
multiplyBy: 4
|
multiplyBy: 4
|
||||||
- whenAbove: 66
|
- whenAbove: 65
|
||||||
multiplyBy: 2
|
multiplyBy: 2
|
||||||
# Automatic currency generation settings.
|
# Automatic currency generation settings.
|
||||||
generation:
|
generation:
|
||||||
|
@ -85,7 +85,7 @@ luckyLadder:
|
||||||
- 2.4
|
- 2.4
|
||||||
- 1.7
|
- 1.7
|
||||||
- 1.5
|
- 1.5
|
||||||
- 1.2
|
- 1.1
|
||||||
- 0.5
|
- 0.5
|
||||||
- 0.3
|
- 0.3
|
||||||
- 0.2
|
- 0.2
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# DO NOT CHANGE
|
# DO NOT CHANGE
|
||||||
version: 5
|
version: 6
|
||||||
coins:
|
coins:
|
||||||
heads:
|
heads:
|
||||||
- https://cdn.nadeko.bot/coins/heads3.png
|
- https://cdn.nadeko.bot/coins/heads3.png
|
||||||
|
@ -22,15 +22,13 @@ dice:
|
||||||
- https://cdn.nadeko.bot/other/dice/9.png
|
- https://cdn.nadeko.bot/other/dice/9.png
|
||||||
xp:
|
xp:
|
||||||
bg: https://cdn.nadeko.bot/other/xp/bg_k.png
|
bg: https://cdn.nadeko.bot/other/xp/bg_k.png
|
||||||
rip:
|
|
||||||
bg: https://cdn.nadeko.bot/other/rip/rip.png
|
|
||||||
overlay: https://cdn.nadeko.bot/other/rip/overlay.png
|
|
||||||
slots:
|
slots:
|
||||||
emojis:
|
emojis:
|
||||||
- https://cdn.nadeko.bot/slots/0.png
|
- https://cdn.nadeko.bot/slots/10.png
|
||||||
- https://cdn.nadeko.bot/slots/1.png
|
- https://cdn.nadeko.bot/slots/11.png
|
||||||
- https://cdn.nadeko.bot/slots/2.png
|
- https://cdn.nadeko.bot/slots/12.png
|
||||||
- https://cdn.nadeko.bot/slots/3.png
|
- https://cdn.nadeko.bot/slots/13.png
|
||||||
- https://cdn.nadeko.bot/slots/4.png
|
- https://cdn.nadeko.bot/slots/14.png
|
||||||
- https://cdn.nadeko.bot/slots/5.png
|
- https://cdn.nadeko.bot/slots/15.png
|
||||||
|
- https://cdn.nadeko.bot/slots/16.png
|
||||||
bg: https://cdn.nadeko.bot/slots/slots_bg.png
|
bg: https://cdn.nadeko.bot/slots/slots_bg.png
|
||||||
|
|
|
@ -4662,3 +4662,11 @@ betstats:
|
||||||
desc: 'The game to show betstats for. Omit to show betstats for all games combined'
|
desc: 'The game to show betstats for. Omit to show betstats for all games combined'
|
||||||
- game:
|
- game:
|
||||||
desc: 'The game to show betstats for. Omit to show betstats for all games combined'
|
desc: 'The game to show betstats for. Omit to show betstats for all games combined'
|
||||||
|
rakeback:
|
||||||
|
desc: |-
|
||||||
|
Try to claim any rakeback that you have avaialable.
|
||||||
|
Rakeback is accumulated by betting (not by winning or losing).
|
||||||
|
Default rakeback is 0.05 * house edge
|
||||||
|
House edge is defined per game
|
||||||
|
params:
|
||||||
|
- {}
|
|
@ -1114,5 +1114,8 @@
|
||||||
"invalid_img_size": "Image must to be {0}x{1} pixels.",
|
"invalid_img_size": "Image must to be {0}x{1} pixels.",
|
||||||
"no_attach_found": "No attachment found. Please send the image along with this command.",
|
"no_attach_found": "No attachment found. Please send the image along with this command.",
|
||||||
"trfl_enabled": "Flag translation enabled on this channel. Reacting to a message with a flag will translate it to that language.",
|
"trfl_enabled": "Flag translation enabled on this channel. Reacting to a message with a flag will translate it to that language.",
|
||||||
"trfl_disabled": "Flag translation disabled."
|
"trfl_disabled": "Flag translation disabled.",
|
||||||
|
"rakeback_claimed": "You've claimed {0} as rakeback!",
|
||||||
|
"rakeback_none": "You don't have any rakeback to claim yet.",
|
||||||
|
"rakeback_available": "You have {0} rakeback available. Click the button to claim."
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue