From a6b181fd699c6cb25ed71ff4810c052b0b23c4fb Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 16 Jan 2025 11:29:16 +0000 Subject: [PATCH 01/14] implement GetCompatibleModFromPool --- Core/Generators/BotEquipmentModGenerator.cs | 77 ++++++++++++++++++- Core/Models/Spt/Bots/GenerateWeaponRequest.cs | 4 +- Core/Models/Spt/Bots/ModToSpawnRequest.cs | 4 +- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/Core/Generators/BotEquipmentModGenerator.cs b/Core/Generators/BotEquipmentModGenerator.cs index 423eecb2..78d7c05b 100644 --- a/Core/Generators/BotEquipmentModGenerator.cs +++ b/Core/Generators/BotEquipmentModGenerator.cs @@ -921,7 +921,23 @@ public class BotEquipmentModGenerator public ChooseRandomCompatibleModResult GetCompatibleWeaponModTplForSlotFromPool(ModToSpawnRequest request, List modPool, Slot parentSlot, ModSpawn? choiceTypeEnum, List weapon, string modSlotName) { - throw new NotImplementedException(); + // Filter out incompatible mods from pool + var preFilteredModPool = GetFilteredModPool(modPool, request.ConflictingItemTpls); + if (preFilteredModPool.Count == 0) { + return new () { + Incompatible = true, + Found = false, + Reason = $"Unable to add mod to {choiceTypeEnum.ToString()} slot: {modSlotName}. All: {modPool.Count()} had conflicts" + }; + } + + // Filter mod pool to only items that appear in parents allowed list + preFilteredModPool = preFilteredModPool.Where((tpl) => parentSlot.Props.Filters[0].Filter.Contains(tpl)).ToList(); + if (preFilteredModPool.Count() == 0) { + return new () { Incompatible = true, Found = false, Reason = "No mods found in parents allowed list" }; + } + + return GetCompatibleModFromPool(preFilteredModPool, choiceTypeEnum, weapon); } /// @@ -931,7 +947,7 @@ public class BotEquipmentModGenerator /// How should the slot choice be handled - forced/normal etc /// Weapon mods at current time /// IChooseRandomCompatibleModResult - public ChooseRandomCompatibleModResult GetCompatibleModFromPool(List modPool, ModSpawn modSpawnType, List weapon) + public ChooseRandomCompatibleModResult GetCompatibleModFromPool(List modPool, ModSpawn? modSpawnType, List weapon) { throw new NotImplementedException(); } @@ -980,7 +996,62 @@ public class BotEquipmentModGenerator public List GetModPoolForDefaultSlot(ModToSpawnRequest request, TemplateItem weaponTemplate) { - throw new NotImplementedException(); + var matchingModFromPreset = GetMatchingModFromPreset(request, weaponTemplate); + if (matchingModFromPreset is null) { + if (request.ItemModPool[request.ModSlot]?.Count > 1) { + _logger.Debug($"{request.BotData.Role} No default: {request.ModSlot} mod found for: {weaponTemplate.Name}, using existing pool"); + } + + // Couldnt find default in globals, use existing mod pool data + return request.ItemModPool[request.ModSlot]; + } + + // Only filter mods down to single default item if it already exists in existing itemModPool, OR the default item has no children + // Filtering mod pool to item that wasnt already there can have problems; + // You'd have a mod being picked without any sub-mods in its chain, possibly resulting in missing required mods not being added + // Mod is in existing mod pool + if (request.ItemModPool[request.ModSlot].Contains(matchingModFromPreset.Template)) { + // Found mod on preset + it already exists in mod pool + return [matchingModFromPreset.Template]; + } + + // Get an array of items that are allowed in slot from parent item + // Check the filter of the slot to ensure a chosen mod fits + var parentSlotCompatibleItems = request.ParentTemplate.Properties.Slots?.FirstOrDefault( + (slot) => slot.Name.ToLower() == request.ModSlot.ToLower() + )?.Props.Filters[0].Filter; + + // Mod isnt in existing pool, only add if it has no children and exists inside parent filter + if ( + parentSlotCompatibleItems?.Contains(matchingModFromPreset.Template) ?? false && + _itemHelper.GetItem(matchingModFromPreset.Template).Value.Properties.Slots?.Count == 0 + ) { + // Chosen mod has no conflicts + no children + is in parent compat list + if (!request.ConflictingItemTpls.Contains(matchingModFromPreset.Template)) { + return [matchingModFromPreset.Template]; + } + + // Above chosen mod had conflicts with existing weapon mods + _logger.Debug($"{request.BotData.Role} Chosen default: {request.ModSlot} mod found for: {weaponTemplate.Name} weapon conflicts with item on weapon, cannot use default"); + + var existingModPool = request.ItemModPool[request.ModSlot]; + if (existingModPool.Count == 1) { + // The only item in pool isn't compatible + _logger.Debug($"{request.BotData.Role} {request.ModSlot} Mod pool for: {weaponTemplate.Name} weapon has only incompatible items, using parent list instead"); + + // Last ditch, use full pool of items minus conflicts + var newListOfModsForSlot = parentSlotCompatibleItems.Where((tpl) => !request.ConflictingItemTpls.Contains(tpl)); + if (newListOfModsForSlot.Count() > 0) { + return newListOfModsForSlot.ToList(); + } + } + + // Return full mod pool + return request.ItemModPool[request.ModSlot]; + } + + // Tried everything, return mod pool + return request.ItemModPool[request.ModSlot]; } public Item GetMatchingModFromPreset(ModToSpawnRequest request, TemplateItem weaponTemplate) diff --git a/Core/Models/Spt/Bots/GenerateWeaponRequest.cs b/Core/Models/Spt/Bots/GenerateWeaponRequest.cs index 128077fb..54af2fec 100644 --- a/Core/Models/Spt/Bots/GenerateWeaponRequest.cs +++ b/Core/Models/Spt/Bots/GenerateWeaponRequest.cs @@ -43,7 +43,7 @@ public class GenerateWeaponRequest /** Array of item tpls the weapon does not support */ [JsonPropertyName("conflictingItemTpls")] - public HashSet? ConflictingItemTpls { get; set; } + public List? ConflictingItemTpls { get; set; } } public class BotData @@ -98,4 +98,4 @@ public class ItemCount { [JsonPropertyName("count")] public int? Count { get; set; } -} \ No newline at end of file +} diff --git a/Core/Models/Spt/Bots/ModToSpawnRequest.cs b/Core/Models/Spt/Bots/ModToSpawnRequest.cs index 3c45078a..0d8dc81d 100644 --- a/Core/Models/Spt/Bots/ModToSpawnRequest.cs +++ b/Core/Models/Spt/Bots/ModToSpawnRequest.cs @@ -74,8 +74,8 @@ public class ModToSpawnRequest /// List of item tpls the weapon does not support /// [JsonPropertyName("conflictingItemTpls")] - public HashSet? ConflictingItemTpls { get; set; } + public List? ConflictingItemTpls { get; set; } [JsonPropertyName("botData")] public BotData? BotData { get; set; } -} \ No newline at end of file +} From 761aaff84c1aaff4ba93f569bc1aa478765aea59 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 16 Jan 2025 11:33:13 +0000 Subject: [PATCH 02/14] implement GetCompatibleModFromPool --- Core/Generators/BotEquipmentModGenerator.cs | 231 +++++++++++++++----- 1 file changed, 180 insertions(+), 51 deletions(-) diff --git a/Core/Generators/BotEquipmentModGenerator.cs b/Core/Generators/BotEquipmentModGenerator.cs index 78d7c05b..86c6c333 100644 --- a/Core/Generators/BotEquipmentModGenerator.cs +++ b/Core/Generators/BotEquipmentModGenerator.cs @@ -659,9 +659,11 @@ public class BotEquipmentModGenerator public List SortModKeys(List unsortedSlotKeys, string itemTplWithKeysToSort) { // No need to sort with only 1 item in array - if (unsortedSlotKeys.Count <= 1) { + if (unsortedSlotKeys.Count <= 1) + { return unsortedSlotKeys; } + var isMount = _itemHelper.IsOfBaseclass(itemTplWithKeysToSort, BaseClasses.MOUNT); List sortedKeys = []; @@ -677,63 +679,78 @@ public class BotEquipmentModGenerator var modScope000Key = "mod_scope_000"; // Mounts are a special case, they need scopes first before more mounts - if (isMount) { - if (unsortedSlotKeys.Contains(modScope000Key)) { + if (isMount) + { + if (unsortedSlotKeys.Contains(modScope000Key)) + { sortedKeys.Add(modScope000Key); unsortedSlotKeys.Remove(modScope000Key); } - if (unsortedSlotKeys.Contains(modScopeKey)) { + if (unsortedSlotKeys.Contains(modScopeKey)) + { sortedKeys.Add(modScopeKey); unsortedSlotKeys.Remove(modScopeKey); } - if (unsortedSlotKeys.Contains(modMountKey)) { + if (unsortedSlotKeys.Contains(modMountKey)) + { sortedKeys.Add(modMountKey); unsortedSlotKeys.Remove(modMountKey); } - } else { - if (unsortedSlotKeys.Contains(modHandguardKey)) { + } + else + { + if (unsortedSlotKeys.Contains(modHandguardKey)) + { sortedKeys.Add(modHandguardKey); unsortedSlotKeys.Remove(modHandguardKey); } - if (unsortedSlotKeys.Contains(modBarrelKey)) { + if (unsortedSlotKeys.Contains(modBarrelKey)) + { sortedKeys.Add(modBarrelKey); unsortedSlotKeys.Remove(modBarrelKey); } - if (unsortedSlotKeys.Contains(modMount001Key)) { + if (unsortedSlotKeys.Contains(modMount001Key)) + { sortedKeys.Add(modMount001Key); unsortedSlotKeys.Remove(modMount001Key); } - if (unsortedSlotKeys.Contains(modRecieverKey)) { + if (unsortedSlotKeys.Contains(modRecieverKey)) + { sortedKeys.Add(modRecieverKey); unsortedSlotKeys.Remove(modRecieverKey); } - if (unsortedSlotKeys.Contains(modPistolGrip)) { + if (unsortedSlotKeys.Contains(modPistolGrip)) + { sortedKeys.Add(modPistolGrip); unsortedSlotKeys.Remove(modPistolGrip); } - if (unsortedSlotKeys.Contains(modGasBlockKey)) { + if (unsortedSlotKeys.Contains(modGasBlockKey)) + { sortedKeys.Add(modGasBlockKey); unsortedSlotKeys.Remove(modGasBlockKey); } - if (unsortedSlotKeys.Contains(modStockKey)) { + if (unsortedSlotKeys.Contains(modStockKey)) + { sortedKeys.Add(modStockKey); unsortedSlotKeys.Remove(modStockKey); } - if (unsortedSlotKeys.Contains(modMountKey)) { + if (unsortedSlotKeys.Contains(modMountKey)) + { sortedKeys.Add(modMountKey); unsortedSlotKeys.Remove(modMountKey); } - if (unsortedSlotKeys.Contains(modScopeKey)) { + if (unsortedSlotKeys.Contains(modScopeKey)) + { sortedKeys.Add(modScopeKey); unsortedSlotKeys.Remove(modScopeKey); } @@ -805,41 +822,52 @@ public class BotEquipmentModGenerator var weaponTemplate = _itemHelper.GetItem(request.Weapon[0].Template).Value; // It's ammo, use predefined ammo parameter - if (GetAmmoContainers().Contains(request.ModSlot) && request.ModSlot != "mod_magazine") { + if (GetAmmoContainers().Contains(request.ModSlot) && request.ModSlot != "mod_magazine") + { return _itemHelper.GetItem(request.AmmoTpl); } // Ensure there's a pool of mods to pick from var modPool = GetModPoolForSlot(request, weaponTemplate); - if (modPool is null && !(parentSlot?.Required ?? false)) { + if (modPool is null && !(parentSlot?.Required ?? false)) + { // Nothing in mod pool + item not required _logger.Debug($"Mod pool for optional slot: {request.ModSlot} on item: {request.ParentTemplate.Name} was empty, skipping mod"); return null; } // Filter out non-whitelisted scopes, use full modpool if filtered pool would have no elements - if (request.ModSlot.Contains("mod_scope") && request.BotWeaponSightWhitelist is not null) { + if (request.ModSlot.Contains("mod_scope") && request.BotWeaponSightWhitelist is not null) + { // scope pool has more than one scope - if (modPool.Count > 1) { + if (modPool.Count > 1) + { modPool = FilterSightsByWeaponType(request.Weapon[0], modPool, request.BotWeaponSightWhitelist); } } - if (request.ModSlot == "mod_gas_block") { - if (request.WeaponStats.HasOptic ?? false && modPool.Count > 1) { + if (request.ModSlot == "mod_gas_block") + { + if (request.WeaponStats.HasOptic ?? false && modPool.Count > 1) + { // Attempt to limit modpool to low profile gas blocks when weapon has an optic - var onlyLowProfileGasBlocks = modPool.Where((tpl) => - _botConfig.LowProfileGasBlockTpls.Contains(tpl) + var onlyLowProfileGasBlocks = modPool.Where( + (tpl) => + _botConfig.LowProfileGasBlockTpls.Contains(tpl) ); - if (onlyLowProfileGasBlocks.Count() > 0) { + if (onlyLowProfileGasBlocks.Count() > 0) + { modPool = onlyLowProfileGasBlocks.ToList(); } - } else if (request.WeaponStats.HasRearIronSight ?? false && modPool.Count() > 1) { + } + else if (request.WeaponStats.HasRearIronSight ?? false && modPool.Count() > 1) + { // Attempt to limit modpool to high profile gas blocks when weapon has rear iron sight + no front iron sight var onlyHighProfileGasBlocks = modPool.Where( (tpl) => !_botConfig.LowProfileGasBlockTpls.Contains(tpl) ); - if (onlyHighProfileGasBlocks.Count() > 0) { + if (onlyHighProfileGasBlocks.Count() > 0) + { modPool = onlyHighProfileGasBlocks.ToList(); } } @@ -850,7 +878,8 @@ public class BotEquipmentModGenerator request?.ModSlot == "mod_magazine" && (request?.IsRandomisableSlot ?? false) && request.RandomisationSettings.MinimumMagazineSize is not null - ) { + ) + { modPool = GetFilterdMagazinePoolByCapacity(request, modPool); } @@ -863,30 +892,38 @@ public class BotEquipmentModGenerator request.Weapon, request.ModSlot ); - if (chosenModResult.SlotBlocked ?? false && !(parentSlot.Required ?? false)) { + if (chosenModResult.SlotBlocked ?? false && !(parentSlot.Required ?? false)) + { // Don't bother trying to fit mod, slot is completely blocked return null; } // Log if mod chosen was incompatible - if (chosenModResult.Incompatible ?? false && !(parentSlot.Required ?? false)) { + if (chosenModResult.Incompatible ?? false && !(parentSlot.Required ?? false)) + { _logger.Debug(chosenModResult.Reason); } // Get random mod to attach from items db for required slots if none found above - if (!(chosenModResult.Found ?? false) && parentSlot != null && (parentSlot.Required ?? false)) { + if (!(chosenModResult.Found ?? false) && parentSlot != null && (parentSlot.Required ?? false)) + { chosenModResult.ChosenTemplate = GetRandomModTplFromItemDb("", parentSlot, request.ModSlot, request.Weapon); chosenModResult.Found = true; } // Compatible item not found + not required - if (!(chosenModResult.Found ?? false) && parentSlot != null && (!parentSlot.Required ?? false)) { + if (!(chosenModResult.Found ?? false) && parentSlot != null && (!parentSlot.Required ?? false)) + { return null; } - if (!(chosenModResult.Found ?? false) && parentSlot != null) { - if (parentSlot.Required ?? false) { - _logger.Warning($"Required slot unable to be filled, {request.ModSlot} on {request.ParentTemplate.Name} {request.ParentTemplate.Id} for weapon: {request.Weapon[0].Template}"); + if (!(chosenModResult.Found ?? false) && parentSlot != null) + { + if (parentSlot.Required ?? false) + { + _logger.Warning( + $"Required slot unable to be filled, {request.ModSlot} on {request.ParentTemplate.Name} {request.ParentTemplate.Id} for weapon: {request.Weapon[0].Template}" + ); } return null; @@ -923,8 +960,10 @@ public class BotEquipmentModGenerator { // Filter out incompatible mods from pool var preFilteredModPool = GetFilteredModPool(modPool, request.ConflictingItemTpls); - if (preFilteredModPool.Count == 0) { - return new () { + if (preFilteredModPool.Count == 0) + { + return new() + { Incompatible = true, Found = false, Reason = $"Unable to add mod to {choiceTypeEnum.ToString()} slot: {modSlotName}. All: {modPool.Count()} had conflicts" @@ -933,8 +972,9 @@ public class BotEquipmentModGenerator // Filter mod pool to only items that appear in parents allowed list preFilteredModPool = preFilteredModPool.Where((tpl) => parentSlot.Props.Filters[0].Filter.Contains(tpl)).ToList(); - if (preFilteredModPool.Count() == 0) { - return new () { Incompatible = true, Found = false, Reason = "No mods found in parents allowed list" }; + if (preFilteredModPool.Count() == 0) + { + return new() { Incompatible = true, Found = false, Reason = "No mods found in parents allowed list" }; } return GetCompatibleModFromPool(preFilteredModPool, choiceTypeEnum, weapon); @@ -949,7 +989,83 @@ public class BotEquipmentModGenerator /// IChooseRandomCompatibleModResult public ChooseRandomCompatibleModResult GetCompatibleModFromPool(List modPool, ModSpawn? modSpawnType, List weapon) { - throw new NotImplementedException(); + // Create exhaustable pool to pick mod item from + var exhaustableModPool = CreateExhaustableArray(modPool); + + // Create default response if no compatible item is found below + ChooseRandomCompatibleModResult chosenModResult = new() + { + Incompatible = true, + Found = false, + Reason = "unknown", + }; + + // Limit how many attempts to find a compatible mod can occur before giving up + var maxBlockedAttempts = Math.Round(modPool.Count() * 0.75); // 75% of pool size + var blockedAttemptCount = 0; + string chosenTpl = null; + while (exhaustableModPool.HasValues()) + { + chosenTpl = exhaustableModPool.GetRandomValue(); + var pickedItemDetails = _itemHelper.GetItem(chosenTpl); + if (!pickedItemDetails.Key) + { + // Not valid item, try again + continue; + } + + if (pickedItemDetails.Value.Properties is null) + { + // no props data, try again + continue; + } + + // Success - Default wanted + only 1 item in pool + if (modSpawnType == ModSpawn.DEFAULT_MOD && modPool.Count() == 1) + { + chosenModResult.Found = true; + chosenModResult.Incompatible = false; + chosenModResult.ChosenTemplate = chosenTpl; + + break; + } + + // Check if existing weapon mods are incompatible with chosen item + var existingItemBlockingChoice = weapon.FirstOrDefault( + (item) => + pickedItemDetails.Value.Properties.ConflictingItems?.Contains(item.Template) ?? false + ); + if (existingItemBlockingChoice is not null) + { + // Give max of x attempts of picking a mod if blocked by another + if (blockedAttemptCount > maxBlockedAttempts) + { + blockedAttemptCount = 0; // reset + break; + } + + blockedAttemptCount++; + + // Not compatible - Try again + continue; + } + + // Edge case- Some mod combos will never work, make sure this isnt the case + if (WeaponModComboIsIncompatible(weapon, chosenTpl)) + { + chosenModResult.Reason = $"Chosen weapon mod: { chosenTpl } can never be compatible with existing weapon mods"; + break; + } + + // Success + chosenModResult.Found = true; + chosenModResult.Incompatible = false; + chosenModResult.ChosenTemplate = chosenTpl; + + break; + } + + return chosenModResult; } public ExhaustableArray CreateExhaustableArray(List itemsToAddToArray) // TODO: this wont likely be needed, reimplement for C# @@ -997,8 +1113,10 @@ public class BotEquipmentModGenerator public List GetModPoolForDefaultSlot(ModToSpawnRequest request, TemplateItem weaponTemplate) { var matchingModFromPreset = GetMatchingModFromPreset(request, weaponTemplate); - if (matchingModFromPreset is null) { - if (request.ItemModPool[request.ModSlot]?.Count > 1) { + if (matchingModFromPreset is null) + { + if (request.ItemModPool[request.ModSlot]?.Count > 1) + { _logger.Debug($"{request.BotData.Role} No default: {request.ModSlot} mod found for: {weaponTemplate.Name}, using existing pool"); } @@ -1010,7 +1128,8 @@ public class BotEquipmentModGenerator // Filtering mod pool to item that wasnt already there can have problems; // You'd have a mod being picked without any sub-mods in its chain, possibly resulting in missing required mods not being added // Mod is in existing mod pool - if (request.ItemModPool[request.ModSlot].Contains(matchingModFromPreset.Template)) { + if (request.ItemModPool[request.ModSlot].Contains(matchingModFromPreset.Template)) + { // Found mod on preset + it already exists in mod pool return [matchingModFromPreset.Template]; } @@ -1018,30 +1137,40 @@ public class BotEquipmentModGenerator // Get an array of items that are allowed in slot from parent item // Check the filter of the slot to ensure a chosen mod fits var parentSlotCompatibleItems = request.ParentTemplate.Properties.Slots?.FirstOrDefault( - (slot) => slot.Name.ToLower() == request.ModSlot.ToLower() - )?.Props.Filters[0].Filter; + (slot) => slot.Name.ToLower() == request.ModSlot.ToLower() + ) + ?.Props.Filters[0].Filter; // Mod isnt in existing pool, only add if it has no children and exists inside parent filter if ( - parentSlotCompatibleItems?.Contains(matchingModFromPreset.Template) ?? false && + parentSlotCompatibleItems?.Contains(matchingModFromPreset.Template) ?? + false && _itemHelper.GetItem(matchingModFromPreset.Template).Value.Properties.Slots?.Count == 0 - ) { + ) + { // Chosen mod has no conflicts + no children + is in parent compat list - if (!request.ConflictingItemTpls.Contains(matchingModFromPreset.Template)) { + if (!request.ConflictingItemTpls.Contains(matchingModFromPreset.Template)) + { return [matchingModFromPreset.Template]; } // Above chosen mod had conflicts with existing weapon mods - _logger.Debug($"{request.BotData.Role} Chosen default: {request.ModSlot} mod found for: {weaponTemplate.Name} weapon conflicts with item on weapon, cannot use default"); + _logger.Debug( + $"{request.BotData.Role} Chosen default: {request.ModSlot} mod found for: {weaponTemplate.Name} weapon conflicts with item on weapon, cannot use default" + ); var existingModPool = request.ItemModPool[request.ModSlot]; - if (existingModPool.Count == 1) { + if (existingModPool.Count == 1) + { // The only item in pool isn't compatible - _logger.Debug($"{request.BotData.Role} {request.ModSlot} Mod pool for: {weaponTemplate.Name} weapon has only incompatible items, using parent list instead"); + _logger.Debug( + $"{request.BotData.Role} {request.ModSlot} Mod pool for: {weaponTemplate.Name} weapon has only incompatible items, using parent list instead" + ); // Last ditch, use full pool of items minus conflicts var newListOfModsForSlot = parentSlotCompatibleItems.Where((tpl) => !request.ConflictingItemTpls.Contains(tpl)); - if (newListOfModsForSlot.Count() > 0) { + if (newListOfModsForSlot.Count() > 0) + { return newListOfModsForSlot.ToList(); } } From bea84c873e6ec984b7d0e78333abccecc94b1d9a Mon Sep 17 00:00:00 2001 From: Chomp Date: Thu, 16 Jan 2025 11:36:05 +0000 Subject: [PATCH 03/14] partially implemented `notificationService` --- Core/Services/NotificationService.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Core/Services/NotificationService.cs b/Core/Services/NotificationService.cs index 2ba21dae..b436bbf2 100644 --- a/Core/Services/NotificationService.cs +++ b/Core/Services/NotificationService.cs @@ -1,4 +1,4 @@ -using Core.Annotations; +using Core.Annotations; using Core.Models.Eft.Ws; namespace Core.Services; @@ -6,6 +6,8 @@ namespace Core.Services; [Injectable(InjectionType.Singleton)] public class NotificationService { + protected Dictionary> _messageQueue = new(); + public Dictionary> GetMessageQueue() { throw new NotImplementedException(); @@ -16,20 +18,20 @@ public class NotificationService throw new NotImplementedException(); } - public void UpdateMessageOnQueue(string sessionId, List value) + public void UpdateMessageOnQueue(string sessionId, List value) { throw new NotImplementedException(); } public bool Has(string sessionID) { - throw new NotImplementedException(); + return _messageQueue.ContainsKey(sessionID); } /// /// Pop first message from queue. /// - public object Pop(string sessionID) + public WsNotificationEvent Pop(string sessionID) { throw new NotImplementedException(); } @@ -39,15 +41,25 @@ public class NotificationService /// public void Add(string sessionID, WsNotificationEvent message) { - throw new NotImplementedException(); + Get(sessionID).Add(message); } /// /// Get message queue for session /// /// - public List Get(string sessionID) + public List Get(string sessionID) { - throw new NotImplementedException(); + if (sessionID is null) + { + throw new Exception("sessionID missing"); + } + + if (!_messageQueue.ContainsKey(sessionID)) + { + _messageQueue[sessionID] = []; + } + + return _messageQueue[sessionID]; } } From c07c5982b526429356af18813e3923277363fa12 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 16 Jan 2025 11:39:20 +0000 Subject: [PATCH 04/14] implement FilterSightsByWeaponType --- Core/Generators/BotEquipmentModGenerator.cs | 54 ++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/Core/Generators/BotEquipmentModGenerator.cs b/Core/Generators/BotEquipmentModGenerator.cs index 86c6c333..48c586c3 100644 --- a/Core/Generators/BotEquipmentModGenerator.cs +++ b/Core/Generators/BotEquipmentModGenerator.cs @@ -1412,6 +1412,58 @@ public class BotEquipmentModGenerator /// Array of scope tpls that have been filtered to just ones allowed for that weapon type public List FilterSightsByWeaponType(Item weapon, List scopes, Dictionary> botWeaponSightWhitelist) { - throw new NotImplementedException(); + var weaponDetails = _itemHelper.GetItem(weapon.Template); + + // Return original scopes array if whitelist not found + var whitelistedSightTypes = botWeaponSightWhitelist[weaponDetails.Value.Parent]; + if (whitelistedSightTypes is null) { + _logger.Debug($"Unable to find whitelist for weapon type: {weaponDetails.Value.Parent} {weaponDetails.Value.Name}, skipping sight filtering"); + + return scopes; + } + + // Filter items that are not directly scopes OR mounts that do not hold the type of scope we allow for this weapon type + List filteredScopesAndMods = []; + foreach (var item in scopes) { + // Mods is a scope, check base class is allowed + if (_itemHelper.IsOfBaseclasses(item, whitelistedSightTypes)) { + // Add mod to allowed list + filteredScopesAndMods.Add(item); + continue; + } + + // Edge case, what if item is a mount for a scope and not directly a scope? + // Check item is mount + has child items + var itemDetails = _itemHelper.GetItem(item).Value; + if (_itemHelper.IsOfBaseclass(item, BaseClasses.MOUNT) && itemDetails.Properties.Slots.Count() > 0) { + // Check to see if mount has a scope slot (only include primary slot, ignore the rest like the backup sight slots) + // Should only find 1 as there's currently no items with a mod_scope AND a mod_scope_000 + List filter = ["mod_scope", "mod_scope_000"]; + var scopeSlot = itemDetails.Properties.Slots.Where((slot) => + filter.Contains(slot.Name) + ); + + // Mods scope slot found must allow ALL whitelisted scope types OR be a mount + if (scopeSlot?.All((slot) => + slot.Props.Filters[0].Filter.All((tpl) => + _itemHelper.IsOfBaseclasses(tpl, whitelistedSightTypes) || + _itemHelper.IsOfBaseclass(tpl, BaseClasses.MOUNT) + ) + ) ?? false) + { + // Add mod to allowed list + filteredScopesAndMods.Add(item); + } + } + } + + // No mods added to return list after filtering has occurred, send back the original mod list + if (filteredScopesAndMods is null || filteredScopesAndMods.Count() == 0) { + _logger.Debug($"Scope whitelist too restrictive for: {weapon.Template} {weaponDetails.Value.Name}, skipping filter"); + + return scopes; + } + + return filteredScopesAndMods; } } From fbe2cd857bbfba39760268447141bd7ba1528e56 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 16 Jan 2025 11:57:20 +0000 Subject: [PATCH 05/14] implement more in botequipmentmodgen --- Core/Generators/BotEquipmentModGenerator.cs | 189 ++++++++++++++++++-- 1 file changed, 171 insertions(+), 18 deletions(-) diff --git a/Core/Generators/BotEquipmentModGenerator.cs b/Core/Generators/BotEquipmentModGenerator.cs index 48c586c3..a16b1e97 100644 --- a/Core/Generators/BotEquipmentModGenerator.cs +++ b/Core/Generators/BotEquipmentModGenerator.cs @@ -800,7 +800,8 @@ public class BotEquipmentModGenerator return ModSpawn.SPAWN; } - var spawnMod = _probabilityHelper.RollChance(modSpawnChances[modSlotName]); + + var spawnMod = _probabilityHelper.RollChance(modSpawnChances.GetValueOrDefault(modSlotName)); if (!spawnMod && (slotRequired.GetValueOrDefault(false) || botEquipConfig.WeaponSlotIdsToMakeRequired.Contains(modSlotName))) { // Edge case: Mod is required but spawn chance roll failed, choose default mod spawn for slot @@ -1053,7 +1054,7 @@ public class BotEquipmentModGenerator // Edge case- Some mod combos will never work, make sure this isnt the case if (WeaponModComboIsIncompatible(weapon, chosenTpl)) { - chosenModResult.Reason = $"Chosen weapon mod: { chosenTpl } can never be compatible with existing weapon mods"; + chosenModResult.Reason = $"Chosen weapon mod: {chosenTpl} can never be compatible with existing weapon mods"; break; } @@ -1304,7 +1305,51 @@ public class BotEquipmentModGenerator public bool IsModValidForSlot(KeyValuePair? modToAdd, Slot slotAddedToTemplate, string modSlot, TemplateItem parentTemplate, string botRole) { - throw new NotImplementedException(); + var modBeingAddedDbTemplate = modToAdd.Value; + + // Mod lacks db template object + if (modBeingAddedDbTemplate.Value is null) + { + _logger.Error( + _localisationService.GetText( + "bot-no_item_template_found_when_adding_mod", + new + { + ModId = modBeingAddedDbTemplate.Value?.Id ?? "UNKNOWN", + ModSlot = modSlot, + } + ) + ); + _logger.Debug($"Item -> {parentTemplate?.Id}; Slot -> {modSlot}"); + + return false; + } + + // Mod has invalid db item + if (!modToAdd.HasValue) + { + // Parent slot must be filled but db object is invalid, show warning and return false + if (slotAddedToTemplate.Required ?? false) + { + _logger.Warning( + _localisationService.GetText( + "bot-unable_to_add_mod_item_invalid", + new + { + ItemName = modBeingAddedDbTemplate.Value?.Name ?? "UNKNOWN", + ModSlot = modSlot, + ParentItemName = parentTemplate.Name, + BotRole = botRole, + } + ) + ); + } + + return false; + } + + // Mod was found in db + return true; } /// @@ -1317,7 +1362,37 @@ public class BotEquipmentModGenerator public void AddCompatibleModsForProvidedMod(string desiredSlotName, TemplateItem modTemplate, Dictionary>> modPool, EquipmentFilterDetails botEquipBlacklist) { - throw new NotImplementedException(); + var desiredSlotObject = modTemplate.Properties.Slots?.FirstOrDefault((slot) => slot.Name.Contains(desiredSlotName)); + if (desiredSlotObject is not null) + { + var supportedSubMods = desiredSlotObject.Props.Filters[0].Filter; + if (supportedSubMods is not null) + { + // Filter mods + var filteredMods = FilterModsByBlacklist(supportedSubMods, botEquipBlacklist, desiredSlotName); + if (filteredMods.Count() == 0) + { + _logger.Warning( + _localisationService.GetText( + "bot-unable_to_filter_mods_all_blacklisted", + new + { + SlotName = desiredSlotObject.Name, + ItemName = modTemplate.Name, + } + ) + ); + filteredMods = supportedSubMods; + } + + if (modPool[modTemplate.Id] is null) + { + modPool[modTemplate.Id] = new(); + } + + modPool[modTemplate.Id][desiredSlotObject.Name] = supportedSubMods; + } + } } /// @@ -1381,7 +1456,75 @@ public class BotEquipmentModGenerator public void FillCamora(List items, Dictionary>> modPool, string cylinderMagParentId, TemplateItem cylinderMagTemplate) { - throw new NotImplementedException(); + var itemModPool = modPool[cylinderMagTemplate.Id]; + if (itemModPool is null) + { + _logger.Warning( + _localisationService.GetText( + "bot-unable_to_fill_camora_slot_mod_pool_empty", + new + { + WeaponId = cylinderMagTemplate.Id, + WeaponName = cylinderMagTemplate.Name, + } + ) + ); + var camoraSlots = cylinderMagTemplate.Properties.Slots.Where((slot) => slot.Name.StartsWith("camora")); + + // Attempt to generate camora slots for item + modPool[cylinderMagTemplate.Id] = new(); + foreach (var camora in camoraSlots) + { + modPool[cylinderMagTemplate.Id][camora.Name] = camora.Props.Filters[0].Filter; + } + + itemModPool = modPool[cylinderMagTemplate.Id]; + } + + ExhaustableArray exhaustableModPool = null; + var modSlot = "cartridges"; + var camoraFirstSlot = "camora_000"; + if (itemModPool[modSlot] is not null) + { + exhaustableModPool = CreateExhaustableArray(itemModPool[modSlot]); + } + else if (itemModPool[camoraFirstSlot] is not null) + { + modSlot = camoraFirstSlot; + exhaustableModPool = CreateExhaustableArray(MergeCamoraPools(itemModPool)); + } + else + { + _logger.Error(_localisationService.GetText("bot-missing_cartridge_slot", cylinderMagTemplate.Id)); + + return; + } + + string modTpl = null; + bool found = false; + while (exhaustableModPool.HasValues()) + { + modTpl = exhaustableModPool.GetRandomValue(); + if (!_botGeneratorHelper.IsItemIncompatibleWithCurrentItems(items, modTpl, modSlot).Incompatible.GetValueOrDefault(false)) + { + found = true; + break; + } + } + + if (!found) + { + _logger.Error(_localisationService.GetText("bot-no_compatible_camora_ammo_found", modSlot)); + + return; + } + + foreach (var slot in cylinderMagTemplate.Properties.Slots) + { + var modSlotId = slot.Name; + var modId = _hashUtil.Generate(); + items.Add(new() { Id = modId, Template = modTpl, ParentId = cylinderMagParentId, SlotId = modSlotId }); + } } /// @@ -1416,7 +1559,8 @@ public class BotEquipmentModGenerator // Return original scopes array if whitelist not found var whitelistedSightTypes = botWeaponSightWhitelist[weaponDetails.Value.Parent]; - if (whitelistedSightTypes is null) { + if (whitelistedSightTypes is null) + { _logger.Debug($"Unable to find whitelist for weapon type: {weaponDetails.Value.Parent} {weaponDetails.Value.Name}, skipping sight filtering"); return scopes; @@ -1424,9 +1568,11 @@ public class BotEquipmentModGenerator // Filter items that are not directly scopes OR mounts that do not hold the type of scope we allow for this weapon type List filteredScopesAndMods = []; - foreach (var item in scopes) { + foreach (var item in scopes) + { // Mods is a scope, check base class is allowed - if (_itemHelper.IsOfBaseclasses(item, whitelistedSightTypes)) { + if (_itemHelper.IsOfBaseclasses(item, whitelistedSightTypes)) + { // Add mod to allowed list filteredScopesAndMods.Add(item); continue; @@ -1435,21 +1581,27 @@ public class BotEquipmentModGenerator // Edge case, what if item is a mount for a scope and not directly a scope? // Check item is mount + has child items var itemDetails = _itemHelper.GetItem(item).Value; - if (_itemHelper.IsOfBaseclass(item, BaseClasses.MOUNT) && itemDetails.Properties.Slots.Count() > 0) { + if (_itemHelper.IsOfBaseclass(item, BaseClasses.MOUNT) && itemDetails.Properties.Slots.Count() > 0) + { // Check to see if mount has a scope slot (only include primary slot, ignore the rest like the backup sight slots) // Should only find 1 as there's currently no items with a mod_scope AND a mod_scope_000 List filter = ["mod_scope", "mod_scope_000"]; - var scopeSlot = itemDetails.Properties.Slots.Where((slot) => - filter.Contains(slot.Name) + var scopeSlot = itemDetails.Properties.Slots.Where( + (slot) => + filter.Contains(slot.Name) ); // Mods scope slot found must allow ALL whitelisted scope types OR be a mount - if (scopeSlot?.All((slot) => - slot.Props.Filters[0].Filter.All((tpl) => - _itemHelper.IsOfBaseclasses(tpl, whitelistedSightTypes) || - _itemHelper.IsOfBaseclass(tpl, BaseClasses.MOUNT) - ) - ) ?? false) + if (scopeSlot?.All( + (slot) => + slot.Props.Filters[0] + .Filter.All( + (tpl) => + _itemHelper.IsOfBaseclasses(tpl, whitelistedSightTypes) || + _itemHelper.IsOfBaseclass(tpl, BaseClasses.MOUNT) + ) + ) ?? + false) { // Add mod to allowed list filteredScopesAndMods.Add(item); @@ -1458,7 +1610,8 @@ public class BotEquipmentModGenerator } // No mods added to return list after filtering has occurred, send back the original mod list - if (filteredScopesAndMods is null || filteredScopesAndMods.Count() == 0) { + if (filteredScopesAndMods is null || filteredScopesAndMods.Count() == 0) + { _logger.Debug($"Scope whitelist too restrictive for: {weapon.Template} {weaponDetails.Value.Name}, skipping filter"); return scopes; From 78fd9616dfa6763980a69f5995f32eb77e935603 Mon Sep 17 00:00:00 2001 From: Chomp Date: Thu, 16 Jan 2025 12:11:05 +0000 Subject: [PATCH 06/14] Further implementations to game start process --- Core/Controllers/GameController.cs | 16 ++++- Core/Helpers/HideoutHelper.cs | 87 ++++++++++++++++++++++++-- Core/Models/Eft/Hideout/HideoutArea.cs | 41 +----------- Core/Services/ProfileFixerService.cs | 75 +++++++++++++++++++++- 4 files changed, 171 insertions(+), 48 deletions(-) diff --git a/Core/Controllers/GameController.cs b/Core/Controllers/GameController.cs index ec0d79da..3731442d 100644 --- a/Core/Controllers/GameController.cs +++ b/Core/Controllers/GameController.cs @@ -3,6 +3,7 @@ using Core.Context; using Core.Helpers; using Core.Models.Eft.Common; using Core.Models.Eft.Game; +using Core.Models.Eft.Match; using Core.Models.Eft.Profile; using Core.Models.Enums; using Core.Models.External; @@ -12,6 +13,8 @@ using Core.Servers; using Core.Services; using Core.Utils; using Core.Utils.Cloners; +using static System.Runtime.InteropServices.JavaScript.JSType; +using System.Diagnostics; namespace Core.Controllers; @@ -570,7 +573,11 @@ public class GameController /// Profile to check for dialog in private void CheckForAndRemoveUndefinedDialogues(SptProfile fullProfile) { - throw new NotImplementedException(); + + if (fullProfile.DialogueRecords.TryGetValue("undefined", out var undefinedDialog)) + { + fullProfile.DialogueRecords.Remove("undefined"); + } } /// @@ -579,7 +586,12 @@ public class GameController /// private void LogProfileDetails(SptProfile fullProfile) { - throw new NotImplementedException(); + _logger.Error("NOT IMPLEMENTED LogProfileDetails"); + _logger.Debug($"Profile made with: ${ fullProfile.SptData.Version}"); + _logger.Debug($"{fullProfile.SptData.Mods.Count} Mods used"); + //_logger.Debug($"Server version: ${ ProgramStatics.SPT_VERSION || _coreConfig.SptVersion} ${ ProgramStatics.COMMIT}"); + //_logger.Debug($"Debug enabled: ${ ProgramStatics.DEBUG}"); + //_logger.Debug($"Mods enabled: ${ ProgramStatics.MODS}"); } public void Load() diff --git a/Core/Helpers/HideoutHelper.cs b/Core/Helpers/HideoutHelper.cs index fe4f82a8..ea26eac3 100644 --- a/Core/Helpers/HideoutHelper.cs +++ b/Core/Helpers/HideoutHelper.cs @@ -1,15 +1,32 @@ -using Core.Annotations; +using Core.Annotations; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.Hideout; using Core.Models.Eft.ItemEvent; using Core.Models.Enums; +using Core.Models.Utils; +using Core.Services; +using Core.Utils; namespace Core.Helpers; [Injectable] public class HideoutHelper { + private readonly ISptLogger _logger; + protected TimeUtil _timeUtil; + private readonly LocalisationService _localisationService; + + public HideoutHelper( + ISptLogger logger, + TimeUtil timeUtil, + LocalisationService localisationService) + { + _logger = logger; + _timeUtil = timeUtil; + _localisationService = localisationService; + } + /// /// Add production to profiles' Hideout.Production array /// @@ -52,9 +69,41 @@ public class HideoutHelper /// /// Profile to add bonus to /// Bonus to add to profile - public void ApplyPlayerUpgradesBonuses(PmcData profileData, StageBonus bonus) + public void ApplyPlayerUpgradesBonuses(PmcData profileData, Bonus bonus) { - throw new NotImplementedException(); + // Handle additional changes some bonuses need before being added + var bonusToAdd = new Bonus(); + switch (bonus.Type) + { + case BonusType.StashSize: + { + // Find stash item and adjust tpl to new tpl from bonus + var stashItem = profileData.Inventory.Items.FirstOrDefault((x) => x.Id == profileData.Inventory.Stash); + if (stashItem is null) + { + _logger.Warning(_localisationService.GetText("hideout-unable_to_apply_stashsize_bonus_no_stash_found", profileData.Inventory.Stash)); + } + + stashItem.Template = bonus.TemplateId; + + break; + } + case BonusType.MaximumEnergyReserve: + // Amend max energy in profile + profileData.Health.Energy.Maximum += bonus.Value; + break; + case BonusType.TextBonus: + // Delete values before they're added to profile + bonus.IsPassive = null; + bonus.IsProduction = null; + bonus.IsVisible = null; + break; + } + + // Add bonus to player bonuses array in profile + // EnergyRegeneration, HealthRegeneration, RagfairCommission, ScavCooldownTimer, SkillGroupLevelingBoost, ExperienceRate, QuestMoneyReward etc + _logger.Debug($"Adding bonus: {bonus.Type} to profile, value: {bonus.Value}"); + profileData.Bonuses.Add(bonus); } /// @@ -420,7 +469,22 @@ public class HideoutHelper /// Profile to upgrade wall in public void UnlockHideoutWallInProfile(PmcData profileData) { - throw new NotImplementedException(); + var profileHideoutAreas = profileData.Hideout.Areas; + var waterCollector = profileHideoutAreas.FirstOrDefault((x) => x.Type == HideoutAreas.WATER_COLLECTOR); + var medStation = profileHideoutAreas.FirstOrDefault((x) => x.Type == HideoutAreas.MEDSTATION); + var wall = profileHideoutAreas.FirstOrDefault((x) => x.Type == HideoutAreas.EMERGENCY_WALL); + + // No collector or med station, skip + if ((waterCollector is null && medStation is null)) + { + return; + } + + // If med-station > level 1 AND water collector > level 1 AND wall is level 0 + if (waterCollector?.Level >= 1 && medStation?.Level >= 1 && wall?.Level <= 0) + { + wall.Level = 3; + } } /// @@ -439,7 +503,20 @@ public class HideoutHelper /// Profile to adjust public void SetHideoutImprovementsToCompleted(PmcData profileData) { - throw new NotImplementedException(); + foreach (var improvementId in profileData.Hideout.Improvements) + { + if (!profileData.Hideout.Improvements.TryGetValue(improvementId.Key, out var improvementDetails)) + { + continue; + } + + if (improvementDetails.Completed == false + && improvementDetails.ImproveCompleteTimestamp < _timeUtil.GetTimeStamp() + ) + { + improvementDetails.Completed = true; + } + } } /// diff --git a/Core/Models/Eft/Hideout/HideoutArea.cs b/Core/Models/Eft/Hideout/HideoutArea.cs index 04e7fe39..99ea612f 100644 --- a/Core/Models/Eft/Hideout/HideoutArea.cs +++ b/Core/Models/Eft/Hideout/HideoutArea.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using Core.Models.Eft.Common.Tables; using Core.Models.Enums; namespace Core.Models.Eft.Hideout; @@ -9,7 +10,7 @@ public class HideoutArea public string? Id { get; set; } [JsonPropertyName("type")] - public int? Type { get; set; } + public HideoutAreas? Type { get; set; } [JsonPropertyName("enabled")] public bool? IsEnabled { get; set; } @@ -57,7 +58,7 @@ public class Stage public bool? AutoUpgrade { get; set; } [JsonPropertyName("bonuses")] - public List? Bonuses { get; set; } + public List? Bonuses { get; set; } [JsonPropertyName("constructionTime")] public double? ConstructionTime { get; set; } @@ -181,39 +182,3 @@ public class StageRequirement : RequirementBase [JsonPropertyName("skillLevel")] public int? SkillLevel { get; set; } } - -public class StageBonus -{ - [JsonPropertyName("value")] - public int? Value { get; set; } - - [JsonPropertyName("passive")] - public bool? Passive { get; set; } - - [JsonPropertyName("production")] - public bool? Production { get; set; } - - [JsonPropertyName("visible")] - public bool? Visible { get; set; } - - [JsonPropertyName("skillType")] - [JsonConverter(typeof(JsonStringEnumConverter))] - public BonusSkillType? SkillType { get; set; } - - [JsonPropertyName("type")] - [JsonConverter(typeof(JsonStringEnumConverter))] - public BonusType? Type { get; set; } - - [JsonPropertyName("filter")] - public List? Filter { get; set; } - - [JsonPropertyName("icon")] - public string? Icon { get; set; } - - /** CHANGES PER DUMP */ - [JsonPropertyName("id")] - public string? Id { get; set; } - - [JsonPropertyName("templateId")] - public string? TemplateId { get; set; } -} \ No newline at end of file diff --git a/Core/Services/ProfileFixerService.cs b/Core/Services/ProfileFixerService.cs index 8e48dcaa..b1af922b 100644 --- a/Core/Services/ProfileFixerService.cs +++ b/Core/Services/ProfileFixerService.cs @@ -11,6 +11,10 @@ using Core.Utils; using System.Text.RegularExpressions; using Core.Models.Spt.Config; using Core.Models.Utils; +using Core.Models.Spt.Bots; +using static System.Runtime.InteropServices.JavaScript.JSType; +using System.Reflection.Emit; +using System.Security.AccessControl; namespace Core.Services; @@ -23,6 +27,7 @@ public class ProfileFixerService protected ItemHelper _itemHelper; protected QuestRewardHelper _questRewardHelper; protected TraderHelper _traderHelper; + protected HideoutHelper _hideoutHelper; protected DatabaseService _databaseService; protected LocalisationService _localisationService; protected ConfigServer _configServer; @@ -36,6 +41,7 @@ public class ProfileFixerService ItemHelper itemHelper, QuestRewardHelper questRewardHelper, TraderHelper traderHelper, + HideoutHelper hideoutHelper, DatabaseService databaseService, LocalisationService localisationService, ConfigServer configServer, @@ -48,6 +54,7 @@ public class ProfileFixerService _itemHelper = itemHelper; _questRewardHelper = questRewardHelper; _traderHelper = traderHelper; + _hideoutHelper = hideoutHelper; _databaseService = databaseService; _localisationService = localisationService; _configServer = configServer; @@ -690,7 +697,54 @@ public class ProfileFixerService */ public void AddMissingHideoutBonusesToProfile(PmcData pmcProfile) { - throw new NotImplementedException(); + var dbHideoutAreas = _databaseService.GetHideout().Areas; + + foreach (var profileArea in pmcProfile.Hideout.Areas) { + var areaType = profileArea.Type; + var level = profileArea.Level; + + if (level.GetValueOrDefault(0) == 0) + { + continue; + } + + // Get array of hideout area upgrade levels to check for bonuses + // Zero indexed + var areaLevelsToCheck = new List(); + for (var index = 0; index < level + 1; index++) + { + // Stage key is saved as string in db + areaLevelsToCheck.Add(index.ToString()); + } + + // Iterate over area levels, check for bonuses, add if needed + var dbArea = dbHideoutAreas.FirstOrDefault((area) => area.Type == areaType); + if (dbArea is null) + { + continue; + } + + foreach (var areaLevel in areaLevelsToCheck) { + // Get areas level bonuses from db + var levelBonuses = dbArea.Stages[areaLevel]?.Bonuses; + if (levelBonuses is null || levelBonuses.Count == 0) + { + continue; + } + + // Iterate over each bonus for the areas level + foreach (var bonus in levelBonuses) { + // Check if profile has bonus + var profileBonus = GetBonusFromProfile(pmcProfile.Bonuses, bonus); + if (profileBonus is null) + { + // no bonus, add to profile + _logger.Debug($"Profile has level {level} area {profileArea.Type} but no bonus found, adding { bonus.Type}"); + _hideoutHelper.ApplyPlayerUpgradesBonuses(pmcProfile, bonus); + } + } + } + } } /** @@ -698,9 +752,24 @@ public class ProfileFixerService * @param bonus bonus to find * @returns matching bonus */ - protected Bonus GetBonusFromProfile(List profileBonuses, StageBonus bonus) + protected Bonus? GetBonusFromProfile(List profileBonuses, Bonus bonus) { - throw new NotImplementedException(); + // match by id first, used by "TextBonus" bonuses + if (bonus.Id is null) + { + return profileBonuses.FirstOrDefault((x) => x.Id == bonus.Id); + } + + return bonus.Type switch + { + BonusType.StashSize => profileBonuses.FirstOrDefault( + (x) => x.Type == bonus.Type && x.TemplateId == bonus.TemplateId + ), + BonusType.AdditionalSlots => profileBonuses.FirstOrDefault( + (x) => x.Type == bonus.Type && x.Value == bonus.Value && x.IsVisible == bonus.IsVisible + ), + _ => profileBonuses.FirstOrDefault((x) => x.Type == bonus.Type && x.Value == bonus.Value) + }; } public void CheckForAndRemoveInvalidTraders(SptProfile fullProfile) From d5c2a5f29ad23896167cea39353e40be90bac77d Mon Sep 17 00:00:00 2001 From: Chomp Date: Thu, 16 Jan 2025 12:13:00 +0000 Subject: [PATCH 07/14] Improved `CheckForOrphanedModdedItems` logic --- Core/Services/ProfileFixerService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Services/ProfileFixerService.cs b/Core/Services/ProfileFixerService.cs index b1af922b..855ce730 100644 --- a/Core/Services/ProfileFixerService.cs +++ b/Core/Services/ProfileFixerService.cs @@ -491,7 +491,7 @@ public class ProfileFixerService // Check each item in inventory to ensure item exists in itemdb foreach (var item in inventoryItemsToCheck) { - if (itemsDb[item.Template] is not null) + if (!itemsDb.ContainsKey(item.Template)) { _logger.Error(_localisationService.GetText("fixer-mod_item_found", item.Template)); From 5ca4aa08d503bcfe21ac8b99693bfbd840aabeb1 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 16 Jan 2025 12:15:22 +0000 Subject: [PATCH 08/14] finish off profileHelper --- Core/Helpers/PresetHelper.cs | 66 +++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/Core/Helpers/PresetHelper.cs b/Core/Helpers/PresetHelper.cs index 08bee62d..b8b82f32 100644 --- a/Core/Helpers/PresetHelper.cs +++ b/Core/Helpers/PresetHelper.cs @@ -11,7 +11,7 @@ public class PresetHelper { protected DatabaseService _databaseService; protected ItemHelper _itemHelper; - protected ICloner _primaryCloner; + protected ICloner _cloner; protected Dictionary> _lookup = new(); protected Dictionary _defaultEquipmentPresets; @@ -21,12 +21,12 @@ public class PresetHelper ( DatabaseService databaseService, ItemHelper itemHelper, - ICloner primaryCloner + ICloner cloner ) { _databaseService = databaseService; _itemHelper = itemHelper; - _primaryCloner = primaryCloner; + _cloner = cloner; } public void HydratePresetStore(Dictionary> input) @@ -91,29 +91,40 @@ public class PresetHelper * @param baseClass The BaseClasses enum to check against * @returns True if the preset is of the given base class, false otherwise */ - public bool IsPresetBaseClass(string id, BaseClasses baseClass) + public bool IsPresetBaseClass(string id, string baseClass) { - throw new NotImplementedException(); + return IsPreset(id) && _itemHelper.IsOfBaseclass(GetPreset(id).Encyclopedia, baseClass); } public bool HasPreset(string templateId) { - throw new NotImplementedException(); + return _lookup.ContainsKey(templateId) ; } public Preset GetPreset(string id) { - throw new NotImplementedException(); + return _cloner.Clone(_databaseService.GetGlobals().ItemPresets[id]); } public List GetAllPresets() { - throw new NotImplementedException(); + return _cloner.Clone(_databaseService.GetGlobals().ItemPresets.Values.ToList()); } public List GetPresets(string templateId) { - throw new NotImplementedException(); + if (!HasPreset(templateId)) { + return []; + } + + List presets = []; + var ids = _lookup[templateId]; + + foreach (var id in ids) { + presets.Add(GetPreset(id)); + } + + return presets; } /** @@ -121,14 +132,36 @@ public class PresetHelper * @param templateId Item tpl to get preset for * @returns null if no default preset, otherwise Preset */ - public Preset GetDefaultPreset(string templateId) + public Preset? GetDefaultPreset(string templateId) { - throw new NotImplementedException(); + if (!HasPreset(templateId)) { + return null; + } + + var allPresets = GetPresets(templateId); + + foreach (var preset in allPresets) { + if (preset.Encyclopedia is not null) { + return preset; + } + } + + return allPresets[0]; } public string GetBaseItemTpl(string presetId) { - throw new NotImplementedException(); + if (IsPreset(presetId)) { + var preset = GetPreset(presetId); + + foreach (var item in preset.Items) { + if (preset.Parent == item.Id) { + return item.Template; + } + } + } + + return ""; } /** @@ -138,6 +171,13 @@ public class PresetHelper */ public decimal GetDefaultPresetOrItemPrice(string tpl) { - throw new NotImplementedException(); + // Get default preset if it exists + var defaultPreset = GetDefaultPreset(tpl); + + // Bundle up tpls we want price for + var tpls = defaultPreset is not null ? defaultPreset.Items.Select((item) => item.Template) : [tpl]; + + // Get price of tpls + return _itemHelper.GetItemAndChildrenPrice(tpls.ToList()); } } From bde1ee600806a7b95d67f623859f875f046acb6d Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 16 Jan 2025 12:22:23 +0000 Subject: [PATCH 09/14] fix check --- Core/Generators/BotEquipmentModGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Generators/BotEquipmentModGenerator.cs b/Core/Generators/BotEquipmentModGenerator.cs index a16b1e97..e9f20317 100644 --- a/Core/Generators/BotEquipmentModGenerator.cs +++ b/Core/Generators/BotEquipmentModGenerator.cs @@ -802,7 +802,7 @@ public class BotEquipmentModGenerator var spawnMod = _probabilityHelper.RollChance(modSpawnChances.GetValueOrDefault(modSlotName)); - if (!spawnMod && (slotRequired.GetValueOrDefault(false) || botEquipConfig.WeaponSlotIdsToMakeRequired.Contains(modSlotName))) + if (!spawnMod && (slotRequired.GetValueOrDefault(false) || (botEquipConfig.WeaponSlotIdsToMakeRequired?.Contains(modSlotName) ?? false))) { // Edge case: Mod is required but spawn chance roll failed, choose default mod spawn for slot return ModSpawn.DEFAULT_MOD; @@ -816,7 +816,7 @@ public class BotEquipmentModGenerator /// /// Data used to choose an appropriate mod with /// itemHelper.getItem() result - public KeyValuePair? ChooseModToPutIntoSlot(ModToSpawnRequest request) // TODO: type fuckery: [boolean, ITemplateItem] | undefined + public KeyValuePair? ChooseModToPutIntoSlot(ModToSpawnRequest request) { /** Slot mod will fill */ var parentSlot = request.ParentTemplate.Properties.Slots?.FirstOrDefault((i) => i.Name == request.ModSlot); From 432a8f337b36cc6be2579f7db7e9e21ff773b352 Mon Sep 17 00:00:00 2001 From: Chomp Date: Thu, 16 Jan 2025 12:30:56 +0000 Subject: [PATCH 10/14] Fixed nullref error --- Core/Controllers/GameController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Controllers/GameController.cs b/Core/Controllers/GameController.cs index 3731442d..3ea2616b 100644 --- a/Core/Controllers/GameController.cs +++ b/Core/Controllers/GameController.cs @@ -225,7 +225,7 @@ public class GameController var profile = _profileHelper.GetPmcProfile(sessionId); var gameTime = profile?.Stats?.Eft?.OverallCounters?.Items?.FirstOrDefault(c => c.Key.Contains("LifeTime") && - c.Key.Contains("Pmc")).Value ?? 0D; + c.Key.Contains("Pmc"))?.Value ?? 0D; var config = new GameConfigResponse { From 8e18d19e26760c1fec47e04a9a911daecf857997 Mon Sep 17 00:00:00 2001 From: Chomp Date: Thu, 16 Jan 2025 12:31:49 +0000 Subject: [PATCH 11/14] Temp implementation of `TraderEnumHasValue` --- Core/Helpers/TraderHelper.cs | 3 ++- Core/Models/Enums/Traders.cs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Core/Helpers/TraderHelper.cs b/Core/Helpers/TraderHelper.cs index c3c0bb11..0036621e 100644 --- a/Core/Helpers/TraderHelper.cs +++ b/Core/Helpers/TraderHelper.cs @@ -331,6 +331,7 @@ public class TraderHelper /// True if Traders enum has the param as a value public bool TraderEnumHasValue(string traderId) { - throw new NotImplementedException(); + _logger.Error("HACK TraderEnumHasValue"); + return Traders.TradersDictionary.ContainsValue(traderId); } } diff --git a/Core/Models/Enums/Traders.cs b/Core/Models/Enums/Traders.cs index 7d6204aa..3d66acbd 100644 --- a/Core/Models/Enums/Traders.cs +++ b/Core/Models/Enums/Traders.cs @@ -13,4 +13,34 @@ public static class Traders public const string LIGHTHOUSEKEEPER = "638f541a29ffd1183d187f57"; public const string BTR = "656f0f98d80a697f855d34b1"; public const string REF = "6617beeaa9cfa777ca915b7c"; -} \ No newline at end of file + + public static Dictionary TradersDictionary { get; set; } = new() + { + {TradersEnum.Prapor, "54cb50c76803fa8b248b4571"}, + {TradersEnum.Therapist, "54cb57776803fa99248b456e"}, + {TradersEnum.Fence, "579dc571d53a0658a154fbec"}, + {TradersEnum.Skier, "58330581ace78e27b8b10cee"}, + {TradersEnum.Peacekeeper, "5935c25fb3acc3127c3d8cd9"}, + {TradersEnum.Mechanic, "5a7c2eca46aef81a7ca2145d"}, + {TradersEnum.Ragman, "5ac3b934156ae10c4430e83c"}, + {TradersEnum.Jaeger, "5c0647fdd443bc2504c2d371"}, + {TradersEnum.LighthouseKeeper, "638f541a29ffd1183d187f57"}, + {TradersEnum.Btr, "656f0f98d80a697f855d34b1"}, + {TradersEnum.Ref, "6617beeaa9cfa777ca915b7c"} + }; +} + +public enum TradersEnum +{ + Prapor, + Therapist, + Fence, + Skier, + Peacekeeper, + Mechanic, + Ragman, + Jaeger, + LighthouseKeeper, + Btr, + Ref +} From 3845b9c856615bf96fd5bafe3afc61722a9abaf5 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 16 Jan 2025 12:33:19 +0000 Subject: [PATCH 12/14] fix logging objects to be lowerCase for localisation --- Core/Controllers/CustomizationController.cs | 4 +-- Core/Generators/BotEquipmentModGenerator.cs | 34 ++++++++++----------- Core/Generators/BotWeaponGenerator.cs | 18 +++++------ Core/Helpers/BotGeneratorHelper.cs | 12 ++++---- Core/Helpers/QuestRewardHelper.cs | 8 ++--- Core/Services/MailSendService.cs | 16 +++++----- Core/Services/SeasonalEventService.cs | 8 ++--- 7 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Core/Controllers/CustomizationController.cs b/Core/Controllers/CustomizationController.cs index 81dc2b20..87da63fb 100644 --- a/Core/Controllers/CustomizationController.cs +++ b/Core/Controllers/CustomizationController.cs @@ -97,8 +97,8 @@ public class CustomizationController var suitDetails = _databaseService.GetCustomization()[suitId]; _logger.Error(_localisationService.GetText("customisation-item_already_purchased", new { - ItemId = suitDetails?.Id, - ItemName = suitDetails?.Name, + itemId = suitDetails?.Id, + itemName = suitDetails?.Name, })); return output; diff --git a/Core/Generators/BotEquipmentModGenerator.cs b/Core/Generators/BotEquipmentModGenerator.cs index e9f20317..d14f238c 100644 --- a/Core/Generators/BotEquipmentModGenerator.cs +++ b/Core/Generators/BotEquipmentModGenerator.cs @@ -299,9 +299,9 @@ public class BotEquipmentModGenerator "bot-unable_to_add_mods_to_weapon_missing_ammo_slot", new { - WeaponName = request.ParentTemplate.Name, - WeaponId = request.ParentTemplate.Id, - BotRole = request.BotData.Role, + weaponName = request.ParentTemplate.Name, + weaponId = request.ParentTemplate.Id, + botRole = request.BotData.Role, } ) ); @@ -332,10 +332,10 @@ public class BotEquipmentModGenerator "bot-weapon_missing_mod_slot", new { - ModSlot = modSlot, - WeaponId = request.ParentTemplate.Id, - WeaponName = request.ParentTemplate.Name, - BotRole = request.BotData.Role, + modSlot = modSlot, + weaponId = request.ParentTemplate.Id, + weaponName = request.ParentTemplate.Name, + botRole = request.BotData.Role, } ) ); @@ -1315,8 +1315,8 @@ public class BotEquipmentModGenerator "bot-no_item_template_found_when_adding_mod", new { - ModId = modBeingAddedDbTemplate.Value?.Id ?? "UNKNOWN", - ModSlot = modSlot, + modId = modBeingAddedDbTemplate.Value?.Id ?? "UNKNOWN", + modSlot = modSlot, } ) ); @@ -1336,10 +1336,10 @@ public class BotEquipmentModGenerator "bot-unable_to_add_mod_item_invalid", new { - ItemName = modBeingAddedDbTemplate.Value?.Name ?? "UNKNOWN", - ModSlot = modSlot, - ParentItemName = parentTemplate.Name, - BotRole = botRole, + itemName = modBeingAddedDbTemplate.Value?.Name ?? "UNKNOWN", + iodSlot = modSlot, + parentItemName = parentTemplate.Name, + botRole = botRole, } ) ); @@ -1377,8 +1377,8 @@ public class BotEquipmentModGenerator "bot-unable_to_filter_mods_all_blacklisted", new { - SlotName = desiredSlotObject.Name, - ItemName = modTemplate.Name, + slotName = desiredSlotObject.Name, + itemName = modTemplate.Name, } ) ); @@ -1464,8 +1464,8 @@ public class BotEquipmentModGenerator "bot-unable_to_fill_camora_slot_mod_pool_empty", new { - WeaponId = cylinderMagTemplate.Id, - WeaponName = cylinderMagTemplate.Name, + weaponId = cylinderMagTemplate.Id, + weaponName = cylinderMagTemplate.Name, } ) ); diff --git a/Core/Generators/BotWeaponGenerator.cs b/Core/Generators/BotWeaponGenerator.cs index c71f6509..a2709f82 100644 --- a/Core/Generators/BotWeaponGenerator.cs +++ b/Core/Generators/BotWeaponGenerator.cs @@ -396,10 +396,10 @@ public class BotWeaponGenerator "bot-weapons_required_slot_missing_item", new { - ModSlot = modSlotTemplate.Name, - ModName = modTemplate.Name, - SlotId = mod.SlotId, - BotRole = botRole, + modSlot = modSlotTemplate.Name, + modName = modTemplate.Name, + slotId = mod.SlotId, + botRole = botRole, } ) ); @@ -561,8 +561,8 @@ public class BotWeaponGenerator "bot-weapon_missing_magazine_or_chamber", new { - WeaponId = weaponTemplate.Id, - BotRole = botRole, + weaponId = weaponTemplate.Id, + botRole = botRole, } ) ); @@ -597,9 +597,9 @@ public class BotWeaponGenerator "bot-no_caliber_data_for_weapon_falling_back_to_default", new { - WeaponId = weaponTemplate.Id, - WeaponName = weaponTemplate.Name, - DefaultAmmo = weaponTemplate.Properties.DefAmmo, + weaponId = weaponTemplate.Id, + weaponName = weaponTemplate.Name, + defaultAmmo = weaponTemplate.Properties.DefAmmo, } ) ); diff --git a/Core/Helpers/BotGeneratorHelper.cs b/Core/Helpers/BotGeneratorHelper.cs index 2c6748ed..d316beff 100644 --- a/Core/Helpers/BotGeneratorHelper.cs +++ b/Core/Helpers/BotGeneratorHelper.cs @@ -237,9 +237,9 @@ public class BotGeneratorHelper "bot-missing_equipment_settings", new { - BotRole = botRole, - Setting = setting, - DefaultValue = defaultValue + botRole = botRole, + setting = setting, + defaultValue = defaultValue } ) ); @@ -257,9 +257,9 @@ public class BotGeneratorHelper "bot-missing_equipment_settings_property", new { - BotRole = botRole, - Setting = setting, - DefaultValue = defaultValue + botRole = botRole, + setting = setting, + defaultValue = defaultValue } ) ); diff --git a/Core/Helpers/QuestRewardHelper.cs b/Core/Helpers/QuestRewardHelper.cs index 2508d0b4..07c47250 100644 --- a/Core/Helpers/QuestRewardHelper.cs +++ b/Core/Helpers/QuestRewardHelper.cs @@ -152,8 +152,8 @@ public class QuestRewardHelper default: _logger.Error(_localisationService.GetText("quest-reward_type_not_handled", new { - RewardType = reward.Type, - QuestId = questId, + rewardType = reward.Type, + questId = questId, questName = questDetails.QuestName })); break; @@ -280,8 +280,8 @@ public class QuestRewardHelper { _logger.Error(_localisationService.GetText("quest-unable_to_find_matching_hideout_production", new { - QuestName = questDetails.QuestName, - MatchCount = matchingProductions.Count + questName = questDetails.QuestName, + matchCount = matchingProductions.Count })); return; diff --git a/Core/Services/MailSendService.cs b/Core/Services/MailSendService.cs index 0cb4238e..c501d205 100644 --- a/Core/Services/MailSendService.cs +++ b/Core/Services/MailSendService.cs @@ -81,8 +81,8 @@ public class MailSendService { _logger.Error(_localisationService.GetText("mailsend-missing_trader", new { - MessageType = messageType, - SessionId = sessionId, + messageType = messageType, + sessionId = sessionId, })); return; @@ -138,8 +138,8 @@ public class MailSendService { _logger.Error(_localisationService.GetText("mailsend-missing_trader", new { - MessageType = messageType, - SessionId = sessionId, + messageType = messageType, + sessionId = sessionId, })); return; @@ -408,8 +408,8 @@ public class MailSendService { _localisationService.GetText("mailsend-missing_parent", new { - TraderId = messageDetails.Trader, - Sender = messageDetails.Sender, + traderId = messageDetails.Trader, + sender = messageDetails.Sender, }); return itemsToSendToPlayer; @@ -436,8 +436,8 @@ public class MailSendService { _logger.Error(_localisationService.GetText("dialog-missing_item_template", new { - Tpl = reward.Template, - Type = dialogType, + tpl = reward.Template, + type = dialogType, })); continue; diff --git a/Core/Services/SeasonalEventService.cs b/Core/Services/SeasonalEventService.cs index 11badef2..485eecc7 100644 --- a/Core/Services/SeasonalEventService.cs +++ b/Core/Services/SeasonalEventService.cs @@ -374,8 +374,8 @@ public class SeasonalEventService _logger.Warning( _localisationService.GetText("seasonal-missing_equipment_slot_on_bot", new { - EquipmentSlot = equipmentSlotKey, - BotRole = botRole, + equipmentSlot = equipmentSlotKey, + botRole = botRole, }) ); } @@ -395,8 +395,8 @@ public class SeasonalEventService _logger.Warning( _localisationService.GetText("seasonal-missing_loot_container_slot_on_bot", new { - LootContainer = lootContainerKey, - BotRole = botRole, + lootContainer = lootContainerKey, + botRole = botRole, }) ); } From fa307a29a393e69553d8874fbbc046420c637c03 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 16 Jan 2025 13:01:03 +0000 Subject: [PATCH 13/14] finish BotWeaponGenHelper --- Core/Helpers/BotWeaponGeneratorHelper.cs | 110 +++++++++++++++++++++-- Core/Models/Eft/Common/Tables/BotType.cs | 2 +- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/Core/Helpers/BotWeaponGeneratorHelper.cs b/Core/Helpers/BotWeaponGeneratorHelper.cs index 2ef93210..d9a81053 100644 --- a/Core/Helpers/BotWeaponGeneratorHelper.cs +++ b/Core/Helpers/BotWeaponGeneratorHelper.cs @@ -1,21 +1,82 @@ using Core.Annotations; using Core.Models.Eft.Common.Tables; using Core.Models.Enums; +using Core.Models.Spt.Bots; +using Core.Models.Utils; +using Core.Servers; +using Core.Services; +using Core.Utils; namespace Core.Helpers; [Injectable] public class BotWeaponGeneratorHelper { + private readonly ISptLogger _logger; + private readonly DatabaseServer _databaseServer; + private readonly ItemHelper _itemHelper; + private readonly RandomUtil _randomUtil; + private readonly HashUtil _hashUtil; + private readonly WeightedRandomHelper _weightedRandomHelper; + private readonly BotGeneratorHelper _botGeneratorHelper; + private readonly LocalisationService _localisationService; + + private readonly List _magCheck = ["CylinderMagazine", "SpringDrivenCylinder"]; + + public BotWeaponGeneratorHelper + ( + ISptLogger logger, + DatabaseServer databaseServer, + ItemHelper itemHelper, + RandomUtil randomUtil, + HashUtil hashUtil, + WeightedRandomHelper weightedRandomHelper, + BotGeneratorHelper botGeneratorHelper, + LocalisationService localisationService + ) + { + _logger = logger; + _databaseServer = databaseServer; + _itemHelper = itemHelper; + _randomUtil = randomUtil; + _hashUtil = hashUtil; + _weightedRandomHelper = weightedRandomHelper; + _botGeneratorHelper = botGeneratorHelper; + _localisationService = localisationService; + } + /// /// Get a randomized number of bullets for a specific magazine /// /// Weights of magazines /// Magazine to generate bullet count for /// Bullet count number - public int GetRandomizedBulletCount(GenerationData magCounts, TemplateItem magTemplate) + public double? GetRandomizedBulletCount(GenerationData magCounts, TemplateItem magTemplate) { - throw new NotImplementedException(); + var randomizedMagazineCount = GetRandomizedMagazineCount(magCounts); + var parentItem = _itemHelper.GetItem(magTemplate.Parent).Value; + double? chamberBulletCount = 0; + if (MagazineIsCylinderRelated(parentItem.Name)) + { + var firstSlotAmmoTpl = magTemplate.Properties.Cartridges[0].Props.Filters[0].Filter[0]; + var ammoMaxStackSize = _itemHelper.GetItem(firstSlotAmmoTpl).Value?.Properties?.StackMaxSize ?? 1; + chamberBulletCount = ammoMaxStackSize == 1 + ? 1 // Rotating grenade launcher + : magTemplate.Properties.Slots.Count; // Shotguns/revolvers. We count the number of camoras as the _max_count of the magazine is 0 + } + else if (parentItem.Id == BaseClasses.UBGL) + { + // Underbarrel launchers can only have 1 chambered grenade + chamberBulletCount = 1; + } + else + { + chamberBulletCount = magTemplate.Properties.Cartridges[0].MaxCount; + } + + // Get the amount of bullets that would fit in the internal magazine + // and multiply by how many magazines were supposed to be created + return chamberBulletCount * randomizedMagazineCount; } /// @@ -25,7 +86,7 @@ public class BotWeaponGeneratorHelper /// Numerical value of magazine count public int GetRandomizedMagazineCount(GenerationData magCounts) { - throw new NotImplementedException(); + return (int)_weightedRandomHelper.GetWeightedValue(magCounts.Weights); } /// @@ -35,7 +96,7 @@ public class BotWeaponGeneratorHelper /// True if it is cylinder related public bool MagazineIsCylinderRelated(string magazineParentName) { - throw new NotImplementedException(); + return _magCheck.Contains(magazineParentName); } /// @@ -47,7 +108,14 @@ public class BotWeaponGeneratorHelper /// Item array public List CreateMagazineWithAmmo(string magazineTpl, string ammoTpl, TemplateItem magTemplate) { - throw new NotImplementedException(); + List magazine = + [ + new() { Id = _hashUtil.Generate(), Template = magazineTpl } + ]; + + _itemHelper.FillMagazineWithCartridge(magazine, magTemplate, ammoTpl, 1); + + return magazine; } /// @@ -61,10 +129,36 @@ public class BotWeaponGeneratorHelper string ammoTpl, int cartridgeCount, BotBaseInventory inventory, - object equipmentSlotsToAddTo // TODO: EquipmentSlots[] equipmentSlotsToAddTo = new EquipmentSlots[] { EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS } + List equipmentSlotsToAddTo ) { - throw new NotImplementedException(); + if (equipmentSlotsToAddTo is null) + equipmentSlotsToAddTo = [EquipmentSlots.TacticalVest.ToString(), EquipmentSlots.Pockets.ToString()]; + + var ammoItems = _itemHelper.SplitStack(new () { + Id = _hashUtil.Generate(), + Template = ammoTpl, + Upd = new () { StackObjectsCount = cartridgeCount }, + }); + + foreach (var ammoItem in ammoItems) { + var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot( + equipmentSlotsToAddTo, + ammoItem.Id, + ammoItem.Template, + [ammoItem], + inventory + ); + + if (result != ItemAddedResult.SUCCESS) { + _logger.Debug($"Unable to add ammo: {ammoItem.Template} to bot inventory, {result.ToString()}"); + + if (result == ItemAddedResult.NO_SPACE || result == ItemAddedResult.NO_CONTAINERS) { + // If there's no space for 1 stack or no containers to hold item, there's no space for the others + break; + } + } + } } /// @@ -74,6 +168,6 @@ public class BotWeaponGeneratorHelper /// Tpl of magazine public string GetWeaponsDefaultMagazineTpl(TemplateItem weaponTemplate) { - throw new NotImplementedException(); + return weaponTemplate.Properties.DefMagType; } } diff --git a/Core/Models/Eft/Common/Tables/BotType.cs b/Core/Models/Eft/Common/Tables/BotType.cs index ccc92bd2..17ab603c 100644 --- a/Core/Models/Eft/Common/Tables/BotType.cs +++ b/Core/Models/Eft/Common/Tables/BotType.cs @@ -303,7 +303,7 @@ public class GenerationData { /** key: number of items, value: weighting */ [JsonPropertyName("weights")] - public Dictionary? Weights { get; set; } + public Dictionary? Weights { get; set; } /** Array of item tpls */ [JsonPropertyName("whitelist")] From 80c4f75d013674f153247e4c8dc28d4a59c2589d Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 16 Jan 2025 13:03:38 +0000 Subject: [PATCH 14/14] formatting go wild, change two params to double from int in AddLootFromPool --- Core/Generators/BotLootGenerator.cs | 62 +++++++++++++++++++---------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/Core/Generators/BotLootGenerator.cs b/Core/Generators/BotLootGenerator.cs index 759f992f..470dea35 100644 --- a/Core/Generators/BotLootGenerator.cs +++ b/Core/Generators/BotLootGenerator.cs @@ -113,7 +113,8 @@ public class BotLootGenerator _logger.Warning(_localisationService.GetText("bot-unable_to_generate_bot_loot", botRole)); return; } - var backpackLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.BackpackLoot.Weights); + + var backpackLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.BackpackLoot.Weights); var pocketLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.PocketLoot.Weights); var vestLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.VestLoot.Weights); var specialLootItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.SpecialItems.Weights); @@ -156,7 +157,8 @@ public class BotLootGenerator botInventory, botRole, botItemLimits, - containersIdFull); + containersIdFull + ); // Healing items / Meds AddLootFromPool( @@ -168,7 +170,8 @@ public class BotLootGenerator null, containersIdFull, 0, - isPmc); + isPmc + ); // Drugs AddLootFromPool( @@ -180,7 +183,8 @@ public class BotLootGenerator null, containersIdFull, 0, - isPmc); + isPmc + ); // Food AddLootFromPool( @@ -192,7 +196,8 @@ public class BotLootGenerator null, containersIdFull, 0, - isPmc); + isPmc + ); // Drink AddLootFromPool( @@ -204,7 +209,8 @@ public class BotLootGenerator null, containersIdFull, 0, - isPmc); + isPmc + ); // Currency AddLootFromPool( @@ -216,7 +222,8 @@ public class BotLootGenerator null, containersIdFull, 0, - isPmc); + isPmc + ); // Stims AddLootFromPool( @@ -228,7 +235,8 @@ public class BotLootGenerator botItemLimits, containersIdFull, 0, - isPmc); + isPmc + ); // Grenades AddLootFromPool( @@ -240,7 +248,8 @@ public class BotLootGenerator null, containersIdFull, 0, - isPmc); + isPmc + ); var itemPriceLimits = GetSingleItemLootPriceLimits(botLevel, isPmc); @@ -259,7 +268,8 @@ public class BotLootGenerator botRole, isPmc, botLevel, - containersIdFull); + containersIdFull + ); } var backpackLootRoubleTotal = GetBackpackRoubleTotalByLevel(botLevel, isPmc); @@ -269,7 +279,8 @@ public class BotLootGenerator isPmc, LootCacheType.Backpack, botJsonTemplate, - itemPriceLimits?.Backpack), + itemPriceLimits?.Backpack + ), [EquipmentSlots.Backpack], backpackLootCount, botInventory, @@ -277,7 +288,8 @@ public class BotLootGenerator botItemLimits, containersIdFull, backpackLootRoubleTotal, - isPmc); + isPmc + ); } // TacticalVest - generate loot if they have one @@ -290,7 +302,8 @@ public class BotLootGenerator isPmc, LootCacheType.Vest, botJsonTemplate, - itemPriceLimits?.Vest), + itemPriceLimits?.Vest + ), [EquipmentSlots.TacticalVest], vestLootCount, botInventory, @@ -298,7 +311,8 @@ public class BotLootGenerator botItemLimits, containersIdFull, _pmcConfig.MaxVestLootTotalRub, - isPmc); + isPmc + ); } // Pockets @@ -308,7 +322,8 @@ public class BotLootGenerator isPmc, LootCacheType.Pocket, botJsonTemplate, - itemPriceLimits?.Pocket), + itemPriceLimits?.Pocket + ), [EquipmentSlots.Pockets], pocketLootCount, botInventory, @@ -316,7 +331,8 @@ public class BotLootGenerator botItemLimits, containersIdFull, _pmcConfig.MaxPocketLootTotalRub, - isPmc); + isPmc + ); // Secure @@ -331,8 +347,9 @@ public class BotLootGenerator botRole, null, containersIdFull, - - 1, - isPmc); + -1, + isPmc + ); } } @@ -342,7 +359,8 @@ public class BotLootGenerator if (isPmc) { var matchingValue = _pmcConfig.LootItemLimitsRub.FirstOrDefault( - (minMaxValue) => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max); + (minMaxValue) => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max + ); return matchingValue; } @@ -399,12 +417,12 @@ public class BotLootGenerator public void AddLootFromPool( Dictionary pool, List equipmentSlots, - int totalItemCount, + double totalItemCount, BotBaseInventory inventoryToAddItemsTo, // TODO: type for containersIdFull was Set string botRole, ItemSpawnLimitSettings itemSpawnLimits, List containersIdFull, - int totalValueLimitRub = 0, + double totalValueLimitRub = 0, bool isPmc = false) { throw new NotImplementedException(); @@ -448,7 +466,7 @@ public class BotLootGenerator public void AddLooseWeaponsToInventorySlot(string sessionId, BotBaseInventory botInventory, EquipmentSlots equipmentSlot, - BotTypeInventory templateInventory, + BotTypeInventory templateInventory, Dictionary modsChances, string botRole, bool isPmc,