Polish EW_A quests

This commit is contained in:
Liza 2024-05-27 21:54:34 +02:00
parent 5040242dea
commit f542135c89
Signed by: liza
GPG Key ID: 7199F8D727D55F67
41 changed files with 980 additions and 138 deletions

View File

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Questionable.External;
namespace Questionable.Controller;
@ -15,16 +18,18 @@ internal sealed class MovementController : IDisposable
private readonly NavmeshIpc _navmeshIpc;
private readonly IClientState _clientState;
private readonly GameFunctions _gameFunctions;
private readonly ICondition _condition;
private readonly IPluginLog _pluginLog;
private CancellationTokenSource? _cancellationTokenSource;
private Task<List<Vector3>>? _pathfindTask;
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, GameFunctions gameFunctions,
IPluginLog pluginLog)
ICondition condition, IPluginLog pluginLog)
{
_navmeshIpc = navmeshIpc;
_clientState = clientState;
_gameFunctions = gameFunctions;
_condition = condition;
_pluginLog = pluginLog;
}
@ -33,6 +38,7 @@ internal sealed class MovementController : IDisposable
public bool IsPathfinding => _pathfindTask is { IsCompleted: false };
public Vector3? Destination { get; private set; }
public float StopDistance { get; private set; }
public bool IsFlying { get; private set; }
public void Update()
{
@ -41,8 +47,35 @@ internal sealed class MovementController : IDisposable
if (_pathfindTask.IsCompletedSuccessfully)
{
_pluginLog.Information(
$"Pathfinding complete, route: [{string.Join(" ", _pathfindTask.Result.Select(x => x.ToString()))}]");
_navmeshIpc.MoveTo(_pathfindTask.Result.Skip(1).ToList());
string.Create(CultureInfo.InvariantCulture,
$"Pathfinding complete, route: [{string.Join(" ", _pathfindTask.Result.Select(x => x.ToString()))}]"));
var navPoints = _pathfindTask.Result.Skip(1).ToList();
if (!IsFlying && !_condition[ConditionFlag.Mounted] && navPoints.Count > 0 &&
!_gameFunctions.HasStatusPreventingSprintOrMount())
{
Vector3 start = _clientState.LocalPlayer?.Position ?? navPoints[0];
float actualDistance = 0;
foreach (Vector3 end in navPoints)
{
actualDistance += (start - end).Length();
start = end;
}
_pluginLog.Information($"Distance: {actualDistance}");
unsafe
{
// 70 is ~10 seconds of sprint
if (actualDistance > 100f &&
ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 4) == 0)
{
_pluginLog.Information("Triggering Sprint");
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 4);
}
}
}
_navmeshIpc.MoveTo(navPoints);
ResetPathfinding();
}
else if (_pathfindTask.IsCompleted)
@ -60,21 +93,32 @@ internal sealed class MovementController : IDisposable
}
}
public void NavigateTo(EMovementType type, Vector3 to, bool fly, float? stopDistance = null)
private void PrepareNavigation(EMovementType type, Vector3 to, bool fly, float? stopDistance)
{
ResetPathfinding();
_gameFunctions.ExecuteCommand("/automove off");
Destination = to;
StopDistance = stopDistance ?? (DefaultStopDistance - 0.2f);
IsFlying = fly;
}
public void NavigateTo(EMovementType type, Vector3 to, bool fly, float? stopDistance = null)
{
PrepareNavigation(type, to, fly, stopDistance);
_cancellationTokenSource = new();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
_pathfindTask =
_navmeshIpc.Pathfind(_clientState.LocalPlayer!.Position, to, fly, _cancellationTokenSource.Token);
}
public void NavigateTo(EMovementType type, List<Vector3> to, bool fly, float? stopDistance)
{
PrepareNavigation(type, to.Last(), fly, stopDistance);
_navmeshIpc.MoveTo(to);
}
public void ResetPathfinding()
{
if (_cancellationTokenSource != null)

View File

@ -10,47 +10,69 @@ using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Questionable.Data;
using Questionable.External;
using Questionable.Model.V1;
namespace Questionable.Controller;
internal sealed class QuestController
{
private readonly DalamudPluginInterface _pluginInterface;
private readonly IDataManager _dataManager;
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 LifestreamIpc _lifestreamIpc;
private readonly TerritoryData _territoryData;
private readonly Dictionary<ushort, Quest> _quests = new();
public QuestController(DalamudPluginInterface pluginInterface, IDataManager dataManager, IClientState clientState,
GameFunctions gameFunctions, MovementController movementController, IPluginLog pluginLog, ICondition condition,
IChatGui chatGui, ICommandManager commandManager)
IChatGui chatGui, AetheryteData aetheryteData, LifestreamIpc lifestreamIpc)
{
_pluginInterface = pluginInterface;
_dataManager = dataManager;
_clientState = clientState;
_gameFunctions = gameFunctions;
_movementController = movementController;
_pluginLog = pluginLog;
_condition = condition;
_chatGui = chatGui;
_commandManager = commandManager;
_aetheryteData = new AetheryteData(dataManager);
_aetheryteData = aetheryteData;
_lifestreamIpc = lifestreamIpc;
_territoryData = new TerritoryData(dataManager);
Reload();
}
public QuestProgress? CurrentQuest { get; set; }
public string? DebugState { get; private set; }
public string? Comment { get; private set; }
public void Reload()
{
_quests.Clear();
CurrentQuest = null;
DebugState = null;
#if false
LoadFromEmbeddedResources();
#endif
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);
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Quest>()!.GetRow((uint)questId + 0x10000);
if (questData == null)
continue;
@ -79,9 +101,6 @@ 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"))
@ -120,6 +139,9 @@ internal sealed class QuestController
public void Update()
{
Comment = null;
DebugState = null;
(ushort currentQuestId, byte currentSequence) = _gameFunctions.GetCurrentQuest();
if (currentQuestId == 0)
{
@ -185,7 +207,8 @@ internal sealed class QuestController
}
var step = sequence.Steps[CurrentQuest.Step];
DebugState = step.Comment ?? sequence.Comment ?? q.Data.Comment;
DebugState = null;
Comment = step.Comment ?? sequence.Comment ?? q.Data.Comment;
}
public (QuestSequence? Sequence, QuestStep? Step) GetNextStep()
@ -216,8 +239,7 @@ internal sealed class QuestController
CurrentQuest = CurrentQuest with
{
Step = CurrentQuest.Step + 1,
AetheryteShortcutUsed = false,
AethernetShortcutUsed = false
StepProgress = new()
};
}
else
@ -225,30 +247,29 @@ internal sealed class QuestController
CurrentQuest = CurrentQuest with
{
Step = 255,
AetheryteShortcutUsed = false,
AethernetShortcutUsed = false
StepProgress = new()
};
}
}
public void ExecuteNextStep()
public unsafe 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)
if (!CurrentQuest.StepProgress.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 ||
Vector3 pos = _clientState.LocalPlayer!.Position;
if (_aetheryteData.CalculateDistance(pos, territoryType, step.AetheryteShortcut.Value) < 11 ||
(step.AethernetShortcut != null &&
(_aetheryteData.CalculateDistance(playerPosition, territoryType, step.AethernetShortcut.From) < 11 ||
_aetheryteData.CalculateDistance(playerPosition, territoryType, step.AethernetShortcut.To) < 11)))
(_aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.From) < 20 ||
_aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.To) < 20)))
{
skipTeleport = true;
}
@ -256,7 +277,10 @@ internal sealed class QuestController
if (skipTeleport)
{
CurrentQuest = CurrentQuest with { AetheryteShortcutUsed = true };
CurrentQuest = CurrentQuest with
{
StepProgress = CurrentQuest.StepProgress with { AetheryteShortcutUsed = true }
};
}
else
{
@ -265,7 +289,10 @@ internal sealed class QuestController
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 };
CurrentQuest = CurrentQuest with
{
StepProgress = CurrentQuest.StepProgress with { AetheryteShortcutUsed = true }
};
else
_chatGui.Print("[Questionable] Unable to teleport to aetheryte.");
}
@ -276,7 +303,7 @@ internal sealed class QuestController
}
}
if (!CurrentQuest.AethernetShortcutUsed)
if (!CurrentQuest.StepProgress.AethernetShortcutUsed)
{
if (step.AethernetShortcut != null)
{
@ -291,10 +318,11 @@ internal sealed class QuestController
{
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 };
_lifestreamIpc.Teleport(to);
CurrentQuest = CurrentQuest with
{
StepProgress = CurrentQuest.StepProgress with { AethernetShortcutUsed = true }
};
}
else
_movementController.NavigateTo(EMovementType.Quest, _aetheryteData.Locations[from], false,
@ -315,30 +343,57 @@ internal sealed class QuestController
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);
}
if (step.Mount == true && !_gameFunctions.HasStatusPreventingSprintOrMount())
{
if (!_condition[ConditionFlag.Mounted] && _territoryData.CanUseMount(_clientState.TerritoryType))
{
if (ActionManager.Instance()->GetActionStatus(ActionType.Mount, 71) == 0)
ActionManager.Instance()->UseAction(ActionType.Mount, 71);
return;
}
else if (actualDistance > distance)
}
else if (step.Mount == false)
{
if (_condition[ConditionFlag.Mounted])
{
_gameFunctions.Unmount();
return;
}
}
if (!step.DisableNavmesh)
{
if (step.Mount != false && actualDistance > 30f && !_condition[ConditionFlag.Mounted] &&
_territoryData.CanUseMount(_clientState.TerritoryType))
{
if (ActionManager.Instance()->GetActionStatus(ActionType.Mount, 71) == 0)
ActionManager.Instance()->UseAction(ActionType.Mount, 71);
return;
}
if (actualDistance > distance)
{
_movementController.NavigateTo(EMovementType.Quest, step.Position.Value,
_gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType), distance);
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);
@ -347,10 +402,81 @@ internal sealed class QuestController
break;
case EInteractionType.AttuneAetheryte:
if (step.DataId != null)
{
if (!_gameFunctions.IsAetheryteUnlocked((EAetheryteLocation)step.DataId.Value))
_gameFunctions.InteractWith(step.DataId.Value);
IncreaseStepCount();
}
break;
case EInteractionType.AttuneAetherCurrent:
if (step.DataId != null)
{
_pluginLog.Information(
$"{step.AetherCurrentId} → {_gameFunctions.IsAetherCurrentUnlocked(step.AetherCurrentId.GetValueOrDefault())}");
if (step.AetherCurrentId == null ||
!_gameFunctions.IsAetherCurrentUnlocked(step.AetherCurrentId.Value))
_gameFunctions.InteractWith(step.DataId.Value);
IncreaseStepCount();
}
break;
case EInteractionType.WalkTo:
IncreaseStepCount();
break;
case EInteractionType.UseItem:
if (step is { DataId: not null, ItemId: not null })
{
if (_gameFunctions.Unmount())
return;
_gameFunctions.UseItem(step.DataId.Value, step.ItemId.Value);
IncreaseStepCount();
}
break;
case EInteractionType.Combat:
if (step.EnemySpawnType != null)
{
if (_gameFunctions.Unmount())
return;
if (step.DataId != null && step.EnemySpawnType == EEnemySpawnType.AfterInteraction)
_gameFunctions.InteractWith(step.DataId.Value);
// next sequence should trigger automatically
IncreaseStepCount();
}
break;
case EInteractionType.Emote:
if (step is { DataId: not null, Emote: not null })
{
_gameFunctions.UseEmote(step.DataId.Value, step.Emote.Value);
IncreaseStepCount();
}
break;
case EInteractionType.WaitForObjectAtPosition:
if (step is { DataId: not null, Position: not null } &&
!_gameFunctions.IsObbjectAtPosition(step.DataId.Value, step.Position.Value))
{
return;
}
IncreaseStepCount();
break;
default:
_pluginLog.Warning($"Action '{step.InteractionType}' is not implemented");
break;
@ -361,6 +487,15 @@ internal sealed class QuestController
Quest Quest,
byte Sequence,
int Step,
StepProgress StepProgress)
{
public QuestProgress(Quest quest, byte sequence, int step)
: this(quest, sequence, step, new StepProgress())
{
}
}
public sealed record StepProgress(
bool AetheryteShortcutUsed = false,
bool AethernetShortcutUsed = false);
}

