mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-05-02 04: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<PocketWatchTransactionDbModel> PocketWatchTransactions { get; set; }
|
||||||
public DbSet<MomDbModel> Moms { get; set; }
|
public DbSet<MomDbModel> Moms { get; set; }
|
||||||
public DbSet<StreamDbModel> Streams { 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.Net;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using KfChatDotNetBot.Extensions;
|
||||||
using KfChatDotNetBot.Models;
|
using KfChatDotNetBot.Models;
|
||||||
using KfChatDotNetBot.Models.DbModels;
|
using KfChatDotNetBot.Models.DbModels;
|
||||||
using KfChatDotNetBot.Services;
|
using KfChatDotNetBot.Services;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using KfChatDotNetBot.Extensions;
|
||||||
using KfChatDotNetBot.Models;
|
using KfChatDotNetBot.Models;
|
||||||
using KfChatDotNetBot.Models.DbModels;
|
using KfChatDotNetBot.Models.DbModels;
|
||||||
using KfChatDotNetBot.Services;
|
using KfChatDotNetBot.Services;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace KfChatDotNetBot;
|
namespace KfChatDotNetBot.Extensions;
|
||||||
|
|
||||||
public static class 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");
|
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 =>
|
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.HowlggBetsDbModel", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -273,6 +370,45 @@ namespace KfChatDotNetBot.Migrations
|
|||||||
b.ToTable("Streams");
|
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 =>
|
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.TwitchViewCountDbModel", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -346,6 +482,79 @@ namespace KfChatDotNetBot.Migrations
|
|||||||
b.ToTable("UsersWhoWere");
|
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 =>
|
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||||
@@ -377,6 +586,23 @@ namespace KfChatDotNetBot.Migrations
|
|||||||
b.Navigation("User");
|
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 =>
|
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserWhoWasDbModel", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
|
||||||
@@ -387,6 +613,17 @@ namespace KfChatDotNetBot.Migrations
|
|||||||
|
|
||||||
b.Navigation("User");
|
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
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace KfChatDotNetBot.Models.DbModels;
|
namespace KfChatDotNetBot.Models.DbModels;
|
||||||
|
|
||||||
@@ -25,11 +26,22 @@ public class GamblerDbModel
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The seed value given to any instance of Random that's associated with the gambler
|
/// The seed value given to any instance of Random that's associated with the gambler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[MaxLength(256)]
|
||||||
public required string RandomSeed { get; set; }
|
public required string RandomSeed { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When the gambler entity was created
|
/// When the gambler entity was created
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required DateTimeOffset Created { get; set; }
|
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
|
public class TransactionDbModel
|
||||||
@@ -39,9 +51,9 @@ public class TransactionDbModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// User whose balance was affected by this transaction
|
/// Gambler whose balance was affected by this transaction
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required GamblerDbModel User { get; set; }
|
public required GamblerDbModel Gambler { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Source of the transaction event
|
/// Source of the transaction event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -51,6 +63,13 @@ public class TransactionDbModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public required DateTimeOffset Time { get; set; }
|
public required DateTimeOffset Time { get; set; }
|
||||||
/// <summary>
|
/// <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
|
/// Effect of the transaction, plus or minus
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required decimal Effect { get; set; }
|
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
|
/// Sender of the transaction in the case of a juicer, null otherwise
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GamblerDbModel? From { get; set; } = null;
|
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
|
public class WagerDbModel
|
||||||
@@ -71,19 +94,26 @@ public class WagerDbModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// User who wagered
|
/// Gambler who wagered
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required GamblerDbModel User { get; set; }
|
public required GamblerDbModel Gambler { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time they wagered
|
/// Time they wagered
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required DateTimeOffset Time { get; set; }
|
public required DateTimeOffset Time { get; set; }
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public required decimal WagerAmount { get; set; }
|
public required decimal WagerAmount { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Effect of the wager on the user's balance
|
/// Effect of the wager on the gambler's balance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required decimal WagerEffect { get; set; }
|
public required decimal WagerEffect { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -92,13 +122,108 @@ public class WagerDbModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public required WagerGame Game { get; set; }
|
public required WagerGame Game { get; set; }
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public required decimal Multiplier { get; set; }
|
public required decimal Multiplier { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An optional field to store serialized information about the game that was played
|
/// An optional field to store serialized information about the game that was played
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? GameMeta { get; set; } = null;
|
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>
|
/// <summary>
|
||||||
@@ -123,10 +248,20 @@ public enum TransactionSourceEventType
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Administrative,
|
Administrative,
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
Bonus,
|
Bonus,
|
||||||
/// <summary>
|
/// <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
|
/// Use this only for hostess juicers as the sum of these juicers in a given day can influence the hostess' behavior
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Hostess
|
Hostess
|
||||||
@@ -142,7 +277,11 @@ public enum WagerGame
|
|||||||
LambChop,
|
LambChop,
|
||||||
Keno,
|
Keno,
|
||||||
[Description("Coinflip")]
|
[Description("Coinflip")]
|
||||||
CoinFlip
|
CoinFlip,
|
||||||
|
/// <summary>
|
||||||
|
/// This is for betting pools based on some sort of event or outcome
|
||||||
|
/// </summary>
|
||||||
|
Event
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum GamblerState
|
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 System.Text.RegularExpressions;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using KfChatDotNetBot.Commands;
|
using KfChatDotNetBot.Commands;
|
||||||
|
using KfChatDotNetBot.Extensions;
|
||||||
using KfChatDotNetBot.Models.DbModels;
|
using KfChatDotNetBot.Models.DbModels;
|
||||||
|
using KfChatDotNetBot.Settings;
|
||||||
using KfChatDotNetWsClient.Models.Events;
|
using KfChatDotNetWsClient.Models.Events;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
@@ -17,9 +19,9 @@ internal class BotCommands
|
|||||||
private IEnumerable<ICommand> Commands;
|
private IEnumerable<ICommand> Commands;
|
||||||
private CancellationToken _cancellationToken;
|
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;
|
_bot = bot;
|
||||||
var interfaceType = typeof(ICommand);
|
var interfaceType = typeof(ICommand);
|
||||||
Commands =
|
Commands =
|
||||||
@@ -60,6 +62,33 @@ internal class BotCommands
|
|||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
if (user.Ignored) return;
|
if (user.Ignored) return;
|
||||||
var continueAfterProcess = HasAttribute<AllowAdditionalMatches>(command);
|
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)
|
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);
|
_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("Command task failed");
|
||||||
_logger.Error(task.Exception);
|
_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
|
private static bool HasAttribute<T>(ICommand command) where T : Attribute
|
||||||
@@ -106,3 +153,19 @@ internal class BotCommands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
internal class AllowAdditionalMatches : Attribute;
|
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 System.Text.Json;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using KfChatDotNetBot.Extensions;
|
||||||
using KfChatDotNetBot.Models;
|
using KfChatDotNetBot.Models;
|
||||||
using KfChatDotNetBot.Models.DbModels;
|
using KfChatDotNetBot.Models.DbModels;
|
||||||
using KfChatDotNetBot.Settings;
|
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",
|
Description = "Whether to respond to Bossman impersonations",
|
||||||
Default = "true",
|
Default = "true",
|
||||||
ValueType = SettingValueType.Boolean,
|
ValueType = SettingValueType.Boolean,
|
||||||
Regex = "(true|false)"
|
|
||||||
Regex = BooleanRegex
|
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 MoneySymbolSuffix = "Money.SymbolSuffix";
|
||||||
public static string MoneySymbolPrefix = "Money.SymbolPrefix";
|
public static string MoneySymbolPrefix = "Money.SymbolPrefix";
|
||||||
public static string MoneyEnabled = "Money.Enabled";
|
public static string MoneyEnabled = "Money.Enabled";
|
||||||
|
public static string MoneyInitialBalance = "Money.InitialBalance";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user