- Added waifu price decay functionality

- Waifu prices changed to long, supporting much higher prices than before
- Fixed .yml comment indentation in some cases
- Updated changelog
This commit is contained in:
Kwoth
2022-02-02 11:25:22 +01:00
parent 41653a317b
commit 15ee3dd638
12 changed files with 287 additions and 139 deletions

View File

@@ -207,7 +207,6 @@ public partial class Gambling : GamblingModule<GamblingService>
=> InternalCurrencyTransactions(usr.Id, page);
// todo curtrs max lifetime
// todo waifu decay
private async Task InternalCurrencyTransactions(ulong userId, int page)
{
if (--page < 0)

View File

@@ -182,10 +182,14 @@ public partial class WheelOfFortuneSettings
public sealed partial class WaifuConfig
{
[Comment(@"Minimum price a waifu can have")]
public int MinPrice { get; set; } = 50;
public long MinPrice { get; set; } = 50;
public MultipliersData Multipliers { get; set; } = new();
[Comment(@"Settings for periodic waifu price decay.
Waifu price decays only if the waifu has no claimer.")]
public WaifuDecayConfig Decay { get; set; } = new();
[Comment(@"List of items available for gifting.
If negative is true, gift will instead reduce waifu value.")]
public List<WaifuItemModel> Items { get; set; } = new();
@@ -230,6 +234,21 @@ If negative is true, gift will instead reduce waifu value.")]
new("🚀", 30000, "Spaceship"),
new("🌕", 50000, "Moon")
};
public class WaifuDecayConfig
{
[Comment(@"Percentage (0 - 100) of the waifu value to reduce.
Set 0 to disable
For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)")]
public int Percent { get; set; } = 0;
[Comment(@"How often to decay waifu values, in hours")]
public int HourInterval { get; set; } = 24;
[Comment(@"Minimum waifu price required for the decay to be applied.
For example if this value is set to 300, any waifu with the price 300 or less will not experience decay.")]
public long MinPrice { get; set; } = 300;
}
}
[Cloneable]
@@ -283,7 +302,7 @@ public sealed class SlotsConfig
public sealed partial class WaifuItemModel
{
public string ItemEmoji { get; set; }
public int Price { get; set; }
public long Price { get; set; }
public string Name { get; set; }
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)]
@@ -295,7 +314,7 @@ public sealed partial class WaifuItemModel
public WaifuItemModel(
string itemEmoji,
int price,
long price,
string name,
bool negative = false)
{

View File

@@ -14,84 +14,131 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
private readonly IEnumerable<WaifuItemModel> _antiGiftSeed = new[]
{
new WaifuItemModel("🥀", 100, "WiltedRose", true), new WaifuItemModel("✂️", 1000, "Haircut", true),
new WaifuItemModel("🥀", 100, "WiltedRose", true),
new WaifuItemModel("✂️", 1000, "Haircut", true),
new WaifuItemModel("🧻", 10000, "ToiletPaper", true)
};
public GamblingConfigService(IConfigSeria serializer, IPubSub pubSub)
: base(FILE_PATH, serializer, pubSub, _changeKey)
{
AddParsedProp("currency.name", gs => gs.Currency.Name, ConfigParsers.String, ConfigPrinters.ToString);
AddParsedProp("currency.sign", gs => gs.Currency.Sign, ConfigParsers.String, ConfigPrinters.ToString);
AddParsedProp("currency.name",
gs => gs.Currency.Name,
ConfigParsers.String,
ConfigPrinters.ToString);
AddParsedProp("currency.sign",
gs => gs.Currency.Sign,
ConfigParsers.String,
ConfigPrinters.ToString);
AddParsedProp("minbet", gs => gs.MinBet, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("maxbet", gs => gs.MaxBet, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
AddParsedProp("minbet",
gs => gs.MinBet,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("maxbet",
gs => gs.MaxBet,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("gen.min", gs => gs.Generation.MinAmount, int.TryParse, ConfigPrinters.ToString, val => val >= 1);
AddParsedProp("gen.max", gs => gs.Generation.MaxAmount, int.TryParse, ConfigPrinters.ToString, val => val >= 1);
AddParsedProp("gen.cd", gs => gs.Generation.GenCooldown, int.TryParse, ConfigPrinters.ToString, val => val > 0);
AddParsedProp("gen.min",
gs => gs.Generation.MinAmount,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 1);
AddParsedProp("gen.max",
gs => gs.Generation.MaxAmount,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 1);
AddParsedProp("gen.cd",
gs => gs.Generation.GenCooldown,
int.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("gen.chance",
gs => gs.Generation.Chance,
decimal.TryParse,
ConfigPrinters.ToString,
val => val is >= 0 and <= 1);
AddParsedProp("gen.has_pw", gs => gs.Generation.HasPassword, bool.TryParse, ConfigPrinters.ToString);
AddParsedProp("gen.has_pw",
gs => gs.Generation.HasPassword,
bool.TryParse,
ConfigPrinters.ToString);
AddParsedProp("bf.multi",
gs => gs.BetFlip.Multiplier,
decimal.TryParse,
ConfigPrinters.ToString,
val => val >= 1);
AddParsedProp("waifu.min_price",
gs => gs.Waifu.MinPrice,
int.TryParse,
long.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("waifu.multi.reset",
gs => gs.Waifu.Multipliers.WaifuReset,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("waifu.multi.crush_claim",
gs => gs.Waifu.Multipliers.CrushClaim,
decimal.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("waifu.multi.normal_claim",
gs => gs.Waifu.Multipliers.NormalClaim,
decimal.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("waifu.multi.divorce_value",
gs => gs.Waifu.Multipliers.DivorceNewValue,
decimal.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("waifu.multi.all_gifts",
gs => gs.Waifu.Multipliers.AllGiftPrices,
decimal.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("waifu.multi.gift_effect",
gs => gs.Waifu.Multipliers.GiftEffect,
decimal.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("waifu.multi.negative_gift_effect",
gs => gs.Waifu.Multipliers.NegativeGiftEffect,
decimal.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("decay.percent",
gs => gs.Decay.Percent,
decimal.TryParse,
ConfigPrinters.ToString,
val => val is >= 0 and <= 1);
AddParsedProp("decay.maxdecay",
gs => gs.Decay.MaxDecay,
int.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("decay.threshold",
gs => gs.Decay.MinThreshold,
int.TryParse,
@@ -116,11 +163,11 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
c.Version = 3;
c.VoteReward = 100;
});
if (data.Version < 4)
if(data.Version < 5)
ModifyConfig(c =>
{
c.Version = 4;
c.Version = 5;
});
}
}

View File

@@ -37,7 +37,7 @@ public partial class Gambling
[Cmd]
[RequireContext(ContextType.Guild)]
public async partial Task WaifuClaim(int amount, [Leftover] IUser target)
public async partial Task WaifuClaim(long amount, [Leftover] IUser target)
{
if (amount < Config.Waifu.MinPrice)
{

View File

@@ -1,5 +1,7 @@
#nullable disable
using LinqToDB;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Gambling.Common;
@@ -8,23 +10,30 @@ using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Gambling.Services;
public class WaifuService : INService
// todo waifu price int
public class WaifuService : INService, IReadyExecutor
{
private readonly DbService _db;
private readonly ICurrencyService _cs;
private readonly IDataCache _cache;
private readonly GamblingConfigService _gss;
private readonly IBotCredentials _creds;
private readonly DiscordSocketClient _client;
public WaifuService(
DbService db,
ICurrencyService cs,
IDataCache cache,
GamblingConfigService gss)
GamblingConfigService gss,
IBotCredentials creds,
DiscordSocketClient client)
{
_db = db;
_cs = cs;
_cache = cache;
_gss = gss;
_creds = creds;
_client = client;
}
public async Task<bool> WaifuTransfer(IUser owner, ulong waifuId, IUser newOwner)
@@ -45,11 +54,11 @@ public class WaifuService : INService
// if waifu likes the person, gotta pay the penalty
if (waifu.AffinityId == ownerUser.Id)
{
if (!await _cs.RemoveAsync(owner.Id, (int)(waifu.Price * 0.6), new("waifu", "affinity-penalty")))
if (!await _cs.RemoveAsync(owner.Id, (long)(waifu.Price * 0.6), new("waifu", "affinity-penalty")))
// unable to pay 60% penalty
return false;
waifu.Price = (int)(waifu.Price * 0.7); // half of 60% = 30% price reduction
waifu.Price = (long)(waifu.Price * 0.7); // half of 60% = 30% price reduction
if (waifu.Price < settings.Waifu.MinPrice)
waifu.Price = settings.Waifu.MinPrice;
}
@@ -58,7 +67,7 @@ public class WaifuService : INService
if (!await _cs.RemoveAsync(owner.Id, waifu.Price / 10, new("waifu", "transfer")))
return false;
waifu.Price = (int)(waifu.Price * 0.95); // half of 10% = 5% price reduction
waifu.Price = (long)(waifu.Price * 0.95); // half of 10% = 5% price reduction
if (waifu.Price < settings.Waifu.MinPrice)
waifu.Price = settings.Waifu.MinPrice;
}
@@ -72,7 +81,7 @@ public class WaifuService : INService
return true;
}
public int GetResetPrice(IUser user)
public long GetResetPrice(IUser user)
{
var settings = _gss.Data;
using var uow = _db.GetDbContext();
@@ -91,7 +100,7 @@ public class WaifuService : INService
.GroupBy(x => x.New)
.Count();
return (int)Math.Ceiling(waifu.Price * 1.25f) + ((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
return (long)Math.Ceiling(waifu.Price * 1.25f) + ((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
}
public async Task<bool> TryReset(IUser user)
@@ -131,7 +140,7 @@ public class WaifuService : INService
return true;
}
public async Task<(WaifuInfo, bool, WaifuClaimResult)> ClaimWaifuAsync(IUser user, IUser target, int amount)
public async Task<(WaifuInfo, bool, WaifuClaimResult)> ClaimWaifuAsync(IUser user, IUser target, long amount)
{
var settings = _gss.Data;
WaifuClaimResult result;
@@ -317,7 +326,7 @@ public class WaifuService : INService
if (w.Affinity?.UserId == user.Id)
{
await _cs.AddAsync(w.Waifu.UserId, amount, new("waifu", "compensation"));
w.Price = (int)Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue);
w.Price = (long)Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue);
result = DivorceResult.SucessWithPenalty;
}
else
@@ -370,13 +379,13 @@ public class WaifuService : INService
});
if (w.Claimer?.UserId == from.Id)
w.Price += (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
w.Price += (long)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
else
w.Price += itemObj.Price / 2;
}
else
{
w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
w.Price -= (long)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
if (w.Price < 1)
w.Price = 1;
}
@@ -479,16 +488,65 @@ public class WaifuService : INService
var conf = _gss.Data;
return conf.Waifu.Items.Select(x
=> new WaifuItemModel(x.ItemEmoji,
(int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices),
(long)(x.Price * conf.Waifu.Multipliers.AllGiftPrices),
x.Name,
x.Negative))
.ToList();
}
public class FullWaifuInfo
public async Task OnReadyAsync()
{
public WaifuInfo Waifu { get; set; }
public IEnumerable<string> Claims { get; set; }
public int Divorces { get; set; }
// only decay waifu values from shard 0
if (_client.ShardId != 0)
return;
var redisKey = $"{_creds.RedisKey()}_last_waifu_decay";
while (true)
{
try
{
var multi = _gss.Data.Waifu.Decay.Percent / 100f;
var minPrice = _gss.Data.Waifu.Decay.MinPrice;
var decayInterval = _gss.Data.Waifu.Decay.HourInterval;
if (multi is < 0f or > 1f || decayInterval < 0)
{
continue;
}
var val = await _cache.Redis.GetDatabase().StringGetAsync(redisKey);
if (val != default)
{
var lastDecay = DateTime.FromBinary((long)val);
var toWait = decayInterval.Hours() - (DateTime.UtcNow - lastDecay);
if (toWait > 0.Hours())
{
continue;
}
}
await _cache.Redis.GetDatabase().StringSetAsync(redisKey, DateTime.UtcNow.ToBinary());
await using var uow = _db.GetDbContext();
await uow.WaifuInfo
.Where(x => x.Price > minPrice && x.ClaimerId == null)
.UpdateAsync(old => new()
{
Price = (long)(old.Price * multi)
});
await uow.SaveChangesAsync();
}
catch (Exception ex)
{
Log.Error(ex, "Unexpected error occured in waifu decay loop: {ErrorMessage}", ex.Message);
}
finally
{
await Task.Delay(1.Hours());
}
}
}
}