View File

@ -40,7 +40,51 @@ internal sealed class AetheryteData
{ EAetheryteLocation.LimsaMarauder, new(-5.1728516f, 44.63257f, -218.06671f) },
{ EAetheryteLocation.LimsaHawkersAlley, new(-213.61108f, 16.739136f, 51.80432f) },
// ... missing a few
{ EAetheryteLocation.Ishgard, new(-63.98114f, 11.154297f, 43.9917f) },
{ EAetheryteLocation.IshgardForgottenKnight, new(45.792236f, 24.551636f, 0.99176025f) },
{ EAetheryteLocation.IshgardSkysteelManufactory, new(-111.436646f, 16.128723f, -27.054321f) },
{ EAetheryteLocation.IshgardBrume, new(49.42395f, -11.154419f, 66.69714f) },
{ EAetheryteLocation.IshgardAthenaeumAstrologicum, new(133.37903f, -8.86554f, -64.77466f) },
{ EAetheryteLocation.IshgardJeweledCrozier, new(-134.6914f, -11.795227f, -15.396423f) },
{ EAetheryteLocation.IshgardSaintReymanaudsCathedral, new(-77.958374f, 10.60498f, -126.54315f) },
{ EAetheryteLocation.IshgardTribunal, new(78.01941f, 11.001709f, -126.51257f) },
{ EAetheryteLocation.IshgardLastVigil, new(0.015197754f, 16.525452f, -32.51703f) },
{ EAetheryteLocation.Idyllshire, new(71.94617f, 211.26111f, -18.905945f) },
{ EAetheryteLocation.IdyllshireWest, new(-75.48645f, 210.22351f, -21.347473f) },
{ EAetheryteLocation.RhalgrsReach, new(78.23291f, 1.9683228f, 97.45935f) },
{ EAetheryteLocation.RhalgrsReachWest, new(-84.275635f, 0.503479f, 9.323181f) },
{ EAetheryteLocation.RhalgrsReachNorthEast, new(101.24353f, 3.463745f, -115.46509f) },
{ EAetheryteLocation.Kugane, new(47.501343f, 8.438171f, -37.30841f) },
{ EAetheryteLocation.KuganeShiokazeHostelry, new(-73.16705f, -6.088379f, -77.77527f) },
{ EAetheryteLocation.KuganePier1, new(-113.57294f, -3.8911133f, 155.41309f) },
{ EAetheryteLocation.KuganeThavnairianConsulate, new(27.17627f, 9.048584f, 141.58838f) },
{ EAetheryteLocation.KuganeMarkets, new(26.687988f, 4.92865f, 73.3501f) },
{ EAetheryteLocation.KuganeBokairoInn, new(-76.00525f, 19.058472f, -161.18109f) },
{ EAetheryteLocation.KuganeRubyBazaar, new(132.40247f, 12.954895f, 83.02429f) },
{ EAetheryteLocation.KuganeSekiseigumiBarracks, new(119.09656f, 13.01593f, -92.881714f) },
{ EAetheryteLocation.KuganeRakuzaDistrict, new(24.64331f, 7.003784f, -152.97174f) },
{ EAetheryteLocation.FringesCastrumOriens, new(-629.11426f, 132.89075f, -509.14783f) },
{ EAetheryteLocation.FringesPeeringStones, new(415.3047f, 117.357056f, 246.75354f) },
{ EAetheryteLocation.PeaksAlaGannha, new(114.579956f, 120.10376f, -747.06647f) },
{ EAetheryteLocation.PeaksAlaGhiri, new(-271.3817f, 259.87634f, 748.86694f) },
{ EAetheryteLocation.LochsPortaPraetoria, new(-652.0333f, 53.391357f, -16.006714f) },
{ EAetheryteLocation.LochsAlaMhiganQuarter, new(612.4512f, 84.45862f, 656.82446f) },
{ EAetheryteLocation.RubySeaTamamizu, new(358.72437f, -118.05908f, -263.4165f) },
{ EAetheryteLocation.RubySeaOnokoro, new(88.181885f, 4.135132f, -583.3677f) },
{ EAetheryteLocation.YanxiaNamai, new(432.66956f, 73.07532f, -90.74542f) },
{ EAetheryteLocation.YanxiaHouseOfTheFierce, new(246.02112f, 9.079041f, -401.3581f) },
{ EAetheryteLocation.AzimSteppeReunion, new(556.1454f, -16.800232f, 340.10828f) },
{ EAetheryteLocation.AzimSteppeDawnThrone, new(78.26355f, 119.37134f, 36.301147f) },
{ EAetheryteLocation.AzimSteppeDhoroIloh, new(-754.63495f, 131.2428f, 116.5636f) },
{ EAetheryteLocation.DomanEnclave, new(42.648926f, 1.4190674f, -14.8776245f) },
{ EAetheryteLocation.DomanEnclaveNorthern, new(8.987488f, 0.8086548f, -105.85187f) },
{ EAetheryteLocation.DomanEnclaveSouthern, new(-61.57019f, 0.77819824f, 90.684326f) },
{ EAetheryteLocation.DomanEnclaveDocks, new(96.269165f, -3.4332886f, 81.01013f) },
{ EAetheryteLocation.Crystarium, new(-65.0188f, 4.5318604f, 0.015197754f) },
{ EAetheryteLocation.CrystariumMarkets, new(-6.149414f, -7.736328f, 148.72961f) },
@ -57,7 +101,21 @@ internal sealed class AetheryteData
{ EAetheryteLocation.EulmoreGloryGate, new(6.9122925f, 6.240906f, -56.351562f) },
{ EAetheryteLocation.EulmoreSoutheastDerelict, new(71.82422f, -10.391418f, 65.32385f) },
// ... missing a few
{ EAetheryteLocation.LakelandFortJobb, new(753.7803f, 24.338135f, -28.82434f) },
{ EAetheryteLocation.LakelandOstallImperative, new(-735.01184f, 53.391357f, -230.02979f) },
{ EAetheryteLocation.KholusiaStilltide, new(668.32983f, 29.465088f, 289.17358f) },
{ EAetheryteLocation.KholusiaWright, new(-244.00702f, 20.736938f, 385.45813f) },
{ EAetheryteLocation.KholusiaTomra, new(-426.38287f, 419.27222f, -623.5294f) },
{ EAetheryteLocation.AmhAraengMordSouq, new(246.38745f, 12.985352f, -220.29456f) },
{ EAetheryteLocation.AmhAraengInnAtJourneysHead, new(399.0996f, -24.521301f, 307.97278f) },
{ EAetheryteLocation.AmhAraengTwine, new(-511.3451f, 47.989624f, -212.604f) },
{ EAetheryteLocation.RaktikaSlitherbough, new(-103.4104f, -19.333252f, 297.23047f) },
{ EAetheryteLocation.RaktikaFanow, new(382.77246f, 21.042175f, -194.11005f) },
{ EAetheryteLocation.IlMhegLydhaLran, new(-344.71655f, 48.722046f, 512.2606f) },
{ EAetheryteLocation.IlMhegPiaEnni, new(-72.55664f, 103.95972f, -857.35864f) },
{ EAetheryteLocation.IlMhegWolekdorf, new(380.51416f, 87.20532f, -687.2511f) },
{ EAetheryteLocation.TempestOndoCups, new(561.76074f, 352.62073f, -199.17603f) },
{ EAetheryteLocation.TempestMacarensesAngle, new(-141.74109f, -280.5371f, 218.00562f) },
{ EAetheryteLocation.OldSharlayan, new(0.07623291f, 4.8065186f, -0.10687256f) },
{ EAetheryteLocation.OldSharlayanStudium, new(-291.1574f, 20.004517f, -74.143616f) },

