using Core.Annotations; using Core.Models.Common; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.Profile; using Core.Models.Enums; using Core.Models.Spt.Config; using Core.Models.Utils; using Core.Servers; using Core.Services; using Core.Utils; namespace Core.Helpers; [Injectable] public class TraderHelper { protected ISptLogger _logger; protected TimeUtil _timeUtil; protected RandomUtil _randomUtil; protected LocalisationService _localisationService; protected ConfigServer _configServer; protected TraderConfig _traderConfig; protected ProfileHelper _profileHelper; protected DatabaseService _databaseService; private Dictionary _highestTraderPriceItems = new(); public TraderHelper( ISptLogger logger, TimeUtil timeUtil, RandomUtil randomUtil, LocalisationService localisationService, ConfigServer configServer, ProfileHelper profileHelper, DatabaseService databaseService ) { _logger = logger; _timeUtil = timeUtil; _randomUtil = randomUtil; _localisationService = localisationService; _configServer = configServer; _profileHelper = profileHelper; _databaseService = databaseService; _traderConfig = _configServer.GetConfig(); } /// /// Get a trader base object, update profile to reflect players current standing in profile /// when trader not found in profile /// /// Traders Id to get /// Players id /// Trader base public TraderBase GetTrader(string traderID, string sessionID) { if (traderID == "ragfair") { return new() { Currency = "RUB" }; } var pmcData = _profileHelper.GetPmcProfile(sessionID); if (pmcData == null) throw new Exception(_localisationService.GetText("trader-unable_to_find_profile_with_id", sessionID)); // Profile has traderInfo dict (profile beyond creation stage) but no requested trader in profile if (pmcData?.TradersInfo != null && (pmcData?.TradersInfo?.ContainsKey(traderID) ?? false)) { // Add trader values to profile ResetTrader(sessionID, traderID); LevelUp(traderID, pmcData); } var traderBase = _databaseService.GetTrader(traderID).Base; if (traderBase == null) _logger.Error(_localisationService.GetText("trader-unable_to_find_trader_by_id", traderID)); return traderBase; } /// /// Get all assort data for a particular trader /// /// Trader to get assorts for /// TraderAssort public TraderAssort GetTraderAssortsByTraderId(string traderId) { throw new NotImplementedException(); } /// /// Retrieve the Item from a traders assort data by its id /// /// Trader to get assorts for /// Id of assort to find /// Item object public Item GetTraderAssortItemByAssortId(string traderId, string assortId) { throw new NotImplementedException(); } /// /// Reset a profiles trader data back to its initial state as seen by a level 1 player /// Does NOT take into account different profile levels /// /// session id of player /// trader id to reset public void ResetTrader(string sessionID, string traderID) { // TODO: implement actually return; } /// /// Get the starting standing of a trader based on the current profiles type (e.g. EoD, Standard etc) /// /// Trader id to get standing for /// Raw profile from profiles.json to look up standing from /// Standing value protected double GetStartingStanding(string traderId, ProfileTraderTemplate rawProfileTemplate) { throw new NotImplementedException(); } /// /// Add a list of suit ids to a profiles suit list, no duplicates /// /// Profile to add to /// Suit Ids to add protected void AddSuitsToProfile(SptProfile fullProfile, List suitIds) { throw new NotImplementedException(); } /// /// Alter a traders unlocked status /// /// Trader to alter /// New status to use /// Session id of player public void SetTraderUnlockedState(string traderId, bool status, string sessionId) { throw new NotImplementedException(); } /// /// Add standing to a trader and level them up if exp goes over level threshold /// /// Session id of player /// Traders id to add standing to /// Standing value to add to trader public void AddStandingToTrader(string sessionId, string traderId, double standingToAdd) { throw new NotImplementedException(); } /// /// Add standing to current standing and clamp value if it goes too low /// /// current trader standing /// standing to add to trader standing /// current standing + added standing (clamped if needed) protected double AddStandingValuesTogether(double currentStanding, double standingToAdd) { throw new NotImplementedException(); } /// /// Iterate over a profile's traders and ensure they have the correct loyalty level for the player. /// /// Profile to check. public void ValidateTraderStandingsAndPlayerLevelForProfile(string sessionId) { throw new NotImplementedException(); } /// /// Calculate trader's level based on experience amount and increments level if over threshold. /// Also validates and updates player level if not correct based on XP value. /// /// Trader to check standing of. /// Profile to update trader in. public void LevelUp(string traderID, PmcData pmcData) { // TODO: implement actually return; } /// /// Get the next update timestamp for a trader. /// /// Trader to look up update value for. /// Future timestamp. public long GetNextUpdateTimestamp(string traderID) { var time = _timeUtil.GetTimeStamp(); var updateSeconds = GetTraderUpdateSeconds(traderID) ?? 0; return time + updateSeconds; } /// /// Get the reset time between trader assort refreshes in seconds. /// /// Trader to look up. /// Time in seconds. public long? GetTraderUpdateSeconds(string traderId) { var traderDetails = _traderConfig.UpdateTime.FirstOrDefault((x) => x.TraderId == traderId); if (traderDetails is null || traderDetails.Seconds?.Min is null || traderDetails.Seconds.Max is null) { _logger.Warning(_localisationService.GetText("trader-missing_trader_details_using_default_refresh_time", new { traderId = traderId, updateTime = _traderConfig.UpdateTimeDefault, })); _traderConfig.UpdateTime.Add(new UpdateTime // create temporary entry to prevent logger spam { TraderId = traderId, Seconds = new MinMax { Min = _traderConfig.UpdateTimeDefault, Max = _traderConfig.UpdateTimeDefault } }); return null; } return _randomUtil.GetInt((int)traderDetails.Seconds.Min, (int)traderDetails.Seconds.Max); } public TraderLoyaltyLevel GetLoyaltyLevel(string traderID, PmcData pmcData) { throw new NotImplementedException(); } /// /// Store the purchase of an assort from a trader in the player profile /// /// Session id /// New item assort id + count public void AddTraderPurchasesToPlayerProfile( string sessionID, object newPurchaseDetails, // TODO: TYPE FUCKEY { items: { itemId: string; count: number }[]; traderId: string } Item itemPurchased) { throw new NotImplementedException(); } /// /// EoD and Unheard get a 20% bonus to personal trader limit purchases /// /// Existing value from trader item /// Profiles game version /// buyRestrictionMax value public double GetAccountTypeAdjustedTraderPurchaseLimit(double buyRestrictionMax, string gameVersion) { throw new NotImplementedException(); } /// /// Get the highest rouble price for an item from traders /// UNUSED /// /// Item to look up highest price for /// highest rouble cost for item public double GetHighestTraderPriceRouble(string tpl) { throw new NotImplementedException(); } /// /// Get the highest price item can be sold to trader for (roubles) /// /// Item to look up best trader sell-to price /// Rouble price public double GetHighestSellToTraderPrice(string tpl) { throw new NotImplementedException(); } /// /// Get a trader enum key by its value /// /// Traders id /// Traders key public Trader GetTraderById(string traderId) { throw new NotImplementedException(); } /// /// Validates that the provided traderEnumValue exists in the Traders enum. If the value is valid, it returns the /// same enum value, effectively serving as a trader ID; otherwise, it logs an error and returns an empty string. /// This method provides a runtime check to prevent undefined behavior when using the enum as a dictionary key. /// /// For example, instead of this: /// const traderId = Traders[Traders.PRAPOR]; /// /// You can use safely use this: /// const traderId = this.traderHelper.getValidTraderIdByEnumValue(Traders.PRAPOR); /// /// /// The trader enum value to validate /// The validated trader enum value as a string, or an empty string if invalid public string GetValidTraderIdByEnumValue(string traderEnumValue) // TODO: param was Traders { var traderId = _databaseService.GetTraders(); var id = traderId.FirstOrDefault(x => x.Value.Base.Nickname.ToLower() == traderEnumValue.ToLower()).Key; return id; } /// /// Does the 'Traders' enum has a value that matches the passed in parameter /// /// Value to check for /// True, values exists in Traders enum as a value public bool TraderEnumHasKey(string key) { throw new NotImplementedException(); } /// /// Accepts a trader id /// /// Trader id /// True if Traders enum has the param as a value public bool TraderEnumHasValue(string traderId) { _logger.Error("HACK TraderEnumHasValue"); return Traders.TradersDictionary.ContainsValue(traderId); } }