More bot generation implementation

This commit is contained in:
Chomp
2025-01-20 15:18:54 +00:00
parent 3a1ff7fbbd
commit 3cc7e91be8
4 changed files with 326 additions and 22 deletions
+208 -6
View File
@@ -1,11 +1,53 @@
using SptCommon.Annotations;
using Core.Models.Eft.Common.Tables;
using Core.Models.Enums;
using Core.Models.Utils;
using Core.Helpers;
using Core.Services;
using Core.Servers;
using Core.Models.Spt.Config;
namespace Core.Generators;
[Injectable]
public class PMCLootGenerator()
public class PMCLootGenerator
{
private readonly ISptLogger<PMCLootGenerator> _logger;
private readonly DatabaseService _databaseService;
private readonly ItemHelper _itemHelper;
private readonly ItemFilterService _itemFilterService;
private readonly RagfairPriceService _ragfairPriceService;
private readonly SeasonalEventService _seasonalEventService;
private readonly WeightedRandomHelper _weightedRandomHelper;
private readonly ConfigServer _configServer;
private Dictionary<string, double>? _backpackLootPool;
private Dictionary<string, double>? _pocketLootPool;
private Dictionary<string, double>? _vestLootPool;
private readonly PmcConfig _pmcConfig;
public PMCLootGenerator(
ISptLogger<PMCLootGenerator> logger,
DatabaseService databaseService,
ItemHelper itemHelper,
ItemFilterService itemFilterService,
RagfairPriceService ragfairPriceService,
SeasonalEventService seasonalEventService,
WeightedRandomHelper weightedRandomHelper,
ConfigServer configServer
)
{
_logger = logger;
_databaseService = databaseService;
_itemHelper = itemHelper;
_itemFilterService = itemFilterService;
_ragfairPriceService = ragfairPriceService;
_seasonalEventService = seasonalEventService;
_weightedRandomHelper = weightedRandomHelper;
_configServer = configServer;
_pmcConfig = _configServer.GetConfig<PmcConfig>();
}
/// <summary>
/// Create a List of loot items a PMC can have in their pockets
@@ -14,7 +56,76 @@ public class PMCLootGenerator()
/// <returns>Dictionary of string and number</returns>
public Dictionary<string, double> GeneratePMCPocketLootPool(string botRole)
{
throw new NotImplementedException();
// Hydrate loot dictionary if empty
if (_pocketLootPool is null)
{
var items = _databaseService.GetItems();
var pmcPriceOverrides =
_databaseService.GetBots().Types[botRole.ToLower() == "pmcbear" ? "bear" : "usec"].BotInventory.Items.Pockets;
var allowedItemTypeWhitelist = _pmcConfig.PocketLoot.Whitelist;
var blacklist = GetLootBlacklist();
var itemsToAdd = items.Where(
(item) =>
allowedItemTypeWhitelist.Contains(item.Value.Parent) &&
_itemHelper.isValidItem(item.Value.Id) &&
!blacklist.Contains(item.Value.Id) &&
!blacklist.Contains(item.Value.Parent) &&
ItemFitsInto1By2Slot(item.Value)
);
foreach (var (tpl, template) in itemsToAdd)
{
// If pmc has price override, use that. Otherwise, use flea price
if (pmcPriceOverrides.ContainsKey(tpl))
{
_pocketLootPool[tpl] = pmcPriceOverrides[tpl];
}
else
{
// Set price of item as its weight
var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES);
_pocketLootPool[tpl] = price;
}
}
var highestPrice = _pocketLootPool.Max(price => price.Value);
foreach (var (key, _) in _pocketLootPool)
{
// Invert price so cheapest has a larger weight
// Times by highest price so most expensive item has weight of 1
_pocketLootPool[key] = Math.Round((1 / _pocketLootPool[key]) * highestPrice);
}
_weightedRandomHelper.ReduceWeightValues(_pocketLootPool);
}
return _pocketLootPool;
}
private HashSet<string> GetLootBlacklist()
{
var blacklist = new HashSet<string>();
foreach (var blacklistedItem in _pmcConfig.PocketLoot.Blacklist)
{
blacklist.Add(blacklistedItem);
}
foreach (var blacklistedItem in _pmcConfig.GlobalLootBlacklist)
{
blacklist.Add(blacklistedItem);
}
foreach (var blacklistedItem in _itemFilterService.GetBlacklistedItems())
{
blacklist.Add(blacklistedItem);
}
foreach (var blacklistedItem in _seasonalEventService.GetInactiveSeasonalEventItems())
{
blacklist.Add(blacklistedItem);
}
return blacklist;
}
/// <summary>
@@ -24,7 +135,52 @@ public class PMCLootGenerator()
/// <returns>Dictionary of string and number</returns>
public Dictionary<string, double> GeneratePMCVestLootPool(string botRole)
{
throw new NotImplementedException();
// Hydrate loot dictionary if empty
if (_vestLootPool is null)
{
var items = _databaseService.GetItems();
var pmcPriceOverrides =
_databaseService.GetBots().Types[botRole.ToLower() == "pmcbear" ? "bear" : "usec"].BotInventory.Items.TacticalVest;
var allowedItemTypeWhitelist = _pmcConfig.VestLoot.Whitelist;
var blacklist = GetLootBlacklist();
var itemsToAdd = items.Where(
(item) =>
allowedItemTypeWhitelist.Contains(item.Value.Parent) &&
_itemHelper.isValidItem(item.Value.Id) &&
!blacklist.Contains(item.Value.Id) &&
!blacklist.Contains(item.Value.Parent) &&
ItemFitsInto2By2Slot(item.Value));
foreach (var (tpl, template) in itemsToAdd)
{
// If pmc has price override, use that. Otherwise, use flea price
if (pmcPriceOverrides.ContainsKey(tpl))
{
_vestLootPool[tpl] = pmcPriceOverrides[tpl];
}
else
{
// Set price of item as its weight
var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES);
_vestLootPool[tpl] = price;
}
}
var highestPrice = _vestLootPool.Max(price => price.Value);
foreach (var (key, _) in _vestLootPool)
{
// Invert price so cheapest has a larger weight
// Times by highest price so most expensive item has weight of 1
_vestLootPool[key] = Math.Round((1 / _vestLootPool[key]) * highestPrice);
}
_weightedRandomHelper.ReduceWeightValues(_vestLootPool);
}
return _vestLootPool;
}
/// <summary>
@@ -35,7 +191,7 @@ public class PMCLootGenerator()
/// <returns>true if it fits</returns>
protected bool ItemFitsInto2By2Slot(TemplateItem item)
{
throw new NotImplementedException();
return item.Properties.Width <= 2 && item.Properties.Height <= 2;
}
/// <summary>
@@ -46,7 +202,11 @@ public class PMCLootGenerator()
/// <returns>true if it fits</returns>
protected bool ItemFitsInto1By2Slot(TemplateItem item)
{
throw new NotImplementedException();
return $"{item.Properties.Width}x{item.Properties.Height}" switch
{
"1x1" or "1x2" or "2x1" => true,
_ => false
};
}
/// <summary>
@@ -56,6 +216,48 @@ public class PMCLootGenerator()
/// <returns>Dictionary of string and number</returns>
public Dictionary<string, double> GeneratePMCBackpackLootPool(string botRole)
{
throw new NotImplementedException();
// Hydrate loot dictionary if empty
if (_backpackLootPool is null)
{
var items = _databaseService.GetItems();
var pmcPriceOverrides =
_databaseService.GetBots().Types[botRole.ToLower() == "pmcbear" ? "bear" : "usec"].BotInventory.Items.Backpack;
var allowedItemTypeWhitelist = _pmcConfig.BackpackLoot.Whitelist;
var blacklist = GetLootBlacklist();
var itemsToAdd = items.Where(
(item) =>
allowedItemTypeWhitelist.Contains(item.Value.Parent) &&
_itemHelper.isValidItem(item.Value.Id) &&
!blacklist.Contains(item.Value.Id) &&
!blacklist.Contains(item.Value.Parent));
foreach (var (tpl, template) in itemsToAdd) {
// If pmc has price override, use that. Otherwise, use flea price
if (pmcPriceOverrides.ContainsKey(tpl))
{
_backpackLootPool[tpl] = pmcPriceOverrides[tpl];
}
else
{
// Set price of item as its weight
var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES);
_backpackLootPool[tpl] = price;
}
}
var highestPrice = _backpackLootPool.Max(price => price.Value);
foreach (var (key, _) in _backpackLootPool) {
// Invert price so cheapest has a larger weight
// Times by highest price so most expensive item has weight of 1
_backpackLootPool[key] = Math.Round((1 / _backpackLootPool[key]) * highestPrice);
}
_weightedRandomHelper.ReduceWeightValues(_backpackLootPool);
}
return _backpackLootPool;
}
}
@@ -1,4 +1,4 @@
using Core.Models.Common;
using Core.Models.Common;
namespace Core.Models.Spt.Config;
@@ -136,7 +136,7 @@ public record Dynamic
[JsonPropertyName("itemPriceMultiplier")]
/** A multipler to apply to individual tpls price just prior to item quality adjustment */
public Dictionary<string, double> ItemPriceMultiplier { get; set; }
public Dictionary<string, double>? ItemPriceMultiplier { get; set; }
[JsonPropertyName("_currencies")]
public string? CurrenciesDescription { get; set; }
+27 -7
View File
@@ -14,9 +14,11 @@ public class ItemFilterService(
ConfigServer _configServer
)
{
protected HashSet<string> _lootableItemBlacklistCache = [];
protected ItemConfig _itemConfig = _configServer.GetConfig<ItemConfig>();
protected HashSet<string>? _lootableItemBlacklistCache;
protected HashSet<string>? _itemBlacklistCache;
/**
* Check if the provided template id is blacklisted in config/item.json/blacklist
* @param tpl template id
@@ -104,12 +106,9 @@ public class ItemFilterService(
public bool IsLootableItemBlacklisted(string itemKey)
{
if (_lootableItemBlacklistCache.Count == 0)
if (_lootableItemBlacklistCache is null)
{
foreach (var item in _itemConfig.LootableItemBlacklist)
{
_lootableItemBlacklistCache.Add(item);
}
HydrateLootableItemBlacklist();
}
return _lootableItemBlacklistCache.Contains(itemKey);
@@ -117,6 +116,27 @@ public class ItemFilterService(
public bool IsItemBlacklisted(string tpl)
{
throw new NotImplementedException();
if (_itemBlacklistCache is null)
{
HydrateBlacklist();
}
return _itemBlacklistCache.Contains(tpl);
}
protected void HydrateLootableItemBlacklist()
{
foreach (var item in _itemConfig.LootableItemBlacklist)
{
_lootableItemBlacklistCache.Add(item);
}
}
protected void HydrateBlacklist()
{
_itemBlacklistCache = [];
foreach (var item in _itemConfig.Blacklist) {
_itemBlacklistCache.Add(item);
}
}
}
+89 -7
View File
@@ -5,6 +5,9 @@ using Core.Models.Eft.Common.Tables;
using Core.Models.Spt.Config;
using Core.Models.Spt.Ragfair;
using Core.Models.Utils;
using Core.Models.Enums;
using System;
using Core.Servers;
namespace Core.Services;
@@ -12,9 +15,15 @@ namespace Core.Services;
public class RagfairPriceService(
ISptLogger<RagfairPriceService> _logger,
HandbookHelper _handbookHelper,
DatabaseService _databaseService
TraderHelper _traderHelper,
PresetHelper _presetHelper,
ItemHelper _itemHelper,
DatabaseService _databaseService,
ConfigServer _configServer
)
{
RagfairConfig _ragfairConfig = _configServer.GetConfig<RagfairConfig>();
protected RagfairServerPrices _prices = new RagfairServerPrices
{ StaticPrices = new Dictionary<string, double>(), DynamicPrices = new Dictionary<string, double>() };
@@ -152,7 +161,80 @@ public class RagfairPriceService(
/// <returns></returns>
public double GetDynamicItemPrice(string itemTemplateId, string desiredCurrency, Item item = null, List<Item> offerItems = null, bool? isPackOffer = null)
{
throw new NotImplementedException();
var isPreset = false;
var price = GetFleaPriceForItem(itemTemplateId);
// Adjust price if below handbook price, based on config.
if (_ragfairConfig.Dynamic.OfferAdjustment.AdjustPriceWhenBelowHandbookPrice)
{
price = AdjustPriceIfBelowHandbook(price, itemTemplateId);
}
// Use trader price if higher, based on config.
if (_ragfairConfig.Dynamic.UseTraderPriceForOffersIfHigher)
{
var traderPrice = _traderHelper.GetHighestSellToTraderPrice(itemTemplateId);
if (traderPrice > price)
{
price = traderPrice;
}
}
// Prices for weapon presets are handled differently.
if (
item?.Upd?.SptPresetId is not null &&
offerItems is not null &&
_presetHelper.IsPresetBaseClass(item.Upd.SptPresetId, BaseClasses.WEAPON)
)
{
price = GetWeaponPresetPrice(item, offerItems, price);
isPreset = true;
}
// Check for existence of manual price adjustment multiplier
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(itemTemplateId, out var multiplier))
{
price *= multiplier;
}
// The quality of the item affects the price + not on the ignore list
if (item is not null && !_ragfairConfig.Dynamic.IgnoreQualityPriceVarianceBlacklist.Contains(itemTemplateId))
{
var qualityModifier = _itemHelper.GetItemQualityModifier(item);
price *= qualityModifier;
}
// Make adjustments for unreasonably priced items.
foreach (var (key, value) in _ragfairConfig.Dynamic.UnreasonableModPrices)
{
if (!_itemHelper.IsOfBaseclass(itemTemplateId, key) || !value.Enabled)
{
continue;
}
price = AdjustUnreasonablePrice(
_databaseService.GetHandbook().Items,
value,
itemTemplateId,
price);
}
// Vary the price based on the type of offer.
var range = GetOfferTypeRangeValues(isPreset, isPackOffer ?? false);
price = RandomiseOfferPrice(price, range);
// Convert to different currency if required.
var roublesId = Money.ROUBLES;
if (desiredCurrency != roublesId)
{
price = _handbookHelper.FromRUB(price, desiredCurrency);
}
if (price < 1)
{
return 1;
}
return price;
}
/// <summary>
@@ -163,11 +245,11 @@ public class RagfairPriceService(
/// <param name="itemTpl">Item being adjusted</param>
/// <param name="price">Current price of item</param>
/// <returns>Adjusted price of item</returns>
protected decimal AdjustUnreasonablePrice(
protected double AdjustUnreasonablePrice(
List<HandbookItem> handbookPrices,
UnreasonableModPrices unreasonableItemChange,
string itemTpl,
decimal price)
double price)
{
throw new NotImplementedException();
}
@@ -189,7 +271,7 @@ public class RagfairPriceService(
/// <param name="itemPrice">price of item</param>
/// <param name="itemTpl">item template Id being checked</param>
/// <returns>adjusted price value in roubles</returns>
protected decimal AdjustPriceIfBelowHandbook(decimal itemPrice, string itemTpl)
protected double AdjustPriceIfBelowHandbook(double itemPrice, string itemTpl)
{
throw new NotImplementedException();
}
@@ -200,7 +282,7 @@ public class RagfairPriceService(
/// <param name="existingPrice">price to alter</param>
/// <param name="rangeValues">min and max to adjust price by</param>
/// <returns>multiplied price</returns>
protected decimal RandomiseOfferPrice(decimal existingPrice, MinMax rangeValues)
protected double RandomiseOfferPrice(double existingPrice, MinMax rangeValues)
{
throw new NotImplementedException();
}
@@ -212,7 +294,7 @@ public class RagfairPriceService(
/// <param name="weaponWithChildren">weapon plus mods</param>
/// <param name="existingPrice">price of existing base weapon</param>
/// <returns>price of weapon in roubles</returns>
protected decimal GetWeaponPresetPrice(Item weaponRootItem, List<Item> weaponWithChildren, decimal existingPrice)
protected double GetWeaponPresetPrice(Item weaponRootItem, List<Item> weaponWithChildren, double existingPrice)
{
throw new NotImplementedException();
}