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:
barelyprofessional
2025-08-20 14:59:09 -05:00
parent 8d100b013b
commit 6ca1cf055c
15 changed files with 1831 additions and 14 deletions

View File

@@ -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

View File

@@ -0,0 +1,3 @@
namespace KfChatDotNetBot.Models;
// Stash all the models used for perk or game metadata here

View 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; }
}