diff --git a/Core/Callbacks/GameCallbacks.cs b/Core/Callbacks/GameCallbacks.cs index c5a5a8e6..5a1a5f16 100644 --- a/Core/Callbacks/GameCallbacks.cs +++ b/Core/Callbacks/GameCallbacks.cs @@ -67,10 +67,9 @@ public class GameCallbacks : OnLoad /// public string GameStart(string url, EmptyRequestData info, string sessionID) { - var today = _timeUtil.GetDate(); - var startTimestampMS = _timeUtil.GetTimeStamp(); - _gameController.GameStart(url, info, sessionID, startTimestampMS); - return _httpResponseUtil.GetBody(new GameStartResponse() { UtcTime = startTimestampMS / 1000 }); + var startTimestampSec = _timeUtil.GetTimeStamp(); + _gameController.GameStart(url, info, sessionID, startTimestampSec); + return _httpResponseUtil.GetBody(new GameStartResponse() { UtcTime = startTimestampSec }); } /// diff --git a/Core/Generators/BotEquipmentModGenerator.cs b/Core/Generators/BotEquipmentModGenerator.cs index d14f238c..03cea0e6 100644 --- a/Core/Generators/BotEquipmentModGenerator.cs +++ b/Core/Generators/BotEquipmentModGenerator.cs @@ -775,11 +775,11 @@ public class BotEquipmentModGenerator case "patron_in_weapon": case "patron_in_weapon_000": case "patron_in_weapon_001": - return parentTemplate.Properties.Chambers.FirstOrDefault((chamber) => chamber.Name.Contains(modSlotLower)); + return parentTemplate?.Properties?.Chambers?.FirstOrDefault((chamber) => chamber.Name.Contains(modSlotLower)); case "cartridges": - return parentTemplate.Properties.Cartridges.FirstOrDefault((c) => c.Name.ToLower() == modSlotLower); + return parentTemplate?.Properties?.Cartridges?.FirstOrDefault((c) => c.Name?.ToLower() == modSlotLower); default: - return parentTemplate.Properties.Slots.FirstOrDefault((s) => s.Name.ToLower() == modSlotLower); + return parentTemplate?.Properties?.Slots?.FirstOrDefault((s) => s.Name?.ToLower() == modSlotLower); } } diff --git a/Core/Generators/BotWeaponGenerator.cs b/Core/Generators/BotWeaponGenerator.cs index a2709f82..0a34195e 100644 --- a/Core/Generators/BotWeaponGenerator.cs +++ b/Core/Generators/BotWeaponGenerator.cs @@ -224,8 +224,9 @@ public class BotWeaponGenerator ); } + var tempList = _cloner.Clone(weaponWithModsArray.Where((item) => item.SlotId == _modMagazineSlotId)); // Fill existing magazines to full and sync ammo type - foreach (var magazine in weaponWithModsArray.Where((item) => item.SlotId == _modMagazineSlotId)) + foreach (var magazine in tempList) { FillExistingMagazines(weaponWithModsArray, magazine, ammoTpl); } diff --git a/Core/Generators/WeaponGen/Implementations/BarrelInvetoryMagGen.cs b/Core/Generators/WeaponGen/Implementations/BarrelInvetoryMagGen.cs index 467f451b..0571f149 100644 --- a/Core/Generators/WeaponGen/Implementations/BarrelInvetoryMagGen.cs +++ b/Core/Generators/WeaponGen/Implementations/BarrelInvetoryMagGen.cs @@ -1,12 +1,23 @@ using Core.Annotations; +using Core.Helpers; +using Core.Utils; namespace Core.Generators.WeaponGen.Implementations; [Injectable] public class BarrelInvetoryMagGen : InventoryMagGen, IInventoryMagGen { - public BarrelInvetoryMagGen() + private readonly RandomUtil _randomUtil; + private readonly BotWeaponGeneratorHelper _botWeaponGeneratorHelper; + + public BarrelInvetoryMagGen + ( + RandomUtil randomUtil, + BotWeaponGeneratorHelper botWeaponGeneratorHelper + ) { + _randomUtil = randomUtil; + _botWeaponGeneratorHelper = botWeaponGeneratorHelper; } public int GetPriority() @@ -16,11 +27,31 @@ public class BarrelInvetoryMagGen : InventoryMagGen, IInventoryMagGen public bool CanHandleInventoryMagGen(InventoryMagGen inventoryMagGen) { - throw new NotImplementedException(); + return inventoryMagGen.GetWeaponTemplate().Properties.ReloadMode == "OnlyBarrel"; } public void Process(InventoryMagGen inventoryMagGen) { - throw new NotImplementedException(); + // Can't be done by _props.ammoType as grenade launcher shoots grenades with ammoType of "buckshot" + double? randomisedAmmoStackSize = null; + if (inventoryMagGen.GetAmmoTemplate().Properties.StackMaxRandom == 1) + { + // doesnt stack + randomisedAmmoStackSize = _randomUtil.GetInt(3, 6); + } + else + { + randomisedAmmoStackSize = _randomUtil.GetInt( + (int)inventoryMagGen.GetAmmoTemplate().Properties.StackMinRandom, + (int)inventoryMagGen.GetAmmoTemplate().Properties.StackMaxRandom + ); + } + + _botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots( + inventoryMagGen.GetAmmoTemplate().Id, + (int)randomisedAmmoStackSize, + inventoryMagGen.GetPmcInventory(), + null + ); } } diff --git a/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs b/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs index d5867ee9..8e78f281 100644 --- a/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs +++ b/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs @@ -1,13 +1,39 @@ using Core.Annotations; +using Core.Helpers; using Core.Models.Eft.Common.Tables; +using Core.Models.Enums; +using Core.Models.Utils; +using Core.Services; +using Core.Utils; namespace Core.Generators.WeaponGen.Implementations; [Injectable] public class ExternalInventoryMagGen : InventoryMagGen, IInventoryMagGen { - public ExternalInventoryMagGen() + private readonly ISptLogger _logger; + private readonly ItemHelper _itemHelper; + private readonly LocalisationService _localisationService; + private readonly BotWeaponGeneratorHelper _botWeaponGeneratorHelper; + private readonly BotGeneratorHelper _botGeneratorHelper; + private readonly RandomUtil _randomUtil; + + public ExternalInventoryMagGen + ( + ISptLogger logger, + ItemHelper itemHelper, + LocalisationService localisationService, + BotWeaponGeneratorHelper botWeaponGeneratorHelper, + BotGeneratorHelper botGeneratorHelper, + RandomUtil randomUtil + ) { + _logger = logger; + _itemHelper = itemHelper; + _localisationService = localisationService; + _botWeaponGeneratorHelper = botWeaponGeneratorHelper; + _botGeneratorHelper = botGeneratorHelper; + _randomUtil = randomUtil; } public int GetPriority() @@ -17,16 +43,124 @@ public class ExternalInventoryMagGen : InventoryMagGen, IInventoryMagGen public bool CanHandleInventoryMagGen(InventoryMagGen inventoryMagGen) { - throw new NotImplementedException(); + return true; // Fallback, if code reaches here it means no other implementation can handle this type of magazine } public void Process(InventoryMagGen inventoryMagGen) { - throw new NotImplementedException(); + // Cout of attempts to fit a magazine into bot inventory + var fitAttempts = 0; + + // Magazine Db template + var magTemplate = inventoryMagGen.GetMagazineTemplate(); + var magazineTpl = magTemplate.Id; + var weapon = inventoryMagGen.GetWeaponTemplate(); + List attemptedMagBlacklist = []; + var defaultMagazineTpl = _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weapon); + var randomizedMagazineCount = _botWeaponGeneratorHelper.GetRandomizedMagazineCount(inventoryMagGen.GetMagCount()); + for (var i = 0; i < randomizedMagazineCount; i++) { + var magazineWithAmmo = _botWeaponGeneratorHelper.CreateMagazineWithAmmo( + magazineTpl, + inventoryMagGen.GetAmmoTemplate().Id, + magTemplate + ); + + var fitsIntoInventory = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot( + [EquipmentSlots.TacticalVest.ToString(), EquipmentSlots.Pockets.ToString()], + magazineWithAmmo[0].Id, + magazineTpl, + magazineWithAmmo, + inventoryMagGen.GetPmcInventory() + ); + + if (fitsIntoInventory == ItemAddedResult.NO_CONTAINERS) { + // No containers to fit magazines, stop trying + break; + } + + // No space for magazine and we haven't reached desired magazine count + if (fitsIntoInventory == ItemAddedResult.NO_SPACE && i < randomizedMagazineCount) { + // Prevent infinite loop by only allowing 5 attempts at fitting a magazine into inventory + if (fitAttempts > 5) { + _logger.Debug($"Failed {fitAttempts} times to add magazine {magazineTpl} to bot inventory, stopping"); + + break; + } + + /* We were unable to fit at least the minimum amount of magazines, + * so we fallback to default magazine and try again. + * Temporary workaround to Killa spawning with no extra mags if he spawns with a drum mag */ + + if (magazineTpl == defaultMagazineTpl) { + // We were already on default - stop here to prevent infinite looping + break; + } + + // Add failed magazine tpl to blacklist + attemptedMagBlacklist.Add(magazineTpl); + + // Set chosen magazine tpl to the weapons default magazine tpl and try to fit into inventory next loop + magazineTpl = defaultMagazineTpl; + magTemplate = _itemHelper.GetItem(magazineTpl).Value; + if (magTemplate is null) { + _logger.Error( + _localisationService.GetText("bot-unable_to_find_default_magazine_item", magazineTpl) + ); + + break; + } + + // Edge case - some weapons (SKS) have an internal magazine as default, choose random non-internal magazine to add to bot instead + if (magTemplate.Properties.ReloadMagType == "InternalMagazine") { + var result = GetRandomExternalMagazineForInternalMagazineGun( + inventoryMagGen.GetWeaponTemplate().Id, + attemptedMagBlacklist + ); + if (result?.Id is null) { + _logger.Debug($"Unable to add additional magazine into bot inventory for weapon: {weapon.Name}, attempted: {fitAttempts} times"); + + break; + } + + magazineTpl = result.Id; + magTemplate = result; + fitAttempts++; + } + + // Reduce loop counter by 1 to ensure we get full cout of desired magazines + i--; + } + + if (fitsIntoInventory == ItemAddedResult.SUCCESS) { + // Reset fit counter now it succeeded + fitAttempts = 0; + } + } } public TemplateItem? GetRandomExternalMagazineForInternalMagazineGun(string weaponTpl, List magazineBlacklist) { - throw new NotImplementedException(); + // The mag Slot data for the weapon + var magSlot = _itemHelper.GetItem(weaponTpl).Value.Properties.Slots.FirstOrDefault((x) => x.Name == "mod_magazine"); + if (magSlot is null) { + return null; + } + + // All possible mags that fit into the weapon excluding blacklisted + var magazinePool = magSlot.Props.Filters[0].Filter.Where((x) => !magazineBlacklist.Contains(x)).Select( + (x) => _itemHelper.GetItem(x).Value + ); + if (magazinePool is null) { + return null; + } + + // Non-internal magazines that fit into the weapon + var externalMagazineOnlyPool = magazinePool.Where((x) => x.Properties.ReloadMagType != "InternalMagazine"); + if (externalMagazineOnlyPool is null || externalMagazineOnlyPool?.Count() == 0) { + return null; + } + + // Randomly chosen external magazine + return _randomUtil.GetArrayValue(externalMagazineOnlyPool); } } diff --git a/Core/Generators/WeaponGen/Implementations/InternalMagazineInventoryMagGen.cs b/Core/Generators/WeaponGen/Implementations/InternalMagazineInventoryMagGen.cs index 7d09eeb7..76991706 100644 --- a/Core/Generators/WeaponGen/Implementations/InternalMagazineInventoryMagGen.cs +++ b/Core/Generators/WeaponGen/Implementations/InternalMagazineInventoryMagGen.cs @@ -1,12 +1,19 @@ using Core.Annotations; +using Core.Helpers; namespace Core.Generators.WeaponGen.Implementations; [Injectable] public class InternalMagazineInventoryMagGen : InventoryMagGen, IInventoryMagGen { - public InternalMagazineInventoryMagGen() + private readonly BotWeaponGeneratorHelper _botWeaponGeneratorHelper; + + public InternalMagazineInventoryMagGen + ( + BotWeaponGeneratorHelper botWeaponGeneratorHelper + ) { + _botWeaponGeneratorHelper = botWeaponGeneratorHelper; } public int GetPriority() @@ -16,11 +23,20 @@ public class InternalMagazineInventoryMagGen : InventoryMagGen, IInventoryMagGen public bool CanHandleInventoryMagGen(InventoryMagGen inventoryMagGen) { - throw new NotImplementedException(); + return inventoryMagGen.GetMagazineTemplate().Properties.ReloadMagType == "InternalMagazine"; } public void Process(InventoryMagGen inventoryMagGen) { - throw new NotImplementedException(); + var bulletCount = _botWeaponGeneratorHelper.GetRandomizedBulletCount( + inventoryMagGen.GetMagCount(), + inventoryMagGen.GetMagazineTemplate() + ); + _botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots( + inventoryMagGen.GetAmmoTemplate().Id, + (int)bulletCount, + inventoryMagGen.GetPmcInventory(), + null + ); } } diff --git a/Core/Generators/WeaponGen/Implementations/UbglExternalMagGen.cs b/Core/Generators/WeaponGen/Implementations/UbglExternalMagGen.cs index 65ff35cc..8d756817 100644 --- a/Core/Generators/WeaponGen/Implementations/UbglExternalMagGen.cs +++ b/Core/Generators/WeaponGen/Implementations/UbglExternalMagGen.cs @@ -1,12 +1,20 @@ using Core.Annotations; +using Core.Helpers; +using Core.Models.Enums; namespace Core.Generators.WeaponGen.Implementations; [Injectable] public class UbglExternalMagGen : InventoryMagGen, IInventoryMagGen { - public UbglExternalMagGen() + private readonly BotWeaponGeneratorHelper _botWeaponGeneratorHelper; + + public UbglExternalMagGen + ( + BotWeaponGeneratorHelper botWeaponGeneratorHelper + ) { + _botWeaponGeneratorHelper = botWeaponGeneratorHelper; } public int GetPriority() @@ -16,11 +24,20 @@ public class UbglExternalMagGen : InventoryMagGen, IInventoryMagGen public bool CanHandleInventoryMagGen(InventoryMagGen inventoryMagGen) { - throw new NotImplementedException(); + return inventoryMagGen.GetWeaponTemplate().Parent == BaseClasses.UBGL; } public void Process(InventoryMagGen inventoryMagGen) { - throw new NotImplementedException(); + var bulletCount = _botWeaponGeneratorHelper.GetRandomizedBulletCount( + inventoryMagGen.GetMagCount(), + inventoryMagGen.GetMagazineTemplate() + ); + _botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots( + inventoryMagGen.GetAmmoTemplate().Id, + (int)bulletCount, + inventoryMagGen.GetPmcInventory(), + [EquipmentSlots.TacticalVest.ToString()] + ); } }