muffin v7.5.13

This commit is contained in:
alydev 2026-08-22 10:25:22 +10:00
parent 911ed68baa
commit acf3e3cb18
69 changed files with 2731 additions and 1425 deletions

View file

@ -56,19 +56,19 @@ internal sealed class BossModModule : ICombatModule, IDisposable
if ((uint)(valueOrDefault - 3) <= 1u)
{
flag = true;
goto IL_00bd;
goto IL_00be;
}
}
flag = false;
goto IL_00bd;
goto IL_00be;
}
goto IL_012e;
IL_00bd:
goto IL_012f;
IL_00be:
bool flag2 = flag;
_logger.LogDebug("BossModModule.Start: isRanged={IsRanged}, job role={Role}", flag2, localPlayer.ClassJob.ValueNullable?.Role);
_bossModIpc.SetRangeStrategy(BossModIpc.EPreset.Active, flag2);
goto IL_012e;
IL_012e:
goto IL_012f;
IL_012f:
return true;
}
catch (IpcError exception)

View file

@ -29,12 +29,28 @@ internal sealed class ItemUseModule : ICombatModule
private long _continueAt;
private bool _isWaitingToUseItem;
private bool _itemUsePending;
private long _itemUsePendingUntil;
private int _itemCountBeforeUse;
public ICombatModule? Delegate => _delegate;
public bool IsItemUseInProgress
{
get
{
if (_isWaitingToUseItem || _itemUsePending)
{
return Environment.TickCount64 < _itemUsePendingUntil;
}
return false;
}
}
public ItemUseModule(IServiceProvider serviceProvider, ICondition condition, ILogger<ItemUseModule> logger)
{
_serviceProvider = serviceProvider;
@ -62,6 +78,7 @@ internal sealed class ItemUseModule : ICombatModule
_combatData = combatData;
_isDoingRotation = true;
_continueAt = Environment.TickCount64;
_isWaitingToUseItem = false;
_itemUsePending = false;
return true;
}
@ -73,11 +90,13 @@ internal sealed class ItemUseModule : ICombatModule
if (_isDoingRotation)
{
_delegate.Stop();
_isDoingRotation = false;
_combatData = null;
_delegate = null;
_continueAt = Environment.TickCount64;
}
_isDoingRotation = false;
_isWaitingToUseItem = false;
_itemUsePending = false;
_combatData = null;
_delegate = null;
_continueAt = Environment.TickCount64;
return true;
}
@ -93,43 +112,78 @@ internal sealed class ItemUseModule : ICombatModule
}
else if (_combatData.KillEnemyDataIds.Contains(nextTarget.BaseId) || _combatData.ComplexCombatDatas.Any((ComplexCombatData x) => x.DataId == nextTarget.BaseId && (!x.NameId.HasValue || (nextTarget is ICharacter character && x.NameId == character.NameId))))
{
if (_isDoingRotation)
int inventoryItemCount = InventoryManager.Instance()->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0);
if (_itemUsePending)
{
int inventoryItemCount = InventoryManager.Instance()->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0);
if (_itemUsePending)
if (inventoryItemCount < _itemCountBeforeUse)
{
if (Environment.TickCount64 < _itemUsePendingUntil)
{
_logger.LogDebug("Item use pending; ignoring temporary inventory count={Count}", inventoryItemCount);
}
else
{
_itemUsePending = false;
}
_itemUsePending = false;
return;
}
if (!_itemUsePending && inventoryItemCount == 0)
if (_condition[ConditionFlag.Casting])
{
_itemUsePendingUntil = Environment.TickCount64 + 1000;
}
if (Environment.TickCount64 < _itemUsePendingUntil)
{
_logger.LogDebug("Item use pending; inventory count={Count}", inventoryItemCount);
return;
}
_logger.LogDebug("Item use was not observed; retrying");
_itemUsePending = false;
_isWaitingToUseItem = true;
_itemUsePendingUntil = Environment.TickCount64 + 4000;
_continueAt = Environment.TickCount64 + 1000;
}
else if (_isDoingRotation)
{
if (inventoryItemCount == 0)
{
_delegate.Update(nextTarget);
}
else if (ShouldUseItem(nextTarget))
{
_logger.LogDebug("Attempting to use item {ItemId}", _combatData.CombatItemUse.ItemId);
_itemUsePending = true;
_itemUsePendingUntil = Environment.TickCount64 + 3000;
AgentInventoryContext.Instance()->UseItem(_combatData.CombatItemUse.ItemId, InventoryType.Invalid, 0u, 0);
_continueAt = Environment.TickCount64 + 2000;
_logger.LogDebug("Pausing rotation before using item {ItemId}", _combatData.CombatItemUse.ItemId);
_isDoingRotation = false;
_isWaitingToUseItem = true;
_delegate.Stop();
_continueAt = Environment.TickCount64 + 3000;
_itemUsePendingUntil = Environment.TickCount64 + 4000;
}
else
{
_delegate.Update(nextTarget);
}
}
else if (_isWaitingToUseItem)
{
if (_condition[ConditionFlag.Casting])
{
_continueAt = Environment.TickCount64 + 500;
_itemUsePendingUntil = Environment.TickCount64 + 4000;
return;
}
if (!ShouldUseItem(nextTarget) || inventoryItemCount == 0)
{
_isWaitingToUseItem = false;
return;
}
_logger.LogDebug("Attempting to use item {ItemId}", _combatData.CombatItemUse.ItemId);
long num = AgentInventoryContext.Instance()->UseItem(_combatData.CombatItemUse.ItemId, InventoryType.Invalid, 0u, 0);
_logger.LogDebug("UseItem result: {Result}", num);
bool itemUsePending = (((ulong)num <= 1uL) ? true : false);
_itemUsePending = itemUsePending;
_isWaitingToUseItem = !_itemUsePending;
_itemCountBeforeUse = inventoryItemCount;
_itemUsePendingUntil = Environment.TickCount64 + 3000;
_continueAt = Environment.TickCount64 + (_itemUsePending ? 2000 : 500);
}
else if (_condition[ConditionFlag.Casting])
{
long num = Environment.TickCount64 + 500;
if (num > _continueAt)
long num2 = Environment.TickCount64 + 500;
if (num2 > _continueAt)
{
_continueAt = num;
_continueAt = num2;
}
}
else

View file