26
Questionable/External/LifestreamIpc.cs vendored Normal file
View File

@ -0,0 +1,26 @@
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Questionable.Data;
using Questionable.Model.V1;
namespace Questionable.External;
internal sealed class LifestreamIpc
{
private readonly AetheryteData _aetheryteData;
private readonly ICallGateSubscriber<string, bool> _aethernetTeleport;
public LifestreamIpc(DalamudPluginInterface pluginInterface, AetheryteData aetheryteData)
{
_aetheryteData = aetheryteData;
_aethernetTeleport = pluginInterface.GetIpcSubscriber<string, bool>("Lifestream.AethernetTeleport");
}
public bool Teleport(EAetheryteLocation aetheryteLocation)
{
if (!_aetheryteData.AethernetNames.TryGetValue(aetheryteLocation, out string? name))
return false;
return _aethernetTeleport.InvokeFunc(name);
}
}

View File

@ -7,10 +7,13 @@ using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Game;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
@ -18,6 +21,8 @@ using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Lumina.Excel.GeneratedSheets;
using Questionable.Model.V1;
using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
using GameObject = Dalamud.Game.ClientState.Objects.Types.GameObject;
namespace Questionable;
@ -34,15 +39,19 @@ internal sealed unsafe class GameFunctions
private readonly ProcessChatBoxDelegate _processChatBox;
private readonly delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString;
private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet;
private readonly ReadOnlyDictionary<EEmote, string> _emoteCommands;
private readonly IObjectTable _objectTable;
private readonly ITargetManager _targetManager;
private readonly ICondition _condition;
private readonly IPluginLog _pluginLog;
public GameFunctions(IDataManager dataManager, IObjectTable objectTable, ISigScanner sigScanner, ITargetManager targetManager, IPluginLog pluginLog)
public GameFunctions(IDataManager dataManager, IObjectTable objectTable, ISigScanner sigScanner,
ITargetManager targetManager, ICondition condition, IPluginLog pluginLog)
{
_objectTable = objectTable;
_targetManager = targetManager;
_condition = condition;
_pluginLog = pluginLog;
_processChatBox =
Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText(Signatures.SendChat));
@ -54,6 +63,13 @@ internal sealed unsafe class GameFunctions
.Where(x => x.Unknown32 > 0)
.ToDictionary(x => (ushort)x.RowId, x => x.Unknown32)
.AsReadOnly();
_emoteCommands = dataManager.GetExcelSheet<Emote>()!
.Where(x => x.RowId > 0)
.Where(x => x.TextCommand != null && x.TextCommand.Value != null)
.Select(x => (x.RowId, Command: x.TextCommand.Value!.Command?.ToString()))
.Where(x => x.Command != null && x.Command.StartsWith('/'))
.ToDictionary(x => (EEmote)x.RowId, x => x.Command!)
.AsReadOnly();
}
public (ushort CurrentQuest, byte Sequence) GetCurrentQuest()
@ -134,6 +150,13 @@ internal sealed unsafe class GameFunctions
playerState->IsAetherCurrentZoneComplete(aetherCurrentCompFlgSet);
}
public bool IsAetherCurrentUnlocked(uint aetherCurrentId)
{
var playerState = PlayerState.Instance();
return playerState != null &&
playerState->IsAetherCurrentUnlocked(aetherCurrentId);
}
public void ExecuteCommand(string command)
{
if (!command.StartsWith('/'))
@ -262,19 +285,81 @@ internal sealed unsafe class GameFunctions
#endregion
public void InteractWith(uint dataId)
private GameObject? FindObjectByDataId(uint dataId)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.DataId == dataId)
{
return gameObject;
}
}
return null;
}
public void InteractWith(uint dataId)
{
GameObject? gameObject = FindObjectByDataId(dataId);
if (gameObject != null)
{
_targetManager.Target = null;
_targetManager.Target = gameObject;
TargetSystem.Instance()->InteractWithObject(
(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address, false);
return;
}
}
public void UseItem(uint dataId, uint itemId)
{
GameObject? gameObject = FindObjectByDataId(dataId);
if (gameObject != null)
{
_targetManager.Target = gameObject;
AgentInventoryContext.Instance()->UseItem(itemId);
}
}
public void UseEmote(uint dataId, EEmote emote)
{
GameObject? gameObject = FindObjectByDataId(dataId);
if (gameObject != null)
{
_targetManager.Target = gameObject;
ExecuteCommand($"{_emoteCommands[emote]} motion");
}
}
public bool IsObbjectAtPosition(uint dataId, Vector3 position)
{
GameObject? gameObject = FindObjectByDataId(dataId);
return gameObject != null && (gameObject.Position - position).Length() < 0.05f;
}
public bool HasStatusPreventingSprintOrMount()
{
var gameObject = GameObjectManager.GetGameObjectByIndex(0);
if (gameObject != null && gameObject->ObjectKind == 1)
{
var battleChara = (BattleChara*)gameObject;
StatusManager* statusManager = battleChara->GetStatusManager;
return statusManager->HasStatus(565);
}
return false;
}
public bool Unmount()
{
if (_condition[ConditionFlag.Mounted])
{
if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0)
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
return true;
}
return false;
}
}

