From 1ff7dcc2c49b2c852fd3bf5a2a8894f91d6d59b7 Mon Sep 17 00:00:00 2001 From: CWX Date: Tue, 21 Jan 2025 13:01:39 +0000 Subject: [PATCH 01/12] go brr --- .../BaseInteractionRequestDataConverter.cs | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/Libraries/Core/Utils/Json/Converters/BaseInteractionRequestDataConverter.cs b/Libraries/Core/Utils/Json/Converters/BaseInteractionRequestDataConverter.cs index bd749049..041f903b 100644 --- a/Libraries/Core/Utils/Json/Converters/BaseInteractionRequestDataConverter.cs +++ b/Libraries/Core/Utils/Json/Converters/BaseInteractionRequestDataConverter.cs @@ -22,141 +22,141 @@ public class BaseInteractionRequestDataConverter : JsonConverter(jsonText, options); - return ConvertToCorrectType(value, jsonText, options); + var value = JsonSerializer.Deserialize(jsonText); + return ConvertToCorrectType(value, jsonText); } - private BaseInteractionRequestData? ConvertToCorrectType(BaseInteractionRequestData? value, string jsonText, JsonSerializerOptions options) + private BaseInteractionRequestData? ConvertToCorrectType(BaseInteractionRequestData? value, string jsonText) { switch (value.Action) { case "CustomizationBuy": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "CustomizationSet": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "Eat": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "Heal": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "RestoreHealth": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_UPGRADE: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_UPGRADE_COMPLETE: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_PUT_ITEMS_IN_AREA_SLOTS: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_TAKE_ITEMS_FROM_AREA_SLOTS: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_TOGGLE_AREA: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_SINGLE_PRODUCTION_START: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_SCAV_CASE_PRODUCTION_START: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_CONTINUOUS_PRODUCTION_START: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_TAKE_PRODUCTION: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_RECORD_SHOOTING_RANGE_POINTS: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_IMPROVE_AREA: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_CANCEL_PRODUCTION_COMMAND: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_CIRCLE_OF_CULTIST_PRODUCTION_START: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_DELETE_PRODUCTION_COMMAND: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_CUSTOMIZATION_APPLY_COMMAND: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case HideoutEventActions.HIDEOUT_CUSTOMIZATION_SET_MANNEQUIN_POSE: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "Insure": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); /////////////////////////////////////////// InventoryBaseActionRequestData case "AddToWishList": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "RemoveFromWishList": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "ChangeWishlistItemCategory": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "TradingConfirm": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "RagFairBuyOffer": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "SellAllFromSavage": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "Repair": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "TraderRepair": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "RagFairAddOffer": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "RagFairRemoveOffer": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "RagFairRenewOffer": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "QuestAccept": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "QuestComplete": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "QuestHandover": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "RepeatableQuestChange": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case "AddNote": case "EditNote": case "DeleteNote": - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.MOVE: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.REMOVE: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.SPLIT: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.MERGE: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.TRANSFER: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.SWAP: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.FOLD: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.TOGGLE: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.TAG: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.BIND: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.UNBIND: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.EXAMINE: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.READ_ENCYCLOPEDIA: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.APPLY_INVENTORY_CHANGES: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.CREATE_MAP_MARKER: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.DELETE_MAP_MARKER: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.EDIT_MAP_MARKER: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.OPEN_RANDOM_LOOT_CONTAINER: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.HIDEOUT_QTE_EVENT: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.REDEEM_PROFILE_REWARD: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.SET_FAVORITE_ITEMS: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.QUEST_FAIL: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); case ItemEventActions.PIN_LOCK: - return JsonSerializer.Deserialize(jsonText, options); + return JsonSerializer.Deserialize(jsonText); default: throw new Exception($"Unhandled action type {value.Action}, make sure the BaseInteractionRequestDataConverter has the deserialization for this action handled."); } From cd414302bf4819f50e9e37ca1b76bac7c6ee359a Mon Sep 17 00:00:00 2001 From: CWX Date: Tue, 21 Jan 2025 13:09:17 +0000 Subject: [PATCH 02/12] Fix debugging response with requestcompressed and responsecompressed --- Libraries/Core/Servers/Http/SptHttpListener.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Core/Servers/Http/SptHttpListener.cs b/Libraries/Core/Servers/Http/SptHttpListener.cs index 7d0237e6..9d41fba5 100644 --- a/Libraries/Core/Servers/Http/SptHttpListener.cs +++ b/Libraries/Core/Servers/Http/SptHttpListener.cs @@ -185,11 +185,11 @@ public class SptHttpListener : IHttpListener public void SendJson(HttpResponse resp, string? output, string sessionID) { - if (!string.IsNullOrEmpty(output)) - resp.Body = new MemoryStream(Encoding.UTF8.GetBytes(output)); resp.StatusCode = 200; resp.ContentType = "application/json"; resp.Headers.Append("Set-Cookie", $"PHPSESSID={sessionID}"); + if (!string.IsNullOrEmpty(output)) + resp.Body.WriteAsync(Encoding.UTF8.GetBytes(output)).AsTask().Wait(); resp.StartAsync().Wait(); resp.CompleteAsync().Wait(); } From eec9c0eba1fa2ad6e589b2d29f5bf512497d7db7 Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 21 Jan 2025 13:16:28 +0000 Subject: [PATCH 03/12] BotGeneration improvemetns --- .../Generators/BotEquipmentModGenerator.cs | 60 ++++++++++++------- .../Core/Generators/BotWeaponGenerator.cs | 2 +- .../Models/Spt/Bots/GenerateWeaponRequest.cs | 2 +- .../Core/Models/Spt/Bots/ModToSpawnRequest.cs | 2 +- .../Services/BotEquipmentModPoolService.cs | 25 ++++++++ 5 files changed, 67 insertions(+), 24 deletions(-) diff --git a/Libraries/Core/Generators/BotEquipmentModGenerator.cs b/Libraries/Core/Generators/BotEquipmentModGenerator.cs index 1405f2fe..d70472d0 100644 --- a/Libraries/Core/Generators/BotEquipmentModGenerator.cs +++ b/Libraries/Core/Generators/BotEquipmentModGenerator.cs @@ -244,11 +244,11 @@ public class BotEquipmentModGenerator( * @param modSlot front/back * @returns Armor IItem */ - protected Item GetDefaultPresetArmorSlot(string armorItemTpl, string modSlot) + protected Item? GetDefaultPresetArmorSlot(string armorItemTpl, string modSlot) { var defaultPreset = _presetHelper.GetDefaultPreset(armorItemTpl); - return defaultPreset?.Items.FirstOrDefault((item) => item.SlotId?.ToLower() == modSlot); + return defaultPreset?.Items?.FirstOrDefault((item) => item.SlotId?.ToLower() == modSlot); } @@ -260,11 +260,6 @@ public class BotEquipmentModGenerator( /// Weapon + mods array public List GenerateModsForWeapon(string sessionId, GenerateWeaponRequest request) { - var pmcProfile = _profileHelper.GetPmcProfile(sessionId); - - // Get pool of mods that fit weapon - var compatibleModsPool = request.ModPool[request.ParentTemplate.Id]; - if ( !( request.ParentTemplate.Properties.Slots.Any() || @@ -288,7 +283,12 @@ public class BotEquipmentModGenerator( return request.Weapon; } - var botEquipConfig = _botConfig.Equipment[request.BotData.EquipmentRole]; + var pmcProfile = _profileHelper.GetPmcProfile(sessionId); + + // Get pool of mods that fit weapon + request.ModPool.TryGetValue(request.ParentTemplate.Id, out var compatibleModsPool); + + _botConfig.Equipment.TryGetValue(request.BotData.EquipmentRole, out var botEquipConfig); var botEquipBlacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist( request.BotData.EquipmentRole, pmcProfile.Info.Level ?? 0 @@ -494,13 +494,26 @@ public class BotEquipmentModGenerator( if (isRandomisableSlot && !containsModInPool && modToAddTemplate.Value.Properties.Slots.Any()) { var modFromService = _botEquipmentModPoolService.GetModsForWeaponSlot(modToAddTemplate.Value.Id); - if (modFromService.Keys.Any()) + if (modFromService?.Keys.Count > 0) { request.ModPool[modToAddTemplate.Value.Id] = modFromService; containsModInPool = true; } } + // Fallback when mods with REQUIRED children are not in the pool, add them and process + if (!containsModInPool && !isRandomisableSlot) + { + // Check for required mods the item we've added needs to be classified as 'valid' + var modFromService = _botEquipmentModPoolService.GetRequiredModsForWeaponSlot(modToAddTemplate.Value.Id); + if (modFromService?.Keys.Count > 0) + { + request.ModPool[modToAddTemplate.Value.Id] = modFromService; + containsModInPool = true; + } + + } + if (containsModInPool) { GenerateWeaponRequest recursiveRequestData = new() @@ -871,16 +884,16 @@ public class BotEquipmentModGenerator( request.Weapon, request.ModSlot ); - if (chosenModResult.SlotBlocked ?? false && !(parentSlot.Required ?? false)) + if (chosenModResult.SlotBlocked.GetValueOrDefault(false) && !parentSlot.Required.GetValueOrDefault(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.GetValueOrDefault(false) && !(parentSlot.Required.GetValueOrDefault(false))) { - _logger.Debug(chosenModResult.Reason); + _logger.Debug($"Unable to find compatible mod of type: {parentSlot.Name}, in slot: {request.ModSlot} reason: {chosenModResult.Reason}"); } // Get random mod to attach from items db for required slots if none found above @@ -891,14 +904,14 @@ public class BotEquipmentModGenerator( } // Compatible item not found + not required - if (!(chosenModResult.Found ?? false) && parentSlot != null && (!parentSlot.Required ?? false)) + if (!(chosenModResult.Found.GetValueOrDefault(false)) && parentSlot is not null && (!parentSlot.Required.GetValueOrDefault(false))) { return null; } - if (!(chosenModResult.Found ?? false) && parentSlot != null) + if (!(chosenModResult.Found ?? false) && parentSlot is not null) { - if (parentSlot.Required ?? false) + if (parentSlot.Required.GetValueOrDefault(false)) { _logger.Warning( $"Required slot unable to be filled, {request.ModSlot} on {request.ParentTemplate.Name} {request.ParentTemplate.Id} for weapon: {request.Weapon[0].Template}" @@ -966,7 +979,7 @@ 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) + if (preFilteredModPool.Count == 0) { return new() { Incompatible = true, Found = false, Reason = "No mods found in parents allowed list" }; } @@ -995,9 +1008,9 @@ public class BotEquipmentModGenerator( }; // 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 maxBlockedAttempts = Math.Round(modPool.Count * 0.75); // 75% of pool size var blockedAttemptCount = 0; - string chosenTpl = null; + string chosenTpl; while (exhaustableModPool.HasValues()) { chosenTpl = exhaustableModPool.GetRandomValue(); @@ -1015,7 +1028,7 @@ public class BotEquipmentModGenerator( } // Success - Default wanted + only 1 item in pool - if (modSpawnType == ModSpawn.DEFAULT_MOD && modPool.Count() == 1) + if (modSpawnType == ModSpawn.DEFAULT_MOD && modPool.Count == 1) { chosenModResult.Found = true; chosenModResult.Incompatible = false; @@ -1032,15 +1045,20 @@ public class BotEquipmentModGenerator( if (existingItemBlockingChoice is not null) { // Give max of x attempts of picking a mod if blocked by another - if (blockedAttemptCount > maxBlockedAttempts) + // OR Blocked and modpool only had 1 item + if (blockedAttemptCount > maxBlockedAttempts || modPool.Count == 1) { blockedAttemptCount = 0; // reset + //chosenModResult.SlotBlocked = true; // Later in code we try to find replacement, but only when "slotBlocked" is not true + chosenModResult.Reason = "Blocked"; + break; } blockedAttemptCount++; // Not compatible - Try again + ; continue; } @@ -1073,7 +1091,7 @@ public class BotEquipmentModGenerator( /// /// Tpls that are incompatible and should not be used /// string array of compatible mod tpls with weapon - public List GetFilteredModPool(HashSet modPool, List tplBlacklist) + public List GetFilteredModPool(HashSet modPool, HashSet tplBlacklist) { return modPool.Where((tpl) => !tplBlacklist.Contains(tpl)).ToList(); } diff --git a/Libraries/Core/Generators/BotWeaponGenerator.cs b/Libraries/Core/Generators/BotWeaponGenerator.cs index 6799090c..7a625bda 100644 --- a/Libraries/Core/Generators/BotWeaponGenerator.cs +++ b/Libraries/Core/Generators/BotWeaponGenerator.cs @@ -176,7 +176,7 @@ public class BotWeaponGenerator( ); } - // Use weapon preset from globals.json if weapon isnt valid + // Use weapon preset from globals.json if weapon isn't valid if (!IsWeaponValid(weaponWithModsArray, botRole)) { // Weapon is bad, fall back to weapons preset diff --git a/Libraries/Core/Models/Spt/Bots/GenerateWeaponRequest.cs b/Libraries/Core/Models/Spt/Bots/GenerateWeaponRequest.cs index e1ec77d4..a1fef553 100644 --- a/Libraries/Core/Models/Spt/Bots/GenerateWeaponRequest.cs +++ b/Libraries/Core/Models/Spt/Bots/GenerateWeaponRequest.cs @@ -43,7 +43,7 @@ public record GenerateWeaponRequest /** Array of item tpls the weapon does not support */ [JsonPropertyName("conflictingItemTpls")] - public List? ConflictingItemTpls { get; set; } + public HashSet? ConflictingItemTpls { get; set; } } public record BotData diff --git a/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs b/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs index a5a8edad..96d0d652 100644 --- a/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs +++ b/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs @@ -74,7 +74,7 @@ public record ModToSpawnRequest /// List of item tpls the weapon does not support /// [JsonPropertyName("conflictingItemTpls")] - public List? ConflictingItemTpls { get; set; } + public HashSet? ConflictingItemTpls { get; set; } [JsonPropertyName("botData")] public BotData? BotData { get; set; } diff --git a/Libraries/Core/Services/BotEquipmentModPoolService.cs b/Libraries/Core/Services/BotEquipmentModPoolService.cs index f2ba4968..5473178d 100644 --- a/Libraries/Core/Services/BotEquipmentModPoolService.cs +++ b/Libraries/Core/Services/BotEquipmentModPoolService.cs @@ -170,6 +170,31 @@ public class BotEquipmentModPoolService return _weaponModPool[itemTpl]; } + public Dictionary>? GetRequiredModsForWeaponSlot(string itemTpl) + { + var result = new Dictionary>(); + + // Get item from db + var itemDb = _itemHelper.GetItem(itemTpl).Value; + if (itemDb.Properties.Slots is not null) + { + // Loop over slots flagged as 'required' + foreach (var slot in itemDb.Properties.Slots.Where(slot => slot.Required.GetValueOrDefault(false))) + { + // Create dict entry for mod slot + result.Add(slot.Name, []); + + // Add compatible tpls to dicts hashset + foreach (var compatibleItemTpl in slot.Props.Filters.FirstOrDefault().Filter) + { + result[slot.Name].Add(compatibleItemTpl); + } + } + } + + return result; + } + /** * Create weapon mod pool and set generated flag to true */ From 639cf7e559f019f157e9c0643092d6084dd7b015 Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 21 Jan 2025 13:47:01 +0000 Subject: [PATCH 04/12] Fixed 'zombie' error spam --- Libraries/Core/Helpers/BotHelper.cs | 7 ++++++- Libraries/Core/Helpers/DurabilityLimitsHelper.cs | 5 +++++ Server/Assets/configs/bot.json | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Libraries/Core/Helpers/BotHelper.cs b/Libraries/Core/Helpers/BotHelper.cs index 3de56ee1..a563ad2b 100644 --- a/Libraries/Core/Helpers/BotHelper.cs +++ b/Libraries/Core/Helpers/BotHelper.cs @@ -51,7 +51,7 @@ public class BotHelper( public bool IsBotBoss(string botRole) { - return _botConfig.Bosses.Any(x => x.ToLower() == botRole.ToLower()); + return _botConfig.Bosses.Any(x => string.Equals(x, botRole, StringComparison.CurrentCultureIgnoreCase)); } public bool IsBotFollower(string botRole) @@ -59,6 +59,11 @@ public class BotHelper( return botRole?.ToLower().StartsWith("follower") ?? false; } + public bool IsBotZombie(string botRole) + { + return botRole?.ToLower().StartsWith("zombie") ?? false; + } + /// /// Add a bot to the FRIENDLY_BOT_TYPES list /// diff --git a/Libraries/Core/Helpers/DurabilityLimitsHelper.cs b/Libraries/Core/Helpers/DurabilityLimitsHelper.cs index a5b0996b..bd3f4f86 100644 --- a/Libraries/Core/Helpers/DurabilityLimitsHelper.cs +++ b/Libraries/Core/Helpers/DurabilityLimitsHelper.cs @@ -102,6 +102,11 @@ public class DurabilityLimitsHelper( return "follower"; } + if (_botHelper.IsBotZombie(botRole)) + { + return "zombie"; + } + var roleExistsInConfig = _botConfig.Durability.BotDurabilities.ContainsKey(botRole); if (!roleExistsInConfig) { diff --git a/Server/Assets/configs/bot.json b/Server/Assets/configs/bot.json index 603e9c67..f383a5f4 100644 --- a/Server/Assets/configs/bot.json +++ b/Server/Assets/configs/bot.json @@ -281,6 +281,20 @@ "minDelta": 0, "minLimitPercent": 15 }, + "weapon": { + "lowestMax": 90, + "highestMax": 100, + "maxDelta": 10, + "minDelta": 0, + "minLimitPercent": 15 + } + }, + "zombie": { + "armor": { + "maxDelta": 10, + "minDelta": 0, + "minLimitPercent": 15 + }, "weapon": { "lowestMax": 90, "highestMax": 100, From 992d183a6f5a39c1380d6cd6422fd0d927d0644b Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 21 Jan 2025 13:49:16 +0000 Subject: [PATCH 05/12] Use correct name --- Libraries/Core/Helpers/BotHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Core/Helpers/BotHelper.cs b/Libraries/Core/Helpers/BotHelper.cs index a563ad2b..3f482db5 100644 --- a/Libraries/Core/Helpers/BotHelper.cs +++ b/Libraries/Core/Helpers/BotHelper.cs @@ -61,7 +61,7 @@ public class BotHelper( public bool IsBotZombie(string botRole) { - return botRole?.ToLower().StartsWith("zombie") ?? false; + return botRole?.ToLower().StartsWith("infected") ?? false; } /// From f2b754892f395b44b15a4fdf27a82f1ab0b7d52c Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 21 Jan 2025 13:51:26 +0000 Subject: [PATCH 06/12] Added `shooterbtr` type to durability --- Server/Assets/configs/bot.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Server/Assets/configs/bot.json b/Server/Assets/configs/bot.json index f383a5f4..24d1c87a 100644 --- a/Server/Assets/configs/bot.json +++ b/Server/Assets/configs/bot.json @@ -301,6 +301,20 @@ "maxDelta": 10, "minDelta": 0, "minLimitPercent": 15 + } + }, + "shooterbtr": { + "armor": { + "maxDelta": 0, + "minDelta": 0, + "minLimitPercent": 0 + }, + "weapon": { + "lowestMax": 100, + "highestMax": 100, + "maxDelta": 0, + "minDelta": 0, + "minLimitPercent": 0 } } } From 8fdfa0658ded65629423f4df15051e40ebcbab01 Mon Sep 17 00:00:00 2001 From: CWX Date: Tue, 21 Jan 2025 13:58:04 +0000 Subject: [PATCH 07/12] inventory callbacks fixed --- .../Core/Callbacks/InventoryCallbacks.cs | 126 ++---- .../Core/Controllers/InventoryController.cs | 101 +++++ Libraries/Core/Controllers/QuestController.cs | 398 +++++++++++++++++- Libraries/Core/Helpers/QuestHelper.cs | 2 +- .../Core/Models/Eft/Common/Tables/Quest.cs | 2 +- 5 files changed, 520 insertions(+), 109 deletions(-) diff --git a/Libraries/Core/Callbacks/InventoryCallbacks.cs b/Libraries/Core/Callbacks/InventoryCallbacks.cs index 74fb7022..6b70468f 100644 --- a/Libraries/Core/Callbacks/InventoryCallbacks.cs +++ b/Libraries/Core/Callbacks/InventoryCallbacks.cs @@ -23,9 +23,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse MoveItem(PmcData pmcData, InventoryMoveRequestData info, string sessionID, ItemEventRouterResponse output) { - _inventoryController.MoveItem(pmcData, info, sessionID, output); - - return output; + _inventoryController.MoveItem(pmcData, info, sessionID, output); + return output; } /// @@ -38,10 +37,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse RemoveItem(PmcData pmcData, InventoryRemoveRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.RemoveItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.RemoveItem(pmcData, info, sessionID, output); + return output; } /// @@ -54,10 +51,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse SplitItem(PmcData pmcData, InventorySplitRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.SplitItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.SplitItem(pmcData, info, sessionID, output); + return output; } /// @@ -70,10 +65,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse MergeItem(PmcData pmcData, InventoryMergeRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.MergeItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.MergeItem(pmcData, info, sessionID, output); + return output; } /// @@ -86,10 +79,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse TransferItem(PmcData pmcData, InventoryTransferRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.TransferItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.TransferItem(pmcData, info, sessionID, output); + return output; } /// @@ -101,10 +92,7 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse SwapItem(PmcData pmcData, InventorySwapRequestData info, string sessionID) { - // _inventoryController.SwapItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + return _inventoryController.SwapItem(pmcData, info, sessionID); } /// @@ -116,10 +104,7 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse FoldItem(PmcData pmcData, InventoryFoldRequestData info, string sessionID) { - // _inventoryController.FoldItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + return _inventoryController.FoldItem(pmcData, info, sessionID); } /// @@ -131,10 +116,7 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse ToggleItem(PmcData pmcData, InventoryToggleRequestData info, string sessionID) { - // _inventoryController.ToggleItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + return _inventoryController.ToggleItem(pmcData, info, sessionID); } /// @@ -146,10 +128,7 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse TagItem(PmcData pmcData, InventoryTagRequestData info, string sessionID) { - // _inventoryController.TagItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + return _inventoryController.TagItem(pmcData, info, sessionID); } /// @@ -162,10 +141,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse BindItem(PmcData pmcData, InventoryBindRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.BindItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.BindItem(pmcData, info, sessionID, output); + return output; } /// @@ -178,10 +155,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse UnBindItem(PmcData pmcData, InventoryBindRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.UnBindItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.UnBindItem(pmcData, info, sessionID, output); + return output; } /// @@ -194,10 +169,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse ExamineItem(PmcData pmcData, InventoryExamineRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.ExamineItem(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.ExamineItem(pmcData, info, sessionID, output); + return output; } /// @@ -209,10 +182,7 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData info, string sessionID) { - // _inventoryController.ReadEncyclopedia(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + return _inventoryController.ReadEncyclopedia(pmcData, info, sessionID); } /// @@ -225,10 +195,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse SortInventory(PmcData pmcData, InventorySortRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.SortInventory(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.SortInventory(pmcData, info, sessionID, output); + return output; } /// @@ -241,10 +209,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse CreateMapMarker(PmcData pmcData, InventoryCreateMarkerRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.CreateMapMarker(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.CreateMapMarker(pmcData, info, sessionID, output); + return output; } /// @@ -257,10 +223,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse DeleteMapMarker(PmcData pmcData, InventoryDeleteMarkerRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.DeleteMapMarker(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.DeleteMapMarker(pmcData, info, sessionID, output); + return output; } /// @@ -273,10 +237,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse EditMapMarker(PmcData pmcData, InventoryEditMarkerRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.EditMapMarker(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.EditMapMarker(pmcData, info, sessionID, output); + return output; } /// @@ -290,10 +252,8 @@ public class InventoryCallbacks( public ItemEventRouterResponse OpenRandomLootContainer(PmcData pmcData, OpenRandomLootContainerRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.OpenRandomLootContainer(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.OpenRandomLootContainer(pmcData, info, sessionID, output); + return output; } /// @@ -306,10 +266,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse RedeemProfileReward(PmcData pmcData, RedeemProfileRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.RedeemProfileReward(pmcData, info, sessionID); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.RedeemProfileReward(pmcData, info, sessionID); + return output; } /// @@ -322,10 +280,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse SetFavoriteItem(PmcData pmcData, SetFavoriteItems info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.SetFavoriteItem(pmcData, info, sessionID); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.SetFavoriteItem(pmcData, info, sessionID); + return output; } /// @@ -339,10 +295,8 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse FailQuest(PmcData pmcData, FailQuestRequestData info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.FailQuest(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _questController.FailQuest(pmcData, info, sessionID, output); + return output; } /// @@ -355,9 +309,7 @@ public class InventoryCallbacks( /// public ItemEventRouterResponse PinOrLock(PmcData pmcData, PinOrLockItemRequest info, string sessionID, ItemEventRouterResponse output) { - // _inventoryController.PinOrLock(pmcData, info, sessionID, output); - // TODO: InventoryController is not implemented rn - // return output; - throw new NotImplementedException(); + _inventoryController.PinOrLock(pmcData, info, sessionID, output); + return output; } } diff --git a/Libraries/Core/Controllers/InventoryController.cs b/Libraries/Core/Controllers/InventoryController.cs index 3006d8a4..9fa52cbe 100644 --- a/Libraries/Core/Controllers/InventoryController.cs +++ b/Libraries/Core/Controllers/InventoryController.cs @@ -4,6 +4,7 @@ using Core.Helpers; using Core.Models.Eft.Common; using Core.Models.Eft.Inventory; using Core.Models.Eft.ItemEvent; +using Core.Models.Eft.Quests; using Core.Models.Enums; using Core.Models.Utils; using Core.Routers; @@ -93,4 +94,104 @@ public class InventoryController( (BackendErrorCodes)228 ); } + + public void PinOrLock(PmcData pmcData, PinOrLockItemRequest info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void SetFavoriteItem(PmcData pmcData, SetFavoriteItems info, string sessionId) + { + throw new NotImplementedException(); + } + + public void RedeemProfileReward(PmcData pmcData, RedeemProfileRequestData info, string sessionId) + { + throw new NotImplementedException(); + } + + public void OpenRandomLootContainer(PmcData pmcData, OpenRandomLootContainerRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void EditMapMarker(PmcData pmcData, InventoryEditMarkerRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void DeleteMapMarker(PmcData pmcData, InventoryDeleteMarkerRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void CreateMapMarker(PmcData pmcData, InventoryCreateMarkerRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void SortInventory(PmcData pmcData, InventorySortRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData info, string sessionId) + { + throw new NotImplementedException(); + } + + public void ExamineItem(PmcData pmcData, InventoryExamineRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void UnBindItem(PmcData pmcData, InventoryBindRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void BindItem(PmcData pmcData, InventoryBindRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public ItemEventRouterResponse TagItem(PmcData pmcData, InventoryTagRequestData info, string sessionId) + { + throw new NotImplementedException(); + } + + public ItemEventRouterResponse ToggleItem(PmcData pmcData, InventoryToggleRequestData info, string sessionId) + { + throw new NotImplementedException(); + } + + public ItemEventRouterResponse FoldItem(PmcData pmcData, InventoryFoldRequestData info, string sessionId) + { + throw new NotImplementedException(); + } + + public ItemEventRouterResponse SwapItem(PmcData pmcData, InventorySwapRequestData info, string sessionId) + { + throw new NotImplementedException(); + } + + public void TransferItem(PmcData pmcData, InventoryTransferRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void MergeItem(PmcData pmcData, InventoryMergeRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void SplitItem(PmcData pmcData, InventorySplitRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } + + public void RemoveItem(PmcData pmcData, InventoryRemoveRequestData info, string sessionId, ItemEventRouterResponse output) + { + throw new NotImplementedException(); + } } diff --git a/Libraries/Core/Controllers/QuestController.cs b/Libraries/Core/Controllers/QuestController.cs index 940419f2..02bb4e1a 100644 --- a/Libraries/Core/Controllers/QuestController.cs +++ b/Libraries/Core/Controllers/QuestController.cs @@ -1,11 +1,19 @@ +using System.Runtime.InteropServices.JavaScript; using SptCommon.Annotations; using Core.Helpers; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.ItemEvent; using Core.Models.Eft.Quests; +using Core.Models.Enums; +using Core.Models.Spt.Config; using Core.Models.Utils; +using Core.Routers; +using Core.Servers; +using Core.Services; using Core.Utils; +using Core.Utils.Cloners; +using Product = Core.Models.Eft.ItemEvent.Product; namespace Core.Controllers; @@ -15,32 +23,382 @@ public class QuestController( ISptLogger _logger, TimeUtil _timeUtil, HttpResponseUtil _httpResponseUtil, + EventOutputHolder _eventOutputHolder, + DatabaseService _databaseService, + ItemHelper _itemHelper, + DialogueHelper _dialogueHelper, + MailSendService _mailSendService, + ProfileHelper _profileHelper, + TraderHelper _traderHelper, QuestHelper _questHelper, - QuestRewardHelper _questRewardHelper + QuestRewardHelper _questRewardHelper, + QuestConditionHelper _questConditionHelper, + PlayerService _playerService, + LocaleService _localeService, + LocalisationService _localisationService, + ConfigServer _configServer, + ICloner _cloner ) { - public ItemEventRouterResponse CompleteQuest(PmcData pmcData, CompleteQuestRequestData info, string sessionId) - { - throw new NotImplementedException(); - } - - public ItemEventRouterResponse AcceptRepeatableQuest(PmcData pmcData, AcceptQuestRequestData info, string sessionId) - { - throw new NotImplementedException(); - } - - public ItemEventRouterResponse AcceptQuest(PmcData pmcData, AcceptQuestRequestData info, string sessionId) - { - throw new NotImplementedException(); - } - - public ItemEventRouterResponse HandoverQuest(PmcData pmcData, HandoverQuestRequestData info, string sessionId) - { - throw new NotImplementedException(); - } + protected QuestConfig _questConfig = _configServer.GetConfig(); + protected List _questTypes = ["PickUp", "Exploration", "Elimination"]; public List GetClientQuest(string sessionId) { return _questHelper.GetClientQuests(sessionId); } + + public ItemEventRouterResponse AcceptQuest(PmcData pmcData, AcceptQuestRequestData acceptedQuest, string sessionID) + { + var acceptQuestResponse = _eventOutputHolder.GetOutput(sessionID); + + // Does quest exist in profile + // Restarting a failed quest can mean quest exists in profile + var existingQuestStatus = pmcData.Quests.FirstOrDefault((x) => x.QId == acceptedQuest.QuestId); + if (existingQuestStatus is not null) + { + // Update existing + _questHelper.ResetQuestState(pmcData, QuestStatusEnum.Started, acceptedQuest.QuestId); + + // Need to send client an empty list of completedConditions (Unsure if this does anything) + acceptQuestResponse.ProfileChanges[sessionID].QuestsStatus.Add(existingQuestStatus); + } + else + { + // Add new quest to server profile + var newQuest = _questHelper.GetQuestReadyForProfile(pmcData, QuestStatusEnum.Started, acceptedQuest); + pmcData.Quests.Add(newQuest); + } + + // Create a dialog message for starting the quest. + // Note that for starting quests, the correct locale field is "description", not "startedMessageText". + var questFromDb = _questHelper.GetQuestFromDb(acceptedQuest.QuestId, pmcData); + + AddTaskConditionCountersToProfile(questFromDb.Conditions.AvailableForFinish, pmcData, acceptedQuest.QuestId); + + // Get messageId of text to send to player as text message in game + var messageId = _questHelper.GetMessageIdForQuestStart( + questFromDb.StartedMessageText, + questFromDb.Description + ); + + // Apply non-item rewards to profile + return item rewards + var startedQuestRewardItems = _questRewardHelper.ApplyQuestReward( + pmcData, + acceptedQuest.QuestId, + QuestStatusEnum.Started, + sessionID, + acceptQuestResponse + ); + + // Send started text + any starting reward items found above to player + _mailSendService.SendLocalisedNpcMessageToPlayer( + sessionID, + _traderHelper.GetTraderById(questFromDb.TraderId).ToString(), + MessageType.QUEST_START, + messageId, + startedQuestRewardItems.ToList(), + _timeUtil.GetHoursAsSeconds((int)_questHelper.GetMailItemRedeemTimeHoursForProfile(pmcData)) + ); + + // Having accepted new quest, look for newly unlocked quests and inform client of them + var newlyAccessibleQuests = _questHelper.GetNewlyAccessibleQuestsWhenStartingQuest( + acceptedQuest.QuestId, + sessionID + ); + if (newlyAccessibleQuests.Count > 0) + { + acceptQuestResponse.ProfileChanges[sessionID].Quests.AddRange(newlyAccessibleQuests); + } + + return acceptQuestResponse; + } + + private void AddTaskConditionCountersToProfile(List? questConditions, PmcData pmcData, string questId) + { + foreach (var condition in questConditions) + { + if (pmcData.TaskConditionCounters.TryGetValue(condition.Id, out var counter)) + { + _logger.Error( + $"Unable to add new task condition counter: {condition.ConditionType} for qeust: {questId} to profile: {pmcData.SessionId} as it already exists:" + ); + } + + switch (condition.ConditionType) + { + case "SellItemToTrader": + pmcData.TaskConditionCounters[condition.Id] = new TaskConditionCounter + { + Id = condition.Id, + SourceId = questId, + Type = condition.ConditionType, + Value = 0, + }; + break; + } + } + } + + public ItemEventRouterResponse AcceptRepeatableQuest(PmcData pmcData, AcceptQuestRequestData acceptedQuest, string sessionID) + { + // Create and store quest status object inside player profile + var newRepeatableQuest = _questHelper.GetQuestReadyForProfile( + pmcData, + QuestStatusEnum.Started, + acceptedQuest + ); + pmcData.Quests.Add(newRepeatableQuest); + + // Look for the generated quest cache in profile.RepeatableQuests + var repeatableQuestProfile = GetRepeatableQuestFromProfile(pmcData, acceptedQuest); + if (repeatableQuestProfile is null) + { + _logger.Error( + _localisationService.GetText( + "repeatable-accepted_repeatable_quest_not_found_in_active_quests", + acceptedQuest.QuestId + ) + ); + + throw new Exception(_localisationService.GetText("repeatable-unable_to_accept_quest_see_log")); + } + + // Some scav quests need to be added to scav profile for them to show up in-raid + if (repeatableQuestProfile.Side == "Scav" && _questTypes.Contains(repeatableQuestProfile.Type.ToString())) + { + var fullProfile = _profileHelper.GetFullProfile(sessionID); + if (fullProfile.CharacterData.ScavData.Quests is null) + { + fullProfile.CharacterData.ScavData.Quests = []; + } + + fullProfile.CharacterData.ScavData.Quests.Add(newRepeatableQuest); + } + + var response = _eventOutputHolder.GetOutput(sessionID); + + return response; + } + + private RepeatableQuest GetRepeatableQuestFromProfile(PmcData pmcData, AcceptQuestRequestData acceptedQuest) + { + foreach (var repeatableQuest in pmcData.RepeatableQuests) + { + var matchingQuest = repeatableQuest.ActiveQuests.FirstOrDefault(x => x.Id == acceptedQuest.QuestId); + if (matchingQuest is not null) + { + _logger.Debug($"Accepted repeatable quest {acceptedQuest.QuestId} from {repeatableQuest.Name}"); + matchingQuest.SptRepatableGroupName = repeatableQuest.Name; + + return matchingQuest; + } + } + + return null; + } + + public ItemEventRouterResponse CompleteQuest(PmcData pmcData, CompleteQuestRequestData info, string sessionId) + { + return _questHelper.CompleteQuest(pmcData, info, sessionId); + } + + + public ItemEventRouterResponse HandoverQuest(PmcData pmcData, HandoverQuestRequestData handoverQuestRequest, string sessionID) + { + var quest = _questHelper.GetQuestFromDb(handoverQuestRequest.QuestId, pmcData); + List handoverQuestTypes = ["HandoverItem", "WeaponAssembly"]; + var output = _eventOutputHolder.GetOutput(sessionID); + + var isItemHandoverQuest = true; + var handedInCount = 0; + + // Decrement number of items handed in + QuestCondition? handoverRequirements = null; + foreach (var condition in quest.Conditions.AvailableForFinish) + { + if (condition.Id == handoverQuestRequest.ConditionId && handoverQuestTypes.Contains(condition.ConditionType)) + { + handedInCount = int.Parse((string)condition.Value); + isItemHandoverQuest = condition.ConditionType == handoverQuestTypes.FirstOrDefault(); + handoverRequirements = condition; + + if (pmcData.TaskConditionCounters.TryGetValue("ConditionId", out var counter)) + { + handedInCount -= (int)(counter.Value ?? 0); + + if (handedInCount <= 0) + { + _logger.Error( + _localisationService.GetText( + "repeatable-quest_handover_failed_condition_already_satisfied", + new + { + questId = handoverQuestRequest.QuestId, + conditionId = handoverQuestRequest.ConditionId, + profileCounter = counter.Value, + value = handedInCount + } + ) + ); + + return output; + } + + break; + } + } + } + + if (isItemHandoverQuest && handedInCount == 0) + { + return ShowRepeatableQuestInvalidConditionError(handoverQuestRequest, output); + } + + var totalItemCountToRemove = 0; + foreach (var itemHandover in handoverQuestRequest.Items) + { + var matchingItemInProfile = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == itemHandover.Id); + if (!(matchingItemInProfile is not null && handoverRequirements.Target.List.Contains(matchingItemInProfile.Template))) + { + // Item handed in by player doesnt match what was requested + return ShowQuestItemHandoverMatchError( + handoverQuestRequest, + matchingItemInProfile, + handoverRequirements, + output + ); + } + + // Remove the right quantity of given items + var itemCountToRemove = Math.Min(itemHandover.Count ?? 0, handedInCount - totalItemCountToRemove); + totalItemCountToRemove += itemCountToRemove; + if (itemHandover.Count - itemCountToRemove > 0) + { + // Remove single item with no children + _questHelper.ChangeItemStack( + pmcData, + itemHandover.Id, + itemHandover.Count - itemCountToRemove ?? 0, + sessionID, + output + ); + if (totalItemCountToRemove == handedInCount) + { + break; + } + } + else + { + // Remove item with children + var toRemove = _itemHelper.FindAndReturnChildrenByItems(pmcData.Inventory.Items, itemHandover.Id); + var index = pmcData.Inventory.Items.Count; + + // Important: don't tell the client to remove the attachments, it will handle it + output.ProfileChanges[sessionID] + .Items.DeletedItems.Add( + new Product + { + Id = itemHandover.Id + } + ); + + // Important: loop backward when removing items from the array we're looping on + while (index-- > 0) + { + if (toRemove.Contains(pmcData.Inventory.Items[index].Id)) + { + var removedItem = _cloner.Clone(pmcData.Inventory.Items[index]); + pmcData.Inventory.Items.RemoveAt(index); + // Remove the item + + // If the removed item has a numeric `location` property, re-calculate all the child + // element `location` properties of the parent so they are sequential, while retaining order + if (removedItem.Location.GetType() == typeof(int)) + { + var childItems = _itemHelper.FindAndReturnChildrenAsItems( + pmcData.Inventory.Items, + removedItem.ParentId + ); + childItems.RemoveAt(0); // Remove the parent + + // Sort by the current `location` and update + childItems.Sort((a, b) => (((int)a.Location) > ((int)b.Location) ? 1 : -1)); + + for (int i = 0; i < childItems.Count; i++) + { + childItems[i].Location = i; + } + } + } + } + } + } + + UpdateProfileTaskConditionCounterValue( + pmcData, + handoverQuestRequest.ConditionId, + handoverQuestRequest.QuestId, + totalItemCountToRemove + ); + + return output; + } + + private ItemEventRouterResponse ShowRepeatableQuestInvalidConditionError(HandoverQuestRequestData handoverQuestRequest, ItemEventRouterResponse output) + { + var errorMessage = _localisationService.GetText( + "repeatable-quest_handover_failed_condition_invalid", + new + { + questId = handoverQuestRequest.QuestId, + conditionId = handoverQuestRequest.ConditionId + } + ); + _logger.Error(errorMessage); + + return _httpResponseUtil.AppendErrorToOutput(output, errorMessage); + } + + private ItemEventRouterResponse ShowQuestItemHandoverMatchError(HandoverQuestRequestData handoverQuestRequest, Item? itemHandedOver, + QuestCondition? handoverRequirements, ItemEventRouterResponse output) + { + var errorMessage = _localisationService.GetText( + "quest-handover_wrong_item", + new + { + questId = handoverQuestRequest.QuestId, + handedInTpl = itemHandedOver?.Template ?? "UNKNOWN", + requiredTpl = handoverRequirements.Target.List.FirstOrDefault(), + } + ); + _logger.Error(errorMessage); + + return _httpResponseUtil.AppendErrorToOutput(output, errorMessage); + } + + private void UpdateProfileTaskConditionCounterValue(PmcData pmcData, string conditionId, string questId, int counterValue) + { + if (pmcData.TaskConditionCounters[conditionId] != null) + { + pmcData.TaskConditionCounters[conditionId].Value += counterValue; + + return; + } + + pmcData.TaskConditionCounters[conditionId] = new TaskConditionCounter + { + Id = conditionId, + SourceId = questId, + Type = "HandoverItem", + Value = counterValue, + }; + } + + public ItemEventRouterResponse FailQuest(PmcData pmcData, FailQuestRequestData request, string sessionID, ItemEventRouterResponse output) + { + _questHelper.FailQuest(pmcData, request, sessionID, output); + + return output; + } } diff --git a/Libraries/Core/Helpers/QuestHelper.cs b/Libraries/Core/Helpers/QuestHelper.cs index edce4612..0e1c5ee0 100644 --- a/Libraries/Core/Helpers/QuestHelper.cs +++ b/Libraries/Core/Helpers/QuestHelper.cs @@ -204,7 +204,7 @@ public class QuestHelper( */ public QuestStatus GetQuestReadyForProfile( PmcData pmcData, - QuestStatus newState, + QuestStatusEnum newState, AcceptQuestRequestData acceptedQuest ) { diff --git a/Libraries/Core/Models/Eft/Common/Tables/Quest.cs b/Libraries/Core/Models/Eft/Common/Tables/Quest.cs index f6e6c837..616db1b9 100644 --- a/Libraries/Core/Models/Eft/Common/Tables/Quest.cs +++ b/Libraries/Core/Models/Eft/Common/Tables/Quest.cs @@ -48,7 +48,7 @@ public record Quest public string? Image { get; set; } [JsonPropertyName("type")] - public string? RewardType { get; set; } + public QuestTypeEnum? Type { get; set; } [JsonPropertyName("isKey")] public bool? IsKey { get; set; } From 8dba8d6a9c33807f8365dc8fc6142dfcfede29da Mon Sep 17 00:00:00 2001 From: CWX Date: Tue, 21 Jan 2025 14:02:08 +0000 Subject: [PATCH 08/12] temp fix --- Libraries/Core/Models/Eft/Common/Tables/Quest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Core/Models/Eft/Common/Tables/Quest.cs b/Libraries/Core/Models/Eft/Common/Tables/Quest.cs index 616db1b9..9255b136 100644 --- a/Libraries/Core/Models/Eft/Common/Tables/Quest.cs +++ b/Libraries/Core/Models/Eft/Common/Tables/Quest.cs @@ -47,8 +47,8 @@ public record Quest [JsonPropertyName("image")] public string? Image { get; set; } - [JsonPropertyName("type")] - public QuestTypeEnum? Type { get; set; } + [JsonPropertyName("type")] // can be string or QuestTypeEnum + public string? Type { get; set; } [JsonPropertyName("isKey")] public bool? IsKey { get; set; } From 5c07a05343c5be90af4a182358103851cf64962b Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 21 Jan 2025 14:21:25 +0000 Subject: [PATCH 09/12] Fixed `GetRandomizedArmorDurability` not handling some bot types --- Libraries/Core/Controllers/BotController.cs | 2 +- .../Core/Generators/BotInventoryGenerator.cs | 2 +- .../ExternalInventoryMagGen.cs | 6 ++++++ Libraries/Core/Helpers/BotHelper.cs | 2 +- .../Core/Helpers/DurabilityLimitsHelper.cs | 21 ++----------------- Libraries/Core/Helpers/InventoryHelper.cs | 19 ++++++++--------- 6 files changed, 20 insertions(+), 32 deletions(-) diff --git a/Libraries/Core/Controllers/BotController.cs b/Libraries/Core/Controllers/BotController.cs index 89b21fb4..54c80d34 100644 --- a/Libraries/Core/Controllers/BotController.cs +++ b/Libraries/Core/Controllers/BotController.cs @@ -258,7 +258,7 @@ public class BotController( requestedBot?.Role, raidSettings.Location ); - if (convertIntoPmcChanceMinMax is not null && botGenerationDetails.IsPmc is not null && !botGenerationDetails.IsPmc.Value) + if (convertIntoPmcChanceMinMax is not null && !botGenerationDetails.IsPmc.GetValueOrDefault(false)) { // Bot has % chance to become pmc and isnt one pmc already var convertToPmc = _botHelper.RollChanceToBePmc(convertIntoPmcChanceMinMax); diff --git a/Libraries/Core/Generators/BotInventoryGenerator.cs b/Libraries/Core/Generators/BotInventoryGenerator.cs index 3b2a4914..a885043c 100644 --- a/Libraries/Core/Generators/BotInventoryGenerator.cs +++ b/Libraries/Core/Generators/BotInventoryGenerator.cs @@ -470,7 +470,7 @@ public class BotInventoryGenerator( // Edge case: Filter the armor items mod pool if bot exists in config dict + config has armor slot if (_botConfig.Equipment.ContainsKey(settings.BotData.EquipmentRole) && - settings.RandomisationDetails is not null && + settings.RandomisationDetails?.RandomisedArmorSlots != null && settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString())) { // Filter out mods from relevant blacklist diff --git a/Libraries/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs b/Libraries/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs index 021bd94b..6222d1b8 100644 --- a/Libraries/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs +++ b/Libraries/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs @@ -95,6 +95,12 @@ public class ExternalInventoryMagGen( break; } + if (defaultMagazineTpl == BaseClasses.MAGAZINE) + { + // Magazine base type, do not use + break; + } + // 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; diff --git a/Libraries/Core/Helpers/BotHelper.cs b/Libraries/Core/Helpers/BotHelper.cs index 3f482db5..64bfc73c 100644 --- a/Libraries/Core/Helpers/BotHelper.cs +++ b/Libraries/Core/Helpers/BotHelper.cs @@ -29,7 +29,7 @@ public class BotHelper( /// BotType object public BotType GetBotTemplate(string role) { - if (!_databaseService.GetBots().Types.TryGetValue(role.ToLower(), out var bot)) + if (!_databaseService.GetBots().Types.TryGetValue(role?.ToLower(), out var bot)) { _logger.Error($"Unable to get bot of type: {role} from DB"); diff --git a/Libraries/Core/Helpers/DurabilityLimitsHelper.cs b/Libraries/Core/Helpers/DurabilityLimitsHelper.cs index bd3f4f86..b81934cc 100644 --- a/Libraries/Core/Helpers/DurabilityLimitsHelper.cs +++ b/Libraries/Core/Helpers/DurabilityLimitsHelper.cs @@ -127,26 +127,9 @@ public class DurabilityLimitsHelper( /// Current armor durability public double GetRandomizedArmorDurability(TemplateItem? itemTemplate, string? botRole, double maxDurability) { - if (botRole is not null) - { - if (_botHelper.IsBotPmc(botRole)) - { - return GenerateArmorDurability("pmc", maxDurability); - } + var durabilityRole = GetDurabilityRole(botRole); - if (_botHelper.IsBotBoss(botRole)) - { - return GenerateArmorDurability("boss", maxDurability); - } - - if (_botHelper.IsBotFollower(botRole)) - { - return GenerateArmorDurability("follower", maxDurability); - } - } - - - return GenerateArmorDurability(botRole, maxDurability); + return GenerateArmorDurability(durabilityRole, maxDurability); } protected double GenerateMaxWeaponDurability(string? botRole = null) diff --git a/Libraries/Core/Helpers/InventoryHelper.cs b/Libraries/Core/Helpers/InventoryHelper.cs index 20b51589..f69c7c1f 100644 --- a/Libraries/Core/Helpers/InventoryHelper.cs +++ b/Libraries/Core/Helpers/InventoryHelper.cs @@ -277,17 +277,16 @@ public class InventoryHelper( protected List GetSizeByInventoryItemHash(string itemTpl, string itemID, InventoryItemHash inventoryItemHash) { var toDo = new List { itemID }; - var result = _itemHelper.GetItem(itemTpl); - var tmpItem = result.Value; + var (key, tmpItem) = _itemHelper.GetItem(itemTpl); // Invalid item - if (!result.Key) + if (!key) { _logger.Error(_localisationService.GetText("inventory-invalid_item_missing_from_db", itemTpl)); } // Item found but no _props property - if (tmpItem is not null && tmpItem.Properties is null) + if (key && tmpItem.Properties is null) { _localisationService.GetText("inventory-item_missing_props_property", new { itemTpl = itemTpl, @@ -296,7 +295,7 @@ public class InventoryHelper( } // No item object or getItem() returned false - if (tmpItem is null && result.Value is null) + if (!key && tmpItem is null) { // return default size of 1x1 _logger.Error(_localisationService.GetText("inventory-return_default_size", itemTpl)); @@ -305,7 +304,7 @@ public class InventoryHelper( } var rootItem = inventoryItemHash.ByItemId[itemID]; - var foldableWeapon = tmpItem.Properties.Foldable; + var isFoldable = tmpItem.Properties.Foldable; var foldedSlot = tmpItem.Properties.FoldedSlot; var sizeUp = 0; @@ -323,10 +322,10 @@ public class InventoryHelper( // Item types to ignore var skipThisItems = new List { BaseClasses.BACKPACK, BaseClasses.SEARCHABLE_ITEM, BaseClasses.SIMPLE_CONTAINER }; - var rootFolded = rootItem?.Upd?.Foldable?.Folded == true; + var rootIsFolded = rootItem?.Upd?.Foldable?.Folded == true; // The item itself is collapsible - if (foldableWeapon is not null && string.IsNullOrEmpty(foldedSlot) && rootFolded) + if (isFoldable is not null && string.IsNullOrEmpty(foldedSlot) && rootIsFolded) { outX -= tmpItem.Properties.SizeReduceRight.Value; } @@ -358,12 +357,12 @@ public class InventoryHelper( var childFoldable = itm.Properties.Foldable.GetValueOrDefault(false); var childFolded = item.Upd?.Foldable is not null && item.Upd.Foldable.Folded == true; - if (foldableWeapon is true && foldedSlot == item.SlotId && (rootFolded || childFolded)) + if (isFoldable is true && foldedSlot == item.SlotId && (rootIsFolded || childFolded)) { continue; } - if (childFoldable && rootFolded && childFolded) + if (childFoldable && rootIsFolded && childFolded) { continue; } From 87f36ea5b8571ad6c75d415700970971fcf2c268 Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 21 Jan 2025 14:24:18 +0000 Subject: [PATCH 10/12] Added gifter values --- Server/Assets/configs/bot.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Server/Assets/configs/bot.json b/Server/Assets/configs/bot.json index 24d1c87a..ebf44e78 100644 --- a/Server/Assets/configs/bot.json +++ b/Server/Assets/configs/bot.json @@ -953,6 +953,9 @@ }, "assaultgroup": {}, "gifter": { + "nvgIsActiveChanceDayPercent": 0, + "nvgIsActiveChanceNightPercent": 90, + "faceShieldIsActiveChancePercent": 0, "lightIsActiveDayChancePercent": 25, "lightIsActiveNightChancePercent": 75, "laserIsActiveChancePercent": 75 From caf0b2bc7d72795a3827da3484e29463b62fb333 Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 21 Jan 2025 14:26:23 +0000 Subject: [PATCH 11/12] Added skier/peacemaker durability values --- Server/Assets/configs/bot.json | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Server/Assets/configs/bot.json b/Server/Assets/configs/bot.json index ebf44e78..4d4e4ea9 100644 --- a/Server/Assets/configs/bot.json +++ b/Server/Assets/configs/bot.json @@ -316,7 +316,35 @@ "minDelta": 0, "minLimitPercent": 0 } - } + }, + "skier": { + "armor": { + "maxDelta": 10, + "minDelta": 0, + "minLimitPercent": 15 + }, + "weapon": { + "lowestMax": 60, + "highestMax": 100, + "maxDelta": 10, + "minDelta": 0, + "minLimitPercent": 15 + } + }, + "peacemaker": { + "armor": { + "maxDelta": 10, + "minDelta": 0, + "minLimitPercent": 15 + }, + "weapon": { + "lowestMax": 60, + "highestMax": 100, + "maxDelta": 10, + "minDelta": 0, + "minLimitPercent": 15 + } + } } }, "lootItemResourceRandomization": { From 35380120bddfd3f8f0d8523036816cc5cf076065 Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 21 Jan 2025 14:53:43 +0000 Subject: [PATCH 12/12] toLower bot role when generating --- Libraries/Core/Services/BotEquipmentFilterService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Core/Services/BotEquipmentFilterService.cs b/Libraries/Core/Services/BotEquipmentFilterService.cs index c8b108b2..8e075ee4 100644 --- a/Libraries/Core/Services/BotEquipmentFilterService.cs +++ b/Libraries/Core/Services/BotEquipmentFilterService.cs @@ -54,7 +54,7 @@ public class BotEquipmentFilterService pmcProfile.Info.Level ?? 0 ); - var botEquipConfig = _botEquipmentConfig[botRole]; + var botEquipConfig = _botEquipmentConfig[botRole.ToLower()]; var randomisationDetails = _botHelper.GetBotRandomizationDetails(botLevel, botEquipConfig); if (botEquipmentBlacklist is not null || botEquipmentWhitelist is not null)