Files
SPT-Server-Build/Core/Generators/BotLootGenerator.cs
T
2025-01-16 16:38:38 +00:00

594 lines
21 KiB
C#

using Core.Annotations;
using Core.Models.Eft.Common.Tables;
using Core.Models.Enums;
using Core.Models.Spt.Bots;
using Core.Models.Spt.Config;
using Core.Utils;
using Core.Helpers;
using Core.Models.Utils;
using Core.Services;
using Core.Servers;
using Core.Utils.Cloners;
namespace Core.Generators;
[Injectable]
public class BotLootGenerator
{
private readonly ISptLogger<BotLootGenerator> _logger;
private readonly HashUtil _hashUtil;
private readonly RandomUtil _randomUtil;
private readonly ItemHelper _itemHelper;
private readonly InventoryHelper _inventoryHelper;
private readonly DatabaseService _databaseService;
private readonly HandbookHelper _handbookHelper;
private readonly BotGeneratorHelper _botGeneratorHelper;
private readonly BotWeaponGenerator _botWeaponGenerator;
private readonly WeightedRandomHelper _weightedRandomHelper;
private readonly BotHelper _botHelper;
private readonly BotLootCacheService _botLootCacheService;
private readonly LocalisationService _localisationService;
private readonly ConfigServer _configServer;
private readonly ICloner _cloner;
private BotConfig _botConfig;
private PmcConfig _pmcConfig;
public BotLootGenerator(
ISptLogger<BotLootGenerator> logger,
HashUtil hashUtil,
RandomUtil randomUtil,
ItemHelper itemHelper,
InventoryHelper inventoryHelper,
DatabaseService databaseService,
HandbookHelper handbookHelper,
BotGeneratorHelper botGeneratorHelper,
BotWeaponGenerator botWeaponGenerator,
WeightedRandomHelper weightedRandomHelper,
BotHelper botHelper,
BotLootCacheService botLootCacheService,
LocalisationService localisationService,
ConfigServer configServer,
ICloner cloner)
{
_logger = logger;
_hashUtil = hashUtil;
_randomUtil = randomUtil;
_itemHelper = itemHelper;
_inventoryHelper = inventoryHelper;
_databaseService = databaseService;
_handbookHelper = handbookHelper;
_botGeneratorHelper = botGeneratorHelper;
_botWeaponGenerator = botWeaponGenerator;
_weightedRandomHelper = weightedRandomHelper;
_botHelper = botHelper;
_botLootCacheService = botLootCacheService;
_localisationService = localisationService;
_configServer = configServer;
_cloner = cloner;
_botConfig = _configServer.GetConfig<BotConfig>();
_pmcConfig = _configServer.GetConfig<PmcConfig>();
}
/// <summary>
///
/// </summary>
/// <param name="botRole"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public ItemSpawnLimitSettings GetItemSpawnLimitsForBot(string botRole)
{
// Init item limits
Dictionary<string, double> limitsForBotDict = new();
InitItemLimitArray(botRole, limitsForBotDict);
return new() { CurrentLimits = limitsForBotDict, GlobalLimits = GetItemSpawnLimitsForBotType(botRole) };
}
/// <summary>
/// Add loot to bots containers
/// </summary>
/// <param name="sessionId">Session id</param>
/// <param name="botJsonTemplate">Base json db file for the bot having its loot generated</param>
/// <param name="isPmc">Will bot be a pmc</param>
/// <param name="botRole">Role of bot, e.g. asssult</param>
/// <param name="botInventory">Inventory to add loot to</param>
/// <param name="botLevel">Level of bot</param>
public void GenerateLoot(string sessionId, BotType botJsonTemplate, bool isPmc, string botRole, BotBaseInventory botInventory, int botLevel)
{
// Limits on item types to be added as loot
var itemCounts = botJsonTemplate.BotGeneration.Items;
if (
itemCounts.BackpackLoot.Weights is null ||
itemCounts.PocketLoot.Weights is null ||
itemCounts.VestLoot.Weights is null ||
itemCounts.SpecialItems.Weights is null ||
itemCounts.Healing.Weights is null ||
itemCounts.Drugs.Weights is null ||
itemCounts.Food.Weights is null ||
itemCounts.Drink.Weights is null ||
itemCounts.Currency.Weights is null ||
itemCounts.Stims.Weights is null ||
itemCounts.Grenades.Weights is null
)
{
_logger.Warning(_localisationService.GetText("bot-unable_to_generate_bot_loot", botRole));
return;
}
var backpackLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.BackpackLoot.Weights);
var pocketLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.PocketLoot.Weights);
var vestLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.VestLoot.Weights);
var specialLootItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.SpecialItems.Weights);
var healingItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Healing.Weights);
var drugItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Drugs.Weights);
var foodItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Food.Weights);
var drinkItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Drink.Weights);
var currencyItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Currency.Weights);
var stimItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Stims.Weights);
var grenadeCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Grenades.Weights);
// If bot has been flagged as not having loot, set below counts to 0
if (_botConfig.DisableLootOnBotTypes.Contains(botRole.ToLower()))
{
backpackLootCount = 0;
pocketLootCount = 0;
vestLootCount = 0;
currencyItemCount = 0;
}
// Forced pmc healing loot into secure container
if (isPmc && _pmcConfig.ForceHealingItemsIntoSecure)
{
AddForcedMedicalItemsToPmcSecure(botInventory, botRole);
}
var botItemLimits = GetItemSpawnLimitsForBot(botRole);
var containersBotHasAvailable = GetAvailableContainersBotCanStoreItemsIn(botInventory);
// This set is passed as a reference to fill up the containers that are already full, this alleviates
// generation of the bots by avoiding checking the slots of containers we already know are full
var containersIdFull = new List<string>();
// Special items
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Special, botJsonTemplate),
containersBotHasAvailable,
specialLootItemCount,
botInventory,
botRole,
botItemLimits,
containersIdFull: containersIdFull
);
// Healing items / Meds
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.HealingItems, botJsonTemplate),
containersBotHasAvailable,
healingItemCount,
botInventory,
botRole,
null,
0,
isPmc,
containersIdFull
);
// Drugs
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrugItems, botJsonTemplate),
containersBotHasAvailable,
drugItemCount,
botInventory,
botRole,
null,
0,
isPmc,
containersIdFull
);
// Food
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.FoodItems, botJsonTemplate),
containersBotHasAvailable,
foodItemCount,
botInventory,
botRole,
null,
0,
isPmc,
containersIdFull
);
// Drink
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrinkItems, botJsonTemplate),
containersBotHasAvailable,
drinkItemCount,
botInventory,
botRole,
null,
0,
isPmc,
containersIdFull
);
// Currency
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.CurrencyItems, botJsonTemplate),
containersBotHasAvailable,
currencyItemCount,
botInventory,
botRole,
null,
0,
isPmc,
containersIdFull
);
// Stims
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.StimItems, botJsonTemplate),
containersBotHasAvailable,
stimItemCount,
botInventory,
botRole,
botItemLimits,
0,
isPmc,
containersIdFull
);
// Grenades
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.GrenadeItems, botJsonTemplate),
[EquipmentSlots.Pockets, EquipmentSlots.TacticalVest], // Can't use containersBotHasEquipped as we don't want grenades added to backpack
grenadeCount,
botInventory,
botRole,
null,
0,
isPmc,
containersIdFull
);
var itemPriceLimits = GetSingleItemLootPriceLimits(botLevel, isPmc);
// Backpack - generate loot if they have one
if (containersBotHasAvailable.Contains(EquipmentSlots.Backpack))
{
// Add randomly generated weapon to PMC backpacks
if (isPmc && _randomUtil.GetChance100(_pmcConfig.LooseWeaponInBackpackChancePercent))
{
AddLooseWeaponsToInventorySlot(
sessionId,
botInventory,
EquipmentSlots.Backpack,
botJsonTemplate.BotInventory,
botJsonTemplate.BotChances.WeaponModsChances,
botRole,
isPmc,
botLevel,
containersIdFull
);
}
var backpackLootRoubleTotal = GetBackpackRoubleTotalByLevel(botLevel, isPmc);
AddLootFromPool(
_botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.Backpack,
botJsonTemplate,
itemPriceLimits?.Backpack
),
[EquipmentSlots.Backpack],
backpackLootCount,
botInventory,
botRole,
botItemLimits,
backpackLootRoubleTotal ?? 0,
isPmc,
containersIdFull
);
}
// TacticalVest - generate loot if they have one
if (containersBotHasAvailable.Contains(EquipmentSlots.TacticalVest))
{
// Vest
AddLootFromPool(
_botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.Vest,
botJsonTemplate,
itemPriceLimits?.Vest
),
[EquipmentSlots.TacticalVest],
vestLootCount,
botInventory,
botRole,
botItemLimits,
_pmcConfig.MaxVestLootTotalRub,
isPmc,
containersIdFull
);
}
// Pockets
AddLootFromPool(
_botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.Pocket,
botJsonTemplate,
itemPriceLimits?.Pocket
),
[EquipmentSlots.Pockets],
pocketLootCount,
botInventory,
botRole,
botItemLimits,
_pmcConfig.MaxPocketLootTotalRub,
isPmc,
containersIdFull
);
// Secure
// only add if not a pmc or is pmc and flag is true
if (!isPmc || (isPmc && _pmcConfig.AddSecureContainerLootFromBotConfig))
{
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Secure, botJsonTemplate),
[EquipmentSlots.SecuredContainer],
50,
botInventory,
botRole,
null,
-1,
isPmc,
containersIdFull
);
}
}
private MinMaxLootItemValue? GetSingleItemLootPriceLimits(int botLevel, bool isPmc)
{
// TODO - extend to other bot types
if (isPmc)
{
var matchingValue = _pmcConfig.LootItemLimitsRub.FirstOrDefault(
(minMaxValue) => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
);
return matchingValue;
}
return null;
}
/// <summary>
/// Gets the rouble cost total for loot in a bots backpack by the bots levl
/// Will return 0 for non PMCs
/// </summary>
/// <param name="botLevel">Bots level</param>
/// <param name="isPmc">Is the bot a PMC</param>
/// <returns>int</returns>
public double? GetBackpackRoubleTotalByLevel(int botLevel, bool isPmc)
{
if (isPmc)
{
var matchingValue = _pmcConfig.MaxBackpackLootTotalRub.FirstOrDefault(
(minMaxValue) => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
);
return matchingValue?.Value;
}
return 0;
}
/// <summary>
///
/// </summary>
/// <param name="botInventory"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public List<EquipmentSlots> GetAvailableContainersBotCanStoreItemsIn(BotBaseInventory botInventory)
{
List<EquipmentSlots> result = [EquipmentSlots.Pockets];
if (botInventory.Items.Any((item) => item.SlotId == EquipmentSlots.TacticalVest.ToString()))
{
result.Add(EquipmentSlots.TacticalVest);
}
if (botInventory.Items.Any((item) => item.SlotId == EquipmentSlots.Backpack.ToString()))
{
result.Add(EquipmentSlots.Backpack);
}
return result;
}
/// <summary>
/// Force healing items onto bot to ensure they can heal in-raid
/// </summary>
/// <param name="botInventory">Inventory to add items to</param>
/// <param name="botRole">Role of bot (pmcBEAR/pmcUSEC)</param>
public void AddForcedMedicalItemsToPmcSecure(BotBaseInventory botInventory, string botRole)
{
// surv12
AddLootFromPool(
new() { { "5d02797c86f774203f38e30a", 1 } },
[EquipmentSlots.SecuredContainer],
1,
botInventory,
botRole,
null,
0,
true
);
// AFAK
AddLootFromPool(
new() { { "60098ad7c2240c0fe85c570a", 1 } },
[EquipmentSlots.SecuredContainer],
10,
botInventory,
botRole,
null,
0,
true
);
}
/// <summary>
/// Take random items from a pool and add to an inventory until totalItemCount or totalValueLimit or space limit is reached
/// </summary>
/// <param name="pool">Pool of items to pick from with weight</param>
/// <param name="equipmentSlots">What equipment slot will the loot items be added to</param>
/// <param name="totalItemCount">Max count of items to add</param>
/// <param name="inventoryToAddItemsTo">Bot inventory loot will be added to</param>
/// <param name="botRole">Role of the bot loot is being generated for (assault/pmcbot)</param>
/// <param name="itemSpawnLimits">Item spawn limits the bot must adhere to</param>
/// <param name="containersIdFull"></param>
/// <param name="totalValueLimitRub">Total value of loot allowed in roubles</param>
/// <param name="isPmc">Is bot being generated for a pmc</param>
/// <exception cref="NotImplementedException"></exception>
public void AddLootFromPool
(
Dictionary<string, int> pool,
List<EquipmentSlots> equipmentSlots,
double totalItemCount,
BotBaseInventory inventoryToAddItemsTo, // TODO: type for containersIdFull was Set<string>
string botRole,
ItemSpawnLimitSettings itemSpawnLimits,
double totalValueLimitRub = 0,
bool isPmc = false,
List<string> containersIdFull = null
)
{
throw new NotImplementedException();
}
/// <summary>
///
/// </summary>
/// <param name="walletId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public List<List<Item>> CrateWalletLoot(string walletId)
{
throw new NotImplementedException();
}
/// <summary>
/// Some items need child items to function, add them to the itemToAddChildrenTo array
/// </summary>
/// <param name="itemToAddTemplate">Db template of item to check</param>
/// <param name="itemToAddChildrenTo">Item to add children to</param>
/// <param name="isPmc">Is the item being generated for a pmc (affects money/ammo stack sizes)</param>
/// <param name="botRole">role bot has that owns item</param>
public void AddRequiredChildItemsToParent(TemplateItem itemToAddTemplate, List<Item> itemToAddChildrenTo, bool isPmc, string botRole)
{
throw new NotImplementedException();
}
/// <summary>
/// Add generated weapons to inventory as loot
/// </summary>
/// <param name="sessionId"></param>
/// <param name="botInventory">inventory to add preset to</param>
/// <param name="equipmentSlot">slot to place the preset in (backpack)</param>
/// <param name="templateInventory">bots template, assault.json</param>
/// <param name="modsChances">chances for mods to spawn on weapon</param>
/// <param name="botRole">bots role .e.g. pmcBot</param>
/// <param name="isPmc">are we generating for a pmc</param>
/// <param name="botLevel"></param>
/// <param name="containersIdFull"></param>
public void AddLooseWeaponsToInventorySlot(string sessionId,
BotBaseInventory botInventory,
EquipmentSlots equipmentSlot,
BotTypeInventory templateInventory,
Dictionary<string, double> modsChances,
string botRole,
bool isPmc,
int botLevel,
List<string>? containersIdFull) // TODO: type for containersIdFull was Set<string>
{
throw new NotImplementedException();
}
/// <summary>
/// Hydrate item limit array to contain items that have a limit for a specific bot type
/// All values are set to 0
/// </summary>
/// <param name="botRole">Role the bot has</param>
/// <param name="limitCount"></param>
public void InitItemLimitArray(string botRole, Dictionary<string, double> limitCount)
{
throw new NotImplementedException();
}
/// <summary>
/// Check if an item has reached its bot-specific spawn limit
/// </summary>
/// <param name="itemTemplate">Item we check to see if its reached spawn limit</param>
/// <param name="botRole">Bot type</param>
/// <param name="itemSpawnLimits"></param>
/// <returns>true if item has reached spawn limit</returns>
public bool ItemHasReachedSpawnLimit(TemplateItem itemTemplate, string botRole, ItemSpawnLimitSettings itemSpawnLimits)
{
throw new NotImplementedException();
}
/// <summary>
/// Randomise the stack size of a money object, uses different values for pmc or scavs
/// </summary>
/// <param name="botRole">Role bot has that has money stack</param>
/// <param name="itemTemplate">item details from db</param>
/// <param name="moneyItem">Money item to randomise</param>
public void RandomiseMoneyStackSize(string botRole, TemplateItem itemTemplate, Item moneyItem)
{
throw new NotImplementedException();
}
/// <summary>
/// Randomise the size of an ammo stack
/// </summary>
/// <param name="isPmc">Is ammo on a PMC bot</param>
/// <param name="itemTemplate">item details from db</param>
/// <param name="ammoItem">Ammo item to randomise</param>
public void RandomiseAmmoStackSize(bool isPmc, TemplateItem itemTemplate, Item ammoItem)
{
throw new NotImplementedException();
}
/// <summary>
/// Get spawn limits for a specific bot type from bot.json config
/// If no limit found for a non pmc bot, fall back to defaults
/// </summary>
/// <param name="botRole">what role does the bot have</param>
/// <returns>Dictionary of tplIds and limit</returns>
public Dictionary<string, double> GetItemSpawnLimitsForBotType(string botRole)
{
throw new NotImplementedException();
}
/// <summary>
/// Get the parentId or tplId of item inside spawnLimits object if it exists
/// </summary>
/// <param name="itemTemplate">item we want to look for in spawn limits</param>
/// <param name="spawnLimits">Limits to check for item</param>
/// <returns>id as string, otherwise undefined</returns>
public string? GetMatchingIdFromSpawnLimits(TemplateItem itemTemplate, Dictionary<string, int> spawnLimits)
{
throw new NotImplementedException();
}
}