Updates for everything

arr-p5
Liza 2024-05-26 21:45:26 +02:00
parent ed109a0fca
commit 5040242dea
Signed by: liza
GPG Key ID: 7199F8D727D55F67
44 changed files with 1093 additions and 122 deletions

View File

@ -11,6 +11,7 @@ namespace Questionable.Controller;
internal sealed class MovementController : IDisposable internal sealed class MovementController : IDisposable
{ {
public const float DefaultStopDistance = 3f;
private readonly NavmeshIpc _navmeshIpc; private readonly NavmeshIpc _navmeshIpc;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
@ -30,6 +31,8 @@ internal sealed class MovementController : IDisposable
public bool IsNavmeshReady => _navmeshIpc.IsReady; public bool IsNavmeshReady => _navmeshIpc.IsReady;
public bool IsPathRunning => _navmeshIpc.IsPathRunning; public bool IsPathRunning => _navmeshIpc.IsPathRunning;
public bool IsPathfinding => _pathfindTask is { IsCompleted: false }; public bool IsPathfinding => _pathfindTask is { IsCompleted: false };
public Vector3? Destination { get; private set; }
public float StopDistance { get; private set; }
public void Update() public void Update()
{ {
@ -48,14 +51,24 @@ internal sealed class MovementController : IDisposable
ResetPathfinding(); 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(); ResetPathfinding();
_gameFunctions.ExecuteCommand("/automove off"); _gameFunctions.ExecuteCommand("/automove off");
Destination = to;
StopDistance = stopDistance ?? (DefaultStopDistance - 0.2f);
_cancellationTokenSource = new(); _cancellationTokenSource = new();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10)); _cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
_pathfindTask = _pathfindTask =

View File

@ -1,22 +1,61 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO; using System.IO;
using System.Numerics;
using System.Text.Json; using System.Text.Json;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin; 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; using Questionable.Model.V1;
namespace Questionable.Controller; namespace Questionable.Controller;
internal sealed class QuestController 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(); 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 #if false
LoadFromEmbeddedResources(); LoadFromEmbeddedResources();
#endif #endif
LoadFromDirectory(new DirectoryInfo(@"E:\ffxiv\Questionable\Questionable\QuestPaths")); LoadFromDirectory(new DirectoryInfo(@"E:\ffxiv\Questionable\Questionable\QuestPaths"));
LoadFromDirectory(pluginInterface.ConfigDirectory); 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 #if false
@ -40,20 +79,30 @@ internal sealed class QuestController
} }
#endif #endif
public QuestProgress? CurrentQuest { get; set; }
public string? DebugState { get; set; }
private void LoadFromDirectory(DirectoryInfo configDirectory) private void LoadFromDirectory(DirectoryInfo configDirectory)
{ {
foreach (FileInfo fileInfo in configDirectory.GetFiles("*.json")) foreach (FileInfo fileInfo in configDirectory.GetFiles("*.json"))
{ {
using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read); try
var (questId, name) = ExtractQuestDataFromName(fileInfo.Name);
Quest quest = new Quest
{ {
FilePath = fileInfo.FullName, using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
QuestId = questId, var (questId, name) = ExtractQuestDataFromName(fileInfo.Name);
Name = name, Quest quest = new Quest
Data = JsonSerializer.Deserialize<QuestData>(stream)!, {
}; FilePath = fileInfo.FullName,
_quests[questId] = quest; 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()) foreach (DirectoryInfo childDirectory in configDirectory.GetDirectories())
@ -65,7 +114,253 @@ internal sealed class QuestController
string name = resourceName.Substring(0, resourceName.Length - ".json".Length); string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
name = name.Substring(name.LastIndexOf('.') + 1); name = name.Substring(name.LastIndexOf('.') + 1);
ushort questId = ushort.Parse(name.Substring(0, name.IndexOf('_'))); string[] parts = name.Split('_', 2);
return (questId, name); 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);
} }

View File

@ -0,0 +1,130 @@
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();
}
}

View File

@ -0,0 +1,21 @@
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);
}

View File

@ -53,6 +53,7 @@ internal sealed class NavmeshIpc
public Task<List<Vector3>> Pathfind(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly, public Task<List<Vector3>> Pathfind(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
_pathSetTolerance.InvokeAction(0.25f);
return _navPathfind.InvokeFunc(localPlayerPosition, targetPosition, fly, cancellationToken); return _navPathfind.InvokeFunc(localPlayerPosition, targetPosition, fly, cancellationToken);
} }

