muffin v7.4.10

This commit is contained in:
alydev 2026-01-19 08:31:23 +10:00
parent 2df81c5d15
commit b8dd142c23
47 changed files with 3604 additions and 1058 deletions

View file

@ -1,3 +1,4 @@
using Questionable.Model.Questing;
using Questionable.Windows;
namespace Questionable.Controller.DebugCommands;
@ -15,6 +16,13 @@ internal sealed class SequencesCommandHandler : IDebugCommandHandler
public void Execute(string[] arguments)
{
_questSequenceWindow.ToggleOrUncollapse();
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null)
{
_questSequenceWindow.OpenForQuest(elementId);
}
else
{
_questSequenceWindow.ToggleOrUncollapse();
}
}
}

View file

@ -0,0 +1,63 @@
using System;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
using FFXIVClientStructs.FFXIV.Common.Lua;
using Microsoft.Extensions.Logging;
namespace Questionable.Controller.GameUi;
internal sealed class AutoSnipeHandler : IDisposable
{
private unsafe delegate ulong EnqueueSnipeTaskDelegate(EventSceneModuleImplBase* scene, lua_State* state);
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly ILogger<AutoSnipeHandler> _logger;
private readonly Hook<EnqueueSnipeTaskDelegate>? _enqueueSnipeTaskHook;
public unsafe AutoSnipeHandler(QuestController questController, Configuration configuration, IGameInteropProvider gameInteropProvider, ILogger<AutoSnipeHandler> logger)
{
_questController = questController;
_configuration = configuration;
_logger = logger;
try
{
_enqueueSnipeTaskHook = gameInteropProvider.HookFromSignature<EnqueueSnipeTaskDelegate>("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 50 48 8B F9 48 8D 4C 24 ??", EnqueueSnipeTask);
_enqueueSnipeTaskHook.Enable();
_logger.LogInformation("AutoSnipeHandler enabled");
}
catch (Exception exception)
{
_logger.LogError(exception, "Failed to initialize AutoSnipeHandler");
_enqueueSnipeTaskHook = null;
}
}
private unsafe ulong EnqueueSnipeTask(EventSceneModuleImplBase* scene, lua_State* state)
{
if (_configuration.General.AutoSnipe && _questController.IsRunning)
{
_logger.LogDebug("Auto-completing snipe task");
TValue* top = state->top;
top->tt = 3;
top->value.n = 1.0;
state->top++;
return 1uL;
}
return _enqueueSnipeTaskHook.Original(scene, state);
}
public void Dispose()
{
if (_enqueueSnipeTaskHook != null)
{
_enqueueSnipeTaskHook.Disable();
_enqueueSnipeTaskHook.Dispose();
_logger.LogInformation("AutoSnipeHandler disabled");
}
}
}

View file

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Model.Questing;
namespace Questionable.Controller.GameUi;
internal sealed class ChocoboNameHandler : IDisposable
{
private static readonly HashSet<ElementId> ChocoboQuestIds = new HashSet<ElementId>
{
new QuestId(700),
new QuestId(701),
new QuestId(702)
};
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly IAddonLifecycle _addonLifecycle;
private readonly ILogger<ChocoboNameHandler> _logger;
private bool _awaitingConfirmation;
public ChocoboNameHandler(QuestController questController, Configuration configuration, IAddonLifecycle addonLifecycle, ILogger<ChocoboNameHandler> logger)
{
_questController = questController;
_configuration = configuration;
_addonLifecycle = addonLifecycle;
_logger = logger;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "InputString", OnInputStringSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", OnSelectYesNoSetup);
}
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", OnSelectYesNoSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "InputString", OnInputStringSetup);
}
private bool IsChocoboQuestActive()
{
QuestController.QuestProgress currentQuest = _questController.CurrentQuest;
if (currentQuest != null)
{
return ChocoboQuestIds.Contains(currentQuest.Quest.Id);
}
return false;
}
private unsafe void OnInputStringSetup(AddonEvent type, AddonArgs args)
{
if (_questController.IsRunning && IsChocoboQuestActive())
{
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
if (LAddon.IsAddonReady(address))
{
string validatedChocoboName = GetValidatedChocoboName(_configuration.Advanced.ChocoboName);
_logger.LogInformation("Entering chocobo name: {Name}", validatedChocoboName);
FireInputStringCallback(address, validatedChocoboName);
_awaitingConfirmation = true;
}
}
}
private unsafe void OnSelectYesNoSetup(AddonEvent type, AddonArgs args)
{
if (_questController.IsRunning && _awaitingConfirmation)
{
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
if (LAddon.IsAddonReady(address))
{
_logger.LogInformation("Confirming chocobo name");
ClickYes(address);
_awaitingConfirmation = false;
}
}
}
private static string GetValidatedChocoboName(string? configuredName)
{
if (string.IsNullOrWhiteSpace(configuredName))
{
return "Kweh";
}
string text = configuredName.Trim();
if (text.Length < 2 || text.Length > 20)
{
return "Kweh";
}
string text2 = text.Substring(0, 1).ToUpperInvariant();
string text3;
if (text.Length <= 1)
{
text3 = string.Empty;
}
else
{
string text4 = text;
text3 = text4.Substring(1, text4.Length - 1).ToLowerInvariant();
}
return text2 + text3;
}
private unsafe void FireInputStringCallback(AtkUnitBase* addon, string text)
{
AtkComponentNode* componentNodeById = addon->GetComponentNodeById(9u);
if (componentNodeById == null)
{
_logger.LogWarning("Could not find text input node in InputString addon");
return;
}
AtkComponentTextInput* asAtkComponentTextInput = componentNodeById->GetAsAtkComponentTextInput();
if (asAtkComponentTextInput == null)
{
_logger.LogWarning("Node is not a text input component");
return;
}
asAtkComponentTextInput->SetText(text);
AtkValue* ptr = stackalloc AtkValue[2];
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr->Int = 0;
byte[] bytes = Encoding.UTF8.GetBytes(text);
nint num = Marshal.AllocHGlobal(bytes.Length + 1);
Marshal.Copy(bytes, 0, num, bytes.Length);
Marshal.WriteByte(num + bytes.Length, 0);
ptr[1].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.String;
ptr[1].String = (byte*)num;
addon->FireCallback(2u, ptr);
Marshal.FreeHGlobal(num);
}
private unsafe static void ClickYes(AtkUnitBase* addon)
{
AtkValue* ptr = stackalloc AtkValue[1];
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr->Int = 0;
addon->FireCallback(1u, ptr);
}
}

View file

@ -0,0 +1,152 @@
using System;
using Dalamud.Plugin.Services;
using LLib.Shop;
using Microsoft.Extensions.Logging;
using Questionable.Model.Questing;
namespace Questionable.Controller.GameUi;
internal sealed class GCShopHandler : IDisposable
{
private enum GCShopState
{
Inactive,
Purchasing,
Completed
}
private readonly QuestController _questController;
private readonly GrandCompanyShop _gcShop;
private readonly IObjectTable _objectTable;
private readonly IFramework _framework;
private readonly ILogger<GCShopHandler> _logger;
private GCShopState _state;
private GCPurchaseInfo? _currentPurchase;
private DateTime _lastActionTime = DateTime.MinValue;
private bool _purchaseCompletedThisStep;
public bool IsActive => _state != GCShopState.Inactive;
public GCShopHandler(QuestController questController, GrandCompanyShop gcShop, IObjectTable objectTable, IFramework framework, ILogger<GCShopHandler> logger)
{
_questController = questController;
_gcShop = gcShop;
_objectTable = objectTable;
_framework = framework;
_logger = logger;
_gcShop.PurchaseCompleted += OnPurchaseCompleted;
_framework.Update += OnFrameworkUpdate;
}
public void Dispose()
{
_framework.Update -= OnFrameworkUpdate;
_gcShop.PurchaseCompleted -= OnPurchaseCompleted;
}
private void OnFrameworkUpdate(IFramework framework)
{
if (_objectTable.LocalPlayer == null)
{
return;
}
if (!_questController.IsRunning)
{
if (_state != GCShopState.Inactive)
{
Reset();
}
return;
}
CheckForGCPurchase();
if (_state == GCShopState.Purchasing)
{
HandlePurchasing();
}
else if (_state == GCShopState.Completed)
{
_logger.LogInformation("GC shop purchase completed!");
_state = GCShopState.Inactive;
_lastActionTime = DateTime.MinValue;
}
}
private void CheckForGCPurchase()
{
QuestController.QuestProgress currentQuest = _questController.CurrentQuest;
if (currentQuest == null)
{
if (_currentPurchase != null)
{
Reset();
}
return;
}
GCPurchaseInfo gCPurchaseInfo = (currentQuest.Quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step))?.GCPurchase;
if (gCPurchaseInfo == null)
{
if (_currentPurchase != null)
{
_currentPurchase = null;
_purchaseCompletedThisStep = false;
}
if (_gcShop.IsOpen)
{
_logger.LogDebug("GC shop is open but current step has no GCPurchase. Quest={QuestId}, Seq={Sequence}, Step={Step}", currentQuest.Quest.Id, currentQuest.Sequence, currentQuest.Step);
}
}
else
{
if (_currentPurchase != gCPurchaseInfo)
{
_currentPurchase = gCPurchaseInfo;
_purchaseCompletedThisStep = false;
}
if (_gcShop.IsOpen && _state == GCShopState.Inactive && !_purchaseCompletedThisStep)
{
_logger.LogInformation("GC shop is open. Starting purchase of item {ItemId}.", gCPurchaseInfo.ItemId);
_state = GCShopState.Purchasing;
}
}
}
private void HandlePurchasing()
{
if (_gcShop.IsOpen && _currentPurchase != null && !((DateTime.Now - _lastActionTime).TotalMilliseconds < 500.0))
{
if (!_gcShop.IsPurchaseInProgress)
{
_gcShop.StartPurchase(_currentPurchase.ItemId, _currentPurchase.RankTab, _currentPurchase.CategoryTab);
_lastActionTime = DateTime.Now;
}
_gcShop.ProcessPurchase();
}
}
private void OnPurchaseCompleted(object? sender, PurchaseCompletedEventArgs e)
{
if (_state == GCShopState.Purchasing && _currentPurchase != null && _questController.IsRunning && e.Success && e.ItemId == _currentPurchase.ItemId)
{
_logger.LogInformation("GC purchase of item {ItemId} completed successfully", e.ItemId);
_gcShop.CloseShop();
_purchaseCompletedThisStep = true;
_state = GCShopState.Completed;
}
}
private void Reset()
{
_state = GCShopState.Inactive;
_currentPurchase = null;
_lastActionTime = DateTime.MinValue;
_purchaseCompletedThisStep = false;
}
}

View file

@ -0,0 +1,113 @@
using System;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
using FFXIVClientStructs.Interop;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Utils;
namespace Questionable.Controller.GameUi;
internal sealed class QteController : IDisposable
{
private readonly Configuration _configuration;
private readonly IGameGui _gameGui;
private readonly IGameConfig _gameConfig;
private readonly IFramework _framework;
private readonly ILogger<QteController> _logger;
private long _throttler = Environment.TickCount64;
private readonly Random _random = new Random();
private bool _hasDirectChat;
private bool _directChatWasEnabled;
public QteController(Configuration configuration, IGameGui gameGui, IGameConfig gameConfig, IFramework framework, ILogger<QteController> logger)
{
_configuration = configuration;
_gameGui = gameGui;
_gameConfig = gameConfig;
_framework = framework;
_logger = logger;
_framework.Update += OnUpdate;
}
private unsafe void OnUpdate(IFramework framework)
{
AtkUnitBase* addonPtr;
if (!_configuration.General.AutoSolveQte)
{
EnableDirectChatIfNeeded();
}
else if (_gameGui.TryGetAddonByName<AtkUnitBase>("QTE", out addonPtr) && LAddon.IsAddonReady(addonPtr) && addonPtr->IsVisible)
{
DisableDirectChatIfNeeded();
if (Environment.TickCount64 >= _throttler)
{
if (IsChatLogFocused())
{
WindowsKeypress.SendKeypress(WindowsKeypress.LimitedKeys.Escape);
}
WindowsKeypress.SendKeypress(WindowsKeypress.LimitedKeys.A);
_throttler = Environment.TickCount64 + _random.Next(25, 50);
}
}
else
{
EnableDirectChatIfNeeded();
}
}
private unsafe bool IsChatLogFocused()
{
AtkStage* ptr = AtkStage.Instance();
if (ptr == null)
{
return false;
}
Span<Pointer<AtkUnitBase>> entries = ptr->RaptureAtkUnitManager->AtkUnitManager.FocusedUnitsList.Entries;
for (int i = 0; i < entries.Length; i++)
{
Pointer<AtkUnitBase> pointer = entries[i];
if (pointer.Value != null && pointer.Value->NameString == "ChatLog")
{
return true;
}
}
return false;
}
private void EnableDirectChatIfNeeded()
{
if (_hasDirectChat)
{
_gameConfig.UiControl.Set("DirectChat", _directChatWasEnabled);
_hasDirectChat = false;
}
}
private void DisableDirectChatIfNeeded()
{
if (!_hasDirectChat && _gameConfig.UiControl.TryGetBool("DirectChat", out var value))
{
_directChatWasEnabled = value;
if (value)
{
_gameConfig.UiControl.Set("DirectChat", value: false);
}
_hasDirectChat = true;
}
}
public void Dispose()
{
_framework.Update -= OnUpdate;
EnableDirectChatIfNeeded();
}
}

View file

