internal sealed class MovementController : IDisposable
{
+ public const float DefaultStopDistance = 3f;
private readonly NavmeshIpc _navmeshIpc;
private readonly IClientState _clientState;
private readonly GameFunctions _gameFunctions;
public bool IsNavmeshReady => _navmeshIpc.IsReady;
public bool IsPathRunning => _navmeshIpc.IsPathRunning;
public bool IsPathfinding => _pathfindTask is { IsCompleted: false };
+ public Vector3? Destination { get; private set; }
+ public float StopDistance { get; private set; }
public void Update()
{
ResetPathfinding();
}
}
+
+ if (IsPathRunning && Destination != null)
+ {
+ Vector3 localPlayerPosition = _clientState.LocalPlayer?.Position ?? Vector3.Zero;
+ if ((localPlayerPosition - Destination.Value).Length() < StopDistance)
+ Stop();
+ }
}
- public void NavigateTo(EMovementType type, Vector3 to, bool fly)
+ public void NavigateTo(EMovementType type, Vector3 to, bool fly, float? stopDistance = null)
{
ResetPathfinding();
+
_gameFunctions.ExecuteCommand("/automove off");
+ Destination = to;
+ StopDistance = stopDistance ?? (DefaultStopDistance - 0.2f);
_cancellationTokenSource = new();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
_pathfindTask =
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
using System.IO;
+using System.Numerics;
using System.Text.Json;
+using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.Game.Object;
+using Questionable.Data;
using Questionable.Model.V1;
namespace Questionable.Controller;
internal sealed class QuestController
{
+ private readonly IClientState _clientState;
+ private readonly GameFunctions _gameFunctions;
+ private readonly MovementController _movementController;
+ private readonly IPluginLog _pluginLog;
+ private readonly ICondition _condition;
+ private readonly IChatGui _chatGui;
+ private readonly ICommandManager _commandManager;
+ private readonly AetheryteData _aetheryteData;
+ private readonly TerritoryData _territoryData;
private readonly Dictionary<ushort, Quest> _quests = new();
- public QuestController(DalamudPluginInterface pluginInterface)
+ public QuestController(DalamudPluginInterface pluginInterface, IDataManager dataManager, IClientState clientState,
+ GameFunctions gameFunctions, MovementController movementController, IPluginLog pluginLog, ICondition condition,
+ IChatGui chatGui, ICommandManager commandManager)
{
+ _clientState = clientState;
+ _gameFunctions = gameFunctions;
+ _movementController = movementController;
+ _pluginLog = pluginLog;
+ _condition = condition;
+ _chatGui = chatGui;
+ _commandManager = commandManager;
+ _aetheryteData = new AetheryteData(dataManager);
+ _territoryData = new TerritoryData(dataManager);
#if false
LoadFromEmbeddedResources();
#endif
LoadFromDirectory(new DirectoryInfo(@"E:\ffxiv\Questionable\Questionable\QuestPaths"));
LoadFromDirectory(pluginInterface.ConfigDirectory);
+
+ foreach (var (questId, quest) in _quests)
+ {
+ var questData =
+ dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Quest>()!.GetRow((uint)questId + 0x10000);
+ if (questData == null)
+ continue;
+
+ quest.Name = questData.Name.ToString();
+ }
}
#if false
}
#endif
+ public QuestProgress? CurrentQuest { get; set; }
+ public string? DebugState { get; set; }
+
private void LoadFromDirectory(DirectoryInfo configDirectory)
{
foreach (FileInfo fileInfo in configDirectory.GetFiles("*.json"))
{
- using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
- var (questId, name) = ExtractQuestDataFromName(fileInfo.Name);
- Quest quest = new Quest
+ try
{
- FilePath = fileInfo.FullName,
- QuestId = questId,
- Name = name,
- Data = JsonSerializer.Deserialize<QuestData>(stream)!,
- };
- _quests[questId] = quest;
+ using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
+ var (questId, name) = ExtractQuestDataFromName(fileInfo.Name);
+ Quest quest = new Quest
+ {
+ FilePath = fileInfo.FullName,
+ QuestId = questId,
+ Name = name,
+ Data = JsonSerializer.Deserialize<QuestData>(stream)!,
+ };
+ _quests[questId] = quest;
+ }
+ catch (Exception e)
+ {
+ throw new InvalidDataException($"Unable to load file {fileInfo.FullName}", e);
+ }
}
foreach (DirectoryInfo childDirectory in configDirectory.GetDirectories())
string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
name = name.Substring(name.LastIndexOf('.') + 1);
- ushort questId = ushort.Parse(name.Substring(0, name.IndexOf('_')));
- return (questId, name);
+ string[] parts = name.Split('_', 2);
+ return (ushort.Parse(parts[0], CultureInfo.InvariantCulture), parts[1]);
+ }
+
+ public void Update()
+ {
+ (ushort currentQuestId, byte currentSequence) = _gameFunctions.GetCurrentQuest();
+ if (currentQuestId == 0)
+ {
+ if (CurrentQuest != null)
+ CurrentQuest = null;
+ }
+ else if (CurrentQuest == null || CurrentQuest.Quest.QuestId != currentQuestId)
+ {
+ if (_quests.TryGetValue(currentQuestId, out var quest))
+ CurrentQuest = new QuestProgress(quest, currentSequence, 0);
+ else if (CurrentQuest != null)
+ CurrentQuest = null;
+ }
+
+ if (CurrentQuest == null)
+ {
+ DebugState = "No quest active";
+ return;
+ }
+
+ if (_condition[ConditionFlag.Occupied] || _condition[ConditionFlag.Occupied30] ||
+ _condition[ConditionFlag.Occupied33] || _condition[ConditionFlag.Occupied38] ||
+ _condition[ConditionFlag.Occupied39] || _condition[ConditionFlag.OccupiedInEvent] ||
+ _condition[ConditionFlag.OccupiedInQuestEvent] || _condition[ConditionFlag.OccupiedInCutSceneEvent] ||
+ _condition[ConditionFlag.Casting] || _condition[ConditionFlag.Unknown57])
+ {
+ DebugState = "Occupied";
+ return;
+ }
+
+ if (!_movementController.IsNavmeshReady)
+ {
+ DebugState = "Navmesh not ready";
+ return;
+ }
+ else if (_movementController.IsPathfinding || _movementController.IsPathRunning)
+ {
+ DebugState = "Path is running";
+ return;
+ }
+
+ if (CurrentQuest.Sequence != currentSequence)
+ CurrentQuest = CurrentQuest with { Sequence = currentSequence, Step = 0 };
+
+ var q = CurrentQuest.Quest;
+ var sequence = q.FindSequence(CurrentQuest.Sequence);
+ if (sequence == null)
+ {
+ DebugState = "Sequence not found";
+ return;
+ }
+
+ if (CurrentQuest.Step == 255)
+ {
+ DebugState = "Step completed";
+ return;
+ }
+
+ if (CurrentQuest.Step >= sequence.Steps.Count)
+ {
+ DebugState = "Step not found";
+ return;
+ }
+
+ var step = sequence.Steps[CurrentQuest.Step];
+ DebugState = step.Comment ?? sequence.Comment ?? q.Data.Comment;
+ }
+
+ public (QuestSequence? Sequence, QuestStep? Step) GetNextStep()
+ {
+ if (CurrentQuest == null)
+ return (null, null);
+
+ var q = CurrentQuest.Quest;
+ var seq = q.FindSequence(CurrentQuest.Sequence);
+ if (seq == null)
+ return (null, null);
+
+ if (CurrentQuest.Step >= seq.Steps.Count)
+ return (null, null);
+
+ return (seq, seq.Steps[CurrentQuest.Step]);
+ }
+
+ public void IncreaseStepCount()
+ {
+ (QuestSequence? seq, QuestStep? step) = GetNextStep();
+ if (seq == null || step == null)
+ return;
+
+ Debug.Assert(CurrentQuest != null, nameof(CurrentQuest) + " != null");
+ if (CurrentQuest.Step + 1 < seq.Steps.Count)
+ {
+ CurrentQuest = CurrentQuest with
+ {
+ Step = CurrentQuest.Step + 1,
+ AetheryteShortcutUsed = false,
+ AethernetShortcutUsed = false
+ };
+ }
+ else
+ {
+ CurrentQuest = CurrentQuest with
+ {
+ Step = 255,
+ AetheryteShortcutUsed = false,
+ AethernetShortcutUsed = false
+ };
+ }
+ }
+
+ public void ExecuteNextStep()
+ {
+ (QuestSequence? seq, QuestStep? step) = GetNextStep();
+ if (seq == null || step == null)
+ return;
+
+ Debug.Assert(CurrentQuest != null, nameof(CurrentQuest) + " != null");
+ if (!CurrentQuest.AetheryteShortcutUsed && step.AetheryteShortcut != null)
+ {
+ bool skipTeleport = false;
+ ushort territoryType = _clientState.TerritoryType;
+ if (step.TerritoryId == territoryType)
+ {
+ Vector3 playerPosition = _clientState.LocalPlayer!.Position;
+ if (_aetheryteData.CalculateDistance(playerPosition, territoryType, step.AetheryteShortcut.Value) < 11 ||
+ (step.AethernetShortcut != null &&
+ (_aetheryteData.CalculateDistance(playerPosition, territoryType, step.AethernetShortcut.From) < 11 ||
+ _aetheryteData.CalculateDistance(playerPosition, territoryType, step.AethernetShortcut.To) < 11)))
+ {
+ skipTeleport = true;
+ }
+ }
+
+ if (skipTeleport)
+ {
+ CurrentQuest = CurrentQuest with { AetheryteShortcutUsed = true };
+ }
+ else
+ {
+ if (step.AetheryteShortcut != null)
+ {
+ if (!_gameFunctions.IsAetheryteUnlocked(step.AetheryteShortcut.Value))
+ _chatGui.Print($"[Questionable] Aetheryte {step.AetheryteShortcut.Value} is not unlocked.");
+ else if (_gameFunctions.TeleportAetheryte(step.AetheryteShortcut.Value))
+ CurrentQuest = CurrentQuest with { AetheryteShortcutUsed = true };
+ else
+ _chatGui.Print("[Questionable] Unable to teleport to aetheryte.");
+ }
+ else
+ _chatGui.Print("[Questionable] No aetheryte for teleport set.");
+
+ return;
+ }
+ }
+
+ if (!CurrentQuest.AethernetShortcutUsed)
+ {
+ if (step.AethernetShortcut != null)
+ {
+ EAetheryteLocation from = step.AethernetShortcut.From;
+ EAetheryteLocation to = step.AethernetShortcut.To;
+ ushort territoryType = _clientState.TerritoryType;
+ Vector3 playerPosition = _clientState.LocalPlayer!.Position;
+
+ // closer to the source
+ if (_aetheryteData.CalculateDistance(playerPosition, territoryType, from) <
+ _aetheryteData.CalculateDistance(playerPosition, territoryType, to))
+ {
+ if (_aetheryteData.CalculateDistance(playerPosition, territoryType, from) < 11)
+ {
+ // unsure if this works across languages
+ _commandManager.ProcessCommand(
+ $"/li {_aetheryteData.AethernetNames[step.AethernetShortcut.To].Replace("The ", "", StringComparison.Ordinal)}");
+ CurrentQuest = CurrentQuest with { AethernetShortcutUsed = true };
+ }
+ else
+ _movementController.NavigateTo(EMovementType.Quest, _aetheryteData.Locations[from], false,
+ 6.9f);
+
+ return;
+ }
+ }
+ }
+
+ if (step.Position != null)
+ {
+ float distance;
+ if (step.InteractionType == EInteractionType.WalkTo)
+ distance = step.StopDistance ?? 0.25f;
+ else
+ distance = step.StopDistance ?? MovementController.DefaultStopDistance;
+
+ var position = _clientState.LocalPlayer?.Position ?? new Vector3();
+ float actualDistance = (position - step.Position.Value).Length();
+ if (actualDistance > 30f && !_condition[ConditionFlag.Mounted] &&
+ _territoryData.CanUseMount(_clientState.TerritoryType))
+ {
+ unsafe
+ {
+ ActionManager.Instance()->UseAction(ActionType.Mount, 71);
+ }
+
+ return;
+ }
+ else if (actualDistance > distance)
+ {
+ _movementController.NavigateTo(EMovementType.Quest, step.Position.Value,
+ _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType), distance);
+ return;
+ }
+ }
+
+ switch (step.InteractionType)
+ {
+ case EInteractionType.Interact:
+ case EInteractionType.AttuneAetheryte:
+ case EInteractionType.AttuneAethernetShard:
+ case EInteractionType.AttuneAetherCurrent:
+ if (step.DataId != null)
+ {
+ _gameFunctions.InteractWith(step.DataId.Value);
+ IncreaseStepCount();
+ }
+
+ break;
+
+ case EInteractionType.WalkTo:
+ IncreaseStepCount();
+ break;
+
+ default:
+ _pluginLog.Warning($"Action '{step.InteractionType}' is not implemented");
+ break;
+ }
}
+
+ public sealed record QuestProgress(
+ Quest Quest,
+ byte Sequence,
+ int Step,
+ bool AetheryteShortcutUsed = false,
+ bool AethernetShortcutUsed = false);
}
--- /dev/null
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Numerics;
+using Dalamud.Plugin.Services;
+using Lumina.Excel.GeneratedSheets2;
+using Questionable.Model.V1;
+
+namespace Questionable.Data;
+
+internal sealed class AetheryteData
+{
+ public ReadOnlyDictionary<EAetheryteLocation, Vector3> Locations { get; } =
+ new Dictionary<EAetheryteLocation, Vector3>
+ {
+ { EAetheryteLocation.Gridania, new(32.913696f, 2.670288f, 30.014404f) },
+ { EAetheryteLocation.GridaniaArcher, new(166.58276f, -1.7243042f, 86.13721f) },
+ { EAetheryteLocation.GridaniaLeatherworker, new(101.27405f, 9.018005f, -111.31464f) },
+ { EAetheryteLocation.GridaniaLancer, new(121.23291f, 12.649658f, -229.63306f) },
+ { EAetheryteLocation.GridaniaConjurer, new(-145.15906f, 4.9591064f, -11.7647705f) },
+ { EAetheryteLocation.GridaniaBotanist, new(-311.0857f, 7.94989f, -177.05048f) },
+ { EAetheryteLocation.GridaniaAmphitheatre, new(-73.92999f, 7.9804688f, -140.15417f) },
+
+ { EAetheryteLocation.Uldah, new(-144.51825f, -1.3580933f, -169.6651f) },
+ { EAetheryteLocation.UldahAdventurers, new(64.22522f, 4.5318604f, -115.31244f) },
+ { EAetheryteLocation.UldahThaumaturge, new(-154.83331f, 14.633362f, 73.07532f) },
+ { EAetheryteLocation.UldahGladiator, new(-53.849182f, 10.696533f, 12.222412f) },
+ { EAetheryteLocation.UldahMiner, new(33.49353f, 13.229492f, 113.206665f) },
+ { EAetheryteLocation.UldahAlchemist, new(-98.25293f, 42.34375f, 88.45642f) },
+ { EAetheryteLocation.UldahWeaver, new(89.64673f, 12.924377f, 58.27417f) },
+ { EAetheryteLocation.UldahGoldsmith, new(-19.333252f, 14.602844f, 72.03784f) },
+ { EAetheryteLocation.UldahSapphireAvenue, new(131.9447f, 4.714966f, -29.800903f) },
+ { EAetheryteLocation.UldahChamberOfRule, new(6.6376343f, 30.655273f, -24.826477f) },
+
+ { EAetheryteLocation.Limsa, new(-84.031494f, 20.767456f, 0.015197754f) },
+ { EAetheryteLocation.LimsaAftcastle, new(16.067688f, 40.787354f, 68.80286f) },
+ { EAetheryteLocation.LimsaCulinarian, new(-56.50421f, 44.47998f, -131.45648f) },
+ { EAetheryteLocation.LimsaArcanist, new(-335.1645f, 12.619202f, 56.381958f) },
+ { EAetheryteLocation.LimsaFisher, new(-179.40033f, 4.8065186f, 182.97095f) },
+ { EAetheryteLocation.LimsaMarauder, new(-5.1728516f, 44.63257f, -218.06671f) },
+ { EAetheryteLocation.LimsaHawkersAlley, new(-213.61108f, 16.739136f, 51.80432f) },
+
+ // ... missing a few
+
+ { EAetheryteLocation.Crystarium, new(-65.0188f, 4.5318604f, 0.015197754f) },
+ { EAetheryteLocation.CrystariumMarkets, new(-6.149414f, -7.736328f, 148.72961f) },
+ { EAetheryteLocation.CrystariumThemenosRookery, new(-107.37775f, -0.015319824f, -58.762512f) },
+ { EAetheryteLocation.CrystariumDossalGate, new(64.86609f, -0.015319824f, -18.173523f) },
+ { EAetheryteLocation.CrystariumPendants, new(35.477173f, -0.015319824f, 222.58337f) },
+ { EAetheryteLocation.CrystariumAmaroLaunch, new(66.60559f, 35.99597f, -131.09033f) },
+ { EAetheryteLocation.CrystariumCrystallineMean, new(-52.506348f, 19.97406f, -173.35773f) },
+ { EAetheryteLocation.CrystariumCabinetOfCuriosity, new(-54.398438f, -37.70508f, -241.07733f) },
+
+ { EAetheryteLocation.Eulmore, new(0.015197754f, 81.986694f, 0.93078613f) },
+ { EAetheryteLocation.EulmoreMainstay, new(10.940674f, 36.087524f, -4.196289f) },
+ { EAetheryteLocation.EulmoreNightsoilPots, new(-54.093323f, -0.83929443f, 52.140015f) },
+ { EAetheryteLocation.EulmoreGloryGate, new(6.9122925f, 6.240906f, -56.351562f) },
+ { EAetheryteLocation.EulmoreSoutheastDerelict, new(71.82422f, -10.391418f, 65.32385f) },
+
+ // ... missing a few
+
+ { EAetheryteLocation.OldSharlayan, new(0.07623291f, 4.8065186f, -0.10687256f) },
+ { EAetheryteLocation.OldSharlayanStudium, new(-291.1574f, 20.004517f, -74.143616f) },
+ { EAetheryteLocation.OldSharlayanBaldesionAnnex, new(-92.21033f, 2.304016f, 29.709229f) },
+ { EAetheryteLocation.OldSharlayanRostra, new(-36.94214f, 41.367188f, -156.6034f) },
+ { EAetheryteLocation.OldSharlayanLeveilleurEstate, new(204.79126f, 21.774597f, -118.73047f) },
+ { EAetheryteLocation.OldSharlayanJourneysEnd, new(206.22559f, 1.8463135f, 13.77887f) },
+ { EAetheryteLocation.OldSharlayanScholarsHarbor, new(16.494995f, -16.250854f, 127.73328f) },
+
+ { EAetheryteLocation.RadzAtHan, new(25.986084f, 3.250122f, -27.023743f) },
+ { EAetheryteLocation.RadzAtHanMeghaduta, new(-365.95715f, 44.99878f, -31.815125f) },
+ { EAetheryteLocation.RadzAtHanRuveydahFibers, new(-156.14563f, 35.99597f, 27.725586f) },
+ { EAetheryteLocation.RadzAtHanAirship, new(-144.33508f, 27.969727f, 202.2583f) },
+ { EAetheryteLocation.RadzAtHanAlzadaalsPeace, new(6.6071167f, -2.02948f, 110.55151f) },
+ { EAetheryteLocation.RadzAtHanHallOfTheRadiantHost, new(-141.37488f, 3.982544f, -98.435974f) },
+ { EAetheryteLocation.RadzAtHanMehrydesMeyhane, new(-42.61847f, -0.015319824f, -197.61963f) },
+ { EAetheryteLocation.RadzAtHanKama, new(129.59485f, 26.993164f, 13.473633f) },
+ { EAetheryteLocation.RadzAtHanHighCrucible, new(57.90796f, -24.704407f, -210.6203f) },
+
+ { EAetheryteLocation.LabyrinthosArcheion, new(443.5338f, 170.6416f, -476.18835f) },
+ { EAetheryteLocation.LabyrinthosSharlayanHamlet, new(8.377136f, -27.542603f, -46.67737f) },
+ { EAetheryteLocation.LabyrinthosAporia, new(-729.18286f, -27.634155f, 302.1438f) },
+ { EAetheryteLocation.ThavnairYedlihmad, new(193.49963f, 6.9733276f, 629.2362f) },
+ { EAetheryteLocation.ThavnairGreatWork, new(-527.48914f, 4.776001f, 36.75891f) },
+ { EAetheryteLocation.ThavnairPalakasStand, new(405.1422f, 5.2643433f, -244.4953f) },
+ { EAetheryteLocation.GarlemaldCampBrokenGlass, new(-408.10254f, 24.15503f, 479.9724f) },
+ { EAetheryteLocation.GarlemaldTertium, new(518.9136f, -35.324707f, -178.36273f) },
+ { EAetheryteLocation.MareLamentorumSinusLacrimarum, new(-566.2471f, 134.66089f, 650.6294f) },
+ { EAetheryteLocation.MareLamentorumBestwaysBurrow, new(-0.015319824f, -128.83197f, -512.0165f) },
+ { EAetheryteLocation.ElpisAnagnorisis, new(159.96033f, 11.703674f, 126.878784f) },
+ { EAetheryteLocation.ElpisTwelveWonders, new(-633.7225f, -19.821533f, 542.56494f) },
+ { EAetheryteLocation.ElpisPoietenOikos, new(-529.9001f, 161.24207f, -222.2782f) },
+ { EAetheryteLocation.UltimaThuleReahTahra, new(-544.152f, 74.32666f, 269.6421f) },
+ { EAetheryteLocation.UltimaThuleAbodeOfTheEa, new(64.286255f, 272.48022f, -657.49603f) },
+ { EAetheryteLocation.UltimaThuleBaseOmicron, new(489.2804f, 437.5829f, 333.63843f) },
+ }
+ .AsReadOnly();
+
+ public ReadOnlyDictionary<EAetheryteLocation, string> AethernetNames { get; }
+ public ReadOnlyDictionary<EAetheryteLocation, ushort> TerritoryIds { get; }
+
+ public AetheryteData(IDataManager dataManager)
+ {
+ Dictionary<EAetheryteLocation, string> aethernetNames = new();
+ Dictionary<EAetheryteLocation, ushort> territoryIds = new();
+ foreach (var aetheryte in dataManager.GetExcelSheet<Aetheryte>()!.Where(x => x.RowId > 0))
+ {
+ string? aethernetName = aetheryte.AethernetName?.Value?.Name.ToString();
+ if (!string.IsNullOrEmpty(aethernetName))
+ aethernetNames[(EAetheryteLocation)aetheryte.RowId] = aethernetName;
+
+ if (aetheryte.Territory != null && aetheryte.Territory.Row > 0)
+ territoryIds[(EAetheryteLocation)aetheryte.RowId] = (ushort)aetheryte.Territory.Row;
+ }
+
+ AethernetNames = aethernetNames.AsReadOnly();
+ TerritoryIds = territoryIds.AsReadOnly();
+ }
+
+ public float CalculateDistance(Vector3 fromPosition, ushort fromTerritoryType, EAetheryteLocation to)
+ {
+ if (!TerritoryIds.TryGetValue(to, out ushort toTerritoryType) || fromTerritoryType != toTerritoryType)
+ return float.MaxValue;
+
+ if (!Locations.TryGetValue(to, out Vector3 toPosition))
+ return float.MaxValue;
+
+ return (fromPosition - toPosition).Length();
+ }
+}
--- /dev/null
+using System.Collections.Immutable;
+using System.Linq;
+using Dalamud.Plugin.Services;
+using Lumina.Excel.GeneratedSheets;
+
+namespace Questionable.Data;
+
+internal sealed class TerritoryData
+{
+ private readonly ImmutableHashSet<uint> _territoriesWithMount;
+
+ public TerritoryData(IDataManager dataManager)
+ {
+ _territoriesWithMount = dataManager.GetExcelSheet<TerritoryType>()!
+ .Where(x => x.RowId > 0 && x.Mount)
+ .Select(x => x.RowId)
+ .ToImmutableHashSet();
+ }
+
+ public bool CanUseMount(ushort territoryId) => _territoriesWithMount.Contains(territoryId);
+}
public Task<List<Vector3>> Pathfind(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly,
CancellationToken cancellationToken)
{
+ _pathSetTolerance.InvokeAction(0.25f);
return _navPathfind.InvokeFunc(localPlayerPosition, targetPosition, fly, cancellationToken);
}
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Game;
+using Dalamud.Game.ClientState.Objects;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Lumina.Excel.GeneratedSheets;
+using Questionable.Model.V1;
namespace Questionable;
private readonly delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString;
private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet;
- public GameFunctions(IDataManager dataManager, ISigScanner sigScanner)
+ private readonly IObjectTable _objectTable;
+ private readonly ITargetManager _targetManager;
+ private readonly IPluginLog _pluginLog;
+
+ public GameFunctions(IDataManager dataManager, IObjectTable objectTable, ISigScanner sigScanner, ITargetManager targetManager, IPluginLog pluginLog)
{
+ _objectTable = objectTable;
+ _targetManager = targetManager;
+ _pluginLog = pluginLog;
_processChatBox =
Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText(Signatures.SendChat));
_sanitiseString =
.AsReadOnly();
}
- public (uint CurrentQuest, byte Sequence) GetCurrentQuest()
+ public (ushort CurrentQuest, byte Sequence) GetCurrentQuest()
{
var scenarioTree = AgentScenarioTree.Instance();
if (scenarioTree == null)
//ImGui.Text($"Current Quest: {currentQuest}");
//ImGui.Text($"Progress: {QuestManager.GetQuestSequence(currentQuest)}");
- return (currentQuest, QuestManager.GetQuestSequence(currentQuest));
+ return ((ushort)currentQuest, QuestManager.GetQuestSequence(currentQuest));
+ }
+
+ public bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
+ {
+ var telepo = Telepo.Instance();
+ if (telepo == null || telepo->UpdateAetheryteList() == null)
+ {
+ subIndex = 0;
+ return false;
+ }
+
+ for (ulong i = 0; i < telepo->TeleportList.Size(); ++ i)
+ {
+ var data = telepo->TeleportList.Get(i);
+ if (data.AetheryteId == aetheryteId)
+ {
+ subIndex = data.SubIndex;
+ return true;
+ }
+ }
+
+ subIndex = 0;
+ return false;
+ }
+
+ public bool IsAetheryteUnlocked(EAetheryteLocation aetheryteLocation)
+ => IsAetheryteUnlocked((uint)aetheryteLocation, out _);
+
+ public bool TeleportAetheryte(uint aetheryteId)
+ {
+ var status = ActionManager.Instance()->GetActionStatus(ActionType.Action, 5);
+ if (status != 0)
+ return false;
+
+ if (IsAetheryteUnlocked(aetheryteId, out var subIndex))
+ {
+ return Telepo.Instance()->Teleport(aetheryteId, subIndex);
+ }
+
+ return false;
}
+ public bool TeleportAetheryte(EAetheryteLocation aetheryteLocation)
+ => TeleportAetheryte((uint)aetheryteLocation);
+
public bool IsFlyingUnlocked(ushort territoryId)
{
var playerState = PlayerState.Instance();
public void ExecuteCommand(string command)
{
- if (!command.StartsWith("/", StringComparison.Ordinal))
+ if (!command.StartsWith('/'))
return;
SendMessage(command);
internal ChatPayload(byte[] stringBytes)
{
- this.textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
- Marshal.Copy(stringBytes, 0, this.textPtr, stringBytes.Length);
- Marshal.WriteByte(this.textPtr + stringBytes.Length, 0);
+ textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
+ Marshal.Copy(stringBytes, 0, textPtr, stringBytes.Length);
+ Marshal.WriteByte(textPtr + stringBytes.Length, 0);
- this.textLen = (ulong)(stringBytes.Length + 1);
+ textLen = (ulong)(stringBytes.Length + 1);
- this.unk1 = 64;
- this.unk2 = 0;
+ unk1 = 64;
+ unk2 = 0;
}
public void Dispose()
{
- Marshal.FreeHGlobal(this.textPtr);
+ Marshal.FreeHGlobal(textPtr);
}
}
#endregion
+
+ public void InteractWith(uint dataId)
+ {
+ foreach (var gameObject in _objectTable)
+ {
+ if (gameObject.DataId == dataId)
+ {
+ _targetManager.Target = null;
+ _targetManager.Target = gameObject;
+
+ TargetSystem.Instance()->InteractWithObject(
+ (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address, false);
+ return;
+ }
+ }
+ }
}
-using Questionable.Model.V1;
+using System.Linq;
+using Questionable.Model.V1;
namespace Questionable;
public required string FilePath { get; init; }
public required ushort QuestId { get; init; }
- public required string Name { get; init; }
+ public required string Name { get; set; }
public required QuestData Data { get; init; }
+
+ public QuestSequence? FindSequence(byte currentSequence)
+ => Data.QuestSequence.SingleOrDefault(seq => seq.Sequence == currentSequence);
}
{ EAetheryteLocation.KuganeSekiseigumiBarracks, "[Kugane] Sekiseigumi Barracks" },
{ EAetheryteLocation.KuganeRakuzaDistrict, "[Kugane] Rakuza District" },
{ EAetheryteLocation.KuganeAirship, "[Kugane] Airship Landing" },
- { EAetheryteLocation.Crystarium, "[The Crystarium] Aetheryte Plaza" },
- { EAetheryteLocation.CrystariumMarkets, "[The Crystarium] Musica Universalis Markets" },
- { EAetheryteLocation.CrystariumThemenosRookery, "[The Crystarium] Themenos Rookery" },
- { EAetheryteLocation.CrystariumDossalGate, "[The Crystarium] The Dossal Gate" },
- { EAetheryteLocation.CrystariumPendants, "[The Crystarium] The Pendants" },
- { EAetheryteLocation.CrystariumAmaroLaunch, "[The Crystarium] The Amaro Launch" },
- { EAetheryteLocation.CrystariumCrystallineMean, "[The Crystarium] The Crystalline Mean" },
- { EAetheryteLocation.CrystariumCabinetOfCuriosity, "[The Crystarium] The Cabinet of Curiosity" },
+ { EAetheryteLocation.Crystarium, "[Crystarium] Aetheryte Plaza" },
+ { EAetheryteLocation.CrystariumMarkets, "[Crystarium] Musica Universalis Markets" },
+ { EAetheryteLocation.CrystariumThemenosRookery, "[Crystarium] Themenos Rookery" },
+ { EAetheryteLocation.CrystariumDossalGate, "[Crystarium] The Dossal Gate" },
+ { EAetheryteLocation.CrystariumPendants, "[Crystarium] The Pendants" },
+ { EAetheryteLocation.CrystariumAmaroLaunch, "[Crystarium] The Amaro Launch" },
+ { EAetheryteLocation.CrystariumCrystallineMean, "[Crystarium] The Crystalline Mean" },
+ { EAetheryteLocation.CrystariumCabinetOfCuriosity, "[Crystarium] The Cabinet of Curiosity" },
{ EAetheryteLocation.Eulmore, "[Eulmore] Aetheryte Plaza" },
{ EAetheryteLocation.EulmoreSoutheastDerelict, "[Eulmore] Southeast Derelicts" },
{ EAetheryteLocation.EulmoreNightsoilPots, "[Eulmore] Nightsoil Pots" },
private static readonly Dictionary<string, EAetheryteLocation> StringToEnum =
EnumToString.ToDictionary(x => x.Value, x => x.Key);
- public override AethernetShortcut? Read(ref Utf8JsonReader reader, Type typeToConvert,
+ public override AethernetShortcut Read(ref Utf8JsonReader reader, Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray)
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Questionable.Model.V1.Converter;
+
+public class AetheryteConverter : JsonConverter<EAetheryteLocation>
+{
+ private static readonly Dictionary<EAetheryteLocation, string> EnumToString = new()
+ {
+ { EAetheryteLocation.Limsa, "Limsa Lominsa" },
+ { EAetheryteLocation.Gridania, "Gridania" },
+ { EAetheryteLocation.Uldah, "Ul'dah" },
+ { EAetheryteLocation.Ishgard, "Ishgard" },
+
+ { EAetheryteLocation.RhalgrsReach, "Rhalgr's Reach" },
+ { EAetheryteLocation.FringesCastrumOriens, "Fringes - Castrum Oriens" },
+ { EAetheryteLocation.FringesPeeringStones, "Fringes - Peering Stones" },
+ { EAetheryteLocation.PeaksAlaGannha, "Peaks - Ala Gannha" },
+ { EAetheryteLocation.PeaksAlaGhiri, "Peaks - Ala Ghiri" },
+ { EAetheryteLocation.LochsPortaPraetoria, "Lochs - Porta Praetoria" },
+ { EAetheryteLocation.LochsAlaMhiganQuarter, "Lochs - Ala Mhigan Quarter" },
+ { EAetheryteLocation.Kugane, "Kugane" },
+ { EAetheryteLocation.RubySeaTamamizu, "Ruby Sea - Tamamizu" },
+ { EAetheryteLocation.RubySeaOnokoro, "Ruby Sea - Onokoro" },
+ { EAetheryteLocation.YanxiaNamai, "Yanxia - Namai" },
+ { EAetheryteLocation.YanxiaHouseOfTheFierce, "Yanxia - House of the Fierce" },
+ { EAetheryteLocation.AzimSteppeReunion, "Azim Steppe - Reunion" },
+ { EAetheryteLocation.AzimSteppeDawnThrone, "Azim Steppe - Dawn Throne" },
+ { EAetheryteLocation.AzimSteppeDhoroIloh, "Azim Steppe - Dhoro Iloh" },
+ { EAetheryteLocation.DomanEnclave, "Doman Enclave" },
+ { EAetheryteLocation.DomamEnclaveNorthern, "Doman Enclave - Northern Enclave" },
+ { EAetheryteLocation.DomamEnclaveSouthern, "Doman Enclave - Southern Enclave" },
+
+ { EAetheryteLocation.Crystarium, "Crystarium" },
+ { EAetheryteLocation.Eulmore, "Eulmore" },
+ { EAetheryteLocation.LakelandFortJobb, "Lakeland - Fort Jobb" },
+ { EAetheryteLocation.LakelandOstallImperative, "Lakeland - Ostall Imperative" },
+ { EAetheryteLocation.KholusiaStilltide, "Kholusia - Stilltide" },
+ { EAetheryteLocation.KholusiaWright, "Kholusia - Wright" },
+ { EAetheryteLocation.KholusiaTomra, "Kholusia - Tomra" },
+ { EAetheryteLocation.AmhAraengMordSouq, "Amh Araeng - Mord Souq" },
+ { EAetheryteLocation.AmhAraengInnAtJourneysHead, "Amh Araeng - Inn at Journey's Head" },
+ { EAetheryteLocation.AmhAraengTwine, "Amh Araeng - Twine" },
+ { EAetheryteLocation.RaktikaSlitherbough, "Rak'tika - Slitherbough" },
+ { EAetheryteLocation.RaktikaFanow, "Rak'tika - Fanow" },
+ { EAetheryteLocation.IlMhegLydhaLran, "Il Mheg - Lydha Lran" },
+ { EAetheryteLocation.IlMhegPiaEnni, "Il Mheg - Pia Enni" },
+ { EAetheryteLocation.IlMhegWolekdorf, "Il Mheg - Wolekdorf" },
+ { EAetheryteLocation.TempestOndoCups, "Tempest - Ondo Cups" },
+ { EAetheryteLocation.TempestMacarensesAngle, "Tempest - Macarenses Angle" },
+
+ { EAetheryteLocation.OldSharlayan, "Old Sharlayan" },
+ { EAetheryteLocation.RadzAtHan, "Radz-at-Han" },
+ { EAetheryteLocation.LabyrinthosArcheion, "Labyrinthos - Archeion" },
+ { EAetheryteLocation.LabyrinthosSharlayanHamlet, "Labyrinthos - Sharlayan Hamlet" },
+ { EAetheryteLocation.LabyrinthosAporia, "Labyrinthos - Aporia" },
+ { EAetheryteLocation.ThavnairYedlihmad, "Thavnair - Yedlihmad" },
+ { EAetheryteLocation.ThavnairGreatWork, "Thavnair - Great Work" },
+ { EAetheryteLocation.ThavnairPalakasStand, "Thavnair - Palaka's Stand" },
+ { EAetheryteLocation.GarlemaldCampBrokenGlass, "Garlemald - Camp Broken Glass" },
+ { EAetheryteLocation.GarlemaldTertium, "Garlemald - Tertium" },
+ { EAetheryteLocation.MareLamentorumSinusLacrimarum, "Mare Lamentorum - Sinus Lacrimarum" },
+ { EAetheryteLocation.MareLamentorumBestwaysBurrow, "Mare Lamentorum - Bestways Burrow" },
+ { EAetheryteLocation.ElpisAnagnorisis, "Elpis - Anagnorisis" },
+ { EAetheryteLocation.ElpisTwelveWonders, "Elpis - Twelve Wonders" },
+ { EAetheryteLocation.ElpisPoietenOikos, "Elpis - Poieten Oikos" },
+ { EAetheryteLocation.UltimaThuleReahTahra, "Ultima Thule - Reah Tahra" },
+ { EAetheryteLocation.UltimaThuleAbodeOfTheEa, "Ultima Thula - Abode of the Ea" },
+ { EAetheryteLocation.UltimaThuleBaseOmicron, "Ultima Thule - Base Omicron" }
+ };
+
+ private static readonly Dictionary<string, EAetheryteLocation> StringToEnum =
+ EnumToString.ToDictionary(x => x.Value, x => x.Key);
+
+ public override EAetheryteLocation Read(ref Utf8JsonReader reader, Type typeToConvert,
+ JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.String)
+ throw new JsonException();
+
+ string? str = reader.GetString();
+ if (str == null)
+ throw new JsonException();
+
+ return StringToEnum.TryGetValue(str, out EAetheryteLocation value) ? value : throw new JsonException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, EAetheryteLocation value, JsonSerializerOptions options)
+ {
+ ArgumentNullException.ThrowIfNull(writer);
+ writer.WriteStringValue(EnumToString[value]);
+ }
+}
{
{ EInteractionType.Interact, "Interact" },
{ EInteractionType.WalkTo, "WalkTo" },
- { EInteractionType.AttuneAethenetShard, "AttuneAethenetShard" },
+ { EInteractionType.AttuneAethernetShard, "AttuneAethenetShard" },
{ EInteractionType.AttuneAetheryte, "AttuneAetheryte" },
{ EInteractionType.AttuneAetherCurrent, "AttuneAetherCurrent" },
{ EInteractionType.Combat, "Combat" },
--- /dev/null
+using System;
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Questionable.Model.V1.Converter;
+
+public class VectorConverter : JsonConverter<Vector3>
+{
+ public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.StartObject)
+ throw new JsonException();
+
+ Vector3 vec = new Vector3();
+ while (reader.Read())
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.PropertyName:
+ string? propertyName = reader.GetString();
+ if (propertyName == null || !reader.Read())
+ throw new JsonException();
+
+ switch (propertyName)
+ {
+ case nameof(Vector3.X):
+ vec.X = reader.GetSingle();
+ break;
+
+ case nameof(Vector3.Y):
+ vec.Y = reader.GetSingle();
+ break;
+
+ case nameof(Vector3.Z):
+ vec.Z = reader.GetSingle();
+ break;
+
+ default:
+ throw new JsonException();
+ }
+
+ break;
+
+ case JsonTokenType.EndObject:
+ return vec;
+
+ default:
+ throw new JsonException();
+ }
+ }
+
+ throw new JsonException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, Vector3 value, JsonSerializerOptions options)
+ {
+ ArgumentNullException.ThrowIfNull(writer);
+
+ writer.WriteStartObject();
+ writer.WriteNumber(nameof(Vector3.X), value.X);
+ writer.WriteNumber(nameof(Vector3.Y), value.X);
+ writer.WriteNumber(nameof(Vector3.Z), value.X);
+ writer.WriteEndObject();
+ }
+}
public enum EAetheryteLocation
{
+ None = 0,
+
Gridania = 2,
GridaniaArcher = 25,
GridaniaLeatherworker = 26,
KuganeRakuzaDistrict = 119,
KuganeAirship = 120,
+ FringesCastrumOriens = 98,
+ FringesPeeringStones = 99,
+ PeaksAlaGannha = 100,
+ PeaksAlaGhiri = 101,
+ LochsPortaPraetoria = 102,
+ LochsAlaMhiganQuarter = 103,
+ RubySeaTamamizu = 105,
+ RubySeaOnokoro = 106,
+ YanxiaNamai = 107,
+ YanxiaHouseOfTheFierce = 108,
+ AzimSteppeReunion = 109,
+ AzimSteppeDawnThrone = 110,
+ AzimSteppeDhoroIloh = 128,
+
+ DomanEnclave = 127,
+ DomamEnclaveNorthern = 129,
+ DomamEnclaveSouthern = 130,
+
Crystarium = 133,
CrystariumMarkets = 149,
CrystariumThemenosRookery = 150,
EulmoreGloryGate = 159,
EulmoreSoutheastDerelict = 135,
+ LakelandFortJobb = 132,
+ LakelandOstallImperative = 136,
+ KholusiaStilltide = 137,
+ KholusiaWright = 138,
+ KholusiaTomra = 139,
+ AmhAraengMordSouq = 140,
+ AmhAraengInnAtJourneysHead = 141,
+ AmhAraengTwine = 142,
+ RaktikaSlitherbough = 143,
+ RaktikaFanow = 144,
+ IlMhegLydhaLran = 145,
+ IlMhegPiaEnni = 146,
+ IlMhegWolekdorf = 147,
+ TempestOndoCups = 148,
+ TempestMacarensesAngle = 156,
+
OldSharlayan = 182,
OldSharlayanStudium = 184,
OldSharlayanBaldesionAnnex = 185,
RadzAtHanMehrydesMeyhane = 196,
RadzAtHanKama = 198,
RadzAtHanHighCrucible = 199,
+
+ LabyrinthosArcheion = 166,
+ LabyrinthosSharlayanHamlet = 167,
+ LabyrinthosAporia = 168,
+ ThavnairYedlihmad = 169,
+ ThavnairGreatWork = 170,
+ ThavnairPalakasStand = 171,
+ GarlemaldCampBrokenGlass = 172,
+ GarlemaldTertium = 173,
+ MareLamentorumSinusLacrimarum = 174,
+ MareLamentorumBestwaysBurrow = 175,
+ ElpisAnagnorisis = 176,
+ ElpisTwelveWonders = 177,
+ ElpisPoietenOikos = 178,
+ UltimaThuleReahTahra = 179,
+ UltimaThuleAbodeOfTheEa = 180,
+ UltimaThuleBaseOmicron = 181
}
{
Interact,
WalkTo,
- AttuneAethenetShard,
+ AttuneAethernetShard,
AttuneAetheryte,
AttuneAetherCurrent,
Combat,
public required int Version { get; set; }
public required string Author { get; set; }
public List<string> Contributors { get; set; } = new();
- public string Comment { get; set; }
+ public string? Comment { get; set; }
public List<ushort> TerritoryBlacklist { get; set; } = new();
public required List<QuestSequence> QuestSequence { get; set; } = new();
}
public class QuestSequence
{
public required int Sequence { get; set; }
- public string Comment { get; set; }
+ public string? Comment { get; set; }
public List<QuestStep> Steps { get; set; } = new();
}
[JsonConverter(typeof(InteractionTypeConverter))]
public EInteractionType InteractionType { get; set; }
- public ulong? DataId { get; set; }
- public Vector3 Position { get; set; }
+ public uint? DataId { get; set; }
+
+ [JsonConverter(typeof(VectorConverter))]
+ public Vector3? Position { get; set; }
+
+ public float? StopDistance { get; set; }
public ushort TerritoryId { get; set; }
public bool Disabled { get; set; }
+ public string? Comment { get; set; }
+
+ [JsonConverter(typeof(AetheryteConverter))]
+ public EAetheryteLocation? AetheryteShortcut { get; set; }
[JsonConverter(typeof(AethernetShortcutConverter))]
- public AethernetShortcut AethernetShortcut { get; set; }
+ public AethernetShortcut? AethernetShortcut { get; set; }
}
},
"TerritoryId": 129,
"InteractionType": "Interact",
+ "AetheryteShortcut": "Limsa Lominsa",
"AethernetShortcut": [
"[Limsa Lominsa] Aetheryte Plaza",
"[Limsa Lominsa] Arcanist's Guild"
{
"DataId": 1038578,
"Position": {
- "X": -53.576973,
+ "X": -56.737106,
"Y": -15.127001,
- "Z": 131.09679
+ "Z": 130.76611
},
+ "StopDistance": 0.25,
"TerritoryId": 962,
"InteractionType": "Interact"
}
{
"DataId": 1038578,
"Position": {
- "X": 0.86839586,
- "Y": 3.2250001,
- "Z": 9.54673
+ "X": -0.03130532,
+ "Y": 3.2249997,
+ "Z": 8.909777
},
+ "StopDistance": 0.25,
"TerritoryId": 962,
"InteractionType": "Interact"
}
{
"DataId": 1038578,
"Position": {
- "X": 65.84385,
+ "X": 66.10567,
"Y": 5.0999994,
- "Z": -63.417946
+ "Z": -63.37148
},
+ "StopDistance": 0.25,
"TerritoryId": 962,
"InteractionType": "Interact"
}
{
"Sequence": 6,
"Steps": [
+ {
+ "Position": {
+ "X": 93.30914,
+ "Y": 8.920153,
+ "Z": -89.12467
+ },
+ "TerritoryId": 962,
+ "InteractionType": "WalkTo"
+ },
{
"DataId": 1038578,
"Position": {
"Y": 41.37599,
"Z": -142.5033
},
+ "StopDistance": 0.25,
"TerritoryId": 962,
"InteractionType": "Interact"
}
"Y": 41.367188,
"Z": -156.6034
},
+ "StopDistance": 0.25,
"TerritoryId": 962,
"InteractionType": "AttuneAethenetShard"
},
"Y": 1.0594833,
"Z": 30.052902
},
+ "StopDistance": 0.25,
"TerritoryId": 962,
"InteractionType": "Interact",
"AethernetShortcut": [
"InteractionType": "Interact"
}
]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1038679,
+ "Position": {
+ "X": -275.5932,
+ "Y": 19.003881,
+ "Z": 13.321045
+ },
+ "TerritoryId": 962,
+ "InteractionType": "Interact"
+ }
+ ]
}
]
}
{
"DataId": 1037077,
"Position": {
- "X": -38.07129,
+ "X": -38.066784,
"Y": -14.169313,
- "Z": 105.30249
+ "Z": 107.68768
},
"TerritoryId": 962,
"InteractionType": "Interact"
"Steps": [
{
"Position": {
- "X": 15.242085,
+ "X": 19.79008,
"Y": -16.247002,
- "Z": 109.177666
+ "Z": 108.36692
},
"TerritoryId": 962,
"InteractionType": "WalkTo"
},
"TerritoryId": 956,
"InteractionType": "Combat",
+ "EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
14024
]
},
"TerritoryId": 956,
"InteractionType": "Combat",
+ "EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
14023,
14022
},
"TerritoryId": 956,
"InteractionType": "ManualAction",
- "Comment": "Duty - Shoot some bird"
+ "Comment": "Duty - Shoot Large Green Bird"
}
]
},
"Z": -72.22095
},
"TerritoryId": 956,
+ "EnemySpawnType": "AfterInteraction",
"InteractionType": "Combat",
"KillEnemyDataIds": [
14020
},
"TerritoryId": 956,
"InteractionType": "Combat",
+ "EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
14021
]
"Sequence": 5,
"Steps": [
{
- "DataId": 2011984,
- "Position": {
- "X": 497.09314,
- "Y": 73.41101,
- "Z": -267.23126
- },
- "TerritoryId": 956,
- "InteractionType": "AttuneAetherCurrent"
- },
- {
- "DataId": 1038708,
+ "DataId": 1037985,
"Position": {
- "X": 408.31604,
- "Y": 65.3329,
- "Z": -130.11371
+ "X": 481.8036,
+ "Y": 66.16195,
+ "Z": -108.537415
},
"TerritoryId": 956,
"InteractionType": "Interact"
"InteractionType": "Interact"
},
{
- "DataId": 1037985,
+ "DataId": 1038708,
"Position": {
- "X": 481.8036,
- "Y": 66.16195,
- "Z": -108.537415
+ "X": 408.31604,
+ "Y": 65.3329,
+ "Z": -130.11371
},
"TerritoryId": 956,
"InteractionType": "Interact"
{
"Sequence": 6,
"Steps": [
+ {
+ "DataId": 2011984,
+ "Position": {
+ "X": 497.09314,
+ "Y": 73.41101,
+ "Z": -267.23126
+ },
+ "TerritoryId": 956,
+ "InteractionType": "AttuneAetherCurrent"
+ },
{
"DataId": 2011843,
"Position": {
"Y": 81.17488,
"Z": -534.99316
},
+ "StopDistance": 0.25,
"TerritoryId": 956,
"InteractionType": "WalkTo"
}
{
"Sequence": 1,
"Steps": [
+ {
+ "Position": {
+ "X": -329.64972,
+ "Y": 77.91884,
+ "Z": -448.5044
+ },
+ "TerritoryId": 956,
+ "InteractionType": "WalkTo"
+ },
+ {
+ "Position": {
+ "X": -327.6718,
+ "Y": 79.535736,
+ "Z": -400.00397
+ },
+ "TerritoryId": 956,
+ "InteractionType": "WalkTo"
+ },
{
"DataId": 2011982,
"Position": {
"TerritoryId": 956,
"InteractionType": "AttuneAetherCurrent"
},
+ {
+ "Position": {
+ "X": -327.6718,
+ "Y": 79.535736,
+ "Z": -400.00397
+ },
+ "TerritoryId": 956,
+ "InteractionType": "WalkTo"
+ },
{
"DataId": 2011983,
"Position": {
"Y": 6.991216,
"Z": 629.2394
},
+ "StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "AttuneAetheryte"
},
{
+ "DataId": 2011948,
"Position": {
- "X": 190.45029,
- "Y": 0.9965663,
- "Z": 703.01746
- },
- "TerritoryId": 957,
- "InteractionType": "WalkTo"
- },
- {
- "DataId": 1037622,
- "Position": {
- "X": 187.9148,
- "Y": 0.26447815,
- "Z": 700.12964
+ "X": 204.66919,
+ "Y": 2.243042,
+ "Z": 715.4192
},
"TerritoryId": 957,
"InteractionType": "Interact"
},
{
- "DataId": 2011948,
+ "DataId": 1037622,
"Position": {
- "X": 204.66919,
- "Y": 2.243042,
- "Z": 715.4192
+ "X": 189.94562,
+ "Y": 0.8560083,
+ "Z": 702.7896
},
+ "StopDistance": 0.25,
"TerritoryId": 957,
"InteractionType": "Interact"
},
"Y": 1.769943,
"Z": 738.9843
},
+ "StopDistance": 0.25,
"TerritoryId": 957,
"InteractionType": "Interact"
}
},
"TerritoryId": 957,
"InteractionType": "Combat",
+ "EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
14006
]
"Y": 4.785123,
"Z": 36.76496
},
+ "StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "AttuneAetheryte"
},
},
"TerritoryId": 957,
"InteractionType": "Combat",
+ "EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
14004
]
"Y": 88.84356,
"Z": -608.6588
},
+ "StopDistance": 0.25,
"TerritoryId": 957,
"InteractionType": "WalkTo"
}
{
"Sequence": 1,
"Steps": [
+ {
+ "Position": {
+ "X": -80.636894,
+ "Y": 99.974266,
+ "Z": -708.7214
+ },
+ "TerritoryId": 957,
+ "InteractionType": "WalkTo"
+ },
+ {
+ "Position": {
+ "X": -67.775665,
+ "Y": 97.140656,
+ "Z": -710.1025
+ },
+ "TerritoryId": 957,
+ "InteractionType": "WalkTo"
+ },
{
"DataId": 2011991,
"Position": {
},
"TerritoryId": 962,
"InteractionType": "Interact",
+ "AetheryteShortcut": "Old Sharlayan",
"AethernetShortcut": [
"[Old Sharlayan] Aetheryte Plaza",
"[Old Sharlayan] The Baldesion Annex"
"Z": -569.8787
},
"TerritoryId": 957,
+ "AetheryteShortcut": "Thavnair - Great Work",
"InteractionType": "Interact"
}
]
"Z": 539.6046
},
"TerritoryId": 621,
- "InteractionType": "Interact"
+ "InteractionType": "ManualAction",
+ "Comment": "Duty - A Frosty Reception"
}
]
},
"InteractionType": "ManualAction",
"Comment": "Cutscene Interaction needed",
"AethernetShortcut": [
- "[The Crystarium] Aetheryte Plaza",
- "[The Crystarium] The Cabinet of Curiosity"
+ "[Crystarium] Aetheryte Plaza",
+ "[Crystarium] The Cabinet of Curiosity"
]
}
]
"TerritoryId": 819,
"InteractionType": "Interact",
"AethernetShortcut": [
- "[The Crystarium] The Cabinet of Curiosity",
- "[The Crystarium] The Dossal Gate"
+ "[Crystarium] The Cabinet of Curiosity",
+ "[Crystarium] The Dossal Gate"
]
}
]
+++ /dev/null
-{
- "Version": 1,
- "Author": "liza",
- "QuestSequence": [
- {
- "Sequence": 0,
- "Steps": [
- {
- "DataId": 1040307,
- "Position": {
- "X": -469.29126,
- "Y": 232.2548,
- "Z": -252.8573
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- }
- ]
- },
- {
- "Sequence": 1,
- "Steps": [
- {
- "DataId": 1040315,
- "Position": {
- "X": -345.87628,
- "Y": 254.66968,
- "Z": -277.27173
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- }
- ]
- },
- {
- "Sequence": 2,
- "Steps": [
- {
- "DataId": 1040313,
- "Position": {
- "X": -339.10126,
- "Y": 255.53401,
- "Z": -281.75793
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- }
- ]
- },
- {
- "Sequence": 3,
- "Steps": [
- {
- "DataId": 2012030,
- "Position": {
- "X": -333.547,
- "Y": 270.83228,
- "Z": -361.50153
- },
- "TerritoryId": 960,
- "InteractionType": "AttuneAetherCurrent"
- },
- {
- "DataId": 2012035,
- "Position": {
- "X": -238.81903,
- "Y": 320.36304,
- "Z": -295.15533
- },
- "TerritoryId": 960,
- "InteractionType": "AttuneAetherCurrent"
- },
- {
- "DataId": 1040317,
- "Position": {
- "X": -200.57983,
- "Y": 268.01642,
- "Z": -312.58112
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- }
- ]
- },
- {
- "Sequence": 4,
- "Steps": [
- {
- "DataId": 1040318,
- "Position": {
- "X": 29.984009,
- "Y": 270.45825,
- "Z": -535.0271
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- }
- ]
- },
- {
- "Sequence": 5,
- "Steps": [
- {
- "DataId": 2012354,
- "Position": {
- "X": 30.777344,
- "Y": 272.51086,
- "Z": -600.7019
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- },
- {
- "DataId": 2012355,
- "Position": {
- "X": 64.10315,
- "Y": 272.4497,
- "Z": -616.4492
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- },
- {
- "DataId": 180,
- "Position": {
- "X": 64.286255,
- "Y": 272.48022,
- "Z": -657.49603
- },
- "TerritoryId": 960,
- "InteractionType": "AttuneAetheryte"
- },
- {
- "DataId": 2012356,
- "Position": {
- "X": 115.526,
- "Y": 272.99915,
- "Z": -617.853
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- },
- {
- "DataId": 2012357,
- "Position": {
- "X": 151.59839,
- "Y": 272.9381,
- "Z": -592.5841
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- }
- ]
- },
- {
- "Sequence": 255,
- "Steps": [
- {
- "DataId": 1040318,
- "Position": {
- "X": 29.984009,
- "Y": 270.45825,
- "Z": -535.0271
- },
- "TerritoryId": 960,
- "InteractionType": "Interact"
- }
- ]
- }
- ]
-}
--- /dev/null
+{
+ "Version": 1,
+ "Author": "liza",
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1040307,
+ "Position": {
+ "X": -469.29126,
+ "Y": 232.2548,
+ "Z": -252.8573
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "DataId": 1040315,
+ "Position": {
+ "X": -345.87628,
+ "Y": 254.66968,
+ "Z": -277.27173
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 2,
+ "Steps": [
+ {
+ "DataId": 1040313,
+ "Position": {
+ "X": -339.10126,
+ "Y": 255.53401,
+ "Z": -281.75793
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 3,
+ "Steps": [
+ {
+ "DataId": 2012030,
+ "Position": {
+ "X": -333.547,
+ "Y": 270.83228,
+ "Z": -361.50153
+ },
+ "TerritoryId": 960,
+ "InteractionType": "AttuneAetherCurrent"
+ },
+ {
+ "DataId": 2012035,
+ "Position": {
+ "X": -238.81903,
+ "Y": 320.36304,
+ "Z": -295.15533
+ },
+ "TerritoryId": 960,
+ "InteractionType": "AttuneAetherCurrent"
+ },
+ {
+ "DataId": 1040317,
+ "Position": {
+ "X": -200.57983,
+ "Y": 268.01642,
+ "Z": -312.58112
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 4,
+ "Steps": [
+ {
+ "DataId": 1040318,
+ "Position": {
+ "X": 29.984009,
+ "Y": 270.45825,
+ "Z": -535.0271
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 5,
+ "Steps": [
+ {
+ "DataId": 2012354,
+ "Position": {
+ "X": 30.777344,
+ "Y": 272.51086,
+ "Z": -600.7019
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ },
+ {
+ "DataId": 2012355,
+ "Position": {
+ "X": 64.10315,
+ "Y": 272.4497,
+ "Z": -616.4492
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ },
+ {
+ "DataId": 180,
+ "Position": {
+ "X": 64.286255,
+ "Y": 272.48022,
+ "Z": -657.49603
+ },
+ "TerritoryId": 960,
+ "InteractionType": "AttuneAetheryte"
+ },
+ {
+ "DataId": 2012356,
+ "Position": {
+ "X": 115.526,
+ "Y": 272.99915,
+ "Z": -617.853
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ },
+ {
+ "DataId": 2012357,
+ "Position": {
+ "X": 151.59839,
+ "Y": 272.9381,
+ "Z": -592.5841
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1040318,
+ "Position": {
+ "X": 29.984009,
+ "Y": 270.45825,
+ "Z": -535.0271
+ },
+ "TerritoryId": 960,
+ "InteractionType": "Interact"
+ }
+ ]
+ }
+ ]
+}
"TerritoryId": 819,
"InteractionType": "Interact",
"AethernetShortcut": [
- "[The Crystarium] Aetheryte Plaza",
- "[The Crystarium] The Cabinet of Curiosity"
+ "[Crystarium] Aetheryte Plaza",
+ "[Crystarium] The Cabinet of Curiosity"
]
}
]
{
- "Version": 1
+ "Version": 1,
"Author": "liza",
"QuestSequence": [
{
"TerritoryId": 819,
"InteractionType": "Interact",
"AethernetShortcut": [
- "[The Crystarium] Aetheryte Plaza",
- "[The Crystarium] The Dossal Gate"
+ "[Crystarium] Aetheryte Plaza",
+ "[Crystarium] The Dossal Gate"
]
}
]
"TerritoryId": 819,
"InteractionType": "Interact",
"AethernetShortcut": [
- "[The Crystarium] Aetheryte Plaza",
- "[The Crystarium] The Dossal Gate"
+ "[Crystarium] Aetheryte Plaza",
+ "[Crystarium] The Dossal Gate"
]
}
]
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://example.com/quest/1.0",
+ "$id": "/quest/1.0",
"title": "Questionable V1",
"description": "A series of quest sequences",
"type": "object",
"Z"
]
},
+ "StopDistance": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
"TerritoryId": {
"type": "integer",
"exclusiveMinimum": 0
"Disabled": {
"type": "boolean"
},
+ "AetheryteShortcut": {
+ "type": "string",
+ "$comment": "TODO add remaining aetherytes for 2.x/3.x",
+ "enum": [
+ "Limsa Lominsa",
+ "Gridania",
+ "Ul'dah",
+ "Ishgard",
+
+ "Rhalgr's Reach",
+ "Fringes - Castrum Oriens",
+ "Fringes - Peering Stones",
+ "Peaks - Ala Gannha",
+ "Peaks - Ala Ghiri",
+ "Lochs - Porta Praetoria",
+ "Lochs - Ala Mhigan Quarter",
+ "Kugane",
+ "Ruby Sea - Tamamizu",
+ "Ruby Sea - Onokoro",
+ "Yanxia - Namai",
+ "Yanxia - House of the Fierce",
+ "Azim Steppe - Reunion",
+ "Azim Steppe - Dawn Throne",
+ "Azim Steppe - Dhoro Iloh",
+ "Doman Enclave",
+ "Doman Enclave - Northern Enclave",
+ "Doman Enclave - Southern Enclave",
+
+ "Crystarium",
+ "Eulmore",
+ "Lakeland - Fort Jobb",
+ "Lakeland - Ostall Imperative",
+ "Kholusia - Stilltide",
+ "Kholusia - Wright",
+ "Kholusia - Tomra",
+ "Amh Araeng - Mord Souq",
+ "Amh Araeng - Inn at Journey's Head",
+ "Amh Araeng - Twine",
+ "Rak'tika - Slitherbough",
+ "Rak'tika - Fanow",
+ "Il Mheg - Lydha Lran",
+ "Il Mheg - Pia Enni",
+ "Il Mheg - Wolekdorf",
+ "Tempest - Ondo Cups",
+ "Tempest - Macarenses Angle",
+
+ "Old Sharlayan",
+ "Radz-at-Han",
+ "Labyrinthos - Archeion",
+ "Labyrinthos - Sharlayan Hamlet",
+ "Labyrinthos - Aporia",
+ "Thavnair - Yedlihmad",
+ "Thavnair - Great Work",
+ "Thavnair - Palaka's Stand",
+ "Garlemald - Camp Broken Glass",
+ "Garlemald - Tertium",
+ "Mare Lamentorum - Sinus Lacrimarum",
+ "Mare Lamentorum - Bestways Burrow",
+ "Elpis - Anagnorisis",
+ "Elpis - Twelve Wonders",
+ "Elpis - Poieten Oikos",
+ "Ultima Thule - Reah Tahra",
+ "Ultima Thula - Abode of the Ea",
+ "Ultima Thule - Base Omicron"
+ ]
+ },
"AethernetShortcut": {
"type": "array",
"minItems": 2,
"[Kugane] Sekiseigumi Barracks",
"[Kugane] Rakuza District",
"[Kugane] Airship Landing",
- "[The Crystarium] Aetheryte Plaza",
- "[The Crystarium] Musica Universalis Markets",
- "[The Crystarium] Themenos Rookery",
- "[The Crystarium] The Dossal Gate",
- "[The Crystarium] The Pendants",
- "[The Crystarium] The Amaro Launch",
- "[The Crystarium] The Crystalline Mean",
- "[The Crystarium] The Cabinet of Curiosity",
+ "[Crystarium] Aetheryte Plaza",
+ "[Crystarium] Musica Universalis Markets",
+ "[Crystarium] Themenos Rookery",
+ "[Crystarium] The Dossal Gate",
+ "[Crystarium] The Pendants",
+ "[Crystarium] The Amaro Launch",
+ "[Crystarium] The Crystalline Mean",
+ "[Crystarium] The Cabinet of Curiosity",
"[Eulmore] Aetheryte Plaza",
"[Eulmore] Southeast Derelicts",
"[Eulmore] Nightsoil Pots",
]
}
},
+ "EnemySpawnType": {
+ "type": "string",
+ "enum": [
+ "AutoOnEnterArea",
+ "AfterInteraction"
+ ]
+ },
"KillEnemyDataIds": {
"type": "array",
"items": {
-using System.Numerics;
+using System;
+using System.Numerics;
using Dalamud.Game;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface.Windowing;
private readonly IFramework _framework;
private readonly IGameGui _gameGui;
private readonly GameFunctions _gameFunctions;
+ private readonly QuestController _questController;
+
private readonly MovementController _movementController;
public Questionable(DalamudPluginInterface pluginInterface, IClientState clientState, ITargetManager targetManager,
IFramework framework, IGameGui gameGui, IDataManager dataManager, ISigScanner sigScanner,
- IPluginLog pluginLog)
+ IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui, ICommandManager commandManager)
{
+ ArgumentNullException.ThrowIfNull(pluginInterface);
+ ArgumentNullException.ThrowIfNull(sigScanner);
+ ArgumentNullException.ThrowIfNull(dataManager);
+ ArgumentNullException.ThrowIfNull(objectTable);
+
_pluginInterface = pluginInterface;
_clientState = clientState;
_framework = framework;
_gameGui = gameGui;
- _gameFunctions = new GameFunctions(dataManager, sigScanner);
+ _gameFunctions = new GameFunctions(dataManager, objectTable, sigScanner, targetManager, pluginLog);
+
+ NavmeshIpc navmeshIpc = new NavmeshIpc(pluginInterface);
_movementController =
- new MovementController(new NavmeshIpc(pluginInterface), clientState, _gameFunctions, pluginLog);
- _windowSystem.AddWindow(new DebugWindow(_movementController, _gameFunctions, clientState, targetManager));
+ new MovementController(navmeshIpc, clientState, _gameFunctions, pluginLog);
+ _questController = new QuestController(pluginInterface, dataManager, _clientState, _gameFunctions,
+ _movementController, pluginLog, condition, chatGui, commandManager);
+ _windowSystem.AddWindow(new DebugWindow(_movementController, _questController, _gameFunctions, clientState,
+ targetManager));
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_framework.Update += FrameworkUpdate;
private void FrameworkUpdate(IFramework framework)
{
- HandleNavigationShortcut();
+ _questController.Update();
+ HandleNavigationShortcut();
_movementController.Update();
}
using System.Globalization;
using System.Numerics;
using Dalamud.Game.ClientState.Objects;
+using Dalamud.Interface;
+using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using ImGuiNET;
using Questionable.Controller;
+using Questionable.Model.V1;
namespace Questionable.Windows;
internal sealed class DebugWindow : Window
{
private readonly MovementController _movementController;
+ private readonly QuestController _questController;
private readonly GameFunctions _gameFunctions;
private readonly IClientState _clientState;
private readonly ITargetManager _targetManager;
- public DebugWindow(MovementController movementController, GameFunctions gameFunctions, IClientState clientState,
+ public DebugWindow(MovementController movementController, QuestController questController,
+ GameFunctions gameFunctions, IClientState clientState,
ITargetManager targetManager)
: base("Questionable", ImGuiWindowFlags.AlwaysAutoResize)
{
_movementController = movementController;
+ _questController = questController;
_gameFunctions = gameFunctions;
_clientState = clientState;
_targetManager = targetManager;
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
return;
+ var currentQuest = _questController.CurrentQuest;
+ if (currentQuest != null)
+ {
+ ImGui.TextUnformatted($"Quest: {currentQuest.Quest.Name} / {currentQuest.Sequence} / {currentQuest.Step}");
+ ImGui.TextUnformatted(_questController.DebugState ?? "--");
+
+ ImGui.BeginDisabled(_questController.GetNextStep().Step == null);
+ ImGui.Text($"{_questController.GetNextStep().Step?.Position}");
+ if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
+ {
+ _questController.ExecuteNextStep();
+ }
+
+ ImGui.SameLine();
+
+ if (ImGuiComponents.IconButton(FontAwesomeIcon.StepForward))
+ {
+ _questController.IncreaseStepCount();
+ }
+
+ ImGui.EndDisabled();
+ }
+ else
+ ImGui.Text("No active quest");
+
+ ImGui.Separator();
+
ImGui.Text(
$"Current TerritoryId: {_clientState.TerritoryType}, Flying: {(_gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType) ? "Yes" : "No")}");
if (_targetManager.Target != null)
{
ImGui.Separator();
- ImGui.Text($"Target: {_targetManager.Target.Name}");
+ ImGui.Text(string.Create(CultureInfo.InvariantCulture,
+ $"Target: {_targetManager.Target.Name} ({(_targetManager.Target.Position - _clientState.LocalPlayer.Position).Length():F2})"));
ImGui.BeginDisabled(!_movementController.IsNavmeshReady);
if (!_movementController.IsPathfinding)
ImGui.SameLine();
- if (ImGui.Button($"Copy"))
+ ImGui.Button("Copy");
+ if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{
ImGui.SetClipboardText($$"""
"DataId": {{_targetManager.Target.DataId}},
"InteractionType": "Interact"
""");
}
+ else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
+ {
+ 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
{