mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-04-30 03:22:04 -04:00
Added the initial framework for the new Money system.
Includes - 5 new tables: Gamblers, Transactions, Wagers, Exclusions, Perks - Still heavily WIP and not ready to be enabled, no games present and a lot of missing functionality - For now it's completely disabled until it's ready to be used.
This commit is contained in:
@@ -23,4 +23,9 @@ public class ApplicationDbContext : DbContext
|
||||
// public DbSet<PocketWatchTransactionDbModel> PocketWatchTransactions { get; set; }
|
||||
public DbSet<MomDbModel> Moms { get; set; }
|
||||
public DbSet<StreamDbModel> Streams { get; set; }
|
||||
public DbSet<GamblerDbModel> Gamblers { get; set; }
|
||||
public DbSet<TransactionDbModel> Transactions { get; set; }
|
||||
public DbSet<WagerDbModel> Wagers { get; set; }
|
||||
public DbSet<GamblerExclusionDbModel> Exclusions { get; set; }
|
||||
public DbSet<GamblerPerkDbModel> Perks { get; set; }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using KfChatDotNetBot.Extensions;
|
||||
using KfChatDotNetBot.Models;
|
||||
using KfChatDotNetBot.Models.DbModels;
|
||||
using KfChatDotNetBot.Services;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using KfChatDotNetBot.Extensions;
|
||||
using KfChatDotNetBot.Models;
|
||||
using KfChatDotNetBot.Models.DbModels;
|
||||
using KfChatDotNetBot.Services;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace KfChatDotNetBot;
|
||||
namespace KfChatDotNetBot.Extensions;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
254
KfChatDotNetBot/Extensions/MoneyExtensions.cs
Normal file
254
KfChatDotNetBot/Extensions/MoneyExtensions.cs
Normal file
@@ -0,0 +1,254 @@
|
||||
using KfChatDotNetBot.Models;
|
||||
using KfChatDotNetBot.Models.DbModels;
|
||||
using KfChatDotNetBot.Services;
|
||||
using KfChatDotNetBot.Settings;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace KfChatDotNetBot.Extensions;
|
||||
|
||||
public static class MoneyExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve a gambler entity for a given user
|
||||
/// Returns null if createIfNoneExists is false and no gambler exists
|
||||
/// Also returns null if the user was permanently banned from gambling
|
||||
/// If there are multiple "active" gamblers, only the newest is returned
|
||||
/// </summary>
|
||||
/// <param name="user">User whose gambler entity you wish to retrieve</param>
|
||||
/// <param name="createIfNoneExists">Whether to create a gambler entity if none exists already</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<GamblerDbModel?> GetGamblerEntity(this UserDbModel user, bool createIfNoneExists = true, CancellationToken ct = default)
|
||||
{
|
||||
await using var db = new ApplicationDbContext();
|
||||
db.Attach(user);
|
||||
var gambler =
|
||||
await db.Gamblers.LastOrDefaultAsync(g => g.User == user && g.State != GamblerState.PermanentlyBanned,
|
||||
cancellationToken: ct);
|
||||
if (!createIfNoneExists) return gambler;
|
||||
var permaBanned = await db.Gamblers.AnyAsync(g => g.User == user && g.State == GamblerState.PermanentlyBanned, cancellationToken: ct);
|
||||
if (permaBanned) return null;
|
||||
var initialBalance = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.MoneyInitialBalance)).ToType<decimal>();
|
||||
await db.Gamblers.AddAsync(new GamblerDbModel
|
||||
{
|
||||
User = user,
|
||||
Balance = initialBalance,
|
||||
Created = DateTimeOffset.UtcNow,
|
||||
RandomSeed = Guid.NewGuid().ToString(),
|
||||
State = GamblerState.Active,
|
||||
TotalWagered = 0,
|
||||
NextVipLevelWagerRequirement = Money.VipLevels[0].BaseWagerRequirement
|
||||
}, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return await db.Gamblers.LastOrDefaultAsync(g => g.User == user, cancellationToken: ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple check to see whether a user has been permanently banned from the kasino
|
||||
/// </summary>
|
||||
/// <param name="user">User to check for the permaban</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<bool> IsPermanentlyBanned(this UserDbModel user, CancellationToken ct = default)
|
||||
{
|
||||
await using var db = new ApplicationDbContext();
|
||||
db.Attach(user);
|
||||
return await db.Gamblers.AnyAsync(u => u.User == user && u.State == GamblerState.PermanentlyBanned,
|
||||
cancellationToken: ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modify a gambler's balance by a given +/- amount
|
||||
/// </summary>
|
||||
/// <param name="gambler">Gambler entity whose balance you wish to modify</param>
|
||||
/// <param name="effect">The 'effect' of this modification, as in how much to add or remove</param>
|
||||
/// <param name="eventSource">The event which initiated this balance modification</param>
|
||||
/// <param name="comment">Optional comment to provide for the transaction</param>
|
||||
/// <param name="from">If applicable, who sent the transaction (e.g. if a juicer)</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
public static async Task ModifyBalance(this GamblerDbModel gambler, decimal effect,
|
||||
TransactionSourceEventType eventSource, string? comment = null, GamblerDbModel? from = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await using var db = new ApplicationDbContext();
|
||||
db.Attach(gambler);
|
||||
gambler.Balance += effect;
|
||||
await db.Transactions.AddAsync(new TransactionDbModel
|
||||
{
|
||||
Gambler = gambler,
|
||||
EventSource = eventSource,
|
||||
Effect = effect,
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
Comment = comment,
|
||||
From = from,
|
||||
NewBalance = gambler.Balance,
|
||||
TimeUnixEpochSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
}, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a wager to the database
|
||||
/// Will also issue a balance update unless you explicitly disable autoModifyBalance
|
||||
/// </summary>
|
||||
/// <param name="gambler">Gambler who wagered</param>
|
||||
/// <param name="wagerAmount">The amount they wagered</param>
|
||||
/// <param name="wagerEffect">The effect of the wager on the gambler's balance.
|
||||
/// Please note this includes the wager itself. So for a bet of 500 that paid out 50, you pass in an effect of -450
|
||||
/// If instead they won 600 then the effect would be +100. the wagered amount is not factored into balance changes,
|
||||
/// it's just recorded for calculating bonuses and statistics</param>
|
||||
/// <param name="game">The game which was played as part of this wager</param>
|
||||
/// <param name="autoModifyBalance">Whether tu automatically update the user's balance according to the wager effect.
|
||||
/// Typically you should leave this on so as to ensure every wager has an associated transaction.</param>
|
||||
/// <param name="gameMeta">Optionally store metadata related to the wager, such as player choices, or game outcomes.
|
||||
/// Data will be serialized to JSON.</param>
|
||||
/// <param name="isComplete">Whether the game is 'complete'. Set to false for wagers with unknown outcomes.
|
||||
/// NOTE: wagerEffect will be ignored, instead value will be derived from the wagerAmount</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
public static async Task NewWager(this GamblerDbModel gambler, decimal wagerAmount, decimal wagerEffect,
|
||||
WagerGame game, bool autoModifyBalance = true, dynamic? gameMeta = null, bool isComplete = true,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var logger = LogManager.GetCurrentClassLogger();
|
||||
await using var db = new ApplicationDbContext();
|
||||
db.Attach(gambler);
|
||||
string? metaJson = null;
|
||||
if (gameMeta != null)
|
||||
{
|
||||
metaJson = JsonConvert.SerializeObject(gameMeta, Formatting.Indented);
|
||||
logger.Debug("Serialized metadata follows");
|
||||
logger.Debug(metaJson);
|
||||
}
|
||||
|
||||
if (!isComplete)
|
||||
{
|
||||
wagerEffect = -wagerAmount;
|
||||
logger.Debug($"isComplete is false, set wagerEffect to {wagerEffect}");
|
||||
}
|
||||
decimal multi = 0;
|
||||
if (isComplete && wagerAmount > 0 && wagerEffect > 0 && wagerAmount + wagerEffect > 0)
|
||||
{
|
||||
multi = (wagerAmount + wagerEffect) / wagerAmount;
|
||||
logger.Debug($"multi is {multi}");
|
||||
}
|
||||
|
||||
gambler.TotalWagered += wagerAmount;
|
||||
var wager = await db.Wagers.AddAsync(new WagerDbModel
|
||||
{
|
||||
Gambler = gambler,
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
WagerAmount = wagerAmount,
|
||||
WagerEffect = wagerEffect,
|
||||
Multiplier = multi,
|
||||
Game = game,
|
||||
GameMeta = metaJson,
|
||||
IsComplete = isComplete,
|
||||
TimeUnixEpochSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
}, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
gambler.Balance += wagerEffect;
|
||||
if (!autoModifyBalance) return;
|
||||
|
||||
await db.Transactions.AddAsync(new TransactionDbModel
|
||||
{
|
||||
Gambler = gambler,
|
||||
EventSource = TransactionSourceEventType.Gambling,
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
Effect = wagerEffect,
|
||||
Comment = $"Win from wager {wager.Entity.Id}",
|
||||
From = null,
|
||||
NewBalance = gambler.Balance,
|
||||
TimeUnixEpochSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
}, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an active exclusion, returns null if there's no active exclusion
|
||||
/// If there's somehow multiple exclusions, will just grab the most recent one
|
||||
/// </summary>
|
||||
/// <param name="gambler">Gambler entity to retrieve the exclusion for</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<GamblerExclusionDbModel?> GetActiveExclusion(this GamblerDbModel gambler, CancellationToken ct = default)
|
||||
{
|
||||
await using var db = new ApplicationDbContext();
|
||||
db.Attach(gambler);
|
||||
return (await db.Exclusions.Where(g => g.Gambler == gambler).ToListAsync(ct))
|
||||
.LastOrDefault(e => e.Expires <= DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get random number using the gambler's seed and a given number of iterations
|
||||
/// </summary>
|
||||
/// <param name="gambler">Gambler entity to reference their random seed</param>
|
||||
/// <param name="min">Minimum value for generating random</param>
|
||||
/// <param name="max">Maximum value for random, incremented by 1 if add1ToMaxParam is true
|
||||
/// so it's consistent with the behavior of min</param>
|
||||
/// <param name="iterations">Number of random number generator iterations to run before returning a result</param>
|
||||
/// <param name="incrementMaxParam">Increments the 'max' param by 1 as otherwise the value will never be returned by Random.Next()
|
||||
/// This is because the default behavior of .NET is unintuitive, min value can be returned but max is never by default</param>
|
||||
/// <returns>A random number based on the given parameters</returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public static int GetRandomNumber(this GamblerDbModel gambler, int min, int max, int iterations = 10,
|
||||
bool incrementMaxParam = true)
|
||||
{
|
||||
var random = new Random(gambler.RandomSeed.GetHashCode());
|
||||
var result = 0;
|
||||
var i = 0;
|
||||
if (incrementMaxParam) max++;
|
||||
if (iterations <= 0) throw new ArgumentException("Iterations cannot be 0 or lower");
|
||||
while (i < iterations)
|
||||
{
|
||||
i++;
|
||||
result = random.Next(min, max);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user's current VIP level
|
||||
/// </summary>
|
||||
/// <param name="gambler">Gambler entity whose VIP level you want to get</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<GamblerPerkDbModel?> GetVipLevel(this GamblerDbModel gambler, CancellationToken ct = default)
|
||||
{
|
||||
await using var db = new ApplicationDbContext();
|
||||
db.Attach(gambler);
|
||||
var perk = await db.Perks.LastOrDefaultAsync(
|
||||
p => p.Gambler == gambler && p.PerkType == GamblerPerkType.VipLevel, ct);
|
||||
return perk;
|
||||
}
|
||||
|
||||
public static async Task<decimal> UpgradeVipLevel(this GamblerDbModel gambler, NextVipLevelModel nextVipLevel,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await using var db = new ApplicationDbContext();
|
||||
db.Attach(gambler);
|
||||
var payout = nextVipLevel.VipLevel.BonusPayout;
|
||||
if (nextVipLevel.Tier > 1)
|
||||
{
|
||||
payout = nextVipLevel.VipLevel.BonusPayout / (nextVipLevel.VipLevel.Tiers - 1);
|
||||
}
|
||||
|
||||
await db.Perks.AddAsync(new GamblerPerkDbModel
|
||||
{
|
||||
Gambler = gambler,
|
||||
PerkType = GamblerPerkType.VipLevel,
|
||||
PerkName = nextVipLevel.VipLevel.Name,
|
||||
PerkTier = nextVipLevel.Tier,
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
Payout = payout,
|
||||
}, ct);
|
||||
gambler.NextVipLevelWagerRequirement = nextVipLevel.WagerRequirement;
|
||||
await db.SaveChangesAsync(ct);
|
||||
await gambler.ModifyBalance(payout, TransactionSourceEventType.Bonus,
|
||||
$"VIP Level '{nextVipLevel.VipLevel.Icon} {nextVipLevel.VipLevel.Name}' Tier {nextVipLevel.Tier} level up bonus", ct: ct);
|
||||
return payout;
|
||||
}
|
||||
}
|
||||
633
KfChatDotNetBot/Migrations/20250820195308_Money.Designer.cs
generated
Normal file
633
KfChatDotNetBot/Migrations/20250820195308_Money.Designer.cs
generated
Normal file
@@ -0,0 +1,633 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using KfChatDotNetBot;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace KfChatDotNetBot.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250820195308_Money")]
|
||||
partial class Money
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.4");
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.ChipsggBetDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Amount")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("BetId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("CurrencyPrice")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("GameTitle")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("Multiplier")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<DateTimeOffset>("Updated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Win")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Winnings")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ChipsggBets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("NextVipLevelWagerRequirement")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RandomSeed")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("State")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("TotalWagered")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Gamblers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerExclusionDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Expires")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GamblerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Source")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GamblerId");
|
||||
|
||||
b.ToTable("Exclusions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerPerkDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GamblerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Metadata")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal?>("Payout")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PerkName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PerkTier")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PerkType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GamblerId");
|
||||
|
||||
b.ToTable("Perks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.HowlggBetsDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Bet")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BetId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Game")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("GameId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Profit")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("HowlggBets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.ImageDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("LastSeen")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Images");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("Amount")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<DateTimeOffset>("JuicedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Juicers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.MomDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Moms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.RainbetBetsDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BetId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("BetSeenAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("GameName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("Multiplier")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<float>("Payout")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("PublicId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("RainbetUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("Value")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RainbetBets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.SettingDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("CacheDuration")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("Default")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSecret")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Regex")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ValueType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.StreamDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AutoCapture")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Metadata")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Service")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("StreamUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Streams");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TransactionDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("Effect")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EventSource")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("FromId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GamblerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("NewBalance")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("TimeUnixEpochSeconds")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FromId");
|
||||
|
||||
b.HasIndex("GamblerId");
|
||||
|
||||
b.ToTable("Transactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TwitchViewCountDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("ServerTime")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Topic")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Viewers")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TwitchViewCounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Ignored")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("KfId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("KfUsername")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserRight")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserWhoWasDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ActivityType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("FirstOccurence")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("LatestOccurence")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UsersWhoWere");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.WagerDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GamblerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Game")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("GameMeta")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsComplete")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("Multiplier")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("TimeUnixEpochSeconds")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("WagerAmount")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("WagerEffect")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GamblerId");
|
||||
|
||||
b.ToTable("Wagers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerExclusionDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler")
|
||||
.WithMany()
|
||||
.HasForeignKey("GamblerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Gambler");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerPerkDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler")
|
||||
.WithMany()
|
||||
.HasForeignKey("GamblerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Gambler");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.MomDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.StreamDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TransactionDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "From")
|
||||
.WithMany()
|
||||
.HasForeignKey("FromId");
|
||||
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler")
|
||||
.WithMany()
|
||||
.HasForeignKey("GamblerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("From");
|
||||
|
||||
b.Navigation("Gambler");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserWhoWasDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.WagerDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler")
|
||||
.WithMany()
|
||||
.HasForeignKey("GamblerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Gambler");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
194
KfChatDotNetBot/Migrations/20250820195308_Money.cs
Normal file
194
KfChatDotNetBot/Migrations/20250820195308_Money.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace KfChatDotNetBot.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Money : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Gamblers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Balance = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
State = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
RandomSeed = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false),
|
||||
Created = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||
TotalWagered = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
NextVipLevelWagerRequirement = table.Column<decimal>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Gamblers", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Gamblers_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Exclusions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GamblerId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Expires = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||
Created = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||
Source = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Exclusions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Exclusions_Gamblers_GamblerId",
|
||||
column: x => x.GamblerId,
|
||||
principalTable: "Gamblers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Perks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GamblerId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
PerkName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false),
|
||||
Time = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||
Metadata = table.Column<string>(type: "TEXT", nullable: true),
|
||||
PerkType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
PerkTier = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
Payout = table.Column<decimal>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Perks", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Perks_Gamblers_GamblerId",
|
||||
column: x => x.GamblerId,
|
||||
principalTable: "Gamblers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Transactions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GamblerId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
EventSource = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Time = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||
TimeUnixEpochSeconds = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
Effect = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
Comment = table.Column<string>(type: "TEXT", nullable: true),
|
||||
FromId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
NewBalance = table.Column<decimal>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Transactions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Transactions_Gamblers_FromId",
|
||||
column: x => x.FromId,
|
||||
principalTable: "Gamblers",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Transactions_Gamblers_GamblerId",
|
||||
column: x => x.GamblerId,
|
||||
principalTable: "Gamblers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Wagers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GamblerId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Time = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||
TimeUnixEpochSeconds = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
WagerAmount = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
WagerEffect = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
Game = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Multiplier = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
GameMeta = table.Column<string>(type: "TEXT", nullable: true),
|
||||
IsComplete = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Wagers", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Wagers_Gamblers_GamblerId",
|
||||
column: x => x.GamblerId,
|
||||
principalTable: "Gamblers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Exclusions_GamblerId",
|
||||
table: "Exclusions",
|
||||
column: "GamblerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Gamblers_UserId",
|
||||
table: "Gamblers",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Perks_GamblerId",
|
||||
table: "Perks",
|
||||
column: "GamblerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Transactions_FromId",
|
||||
table: "Transactions",
|
||||
column: "FromId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Transactions_GamblerId",
|
||||
table: "Transactions",
|
||||
column: "GamblerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Wagers_GamblerId",
|
||||
table: "Wagers",
|
||||
column: "GamblerId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Exclusions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Perks");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Transactions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Wagers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Gamblers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,103 @@ namespace KfChatDotNetBot.Migrations
|
||||
b.ToTable("ChipsggBets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("NextVipLevelWagerRequirement")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RandomSeed")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("State")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("TotalWagered")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Gamblers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerExclusionDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Expires")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GamblerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Source")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GamblerId");
|
||||
|
||||
b.ToTable("Exclusions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerPerkDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GamblerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Metadata")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal?>("Payout")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PerkName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PerkTier")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PerkType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GamblerId");
|
||||
|
||||
b.ToTable("Perks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.HowlggBetsDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -273,6 +370,45 @@ namespace KfChatDotNetBot.Migrations
|
||||
b.ToTable("Streams");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TransactionDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("Effect")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EventSource")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("FromId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GamblerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("NewBalance")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("TimeUnixEpochSeconds")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FromId");
|
||||
|
||||
b.HasIndex("GamblerId");
|
||||
|
||||
b.ToTable("Transactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TwitchViewCountDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -346,6 +482,79 @@ namespace KfChatDotNetBot.Migrations
|
||||
b.ToTable("UsersWhoWere");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.WagerDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GamblerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Game")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("GameMeta")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsComplete")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("Multiplier")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("TimeUnixEpochSeconds")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("WagerAmount")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("WagerEffect")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GamblerId");
|
||||
|
||||
b.ToTable("Wagers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerExclusionDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler")
|
||||
.WithMany()
|
||||
.HasForeignKey("GamblerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Gambler");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.GamblerPerkDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler")
|
||||
.WithMany()
|
||||
.HasForeignKey("GamblerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Gambler");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||
@@ -377,6 +586,23 @@ namespace KfChatDotNetBot.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TransactionDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "From")
|
||||
.WithMany()
|
||||
.HasForeignKey("FromId");
|
||||
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler")
|
||||
.WithMany()
|
||||
.HasForeignKey("GamblerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("From");
|
||||
|
||||
b.Navigation("Gambler");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserWhoWasDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||
@@ -387,6 +613,17 @@ namespace KfChatDotNetBot.Migrations
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.WagerDbModel", b =>
|
||||
{
|
||||
b.HasOne("KfChatDotNetBot.Models.DbModels.GamblerDbModel", "Gambler")
|
||||
.WithMany()
|
||||
.HasForeignKey("GamblerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Gambler");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace KfChatDotNetBot.Models.DbModels;
|
||||
|
||||
@@ -25,11 +26,22 @@ public class GamblerDbModel
|
||||
/// <summary>
|
||||
/// The seed value given to any instance of Random that's associated with the gambler
|
||||
/// </summary>
|
||||
[MaxLength(256)]
|
||||
public required string RandomSeed { get; set; }
|
||||
/// <summary>
|
||||
/// When the gambler entity was created
|
||||
/// </summary>
|
||||
public required DateTimeOffset Created { get; set; }
|
||||
/// <summary>
|
||||
/// Reference value for total wagered during the entity's lifetime
|
||||
/// This value is recalculated whenever the bot restarts to ensure integrity
|
||||
/// </summary>
|
||||
public required decimal TotalWagered { get; set; }
|
||||
/// <summary>
|
||||
/// Wager requirement for the next VIP level
|
||||
/// If TotalWagered reaches this value, it'll trigger the calculation
|
||||
/// </summary>
|
||||
public required decimal NextVipLevelWagerRequirement { get; set; }
|
||||
}
|
||||
|
||||
public class TransactionDbModel
|
||||
@@ -39,9 +51,9 @@ public class TransactionDbModel
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// User whose balance was affected by this transaction
|
||||
/// Gambler whose balance was affected by this transaction
|
||||
/// </summary>
|
||||
public required GamblerDbModel User { get; set; }
|
||||
public required GamblerDbModel Gambler { get; set; }
|
||||
/// <summary>
|
||||
/// Source of the transaction event
|
||||
/// </summary>
|
||||
@@ -51,6 +63,13 @@ public class TransactionDbModel
|
||||
/// </summary>
|
||||
public required DateTimeOffset Time { get; set; }
|
||||
/// <summary>
|
||||
/// Time represented as a 64-bit UNIX epoch
|
||||
/// This just exists to make it far more efficient to query a range of txns
|
||||
/// as then we can use native SQLite dialect to select e.g. last 24 hours
|
||||
/// instead of copying thousands of rows into memory and using LINQ
|
||||
/// </summary>
|
||||
public required long TimeUnixEpochSeconds { get; set; }
|
||||
/// <summary>
|
||||
/// Effect of the transaction, plus or minus
|
||||
/// </summary>
|
||||
public required decimal Effect { get; set; }
|
||||
@@ -62,6 +81,10 @@ public class TransactionDbModel
|
||||
/// Sender of the transaction in the case of a juicer, null otherwise
|
||||
/// </summary>
|
||||
public GamblerDbModel? From { get; set; } = null;
|
||||
/// <summary>
|
||||
/// Snapshot of the gambler's balance after this transaction's effect was applied
|
||||
/// </summary>
|
||||
public required decimal NewBalance { get; set; }
|
||||
}
|
||||
|
||||
public class WagerDbModel
|
||||
@@ -71,19 +94,26 @@ public class WagerDbModel
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// User who wagered
|
||||
/// Gambler who wagered
|
||||
/// </summary>
|
||||
public required GamblerDbModel User { get; set; }
|
||||
public required GamblerDbModel Gambler { get; set; }
|
||||
/// <summary>
|
||||
/// Time they wagered
|
||||
/// </summary>
|
||||
public required DateTimeOffset Time { get; set; }
|
||||
/// <summary>
|
||||
/// Amount the user wagered
|
||||
/// Time represented as a 64-bit UNIX epoch
|
||||
/// This just exists to make it far more efficient to query a range of wagers
|
||||
/// as then we can use native SQLite dialect to select e.g. last 24 hours
|
||||
/// instead of copying thousands of rows into memory and using LINQ
|
||||
/// </summary>
|
||||
public required long TimeUnixEpochSeconds { get; set; }
|
||||
/// <summary>
|
||||
/// Amount the gambler wagered
|
||||
/// </summary>
|
||||
public required decimal WagerAmount { get; set; }
|
||||
/// <summary>
|
||||
/// Effect of the wager on the user's balance
|
||||
/// Effect of the wager on the gambler's balance
|
||||
/// </summary>
|
||||
public required decimal WagerEffect { get; set; }
|
||||
/// <summary>
|
||||
@@ -92,13 +122,108 @@ public class WagerDbModel
|
||||
/// </summary>
|
||||
public required WagerGame Game { get; set; }
|
||||
/// <summary>
|
||||
/// Multiplier if applicable. 0 if it was a complete loss
|
||||
/// Multiplier, e.g. 10.5x if a $1 wager paid out $10.50. 0 if it was a complete loss
|
||||
/// </summary>
|
||||
public required decimal Multiplier { get; set; }
|
||||
/// <summary>
|
||||
/// An optional field to store serialized information about the game that was played
|
||||
/// </summary>
|
||||
public string? GameMeta { get; set; } = null;
|
||||
/// <summary>
|
||||
/// Whether the results of the wager have been realized yet (i.e., is the game 'complete'?)
|
||||
/// This is useful for wagers related to bets on the outcome of events
|
||||
/// For incomplete bets: set the effect to -wager, subtract it from the user's balance, generate a txn for the wager
|
||||
/// Then when the outcome of the bet is fully realized, modify the effect accordingly, generate a new txn for the
|
||||
/// payout and set a multiplier based on the win (if any)
|
||||
/// </summary>
|
||||
public required bool IsComplete { get; set; }
|
||||
}
|
||||
|
||||
public class GamblerExclusionDbModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID fo the database row
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Gambler who is excluded
|
||||
/// </summary>
|
||||
public required GamblerDbModel Gambler { get; set; }
|
||||
/// <summary>
|
||||
/// When the exclusion expires
|
||||
/// </summary>
|
||||
public required DateTimeOffset Expires { get; set; }
|
||||
/// <summary>
|
||||
/// When the exclusion was created / began
|
||||
/// </summary>
|
||||
public required DateTimeOffset Created { get; set; }
|
||||
/// <summary>
|
||||
/// What triggered the exclusion
|
||||
/// </summary>
|
||||
public required ExclusionSource Source { get; set; }
|
||||
}
|
||||
|
||||
public class GamblerPerkDbModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID fo the database row
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Gambler entity the perk is associated with
|
||||
/// </summary>
|
||||
public required GamblerDbModel Gambler { get; set; }
|
||||
/// <summary>
|
||||
/// Name of the perk
|
||||
/// </summary>
|
||||
[MaxLength(256)]
|
||||
public required string PerkName { get; set; }
|
||||
/// <summary>
|
||||
/// Time when the perk was attained
|
||||
/// </summary>
|
||||
public required DateTimeOffset Time { get; set; }
|
||||
/// <summary>
|
||||
/// Optional metadata associated with the perk
|
||||
/// </summary>
|
||||
public string? Metadata { get; set; } = null;
|
||||
/// <summary>
|
||||
/// What type of perk is this
|
||||
/// </summary>
|
||||
public required GamblerPerkType PerkType { get; set; }
|
||||
/// <summary>
|
||||
/// The tier the perk is at.
|
||||
/// If tiers are not applicable, set to null
|
||||
/// </summary>
|
||||
public int? PerkTier { get; set; }
|
||||
/// <summary>
|
||||
/// The payout from this perk, if any. If none, set to null
|
||||
/// </summary>
|
||||
public decimal? Payout { get; set; }
|
||||
}
|
||||
|
||||
public enum GamblerPerkType
|
||||
{
|
||||
/// <summary>
|
||||
/// For literally anything else, though you should probably just extend this enum
|
||||
/// </summary>
|
||||
Other = -1,
|
||||
/// <summary>
|
||||
/// Used for tracking VIP levels attained
|
||||
/// </summary>
|
||||
[Description("VIP Level")]
|
||||
VipLevel
|
||||
}
|
||||
|
||||
public enum ExclusionSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Exclusion as a result of the hostess' action
|
||||
/// </summary>
|
||||
Hostess,
|
||||
/// <summary>
|
||||
/// Exclusions placed by administrators
|
||||
/// </summary>
|
||||
Administrative
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,10 +248,20 @@ public enum TransactionSourceEventType
|
||||
/// </summary>
|
||||
Administrative,
|
||||
/// <summary>
|
||||
/// Some type of bonus, like rakeback or a reload. Do not use for hostess rewards
|
||||
/// Some type of bonus, like a VIP level up. Rakeback / reloads have separate enums for this
|
||||
/// </summary>
|
||||
Bonus,
|
||||
/// <summary>
|
||||
/// Specifically use for rakeback as we use the delta between last rakeback txn to calculate total wagered
|
||||
/// to figure out what the next rakeback should be (if they've wagered enough to be eligible for one)
|
||||
/// </summary>
|
||||
Rakeback,
|
||||
/// <summary>
|
||||
/// Use specifically for daily reloads as we use the timing of the last reload txn to figure out if the most
|
||||
/// recent reload has been claimed yet or not
|
||||
/// </summary>
|
||||
Reload,
|
||||
/// <summary>
|
||||
/// Use this only for hostess juicers as the sum of these juicers in a given day can influence the hostess' behavior
|
||||
/// </summary>
|
||||
Hostess
|
||||
@@ -142,7 +277,11 @@ public enum WagerGame
|
||||
LambChop,
|
||||
Keno,
|
||||
[Description("Coinflip")]
|
||||
CoinFlip
|
||||
CoinFlip,
|
||||
/// <summary>
|
||||
/// This is for betting pools based on some sort of event or outcome
|
||||
/// </summary>
|
||||
Event
|
||||
}
|
||||
|
||||
public enum GamblerState
|
||||
|
||||
3
KfChatDotNetBot/Models/MoneyMetaModels.cs
Normal file
3
KfChatDotNetBot/Models/MoneyMetaModels.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace KfChatDotNetBot.Models;
|
||||
|
||||
// Stash all the models used for perk or game metadata here
|
||||
57
KfChatDotNetBot/Models/MoneyModels.cs
Normal file
57
KfChatDotNetBot/Models/MoneyModels.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using KfChatDotNetBot.Models.DbModels;
|
||||
|
||||
namespace KfChatDotNetBot.Models;
|
||||
|
||||
public class MoneyVipLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the VIP level
|
||||
/// </summary>
|
||||
public required string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Number of tiers the VIP level has
|
||||
/// Steps between VIP tiers are calculated by comparing with the next VIP level and dividing by number of tiers
|
||||
/// e.g. (100,000 - 10,000) / 5 = 18,000 steps
|
||||
/// Tier 1 = 10,000
|
||||
/// Tier 2 = 28,000
|
||||
/// Tier 3 = 46,000
|
||||
/// Tier 4 = 64,000
|
||||
/// Tier 5 = 84,000
|
||||
/// Next VIP level at 100,000
|
||||
/// What happens if they're at the last VIP level? They remain stuck at tier 1 forever regardless of this value
|
||||
/// This is really just so that we have flexibility to add further tiers later without messing anything up
|
||||
/// since there's no telling how easy it will be to attain the high levels at this point
|
||||
/// </summary>
|
||||
public required int Tiers { get; set; }
|
||||
/// <summary>
|
||||
/// Icon to display next to the name, like an emoji diamond or a small image embedded with bbcode [img] tags
|
||||
/// </summary>
|
||||
public required string Icon { get; set; }
|
||||
/// <summary>
|
||||
/// The wager requirement for this level. This is the requirement for the base (tier 1) level
|
||||
/// Remaining tiers are calculated based on the wager requirement for the next tier
|
||||
/// </summary>
|
||||
public required decimal BaseWagerRequirement { get; set; }
|
||||
/// <summary>
|
||||
/// Payout when you attain this level.
|
||||
/// Tiers (beyond 1) pay out: BonusPayout / (Tiers - 1) (e.g. 1,000 / 4 = 250 for tier 2-5
|
||||
/// </summary>
|
||||
public required decimal BonusPayout { get; set; }
|
||||
}
|
||||
|
||||
public class NextVipLevelModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The VIP level that's coming up next.
|
||||
/// Could be the same as the existing level if it's just the next tier.
|
||||
/// </summary>
|
||||
public required MoneyVipLevel VipLevel { get; set; }
|
||||
/// <summary>
|
||||
/// What tier this is for
|
||||
/// </summary>
|
||||
public required int Tier { get; set; }
|
||||
/// <summary>
|
||||
/// The wager requirement to reach this tier that factors in the tier
|
||||
/// </summary>
|
||||
public required decimal WagerRequirement { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Humanizer;
|
||||
using KfChatDotNetBot.Commands;
|
||||
using KfChatDotNetBot.Extensions;
|
||||
using KfChatDotNetBot.Models.DbModels;
|
||||
using KfChatDotNetBot.Settings;
|
||||
using KfChatDotNetWsClient.Models.Events;
|
||||
using NLog;
|
||||
|
||||
@@ -17,9 +19,9 @@ internal class BotCommands
|
||||
private IEnumerable<ICommand> Commands;
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
internal BotCommands(ChatBot bot, CancellationToken? ctx = null)
|
||||
internal BotCommands(ChatBot bot, CancellationToken ctx = default)
|
||||
{
|
||||
_cancellationToken = ctx ?? CancellationToken.None;
|
||||
_cancellationToken = ctx;
|
||||
_bot = bot;
|
||||
var interfaceType = typeof(ICommand);
|
||||
Commands =
|
||||
@@ -60,6 +62,33 @@ internal class BotCommands
|
||||
if (user == null) return;
|
||||
if (user.Ignored) return;
|
||||
var continueAfterProcess = HasAttribute<AllowAdditionalMatches>(command);
|
||||
var kasinoCommand = HasAttribute<KasinoCommand>(command);
|
||||
var wagerCommand = HasAttribute<WagerCommand>(command);
|
||||
if (kasinoCommand)
|
||||
{
|
||||
var kasinoEnabled = SettingsProvider.GetValueAsync(BuiltIn.Keys.MoneyEnabled).Result.ToBoolean();
|
||||
if (!kasinoEnabled) return;
|
||||
}
|
||||
|
||||
if (kasinoCommand && user.IsPermanentlyBanned(_cancellationToken).Result)
|
||||
{
|
||||
_bot.SendChatMessage($"@{message.Author.Username}, you've been permanently banned from the kasino. Contact support for more information.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wagerCommand)
|
||||
{
|
||||
// GetGamblerEntity will only return null if the user is permanbanned
|
||||
// and we have a check further up the chain for that hence ignoring the null
|
||||
var exclusion = user.GetGamblerEntity(ct: _cancellationToken).Result
|
||||
!.GetActiveExclusion(ct: _cancellationToken).Result;
|
||||
if (exclusion != null)
|
||||
{
|
||||
_bot.SendChatMessage(
|
||||
$"@{message.Author.Username}, you're self excluded from the kasino for another {(exclusion.Expires - DateTimeOffset.UtcNow).Humanize(precision: 3)}", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (user.UserRight < command.RequiredRight)
|
||||
{
|
||||
_bot.SendChatMessage($"@{message.Author.Username}, you do not have access to use this command. Your rank: {user.UserRight.Humanize()}; Required rank: {command.RequiredRight.Humanize()}", true);
|
||||
@@ -89,7 +118,25 @@ internal class BotCommands
|
||||
{
|
||||
_logger.Error("Command task failed");
|
||||
_logger.Error(task.Exception);
|
||||
return;
|
||||
}
|
||||
|
||||
var moneySettings =
|
||||
await SettingsProvider.GetMultipleValuesAsync([BuiltIn.Keys.MoneyEnabled, BuiltIn.Keys.MoneySymbolSuffix]);
|
||||
if (!moneySettings[BuiltIn.Keys.MoneyEnabled].ToBoolean()) return;
|
||||
var wagerCommand = HasAttribute<WagerCommand>(command);
|
||||
if (!wagerCommand) return;
|
||||
var gambler = await user.GetGamblerEntity(ct: _cancellationToken);
|
||||
if (gambler == null) return;
|
||||
if (gambler.TotalWagered < gambler.NextVipLevelWagerRequirement) return;
|
||||
// The reason for doing this instead of passing in TotalWagered is that otherwise VIP levels might
|
||||
// get skipped if the user is a low VIP level but wagering very large amounts
|
||||
var newLevel = Money.GetNextVipLevel(gambler.NextVipLevelWagerRequirement);
|
||||
if (newLevel == null) return;
|
||||
var payout = await gambler.UpgradeVipLevel(newLevel, _cancellationToken);
|
||||
await _bot.SendChatMessageAsync(
|
||||
$"🤑🤑 {user.KfUsername} has leveled up to to {newLevel.VipLevel.Icon} {newLevel.VipLevel.Name} Tier {newLevel.Tier} " +
|
||||
$"and received a bonus of {payout:N2} {moneySettings[BuiltIn.Keys.MoneySymbolSuffix].Value}", true);
|
||||
}
|
||||
|
||||
private static bool HasAttribute<T>(ICommand command) where T : Attribute
|
||||
@@ -106,3 +153,19 @@ internal class BotCommands
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class AllowAdditionalMatches : Attribute;
|
||||
|
||||
/// <summary>
|
||||
/// Use this on commands where a wager is taking place.
|
||||
/// This will cause the bot to check total wagered and see if the gambler has leveled up.
|
||||
/// It'll also check whether the gambler is currently temp excluded before running the command.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class WagerCommand : Attribute;
|
||||
|
||||
/// <summary>
|
||||
/// Use this on all commands that interact with the gambling / monetary system
|
||||
/// When used, this will check if the system is globally enabled before running the command.
|
||||
/// It'll also check whether the user is permanently banned before running the command.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class KasinoCommand : Attribute;
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using Humanizer;
|
||||
using KfChatDotNetBot.Extensions;
|
||||
using KfChatDotNetBot.Models;
|
||||
using KfChatDotNetBot.Models.DbModels;
|
||||
using KfChatDotNetBot.Settings;
|
||||
|
||||
199
KfChatDotNetBot/Services/Money.cs
Normal file
199
KfChatDotNetBot/Services/Money.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using KfChatDotNetBot.Models;
|
||||
|
||||
namespace KfChatDotNetBot.Services;
|
||||
|
||||
public static class Money
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the list of available VIP levels for gamblers to ascend
|
||||
/// The order of this array is important, it begins with the loest level VIP, and ascends IN ORDER to the max level
|
||||
/// </summary>
|
||||
public static List<MoneyVipLevel> VipLevels =
|
||||
[
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Rip City",
|
||||
Tiers = 5,
|
||||
Icon = ":ross:",
|
||||
BaseWagerRequirement = 10_000,
|
||||
BonusPayout = 250
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Chinesium",
|
||||
Tiers = 5,
|
||||
Icon = ":gold:",
|
||||
BaseWagerRequirement = 100_000,
|
||||
BonusPayout = 500
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Juice Fiend",
|
||||
Tiers = 5,
|
||||
Icon = ":juice:",
|
||||
BaseWagerRequirement = 1_000_000,
|
||||
BonusPayout = 1000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Unemployment Line",
|
||||
Tiers = 5,
|
||||
Icon = ":tugboat:",
|
||||
BaseWagerRequirement = 5_000_000,
|
||||
BonusPayout = 2500
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Down Immensely",
|
||||
Tiers = 5,
|
||||
Icon = ":felted:",
|
||||
BaseWagerRequirement = 10_000_000,
|
||||
BonusPayout = 5000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Glutton for Punishment",
|
||||
Tiers = 5,
|
||||
Icon = ":ow:",
|
||||
BaseWagerRequirement = 25_000_000,
|
||||
BonusPayout = 7500
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Targeted by Evil Eddie",
|
||||
Tiers = 5,
|
||||
Icon = ":bogged:",
|
||||
BaseWagerRequirement = 50_000_000,
|
||||
BonusPayout = 10_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Epic High Roller",
|
||||
Tiers = 5,
|
||||
Icon = ":wow:",
|
||||
BaseWagerRequirement = 100_000_000,
|
||||
BonusPayout = 15_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "99% of Gamblers",
|
||||
Tiers = 5,
|
||||
Icon = ":wall:",
|
||||
BaseWagerRequirement = 500_000_000,
|
||||
BonusPayout = 25_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Billionaire Club",
|
||||
Tiers = 5,
|
||||
Icon = ":drink:",
|
||||
BaseWagerRequirement = 1_000_000_000,
|
||||
BonusPayout = 50_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Upfag",
|
||||
Tiers = 5,
|
||||
Icon = ":gay:",
|
||||
BaseWagerRequirement = 5_000_000_000,
|
||||
BonusPayout = 75_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "No Regrets",
|
||||
Tiers = 5,
|
||||
Icon = ":woo:",
|
||||
BaseWagerRequirement = 50_000_000_000,
|
||||
BonusPayout = 100_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "A Small Juicer of a Million Dollars",
|
||||
Tiers = 5,
|
||||
Icon = ":trump:",
|
||||
BaseWagerRequirement = 250_000_000_000,
|
||||
BonusPayout = 1_000_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Wannabe Bossman",
|
||||
Tiers = 5,
|
||||
Icon = ":lossmanjack:",
|
||||
BaseWagerRequirement = 500_000_000_000,
|
||||
BonusPayout = 2_000_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "TRILLION DOLLAR COIN FLIP",
|
||||
Tiers = 5,
|
||||
Icon = ":winner:",
|
||||
BaseWagerRequirement = 1_000_000_000_000,
|
||||
BonusPayout = 4_000_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Madness",
|
||||
Tiers = 5,
|
||||
Icon = ":lunacy:",
|
||||
BaseWagerRequirement = 5_000_000_000_000,
|
||||
BonusPayout = 10_000_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Nowhere to go from here",
|
||||
Tiers = 5,
|
||||
Icon = ":achievement:",
|
||||
BaseWagerRequirement = 50_000_000_000_000,
|
||||
// Fuck you pussy
|
||||
BonusPayout = 1
|
||||
}
|
||||
];
|
||||
|
||||
public static List<decimal> CalculateTiers(MoneyVipLevel vipLevel)
|
||||
{
|
||||
// The list is in ascending order
|
||||
var nextLevel = VipLevels.FirstOrDefault(v => v.BaseWagerRequirement > vipLevel.BaseWagerRequirement);
|
||||
// Max level has no tiers
|
||||
if (nextLevel == null) return [vipLevel.BaseWagerRequirement];
|
||||
var wagerRequirement = vipLevel.BaseWagerRequirement;
|
||||
var step = (nextLevel.BaseWagerRequirement - vipLevel.BaseWagerRequirement) / vipLevel.Tiers;
|
||||
var tiers = new List<decimal>();
|
||||
while (wagerRequirement < nextLevel.BaseWagerRequirement)
|
||||
{
|
||||
tiers.Add(wagerRequirement);
|
||||
wagerRequirement += step;
|
||||
}
|
||||
return tiers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the next VIP level based on the wager amount given
|
||||
/// </summary>
|
||||
/// <param name="wagered">Wager amount to calculate the next level</param>
|
||||
/// <returns>null if the user is at the max level</returns>
|
||||
public static NextVipLevelModel? GetNextVipLevel(decimal wagered)
|
||||
{
|
||||
var level = VipLevels.LastOrDefault(v => v.BaseWagerRequirement < wagered);
|
||||
if (level == null) return null;
|
||||
var tiers = CalculateTiers(level);
|
||||
var nextTier = tiers.FirstOrDefault(t => wagered < t);
|
||||
// default(decimal) is 0
|
||||
// This happens if the user is between tier 5 and their next level
|
||||
if (nextTier == 0)
|
||||
{
|
||||
var nextLevel = VipLevels[VipLevels.IndexOf(level) + 1];
|
||||
return new NextVipLevelModel
|
||||
{
|
||||
VipLevel = nextLevel,
|
||||
Tier = 1,
|
||||
WagerRequirement = nextLevel.BaseWagerRequirement
|
||||
};
|
||||
}
|
||||
return new NextVipLevelModel
|
||||
{
|
||||
VipLevel = level,
|
||||
Tier = tiers.IndexOf(nextTier) + 1,
|
||||
WagerRequirement = nextTier
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -936,9 +936,38 @@ public static class BuiltIn
|
||||
Description = "Whether to respond to Bossman impersonations",
|
||||
Default = "true",
|
||||
ValueType = SettingValueType.Boolean,
|
||||
Regex = "(true|false)"
|
||||
Regex = BooleanRegex
|
||||
},
|
||||
new BuiltInSettingsModel
|
||||
{
|
||||
Key = Keys.MoneySymbolSuffix,
|
||||
Description = "What is the symbol of the bot's currency when used as a suffix for an amount",
|
||||
Default = "KKK",
|
||||
ValueType = SettingValueType.Text
|
||||
},
|
||||
new BuiltInSettingsModel
|
||||
{
|
||||
Key = Keys.MoneySymbolPrefix,
|
||||
Description = "What is the symbol of the bot's currency when used as a prefix for an amount",
|
||||
Default = "KKK$",
|
||||
ValueType = SettingValueType.Text
|
||||
},
|
||||
new BuiltInSettingsModel
|
||||
{
|
||||
Key = Keys.MoneyEnabled,
|
||||
Description = "Whether the monetary system is enabled at all. " +
|
||||
"If disabled, the bot won't answer any commands related to balance, transactions, gambling, etc.",
|
||||
Default = "false",
|
||||
ValueType = SettingValueType.Boolean,
|
||||
Regex = BooleanRegex
|
||||
},
|
||||
new BuiltInSettingsModel
|
||||
{
|
||||
Key = Keys.MoneyInitialBalance,
|
||||
Description = "Gambler's initial balance on creation",
|
||||
Default = "100",
|
||||
ValueType = SettingValueType.Text,
|
||||
Regex = WholeNumberRegex
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1048,5 +1077,6 @@ public static class BuiltIn
|
||||
public static string MoneySymbolSuffix = "Money.SymbolSuffix";
|
||||
public static string MoneySymbolPrefix = "Money.SymbolPrefix";
|
||||
public static string MoneyEnabled = "Money.Enabled";
|
||||
public static string MoneyInitialBalance = "Money.InitialBalance";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user