View File

@ -3,17 +3,21 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.System.Memory; using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Questionable.Model.V1;
namespace Questionable; namespace Questionable;
@ -31,8 +35,15 @@ internal sealed unsafe class GameFunctions
private readonly delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString; private readonly delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString;
private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet; 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 = _processChatBox =
Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText(Signatures.SendChat)); Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText(Signatures.SendChat));
_sanitiseString = _sanitiseString =
@ -45,7 +56,7 @@ internal sealed unsafe class GameFunctions
.AsReadOnly(); .AsReadOnly();
} }
public (uint CurrentQuest, byte Sequence) GetCurrentQuest() public (ushort CurrentQuest, byte Sequence) GetCurrentQuest()
{ {
var scenarioTree = AgentScenarioTree.Instance(); var scenarioTree = AgentScenarioTree.Instance();
if (scenarioTree == null) if (scenarioTree == null)
@ -69,9 +80,52 @@ internal sealed unsafe class GameFunctions
//ImGui.Text($"Current Quest: {currentQuest}"); //ImGui.Text($"Current Quest: {currentQuest}");
//ImGui.Text($"Progress: {QuestManager.GetQuestSequence(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) public bool IsFlyingUnlocked(ushort territoryId)
{ {
var playerState = PlayerState.Instance(); var playerState = PlayerState.Instance();
@ -82,7 +136,7 @@ internal sealed unsafe class GameFunctions
public void ExecuteCommand(string command) public void ExecuteCommand(string command)
{ {
if (!command.StartsWith("/", StringComparison.Ordinal)) if (!command.StartsWith('/'))
return; return;
SendMessage(command); SendMessage(command);
@ -190,21 +244,37 @@ internal sealed unsafe class GameFunctions
internal ChatPayload(byte[] stringBytes) internal ChatPayload(byte[] stringBytes)
{ {
this.textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30); textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
Marshal.Copy(stringBytes, 0, this.textPtr, stringBytes.Length); Marshal.Copy(stringBytes, 0, textPtr, stringBytes.Length);
Marshal.WriteByte(this.textPtr + stringBytes.Length, 0); Marshal.WriteByte(textPtr + stringBytes.Length, 0);
this.textLen = (ulong)(stringBytes.Length + 1); textLen = (ulong)(stringBytes.Length + 1);
this.unk1 = 64; unk1 = 64;
this.unk2 = 0; unk2 = 0;
} }
public void Dispose() public void Dispose()
{ {
Marshal.FreeHGlobal(this.textPtr); Marshal.FreeHGlobal(textPtr);
} }
} }
#endregion #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;
}
}
}
} }

View File

@ -1,4 +1,5 @@
using Questionable.Model.V1; using System.Linq;
using Questionable.Model.V1;
namespace Questionable; namespace Questionable;
@ -7,6 +8,9 @@ internal sealed class Quest
public required string FilePath { get; init; } public required string FilePath { get; init; }
public required ushort QuestId { 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 required QuestData Data { get; init; }
public QuestSequence? FindSequence(byte currentSequence)
=> Data.QuestSequence.SingleOrDefault(seq => seq.Sequence == currentSequence);
} }

View File

