diff --git a/Workshoppa/Configuration.cs b/Workshoppa/Configuration.cs index 9913a4c..76971a5 100644 --- a/Workshoppa/Configuration.cs +++ b/Workshoppa/Configuration.cs @@ -1,5 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Dalamud.Configuration; +using Workshoppa.GameData; namespace Workshoppa; @@ -20,6 +23,55 @@ internal sealed class Configuration : IPluginConfiguration { public uint WorkshopItemId { get; set; } public bool StartedCrafting { get; set; } - public bool FinishedCrafting { get; set; } + + public uint PhasesComplete { get; set; } = 0; + public List ContributedItemsInCurrentPhase { get; set; } = new(); + + public bool UpdateFromCraftState(CraftState craftState) + { + bool changed = false; + if (PhasesComplete != craftState.StepsComplete) + { + PhasesComplete = craftState.StepsComplete; + changed = true; + } + + if (ContributedItemsInCurrentPhase.Count != craftState.Items.Count) + { + ContributedItemsInCurrentPhase = craftState.Items.Select(x => new PhaseItem + { + ItemId = x.ItemId, + QuantityComplete = x.QuantityComplete, + }).ToList(); + changed = true; + } + else + { + for (int i = 0; i < ContributedItemsInCurrentPhase.Count; ++i) + { + var contributedItem = ContributedItemsInCurrentPhase[i]; + var craftItem = craftState.Items[i]; + if (contributedItem.ItemId != craftItem.ItemId) + { + contributedItem.ItemId = craftItem.ItemId; + changed = true; + } + + if (contributedItem.QuantityComplete != craftItem.QuantityComplete) + { + contributedItem.QuantityComplete = craftItem.QuantityComplete; + changed = true; + } + } + } + + return changed; + } + } + + internal sealed class PhaseItem + { + public uint ItemId { get; set; } + public uint QuantityComplete { get; set; } } } diff --git a/Workshoppa/GameData/CraftItem.cs b/Workshoppa/GameData/CraftItem.cs index 230f553..ec00330 100644 --- a/Workshoppa/GameData/CraftItem.cs +++ b/Workshoppa/GameData/CraftItem.cs @@ -14,4 +14,6 @@ public class CraftItem public uint StepsTotal { get; set; } public bool Finished { get; set; } public uint CrafterMinimumLevel { get; set; } + + public uint QuantityComplete => StepsComplete * ItemCountPerStep; } diff --git a/Workshoppa/Windows/MainWindow.cs b/Workshoppa/Windows/MainWindow.cs index 09586e5..0003cc6 100644 --- a/Workshoppa/Windows/MainWindow.cs +++ b/Workshoppa/Windows/MainWindow.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; using Dalamud.Interface; @@ -44,7 +45,7 @@ internal sealed class MainWindow : Window Flags = ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoCollapse; } - public bool NearFabricationStation { get; set; } = false; + public bool NearFabricationStation { get; set; } public ButtonState State { get; set; } = ButtonState.None; public bool IsDiscipleOfHand => @@ -60,7 +61,7 @@ internal sealed class MainWindow : Window if (_plugin.CurrentStage == Stage.Stopped) { - if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Search, "Check Material")) + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Search, "Check Inventory")) ImGui.OpenPopup(nameof(CheckMaterial)); ImGui.SameLine(); @@ -109,7 +110,7 @@ internal sealed class MainWindow : Window { ImGui.Text("Currently Crafting: ---"); - if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Search, "Check Material")) + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Search, "Check Inventory")) ImGui.OpenPopup(nameof(CheckMaterial)); ImGui.SameLine(); @@ -200,34 +201,67 @@ internal sealed class MainWindow : Window private unsafe void CheckMaterial() { - if (_configuration.CurrentlyCraftedItem != null) - ImGui.Text("Items needed for all crafts in queue (not including current in-progress craft):"); - else - ImGui.Text("Items needed for all crafts in queue:"); + ImGui.Text("Items needed for all crafts in queue:"); - var items = _configuration.ItemQueue - .SelectMany(x => - Enumerable.Range(0, x.Quantity).Select(_ => - _workshopCache.Crafts.Single(y => y.WorkshopItemId == x.WorkshopItemId))) + List workshopItemIds = _configuration.ItemQueue + .SelectMany(x => Enumerable.Range(0, x.Quantity).Select(_ => x.WorkshopItemId)) + .ToList(); + Dictionary completedForCurrentCraft = new(); + var currentItem = _configuration.CurrentlyCraftedItem; + if (currentItem != null) + { + workshopItemIds.Add(currentItem.WorkshopItemId); + + var craft = _workshopCache.Crafts.Single(x => + x.WorkshopItemId == currentItem.WorkshopItemId); + for (int i = 0; i < currentItem.PhasesComplete; ++i) + { + foreach (var item in craft.Phases[i].Items) + AddMaterial(completedForCurrentCraft, item.ItemId, item.TotalQuantity); + } + + if (currentItem.PhasesComplete < craft.Phases.Count) + { + foreach (var item in currentItem.ContributedItemsInCurrentPhase) + AddMaterial(completedForCurrentCraft, item.ItemId, (int)item.QuantityComplete); + } + } + + var items = workshopItemIds.Select(x => _workshopCache.Crafts.Single(y => y.WorkshopItemId == x)) .SelectMany(x => x.Phases) .SelectMany(x => x.Items) .GroupBy(x => new { x.Name, x.ItemId }) - .OrderBy(x => x.Key.Name); + .OrderBy(x => x.Key.Name) + .Select(x => new + { + x.Key.ItemId, + x.Key.Name, + TotalQuantity = completedForCurrentCraft.TryGetValue(x.Key.ItemId, out var completed) + ? x.Sum(y => y.TotalQuantity) - completed + : x.Sum(y => y.TotalQuantity), + }); ImGui.Indent(20); InventoryManager* inventoryManager = InventoryManager.Instance(); foreach (var item in items) { - int inInventory = inventoryManager->GetInventoryItemCount(item.Key.ItemId, true, false, false) + - inventoryManager->GetInventoryItemCount(item.Key.ItemId, false, false, false); - int required = item.Sum(x => x.TotalQuantity); - ImGui.TextColored(inInventory >= required ? ImGuiColors.HealerGreen : ImGuiColors.DalamudRed, - $"{item.Key.Name} ({inInventory} / {required})"); + int inInventory = inventoryManager->GetInventoryItemCount(item.ItemId, true, false, false) + + inventoryManager->GetInventoryItemCount(item.ItemId, false, false, false); + ImGui.TextColored(inInventory >= item.TotalQuantity ? ImGuiColors.HealerGreen : ImGuiColors.DalamudRed, + $"{item.Name} ({inInventory} / {item.TotalQuantity})"); } ImGui.Unindent(20); } + private void AddMaterial(Dictionary completedForCurrentCraft, uint itemId, int quantity) + { + if (completedForCurrentCraft.TryGetValue(itemId, out var existingQuantity)) + completedForCurrentCraft[itemId] = quantity + existingQuantity; + else + completedForCurrentCraft[itemId] = quantity; + } + public enum ButtonState { None, diff --git a/Workshoppa/WorkshopPlugin.Craft.cs b/Workshoppa/WorkshopPlugin.Craft.cs index 8a6f5cd..6c7a573 100644 --- a/Workshoppa/WorkshopPlugin.Craft.cs +++ b/Workshoppa/WorkshopPlugin.Craft.cs @@ -10,6 +10,41 @@ partial class WorkshopPlugin { private uint? _contributingItemId; + /// + /// Check if delivery window is open when we clicked resume. + /// + private unsafe bool CheckContinueWithDelivery() + { + if (_configuration.CurrentlyCraftedItem != null) + { + AtkUnitBase* addonMaterialDelivery = GetMaterialDeliveryAddon(); + if (addonMaterialDelivery == null) + return false; + + _pluginLog.Warning("Material delivery window is open, although unexpected... checking current craft"); + CraftState? craftState = ReadCraftState(addonMaterialDelivery); + if (craftState == null || craftState.ResultItem == 0) + { + _pluginLog.Error("Unable to read craft state"); + _continueAt = DateTime.Now.AddSeconds(1); + return false; + } + + var craft = _workshopCache.Crafts.SingleOrDefault(x => x.ResultItem == craftState.ResultItem); + if (craft == null || craft.WorkshopItemId != _configuration.CurrentlyCraftedItem.WorkshopItemId) + { + _pluginLog.Error("Unable to match currently crafted item with game state"); + _continueAt = DateTime.Now.AddSeconds(1); + return false; + } + + _pluginLog.Information("Delivering materials for current active craft, switching to delivery"); + return true; + } + + return false; + } + private void SelectCraftBranch() { if (SelectSelectString("contrib", 0, s => s.StartsWith("Contribute materials."))) @@ -20,6 +55,11 @@ partial class WorkshopPlugin else if (SelectSelectString("advance", 0, s => s.StartsWith("Advance to the next phase of production."))) { _pluginLog.Information("Phase is complete"); + + _configuration.CurrentlyCraftedItem!.PhasesComplete++; + _configuration.CurrentlyCraftedItem!.ContributedItemsInCurrentPhase = new(); + _pluginInterface.SavePluginConfig(_configuration); + CurrentStage = Stage.TargetFabricationStation; _continueAt = DateTime.Now.AddSeconds(3); } @@ -51,6 +91,12 @@ partial class WorkshopPlugin return; } + if (_configuration.CurrentlyCraftedItem!.UpdateFromCraftState(craftState)) + { + _pluginLog.Information("Saving updated current craft information"); + _pluginInterface.SavePluginConfig(_configuration); + } + for (int i = 0; i < craftState.Items.Count; ++i) { var item = craftState.Items[i]; @@ -59,7 +105,8 @@ partial class WorkshopPlugin if (!HasItemInSingleSlot(item.ItemId, item.ItemCountPerStep)) { - _pluginLog.Error($"Can't contribute item {item.ItemId} to craft, couldn't find {item.ItemCountPerStep}x in a single inventory slot"); + _pluginLog.Error( + $"Can't contribute item {item.ItemId} to craft, couldn't find {item.ItemCountPerStep}x in a single inventory slot"); CurrentStage = Stage.RequestStop; break; } @@ -113,6 +160,11 @@ partial class WorkshopPlugin } else { + _configuration.CurrentlyCraftedItem!.ContributedItemsInCurrentPhase + .Single(x => x.ItemId == item.ItemId) + .QuantityComplete = item.QuantityComplete; + _pluginInterface.SavePluginConfig(_configuration); + CurrentStage = Stage.ContributeMaterials; _continueAt = DateTime.Now.AddSeconds(1); } diff --git a/Workshoppa/WorkshopPlugin.cs b/Workshoppa/WorkshopPlugin.cs index 4f00ed4..b60a836 100644 --- a/Workshoppa/WorkshopPlugin.cs +++ b/Workshoppa/WorkshopPlugin.cs @@ -107,6 +107,7 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin } else if (_mainWindow.State is MainWindow.ButtonState.Start or MainWindow.ButtonState.Resume && CurrentStage == Stage.Stopped) { + // TODO Error checking, we should ensure the player has the required job level for *all* crafting parts _mainWindow.State = MainWindow.ButtonState.None; CurrentStage = Stage.TakeItemFromQueue; } @@ -117,7 +118,10 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin switch (CurrentStage) { case Stage.TakeItemFromQueue: - TakeItemFromQueue(); + if (CheckContinueWithDelivery()) + CurrentStage = Stage.ContributeMaterials; + else + TakeItemFromQueue(); break; case Stage.TargetFabricationStation: