feat: Make adding new traders easier (#251)
* Make adding a new trader easier * Improvements
This commit is contained in:
@@ -31,7 +31,6 @@ public class InsuranceController(
|
||||
ItemHelper _itemHelper,
|
||||
ProfileHelper _profileHelper,
|
||||
WeightedRandomHelper _weightedRandomHelper,
|
||||
TraderHelper _traderHelper,
|
||||
PaymentService _paymentService,
|
||||
InsuranceService _insuranceService,
|
||||
DatabaseService _databaseService,
|
||||
@@ -39,6 +38,7 @@ public class InsuranceController(
|
||||
RagfairPriceService _ragfairPriceService,
|
||||
LocalisationService _localisationService,
|
||||
SaveServer _saveServer,
|
||||
TraderStore _traderStore,
|
||||
ConfigServer _configServer,
|
||||
ICloner _cloner
|
||||
)
|
||||
@@ -643,7 +643,7 @@ public class InsuranceController(
|
||||
/// <returns>Should item be deleted</returns>
|
||||
protected bool? RollForDelete(string traderId, Item? insuredItem = null)
|
||||
{
|
||||
var trader = _traderHelper.GetTraderById(traderId);
|
||||
var trader = _traderStore.GetTraderById(traderId);
|
||||
if (trader is null)
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -7,6 +7,7 @@ public static class OnLoadOrder
|
||||
public const int PostSptDatabase = 2;
|
||||
public const int GameCallbacks = 100;
|
||||
public const int PostDBModLoader = 200;
|
||||
public const int TraderRegistration = 250;
|
||||
public const int HandbookCallbacks = 300;
|
||||
public const int HttpCallbacks = 400;
|
||||
public const int SaveCallbacks = 500;
|
||||
|
||||
@@ -25,6 +25,7 @@ public class TraderHelper(
|
||||
PlayerService _playerService,
|
||||
LocalisationService _localisationService,
|
||||
FenceService _fenceService,
|
||||
TraderStore _traderStore,
|
||||
TimeUtil _timeUtil,
|
||||
RandomUtil _randomUtil,
|
||||
ConfigServer _configServer
|
||||
@@ -520,16 +521,16 @@ public class TraderHelper(
|
||||
}
|
||||
|
||||
// Init dict and fill
|
||||
foreach (var traderName in Traders.TradersDictionary)
|
||||
foreach (var trader in _traderStore.GetAllTraders())
|
||||
{
|
||||
// Skip some traders
|
||||
if (traderName.Value == Traders.FENCE)
|
||||
if (trader.Id == Traders.FENCE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get assorts for trader, skip trader if no assorts found
|
||||
var traderAssorts = _databaseService.GetTrader(traderName.Value).Assort;
|
||||
var traderAssorts = _databaseService.GetTrader(trader.Id).Assort;
|
||||
if (traderAssorts is null)
|
||||
{
|
||||
continue;
|
||||
@@ -566,10 +567,10 @@ public class TraderHelper(
|
||||
{
|
||||
// Find largest trader price for item
|
||||
var highestPrice = 1d; // Default price
|
||||
foreach (var trader in Traders.TradersDictionary)
|
||||
foreach (var trader in _traderStore.GetAllTraders())
|
||||
{
|
||||
// Get trader and check buy category allows tpl
|
||||
var traderBase = _databaseService.GetTrader(trader.Value).Base;
|
||||
var traderBase = _databaseService.GetTrader(trader.Id).Base;
|
||||
|
||||
// Skip traders that don't sell this category of item
|
||||
if (traderBase is null || !_itemHelper.IsOfBaseclasses(tpl, traderBase.ItemsBuy.Category))
|
||||
@@ -595,63 +596,13 @@ public class TraderHelper(
|
||||
return highestPrice;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a trader enum key by its value
|
||||
/// </summary>
|
||||
/// <param name="traderId">Traders id</param>
|
||||
/// <returns>Traders key</returns>
|
||||
public TradersEnum? GetTraderById(string traderId)
|
||||
{
|
||||
var kvp = Traders.TradersDictionary.Where(x => x.Value == traderId);
|
||||
|
||||
if (!kvp.Any())
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("trader-unable_to_find_trader_in_enum", traderId));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return kvp.FirstOrDefault().Key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
/// </summary>
|
||||
/// <param name="traderEnumValue">The trader enum value to validate</param>
|
||||
/// <returns>The validated trader enum value as a string, or an empty string if invalid</returns>
|
||||
/// TODO: might not be needed
|
||||
public string GetValidTraderIdByEnumValue(string traderEnumValue)
|
||||
{
|
||||
var traderId = _databaseService.GetTraders();
|
||||
var id = traderId.FirstOrDefault(x =>
|
||||
x.Value.Base.Id == traderEnumValue || string.Equals(x.Value.Base.Nickname, traderEnumValue, StringComparison.OrdinalIgnoreCase)).Key;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the 'Traders' enum has a value that matches the passed in parameter
|
||||
/// </summary>
|
||||
/// <param name="key">Value to check for</param>
|
||||
/// <returns>True, values exists in Traders enum as a value</returns>
|
||||
public bool TraderEnumHasKey(string key)
|
||||
{
|
||||
return Traders.TradersDictionary.Any(x => x.Value == key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts a trader id
|
||||
/// </summary>
|
||||
/// <param name="traderId">Trader id</param>
|
||||
/// <returns>True if Traders enum has the param as a value</returns>
|
||||
public bool TraderEnumHasValue(string traderId)
|
||||
/// <returns>True if a Trader exists with given ID</returns>
|
||||
public bool TraderExists(string traderId)
|
||||
{
|
||||
return Traders.TradersDictionary.ContainsValue(traderId);
|
||||
return _traderStore.GetTraderById(traderId) != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,38 +13,4 @@ public static class Traders
|
||||
public const string LIGHTHOUSEKEEPER = "638f541a29ffd1183d187f57";
|
||||
public const string BTR = "656f0f98d80a697f855d34b1";
|
||||
public const string REF = "6617beeaa9cfa777ca915b7c";
|
||||
|
||||
public static Dictionary<TradersEnum, string> TradersDictionary
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new()
|
||||
{
|
||||
{ TradersEnum.Prapor, "54cb50c76803fa8b248b4571" },
|
||||
{ TradersEnum.Therapist, "54cb57776803fa99248b456e" },
|
||||
{ TradersEnum.Fence, "579dc571d53a0658a154fbec" },
|
||||
{ TradersEnum.Skier, "58330581ace78e27b8b10cee" },
|
||||
{ TradersEnum.Peacekeeper, "5935c25fb3acc3127c3d8cd9" },
|
||||
{ TradersEnum.Mechanic, "5a7c2eca46aef81a7ca2145d" },
|
||||
{ TradersEnum.Ragman, "5ac3b934156ae10c4430e83c" },
|
||||
{ TradersEnum.Jaeger, "5c0647fdd443bc2504c2d371" },
|
||||
{ TradersEnum.LighthouseKeeper, "638f541a29ffd1183d187f57" },
|
||||
{ TradersEnum.Btr, "656f0f98d80a697f855d34b1" },
|
||||
{ TradersEnum.Ref, "6617beeaa9cfa777ca915b7c" }
|
||||
};
|
||||
}
|
||||
|
||||
public enum TradersEnum
|
||||
{
|
||||
Prapor,
|
||||
Therapist,
|
||||
Fence,
|
||||
Skier,
|
||||
Peacekeeper,
|
||||
Mechanic,
|
||||
Ragman,
|
||||
Jaeger,
|
||||
LighthouseKeeper,
|
||||
Btr,
|
||||
Ref
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
||||
using SPTarkov.Server.Core.Models.Spt.Services;
|
||||
|
||||
namespace SPTarkov.Server.Core.Models;
|
||||
|
||||
public interface ITrader
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Id { get; }
|
||||
}
|
||||
|
||||
public abstract record ICustomTrader : ITrader
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
public abstract string Id { get; }
|
||||
public abstract TraderAssort? GetAssort();
|
||||
public abstract Dictionary<string, Dictionary<string, string>>? GetQuestAssort();
|
||||
public abstract TraderBase? GetBase();
|
||||
|
||||
public virtual List<Suit>? GetSuits()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual List<TraderServiceModel>? GetServices()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual Dictionary<string, List<string>?>? GetDialogues()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using SPTarkov.DI.Annotations;
|
||||
using SPTarkov.Server.Core.Models.Enums;
|
||||
|
||||
namespace SPTarkov.Server.Core.Models;
|
||||
|
||||
[Injectable]
|
||||
public record Prapor() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Prapor";
|
||||
public string Id { get; } = Traders.PRAPOR;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record Therapist() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Therapist";
|
||||
public string Id { get; } = Traders.THERAPIST;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record Fence() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Fence";
|
||||
public string Id { get; } = Traders.FENCE;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record Skier() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Skier";
|
||||
public string Id { get; } = Traders.SKIER;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record Peacekeeper() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Peacekeeper";
|
||||
public string Id { get; } = Traders.PEACEKEEPER;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record Mechanic() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Mechanic";
|
||||
public string Id { get; } = Traders.MECHANIC;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record Ragman() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Ragman";
|
||||
public string Id { get; } = Traders.RAGMAN;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record Jaeger() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Jaeger";
|
||||
public string Id { get; } = Traders.JAEGER;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record LighthouseKeeper() : ITrader
|
||||
{
|
||||
public string Name { get; } = "LighthouseKeeper";
|
||||
public string Id { get; } = Traders.LIGHTHOUSEKEEPER;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record Btr() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Btr";
|
||||
public string Id { get; } = Traders.BTR;
|
||||
}
|
||||
|
||||
[Injectable]
|
||||
public record Ref() : ITrader
|
||||
{
|
||||
public string Name { get; } = "Ref";
|
||||
public string Id { get; } = Traders.REF;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public class PaymentService(
|
||||
{
|
||||
// May need to convert to trader currency
|
||||
var trader = _traderHelper.GetTrader(request.TransactionId, sessionID);
|
||||
var payToTrader = _traderHelper.TraderEnumHasValue(request.TransactionId);
|
||||
var payToTrader = _traderHelper.TraderExists(request.TransactionId);
|
||||
|
||||
// Track the amounts of each type of currency involved in the trade.
|
||||
var currencyAmounts = new Dictionary<string, double?>();
|
||||
|
||||
@@ -666,7 +666,7 @@ public class ProfileFixerService(
|
||||
|
||||
foreach (var activeQuest in repeatable.ActiveQuests)
|
||||
{
|
||||
if (!_traderHelper.TraderEnumHasValue(activeQuest.TraderId))
|
||||
if (!_traderHelper.TraderExists(activeQuest.TraderId))
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("fixer-trader_found", activeQuest.TraderId));
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
@@ -701,7 +701,7 @@ public class ProfileFixerService(
|
||||
}
|
||||
|
||||
foreach (var TraderPurchaseKvP in fullProfile.TraderPurchases
|
||||
.Where(TraderPurchase => !_traderHelper.TraderEnumHasValue(TraderPurchase.Key)))
|
||||
.Where(TraderPurchase => !_traderHelper.TraderExists(TraderPurchase.Key)))
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("fixer-trader_found", TraderPurchaseKvP.Key));
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
@@ -893,7 +893,7 @@ public class ProfileFixerService(
|
||||
foreach (var traderKvP in fullProfile.CharacterData?.PmcData?.TradersInfo)
|
||||
{
|
||||
var traderId = traderKvP.Key;
|
||||
if (!_traderHelper.TraderEnumHasValue(traderId))
|
||||
if (!_traderHelper.TraderExists(traderId))
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("fixer-trader_found", traderId));
|
||||
if (_coreConfig.Fixes.RemoveInvalidTradersFromProfile)
|
||||
@@ -907,7 +907,7 @@ public class ProfileFixerService(
|
||||
foreach (var traderKvP in fullProfile.CharacterData.ScavData?.TradersInfo)
|
||||
{
|
||||
var traderId = traderKvP.Key;
|
||||
if (!_traderHelper.TraderEnumHasValue(traderId))
|
||||
if (!_traderHelper.TraderExists(traderId))
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("fixer-trader_found", traderId));
|
||||
if (_coreConfig.Fixes.RemoveInvalidTradersFromProfile)
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
using SPTarkov.DI.Annotations;
|
||||
using SPTarkov.Server.Core.DI;
|
||||
using SPTarkov.Server.Core.Models;
|
||||
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
||||
using SPTarkov.Server.Core.Models.Utils;
|
||||
|
||||
namespace SPTarkov.Server.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Source of truth for all default traders as well as any additional trader a server mod may add.
|
||||
/// </summary>
|
||||
[Injectable(InjectionType.Singleton, TypePriority = OnLoadOrder.TraderRegistration)]
|
||||
public class TraderStore : IOnLoad
|
||||
{
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly IEnumerable<ITrader> _injectedTraders;
|
||||
private readonly ISptLogger<TraderStore> _logger;
|
||||
|
||||
private readonly Dictionary<string, ITrader> _traders = new();
|
||||
|
||||
public TraderStore(DatabaseService databaseService,
|
||||
IEnumerable<ITrader> injectedTraders,
|
||||
ISptLogger<TraderStore> logger)
|
||||
{
|
||||
_databaseService = databaseService;
|
||||
_injectedTraders = injectedTraders;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task OnLoad()
|
||||
{
|
||||
_logger.Info("Importing traders...");
|
||||
var customTraders = 0;
|
||||
|
||||
foreach (var trader in _injectedTraders)
|
||||
{
|
||||
if (trader is ICustomTrader customTrader)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dbTrader = new Trader()
|
||||
{
|
||||
Assort = customTrader.GetAssort(),
|
||||
Base = customTrader.GetBase(),
|
||||
QuestAssort = customTrader.GetQuestAssort(),
|
||||
Dialogue = customTrader.GetDialogues(),
|
||||
Suits = customTrader.GetSuits(),
|
||||
Services = customTrader.GetServices(),
|
||||
};
|
||||
_databaseService.GetTraders().Add(trader.Id, dbTrader);
|
||||
_traders.Add(trader.Id, trader);
|
||||
_logger.Info($"Loaded custom trader: {trader.Name}");
|
||||
customTraders++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Failed to load custom trader", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_traders.Add(trader.Id, trader);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info($"Importing traders complete {(customTraders == 0 ? "" : $"[{customTraders} traders loaded]")}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-trader-registration";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a trader by given ID.
|
||||
/// </summary>
|
||||
/// <param name="traderId"></param>
|
||||
/// <returns></returns>
|
||||
public ITrader? GetTraderById(string traderId)
|
||||
{
|
||||
if (_traders.TryGetValue(traderId, out var trader))
|
||||
{
|
||||
return trader;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all traders in the game, including custom traders.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<ITrader> GetAllTraders()
|
||||
{
|
||||
return _traders.Values;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user