@ -61,14 +61,14 @@ public sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut
{ EAetheryteLocation.KuganeSekiseigumiBarracks, "[Kugane] Sekiseigumi Barracks" }, { EAetheryteLocation.KuganeSekiseigumiBarracks, "[Kugane] Sekiseigumi Barracks" },
{ EAetheryteLocation.KuganeRakuzaDistrict, "[Kugane] Rakuza District" }, { EAetheryteLocation.KuganeRakuzaDistrict, "[Kugane] Rakuza District" },
{ EAetheryteLocation.KuganeAirship, "[Kugane] Airship Landing" }, { EAetheryteLocation.KuganeAirship, "[Kugane] Airship Landing" },
{ EAetheryteLocation.Crystarium, "[The Crystarium] Aetheryte Plaza" }, { EAetheryteLocation.Crystarium, "[Crystarium] Aetheryte Plaza" },
{ EAetheryteLocation.CrystariumMarkets, "[The Crystarium] Musica Universalis Markets" }, { EAetheryteLocation.CrystariumMarkets, "[Crystarium] Musica Universalis Markets" },
{ EAetheryteLocation.CrystariumThemenosRookery, "[The Crystarium] Themenos Rookery" }, { EAetheryteLocation.CrystariumThemenosRookery, "[Crystarium] Themenos Rookery" },
{ EAetheryteLocation.CrystariumDossalGate, "[The Crystarium] The Dossal Gate" }, { EAetheryteLocation.CrystariumDossalGate, "[Crystarium] The Dossal Gate" },
{ EAetheryteLocation.CrystariumPendants, "[The Crystarium] The Pendants" }, { EAetheryteLocation.CrystariumPendants, "[Crystarium] The Pendants" },
{ EAetheryteLocation.CrystariumAmaroLaunch, "[The Crystarium] The Amaro Launch" }, { EAetheryteLocation.CrystariumAmaroLaunch, "[Crystarium] The Amaro Launch" },
{ EAetheryteLocation.CrystariumCrystallineMean, "[The Crystarium] The Crystalline Mean" }, { EAetheryteLocation.CrystariumCrystallineMean, "[Crystarium] The Crystalline Mean" },
{ EAetheryteLocation.CrystariumCabinetOfCuriosity, "[The Crystarium] The Cabinet of Curiosity" }, { EAetheryteLocation.CrystariumCabinetOfCuriosity, "[Crystarium] The Cabinet of Curiosity" },
{ EAetheryteLocation.Eulmore, "[Eulmore] Aetheryte Plaza" }, { EAetheryteLocation.Eulmore, "[Eulmore] Aetheryte Plaza" },
{ EAetheryteLocation.EulmoreSoutheastDerelict, "[Eulmore] Southeast Derelicts" }, { EAetheryteLocation.EulmoreSoutheastDerelict, "[Eulmore] Southeast Derelicts" },
{ EAetheryteLocation.EulmoreNightsoilPots, "[Eulmore] Nightsoil Pots" }, { EAetheryteLocation.EulmoreNightsoilPots, "[Eulmore] Nightsoil Pots" },
@ -95,7 +95,7 @@ public sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut
private static readonly Dictionary<string, EAetheryteLocation> StringToEnum = private static readonly Dictionary<string, EAetheryteLocation> StringToEnum =
EnumToString.ToDictionary(x => x.Value, x => x.Key); 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) JsonSerializerOptions options)
{ {
if (reader.TokenType != JsonTokenType.StartArray) if (reader.TokenType != JsonTokenType.StartArray)

View File

@ -0,0 +1,96 @@
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]);
}
}

View File

@ -12,7 +12,7 @@ public sealed class InteractionTypeConverter : JsonConverter<EInteractionType>
{ {
{ EInteractionType.Interact, "Interact" }, { EInteractionType.Interact, "Interact" },
{ EInteractionType.WalkTo, "WalkTo" }, { EInteractionType.WalkTo, "WalkTo" },
{ EInteractionType.AttuneAethenetShard, "AttuneAethenetShard" }, { EInteractionType.AttuneAethernetShard, "AttuneAethenetShard" },
{ EInteractionType.AttuneAetheryte, "AttuneAetheryte" }, { EInteractionType.AttuneAetheryte, "AttuneAetheryte" },
{ EInteractionType.AttuneAetherCurrent, "AttuneAetherCurrent" }, { EInteractionType.AttuneAetherCurrent, "AttuneAetherCurrent" },
{ EInteractionType.Combat, "Combat" }, { EInteractionType.Combat, "Combat" },

View File

@ -0,0 +1,66 @@
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();
}
}

View File

@ -2,6 +2,8 @@
public enum EAetheryteLocation public enum EAetheryteLocation
{ {
None = 0,
Gridania = 2, Gridania = 2,
GridaniaArcher = 25, GridaniaArcher = 25,
GridaniaLeatherworker = 26, GridaniaLeatherworker = 26,
@ -60,6 +62,24 @@ public enum EAetheryteLocation
KuganeRakuzaDistrict = 119, KuganeRakuzaDistrict = 119,
KuganeAirship = 120, 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, Crystarium = 133,
CrystariumMarkets = 149, CrystariumMarkets = 149,
CrystariumThemenosRookery = 150, CrystariumThemenosRookery = 150,
@ -75,6 +95,22 @@ public enum EAetheryteLocation
EulmoreGloryGate = 159, EulmoreGloryGate = 159,
EulmoreSoutheastDerelict = 135, 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, OldSharlayan = 182,
OldSharlayanStudium = 184, OldSharlayanStudium = 184,
OldSharlayanBaldesionAnnex = 185, OldSharlayanBaldesionAnnex = 185,
@ -92,4 +128,21 @@ public enum EAetheryteLocation
RadzAtHanMehrydesMeyhane = 196, RadzAtHanMehrydesMeyhane = 196,
RadzAtHanKama = 198, RadzAtHanKama = 198,
RadzAtHanHighCrucible = 199, 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
} }

