143 lines
5.6 KiB
C#
143 lines
5.6 KiB
C#
using System.Globalization;
|
|
using SPTarkov.DI.Annotations;
|
|
using SPTarkov.Server.Core.Models.Eft.Ragfair;
|
|
using SPTarkov.Server.Core.Models.Spt.Config;
|
|
using SPTarkov.Server.Core.Models.Utils;
|
|
using SPTarkov.Server.Core.Servers;
|
|
using SPTarkov.Server.Core.Services;
|
|
using SPTarkov.Server.Core.Utils;
|
|
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
|
|
|
|
namespace SPTarkov.Server.Core.Helpers;
|
|
|
|
[Injectable]
|
|
public class RagfairSellHelper(
|
|
ISptLogger<RagfairSellHelper> logger,
|
|
TimeUtil timeUtil,
|
|
RandomUtil randomUtil,
|
|
DatabaseService databaseService,
|
|
ConfigServer configServer
|
|
)
|
|
{
|
|
protected readonly RagfairConfig RagfairConfig = configServer.GetConfig<RagfairConfig>();
|
|
|
|
/// <summary>
|
|
/// Get the percent chance to sell an item based on its average listed price vs player chosen listing price
|
|
/// </summary>
|
|
/// <param name="averageOfferPriceRub">Price of average offer in roubles</param>
|
|
/// <param name="playerListedPriceRub">Price player listed item for in roubles</param>
|
|
/// <param name="qualityMultiplier">Quality multiplier of item being sold</param>
|
|
/// <returns>percent value</returns>
|
|
public double CalculateSellChance(double averageOfferPriceRub, double playerListedPriceRub, double qualityMultiplier)
|
|
{
|
|
var sellConfig = RagfairConfig.Sell.Chance;
|
|
|
|
// Base sell chance modified by items quality
|
|
var baseSellChancePercent = sellConfig.Base * qualityMultiplier;
|
|
|
|
// Modifier gets applied twice to either penalize or incentivize over/under pricing (Probably a cleaner way to do this)
|
|
var sellModifier = averageOfferPriceRub / playerListedPriceRub * sellConfig.SellMultiplier;
|
|
var sellChance = Math.Round(baseSellChancePercent * sellModifier * (Math.Pow(sellModifier, 3) + 10)); // Power of 3
|
|
|
|
// Adjust sell chance if below config value
|
|
if (sellChance < sellConfig.MinSellChancePercent)
|
|
{
|
|
sellChance = sellConfig.MinSellChancePercent;
|
|
}
|
|
|
|
// Adjust sell chance if above config value
|
|
if (sellChance > sellConfig.MaxSellChancePercent)
|
|
{
|
|
sellChance = sellConfig.MaxSellChancePercent;
|
|
}
|
|
|
|
return sellChance;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get list of item count and sell time (empty list = no sell)
|
|
/// </summary>
|
|
/// <param name="sellChancePercent">chance item will sell</param>
|
|
/// <param name="itemSellCount">count of items to sell</param>
|
|
/// <param name="sellInOneGo">All items listed get sold at once</param>
|
|
/// <returns>List of purchases of item(s) listed</returns>
|
|
public List<SellResult> RollForSale(double? sellChancePercent, int itemSellCount, bool sellInOneGo = false)
|
|
{
|
|
var startTimestamp = timeUtil.GetTimeStamp();
|
|
|
|
// Get a time in future to stop simulating sell chances at
|
|
var endTime =
|
|
startTimestamp + timeUtil.GetHoursAsSeconds((int)databaseService.GetGlobals().Configuration.RagFair.OfferDurationTimeInHour);
|
|
|
|
var sellTimestamp = startTimestamp;
|
|
var remainingCount = itemSellCount;
|
|
var result = new List<SellResult>();
|
|
|
|
var effectiveSellChance = sellChancePercent;
|
|
|
|
if (sellChancePercent is null)
|
|
{
|
|
effectiveSellChance = RagfairConfig.Sell.Chance.Base;
|
|
logger.Warning($"Sell chance was not a number: {sellChancePercent}, defaulting to {RagfairConfig.Sell.Chance.Base}%");
|
|
}
|
|
|
|
if (logger.IsLogEnabled(LogLevel.Debug))
|
|
{
|
|
logger.Debug($"Rolling to sell: {itemSellCount} item(s) - (chance: {effectiveSellChance}%)");
|
|
}
|
|
|
|
// No point rolling for a sale on a 0% chance item, exit early
|
|
if (effectiveSellChance == 0)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
while (remainingCount > 0 && sellTimestamp < endTime)
|
|
{
|
|
var boughtAmount = sellInOneGo ? remainingCount : randomUtil.GetInt(1, remainingCount);
|
|
if (randomUtil.GetChance100(effectiveSellChance))
|
|
{
|
|
// Passed roll check, item will be sold
|
|
// Weight time to sell towards selling faster based on how cheap the item sold
|
|
var weighting = (100 - effectiveSellChance) / 100;
|
|
var maximumTime = weighting * RagfairConfig.Sell.Time.Max * 60d;
|
|
var minimumTime = RagfairConfig.Sell.Time.Min * 60d;
|
|
if (maximumTime < minimumTime)
|
|
{
|
|
maximumTime = minimumTime + 5;
|
|
}
|
|
|
|
// Sell time will be random between min/max
|
|
var random = new Random();
|
|
var newSellTime = Math.Floor(random.NextDouble() * (maximumTime.Value - minimumTime) + minimumTime);
|
|
if (newSellTime == 0)
|
|
// Ensure all sales don't occur the same exact time
|
|
{
|
|
newSellTime += 1;
|
|
}
|
|
|
|
sellTimestamp += (long)newSellTime;
|
|
result.Add(new SellResult { SellTime = sellTimestamp, Amount = boughtAmount });
|
|
|
|
if (logger.IsLogEnabled(LogLevel.Debug))
|
|
{
|
|
logger.Debug(
|
|
$"Offer will sell at: {timeUtil.GetDateTimeFromTimeStamp(sellTimestamp).ToLocalTime().ToString(CultureInfo.InvariantCulture)}, bought: {boughtAmount}"
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.IsLogEnabled(LogLevel.Debug))
|
|
{
|
|
logger.Debug($"Offer rolled not to sell, item count: {boughtAmount}");
|
|
}
|
|
}
|
|
|
|
remainingCount -= boughtAmount;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|