diff --git a/ItemCatalogueUI.cs b/ItemCatalogueUI.cs index d3ab910..c1906bd 100644 --- a/ItemCatalogueUI.cs +++ b/ItemCatalogueUI.cs @@ -296,6 +296,14 @@ internal void Update() List slotsToUse = itemSlots; if (SharedUI.instance.SelectedCategory.name == ArmorSetFeatureHelper.ArmorSetsInternalName) { + if (ArmorSetFeatureHelper.IsCalculating && !ArmorSetFeatureHelper.IsCalculationComplete) { + //still calculating + slowUpdateNeeded = 10; //check again in 10 frames + ArmorSetFeatureHelper.AppendSpecialUI(itemGrid); + itemGrid.UpdateOrder(); + itemGrid._innerList.Recalculate(); + return; + } if (ArmorSetFeatureHelper.armorSetSlots == null) ArmorSetFeatureHelper.CalculateArmorSets(); slotsToUse = ArmorSetFeatureHelper.armorSetSlots.Cast().ToList(); diff --git a/Localization/de-DE.hjson b/Localization/de-DE.hjson index 41a258d..456f8fd 100644 --- a/Localization/de-DE.hjson +++ b/Localization/de-DE.hjson @@ -281,11 +281,7 @@ Mods: { UIArmorSetCatalogue: { // TotalSetDefense: Total Set Defense: {0} - /* ArmorSets: - ''' - Armor Sets - (Warning: May take many seconds to calculate) - ''' */ + // ArmorSets: Armor Sets // ShowItems: Show Items // ShowItemsTooltip: Display the items that make up the set // UseDye: Use Dye diff --git a/Localization/en-US.hjson b/Localization/en-US.hjson index fc13d43..396148e 100644 --- a/Localization/en-US.hjson +++ b/Localization/en-US.hjson @@ -281,11 +281,7 @@ Mods: { UIArmorSetCatalogue: { TotalSetDefense: Total Set Defense: {0} - ArmorSets: - ''' - Armor Sets - (Warning: May take many seconds to calculate) - ''' + ArmorSets: Armor Sets ShowItems: Show Items ShowItemsTooltip: Display the items that make up the set UseDye: Use Dye diff --git a/Localization/es-ES.hjson b/Localization/es-ES.hjson index 0f4472b..cb56624 100644 --- a/Localization/es-ES.hjson +++ b/Localization/es-ES.hjson @@ -281,11 +281,7 @@ Mods: { UIArmorSetCatalogue: { // TotalSetDefense: Total Set Defense: {0} - /* ArmorSets: - ''' - Armor Sets - (Warning: May take many seconds to calculate) - ''' */ + // ArmorSets: Armor Sets // ShowItems: Show Items // ShowItemsTooltip: Display the items that make up the set // UseDye: Use Dye diff --git a/Localization/fr-FR.hjson b/Localization/fr-FR.hjson index d6ad402..df6843d 100644 --- a/Localization/fr-FR.hjson +++ b/Localization/fr-FR.hjson @@ -293,11 +293,7 @@ Mods: { UIArmorSetCatalogue: { // TotalSetDefense: Total Set Defense: {0} - /* ArmorSets: - ''' - Armor Sets - (Warning: May take many seconds to calculate) - ''' */ + // ArmorSets: Armor Sets // ShowItems: Show Items // ShowItemsTooltip: Display the items that make up the set // UseDye: Use Dye diff --git a/Localization/it-IT.hjson b/Localization/it-IT.hjson index 04f26c9..c3a17e3 100644 --- a/Localization/it-IT.hjson +++ b/Localization/it-IT.hjson @@ -281,11 +281,7 @@ Mods: { UIArmorSetCatalogue: { // TotalSetDefense: Total Set Defense: {0} - /* ArmorSets: - ''' - Armor Sets - (Warning: May take many seconds to calculate) - ''' */ + // ArmorSets: Armor Sets // ShowItems: Show Items // ShowItemsTooltip: Display the items that make up the set // UseDye: Use Dye diff --git a/Localization/pl-PL.hjson b/Localization/pl-PL.hjson index 52c4fa6..3eeea3c 100644 --- a/Localization/pl-PL.hjson +++ b/Localization/pl-PL.hjson @@ -281,11 +281,7 @@ Mods: { UIArmorSetCatalogue: { // TotalSetDefense: Total Set Defense: {0} - /* ArmorSets: - ''' - Armor Sets - (Warning: May take many seconds to calculate) - ''' */ + // ArmorSets: Armor Sets // ShowItems: Show Items // ShowItemsTooltip: Display the items that make up the set // UseDye: Use Dye diff --git a/Localization/pt-BR.hjson b/Localization/pt-BR.hjson index 14992e7..e13fcef 100644 --- a/Localization/pt-BR.hjson +++ b/Localization/pt-BR.hjson @@ -281,11 +281,7 @@ Mods: { UIArmorSetCatalogue: { // TotalSetDefense: Total Set Defense: {0} - /* ArmorSets: - ''' - Armor Sets - (Warning: May take many seconds to calculate) - ''' */ + // ArmorSets: Armor Sets // ShowItems: Show Items // ShowItemsTooltip: Display the items that make up the set // UseDye: Use Dye diff --git a/UIElements/UIArmorSetCatalogueItemSlot.cs b/UIElements/UIArmorSetCatalogueItemSlot.cs index 495724f..e455100 100644 --- a/UIElements/UIArmorSetCatalogueItemSlot.cs +++ b/UIElements/UIArmorSetCatalogueItemSlot.cs @@ -1,7 +1,10 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Terraria; using Terraria.GameContent.UI.Elements; using Terraria.Localization; @@ -205,10 +208,34 @@ internal static class ArmorSetFeatureHelper internal static string ArmorSetsHoverTest = Language.GetTextValue("Mods.RecipeBrowser.UIArmorSetCatalogue.ArmorSets"); internal static string ArmorSetsInternalName = "Armor Sets"; + private static Task calculationTask; + private static CancellationTokenSource cancellationTokenSource; + private static volatile bool isCalculating; + private static volatile bool calculationComplete; + private static ConcurrentBag> pendingSets; + + private static List cachedHeads; + private static List cachedBodies; + private static List cachedLegs; + + internal static bool IsCalculating => isCalculating; + internal static bool IsCalculationComplete => calculationComplete; + internal static void Unload() { + cancellationTokenSource?.Cancel(); + calculationTask = null; + cancellationTokenSource = null; + sets = null; armorSetSlots = null; UIArmorSetCatalogueItemSlot.drawPlayer = null; + + cachedHeads = null; + cachedBodies = null; + cachedLegs = null; + pendingSets = null; + isCalculating = false; + calculationComplete = false; } internal static void AppendSpecialUI(UIGrid itemGrid) { @@ -260,101 +287,171 @@ internal static void AppendSpecialUI(UIGrid itemGrid) { itemGrid._innerList.Append(panel); } - internal static void CalculateArmorSets() { - //new Category("Head", x => x.headSlot != -1, smallHead), - //new Category("Body", x => x.bodySlot != -1, smallBody), - //new Category("Legs", x => x.legSlot != -1, smallLegs), - var testPlayer = new Player(); - testPlayer.whoAmI = 255; - List Heads = new List(); - List Bodys = new List(); - List Legs = new List(); - for (int type = 1; type < ItemLoader.ItemCount; type++) { - Item item = new Item(); - item.SetDefaults(type, false); - if (item.type == 0) - continue; - - if (item.headSlot != -1) - Heads.Add(item); - if (item.bodySlot != -1) - Bodys.Add(item); - if (item.legSlot != -1) - Legs.Add(item); - } - sets = new List>(); - foreach (var head in Heads) { - foreach (var body in Bodys) { - foreach (var leg in Legs) { - testPlayer.statDefense = Player.DefenseStat.Default; - testPlayer.head = head.headSlot; - testPlayer.body = body.bodySlot; - testPlayer.legs = leg.legSlot; - testPlayer.armor[0] = head; - testPlayer.armor[1] = body; - testPlayer.armor[2] = leg; - - // TODO: Vanity Set calculation somehow? - testPlayer.UpdateArmorSets(255); - if (testPlayer.setBonus != "") { - string fullSetBonus = testPlayer.setBonus; - int fullDefenseBonus = testPlayer.statDefense; - - // This section for testing leg-less sets - testPlayer.legs = -1; - testPlayer.armor[2] = new Item(); - testPlayer.statDefense = Player.DefenseStat.Default; - testPlayer.UpdateArmorSets(255); - int noLegsDefenseBonus = testPlayer.statDefense; - string noLegSetBonus = testPlayer.setBonus; - testPlayer.legs = leg.legSlot; - testPlayer.armor[2] = leg; + internal static void StartBackgroundCalculation() { + if (isCalculating || calculationComplete) + return; + + isCalculating = true; + pendingSets = new ConcurrentBag>(); + cancellationTokenSource = new CancellationTokenSource(); + + calculationTask = Task.Run(() => CalculateArmorSetsAsync(cancellationTokenSource.Token), cancellationTokenSource.Token); + } + + private static void CalculateArmorSetsAsync(CancellationToken token) { + try { + if (cachedHeads == null || cachedBodies == null || cachedLegs == null) { + cachedHeads = new List(); + cachedBodies = new List(); + cachedLegs = new List(); + + for (int type = 1; type < ItemLoader.ItemCount; type++) { + if (token.IsCancellationRequested) + return; + + Item item = new Item(); + item.SetDefaults(type, false); + if (item.type == 0) + continue; + + if (item.headSlot != -1) + cachedHeads.Add(item); + if (item.bodySlot != -1) + cachedBodies.Add(item); + if (item.legSlot != -1) + cachedLegs.Add(item); + } + } + + var seenSets = new HashSet<(int?, int?, int?, string)>(); + var localSets = new List>(); + + var testPlayer = new Player(); + testPlayer.whoAmI = 255; + + foreach (var head in cachedHeads) { + if (token.IsCancellationRequested) + return; + + foreach (var body in cachedBodies) { + if (token.IsCancellationRequested) + return; + + foreach (var leg in cachedLegs) { + if (token.IsCancellationRequested) + return; - // This section for testing head-less sets - testPlayer.head = -1; - testPlayer.armor[0] = new Item(); testPlayer.statDefense = Player.DefenseStat.Default; - testPlayer.UpdateArmorSets(255); - int noHeadDefenseBonus = testPlayer.statDefense; - string noHeadSetBonus = testPlayer.setBonus; testPlayer.head = head.headSlot; + testPlayer.body = body.bodySlot; + testPlayer.legs = leg.legSlot; testPlayer.armor[0] = head; + testPlayer.armor[1] = body; + testPlayer.armor[2] = leg; - // This section for testing body-less sets - testPlayer.body = -1; - testPlayer.armor[1] = new Item(); - testPlayer.statDefense = Player.DefenseStat.Default; + // TODO: Vanity Set calculation somehow? testPlayer.UpdateArmorSets(255); - int noBodyDefenseBonus = testPlayer.statDefense; - string noBodySetBonus = testPlayer.setBonus; - - if (noLegSetBonus != "") { - var tupleToAdd = new Tuple(head, body, null, noLegSetBonus, head.defense + body.defense + noLegsDefenseBonus); - if (!sets.Contains(tupleToAdd)) - sets.Add(tupleToAdd); - } - else if (noHeadSetBonus != "") { - var tupleToAdd = new Tuple(null, body, leg, noHeadSetBonus, body.defense + leg.defense + noHeadDefenseBonus); - if (!sets.Contains(tupleToAdd)) - sets.Add(tupleToAdd); - } - else if (noBodySetBonus != "") { - var tupleToAdd = new Tuple(head, null, leg, noBodySetBonus, head.defense + leg.defense + noBodyDefenseBonus); - if (!sets.Contains(tupleToAdd)) - sets.Add(tupleToAdd); - } - else { - sets.Add(new Tuple(head, body, leg, fullSetBonus, head.defense + body.defense + leg.defense + fullDefenseBonus)); + if (testPlayer.setBonus != "") { + string fullSetBonus = testPlayer.setBonus; + int fullDefenseBonus = testPlayer.statDefense; + + // This section for testing leg-less sets + testPlayer.legs = -1; + testPlayer.armor[2] = new Item(); + testPlayer.statDefense = Player.DefenseStat.Default; + testPlayer.UpdateArmorSets(255); + int noLegsDefenseBonus = testPlayer.statDefense; + string noLegSetBonus = testPlayer.setBonus; + testPlayer.legs = leg.legSlot; + testPlayer.armor[2] = leg; + + // This section for testing head-less sets + testPlayer.head = -1; + testPlayer.armor[0] = new Item(); + testPlayer.statDefense = Player.DefenseStat.Default; + testPlayer.UpdateArmorSets(255); + int noHeadDefenseBonus = testPlayer.statDefense; + string noHeadSetBonus = testPlayer.setBonus; + testPlayer.head = head.headSlot; + testPlayer.armor[0] = head; + + // This section for testing body-less sets + testPlayer.body = -1; + testPlayer.armor[1] = new Item(); + testPlayer.statDefense = Player.DefenseStat.Default; + testPlayer.UpdateArmorSets(255); + int noBodyDefenseBonus = testPlayer.statDefense; + string noBodySetBonus = testPlayer.setBonus; + + if (noLegSetBonus != "") { + var key = (head?.type, body?.type, (int?)null, noLegSetBonus); + if (seenSets.Add(key)) { + var tupleToAdd = new Tuple(head, body, null, noLegSetBonus, head.defense + body.defense + noLegsDefenseBonus); + localSets.Add(tupleToAdd); + } + } + else if (noHeadSetBonus != "") { + var key = ((int?)null, body?.type, leg?.type, noHeadSetBonus); + if (seenSets.Add(key)) { + var tupleToAdd = new Tuple(null, body, leg, noHeadSetBonus, body.defense + leg.defense + noHeadDefenseBonus); + localSets.Add(tupleToAdd); + } + } + else if (noBodySetBonus != "") { + var key = (head?.type, (int?)null, leg?.type, noBodySetBonus); + if (seenSets.Add(key)) { + var tupleToAdd = new Tuple(head, null, leg, noBodySetBonus, head.defense + leg.defense + noBodyDefenseBonus); + localSets.Add(tupleToAdd); + } + } + else { + var key = (head?.type, body?.type, leg?.type, fullSetBonus); + if (seenSets.Add(key)) { + localSets.Add(new Tuple(head, body, leg, fullSetBonus, head.defense + body.defense + leg.defense + fullDefenseBonus)); + } + } } } } } + + if (token.IsCancellationRequested) + return; + + sets = localSets; + calculationComplete = true; + } + finally { + isCalculating = false; + } + } + + internal static void CalculateArmorSets() { + //if already calculated, just build the UI slots + if (calculationComplete && sets != null) { + BuildArmorSetSlots(); + return; + } + + //start calc if not started yet + if (!isCalculating && calculationTask == null) { + StartBackgroundCalculation(); } - // How to detect "anything goes" sets? - // Check Head/Body, Head/Legs, etc? + //wait for background calculation to complete + if (calculationTask != null && !calculationTask.IsCompleted) { + calculationTask.Wait(); + } + + BuildArmorSetSlots(); + } + + private static void BuildArmorSetSlots() { + if (armorSetSlots != null) + return; + armorSetSlots = new List(); - if (armorSetSlots.Count == 0) { + if (sets != null) { foreach (var set in sets) { var slot = new UIArmorSetCatalogueItemSlot(set); armorSetSlots.Add(slot); diff --git a/UISystem.cs b/UISystem.cs index cb7f8d4..68d79e9 100644 --- a/UISystem.cs +++ b/UISystem.cs @@ -28,6 +28,7 @@ public override void PostAddRecipes() if (!Main.dedServ) { LootCacheManager.Setup(RecipeBrowser.instance); RecipeBrowserUI.instance.PostSetupContent(); + ArmorSetFeatureHelper.StartBackgroundCalculation(); } }