View File

@ -20,7 +20,7 @@ public sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut
{ EAetheryteLocation.LimsaAirship, "[Limsa Lominsa] Airship Landing" },
{ EAetheryteLocation.Gridania, "[Gridania] Aetheryte Plaza" },
{ EAetheryteLocation.GridaniaArcher, "[Gridania] Archer's Guild" },
{ EAetheryteLocation.GridaniaLeatherworker, "[Gridania] Leatherworker's Guld & Shaded Bower" },
{ EAetheryteLocation.GridaniaLeatherworker, "[Gridania] Leatherworker's Guild & Shaded Bower" },
{ EAetheryteLocation.GridaniaLancer, "[Gridania] Lancer's Guild" },
{ EAetheryteLocation.GridaniaConjurer, "[Gridania] Conjurer's Guild" },
{ EAetheryteLocation.GridaniaBotanist, "[Gridania] Botanist's Guild" },

View File

@ -6,7 +6,7 @@ using System.Text.Json.Serialization;
namespace Questionable.Model.V1.Converter;
public class AetheryteConverter : JsonConverter<EAetheryteLocation>
public sealed class AetheryteConverter : JsonConverter<EAetheryteLocation>
{
private static readonly Dictionary<EAetheryteLocation, string> EnumToString = new()
{
@ -14,6 +14,7 @@ public class AetheryteConverter : JsonConverter<EAetheryteLocation>
{ EAetheryteLocation.Gridania, "Gridania" },
{ EAetheryteLocation.Uldah, "Ul'dah" },
{ EAetheryteLocation.Ishgard, "Ishgard" },
{ EAetheryteLocation.Idyllshire, "Idyllshire" },
{ EAetheryteLocation.RhalgrsReach, "Rhalgr's Reach" },
{ EAetheryteLocation.FringesCastrumOriens, "Fringes - Castrum Oriens" },
@ -31,8 +32,6 @@ public class AetheryteConverter : JsonConverter<EAetheryteLocation>
{ 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" },

View File

@ -0,0 +1,40 @@
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 EmoteConverter : JsonConverter<EEmote>
{
private static readonly Dictionary<EEmote, string> EnumToString = new()
{
{ EEmote.Stretch, "stretch" },
{ EEmote.Wave, "wave" },
{ EEmote.Rally, "rally" },
{ EEmote.Deny, "deny" },
};
private static readonly Dictionary<string, EEmote> StringToEnum =
EnumToString.ToDictionary(x => x.Value, x => x.Key);
public override EEmote 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 EEmote value) ? value : throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, EEmote value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStringValue(EnumToString[value]);
}
}

View File

@ -0,0 +1,38 @@
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 EnemySpawnTypeConverter : JsonConverter<EEnemySpawnType>
{
private static readonly Dictionary<EEnemySpawnType, string> EnumToString = new()
{
{ EEnemySpawnType.AfterInteraction, "AfterInteraction" },
{ EEnemySpawnType.AutoOnEnterArea, "AutoOnEnterArea" },
};
private static readonly Dictionary<string, EEnemySpawnType> StringToEnum =
EnumToString.ToDictionary(x => x.Value, x => x.Key);
public override EEnemySpawnType 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 EEnemySpawnType value) ? value : throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, EEnemySpawnType value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStringValue(EnumToString[value]);
}
}

View File