@ -10,14 +10,14 @@ namespace Questionable.Controller.Steps.Common;
internal static class SendNotification
{
internal sealed class Factory(AutomatonIpc automatonIpc, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc, TerritoryData territoryData) : SimpleTaskFactory()
internal sealed class Factory(Configuration configuration, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc, TerritoryData territoryData) : SimpleTaskFactory()
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
switch (step.InteractionType)
{
case EInteractionType.Snipe:
if (!automatonIpc.IsAutoSnipeEnabled)
if (!configuration.General.AutoSnipe)
{
return new Task(step.InteractionType, step.Comment);
}

View file

@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Input;
using FFXIVClientStructs.FFXIV.Client.UI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Utils;
using Questionable.Model;
using Questionable.Model.Questing;
@ -67,7 +68,7 @@ internal static class Dive
return ETaskResult.StillRunning;
}
logger.LogDebug("{Action} key {KeyCode:X2}", (result.Item1 == 256) ? "Pressing" : "Releasing", result.Item2);
NativeMethods.SendMessage((nint)Device.Instance()->hWnd, result.Item1, result.Item2, IntPtr.Zero);
WindowsKeypress.SendMessage((nint)Device.Instance()->hWnd, result.Item1, result.Item2);
if (result.Item1 == 256)
{
_pressedKeys.Add(result.Item2);
@ -86,7 +87,7 @@ internal static class Dive
foreach (nint pressedKey in _pressedKeys)
{
logger.LogDebug("Releasing stuck key {KeyCode:X2} on stop", pressedKey);
NativeMethods.SendMessage((nint)Device.Instance()->hWnd, 257u, pressedKey, IntPtr.Zero);
WindowsKeypress.SendMessage((nint)Device.Instance()->hWnd, 257u, pressedKey);
}
_pressedKeys.Clear();
_keysToPress.Clear();
@ -160,17 +161,6 @@ internal static class Dive
}
}
private static class NativeMethods
{
public const uint WM_KEYUP = 257u;
public const uint WM_KEYDOWN = 256u;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
public static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
}
private static List<nint>? GetKeysToPress(SeVirtualKey key, KeyModifierFlag modifier)
{
List<nint> list = new List<nint>();

View file

@ -135,25 +135,28 @@ internal static class Duty
{
throw new TaskException("Failed to get territory ID for content finder condition");
}
InventoryManager* intPtr = InventoryManager.Instance();
if (intPtr == null)
if (base.Task.DutyMode != AutoDutyIpc.DutyMode.UnsyncRegular)
{
throw new TaskException("Inventory unavailable");
}
InventoryContainer* inventoryContainer = intPtr->GetInventoryContainer(InventoryType.EquippedItems);
if (inventoryContainer == null)
{
throw new TaskException("Equipped items unavailable");
}
short num = gearStatsCalculator.CalculateAverageItemLevel(inventoryContainer);
if (contentFinderConditionData.RequiredItemLevel > num)
{
string text = $"Could not use AutoDuty to queue for {contentFinderConditionData.Name}, required item level: {contentFinderConditionData.RequiredItemLevel}, current item level: {num}.";
if (!sendNotificationExecutor.Start(new SendNotification.Task(EInteractionType.Duty, text)))
InventoryManager* intPtr = InventoryManager.Instance();
if (intPtr == null)
{
chatGui.PrintError(text, "Questionable", 576);
throw new TaskException("Inventory unavailable");
}
InventoryContainer* inventoryContainer = intPtr->GetInventoryContainer(InventoryType.EquippedItems);
if (inventoryContainer == null)
{
throw new TaskException("Equipped items unavailable");
}
short num = gearStatsCalculator.CalculateAverageItemLevel(inventoryContainer);
if (contentFinderConditionData.RequiredItemLevel > num)
{
string text = $"Could not use AutoDuty to queue for {contentFinderConditionData.Name}, required item level: {contentFinderConditionData.RequiredItemLevel}, current item level: {num}.";
if (!sendNotificationExecutor.Start(new SendNotification.Task(EInteractionType.Duty, text)))
{
chatGui.PrintError(text, "Questionable", 576);
}
return false;
}
return false;
}
autoDutyIpc.StartInstance(base.Task.ContentFinderConditionId, base.Task.DutyMode);
return true;

View file

@ -11,7 +11,6 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Shared;
using Questionable.Controller.Utils;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
@ -20,7 +19,7 @@ namespace Questionable.Controller.Steps.Interactions;
internal static class Interact
{
internal sealed class Factory(AutomatonIpc automatonIpc, Configuration configuration) : ITaskFactory
internal sealed class Factory(Configuration configuration) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -41,7 +40,7 @@ internal static class Interact
}
else if (step.InteractionType == EInteractionType.Snipe)
{
if (!automatonIpc.IsAutoSnipeEnabled)
if (!configuration.General.AutoSnipe)
{
yield break;
}
@ -73,21 +72,21 @@ internal static class Interact
SkipStepConditions stepIf = skipConditions.StepIf;
if (stepIf != null && stepIf.Never)
{
goto IL_0247;
goto IL_024f;
}
}
if (step.InteractionType != EInteractionType.PurchaseItem)
{
skipMarkerCheck = ((step.DataId == 1052475) ? 1 : 0);
goto IL_0248;
goto IL_0250;
}
}
goto IL_0247;
IL_0247:
goto IL_024f;
IL_024f:
skipMarkerCheck = 1;
goto IL_0248;
IL_0248:
yield return new Task(value, quest, interactionType2, (byte)skipMarkerCheck != 0, step.PickUpItemId, step.TaxiStandId, step.SkipConditions?.StepIf, step.CompletionQuestVariablesFlags);
goto IL_0250;
IL_0250:
yield return new Task(value, quest, interactionType2, (byte)skipMarkerCheck != 0, step.PickUpItemId ?? step.GCPurchase?.ItemId, step.TaxiStandId, step.SkipConditions?.StepIf, step.CompletionQuestVariablesFlags);
}
}

View file

@ -0,0 +1,37 @@
using System;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
namespace Questionable.Controller.Utils;
internal static class WindowsKeypress
{
public enum LimitedKeys
{
A = 65,
Escape = 27
}
private static class NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
public static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
}
public const uint WM_KEYUP = 257u;
public const uint WM_KEYDOWN = 256u;
public unsafe static void SendKeypress(LimitedKeys key)
{
void* hWnd = Device.Instance()->hWnd;
NativeMethods.SendMessage((nint)hWnd, 256u, (nint)key, IntPtr.Zero);
NativeMethods.SendMessage((nint)hWnd, 257u, (nint)key, IntPtr.Zero);
}
public static void SendMessage(nint hWnd, uint msg, nint key)
{
NativeMethods.SendMessage(hWnd, msg, key, IntPtr.Zero);
}
}

View file

@ -9,6 +9,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Gui.Toast;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.Logging;
@ -118,6 +119,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private readonly AutoDutyIpc _autoDutyIpc;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly ILogger<QuestController> _logger;
private readonly object _progressLock = new object();
@ -241,7 +244,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
public event AutomationTypeChangedEventHandler? AutomationTypeChanged;
public QuestController(IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, QuestFunctions questFunctions, MovementController movementController, CombatController combatController, GatheringController gatheringController, ILogger<QuestController> logger, QuestRegistry questRegistry, JournalData journalData, IKeyState keyState, IChatGui chatGui, ICondition condition, IToastGui toastGui, Configuration configuration, TaskCreator taskCreator, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent, AutoDutyIpc autoDutyIpc)
public QuestController(IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, QuestFunctions questFunctions, MovementController movementController, CombatController combatController, GatheringController gatheringController, ILogger<QuestController> logger, QuestRegistry questRegistry, JournalData journalData, IKeyState keyState, IChatGui chatGui, ICondition condition, IToastGui toastGui, Configuration configuration, TaskCreator taskCreator, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent, AutoDutyIpc autoDutyIpc, IDalamudPluginInterface pluginInterface)
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
{
_clientState = clientState;
@ -261,6 +264,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_taskCreator = taskCreator;
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
_autoDutyIpc = autoDutyIpc;
_pluginInterface = pluginInterface;
_logger = logger;
_toastGui.ErrorToast += base.OnErrorToast;
_toastGui.Toast += OnNormalToast;
@ -273,7 +277,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
lock (_progressLock)
{
_logger.LogInformation("Reload, resetting curent quest progress");
_logger.LogInformation("Reload, resetting current quest progress");
ResetInternalState();
ResetAutoRefreshState();
_questRegistry.Reload();
@ -699,7 +703,6 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
if (_startedQuest != null && !_taskQueue.AllTasksComplete)
{
_logger.LogTrace("Not switching from quest {CurrentQuestId} to {NewQuestId} because tasks are still running", _startedQuest.Quest.Id, elementId);
questProgress = _startedQuest;
b = _startedQuest.Sequence;
}
@ -1415,6 +1418,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_logger.LogWarning("Could not find quest {QuestId} during import", questElement);
}
_logger.LogInformation("Imported {Count} priority quests", ManualPriorityQuests.Count);
SavePriorityQuests();
}
public string ExportQuestPriority()
@ -1425,6 +1429,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
public void ClearQuestPriority()
{
ManualPriorityQuests.Clear();
SavePriorityQuests();
}
public bool AddQuestPriority(ElementId elementId)
@ -1432,6 +1437,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (_questRegistry.TryGetQuest(elementId, out Quest quest) && !ManualPriorityQuests.Contains(quest))
{
ManualPriorityQuests.Add(quest);
SavePriorityQuests();
return true;
}
return false;
@ -1439,25 +1445,25 @@ internal sealed class QuestController : MiniTaskController<QuestController>
public int AddAllAvailableQuests()
{
List<ElementId> list = (from q in _questRegistry.AllQuests.Where(delegate(Quest quest)
List<ElementId> list = (from q in _questRegistry.AllQuests.Where(delegate(Quest quest2)
{
if (quest.Root.Disabled || _questFunctions.IsQuestRemoved(quest.Id) || _questFunctions.IsQuestComplete(quest.Id) || _questFunctions.IsQuestAccepted(quest.Id) || quest.Info.IsRepeatable)
if (quest2.Root.Disabled || _questFunctions.IsQuestRemoved(quest2.Id) || _questFunctions.IsQuestComplete(quest2.Id) || _questFunctions.IsQuestAccepted(quest2.Id) || quest2.Info.IsRepeatable)
{
return false;
}
if (quest.Info.AlliedSociety != EAlliedSociety.None)
if (quest2.Info.AlliedSociety != EAlliedSociety.None)
{
_logger.LogDebug("Excluding allied society quest {QuestId} from bulk add", quest.Id);
_logger.LogDebug("Excluding allied society quest {QuestId} from bulk add", quest2.Id);
return false;
}
if (quest.Info is QuestInfo questInfo && _journalData.MoogleDeliveryGenreId.HasValue && questInfo.JournalGenre == _journalData.MoogleDeliveryGenreId.Value)
if (quest2.Info is QuestInfo questInfo && _journalData.MoogleDeliveryGenreId.HasValue && questInfo.JournalGenre == _journalData.MoogleDeliveryGenreId.Value)
{
_logger.LogDebug("Excluding moogle delivery quest {QuestId} from bulk add", quest.Id);
_logger.LogDebug("Excluding moogle delivery quest {QuestId} from bulk add", quest2.Id);
return false;
}
if (!_questFunctions.IsReadyToAcceptQuest(quest.Id))
if (!_questFunctions.IsReadyToAcceptQuest(quest2.Id))
{
_logger.LogTrace("Quest {QuestId} not ready to accept", quest.Id);
_logger.LogTrace("Quest {QuestId} not ready to accept", quest2.Id);
return false;
}
return true;
@ -1467,11 +1473,16 @@ internal sealed class QuestController : MiniTaskController<QuestController>
int num = 0;
foreach (ElementId item in list)
{
if (AddQuestPriority(item))
if (_questRegistry.TryGetQuest(item, out Quest quest) && !ManualPriorityQuests.Contains(quest))
{
ManualPriorityQuests.Add(quest);
num++;
}
}
if (num > 0)
{
SavePriorityQuests();
}
return num;
}
@ -1482,6 +1493,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (_questRegistry.TryGetQuest(elementId, out Quest quest) && !ManualPriorityQuests.Contains(quest))
{
ManualPriorityQuests.Insert(index, quest);
SavePriorityQuests();
}
return true;
}
@ -1493,14 +1505,39 @@ internal sealed class QuestController : MiniTaskController<QuestController>
}
}
public bool WasLastTaskUpdateWithin(TimeSpan timeSpan)
public void LoadPriorityQuests()
{
_logger.LogInformation("Last update: {Update}", _lastTaskUpdate);
if (!IsRunning)
if (!_configuration.General.PersistPriorityQuestsBetweenSessions)
{
return DateTime.Now <= _lastTaskUpdate.Add(timeSpan);
_logger.LogDebug("Priority quest persistence is disabled, skipping load");
return;
}
return true;
ManualPriorityQuests.Clear();
foreach (ElementId priorityQuest in _configuration.General.PriorityQuests)
{
if (_questRegistry.TryGetQuest(priorityQuest, out Quest quest))
{
ManualPriorityQuests.Add(quest);
continue;
}
_logger.LogWarning("Could not find quest {QuestId} during load from config", priorityQuest);
}
if (ManualPriorityQuests.Count > 0)
{
_logger.LogInformation("Loaded {Count} priority quests from configuration", ManualPriorityQuests.Count);
}
}
public void SavePriorityQuests()
{
if (!_configuration.General.PersistPriorityQuestsBetweenSessions)
{
_logger.LogDebug("Priority quest persistence is disabled, skipping save");
return;
}
_configuration.General.PriorityQuests = ManualPriorityQuests.Select((Quest q) => q.Id).ToList();
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogDebug("Saved {Count} priority quests to configuration", ManualPriorityQuests.Count);
}
private void OnConditionChange(ConditionFlag flag, bool value)
@ -1530,6 +1567,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
_logger.LogInformation("Clearing priority quests on logout");
ManualPriorityQuests.Clear();
if (_configuration.General.PersistPriorityQuestsBetweenSessions)
{
_configuration.General.PriorityQuests.Clear();
_pluginInterface.SavePluginConfig(_configuration);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
using System.Collections.Immutable;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Questionable.Model.Questing;
namespace Questionable.Data;
public sealed record DutyInfo(uint ContentFinderConditionId, uint ContentId, string Name, uint ContentTypeId, string ContentTypeName, ushort Level, ushort ItemLevel, ImmutableList<QuestId> UnlockQuests, bool IsHighEndDuty, EDutyCategory DutyCategory)
{
public bool IsUnlocked => UIState.IsInstanceContentUnlocked(ContentId);
public bool? IsCompleted
{
get
{
if (DutyCategory != EDutyCategory.DeepDungeon)
{
return UIState.IsInstanceContentCompleted((ushort)ContentId);
}
return null;
}
}
}

View file

@ -0,0 +1,384 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Dalamud.Game;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Data;
internal sealed class DutyUnlockData
{
private static class ContentTypeIds
{
public const uint Dungeon = 2u;
public const uint Guildhest = 3u;
public const uint Trial = 4u;
public const uint Raid = 5u;
public const uint PvP = 6u;
public const uint QuestBattle = 7u;
public const uint DeepDungeon = 21u;
public const uint AllianceRaid = 26u;
public const uint Ultimate = 28u;
public const uint VCDungeonFinder = 29u;
public const uint Criterion = 30u;
public const uint CriterionSavage = 31u;
}
private static class ContentMemberTypeIds
{
public const byte LightParty = 2;
public const byte FullParty = 3;
public const byte Alliance = 4;
}
private readonly ImmutableDictionary<uint, DutyInfo> _duties;
private readonly ImmutableDictionary<QuestId, ImmutableList<uint>> _questToDuties;
private readonly ImmutableDictionary<uint, ImmutableList<QuestId>> _dutyToQuests;
private readonly ILogger<DutyUnlockData> _logger;
private static readonly HashSet<uint> VariantDungeonCfcIds = new HashSet<uint> { 868u, 945u, 961u };
public DutyUnlockData(IDataManager dataManager, ILogger<DutyUnlockData> logger)
{
_logger = logger;
ExcelSheet<ContentFinderCondition> excelSheet = dataManager.GetExcelSheet<ContentFinderCondition>();
ExcelSheet<QuestEx> excelSheet2 = dataManager.GetExcelSheet<QuestEx>();
ImmutableDictionary<uint, uint> immutableDictionary = (from c in excelSheet
where c.RowId != 0 && c.Content.RowId != 0 && c.ContentLinkType == 1
group c by c.Content.RowId).ToImmutableDictionary((IGrouping<uint, ContentFinderCondition> g) => g.Key, (IGrouping<uint, ContentFinderCondition> g) => g.First().RowId);
Dictionary<QuestId, List<uint>> dictionary = new Dictionary<QuestId, List<uint>>();
Dictionary<uint, List<QuestId>> dictionary2 = new Dictionary<uint, List<QuestId>>();
foreach (QuestEx item in excelSheet2.Where((QuestEx q) => q.RowId != 0))
{
uint rowId = item.InstanceContentUnlock.RowId;
if (rowId != 0 && immutableDictionary.TryGetValue(rowId, out var value))
{
QuestId questId = QuestId.FromRowId(item.RowId);
if (!dictionary.ContainsKey(questId))
{
dictionary[questId] = new List<uint>();
}
dictionary[questId].Add(value);
if (!dictionary2.ContainsKey(value))
{
dictionary2[value] = new List<QuestId>();
}
dictionary2[value].Add(questId);
}
}
_questToDuties = dictionary.ToImmutableDictionary((KeyValuePair<QuestId, List<uint>> x) => x.Key, (KeyValuePair<QuestId, List<uint>> x) => x.Value.ToImmutableList());
_dutyToQuests = dictionary2.ToImmutableDictionary((KeyValuePair<uint, List<QuestId>> x) => x.Key, (KeyValuePair<uint, List<QuestId>> x) => x.Value.ToImmutableList());
Dictionary<uint, DutyInfo> dictionary3 = new Dictionary<uint, DutyInfo>();
foreach (ContentFinderCondition item2 in excelSheet.Where((ContentFinderCondition x) => x.RowId != 0 && x.Content.RowId != 0))
{
if (item2.ContentLinkType != 1)
{
continue;
}
ContentType? valueNullable = item2.ContentType.ValueNullable;
if (!valueNullable.HasValue)
{
continue;
}
uint rowId2 = valueNullable.Value.RowId;
if (rowId2 - 6 > 1)
{
string text = FixName(item2.Name.ToDalamudString().ToString(), dataManager.Language);
if (!string.IsNullOrWhiteSpace(text) && !IsExcludedContent(text, item2.RowId))
{
List<QuestId> value2;
ImmutableList<QuestId> unlockQuests = (dictionary2.TryGetValue(item2.RowId, out value2) ? value2.ToImmutableList() : ImmutableList<QuestId>.Empty);
uint rowId3 = item2.ContentMemberType.RowId;
EDutyCategory dutyCategory = DetermineDutyCategory(rowId2, text, item2.HighEndDuty, item2.RowId, (byte)rowId3);
DutyInfo value3 = new DutyInfo(item2.RowId, item2.Content.RowId, text, rowId2, valueNullable.Value.Name.ToDalamudString().ToString(), item2.ClassJobLevelRequired, item2.ItemLevelRequired, unlockQuests, item2.HighEndDuty, dutyCategory);
dictionary3[item2.RowId] = value3;
}
}
}
_duties = dictionary3.ToImmutableDictionary();
_logger.LogInformation("Loaded {DutyCount} duties with {QuestMappings} quest unlock mappings", _duties.Count, _questToDuties.Values.Sum((ImmutableList<uint> x) => x.Count));
}
private static bool IsExcludedContent(string name, uint cfcId)
{
if (cfcId - 69 <= 2)
{
return true;
}
return false;
}
private static EDutyCategory DetermineDutyCategory(uint contentTypeId, string name, bool isHighEndDuty, uint cfcId, byte contentMemberType)
{
if (VariantDungeonCfcIds.Contains(cfcId))
{
return EDutyCategory.VariantDungeon;
}
switch (contentTypeId)
{
case 2u:
if (name.EndsWith("(Hard)", StringComparison.OrdinalIgnoreCase))
{
return EDutyCategory.HardDungeon;
}
return EDutyCategory.Dungeon;
case 3u:
return EDutyCategory.Guildhest;
case 4u:
if (name.EndsWith("(Unreal)", StringComparison.OrdinalIgnoreCase))
{
return EDutyCategory.UnrealTrial;
}
if (name.EndsWith("(Extreme)", StringComparison.OrdinalIgnoreCase) || name.StartsWith("The Minstrel's Ballad:", StringComparison.OrdinalIgnoreCase) || isHighEndDuty)
{
return EDutyCategory.ExtremeTrial;
}
if (name.EndsWith("(Hard)", StringComparison.OrdinalIgnoreCase))
{
return EDutyCategory.HardTrial;
}
return EDutyCategory.Trial;
case 5u:
if (contentMemberType == 4)
{
if (isHighEndDuty)
{
return EDutyCategory.ChaoticAllianceRaid;
}
return EDutyCategory.AllianceRaid;
}
if (name.EndsWith("(Savage)", StringComparison.OrdinalIgnoreCase) || isHighEndDuty)
{
return EDutyCategory.SavageRaid;
}
return EDutyCategory.NormalRaid;
case 21u:
return EDutyCategory.DeepDungeon;
case 26u:
if (isHighEndDuty)
{
return EDutyCategory.ChaoticAllianceRaid;
}
return EDutyCategory.AllianceRaid;
case 28u:
return EDutyCategory.Ultimate;
case 29u:
if (name.EndsWith("(Savage)", StringComparison.OrdinalIgnoreCase) || isHighEndDuty)
{
return EDutyCategory.CriterionSavage;
}
return EDutyCategory.CriterionDungeon;
case 30u:
if (name.EndsWith("(Savage)", StringComparison.OrdinalIgnoreCase) || isHighEndDuty)
{
return EDutyCategory.CriterionSavage;
}
return EDutyCategory.CriterionDungeon;
case 31u:
return EDutyCategory.CriterionSavage;
default:
if (name.EndsWith("(Savage)", StringComparison.OrdinalIgnoreCase))
{
return EDutyCategory.SavageRaid;
}
if (name.EndsWith("(Extreme)", StringComparison.OrdinalIgnoreCase) || name.StartsWith("The Minstrel's Ballad:", StringComparison.OrdinalIgnoreCase))
{
return EDutyCategory.ExtremeTrial;
}
return EDutyCategory.Other;
}
}
private static string FixName(string name, ClientLanguage language)
{
if (string.IsNullOrEmpty(name) || language != ClientLanguage.English)
{
return name;
}
return string.Concat(name[0].ToString().ToUpper(CultureInfo.InvariantCulture).AsSpan(), name.AsSpan(1));
}
public IEnumerable<DutyInfo> GetAllDuties()
{
return _duties.Values;
}
public IEnumerable<DutyInfo> GetDutiesByContentType(uint contentTypeId)
{
return _duties.Values.Where((DutyInfo d) => d.ContentTypeId == contentTypeId);
}
public IEnumerable<DutyInfo> GetDutiesByCategory(EDutyCategory category)
{
return _duties.Values.Where((DutyInfo d) => d.DutyCategory == category);
}
public IEnumerable<DutyInfo> GetDungeons()
{
return GetDutiesByCategory(EDutyCategory.Dungeon);
}
public IEnumerable<DutyInfo> GetHardDungeons()
{
return GetDutiesByCategory(EDutyCategory.HardDungeon);
}
public IEnumerable<DutyInfo> GetGuildhests()
{
return GetDutiesByCategory(EDutyCategory.Guildhest);
}
public IEnumerable<DutyInfo> GetTrials()
{
return GetDutiesByCategory(EDutyCategory.Trial);
}
public IEnumerable<DutyInfo> GetHardTrials()
{
return GetDutiesByCategory(EDutyCategory.HardTrial);
}
public IEnumerable<DutyInfo> GetExtremeTrials()
{
return GetDutiesByCategory(EDutyCategory.ExtremeTrial);
}
public IEnumerable<DutyInfo> GetUnrealTrials()
{
return GetDutiesByCategory(EDutyCategory.UnrealTrial);
}
public IEnumerable<DutyInfo> GetNormalRaids()
{
return GetDutiesByCategory(EDutyCategory.NormalRaid);
}
public IEnumerable<DutyInfo> GetSavageRaids()
{
return GetDutiesByCategory(EDutyCategory.SavageRaid);
}
public IEnumerable<DutyInfo> GetUltimateRaids()
{
return GetDutiesByCategory(EDutyCategory.Ultimate);
}
public IEnumerable<DutyInfo> GetAllianceRaids()
{
return GetDutiesByCategory(EDutyCategory.AllianceRaid);
}
public IEnumerable<DutyInfo> GetChaoticAllianceRaids()
{
return GetDutiesByCategory(EDutyCategory.ChaoticAllianceRaid);
}
public IEnumerable<DutyInfo> GetDeepDungeons()
{
return GetDutiesByCategory(EDutyCategory.DeepDungeon);
}
public IEnumerable<DutyInfo> GetVariantDungeons()
{
return GetDutiesByCategory(EDutyCategory.VariantDungeon);
}
public IEnumerable<DutyInfo> GetCriterionDungeons()
{
return GetDutiesByCategory(EDutyCategory.CriterionDungeon);
}
public IEnumerable<DutyInfo> GetCriterionSavageDungeons()
{
return GetDutiesByCategory(EDutyCategory.CriterionSavage);
}
public IEnumerable<DutyInfo> GetDutiesUnlockedByQuest(QuestId questId)
{
if (!_questToDuties.TryGetValue(questId, out ImmutableList<uint> value))
{
yield break;
}
foreach (uint item in value)
{
if (_duties.TryGetValue(item, out DutyInfo value2))
{
yield return value2;
}
}
}
public IEnumerable<QuestId> GetUnlockQuestsForDuty(uint cfcId)
{
if (_dutyToQuests.TryGetValue(cfcId, out ImmutableList<QuestId> value))
{
return value;
}
return Array.Empty<QuestId>();
}
public bool IsDutyUnlocked(uint cfcId)
{
if (!_duties.TryGetValue(cfcId, out DutyInfo value))
{
return false;
}
return UIState.IsInstanceContentUnlocked(value.ContentId);
}
public static bool IsContentUnlocked(uint contentId)
{
return UIState.IsInstanceContentUnlocked(contentId);
}
public bool TryGetDuty(uint cfcId, [NotNullWhen(true)] out DutyInfo? duty)
{
return _duties.TryGetValue(cfcId, out duty);
}
public DutyInfo? GetDuty(uint cfcId)
{
return _duties.GetValueOrDefault(cfcId);
}
public IEnumerable<DutyInfo> GetUnlockedDuties()
{
return _duties.Values.Where((DutyInfo d) => UIState.IsInstanceContentUnlocked(d.ContentId));
}
public IEnumerable<DutyInfo> GetLockedDuties()
{
return _duties.Values.Where((DutyInfo d) => !UIState.IsInstanceContentUnlocked(d.ContentId));
}
public IEnumerable<DutyInfo> GetDutiesWithUnlockQuests()
{
return _duties.Values.Where((DutyInfo d) => d.UnlockQuests.Count > 0);
}
}

View file

@ -0,0 +1,22 @@
namespace Questionable.Data;
public enum EDutyCategory
{
Dungeon,
HardDungeon,
Guildhest,
Trial,
HardTrial,
ExtremeTrial,
UnrealTrial,
NormalRaid,
SavageRaid,
AllianceRaid,
ChaoticAllianceRaid,
Ultimate,
DeepDungeon,
VariantDungeon,
CriterionDungeon,
CriterionSavage,
Other
}

View file

@ -111,7 +111,7 @@ internal sealed class QuestData
list3.Add(0);
list3.AddRange((from QuestInfo y in quests.Where((IQuestInfo y) => (uint)y.AlliedSociety == (byte)x.RowId && y.IsRepeatable)
select (byte)y.AlliedSocietyRank).Distinct());
return new _003C_003Ez__ReadOnlyList<byte>(list3).Select((byte rank) => new AlliedSocietyDailyInfo(x, rank, classJobUtils));
return new global::_003C_003Ez__ReadOnlyList<byte>(list3).Select((byte rank) => new AlliedSocietyDailyInfo(x, rank, classJobUtils));
}
return new global::_003C_003Ez__ReadOnlySingleElementList<AlliedSocietyDailyInfo>(new AlliedSocietyDailyInfo(x, 0, classJobUtils));
}));

