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());
#endregion
#region GamblingStats
modelBuilder.Entity<GamblingStats>(gs => gs
.HasIndex(x => x.Feature)
.IsUnique());
#endregion
}
#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");
});
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 =>
{
b.Property<int>("Id")

View File

@@ -29,6 +29,7 @@ public partial class Gambling : GamblingModule<GamblingService>
private readonly IBankService _bank;
private readonly IPatronageService _ps;
private readonly RemindService _remind;
private readonly GamblingTxTracker _gamblingTxTracker;
private IUserMessage rdMsg;
@@ -41,7 +42,8 @@ public partial class Gambling : GamblingModule<GamblingService>
GamblingConfigService configService,
IBankService bank,
IPatronageService ps,
RemindService remind)
RemindService remind,
GamblingTxTracker gamblingTxTracker)
: base(configService)
{
_gs = gs;
@@ -51,6 +53,7 @@ public partial class Gambling : GamblingModule<GamblingService>
_bank = bank;
_ps = ps;
_remind = remind;
_gamblingTxTracker = gamblingTxTracker;
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
_enUsCulture.NumberDecimalDigits = 0;
@@ -65,6 +68,26 @@ public partial class Gambling : GamblingModule<GamblingService>
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]
public async Task Economy()
{

View File

@@ -47,27 +47,6 @@ public partial class Gambling
public Task Test()
=> 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]
public async Task Slot(ShmartNumber amount)
{

View File

@@ -85,10 +85,6 @@
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
<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 -->
<PackageReference Include="TwitchLib.Api" Version="3.4.1" />

View File

@@ -4,12 +4,16 @@ using NadekoBot.Services.Currency;
namespace NadekoBot.Services;
public class CurrencyService : ICurrencyService, INService
public sealed class CurrencyService : ICurrencyService, INService
{
private readonly DbService _db;
private readonly ITxTracker _txTracker;
public CurrencyService(DbService db)
=> _db = db;
public CurrencyService(DbService db, ITxTracker txTracker)
{
_db = db;
_txTracker = txTracker;
}
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
{
@@ -32,7 +36,7 @@ public class CurrencyService : ICurrencyService, INService
var wallet = await GetWalletAsync(userId);
await wallet.Add(amount, txData);
}
return;
}
@@ -49,13 +53,13 @@ public class CurrencyService : ICurrencyService, INService
{
await using var ctx = _db.GetDbContext();
await ctx.DiscordUser
.Where(x => userIds.Contains(x.UserId))
.UpdateAsync(du => new()
{
CurrencyAmount = du.CurrencyAmount >= amount
? du.CurrencyAmount - amount
: 0
});
.Where(x => userIds.Contains(x.UserId))
.UpdateAsync(du => new()
{
CurrencyAmount = du.CurrencyAmount >= amount
? du.CurrencyAmount - amount
: 0
});
await ctx.SaveChangesAsync();
return;
}
@@ -70,16 +74,14 @@ public class CurrencyService : ICurrencyService, INService
{
var wallet = await GetWalletAsync(userId);
await wallet.Add(amount, txData);
await _txTracker.TrackAdd(amount, txData);
}
public async Task AddAsync(
IUser user,
long amount,
TxData txData)
{
var wallet = await GetWalletAsync(user.Id);
await wallet.Add(amount, txData);
}
=> await AddAsync(user.Id, amount, txData);
public async Task<bool> RemoveAsync(
ulong userId,
@@ -87,15 +89,14 @@ public class CurrencyService : ICurrencyService, INService
TxData txData)
{
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(
IUser user,
long amount,
TxData txData)
{
var wallet = await GetWalletAsync(user.Id);
return await wallet.Take(amount, txData);
}
=> await RemoveAsync(user.Id, 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
eventstart:
- eventstart
slotstats:
- slotstats
betstats:
- betstats
bettest:
- bettest
slot:

View File

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