@ -12,12 +12,13 @@ public sealed class InteractionTypeConverter : JsonConverter<EInteractionType>
{
{ EInteractionType.Interact, "Interact" },
{ EInteractionType.WalkTo, "WalkTo" },
{ EInteractionType.AttuneAethernetShard, "AttuneAethenetShard" },
{ EInteractionType.AttuneAethernetShard, "AttuneAethernetShard" },
{ EInteractionType.AttuneAetheryte, "AttuneAetheryte" },
{ EInteractionType.AttuneAetherCurrent, "AttuneAetherCurrent" },
{ EInteractionType.Combat, "Combat" },
{ EInteractionType.UseItem, "UseItem" },
{ EInteractionType.Emote, "Emote" },
{ EInteractionType.WaitForObjectAtPosition, "WaitForNpcAtPosition" },
{ EInteractionType.ManualAction, "ManualAction" }
};

View File

@ -45,7 +45,7 @@ public enum EAetheryteLocation
IshgardLastVigil = 87,
Idyllshire = 75,
IdyllshireWest = 76,
IdyllshireWest = 90,
RhalgrsReach = 104,
RhalgrsReachWest = 121,
@ -77,8 +77,9 @@ public enum EAetheryteLocation
AzimSteppeDhoroIloh = 128,
DomanEnclave = 127,
DomamEnclaveNorthern = 129,
DomamEnclaveSouthern = 130,
DomanEnclaveNorthern = 129,
DomanEnclaveSouthern = 130,
DomanEnclaveDocks = 162,
Crystarium = 133,
CrystariumMarkets = 149,
@ -101,15 +102,15 @@ public enum EAetheryteLocation
KholusiaWright = 138,
KholusiaTomra = 139,
AmhAraengMordSouq = 140,
AmhAraengInnAtJourneysHead = 141,
AmhAraengTwine = 142,
RaktikaSlitherbough = 143,
RaktikaFanow = 144,
IlMhegLydhaLran = 145,
IlMhegPiaEnni = 146,
IlMhegWolekdorf = 147,
TempestOndoCups = 148,
TempestMacarensesAngle = 156,
AmhAraengInnAtJourneysHead = 161,
AmhAraengTwine = 141,
RaktikaSlitherbough = 142,
RaktikaFanow = 143,
IlMhegLydhaLran = 144,
IlMhegPiaEnni = 145,
IlMhegWolekdorf = 146,
TempestOndoCups = 147,
TempestMacarensesAngle = 148,
OldSharlayan = 182,
OldSharlayanStudium = 184,

View File

@ -0,0 +1,11 @@
namespace Questionable.Model.V1;
public enum EEmote
{
None = 0,
Stretch = 37,
Wave = 16,
Rally = 34,
Deny = 25,
}

View File

@ -0,0 +1,8 @@
namespace Questionable.Model.V1;
public enum EEnemySpawnType
{
None = 0,
AfterInteraction,
AutoOnEnterArea,
}

View File

@ -10,5 +10,6 @@ public enum EInteractionType
Combat,
UseItem,
Emote,
WaitForObjectAtPosition,
ManualAction
}

View File

@ -18,6 +18,8 @@ public class QuestStep
public float? StopDistance { get; set; }
public ushort TerritoryId { get; set; }
public bool Disabled { get; set; }
public bool DisableNavmesh { get; set; }
public bool? Mount { get; set; }
public string? Comment { get; set; }
[JsonConverter(typeof(AetheryteConverter))]
@ -25,4 +27,15 @@ public class QuestStep
[JsonConverter(typeof(AethernetShortcutConverter))]
public AethernetShortcut? AethernetShortcut { get; set; }
public uint? AetherCurrentId { get; set; }
public uint? ItemId { get; set; }
[JsonConverter(typeof(EmoteConverter))]
public EEmote? Emote { get; set; }
[JsonConverter(typeof(EnemySpawnTypeConverter))]
public EEnemySpawnType? EnemySpawnType { get; set; }
public IList<uint>? KillEnemyDataIds { get; set; }
}

View File

@ -58,7 +58,7 @@
"Z": 127.753
},
"TerritoryId": 962,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1038578,
@ -76,6 +76,15 @@
{
"Sequence": 4,
"Steps": [
{
"Position": {
"X": -8.38828,
"Y": 3.2249968,
"Z": 9.224017
},
"TerritoryId": 962,
"InteractionType": "WalkTo"
},
{
"DataId": 1038578,
"Position": {
@ -127,6 +136,15 @@
"TerritoryId": 962,
"InteractionType": "WalkTo"
},
{
"Position": {
"X": 96.67595,
"Y": 15.025446,
"Z": -134.08261
},
"TerritoryId": 962,
"InteractionType": "WalkTo"
},
{
"DataId": 1038578,
"Position": {
@ -150,9 +168,8 @@
"Y": 41.367188,
"Z": -156.6034
},
"StopDistance": 0.25,
"TerritoryId": 962,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1038578,
@ -161,6 +178,7 @@
"Y": 18.800978,
"Z": -142.65858
},
"StopDistance": 0.25,
"TerritoryId": 962,
"InteractionType": "Interact"
}
@ -177,7 +195,7 @@
"Z": -118.73047
},
"TerritoryId": 962,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 188,
@ -187,7 +205,7 @@
"Z": 13.77887
},
"TerritoryId": 962,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1038578,

View File

@ -38,7 +38,7 @@
"Z": 29.709229
},
"TerritoryId": 962,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 184,
@ -48,7 +48,7 @@
"Z": -74.143616
},
"TerritoryId": 962,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1038675,
@ -167,6 +167,7 @@
"Y": 19.003881,
"Z": 13.321045
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
}

View File

@ -12,6 +12,7 @@
"Y": 19.003874,
"Z": 18.966919
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
}

View File