View file

@ -1,42 +1,123 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Ipc.Exceptions;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Controller;
using Questionable.Data;
namespace Questionable.External;
internal sealed class AutomatonIpc
internal sealed class AutomatonIpc : IDisposable
{
private static readonly ImmutableHashSet<string> ConflictingTweaks = new HashSet<string>
{
"DateWithDestiny", "AutoFollow", "AutoPillion", "AutoInvite", "AutoBusy", "AutoEquipXPBoosts", "FateToolKit", "AutoMerge", "AutoQueue", "EnhancedDutyStartEnd",
"EnhancedLoginLogout", "GettingTooAttached", "RetrieveMateria"
}.ToImmutableHashSet();
private const string AutoSnipeTweak = "AutoSnipeQuests";
private readonly Configuration _configuration;
private readonly QuestController _questController;
private readonly TerritoryData _territoryData;
private readonly IClientState _clientState;
private readonly ILogger<AutomatonIpc> _logger;
private readonly ICallGateSubscriber<string, bool> _isTweakEnabled;
private bool _loggedIpcError;
private readonly ICallGateSubscriber<string, bool, object?> _setTweakState;
public bool IsAutoSnipeEnabled
private HashSet<string>? _pausedTweaks;
public AutomatonIpc(Configuration configuration, IDalamudPluginInterface pluginInterface, QuestController questController, TerritoryData territoryData, IClientState clientState, ILogger<AutomatonIpc> logger)
{
get
_configuration = configuration;
_questController = questController;
_territoryData = territoryData;
_clientState = clientState;
_logger = logger;
_isTweakEnabled = pluginInterface.GetIpcSubscriber<string, bool>("Automaton.IsTweakEnabled");
_setTweakState = pluginInterface.GetIpcSubscriber<string, bool, object>("Automaton.SetTweakState");
_questController.AutomationTypeChanged += OnAutomationTypeChanged;
}
private void OnAutomationTypeChanged(object sender, QuestController.EAutomationType automationType)
{
if (automationType != QuestController.EAutomationType.Manual && !_territoryData.IsDutyInstance(_clientState.TerritoryType))
{
try
{
return _isTweakEnabled.InvokeFunc("AutoSnipeQuests");
}
catch (IpcError exception)
{
if (!_loggedIpcError)
{
_loggedIpcError = true;
_logger.LogWarning(exception, "Could not query automaton for tweak status, probably not installed");
}
return false;
}
Task.Run((Action)DisableConflictingTweaks);
}
else
{
Task.Run((Action)RestoreConflictingTweaks);
}
}
public AutomatonIpc(IDalamudPluginInterface pluginInterface, ILogger<AutomatonIpc> logger)
private void DisableConflictingTweaks()
{
_logger = logger;
_isTweakEnabled = pluginInterface.GetIpcSubscriber<string, bool>("Automaton.IsTweakEnabled");
logger.LogInformation("Automaton auto-snipe enabled: {IsTweakEnabled}", IsAutoSnipeEnabled);
if (_pausedTweaks != null)
{
return;
}
_pausedTweaks = new HashSet<string>();
foreach (string conflictingTweak in ConflictingTweaks)
{
TryDisableTweak(conflictingTweak);
}
if (_configuration.General.AutoSnipe)
{
TryDisableTweak("AutoSnipeQuests");
}
}
private void TryDisableTweak(string tweak)
{
try
{
if (_isTweakEnabled.InvokeFunc(tweak))
{
_setTweakState.InvokeAction(tweak, arg2: false);
_pausedTweaks.Add(tweak);
_logger.LogInformation("Paused Automaton tweak: {Tweak}", tweak);
}
}
catch (IpcError)
{
}
}
private void RestoreConflictingTweaks()
{
if (_pausedTweaks == null)
{
return;
}
foreach (string pausedTweak in _pausedTweaks)
{
try
{
_setTweakState.InvokeAction(pausedTweak, arg2: true);
_logger.LogInformation("Restored Automaton tweak: {Tweak}", pausedTweak);
}
catch (IpcError)
{
}
}
_pausedTweaks = null;
}
public void Dispose()
{
_questController.AutomationTypeChanged -= OnAutomationTypeChanged;
RestoreConflictingTweaks();
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Ipc.Exceptions;
@ -20,7 +21,9 @@ internal sealed class PandorasBoxIpc : IDisposable
"Pandora Quick Gather", "Switch Gatherers Automatically"
}.ToImmutableHashSet();
private readonly IFramework _framework;
private const string AutoActiveTimeManeuverFeature = "Auto Active Time Maneuver";
private readonly Configuration _configuration;
private readonly QuestController _questController;
@ -34,58 +37,35 @@ internal sealed class PandorasBoxIpc : IDisposable
private readonly ICallGateSubscriber<string, bool, object?> _setFeatureEnabled;
private bool _loggedIpcError;
private HashSet<string>? _pausedFeatures;
public bool IsAutoActiveTimeManeuverEnabled
public PandorasBoxIpc(Configuration configuration, IDalamudPluginInterface pluginInterface, QuestController questController, TerritoryData territoryData, IClientState clientState, ILogger<PandorasBoxIpc> logger)
{
get
{
try
{
return _getFeatureEnabled.InvokeFunc("Auto Active Time Maneuver") == true;
}
catch (IpcError exception)
{
if (!_loggedIpcError)
{
_loggedIpcError = true;
_logger.LogWarning(exception, "Could not query pandora's box for feature status, probably not installed");
}
return false;
}
}
}
public PandorasBoxIpc(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, TerritoryData territoryData, IClientState clientState, ILogger<PandorasBoxIpc> logger)
{
_framework = framework;
_configuration = configuration;
_questController = questController;
_territoryData = territoryData;
_clientState = clientState;
_logger = logger;
_getFeatureEnabled = pluginInterface.GetIpcSubscriber<string, bool?>("PandorasBox.GetFeatureEnabled");
_setFeatureEnabled = pluginInterface.GetIpcSubscriber<string, bool, object>("PandorasBox.SetFeatureEnabled");
logger.LogInformation("Pandora's Box auto active time maneuver enabled: {IsAtmEnabled}", IsAutoActiveTimeManeuverEnabled);
_framework.Update += OnUpdate;
_questController.AutomationTypeChanged += OnAutomationTypeChanged;
}
private void OnUpdate(IFramework framework)
private void OnAutomationTypeChanged(object sender, QuestController.EAutomationType automationType)
{
if ((_questController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual) && !_territoryData.IsDutyInstance(_clientState.TerritoryType))
if (automationType != QuestController.EAutomationType.Manual && !_territoryData.IsDutyInstance(_clientState.TerritoryType))
{
DisableConflictingFeatures();
Task.Run((Action)DisableConflictingFeatures);
}
else
{
RestoreConflictingFeatures();
Task.Run((Action)RestoreConflictingFeatures);
}
}
public void Dispose()
{
_framework.Update -= OnUpdate;
_questController.AutomationTypeChanged -= OnAutomationTypeChanged;
RestoreConflictingFeatures();
}
@ -98,20 +78,28 @@ internal sealed class PandorasBoxIpc : IDisposable
_pausedFeatures = new HashSet<string>();
foreach (string conflictingFeature in ConflictingFeatures)
{
try
TryDisableFeature(conflictingFeature);
}
if (_configuration.General.AutoSolveQte)
{
TryDisableFeature("Auto Active Time Maneuver");
}
}
private void TryDisableFeature(string feature)
{
try
{
if (_getFeatureEnabled.InvokeFunc(feature) == true)
{
if (_getFeatureEnabled.InvokeFunc(conflictingFeature) == true)
{
_setFeatureEnabled.InvokeAction(conflictingFeature, arg2: false);
_pausedFeatures.Add(conflictingFeature);
_logger.LogInformation("Paused Pandora's Box feature: {Feature}", conflictingFeature);
}
}
catch (IpcError exception)
{
_logger.LogWarning(exception, "Failed to pause Pandora's Box feature: {Feature}", conflictingFeature);
_setFeatureEnabled.InvokeAction(feature, arg2: false);
_pausedFeatures.Add(feature);
_logger.LogInformation("Paused Pandora's Box feature: {Feature}", feature);
}
}
catch (IpcError)
{
}
}
private void RestoreConflictingFeatures()
@ -127,9 +115,8 @@ internal sealed class PandorasBoxIpc : IDisposable
_setFeatureEnabled.InvokeAction(pausedFeature, arg2: true);
_logger.LogInformation("Restored Pandora's Box feature: {Feature}", pausedFeature);
}
catch (IpcError exception)
catch (IpcError)
{
_logger.LogWarning(exception, "Failed to restore Pandora's Box feature: {Feature}", pausedFeature);
}
}
_pausedFeatures = null;