View File

@ -4,7 +4,7 @@ public enum EInteractionType
{ {
Interact, Interact,
WalkTo, WalkTo,
AttuneAethenetShard, AttuneAethernetShard,
AttuneAetheryte, AttuneAetheryte,
AttuneAetherCurrent, AttuneAetherCurrent,
Combat, Combat,

View File

@ -7,7 +7,7 @@ public class QuestData
public required int Version { get; set; } public required int Version { get; set; }
public required string Author { get; set; } public required string Author { get; set; }
public List<string> Contributors { get; set; } = new(); 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 List<ushort> TerritoryBlacklist { get; set; } = new();
public required List<QuestSequence> QuestSequence { get; set; } = new(); public required List<QuestSequence> QuestSequence { get; set; } = new();
} }

View File

@ -5,6 +5,6 @@ namespace Questionable.Model.V1;
public class QuestSequence public class QuestSequence
{ {
public required int Sequence { get; set; } public required int Sequence { get; set; }
public string Comment { get; set; } public string? Comment { get; set; }
public List<QuestStep> Steps { get; set; } = new(); public List<QuestStep> Steps { get; set; } = new();
} }

View File

@ -10,11 +10,19 @@ public class QuestStep
[JsonConverter(typeof(InteractionTypeConverter))] [JsonConverter(typeof(InteractionTypeConverter))]
public EInteractionType InteractionType { get; set; } public EInteractionType InteractionType { get; set; }
public ulong? DataId { get; set; } public uint? DataId { get; set; }
public Vector3 Position { get; set; }
[JsonConverter(typeof(VectorConverter))]
public Vector3? Position { get; set; }
public float? StopDistance { get; set; }
public ushort TerritoryId { get; set; } public ushort TerritoryId { get; set; }
public bool Disabled { get; set; } public bool Disabled { get; set; }
public string? Comment { get; set; }
[JsonConverter(typeof(AetheryteConverter))]
public EAetheryteLocation? AetheryteShortcut { get; set; }
[JsonConverter(typeof(AethernetShortcutConverter))] [JsonConverter(typeof(AethernetShortcutConverter))]
public AethernetShortcut AethernetShortcut { get; set; } public AethernetShortcut? AethernetShortcut { get; set; }
} }

View File

@ -15,6 +15,7 @@
}, },
"TerritoryId": 129, "TerritoryId": 129,
"InteractionType": "Interact", "InteractionType": "Interact",
"AetheryteShortcut": "Limsa Lominsa",
"AethernetShortcut": [ "AethernetShortcut": [
"[Limsa Lominsa] Aetheryte Plaza", "[Limsa Lominsa] Aetheryte Plaza",
"[Limsa Lominsa] Arcanist's Guild" "[Limsa Lominsa] Arcanist's Guild"

View File

@ -63,10 +63,11 @@
{ {
"DataId": 1038578, "DataId": 1038578,
"Position": { "Position": {
"X": -53.576973, "X": -56.737106,
"Y": -15.127001, "Y": -15.127001,
"Z": 131.09679 "Z": 130.76611
}, },
"StopDistance": 0.25,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
} }
@ -78,10 +79,11 @@
{ {
"DataId": 1038578, "DataId": 1038578,
"Position": { "Position": {
"X": 0.86839586, "X": -0.03130532,
"Y": 3.2250001, "Y": 3.2249997,
"Z": 9.54673 "Z": 8.909777
}, },
"StopDistance": 0.25,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
} }
@ -103,10 +105,11 @@
{ {
"DataId": 1038578, "DataId": 1038578,
"Position": { "Position": {
"X": 65.84385, "X": 66.10567,
"Y": 5.0999994, "Y": 5.0999994,
"Z": -63.417946 "Z": -63.37148
}, },
"StopDistance": 0.25,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
} }
@ -115,6 +118,15 @@
{ {
"Sequence": 6, "Sequence": 6,
"Steps": [ "Steps": [
{
"Position": {
"X": 93.30914,
"Y": 8.920153,
"Z": -89.12467
},
"TerritoryId": 962,
"InteractionType": "WalkTo"
},
{ {
"DataId": 1038578, "DataId": 1038578,
"Position": { "Position": {
@ -122,6 +134,7 @@
"Y": 41.37599, "Y": 41.37599,
"Z": -142.5033 "Z": -142.5033
}, },
"StopDistance": 0.25,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
} }
@ -137,6 +150,7 @@
"Y": 41.367188, "Y": 41.367188,
"Z": -156.6034 "Z": -156.6034
}, },
"StopDistance": 0.25,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "AttuneAethenetShard" "InteractionType": "AttuneAethenetShard"
}, },
@ -182,6 +196,7 @@
"Y": 1.0594833, "Y": 1.0594833,
"Z": 30.052902 "Z": 30.052902
}, },
"StopDistance": 0.25,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact", "InteractionType": "Interact",
"AethernetShortcut": [ "AethernetShortcut": [

View File

@ -156,6 +156,21 @@
"InteractionType": "Interact" "InteractionType": "Interact"
} }
] ]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1038679,
"Position": {
"X": -275.5932,
"Y": 19.003881,
"Z": 13.321045
},
"TerritoryId": 962,
"InteractionType": "Interact"
}
]
} }
] ]
} }

