This commit is contained in:
Alex
2025-01-15 15:06:58 +00:00
26 changed files with 1117 additions and 144 deletions
+1 -1
View File
@@ -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);
+7 -7
View File
@@ -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
}
+254 -34
View File
@@ -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; }
}
+330 -8
View File
@@ -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();
}
+2 -2
View File
@@ -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();
}
+4 -4
View File
@@ -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();
}
+3 -1
View File
@@ -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
+1 -1
View File
@@ -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);
+2 -1
View File
@@ -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();
}
+6 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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) %
+3 -4
View File
@@ -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)]
+1 -1
View File
@@ -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
+49 -7
View File
@@ -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>>>;
+15 -15
View File
@@ -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";
}
}
+1 -1
View File
@@ -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; }
+12 -9
View File
@@ -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; }
}
+26 -26
View File
@@ -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; }
}
+293 -3
View File
@@ -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();
}
+93 -6
View File
@@ -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];
}
}
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
+6 -5
View File
@@ -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;
}
}
+3 -2
View File
@@ -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;
}
}