View file

@ -196,6 +196,20 @@ internal sealed class QuestionableIpc : IDisposable
private const string IpcStopLevelingMode = "Questionable.StopLevelingMode";
private const string IpcGetBlacklistedQuests = "Questionable.GetBlacklistedQuests";
private const string IpcAddBlacklistedQuest = "Questionable.AddBlacklistedQuest";
private const string IpcRemoveBlacklistedQuest = "Questionable.RemoveBlacklistedQuest";
private const string IpcClearBlacklistedQuests = "Questionable.ClearBlacklistedQuests";
private const string IpcIsQuestBlacklisted = "Questionable.IsQuestBlacklisted";
private const string IpcGetMsqPriorityMode = "Questionable.GetMsqPriorityMode";
private const string IpcSetMsqPriorityMode = "Questionable.SetMsqPriorityMode";
private readonly QuestController _questController;
private readonly QuestRegistry _questRegistry;
@ -350,6 +364,20 @@ internal sealed class QuestionableIpc : IDisposable
private readonly ICallGateProvider<bool> _stopLevelingMode;
private readonly ICallGateProvider<List<string>> _getBlacklistedQuests;
private readonly ICallGateProvider<string, bool> _addBlacklistedQuest;
private readonly ICallGateProvider<string, bool> _removeBlacklistedQuest;
private readonly ICallGateProvider<bool> _clearBlacklistedQuests;
private readonly ICallGateProvider<string, bool> _isQuestBlacklisted;
private readonly ICallGateProvider<int> _getMsqPriorityMode;
private readonly ICallGateProvider<int, bool> _setMsqPriorityMode;
public QuestionableIpc(QuestController questController, EventInfoComponent eventInfoComponent, QuestRegistry questRegistry, QuestFunctions questFunctions, QuestData questData, ManualPriorityComponent manualPriorityComponent, PresetBuilderComponent presetBuilderComponent, Configuration configuration, IDalamudPluginInterface pluginInterface, IServiceProvider serviceProvider, AutoDutyIpc autoDutyIpc)
{
QuestionableIpc questionableIpc = this;
@ -542,6 +570,20 @@ internal sealed class QuestionableIpc : IDisposable
_startLevelingMode.RegisterFunc(StartLevelingMode);
_stopLevelingMode = pluginInterface.GetIpcProvider<bool>("Questionable.StopLevelingMode");
_stopLevelingMode.RegisterFunc(StopLevelingMode);
_getBlacklistedQuests = pluginInterface.GetIpcProvider<List<string>>("Questionable.GetBlacklistedQuests");
_getBlacklistedQuests.RegisterFunc(GetBlacklistedQuests);
_addBlacklistedQuest = pluginInterface.GetIpcProvider<string, bool>("Questionable.AddBlacklistedQuest");
_addBlacklistedQuest.RegisterFunc(AddBlacklistedQuest);
_removeBlacklistedQuest = pluginInterface.GetIpcProvider<string, bool>("Questionable.RemoveBlacklistedQuest");
_removeBlacklistedQuest.RegisterFunc(RemoveBlacklistedQuest);
_clearBlacklistedQuests = pluginInterface.GetIpcProvider<bool>("Questionable.ClearBlacklistedQuests");
_clearBlacklistedQuests.RegisterFunc(ClearBlacklistedQuests);
_isQuestBlacklisted = pluginInterface.GetIpcProvider<string, bool>("Questionable.IsQuestBlacklisted");
_isQuestBlacklisted.RegisterFunc(IsQuestBlacklistedIpc);
_getMsqPriorityMode = pluginInterface.GetIpcProvider<int>("Questionable.GetMsqPriorityMode");
_getMsqPriorityMode.RegisterFunc(GetMsqPriorityMode);
_setMsqPriorityMode = pluginInterface.GetIpcProvider<int, bool>("Questionable.SetMsqPriorityMode");
_setMsqPriorityMode.RegisterFunc(SetMsqPriorityMode);
}
private bool StartQuest(string questId, bool single)
@ -1387,6 +1429,67 @@ internal sealed class QuestionableIpc : IDisposable
}
}
private List<string> GetBlacklistedQuests()
{
return _configuration.General.BlacklistedQuests.Select((ElementId q) => q.ToString()).ToList();
}
private bool AddBlacklistedQuest(string questId)
{
if (ElementId.TryFromString(questId, out ElementId elementId) && elementId != null && _configuration.General.BlacklistedQuests.Add(elementId))
{
_pluginInterface.SavePluginConfig(_configuration);
return true;
}
return false;
}
private bool RemoveBlacklistedQuest(string questId)
{
if (ElementId.TryFromString(questId, out ElementId elementId) && elementId != null)
{
bool num = _configuration.General.BlacklistedQuests.Remove(elementId);
if (num)
{
_pluginInterface.SavePluginConfig(_configuration);
}
return num;
}
return false;
}
private bool ClearBlacklistedQuests()
{
_configuration.General.BlacklistedQuests.Clear();
_pluginInterface.SavePluginConfig(_configuration);
return true;
}
private bool IsQuestBlacklistedIpc(string questId)
{
if (ElementId.TryFromString(questId, out ElementId elementId) && elementId != null)
{
return _questFunctions.IsQuestBlacklisted(elementId);
}
return false;
}
private int GetMsqPriorityMode()
{
return (int)_configuration.General.MsqPriority;
}
private bool SetMsqPriorityMode(int mode)
{
if (!Enum.IsDefined(typeof(Configuration.EMsqPriorityMode), mode))
{
return false;
}
_configuration.General.MsqPriority = (Configuration.EMsqPriorityMode)mode;
_pluginInterface.SavePluginConfig(_configuration);
return true;
}
public void Dispose()
{
_exportQuestPriority.UnregisterFunc();
@ -1456,5 +1559,12 @@ internal sealed class QuestionableIpc : IDisposable
_getMsqLevelLockInfo.UnregisterFunc();
_setLevelingModeEnabled.UnregisterFunc();
_isLevelingModeEnabled.UnregisterFunc();
_isQuestBlacklisted.UnregisterFunc();
_clearBlacklistedQuests.UnregisterFunc();
_removeBlacklistedQuest.UnregisterFunc();
_addBlacklistedQuest.UnregisterFunc();
_getBlacklistedQuests.UnregisterFunc();
_setMsqPriorityMode.UnregisterFunc();
_getMsqPriorityMode.UnregisterFunc();
}
}

View file

@ -2,4 +2,5 @@ namespace Questionable.Functions;
internal static class GameSignatures
{
internal const string EnqueueSnipeTask = "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 50 48 8B F9 48 8D 4C 24 ??";
}

View file

@ -179,7 +179,6 @@ internal sealed class QuestFunctions
Questionable.Model.Quest quest;
bool flag = _questRegistry.TryGetQuest(questReference.CurrentQuest, out quest);
bool flag2 = IsQuestAccepted(questReference.CurrentQuest);
_logger.LogTrace("MSQ check: QuestId={QuestId}, InRegistry={InRegistry}, Level={QuestLevel}, CurrentLevel={CurrentLevel}, IsAccepted={IsAccepted}", questReference.CurrentQuest, flag, ((int?)quest?.Info.Level) ?? (-1), currentLevel, flag2);
EClassJob valueOrDefault = ((EClassJob?)_objectTable.LocalPlayer?.ClassJob.RowId).GetValueOrDefault();
if (valueOrDefault != EClassJob.Adventurer)
{
@ -193,7 +192,7 @@ internal sealed class QuestFunctions
return new QuestReference(questInfo.QuestId, QuestManager.GetQuestSequence(questInfo.QuestId.Value), questReference.State);
}
}
if (flag && quest.Info.Level > currentLevel && !flag2)
if (flag && quest.Info.Level > currentLevel && !flag2 && _configuration.General.MsqPriority != Configuration.EMsqPriorityMode.Manual)
{
if (_lastLoggedLevelLockedMsq != questReference.CurrentQuest)
{
@ -208,10 +207,10 @@ internal sealed class QuestFunctions
List<QuestId> lockedQuests = _questData.GetLockedClassQuests();
List<QuestInfo> list = (from x in classJobQuests.Where(delegate(QuestInfo x)
{
bool num2 = x.Level <= currentLevel && x.Level <= 5;
bool flag3 = !IsQuestComplete(x.QuestId);
bool flag4 = !lockedQuests.Contains(x.QuestId);
return num2 && flag3 && flag4;
bool num3 = x.Level <= currentLevel && x.Level <= 5;
bool flag4 = !IsQuestComplete(x.QuestId);
bool flag5 = !lockedQuests.Contains(x.QuestId);
return num3 && flag4 && flag5;
})
orderby x.Level
select x).ToList();
@ -276,24 +275,33 @@ internal sealed class QuestFunctions
_loggedNoClassQuestsAvailable = false;
_loggedAdventurerClass = false;
}
if (questReference.CurrentQuest != null && !IsQuestAccepted(questReference.CurrentQuest))
bool num = questReference.CurrentQuest != null && !IsQuestAccepted(questReference.CurrentQuest);
bool flag3 = true;
if (num)
{
if (allowNewMsq)
switch (_configuration.General.MsqPriority)
{
return questReference;
case Configuration.EMsqPriorityMode.Always:
if (allowNewMsq)
{
return questReference;
}
break;
case Configuration.EMsqPriorityMode.Manual:
flag3 = false;
break;
}
questReference = QuestReference.NoQuest(questReference.State);
}
List<(ElementId, byte)> list3 = new List<(ElementId, byte)>();
for (int num = ptr->TrackedQuests.Length - 1; num >= 0; num--)
for (int num2 = ptr->TrackedQuests.Length - 1; num2 >= 0; num2--)
{
TrackingWork trackingWork = ptr->TrackedQuests[num];
TrackingWork trackingWork = ptr->TrackedQuests[num2];
switch (trackingWork.QuestType)
{
case 1:
{
ElementId elementId2 = new QuestId(ptr->NormalQuests[trackingWork.Index].QuestId);
if (_questRegistry.IsKnownQuest(elementId2))
if (_questRegistry.IsKnownQuest(elementId2) && !IsQuestBlacklisted(elementId2))
{
list3.Add((elementId2, QuestManager.GetQuestSequence(elementId2.Value)));
}
@ -356,7 +364,7 @@ internal sealed class QuestFunctions
{
return new QuestReference(elementId3, QuestManager.GetQuestSequence(elementId3.Value), questReference.State);
}
if (questReference.CurrentQuest != null)
if (flag3 && questReference.CurrentQuest != null)
{
return questReference;
}
@ -687,6 +695,10 @@ internal sealed class QuestFunctions
public unsafe bool IsReadyToAcceptQuest(ElementId questId, bool ignoreLevel = false)
{
if (IsQuestBlacklisted(questId))
{
return false;
}
_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest);
if (quest != null)
{
@ -712,15 +724,15 @@ internal sealed class QuestFunctions
{
return false;
}
goto IL_0077;
goto IL_0082;
}
}
if (IsQuestAcceptedOrComplete(questId))
{
return false;
}
goto IL_0077;
IL_0077:
goto IL_0082;
IL_0082:
if (IsQuestLocked(questId))
{
return false;
@ -752,6 +764,11 @@ internal sealed class QuestFunctions
return true;
}
public bool IsQuestBlacklisted(ElementId questId)
{
return _configuration.General.BlacklistedQuests.Contains(questId);
}
public bool IsQuestAcceptedOrComplete(ElementId elementId)
{
if (!IsQuestComplete(elementId))
@ -1069,18 +1086,17 @@ internal sealed class QuestFunctions
List<QuestSequence> list = quest?.Root?.QuestSequence;
if (list != null && list.Count > 0)
{
goto IL_0228;
goto IL_01fd;
}
}
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
{
_questData.ApplySeasonalOverride(questId, isSeasonal: true, null);
_logger.LogDebug("Quest {QuestId} unobtainable: journal genre {Genre} is 'event (seasonal)' and no quest path", questId, questInfo2.JournalGenre);
}
return true;
}
goto IL_0228;
IL_0228:
goto IL_01fd;
IL_01fd:
if (questInfo2.QuestLocks.Count > 0)
{
int num = questInfo2.QuestLocks.Count((QuestId x) => IsQuestComplete(x) || x.Equals(extraCompletedQuest));

View file

@ -95,7 +95,7 @@ internal sealed class AlliedSocietyDailyInfo : IQuestInfo
List<EClassJob> list4 = new List<EClassJob>();
list4.AddRange(classJobUtils.AsIndividualJobs(EExtendedClassJob.DoW, null));
list4.AddRange(classJobUtils.AsIndividualJobs(EExtendedClassJob.DoM, null));
readOnlyList = new _003C_003Ez__ReadOnlyList<EClassJob>(list4);
readOnlyList = new global::_003C_003Ez__ReadOnlyList<EClassJob>(list4);
}
ClassJobs = readOnlyList;
Expansion = (EExpansionVersion)beastTribe.Expansion.RowId;

View file

@ -0,0 +1,248 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Questionable.Controller;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
using Questionable.Windows.QuestComponents;
using Questionable.Windows.Utils;
namespace Questionable.Windows.ConfigComponents;
internal sealed class BlacklistConfigComponent : ConfigComponent
{
private readonly IDalamudPluginInterface _pluginInterface;
private readonly QuestSelector _questSelector;
private readonly QuestRegistry _questRegistry;
private readonly QuestFunctions _questFunctions;
private readonly QuestTooltipComponent _questTooltipComponent;
private readonly UiUtils _uiUtils;
public BlacklistConfigComponent(IDalamudPluginInterface pluginInterface, QuestSelector questSelector, QuestFunctions questFunctions, QuestRegistry questRegistry, QuestTooltipComponent questTooltipComponent, UiUtils uiUtils, Configuration configuration)
: base(pluginInterface, configuration)
{
BlacklistConfigComponent blacklistConfigComponent = this;
_pluginInterface = pluginInterface;
_questSelector = questSelector;
_questRegistry = questRegistry;
_questFunctions = questFunctions;
_questTooltipComponent = questTooltipComponent;
_uiUtils = uiUtils;
_questSelector.SuggestionPredicate = (Quest quest) => !configuration.General.BlacklistedQuests.Contains(quest.Id);
_questSelector.DefaultPredicate = (Quest quest) => !quest.Root.Disabled && questFunctions.IsQuestAccepted(quest.Id);
_questSelector.QuestSelected = delegate(Quest quest)
{
configuration.General.BlacklistedQuests.Add(quest.Id);
blacklistConfigComponent.Save();
};
}
public override void DrawTab()
{
using ImRaii.IEndObject endObject = ImRaii.TabItem("Blacklist###QuestBlacklist");
if (!endObject)
{
return;
}
ImGui.TextWrapped("Blacklisted quests will never be automatically picked up or executed by Questionable.");
ImGui.TextWrapped("Use this to permanently skip specific quests you don't want to do.");
ImGui.Separator();
DrawCurrentlyAcceptedQuests();
HashSet<ElementId> blacklistedQuests = base.Configuration.General.BlacklistedQuests;
ImU8String text = new ImU8String(22, 1);
text.AppendLiteral("Blacklisted Quests (");
text.AppendFormatted(blacklistedQuests.Count);
text.AppendLiteral("):");
ImGui.Text(text);
using (ImRaii.IEndObject endObject2 = ImRaii.Child("BlacklistedQuestsList", new Vector2(-1f, 200f), border: true))
{
if (endObject2)
{
if (blacklistedQuests.Count > 0)
{
ElementId elementId = null;
int num = 0;
foreach (ElementId item in blacklistedQuests.OrderBy((ElementId x) => x.ToString()))
{
if (!_questRegistry.TryGetQuest(item, out Quest quest))
{
ImU8String id = new ImU8String(5, 1);
id.AppendLiteral("Quest");
id.AppendFormatted(item);
using (ImRaii.PushId(id))
{
ImGui.AlignTextToFramePadding();
Vector4 col = new Vector4(0.7f, 0.7f, 0.7f, 1f);
ImU8String text2 = new ImU8String(16, 1);
text2.AppendLiteral("Unknown Quest (");
text2.AppendFormatted(item);
text2.AppendLiteral(")");
ImGui.TextColored(in col, text2);
ImGui.SameLine(ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X - ImGui.GetStyle().FramePadding.X * 2f);
if (ImGuiComponents.IconButton($"##Remove{num}", FontAwesomeIcon.Times))
{
elementId = item;
}
}
num++;
continue;
}
ImU8String id2 = new ImU8String(5, 1);
id2.AppendLiteral("Quest");
id2.AppendFormatted(item);
using (ImRaii.PushId(id2))
{
(Vector4, FontAwesomeIcon, string) questStyle = _uiUtils.GetQuestStyle(item);
bool flag;
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
ImGui.AlignTextToFramePadding();
ImGui.TextColored(in questStyle.Item1, questStyle.Item2.ToIconString());
flag = ImGui.IsItemHovered();
}
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(quest.Info.Name);
flag |= ImGui.IsItemHovered();
if (flag)
{
_questTooltipComponent.Draw(quest.Info);
}
ImGui.SameLine(ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X - ImGui.GetStyle().FramePadding.X * 2f);
if (ImGuiComponents.IconButton($"##Remove{num}", FontAwesomeIcon.Times))
{
elementId = item;
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Remove from blacklist");
}
}
num++;
}
if (elementId != null)
{
base.Configuration.General.BlacklistedQuests.Remove(elementId);
Save();
}
}
else
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.7f, 0.7f, 0.7f, 1f));
ImGui.TextWrapped("No quests blacklisted. Use the dropdown above or add from currently accepted quests.");
ImGui.PopStyleColor();
}
}
}
_questSelector.DrawSelection();
if (blacklistedQuests.Count <= 0)
{
return;
}
using (ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Trash, "Clear All"))
{
base.Configuration.General.BlacklistedQuests.Clear();
Save();
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Hold CTRL to enable this button.");
}
}
private void DrawCurrentlyAcceptedQuests()
{
List<Quest> currentlyAcceptedQuests = GetCurrentlyAcceptedQuests();
ImGui.Text("Currently Accepted Quests:");
using (ImRaii.IEndObject endObject = ImRaii.Child("AcceptedQuestsList", new Vector2(-1f, 100f), border: true))
{
if (endObject)
{
if (currentlyAcceptedQuests.Count > 0)
{
foreach (Quest item in currentlyAcceptedQuests)
{
ImU8String id = new ImU8String(13, 1);
id.AppendLiteral("AcceptedQuest");
id.AppendFormatted(item.Id);
using (ImRaii.PushId(id))
{
(Vector4, FontAwesomeIcon, string) questStyle = _uiUtils.GetQuestStyle(item.Id);
bool flag = false;
bool flag2 = base.Configuration.General.BlacklistedQuests.Contains(item.Id);
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
ImGui.AlignTextToFramePadding();
ImGui.TextColored(flag2 ? new Vector4(0.5f, 0.5f, 0.5f, 1f) : questStyle.Item1, questStyle.Item2.ToIconString());
flag = ImGui.IsItemHovered();
}
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.TextColored(flag2 ? new Vector4(0.7f, 0.7f, 0.7f, 1f) : new Vector4(1f, 1f, 1f, 1f), item.Info.Name);
if (flag | ImGui.IsItemHovered())
{
_questTooltipComponent.Draw(item.Info);
}
ImGui.SameLine(ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize(FontAwesomeIcon.Ban.ToIconString()).X - ImGui.GetStyle().FramePadding.X * 2f);
using (ImRaii.Disabled(flag2))
{
if (ImGuiComponents.IconButton($"##Blacklist{item.Id}", FontAwesomeIcon.Ban))
{
base.Configuration.General.BlacklistedQuests.Add(item.Id);
Save();
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip(flag2 ? "Quest already blacklisted" : "Add this quest to blacklist");
}
}
}
}
else
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.7f, 0.7f, 0.7f, 1f));
ImGui.TextWrapped("No quests currently accepted");
ImGui.PopStyleColor();
}
}
}
ImGui.Spacing();
}
private List<Quest> GetCurrentlyAcceptedQuests()
{
List<Quest> list = new List<Quest>();
try
{
foreach (Quest allQuest in _questRegistry.AllQuests)
{
if (_questFunctions.IsQuestAccepted(allQuest.Id))
{
list.Add(allQuest);
}
}
list.Sort((Quest a, Quest b) => string.Compare(a.Info.Name, b.Info.Name, StringComparison.OrdinalIgnoreCase));
}
catch (Exception)
{
list.Clear();
}
return list;
}
}