@ -42,6 +42,7 @@
"Y": 186.03699,
"Z": -740.9324
},
"StopDistance": 1,
"TerritoryId": 956,
"InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
@ -77,6 +78,7 @@
"Z": -595.69696
},
"TerritoryId": 956,
"StopDistance": 1,
"InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
@ -97,13 +99,23 @@
"Z": -595.69696
},
"TerritoryId": 956,
"InteractionType": "UseItem"
"InteractionType": "UseItem",
"ItemId": 2003129
}
]
},
{
"Sequence": 6,
"Steps": [
{
"Position": {
"X": 222.61905,
"Y": 182.78828,
"Z": -704.0299
},
"TerritoryId": 956,
"InteractionType": "WalkTo"
},
{
"DataId": 2011980,
"Position": {
@ -112,7 +124,8 @@
"Z": -767.7577
},
"TerritoryId": 956,
"InteractionType": "AttuneAetherCurrent"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818314
},
{
"DataId": 1038701,
@ -122,7 +135,8 @@
"Z": -823.3921
},
"TerritoryId": 956,
"InteractionType": "UseItem"
"InteractionType": "UseItem",
"ItemId": 2003129
}
]
},
@ -144,6 +158,15 @@
{
"Sequence": 255,
"Steps": [
{
"Position": {
"X": 254.80028,
"Y": 163.44171,
"Z": -626.4951
},
"TerritoryId": 956,
"InteractionType": "WalkTo"
},
{
"DataId": 1038702,
"Position": {

View File

@ -28,7 +28,17 @@
"Z": 66.75818
},
"TerritoryId": 956,
"InteractionType": "AttuneAetherCurrent"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818315
},
{
"Position": {
"X": 760.1999,
"Y": 145.74788,
"Z": -52.025288
},
"TerritoryId": 956,
"InteractionType": "WalkTo"
},
{
"DataId": 2011840,
@ -83,6 +93,16 @@
{
"Sequence": 4,
"Steps": [
{
"Position": {
"X": 483.16574,
"Y": 83.132675,
"Z": 74.693695
},
"TerritoryId": 956,
"InteractionType": "WalkTo",
"Comment": "Avoids aggroing some enemies on the hill"
},
{
"DataId": 2011842,
"Position": {
@ -141,7 +161,8 @@
"Z": -267.23126
},
"TerritoryId": 956,
"InteractionType": "AttuneAetherCurrent"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818320
},
{
"DataId": 2011843,

View File

@ -20,6 +20,17 @@
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": 304.306,
"Y": 84.01365,
"Z": -292.01114
},
"TerritoryId": 956,
"InteractionType": "WalkTo",
"DisableNavmesh": true,
"Mount": true
},
{
"DataId": 1038736,
"Position": {

View File

@ -46,16 +46,19 @@
"Z": -395.31555
},
"TerritoryId": 956,
"InteractionType": "AttuneAetherCurrent"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818318
},
{
"Position": {
"X": -327.6718,
"Y": 79.535736,
"Z": -400.00397
"X": -300.80545,
"Y": 59.384476,
"Z": -409.0928
},
"TerritoryId": 956,
"InteractionType": "WalkTo"
"InteractionType": "WalkTo",
"DisableNavmesh": true,
"Mount": true
},
{
"DataId": 2011983,
@ -65,7 +68,8 @@
"Z": -286.27454
},
"TerritoryId": 956,
"InteractionType": "AttuneAetherCurrent"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818319
},
{
"DataId": 1038736,
@ -114,6 +118,7 @@
"Y": 41.37599,
"Z": -141.16125
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
}

View File

@ -12,6 +12,7 @@
"Y": 41.37599,
"Z": -142.1684
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
}
@ -27,6 +28,7 @@
"Y": 41.37599,
"Z": -141.00867
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
}
@ -42,6 +44,7 @@
"Y": 41.37599,
"Z": -142.1684
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
}
@ -92,7 +95,11 @@
"Z": 4.5318604
},
"TerritoryId": 962,
"InteractionType": "Interact"
"InteractionType": "Interact",
"AethernetShortcut": [
"[Old Sharlayan] The Leveilleur Estate",
"[Old Sharlayan] The Baldesion Annex"
]
},
{
"DataId": 1040291,

View File

@ -83,7 +83,8 @@
"Z": 633.93604
},
"TerritoryId": 957,
"InteractionType": "Interact"
"InteractionType": "Interact",
"DisableNavmesh": true
}
]
},

View File

@ -83,6 +83,16 @@
{
"Sequence": 5,
"Steps": [
{
"Position": {
"X": 232.93636,
"Y": 15.136732,
"Z": 526.6279
},
"TerritoryId": 957,
"InteractionType": "WalkTo",
"Mount": true
},
{
"DataId": 2011992,
"Position": {
@ -91,7 +101,9 @@
"Z": 473.65527
},
"TerritoryId": 957,
"InteractionType": "AttuneAetherCurrent"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818333,
"DisableNavmesh": true
},
{
"Position": {
@ -102,6 +114,16 @@
"TerritoryId": 957,
"InteractionType": "WalkTo"
},
{
"Position": {
"X": 199.50157,
"Y": 1.769943,
"Z": 738.831
},
"StopDistance": 0.25,
"TerritoryId": 957,
"InteractionType": "WalkTo"
},
{
"DataId": 1038608,
"Position": {
@ -109,9 +131,9 @@
"Y": 1.769943,
"Z": 738.9843
},
"StopDistance": 0.25,
"TerritoryId": 957,
"InteractionType": "Interact"
"InteractionType": "Interact",
"Mount": false
}
]
},

View File

@ -12,6 +12,7 @@
"Y": 5.880045,
"Z": 612.02405
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "Interact"
}
@ -92,7 +93,8 @@
"Z": 537.8346
},
"TerritoryId": 957,
"InteractionType": "AttuneAetherCurrent"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818329
},
{
"DataId": 1038616,

View File

@ -1,6 +1,7 @@
{
"Version": 1,
"Author": "liza",
"Comment": "This is possibly the least polished quest so far, as the follow steps are awkward (need to move >10 units away to trigger the follow-up)",
"QuestSequence": [
{
"Sequence": 0,
@ -83,23 +84,184 @@
"Y": 49.103825,
"Z": 152.91064
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "ManualAction",
"Comment": "Follow the Lantern"
"InteractionType": "Interact"
},
{
"DataId": 1038636,
"Position": {
"X": -425.414,
"Y": 38.415024,
"Z": 160.1206
},
"StopDistance": 4,
"TerritoryId": 957,
"InteractionType": "WaitForNpcAtPosition"
},
{
"Position": {
"X": -425.43683,
"Y": 38.413155,
"Z": 160.11292
},
"TerritoryId": 957,
"InteractionType": "WalkTo"
},
{
"DataId": 1038636,
"Position": {
"X": -425.414,
"Y": 38.415024,
"Z": 160.1206
},
"StopDistance": 4,
"TerritoryId": 957,
"InteractionType": "WalkTo"
}
]
},
{
"Sequence": 5,
"Comment": "Follow the Lantern"
"Steps": [
{
"DataId": 1041219,
"Position": {
"X": -425.43683,
"Y": 38.413155,
"Z": 160.11292
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "Interact"
},
{
"DataId": 1041219,
"Position": {
"X": -419.6078,
"Y": 43.950115,
"Z": 87.3025
},
"StopDistance": 4,
"TerritoryId": 957,
"InteractionType": "WaitForNpcAtPosition"
},
{
"Position": {
"X": -430.35034,
"Y": 46.160213,
"Z": 93.2392
},
"TerritoryId": 957,
"InteractionType": "WalkTo"
},
{
"DataId": 1041220,
"Position": {
"X": -419.60785,
"Y": 43.9499,
"Z": 87.296875
},
"StopDistance": 4,
"TerritoryId": 957,
"InteractionType": "WalkTo"
}
]
},
{
"Sequence": 6,
"Comment": "Follow the Lantern"
"Steps": [
{
"DataId": 1041220,
"Position": {
"X": -419.60785,
"Y": 43.9499,
"Z": 87.296875
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "Interact"
},
{
"DataId": 1041220,
"Position": {
"X": -444.7032,
"Y": 49.551,
"Z": 97.1969
},
"StopDistance": 4,
"TerritoryId": 957,
"InteractionType": "WaitForNpcAtPosition"
},
{
"Position": {
"X": -433.19608,
"Y": 46.94587,
"Z": 93.295135
},
"StopDistance": 0.35,
"TerritoryId": 957,
"InteractionType": "WalkTo"
},
{
"DataId": 1041221,
"Position": {
"X": -444.72424,
"Y": 49.55681,
"Z": 97.18469
},
"StopDistance": 4,
"TerritoryId": 957,
"InteractionType": "WalkTo"
}
]
},
{
"Sequence": 7,
"Comment": "Follow the Lantern"
"Steps": [
{
"DataId": 1041221,
"Position": {
"X": -444.72424,
"Y": 49.55681,
"Z": 97.18469
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "Interact"
},
{
"DataId": 1041221,
"Position": {
"X": -484.5909,
"Y": 54.18516,
"Z": 128.3601
},
"StopDistance": 4,
"TerritoryId": 957,
"InteractionType": "WaitForNpcAtPosition"
},
{
"Position": {
"X": -472.65085,
"Y": 53.779083,
"Z": 124.34451
},
"TerritoryId": 957,
"InteractionType": "WalkTo"
},
{
"DataId": 1038637,
"Position": {
"X": -484.61133,
"Y": 54.187614,
"Z": 128.34363
},
"StopDistance": 4,
"TerritoryId": 957,
"InteractionType": "WalkTo"
}
]
},
{
"Sequence": 8,
@ -111,6 +273,7 @@
"Y": 54.187614,
"Z": 128.34363
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "Interact"
}

View File

@ -107,7 +107,8 @@
"Z": 117.69275
},
"TerritoryId": 957,
"InteractionType": "Interact"
"InteractionType": "Interact",
"AetheryteShortcut": "Thavnair - Great Work"
}
]
}

