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 NeverFly { get; set; }
|
||||
public bool AdditionalStatusInformation { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,9 @@ internal sealed class MovementOverrideController
|
||||
// New Gridania Navmesh workaround
|
||||
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)),
|
||||
|
||||
// eastern thanalan
|
||||
|
@ -391,7 +391,8 @@ internal sealed class QuestController
|
||||
catch (Exception e)
|
||||
{
|
||||
_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");
|
||||
return;
|
||||
}
|
||||
@ -466,6 +467,9 @@ internal sealed class QuestController
|
||||
ClearTasksInternal();
|
||||
_automatic = automatic;
|
||||
|
||||
if (TryPickPriorityQuest())
|
||||
_logger.LogInformation("Using priority quest over current quest");
|
||||
|
||||
(QuestSequence? seq, QuestStep? step) = GetNextStep();
|
||||
if (CurrentQuest == null || seq == null || step == null)
|
||||
{
|
||||
@ -590,6 +594,47 @@ internal sealed class QuestController
|
||||
_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(
|
||||
DateTime StartedAt,
|
||||
int PointMenuCounter = 0);
|
||||
|
@ -121,7 +121,7 @@ internal static class UseItem
|
||||
if (DateTime.Now <= _continueAt)
|
||||
return ETaskResult.StillRunning;
|
||||
|
||||
if (ItemId == VesperBayAetheryteTicket)
|
||||
if (ItemId == VesperBayAetheryteTicket && _usedItem)
|
||||
{
|
||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||
if (inventoryManager == null)
|
||||
@ -131,7 +131,7 @@ internal static class UseItem
|
||||
}
|
||||
|
||||
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)
|
||||
logger.LogInformation(
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
@ -12,6 +13,7 @@ internal sealed class TerritoryData
|
||||
private readonly ImmutableDictionary<uint, string> _territoryNames;
|
||||
private readonly ImmutableHashSet<ushort> _territoriesWithMount;
|
||||
private readonly ImmutableHashSet<ushort> _dutyTerritories;
|
||||
private readonly ImmutableDictionary<ushort, string> _instanceNames;
|
||||
|
||||
public TerritoryData(IDataManager dataManager)
|
||||
{
|
||||
@ -35,6 +37,10 @@ internal sealed class TerritoryData
|
||||
.Where(x => x.RowId > 0 && x.ContentFinderCondition.Row != 0)
|
||||
.Select(x => (ushort)x.RowId)
|
||||
.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);
|
||||
@ -51,4 +57,6 @@ internal sealed class TerritoryData
|
||||
public bool CanUseMount(ushort territoryId) => _territoriesWithMount.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;
|
||||
}
|
||||
|
||||
if (IsQuestLocked(questId))
|
||||
return false;
|
||||
|
||||
// if we're not at a high enough level to continue, we also ignore it
|
||||
var currentLevel = _clientState.LocalPlayer?.Level ?? 0;
|
||||
if (currentLevel != 0 && quest != null && quest.Info.Level > currentLevel)
|
||||
@ -288,18 +291,36 @@ internal sealed unsafe class GameFunctions
|
||||
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);
|
||||
if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.All &&
|
||||
questInfo.PreviousQuests.Count == completedQuests)
|
||||
return false;
|
||||
else if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
|
||||
return false;
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -23,8 +23,11 @@ internal sealed class QuestInfo
|
||||
QuestLockJoin = (QuestJoin)quest.QuestLockJoin;
|
||||
IsMainScenarioQuest = quest.JournalGenre?.Value?.JournalCategory?.Value?.JournalSection?.Row is 0 or 1;
|
||||
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 string Name { get; }
|
||||
public ushort Level { get; }
|
||||
@ -34,6 +37,8 @@ internal sealed class QuestInfo
|
||||
public QuestJoin PreviousQuestJoin { get; }
|
||||
public ImmutableList<ushort> QuestLocks { get; set; }
|
||||
public QuestJoin QuestLockJoin { get; set; }
|
||||
public List<ushort> PreviousInstanceContent { get; set; }
|
||||
public QuestJoin PreviousInstanceContentJoin { get; set; }
|
||||
public bool IsMainScenarioQuest { get; }
|
||||
public bool CompletesInstantly { get; set; }
|
||||
|
||||
|
@ -19,6 +19,7 @@ using Questionable.External;
|
||||
using Questionable.Validation;
|
||||
using Questionable.Validation.Validators;
|
||||
using Questionable.Windows;
|
||||
using Questionable.Windows.QuestComponents;
|
||||
using Action = Questionable.Controller.Steps.Interactions.Action;
|
||||
|
||||
namespace Questionable;
|
||||
@ -152,6 +153,14 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
|
||||
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<ConfigWindow>();
|
||||
serviceCollection.AddSingleton<DebugOverlay>();
|
||||
|
@ -100,6 +100,13 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
|
||||
Save();
|
||||
}
|
||||
|
||||
bool additionalStatusInformation = _configuration.Advanced.AdditionalStatusInformation;
|
||||
if (ImGui.Checkbox("Draw additional status information", ref additionalStatusInformation))
|
||||
{
|
||||
_configuration.Advanced.AdditionalStatusInformation = additionalStatusInformation;
|
||||
Save();
|
||||
}
|
||||
|
||||
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 TerritoryData _territoryData;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly UiUtils _uiUtils;
|
||||
|
||||
private List<QuestInfo> _quests = [];
|
||||
private List<QuestInfo> _offeredQuests = [];
|
||||
@ -40,7 +41,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
|
||||
public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions,
|
||||
QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface,
|
||||
TerritoryData territoryData, IClientState clientState)
|
||||
TerritoryData territoryData, IClientState clientState, UiUtils uiUtils)
|
||||
: base($"Quest Selection{WindowId}")
|
||||
{
|
||||
_questData = questData;
|
||||
@ -52,6 +53,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
_pluginInterface = pluginInterface;
|
||||
_territoryData = territoryData;
|
||||
_clientState = clientState;
|
||||
_uiUtils = uiUtils;
|
||||
|
||||
Size = new Vector2(500, 200);
|
||||
SizeCondition = ImGuiCond.Once;
|
||||
@ -151,7 +153,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
var (color, icon, tooltipText) = GetQuestStyle(quest.QuestId);
|
||||
var (color, icon, tooltipText) = _uiUtils.GetQuestStyle(quest.QuestId);
|
||||
using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
if (isKnownQuest)
|
||||
@ -251,18 +253,6 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
_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)
|
||||
{
|
||||
if (counter >= 10)
|
||||
@ -287,7 +277,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
foreach (var q in quest.PreviousQuests)
|
||||
{
|
||||
var qInfo = _questData.GetQuestInfo(q);
|
||||
var (iconColor, icon, _) = GetQuestStyle(q);
|
||||
var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q);
|
||||
// ReSharper disable once UnusedVariable
|
||||
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
@ -319,7 +309,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
foreach (var q in quest.QuestLocks)
|
||||
{
|
||||
var qInfo = _questData.GetQuestInfo(q);
|
||||
var (iconColor, icon, _) = GetQuestStyle(q);
|
||||
var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q);
|
||||
// ReSharper disable once UnusedVariable
|
||||
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)
|
||||
ImGui.Unindent();
|
||||
}
|
||||
|
@ -1,102 +1,50 @@
|
||||
using System;
|
||||
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 System.Numerics;
|
||||
using Dalamud.Plugin;
|
||||
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 LLib.ImGui;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.V1;
|
||||
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||
using Questionable.Windows.QuestComponents;
|
||||
|
||||
namespace Questionable.Windows;
|
||||
|
||||
internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
|
||||
{
|
||||
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
private readonly MovementController _movementController;
|
||||
private readonly QuestController _questController;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly ChatFunctions _chatFunctions;
|
||||
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 NavmeshIpc _navmeshIpc;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestData _questData;
|
||||
private readonly TerritoryData _territoryData;
|
||||
private readonly ICondition _condition;
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly QuestSelectionWindow _questSelectionWindow;
|
||||
private readonly QuestValidationWindow _questValidationWindow;
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly ILogger<QuestWindow> _logger;
|
||||
private readonly ActiveQuestComponent _activeQuestComponent;
|
||||
private readonly ARealmRebornComponent _aRealmRebornComponent;
|
||||
private readonly CreationUtilsComponent _creationUtilsComponent;
|
||||
private readonly QuickAccessButtonsComponent _quickAccessButtonsComponent;
|
||||
private readonly RemainingTasksComponent _remainingTasksComponent;
|
||||
|
||||
public QuestWindow(IDalamudPluginInterface pluginInterface,
|
||||
MovementController movementController,
|
||||
QuestController questController,
|
||||
GameFunctions gameFunctions,
|
||||
ChatFunctions chatFunctions,
|
||||
IClientState clientState,
|
||||
IFramework framework,
|
||||
ITargetManager targetManager,
|
||||
GameUiController gameUiController,
|
||||
CombatController combatController,
|
||||
Configuration configuration,
|
||||
NavmeshIpc navmeshIpc,
|
||||
QuestRegistry questRegistry,
|
||||
QuestData questData,
|
||||
TerritoryData territoryData,
|
||||
ICondition condition,
|
||||
IGameGui gameGui,
|
||||
QuestSelectionWindow questSelectionWindow,
|
||||
QuestValidationWindow questValidationWindow,
|
||||
ICommandManager commandManager,
|
||||
ILogger<QuestWindow> logger)
|
||||
ActiveQuestComponent activeQuestComponent,
|
||||
ARealmRebornComponent aRealmRebornComponent,
|
||||
CreationUtilsComponent creationUtilsComponent,
|
||||
QuickAccessButtonsComponent quickAccessButtonsComponent,
|
||||
RemainingTasksComponent remainingTasksComponent)
|
||||
: base("Questionable###Questionable", ImGuiWindowFlags.AlwaysAutoResize)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
_movementController = movementController;
|
||||
_questController = questController;
|
||||
_gameFunctions = gameFunctions;
|
||||
_chatFunctions = chatFunctions;
|
||||
_clientState = clientState;
|
||||
_framework = framework;
|
||||
_targetManager = targetManager;
|
||||
_gameUiController = gameUiController;
|
||||
_combatController = combatController;
|
||||
_configuration = configuration;
|
||||
_navmeshIpc = navmeshIpc;
|
||||
_questRegistry = questRegistry;
|
||||
_questData = questData;
|
||||
_territoryData = territoryData;
|
||||
_condition = condition;
|
||||
_gameGui = gameGui;
|
||||
_questSelectionWindow = questSelectionWindow;
|
||||
_questValidationWindow = questValidationWindow;
|
||||
_commandManager = commandManager;
|
||||
_logger = logger;
|
||||
_activeQuestComponent = activeQuestComponent;
|
||||
_aRealmRebornComponent = aRealmRebornComponent;
|
||||
_creationUtilsComponent = creationUtilsComponent;
|
||||
_quickAccessButtonsComponent = quickAccessButtonsComponent;
|
||||
_remainingTasksComponent = remainingTasksComponent;
|
||||
|
||||
#if DEBUG
|
||||
IsOpen = true;
|
||||
@ -127,564 +75,19 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
DrawQuest();
|
||||
_activeQuestComponent.Draw();
|
||||
ImGui.Separator();
|
||||
|
||||
DrawCreationUtils();
|
||||
if (_aRealmRebornComponent.ShouldDraw)
|
||||
{
|
||||
_aRealmRebornComponent.Draw();
|
||||
ImGui.Separator();
|
||||
}
|
||||
|
||||
_creationUtilsComponent.Draw();
|
||||
ImGui.Separator();
|
||||
|
||||
DrawQuickAccessButtons();
|
||||
DrawRemainingTasks();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
_quickAccessButtonsComponent.Draw();
|
||||
_remainingTasksComponent.Draw();
|
||||
}
|
||||
}
|
||||
|
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