View file

@ -22,6 +22,8 @@ internal sealed class DebugConfigComponent : ConfigComponent
}
ImGui.TextColored(ImGuiColors.DalamudRed, "Enabling any option here may cause unexpected behavior. Use at your own risk.");
ImGui.Separator();
DrawChocoboSettings();
ImGui.Separator();
bool v = base.Configuration.Advanced.DebugOverlay;
if (ImGui.Checkbox("Enable debug overlay", ref v))
{
@ -120,4 +122,55 @@ internal sealed class DebugConfigComponent : ConfigComponent
ImGuiComponents.HelpMarker("When enabled, Questionable will not attempt to turn-in and complete quests. This will do everything automatically except the final turn-in step.");
}
}
private void DrawChocoboSettings()
{
ImGui.Text("Chocobo Settings");
using (ImRaii.PushIndent())
{
ImGui.AlignTextToFramePadding();
ImGui.Text("Chocobo Name:");
ImGui.SameLine();
string buf = base.Configuration.Advanced.ChocoboName;
ImGui.SetNextItemWidth(200f);
if (ImGui.InputText("##ChocoboName", ref buf, 20))
{
string chocoboName = FormatChocoboName(buf);
base.Configuration.Advanced.ChocoboName = chocoboName;
Save();
}
if (!string.IsNullOrEmpty(buf) && buf.Length < 2)
{
ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudRed, "Name must be at least 2 characters");
}
ImGui.SameLine();
ImGuiComponents.HelpMarker("The name to give your chocobo during the 'My Little Chocobo' quest.\nMust be 2-20 characters. First letter will be capitalized.\nIf left empty, defaults to 'Kweh'.");
}
}
private static string FormatChocoboName(string name)
{
if (string.IsNullOrEmpty(name))
{
return string.Empty;
}
name = name.Trim();
if (name.Length == 0)
{
return string.Empty;
}
string text = name.Substring(0, 1).ToUpperInvariant();
string text2;
if (name.Length <= 1)
{
text2 = string.Empty;
}
else
{
string text3 = name;
text2 = text3.Substring(1, text3.Length - 1).ToLowerInvariant();
}
return text + text2;
}
}

View file