View File

@ -67,9 +67,9 @@
{ {
"DataId": 1037077, "DataId": 1037077,
"Position": { "Position": {
"X": -38.07129, "X": -38.066784,
"Y": -14.169313, "Y": -14.169313,
"Z": 105.30249 "Z": 107.68768
}, },
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
@ -141,9 +141,9 @@
"Steps": [ "Steps": [
{ {
"Position": { "Position": {
"X": 15.242085, "X": 19.79008,
"Y": -16.247002, "Y": -16.247002,
"Z": 109.177666 "Z": 108.36692
}, },
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "WalkTo" "InteractionType": "WalkTo"

View File

@ -44,6 +44,7 @@
}, },
"TerritoryId": 956, "TerritoryId": 956,
"InteractionType": "Combat", "InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
14024 14024
] ]
@ -77,6 +78,7 @@
}, },
"TerritoryId": 956, "TerritoryId": 956,
"InteractionType": "Combat", "InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
14023, 14023,
14022 14022

View File

@ -109,7 +109,7 @@
}, },
"TerritoryId": 956, "TerritoryId": 956,
"InteractionType": "ManualAction", "InteractionType": "ManualAction",
"Comment": "Duty - Shoot some bird" "Comment": "Duty - Shoot Large Green Bird"
} }
] ]
}, },

View File