@ -31,6 +31,8 @@ internal static class Dive
internal sealed class Task : ITask
{
public bool SmartNavRouted { get; init; }
public override string ToString()
{
return "Dive";
@ -51,6 +53,10 @@ internal static class Dive
{
return false;
}
if (base.Task.SmartNavRouted && !condition[ConditionFlag.Swimming])
{
return false;
}
if (condition[ConditionFlag.Mounted] || condition[ConditionFlag.Swimming])
{
Descend();

View file

@ -80,14 +80,14 @@ internal static class Duty
private EDutyMode GetEffectiveEDutyMode(uint cfcId, EDutyMode? registryDutyMode)
{
if (registryDutyMode.HasValue)
{
return registryDutyMode.Value;
}
if (configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value))
{
return value;
}
if (registryDutyMode.HasValue)
{
return registryDutyMode.Value;
}
EDutyMode defaultDutyMode = configuration.Duties.DefaultDutyMode;
if (defaultDutyMode == EDutyMode.Support && configuration.Duties.AutoUnsyncOverleveled && IsSafelyOverleveled(cfcId))
{
@ -380,7 +380,7 @@ internal static class Duty
}
}
internal sealed record EnableBossModForDutyTask : ITask
internal sealed record EnableBossModForDutyTask(bool ActivateAi = true) : ITask
{
public override string ToString()
{
@ -392,7 +392,7 @@ internal static class Duty
{
protected override bool Start()
{
bossModIpc.EnableAi(BossModIpc.EPreset.Active);
bossModIpc.EnableAi(BossModIpc.EPreset.Active, base.Task.ActivateAi);
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
bool flag;
if (localPlayer != null)
@ -404,18 +404,18 @@ internal static class Duty
if ((uint)(valueOrDefault - 3) <= 1u)
{
flag = true;
goto IL_0077;
goto IL_0082;
}
}
flag = false;
goto IL_0077;
goto IL_0082;
}
goto IL_0087;
IL_0077:
goto IL_0092;
IL_0082:
bool isRanged = flag;
bossModIpc.SetRangeStrategy(BossModIpc.EPreset.Active, isRanged);
goto IL_0087;
IL_0087:
goto IL_0092;
IL_0092:
logger.LogDebug("Enabled BossMod Active preset for AutoDuty run");
return true;
}
@ -463,7 +463,7 @@ internal static class Duty
}
}
internal sealed record EnableBossModPassiveForDutyTask : ITask
internal sealed record EnableBossModPassiveForDutyTask(bool ActivateAi = true) : ITask
{
public override string ToString()
{
@ -475,7 +475,7 @@ internal static class Duty
{
protected override bool Start()
{
bossModIpc.EnableAi(BossModIpc.EPreset.Passive);
bossModIpc.EnableAi(BossModIpc.EPreset.Passive, base.Task.ActivateAi);
logger.LogDebug("Enabled BossMod Passive preset for AutoDuty run (hazard avoidance)");
return true;
}

View file

@ -80,6 +80,8 @@ internal static class EquipRecommended
private bool _checkedOrTriggeredEquipmentUpdate;
private List<uint>? _gameRecommendedItemIds;
private long _timeoutAt;
private List<(int Slot, BestItemRef Item)>? _smartUpgrades;
@ -177,8 +179,7 @@ internal static class EquipRecommended
{
if (!TryStartMove(pendingMove))
{
chatGui.Print($"Equip move {_smartMoveCursor + 1}/{smartMoves.Count} rejected; skipping equip step.", "Questionable", 576);
return ETaskResult.TaskComplete;
throw EquipFailure($"Equip move {_smartMoveCursor + 1}/{smartMoves.Count} was rejected");
}
_currentMoveStarted = true;
_timeoutAt = Environment.TickCount64 + 2000;
@ -196,8 +197,7 @@ internal static class EquipRecommended
}
if (Environment.TickCount64 >= _timeoutAt)
{
chatGui.Print($"Equip move {_smartMoveCursor + 1}/{smartMoves.Count} timed out; skipping equip step.", "Questionable", 576);
return ETaskResult.TaskComplete;
throw EquipFailure($"Equip move {_smartMoveCursor + 1}/{smartMoves.Count} timed out");
}
return ETaskResult.StillRunning;
}
@ -227,11 +227,11 @@ internal static class EquipRecommended
{
return ETaskResult.TaskComplete;
}
if (Environment.TickCount64 < _timeoutAt)
if (Environment.TickCount64 >= _timeoutAt)
{
return ETaskResult.StillRunning;
throw EquipFailure("Recommended gear was not equipped before the timeout");
}
return ETaskResult.TaskComplete;
return ETaskResult.StillRunning;
}
private ETaskResult DirectEquipPhase()
@ -247,20 +247,18 @@ internal static class EquipRecommended
{
return ETaskResult.TaskComplete;
}
if (Environment.TickCount64 < _timeoutAt)
if (Environment.TickCount64 >= _timeoutAt)
{
return ETaskResult.StillRunning;
throw EquipFailure("Recommended gear was not equipped before the timeout");
}
return ETaskResult.TaskComplete;
return ETaskResult.StillRunning;
}
var (num, item) = smartUpgrades[_directEquipCursor];
if (!_directEquipStarted)
{
if (!TryStartDirectEquip(item, num))
{
chatGui.Print($"Direct equip for slot {num} rejected, skipping.", "Questionable", 576);
_directEquipCursor++;
return ETaskResult.StillRunning;
throw EquipFailure($"Direct equip for slot {num} was rejected");
}
_directEquipStarted = true;
_timeoutAt = Environment.TickCount64 + 2000;
@ -274,8 +272,7 @@ internal static class EquipRecommended
}
if (Environment.TickCount64 >= _timeoutAt)
{
_directEquipCursor++;
_directEquipStarted = false;
throw EquipFailure($"Direct equip for slot {num} timed out");
}
return ETaskResult.StillRunning;
}
@ -366,7 +363,8 @@ internal static class EquipRecommended
}
if (!_checkedOrTriggeredEquipmentUpdate)
{
if (IsAllRecommendedGearEquipped())
_gameRecommendedItemIds = SnapshotRecommendedItemIds(ptr);
if (AreAllItemsEquipped(_gameRecommendedItemIds))
{
return ETaskResult.TaskComplete;
}
@ -376,15 +374,15 @@ internal static class EquipRecommended
_checkedOrTriggeredEquipmentUpdate = true;
return ETaskResult.StillRunning;
}
if (IsAllRecommendedGearEquipped())
if (_gameRecommendedItemIds != null && AreAllItemsEquipped(_gameRecommendedItemIds))
{
return ETaskResult.TaskComplete;
}
if (Environment.TickCount64 < _timeoutAt)
if (Environment.TickCount64 >= _timeoutAt)
{
return ETaskResult.StillRunning;
throw EquipFailure("EquipRecommendedGear did not finish before the timeout");
}
return ETaskResult.TaskComplete;
return ETaskResult.StillRunning;
}
private unsafe bool AreUpgradesApplied(IReadOnlyList<(int Slot, BestItemRef Item)> upgrades)
@ -621,19 +619,70 @@ internal static class EquipRecommended
return (byte)classJobLevel;
}
private unsafe bool IsAllRecommendedGearEquipped()
private unsafe static List<uint> SnapshotRecommendedItemIds(RecommendEquipModule* recommendedEquipModule)
{
Span<Pointer<InventoryItem>> recommendedItems = RecommendEquipModule.Instance()->RecommendedItems;
List<uint> list = new List<uint>();
Span<Pointer<InventoryItem>> recommendedItems = recommendedEquipModule->RecommendedItems;
for (int i = 0; i < recommendedItems.Length; i++)
{
Pointer<InventoryItem> pointer = recommendedItems[i];
InventoryItem* value = pointer.Value;
if (value != null && value->ItemId != 0 && !InventoryHelper.IsItemEquipped(value->ItemId))
if (value != null && value->ItemId != 0)
{
return false;
list.Add(((ItemHandle)value->ItemId).Id);
}
}
return true;
return list;
}
private unsafe static bool AreAllItemsEquipped(IReadOnlyList<uint> expectedItemIds)
{
InventoryManager* ptr = InventoryManager.Instance();
if (ptr == null)
{
return false;
}
InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(InventoryType.EquippedItems);
if (inventoryContainer == null)
{
return false;
}
Dictionary<uint, int> dictionary = new Dictionary<uint, int>();
foreach (uint expectedItemId in expectedItemIds)
{
dictionary[expectedItemId] = dictionary.GetValueOrDefault(expectedItemId) + 1;
}
for (int i = 0; i < inventoryContainer->Size; i++)
{
if (dictionary.Count <= 0)
{
break;
}
InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i);
if (inventorySlot == null || inventorySlot->ItemId == 0)
{
continue;
}
uint id = ItemHandle.FromInventorySlot(inventorySlot).Id;
if (dictionary.TryGetValue(id, out var value))
{
if (value == 1)
{
dictionary.Remove(id);
}
else
{
dictionary[id] = value - 1;
}
}
}
return dictionary.Count == 0;
}
private TaskException EquipFailure(string message)
{
chatGui.PrintError(message + "; stopping automation.", "Questionable", 576);
return new TaskException(message);
}
public override bool ShouldInterruptOnDamage()

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Enums;
@ -16,12 +17,13 @@ using Questionable.Controller.Utils;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
using SmartNav.Data;
namespace Questionable.Controller.Steps.Interactions;
internal static class Interact
{
internal sealed class Factory(Configuration configuration) : ITaskFactory
internal sealed class Factory(Configuration configuration, WarpDataService warpDataService) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -63,6 +65,25 @@ internal static class Interact
{
yield return new WaitAtEnd.WaitDelay();
}
ushort? targetTerritoryId = step.TargetTerritoryId;
List<uint> list;
if (targetTerritoryId.HasValue)
{
ushort targetTerritoryId2 = targetTerritoryId.GetValueOrDefault();
list = (from x in warpDataService.Warps
where x.NpcDataId == step.DataId && x.SourceTerritoryId == step.TerritoryId && x.DestTerritoryId == targetTerritoryId2
select x.RowId).Distinct().Take(2).ToList();
}
else
{
list = new List<uint>();
}
List<uint> list2 = list;
if (list2.Count == 1)
{
yield return new WarpInteract.Task(step.DataId.Value, list2[0], step.TerritoryId, step.TargetTerritoryId.Value);
yield break;
}
uint value = step.DataId.Value;
EInteractionType interactionType2 = step.InteractionType;
int skipMarkerCheck;
@ -74,20 +95,17 @@ internal static class Interact
SkipStepConditions stepIf = skipConditions.StepIf;
if (stepIf != null && stepIf.Never)
{
goto IL_024a;
goto IL_03d7;
}
}
if (step.InteractionType != EInteractionType.PurchaseItem)
{
skipMarkerCheck = ((step.DataId == 1052475) ? 1 : 0);
goto IL_024b;
goto IL_03d8;
}
}
goto IL_024a;
IL_024a:
skipMarkerCheck = 1;
goto IL_024b;
IL_024b:
goto IL_03d7;
IL_03d8:
uint? pickUpItemId = step.PickUpItemId ?? step.GCPurchase?.ItemId;
byte? taxiStandId = step.TaxiStandId;
SkipStepConditions? skipConditions2 = step.SkipConditions?.StepIf;
@ -95,6 +113,10 @@ internal static class Interact
uint? purchaseItemId = ((step.InteractionType == EInteractionType.PurchaseItem) ? step.ItemId : ((uint?)null));
int purchaseItemCount = ((step.InteractionType == EInteractionType.PurchaseItem) ? step.ItemCount.GetValueOrDefault() : 0);
yield return new Task(value, quest, interactionType2, (byte)skipMarkerCheck != 0, pickUpItemId, taxiStandId, skipConditions2, completionQuestVariablesFlags, null, purchaseItemId, purchaseItemCount);
yield break;
IL_03d7:
skipMarkerCheck = 1;
goto IL_03d8;
}
}