@ -35,6 +35,8 @@ internal sealed class GeneralConfigComponent : ConfigComponent
private readonly string[] _grandCompanyNames = new string[4] { "None (manually pick quest)", "Maelstrom", "Twin Adder", "Immortal Flames" };
private readonly string[] _msqPriorityNames = new string[3] { "Always (MSQ first)", "After Tracked (MSQ last)", "Manual (never auto-accept MSQ)" };
private readonly EClassJob[] _classJobIds;
private readonly string[] _classJobNames;
@ -159,6 +161,28 @@ internal sealed class GeneralConfigComponent : ConfigComponent
base.Configuration.General.GrandCompany = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)currentItem2;
Save();
}
int currentItem3 = (int)base.Configuration.General.MsqPriority;
if (ImGui.Combo((ImU8String)"MSQ Priority", ref currentItem3, (ReadOnlySpan<string>)_msqPriorityNames, _msqPriorityNames.Length))
{
base.Configuration.General.MsqPriority = (Configuration.EMsqPriorityMode)currentItem3;
Save();
}
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString());
}
if (ImGui.IsItemHovered())
{
using (ImRaii.Tooltip())
{
ImGui.Text("Controls when the Main Scenario Quest (MSQ) is automatically accepted:");
ImGui.Spacing();
ImGui.BulletText("Always: Auto-accept MSQ immediately");
ImGui.BulletText("After Tracked: Complete accepted quests first, then pick up MSQ");
ImGui.BulletText("Manual: Never auto-accept MSQ, only do manually accepted quests");
}
}
int num3 = Array.IndexOf(_classJobIds, base.Configuration.General.CombatJob);
if (num3 == -1)
{
@ -253,10 +277,48 @@ internal sealed class GeneralConfigComponent : ConfigComponent
ImGui.Text("Questing");
using (ImRaii.PushIndent())
{
bool v7 = base.Configuration.General.CinemaMode;
if (ImGui.Checkbox("Cinema Mode (watch cutscenes)", ref v7))
bool v7 = base.Configuration.General.AutoSolveQte;
if (ImGui.Checkbox("Automatically solve Quick Time Events (QTEs)", ref v7))
{
base.Configuration.General.CinemaMode = v7;
base.Configuration.General.AutoSolveQte = v7;
Save();
}
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString());
}
if (ImGui.IsItemHovered())
{
using (ImRaii.Tooltip())
{
ImGui.Text("Automatically mashes the button during Active Time Maneuver (ATM)");
ImGui.Text("prompts that appear in certain duties and quest battles.");
}
}
bool v8 = base.Configuration.General.AutoSnipe;
if (ImGui.Checkbox("Automatically complete snipe quests", ref v8))
{
base.Configuration.General.AutoSnipe = v8;
Save();
}
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString());
}
if (ImGui.IsItemHovered())
{
using (ImRaii.Tooltip())
{
ImGui.Text("Automatically completes sniping minigames introduced in Stormblood.");
ImGui.Text("When enabled, snipe targets are instantly hit without manual aiming.");
}
}
bool v9 = base.Configuration.General.CinemaMode;
if (ImGui.Checkbox("Cinema Mode (watch cutscenes)", ref v9))
{
base.Configuration.General.CinemaMode = v9;
Save();
}
ImGui.SameLine();
@ -275,16 +337,16 @@ internal sealed class GeneralConfigComponent : ConfigComponent
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.7f, 1f), "Recommended for first-time story playthroughs.");
}
}
bool v8 = base.Configuration.General.ConfigureTextAdvance;
if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v8))
bool v10 = base.Configuration.General.ConfigureTextAdvance;
if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v10))
{
base.Configuration.General.ConfigureTextAdvance = v8;
base.Configuration.General.ConfigureTextAdvance = v10;
Save();
}
bool v9 = base.Configuration.General.SkipLowPriorityDuties;
if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v9))
bool v11 = base.Configuration.General.SkipLowPriorityDuties;
if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v11))
{
base.Configuration.General.SkipLowPriorityDuties = v9;
base.Configuration.General.SkipLowPriorityDuties = v11;
Save();
}
ImGui.SameLine();
@ -312,10 +374,10 @@ internal sealed class GeneralConfigComponent : ConfigComponent
}
}
ImGui.Spacing();
bool v10 = base.Configuration.General.AutoStepRefreshEnabled;
if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v10))
bool v12 = base.Configuration.General.AutoStepRefreshEnabled;
if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v12))
{
base.Configuration.General.AutoStepRefreshEnabled = v10;
base.Configuration.General.AutoStepRefreshEnabled = v12;
Save();
}
ImGui.SameLine();
@ -331,20 +393,20 @@ internal sealed class GeneralConfigComponent : ConfigComponent
ImGui.Text("This helps resume automated quest completion when interruptions occur.");
}
}
using (ImRaii.Disabled(!v10))
using (ImRaii.Disabled(!v12))
{
ImGui.Indent();
int v11 = base.Configuration.General.AutoStepRefreshDelaySeconds;
int v13 = base.Configuration.General.AutoStepRefreshDelaySeconds;
ImGui.SetNextItemWidth(150f);
if (ImGui.SliderInt("Refresh delay (seconds)", ref v11, 10, 180))
if (ImGui.SliderInt("Refresh delay (seconds)", ref v13, 10, 180))
{
base.Configuration.General.AutoStepRefreshDelaySeconds = v11;
base.Configuration.General.AutoStepRefreshDelaySeconds = v13;
Save();
}
Vector4 col = new Vector4(0.7f, 0.7f, 0.7f, 1f);
ImU8String text4 = new ImU8String(77, 1);
text4.AppendLiteral("Quest steps will refresh automatically after ");
text4.AppendFormatted(v11);
text4.AppendFormatted(v13);
text4.AppendLiteral(" seconds if no progress is made.");
ImGui.TextColored(in col, text4);
ImGui.Unindent();
@ -352,16 +414,48 @@ internal sealed class GeneralConfigComponent : ConfigComponent
ImGui.Spacing();
ImGui.Separator();
ImGui.Text("Priority Quest Management");
bool v12 = base.Configuration.General.ClearPriorityQuestsOnLogout;
if (ImGui.Checkbox("Clear priority quests on character logout", ref v12))
bool v14 = base.Configuration.General.PersistPriorityQuestsBetweenSessions;
if (ImGui.Checkbox("Save priority quests between sessions", ref v14))
{
base.Configuration.General.ClearPriorityQuestsOnLogout = v12;
base.Configuration.General.PersistPriorityQuestsBetweenSessions = v14;
Save();
}
bool v13 = base.Configuration.General.ClearPriorityQuestsOnCompletion;
if (ImGui.Checkbox("Remove priority quests when completed", ref v13))
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
base.Configuration.General.ClearPriorityQuestsOnCompletion = v13;
ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString());
}
if (ImGui.IsItemHovered())
{
using (ImRaii.Tooltip())
{
ImGui.Text("When enabled, your priority quest list will be saved and restored");
ImGui.Text("when the plugin reloads or the game restarts.");
}
}
bool v15 = base.Configuration.General.ClearPriorityQuestsOnLogout;
if (ImGui.Checkbox("Clear priority quests on character logout", ref v15))
{
base.Configuration.General.ClearPriorityQuestsOnLogout = v15;
Save();
}
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString());
}
if (ImGui.IsItemHovered())
{
using (ImRaii.Tooltip())
{
ImGui.Text("Clears the priority queue when your character logs out.");
ImGui.Text("This also clears the saved list if persistence is enabled.");
}
}
bool v16 = base.Configuration.General.ClearPriorityQuestsOnCompletion;
if (ImGui.Checkbox("Remove priority quests when completed", ref v16))
{
base.Configuration.General.ClearPriorityQuestsOnCompletion = v16;
Save();
}
ImGui.SameLine();

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
@ -13,7 +12,6 @@ using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Questionable.Controller;
using Questionable.External;
namespace Questionable.Windows.ConfigComponents;
@ -58,7 +56,7 @@ internal sealed class PluginConfigComponent : ConfigComponent
private readonly ICommandManager _commandManager;
public PluginConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration, CombatController combatController, UiUtils uiUtils, ICommandManager commandManager, AutomatonIpc automatonIpc, PandorasBoxIpc pandorasBoxIpc)
public PluginConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration, CombatController combatController, UiUtils uiUtils, ICommandManager commandManager)
: base(pluginInterface, configuration)
{
_configuration = configuration;
@ -66,34 +64,12 @@ internal sealed class PluginConfigComponent : ConfigComponent
_pluginInterface = pluginInterface;
_uiUtils = uiUtils;
_commandManager = commandManager;
PluginInfo[] obj = new PluginInfo[5]
_recommendedPlugins = new global::_003C_003Ez__ReadOnlyArray<PluginInfo>(new PluginInfo[3]
{
new PluginInfo("Artisan", "Artisan", "Handles automatic crafting for quests that require\ncrafted items.", new Uri("https://github.com/PunishXIV/Artisan"), new Uri("https://puni.sh/api/plugins")),
new PluginInfo("AutoDuty", "AutoDuty", "Automatically handles dungeon and trial completion during\nMain Scenario Quest progression.", new Uri("https://github.com/erdelf/AutoDuty"), new Uri("https://puni.sh/api/repository/erdelf")),
null,
null,
null
};
Uri websiteUri = new Uri("https://github.com/Jaksuhn/Automaton");
Uri dalamudRepositoryUri = new Uri("https://puni.sh/api/repository/croizat");
int num = 1;
List<PluginDetailInfo> list = new List<PluginDetailInfo>(num);
CollectionsMarshal.SetCount(list, num);
Span<PluginDetailInfo> span = CollectionsMarshal.AsSpan(list);
int index = 0;
span[index] = new PluginDetailInfo("'Sniper no sniping' enabled", "Automatically completes sniping tasks introduced in Stormblood", () => automatonIpc.IsAutoSnipeEnabled);
obj[2] = new PluginInfo("CBT (formerly known as Automaton)", "Automaton", "Automaton is a collection of automation-related tweaks.", websiteUri, dalamudRepositoryUri, "/cbt", list);
Uri websiteUri2 = new Uri("https://github.com/PunishXIV/PandorasBox");
Uri dalamudRepositoryUri2 = new Uri("https://puni.sh/api/plugins");
index = 1;
List<PluginDetailInfo> list2 = new List<PluginDetailInfo>(index);
CollectionsMarshal.SetCount(list2, index);
Span<PluginDetailInfo> span2 = CollectionsMarshal.AsSpan(list2);
num = 0;
span2[num] = new PluginDetailInfo("'Auto Active Time Maneuver' enabled", "Automatically completes active time maneuvers in\nsingle player instances, trials and raids\"", () => pandorasBoxIpc.IsAutoActiveTimeManeuverEnabled);
obj[3] = new PluginInfo("Pandora's Box", "PandorasBox", "Pandora's Box is a collection of tweaks.", websiteUri2, dalamudRepositoryUri2, "/pandora", list2);
obj[4] = new PluginInfo("QuestMap", "QuestMap", "Displays quest objectives and markers on the map for\nbetter navigation and tracking.", new Uri("https://github.com/rreminy/QuestMap"), null);
_recommendedPlugins = new global::_003C_003Ez__ReadOnlyArray<PluginInfo>(obj);
new PluginInfo("QuestMap", "QuestMap", "Displays quest objectives and markers on the map for\nbetter navigation and tracking.", new Uri("https://github.com/rreminy/QuestMap"), null)
});
}
public override void DrawTab()

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
@ -39,6 +40,8 @@ internal sealed class StopConditionComponent : ConfigComponent
private readonly QuestController _questController;
private ElementId? _draggedItem;
public StopConditionComponent(IDalamudPluginInterface pluginInterface, QuestSelector questSelector, QuestFunctions questFunctions, QuestRegistry questRegistry, QuestTooltipComponent questTooltipComponent, UiUtils uiUtils, IObjectTable objectTable, IClientState clientState, QuestController questController, Configuration configuration)
: base(pluginInterface, configuration)
{
@ -167,112 +170,172 @@ internal sealed class StopConditionComponent : ConfigComponent
ImGui.SetTooltip("Hold CTRL to enable this button.");
}
ImGui.Separator();
ImGui.Text("(Drag arrow buttons to reorder)");
}
Quest quest = null;
for (int i = 0; i < questsToStopAfter.Count; i++)
DrawStopQuestList(questsToStopAfter);
}
}
private void DrawStopQuestList(List<ElementId> questsToStopAfter)
{
Quest quest = null;
Quest quest2 = null;
int index = 0;
float x = ImGui.GetContentRegionAvail().X;
List<(Vector2, Vector2)> list = new List<(Vector2, Vector2)>();
for (int i = 0; i < questsToStopAfter.Count; i++)
{
Vector2 item = ImGui.GetCursorScreenPos() + new Vector2(0f, (0f - ImGui.GetStyle().ItemSpacing.Y) / 2f);
ElementId elementId = questsToStopAfter[i];
if (!_questRegistry.TryGetQuest(elementId, out Quest quest3))
{
ElementId elementId = questsToStopAfter[i];
if (!_questRegistry.TryGetQuest(elementId, out Quest quest2))
continue;
}
ImU8String id = new ImU8String(5, 1);
id.AppendLiteral("Quest");
id.AppendFormatted(elementId);
using (ImRaii.PushId(id))
{
ImGui.AlignTextToFramePadding();
ImU8String text = new ImU8String(1, 1);
text.AppendFormatted(i + 1);
text.AppendLiteral(".");
ImGui.Text(text);
ImGui.SameLine();
(Vector4, FontAwesomeIcon, string) questStyle = _uiUtils.GetQuestStyle(elementId);
bool flag;
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
continue;
}
ImU8String id = new ImU8String(5, 1);
id.AppendLiteral("Quest");
id.AppendFormatted(elementId);
using (ImRaii.PushId(id))
{
(Vector4, FontAwesomeIcon, string) questStyle = _uiUtils.GetQuestStyle(elementId);
bool flag;
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
ImGui.AlignTextToFramePadding();
ImGui.TextColored(in questStyle.Item1, questStyle.Item2.ToIconString());
flag = ImGui.IsItemHovered();
}
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.Text(quest2.Info.Name);
flag |= ImGui.IsItemHovered();
if (flag)
ImGui.TextColored(in questStyle.Item1, questStyle.Item2.ToIconString());
flag = ImGui.IsItemHovered();
}
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.Text(quest3.Info.Name);
flag |= ImGui.IsItemHovered();
if (flag)
{
_questTooltipComponent.Draw(quest3.Info);
}
string text2 = elementId.ToString();
int currentItem = (int)base.Configuration.Stop.QuestStopModes.GetValueOrDefault(text2, Questionable.Configuration.EStopConditionMode.Stop);
ImGui.SameLine();
ImGui.SetNextItemWidth(70f);
ImU8String label = new ImU8String(6, 1);
label.AppendLiteral("##Mode");
label.AppendFormatted(text2);
if (ImGui.Combo(label, ref currentItem, (ReadOnlySpan<string>)StopModeNames, StopModeNames.Length))
{
base.Configuration.Stop.QuestStopModes[text2] = (Configuration.EStopConditionMode)currentItem;
Save();
}
ImGui.SameLine();
base.Configuration.Stop.QuestSequences.TryGetValue(text2, out var value);
bool v = value.HasValue;
ImU8String label2 = new ImU8String(8, 1);
label2.AppendLiteral("##UseSeq");
label2.AppendFormatted(text2);
if (ImGui.Checkbox(label2, ref v))
{
if (v)
{
_questTooltipComponent.Draw(quest2.Info);
base.Configuration.Stop.QuestSequences[text2] = 1;
}
string text3 = elementId.ToString();
int currentItem3 = (int)base.Configuration.Stop.QuestStopModes.GetValueOrDefault(text3, Questionable.Configuration.EStopConditionMode.Stop);
ImGui.SameLine();
ImGui.SetNextItemWidth(70f);
ImU8String label = new ImU8String(6, 1);
label.AppendLiteral("##Mode");
label.AppendFormatted(text3);
if (ImGui.Combo(label, ref currentItem3, (ReadOnlySpan<string>)StopModeNames, StopModeNames.Length))
else
{
if (currentItem3 == 0)
{
quest = quest2;
}
else
{
base.Configuration.Stop.QuestStopModes[text3] = (Configuration.EStopConditionMode)currentItem3;
Save();
}
base.Configuration.Stop.QuestSequences.Remove(text2);
}
Save();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Stop at specific sequence (unchecked = stop on quest completion)");
}
using (ImRaii.Disabled(!v))
{
ImGui.SameLine();
base.Configuration.Stop.QuestSequences.TryGetValue(text3, out var value);
bool v2 = value.HasValue;
ImU8String label2 = new ImU8String(8, 1);
label2.AppendLiteral("##UseSeq");
label2.AppendFormatted(text3);
if (ImGui.Checkbox(label2, ref v2))
ImGui.Text("Seq:");
ImGui.SameLine();
ImGui.SetNextItemWidth(85f);
int data = value ?? 1;
ImU8String label3 = new ImU8String(10, 1);
label3.AppendLiteral("##SeqValue");
label3.AppendFormatted(text2);
if (ImGui.InputInt(label3, ref data, 1, 1) && v)
{
if (v2)
{
base.Configuration.Stop.QuestSequences[text3] = 1;
}
else
{
base.Configuration.Stop.QuestSequences.Remove(text3);
}
base.Configuration.Stop.QuestSequences[text2] = Math.Max(0, Math.Min(255, data));
Save();
}
if (ImGui.IsItemHovered())
}
if (questsToStopAfter.Count > 1)
{
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.SetTooltip("Stop at specific sequence (unchecked = stop on quest completion)");
ImGui.SameLine(ImGui.GetContentRegionAvail().X + ImGui.GetStyle().WindowPadding.X - ImGui.CalcTextSize(FontAwesomeIcon.ArrowsUpDown.ToIconString()).X - ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X - ImGui.GetStyle().FramePadding.X * 4f - ImGui.GetStyle().ItemSpacing.X);
}
using (ImRaii.Disabled(!v2))
if (_draggedItem == elementId)
{
ImGui.SameLine();
ImGui.Text("Seq:");
ImGui.SameLine();
ImGui.SetNextItemWidth(85f);
int data3 = value ?? 1;
ImU8String label3 = new ImU8String(10, 1);
label3.AppendLiteral("##SeqValue");
label3.AppendFormatted(text3);
if (ImGui.InputInt(label3, ref data3, 1, 1) && v2)
{
base.Configuration.Stop.QuestSequences[text3] = Math.Max(0, Math.Min(255, data3));
Save();
}
ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown, ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.ButtonActive)));
}
else
{
ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown);
}
if (_draggedItem == null && ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
{
_draggedItem = elementId;
}
ImGui.SameLine();
}
else
{
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.SameLine(ImGui.GetContentRegionAvail().X + ImGui.GetStyle().WindowPadding.X - ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X - ImGui.GetStyle().FramePadding.X * 2f);
}
if (ImGuiComponents.IconButton($"##Remove{i}", FontAwesomeIcon.Times))
{
quest = quest2;
}
}
if (ImGuiComponents.IconButton($"##Remove{i}", FontAwesomeIcon.Times))
{
quest = quest3;
}
}
if (quest != null)
Vector2 item2 = new Vector2(item.X + x, ImGui.GetCursorScreenPos().Y - ImGui.GetStyle().ItemSpacing.Y + 2f);
list.Add((item, item2));
}
if (!ImGui.IsMouseDragging(ImGuiMouseButton.Left))
{
_draggedItem = null;
}
else if (_draggedItem != null)
{
int num = questsToStopAfter.FindIndex((ElementId elementId2) => elementId2 == _draggedItem);
if (num >= 0 && num < list.Count)
{
string key2 = quest.Id.ToString();
base.Configuration.Stop.QuestsToStopAfter.Remove(quest.Id);
base.Configuration.Stop.QuestSequences.Remove(key2);
base.Configuration.Stop.QuestStopModes.Remove(key2);
Save();
var (pMin, pMax) = list[num];
ImGui.GetWindowDrawList().AddRect(pMin, pMax, ImGui.GetColorU32(ImGuiColors.DalamudGrey), 3f, ImDrawFlags.RoundCornersAll);
int num2 = list.FindIndex(((Vector2 TopLeft, Vector2 BottomRight) tuple2) => ImGui.IsMouseHoveringRect(tuple2.TopLeft, tuple2.BottomRight, clip: true));
if (num2 >= 0 && num != num2)
{
quest2 = (_questRegistry.TryGetQuest(_draggedItem, out Quest quest4) ? quest4 : null);
index = num2;
}
}
}
if (quest != null)
{
string key = quest.Id.ToString();
base.Configuration.Stop.QuestsToStopAfter.Remove(quest.Id);
base.Configuration.Stop.QuestSequences.Remove(key);
base.Configuration.Stop.QuestStopModes.Remove(key);
Save();
}
if (quest2 != null)
{
questsToStopAfter.Remove(quest2.Id);
questsToStopAfter.Insert(index, quest2.Id);
Save();
}
}
private void DrawCurrentlyAcceptedQuests()

View file