@ -38,6 +38,7 @@
"Z": -72.22095 "Z": -72.22095
}, },
"TerritoryId": 956, "TerritoryId": 956,
"EnemySpawnType": "AfterInteraction",
"InteractionType": "Combat", "InteractionType": "Combat",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
14020 14020
@ -57,6 +58,7 @@
}, },
"TerritoryId": 956, "TerritoryId": 956,
"InteractionType": "Combat", "InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
14021 14021
] ]
@ -97,21 +99,11 @@
"Sequence": 5, "Sequence": 5,
"Steps": [ "Steps": [
{ {
"DataId": 2011984, "DataId": 1037985,
"Position": { "Position": {
"X": 497.09314, "X": 481.8036,
"Y": 73.41101, "Y": 66.16195,
"Z": -267.23126 "Z": -108.537415
},
"TerritoryId": 956,
"InteractionType": "AttuneAetherCurrent"
},
{
"DataId": 1038708,
"Position": {
"X": 408.31604,
"Y": 65.3329,
"Z": -130.11371
}, },
"TerritoryId": 956, "TerritoryId": 956,
"InteractionType": "Interact" "InteractionType": "Interact"
@ -127,11 +119,11 @@
"InteractionType": "Interact" "InteractionType": "Interact"
}, },
{ {
"DataId": 1037985, "DataId": 1038708,
"Position": { "Position": {
"X": 481.8036, "X": 408.31604,
"Y": 66.16195, "Y": 65.3329,
"Z": -108.537415 "Z": -130.11371
}, },
"TerritoryId": 956, "TerritoryId": 956,
"InteractionType": "Interact" "InteractionType": "Interact"
@ -141,6 +133,16 @@
{ {
"Sequence": 6, "Sequence": 6,
"Steps": [ "Steps": [
{
"DataId": 2011984,
"Position": {
"X": 497.09314,
"Y": 73.41101,
"Z": -267.23126
},
"TerritoryId": 956,
"InteractionType": "AttuneAetherCurrent"
},
{ {
"DataId": 2011843, "DataId": 2011843,
"Position": { "Position": {

View File

@ -56,6 +56,7 @@
"Y": 81.17488, "Y": 81.17488,
"Z": -534.99316 "Z": -534.99316
}, },
"StopDistance": 0.25,
"TerritoryId": 956, "TerritoryId": 956,
"InteractionType": "WalkTo" "InteractionType": "WalkTo"
} }

View File

@ -20,6 +20,24 @@
{ {
"Sequence": 1, "Sequence": 1,
"Steps": [ "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, "DataId": 2011982,
"Position": { "Position": {
@ -30,6 +48,15 @@
"TerritoryId": 956, "TerritoryId": 956,
"InteractionType": "AttuneAetherCurrent" "InteractionType": "AttuneAetherCurrent"
}, },
{
"Position": {
"X": -327.6718,
"Y": 79.535736,
"Z": -400.00397
},
"TerritoryId": 956,
"InteractionType": "WalkTo"
},
{ {
"DataId": 2011983, "DataId": 2011983,
"Position": { "Position": {

View File

@ -27,28 +27,10 @@
"Y": 6.991216, "Y": 6.991216,
"Z": 629.2394 "Z": 629.2394
}, },
"StopDistance": 5,
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "AttuneAetheryte" "InteractionType": "AttuneAetheryte"
}, },
{
"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
},
"TerritoryId": 957,
"InteractionType": "Interact"
},
{ {
"DataId": 2011948, "DataId": 2011948,
"Position": { "Position": {
@ -59,6 +41,17 @@
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact" "InteractionType": "Interact"
}, },
{
"DataId": 1037622,
"Position": {
"X": 189.94562,
"Y": 0.8560083,
"Z": 702.7896
},
"StopDistance": 0.25,
"TerritoryId": 957,
"InteractionType": "Interact"
},
{ {
"DataId": 2011949, "DataId": 2011949,
"Position": { "Position": {

View File

@ -109,6 +109,7 @@
"Y": 1.769943, "Y": 1.769943,
"Z": 738.9843 "Z": 738.9843
}, },
"StopDistance": 0.25,
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact" "InteractionType": "Interact"
} }

View File

@ -59,6 +59,7 @@
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Combat", "InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
14006 14006
] ]

View File

@ -42,6 +42,7 @@
"Y": 4.785123, "Y": 4.785123,
"Z": 36.76496 "Z": 36.76496
}, },
"StopDistance": 5,
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "AttuneAetheryte" "InteractionType": "AttuneAetheryte"
}, },

View File

@ -29,6 +29,7 @@
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Combat", "InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
14004 14004
] ]

View File

@ -121,6 +121,7 @@
"Y": 88.84356, "Y": 88.84356,
"Z": -608.6588 "Z": -608.6588
}, },
"StopDistance": 0.25,
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "WalkTo" "InteractionType": "WalkTo"
} }

View File

@ -20,6 +20,24 @@
{ {
"Sequence": 1, "Sequence": 1,
"Steps": [ "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, "DataId": 2011991,
"Position": { "Position": {

View File

@ -69,6 +69,7 @@
}, },
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact", "InteractionType": "Interact",
"AetheryteShortcut": "Old Sharlayan",
"AethernetShortcut": [ "AethernetShortcut": [
"[Old Sharlayan] Aetheryte Plaza", "[Old Sharlayan] Aetheryte Plaza",
"[Old Sharlayan] The Baldesion Annex" "[Old Sharlayan] The Baldesion Annex"

View File

@ -28,6 +28,7 @@
"Z": -569.8787 "Z": -569.8787
}, },
"TerritoryId": 957, "TerritoryId": 957,
"AetheryteShortcut": "Thavnair - Great Work",
"InteractionType": "Interact" "InteractionType": "Interact"
} }
] ]

View File

@ -31,7 +31,8 @@
"Z": 539.6046 "Z": 539.6046
}, },
"TerritoryId": 621, "TerritoryId": 621,
"InteractionType": "Interact" "InteractionType": "ManualAction",
"Comment": "Duty - A Frosty Reception"
} }
] ]
}, },

View File

