diff --git a/LLib b/LLib index e59d291..7649b0d 160000 --- a/LLib +++ b/LLib @@ -1 +1 @@ -Subproject commit e59d291f04473eae0b76712397733e2e25349953 +Subproject commit 7649b0d51b35c993839b918805718f046f06ae9b diff --git a/Workshoppa/External/DalamudReflector.cs b/Workshoppa/External/DalamudReflector.cs deleted file mode 100644 index cc0f360..0000000 --- a/Workshoppa/External/DalamudReflector.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Dalamud.Plugin; -using System; -using System.Collections.Generic; -using System.Reflection; -using Dalamud.Plugin.Services; - -namespace Workshoppa.External; - -/// -/// Originally part of ECommons by NightmareXIV. -/// -/// https://github.com/NightmareXIV/ECommons/blob/master/ECommons/Reflection/DalamudReflector.cs -/// -internal sealed class DalamudReflector : IDisposable -{ - private readonly DalamudPluginInterface _pluginInterface; - private readonly IFramework _framework; - private readonly IPluginLog _pluginLog; - private readonly Dictionary _pluginCache = new(); - private bool _pluginsChanged = false; - - public DalamudReflector(DalamudPluginInterface pluginInterface, IFramework framework, IPluginLog pluginLog) - { - _pluginInterface = pluginInterface; - _framework = framework; - _pluginLog = pluginLog; - var pm = GetPluginManager(); - pm.GetType().GetEvent("OnInstalledPluginsChanged")!.AddEventHandler(pm, OnInstalledPluginsChanged); - - _framework.Update += FrameworkUpdate; - } - - public void Dispose() - { - _framework.Update -= FrameworkUpdate; - - var pm = GetPluginManager(); - pm.GetType().GetEvent("OnInstalledPluginsChanged")!.RemoveEventHandler(pm, OnInstalledPluginsChanged); - } - - private void FrameworkUpdate(IFramework framework) - { - if (_pluginsChanged) - { - _pluginsChanged = false; - _pluginCache.Clear(); - } - } - - private object GetPluginManager() - { - return _pluginInterface.GetType().Assembly.GetType("Dalamud.Service`1", true)! - .MakeGenericType( - _pluginInterface.GetType().Assembly.GetType("Dalamud.Plugin.Internal.PluginManager", true)!) - .GetMethod("Get")!.Invoke(null, BindingFlags.Default, null, Array.Empty(), null)!; - } - - public bool TryGetDalamudPlugin(string internalName, out IDalamudPlugin? instance, bool suppressErrors = false, - bool ignoreCache = false) - { - if (!ignoreCache && _pluginCache.TryGetValue(internalName, out instance)) - { - return true; - } - - try - { - var pluginManager = GetPluginManager(); - var installedPlugins = - (System.Collections.IList)pluginManager.GetType().GetProperty("InstalledPlugins")!.GetValue( - pluginManager)!; - - foreach (var t in installedPlugins) - { - if ((string?)t.GetType().GetProperty("Name")!.GetValue(t) == internalName) - { - var type = t.GetType().Name == "LocalDevPlugin" ? t.GetType().BaseType : t.GetType(); - var plugin = (IDalamudPlugin?)type! - .GetField("instance", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(t); - if (plugin == null) - { - _pluginLog.Warning($"[DalamudReflector] Found requested plugin {internalName} but it was null"); - } - else - { - instance = plugin; - _pluginCache[internalName] = plugin; - return true; - } - } - } - - instance = null; - return false; - } - catch (Exception e) - { - if (!suppressErrors) - { - _pluginLog.Error(e, $"Can't find {internalName} plugin: {e.Message}"); - } - - instance = null; - return false; - } - } - - private void OnInstalledPluginsChanged() - { - _pluginLog.Verbose("Installed plugins changed event fired"); - _pluginsChanged = true; - } -} diff --git a/Workshoppa/External/ExternalPluginHandler.cs b/Workshoppa/External/ExternalPluginHandler.cs new file mode 100644 index 0000000..b77db50 --- /dev/null +++ b/Workshoppa/External/ExternalPluginHandler.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using Dalamud.Plugin; +using Dalamud.Plugin.Services; +using LLib; + +namespace Workshoppa.External; + +internal sealed class ExternalPluginHandler +{ + private readonly DalamudPluginInterface _pluginInterface; + private readonly IPluginLog _pluginLog; + private readonly YesAlreadyIpc _yesAlreadyIpc; + private readonly PandoraIpc _pandoraIpc; + + private bool? _yesAlreadyState; + private bool? _pandoraState; + + public ExternalPluginHandler(DalamudPluginInterface pluginInterface, IFramework framework, IPluginLog pluginLog) + { + _pluginInterface = pluginInterface; + _pluginLog = pluginLog; + + var dalamudReflector = new DalamudReflector(pluginInterface, framework, pluginLog); + _yesAlreadyIpc = new YesAlreadyIpc(dalamudReflector); + _pandoraIpc = new PandoraIpc(pluginInterface, pluginLog); + } + + public bool Saved { get; private set; } + + public void Save() + { + if (Saved) + { + _pluginLog.Information("Not overwriting external plugin state"); + return; + } + + _pluginLog.Information("Saving external plugin state..."); + SaveYesAlreadyState(); + SavePandoraState(); + Saved = true; + } + + private void SaveYesAlreadyState() + { + _yesAlreadyState = _yesAlreadyIpc.DisableIfNecessary(); + _pluginLog.Information($"Previous yesalready state: {_yesAlreadyState}"); + } + + private void SavePandoraState() + { + _pandoraState = _pandoraIpc.DisableIfNecessary(); + _pluginLog.Information($"Previous pandora feature state: {_pandoraState}"); + } + + /// + /// Unlike Pandora/YesAlready, we only disable TextAdvance during the item turn-in so that the cutscene skip + /// still works (if enabled). + /// + public void SaveTextAdvance() + { + if (_pluginInterface.TryGetData>("TextAdvance.StopRequests", out var data) && + !data.Contains(nameof(Workshoppa))) + { + _pluginLog.Debug("Disabling textadvance"); + data.Add(nameof(Workshoppa)); + } + } + + public void Restore() + { + if (Saved) + { + RestoreYesAlready(); + RestorePandora(); + } + + Saved = false; + _yesAlreadyState = null; + _pandoraState = null; + } + + private void RestoreYesAlready() + { + _pluginLog.Information($"Restoring previous yesalready state: {_yesAlreadyState}"); + if (_yesAlreadyState == true) + _yesAlreadyIpc.Enable(); + } + + private void RestorePandora() + { + _pluginLog.Information($"Restoring previous pandora state: {_pandoraState}"); + if (_pandoraState == true) + _pandoraIpc.Enable(); + } + + public void RestoreTextAdvance() + { + if (_pluginInterface.TryGetData>("TextAdvance.StopRequests", out var data) && + data.Contains(nameof(Workshoppa))) + { + _pluginLog.Debug("Restoring textadvance"); + data.Remove(nameof(Workshoppa)); + } + } +} diff --git a/Workshoppa/External/PandoraIpc.cs b/Workshoppa/External/PandoraIpc.cs new file mode 100644 index 0000000..912bcef --- /dev/null +++ b/Workshoppa/External/PandoraIpc.cs @@ -0,0 +1,52 @@ +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using Dalamud.Plugin.Ipc.Exceptions; +using Dalamud.Plugin.Services; + +namespace Workshoppa.External; + +internal sealed class PandoraIpc +{ + private const string AutoTurnInFeature = "Auto-select Turn-ins"; + + private readonly IPluginLog _pluginLog; + private readonly ICallGateSubscriber _getEnabled; + private readonly ICallGateSubscriber _setEnabled; + + public PandoraIpc(DalamudPluginInterface pluginInterface, IPluginLog pluginLog) + { + _pluginLog = pluginLog; + _getEnabled = pluginInterface.GetIpcSubscriber("PandorasBox.GetFeatureEnabled"); + _setEnabled = pluginInterface.GetIpcSubscriber("PandorasBox.SetFeatureEnabled"); + } + + public bool? DisableIfNecessary() + { + try + { + bool? enabled = _getEnabled.InvokeFunc(AutoTurnInFeature); + _pluginLog.Information($"Pandora's {AutoTurnInFeature} is {enabled?.ToString() ?? "null"}"); + if (enabled == true) + _setEnabled.InvokeAction(AutoTurnInFeature, false); + + return enabled; + } + catch (IpcNotReadyError e) + { + _pluginLog.Information(e, "Unable to read pandora state"); + return null; + } + } + + public void Enable() + { + try + { + _setEnabled.InvokeAction(AutoTurnInFeature, true); + } + catch (IpcNotReadyError e) + { + _pluginLog.Error(e, "Unable to restore pandora state"); + } + } +} diff --git a/Workshoppa/External/YesAlreadyIpc.cs b/Workshoppa/External/YesAlreadyIpc.cs index a070899..259ba57 100644 --- a/Workshoppa/External/YesAlreadyIpc.cs +++ b/Workshoppa/External/YesAlreadyIpc.cs @@ -1,4 +1,5 @@ using System.Reflection; +using LLib; namespace Workshoppa.External; diff --git a/Workshoppa/GameData/GameStrings.cs b/Workshoppa/GameData/GameStrings.cs index b91f656..8209221 100644 --- a/Workshoppa/GameData/GameStrings.cs +++ b/Workshoppa/GameData/GameStrings.cs @@ -2,6 +2,8 @@ using System.Text.RegularExpressions; using Dalamud.Plugin.Services; using LLib; +using Lumina.Excel; +using Lumina.Excel.CustomSheets; using Lumina.Excel.GeneratedSheets; namespace Workshoppa.GameData; @@ -11,8 +13,27 @@ internal sealed class GameStrings public GameStrings(IDataManager dataManager, IPluginLog pluginLog) { PurchaseItem = dataManager.GetRegex(3406, addon => addon.Text, pluginLog) - ?? throw new Exception($"Unable to resolve {nameof(PurchaseItem)}"); + ?? throw new Exception($"Unable to resolve {nameof(PurchaseItem)}"); + ViewCraftingLog = + dataManager.GetString("TEXT_CMNDEFCOMPANYMANUFACTORY_00150_MENU_CC_NOTE", + pluginLog) ?? throw new Exception($"Unable to resolve {nameof(ViewCraftingLog)}"); + TurnInHighQualityItem = dataManager.GetString(102434, addon => addon.Text, pluginLog) + ?? throw new Exception($"Unable to resolve {nameof(TurnInHighQualityItem)}"); + ContributeItems = dataManager.GetRegex(6652, addon => addon.Text, pluginLog) + ?? throw new Exception($"Unable to resolve {nameof(ContributeItems)}"); + RetrieveFinishedItem = + dataManager.GetRegex("TEXT_CMNDEFCOMPANYMANUFACTORY_00150_FINISH_CONF", pluginLog) + ?? throw new Exception($"Unable to resolve {nameof(RetrieveFinishedItem)}"); } public Regex PurchaseItem { get; } + public string ViewCraftingLog { get; } + public string TurnInHighQualityItem { get; } + public Regex ContributeItems { get; } + public Regex RetrieveFinishedItem { get; } + + [Sheet("custom/001/CmnDefCompanyManufactory_00150")] + private class WorkshopDialogue : QuestDialogueText + { + } } diff --git a/Workshoppa/Stage.cs b/Workshoppa/Stage.cs index 1dcb03b..022e09c 100644 --- a/Workshoppa/Stage.cs +++ b/Workshoppa/Stage.cs @@ -12,6 +12,9 @@ public enum Stage SelectCraftBranch, ContributeMaterials, + OpenRequestItemWindow, + OpenRequestItemSelect, + ConfirmRequestItemWindow, ConfirmMaterialDelivery, ConfirmCollectProduct, diff --git a/Workshoppa/Windows/MainWindow.cs b/Workshoppa/Windows/MainWindow.cs index e892830..ac63e3c 100644 --- a/Workshoppa/Windows/MainWindow.cs +++ b/Workshoppa/Windows/MainWindow.cs @@ -15,6 +15,7 @@ using Workshoppa.GameData; namespace Workshoppa.Windows; +// FIXME The close button doesn't work near the workshop, either hide it or make it work internal sealed class MainWindow : Window { private readonly WorkshopPlugin _plugin; @@ -24,6 +25,7 @@ internal sealed class MainWindow : Window private readonly WorkshopCache _workshopCache; private string _searchString = string.Empty; + private bool _checkInventory; public MainWindow(WorkshopPlugin plugin, DalamudPluginInterface pluginInterface, IClientState clientState, Configuration configuration, WorkshopCache workshopCache) : base("Workshoppa###WorkshoppaMainWindow") @@ -66,7 +68,7 @@ internal sealed class MainWindow : Window if (_plugin.CurrentStage == Stage.Stopped) { if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Search, "Check Inventory")) - ImGui.OpenPopup(nameof(CheckMaterial)); + _checkInventory = !_checkInventory; ImGui.SameLine(); ImGui.BeginDisabled(!NearFabricationStation); @@ -74,12 +76,18 @@ internal sealed class MainWindow : Window if (currentItem.StartedCrafting) { if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Play, "Resume")) + { State = ButtonState.Resume; + _checkInventory = false; + } } else { if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Play, "Start Crafting")) + { State = ButtonState.Start; + _checkInventory = false; + } } ImGui.EndDisabled(); @@ -114,21 +122,25 @@ internal sealed class MainWindow : Window ImGui.Text("Currently Crafting: ---"); if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Search, "Check Inventory")) - ImGui.OpenPopup(nameof(CheckMaterial)); + _checkInventory = !_checkInventory; ImGui.SameLine(); ImGui.BeginDisabled(!NearFabricationStation || _configuration.ItemQueue.Sum(x => x.Quantity) == 0 || _plugin.CurrentStage != Stage.Stopped || !IsDiscipleOfHand); if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Play, "Start Crafting")) + { State = ButtonState.Start; + _checkInventory = false; + } + ImGui.EndDisabled(); ShowErrorConditions(); } - if (ImGui.BeginPopup(nameof(CheckMaterial))) + if (_checkInventory) { + ImGui.Separator(); CheckMaterial(); - ImGui.EndPopup(); } ImGui.Separator(); diff --git a/Workshoppa/Windows/RepairKitWindow.cs b/Workshoppa/Windows/RepairKitWindow.cs index 8f8036a..ca5a141 100644 --- a/Workshoppa/Windows/RepairKitWindow.cs +++ b/Workshoppa/Windows/RepairKitWindow.cs @@ -15,6 +15,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; using LLib; using LLib.GameUI; +using Workshoppa.External; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; namespace Workshoppa.Windows; @@ -29,11 +30,14 @@ internal sealed class RepairKitWindow : Window, IDisposable private readonly IGameGui _gameGui; private readonly IAddonLifecycle _addonLifecycle; private readonly Configuration _configuration; + private readonly ExternalPluginHandler _externalPluginHandler; private ItemForSale? _itemForSale; private PurchaseState? _purchaseState; - public RepairKitWindow(WorkshopPlugin plugin, DalamudPluginInterface pluginInterface, IPluginLog pluginLog, IGameGui gameGui, IAddonLifecycle addonLifecycle, Configuration configuration) + public RepairKitWindow(WorkshopPlugin plugin, DalamudPluginInterface pluginInterface, IPluginLog pluginLog, + IGameGui gameGui, IAddonLifecycle addonLifecycle, Configuration configuration, + ExternalPluginHandler externalPluginHandler) : base("Repair Kits###WorkshoppaRepairKitWindow") { _plugin = plugin; @@ -42,6 +46,7 @@ internal sealed class RepairKitWindow : Window, IDisposable _gameGui = gameGui; _addonLifecycle = addonLifecycle; _configuration = configuration; + _externalPluginHandler = externalPluginHandler; Position = new Vector2(100, 100); PositionCondition = ImGuiCond.Always; @@ -77,7 +82,7 @@ internal sealed class RepairKitWindow : Window, IDisposable private void ShopPreFinalize(AddonEvent type, AddonArgs args) { _purchaseState = null; - _plugin.RestoreYesAlready(); + _externalPluginHandler.Restore(); IsOpen = false; } @@ -200,7 +205,8 @@ internal sealed class RepairKitWindow : Window, IDisposable ImGui.Unindent(); int missingItems = Math.Max(0, darkMatterClusters * 5 - (int)_itemForSale.OwnedItems); - ImGui.TextColored(missingItems == 0 ? ImGuiColors.HealerGreen : ImGuiColors.DalamudRed, $"Missing Grade 6 Dark Matter: {missingItems:N0}"); + ImGui.TextColored(missingItems == 0 ? ImGuiColors.HealerGreen : ImGuiColors.DalamudRed, + $"Missing Grade 6 Dark Matter: {missingItems:N0}"); if (_purchaseState != null) { @@ -209,7 +215,7 @@ internal sealed class RepairKitWindow : Window, IDisposable if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Times, "Cancel Auto-Buy")) { _purchaseState = null; - _plugin.RestoreYesAlready(); + _externalPluginHandler.Restore(); } } else @@ -217,10 +223,11 @@ internal sealed class RepairKitWindow : Window, IDisposable int toPurchase = Math.Min(GetMaxItemsToPurchase(), missingItems); if (toPurchase > 0) { - if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.DollarSign, $"Auto-Buy missing Dark Matter for {_itemForSale.Price * toPurchase:N0}{SeIconChar.Gil.ToIconString()}")) + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.DollarSign, + $"Auto-Buy missing Dark Matter for {_itemForSale.Price * toPurchase:N0}{SeIconChar.Gil.ToIconString()}")) { _purchaseState = new((int)_itemForSale.OwnedItems + toPurchase, (int)_itemForSale.OwnedItems); - _plugin.SaveYesAlready(); + _externalPluginHandler.Save(); HandleNextPurchaseStep(); } @@ -237,11 +244,12 @@ internal sealed class RepairKitWindow : Window, IDisposable { _pluginLog.Warning($"No free inventory slots, can't buy more {_itemForSale.ItemName}"); _purchaseState = null; - _plugin.RestoreYesAlready(); + _externalPluginHandler.Restore(); } else if (!_purchaseState.IsComplete) { - if (_purchaseState.NextStep <= DateTime.Now && _gameGui.TryGetAddonByName("Shop", out AtkUnitBase* addonShop)) + if (_purchaseState.NextStep <= DateTime.Now && + _gameGui.TryGetAddonByName("Shop", out AtkUnitBase* addonShop)) { int buyNow = Math.Min(_purchaseState.ItemsLeftToBuy, 99); _pluginLog.Information($"Buying {buyNow}x {_itemForSale.ItemName}"); @@ -261,9 +269,10 @@ internal sealed class RepairKitWindow : Window, IDisposable } else { - _pluginLog.Information($"Stopping item purchase (desired = {_purchaseState.DesiredItems}, owned = {_purchaseState.OwnedItems})"); + _pluginLog.Information( + $"Stopping item purchase (desired = {_purchaseState.DesiredItems}, owned = {_purchaseState.OwnedItems})"); _purchaseState = null; - _plugin.RestoreYesAlready(); + _externalPluginHandler.Restore(); } } diff --git a/Workshoppa/WorkshopPlugin.Craft.cs b/Workshoppa/WorkshopPlugin.Craft.cs index 6c7a573..69677fb 100644 --- a/Workshoppa/WorkshopPlugin.Craft.cs +++ b/Workshoppa/WorkshopPlugin.Craft.cs @@ -1,5 +1,8 @@ using System; using System.Linq; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; using Workshoppa.GameData; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; @@ -111,6 +114,8 @@ partial class WorkshopPlugin break; } + _externalPluginHandler.SaveTextAdvance(); + _pluginLog.Information($"Contributing {item.ItemCountPerStep}x {item.ItemName}"); _contributingItemId = item.ItemId; var contributeMaterial = stackalloc AtkValue[] @@ -121,13 +126,75 @@ partial class WorkshopPlugin new() { Type = 0, Int = 0 } }; addonMaterialDelivery->FireCallback(4, contributeMaterial); - CurrentStage = Stage.ConfirmMaterialDelivery; - _continueAt = DateTime.Now.AddSeconds(0.5); + _fallbackAt = DateTime.Now.AddSeconds(0.2); + CurrentStage = Stage.OpenRequestItemWindow; break; } } - private unsafe void ConfirmMaterialDelivery() + private unsafe void RequestPostSetup(AddonEvent type, AddonArgs addon) + { + var addonRequest = (AddonRequest*)addon.Addon; + _pluginLog.Verbose($"{nameof(RequestPostSetup)}: {CurrentStage}, {addonRequest->EntryCount}"); + if (CurrentStage != Stage.OpenRequestItemWindow) + return; + + if (addonRequest->EntryCount != 1) + return; + + _fallbackAt = DateTime.MaxValue; + CurrentStage = Stage.OpenRequestItemSelect; + var contributeMaterial = stackalloc AtkValue[] + { + new() { Type = ValueType.Int, Int = 2 }, + new() { Type = ValueType.UInt, Int = 0 }, + new() { Type = ValueType.UInt, UInt = 44 }, + new() { Type = ValueType.UInt, UInt = 0 } + }; + addonRequest->AtkUnitBase.FireCallback(4, contributeMaterial); + } + + private unsafe void ContextIconMenuPostReceiveEvent(AddonEvent type, AddonArgs addon) + { + if (CurrentStage != Stage.OpenRequestItemSelect) + return; + + CurrentStage = Stage.ConfirmRequestItemWindow; + var selectSlot = stackalloc AtkValue[] + { + new() { Type = ValueType.Int, Int = 0 }, + new() { Type = ValueType.Int, Int = 0 /* slot */ }, + new() { Type = ValueType.UInt, UInt = 20802 /* probably the item's icon */ }, + new() { Type = ValueType.UInt, UInt = 0 }, + new() { Type = 0, Int = 0 }, + }; + ((AddonContextIconMenu*)addon.Addon)->AtkUnitBase.FireCallback(5, selectSlot); + } + + private unsafe void RequestPostRefresh(AddonEvent type, AddonArgs addon) + { + _pluginLog.Verbose($"{nameof(RequestPostRefresh)}: {CurrentStage}"); + if (CurrentStage != Stage.ConfirmRequestItemWindow) + return; + + var addonRequest = (AddonRequest*)addon.Addon; + if (addonRequest->EntryCount != 1) + return; + + CurrentStage = Stage.ConfirmMaterialDelivery; + var closeWindow = stackalloc AtkValue[] + { + new() { Type = ValueType.Int, Int = 0 }, + new() { Type = ValueType.UInt, UInt = 0 }, + new() { Type = ValueType.UInt, UInt = 0 }, + new() { Type = ValueType.UInt, UInt = 0 } + }; + addonRequest->AtkUnitBase.FireCallback(4, closeWindow); + addonRequest->AtkUnitBase.Close(false); + _externalPluginHandler.RestoreTextAdvance(); + } + + private unsafe void ConfirmMaterialDeliveryFollowUp() { AtkUnitBase* addonMaterialDelivery = GetMaterialDeliveryAddon(); if (addonMaterialDelivery == null) @@ -141,50 +208,22 @@ partial class WorkshopPlugin return; } - if (SelectSelectYesno(0, s => s == "Do you really want to trade a high-quality item?")) + var item = craftState.Items.Single(x => x.ItemId == _contributingItemId); + item.StepsComplete++; + if (craftState.IsPhaseComplete()) { - _pluginLog.Information("Confirming HQ item turn in"); - CurrentStage = Stage.ConfirmMaterialDelivery; - _continueAt = DateTime.Now.AddSeconds(0.1); - return; + CurrentStage = Stage.TargetFabricationStation; + _continueAt = DateTime.Now.AddSeconds(0.5); } - - if (SelectSelectYesno(0, s => s.StartsWith("Contribute") && s.EndsWith("to the company project?"))) + else { - var item = craftState.Items.Single(x => x.ItemId == _contributingItemId); - item.StepsComplete++; - if (craftState.IsPhaseComplete()) - { - CurrentStage = Stage.TargetFabricationStation; - _continueAt = DateTime.Now.AddSeconds(0.5); - } - else - { - _configuration.CurrentlyCraftedItem!.ContributedItemsInCurrentPhase - .Single(x => x.ItemId == item.ItemId) - .QuantityComplete = item.QuantityComplete; - _pluginInterface.SavePluginConfig(_configuration); - - CurrentStage = Stage.ContributeMaterials; - _continueAt = DateTime.Now.AddSeconds(1); - } - } - else if (DateTime.Now > _continueAt.AddSeconds(20)) - { - _pluginLog.Warning("No confirmation dialog, falling back to previous stage"); - CurrentStage = Stage.ContributeMaterials; - } - } - - private void ConfirmCollectProduct() - { - if (SelectSelectYesno(0, s => s.StartsWith("Retrieve"))) - { - _configuration.CurrentlyCraftedItem = null; + _configuration.CurrentlyCraftedItem!.ContributedItemsInCurrentPhase + .Single(x => x.ItemId == item.ItemId) + .QuantityComplete = item.QuantityComplete; _pluginInterface.SavePluginConfig(_configuration); - CurrentStage = Stage.TakeItemFromQueue; - _continueAt = DateTime.Now.AddSeconds(0.5); + CurrentStage = Stage.ContributeMaterials; + _continueAt = DateTime.Now.AddSeconds(1); } } } diff --git a/Workshoppa/WorkshopPlugin.CraftingLog.cs b/Workshoppa/WorkshopPlugin.CraftingLog.cs index 35a0e37..67b59ff 100644 --- a/Workshoppa/WorkshopPlugin.CraftingLog.cs +++ b/Workshoppa/WorkshopPlugin.CraftingLog.cs @@ -9,11 +9,8 @@ namespace Workshoppa; partial class WorkshopPlugin { - private bool InteractWithFabricationStation(GameObject fabricationStation) - { - InteractWithTarget(fabricationStation); - return true; - } + private void InteractWithFabricationStation(GameObject fabricationStation) + => InteractWithTarget(fabricationStation); private void TakeItemFromQueue() { @@ -48,12 +45,6 @@ partial class WorkshopPlugin CurrentStage = Stage.TargetFabricationStation; } - private void OpenCraftingLog() - { - if (SelectSelectString("craftlog", 0, s => s == "View company crafting log.")) - CurrentStage = Stage.SelectCraftCategory; - } - private unsafe void SelectCraftCategory() { AtkUnitBase* addonCraftingLog = GetCompanyCraftingLogAddon(); diff --git a/Workshoppa/WorkshopPlugin.SelectString.cs b/Workshoppa/WorkshopPlugin.SelectString.cs new file mode 100644 index 0000000..462521f --- /dev/null +++ b/Workshoppa/WorkshopPlugin.SelectString.cs @@ -0,0 +1,63 @@ +using System; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Memory; +using FFXIVClientStructs.FFXIV.Client.UI; + +namespace Workshoppa; + +partial class WorkshopPlugin +{ + private unsafe void SelectStringPostSetup(AddonEvent type, AddonArgs args) + { + _pluginLog.Verbose("SelectString post-setup"); + + string desiredText; + Action followUp; + if (CurrentStage == Stage.OpenCraftingLog) + { + desiredText = _gameStrings.ViewCraftingLog; + followUp = OpenCraftingLogFollowUp; + } + else + return; + + _pluginLog.Verbose($"Looking for '{desiredText}' in prompt"); + AddonSelectString* addonSelectString = (AddonSelectString*)args.Addon; + int entries = addonSelectString->PopupMenu.PopupMenu.EntryCount; + + for (int i = 0; i < entries; ++i) + { + var textPointer = addonSelectString->PopupMenu.PopupMenu.EntryNames[i]; + if (textPointer == null) + continue; + + var text = MemoryHelper.ReadSeStringNullTerminated((nint)textPointer).ToString(); + _pluginLog.Verbose($" Choice {i} → {text}"); + if (text == desiredText) + { + _pluginLog.Information($"Selecting choice {i} ({text})"); + addonSelectString->AtkUnitBase.FireCallbackInt(i); + + followUp(); + return; + } + } + + _pluginLog.Verbose($"Text '{desiredText}' was not found in prompt."); + } + + private void OpenCraftingLogFollowUp() + { + CurrentStage = Stage.SelectCraftCategory; + } + + private void ConfirmCollectProductFollowUp() + { + _configuration.CurrentlyCraftedItem = null; + _pluginInterface.SavePluginConfig(_configuration); + + CurrentStage = Stage.TakeItemFromQueue; + _continueAt = DateTime.Now.AddSeconds(0.5); + } +} diff --git a/Workshoppa/WorkshopPlugin.SelectYesNo.cs b/Workshoppa/WorkshopPlugin.SelectYesNo.cs index 6eb7eb6..c12a420 100644 --- a/Workshoppa/WorkshopPlugin.SelectYesNo.cs +++ b/Workshoppa/WorkshopPlugin.SelectYesNo.cs @@ -30,9 +30,27 @@ partial class WorkshopPlugin _pluginLog.Verbose("Not a purchase confirmation match"); } } - else if (_mainWindow.IsOpen) + else if (CurrentStage != Stage.Stopped) { - // TODO + if (CurrentStage == Stage.ConfirmMaterialDelivery && _gameStrings.TurnInHighQualityItem == text) + { + _pluginLog.Information($"Selecting 'yes' ({text})"); + addonSelectYesNo->AtkUnitBase.FireCallbackInt(0); + } + else if (CurrentStage == Stage.ConfirmMaterialDelivery && _gameStrings.ContributeItems.IsMatch(text)) + { + _pluginLog.Information($"Selecting 'yes' ({text})"); + addonSelectYesNo->AtkUnitBase.FireCallbackInt(0); + + ConfirmMaterialDeliveryFollowUp(); + } + else if (CurrentStage == Stage.ConfirmCollectProduct && _gameStrings.RetrieveFinishedItem.IsMatch(text)) + { + _pluginLog.Information($"Selecting 'yes' ({text})"); + addonSelectYesNo->AtkUnitBase.FireCallbackInt(0); + + ConfirmCollectProductFollowUp(); + } } } } diff --git a/Workshoppa/WorkshopPlugin.cs b/Workshoppa/WorkshopPlugin.cs index dbd7997..340c913 100644 --- a/Workshoppa/WorkshopPlugin.cs +++ b/Workshoppa/WorkshopPlugin.cs @@ -8,6 +8,7 @@ using Dalamud.Game.Command; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Dalamud.Plugin.Services; +using LLib; using Workshoppa.External; using Workshoppa.GameData; using Workshoppa.Windows; @@ -32,7 +33,7 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin private readonly IAddonLifecycle _addonLifecycle; private readonly Configuration _configuration; - private readonly YesAlreadyIpc _yesAlreadyIpc; + private readonly ExternalPluginHandler _externalPluginHandler; private readonly WorkshopCache _workshopCache; private readonly GameStrings _gameStrings; @@ -42,7 +43,7 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin private Stage _currentStageInternal = Stage.Stopped; private DateTime _continueAt = DateTime.MinValue; - private (bool Saved, bool? PreviousState) _yesAlreadyState = (false, null); + private DateTime _fallbackAt = DateTime.MaxValue; public WorkshopPlugin(DalamudPluginInterface pluginInterface, IGameGui gameGui, IFramework framework, ICondition condition, IClientState clientState, IObjectTable objectTable, IDataManager dataManager, @@ -58,8 +59,7 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin _pluginLog = pluginLog; _addonLifecycle = addonLifecycle; - var dalamudReflector = new DalamudReflector(_pluginInterface, _framework, _pluginLog); - _yesAlreadyIpc = new YesAlreadyIpc(dalamudReflector); + _externalPluginHandler = new ExternalPluginHandler(_pluginInterface, _framework, _pluginLog); _configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration(); _workshopCache = new WorkshopCache(dataManager, _pluginLog); _gameStrings = new(dataManager, _pluginLog); @@ -68,7 +68,7 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin _windowSystem.AddWindow(_mainWindow); _configWindow = new(_pluginInterface, _configuration); _windowSystem.AddWindow(_configWindow); - _repairKitWindow = new(this, _pluginInterface, _pluginLog, _gameGui, addonLifecycle, _configuration); + _repairKitWindow = new(this, _pluginInterface, _pluginLog, _gameGui, addonLifecycle, _configuration, _externalPluginHandler); _windowSystem.AddWindow(_repairKitWindow); _pluginInterface.UiBuilder.Draw += _windowSystem.Draw; @@ -80,7 +80,11 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin HelpMessage = "Open UI" }); + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesNoPostSetup); + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "Request", RequestPostSetup); + _addonLifecycle.RegisterListener(AddonEvent.PostRefresh, "Request", RequestPostRefresh); + _addonLifecycle.RegisterListener(AddonEvent.PostUpdate, "ContextIconMenu", ContextIconMenuPostReceiveEvent); } internal Stage CurrentStage @@ -90,7 +94,7 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin { if (_currentStageInternal != value) { - _pluginLog.Information($"Changing stage from {_currentStageInternal} to {value}"); + _pluginLog.Debug($"Changing stage from {_currentStageInternal} to {value}"); _currentStageInternal = value; } } @@ -130,7 +134,7 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin _mainWindow.State = MainWindow.ButtonState.None; if (CurrentStage != Stage.Stopped) { - RestoreYesAlready(); + _externalPluginHandler.Restore(); CurrentStage = Stage.Stopped; } @@ -143,8 +147,8 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin CurrentStage = Stage.TakeItemFromQueue; } - if (CurrentStage != Stage.Stopped && CurrentStage != Stage.RequestStop && !_yesAlreadyState.Saved) - SaveYesAlready(); + if (CurrentStage != Stage.Stopped && CurrentStage != Stage.RequestStop && !_externalPluginHandler.Saved) + _externalPluginHandler.Save(); switch (CurrentStage) { @@ -156,18 +160,17 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin break; case Stage.TargetFabricationStation: - if (InteractWithFabricationStation(fabricationStation!)) - { if (_configuration.CurrentlyCraftedItem is { StartedCrafting: true }) CurrentStage = Stage.SelectCraftBranch; else CurrentStage = Stage.OpenCraftingLog; - } + + InteractWithFabricationStation(fabricationStation!); break; case Stage.OpenCraftingLog: - OpenCraftingLog(); + // see SelectStringPostSetup break; case Stage.SelectCraftCategory: @@ -183,7 +186,7 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin break; case Stage.RequestStop: - RestoreYesAlready(); + _externalPluginHandler.Restore(); CurrentStage = Stage.Stopped; break; @@ -195,12 +198,24 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin ContributeMaterials(); break; + case Stage.OpenRequestItemWindow: + // see RequestPostSetup and related + if (DateTime.Now > _fallbackAt) + goto case Stage.ContributeMaterials; + break; + + case Stage.OpenRequestItemSelect: + case Stage.ConfirmRequestItemWindow: + // see RequestPostSetup and related + break; + + case Stage.ConfirmMaterialDelivery: - ConfirmMaterialDelivery(); + // see SelectYesNoPostSetup break; case Stage.ConfirmCollectProduct: - ConfirmCollectProduct(); + // see SelectStringPostSetup break; case Stage.Stopped: @@ -231,7 +246,11 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin public void Dispose() { + _addonLifecycle.UnregisterListener(AddonEvent.PostUpdate, "ContextIconMenu", ContextIconMenuPostReceiveEvent); + _addonLifecycle.UnregisterListener(AddonEvent.PostRefresh, "Request", RequestPostRefresh); + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "Request", RequestPostSetup); _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesNoPostSetup); + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup); _commandManager.RemoveHandler("/ws"); _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw; _pluginInterface.UiBuilder.OpenConfigUi -= _configWindow.Toggle; @@ -240,30 +259,7 @@ public sealed partial class WorkshopPlugin : IDalamudPlugin _repairKitWindow.Dispose(); - RestoreYesAlready(); - } - - public void SaveYesAlready() - { - if (_yesAlreadyState.Saved) - { - _pluginLog.Information("Not overwriting yesalready state"); - return; - } - - _yesAlreadyState = (true, _yesAlreadyIpc.DisableIfNecessary()); - _pluginLog.Information($"Previous yesalready state: {_yesAlreadyState.PreviousState}"); - } - - public void RestoreYesAlready() - { - if (_yesAlreadyState.Saved) - { - _pluginLog.Information($"Restoring previous yesalready state: {_yesAlreadyState.PreviousState}"); - if (_yesAlreadyState.PreviousState == true) - _yesAlreadyIpc.Enable(); - } - - _yesAlreadyState = (false, null); + _externalPluginHandler.RestoreTextAdvance(); + _externalPluginHandler.Restore(); } } diff --git a/Workshoppa/Workshoppa.csproj b/Workshoppa/Workshoppa.csproj index 3cb49fd..cf1b526 100644 --- a/Workshoppa/Workshoppa.csproj +++ b/Workshoppa/Workshoppa.csproj @@ -1,7 +1,7 @@ net7.0-windows - 2.5 + 3.0 11.0 enable true