View file

@ -33,7 +33,7 @@ internal static class SinglePlayerDuty
public const ushort Naadam = 688;
}
internal sealed class Factory(BossModIpc bossModIpc, WrathComboModule wrathComboModule, RotationSolverRebornModule rsrModule, Configuration configuration, TerritoryData territoryData, QuestFunctions questFunctions, DutyRetryState dutyRetryState, ICondition condition, IClientState clientState, IObjectTable objectTable) : ITaskFactory
internal sealed class Factory(BossModIpc bossModIpc, WrathComboModule wrathComboModule, RotationSolverRebornModule rsrModule, Configuration configuration, TerritoryData territoryData, QuestFunctions questFunctions, GameFunctions gameFunctions, DutyRetryState dutyRetryState, ICondition condition, IClientState clientState, IObjectTable objectTable) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -54,7 +54,12 @@ internal static class SinglePlayerDuty
byte seqBefore = questProgressInfo?.Sequence ?? 0;
IReadOnlyList<byte> varsBefore = questProgressInfo?.Variables.ToArray();
dutyRetryState.Reset();
List<ITask> dutyTasks = new List<ITask>();
List<ITask> dutyTasks = new List<ITask>
{
new StartSinglePlayerDuty(contentFinderConditionData.ContentFinderConditionId),
new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(10L)),
new WaitCondition.Task(() => !gameFunctions.IsOccupied(), "Wait(solo duty ready)")
};
if (wrathComboModule.IsAvailable())
{
dutyTasks.Add(new Duty.EnableWrathForDutyTask());
@ -65,14 +70,12 @@ internal static class SinglePlayerDuty
}
if (configuration.General.CombatModule == Configuration.ECombatModule.BossMod && bossModIpc.IsSupported())
{
dutyTasks.Add(new Duty.EnableBossModForDutyTask());
dutyTasks.Add(new Duty.EnableBossModForDutyTask(configuration.SinglePlayerDuties.EnableBossModAi));
}
else if (bossModIpc.IsSupported())
{
dutyTasks.Add(new Duty.EnableBossModPassiveForDutyTask());
dutyTasks.Add(new Duty.EnableBossModPassiveForDutyTask(configuration.SinglePlayerDuties.EnableBossModAi));
}
dutyTasks.Add(new StartSinglePlayerDuty(contentFinderConditionData.ContentFinderConditionId));
dutyTasks.Add(new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(2L)));
dutyTasks.Add(new EnableAi());
if (contentFinderConditionData.TerritoryId == 1052)
{
@ -132,8 +135,6 @@ internal static class SinglePlayerDuty
internal sealed class StartSinglePlayerDutyExecutor(ICondition condition) : TaskExecutor<StartSinglePlayerDuty>()
{
private long _enteredAtMs;
protected override bool Start()
{
return true;
@ -149,14 +150,6 @@ internal static class SinglePlayerDuty
{
return ETaskResult.StillRunning;
}
if (_enteredAtMs == 0L)
{
_enteredAtMs = Environment.TickCount64;
}
if (Environment.TickCount64 - _enteredAtMs < 2000)
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}

View file

@ -130,10 +130,10 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
};
}
}
float num2 = base.Task.StopDistance ?? 3f;
float stopDistance = base.Task.StopDistance ?? 3f;
Vector3? vector = _objectTable.LocalPlayer?.Position;
float num3 = ((!vector.HasValue) ? float.MaxValue : Vector3.Distance(vector.Value, _destination));
if (num3 > num2)
float num2 = ((!vector.HasValue) ? float.MaxValue : Vector3.Distance(vector.Value, _destination));
if (!vector.HasValue || !IsWithinInteractionRange(vector.Value, _destination, stopDistance, GetVerticalStopDistance()))
{
PrepareMovementIfNeeded();
}
@ -176,7 +176,7 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
else if (!base.Task.DisableNavmesh)
{
bool flag = _gameFunctions.IsFlyingUnlocked(base.Task.TerritoryId);
if (!base.Task.SmartNavRouted && !base.Task.Fly && flag && num3 > _configuration.Navigation.MountFlyDistance)
if (!base.Task.SmartNavRouted && !base.Task.Fly && flag && num2 > _configuration.Navigation.MountFlyDistance)
{
base.Task = base.Task with
{
@ -184,8 +184,8 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
Land = true
};
}
float num4 = (base.Task.Fly ? _configuration.Navigation.MountFlyDistance : _configuration.Navigation.MountGroundDistance);
Questionable.Controller.Steps.Common.Mount.EMountIf mountIf = ((!(num3 > num4)) ? Questionable.Controller.Steps.Common.Mount.EMountIf.AwayFromPosition : Questionable.Controller.Steps.Common.Mount.EMountIf.Always);
float num3 = (base.Task.Fly ? _configuration.Navigation.MountFlyDistance : _configuration.Navigation.MountGroundDistance);
Questionable.Controller.Steps.Common.Mount.EMountIf mountIf = ((!(num2 > num3)) ? Questionable.Controller.Steps.Common.Mount.EMountIf.AwayFromPosition : Questionable.Controller.Steps.Common.Mount.EMountIf.Always);
Questionable.Controller.Steps.Common.Mount.MountTask mountTask3 = new Questionable.Controller.Steps.Common.Mount.MountTask(base.Task.TerritoryId, mountIf, _destination);
long retryAtMs = 0L;
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask)? tuple;
@ -244,7 +244,7 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
}
IPlayerCharacter localPlayer = _objectTable.LocalPlayer;
float num = base.Task.StopDistance ?? 3f;
if (base.Task.Fly && base.Task.Land && _canRestart && localPlayer != null && _clientState.TerritoryType == base.Task.TerritoryId && Vector3.Distance(localPlayer.Position, _destination) > num)
if (base.Task.Fly && base.Task.Land && _canRestart && localPlayer != null && _clientState.TerritoryType == base.Task.TerritoryId && (_condition[ConditionFlag.InFlight] || Vector3.Distance(localPlayer.Position, _destination) > num))
{
if (_condition[ConditionFlag.InFlight])
{
@ -255,20 +255,25 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
}
if (!_movementController.IsNavmeshReady)
{
_logger.LogDebug("Airborne outside stop distance, waiting for navmesh to descend");
_logger.LogDebug("Still airborne after fly-and-land movement, waiting for navmesh to descend");
return ETaskResult.StillRunning;
}
Vector3 position = localPlayer.Position;
position.Y = localPlayer.Position.Y - 100f;
Vector3 vector = position;
Vector3 destination = _destination;
destination.Y = localPlayer.Position.Y;
Vector3 vector = destination;
destination = _destination;
destination.Y = _destination.Y - 100f;
Vector3 vector2 = destination;
_descendAttempted = true;
_logger.LogDebug("Outside stop distance but still airborne, descending to {Target}", vector.ToString("G", CultureInfo.InvariantCulture));
_logger.LogDebug("Still airborne, moving above {LandingTarget} and descending to {DescendTarget}", _destination.ToString("G", CultureInfo.InvariantCulture), vector2.ToString("G", CultureInfo.InvariantCulture));
MovementController movementController = _movementController;
uint? dataId = base.Task.DataId;
int num2 = 1;
int num2 = 2;
List<Vector3> list = new List<Vector3>(num2);
CollectionsMarshal.SetCount(list, num2);
CollectionsMarshal.AsSpan(list)[0] = vector;
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
span[0] = vector;
span[1] = vector2;
movementController.NavigateTo(EMovementType.Landing, dataId, list, fly: true, sprint: false, null);
return ETaskResult.StillRunning;
}
@ -276,22 +281,29 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
_logger.LogDebug("Landed outside stop distance, closing gap on foot");
MovementController movementController2 = _movementController;
uint? dataId2 = base.Task.DataId;
Vector3 destination = _destination;
Vector3 destination2 = _destination;
float? stopDistance = base.Task.StopDistance;
bool smartNavRouted = base.Task.SmartNavRouted;
movementController2.NavigateTo(EMovementType.Quest, dataId2, destination, fly: false, sprint: false, stopDistance, null, land: false, smartNavRouted);
movementController2.NavigateTo(EMovementType.Quest, dataId2, destination2, fly: false, sprint: false, stopDistance, null, land: false, smartNavRouted);
return ETaskResult.StillRunning;
}
if (base.Task.DataId.HasValue && !_adjustedToObject && localPlayer != null && !_condition[ConditionFlag.InFlight] && _clientState.TerritoryType == base.Task.TerritoryId)
if (base.Task.DataId.HasValue && localPlayer != null && !_condition[ConditionFlag.InFlight] && _clientState.TerritoryType == base.Task.TerritoryId)
{
IGameObject gameObject = _gameFunctions.FindObjectByDataId(base.Task.DataId.Value, null, warnIfMissing: false);
if (gameObject != null)
{
float num3 = Vector3.Distance(localPlayer.Position, gameObject.Position);
if (num3 > num)
float num4 = Math.Abs(localPlayer.Position.Y - gameObject.Position.Y);
if (!IsWithinInteractionRange(localPlayer.Position, gameObject.Position, num, GetVerticalStopDistance()))
{
if (_adjustedToObject)
{
_logger.LogWarning("Movement to actual object position ended out of interaction range ({Distance:F1}y away, vertical difference {VerticalDistance:F1}y), treating as unreachable", num3, num4);
throw new MovementController.PathfindingFailedException($"Movement to object ended {num3:F1}y away with a {num4:F1}y vertical difference");
}
_adjustedToObject = true;
_logger.LogDebug("Adjusting to actual object position ({Distance:F1}y away)", num3);
_canRestart = false;
_logger.LogDebug("Adjusting to actual object position ({Distance:F1}y away, vertical difference {VerticalDistance:F1}y)", num3, num4);
_movementController.NavigateTo(EMovementType.Quest, base.Task.DataId, gameObject.Position, fly: false, sprint: false, base.Task.StopDistance);
return ETaskResult.StillRunning;
}
@ -316,13 +328,31 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
}
if (base.Task.SmartNavRouted && !_adjustedToObject && localPlayer != null && _clientState.TerritoryType == base.Task.TerritoryId && Vector3.Distance(localPlayer.Position, _destination) > num + 5f)
{
float num4 = Vector3.Distance(localPlayer.Position, _destination);
_logger.LogWarning("SmartNav movement ended {Distance:F1}y from target (stop distance {StopDistance:F1}y), treating as unreachable", num4, num);
throw new MovementController.PathfindingFailedException($"SmartNav movement ended {num4:F1}y from target (stop distance {num:F1}y)");
float num5 = Vector3.Distance(localPlayer.Position, _destination);
_logger.LogWarning("SmartNav movement ended {Distance:F1}y from target (stop distance {StopDistance:F1}y), treating as unreachable", num5, num);
throw new MovementController.PathfindingFailedException($"SmartNav movement ended {num5:F1}y from target (stop distance {num:F1}y)");
}
return ETaskResult.TaskComplete;
}
private float GetVerticalStopDistance()
{
if (!base.Task.IgnoreDistanceToObject)
{
return 1.95f;
}
return float.MaxValue;
}
internal static bool IsWithinInteractionRange(Vector3 playerPosition, Vector3 targetPosition, float stopDistance, float verticalStopDistance)
{
if (Vector3.Distance(playerPosition, targetPosition) <= stopDistance)
{
return Math.Abs(playerPosition.Y - targetPosition.Y) <= verticalStopDistance;
}
return false;
}
private ETaskResult? UpdateMountState()
{
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask)? mountBeforeMovement = _mountBeforeMovement;