@ -46,8 +46,8 @@
"InteractionType": "ManualAction", "InteractionType": "ManualAction",
"Comment": "Cutscene Interaction needed", "Comment": "Cutscene Interaction needed",
"AethernetShortcut": [ "AethernetShortcut": [
"[The Crystarium] Aetheryte Plaza", "[Crystarium] Aetheryte Plaza",
"[The Crystarium] The Cabinet of Curiosity" "[Crystarium] The Cabinet of Curiosity"
] ]
} }
] ]

View File

@ -30,8 +30,8 @@
"TerritoryId": 819, "TerritoryId": 819,
"InteractionType": "Interact", "InteractionType": "Interact",
"AethernetShortcut": [ "AethernetShortcut": [
"[The Crystarium] The Cabinet of Curiosity", "[Crystarium] The Cabinet of Curiosity",
"[The Crystarium] The Dossal Gate" "[Crystarium] The Dossal Gate"
] ]
} }
] ]

View File

@ -45,8 +45,8 @@
"TerritoryId": 819, "TerritoryId": 819,
"InteractionType": "Interact", "InteractionType": "Interact",
"AethernetShortcut": [ "AethernetShortcut": [
"[The Crystarium] Aetheryte Plaza", "[Crystarium] Aetheryte Plaza",
"[The Crystarium] The Cabinet of Curiosity" "[Crystarium] The Cabinet of Curiosity"
] ]
} }
] ]

View File

