mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-06-15 16:55:18 -04:00
Cecil and shop update (#121)
* Implement FromToken method for Skew configuration Added a method to parse a serialized configuration token for Skew profiles. Tokens can be generated and viewed using the updated cecil helper tool. * Add Difficulty property to KasinoShop profile Add Difficulty property to KasinoShop profile * Add HOUSE_EDGE variable and custom difficulties Add HOUSE_EDGE variable and custom difficulties * Implement ShopSetDifficultyCommand for difficulty settings Added ShopSetDifficultyCommand to manage player difficulty settings in casino games, including validation for input parameters. * adds return to default difficulty setting adds return to default difficulty setting
This commit is contained in:
@@ -28,6 +28,8 @@ public class CecilCommand : ICommand
|
||||
};
|
||||
public bool WhisperCanInvoke => true;
|
||||
|
||||
public decimal HOUSE_EDGE = 0.98m;
|
||||
|
||||
public async Task RunCommand(ChatBot botInstance, BotCommandMessageModel message, UserDbModel user,
|
||||
GroupCollection arguments,
|
||||
CancellationToken ctx)
|
||||
@@ -71,17 +73,34 @@ public class CecilCommand : ICommand
|
||||
return;
|
||||
}
|
||||
|
||||
var difficulty = 1.0;
|
||||
|
||||
bool shopActive = botInstance.BotServices.KasinoShop != null;
|
||||
if (shopActive)
|
||||
{
|
||||
await GlobalShopFunctions.CheckProfile(botInstance, user, gambler);
|
||||
HOUSE_EDGE += botInstance.BotServices.KasinoShop!.Gambler_Profiles[user.KfId].HouseEdgeModifier;
|
||||
}
|
||||
|
||||
var difficulty = 1.0;
|
||||
bool customDiff = false;
|
||||
double result;
|
||||
if (arguments.TryGetValue("difficulty", out var diff))
|
||||
{
|
||||
difficulty = Convert.ToDouble(diff.Value);
|
||||
customDiff = true;
|
||||
}
|
||||
|
||||
if (!arguments.TryGetValue("maxwin", out var maxWin))
|
||||
if (!customDiff && shopActive &&
|
||||
botInstance.BotServices.KasinoShop!.Gambler_Profiles[user.KfId].Difficulty != "")
|
||||
{
|
||||
var skew = Skew.FromToken(botInstance.BotServices.KasinoShop!.Gambler_Profiles[user.KfId].Difficulty);
|
||||
skew.Rig((double)HOUSE_EDGE, 0);
|
||||
result = Cecil.Consult(skew);
|
||||
}
|
||||
else if (!arguments.TryGetValue("maxwin", out var maxWin))
|
||||
{
|
||||
var skew = new GammaSkew(difficulty, 0);
|
||||
skew.Rig((double)HOUSE_EDGE, 0);
|
||||
result = Cecil.Consult(skew, 0);
|
||||
}
|
||||
else
|
||||
@@ -93,8 +112,10 @@ public class CecilCommand : ICommand
|
||||
return;
|
||||
}
|
||||
var skew = new BetaSkew(difficulty, mWin, 0);
|
||||
skew.Rig((double)HOUSE_EDGE, 0);
|
||||
result = Cecil.Consult(skew);
|
||||
}
|
||||
|
||||
|
||||
var payout = wager * Convert.ToDecimal(result);
|
||||
var net = payout - wager;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using KfChatDotNetBot.Extensions;
|
||||
using KfChatDotNetBot.Models;
|
||||
@@ -107,6 +108,174 @@ public class ShopHelpCommand : ICommand
|
||||
}
|
||||
}
|
||||
|
||||
public class ShopSetDifficultyCommand : ICommand
|
||||
{
|
||||
public List<Regex> Patterns =>
|
||||
[
|
||||
new Regex(@"^shop difficulty (?<betstr>.*)$", RegexOptions.IgnoreCase),
|
||||
new Regex(@"^shop difficulty", RegexOptions.IgnoreCase)
|
||||
];
|
||||
|
||||
public string? HelpText => "Set your difficulty for cecil based games using the cecil tool";
|
||||
|
||||
public UserRight RequiredRight => UserRight.Loser;
|
||||
|
||||
public TimeSpan Timeout => TimeSpan.FromSeconds(30);
|
||||
|
||||
public RateLimitOptionsModel? RateLimitOptions => new RateLimitOptionsModel
|
||||
{
|
||||
MaxInvocations = 1,
|
||||
Window = TimeSpan.FromSeconds(120)
|
||||
};
|
||||
public bool WhisperCanInvoke => true;
|
||||
|
||||
public async Task RunCommand(ChatBot botInstance, BotCommandMessageModel message, UserDbModel user,
|
||||
GroupCollection arguments, CancellationToken ctx)
|
||||
{
|
||||
var cleanupDelay = TimeSpan.FromSeconds(10);
|
||||
var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx);
|
||||
if (gambler == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Caught a null when retrieving gambler for {user.KfUsername}");
|
||||
}
|
||||
|
||||
bool shopActive = botInstance.BotServices.KasinoShop != null;
|
||||
if (!shopActive)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync("KasinoShop is not currently running.", true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
await GlobalShopFunctions.CheckProfile(botInstance, user, gambler);
|
||||
|
||||
|
||||
|
||||
|
||||
if (!arguments.TryGetValue("betstr", out var betstr))
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, use the cecil tool to set your difficulty. https://i.ddos.lgbt/raw/CecilHelper.html",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
//validate difficulty string
|
||||
|
||||
string diff = betstr.Value;
|
||||
if (diff.ToUpper() == "DEFAULT")
|
||||
{
|
||||
botInstance.BotServices.KasinoShop!.Gambler_Profiles[user.KfId].Difficulty = "";
|
||||
return;
|
||||
}
|
||||
var parts = diff.Trim().Split(':');
|
||||
string typeId = parts[0].ToUpper(CultureInfo.InvariantCulture);
|
||||
|
||||
if (typeId != "B" && typeId != "G")
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, distribution type {typeId} is not valid, type must be B for beta or G for gamma.", true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
if (typeId == "B")
|
||||
{
|
||||
if (parts.Length != 5)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, Beta profile format error. Expected 5 parameters, but found {parts.Length}.",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out double weight) || weight <= 0)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, volatility weight value '{parts[1]}' is not valid, value must be a number greater than 0.",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(parts[2], NumberStyles.Any, CultureInfo.InvariantCulture, out double maxWin) || maxWin <= 1.0)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, Max Win value '{parts[2]}' is not valid, value must be a number greater than 1.0x.",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(parts[3], NumberStyles.Any, CultureInfo.InvariantCulture, out double lossRate) || lossRate < 0 || lossRate >= 1.0)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, loss rate percentage '{parts[3]}' is not valid, value must be between 0.0 and 1.0.",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(parts[4], NumberStyles.Any, CultureInfo.InvariantCulture, out double targetEv) || targetEv <= 0)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, target EV calculation setup value '{parts[4]}' is not valid, value must be a number greater than 0.",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetEv > 1.0)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, house protection violation, target EV value {targetEv} cannot exceed 1.0 (100% RTP).",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// --- Gamma Validation: G:[weight]:[lossRate]:[targetEv] ---
|
||||
else if (typeId == "G")
|
||||
{
|
||||
if (parts.Length != 4)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, Gamma profile format error. Expected 4 parameters, but found {parts.Length}.",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out double weight) || weight <= 0)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, inverse risk weight value '{parts[1]}' is not valid, value must be a number greater than 0.",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(parts[2], NumberStyles.Any, CultureInfo.InvariantCulture, out double lossRate) || lossRate < 0 || lossRate >= 1.0)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, loss rate percentage '{parts[2]}' is not valid, value must be between 0.0 and 1.0.",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(parts[3], NumberStyles.Any, CultureInfo.InvariantCulture, out double targetEv) || targetEv <= 0)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, target EV calculation setup value '{parts[3]}' is not valid, value must be a number greater than 0.",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetEv > 1.0)
|
||||
{
|
||||
await botInstance.SendChatMessageAsync(
|
||||
$"{user.FormatUsername()}, house protection violation, target EV value {targetEv} cannot exceed 1.0 (100% RTP).",
|
||||
true, autoDeleteAfter: cleanupDelay);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
botInstance.BotServices.KasinoShop.Gambler_Profiles[user.KfId].Difficulty = diff;
|
||||
await botInstance.BotServices.KasinoShop.SaveProfiles();
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class ShopListCommand : ICommand
|
||||
{
|
||||
public List<Regex> Patterns =>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using MathNet.Numerics;
|
||||
using MathNet.Numerics.Distributions;
|
||||
using RandN;
|
||||
@@ -23,7 +24,7 @@ public static class Cecil
|
||||
switch (skew)
|
||||
{
|
||||
case BetaSkew betaSkew:
|
||||
baseResult = Beta.InvCDF(betaSkew.Weight, betaSkew.Beta, scaledR) * betaSkew.CalibratedMaxWin;
|
||||
baseResult = Beta.InvCDF(betaSkew.Alpha, betaSkew.Beta, scaledR) * betaSkew.CalibratedMaxWin;
|
||||
break;
|
||||
case GammaSkew gammaSkew:
|
||||
baseResult = Gamma.InvCDF(gammaSkew.Weight, gammaSkew.Weight, scaledR);
|
||||
@@ -63,6 +64,52 @@ public abstract class Skew
|
||||
TargetEv = desiredEv;
|
||||
Calibrate(1-lr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a serialized configuration token string from the HTML configuration utility
|
||||
/// and instantiates a fully calibrated Skew state profile.
|
||||
/// </summary>
|
||||
/// <param name="token">Example formats: "B:0.5:50:0:0.95" or "G:1.2:0.05:1.0"</param>
|
||||
public static Skew FromToken(string token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
throw new ArgumentException("Engine initialization token cannot be empty.", nameof(token));
|
||||
|
||||
string[] parts = token.Split(':');
|
||||
string typeId = parts[0].ToUpper(CultureInfo.InvariantCulture);
|
||||
|
||||
switch (typeId)
|
||||
{
|
||||
case "B":
|
||||
if (parts.Length != 5)
|
||||
throw new FormatException("BetaSkew token profile requires exactly 5 dynamic segments.");
|
||||
|
||||
double bVol = double.Parse(parts[1], CultureInfo.InvariantCulture);
|
||||
double bMw = double.Parse(parts[2], CultureInfo.InvariantCulture);
|
||||
double bLr = double.Parse(parts[3], CultureInfo.InvariantCulture);
|
||||
double bEv = double.Parse(parts[4], CultureInfo.InvariantCulture);
|
||||
|
||||
// Instantiates structural object components, automatically applying calibrated settings.
|
||||
BetaSkew beta = new BetaSkew(bVol, bMw, bLr);
|
||||
beta.Rig(bEv, bLr);
|
||||
return beta;
|
||||
|
||||
case "G":
|
||||
if (parts.Length != 4)
|
||||
throw new FormatException("GammaSkew token profile requires exactly 4 dynamic segments.");
|
||||
|
||||
double gWght = double.Parse(parts[1], CultureInfo.InvariantCulture);
|
||||
double gLr = double.Parse(parts[2], CultureInfo.InvariantCulture);
|
||||
double gEv = double.Parse(parts[3], CultureInfo.InvariantCulture);
|
||||
|
||||
GammaSkew gamma = new GammaSkew(gWght, gLr);
|
||||
gamma.Rig(gEv, gLr);
|
||||
return gamma;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unrecognized skew mathematical distribution identifier classification: '{typeId}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BetaSkew : Skew
|
||||
|
||||
@@ -1050,6 +1050,7 @@ public class KasinoShop
|
||||
public string name;
|
||||
private decimal CryptoBalance;
|
||||
public decimal OutstandingLoanBalance;
|
||||
public string Difficulty = "";
|
||||
public Dictionary<int, Asset> Assets;
|
||||
public Dictionary<int, Loan> Loans = new();
|
||||
public decimal[] SponsorWagerLock = new decimal[2]; //[0] is how much you've wagered against your wager requirement, [1] is the wager requirement
|
||||
|
||||
Reference in New Issue
Block a user