Merge branch 'main' of https://github.com/sp-tarkov/server-csharp
This commit is contained in:
@@ -315,7 +315,7 @@ public class BotController
|
||||
_matchBotDetailsCacheService.CacheBot(botToCache);
|
||||
}
|
||||
|
||||
private void UpdateBotGenerationDetailsToRandomBoss(BotGenerationDetails botGenerationDetails, Dictionary<string, int> bossesToConvertToWeights)
|
||||
private void UpdateBotGenerationDetailsToRandomBoss(BotGenerationDetails botGenerationDetails, Dictionary<string, double> bossesToConvertToWeights)
|
||||
{
|
||||
// Seems Actual bosses have the same Brain issues like PMC gaining Boss Brains We can't use all bosses
|
||||
botGenerationDetails.Role = _weightedRandomHelper.GetWeightedValue(bossesToConvertToWeights);
|
||||
|
||||
@@ -146,7 +146,7 @@ public class BotGenerator
|
||||
WishList = bot.WishList,
|
||||
MoneyTransferLimitData = bot.MoneyTransferLimitData,
|
||||
IsPmc = bot.IsPmc,
|
||||
Prestige = new Prestige()
|
||||
Prestige = new Dictionary<string, long>()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -435,9 +435,9 @@ public class BotGenerator
|
||||
public void AddAdditionalPocketLootWeightsForUnheardBot(BotType botJsonTemplate)
|
||||
{
|
||||
// Adjust pocket loot weights to allow for 5 or 6 items
|
||||
var pocketWeights = botJsonTemplate.BotGeneration.Items["pocketLoot"].Weights;
|
||||
pocketWeights["5"] = 1;
|
||||
pocketWeights["6"] = 1;
|
||||
var pocketWeights = botJsonTemplate.BotGeneration.Items.PocketLoot.Weights;
|
||||
pocketWeights[5] = 1;
|
||||
pocketWeights[6] = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -496,9 +496,9 @@ public class BotGenerator
|
||||
var chosenBodyTemplate = _databaseService.GetCustomization()[bot.Customization.Body];
|
||||
|
||||
// Some bodies have matching hands, look up body to see if this is the case
|
||||
var chosenBody = bodyGlobalDictDb[chosenBodyTemplate?.Name.Trim()];
|
||||
bot.Customization.Hands = chosenBody?.IsNotRandom ?? false
|
||||
? chosenBody.Hands // Has fixed hands for chosen body, update to match
|
||||
var chosenBody = bodyGlobalDictDb.FirstOrDefault(c => c.Key == chosenBodyTemplate?.Name.Trim());
|
||||
bot.Customization.Hands = chosenBody.Value.IsNotRandom ?? false
|
||||
? chosenBody.Value.Hands // Has fixed hands for chosen body, update to match
|
||||
: _weightedRandomHelper.GetWeightedValue<string>(appearance.Hands); // Hands can be random, choose any from weighted dict
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,8 @@ public class BotInventoryGenerator
|
||||
var questStashItemsId = _hashUtil.Generate();
|
||||
var sortingTableId = _hashUtil.Generate();
|
||||
|
||||
return new BotBaseInventory{
|
||||
return new BotBaseInventory
|
||||
{
|
||||
Items =
|
||||
[
|
||||
new() { Id = equipmentId, Template = ItemTpl.INVENTORY_DEFAULT },
|
||||
@@ -181,7 +182,8 @@ public class BotInventoryGenerator
|
||||
BotBaseInventory botInventory, int botLevel, string chosenGameVersion, GetRaidConfigurationRequestData raidConfig)
|
||||
{
|
||||
// These will be handled later
|
||||
var excludedSlots = new List<EquipmentSlots>(){
|
||||
var excludedSlots = new List<EquipmentSlots>()
|
||||
{
|
||||
EquipmentSlots.Pockets,
|
||||
EquipmentSlots.FirstPrimaryWeapon,
|
||||
EquipmentSlots.SecondPrimaryWeapon,
|
||||
@@ -203,10 +205,11 @@ public class BotInventoryGenerator
|
||||
_weatherHelper.IsNightTime(raidConfig.TimeVariant)
|
||||
)
|
||||
{
|
||||
foreach (var equipmentSlotKvP in (randomistionDetails.NighttimeChanges.EquipmentModsModifiers)) {
|
||||
foreach (var equipmentSlotKvP in (randomistionDetails.NighttimeChanges.EquipmentModsModifiers))
|
||||
{
|
||||
// Never let mod chance go outside of 0 - 100
|
||||
randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] = Math.Min(
|
||||
Math.Max( randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] + equipmentSlotKvP.Value, 0), 100);
|
||||
Math.Max(randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] + equipmentSlotKvP.Value, 0), 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,14 +221,16 @@ public class BotInventoryGenerator
|
||||
// Iterate over all equipment slots of bot, do it in specifc order to reduce conflicts
|
||||
// e.g. ArmorVest should be generated after TactivalVest
|
||||
// or FACE_COVER before HEADWEAR
|
||||
foreach (var equipmentSlotKvP in templateInventory.Equipment) {
|
||||
foreach (var equipmentSlotKvP in templateInventory.Equipment)
|
||||
{
|
||||
// Skip some slots as they need to be done in a specific order + with specific parameter values
|
||||
// e.g. Weapons
|
||||
if (excludedSlots.Contains(equipmentSlotKvP.Key)) {
|
||||
if (excludedSlots.Contains(equipmentSlotKvP.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
GenerateEquipment(new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = equipmentSlotKvP.Key,
|
||||
RootEquipmentPool = equipmentSlotKvP.Value,
|
||||
@@ -240,17 +245,17 @@ public class BotInventoryGenerator
|
||||
}
|
||||
|
||||
// Generate below in specific order
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
GenerateEquipment(new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Pockets,
|
||||
// Unheard profiles have unique sized pockets, TODO - handle this somewhere else in a better way
|
||||
RootEquipmentPool =
|
||||
chosenGameVersion == GameEditions.UNHEARD
|
||||
? new Dictionary<string, double>{ [ItemTpl.POCKETS_1X4_TUE] = 1 }
|
||||
? new Dictionary<string, double> { [ItemTpl.POCKETS_1X4_TUE] = 1 }
|
||||
: templateInventory.Equipment[EquipmentSlots.Pockets],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData{ Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
@@ -258,7 +263,7 @@ public class BotInventoryGenerator
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
GenerateEquipment(new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.FaceCover,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.FaceCover],
|
||||
@@ -271,7 +276,7 @@ public class BotInventoryGenerator
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
GenerateEquipment(new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Headwear,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Headwear],
|
||||
@@ -297,7 +302,7 @@ public class BotInventoryGenerator
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
var hasArmorVest = GenerateEquipment( new GenerateEquipmentProperties
|
||||
var hasArmorVest = GenerateEquipment(new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.ArmorVest,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.ArmorVest],
|
||||
@@ -311,23 +316,26 @@ public class BotInventoryGenerator
|
||||
});
|
||||
|
||||
// Bot has no armor vest and flagged to be forced to wear armored rig in this event
|
||||
if (botEquipConfig.ForceOnlyArmoredRigWhenNoArmor.GetValueOrDefault(false) && !hasArmorVest) {
|
||||
if (botEquipConfig.ForceOnlyArmoredRigWhenNoArmor.GetValueOrDefault(false) && !hasArmorVest)
|
||||
{
|
||||
// Filter rigs down to only those with armor
|
||||
FilterRigsToThoseWithProtection(templateInventory.Equipment, botRole);
|
||||
}
|
||||
|
||||
// Optimisation - Remove armored rigs from pool
|
||||
if (hasArmorVest) {
|
||||
if (hasArmorVest)
|
||||
{
|
||||
// Filter rigs down to only those with armor
|
||||
FilterRigsToThoseWithoutProtection(templateInventory.Equipment, botRole);
|
||||
}
|
||||
|
||||
// Bot is flagged as always needing a vest
|
||||
if (botEquipConfig.ForceRigWhenNoVest.GetValueOrDefault(false) && !hasArmorVest) {
|
||||
if (botEquipConfig.ForceRigWhenNoVest.GetValueOrDefault(false) && !hasArmorVest)
|
||||
{
|
||||
wornItemChances.EquipmentChances["TacticalVest"] = 100;
|
||||
}
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
GenerateEquipment(new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Earpiece,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Earpiece],
|
||||
@@ -348,12 +356,17 @@ public class BotInventoryGenerator
|
||||
/// <param name="botRole">Role of bot vests are being filtered for</param>
|
||||
public void FilterRigsToThoseWithProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void FilterRigsToThoseWithoutProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole, bool allowEmptyResult = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var tacVestsWithArmor = templateEquipment[EquipmentSlots.TacticalVest].Where(kvp => _itemHelper.ItemHasSlots(kvp.Key))
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
|
||||
if (tacVestsWithArmor.Count() == 0)
|
||||
{
|
||||
_logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
templateEquipment[EquipmentSlots.TacticalVest] = tacVestsWithArmor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -362,9 +375,20 @@ public class BotInventoryGenerator
|
||||
/// <param name="templateEquipment">Equpiment to filter TacticalVest of</param>
|
||||
/// <param name="botRole">Role of bot vests are being filtered for</param>
|
||||
/// <param name="allowEmptyRequest">Should the function return all rigs when 0 unarmored are found</param>
|
||||
public void FilterRigsTothoseWithoutProtection(Equipment templateEquipment, string botRole, bool allowEmptyRequest = false)
|
||||
public void FilterRigsToThoseWithoutProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole,
|
||||
bool allowEmptyResult = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var tacVestsWithoutArmor = templateEquipment[EquipmentSlots.TacticalVest].Where(kvp => !_itemHelper.ItemHasSlots(kvp.Key))
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
|
||||
if (!allowEmptyResult && tacVestsWithoutArmor.Count() == 0)
|
||||
{
|
||||
_logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
templateEquipment[EquipmentSlots.TacticalVest] = tacVestsWithoutArmor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -375,7 +399,129 @@ public class BotInventoryGenerator
|
||||
public bool GenerateEquipment(GenerateEquipmentProperties settings)
|
||||
{
|
||||
_logger.Error("NOT IMPLEMENTED - GenerateEquipment");
|
||||
return true;
|
||||
List<string> slotsToCheck = [EquipmentSlots.Pockets.ToString(), EquipmentSlots.SecuredContainer.ToString()];
|
||||
double? spawnChance = slotsToCheck.Contains(settings.RootEquipmentSlot.ToString())
|
||||
? 100
|
||||
: settings.SpawnChances.EquipmentChances[settings.RootEquipmentSlot.ToString()];
|
||||
|
||||
if (spawnChance is null)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("bot-no_spawn_chance_defined_for_equipment_slot",
|
||||
settings.RootEquipmentSlot));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Roll dice on equipment item
|
||||
var shouldSpawn = _randomUtil.GetChance100(spawnChance ?? 0);
|
||||
if (shouldSpawn && settings.RootEquipmentPool.Count() == 0)
|
||||
{
|
||||
TemplateItem pickedItemDb = new TemplateItem();
|
||||
var found = false;
|
||||
|
||||
// Limit attempts to find a compatible item as its expensive to check them all
|
||||
var maxAttempts = Math.Round(settings.RootEquipmentPool.Count() * 0.75); // Roughly 75% of pool size
|
||||
var attempts = 0;
|
||||
while (!found)
|
||||
{
|
||||
if (settings.RootEquipmentPool.Count() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var chosenItemTpl = _weightedRandomHelper.GetWeightedValue<string>(settings.RootEquipmentPool);
|
||||
var dbResult = _itemHelper.GetItem(chosenItemTpl);
|
||||
|
||||
if (!dbResult.Key)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("bot-missing_item_template", chosenItemTpl));
|
||||
_logger.Debug($"EquipmentSlot-> {settings.RootEquipmentSlot}");
|
||||
|
||||
// Remove picked item
|
||||
settings.RootEquipmentPool.Remove(chosenItemTpl);
|
||||
|
||||
attempts++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is the chosen item compatible with other items equipped
|
||||
var compatibilityResult = _botGeneratorHelper.IsItemIncompatibleWithCurrentItems(
|
||||
settings.Inventory.Items,
|
||||
chosenItemTpl,
|
||||
settings.RootEquipmentSlot.ToString()
|
||||
);
|
||||
if (compatibilityResult.Incompatible ?? false)
|
||||
{
|
||||
// Tried x different items that failed, stop
|
||||
if (attempts > maxAttempts)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove picked item from pool
|
||||
settings.RootEquipmentPool.Remove(chosenItemTpl);
|
||||
|
||||
// Increment times tried
|
||||
attempts++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Success
|
||||
found = true;
|
||||
pickedItemDb = dbResult.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Create root item
|
||||
var id = _hashUtil.Generate();
|
||||
Item item = new()
|
||||
{
|
||||
Id = id,
|
||||
Template = pickedItemDb.Id,
|
||||
ParentId = settings.Inventory.Equipment,
|
||||
SlotId = settings.RootEquipmentSlot.ToString(),
|
||||
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(pickedItemDb, settings.BotData.Role)
|
||||
};
|
||||
|
||||
var botEquipBlacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
|
||||
settings.BotData.EquipmentRole,
|
||||
(double)settings.GeneratingPlayerLevel
|
||||
);
|
||||
|
||||
// Edge case: Filter the armor items mod pool if bot exists in config dict + config has armor slot
|
||||
if ((_botConfig.Equipment[settings.BotData.EquipmentRole] is not null) &&
|
||||
(settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString())))
|
||||
{
|
||||
// Filter out mods from relevant blacklist
|
||||
settings.ModPool[pickedItemDb.Id] = GetFilteredDynamicModsForItem(
|
||||
pickedItemDb.Id,
|
||||
botEquipBlacklist.Equipment
|
||||
);
|
||||
}
|
||||
|
||||
// Does item have slots for sub-mods to be inserted into
|
||||
if (pickedItemDb.Properties.Slots?.Count() > 0 && (settings.GenerateModsBlacklist.Contains(pickedItemDb.Id)))
|
||||
{
|
||||
var childItemsToAdd = _botEquipmentModGenerator.GenerateModsForEquipment(
|
||||
[item],
|
||||
id,
|
||||
pickedItemDb,
|
||||
settings,
|
||||
botEquipBlacklist
|
||||
);
|
||||
settings.Inventory.Items.AddRange(childItemsToAdd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No slots, add root item only
|
||||
settings.Inventory.Items.Add(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -386,7 +532,19 @@ public class BotInventoryGenerator
|
||||
/// <returns>Filtered pool of mods</returns>
|
||||
public Dictionary<string, List<string>> GetFilteredDynamicModsForItem(string itemTpl, Dictionary<string, List<string>> equipmentBlacklist)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var modPool = _botEquipmentModPoolService.GetModsForGearSlot(itemTpl);
|
||||
foreach (var modSlot in modPool.Keys ?? Enumerable.Empty<string>())
|
||||
{
|
||||
var blacklistedMods = equipmentBlacklist[modSlot] ?? [];
|
||||
var filteredMods = modPool[modSlot].Where((slotName) => !blacklistedMods.Contains(slotName));
|
||||
|
||||
if (filteredMods.Count() > 0)
|
||||
{
|
||||
modPool[modSlot] = filteredMods.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
return modPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -401,10 +559,27 @@ public class BotInventoryGenerator
|
||||
/// <param name="itemGenerationLimitsMinMax">Limits for items the bot can have</param>
|
||||
/// <param name="botLevel">level of bot having weapon generated</param>
|
||||
public void GenerateAndAddWeaponsToBot(BotTypeInventory templateInventory, Chances equipmentChances, string sessionId, BotBaseInventory botInventory,
|
||||
string botRole,
|
||||
bool isPmc, Generation itemGenerationLimitsMinMax, int botLevel)
|
||||
string botRole, bool isPmc, Generation itemGenerationLimitsMinMax, int botLevel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var weaponSlotsToFill = GetDesiredWeaponsForBot(equipmentChances);
|
||||
foreach (var weaponSlot in weaponSlotsToFill)
|
||||
{
|
||||
// Add weapon to bot if true and bot json has something to put into the slot
|
||||
if (weaponSlot.ShouldSpawn && templateInventory.Equipment[weaponSlot.Slot].Any())
|
||||
{
|
||||
AddWeaponAndMagazinesToInventory(
|
||||
sessionId,
|
||||
weaponSlot,
|
||||
templateInventory,
|
||||
botInventory,
|
||||
equipmentChances,
|
||||
botRole,
|
||||
isPmc,
|
||||
itemGenerationLimitsMinMax,
|
||||
botLevel
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -412,9 +587,30 @@ public class BotInventoryGenerator
|
||||
/// </summary>
|
||||
/// <param name="equipmentChances">Chances bot has certain equipment</param>
|
||||
/// <returns>What slots bot should have weapons generated for</returns>
|
||||
public object GetDesiredWeaponsForBot(Chances equipmentChances) // TODO: Type fuckery { slot: EquipmentSlots; shouldSpawn: boolean }[]
|
||||
public List<DesiredWeapons> GetDesiredWeaponsForBot(Chances equipmentChances) // TODO: Type fuckery { slot: EquipmentSlots; shouldSpawn: boolean }[]
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var shouldSpawnPrimary = _randomUtil.GetChance100(equipmentChances.EquipmentChances["FirstPrimaryWeapon"]);
|
||||
return
|
||||
[
|
||||
new()
|
||||
{
|
||||
Slot = EquipmentSlots.FirstPrimaryWeapon, ShouldSpawn = shouldSpawnPrimary
|
||||
},
|
||||
new()
|
||||
{
|
||||
Slot = EquipmentSlots.SecondPrimaryWeapon,
|
||||
ShouldSpawn = shouldSpawnPrimary
|
||||
? _randomUtil.GetChance100(equipmentChances.EquipmentChances["SecondPrimaryWeapon"])
|
||||
: false
|
||||
},
|
||||
new()
|
||||
{
|
||||
Slot = EquipmentSlots.Holster,
|
||||
ShouldSpawn = shouldSpawnPrimary
|
||||
? _randomUtil.GetChance100(equipmentChances.EquipmentChances["Holster"]) // Primary weapon = roll for chance at pistol
|
||||
: true // No primary = force pistol
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -429,10 +625,34 @@ public class BotInventoryGenerator
|
||||
/// <param name="isPmc">Is the bot being generated as a pmc</param>
|
||||
/// <param name="itemGenerationWeights"></param>
|
||||
/// <param name="botLevel"></param>
|
||||
public void AddWeaponAndMagazineToInventory(string sessionId, object weaponSlot, BotBaseInventory templateInventory, BotBaseInventory botInventory,
|
||||
public void AddWeaponAndMagazinesToInventory(string sessionId, DesiredWeapons weaponSlot, BotTypeInventory templateInventory, BotBaseInventory botInventory,
|
||||
Chances equipmentChances, string botRole,
|
||||
bool isPmc, Generation itemGenerationWeights, int botLevel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var generatedweapon = _botWeaponGenerator.GenerateRandomWeapon(
|
||||
sessionId,
|
||||
weaponSlot.Slot.ToString(),
|
||||
templateInventory,
|
||||
botInventory.Equipment,
|
||||
equipmentChances.WeaponModsChances,
|
||||
botRole,
|
||||
isPmc,
|
||||
botLevel
|
||||
);
|
||||
|
||||
botInventory.Items.AddRange(generatedweapon.Weapon);
|
||||
|
||||
_botWeaponGenerator.AddExtraMagazinesToInventory(
|
||||
generatedweapon,
|
||||
itemGenerationWeights.Items.Magazines,
|
||||
botInventory,
|
||||
botRole);
|
||||
}
|
||||
}
|
||||
|
||||
public class DesiredWeapons
|
||||
{
|
||||
public EquipmentSlots Slot { get; set; }
|
||||
|
||||
public bool ShouldSpawn { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,19 +1,74 @@
|
||||
using Core.Annotations;
|
||||
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.Services;
|
||||
using Core.Servers;
|
||||
using Core.Utils.Cloners;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
[Injectable]
|
||||
public class BotLootGenerator
|
||||
{
|
||||
private readonly ILogger _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()
|
||||
public BotLootGenerator(
|
||||
ILogger 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>
|
||||
@@ -38,7 +93,261 @@ public class BotLootGenerator
|
||||
/// <param name="botLevel">Level of bot</param>
|
||||
public void GenerateLoot(string sessionId, BotType botJsonTemplate, bool isPmc, string botRole, BotBaseInventory botInventory, int botLevel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// 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<int>(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);
|
||||
|
||||
// Healing items / Meds
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.HealingItems, botJsonTemplate),
|
||||
containersBotHasAvailable,
|
||||
healingItemCount,
|
||||
botInventory,
|
||||
botRole,
|
||||
null,
|
||||
containersIdFull,
|
||||
0,
|
||||
isPmc);
|
||||
|
||||
// Drugs
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrugItems, botJsonTemplate),
|
||||
containersBotHasAvailable,
|
||||
drugItemCount,
|
||||
botInventory,
|
||||
botRole,
|
||||
null,
|
||||
containersIdFull,
|
||||
0,
|
||||
isPmc);
|
||||
|
||||
// Food
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.FoodItems, botJsonTemplate),
|
||||
containersBotHasAvailable,
|
||||
foodItemCount,
|
||||
botInventory,
|
||||
botRole,
|
||||
null,
|
||||
containersIdFull,
|
||||
0,
|
||||
isPmc);
|
||||
|
||||
// Drink
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrinkItems, botJsonTemplate),
|
||||
containersBotHasAvailable,
|
||||
drinkItemCount,
|
||||
botInventory,
|
||||
botRole,
|
||||
null,
|
||||
containersIdFull,
|
||||
0,
|
||||
isPmc);
|
||||
|
||||
// Currency
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.CurrencyItems, botJsonTemplate),
|
||||
containersBotHasAvailable,
|
||||
currencyItemCount,
|
||||
botInventory,
|
||||
botRole,
|
||||
null,
|
||||
containersIdFull,
|
||||
0,
|
||||
isPmc);
|
||||
|
||||
// Stims
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.StimItems, botJsonTemplate),
|
||||
containersBotHasAvailable,
|
||||
stimItemCount,
|
||||
botInventory,
|
||||
botRole,
|
||||
botItemLimits,
|
||||
containersIdFull,
|
||||
0,
|
||||
isPmc);
|
||||
|
||||
// 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,
|
||||
containersIdFull,
|
||||
0,
|
||||
isPmc);
|
||||
|
||||
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,
|
||||
containersIdFull,
|
||||
backpackLootRoubleTotal,
|
||||
isPmc);
|
||||
}
|
||||
|
||||
// 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,
|
||||
containersIdFull,
|
||||
_pmcConfig.MaxVestLootTotalRub,
|
||||
isPmc);
|
||||
}
|
||||
|
||||
// Pockets
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.Pocket,
|
||||
botJsonTemplate,
|
||||
itemPriceLimits?.Pocket),
|
||||
[EquipmentSlots.Pockets],
|
||||
pocketLootCount,
|
||||
botInventory,
|
||||
botRole,
|
||||
botItemLimits,
|
||||
containersIdFull,
|
||||
_pmcConfig.MaxPocketLootTotalRub,
|
||||
isPmc);
|
||||
|
||||
// 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,
|
||||
containersIdFull,
|
||||
- 1,
|
||||
isPmc);
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -87,9 +396,16 @@ public class BotLootGenerator
|
||||
/// <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<string> equipmentSlots, int totalItemCount,
|
||||
public void AddLootFromPool(
|
||||
Dictionary<string, int> pool,
|
||||
List<EquipmentSlots> equipmentSlots,
|
||||
int totalItemCount,
|
||||
BotBaseInventory inventoryToAddItemsTo, // TODO: type for containersIdFull was Set<string>
|
||||
string botRole, ItemSpawnLimitSettings? itemSpawnLimits, List<string> containersIdFull, int totalValueLimitRub = 0, bool isPmc = false)
|
||||
string botRole,
|
||||
ItemSpawnLimitSettings itemSpawnLimits,
|
||||
List<string> containersIdFull,
|
||||
int totalValueLimitRub = 0,
|
||||
bool isPmc = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -129,9 +445,15 @@ public class BotLootGenerator
|
||||
/// <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, string equipmentSlot,
|
||||
BotBaseInventory templateInventory, // TODO: type for containersIdFull was Set<string>
|
||||
Dictionary<string, double> modsChances, string botRole, bool isPmc, int botLevel, List<string>? containersIdFull)
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class BotWeaponGenerator
|
||||
/// <param name="isPmc">Is weapon generated for a pmc</param>
|
||||
/// <param name="botLevel"></param>
|
||||
/// <returns>GenerateWeaponResult object</returns>
|
||||
public GenerateWeaponResult GenerateRandomWeapon(string sessionId, string equipmentSlot, BotBaseInventory botTemplateInventory, string weaponParentId,
|
||||
public GenerateWeaponResult GenerateRandomWeapon(string sessionId, string equipmentSlot, BotTypeInventory botTemplateInventory, string weaponParentId,
|
||||
Dictionary<string, double> modChances, string botRole, bool isPmc, int botLevel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -41,7 +41,7 @@ public class BotWeaponGenerator
|
||||
/// <param name="equipmentSlot">Primary/secondary/holster</param>
|
||||
/// <param name="botTemplateInventory">e.g. assault.json</param>
|
||||
/// <returns>Weapon template</returns>
|
||||
public string PickWeightedWeaponTemplateFromPool(string equipmentSlot, BotBaseInventory botTemplateInventory)
|
||||
public string PickWeightedWeaponTemplateFromPool(string equipmentSlot, BotTypeInventory botTemplateInventory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
|
||||
namespace Core.Generators;
|
||||
@@ -15,7 +15,7 @@ public class PMCLootGenerator
|
||||
/// </summary>
|
||||
/// <param name="botRole"></param>
|
||||
/// <returns>Dictionary of string and number</returns>
|
||||
public Dictionary<string, int> GeneratePMCPocketLootPool(string botRole)
|
||||
public Dictionary<string, double> GeneratePMCPocketLootPool(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public class PMCLootGenerator
|
||||
/// </summary>
|
||||
/// <param name="botRole"></param>
|
||||
/// <returns>Dictionary of string and number</returns>
|
||||
public Dictionary<string, int> GeneratePMCVestLootPool(string botRole)
|
||||
public Dictionary<string, double> GeneratePMCVestLootPool(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public class PMCLootGenerator
|
||||
/// </summary>
|
||||
/// <param name="botRole"></param>
|
||||
/// <returns>Dictionary of string and number</returns>
|
||||
public Dictionary<string, int> GeneratePMCBackpackLootPool(string botRole)
|
||||
public Dictionary<string, double> GeneratePMCBackpackLootPool(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -279,9 +279,11 @@ public class PlayerScavGenerator
|
||||
}
|
||||
|
||||
// Adjust item spawn quantity values
|
||||
var props = baseBotNode.BotGeneration.Items.GetType().GetProperties();
|
||||
foreach (var itemLimitKvP in karmaSettings.ItemLimits)
|
||||
{
|
||||
baseBotNode.BotGeneration.Items[itemLimitKvP.Key] = itemLimitKvP.Value;
|
||||
var prop = props.FirstOrDefault(x => x.Name.ToLower() == itemLimitKvP.Key.ToLower());
|
||||
prop.SetValue(baseBotNode.BotGeneration.Items, itemLimitKvP.Value);
|
||||
}
|
||||
|
||||
// Blacklist equipment, keyed by equipment slot
|
||||
|
||||
@@ -159,7 +159,7 @@ public class WeatherGenerator
|
||||
var formattedDate = _timeUtil.FormatDate(timestamp.HasValue ? _timeUtil.GetDateTimeFromTimeStamp(timestamp.Value) : DateTime.UtcNow);
|
||||
var datetimeBsgFormat = $"{formattedDate} {normalTime}";
|
||||
|
||||
weather.Timestamp = timestamp ?? _timeUtil.GetTimeStampFromEpoch(inRaidTime); // matches weather.date We use to divide by 1000
|
||||
weather.Timestamp = timestamp ?? _timeUtil.GetTimeStampFromEpoch(inRaidTime) / 1000; // matches weather.date
|
||||
weather.Date = formattedDate; // matches weather.timestamp
|
||||
weather.Time = datetimeBsgFormat; // matches weather.timestamp
|
||||
weather.SptInRaidTimestamp = _timeUtil.GetTimeStampFromEpoch(inRaidTime);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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.Servers;
|
||||
|
||||
@@ -84,7 +85,7 @@ public class BotGeneratorHelper
|
||||
/// <param name="tplToCheck">Tpl of the item to check for incompatibilities</param>
|
||||
/// <param name="equipmentSlot">Slot the item will be placed into</param>
|
||||
/// <returns>false if no incompatibilities, also has incompatibility reason</returns>
|
||||
public object IsItemIncompatibleWithCurrentItems(List<Item> itemsEquipped, string tplToCheck, string equipmentSlot)
|
||||
public ChooseRandomCompatibleModResult IsItemIncompatibleWithCurrentItems(List<Item> itemsEquipped, string tplToCheck, string equipmentSlot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1045,7 +1045,12 @@ public class ItemHelper
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsOfBaseclass(string tpl, List<string> baseClassTpls)
|
||||
public bool IsOfBaseclass(string tpl, string baseClassTpl)
|
||||
{
|
||||
return _itemBaseClassService.ItemHasBaseClass(tpl, [baseClassTpl]);
|
||||
}
|
||||
|
||||
public bool isOfBaseclasses(string tpl, List<string> baseClassTpls)
|
||||
{
|
||||
return _itemBaseClassService.ItemHasBaseClass(tpl, baseClassTpls);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class PresetHelper
|
||||
var tempPresets = _databaseService.GetGlobals().ItemPresets;
|
||||
tempPresets = tempPresets.Where(p =>
|
||||
p.Value.Encyclopedia != null &&
|
||||
_itemHelper.IsOfBaseclass(p.Value.Encyclopedia, [BaseClasses.WEAPON])).ToDictionary();
|
||||
_itemHelper.IsOfBaseclass(p.Value.Encyclopedia, BaseClasses.WEAPON)).ToDictionary();
|
||||
}
|
||||
|
||||
return _defaultWeaponPresets;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class WeatherHelper
|
||||
var twentyFourHoursMilliseconds = _timeUtil.GetHoursAsSeconds(24) * 1000;
|
||||
var currentTimestampMilliSeconds = timestamp.HasValue
|
||||
? timestamp ?? 0
|
||||
: (DateTime.UtcNow - DateTime.UnixEpoch).TotalMilliseconds;
|
||||
: _timeUtil.GetTimeStampFromEpoch();
|
||||
|
||||
return _timeUtil.GetDateTimeFromTimeStamp((long)
|
||||
(russiaOffsetMilliseconds + currentTimestampMilliSeconds * _weatherConfig.Acceleration) %
|
||||
|
||||
@@ -22,7 +22,7 @@ public class WeightedRandomHelper
|
||||
/// </summary>
|
||||
/// <param name="values">Items and weights to use</param>
|
||||
/// <returns>Chosen item from array</returns>
|
||||
public T GetWeightedValue<T>(Dictionary<T, int> values) where T : notnull
|
||||
public T GetWeightedValue<T>(Dictionary<T, double> values) where T : notnull
|
||||
{
|
||||
var itemKeys = values.Keys.ToList();
|
||||
var weights = values.Values.ToList();
|
||||
@@ -30,7 +30,6 @@ public class WeightedRandomHelper
|
||||
var chosenItem = WeightedRandom<T>(itemKeys, weights);
|
||||
|
||||
return chosenItem.Item;
|
||||
// SORRY IF THIS BLEW UP, I DONT SEE A REASON ITS GENERIC - CWX
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,7 +46,7 @@ public class WeightedRandomHelper
|
||||
/// <param name="items">List of items</param>
|
||||
/// <param name="weights">List of weights</param>
|
||||
/// <returns>Dictionary with item and index</returns>
|
||||
public WeightedRandomResult<T> WeightedRandom<T>(List<T> items, List<int> weights)
|
||||
public WeightedRandomResult<T> WeightedRandom<T>(List<T> items, List<double> weights)
|
||||
{
|
||||
if (items.Count == 0)
|
||||
{
|
||||
@@ -68,7 +67,7 @@ public class WeightedRandomHelper
|
||||
List<int> cumulativeWeights = [];
|
||||
for (var i = 0; i < weights.Count; i++)
|
||||
{
|
||||
cumulativeWeights.Add(weights[i] + (i > 0 ? cumulativeWeights[i - 1] : 0));
|
||||
cumulativeWeights.Add((int)(weights[i]) + (i > 0 ? (cumulativeWeights[i - 1]) : 0));
|
||||
}
|
||||
|
||||
// Getting the random number in a range of [0...sum(weights)]
|
||||
|
||||
@@ -8,7 +8,7 @@ public class PmcData : BotBase
|
||||
{
|
||||
[JsonPropertyName("Prestige")]
|
||||
[JsonConverter(typeof(ArrayToObjectFactoryConverter))]
|
||||
public Tables.Prestige? Prestige { get; set; }
|
||||
public Dictionary<string, long>? Prestige { get; set; }
|
||||
}
|
||||
|
||||
public class PostRaidPmcData : BotBase
|
||||
|
||||
@@ -41,22 +41,22 @@ public class BotType
|
||||
public class Appearance
|
||||
{
|
||||
[JsonPropertyName("body")]
|
||||
public Dictionary<string, int>? Body { get; set; }
|
||||
public Dictionary<string, double>? Body { get; set; }
|
||||
|
||||
[JsonPropertyName("feet")]
|
||||
public Dictionary<string, int>? Feet { get; set; }
|
||||
public Dictionary<string, double>? Feet { get; set; }
|
||||
|
||||
[JsonPropertyName("hands")]
|
||||
[JsonConverter(typeof(ArrayToObjectFactoryConverter))]
|
||||
public Dictionary<string, int>? Hands { get; set; }
|
||||
public Dictionary<string, double>? Hands { get; set; }
|
||||
|
||||
[JsonPropertyName("head")]
|
||||
[JsonConverter(typeof(ArrayToObjectFactoryConverter))]
|
||||
public Dictionary<string, int>? Head { get; set; }
|
||||
public Dictionary<string, double>? Head { get; set; }
|
||||
|
||||
[JsonPropertyName("voice")]
|
||||
[JsonConverter(typeof(ArrayToObjectFactoryConverter))]
|
||||
public Dictionary<string, int>? Voice { get; set; }
|
||||
public Dictionary<string, double>? Voice { get; set; }
|
||||
}
|
||||
|
||||
public class Chances
|
||||
@@ -296,14 +296,14 @@ public class Experience
|
||||
public class Generation
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public Dictionary<string, GenerationData>? Items { get; set; }
|
||||
public GenerationWeightingItems? Items { get; set; }
|
||||
}
|
||||
|
||||
public class GenerationData
|
||||
{
|
||||
/** key: number of items, value: weighting */
|
||||
[JsonPropertyName("weights")]
|
||||
public Dictionary<string, double>? Weights { get; set; }
|
||||
public Dictionary<int, double>? Weights { get; set; }
|
||||
|
||||
/** Array of item tpls */
|
||||
[JsonPropertyName("whitelist")]
|
||||
@@ -311,6 +311,48 @@ public class GenerationData
|
||||
public Dictionary<string, double>? Whitelist { get; set; }
|
||||
}
|
||||
|
||||
public class GenerationWeightingItems
|
||||
{
|
||||
[JsonPropertyName("grenades")]
|
||||
public GenerationData Grenades { get; set; }
|
||||
|
||||
[JsonPropertyName("healing")]
|
||||
public GenerationData Healing { get; set; }
|
||||
|
||||
[JsonPropertyName("drugs")]
|
||||
public GenerationData Drugs { get; set; }
|
||||
|
||||
[JsonPropertyName("food")]
|
||||
public GenerationData Food { get; set; }
|
||||
|
||||
[JsonPropertyName("drink")]
|
||||
public GenerationData Drink { get; set; }
|
||||
|
||||
[JsonPropertyName("currency")]
|
||||
public GenerationData Currency { get; set; }
|
||||
|
||||
[JsonPropertyName("stims")]
|
||||
public GenerationData Stims { get; set; }
|
||||
|
||||
[JsonPropertyName("backpackLoot")]
|
||||
public GenerationData BackpackLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("pocketLoot")]
|
||||
public GenerationData PocketLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("vestLoot")]
|
||||
public GenerationData VestLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("magazines")]
|
||||
public GenerationData Magazines { get; set; }
|
||||
|
||||
[JsonPropertyName("specialItems")]
|
||||
public GenerationData SpecialItems { get; set; }
|
||||
|
||||
[JsonPropertyName("looseLoot")]
|
||||
public GenerationData LooseLoot { get; set; }
|
||||
}
|
||||
|
||||
public class BotTypeHealth
|
||||
{
|
||||
public List<BodyPart>? BodyParts { get; set; }
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
global using GlobalAmmo = System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, int>>;
|
||||
global using GlobalMods = System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, string[]>>;
|
||||
global using GlobalMods = System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<string>>>;
|
||||
|
||||
@@ -5,46 +5,46 @@ namespace Core.Models.Spt.Bots;
|
||||
public class BotLootCache
|
||||
{
|
||||
[JsonPropertyName("backpackLoot")]
|
||||
public Dictionary<string, int>? BackpackLoot { get; set; }
|
||||
public Dictionary<string, double>? BackpackLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("pocketLoot")]
|
||||
public Dictionary<string, int>? PocketLoot { get; set; }
|
||||
public Dictionary<string, double>? PocketLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("vestLoot")]
|
||||
public Dictionary<string, int>? VestLoot { get; set; }
|
||||
public Dictionary<string, double>? VestLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("secureLoot")]
|
||||
public Dictionary<string, int>? SecureLoot { get; set; }
|
||||
public Dictionary<string, double>? SecureLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("combinedPoolLoot")]
|
||||
public Dictionary<string, int>? CombinedPoolLoot { get; set; }
|
||||
public Dictionary<string, double>? CombinedPoolLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("specialItems")]
|
||||
public Dictionary<string, int>? SpecialItems { get; set; }
|
||||
public Dictionary<string, double>? SpecialItems { get; set; }
|
||||
|
||||
[JsonPropertyName("healingItems")]
|
||||
public Dictionary<string, int>? HealingItems { get; set; }
|
||||
public Dictionary<string, double>? HealingItems { get; set; }
|
||||
|
||||
[JsonPropertyName("drugItems")]
|
||||
public Dictionary<string, int>? DrugItems { get; set; }
|
||||
public Dictionary<string, double>? DrugItems { get; set; }
|
||||
|
||||
[JsonPropertyName("foodItems")]
|
||||
public Dictionary<string, int>? FoodItems { get; set; }
|
||||
public Dictionary<string, double>? FoodItems { get; set; }
|
||||
|
||||
[JsonPropertyName("drinkItems")]
|
||||
public Dictionary<string, int>? DrinkItems { get; set; }
|
||||
public Dictionary<string, double>? DrinkItems { get; set; }
|
||||
|
||||
[JsonPropertyName("currencyItems")]
|
||||
public Dictionary<string, int>? CurrencyItems { get; set; }
|
||||
public Dictionary<string, double>? CurrencyItems { get; set; }
|
||||
|
||||
[JsonPropertyName("stimItems")]
|
||||
public Dictionary<string, int>? StimItems { get; set; }
|
||||
public Dictionary<string, double>? StimItems { get; set; }
|
||||
|
||||
[JsonPropertyName("grenadeItems")]
|
||||
public Dictionary<string, int>? GrenadeItems { get; set; }
|
||||
public Dictionary<string, double>? GrenadeItems { get; set; }
|
||||
}
|
||||
|
||||
public static class LootCacheType
|
||||
public class LootCacheType
|
||||
{
|
||||
public const string Special = "Special";
|
||||
public const string Backpack = "Backpack";
|
||||
@@ -59,4 +59,4 @@ public static class LootCacheType
|
||||
public const string FoodItems = "FoodItems";
|
||||
public const string DrinkItems = "DrinkItems";
|
||||
public const string CurrencyItems = "CurrencyItems";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public class AssaultToBossConversion
|
||||
public bool BossConvertEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("bossesToConvertToWeights")]
|
||||
public Dictionary<string, int> BossesToConvertToWeights { get; set; }
|
||||
public Dictionary<string, double> BossesToConvertToWeights { get; set; }
|
||||
|
||||
[JsonPropertyName("bossConvertMinMax")]
|
||||
public Dictionary<string, MinMax> BossConvertMinMax { get; set; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Enums;
|
||||
@@ -12,11 +12,11 @@ public class PmcConfig : BaseConfig
|
||||
|
||||
/** What game version should the PMC have */
|
||||
[JsonPropertyName("gameVersionWeight")]
|
||||
public Dictionary<string, int> GameVersionWeight { get; set; }
|
||||
public Dictionary<string, double> GameVersionWeight { get; set; }
|
||||
|
||||
/** What account type should the PMC have */
|
||||
[JsonPropertyName("accountTypeWeight")]
|
||||
public Dictionary<MemberCategory, int> AccountTypeWeight { get; set; }
|
||||
public Dictionary<MemberCategory, double> AccountTypeWeight { get; set; }
|
||||
|
||||
/** Global whitelist/blacklist of vest loot for PMCs */
|
||||
[JsonPropertyName("vestLoot")]
|
||||
@@ -79,10 +79,10 @@ public class PmcConfig : BaseConfig
|
||||
public List<MinMaxLootValue> MaxBackpackLootTotalRub { get; set; }
|
||||
|
||||
[JsonPropertyName("maxPocketLootTotalRub")]
|
||||
public double MaxPocketLootTotalRub { get; set; }
|
||||
public int MaxPocketLootTotalRub { get; set; }
|
||||
|
||||
[JsonPropertyName("maxVestLootTotalRub")]
|
||||
public double MaxVestLootTotalRub { get; set; }
|
||||
public int MaxVestLootTotalRub { get; set; }
|
||||
|
||||
/** Percentage chance a bot from a wave is converted into a PMC, first key = map, second key = bot wildspawn type (assault/exusec), value: min+max chance to be converted */
|
||||
[JsonPropertyName("convertIntoPmcChance")]
|
||||
@@ -117,7 +117,7 @@ public class PmcConfig : BaseConfig
|
||||
public int? AddPrefixToSameNamePMCAsPlayerChance { get; set; }
|
||||
|
||||
[JsonPropertyName("lootItemLimitsRub")]
|
||||
public List<MinMaxLootValue>? LootItemLimitsRub { get; set; }
|
||||
public List<MinMaxLootItemValue>? LootItemLimitsRub { get; set; }
|
||||
}
|
||||
|
||||
public class HostilitySettings
|
||||
@@ -171,13 +171,16 @@ public class MinMaxLootValue : MinMax
|
||||
{
|
||||
[JsonPropertyName("value")]
|
||||
public double Value { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class MinMaxLootItemValue : MinMax
|
||||
{
|
||||
[JsonPropertyName("backpack")]
|
||||
public MinMax Backpack { get; set; }
|
||||
|
||||
|
||||
[JsonPropertyName("pocket")]
|
||||
public MinMax Pocket { get; set; }
|
||||
|
||||
|
||||
[JsonPropertyName("vest")]
|
||||
public MinMax Vest { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@ namespace Core.Models.Spt.Config;
|
||||
public class WeatherConfig : BaseConfig
|
||||
{
|
||||
[JsonPropertyName("kind")]
|
||||
public string Kind { get; set; } = "spt-weather";
|
||||
public string? Kind { get; set; } = "spt-weather";
|
||||
|
||||
[JsonPropertyName("acceleration")]
|
||||
public double Acceleration { get; set; }
|
||||
public double? Acceleration { get; set; }
|
||||
|
||||
[JsonPropertyName("weather")]
|
||||
public WeatherValues Weather { get; set; }
|
||||
public WeatherValues? Weather { get; set; }
|
||||
|
||||
[JsonPropertyName("seasonDates")]
|
||||
public List<SeasonDateTimes> SeasonDates { get; set; }
|
||||
public List<SeasonDateTimes>? SeasonDates { get; set; }
|
||||
|
||||
[JsonPropertyName("overrideSeason")]
|
||||
public Season? OverrideSeason { get; set; }
|
||||
@@ -26,86 +26,86 @@ public class WeatherConfig : BaseConfig
|
||||
public class SeasonDateTimes
|
||||
{
|
||||
[JsonPropertyName("seasonType")]
|
||||
public Season SeasonType { get; set; }
|
||||
public Season? SeasonType { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonPropertyName("startDay")]
|
||||
[JsonConverter(typeof(StringToNumberFactoryConverter))]
|
||||
public int StartDay { get; set; }
|
||||
public int? StartDay { get; set; }
|
||||
|
||||
[JsonPropertyName("startMonth")]
|
||||
[JsonConverter(typeof(StringToNumberFactoryConverter))]
|
||||
public int StartMonth { get; set; }
|
||||
public int? StartMonth { get; set; }
|
||||
|
||||
[JsonPropertyName("endDay")]
|
||||
[JsonConverter(typeof(StringToNumberFactoryConverter))]
|
||||
public int EndDay { get; set; }
|
||||
public int? EndDay { get; set; }
|
||||
|
||||
[JsonPropertyName("endMonth")]
|
||||
[JsonConverter(typeof(StringToNumberFactoryConverter))]
|
||||
public int EndMonth { get; set; }
|
||||
public int? EndMonth { get; set; }
|
||||
}
|
||||
|
||||
public class WeatherValues
|
||||
{
|
||||
[JsonPropertyName("seasonValues")]
|
||||
public Dictionary<string, SeasonalValues> SeasonValues { get; set; }
|
||||
public Dictionary<string, SeasonalValues>? SeasonValues { get; set; }
|
||||
|
||||
/** How many hours to generate weather data into the future */
|
||||
[JsonPropertyName("generateWeatherAmountHours")]
|
||||
public int GenerateWeatherAmountHours { get; set; }
|
||||
public int? GenerateWeatherAmountHours { get; set; }
|
||||
|
||||
/** Length of each weather period */
|
||||
[JsonPropertyName("timePeriod")]
|
||||
public WeatherSettings<int> TimePeriod { get; set; }
|
||||
public WeatherSettings<int>? TimePeriod { get; set; }
|
||||
}
|
||||
|
||||
public class SeasonalValues
|
||||
{
|
||||
[JsonPropertyName("clouds")]
|
||||
public WeatherSettings<double> Clouds { get; set; }
|
||||
public WeatherSettings<double>? Clouds { get; set; }
|
||||
|
||||
[JsonPropertyName("windSpeed")]
|
||||
public WeatherSettings<double> WindSpeed { get; set; }
|
||||
public WeatherSettings<double>? WindSpeed { get; set; }
|
||||
|
||||
[JsonPropertyName("windDirection")]
|
||||
public WeatherSettings<WindDirection> WindDirection { get; set; }
|
||||
public WeatherSettings<WindDirection>? WindDirection { get; set; }
|
||||
|
||||
[JsonPropertyName("windGustiness")]
|
||||
public MinMax WindGustiness { get; set; }
|
||||
public MinMax? WindGustiness { get; set; }
|
||||
|
||||
[JsonPropertyName("rain")]
|
||||
public WeatherSettings<double> Rain { get; set; }
|
||||
public WeatherSettings<double>? Rain { get; set; }
|
||||
|
||||
[JsonPropertyName("rainIntensity")]
|
||||
public MinMax RainIntensity { get; set; }
|
||||
public MinMax? RainIntensity { get; set; }
|
||||
|
||||
[JsonPropertyName("fog")]
|
||||
public WeatherSettings<double> Fog { get; set; }
|
||||
public WeatherSettings<double>? Fog { get; set; }
|
||||
|
||||
[JsonPropertyName("temp")]
|
||||
public TempDayNight Temp { get; set; }
|
||||
public TempDayNight? Temp { get; set; }
|
||||
|
||||
[JsonPropertyName("pressure")]
|
||||
public MinMax Pressure { get; set; }
|
||||
public MinMax? Pressure { get; set; }
|
||||
}
|
||||
|
||||
public class TempDayNight
|
||||
{
|
||||
[JsonPropertyName("day")]
|
||||
public MinMax Day { get; set; }
|
||||
public MinMax? Day { get; set; }
|
||||
|
||||
[JsonPropertyName("night")]
|
||||
public MinMax Night { get; set; }
|
||||
public MinMax? Night { get; set; }
|
||||
}
|
||||
|
||||
public class WeatherSettings<T>
|
||||
{
|
||||
[JsonPropertyName("values")]
|
||||
public List<T> Values { get; set; }
|
||||
public List<T>? Values { get; set; }
|
||||
|
||||
[JsonPropertyName("weights")]
|
||||
public List<int> Weights { get; set; }
|
||||
public List<double>? Weights { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Core.Annotations;
|
||||
using Core.Generators;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Models.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
@@ -55,7 +57,8 @@ public class BotLootCacheService
|
||||
string botRole,
|
||||
bool isPmc,
|
||||
string lootType,
|
||||
BotType botJsonTemplate)
|
||||
BotType botJsonTemplate,
|
||||
MinMax? itemPriceMinMax = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -68,7 +71,294 @@ public class BotLootCacheService
|
||||
/// <param name="botJsonTemplate">db template for bot having its loot generated</param>
|
||||
protected void AddLootToCache(string botRole, bool isPmc, BotType botJsonTemplate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Full pool of loot we use to create the various sub-categories with
|
||||
var lootPool = botJsonTemplate.BotInventory.Items;
|
||||
|
||||
// Flatten all individual slot loot pools into one big pool, while filtering out potentially missing templates
|
||||
Dictionary<string, double> specialLootPool = new();
|
||||
Dictionary<string, double> backpackLootPool= new();
|
||||
Dictionary<string, double> pocketLootPool = new();
|
||||
Dictionary<string, double> vestLootPool = new();
|
||||
Dictionary<string, double> secureLootTPool = new();
|
||||
Dictionary<string, double> combinedLootPool = new();
|
||||
|
||||
if (isPmc)
|
||||
{
|
||||
// Replace lootPool from bot json with our own generated list for PMCs
|
||||
lootPool.Backpack = _cloner.Clone(_pmcLootGenerator.GeneratePMCBackpackLootPool(botRole));
|
||||
lootPool.Pockets = _cloner.Clone(_pmcLootGenerator.GeneratePMCPocketLootPool(botRole));
|
||||
lootPool.TacticalVest = _cloner.Clone(_pmcLootGenerator.GeneratePMCVestLootPool(botRole));
|
||||
}
|
||||
|
||||
// Backpack/Pockets etc
|
||||
var poolsToProcess =
|
||||
new Dictionary<string, Dictionary<string, double>>
|
||||
{
|
||||
{ "Backpack", lootPool.Backpack },
|
||||
{ "Pockets", lootPool.Pockets },
|
||||
{ "SecuredContainer", lootPool.SecuredContainer },
|
||||
{ "SpecialLoot", lootPool.SpecialLoot },
|
||||
{ "TacticalVest", lootPool.TacticalVest }
|
||||
};
|
||||
|
||||
|
||||
foreach (var kvp in poolsToProcess)
|
||||
{
|
||||
// No items to add, skip
|
||||
if (kvp.Value.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sort loot pool into separate buckets
|
||||
switch (kvp.Key)
|
||||
{
|
||||
case "specialloot":
|
||||
AddItemsToPool(specialLootPool, kvp.Value);
|
||||
break;
|
||||
case "pockets":
|
||||
AddItemsToPool(pocketLootPool, kvp.Value);
|
||||
break;
|
||||
case "tacticalvest":
|
||||
AddItemsToPool(vestLootPool, kvp.Value);
|
||||
break;
|
||||
case "securedcontainer":
|
||||
AddItemsToPool(secureLootTPool, kvp.Value);
|
||||
break;
|
||||
case "backpack":
|
||||
AddItemsToPool(backpackLootPool, kvp.Value);
|
||||
break;
|
||||
default:
|
||||
_logger.Warning($"How did you get here {kvp.Key}");
|
||||
break;
|
||||
}
|
||||
|
||||
// Add all items (if any) to combined pool (excluding secure)
|
||||
if (kvp.Value.Count > 0 && kvp.Key.ToLower() != "securedcontainer")
|
||||
{
|
||||
AddItemsToPool(combinedLootPool, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted special items to bot if any exist
|
||||
var specialLootItems =
|
||||
botJsonTemplate.BotGeneration.Items.SpecialItems.Whitelist.Count > 0
|
||||
? botJsonTemplate.BotGeneration.Items.SpecialItems.Whitelist
|
||||
: new Dictionary<string, double>();
|
||||
|
||||
// no whitelist, find and assign from combined item pool
|
||||
if (!specialLootItems.Any())
|
||||
{
|
||||
// key = tpl, value = weight
|
||||
foreach (var itemKvP in specialLootPool) {
|
||||
var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value;
|
||||
if (!(IsBulletOrGrenade(itemTemplate.Properties) || IsMagazine(itemTemplate.Properties)))
|
||||
{
|
||||
specialLootItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted healing items to bot if any exist
|
||||
var healingItems =
|
||||
botJsonTemplate.BotGeneration.Items.Healing.Whitelist.Count > 0
|
||||
? botJsonTemplate.BotGeneration.Items.Healing.Whitelist
|
||||
: new Dictionary<string, double>();
|
||||
|
||||
// No whitelist, find and assign from combined item pool
|
||||
if (!healingItems.Any())
|
||||
{
|
||||
// key = tpl, value = weight
|
||||
foreach (var itemKvP in combinedLootPool) {
|
||||
var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value;
|
||||
if (
|
||||
IsMedicalItem(itemTemplate.Properties) &&
|
||||
itemTemplate.Parent != BaseClasses.STIMULATOR &&
|
||||
itemTemplate.Parent != BaseClasses.DRUGS
|
||||
)
|
||||
{
|
||||
healingItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted drugs to bot if any exist
|
||||
var drugItems = botJsonTemplate.BotGeneration.Items.Drugs.Whitelist ?? new Dictionary<string, double>();
|
||||
// no drugs whitelist, find and assign from combined item pool
|
||||
if (!drugItems.Any())
|
||||
{
|
||||
foreach (var itemKvP in (combinedLootPool)) {
|
||||
var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value;
|
||||
if (IsMedicalItem(itemTemplate.Properties) && itemTemplate.Parent == BaseClasses.DRUGS)
|
||||
{
|
||||
drugItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted food to bot if any exist
|
||||
var foodItems = botJsonTemplate.BotGeneration.Items.Food.Whitelist ?? new Dictionary<string, double>();
|
||||
// No food whitelist, find and assign from combined item pool
|
||||
if (!foodItems.Any())
|
||||
{
|
||||
foreach (var itemKvP in (combinedLootPool)) {
|
||||
var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value;
|
||||
if (_itemHelper.IsOfBaseclass(itemTemplate.Id, BaseClasses.FOOD))
|
||||
{
|
||||
foodItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted drink to bot if any exist
|
||||
var drinkItems = botJsonTemplate.BotGeneration.Items.Food.Whitelist ?? new Dictionary<string, double>();
|
||||
// No drink whitelist, find and assign from combined item pool
|
||||
if (!drinkItems.Any())
|
||||
{
|
||||
foreach (var itemKvP in combinedLootPool) {
|
||||
var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value;
|
||||
if (_itemHelper.IsOfBaseclass(itemTemplate.Id, BaseClasses.DRINK))
|
||||
{
|
||||
drinkItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted currency to bot if any exist
|
||||
var currencyItems = botJsonTemplate.BotGeneration.Items.Currency.Whitelist ?? new Dictionary<string, double>();
|
||||
// No currency whitelist, find and assign from combined item pool
|
||||
if (!currencyItems.Any())
|
||||
{
|
||||
foreach (var itemKvP in combinedLootPool) {
|
||||
var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value;
|
||||
if (_itemHelper.IsOfBaseclass(itemTemplate.Id, BaseClasses.MONEY))
|
||||
{
|
||||
currencyItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted stims to bot if any exist
|
||||
var stimItems = botJsonTemplate.BotGeneration.Items.Stims.Whitelist ?? new Dictionary<string, double>();
|
||||
// No whitelist, find and assign from combined item pool
|
||||
if (!stimItems.Any())
|
||||
{
|
||||
foreach (var itemKvP in combinedLootPool) {
|
||||
var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value;
|
||||
if (IsMedicalItem(itemTemplate.Properties) && itemTemplate.Parent == BaseClasses.STIMULATOR)
|
||||
{
|
||||
stimItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted grenades to bot if any exist
|
||||
var grenadeItems = botJsonTemplate.BotGeneration.Items.Grenades.Whitelist ?? new Dictionary<string, double>();
|
||||
// no whitelist, find and assign from combined item pool
|
||||
if (!grenadeItems.Any())
|
||||
{
|
||||
foreach (var itemKvP in combinedLootPool) {
|
||||
var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value;
|
||||
if (IsGrenade(itemTemplate.Properties))
|
||||
{
|
||||
grenadeItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get backpack loot (excluding magazines, bullets, grenades, drink, food and healing/stim items)
|
||||
var filteredBackpackItems = new Dictionary<string, double>();
|
||||
foreach (var itemKvP in backpackLootPool) {
|
||||
var itemResult = _itemHelper.GetItem(itemKvP.Key);
|
||||
if (itemResult.Value is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var itemTemplate = itemResult.Value;
|
||||
if (
|
||||
IsBulletOrGrenade(itemTemplate.Properties) ||
|
||||
IsMagazine(itemTemplate.Properties) ||
|
||||
IsMedicalItem(itemTemplate.Properties) ||
|
||||
IsGrenade(itemTemplate.Properties) ||
|
||||
IsFood(itemTemplate.Id) ||
|
||||
IsDrink(itemTemplate.Id) ||
|
||||
IsCurrency(itemTemplate.Id)
|
||||
)
|
||||
{
|
||||
// Is type we don't want as backpack loot, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
filteredBackpackItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
|
||||
// Get pocket loot (excluding magazines, bullets, grenades, drink, food medical and healing/stim items)
|
||||
var filteredPocketItems = new Dictionary<string, double>();
|
||||
foreach (var itemKvP in pocketLootPool) {
|
||||
var itemResult = _itemHelper.GetItem(itemKvP.Key);
|
||||
if (itemResult.Value is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var itemTemplate = itemResult.Value;
|
||||
if (
|
||||
IsBulletOrGrenade(itemTemplate.Properties) ||
|
||||
IsMagazine(itemTemplate.Properties) ||
|
||||
IsMedicalItem(itemTemplate.Properties) ||
|
||||
IsGrenade(itemTemplate.Properties) ||
|
||||
IsFood(itemTemplate.Id) ||
|
||||
IsDrink(itemTemplate.Id) ||
|
||||
IsCurrency(itemTemplate.Id) ||
|
||||
itemTemplate.Properties.Height is null || // lacks height
|
||||
itemTemplate.Properties.Width is null // lacks width
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filteredPocketItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
|
||||
// Get vest loot (excluding magazines, bullets, grenades, medical and healing/stim items)
|
||||
var filteredVestItems = new Dictionary<string, double>();
|
||||
foreach (var itemKvP in vestLootPool) {
|
||||
var itemResult = _itemHelper.GetItem(itemKvP.Key);
|
||||
if (itemResult.Value is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var itemTemplate = itemResult.Value;
|
||||
if (
|
||||
IsBulletOrGrenade(itemTemplate.Properties) ||
|
||||
IsMagazine(itemTemplate.Properties) ||
|
||||
IsMedicalItem(itemTemplate.Properties) ||
|
||||
IsGrenade(itemTemplate.Properties) ||
|
||||
IsFood(itemTemplate.Id) ||
|
||||
IsDrink(itemTemplate.Id) ||
|
||||
IsCurrency(itemTemplate.Id)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
filteredVestItems[itemKvP.Key] = itemKvP.Value;
|
||||
}
|
||||
|
||||
var cacheForRole = _lootCache[botRole];
|
||||
|
||||
cacheForRole.HealingItems = healingItems;
|
||||
cacheForRole.DrugItems = drugItems;
|
||||
cacheForRole.FoodItems = foodItems;
|
||||
cacheForRole.DrinkItems = drinkItems;
|
||||
cacheForRole.CurrencyItems = currencyItems;
|
||||
cacheForRole.StimItems = stimItems;
|
||||
cacheForRole.GrenadeItems = grenadeItems;
|
||||
|
||||
cacheForRole.SpecialItems = specialLootItems;
|
||||
cacheForRole.BackpackLoot = filteredBackpackItems;
|
||||
cacheForRole.PocketLoot = filteredPocketItems;
|
||||
cacheForRole.VestLoot = filteredVestItems;
|
||||
cacheForRole.SecureLoot = secureLootTPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,7 +371,7 @@ public class BotLootCacheService
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected void AddItemsToPool(Dictionary<string, int> poolToAddTo, Dictionary<string, int> poolOfItemsToAdd)
|
||||
protected void AddItemsToPool(Dictionary<string, double> poolToAddTo, Dictionary<string, double> poolOfItemsToAdd)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,18 +1,51 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Services;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class ItemBaseClassService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly LocalisationService _localisationService;
|
||||
|
||||
private bool _cacheGenerated;
|
||||
private Dictionary<string, List<string>> _itemBaseClassesCache;
|
||||
|
||||
public ItemBaseClassService(
|
||||
ILogger logger,
|
||||
DatabaseService databaseService,
|
||||
LocalisationService localisationService)
|
||||
{
|
||||
_logger = logger;
|
||||
_databaseService = databaseService;
|
||||
_localisationService = localisationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create cache and store inside ItemBaseClassService
|
||||
* Store a dict of an items tpl to the base classes it and its parents have
|
||||
*/
|
||||
public void HydrateItemBaseClassCache()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Clear existing cache
|
||||
_itemBaseClassesCache = new Dictionary<string, List<string>>();
|
||||
|
||||
var items = _databaseService.GetItems();
|
||||
var filteredDbItems = (items).Where((x) => x.Value.Type == "Item");
|
||||
foreach (var item in filteredDbItems) {
|
||||
var itemIdToUpdate = item.Value.Id;
|
||||
if (!_itemBaseClassesCache.ContainsKey(item.Value.Id))
|
||||
{
|
||||
_itemBaseClassesCache[item.Value.Id] = [];
|
||||
}
|
||||
|
||||
AddBaseItems(itemIdToUpdate, item.Value);
|
||||
}
|
||||
|
||||
_cacheGenerated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,7 +55,13 @@ public class ItemBaseClassService
|
||||
*/
|
||||
protected void AddBaseItems(string itemIdToUpdate, TemplateItem item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_itemBaseClassesCache[itemIdToUpdate].Add(item.Parent);
|
||||
var parent = _databaseService.GetItems()[item.Parent];
|
||||
|
||||
if (parent.Parent != "")
|
||||
{
|
||||
AddBaseItems(itemIdToUpdate, parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,7 +72,45 @@ public class ItemBaseClassService
|
||||
*/
|
||||
public bool ItemHasBaseClass(string itemTpl, List<string> baseClasses)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!_cacheGenerated)
|
||||
{
|
||||
HydrateItemBaseClassCache();
|
||||
}
|
||||
|
||||
if (itemTpl is null)
|
||||
{
|
||||
_logger.Warning("Unable to check itemTpl base class as value passed is null");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// The cache is only generated for item templates with `_type === "Item"`, so return false for any other type,
|
||||
// including item templates that simply don't exist.
|
||||
if (!CachedItemIsOfItemType(itemTpl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// No item in cache
|
||||
if (_itemBaseClassesCache.ContainsKey(itemTpl))
|
||||
{
|
||||
return _itemBaseClassesCache[itemTpl].Any(baseClasses.Contains);
|
||||
}
|
||||
|
||||
_logger.Debug(_localisationService.GetText("baseclass-item_not_found", itemTpl));
|
||||
|
||||
// Not found in cache, Hydrate again - some mods add items late
|
||||
HydrateItemBaseClassCache();
|
||||
|
||||
// Check for item again, return false if item not found a second time
|
||||
if (_itemBaseClassesCache.ContainsKey(itemTpl))
|
||||
{
|
||||
return _itemBaseClassesCache[itemTpl].Any(baseClasses.Contains);
|
||||
}
|
||||
|
||||
_logger.Warning(_localisationService.GetText("baseclass-item_not_found_failed", itemTpl));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +120,7 @@ public class ItemBaseClassService
|
||||
*/
|
||||
private bool CachedItemIsOfItemType(string itemTemplateId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _databaseService.GetItems()[itemTemplateId]?.Type == "Item";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,6 +130,16 @@ public class ItemBaseClassService
|
||||
*/
|
||||
public List<string> GetItemBaseClasses(string itemTpl)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!_cacheGenerated)
|
||||
{
|
||||
HydrateItemBaseClassCache();
|
||||
}
|
||||
|
||||
if (!_itemBaseClassesCache.ContainsKey(itemTpl))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return _itemBaseClassesCache[itemTpl];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ public class MailSendService
|
||||
}
|
||||
|
||||
// Boxes can contain sub-items
|
||||
if (_itemHelper.IsOfBaseclass(itemTemplate.Id, [BaseClasses.AMMO_BOX]))
|
||||
if (_itemHelper.IsOfBaseclass(itemTemplate.Id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
var boxAndCartridges = new List<Item>();
|
||||
boxAndCartridges.Add(reward);
|
||||
|
||||
@@ -54,7 +54,7 @@ public class RaidWeatherService
|
||||
var staringTimestampMs = _timeUtil.GetTodayMidnightTimeStamp();
|
||||
|
||||
// How far into future do we generate weather
|
||||
var futureTimestampToReachMs = staringTimestampMs + _timeUtil.GetHoursAsSeconds(_weatherConfig.Weather.GenerateWeatherAmountHours) * 1000; // Convert to milliseconds
|
||||
var futureTimestampToReachMs = staringTimestampMs + _timeUtil.GetHoursAsSeconds(_weatherConfig.Weather.GenerateWeatherAmountHours ?? 1) * 1000; // Convert to milliseconds
|
||||
|
||||
// Keep adding new weather until we have reached desired future date
|
||||
var nextTimestampMs = staringTimestampMs;
|
||||
|
||||
@@ -320,13 +320,14 @@ public class SeasonalEventService
|
||||
if (
|
||||
DateIsBetweenTwoDates(
|
||||
currentDate,
|
||||
seasonRange.StartMonth,
|
||||
seasonRange.StartDay,
|
||||
seasonRange.EndMonth,
|
||||
seasonRange.EndDay)
|
||||
seasonRange.StartMonth ?? 0,
|
||||
seasonRange.StartDay ?? 0,
|
||||
seasonRange.EndMonth ?? 0,
|
||||
seasonRange.EndDay ?? 0
|
||||
)
|
||||
)
|
||||
{
|
||||
return seasonRange.SeasonType;
|
||||
return seasonRange.SeasonType ?? Season.SUMMER;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,14 +186,15 @@ public class TimeUtil
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a timestamp and gets difference between Epoch time and time provided resulting in a unixtimestamp (date defaults to utcnow)
|
||||
/// Takes a date and gets difference between Epoch time and time provided resulting in a timestamp (date defaults to utcnow)
|
||||
/// This attempts to mimic gettime() in js
|
||||
/// </summary>
|
||||
/// <param name="date"></param>
|
||||
/// <returns></returns>
|
||||
public long GetTimeStampFromEpoch(DateTime? date = null)
|
||||
{
|
||||
var dateToCompare = date ?? DateTime.UtcNow;
|
||||
return (long)(dateToCompare - DateTime.UnixEpoch).TotalSeconds;
|
||||
return (long)(dateToCompare - DateTime.UnixEpoch).TotalMilliseconds;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user