View file

@ -7,10 +7,64 @@ namespace Questionable.Controller.Steps.Movement;
internal sealed record MoveTask(ushort TerritoryId, Vector3 Destination, bool? Mount = null, bool MountRequired = false, bool DismountRequired = false, float? StopDistance = null, uint? DataId = null, bool DisableNavmesh = false, bool? Sprint = null, bool Fly = false, bool Land = false, bool IgnoreDistanceToObject = false, bool RestartNavigation = true, EInteractionType InteractionType = EInteractionType.None, bool SmartNavRouted = false, bool AllowZoneTransition = false, string? RouteTargetNodeId = null, string? RouteApproachNodeId = null) : ITask
{
public MoveTask(QuestStep step, Vector3 destination)
: this(step.TerritoryId, destination, step.Mount, step.MountRequired, step.DismountRequired, step.CalculateActualStopDistance(), step.DataId, step.DisableNavmesh, step.Sprint, step.Fly == true, step.Land == true, step.IgnoreDistanceToObject == true, step.RestartNavigationIfCancelled != false, step.InteractionType)
: this(step.TerritoryId, destination, ResolveMount(step), step.MountRequired, step.DismountRequired, step.CalculateActualStopDistance(), step.DataId, step.DisableNavmesh, step.Sprint, ResolveFly(step), ResolveLand(step), step.IgnoreDistanceToObject == true, step.RestartNavigationIfCancelled != false, step.InteractionType)
{
}
private static bool? ResolveMount(QuestStep step)
{
if (step.MountRequired)
{
return true;
}
if (step.DismountRequired)
{
return false;
}
switch (step.TravelMode)
{
case ETravelMode.OnFoot:
return false;
case ETravelMode.GroundMount:
case ETravelMode.Flying:
return true;
default:
return step.Mount;
}
}
private static bool ResolveFly(QuestStep step)
{
if (step.DismountRequired)
{
return false;
}
switch (step.TravelMode)
{
case ETravelMode.OnFoot:
case ETravelMode.GroundMount:
return false;
case ETravelMode.Flying:
return true;
default:
return step.Fly == true;
}
}
private static bool ResolveLand(QuestStep step)
{
ETravelMode travelMode = step.TravelMode;
bool flag = (uint)(travelMode - 1) <= 1u;
bool flag2 = !flag;
if (flag2)
{
EArrivalMode arrivalMode = step.ArrivalMode;
bool flag3 = (uint)(arrivalMode - 2) <= 1u;
flag2 = flag3 || step.Land == true;
}
return flag2;
}
public bool ShouldRedoOnInterrupt()
{
return true;

View file

@ -109,6 +109,14 @@ internal static class MoveTo
yield return new LandTask();
}
}
if (step.ArrivalMode == EArrivalMode.StayMounted && !step.DismountRequired)
{
yield return new Mount.MountTask(step.TerritoryId, Mount.EMountIf.Always);
}
else if (step.ArrivalMode == EArrivalMode.Dismount && !step.MountRequired)
{
yield return new Mount.UnmountTask();
}
}
}
}

View file

@ -30,6 +30,10 @@ internal static class SmartNavStep
{
yield break;
}
(bool AllowMount, bool AllowFlight) capabilities = SmartNavReRouteService.GetCapabilities(step.TravelMode);
bool item = capabilities.AllowMount;
bool item2 = capabilities.AllowFlight;
playerNavState = playerNavState.WithTravelCapabilities(item, item2);
RouteResult routeResult = navRouter.FindRoute(playerNavState, step.TerritoryId, vector.Value);
if (routeResult.Segments.Count == 0)
{
@ -37,10 +41,10 @@ internal static class SmartNavStep
}
HandledRouting = true;
logger.LogDebug("SmartNav routing to {Territory} with {Count} segments", territoryData.GetNameAndId(step.TerritoryId), routeResult.Segments.Count);
reRouteService.SetDestination(step.TerritoryId, vector.Value);
foreach (ITask item in taskMapper.MapInstructions(instructionBuilder.Build(routeResult, step.TerritoryId, step.DisableNavmesh), step))
reRouteService.SetDestination(step.TerritoryId, vector.Value, step.TravelMode);
foreach (ITask item3 in taskMapper.MapInstructions(instructionBuilder.Build(routeResult, step.TerritoryId, step.DisableNavmesh), step, step.TravelMode))
{
yield return item;
yield return item3;
}
}

View file

@ -261,6 +261,10 @@ internal sealed class CombatController : IDisposable
SetTarget(gameObject3);
}
}
if (_currentFight.Module is ItemUseModule { IsItemUseInProgress: not false })
{
return EStatus.InCombat;
}
if (_condition[ConditionFlag.InCombat])
{
_wasInCombat = true;
@ -362,43 +366,39 @@ internal sealed class CombatController : IDisposable
int num = 0;
float num2 = float.MaxValue;
bool flag5 = _logger.IsEnabled(LogLevel.Debug);
foreach (IGameObject item in _objectTable)
foreach (IGameObject item2 in _objectTable)
{
var (num3, text) = GetKillPriority(item, localPlayer);
if (num3 <= 0)
int item = GetKillPriority(item2, localPlayer).Priority;
if (item <= 0)
{
continue;
}
if (_losIgnoreList.TryGetValue(item.GameObjectId, out var value2))
if (_losIgnoreList.TryGetValue(item2.GameObjectId, out var value2))
{
if (Environment.TickCount64 < value2)
{
if (flag5)
{
_logger.LogDebug("Target {Name} ({Id:X8}) skipped - LoS ignore list ({Remaining}ms left)", item.Name, item.GameObjectId, value2 - Environment.TickCount64);
_logger.LogDebug("Target {Name} ({Id:X8}) skipped - LoS ignore list ({Remaining}ms left)", item2.Name, item2.GameObjectId, value2 - Environment.TickCount64);
}
continue;
}
_losIgnoreList.Remove(item.GameObjectId);
_losIgnoreList.Remove(item2.GameObjectId);
if (flag5)
{
_logger.LogDebug("Target {Name} ({Id:X8}) - LoS ignore expired", item.Name, item.GameObjectId);
_logger.LogDebug("Target {Name} ({Id:X8}) - LoS ignore expired", item2.Name, item2.GameObjectId);
}
}
float num4 = Vector3.Distance(item.Position, value);
if (flag5)
float num3 = Vector3.Distance(item2.Position, value);
if (item > num || (item == num && num3 < num2))
{
_logger.LogDebug("Target candidate: {Name} ({Id:X8}) BaseId={BaseId} Priority={Priority} ({Reason}) Distance={Distance:N1}", item.Name, item.GameObjectId, item.BaseId, num3, text, num4);
}
if (num3 > num || (num3 == num && num4 < num2))
{
gameObject = item;
num = num3;
num2 = num4;
gameObject = item2;
num = item;
num2 = num3;
}
}
ulong? num5 = gameObject?.GameObjectId;
if (num5 != _lastTargetId)
ulong? num4 = gameObject?.GameObjectId;
if (num4 != _lastTargetId)
{
if (gameObject != null)
{
@ -408,7 +408,7 @@ internal sealed class CombatController : IDisposable
{
_logger.LogDebug("No valid target found");
}
_lastTargetId = num5;
_lastTargetId = num4;
}
if (gameObject != null && num <= 15 && !_condition[ConditionFlag.InCombat])
{

View file

@ -416,7 +416,7 @@ internal sealed class MovementController : IDisposable
}
else if ((vector4 - Destination.Position).Length() < Destination.StopDistance)
{
if (vector4.Y - Destination.Position.Y <= Destination.VerticalStopDistance)
if (Math.Abs(vector4.Y - Destination.Position.Y) <= Destination.VerticalStopDistance)
{
Stop();
}

View file

@ -1538,6 +1538,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
return false;
}
if (_startedQuest != null && QuestFunctions.IsStartingCityOpeningQuest(_startedQuest.Quest.Id))
{
return false;
}
ElementId elementId = (from x in _questFunctions.GetNextPriorityQuestsThatCanBeAccepted()
where x.IsAvailable
select x.QuestId).FirstOrDefault();

View file

@ -103,15 +103,10 @@ internal sealed class QuestPriorityResolver
QuestResolution valueOrDefault2 = questResolution.GetValueOrDefault();
return ApplyQuestRedirects(valueOrDefault2);
}
uint territoryType = _clientState.TerritoryType;
bool flag2 = territoryType - 181 <= 2;
if (flag2 && flag && _configuration.General.MsqPriority != Configuration.EMsqPriorityMode.Manual)
QuestId activeStartingCityOpeningQuest = _questFunctions.GetActiveStartingCityOpeningQuest();
if ((object)activeStartingCityOpeningQuest != null && (_questFunctions.IsQuestAccepted(activeStartingCityOpeningQuest) || (flag && _configuration.General.MsqPriority != Configuration.EMsqPriorityMode.Manual)))
{
ElementId currentQuest = resolvedMsqQuest.CurrentQuest;
if ((object)currentQuest != null && currentQuest.Value > 0 && !_questFunctions.IsQuestAccepted(currentQuest))
{
return QuestResolution.ForQuest(EQuestResolutionType.MsqImmediate, currentQuest, resolvedMsqQuest.Sequence, resolvedMsqQuest.State, $"Starting city opening MSQ {currentQuest}");
}
return QuestResolution.ForQuest(EQuestResolutionType.MsqImmediate, activeStartingCityOpeningQuest, _questFunctions.GetStartingCityOpeningSequence(activeStartingCityOpeningQuest), resolvedMsqQuest.State, $"Starting city opening MSQ {activeStartingCityOpeningQuest}");
}
questResolution = TryResolveInProgressClassQuest(valueOrDefault, resolvedMsqQuest.State);
if (questResolution.HasValue)

File diff suppressed because it is too large Load diff

View file

@ -391,7 +391,7 @@ internal sealed class BossModIpc : IDisposable
AddTransientStrategy(preset, "BossMod.Autorotation.MiscAI.StayCloseToTarget", "Range", isRanged ? "15" : "3");
}
public bool EnableAi(EPreset preset)
public bool EnableAi(EPreset preset, bool activateAi = true)
{
if (!CreateShellPreset(preset))
{
@ -411,6 +411,10 @@ internal sealed class BossModIpc : IDisposable
_logger.LogWarning("Unable to set BossMod preset {Preset}: {Message}", preset, ipcError.Message);
return false;
}
if (!activateAi)
{
return true;
}
string aiCommand = GetAiCommand();
if (aiCommand != null)
{
@ -424,7 +428,7 @@ internal sealed class BossModIpc : IDisposable
public void EnableQuestBattleAi()
{
EnableAi(EPreset.Active);
EnableAi(EPreset.Active, _configuration.SinglePlayerDuties.EnableBossModAi);
ConfigureSoloDutyZone();
}

View file

@ -23,6 +23,22 @@ namespace Questionable.Functions;
internal sealed class QuestFunctions
{
private static readonly Dictionary<byte, QuestId> ComingToStartingCityQuests = new Dictionary<byte, QuestId>
{
{
1,
new QuestId(107)
},
{
2,
new QuestId(39)
},
{
3,
new QuestId(594)
}
};
private static readonly Dictionary<ushort, EClassJob> CloseToHomeQuests = new Dictionary<ushort, EClassJob>
{
{
@ -95,6 +111,56 @@ internal sealed class QuestFunctions
private static readonly HashSet<ushort> RemovedQuestIds = new HashSet<ushort> { 487, 1428, 1429 };
public static bool IsStartingCityOpeningQuest(ElementId elementId)
{
if (elementId is QuestId questId)
{
if (!ComingToStartingCityQuests.ContainsValue(questId))
{
return CloseToHomeQuests.ContainsKey(questId.Value);
}
return true;
}
return false;
}
public QuestId? GetActiveStartingCityOpeningQuest()
{
QuestId questId = ComingToStartingCityQuests.Values.Concat(CloseToHomeQuests.Keys.Select((ushort x) => new QuestId(x))).Where(IsQuestAccepted).Cast<QuestId>()
.FirstOrDefault();
if (questId != null)
{
return questId;
}
EClassJob startingClass = ((EClassJob?)_objectTable.LocalPlayer?.ClassJob.RowId).GetValueOrDefault();
ushort num = (from x in CloseToHomeQuests
where x.Value == startingClass
select x.Key).FirstOrDefault();
if (num != 0)
{
QuestId questId2 = new QuestId(num);
if (IsReadyToAcceptQuest(questId2, ignoreLevel: true))
{
return questId2;
}
}
if (ComingToStartingCityQuests.TryGetValue(PlayerStateHelper.GetStartTown(), out QuestId value) && IsReadyToAcceptQuest(value, ignoreLevel: true))
{
return value;
}
return null;
}
public byte GetStartingCityOpeningSequence(QuestId questId)
{
byte questSequence = QuestManager.GetQuestSequence(questId.Value);
if (ComingToStartingCityQuests.ContainsValue(questId) && IsQuestAccepted(questId) && questSequence == 0)
{
return byte.MaxValue;
}
return questSequence;
}
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, JournalData journalData, AetheryteFunctions aetheryteFunctions, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, AlliedSocietyData alliedSocietyData, Configuration configuration, IDataManager dataManager, IObjectTable objectTable, IGameGui gameGui, ILogger<QuestFunctions> logger)
{
_questRegistry = questRegistry;

View file

@ -5,21 +5,28 @@ using Microsoft.Extensions.Logging;
using Questionable.Controller.CustomDelivery;
using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Movement;
using Questionable.Controller.Steps.Shared;
using Questionable.Model.Questing;
using SmartNav;
namespace Questionable.Navigation;
internal sealed class SmartNavReRouteService(ReRoutePolicy reRoutePolicy, SmartNavTaskMapper taskMapper, Configuration configuration, ILogger<SmartNavReRouteService> logger)
{
public void SetDestination(uint territoryId, Vector3 position)
private ETravelMode _travelMode;
public void SetDestination(uint territoryId, Vector3 position, ETravelMode travelMode = ETravelMode.Auto)
{
reRoutePolicy.SetDestination(territoryId, position);
_travelMode = travelMode;
var (allowMount, allowFlight) = GetCapabilities(travelMode);
reRoutePolicy.SetDestination(territoryId, position, allowMount, allowFlight);
}
public void ClearDestination()
{
_travelMode = ETravelMode.Auto;
reRoutePolicy.ClearDestination();
}
@ -51,7 +58,7 @@ internal sealed class SmartNavReRouteService(ReRoutePolicy reRoutePolicy, SmartN
if (reRouteDecision is ReRouteDecision.Replan replan)
{
taskQueue.Reset();
foreach (ITask item2 in taskMapper.MapInstructions(replan.Instructions))
foreach (ITask item2 in taskMapper.MapInstructions(replan.Instructions, null, _travelMode))
{
taskQueue.Enqueue(item2);
}
@ -79,10 +86,33 @@ internal sealed class SmartNavReRouteService(ReRoutePolicy reRoutePolicy, SmartN
private static bool IsMovementTask(ITask task)
{
if (task is MoveTask || task is LandTask || task is Mount.MountTask || task is AetheryteTeleport.Task || task is AethernetRide.Task || task is WaitNavmesh.Task || task is WaitCondition.Task || task is WarpInteract.Task || task is TaxiInteract.Task || task is SubRegionTransport.Task || task is TicketTeleport.Task)
if (!(task is MoveTask))
{
return true;
if (task is Dive.Task task2)
{
if (task2.SmartNavRouted)
{
goto IL_006c;
}
}
else if (task is LandTask || task is Mount.MountTask || task is AetheryteTeleport.Task || task is AethernetRide.Task || task is WaitNavmesh.Task || task is WaitCondition.Task || task is WarpInteract.Task || task is TaxiInteract.Task || task is SubRegionTransport.Task || task is TicketTeleport.Task)
{
goto IL_006c;
}
return false;
}
return false;
goto IL_006c;
IL_006c:
return true;
}
internal static (bool AllowMount, bool AllowFlight) GetCapabilities(ETravelMode travelMode)
{
return travelMode switch
{
ETravelMode.OnFoot => (AllowMount: false, AllowFlight: false),
ETravelMode.GroundMount => (AllowMount: true, AllowFlight: false),
_ => (AllowMount: true, AllowFlight: true),
};
}
}

View file

@ -53,7 +53,27 @@ internal sealed class SmartNavRouteEnqueuer(NavRouter navRouter, PlayerNavStateB
{
return false;
}
List<NavInstruction> list = instructionBuilder.Build(routeResult, territoryId).TakeWhile((NavInstruction x) => !(x is NavInstruction.Move move) || !move.IsFinal).ToList();
List<NavInstruction> list = instructionBuilder.Build(routeResult, territoryId).TakeWhile(delegate(NavInstruction x)
{
if (x is NavInstruction.Move move)
{
if (move.IsFinal)
{
goto IL_0026;
}
}
else if (x is NavInstruction.Swim { IsFinal: not false })
{
goto IL_0026;
}
bool flag = false;
goto IL_002c;
IL_0026:
flag = true;
goto IL_002c;
IL_002c:
return !flag;
}).ToList();
if (list.Count == 0)
{
return false;
@ -120,6 +140,18 @@ internal sealed class SmartNavRouteEnqueuer(NavRouter navRouter, PlayerNavStateB
{
return false;
}
if (instruction is NavInstruction.Swim)
{
return false;
}
if (instruction is NavInstruction.DiveEntry)
{
return false;
}
if (instruction is NavInstruction.Surface)
{
return false;
}
if (instruction is NavInstruction.AethernetHop)
{
return false;

View file

@ -5,6 +5,7 @@ using System.Numerics;
using Dalamud.Plugin.Services;
using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Movement;
using Questionable.Controller.Steps.Shared;
using Questionable.Data;
@ -15,13 +16,22 @@ namespace Questionable.Navigation;
internal sealed class SmartNavTaskMapper(IClientState clientState, TerritoryData territoryData)
{
public IEnumerable<ITask> MapInstructions(IReadOnlyList<NavInstruction> instructions, QuestStep? step = null)
private const float SurfaceExitRise = 3f;
public IEnumerable<ITask> MapInstructions(IReadOnlyList<NavInstruction> instructions, QuestStep? step = null, ETravelMode travelMode = ETravelMode.Auto)
{
for (int i = 0; i < instructions.Count; i++)
{
foreach (ITask item in Map(instructions[i], NextTravelInstruction(instructions, i), step))
{
yield return item;
if (item is MoveTask task && !(instructions[i] is NavInstruction.Swim))
{
yield return ApplyTravelMode(task, travelMode);
}
else
{
yield return item;
}
}
}
}
@ -48,66 +58,131 @@ internal sealed class SmartNavTaskMapper(IClientState clientState, TerritoryData
{
if (!(instruction is NavInstruction.Move move))
{
if (!(instruction is NavInstruction.Land))
if (!(instruction is NavInstruction.Swim swim))
{
if (!(instruction is NavInstruction.Unmount))
if (!(instruction is NavInstruction.DiveEntry dive))
{
NavInstruction.WaitForTerritory waitForTerritory = instruction as NavInstruction.WaitForTerritory;
if ((object)waitForTerritory == null)
if (!(instruction is NavInstruction.Surface surface))
{
if (!(instruction is NavInstruction.WaitForNavmesh))
if (!(instruction is NavInstruction.Land))
{
if (!(instruction is NavInstruction.InteractWarp interactWarp))
if (!(instruction is NavInstruction.Unmount))
{
if (!(instruction is NavInstruction.UseTeleportTicket useTeleportTicket))
NavInstruction.WaitForTerritory waitForTerritory = instruction as NavInstruction.WaitForTerritory;
if ((object)waitForTerritory == null)
{
if (!(instruction is NavInstruction.SubRegionTransport subRegionTransport))
if (!(instruction is NavInstruction.WaitForNavmesh))
{
throw new InvalidOperationException($"Unmapped NavInstruction: {instruction}");
if (!(instruction is NavInstruction.InteractWarp interactWarp))
{
if (!(instruction is NavInstruction.UseTeleportTicket useTeleportTicket))
{
if (!(instruction is NavInstruction.SubRegionTransport subRegionTransport))
{
throw new InvalidOperationException($"Unmapped NavInstruction: {instruction}");
}
yield return new SubRegionTransport.Task(subRegionTransport.From, subRegionTransport.To, subRegionTransport.Territory);
}
else
{
yield return new TicketTeleport.Task(useTeleportTicket.ItemId, useTeleportTicket.Territory);
}
}
else
{
yield return new WarpInteract.Task(interactWarp.NpcDataId, interactWarp.WarpRowId, interactWarp.FromTerritory, interactWarp.ToTerritory);
}
}
else
{
yield return new WaitNavmesh.Task();
}
yield return new SubRegionTransport.Task(subRegionTransport.From, subRegionTransport.To, subRegionTransport.Territory);
}
else
{
yield return new TicketTeleport.Task(useTeleportTicket.ItemId, useTeleportTicket.Territory);
yield return new WaitCondition.Task(() => waitForTerritory.Acceptable.Contains(clientState.TerritoryType), waitForTerritory.Description);
}
}
else
{
yield return new WarpInteract.Task(interactWarp.NpcDataId, interactWarp.WarpRowId, interactWarp.FromTerritory, interactWarp.ToTerritory);
yield return new Mount.UnmountTask();
}
}
else
{
yield return new WaitNavmesh.Task();
yield return new LandTask();
}
}
else
{
yield return new WaitCondition.Task(() => waitForTerritory.Acceptable.Contains(clientState.TerritoryType), waitForTerritory.Description);
ushort territory = surface.Territory;
Vector3 position = surface.Position;
position.Y = surface.Position.Y + 3f;
Vector3 destination = position;
bool? mount = true;
string targetNodeId = surface.TargetNodeId;
string approachNodeId = surface.ApproachNodeId;
yield return new MoveTask(territory, destination, mount, MountRequired: false, DismountRequired: false, null, null, DisableNavmesh: true, null, Fly: true, Land: false, IgnoreDistanceToObject: false, RestartNavigation: true, EInteractionType.None, SmartNavRouted: true, AllowZoneTransition: false, targetNodeId, approachNodeId);
}
}
else
{
yield return new Mount.UnmountTask();
yield return new Dive.Task
{
SmartNavRouted = true
};
ushort territory2 = dive.Territory;
Vector3 position = dive.Position;
position.Y = dive.Position.Y - 25f;
Vector3 destination2 = position;
string approachNodeId = dive.TargetNodeId;
string targetNodeId = dive.ApproachNodeId;
yield return new MoveTask(territory2, destination2, null, MountRequired: false, DismountRequired: false, null, null, DisableNavmesh: true, null, Fly: true, Land: false, IgnoreDistanceToObject: false, RestartNavigation: true, EInteractionType.None, SmartNavRouted: true, AllowZoneTransition: false, approachNodeId, targetNodeId);
}
}
else if (swim.IsFinal && step != null)
{
Vector3 destination3 = step.Position ?? swim.Position;
yield return new MoveTask(step, destination3)with
{
Fly = true,
Land = false,
SmartNavRouted = true,
RouteTargetNodeId = swim.TargetNodeId,
RouteApproachNodeId = swim.ApproachNodeId
};
}
else
{
yield return new LandTask();
NavInstruction.Swim swim2 = swim;
ushort territory3 = swim2.Territory;
Vector3 position2 = swim2.Position;
float? stopDistance = swim2.StopDistance;
string targetNodeId = swim2.TargetNodeId;
string approachNodeId = swim2.ApproachNodeId;
yield return new MoveTask(territory3, position2, null, MountRequired: false, DismountRequired: false, stopDistance, null, DisableNavmesh: false, null, Fly: true, Land: false, IgnoreDistanceToObject: false, RestartNavigation: true, EInteractionType.None, SmartNavRouted: true, AllowZoneTransition: false, targetNodeId, approachNodeId);
}
}
else if (move.IsFinal && step != null)
{
Vector3 destination = step.Position ?? move.Position;
yield return new MoveTask(step, destination)with
Vector3 destination4 = step.Position ?? move.Position;
MoveTask moveTask = new MoveTask(step, destination4)with
{
Fly = (move.Fly || step.Fly == true),
Land = (move.Fly || step.Land == true),
SmartNavRouted = true,
RouteTargetNodeId = move.TargetNodeId,
RouteApproachNodeId = move.ApproachNodeId
Fly = (move.Fly || step.Fly == true)
};
MoveTask moveTask2 = moveTask;
bool flag = move.Fly || step.Land == true;
if (!flag)
{
EArrivalMode arrivalMode = step.ArrivalMode;
bool flag2 = (uint)(arrivalMode - 2) <= 1u;
flag = flag2;
}
moveTask2.Land = flag;
moveTask.SmartNavRouted = true;
moveTask.RouteTargetNodeId = move.TargetNodeId;
moveTask.RouteApproachNodeId = move.ApproachNodeId;
yield return moveTask;
if (step != null)
{
bool? fly = step.Fly;
@ -116,22 +191,30 @@ internal sealed class SmartNavTaskMapper(IClientState clientState, TerritoryData
yield return new LandTask();
}
}
if (step.ArrivalMode == EArrivalMode.StayMounted && !step.DismountRequired)
{
yield return new Mount.MountTask(step.TerritoryId, Mount.EMountIf.Always);
}
else if (step.ArrivalMode == EArrivalMode.Dismount && !step.MountRequired)
{
yield return new Mount.UnmountTask();
}
}
else
{
NavInstruction.Move move2 = move;
ushort territory = move2.Territory;
Vector3 position = move2.Position;
bool? mount = move2.Mount;
float? stopDistance = move2.StopDistance;
ushort territory4 = move2.Territory;
Vector3 position3 = move2.Position;
bool? mount2 = move2.Mount;
float? stopDistance2 = move2.StopDistance;
uint? dataId = move2.DataId;
bool disableNavmesh = move2.DisableNavmesh;
bool fly2 = move2.Fly;
bool landAtTarget = move2.LandAtTarget;
bool flag = move2.Fly;
bool flag2 = move2.LandAtTarget;
bool allowZoneTransition = move2.AllowZoneTransition;
string targetNodeId = move2.TargetNodeId;
string approachNodeId = move2.ApproachNodeId;
yield return new MoveTask(territory, position, mount, MountRequired: false, DismountRequired: false, stopDistance, dataId, disableNavmesh, null, fly2, landAtTarget, IgnoreDistanceToObject: false, RestartNavigation: true, EInteractionType.None, SmartNavRouted: true, allowZoneTransition, targetNodeId, approachNodeId);
string approachNodeId = move2.TargetNodeId;
string targetNodeId = move2.ApproachNodeId;
yield return new MoveTask(territory4, position3, mount2, MountRequired: false, DismountRequired: false, stopDistance2, dataId, disableNavmesh, null, flag, flag2, IgnoreDistanceToObject: false, RestartNavigation: true, EInteractionType.None, SmartNavRouted: true, allowZoneTransition, approachNodeId, targetNodeId);
}
}
else
@ -147,4 +230,50 @@ internal sealed class SmartNavTaskMapper(IClientState clientState, TerritoryData
yield return new AetheryteTeleport.MoveAwayFromAetheryte(teleport.Target);
}
}
private static MoveTask ApplyTravelMode(MoveTask task, ETravelMode travelMode)
{
if (travelMode == ETravelMode.Auto)
{
return task;
}
if (task.MountRequired)
{
return task with
{
Mount = true,
Fly = (travelMode == ETravelMode.Flying && task.Fly),
Land = (travelMode == ETravelMode.Flying && task.Land)
};
}
if (task.DismountRequired)
{
return task with
{
Mount = false,
Fly = false,
Land = false
};
}
return travelMode switch
{
ETravelMode.OnFoot => task with
{
Mount = false,
Fly = false,
Land = false
},
ETravelMode.GroundMount => task with
{
Mount = true,
Fly = false,
Land = false
},
ETravelMode.Flying => task with
{
Mount = true
},
_ => task,
};
}
}

View file

@ -317,6 +317,13 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
}
using (ImRaii.Disabled(!value))
{
ImGui.Spacing();
bool value2 = base.Configuration.SinglePlayerDuties.EnableBossModAi;
if (UiThemeUtils.WrappedCheckbox("Activate BossMod AI during quest battles", ref value2, "When disabled, Questionable still loads its BossMod preset for combat, but does not issue the /vbmai on or /bmrai on command for autonomous movement and targeting."))
{
base.Configuration.SinglePlayerDuties.EnableBossModAi = value2;
Save();
}
ImGui.Spacing();
int currentItem = (int)base.Configuration.SinglePlayerDuties.RetryDifficulty;
ImGui.SetNextItemWidth(MathF.Min(ImGui.GetContentRegionAvail().X * 0.4f, 260f));

View file

@ -338,30 +338,27 @@ internal sealed class QuestSequenceComponent
ImGui.Spacing();
try
{
Lumina.Excel.Sheets.Quest? quest = _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>()?.GetRowOrDefault((uint)(questId.Value + 65536));
if (!quest.HasValue)
if (!(_dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>()?.GetRowOrDefault((uint)(questId.Value + 65536))).HasValue)
{
DrawErrorState("Quest Not Found in Game Data", "Unable to load quest information from Lumina.");
return;
}
List<int> list = (from result in quest.Value.TodoParams.Where((Lumina.Excel.Sheets.Quest.TodoParamsStruct todoParamsStruct) => todoParamsStruct.ToDoCompleteSeq > 0 && todoParamsStruct.ToDoCompleteSeq < byte.MaxValue).Select((Func<Lumina.Excel.Sheets.Quest.TodoParamsStruct, int>)((Lumina.Excel.Sheets.Quest.TodoParamsStruct todoParamsStruct) => todoParamsStruct.ToDoCompleteSeq)).Distinct()
orderby result
select result).ToList();
if (list.Count == 0)
List<int> completionSequences = GameQuestSequences.GetCompletionSequences(_dataManager, questId);
if (completionSequences.Count == 0)
{
DrawErrorState("No Sequence Data Available", "This quest may not have traditional sequences.");
return;
}
int num8 = list.Max();
int num8 = completionSequences.Where((int num13) => num13 != 255).DefaultIfEmpty(0).Max();
int num9 = num8 + 1;
Questionable.Model.Quest quest2;
bool flag = _questRegistry.TryGetQuest(questElementId, out quest2);
Questionable.Model.Quest quest;
bool flag = _questRegistry.TryGetQuest(questElementId, out quest);
HashSet<int> hashSet = new HashSet<int>();
Dictionary<int, (int, string)> dictionary = new Dictionary<int, (int, string)>();
bool flag2 = false;
if (flag && quest2 != null && quest2.Root.QuestSequence.Count > 0)
if (flag && quest != null && quest.Root.QuestSequence.Count > 0)
{
foreach (QuestSequence item in quest2.Root.QuestSequence)
foreach (QuestSequence item in quest.Root.QuestSequence)
{
if (item.Sequence == byte.MaxValue)
{

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Dalamud.Configuration;
using Dalamud.Game.Text;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
@ -14,7 +13,6 @@ using Questionable.Model.Questing;
namespace Questionable;
[Obfuscation(Feature = "-rename", Exclude = false, ApplyToMembers = true)]
internal sealed class Configuration : IPluginConfiguration
{
internal sealed class GeneralConfiguration
@ -160,6 +158,8 @@ internal sealed class Configuration : IPluginConfiguration
{
public bool RunSoloInstancesWithBossMod { get; set; }
public bool EnableBossModAi { get; set; } = true;
public ERetryDifficulty RetryDifficulty { get; set; }
public int MaxRetries { get; set; }

View file

@ -2,7 +2,6 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@ -53,7 +52,6 @@ using SmartNav.Data;
namespace Questionable;
[Obfuscation(Feature = "-ctrl flow", Exclude = false, ApplyToMembers = true)]
public sealed class QuestionablePlugin : IAsyncDalamudPlugin, IAsyncDisposable
{
private ServiceProvider? _serviceProvider;
@ -326,6 +324,8 @@ public sealed class QuestionablePlugin : IAsyncDalamudPlugin, IAsyncDisposable
serviceCollection.AddSingleton<TeleportTicketService>();
serviceCollection.AddSingleton((IServiceProvider sp) => new LgbZoneBoundarySource(sp.GetRequiredService<ILogger<LgbZoneBoundarySource>>(), sp.GetRequiredService<SmartNavDataOptions>()));
((IServiceCollection)serviceCollection).AddSingleton((Func<IServiceProvider, IZoneBoundarySource>)((IServiceProvider sp) => sp.GetRequiredService<LgbZoneBoundarySource>()));
serviceCollection.AddSingleton<WaterMapService>();
serviceCollection.AddSingleton<WaterRoutePlanner>();
((IServiceCollection)serviceCollection).AddSingleton((Func<IServiceProvider, ITerritoryInfo>)((IServiceProvider sp) => sp.GetRequiredService<TerritoryData>()));
serviceCollection.AddSingleton<IPlayerContext, QuestionablePlayerContext>();
serviceCollection.AddSingleton<CostCalculator>();