Added .betstats command, shows statistics for multiple gambling commands, .slotstats removed as it is obsolete

This commit is contained in:
Kwoth
2022-10-03 13:49:52 +02:00
parent 9ffa701742
commit df3909fc55
13 changed files with 3145 additions and 50 deletions

View File

@@ -0,0 +1,9 @@
#nullable disable
namespace NadekoBot.Services.Database.Models;
public class GamblingStats : DbEntity
{
public string Feature { get; set; }
public decimal Bet { get; set; }
public decimal PaidOut { get; set; }
}

View File

@@ -472,6 +472,14 @@ public abstract class NadekoContext : DbContext
.IsUnique()); .IsUnique());
#endregion #endregion
#region GamblingStats
modelBuilder.Entity<GamblingStats>(gs => gs
.HasIndex(x => x.Feature)
.IsUnique());
#endregion
} }
#if DEBUG #if DEBUG

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class gamblingstats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GamblingStats",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Feature = table.Column<string>(type: "TEXT", nullable: true),
Bet = table.Column<decimal>(type: "TEXT", nullable: false),
PaidOut = table.Column<decimal>(type: "TEXT", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_GamblingStats", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_GamblingStats_Feature",
table: "GamblingStats",
column: "Feature",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GamblingStats");
}
}
}

View File

@@ -864,6 +864,32 @@ namespace NadekoBot.Migrations
b.ToTable("FilterWordsChannelId"); b.ToTable("FilterWordsChannelId");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<decimal>("Bet")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<string>("Feature")
.HasColumnType("TEXT");
b.Property<decimal>("PaidOut")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Feature")
.IsUnique();
b.ToTable("GamblingStats");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")

View File

