using Core.Models.Eft.Ragfair; using Core.Models.Spt.Config; using Core.Models.Utils; using Core.Servers; using Core.Services; using Core.Utils; using SptCommon.Annotations; using LogLevel = Core.Models.Spt.Logging.LogLevel; namespace Core.Helpers; [Injectable] public class RagfairSellHelper( ISptLogger _logger, TimeUtil _timeUtil, RandomUtil _randomUtil, DatabaseService _databaseService, ConfigServer _configServer) { protected RagfairConfig _ragfairConfig = _configServer.GetConfig(); /// /// Get the percent chance to sell an item based on its average listed price vs player chosen listing price /// /// Price of average offer in roubles /// Price player listed item for in roubles /// Quality multiplier of item being sold /// percent value 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; } /// /// Get list of item count and sell time (empty list = no sell) /// /// chance item will sell /// count of items to sell /// All items listed get sold at once /// List of purchases of item(s) listed public List 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.Value); var sellTimestamp = startTimestamp; var remainingCount = itemSellCount; var result = new List(); 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}items(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()}, bought: {boughtAmount}"); } } else { if (_logger.IsLogEnabled(LogLevel.Debug)) { _logger.Debug($"Offer rolled not to sell, item count: {boughtAmount}"); } } remainingCount -= boughtAmount; } return result; } }