@ -0,0 +1,369 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Windows.JournalComponents;
internal sealed class DutyJournalComponent
{
private sealed record DutyCategory(string Name, List<DutyInfo> Duties);
private sealed record DutyCounts(int Unlocked, int Completed, int CompletionTrackable, int Total)
{
public DutyCounts()
: this(0, 0, 0, 0)
{
}
}
private readonly DutyUnlockData _dutyUnlockData;
private readonly QuestData _questData;
private readonly QuestFunctions _questFunctions;
private readonly UiUtils _uiUtils;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly ILogger<DutyJournalComponent> _logger;
private List<DutyCategory> _filteredCategories = new List<DutyCategory>();
private Dictionary<string, DutyCounts> _categoryCounts = new Dictionary<string, DutyCounts>();
private string _searchText = string.Empty;
private bool _showLockedOnly;
private bool _showUnlockedOnly;
public DutyJournalComponent(DutyUnlockData dutyUnlockData, QuestData questData, QuestFunctions questFunctions, UiUtils uiUtils, IDalamudPluginInterface pluginInterface, ILogger<DutyJournalComponent> logger)
{
_dutyUnlockData = dutyUnlockData;
_questData = questData;
_questFunctions = questFunctions;
_uiUtils = uiUtils;
_pluginInterface = pluginInterface;
_logger = logger;
}
public void DrawDuties()
{
using ImRaii.IEndObject endObject = ImRaii.TabItem("Duties");
if (!endObject)
{
return;
}
if (ImGui.CollapsingHeader("Explanation", ImGuiTreeNodeFlags.DefaultOpen))
{
ImGui.Text("The list below shows all duties and their unlock status.");
ImGui.BulletText("'Unlocked' shows duties you have access to.");
ImGui.BulletText("'Completed' shows duties you have finished at least once.");
ImGui.BulletText("Click on a duty to see which quest unlocks it.");
ImGui.Spacing();
ImGui.Separator();
ImGui.Spacing();
}
DrawFilterControls();
if (_filteredCategories.Count > 0)
{
using (ImRaii.IEndObject endObject2 = ImRaii.Table("Duties", 3, ImGuiTableFlags.NoSavedSettings))
{
if (!endObject2)
{
return;
}
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.NoHide);
ImGui.TableSetupColumn("Unlocked", ImGuiTableColumnFlags.WidthFixed, 120f * ImGui.GetIO().FontGlobalScale);
ImGui.TableSetupColumn("Completed", ImGuiTableColumnFlags.WidthFixed, 120f * ImGui.GetIO().FontGlobalScale);
ImGui.TableHeadersRow();
foreach (DutyCategory filteredCategory in _filteredCategories)
{
DrawCategory(filteredCategory);
}
return;
}
}
ImGui.Text("No duties match your search.");
}
private void DrawFilterControls()
{
if (ImGui.Checkbox("Locked Only", ref _showLockedOnly))
{
if (_showLockedOnly)
{
_showUnlockedOnly = false;
}
UpdateFilter();
}
ImGui.SameLine();
if (ImGui.Checkbox("Unlocked Only", ref _showUnlockedOnly))
{
if (_showUnlockedOnly)
{
_showLockedOnly = false;
}
UpdateFilter();
}
ImGui.SameLine();
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
if (ImGui.InputTextWithHint(string.Empty, "Search duties...", ref _searchText, 256))
{
UpdateFilter();
}
}
private void DrawCategory(DutyCategory category)
{
DutyCounts valueOrDefault = _categoryCounts.GetValueOrDefault(category.Name, new DutyCounts());
if (valueOrDefault.Total == 0)
{
return;
}
ImGui.TableNextRow();
ImGui.TableNextColumn();
bool num = ImGui.TreeNodeEx(category.Name, ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn();
DrawCount(valueOrDefault.Unlocked, valueOrDefault.Total);
ImGui.TableNextColumn();
DrawCount(valueOrDefault.Completed, valueOrDefault.CompletionTrackable);
if (!num)
{
return;
}
foreach (DutyInfo duty in category.Duties)
{
DrawDuty(duty);
}
ImGui.TreePop();
}
private void DrawDuty(DutyInfo duty)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
string text = $"Lv{duty.Level} {duty.Name}";
if (duty.ItemLevel > 0)
{
text += $" (i{duty.ItemLevel})";
}
ImGui.TreeNodeEx(text, ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.SpanFullWidth);
if (ImGui.IsItemHovered())
{
DrawDutyTooltip(duty);
}
ImGui.TableNextColumn();
DrawDutyStatus(duty.IsUnlocked);
ImGui.TableNextColumn();
DrawDutyStatus(duty.IsCompleted);
}
private void DrawDutyTooltip(DutyInfo duty)
{
using ImRaii.IEndObject endObject = ImRaii.Tooltip();
if (!endObject)
{
return;
}
ImGui.TextColored(ImGuiColors.DalamudWhite, duty.Name);
Vector4 col = ImGuiColors.DalamudGrey;
ImU8String text = new ImU8String(12, 1);
text.AppendLiteral("Content ID: ");
text.AppendFormatted(duty.ContentFinderConditionId);
ImGui.TextColored(in col, text);
ImGui.Separator();
ImU8String text2 = new ImU8String(16, 1);
text2.AppendLiteral("Level Required: ");
text2.AppendFormatted(duty.Level);
ImGui.Text(text2);
if (duty.ItemLevel > 0)
{
ImU8String text3 = new ImU8String(21, 1);
text3.AppendLiteral("Item Level Required: ");
text3.AppendFormatted(duty.ItemLevel);
ImGui.Text(text3);
}
ImU8String text4 = new ImU8String(6, 1);
text4.AppendLiteral("Type: ");
text4.AppendFormatted(duty.ContentTypeName);
ImGui.Text(text4);
if (duty.IsHighEndDuty)
{
ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudOrange, "(High-End)");
}
ImGui.Separator();
if (duty.IsUnlocked)
{
ImGui.TextColored(ImGuiColors.ParsedGreen, "Status: Unlocked");
}
else
{
ImGui.TextColored(ImGuiColors.DalamudRed, "Status: Locked");
}
if (duty.UnlockQuests.Count > 0)
{
ImGui.Separator();
ImGui.Text("Unlock Quest(s):");
{
foreach (QuestId unlockQuest in duty.UnlockQuests)
{
if (_questData.TryGetQuestInfo(unlockQuest, out IQuestInfo questInfo))
{
var (color, icon, _) = _uiUtils.GetQuestStyle(unlockQuest);
_uiUtils.ChecklistItem($"{questInfo.Name} ({unlockQuest})", color, icon);
}
else
{
_uiUtils.ChecklistItem($"Unknown Quest ({unlockQuest})", ImGuiColors.DalamudGrey, FontAwesomeIcon.Question);
}
}
return;
}
}
ImGui.Separator();
ImGui.TextColored(ImGuiColors.DalamudGrey, "No unlock quest data available.");
}
private void DrawDutyStatus(bool? status)
{
float num;
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
num = ImGui.GetColumnWidth() / 2f - ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
}
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num);
if (!status.HasValue)
{
_uiUtils.ChecklistItem(string.Empty, ImGuiColors.DalamudGrey, FontAwesomeIcon.Minus);
}
else if (status.Value)
{
_uiUtils.ChecklistItem(string.Empty, ImGuiColors.ParsedGreen, FontAwesomeIcon.Check);
}
else
{
_uiUtils.ChecklistItem(string.Empty, ImGuiColors.DalamudRed, FontAwesomeIcon.Times);
}
}
private static void DrawCount(int count, int total)
{
string text = 9999.ToString(CultureInfo.CurrentCulture);
ImGui.PushFont(UiBuilder.MonoFont);
if (total == 0)
{
Vector4 col = ImGuiColors.DalamudGrey;
ImU8String text2 = new ImU8String(3, 2);
text2.AppendFormatted("-".PadLeft(text.Length));
text2.AppendLiteral(" / ");
text2.AppendFormatted("-".PadLeft(text.Length));
ImGui.TextColored(in col, text2);
}
else
{
string text3 = count.ToString(CultureInfo.CurrentCulture).PadLeft(text.Length) + " / " + total.ToString(CultureInfo.CurrentCulture).PadLeft(text.Length);
if (count == total)
{
ImGui.TextColored(ImGuiColors.ParsedGreen, text3);
}
else
{
ImGui.TextUnformatted(text3);
}
}
ImGui.PopFont();
}
public void UpdateFilter()
{
List<DutyCategory> list = new List<DutyCategory>();
AddCategory(list, "Dungeons", _dutyUnlockData.GetDungeons());
AddCategory(list, "Hard Dungeons", _dutyUnlockData.GetHardDungeons());
AddCategory(list, "Guildhests", _dutyUnlockData.GetGuildhests());
AddCategory(list, "Trials", _dutyUnlockData.GetTrials());
AddCategory(list, "Hard Trials", _dutyUnlockData.GetHardTrials());
AddCategory(list, "Extreme Trials", _dutyUnlockData.GetExtremeTrials());
AddCategory(list, "Unreal Trials", _dutyUnlockData.GetUnrealTrials());
AddCategory(list, "Normal Raids", _dutyUnlockData.GetNormalRaids());
AddCategory(list, "Savage Raids", _dutyUnlockData.GetSavageRaids());
AddCategory(list, "Alliance Raids", _dutyUnlockData.GetAllianceRaids());
AddCategory(list, "Chaotic Alliance Raids", _dutyUnlockData.GetChaoticAllianceRaids());
AddCategory(list, "Ultimate Raids", _dutyUnlockData.GetUltimateRaids());
AddCategory(list, "Deep Dungeons", _dutyUnlockData.GetDeepDungeons());
AddCategory(list, "Variant Dungeons", _dutyUnlockData.GetVariantDungeons());
AddCategory(list, "Criterion Dungeons", _dutyUnlockData.GetCriterionDungeons());
AddCategory(list, "Criterion Savage", _dutyUnlockData.GetCriterionSavageDungeons());
_filteredCategories = list.Where((DutyCategory c) => c.Duties.Count > 0).ToList();
RefreshCounts();
}
private void AddCategory(List<DutyCategory> categories, string name, IEnumerable<DutyInfo> duties)
{
List<DutyInfo> list = (from d in duties
where MatchesFilter(d)
orderby d.Level, d.ItemLevel, d.ContentFinderConditionId
select d).ToList();
if (list.Count > 0)
{
categories.Add(new DutyCategory(name, list));
}
}
private bool MatchesFilter(DutyInfo duty)
{
if (!string.IsNullOrEmpty(_searchText) && !duty.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (_showLockedOnly && duty.IsUnlocked)
{
return false;
}
if (_showUnlockedOnly && !duty.IsUnlocked)
{
return false;
}
return true;
}
public void RefreshCounts()
{
_categoryCounts.Clear();
foreach (DutyCategory filteredCategory in _filteredCategories)
{
int unlocked = filteredCategory.Duties.Count((DutyInfo d) => d.IsUnlocked);
int completed = filteredCategory.Duties.Count((DutyInfo d) => d.IsCompleted == true);
int completionTrackable = filteredCategory.Duties.Count((DutyInfo d) => d.IsCompleted.HasValue);
int count = filteredCategory.Duties.Count;
_categoryCounts[filteredCategory.Name] = new DutyCounts(unlocked, completed, completionTrackable, count);
}
}
public void ClearCounts(int type, int code)
{
foreach (string item in _categoryCounts.Keys.ToList())
{
_categoryCounts[item] = _categoryCounts[item]with
{
Unlocked = 0,
Completed = 0,
CompletionTrackable = 0
};
}
}
}

View file

@ -388,13 +388,18 @@ internal sealed class QuestJournalComponent
{
bool num = IsQuestExpired(questInfo);
bool flag = _questFunctions.IsQuestUnobtainable(questInfo.QuestId);
bool flag2 = _questFunctions.IsQuestLocked(questInfo.QuestId);
bool flag3 = _questFunctions.IsReadyToAcceptQuest(questInfo.QuestId);
bool flag2 = _questFunctions.IsQuestBlacklisted(questInfo.QuestId);
bool flag3 = _questFunctions.IsQuestLocked(questInfo.QuestId);
bool flag4 = _questFunctions.IsReadyToAcceptQuest(questInfo.QuestId);
if (num || flag)
{
_uiUtils.ChecklistItem("Unobtainable", ImGuiColors.DalamudGrey, FontAwesomeIcon.Minus);
}
else if (flag2 || !flag3 || !_questRegistry.IsKnownQuest(questInfo.QuestId))
else if (flag2)
{
_uiUtils.ChecklistItem("Blacklisted", ImGuiColors.DalamudGrey3, FontAwesomeIcon.Ban);
}
else if (flag3 || !flag4 || !_questRegistry.IsKnownQuest(questInfo.QuestId))
{
_uiUtils.ChecklistItem("Locked", ImGuiColors.DalamudRed, FontAwesomeIcon.Times);
}

View file

@ -57,7 +57,7 @@ internal sealed class ActiveQuestComponent
public event EventHandler? Reload;
[GeneratedRegex("\\s\\s+", RegexOptions.IgnoreCase, "en-US")]
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.13.7005")]
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.13.11305")]
private static Regex MultipleWhitespaceRegex()
{
return _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0.Instance;

View file

@ -51,6 +51,7 @@ internal sealed class ManualPriorityComponent
_questSelector.QuestSelected = delegate(Quest quest)
{
manualPriorityComponent._questController.ManualPriorityQuests.Add(quest);
manualPriorityComponent._questController.SavePriorityQuests();
};
}
@ -92,9 +93,9 @@ internal sealed class ManualPriorityComponent
ExportToClipboard();
}
ImGui.SameLine();
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Check, "Remove finished Quests"))
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Check, "Remove finished Quests") && _questController.ManualPriorityQuests.RemoveAll((Quest q) => _questFunctions.IsQuestComplete(q.Id)) > 0)
{
_questController.ManualPriorityQuests.RemoveAll((Quest q) => _questFunctions.IsQuestComplete(q.Id));
_questController.SavePriorityQuests();
}
ImGui.SameLine();
using (ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
@ -212,11 +213,13 @@ internal sealed class ManualPriorityComponent
if (quest != null)
{
manualPriorityQuests.Remove(quest);
_questController.SavePriorityQuests();
}
if (quest2 != null)
{
manualPriorityQuests.Remove(quest2);
manualPriorityQuests.Insert(index, quest2);
_questController.SavePriorityQuests();
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
@ -35,6 +36,8 @@ internal sealed class QuestSequenceComponent
private int _hoveredSequenceId = -1;
public bool HasLookedUpQuest => _lookedUpQuestId != null;
public QuestSequenceComponent(QuestData questData, QuestRegistry questRegistry, QuestFunctions questFunctions, IDataManager dataManager, ILogger<QuestSequenceComponent> logger)
{
_questData = questData;
@ -96,6 +99,26 @@ internal sealed class QuestSequenceComponent
{
DrawEmptyState(FontAwesomeIcon.QuestionCircle, "No Active Quest", "Accept a quest to view its sequence information.", new Vector4(0.6f, 0.5f, 0.8f, 0.7f));
}
if (!(tuple.Item1 is QuestId questId))
{
return;
}
ImGui.Spacing();
if (ImGui.Button("Copy filename"))
{
if (_questData.TryGetQuestInfo(questId, out IQuestInfo questInfo))
{
ImGui.SetClipboardText(GetSequenceFilename(questId.Value, questInfo.Name));
}
else
{
ImGui.SetClipboardText(GetSequenceFilename(questId.Value, questId.Value.ToString(CultureInfo.InvariantCulture)));
}
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Copies filename as QuestID_QuestNameWithSpaces.json");
}
}
public void DrawQuestLookupTab()
@ -230,6 +253,25 @@ internal sealed class QuestSequenceComponent
text.AppendFormatted(questId.Value);
text.AppendLiteral(")");
ImGui.TextColored(in col, text);
ImGui.SameLine();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 8f);
using (ImRaii.PushColor(ImGuiCol.Button, new Vector4(0.18f, 0.16f, 0.22f, 0.8f)))
{
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, new Vector4(0.28f, 0.24f, 0.32f, 0.95f)))
{
using (ImRaii.PushColor(ImGuiCol.ButtonActive, new Vector4(0.35f, 0.3f, 0.45f, 1f)))
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy))
{
ImGui.SetClipboardText(GetSequenceFilename(questId.Value, questInfo.Name));
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Copies filename as QuestID_QuestNameWithSpaces.json");
}
}
}
}
if (currentSequence.HasValue)
{
ImGui.SameLine();
@ -592,4 +634,15 @@ internal sealed class QuestSequenceComponent
}
ImGui.SetCursorScreenPos(cursorScreenPos + new Vector2(0f, vector.Y));
}
private static string GetSequenceFilename(int questId, string questName)
{
string value = (questName ?? string.Empty).Trim();
return $"{questId}_{value}.json";
}
public void SetLookedUpQuest(ElementId? id)
{
_lookedUpQuestId = id;
}
}

View file

@ -23,6 +23,8 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
private readonly StopConditionComponent _stopConditionComponent;
private readonly BlacklistConfigComponent _blacklistConfigComponent;
private readonly NotificationConfigComponent _notificationConfigComponent;
private readonly DebugConfigComponent _debugConfigComponent;
@ -31,7 +33,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
public WindowConfig WindowConfig => _configuration.ConfigWindowConfig;
public ConfigWindow(IDalamudPluginInterface pluginInterface, GeneralConfigComponent generalConfigComponent, PluginConfigComponent pluginConfigComponent, DutyConfigComponent dutyConfigComponent, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent, StopConditionComponent stopConditionComponent, NotificationConfigComponent notificationConfigComponent, DebugConfigComponent debugConfigComponent, Configuration configuration, ChangelogWindow changelogWindow)
public ConfigWindow(IDalamudPluginInterface pluginInterface, GeneralConfigComponent generalConfigComponent, PluginConfigComponent pluginConfigComponent, DutyConfigComponent dutyConfigComponent, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent, StopConditionComponent stopConditionComponent, BlacklistConfigComponent blacklistConfigComponent, NotificationConfigComponent notificationConfigComponent, DebugConfigComponent debugConfigComponent, Configuration configuration, ChangelogWindow changelogWindow)
: base("Config - Questionable###QuestionableConfig", ImGuiWindowFlags.AlwaysAutoResize)
{
_pluginInterface = pluginInterface;
@ -40,6 +42,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
_dutyConfigComponent = dutyConfigComponent;
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
_stopConditionComponent = stopConditionComponent;
_blacklistConfigComponent = blacklistConfigComponent;
_notificationConfigComponent = notificationConfigComponent;
_debugConfigComponent = debugConfigComponent;
_configuration = configuration;
@ -70,6 +73,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
_dutyConfigComponent.DrawTab();
_singlePlayerDutyConfigComponent.DrawTab();
_stopConditionComponent.DrawTab();
_blacklistConfigComponent.DrawTab();
_notificationConfigComponent.DrawTab();
_debugConfigComponent.DrawTab();
}

