using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Generators; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Game; using SPTarkov.Server.Core.Models.Enums; 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 SPTarkov.Server.Core.Utils.Cloners; namespace SPTarkov.Server.Core.Controllers; [Injectable] public class TraderController( ISptLogger _logger, TimeUtil _timeUtil, DatabaseService _databaseService, TraderAssortHelper _traderAssortHelper, ProfileHelper _profileHelper, TraderHelper _traderHelper, PaymentHelper _paymentHelper, RagfairPriceService _ragfairPriceService, TraderPurchasePersisterService _traderPurchasePersisterService, FenceService _fenceService, FenceBaseAssortGenerator _fenceBaseAssortGenerator, ConfigServer _configServer, ICloner _cloner ) { protected TraderConfig _traderConfig = _configServer.GetConfig(); /// /// Runs when onLoad event is fired /// Iterate over traders, ensure a pristine copy of their assorts is stored in traderAssortService /// Store timestamp of next assort refresh in nextResupply property of traders .base object /// public void Load() { var nextHourTimestamp = _timeUtil.GetTimeStampOfNextHour(); var traderResetStartsWithServer = _traderConfig.TradersResetFromServerStart; var traders = _databaseService.GetTraders(); foreach (var (traderId, trader) in traders) { if (traderId is "ragfair" or Traders.LIGHTHOUSEKEEPER) { continue; } if (traderId == Traders.FENCE) { _fenceBaseAssortGenerator.GenerateFenceBaseAssorts(); _fenceService.GenerateFenceAssorts(); continue; } // Adjust price by traderPriceMultiplier config property if (_traderConfig.TraderPriceMultiplier != 1) { AdjustTraderItemPrices(trader, _traderConfig.TraderPriceMultiplier); } _traderPurchasePersisterService.RemoveStalePurchasesFromProfiles(traderId); // Set to next hour on clock or current time + 60 minutes trader.Base.NextResupply = traderResetStartsWithServer ? (int) _traderHelper.GetNextUpdateTimestamp(trader.Base.Id) : (int) nextHourTimestamp; } } /// /// Adjust trader item prices based on config value multiplier /// only applies to items sold for currency /// /// Trader to adjust prices of /// Coef to apply to traders' items' prices protected void AdjustTraderItemPrices(Trader trader, double multiplier) { foreach (var kvp in trader.Assort?.BarterScheme) { var barterSchemeItem = kvp.Value?.FirstOrDefault()?.FirstOrDefault(); if (barterSchemeItem != null && _paymentHelper.IsMoneyTpl(barterSchemeItem.Template)) { barterSchemeItem.Count += Math.Round( barterSchemeItem?.Count * multiplier ?? 0D, 2 ); } } } /// /// Runs when onUpdate is fired /// If current time is > nextResupply(expire) time of trader, refresh traders assorts and /// Fence is handled slightly differently /// /// True if ran successfully public bool Update() { foreach (var (traderId, data) in _databaseService.GetTables().Traders) { switch (traderId) { case Traders.LIGHTHOUSEKEEPER: continue; case Traders.FENCE: { if (_fenceService.NeedsPartialRefresh()) { _fenceService.GenerateFenceAssorts(); } continue; } } // Trader needs to be refreshed if (_traderAssortHelper.TraderAssortsHaveExpired(traderId)) { _traderAssortHelper.ResetExpiredTrader(data); // Reset purchase data per trader as they have independent reset times _traderPurchasePersisterService.ResetTraderPurchasesStoredInProfile(traderId); } } return true; } /// /// Handle client/trading/api/traderSettings /// /// session id /// Return a list of all traders public List GetAllTraders(string sessionId) { var traders = new List(); var pmcData = _profileHelper.GetPmcProfile(sessionId); foreach (var (traderId, _) in _databaseService.GetTables().Traders) { traders.Add(_traderHelper.GetTrader(traderId, sessionId)); if (pmcData?.Info != null) { _traderHelper.LevelUp(traderId, pmcData); } } traders.Sort(SortByTraderId); return traders; } /// /// Order traders by their traderId (tid) /// /// First trader to compare /// Second trader to compare /// 1,-1 or 0 protected static int SortByTraderId(TraderBase traderA, TraderBase traderB) { return string.CompareOrdinal(traderA.Id, traderB.Id); } /// /// Handle client/trading/api/getTrader /// /// Session/Player id /// /// public TraderBase GetTrader(string sessionId, string traderId) { return _traderHelper.GetTrader(sessionId, traderId); } /// /// Handle client/trading/api/getTraderAssort /// /// Session/Player id /// /// public TraderAssort GetAssort(string sessionId, string traderId) { return _traderAssortHelper.GetAssort(sessionId, traderId); } /// /// Handle client/items/prices/TRADERID /// /// public GetItemPricesResponse GetItemPrices(string sessionId, string traderId) { var handbookPrices = _ragfairPriceService.GetAllStaticPrices(); return new GetItemPricesResponse { SupplyNextTime = _traderHelper.GetNextUpdateTimestamp(traderId), Prices = handbookPrices, CurrencyCourses = new Dictionary { { "5449016a4bdc2d6f028b456f", handbookPrices[Money.ROUBLES] }, { "569668774bdc2da2298b4568", handbookPrices[Money.EUROS] }, { "5696686a4bdc2da3298b456a", handbookPrices[Money.DOLLARS] } } }; } }