Updates for everything
This commit is contained in:
parent
ed109a0fca
commit
5040242dea
@ -11,6 +11,7 @@ namespace Questionable.Controller;
|
||||
|
||||
internal sealed class MovementController : IDisposable
|
||||
{
|
||||
public const float DefaultStopDistance = 3f;
|
||||
private readonly NavmeshIpc _navmeshIpc;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
@ -30,6 +31,8 @@ internal sealed class MovementController : IDisposable
|
||||
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()
|
||||
{
|
||||
@ -48,14 +51,24 @@ internal sealed class MovementController : IDisposable
|
||||
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 =
|
||||
|
@ -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.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
|
||||
@ -40,20 +79,30 @@ internal sealed class QuestController
|
||||
}
|
||||
#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())
|
||||
@ -65,7 +114,253 @@ internal sealed class QuestController
|
||||
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);
|
||||
}
|
||||
|
130
Questionable/Data/AetheryteData.cs
Normal file
130
Questionable/Data/AetheryteData.cs
Normal 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();
|
||||
}
|
||||
}
|
21
Questionable/Data/TerritoryData.cs
Normal file
21
Questionable/Data/TerritoryData.cs
Normal 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);
|
||||
}
|
1
Questionable/External/NavmeshIpc.cs
vendored
1
Questionable/External/NavmeshIpc.cs
vendored
@ -53,6 +53,7 @@ internal sealed class NavmeshIpc
|
||||
public Task<List<Vector3>> Pathfind(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_pathSetTolerance.InvokeAction(0.25f);
|
||||
return _navPathfind.InvokeFunc(localPlayerPosition, targetPosition, fly, cancellationToken);
|
||||
}
|
||||
|
||||
|
@ -3,17 +3,21 @@ using System.Collections.Generic;
|
||||
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;
|
||||
|
||||
@ -31,8 +35,15 @@ internal sealed unsafe class GameFunctions
|
||||
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 =
|
||||
@ -45,7 +56,7 @@ internal sealed unsafe class GameFunctions
|
||||
.AsReadOnly();
|
||||
}
|
||||
|
||||
public (uint CurrentQuest, byte Sequence) GetCurrentQuest()
|
||||
public (ushort CurrentQuest, byte Sequence) GetCurrentQuest()
|
||||
{
|
||||
var scenarioTree = AgentScenarioTree.Instance();
|
||||
if (scenarioTree == null)
|
||||
@ -69,9 +80,52 @@ internal sealed unsafe class GameFunctions
|
||||
|
||||
//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();
|
||||
@ -82,7 +136,7 @@ internal sealed unsafe class GameFunctions
|
||||
|
||||
public void ExecuteCommand(string command)
|
||||
{
|
||||
if (!command.StartsWith("/", StringComparison.Ordinal))
|
||||
if (!command.StartsWith('/'))
|
||||
return;
|
||||
|
||||
SendMessage(command);
|
||||
@ -190,21 +244,37 @@ internal sealed unsafe class GameFunctions
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Questionable.Model.V1;
|
||||
using System.Linq;
|
||||
using Questionable.Model.V1;
|
||||
|
||||
namespace Questionable;
|
||||
|
||||
@ -7,6 +8,9 @@ internal sealed class Quest
|
||||
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);
|
||||
}
|
||||
|
@ -61,14 +61,14 @@ public sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut
|
||||
{ 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" },
|
||||
@ -95,7 +95,7 @@ public sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut
|
||||
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)
|
||||
|
96
Questionable/Model/V1/Converter/AetheryteConverter.cs
Normal file
96
Questionable/Model/V1/Converter/AetheryteConverter.cs
Normal 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]);
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ public sealed class InteractionTypeConverter : JsonConverter<EInteractionType>
|
||||
{
|
||||
{ EInteractionType.Interact, "Interact" },
|
||||
{ EInteractionType.WalkTo, "WalkTo" },
|
||||
{ EInteractionType.AttuneAethenetShard, "AttuneAethenetShard" },
|
||||
{ EInteractionType.AttuneAethernetShard, "AttuneAethenetShard" },
|
||||
{ EInteractionType.AttuneAetheryte, "AttuneAetheryte" },
|
||||
{ EInteractionType.AttuneAetherCurrent, "AttuneAetherCurrent" },
|
||||
{ EInteractionType.Combat, "Combat" },
|
||||
|
66
Questionable/Model/V1/Converter/VectorConverter.cs
Normal file
66
Questionable/Model/V1/Converter/VectorConverter.cs
Normal 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();
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
|
||||
public enum EAetheryteLocation
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Gridania = 2,
|
||||
GridaniaArcher = 25,
|
||||
GridaniaLeatherworker = 26,
|
||||
@ -60,6 +62,24 @@ public enum EAetheryteLocation
|
||||
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,
|
||||
@ -75,6 +95,22 @@ public enum EAetheryteLocation
|
||||
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,
|
||||
@ -92,4 +128,21 @@ public enum EAetheryteLocation
|
||||
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
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ public enum EInteractionType
|
||||
{
|
||||
Interact,
|
||||
WalkTo,
|
||||
AttuneAethenetShard,
|
||||
AttuneAethernetShard,
|
||||
AttuneAetheryte,
|
||||
AttuneAetherCurrent,
|
||||
Combat,
|
||||
|
@ -7,7 +7,7 @@ public class QuestData
|
||||
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();
|
||||
}
|
||||
|
@ -5,6 +5,6 @@ namespace Questionable.Model.V1;
|
||||
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();
|
||||
}
|
||||
|
@ -10,11 +10,19 @@ public class QuestStep
|
||||
[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; }
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
},
|
||||
"TerritoryId": 129,
|
||||
"InteractionType": "Interact",
|
||||
"AetheryteShortcut": "Limsa Lominsa",
|
||||
"AethernetShortcut": [
|
||||
"[Limsa Lominsa] Aetheryte Plaza",
|
||||
"[Limsa Lominsa] Arcanist's Guild"
|
||||
|
@ -63,10 +63,11 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@ -78,10 +79,11 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@ -103,10 +105,11 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@ -115,6 +118,15 @@
|
||||
{
|
||||
"Sequence": 6,
|
||||
"Steps": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 93.30914,
|
||||
"Y": 8.920153,
|
||||
"Z": -89.12467
|
||||
},
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "WalkTo"
|
||||
},
|
||||
{
|
||||
"DataId": 1038578,
|
||||
"Position": {
|
||||
@ -122,6 +134,7 @@
|
||||
"Y": 41.37599,
|
||||
"Z": -142.5033
|
||||
},
|
||||
"StopDistance": 0.25,
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
@ -137,6 +150,7 @@
|
||||
"Y": 41.367188,
|
||||
"Z": -156.6034
|
||||
},
|
||||
"StopDistance": 0.25,
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "AttuneAethenetShard"
|
||||
},
|
||||
@ -182,6 +196,7 @@
|
||||
"Y": 1.0594833,
|
||||
"Z": 30.052902
|
||||
},
|
||||
"StopDistance": 0.25,
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "Interact",
|
||||
"AethernetShortcut": [
|
||||
|
@ -156,6 +156,21 @@
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 255,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 1038679,
|
||||
"Position": {
|
||||
"X": -275.5932,
|
||||
"Y": 19.003881,
|
||||
"Z": 13.321045
|
||||
},
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -67,9 +67,9 @@
|
||||
{
|
||||
"DataId": 1037077,
|
||||
"Position": {
|
||||
"X": -38.07129,
|
||||
"X": -38.066784,
|
||||
"Y": -14.169313,
|
||||
"Z": 105.30249
|
||||
"Z": 107.68768
|
||||
},
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "Interact"
|
||||
@ -141,9 +141,9 @@
|
||||
"Steps": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 15.242085,
|
||||
"X": 19.79008,
|
||||
"Y": -16.247002,
|
||||
"Z": 109.177666
|
||||
"Z": 108.36692
|
||||
},
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "WalkTo"
|
||||
|
@ -44,6 +44,7 @@
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "Combat",
|
||||
"EnemySpawnType": "AutoOnEnterArea",
|
||||
"KillEnemyDataIds": [
|
||||
14024
|
||||
]
|
||||
@ -77,6 +78,7 @@
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "Combat",
|
||||
"EnemySpawnType": "AutoOnEnterArea",
|
||||
"KillEnemyDataIds": [
|
||||
14023,
|
||||
14022
|
||||
|
@ -109,7 +109,7 @@
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "ManualAction",
|
||||
"Comment": "Duty - Shoot some bird"
|
||||
"Comment": "Duty - Shoot Large Green Bird"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -38,6 +38,7 @@
|
||||
"Z": -72.22095
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"EnemySpawnType": "AfterInteraction",
|
||||
"InteractionType": "Combat",
|
||||
"KillEnemyDataIds": [
|
||||
14020
|
||||
@ -57,6 +58,7 @@
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "Combat",
|
||||
"EnemySpawnType": "AfterInteraction",
|
||||
"KillEnemyDataIds": [
|
||||
14021
|
||||
]
|
||||
@ -97,21 +99,11 @@
|
||||
"Sequence": 5,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 2011984,
|
||||
"DataId": 1037985,
|
||||
"Position": {
|
||||
"X": 497.09314,
|
||||
"Y": 73.41101,
|
||||
"Z": -267.23126
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "AttuneAetherCurrent"
|
||||
},
|
||||
{
|
||||
"DataId": 1038708,
|
||||
"Position": {
|
||||
"X": 408.31604,
|
||||
"Y": 65.3329,
|
||||
"Z": -130.11371
|
||||
"X": 481.8036,
|
||||
"Y": 66.16195,
|
||||
"Z": -108.537415
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "Interact"
|
||||
@ -127,11 +119,11 @@
|
||||
"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"
|
||||
@ -141,6 +133,16 @@
|
||||
{
|
||||
"Sequence": 6,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 2011984,
|
||||
"Position": {
|
||||
"X": 497.09314,
|
||||
"Y": 73.41101,
|
||||
"Z": -267.23126
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "AttuneAetherCurrent"
|
||||
},
|
||||
{
|
||||
"DataId": 2011843,
|
||||
"Position": {
|
||||
|
@ -56,6 +56,7 @@
|
||||
"Y": 81.17488,
|
||||
"Z": -534.99316
|
||||
},
|
||||
"StopDistance": 0.25,
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "WalkTo"
|
||||
}
|
||||
|
@ -20,6 +20,24 @@
|
||||
{
|
||||
"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": {
|
||||
@ -30,6 +48,15 @@
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "AttuneAetherCurrent"
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": -327.6718,
|
||||
"Y": 79.535736,
|
||||
"Z": -400.00397
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "WalkTo"
|
||||
},
|
||||
{
|
||||
"DataId": 2011983,
|
||||
"Position": {
|
||||
|
@ -27,28 +27,10 @@
|
||||
"Y": 6.991216,
|
||||
"Z": 629.2394
|
||||
},
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 957,
|
||||
"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,
|
||||
"Position": {
|
||||
@ -59,6 +41,17 @@
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "Interact"
|
||||
},
|
||||
{
|
||||
"DataId": 1037622,
|
||||
"Position": {
|
||||
"X": 189.94562,
|
||||
"Y": 0.8560083,
|
||||
"Z": 702.7896
|
||||
},
|
||||
"StopDistance": 0.25,
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "Interact"
|
||||
},
|
||||
{
|
||||
"DataId": 2011949,
|
||||
"Position": {
|
||||
|
@ -109,6 +109,7 @@
|
||||
"Y": 1.769943,
|
||||
"Z": 738.9843
|
||||
},
|
||||
"StopDistance": 0.25,
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
|
@ -59,6 +59,7 @@
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "Combat",
|
||||
"EnemySpawnType": "AutoOnEnterArea",
|
||||
"KillEnemyDataIds": [
|
||||
14006
|
||||
]
|
||||
|
@ -42,6 +42,7 @@
|
||||
"Y": 4.785123,
|
||||
"Z": 36.76496
|
||||
},
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "AttuneAetheryte"
|
||||
},
|
||||
|
@ -29,6 +29,7 @@
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "Combat",
|
||||
"EnemySpawnType": "AfterInteraction",
|
||||
"KillEnemyDataIds": [
|
||||
14004
|
||||
]
|
||||
|
@ -121,6 +121,7 @@
|
||||
"Y": 88.84356,
|
||||
"Z": -608.6588
|
||||
},
|
||||
"StopDistance": 0.25,
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "WalkTo"
|
||||
}
|
||||
|
@ -20,6 +20,24 @@
|
||||
{
|
||||
"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": {
|
||||
|
@ -69,6 +69,7 @@
|
||||
},
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "Interact",
|
||||
"AetheryteShortcut": "Old Sharlayan",
|
||||
"AethernetShortcut": [
|
||||
"[Old Sharlayan] Aetheryte Plaza",
|
||||
"[Old Sharlayan] The Baldesion Annex"
|
||||
|
@ -28,6 +28,7 @@
|
||||
"Z": -569.8787
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"AetheryteShortcut": "Thavnair - Great Work",
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
]
|
||||
|
@ -31,7 +31,8 @@
|
||||
"Z": 539.6046
|
||||
},
|
||||
"TerritoryId": 621,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "ManualAction",
|
||||
"Comment": "Duty - A Frosty Reception"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -46,8 +46,8 @@
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -30,8 +30,8 @@
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -45,8 +45,8 @@
|
||||
"TerritoryId": 819,
|
||||
"InteractionType": "Interact",
|
||||
"AethernetShortcut": [
|
||||
"[The Crystarium] Aetheryte Plaza",
|
||||
"[The Crystarium] The Cabinet of Curiosity"
|
||||
"[Crystarium] Aetheryte Plaza",
|
||||
"[Crystarium] The Cabinet of Curiosity"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"Version": 1
|
||||
"Version": 1,
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
@ -30,8 +30,8 @@
|
||||
"TerritoryId": 819,
|
||||
"InteractionType": "Interact",
|
||||
"AethernetShortcut": [
|
||||
"[The Crystarium] Aetheryte Plaza",
|
||||
"[The Crystarium] The Dossal Gate"
|
||||
"[Crystarium] Aetheryte Plaza",
|
||||
"[Crystarium] The Dossal Gate"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -30,8 +30,8 @@
|
||||
"TerritoryId": 819,
|
||||
"InteractionType": "Interact",
|
||||
"AethernetShortcut": [
|
||||
"[The Crystarium] Aetheryte Plaza",
|
||||
"[The Crystarium] The Dossal Gate"
|
||||
"[Crystarium] Aetheryte Plaza",
|
||||
"[Crystarium] The Dossal Gate"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"$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",
|
||||
@ -68,6 +68,10 @@
|
||||
"Z"
|
||||
]
|
||||
},
|
||||
"StopDistance": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"TerritoryId": {
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0
|
||||
@ -89,6 +93,72 @@
|
||||
"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,
|
||||
@ -147,14 +217,14 @@
|
||||
"[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",
|
||||
@ -179,6 +249,13 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"EnemySpawnType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"AutoOnEnterArea",
|
||||
"AfterInteraction"
|
||||
]
|
||||
},
|
||||
"KillEnemyDataIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Interface.Windowing;
|
||||
@ -20,20 +21,32 @@ public sealed class Questionable : IDalamudPlugin
|
||||
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;
|
||||
@ -41,8 +54,9 @@ public sealed class Questionable : IDalamudPlugin
|
||||
|
||||
private void FrameworkUpdate(IFramework framework)
|
||||
{
|
||||
HandleNavigationShortcut();
|
||||
_questController.Update();
|
||||
|
||||
HandleNavigationShortcut();
|
||||
_movementController.Update();
|
||||
}
|
||||
|
||||
|
@ -1,27 +1,33 @@
|
||||
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;
|
||||
@ -39,6 +45,33 @@ internal sealed class DebugWindow : Window
|
||||
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")}");
|
||||
|
||||
@ -48,7 +81,8 @@ internal sealed class DebugWindow : Window
|
||||
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)
|
||||
@ -76,7 +110,8 @@ internal sealed class DebugWindow : Window
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button($"Copy"))
|
||||
ImGui.Button("Copy");
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
ImGui.SetClipboardText($$"""
|
||||
"DataId": {{_targetManager.Target.DataId}},
|
||||
@ -89,6 +124,12 @@ internal sealed class DebugWindow : Window
|
||||
"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
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user