forked from liza/Questionable
Split QuestWindow into components; fix item use; show required instances to unlock quests
This commit is contained in:
parent
07b4765478
commit
a9b25e3722
@ -24,5 +24,6 @@ internal sealed class Configuration : IPluginConfiguration
|
|||||||
{
|
{
|
||||||
public bool DebugOverlay { get; set; }
|
public bool DebugOverlay { get; set; }
|
||||||
public bool NeverFly { get; set; }
|
public bool NeverFly { get; set; }
|
||||||
|
public bool AdditionalStatusInformation { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@ internal sealed class MovementOverrideController
|
|||||||
// New Gridania Navmesh workaround
|
// New Gridania Navmesh workaround
|
||||||
new BlacklistedPoint(128, new(2f, 40.25f, 36.5f), new(0.25f, 40.25f, 36.5f)),
|
new BlacklistedPoint(128, new(2f, 40.25f, 36.5f), new(0.25f, 40.25f, 36.5f)),
|
||||||
|
|
||||||
|
// lotus stand
|
||||||
|
new BlacklistedPoint(205, new(26.75f, 0.5f, 20.75f), new(27.179117f, 0.26728272f, 19.714373f)),
|
||||||
|
|
||||||
new BlacklistedPoint(132, new(45.5f, -8f, 101f), new(50.53978f, -8.046954f, 101.06045f)),
|
new BlacklistedPoint(132, new(45.5f, -8f, 101f), new(50.53978f, -8.046954f, 101.06045f)),
|
||||||
|
|
||||||
// eastern thanalan
|
// eastern thanalan
|
||||||
|
@ -391,7 +391,8 @@ internal sealed class QuestController
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "Failed to start task {TaskName}", upcomingTask.ToString());
|
_logger.LogError(e, "Failed to start task {TaskName}", upcomingTask.ToString());
|
||||||
_chatGui.PrintError($"[Questionable] Failed to start task '{upcomingTask}', please check /xllog for details");
|
_chatGui.PrintError(
|
||||||
|
$"[Questionable] Failed to start task '{upcomingTask}', please check /xllog for details");
|
||||||
Stop("Task failed to start");
|
Stop("Task failed to start");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -466,6 +467,9 @@ internal sealed class QuestController
|
|||||||
ClearTasksInternal();
|
ClearTasksInternal();
|
||||||
_automatic = automatic;
|
_automatic = automatic;
|
||||||
|
|
||||||
|
if (TryPickPriorityQuest())
|
||||||
|
_logger.LogInformation("Using priority quest over current quest");
|
||||||
|
|
||||||
(QuestSequence? seq, QuestStep? step) = GetNextStep();
|
(QuestSequence? seq, QuestStep? step) = GetNextStep();
|
||||||
if (CurrentQuest == null || seq == null || step == null)
|
if (CurrentQuest == null || seq == null || step == null)
|
||||||
{
|
{
|
||||||
@ -590,6 +594,47 @@ internal sealed class QuestController
|
|||||||
_currentTask = null;
|
_currentTask = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsInterruptible()
|
||||||
|
{
|
||||||
|
var details = CurrentQuestDetails;
|
||||||
|
if (details == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var (currentQuest, type) = details.Value;
|
||||||
|
if (type != CurrentQuestType.Normal)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QuestSequence? currentSequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
|
||||||
|
QuestStep? currentStep = currentSequence?.FindStep(currentQuest.Step);
|
||||||
|
return currentStep?.AetheryteShortcut != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryPickPriorityQuest()
|
||||||
|
{
|
||||||
|
if (!IsInterruptible())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ushort[] priorityQuests =
|
||||||
|
[
|
||||||
|
1157, // Garuda (Hard)
|
||||||
|
1158, // Titan (Hard)
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (var questId in priorityQuests)
|
||||||
|
{
|
||||||
|
if (_gameFunctions.IsReadyToAcceptQuest(questId) && _questRegistry.TryGetQuest(questId, out var quest))
|
||||||
|
{
|
||||||
|
SetNextQuest(quest);
|
||||||
|
_chatGui.Print(
|
||||||
|
"[Questionable] Picking up quest '{Name}' as a priority over current main story/side quests",
|
||||||
|
quest.Info.Name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public sealed record StepProgress(
|
public sealed record StepProgress(
|
||||||
DateTime StartedAt,
|
DateTime StartedAt,
|
||||||
int PointMenuCounter = 0);
|
int PointMenuCounter = 0);
|
||||||
|
@ -121,7 +121,7 @@ internal static class UseItem
|
|||||||
if (DateTime.Now <= _continueAt)
|
if (DateTime.Now <= _continueAt)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
if (ItemId == VesperBayAetheryteTicket)
|
if (ItemId == VesperBayAetheryteTicket && _usedItem)
|
||||||
{
|
{
|
||||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||||
if (inventoryManager == null)
|
if (inventoryManager == null)
|
||||||
@ -131,7 +131,7 @@ internal static class UseItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
int itemCount = inventoryManager->GetInventoryItemCount(ItemId);
|
int itemCount = inventoryManager->GetInventoryItemCount(ItemId);
|
||||||
if (!_usedItem && itemCount == _itemCount)
|
if (itemCount == _itemCount)
|
||||||
{
|
{
|
||||||
// TODO Better handling for game-provided errors, i.e. reacting to the 'Could not use' messages. UseItem() is successful in this case (and returns 0)
|
// TODO Better handling for game-provided errors, i.e. reacting to the 'Could not use' messages. UseItem() is successful in this case (and returns 0)
|
||||||
logger.LogInformation(
|
logger.LogInformation(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
@ -12,6 +13,7 @@ internal sealed class TerritoryData
|
|||||||
private readonly ImmutableDictionary<uint, string> _territoryNames;
|
private readonly ImmutableDictionary<uint, string> _territoryNames;
|
||||||
private readonly ImmutableHashSet<ushort> _territoriesWithMount;
|
private readonly ImmutableHashSet<ushort> _territoriesWithMount;
|
||||||
private readonly ImmutableHashSet<ushort> _dutyTerritories;
|
private readonly ImmutableHashSet<ushort> _dutyTerritories;
|
||||||
|
private readonly ImmutableDictionary<ushort, string> _instanceNames;
|
||||||
|
|
||||||
public TerritoryData(IDataManager dataManager)
|
public TerritoryData(IDataManager dataManager)
|
||||||
{
|
{
|
||||||
@ -35,6 +37,10 @@ internal sealed class TerritoryData
|
|||||||
.Where(x => x.RowId > 0 && x.ContentFinderCondition.Row != 0)
|
.Where(x => x.RowId > 0 && x.ContentFinderCondition.Row != 0)
|
||||||
.Select(x => (ushort)x.RowId)
|
.Select(x => (ushort)x.RowId)
|
||||||
.ToImmutableHashSet();
|
.ToImmutableHashSet();
|
||||||
|
|
||||||
|
_instanceNames = dataManager.GetExcelSheet<ContentFinderCondition>()!
|
||||||
|
.Where(x => x.RowId > 0 && x.Content != 0 && x.ContentLinkType == 1 && x.ContentType.Row != 6)
|
||||||
|
.ToImmutableDictionary(x => x.Content, x => x.Name.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? GetName(ushort territoryId) => _territoryNames.GetValueOrDefault(territoryId);
|
public string? GetName(ushort territoryId) => _territoryNames.GetValueOrDefault(territoryId);
|
||||||
@ -51,4 +57,6 @@ internal sealed class TerritoryData
|
|||||||
public bool CanUseMount(ushort territoryId) => _territoriesWithMount.Contains(territoryId);
|
public bool CanUseMount(ushort territoryId) => _territoriesWithMount.Contains(territoryId);
|
||||||
|
|
||||||
public bool IsDutyInstance(ushort territoryId) => _dutyTerritories.Contains(territoryId);
|
public bool IsDutyInstance(ushort territoryId) => _dutyTerritories.Contains(territoryId);
|
||||||
|
|
||||||
|
public string? GetInstanceName(ushort instanceId) => _instanceNames.GetValueOrDefault(instanceId);
|
||||||
}
|
}
|
||||||
|
@ -251,6 +251,9 @@ internal sealed unsafe class GameFunctions
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsQuestLocked(questId))
|
||||||
|
return false;
|
||||||
|
|
||||||
// if we're not at a high enough level to continue, we also ignore it
|
// if we're not at a high enough level to continue, we also ignore it
|
||||||
var currentLevel = _clientState.LocalPlayer?.Level ?? 0;
|
var currentLevel = _clientState.LocalPlayer?.Level ?? 0;
|
||||||
if (currentLevel != 0 && quest != null && quest.Info.Level > currentLevel)
|
if (currentLevel != 0 && quest != null && quest.Info.Level > currentLevel)
|
||||||
@ -288,18 +291,36 @@ internal sealed unsafe class GameFunctions
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (questInfo.PreviousQuests.Count > 0)
|
return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasCompletedPreviousQuests(QuestInfo questInfo, ushort? extraCompletedQuest)
|
||||||
{
|
{
|
||||||
|
if (questInfo.PreviousQuests.Count == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
var completedQuests = questInfo.PreviousQuests.Count(x => IsQuestComplete(x) || x == extraCompletedQuest);
|
var completedQuests = questInfo.PreviousQuests.Count(x => IsQuestComplete(x) || x == extraCompletedQuest);
|
||||||
if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.All &&
|
if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.All &&
|
||||||
questInfo.PreviousQuests.Count == completedQuests)
|
questInfo.PreviousQuests.Count == completedQuests)
|
||||||
return false;
|
|
||||||
else if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return true;
|
return true;
|
||||||
|
else if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasCompletedPreviousInstances(QuestInfo questInfo)
|
||||||
|
{
|
||||||
|
if (questInfo.PreviousInstanceContent.Count == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var completedInstances = questInfo.PreviousInstanceContent.Count(x => UIState.IsInstanceContentCompleted(x));
|
||||||
|
if (questInfo.PreviousInstanceContentJoin == QuestInfo.QuestJoin.All &&
|
||||||
|
questInfo.PreviousInstanceContent.Count == completedInstances)
|
||||||
|
return true;
|
||||||
|
else if (questInfo.PreviousInstanceContentJoin == QuestInfo.QuestJoin.AtLeastOne && completedInstances > 0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,11 @@ internal sealed class QuestInfo
|
|||||||
QuestLockJoin = (QuestJoin)quest.QuestLockJoin;
|
QuestLockJoin = (QuestJoin)quest.QuestLockJoin;
|
||||||
IsMainScenarioQuest = quest.JournalGenre?.Value?.JournalCategory?.Value?.JournalSection?.Row is 0 or 1;
|
IsMainScenarioQuest = quest.JournalGenre?.Value?.JournalCategory?.Value?.JournalSection?.Row is 0 or 1;
|
||||||
CompletesInstantly = quest.ToDoCompleteSeq[0] == 0;
|
CompletesInstantly = quest.ToDoCompleteSeq[0] == 0;
|
||||||
|
PreviousInstanceContent = quest.InstanceContent.Select(x => (ushort)x.Row).Where(x => x != 0).ToList();
|
||||||
|
PreviousInstanceContentJoin = (QuestJoin)quest.InstanceContentJoin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ushort QuestId { get; }
|
public ushort QuestId { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public ushort Level { get; }
|
public ushort Level { get; }
|
||||||
@ -34,6 +37,8 @@ internal sealed class QuestInfo
|
|||||||
public QuestJoin PreviousQuestJoin { get; }
|
public QuestJoin PreviousQuestJoin { get; }
|
||||||
public ImmutableList<ushort> QuestLocks { get; set; }
|
public ImmutableList<ushort> QuestLocks { get; set; }
|
||||||
public QuestJoin QuestLockJoin { get; set; }
|
public QuestJoin QuestLockJoin { get; set; }
|
||||||
|
public List<ushort> PreviousInstanceContent { get; set; }
|
||||||
|
public QuestJoin PreviousInstanceContentJoin { get; set; }
|
||||||
public bool IsMainScenarioQuest { get; }
|
public bool IsMainScenarioQuest { get; }
|
||||||
public bool CompletesInstantly { get; set; }
|
public bool CompletesInstantly { get; set; }
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ using Questionable.External;
|
|||||||
using Questionable.Validation;
|
using Questionable.Validation;
|
||||||
using Questionable.Validation.Validators;
|
using Questionable.Validation.Validators;
|
||||||
using Questionable.Windows;
|
using Questionable.Windows;
|
||||||
|
using Questionable.Windows.QuestComponents;
|
||||||
using Action = Questionable.Controller.Steps.Interactions.Action;
|
using Action = Questionable.Controller.Steps.Interactions.Action;
|
||||||
|
|
||||||
namespace Questionable;
|
namespace Questionable;
|
||||||
@ -152,6 +153,14 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
|
|
||||||
private static void AddWindows(ServiceCollection serviceCollection)
|
private static void AddWindows(ServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
|
serviceCollection.AddSingleton<UiUtils>();
|
||||||
|
|
||||||
|
serviceCollection.AddSingleton<ActiveQuestComponent>();
|
||||||
|
serviceCollection.AddSingleton<ARealmRebornComponent>();
|
||||||
|
serviceCollection.AddSingleton<CreationUtilsComponent>();
|
||||||
|
serviceCollection.AddSingleton<QuickAccessButtonsComponent>();
|
||||||
|
serviceCollection.AddSingleton<RemainingTasksComponent>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<QuestWindow>();
|
serviceCollection.AddSingleton<QuestWindow>();
|
||||||
serviceCollection.AddSingleton<ConfigWindow>();
|
serviceCollection.AddSingleton<ConfigWindow>();
|
||||||
serviceCollection.AddSingleton<DebugOverlay>();
|
serviceCollection.AddSingleton<DebugOverlay>();
|
||||||
|
@ -100,6 +100,13 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
|
|||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool additionalStatusInformation = _configuration.Advanced.AdditionalStatusInformation;
|
||||||
|
if (ImGui.Checkbox("Draw additional status information", ref additionalStatusInformation))
|
||||||
|
{
|
||||||
|
_configuration.Advanced.AdditionalStatusInformation = additionalStatusInformation;
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Questionable.Data;
|
||||||
|
|
||||||
|
namespace Questionable.Windows.QuestComponents;
|
||||||
|
|
||||||
|
internal sealed class ARealmRebornComponent
|
||||||
|
{
|
||||||
|
private const ushort ATimeForEveryPurpose = 425;
|
||||||
|
private const ushort TheUltimateWeapon = 524;
|
||||||
|
private static readonly ushort[] RequiredPrimalInstances = [20004, 20006, 20005];
|
||||||
|
private static readonly ushort[] RequiredAllianceRaidQuests = [1709, 1200, 1201, 1202, 1203, 1474, 494, 495];
|
||||||
|
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly QuestData _questData;
|
||||||
|
private readonly TerritoryData _territoryData;
|
||||||
|
private readonly UiUtils _uiUtils;
|
||||||
|
|
||||||
|
public ARealmRebornComponent(GameFunctions gameFunctions, QuestData questData, TerritoryData territoryData,
|
||||||
|
UiUtils uiUtils)
|
||||||
|
{
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
_questData = questData;
|
||||||
|
_territoryData = territoryData;
|
||||||
|
_uiUtils = uiUtils;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShouldDraw => !_gameFunctions.IsQuestComplete(ATimeForEveryPurpose) &&
|
||||||
|
_gameFunctions.IsQuestComplete(TheUltimateWeapon);
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var completedPrimals = UIState.IsInstanceContentCompleted(RequiredPrimalInstances.Last());
|
||||||
|
bool completedRaids = _gameFunctions.IsQuestComplete(RequiredAllianceRaidQuests.Last());
|
||||||
|
bool complete = completedPrimals && completedRaids;
|
||||||
|
bool hover = _uiUtils.ChecklistItem("ARR Primals & Raids",
|
||||||
|
complete ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed,
|
||||||
|
complete ? FontAwesomeIcon.Check : FontAwesomeIcon.Times);
|
||||||
|
if (complete || !hover)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var tooltip = ImRaii.Tooltip();
|
||||||
|
if (!tooltip)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui.Text("Primals:");
|
||||||
|
foreach (var instanceId in RequiredPrimalInstances)
|
||||||
|
{
|
||||||
|
(Vector4 color, FontAwesomeIcon icon) = UiUtils.GetInstanceStyle(instanceId);
|
||||||
|
_uiUtils.ChecklistItem(_territoryData.GetInstanceName(instanceId) ?? "?", color, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Text("Alliance Raids:");
|
||||||
|
foreach (var questId in RequiredAllianceRaidQuests)
|
||||||
|
{
|
||||||
|
(Vector4 color, FontAwesomeIcon icon, _) = _uiUtils.GetQuestStyle(questId);
|
||||||
|
_uiUtils.ChecklistItem(_questData.GetQuestInfo(questId).Name, color, icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
325
Questionable/Windows/QuestComponents/ActiveQuestComponent.cs
Normal file
325
Questionable/Windows/QuestComponents/ActiveQuestComponent.cs
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Interface.Components;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Questionable.Controller;
|
||||||
|
using Questionable.Controller.Steps.Shared;
|
||||||
|
using Questionable.Model.V1;
|
||||||
|
|
||||||
|
namespace Questionable.Windows.QuestComponents;
|
||||||
|
|
||||||
|
internal sealed class ActiveQuestComponent
|
||||||
|
{
|
||||||
|
private readonly QuestController _questController;
|
||||||
|
private readonly MovementController _movementController;
|
||||||
|
private readonly CombatController _combatController;
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly ICommandManager _commandManager;
|
||||||
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly Configuration _configuration;
|
||||||
|
private readonly QuestRegistry _questRegistry;
|
||||||
|
|
||||||
|
public ActiveQuestComponent(QuestController questController, MovementController movementController,
|
||||||
|
CombatController combatController, GameFunctions gameFunctions, ICommandManager commandManager,
|
||||||
|
IDalamudPluginInterface pluginInterface, Configuration configuration, QuestRegistry questRegistry)
|
||||||
|
{
|
||||||
|
_questController = questController;
|
||||||
|
_movementController = movementController;
|
||||||
|
_combatController = combatController;
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
_commandManager = commandManager;
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
_configuration = configuration;
|
||||||
|
_questRegistry = questRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var currentQuestDetails = _questController.CurrentQuestDetails;
|
||||||
|
QuestController.QuestProgress? currentQuest = currentQuestDetails?.Progress;
|
||||||
|
QuestController.CurrentQuestType? currentQuestType = currentQuestDetails?.Type;
|
||||||
|
if (currentQuest != null)
|
||||||
|
{
|
||||||
|
DrawQuestNames(currentQuest, currentQuestType);
|
||||||
|
var questWork = DrawQuestWork(currentQuest);
|
||||||
|
|
||||||
|
if (_combatController.IsRunning)
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudOrange, "In Combat");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.BeginDisabled();
|
||||||
|
ImGui.TextUnformatted(_questController.DebugState ?? string.Empty);
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
QuestSequence? currentSequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
|
||||||
|
QuestStep? currentStep = currentSequence?.FindStep(currentQuest.Step);
|
||||||
|
bool colored = currentStep is
|
||||||
|
{ InteractionType: EInteractionType.Instruction or EInteractionType.WaitForManualProgress };
|
||||||
|
if (colored)
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange);
|
||||||
|
ImGui.TextUnformatted(currentStep?.Comment ??
|
||||||
|
currentSequence?.Comment ?? currentQuest.Quest.Root.Comment ?? string.Empty);
|
||||||
|
if (colored)
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
//var nextStep = _questController.GetNextStep();
|
||||||
|
//ImGui.BeginDisabled(nextStep.Step == null);
|
||||||
|
ImGui.Text(_questController.ToStatString());
|
||||||
|
//ImGui.EndDisabled();
|
||||||
|
|
||||||
|
DrawQuestIcons(currentQuest, currentStep, questWork);
|
||||||
|
|
||||||
|
DrawSimulationControls();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text("No active quest");
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudGrey, $"{_questRegistry.Count} quests loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawQuestNames(QuestController.QuestProgress currentQuest,
|
||||||
|
QuestController.CurrentQuestType? currentQuestType)
|
||||||
|
{
|
||||||
|
if (currentQuestType == QuestController.CurrentQuestType.Simulated)
|
||||||
|
{
|
||||||
|
var simulatedQuest = _questController.SimulatedQuest ?? currentQuest;
|
||||||
|
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
||||||
|
ImGui.TextUnformatted(
|
||||||
|
$"Simulated Quest: {simulatedQuest.Quest.Info.Name} / {simulatedQuest.Sequence} / {simulatedQuest.Step}");
|
||||||
|
}
|
||||||
|
else if (currentQuestType == QuestController.CurrentQuestType.Next)
|
||||||
|
{
|
||||||
|
var startedQuest = _questController.StartedQuest;
|
||||||
|
if (startedQuest != null)
|
||||||
|
DrawCurrentQuest(startedQuest);
|
||||||
|
|
||||||
|
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
|
||||||
|
ImGui.TextUnformatted(
|
||||||
|
$"Next Quest: {currentQuest.Quest.Info.Name} / {currentQuest.Sequence} / {currentQuest.Step}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DrawCurrentQuest(currentQuest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCurrentQuest(QuestController.QuestProgress currentQuest)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(
|
||||||
|
$"Quest: {currentQuest.Quest.Info.Name} / {currentQuest.Sequence} / {currentQuest.Step}");
|
||||||
|
|
||||||
|
if (currentQuest.Quest.Root.Disabled)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudRed, "Disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_configuration.Advanced.AdditionalStatusInformation && _questController.IsInterruptible())
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudYellow, "Interruptible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuestWork? DrawQuestWork(QuestController.QuestProgress currentQuest)
|
||||||
|
{
|
||||||
|
ImGui.BeginDisabled();
|
||||||
|
var questWork = _gameFunctions.GetQuestEx(currentQuest.Quest.QuestId);
|
||||||
|
if (questWork != null)
|
||||||
|
{
|
||||||
|
var qw = questWork.Value;
|
||||||
|
string vars = "";
|
||||||
|
for (int i = 0; i < 6; ++i)
|
||||||
|
{
|
||||||
|
vars += qw.Variables[i] + " ";
|
||||||
|
if (i % 2 == 1)
|
||||||
|
vars += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// For combat quests, a sequence to kill 3 enemies works a bit like this:
|
||||||
|
// Trigger enemies → 0
|
||||||
|
// Kill first enemy → 1
|
||||||
|
// Kill second enemy → 2
|
||||||
|
// Last enemy → increase sequence, reset variable to 0
|
||||||
|
// The order in which enemies are killed doesn't seem to matter.
|
||||||
|
// If multiple waves spawn, this continues to count up (e.g. 1 enemy from wave 1, 2 enemies from wave 2, 1 from wave 3) would count to 3 then 0
|
||||||
|
ImGui.Text($"QW: {vars.Trim()}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (currentQuest.Quest.QuestId == _questController.NextQuest?.Quest.QuestId)
|
||||||
|
ImGui.TextUnformatted("(Next quest in story line not accepted)");
|
||||||
|
else
|
||||||
|
ImGui.TextUnformatted("(Not accepted)");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
return questWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawQuestIcons(QuestController.QuestProgress currentQuest, QuestStep? currentStep,
|
||||||
|
QuestWork? questWork)
|
||||||
|
{
|
||||||
|
ImGui.BeginDisabled(_questController.IsRunning);
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
|
||||||
|
{
|
||||||
|
// if we haven't accepted this quest, mark it as next quest so that we can optionally use aetherytes to travel
|
||||||
|
if (questWork == null)
|
||||||
|
_questController.SetNextQuest(currentQuest.Quest);
|
||||||
|
|
||||||
|
_questController.ExecuteNextStep(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.StepForward, "Step"))
|
||||||
|
{
|
||||||
|
_questController.ExecuteNextStep(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
|
||||||
|
{
|
||||||
|
_movementController.Stop();
|
||||||
|
_questController.Stop("Manual");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lastStep = currentStep ==
|
||||||
|
currentQuest.Quest.FindSequence(currentQuest.Sequence)?.Steps.LastOrDefault();
|
||||||
|
bool colored = currentStep != null
|
||||||
|
&& !lastStep
|
||||||
|
&& currentStep.InteractionType == EInteractionType.Instruction
|
||||||
|
&& _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(lastStep);
|
||||||
|
if (colored)
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.ParsedGreen);
|
||||||
|
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip"))
|
||||||
|
{
|
||||||
|
_movementController.Stop();
|
||||||
|
_questController.Skip(currentQuest.Quest.QuestId, currentQuest.Sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colored)
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
if (_commandManager.Commands.TryGetValue("/questinfo", out var commandInfo))
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Atlas))
|
||||||
|
_commandManager.DispatchCommand("/questinfo",
|
||||||
|
currentQuest.Quest.QuestId.ToString(CultureInfo.InvariantCulture), commandInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest;
|
||||||
|
if (ImGui.Checkbox("Automatically accept next quest", ref autoAcceptNextQuest))
|
||||||
|
{
|
||||||
|
_configuration.General.AutoAcceptNextQuest = autoAcceptNextQuest;
|
||||||
|
_pluginInterface.SavePluginConfig(_configuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSimulationControls()
|
||||||
|
{
|
||||||
|
if (_questController.SimulatedQuest == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var simulatedQuest = _questController.SimulatedQuest;
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudRed, "Quest sim active (experimental)");
|
||||||
|
ImGui.Text($"Sequence: {simulatedQuest.Sequence}");
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(simulatedQuest.Sequence == 0);
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus))
|
||||||
|
{
|
||||||
|
_movementController.Stop();
|
||||||
|
_questController.Stop("Sim-");
|
||||||
|
|
||||||
|
byte oldSequence = simulatedQuest.Sequence;
|
||||||
|
byte newSequence = simulatedQuest.Quest.Root.QuestSequence
|
||||||
|
.Select(x => (byte)x.Sequence)
|
||||||
|
.LastOrDefault(x => x < oldSequence, byte.MinValue);
|
||||||
|
|
||||||
|
_questController.SimulatedQuest.SetSequence(newSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.BeginDisabled(simulatedQuest.Sequence >= 255);
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
|
||||||
|
{
|
||||||
|
_movementController.Stop();
|
||||||
|
_questController.Stop("Sim+");
|
||||||
|
|
||||||
|
byte oldSequence = simulatedQuest.Sequence;
|
||||||
|
byte newSequence = simulatedQuest.Quest.Root.QuestSequence
|
||||||
|
.Select(x => (byte)x.Sequence)
|
||||||
|
.FirstOrDefault(x => x > oldSequence, byte.MaxValue);
|
||||||
|
|
||||||
|
simulatedQuest.SetSequence(newSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
var simulatedSequence = simulatedQuest.Quest.FindSequence(simulatedQuest.Sequence);
|
||||||
|
if (simulatedSequence != null)
|
||||||
|
{
|
||||||
|
using var _ = ImRaii.PushId("SimulatedStep");
|
||||||
|
|
||||||
|
ImGui.Text($"Step: {simulatedQuest.Step} / {simulatedSequence.Steps.Count - 1}");
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(simulatedQuest.Step == 0);
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus))
|
||||||
|
{
|
||||||
|
_movementController.Stop();
|
||||||
|
_questController.Stop("SimStep-");
|
||||||
|
|
||||||
|
simulatedQuest.SetStep(Math.Min(simulatedQuest.Step - 1,
|
||||||
|
simulatedSequence.Steps.Count - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.BeginDisabled(simulatedQuest.Step >= simulatedSequence.Steps.Count);
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
|
||||||
|
{
|
||||||
|
_movementController.Stop();
|
||||||
|
_questController.Stop("SimStep+");
|
||||||
|
|
||||||
|
simulatedQuest.SetStep(
|
||||||
|
simulatedQuest.Step == simulatedSequence.Steps.Count - 1
|
||||||
|
? 255
|
||||||
|
: (simulatedQuest.Step + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
if (ImGui.Button("Skip current task"))
|
||||||
|
{
|
||||||
|
_questController.SkipSimulatedTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Clear sim"))
|
||||||
|
{
|
||||||
|
_questController.SimulateQuest(null);
|
||||||
|
|
||||||
|
_movementController.Stop();
|
||||||
|
_questController.Stop("ClearSim");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
227
Questionable/Windows/QuestComponents/CreationUtilsComponent.cs
Normal file
227
Questionable/Windows/QuestComponents/CreationUtilsComponent.cs
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Game.Text;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Interface.Components;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Controller;
|
||||||
|
using Questionable.Data;
|
||||||
|
using Questionable.Model;
|
||||||
|
using Questionable.Model.V1;
|
||||||
|
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||||
|
|
||||||
|
namespace Questionable.Windows.QuestComponents;
|
||||||
|
|
||||||
|
internal sealed class CreationUtilsComponent
|
||||||
|
{
|
||||||
|
private readonly MovementController _movementController;
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly TerritoryData _territoryData;
|
||||||
|
private readonly QuestData _questData;
|
||||||
|
private readonly QuestSelectionWindow _questSelectionWindow;
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
private readonly ITargetManager _targetManager;
|
||||||
|
private readonly ICondition _condition;
|
||||||
|
private readonly IGameGui _gameGui;
|
||||||
|
private readonly ILogger<CreationUtilsComponent> _logger;
|
||||||
|
|
||||||
|
public CreationUtilsComponent(MovementController movementController, GameFunctions gameFunctions,
|
||||||
|
TerritoryData territoryData, QuestData questData, QuestSelectionWindow questSelectionWindow,
|
||||||
|
IClientState clientState, ITargetManager targetManager, ICondition condition, IGameGui gameGui,
|
||||||
|
ILogger<CreationUtilsComponent> logger)
|
||||||
|
{
|
||||||
|
_movementController = movementController;
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
_territoryData = territoryData;
|
||||||
|
_questData = questData;
|
||||||
|
_questSelectionWindow = questSelectionWindow;
|
||||||
|
_clientState = clientState;
|
||||||
|
_targetManager = targetManager;
|
||||||
|
_condition = condition;
|
||||||
|
_gameGui = gameGui;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Draw()
|
||||||
|
{
|
||||||
|
Debug.Assert(_clientState.LocalPlayer != null, "_clientState.LocalPlayer != null");
|
||||||
|
|
||||||
|
string territoryName = _territoryData.GetNameAndId(_clientState.TerritoryType);
|
||||||
|
ImGui.Text(territoryName);
|
||||||
|
|
||||||
|
if (_gameFunctions.IsFlyingUnlockedInCurrentZone())
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(SeIconChar.BotanistSprout.ToIconString());
|
||||||
|
}
|
||||||
|
|
||||||
|
var q = _gameFunctions.GetCurrentQuest();
|
||||||
|
ImGui.Text($"Current Quest: {q.CurrentQuest} → {q.Sequence}");
|
||||||
|
|
||||||
|
#if false
|
||||||
|
var questManager = QuestManager.Instance();
|
||||||
|
if (questManager != null)
|
||||||
|
{
|
||||||
|
for (int i = questManager->TrackedQuests.Length - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
var trackedQuest = questManager->TrackedQuests[i];
|
||||||
|
switch (trackedQuest.QuestType)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
ImGui.Text($"Tracked quest {i}: {trackedQuest.QuestType}, {trackedQuest.Index}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
_questRegistry.TryGetQuest(questManager->NormalQuests[trackedQuest.Index].QuestId,
|
||||||
|
out var quest);
|
||||||
|
ImGui.Text(
|
||||||
|
$"Tracked quest: {questManager->NormalQuests[trackedQuest.Index].QuestId}, {trackedQuest.Index}: {quest?.Info.Name}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_targetManager.Target != null)
|
||||||
|
{
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Text(string.Create(CultureInfo.InvariantCulture,
|
||||||
|
$"Target: {_targetManager.Target.Name} ({_targetManager.Target.ObjectKind}; {_targetManager.Target.DataId})"));
|
||||||
|
|
||||||
|
GameObject* gameObject = (GameObject*)_targetManager.Target.Address;
|
||||||
|
ImGui.Text(string.Create(CultureInfo.InvariantCulture,
|
||||||
|
$"Distance: {(_targetManager.Target.Position - _clientState.LocalPlayer.Position).Length():F2}"));
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
float verticalDistance = _targetManager.Target.Position.Y - _clientState.LocalPlayer.Position.Y;
|
||||||
|
string verticalDistanceText = string.Create(CultureInfo.InvariantCulture, $"Y: {verticalDistance:F2}");
|
||||||
|
if (Math.Abs(verticalDistance) >= MovementController.DefaultVerticalInteractionDistance)
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudOrange, verticalDistanceText);
|
||||||
|
else
|
||||||
|
ImGui.Text(verticalDistanceText);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text($"QM: {gameObject->NamePlateIconId}");
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(!_movementController.IsNavmeshReady);
|
||||||
|
if (!_movementController.IsPathfinding)
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Move to Target"))
|
||||||
|
{
|
||||||
|
_movementController.NavigateTo(EMovementType.DebugWindow, _targetManager.Target.DataId,
|
||||||
|
_targetManager.Target.Position,
|
||||||
|
fly: _condition[ConditionFlag.Mounted] && _gameFunctions.IsFlyingUnlockedInCurrentZone(),
|
||||||
|
sprint: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Cancel pathfinding"))
|
||||||
|
_movementController.ResetPathfinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.BeginDisabled(!_questData.IsIssuerOfAnyQuest(_targetManager.Target.DataId));
|
||||||
|
bool showQuests = ImGuiComponents.IconButton(FontAwesomeIcon.MapMarkerAlt);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Show all Quests starting with your current target.");
|
||||||
|
if (showQuests)
|
||||||
|
_questSelectionWindow.OpenForTarget(_targetManager.Target);
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
bool interact = ImGuiComponents.IconButton(FontAwesomeIcon.MousePointer);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Interact with your current target.");
|
||||||
|
if (interact)
|
||||||
|
{
|
||||||
|
ulong result = TargetSystem.Instance()->InteractWithObject(
|
||||||
|
(GameObject*)_targetManager.Target.Address, false);
|
||||||
|
_logger.LogInformation("XXXXX Interaction Result: {Result}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip(
|
||||||
|
"Left click: Copy target position as JSON.\nRight click: Copy target position as C# code.");
|
||||||
|
if (copy)
|
||||||
|
{
|
||||||
|
string interactionType = gameObject->NamePlateIconId switch
|
||||||
|
{
|
||||||
|
71201 or 71211 or 71221 or 71231 or 71341 or 71351 => "AcceptQuest",
|
||||||
|
71202 or 71212 or 71222 or 71232 or 71342 or 71352 => "AcceptQuest", // repeatable
|
||||||
|
71205 or 71215 or 71225 or 71235 or 71345 or 71355 => "CompleteQuest",
|
||||||
|
_ => "Interact",
|
||||||
|
};
|
||||||
|
ImGui.SetClipboardText($$"""
|
||||||
|
"DataId": {{_targetManager.Target.DataId}},
|
||||||
|
"Position": {
|
||||||
|
"X": {{_targetManager.Target.Position.X.ToString(CultureInfo.InvariantCulture)}},
|
||||||
|
"Y": {{_targetManager.Target.Position.Y.ToString(CultureInfo.InvariantCulture)}},
|
||||||
|
"Z": {{_targetManager.Target.Position.Z.ToString(CultureInfo.InvariantCulture)}}
|
||||||
|
},
|
||||||
|
"TerritoryId": {{_clientState.TerritoryType}},
|
||||||
|
"InteractionType": "{{interactionType}}"
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
|
{
|
||||||
|
if (_targetManager.Target.ObjectKind == ObjectKind.Aetheryte)
|
||||||
|
{
|
||||||
|
EAetheryteLocation location = (EAetheryteLocation)_targetManager.Target.DataId;
|
||||||
|
ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
|
||||||
|
$"{{EAetheryteLocation.{location}, new({_targetManager.Target.Position.X}f, {_targetManager.Target.Position.Y}f, {_targetManager.Target.Position.Z}f)}},"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
|
||||||
|
$"new({_targetManager.Target.Position.X}f, {_targetManager.Target.Position.Y}f, {_targetManager.Target.Position.Z}f)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip(
|
||||||
|
"Left click: Copy your position as JSON.\nRight click: Copy your position as C# code.");
|
||||||
|
if (copy)
|
||||||
|
{
|
||||||
|
ImGui.SetClipboardText($$"""
|
||||||
|
"Position": {
|
||||||
|
"X": {{_clientState.LocalPlayer.Position.X.ToString(CultureInfo.InvariantCulture)}},
|
||||||
|
"Y": {{_clientState.LocalPlayer.Position.Y.ToString(CultureInfo.InvariantCulture)}},
|
||||||
|
"Z": {{_clientState.LocalPlayer.Position.Z.ToString(CultureInfo.InvariantCulture)}}
|
||||||
|
},
|
||||||
|
"TerritoryId": {{_clientState.TerritoryType}},
|
||||||
|
"InteractionType": ""
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
|
{
|
||||||
|
Vector3 position = _clientState.LocalPlayer!.Position;
|
||||||
|
ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
|
||||||
|
$"new({position.X}f, {position.Y}f, {position.Z}f)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong hoveredItemId = _gameGui.HoveredItem;
|
||||||
|
if (hoveredItemId != 0)
|
||||||
|
{
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Text($"Hovered Item: {hoveredItemId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Questionable.Controller;
|
||||||
|
using Questionable.External;
|
||||||
|
|
||||||
|
namespace Questionable.Windows.QuestComponents;
|
||||||
|
|
||||||
|
internal sealed class QuickAccessButtonsComponent
|
||||||
|
{
|
||||||
|
private readonly QuestController _questController;
|
||||||
|
private readonly MovementController _movementController;
|
||||||
|
private readonly GameUiController _gameUiController;
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly ChatFunctions _chatFunctions;
|
||||||
|
private readonly QuestRegistry _questRegistry;
|
||||||
|
private readonly NavmeshIpc _navmeshIpc;
|
||||||
|
private readonly QuestValidationWindow _questValidationWindow;
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
private readonly ICondition _condition;
|
||||||
|
private readonly IFramework _framework;
|
||||||
|
|
||||||
|
public QuickAccessButtonsComponent(QuestController questController, MovementController movementController,
|
||||||
|
GameUiController gameUiController, GameFunctions gameFunctions, ChatFunctions chatFunctions,
|
||||||
|
QuestRegistry questRegistry, NavmeshIpc navmeshIpc, QuestValidationWindow questValidationWindow,
|
||||||
|
IClientState clientState, ICondition condition, IFramework framework)
|
||||||
|
{
|
||||||
|
_questController = questController;
|
||||||
|
_movementController = movementController;
|
||||||
|
_gameUiController = gameUiController;
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
_chatFunctions = chatFunctions;
|
||||||
|
_questRegistry = questRegistry;
|
||||||
|
_navmeshIpc = navmeshIpc;
|
||||||
|
_questValidationWindow = questValidationWindow;
|
||||||
|
_clientState = clientState;
|
||||||
|
_condition = condition;
|
||||||
|
_framework = framework;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Draw()
|
||||||
|
{
|
||||||
|
var map = AgentMap.Instance();
|
||||||
|
ImGui.BeginDisabled(map == null || map->IsFlagMarkerSet == 0 ||
|
||||||
|
map->FlagMapMarker.TerritoryId != _clientState.TerritoryType ||
|
||||||
|
!_navmeshIpc.IsReady);
|
||||||
|
if (ImGui.Button("Move to Flag"))
|
||||||
|
{
|
||||||
|
_movementController.Destination = null;
|
||||||
|
_chatFunctions.ExecuteCommand(
|
||||||
|
$"/vnav {(_condition[ConditionFlag.Mounted] && _gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(!_movementController.IsPathRunning);
|
||||||
|
if (ImGui.Button("Stop Nav"))
|
||||||
|
{
|
||||||
|
_movementController.Stop();
|
||||||
|
_questController.Stop("Manual");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
if (ImGui.Button("Reload Data"))
|
||||||
|
{
|
||||||
|
_questController.Reload();
|
||||||
|
_framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(),
|
||||||
|
TimeSpan.FromMilliseconds(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_questRegistry.ValidationIssueCount > 0)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (DrawValidationIssuesButton())
|
||||||
|
_questValidationWindow.IsOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DrawValidationIssuesButton()
|
||||||
|
{
|
||||||
|
int errorCount = _questRegistry.ValidationErrorCount;
|
||||||
|
int infoCount = _questRegistry.ValidationIssueCount - _questRegistry.ValidationErrorCount;
|
||||||
|
if (errorCount == 0 && infoCount == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int partsToRender = errorCount == 0 || infoCount == 0 ? 1 : 2;
|
||||||
|
using var id = ImRaii.PushId("validationissues");
|
||||||
|
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
var icon1 = FontAwesomeIcon.TimesCircle;
|
||||||
|
var icon2 = FontAwesomeIcon.InfoCircle;
|
||||||
|
Vector2 iconSize1 = errorCount > 0 ? ImGui.CalcTextSize(icon1.ToIconString()) : Vector2.Zero;
|
||||||
|
Vector2 iconSize2 = infoCount > 0 ? ImGui.CalcTextSize(icon2.ToIconString()) : Vector2.Zero;
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
string text1 = errorCount > 0 ? errorCount.ToString(CultureInfo.InvariantCulture) : string.Empty;
|
||||||
|
string text2 = infoCount > 0 ? infoCount.ToString(CultureInfo.InvariantCulture) : string.Empty;
|
||||||
|
Vector2 textSize1 = errorCount > 0 ? ImGui.CalcTextSize(text1) : Vector2.Zero;
|
||||||
|
Vector2 textSize2 = infoCount > 0 ? ImGui.CalcTextSize(text2) : Vector2.Zero;
|
||||||
|
var dl = ImGui.GetWindowDrawList();
|
||||||
|
var cursor = ImGui.GetCursorScreenPos();
|
||||||
|
|
||||||
|
var iconPadding = 3 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
// Draw an ImGui button with the icon and text
|
||||||
|
var buttonWidth = iconSize1.X + iconSize2.X + textSize1.X + textSize2.X +
|
||||||
|
(ImGui.GetStyle().FramePadding.X * 2) + iconPadding * 2 * partsToRender;
|
||||||
|
var buttonHeight = ImGui.GetFrameHeight();
|
||||||
|
var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight));
|
||||||
|
|
||||||
|
// Draw the icon on the window drawlist
|
||||||
|
Vector2 position = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X,
|
||||||
|
cursor.Y + ImGui.GetStyle().FramePadding.Y);
|
||||||
|
if (errorCount > 0)
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
dl.AddText(position, ImGui.GetColorU32(ImGuiColors.DalamudRed), icon1.ToIconString());
|
||||||
|
ImGui.PopFont();
|
||||||
|
position = position with { X = position.X + iconSize1.X + iconPadding };
|
||||||
|
|
||||||
|
// Draw the text on the window drawlist
|
||||||
|
dl.AddText(position, ImGui.GetColorU32(ImGuiCol.Text), text1);
|
||||||
|
position = position with { X = position.X + textSize1.X + 2 * iconPadding };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infoCount > 0)
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
dl.AddText(position, ImGui.GetColorU32(ImGuiColors.ParsedBlue), icon2.ToIconString());
|
||||||
|
ImGui.PopFont();
|
||||||
|
position = position with { X = position.X + iconSize2.X + iconPadding };
|
||||||
|
|
||||||
|
// Draw the text on the window drawlist
|
||||||
|
dl.AddText(position, ImGui.GetColorU32(ImGuiCol.Text), text2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
using ImGuiNET;
|
||||||
|
using Questionable.Controller;
|
||||||
|
|
||||||
|
namespace Questionable.Windows.QuestComponents;
|
||||||
|
|
||||||
|
internal sealed class RemainingTasksComponent
|
||||||
|
{
|
||||||
|
private readonly QuestController _questController;
|
||||||
|
|
||||||
|
public RemainingTasksComponent(QuestController questController)
|
||||||
|
{
|
||||||
|
_questController = questController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var remainingTasks = _questController.GetRemainingTaskNames();
|
||||||
|
if (remainingTasks.Count > 0)
|
||||||
|
{
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.BeginDisabled();
|
||||||
|
foreach (var task in remainingTasks)
|
||||||
|
ImGui.TextUnformatted(task);
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
private readonly TerritoryData _territoryData;
|
private readonly TerritoryData _territoryData;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
private readonly UiUtils _uiUtils;
|
||||||
|
|
||||||
private List<QuestInfo> _quests = [];
|
private List<QuestInfo> _quests = [];
|
||||||
private List<QuestInfo> _offeredQuests = [];
|
private List<QuestInfo> _offeredQuests = [];
|
||||||
@ -40,7 +41,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
|
|
||||||
public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions,
|
public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions,
|
||||||
QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface,
|
QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface,
|
||||||
TerritoryData territoryData, IClientState clientState)
|
TerritoryData territoryData, IClientState clientState, UiUtils uiUtils)
|
||||||
: base($"Quest Selection{WindowId}")
|
: base($"Quest Selection{WindowId}")
|
||||||
{
|
{
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
@ -52,6 +53,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_territoryData = territoryData;
|
_territoryData = territoryData;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_uiUtils = uiUtils;
|
||||||
|
|
||||||
Size = new Vector2(500, 200);
|
Size = new Vector2(500, 200);
|
||||||
SizeCondition = ImGuiCond.Once;
|
SizeCondition = ImGuiCond.Once;
|
||||||
@ -151,7 +153,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
if (ImGui.TableNextColumn())
|
if (ImGui.TableNextColumn())
|
||||||
{
|
{
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
var (color, icon, tooltipText) = GetQuestStyle(quest.QuestId);
|
var (color, icon, tooltipText) = _uiUtils.GetQuestStyle(quest.QuestId);
|
||||||
using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||||
{
|
{
|
||||||
if (isKnownQuest)
|
if (isKnownQuest)
|
||||||
@ -251,18 +253,6 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
_chatGui.Print($"Copied '{fileName}' to clipboard");
|
_chatGui.Print($"Copied '{fileName}' to clipboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
private (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(ushort questId)
|
|
||||||
{
|
|
||||||
if (_gameFunctions.IsQuestAccepted(questId))
|
|
||||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Active");
|
|
||||||
else if (_gameFunctions.IsQuestAcceptedOrComplete(questId))
|
|
||||||
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
|
|
||||||
else if (_gameFunctions.IsQuestLocked(questId))
|
|
||||||
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
|
|
||||||
else
|
|
||||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Available");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawQuestUnlocks(QuestInfo quest, int counter)
|
private void DrawQuestUnlocks(QuestInfo quest, int counter)
|
||||||
{
|
{
|
||||||
if (counter >= 10)
|
if (counter >= 10)
|
||||||
@ -287,7 +277,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
foreach (var q in quest.PreviousQuests)
|
foreach (var q in quest.PreviousQuests)
|
||||||
{
|
{
|
||||||
var qInfo = _questData.GetQuestInfo(q);
|
var qInfo = _questData.GetQuestInfo(q);
|
||||||
var (iconColor, icon, _) = GetQuestStyle(q);
|
var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q);
|
||||||
// ReSharper disable once UnusedVariable
|
// ReSharper disable once UnusedVariable
|
||||||
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||||
{
|
{
|
||||||
@ -319,7 +309,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
foreach (var q in quest.QuestLocks)
|
foreach (var q in quest.QuestLocks)
|
||||||
{
|
{
|
||||||
var qInfo = _questData.GetQuestInfo(q);
|
var qInfo = _questData.GetQuestInfo(q);
|
||||||
var (iconColor, icon, _) = GetQuestStyle(q);
|
var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q);
|
||||||
// ReSharper disable once UnusedVariable
|
// ReSharper disable once UnusedVariable
|
||||||
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||||
{
|
{
|
||||||
@ -334,6 +324,34 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (counter == 0 && quest.PreviousInstanceContent.Count > 0)
|
||||||
|
{
|
||||||
|
if (quest.PreviousInstanceContent.Count > 1)
|
||||||
|
{
|
||||||
|
if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.All)
|
||||||
|
ImGui.Text("Requires all:");
|
||||||
|
else if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne)
|
||||||
|
ImGui.Text("Requires one:");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ImGui.Text("Requires:");
|
||||||
|
|
||||||
|
foreach (var instanceId in quest.PreviousInstanceContent)
|
||||||
|
{
|
||||||
|
string instanceName = _territoryData.GetInstanceName(instanceId) ?? "?";
|
||||||
|
var (iconColor, icon) = UiUtils.GetInstanceStyle(instanceId);
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedVariable
|
||||||
|
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||||
|
{
|
||||||
|
ImGui.TextColored(iconColor, icon.ToIconString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted(instanceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (counter > 0)
|
if (counter > 0)
|
||||||
ImGui.Unindent();
|
ImGui.Unindent();
|
||||||
}
|
}
|
||||||
|
@ -1,102 +1,50 @@
|
|||||||
using System;
|
using System.Numerics;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
|
||||||
using Dalamud.Game.ClientState.Objects;
|
|
||||||
using Dalamud.Game.Text;
|
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Interface.Colors;
|
|
||||||
using Dalamud.Interface.Components;
|
|
||||||
using Dalamud.Interface.Utility;
|
|
||||||
using Dalamud.Interface.Utility.Raii;
|
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using LLib.ImGui;
|
using LLib.ImGui;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
using Questionable.Controller.Steps.Shared;
|
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
using Questionable.External;
|
using Questionable.Windows.QuestComponents;
|
||||||
using Questionable.Model;
|
|
||||||
using Questionable.Model.V1;
|
|
||||||
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
|
||||||
|
|
||||||
namespace Questionable.Windows;
|
namespace Questionable.Windows;
|
||||||
|
|
||||||
internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
|
internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
|
||||||
{
|
{
|
||||||
|
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
private readonly MovementController _movementController;
|
|
||||||
private readonly QuestController _questController;
|
private readonly QuestController _questController;
|
||||||
private readonly GameFunctions _gameFunctions;
|
|
||||||
private readonly ChatFunctions _chatFunctions;
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly IFramework _framework;
|
|
||||||
private readonly ITargetManager _targetManager;
|
|
||||||
private readonly GameUiController _gameUiController;
|
|
||||||
private readonly CombatController _combatController;
|
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
private readonly NavmeshIpc _navmeshIpc;
|
|
||||||
private readonly QuestRegistry _questRegistry;
|
|
||||||
private readonly QuestData _questData;
|
|
||||||
private readonly TerritoryData _territoryData;
|
private readonly TerritoryData _territoryData;
|
||||||
private readonly ICondition _condition;
|
private readonly ActiveQuestComponent _activeQuestComponent;
|
||||||
private readonly IGameGui _gameGui;
|
private readonly ARealmRebornComponent _aRealmRebornComponent;
|
||||||
private readonly QuestSelectionWindow _questSelectionWindow;
|
private readonly CreationUtilsComponent _creationUtilsComponent;
|
||||||
private readonly QuestValidationWindow _questValidationWindow;
|
private readonly QuickAccessButtonsComponent _quickAccessButtonsComponent;
|
||||||
private readonly ICommandManager _commandManager;
|
private readonly RemainingTasksComponent _remainingTasksComponent;
|
||||||
private readonly ILogger<QuestWindow> _logger;
|
|
||||||
|
|
||||||
public QuestWindow(IDalamudPluginInterface pluginInterface,
|
public QuestWindow(IDalamudPluginInterface pluginInterface,
|
||||||
MovementController movementController,
|
|
||||||
QuestController questController,
|
QuestController questController,
|
||||||
GameFunctions gameFunctions,
|
|
||||||
ChatFunctions chatFunctions,
|
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IFramework framework,
|
|
||||||
ITargetManager targetManager,
|
|
||||||
GameUiController gameUiController,
|
|
||||||
CombatController combatController,
|
|
||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
NavmeshIpc navmeshIpc,
|
|
||||||
QuestRegistry questRegistry,
|
|
||||||
QuestData questData,
|
|
||||||
TerritoryData territoryData,
|
TerritoryData territoryData,
|
||||||
ICondition condition,
|
ActiveQuestComponent activeQuestComponent,
|
||||||
IGameGui gameGui,
|
ARealmRebornComponent aRealmRebornComponent,
|
||||||
QuestSelectionWindow questSelectionWindow,
|
CreationUtilsComponent creationUtilsComponent,
|
||||||
QuestValidationWindow questValidationWindow,
|
QuickAccessButtonsComponent quickAccessButtonsComponent,
|
||||||
ICommandManager commandManager,
|
RemainingTasksComponent remainingTasksComponent)
|
||||||
ILogger<QuestWindow> logger)
|
|
||||||
: base("Questionable###Questionable", ImGuiWindowFlags.AlwaysAutoResize)
|
: base("Questionable###Questionable", ImGuiWindowFlags.AlwaysAutoResize)
|
||||||
{
|
{
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_movementController = movementController;
|
|
||||||
_questController = questController;
|
_questController = questController;
|
||||||
_gameFunctions = gameFunctions;
|
|
||||||
_chatFunctions = chatFunctions;
|
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_framework = framework;
|
|
||||||
_targetManager = targetManager;
|
|
||||||
_gameUiController = gameUiController;
|
|
||||||
_combatController = combatController;
|
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_navmeshIpc = navmeshIpc;
|
|
||||||
_questRegistry = questRegistry;
|
|
||||||
_questData = questData;
|
|
||||||
_territoryData = territoryData;
|
_territoryData = territoryData;
|
||||||
_condition = condition;
|
_activeQuestComponent = activeQuestComponent;
|
||||||
_gameGui = gameGui;
|
_aRealmRebornComponent = aRealmRebornComponent;
|
||||||
_questSelectionWindow = questSelectionWindow;
|
_creationUtilsComponent = creationUtilsComponent;
|
||||||
_questValidationWindow = questValidationWindow;
|
_quickAccessButtonsComponent = quickAccessButtonsComponent;
|
||||||
_commandManager = commandManager;
|
_remainingTasksComponent = remainingTasksComponent;
|
||||||
_logger = logger;
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
@ -127,564 +75,19 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
|
|||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
DrawQuest();
|
_activeQuestComponent.Draw();
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
DrawCreationUtils();
|
if (_aRealmRebornComponent.ShouldDraw)
|
||||||
|
{
|
||||||
|
_aRealmRebornComponent.Draw();
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
_creationUtilsComponent.Draw();
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
DrawQuickAccessButtons();
|
_quickAccessButtonsComponent.Draw();
|
||||||
DrawRemainingTasks();
|
_remainingTasksComponent.Draw();
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawQuest()
|
|
||||||
{
|
|
||||||
var currentQuestDetails = _questController.CurrentQuestDetails;
|
|
||||||
QuestController.QuestProgress? currentQuest = currentQuestDetails?.Progress;
|
|
||||||
QuestController.CurrentQuestType? currentQuestType = currentQuestDetails?.Type;
|
|
||||||
if (currentQuest != null)
|
|
||||||
{
|
|
||||||
if (currentQuestType == QuestController.CurrentQuestType.Simulated)
|
|
||||||
{
|
|
||||||
var simulatedQuest = _questController.SimulatedQuest ?? currentQuest;
|
|
||||||
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
|
||||||
ImGui.TextUnformatted(
|
|
||||||
$"Simulated Quest: {simulatedQuest.Quest.Info.Name} / {simulatedQuest.Sequence} / {simulatedQuest.Step}");
|
|
||||||
}
|
|
||||||
else if (currentQuestType == QuestController.CurrentQuestType.Next)
|
|
||||||
{
|
|
||||||
var startedQuest = _questController.StartedQuest;
|
|
||||||
if (startedQuest != null)
|
|
||||||
{
|
|
||||||
ImGui.TextUnformatted(
|
|
||||||
$"Quest: {startedQuest.Quest.Info.Name} / {startedQuest.Sequence} / {startedQuest.Step}");
|
|
||||||
|
|
||||||
if (startedQuest.Quest.Root.Disabled)
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, "Disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
|
|
||||||
ImGui.TextUnformatted(
|
|
||||||
$"Next Quest: {currentQuest.Quest.Info.Name} / {currentQuest.Sequence} / {currentQuest.Step}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.TextUnformatted(
|
|
||||||
$"Quest: {currentQuest.Quest.Info.Name} / {currentQuest.Sequence} / {currentQuest.Step}");
|
|
||||||
|
|
||||||
if (currentQuest.Quest.Root.Disabled)
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, "Disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ImGui.BeginDisabled();
|
|
||||||
var questWork = _gameFunctions.GetQuestEx(currentQuest.Quest.QuestId);
|
|
||||||
if (questWork != null)
|
|
||||||
{
|
|
||||||
var qw = questWork.Value;
|
|
||||||
string vars = "";
|
|
||||||
for (int i = 0; i < 6; ++i)
|
|
||||||
{
|
|
||||||
vars += qw.Variables[i] + " ";
|
|
||||||
if (i % 2 == 1)
|
|
||||||
vars += " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
// For combat quests, a sequence to kill 3 enemies works a bit like this:
|
|
||||||
// Trigger enemies → 0
|
|
||||||
// Kill first enemy → 1
|
|
||||||
// Kill second enemy → 2
|
|
||||||
// Last enemy → increase sequence, reset variable to 0
|
|
||||||
// The order in which enemies are killed doesn't seem to matter.
|
|
||||||
// If multiple waves spawn, this continues to count up (e.g. 1 enemy from wave 1, 2 enemies from wave 2, 1 from wave 3) would count to 3 then 0
|
|
||||||
ImGui.Text($"QW: {vars.Trim()}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (currentQuest.Quest.QuestId == _questController.NextQuest?.Quest.QuestId)
|
|
||||||
ImGui.TextUnformatted("(Next quest in story line not accepted)");
|
|
||||||
else
|
|
||||||
ImGui.TextUnformatted("(Not accepted)");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
if (_combatController.IsRunning)
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudOrange, "In Combat");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.BeginDisabled();
|
|
||||||
ImGui.TextUnformatted(_questController.DebugState ?? string.Empty);
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
QuestSequence? currentSequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
|
|
||||||
QuestStep? currentStep = currentSequence?.FindStep(currentQuest.Step);
|
|
||||||
bool colored = currentStep is
|
|
||||||
{ InteractionType: EInteractionType.Instruction or EInteractionType.WaitForManualProgress };
|
|
||||||
if (colored)
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange);
|
|
||||||
ImGui.TextUnformatted(currentStep?.Comment ?? currentSequence?.Comment ?? currentQuest.Quest.Root.Comment ?? string.Empty);
|
|
||||||
if (colored)
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
|
|
||||||
//var nextStep = _questController.GetNextStep();
|
|
||||||
//ImGui.BeginDisabled(nextStep.Step == null);
|
|
||||||
ImGui.Text(_questController.ToStatString());
|
|
||||||
//ImGui.EndDisabled();
|
|
||||||
|
|
||||||
ImGui.BeginDisabled(_questController.IsRunning);
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
|
|
||||||
{
|
|
||||||
// if we haven't accepted this quest, mark it as next quest so that we can optionally use aetherytes to travel
|
|
||||||
if (questWork == null)
|
|
||||||
_questController.SetNextQuest(currentQuest.Quest);
|
|
||||||
|
|
||||||
_questController.ExecuteNextStep(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.StepForward, "Step"))
|
|
||||||
{
|
|
||||||
_questController.ExecuteNextStep(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
|
|
||||||
{
|
|
||||||
_movementController.Stop();
|
|
||||||
_questController.Stop("Manual");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool lastStep = currentStep ==
|
|
||||||
currentQuest.Quest.FindSequence(currentQuest.Sequence)?.Steps.LastOrDefault();
|
|
||||||
colored = currentStep != null
|
|
||||||
&& !lastStep
|
|
||||||
&& currentStep.InteractionType == EInteractionType.Instruction
|
|
||||||
&& _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
|
|
||||||
|
|
||||||
ImGui.BeginDisabled(lastStep);
|
|
||||||
if (colored)
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
|
|
||||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip"))
|
|
||||||
{
|
|
||||||
_movementController.Stop();
|
|
||||||
_questController.Skip(currentQuest.Quest.QuestId, currentQuest.Sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (colored)
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
if (_commandManager.Commands.TryGetValue("/questinfo", out var commandInfo))
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Atlas))
|
|
||||||
_commandManager.DispatchCommand("/questinfo",
|
|
||||||
currentQuest.Quest.QuestId.ToString(CultureInfo.InvariantCulture), commandInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest;
|
|
||||||
if (ImGui.Checkbox("Automatically accept next quest", ref autoAcceptNextQuest))
|
|
||||||
{
|
|
||||||
_configuration.General.AutoAcceptNextQuest = autoAcceptNextQuest;
|
|
||||||
_pluginInterface.SavePluginConfig(_configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (_questController.SimulatedQuest != null)
|
|
||||||
{
|
|
||||||
var simulatedQuest = _questController.SimulatedQuest;
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, "Quest sim active (experimental)");
|
|
||||||
ImGui.Text($"Sequence: {simulatedQuest.Sequence}");
|
|
||||||
|
|
||||||
ImGui.BeginDisabled(simulatedQuest.Sequence == 0);
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus))
|
|
||||||
{
|
|
||||||
_movementController.Stop();
|
|
||||||
_questController.Stop("Sim-");
|
|
||||||
|
|
||||||
byte oldSequence = simulatedQuest.Sequence;
|
|
||||||
byte newSequence = simulatedQuest.Quest.Root.QuestSequence
|
|
||||||
.Select(x => (byte)x.Sequence)
|
|
||||||
.LastOrDefault(x => x < oldSequence, byte.MinValue);
|
|
||||||
|
|
||||||
_questController.SimulatedQuest.SetSequence(newSequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.BeginDisabled(simulatedQuest.Sequence >= 255);
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
|
|
||||||
{
|
|
||||||
_movementController.Stop();
|
|
||||||
_questController.Stop("Sim+");
|
|
||||||
|
|
||||||
byte oldSequence = simulatedQuest.Sequence;
|
|
||||||
byte newSequence = simulatedQuest.Quest.Root.QuestSequence
|
|
||||||
.Select(x => (byte)x.Sequence)
|
|
||||||
.FirstOrDefault(x => x > oldSequence, byte.MaxValue);
|
|
||||||
|
|
||||||
simulatedQuest.SetSequence(newSequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
var simulatedSequence = simulatedQuest.Quest.FindSequence(simulatedQuest.Sequence);
|
|
||||||
if (simulatedSequence != null)
|
|
||||||
{
|
|
||||||
using var _ = ImRaii.PushId("SimulatedStep");
|
|
||||||
|
|
||||||
ImGui.Text($"Step: {simulatedQuest.Step} / {simulatedSequence.Steps.Count - 1}");
|
|
||||||
|
|
||||||
ImGui.BeginDisabled(simulatedQuest.Step == 0);
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus))
|
|
||||||
{
|
|
||||||
_movementController.Stop();
|
|
||||||
_questController.Stop("SimStep-");
|
|
||||||
|
|
||||||
simulatedQuest.SetStep(Math.Min(simulatedQuest.Step - 1,
|
|
||||||
simulatedSequence.Steps.Count - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.BeginDisabled(simulatedQuest.Step >= simulatedSequence.Steps.Count);
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
|
|
||||||
{
|
|
||||||
_movementController.Stop();
|
|
||||||
_questController.Stop("SimStep+");
|
|
||||||
|
|
||||||
simulatedQuest.SetStep(
|
|
||||||
simulatedQuest.Step == simulatedSequence.Steps.Count - 1
|
|
||||||
? 255
|
|
||||||
: (simulatedQuest.Step + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
if (ImGui.Button("Skip current task"))
|
|
||||||
{
|
|
||||||
_questController.SkipSimulatedTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("Clear sim"))
|
|
||||||
{
|
|
||||||
_questController.SimulateQuest(null);
|
|
||||||
|
|
||||||
_movementController.Stop();
|
|
||||||
_questController.Stop("ClearSim");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.Text("No active quest");
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudGrey, $"{_questRegistry.Count} quests loaded");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void DrawCreationUtils()
|
|
||||||
{
|
|
||||||
Debug.Assert(_clientState.LocalPlayer != null, "_clientState.LocalPlayer != null");
|
|
||||||
|
|
||||||
string territoryName = _territoryData.GetNameAndId(_clientState.TerritoryType);
|
|
||||||
ImGui.Text(territoryName);
|
|
||||||
|
|
||||||
if (_gameFunctions.IsFlyingUnlockedInCurrentZone())
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.Text(SeIconChar.BotanistSprout.ToIconString());
|
|
||||||
}
|
|
||||||
|
|
||||||
var q = _gameFunctions.GetCurrentQuest();
|
|
||||||
ImGui.Text($"Current Quest: {q.CurrentQuest} → {q.Sequence}");
|
|
||||||
|
|
||||||
#if false
|
|
||||||
var questManager = QuestManager.Instance();
|
|
||||||
if (questManager != null)
|
|
||||||
{
|
|
||||||
for (int i = questManager->TrackedQuests.Length - 1; i >= 0; --i)
|
|
||||||
{
|
|
||||||
var trackedQuest = questManager->TrackedQuests[i];
|
|
||||||
switch (trackedQuest.QuestType)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
ImGui.Text($"Tracked quest {i}: {trackedQuest.QuestType}, {trackedQuest.Index}");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
_questRegistry.TryGetQuest(questManager->NormalQuests[trackedQuest.Index].QuestId,
|
|
||||||
out var quest);
|
|
||||||
ImGui.Text(
|
|
||||||
$"Tracked quest: {questManager->NormalQuests[trackedQuest.Index].QuestId}, {trackedQuest.Index}: {quest?.Info.Name}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (_targetManager.Target != null)
|
|
||||||
{
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.Text(string.Create(CultureInfo.InvariantCulture,
|
|
||||||
$"Target: {_targetManager.Target.Name} ({_targetManager.Target.ObjectKind}; {_targetManager.Target.DataId})"));
|
|
||||||
|
|
||||||
GameObject* gameObject = (GameObject*)_targetManager.Target.Address;
|
|
||||||
ImGui.Text(string.Create(CultureInfo.InvariantCulture,
|
|
||||||
$"Distance: {(_targetManager.Target.Position - _clientState.LocalPlayer.Position).Length():F2}"));
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
float verticalDistance = _targetManager.Target.Position.Y - _clientState.LocalPlayer.Position.Y;
|
|
||||||
string verticalDistanceText = string.Create(CultureInfo.InvariantCulture, $"Y: {verticalDistance:F2}");
|
|
||||||
if (Math.Abs(verticalDistance) >= MovementController.DefaultVerticalInteractionDistance)
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudOrange, verticalDistanceText);
|
|
||||||
else
|
|
||||||
ImGui.Text(verticalDistanceText);
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.Text($"QM: {gameObject->NamePlateIconId}");
|
|
||||||
|
|
||||||
ImGui.BeginDisabled(!_movementController.IsNavmeshReady);
|
|
||||||
if (!_movementController.IsPathfinding)
|
|
||||||
{
|
|
||||||
if (ImGui.Button("Move to Target"))
|
|
||||||
{
|
|
||||||
_movementController.NavigateTo(EMovementType.DebugWindow, _targetManager.Target.DataId,
|
|
||||||
_targetManager.Target.Position,
|
|
||||||
fly: _condition[ConditionFlag.Mounted] && _gameFunctions.IsFlyingUnlockedInCurrentZone(),
|
|
||||||
sprint: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (ImGui.Button("Cancel pathfinding"))
|
|
||||||
_movementController.ResetPathfinding();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.BeginDisabled(!_questData.IsIssuerOfAnyQuest(_targetManager.Target.DataId));
|
|
||||||
bool showQuests = ImGuiComponents.IconButton(FontAwesomeIcon.MapMarkerAlt);
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Show all Quests starting with your current target.");
|
|
||||||
if (showQuests)
|
|
||||||
_questSelectionWindow.OpenForTarget(_targetManager.Target);
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
bool interact = ImGuiComponents.IconButton(FontAwesomeIcon.MousePointer);
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Interact with your current target.");
|
|
||||||
if (interact)
|
|
||||||
{
|
|
||||||
ulong result = TargetSystem.Instance()->InteractWithObject(
|
|
||||||
(GameObject*)_targetManager.Target.Address, false);
|
|
||||||
_logger.LogInformation("XXXXX Interaction Result: {Result}", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip(
|
|
||||||
"Left click: Copy target position as JSON.\nRight click: Copy target position as C# code.");
|
|
||||||
if (copy)
|
|
||||||
{
|
|
||||||
string interactionType = gameObject->NamePlateIconId switch
|
|
||||||
{
|
|
||||||
71201 or 71211 or 71221 or 71231 or 71341 or 71351 => "AcceptQuest",
|
|
||||||
71202 or 71212 or 71222 or 71232 or 71342 or 71352 => "AcceptQuest", // repeatable
|
|
||||||
71205 or 71215 or 71225 or 71235 or 71345 or 71355 => "CompleteQuest",
|
|
||||||
_ => "Interact",
|
|
||||||
};
|
|
||||||
ImGui.SetClipboardText($$"""
|
|
||||||
"DataId": {{_targetManager.Target.DataId}},
|
|
||||||
"Position": {
|
|
||||||
"X": {{_targetManager.Target.Position.X.ToString(CultureInfo.InvariantCulture)}},
|
|
||||||
"Y": {{_targetManager.Target.Position.Y.ToString(CultureInfo.InvariantCulture)}},
|
|
||||||
"Z": {{_targetManager.Target.Position.Z.ToString(CultureInfo.InvariantCulture)}}
|
|
||||||
},
|
|
||||||
"TerritoryId": {{_clientState.TerritoryType}},
|
|
||||||
"InteractionType": "{{interactionType}}"
|
|
||||||
""");
|
|
||||||
}
|
|
||||||
else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
|
||||||
{
|
|
||||||
if (_targetManager.Target.ObjectKind == ObjectKind.Aetheryte)
|
|
||||||
{
|
|
||||||
EAetheryteLocation location = (EAetheryteLocation)_targetManager.Target.DataId;
|
|
||||||
ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
|
|
||||||
$"{{EAetheryteLocation.{location}, new({_targetManager.Target.Position.X}f, {_targetManager.Target.Position.Y}f, {_targetManager.Target.Position.Z}f)}},"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
|
|
||||||
$"new({_targetManager.Target.Position.X}f, {_targetManager.Target.Position.Y}f, {_targetManager.Target.Position.Z}f)"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip(
|
|
||||||
"Left click: Copy your position as JSON.\nRight click: Copy your position as C# code.");
|
|
||||||
if (copy)
|
|
||||||
{
|
|
||||||
ImGui.SetClipboardText($$"""
|
|
||||||
"Position": {
|
|
||||||
"X": {{_clientState.LocalPlayer.Position.X.ToString(CultureInfo.InvariantCulture)}},
|
|
||||||
"Y": {{_clientState.LocalPlayer.Position.Y.ToString(CultureInfo.InvariantCulture)}},
|
|
||||||
"Z": {{_clientState.LocalPlayer.Position.Z.ToString(CultureInfo.InvariantCulture)}}
|
|
||||||
},
|
|
||||||
"TerritoryId": {{_clientState.TerritoryType}},
|
|
||||||
"InteractionType": ""
|
|
||||||
""");
|
|
||||||
}
|
|
||||||
else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
|
||||||
{
|
|
||||||
Vector3 position = _clientState.LocalPlayer!.Position;
|
|
||||||
ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
|
|
||||||
$"new({position.X}f, {position.Y}f, {position.Z}f)"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong hoveredItemId = _gameGui.HoveredItem;
|
|
||||||
if (hoveredItemId != 0)
|
|
||||||
{
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.Text($"Hovered Item: {hoveredItemId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void DrawQuickAccessButtons()
|
|
||||||
{
|
|
||||||
var map = AgentMap.Instance();
|
|
||||||
ImGui.BeginDisabled(map == null || map->IsFlagMarkerSet == 0 ||
|
|
||||||
map->FlagMapMarker.TerritoryId != _clientState.TerritoryType ||
|
|
||||||
!_navmeshIpc.IsReady);
|
|
||||||
if (ImGui.Button("Move to Flag"))
|
|
||||||
{
|
|
||||||
_movementController.Destination = null;
|
|
||||||
_chatFunctions.ExecuteCommand(
|
|
||||||
$"/vnav {(_condition[ConditionFlag.Mounted] && _gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
ImGui.BeginDisabled(!_movementController.IsPathRunning);
|
|
||||||
if (ImGui.Button("Stop Nav"))
|
|
||||||
{
|
|
||||||
_movementController.Stop();
|
|
||||||
_questController.Stop("Manual");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
if (ImGui.Button("Reload Data"))
|
|
||||||
{
|
|
||||||
_questController.Reload();
|
|
||||||
_framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(),
|
|
||||||
TimeSpan.FromMilliseconds(200));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_questRegistry.ValidationIssueCount > 0)
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (DrawValidationIssuesButton())
|
|
||||||
_questValidationWindow.IsOpen = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool DrawValidationIssuesButton()
|
|
||||||
{
|
|
||||||
int errorCount = _questRegistry.ValidationErrorCount;
|
|
||||||
int infoCount = _questRegistry.ValidationIssueCount - _questRegistry.ValidationErrorCount;
|
|
||||||
if (errorCount == 0 && infoCount == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int partsToRender = errorCount == 0 || infoCount == 0 ? 1 : 2;
|
|
||||||
using var id = ImRaii.PushId("validationissues");
|
|
||||||
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
var icon1 = FontAwesomeIcon.TimesCircle;
|
|
||||||
var icon2 = FontAwesomeIcon.InfoCircle;
|
|
||||||
Vector2 iconSize1 = errorCount > 0 ? ImGui.CalcTextSize(icon1.ToIconString()) : Vector2.Zero;
|
|
||||||
Vector2 iconSize2 = infoCount > 0 ? ImGui.CalcTextSize(icon2.ToIconString()) : Vector2.Zero;
|
|
||||||
ImGui.PopFont();
|
|
||||||
|
|
||||||
string text1 = errorCount > 0 ? errorCount.ToString(CultureInfo.InvariantCulture) : string.Empty;
|
|
||||||
string text2 = infoCount > 0 ? infoCount.ToString(CultureInfo.InvariantCulture) : string.Empty;
|
|
||||||
Vector2 textSize1 = errorCount > 0 ? ImGui.CalcTextSize(text1) : Vector2.Zero;
|
|
||||||
Vector2 textSize2 = infoCount > 0 ? ImGui.CalcTextSize(text2) : Vector2.Zero;
|
|
||||||
var dl = ImGui.GetWindowDrawList();
|
|
||||||
var cursor = ImGui.GetCursorScreenPos();
|
|
||||||
|
|
||||||
var iconPadding = 3 * ImGuiHelpers.GlobalScale;
|
|
||||||
|
|
||||||
// Draw an ImGui button with the icon and text
|
|
||||||
var buttonWidth = iconSize1.X + iconSize2.X + textSize1.X + textSize2.X +
|
|
||||||
(ImGui.GetStyle().FramePadding.X * 2) + iconPadding * 2 * partsToRender;
|
|
||||||
var buttonHeight = ImGui.GetFrameHeight();
|
|
||||||
var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight));
|
|
||||||
|
|
||||||
// Draw the icon on the window drawlist
|
|
||||||
Vector2 position = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X,
|
|
||||||
cursor.Y + ImGui.GetStyle().FramePadding.Y);
|
|
||||||
if (errorCount > 0)
|
|
||||||
{
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
dl.AddText(position, ImGui.GetColorU32(ImGuiColors.DalamudRed), icon1.ToIconString());
|
|
||||||
ImGui.PopFont();
|
|
||||||
position = position with { X = position.X + iconSize1.X + iconPadding };
|
|
||||||
|
|
||||||
// Draw the text on the window drawlist
|
|
||||||
dl.AddText(position, ImGui.GetColorU32(ImGuiCol.Text), text1);
|
|
||||||
position = position with { X = position.X + textSize1.X + 2 * iconPadding };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infoCount > 0)
|
|
||||||
{
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
dl.AddText(position, ImGui.GetColorU32(ImGuiColors.ParsedBlue), icon2.ToIconString());
|
|
||||||
ImGui.PopFont();
|
|
||||||
position = position with { X = position.X + iconSize2.X + iconPadding };
|
|
||||||
|
|
||||||
// Draw the text on the window drawlist
|
|
||||||
dl.AddText(position, ImGui.GetColorU32(ImGuiCol.Text), text2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawRemainingTasks()
|
|
||||||
{
|
|
||||||
var remainingTasks = _questController.GetRemainingTaskNames();
|
|
||||||
if (remainingTasks.Count > 0)
|
|
||||||
{
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.BeginDisabled();
|
|
||||||
foreach (var task in remainingTasks)
|
|
||||||
ImGui.TextUnformatted(task);
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
57
Questionable/Windows/UiUtils.cs
Normal file
57
Questionable/Windows/UiUtils.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Questionable.Windows;
|
||||||
|
|
||||||
|
internal sealed class UiUtils
|
||||||
|
{
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
|
|
||||||
|
public UiUtils(GameFunctions gameFunctions, IDalamudPluginInterface pluginInterface)
|
||||||
|
{
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(ushort questId)
|
||||||
|
{
|
||||||
|
if (_gameFunctions.IsQuestAccepted(questId))
|
||||||
|
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Active");
|
||||||
|
else if (_gameFunctions.IsQuestAcceptedOrComplete(questId))
|
||||||
|
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
|
||||||
|
else if (_gameFunctions.IsQuestLocked(questId))
|
||||||
|
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
|
||||||
|
else
|
||||||
|
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Available");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (Vector4 color, FontAwesomeIcon icon) GetInstanceStyle(ushort instanceId)
|
||||||
|
{
|
||||||
|
if (UIState.IsInstanceContentCompleted(instanceId))
|
||||||
|
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check);
|
||||||
|
else if (UIState.IsInstanceContentUnlocked(instanceId))
|
||||||
|
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight);
|
||||||
|
else
|
||||||
|
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChecklistItem(string text, Vector4 color, FontAwesomeIcon icon)
|
||||||
|
{
|
||||||
|
// ReSharper disable once UnusedVariable
|
||||||
|
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||||
|
{
|
||||||
|
ImGui.TextColored(color, icon.ToIconString());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hover = ImGui.IsItemHovered();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted(text);
|
||||||
|
return hover;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user