@@ -29,6 +29,7 @@ public partial class Gambling : GamblingModule<GamblingService>
private readonly IBankService _bank; private readonly IBankService _bank;
private readonly IPatronageService _ps; private readonly IPatronageService _ps;
private readonly RemindService _remind; private readonly RemindService _remind;
private readonly GamblingTxTracker _gamblingTxTracker;
private IUserMessage rdMsg; private IUserMessage rdMsg;
@@ -41,7 +42,8 @@ public partial class Gambling : GamblingModule<GamblingService>
GamblingConfigService configService, GamblingConfigService configService,
IBankService bank, IBankService bank,
IPatronageService ps, IPatronageService ps,
RemindService remind) RemindService remind,
GamblingTxTracker gamblingTxTracker)
: base(configService) : base(configService)
{ {
_gs = gs; _gs = gs;
@@ -51,6 +53,7 @@ public partial class Gambling : GamblingModule<GamblingService>
_bank = bank; _bank = bank;
_ps = ps; _ps = ps;
_remind = remind; _remind = remind;
_gamblingTxTracker = gamblingTxTracker;
_enUsCulture = new CultureInfo("en-US", false).NumberFormat; _enUsCulture = new CultureInfo("en-US", false).NumberFormat;
_enUsCulture.NumberDecimalDigits = 0; _enUsCulture.NumberDecimalDigits = 0;
@@ -65,6 +68,26 @@ public partial class Gambling : GamblingModule<GamblingService>
return N(bal); return N(bal);
} }
[Cmd]
public async Task BetStats()
{
var stats = await _gamblingTxTracker.GetAllAsync();
var eb = _eb.Create(ctx)
.WithOkColor();
var str = "**Feature | Bet | Paid Out | Payout %** \n";
foreach (var stat in stats)
{
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
str += $"{stat.Feature, 8} | {stat.Bet, 15} | {stat.PaidOut, 15} | {perc}\n";
}
eb.WithDescription(str);
await ctx.Channel.EmbedAsync(eb);
}
[Cmd] [Cmd]
public async Task Economy() public async Task Economy()
{ {

View File

@@ -47,27 +47,6 @@ public partial class Gambling
public Task Test() public Task Test()
=> Task.CompletedTask; => Task.CompletedTask;
[Cmd]
[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", N(bet), true)
.AddField("Paid Out", N(paid), true)
.WithFooter($"Payout Rate: {paid * 1.0M / bet * 100:f4}%");
await ctx.Channel.EmbedAsync(embed);
}
[Cmd] [Cmd]
public async Task Slot(ShmartNumber amount) public async Task Slot(ShmartNumber amount)
{ {

View File

@@ -85,10 +85,6 @@
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
<!-- <PackageReference Include="System.Runtime.Experimental" Version="6.0.2" />-->
<!-- Used by .crypto command -->
<!-- Used by stream notifications --> <!-- Used by stream notifications -->
<PackageReference Include="TwitchLib.Api" Version="3.4.1" /> <PackageReference Include="TwitchLib.Api" Version="3.4.1" />

View File

@@ -4,12 +4,16 @@ using NadekoBot.Services.Currency;
namespace NadekoBot.Services; namespace NadekoBot.Services;
public class CurrencyService : ICurrencyService, INService public sealed class CurrencyService : ICurrencyService, INService
{ {
private readonly DbService _db; private readonly DbService _db;
private readonly ITxTracker _txTracker;
public CurrencyService(DbService db) public CurrencyService(DbService db, ITxTracker txTracker)
=> _db = db; {
_db = db;
_txTracker = txTracker;
}
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default) public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
{ {
@@ -70,16 +74,14 @@ public class CurrencyService : ICurrencyService, INService
{ {
var wallet = await GetWalletAsync(userId); var wallet = await GetWalletAsync(userId);
await wallet.Add(amount, txData); await wallet.Add(amount, txData);
await _txTracker.TrackAdd(amount, txData);
} }
public async Task AddAsync( public async Task AddAsync(
IUser user, IUser user,
long amount, long amount,
TxData txData) TxData txData)
{ => await AddAsync(user.Id, amount, txData);
var wallet = await GetWalletAsync(user.Id);
await wallet.Add(amount, txData);
}
public async Task<bool> RemoveAsync( public async Task<bool> RemoveAsync(
ulong userId, ulong userId,
@@ -87,15 +89,14 @@ public class CurrencyService : ICurrencyService, INService
TxData txData) TxData txData)
{ {
var wallet = await GetWalletAsync(userId); var wallet = await GetWalletAsync(userId);
return await wallet.Take(amount, txData); var result = await wallet.Take(amount, txData);
await _txTracker.TrackRemove(amount, txData);
return result;
} }
public async Task<bool> RemoveAsync( public async Task<bool> RemoveAsync(
IUser user, IUser user,
long amount, long amount,
TxData txData) TxData txData)
{ => await RemoveAsync(user.Id, amount, txData);
var wallet = await GetWalletAsync(user.Id);
return await wallet.Take(amount, txData);
}
} }

View File

@@ -0,0 +1,105 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Services.Currency;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services;
public sealed class GamblingTxTracker : ITxTracker, INService, IReadyExecutor
{
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
{
"lula",
"betroll",
"betflip",
"blackjack",
"betdraw",
"slot",
});
private ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> _stats = new();
private readonly DbService _db;
public GamblingTxTracker(DbService db)
{
_db = db;
}
public async Task OnReadyAsync()
{
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
while (await timer.WaitForNextTickAsync())
{
await using var ctx = _db.GetDbContext();
await using var trans = await ctx.Database.BeginTransactionAsync();
try
{
var keys = _stats.Keys;
foreach (var key in keys)
{
if (_stats.TryRemove(key, out var stat))
{
await ctx.GetTable<GamblingStats>()
.InsertOrUpdateAsync(() => new()
{
Feature = key,
Bet = stat.Bet,
PaidOut = stat.PaidOut,
DateAdded = DateTime.UtcNow
}, old => new()
{
Bet = old.Bet + stat.Bet,
PaidOut = old.PaidOut + stat.PaidOut,
}, () => new()
{
Feature = key
});
}
}
}
catch (Exception ex)
{
Log.Error(ex, "An error occurred in gambling tx tracker");
}
finally
{
await trans.CommitAsync();
}
}
}
public Task TrackAdd(long amount, TxData txData)
{
if (_gamblingTypes.Contains(txData.Type))
{
_stats.AddOrUpdate(txData.Type,
_ => (0, amount),
(_, old) => (old.Bet, old.PaidOut + amount));
}
return Task.CompletedTask;
}
public Task TrackRemove(long amount, TxData txData)
{
if (_gamblingTypes.Contains(txData.Type))
{
_stats.AddOrUpdate(txData.Type,
_ => (amount, 0),
(_, old) => (old.Bet + amount, old.PaidOut));
}
return Task.CompletedTask;
}
public async Task<IReadOnlyCollection<GamblingStats>> GetAllAsync()
{
await using var ctx = _db.GetDbContext();
return await ctx
.GetTable<GamblingStats>()
.ToListAsync();
}
}

View File

@@ -0,0 +1,9 @@
using NadekoBot.Services.Currency;
namespace NadekoBot.Services;
public interface ITxTracker
{
Task TrackAdd(long amount, TxData txData);
Task TrackRemove(long amount, TxData txData);
}

View File

@@ -852,8 +852,8 @@ antispamignore:
- antispamignore - antispamignore
eventstart: eventstart:
- eventstart - eventstart
slotstats: betstats:
- slotstats - betstats
bettest: bettest:
- bettest - bettest
slot: slot:

View File

@@ -1458,8 +1458,8 @@ eventstart:
args: args:
- "reaction" - "reaction"
- "reaction -d 1 -a 50 --pot-size 1500" - "reaction -d 1 -a 50 --pot-size 1500"
slotstats: betstats:
desc: "Shows the total stats of the slot command for this bot's session." desc: "Shows the total stats of several gambling features. Updates once an hour."
args: args:
- "" - ""
slottest: slottest: