"TerritoryId": 957,
"InteractionType": "WalkTo",
"DisableNavmesh": true,
- "Mount": true
+ "Mount": true,
+ "Comment": "FIXME Returning to the surface means navmesh won't move anymore, but the path is still 'running'"
},
{
"Position": {
"[Crystarium] The Cabinet of Curiosity",
"[Crystarium] The Dossal Gate"
],
- "Comment": "TODO Check target territory id",
"TargetTerritoryId": 844
}
]
null,
null,
null,
- 16
+ -16
]
},
{
ResetPathfinding();
if (InputManager.IsAutoRunning())
+ {
+ _logger.LogInformation("Turning off auto-move");
_gameFunctions.ExecuteCommand("/automove off");
+ }
Destination = new DestinationData(dataId, to, stopDistance ?? (DefaultStopDistance - 0.2f), fly, sprint);
MovementStartedAt = DateTime.MaxValue;
ResetPathfinding();
if (InputManager.IsAutoRunning())
+ {
+ _logger.LogInformation("Turning off auto-move [stop]");
_gameFunctions.ExecuteCommand("/automove off");
+ }
}
public void Dispose()
return ETaskResult.StillRunning;
}
- if (aetheryteData.CalculateDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
- clientState.TerritoryType, To) > 11)
- return ETaskResult.StillRunning;
+ if (aetheryteData.IsCityAetheryte(To))
+ {
+ if (aetheryteData.CalculateDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
+ clientState.TerritoryType, To) > 11)
+ return ETaskResult.StillRunning;
+ }
+ else
+ {
+ // some overworld location (e.g. 'Tesselation (Lakeland)' would end up here
+ if (clientState.TerritoryType != aetheryteData.TerritoryIds[To])
+ return ETaskResult.StillRunning;
+ }
+
return ETaskResult.TaskComplete;
}
internal static class AetheryteShortcut
{
- internal sealed class Factory(IServiceProvider serviceProvider, GameFunctions gameFunctions) : ITaskFactory
+ internal sealed class Factory(
+ IServiceProvider serviceProvider,
+ GameFunctions gameFunctions,
+ AetheryteData aetheryteData) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
return [];
var task = serviceProvider.GetRequiredService<UseAetheryteShortcut>()
- .With(step, step.AetheryteShortcut.Value);
+ .With(step, step.AetheryteShortcut.Value, aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
return [new WaitConditionTask(gameFunctions.CanTeleport, "CanTeleport"), task];
}
public QuestStep Step { get; set; } = null!;
public EAetheryteLocation TargetAetheryte { get; set; }
- public ITask With(QuestStep step, EAetheryteLocation targetAetheryte)
+ /// <summary>
+ /// If using an aethernet shortcut after, the aetheryte's territory-id and the step's territory-id can differ,
+ /// we always use the aetheryte's territory-id.
+ /// </summary>
+ public ushort ExpectedTerritoryId { get; set; }
+
+ public ITask With(QuestStep step, EAetheryteLocation targetAetheryte, ushort expectedTerritoryId)
{
Step = step;
TargetAetheryte = targetAetheryte;
+ ExpectedTerritoryId = expectedTerritoryId;
return this;
}
{
_continueAt = DateTime.Now.AddSeconds(8);
ushort territoryType = clientState.TerritoryType;
- if (Step.TerritoryId == territoryType)
+ if (ExpectedTerritoryId == territoryType)
{
Vector3 pos = clientState.LocalPlayer!.Position;
if (aetheryteData.CalculateDistance(pos, territoryType, TargetAetheryte) < 11 ||
public ETaskResult Update()
{
-
- if (DateTime.Now >= _continueAt && clientState.TerritoryType == Step.TerritoryId)
+ if (DateTime.Now >= _continueAt && clientState.TerritoryType == ExpectedTerritoryId)
return ETaskResult.TaskComplete;
return ETaskResult.StillRunning;
yield break;
}
- yield return new WaitConditionTask(() => clientState.TerritoryType == Step.TerritoryId, $"Wait(territory: {Step.TerritoryId}");
- yield return new WaitConditionTask(() => movementController.IsNavmeshReady, "Wait(navmesh ready)");
+ yield return new WaitConditionTask(() => clientState.TerritoryType == Step.TerritoryId,
+ $"Wait(territory: {Step.TerritoryId})");
+ yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
+ "Wait(navmesh ready)");
float distance;
if (Step.InteractionType == EInteractionType.WalkTo)
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
- if (step.InteractionType != EInteractionType.Emote)
+ if (step.InteractionType != EInteractionType.Say)
return [];
internal sealed class AetheryteData
{
+ 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();
+
+ TownTerritoryIds = dataManager.GetExcelSheet<TerritoryType>()!
+ .Where(x => x.RowId > 0 && !string.IsNullOrEmpty(x.Name) && x.TerritoryIntendedUse == 0)
+ .Select(x => (ushort)x.RowId)
+ .ToList();
+ }
+
public ReadOnlyDictionary<EAetheryteLocation, Vector3> Locations { get; } =
new Dictionary<EAetheryteLocation, Vector3>
{
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 IReadOnlyList<ushort> TownTerritoryIds { get; set; }
public float CalculateDistance(Vector3 fromPosition, ushort fromTerritoryType, EAetheryteLocation to)
{
return (fromPosition - toPosition).Length();
}
+
+ public bool IsCityAetheryte(EAetheryteLocation aetheryte)
+ {
+ var territoryId = TerritoryIds[aetheryte];
+ return TownTerritoryIds.Contains(territoryId);
+ }
}
{
if (ActionManager.Instance()->GetActionStatus(ActionType.Mount, 71) == 0)
{
- _logger.LogInformation("Using SDS Fenrir as mount");
- return ActionManager.Instance()->UseAction(ActionType.Mount, 71);
+ if (ActionManager.Instance()->UseAction(ActionType.Mount, 71))
+ {
+ _logger.LogInformation("Using SDS Fenrir as mount");
+ return true;
+ }
+
+ return false;
}
}
else
{
if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 9) == 0)
{
- _logger.LogInformation("Using mount roulette");
- return ActionManager.Instance()->UseAction(ActionType.GeneralAction, 9);
+ if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 9))
+ {
+ _logger.LogInformation("Using mount roulette");
+ return true;
+ }
+
+ return false;
}
}
contentFinderConditionId, contentId);
}
else
- _logger.LogError("Could not find content for content finder condition (cf: {ContentFinderId})", contentFinderConditionId);
+ _logger.LogError("Could not find content for content finder condition (cf: {ContentFinderId})",
+ contentFinderConditionId);
}
public string? GetDialogueText(Quest currentQuest, string? excelSheetName, string key)
{
if (excelSheetName == null)
{
- var questRow = _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId + 0x10000);
+ var questRow =
+ _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId +
+ 0x10000);
if (questRow == null)
{
_logger.LogError("Could not find quest row for {QuestId}", currentQuest.QuestId);
return true;
return _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] ||
- _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
+ _condition[ConditionFlag.Occupied33] || _condition[ConditionFlag.Occupied38] ||
+ _condition[ConditionFlag.Occupied39] || _condition[ConditionFlag.OccupiedInEvent] ||
+ _condition[ConditionFlag.OccupiedInQuestEvent] || _condition[ConditionFlag.OccupiedInCutSceneEvent] ||
+ _condition[ConditionFlag.Casting] || _condition[ConditionFlag.Unknown57] ||
+ _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
- <Version>0.6</Version>
+ <Version>0.7</Version>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();
serviceCollection.AddTaskWithFactory<UseItem.Factory, UseItem.UseOnGround, UseItem.UseOnObject, UseItem.Use>();
- // TODO sort this in properly
- serviceCollection.AddTaskWithFactory<ZoneChange.Factory, ZoneChange.WaitForZone>();
-
serviceCollection
.AddTaskWithFactory<WaitAtEnd.Factory,
WaitAtEnd.WaitDelay,