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:
@@ -1,7 +1,9 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Humanizer;
|
||||
using KfChatDotNetBot.Commands;
|
||||
using KfChatDotNetBot.Extensions;
|
||||
using KfChatDotNetBot.Models.DbModels;
|
||||
using KfChatDotNetBot.Settings;
|
||||
using KfChatDotNetWsClient.Models.Events;
|
||||
using NLog;
|
||||
|
||||
@@ -17,9 +19,9 @@ internal class BotCommands
|
||||
private IEnumerable<ICommand> Commands;
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
internal BotCommands(ChatBot bot, CancellationToken? ctx = null)
|
||||
internal BotCommands(ChatBot bot, CancellationToken ctx = default)
|
||||
{
|
||||
_cancellationToken = ctx ?? CancellationToken.None;
|
||||
_cancellationToken = ctx;
|
||||
_bot = bot;
|
||||
var interfaceType = typeof(ICommand);
|
||||
Commands =
|
||||
@@ -60,6 +62,33 @@ internal class BotCommands
|
||||
if (user == null) return;
|
||||
if (user.Ignored) return;
|
||||
var continueAfterProcess = HasAttribute<AllowAdditionalMatches>(command);
|
||||
var kasinoCommand = HasAttribute<KasinoCommand>(command);
|
||||
var wagerCommand = HasAttribute<WagerCommand>(command);
|
||||
if (kasinoCommand)
|
||||
{
|
||||
var kasinoEnabled = SettingsProvider.GetValueAsync(BuiltIn.Keys.MoneyEnabled).Result.ToBoolean();
|
||||
if (!kasinoEnabled) return;
|
||||
}
|
||||
|
||||
if (kasinoCommand && user.IsPermanentlyBanned(_cancellationToken).Result)
|
||||
{
|
||||
_bot.SendChatMessage($"@{message.Author.Username}, you've been permanently banned from the kasino. Contact support for more information.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wagerCommand)
|
||||
{
|
||||
// GetGamblerEntity will only return null if the user is permanbanned
|
||||
// and we have a check further up the chain for that hence ignoring the null
|
||||
var exclusion = user.GetGamblerEntity(ct: _cancellationToken).Result
|
||||
!.GetActiveExclusion(ct: _cancellationToken).Result;
|
||||
if (exclusion != null)
|
||||
{
|
||||
_bot.SendChatMessage(
|
||||
$"@{message.Author.Username}, you're self excluded from the kasino for another {(exclusion.Expires - DateTimeOffset.UtcNow).Humanize(precision: 3)}", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (user.UserRight < command.RequiredRight)
|
||||
{
|
||||
_bot.SendChatMessage($"@{message.Author.Username}, you do not have access to use this command. Your rank: {user.UserRight.Humanize()}; Required rank: {command.RequiredRight.Humanize()}", true);
|
||||
@@ -89,7 +118,25 @@ internal class BotCommands
|
||||
{
|
||||
_logger.Error("Command task failed");
|
||||
_logger.Error(task.Exception);
|
||||
return;
|
||||
}
|
||||
|
||||
var moneySettings =
|
||||
await SettingsProvider.GetMultipleValuesAsync([BuiltIn.Keys.MoneyEnabled, BuiltIn.Keys.MoneySymbolSuffix]);
|
||||
if (!moneySettings[BuiltIn.Keys.MoneyEnabled].ToBoolean()) return;
|
||||
var wagerCommand = HasAttribute<WagerCommand>(command);
|
||||
if (!wagerCommand) return;
|
||||
var gambler = await user.GetGamblerEntity(ct: _cancellationToken);
|
||||
if (gambler == null) return;
|
||||
if (gambler.TotalWagered < gambler.NextVipLevelWagerRequirement) return;
|
||||
// The reason for doing this instead of passing in TotalWagered is that otherwise VIP levels might
|
||||
// get skipped if the user is a low VIP level but wagering very large amounts
|
||||
var newLevel = Money.GetNextVipLevel(gambler.NextVipLevelWagerRequirement);
|
||||
if (newLevel == null) return;
|
||||
var payout = await gambler.UpgradeVipLevel(newLevel, _cancellationToken);
|
||||
await _bot.SendChatMessageAsync(
|
||||
$"🤑🤑 {user.KfUsername} has leveled up to to {newLevel.VipLevel.Icon} {newLevel.VipLevel.Name} Tier {newLevel.Tier} " +
|
||||
$"and received a bonus of {payout:N2} {moneySettings[BuiltIn.Keys.MoneySymbolSuffix].Value}", true);
|
||||
}
|
||||
|
||||
private static bool HasAttribute<T>(ICommand command) where T : Attribute
|
||||
@@ -105,4 +152,20 @@ internal class BotCommands
|
||||
/// Keep in mind since commands are executed in a throwaway task and not awaited, they will run concurrently
|
||||
/// </summary>
|
||||
[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 Humanizer;
|
||||
using KfChatDotNetBot.Extensions;
|
||||
using KfChatDotNetBot.Models;
|
||||
using KfChatDotNetBot.Models.DbModels;
|
||||
using KfChatDotNetBot.Settings;
|
||||
|
||||
199
KfChatDotNetBot/Services/Money.cs
Normal file
199
KfChatDotNetBot/Services/Money.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using KfChatDotNetBot.Models;
|
||||
|
||||
namespace KfChatDotNetBot.Services;
|
||||
|
||||
public static class Money
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the list of available VIP levels for gamblers to ascend
|
||||
/// The order of this array is important, it begins with the loest level VIP, and ascends IN ORDER to the max level
|
||||
/// </summary>
|
||||
public static List<MoneyVipLevel> VipLevels =
|
||||
[
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Rip City",
|
||||
Tiers = 5,
|
||||
Icon = ":ross:",
|
||||
BaseWagerRequirement = 10_000,
|
||||
BonusPayout = 250
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Chinesium",
|
||||
Tiers = 5,
|
||||
Icon = ":gold:",
|
||||
BaseWagerRequirement = 100_000,
|
||||
BonusPayout = 500
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Juice Fiend",
|
||||
Tiers = 5,
|
||||
Icon = ":juice:",
|
||||
BaseWagerRequirement = 1_000_000,
|
||||
BonusPayout = 1000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Unemployment Line",
|
||||
Tiers = 5,
|
||||
Icon = ":tugboat:",
|
||||
BaseWagerRequirement = 5_000_000,
|
||||
BonusPayout = 2500
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Down Immensely",
|
||||
Tiers = 5,
|
||||
Icon = ":felted:",
|
||||
BaseWagerRequirement = 10_000_000,
|
||||
BonusPayout = 5000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Glutton for Punishment",
|
||||
Tiers = 5,
|
||||
Icon = ":ow:",
|
||||
BaseWagerRequirement = 25_000_000,
|
||||
BonusPayout = 7500
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Targeted by Evil Eddie",
|
||||
Tiers = 5,
|
||||
Icon = ":bogged:",
|
||||
BaseWagerRequirement = 50_000_000,
|
||||
BonusPayout = 10_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Epic High Roller",
|
||||
Tiers = 5,
|
||||
Icon = ":wow:",
|
||||
BaseWagerRequirement = 100_000_000,
|
||||
BonusPayout = 15_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "99% of Gamblers",
|
||||
Tiers = 5,
|
||||
Icon = ":wall:",
|
||||
BaseWagerRequirement = 500_000_000,
|
||||
BonusPayout = 25_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Billionaire Club",
|
||||
Tiers = 5,
|
||||
Icon = ":drink:",
|
||||
BaseWagerRequirement = 1_000_000_000,
|
||||
BonusPayout = 50_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Upfag",
|
||||
Tiers = 5,
|
||||
Icon = ":gay:",
|
||||
BaseWagerRequirement = 5_000_000_000,
|
||||
BonusPayout = 75_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "No Regrets",
|
||||
Tiers = 5,
|
||||
Icon = ":woo:",
|
||||
BaseWagerRequirement = 50_000_000_000,
|
||||
BonusPayout = 100_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "A Small Juicer of a Million Dollars",
|
||||
Tiers = 5,
|
||||
Icon = ":trump:",
|
||||
BaseWagerRequirement = 250_000_000_000,
|
||||
BonusPayout = 1_000_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Wannabe Bossman",
|
||||
Tiers = 5,
|
||||
Icon = ":lossmanjack:",
|
||||
BaseWagerRequirement = 500_000_000_000,
|
||||
BonusPayout = 2_000_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "TRILLION DOLLAR COIN FLIP",
|
||||
Tiers = 5,
|
||||
Icon = ":winner:",
|
||||
BaseWagerRequirement = 1_000_000_000_000,
|
||||
BonusPayout = 4_000_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Madness",
|
||||
Tiers = 5,
|
||||
Icon = ":lunacy:",
|
||||
BaseWagerRequirement = 5_000_000_000_000,
|
||||
BonusPayout = 10_000_000
|
||||
},
|
||||
new MoneyVipLevel
|
||||
{
|
||||
Name = "Nowhere to go from here",
|
||||
Tiers = 5,
|
||||
Icon = ":achievement:",
|
||||
BaseWagerRequirement = 50_000_000_000_000,
|
||||
// Fuck you pussy
|
||||
BonusPayout = 1
|
||||
}
|
||||
];
|
||||
|
||||
public static List<decimal> CalculateTiers(MoneyVipLevel vipLevel)
|
||||
{
|
||||
// The list is in ascending order
|
||||
var nextLevel = VipLevels.FirstOrDefault(v => v.BaseWagerRequirement > vipLevel.BaseWagerRequirement);
|
||||
// Max level has no tiers
|
||||
if (nextLevel == null) return [vipLevel.BaseWagerRequirement];
|
||||
var wagerRequirement = vipLevel.BaseWagerRequirement;
|
||||
var step = (nextLevel.BaseWagerRequirement - vipLevel.BaseWagerRequirement) / vipLevel.Tiers;
|
||||
var tiers = new List<decimal>();
|
||||
while (wagerRequirement < nextLevel.BaseWagerRequirement)
|
||||
{
|
||||
tiers.Add(wagerRequirement);
|
||||
wagerRequirement += step;
|
||||
}
|
||||
return tiers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the next VIP level based on the wager amount given
|
||||
/// </summary>
|
||||
/// <param name="wagered">Wager amount to calculate the next level</param>
|
||||
/// <returns>null if the user is at the max level</returns>
|
||||
public static NextVipLevelModel? GetNextVipLevel(decimal wagered)
|
||||
{
|
||||
var level = VipLevels.LastOrDefault(v => v.BaseWagerRequirement < wagered);
|
||||
if (level == null) return null;
|
||||
var tiers = CalculateTiers(level);
|
||||
var nextTier = tiers.FirstOrDefault(t => wagered < t);
|
||||
// default(decimal) is 0
|
||||
// This happens if the user is between tier 5 and their next level
|
||||
if (nextTier == 0)
|
||||
{
|
||||
var nextLevel = VipLevels[VipLevels.IndexOf(level) + 1];
|
||||
return new NextVipLevelModel
|
||||
{
|
||||
VipLevel = nextLevel,
|
||||
Tier = 1,
|
||||
WagerRequirement = nextLevel.BaseWagerRequirement
|
||||
};
|
||||
}
|
||||
return new NextVipLevelModel
|
||||
{
|
||||
VipLevel = level,
|
||||
Tier = tiers.IndexOf(nextTier) + 1,
|
||||
WagerRequirement = nextTier
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user