View File

@ -22,18 +22,18 @@
"Steps": [
{
"Position": {
"X": -80.636894,
"Y": 99.974266,
"Z": -708.7214
"X": -82.02301,
"Y": 95.24942,
"Z": -697.3925
},
"TerritoryId": 957,
"InteractionType": "WalkTo"
},
{
"Position": {
"X": -67.775665,
"Y": 97.140656,
"Z": -710.1025
"X": -78.47051,
"Y": 99.96379,
"Z": -711.17303
},
"TerritoryId": 957,
"InteractionType": "WalkTo"
@ -46,7 +46,9 @@
"Z": -710.7805
},
"TerritoryId": 957,
"InteractionType": "AttuneAetherCurrent"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818330,
"DisableNavmesh": true
},
{
"DataId": 1038631,
@ -56,7 +58,8 @@
"Z": 117.69275
},
"TerritoryId": 957,
"InteractionType": "Interact"
"InteractionType": "Interact",
"AetheryteShortcut": "Thavnair - Great Work"
}
]
},
@ -71,7 +74,28 @@
"Z": -561.82196
},
"TerritoryId": 957,
"InteractionType": "Interact"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818334
},
{
"Position": {
"X": -489.27457,
"Y": 72.74904,
"Z": -546.8438
},
"TerritoryId": 957,
"InteractionType": "WalkTo",
"Mount": true
},
{
"Position": {
"X": -523.7225,
"Y": 9.401685,
"Z": -554.4276
},
"TerritoryId": 957,
"InteractionType": "WalkTo",
"DisableNavmesh": true
},
{
"DataId": 1038651,
@ -95,6 +119,7 @@
"Y": -0.090369135,
"Z": -562.4323
},
"StopDistance": 6,
"TerritoryId": 957,
"InteractionType": "Interact"
}
@ -111,7 +136,8 @@
"Z": 17.532532
},
"TerritoryId": 957,
"InteractionType": "Interact"
"InteractionType": "Interact",
"AetheryteShortcut": "Thavnair - Great Work"
}
]
}

View File

@ -20,6 +20,15 @@
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -130.78743,
"Y": 86.83725,
"Z": -252.08578
},
"TerritoryId": 957,
"InteractionType": "WalkTo"
},
{
"DataId": 2011994,
"Position": {
@ -28,7 +37,28 @@
"Z": -288.3192
},
"TerritoryId": 957,
"InteractionType": "AttuneAetherCurrent"
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818335,
"DisableNavmesh": true
},
{
"Position": {
"X": -156.25183,
"Y": 90.34184,
"Z": -399.8714
},
"TerritoryId": 957,
"InteractionType": "WalkTo"
},
{
"Position": {
"X": -126.149765,
"Y": 73.745605,
"Z": -427.64508
},
"TerritoryId": 957,
"InteractionType": "WalkTo",
"DisableNavmesh": true
},
{
"DataId": 1038656,

View File

@ -12,6 +12,7 @@
"Y": 1.9073486E-06,
"Z": 1.5715942
},
"StopDistance": 5,
"TerritoryId": 987,
"InteractionType": "Interact"
}

View File

@ -42,6 +42,7 @@
"Y": -27.000013,
"Z": 177.5387
},
"StopDistance": 5,
"TerritoryId": 963,
"InteractionType": "Interact"
}
@ -58,7 +59,7 @@
"Z": 110.55151
},
"TerritoryId": 963,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1040256,
@ -113,7 +114,7 @@
"Z": 202.2583
},
"TerritoryId": 963,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 191,
@ -123,7 +124,7 @@
"Z": -31.815125
},
"TerritoryId": 963,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1040259,

View File

@ -43,7 +43,7 @@
"Z": 27.725586
},
"TerritoryId": 963,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1040264,
@ -98,7 +98,12 @@
"Z": 1.3884888
},
"TerritoryId": 962,
"InteractionType": "Interact"
"InteractionType": "Interact",
"AetheryteShortcut": "Old Sharlayan",
"AethernetShortcut": [
"[Old Sharlayan] Aetheryte Plaza",
"[Old Sharlayan] The Baldesion Annex"
]
}
]
},
@ -112,6 +117,7 @@
"Y": 4.357494,
"Z": 0.7476196
},
"StopDistance": 6,
"TerritoryId": 962,
"InteractionType": "Interact"
}

View File

@ -72,7 +72,7 @@
"InteractionType": "Interact"
},
{
"ItemId": -1,
"ItemId": null,
"TerritoryId": 959,
"InteractionType": "UseItem"
}

View File

@ -111,7 +111,7 @@
"Z": -197.61963
},
"TerritoryId": 963,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1039001,

View File

@ -57,7 +57,7 @@
"Z": 13.473633
},
"TerritoryId": 963,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1039540,

View File