View file

@ -19,21 +19,26 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
private readonly GatheringJournalComponent _gatheringJournalComponent;
private readonly DutyJournalComponent _dutyJournalComponent;
private readonly QuestRegistry _questRegistry;
private readonly IClientState _clientState;
public JournalProgressWindow(QuestJournalComponent questJournalComponent, QuestRewardComponent questRewardComponent, AlliedSocietyJournalComponent alliedSocietyJournalComponent, GatheringJournalComponent gatheringJournalComponent, QuestRegistry questRegistry, IClientState clientState)
public JournalProgressWindow(QuestJournalComponent questJournalComponent, QuestRewardComponent questRewardComponent, AlliedSocietyJournalComponent alliedSocietyJournalComponent, GatheringJournalComponent gatheringJournalComponent, DutyJournalComponent dutyJournalComponent, QuestRegistry questRegistry, IClientState clientState)
: base("Journal Progress###QuestionableJournalProgress")
{
_questJournalComponent = questJournalComponent;
_alliedSocietyJournalComponent = alliedSocietyJournalComponent;
_questRewardComponent = questRewardComponent;
_gatheringJournalComponent = gatheringJournalComponent;
_dutyJournalComponent = dutyJournalComponent;
_questRegistry = questRegistry;
_clientState = clientState;
_clientState.Login += _questJournalComponent.RefreshCounts;
_clientState.Logout += _dutyJournalComponent.ClearCounts;
_clientState.Login += _gatheringJournalComponent.RefreshCounts;
_clientState.Login += _dutyJournalComponent.RefreshCounts;
_clientState.Logout += _questJournalComponent.ClearCounts;
_clientState.Logout += _gatheringJournalComponent.ClearCounts;
_questRegistry.Reloaded += OnQuestsReloaded;
@ -46,6 +51,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
private void OnQuestsReloaded(object? sender, EventArgs e)
{
_questJournalComponent.RefreshCounts();
_dutyJournalComponent.RefreshCounts();
_gatheringJournalComponent.RefreshCounts();
}
@ -53,6 +59,8 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
{
_questJournalComponent.UpdateFilter();
_questJournalComponent.RefreshCounts();
_dutyJournalComponent.UpdateFilter();
_dutyJournalComponent.RefreshCounts();
_gatheringJournalComponent.UpdateFilter();
_gatheringJournalComponent.RefreshCounts();
}
@ -63,6 +71,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
if (!(!endObject))
{
_questJournalComponent.DrawQuests();
_dutyJournalComponent.DrawDuties();
_alliedSocietyJournalComponent.DrawAlliedSocietyQuests();
_questRewardComponent.DrawItemRewards();
_gatheringJournalComponent.DrawGatheringItems();
@ -74,7 +83,9 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
_questRegistry.Reloaded -= OnQuestsReloaded;
_clientState.Logout -= _gatheringJournalComponent.ClearCounts;
_clientState.Logout -= _questJournalComponent.ClearCounts;
_clientState.Login -= _dutyJournalComponent.RefreshCounts;
_clientState.Login -= _gatheringJournalComponent.RefreshCounts;
_clientState.Logout -= _dutyJournalComponent.ClearCounts;
_clientState.Login -= _questJournalComponent.RefreshCounts;
}
}

View file

@ -4,6 +4,7 @@ using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using LLib.ImGui;
using Questionable.Model.Questing;
using Questionable.Windows.QuestComponents;
namespace Questionable.Windows;
@ -16,6 +17,8 @@ internal sealed class QuestSequenceWindow : LWindow, IPersistableWindowConfig
private readonly QuestSequenceComponent _questSequenceComponent;
private bool _selectLookupTabNextFrame;
public WindowConfig WindowConfig => _configuration.QuestSequenceWindowConfig;
public QuestSequenceWindow(IDalamudPluginInterface pluginInterface, Configuration configuration, QuestSequenceComponent questSequenceComponent)
@ -38,6 +41,13 @@ internal sealed class QuestSequenceWindow : LWindow, IPersistableWindowConfig
_pluginInterface.SavePluginConfig(_configuration);
}
public void OpenForQuest(ElementId questId)
{
_questSequenceComponent.SetLookedUpQuest(questId);
_selectLookupTabNextFrame = true;
base.IsOpenAndUncollapsed = true;
}
public override void DrawContent()
{
bool isOpen = base.IsOpen;
@ -51,31 +61,29 @@ internal sealed class QuestSequenceWindow : LWindow, IPersistableWindowConfig
{
using (ImRaii.PushColor(ImGuiCol.TabActive, new Vector4(0.28f, 0.24f, 0.35f, 1f)))
{
using (ImRaii.PushColor(ImGuiCol.TabUnfocused, new Vector4(0.13f, 0.11f, 0.18f, 0.6f)))
using ImRaii.IEndObject endObject = ImRaii.TabBar("QuestSequenceTabs", ImGuiTabBarFlags.None);
if (!endObject)
{
using (ImRaii.PushColor(ImGuiCol.TabUnfocusedActive, new Vector4(0.25f, 0.22f, 0.3f, 0.95f)))
return;
}
using (ImRaii.IEndObject endObject2 = ImRaii.TabItem("Current Quest", ImGuiTabItemFlags.None))
{
if (endObject2)
{
using ImRaii.IEndObject endObject = ImRaii.TabBar("QuestSequenceTabs", ImGuiTabBarFlags.None);
if (!endObject)
{
return;
}
using (ImRaii.IEndObject endObject2 = ImRaii.TabItem("Current Quest"))
{
if (endObject2)
{
ImGui.Spacing();
_questSequenceComponent.DrawCurrentQuestTab();
}
}
using ImRaii.IEndObject endObject3 = ImRaii.TabItem("Quest Lookup");
if (endObject3)
{
ImGui.Spacing();
_questSequenceComponent.DrawQuestLookupTab();
}
ImGui.Spacing();
_questSequenceComponent.DrawCurrentQuestTab();
}
}
ImGuiTabItemFlags flags = (_selectLookupTabNextFrame ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None);
using (ImRaii.IEndObject endObject3 = ImRaii.TabItem("Quest Lookup", flags))
{
if (endObject3)
{
ImGui.Spacing();
_questSequenceComponent.DrawQuestLookupTab();
}
}
_selectLookupTabNextFrame = false;
}
}
}

View file

@ -33,6 +33,10 @@ internal sealed class UiUtils
}
if (elementId is QuestId questId && _questFunctions.IsDailyAlliedSocietyQuestAndAvailableToday(questId))
{
if (_questFunctions.IsQuestBlacklisted(questId))
{
return (Color: ImGuiColors.DalamudGrey3, Icon: FontAwesomeIcon.Ban, Status: "Blacklisted");
}
if (!_questFunctions.IsReadyToAcceptQuest(questId))
{
return (Color: ImGuiColors.ParsedGreen, Icon: FontAwesomeIcon.Check, Status: "Complete");
@ -51,6 +55,10 @@ internal sealed class UiUtils
{
return (Color: ImGuiColors.DalamudGrey, Icon: FontAwesomeIcon.Minus, Status: "Unobtainable");
}
if (_questFunctions.IsQuestBlacklisted(elementId))
{
return (Color: ImGuiColors.DalamudGrey3, Icon: FontAwesomeIcon.Ban, Status: "Blacklisted");
}
if (_questFunctions.IsQuestLocked(elementId) || !_questFunctions.IsReadyToAcceptQuest(elementId) || !_questRegistry.IsKnownQuest(elementId))
{
return (Color: ImGuiColors.DalamudRed, Icon: FontAwesomeIcon.Times, Status: "Locked");

View file

@ -22,6 +22,8 @@ internal sealed class Configuration : IPluginConfiguration
public EClassJob CombatJob { get; set; }
public EMsqPriorityMode MsqPriority { get; set; }
public bool HideInAllInstances { get; set; } = true;
public bool UseEscToCancelQuesting { get; set; } = true;
@ -44,9 +46,21 @@ internal sealed class Configuration : IPluginConfiguration
public bool ClearPriorityQuestsOnCompletion { get; set; }
public bool PersistPriorityQuestsBetweenSessions { get; set; }
public bool ShowChangelogOnUpdate { get; set; } = true;
public bool StopOnPlayerInput { get; set; }
public bool AutoSolveQte { get; set; } = true;
public bool AutoSnipe { get; set; } = true;
[JsonProperty(ItemConverterType = typeof(ElementIdNConverter))]
public HashSet<ElementId> BlacklistedQuests { get; set; } = new HashSet<ElementId>();
[JsonProperty(ItemConverterType = typeof(ElementIdNConverter))]
public List<ElementId> PriorityQuests { get; set; } = new List<ElementId>();
}
internal sealed class StopConfiguration
@ -129,6 +143,8 @@ internal sealed class Configuration : IPluginConfiguration
public bool SkipCrystalTowerRaids { get; set; }
public bool PreventQuestCompletion { get; set; }
public string ChocoboName { get; set; } = string.Empty;
}
internal enum ECombatModule
@ -139,6 +155,13 @@ internal sealed class Configuration : IPluginConfiguration
RotationSolverReborn
}
internal enum EMsqPriorityMode
{
Always,
AfterTracked,
Manual
}
internal enum EStopConditionMode
{
Off,

View file

@ -5,6 +5,7 @@ using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using LLib;
using LLib.Gear;
using LLib.Shop;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Controller;
@ -36,7 +37,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
{
private readonly ServiceProvider? _serviceProvider;
public QuestionablePlugin(IDalamudPluginInterface pluginInterface, IClientState clientState, ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager, ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui, ICommandManager commandManager, IAddonLifecycle addonLifecycle, IKeyState keyState, IContextMenu contextMenu, IToastGui toastGui, IGameInteropProvider gameInteropProvider, IAetheryteList aetheryteList)
public QuestionablePlugin(IDalamudPluginInterface pluginInterface, IClientState clientState, ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager, ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui, ICommandManager commandManager, IAddonLifecycle addonLifecycle, IKeyState keyState, IContextMenu contextMenu, IToastGui toastGui, IGameInteropProvider gameInteropProvider, IAetheryteList aetheryteList, IGameConfig gameConfig)
{
ArgumentNullException.ThrowIfNull(pluginInterface, "pluginInterface");
ArgumentNullException.ThrowIfNull(chatGui, "chatGui");
@ -74,6 +75,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddSingleton(toastGui);
serviceCollection.AddSingleton(gameInteropProvider);
serviceCollection.AddSingleton(aetheryteList);
serviceCollection.AddSingleton(gameConfig);
serviceCollection.AddSingleton(new WindowSystem("Questionable"));
serviceCollection.AddSingleton(((Configuration)pluginInterface.GetPluginConfig()) ?? new Configuration());
AddBasicFunctionsAndData(serviceCollection);
@ -106,6 +108,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddSingleton<AetherCurrentData>();
serviceCollection.AddSingleton<AetheryteData>();
serviceCollection.AddSingleton<AlliedSocietyData>();
serviceCollection.AddSingleton<DutyUnlockData>();
serviceCollection.AddSingleton<GatheringData>();
serviceCollection.AddSingleton<JournalData>();
serviceCollection.AddSingleton<QuestData>();
@ -115,10 +118,10 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddSingleton<ArtisanIpc>();
serviceCollection.AddSingleton<QuestionableIpc>();
serviceCollection.AddSingleton<TextAdvanceIpc>();
serviceCollection.AddSingleton<AutomatonIpc>();
serviceCollection.AddSingleton<AutoDutyIpc>();
serviceCollection.AddSingleton<BossModIpc>();
serviceCollection.AddSingleton<PandorasBoxIpc>();
serviceCollection.AddSingleton<AutomatonIpc>();
serviceCollection.AddSingleton<GearStatsCalculator>();
}
@ -216,10 +219,15 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddSingleton<ShopController>();
serviceCollection.AddSingleton<InterruptHandler>();
serviceCollection.AddSingleton<PartyWatchdog>();
serviceCollection.AddSingleton<AutoSnipeHandler>();
serviceCollection.AddSingleton<CraftworksSupplyController>();
serviceCollection.AddSingleton<CreditsController>();
serviceCollection.AddSingleton<HelpUiController>();
serviceCollection.AddSingleton<InteractionUiController>();
serviceCollection.AddSingleton<QteController>();
serviceCollection.AddSingleton<GrandCompanyShop>();
serviceCollection.AddSingleton<GCShopHandler>();
serviceCollection.AddSingleton<ChocoboNameHandler>();
serviceCollection.AddSingleton<ICombatModule, Mount128Module>();
serviceCollection.AddSingleton<ICombatModule, Mount147Module>();
serviceCollection.AddSingleton<ICombatModule, ItemUseModule>();
@ -247,6 +255,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddSingleton<QuestRewardComponent>();
serviceCollection.AddSingleton<GatheringJournalComponent>();
serviceCollection.AddSingleton<AlliedSocietyJournalComponent>();
serviceCollection.AddSingleton<DutyJournalComponent>();
serviceCollection.AddSingleton<OneTimeSetupWindow>();
serviceCollection.AddSingleton<QuestWindow>();
serviceCollection.AddSingleton<ConfigWindow>();
@ -261,6 +270,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddSingleton<DutyConfigComponent>();
serviceCollection.AddSingleton<SinglePlayerDutyConfigComponent>();
serviceCollection.AddSingleton<StopConditionComponent>();
serviceCollection.AddSingleton<BlacklistConfigComponent>();
serviceCollection.AddSingleton<NotificationConfigComponent>();
serviceCollection.AddSingleton<DebugConfigComponent>();
serviceCollection.AddSingleton<ChangelogWindow>();
@ -290,13 +300,20 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceProvider.GetRequiredService<QuestRegistry>().Reload();
serviceProvider.GetRequiredService<GatheringPointRegistry>().Reload();
serviceProvider.GetRequiredService<SinglePlayerDutyConfigComponent>().Reload();
serviceProvider.GetRequiredService<QuestController>().LoadPriorityQuests();
serviceProvider.GetRequiredService<CommandHandler>();
serviceProvider.GetRequiredService<ContextMenuController>();
serviceProvider.GetRequiredService<AutoSnipeHandler>();
serviceProvider.GetRequiredService<CraftworksSupplyController>();
serviceProvider.GetRequiredService<CreditsController>();
serviceProvider.GetRequiredService<HelpUiController>();
serviceProvider.GetRequiredService<QteController>();
serviceProvider.GetRequiredService<PandorasBoxIpc>();
serviceProvider.GetRequiredService<AutomatonIpc>();
serviceProvider.GetRequiredService<ShopController>();
serviceProvider.GetRequiredService<QuestionableIpc>();
serviceProvider.GetRequiredService<GCShopHandler>();
serviceProvider.GetRequiredService<ChocoboNameHandler>();
serviceProvider.GetRequiredService<DalamudInitializer>();
serviceProvider.GetRequiredService<TextAdvanceIpc>();
ChangelogWindow requiredService = serviceProvider.GetRequiredService<ChangelogWindow>();

View file

@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
namespace System.Text.RegularExpressions.Generated;
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.13.7005")]
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.13.11305")]
[SkipLocalsInit]
internal sealed class _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0 : Regex
{

View file

@ -3,7 +3,7 @@ using System.CodeDom.Compiler;
namespace System.Text.RegularExpressions.Generated;
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.13.7005")]
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.13.11305")]
internal static class _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__Utilities
{
internal static readonly TimeSpan s_defaultTimeout = ((AppContext.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") is TimeSpan timeSpan) ? timeSpan : Regex.InfiniteMatchTimeout);