@ -1,5 +1,5 @@
{ {
"Version": 1 "Version": 1,
"Author": "liza", "Author": "liza",
"QuestSequence": [ "QuestSequence": [
{ {
@ -30,8 +30,8 @@
"TerritoryId": 819, "TerritoryId": 819,
"InteractionType": "Interact", "InteractionType": "Interact",
"AethernetShortcut": [ "AethernetShortcut": [
"[The Crystarium] Aetheryte Plaza", "[Crystarium] Aetheryte Plaza",
"[The Crystarium] The Dossal Gate" "[Crystarium] The Dossal Gate"
] ]
} }
] ]

View File

@ -30,8 +30,8 @@
"TerritoryId": 819, "TerritoryId": 819,
"InteractionType": "Interact", "InteractionType": "Interact",
"AethernetShortcut": [ "AethernetShortcut": [
"[The Crystarium] Aetheryte Plaza", "[Crystarium] Aetheryte Plaza",
"[The Crystarium] The Dossal Gate" "[Crystarium] The Dossal Gate"
] ]
} }
] ]

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/quest/1.0", "$id": "/quest/1.0",
"title": "Questionable V1", "title": "Questionable V1",
"description": "A series of quest sequences", "description": "A series of quest sequences",
"type": "object", "type": "object",
@ -68,6 +68,10 @@
"Z" "Z"
] ]
}, },
"StopDistance": {
"type": "number",
"exclusiveMinimum": 0
},
"TerritoryId": { "TerritoryId": {
"type": "integer", "type": "integer",
"exclusiveMinimum": 0 "exclusiveMinimum": 0
@ -89,6 +93,72 @@
"Disabled": { "Disabled": {
"type": "boolean" "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": { "AethernetShortcut": {
"type": "array", "type": "array",
"minItems": 2, "minItems": 2,
@ -147,14 +217,14 @@
"[Kugane] Sekiseigumi Barracks", "[Kugane] Sekiseigumi Barracks",
"[Kugane] Rakuza District", "[Kugane] Rakuza District",
"[Kugane] Airship Landing", "[Kugane] Airship Landing",
"[The Crystarium] Aetheryte Plaza", "[Crystarium] Aetheryte Plaza",
"[The Crystarium] Musica Universalis Markets", "[Crystarium] Musica Universalis Markets",
"[The Crystarium] Themenos Rookery", "[Crystarium] Themenos Rookery",
"[The Crystarium] The Dossal Gate", "[Crystarium] The Dossal Gate",
"[The Crystarium] The Pendants", "[Crystarium] The Pendants",
"[The Crystarium] The Amaro Launch", "[Crystarium] The Amaro Launch",
"[The Crystarium] The Crystalline Mean", "[Crystarium] The Crystalline Mean",
"[The Crystarium] The Cabinet of Curiosity", "[Crystarium] The Cabinet of Curiosity",
"[Eulmore] Aetheryte Plaza", "[Eulmore] Aetheryte Plaza",
"[Eulmore] Southeast Derelicts", "[Eulmore] Southeast Derelicts",
"[Eulmore] Nightsoil Pots", "[Eulmore] Nightsoil Pots",
@ -179,6 +249,13 @@
] ]
} }
}, },
"EnemySpawnType": {
"type": "string",
"enum": [
"AutoOnEnterArea",
"AfterInteraction"
]
},
"KillEnemyDataIds": { "KillEnemyDataIds": {
"type": "array", "type": "array",
"items": { "items": {

View File

@ -1,4 +1,5 @@
using System.Numerics; using System;
using System.Numerics;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
@ -20,20 +21,32 @@ public sealed class Questionable : IDalamudPlugin
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly QuestController _questController;
private readonly MovementController _movementController; private readonly MovementController _movementController;
public Questionable(DalamudPluginInterface pluginInterface, IClientState clientState, ITargetManager targetManager, public Questionable(DalamudPluginInterface pluginInterface, IClientState clientState, ITargetManager targetManager,
IFramework framework, IGameGui gameGui, IDataManager dataManager, ISigScanner sigScanner, 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; _pluginInterface = pluginInterface;
_clientState = clientState; _clientState = clientState;
_framework = framework; _framework = framework;
_gameGui = gameGui; _gameGui = gameGui;
_gameFunctions = new GameFunctions(dataManager, sigScanner); _gameFunctions = new GameFunctions(dataManager, objectTable, sigScanner, targetManager, pluginLog);
NavmeshIpc navmeshIpc = new NavmeshIpc(pluginInterface);
_movementController = _movementController =
new MovementController(new NavmeshIpc(pluginInterface), clientState, _gameFunctions, pluginLog); new MovementController(navmeshIpc, clientState, _gameFunctions, pluginLog);
_windowSystem.AddWindow(new DebugWindow(_movementController, _gameFunctions, clientState, targetManager)); _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; _pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_framework.Update += FrameworkUpdate; _framework.Update += FrameworkUpdate;
@ -41,8 +54,9 @@ public sealed class Questionable : IDalamudPlugin
private void FrameworkUpdate(IFramework framework) private void FrameworkUpdate(IFramework framework)
{ {
HandleNavigationShortcut(); _questController.Update();
HandleNavigationShortcut();
_movementController.Update(); _movementController.Update();
} }

View File

@ -1,27 +1,33 @@
using System.Globalization; using System.Globalization;
using System.Numerics; using System.Numerics;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using ImGuiNET; using ImGuiNET;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Model.V1;
namespace Questionable.Windows; namespace Questionable.Windows;
internal sealed class DebugWindow : Window internal sealed class DebugWindow : Window
{ {
private readonly MovementController _movementController; private readonly MovementController _movementController;
private readonly QuestController _questController;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly ITargetManager _targetManager; private readonly ITargetManager _targetManager;
public DebugWindow(MovementController movementController, GameFunctions gameFunctions, IClientState clientState, public DebugWindow(MovementController movementController, QuestController questController,
GameFunctions gameFunctions, IClientState clientState,
ITargetManager targetManager) ITargetManager targetManager)
: base("Questionable", ImGuiWindowFlags.AlwaysAutoResize) : base("Questionable", ImGuiWindowFlags.AlwaysAutoResize)
{ {
_movementController = movementController; _movementController = movementController;
_questController = questController;
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
_clientState = clientState; _clientState = clientState;
_targetManager = targetManager; _targetManager = targetManager;
@ -39,6 +45,33 @@ internal sealed class DebugWindow : Window
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null) if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
return; 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( ImGui.Text(
$"Current TerritoryId: {_clientState.TerritoryType}, Flying: {(_gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType) ? "Yes" : "No")}"); $"Current TerritoryId: {_clientState.TerritoryType}, Flying: {(_gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType) ? "Yes" : "No")}");
@ -48,7 +81,8 @@ internal sealed class DebugWindow : Window
if (_targetManager.Target != null) if (_targetManager.Target != null)
{ {
ImGui.Separator(); 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); ImGui.BeginDisabled(!_movementController.IsNavmeshReady);
if (!_movementController.IsPathfinding) if (!_movementController.IsPathfinding)
@ -76,7 +110,8 @@ internal sealed class DebugWindow : Window
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button($"Copy")) ImGui.Button("Copy");
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{ {
ImGui.SetClipboardText($$""" ImGui.SetClipboardText($$"""
"DataId": {{_targetManager.Target.DataId}}, "DataId": {{_targetManager.Target.DataId}},
@ -89,6 +124,12 @@ internal sealed class DebugWindow : Window
"InteractionType": "Interact" "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 else
{ {