@ -53,7 +53,7 @@
"Z": -98.43509
},
"TerritoryId": 963,
"InteractionType": "AttuneAethenetShard"
"InteractionType": "AttuneAethernetShard"
},
{
"DataId": 1040374,

View File

@ -28,7 +28,7 @@
"Z": -210.6151
},
"TerritoryId": 963,
"InteractionType": "AttuneAethenetShard",
"InteractionType": "AttuneAethernetShard",
"Comment": "This is pretty late here, maybe move it to some other quest"
},
{

View File

@ -47,10 +47,12 @@
"properties": {
"DataId": {
"type": "integer",
"description": "The data id of the NPC/Object/Aetheryte/Aether Current",
"exclusiveMinimum": 0
},
"Position": {
"type": "object",
"description": "Position to (typically) walk to",
"properties": {
"X": {
"type": "number"
@ -69,38 +71,53 @@
]
},
"StopDistance": {
"type": "number",
"type": ["number", "null"],
"description": "Set if pathfinding should stop closer or further away from the default stop distance",
"exclusiveMinimum": 0
},
"TerritoryId": {
"type": "integer",
"description": "The territory id associated with the location",
"exclusiveMinimum": 0
},
"InteractionType": {
"type": "string",
"description": "What to do at the position",
"enum": [
"Interact",
"WalkTo",
"AttuneAethenetShard",
"AttuneAethernetShard",
"AttuneAetheryte",
"AttuneAetherCurrent",
"Combat",
"UseItem",
"Emote",
"WaitForNpcAtPosition",
"ManualAction"
]
},
"Disabled": {
"description": "Unused (TODO)",
"type": "boolean"
},
"DisableNavmesh": {
"description": "If true, will go to the position in a straight line instead of using pathfinding",
"type": "boolean"
},
"Mount": {
"type": ["boolean", "null"],
"description": "If true, will mount regardless of distance to position. If false, will unmount."
},
"AetheryteShortcut": {
"type": "string",
"description": "The Aetheryte to teleport to (before moving)",
"$comment": "TODO add remaining aetherytes for 2.x/3.x",
"enum": [
"Limsa Lominsa",
"Gridania",
"Ul'dah",
"Ishgard",
"Idyllshire",
"Rhalgr's Reach",
"Fringes - Castrum Oriens",
@ -118,8 +135,6 @@
"Azim Steppe - Dawn Throne",
"Azim Steppe - Dhoro Iloh",
"Doman Enclave",
"Doman Enclave - Northern Enclave",
"Doman Enclave - Southern Enclave",
"Crystarium",
"Eulmore",
@ -155,12 +170,13 @@
"Elpis - Twelve Wonders",
"Elpis - Poieten Oikos",
"Ultima Thule - Reah Tahra",
"Ultima Thula - Abode of the Ea",
"Ultima Thule - Abode of the Ea",
"Ultima Thule - Base Omicron"
]
},
"AethernetShortcut": {
"type": "array",
"description": "A pair of aethernet locations (from + to) to use as a shortcut",
"minItems": 2,
"maxItems": 2,
"items": {
@ -176,7 +192,7 @@
"[Limsa Lominsa] Airship Landing",
"[Gridania] Aetheryte Plaza",
"[Gridania] Archer's Guild",
"[Gridania] Leatherworker's Guld & Shaded Bower",
"[Gridania] Leatherworker's Guild & Shaded Bower",
"[Gridania] Lancer's Guild",
"[Gridania] Conjurer's Guild",
"[Gridania] Botanist's Guild",
@ -249,14 +265,20 @@
]
}
},
"AetherCurrentId": {
"type": "number",
"description": "The aether current id, used to check if a given aetheryte is unlocked"
},
"EnemySpawnType": {
"type": "string",
"description": "Determines how enemy spawning is handled in combat locations",
"enum": [
"AutoOnEnterArea",
"AfterInteraction"
]
},
"KillEnemyDataIds": {
"description": "The enemy data ids which are supposed to be killed",
"type": "array",
"items": {
"type": "integer"
@ -264,6 +286,7 @@
},
"Emote": {
"type": "string",
"description": "The emote to use",
"enum": [
"stretch",
"wave",
@ -271,8 +294,14 @@
"deny"
]
},
"ItemId": {
"type": ["number", "null"],
"description": "The Item to use",
"exclusiveMinimum": 0
},
"SkipIf": {
"type": "array",
"description": "TODO Not implemented",
"items": {
"type": "string",
"enum": [

View File

@ -7,12 +7,13 @@ using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using Questionable.Controller;
using Questionable.Data;
using Questionable.External;
using Questionable.Windows;
namespace Questionable;
public sealed class Questionable : IDalamudPlugin
public sealed class QuestionablePlugin : IDalamudPlugin
{
private readonly WindowSystem _windowSystem = new(nameof(Questionable));
@ -25,9 +26,10 @@ public sealed class Questionable : IDalamudPlugin
private readonly MovementController _movementController;
public Questionable(DalamudPluginInterface pluginInterface, IClientState clientState, ITargetManager targetManager,
IFramework framework, IGameGui gameGui, IDataManager dataManager, ISigScanner sigScanner,
IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui, ICommandManager commandManager)
public QuestionablePlugin(DalamudPluginInterface pluginInterface, IClientState clientState,
ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager,
ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui,
ICommandManager commandManager)
{
ArgumentNullException.ThrowIfNull(pluginInterface);
ArgumentNullException.ThrowIfNull(sigScanner);
@ -38,13 +40,15 @@ public sealed class Questionable : IDalamudPlugin
_clientState = clientState;
_framework = framework;
_gameGui = gameGui;
_gameFunctions = new GameFunctions(dataManager, objectTable, sigScanner, targetManager, pluginLog);
_gameFunctions = new GameFunctions(dataManager, objectTable, sigScanner, targetManager, condition, pluginLog);
AetheryteData aetheryteData = new AetheryteData(dataManager);
NavmeshIpc navmeshIpc = new NavmeshIpc(pluginInterface);
LifestreamIpc lifestreamIpc = new LifestreamIpc(pluginInterface, aetheryteData);
_movementController =
new MovementController(navmeshIpc, clientState, _gameFunctions, pluginLog);
new MovementController(navmeshIpc, clientState, _gameFunctions, condition, pluginLog);
_questController = new QuestController(pluginInterface, dataManager, _clientState, _gameFunctions,
_movementController, pluginLog, condition, chatGui, commandManager);
_movementController, pluginLog, condition, chatGui, aetheryteData, lifestreamIpc);
_windowSystem.AddWindow(new DebugWindow(_movementController, _questController, _gameFunctions, clientState,
targetManager));

View File

@ -35,7 +35,7 @@ internal sealed class DebugWindow : Window
IsOpen = true;
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(100, 0),
MinimumSize = new Vector2(200, 30),
MaximumSize = default
};
}
@ -43,16 +43,22 @@ internal sealed class DebugWindow : Window
public override unsafe void Draw()
{
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
{
ImGui.Text("Not logged in.");
return;
}
var currentQuest = _questController.CurrentQuest;
if (currentQuest != null)
{
ImGui.TextUnformatted($"Quest: {currentQuest.Quest.Name} / {currentQuest.Sequence} / {currentQuest.Step}");
ImGui.TextUnformatted(_questController.DebugState ?? "--");
ImGui.TextUnformatted(_questController.Comment ?? "--");
ImGui.BeginDisabled(_questController.GetNextStep().Step == null);
ImGui.Text($"{_questController.GetNextStep().Step?.Position}");
var nextStep = _questController.GetNextStep();
ImGui.BeginDisabled(nextStep.Step == null);
ImGui.Text(string.Create(CultureInfo.InvariantCulture,
$"{nextStep.Step?.InteractionType} @ {nextStep.Step?.Position}"));
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
{
_questController.ExecuteNextStep();
@ -163,5 +169,8 @@ internal sealed class DebugWindow : Window
if (ImGui.Button("Stop Nav"))
_movementController.Stop();
ImGui.EndDisabled();
if (ImGui.Button("Reload Data"))
_questController.Reload();
}
}