diff --git a/FatePaths/Questionable.FatePaths.FateBundle b/FatePaths/Questionable.FatePaths.FateBundle index 570b2f6..8a21308 100644 Binary files a/FatePaths/Questionable.FatePaths.FateBundle and b/FatePaths/Questionable.FatePaths.FateBundle differ diff --git a/FatePaths/Questionable.FatePaths/PathBundleSecret.cs b/FatePaths/Questionable.FatePaths/PathBundleSecret.cs index b2ec572..e8f2f72 100644 --- a/FatePaths/Questionable.FatePaths/PathBundleSecret.cs +++ b/FatePaths/Questionable.FatePaths/PathBundleSecret.cs @@ -4,18 +4,18 @@ internal static class PathBundleSecret { private static readonly byte[] A = new byte[32] { - 48, 212, 251, 149, 233, 68, 48, 175, 219, 236, - 217, 100, 130, 200, 255, 152, 233, 112, 177, 94, - 93, 198, 133, 46, 110, 244, 154, 244, 247, 13, - 145, 158 + 183, 148, 243, 175, 81, 166, 60, 18, 24, 50, + 233, 21, 123, 58, 154, 174, 171, 214, 240, 163, + 19, 222, 77, 126, 21, 146, 142, 237, 152, 128, + 16, 0 }; private static readonly byte[] B = new byte[32] { - 79, 60, 140, 255, 167, 84, 206, 132, 105, 228, - 71, 209, 251, 6, 250, 62, 38, 86, 249, 66, - 102, 234, 109, 58, 57, 229, 131, 131, 42, 66, - 172, 97 + 167, 25, 83, 195, 35, 102, 138, 74, 178, 77, + 83, 219, 116, 213, 10, 227, 200, 219, 186, 181, + 158, 22, 1, 102, 179, 218, 15, 30, 54, 210, + 214, 232 }; internal static byte[] Key() diff --git a/GatheringPaths/Questionable.GatheringPaths.GatheringBundle b/GatheringPaths/Questionable.GatheringPaths.GatheringBundle index cde9fd5..e2f70c3 100644 Binary files a/GatheringPaths/Questionable.GatheringPaths.GatheringBundle and b/GatheringPaths/Questionable.GatheringPaths.GatheringBundle differ diff --git a/GatheringPaths/Questionable.GatheringPaths/PathBundleSecret.cs b/GatheringPaths/Questionable.GatheringPaths/PathBundleSecret.cs index 4c32249..742c765 100644 --- a/GatheringPaths/Questionable.GatheringPaths/PathBundleSecret.cs +++ b/GatheringPaths/Questionable.GatheringPaths/PathBundleSecret.cs @@ -4,18 +4,18 @@ internal static class PathBundleSecret { private static readonly byte[] A = new byte[32] { - 83, 53, 162, 177, 116, 210, 154, 147, 32, 246, - 109, 49, 21, 10, 185, 40, 3, 223, 158, 234, - 82, 124, 250, 209, 185, 174, 77, 93, 58, 15, - 16, 246 + 219, 178, 133, 86, 37, 102, 10, 221, 178, 245, + 197, 51, 203, 134, 231, 122, 11, 25, 197, 240, + 44, 220, 234, 91, 41, 93, 186, 183, 17, 72, + 109, 31 }; private static readonly byte[] B = new byte[32] { - 11, 0, 215, 132, 153, 74, 202, 77, 15, 86, - 243, 43, 187, 26, 168, 53, 42, 2, 79, 60, - 154, 215, 216, 52, 147, 70, 38, 49, 232, 46, - 225, 130 + 107, 156, 123, 202, 221, 77, 66, 210, 234, 31, + 174, 55, 4, 171, 94, 88, 13, 248, 215, 191, + 115, 73, 208, 117, 230, 149, 32, 243, 105, 23, + 154, 250 }; internal static byte[] Key() diff --git a/LLib/LLib.ImGui/LWindow.cs b/LLib/LLib.ImGui/LWindow.cs index fc3c02a..9dd6437 100644 --- a/LLib/LLib.ImGui/LWindow.cs +++ b/LLib/LLib.ImGui/LWindow.cs @@ -1,11 +1,9 @@ using System; -using System.Reflection; using Dalamud.Bindings.ImGui; using Dalamud.Interface.Windowing; namespace LLib.ImGui; -[Obfuscation(Feature = "-rename", Exclude = false, ApplyToMembers = true)] public abstract class LWindow : Window { private bool _initializedConfig; diff --git a/LLib/LLib.ImGui/WindowConfig.cs b/LLib/LLib.ImGui/WindowConfig.cs index acd18ca..4aa4e7f 100644 --- a/LLib/LLib.ImGui/WindowConfig.cs +++ b/LLib/LLib.ImGui/WindowConfig.cs @@ -1,8 +1,5 @@ -using System.Reflection; - namespace LLib.ImGui; -[Obfuscation(Feature = "-rename", Exclude = false, ApplyToMembers = true)] public class WindowConfig { public bool IsPinned { get; set; } diff --git a/LLib/LLib.Logging/DalamudLoggerProvider.cs b/LLib/LLib.Logging/DalamudLoggerProvider.cs index 710ab76..a6fd3c4 100644 --- a/LLib/LLib.Logging/DalamudLoggerProvider.cs +++ b/LLib/LLib.Logging/DalamudLoggerProvider.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Text; using Dalamud.Plugin.Services; using Microsoft.Extensions.Logging; @@ -120,33 +119,7 @@ public sealed class DalamudLoggerProvider : ILoggerProvider, IDisposable, ISuppo public ILogger CreateLogger(string categoryName) { - string name = _categoryNameFormatter(categoryName); - if (LooksObfuscated(name)) - { - name = null; - } - return new DalamudLogger(name, this, _pluginLog); - } - - private static bool LooksObfuscated(string? name) - { - if (string.IsNullOrEmpty(name)) - { - return false; - } - foreach (char c in name) - { - if (char.IsControl(c)) - { - return true; - } - UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); - if (((uint)(unicodeCategory - 15) <= 2u || unicodeCategory == UnicodeCategory.OtherNotAssigned) ? true : false) - { - return true; - } - } - return false; + return new DalamudLogger(_categoryNameFormatter(categoryName), this, _pluginLog); } public void SetScopeProvider(IExternalScopeProvider scopeProvider) diff --git a/QuestPaths/Questionable.QuestPaths.QuestBundle b/QuestPaths/Questionable.QuestPaths.QuestBundle index d5cde1a..7a1377d 100644 Binary files a/QuestPaths/Questionable.QuestPaths.QuestBundle and b/QuestPaths/Questionable.QuestPaths.QuestBundle differ diff --git a/QuestPaths/Questionable.QuestPaths.QuestSchema b/QuestPaths/Questionable.QuestPaths.QuestSchema index 62f99c6..1f49eae 100644 --- a/QuestPaths/Questionable.QuestPaths.QuestSchema +++ b/QuestPaths/Questionable.QuestPaths.QuestSchema @@ -185,34 +185,58 @@ "description": "If true, will go to the position in a straight line instead of using pathfinding", "type": "boolean" }, + "TravelMode": { + "type": "string", + "enum": [ + "Auto", + "OnFoot", + "GroundMount", + "Flying" + ], + "description": "ROUTE PREFERENCE: How to travel for the full route. Auto lets navigation choose; OnFoot forbids mounting and flying; GroundMount forces a mount but forbids flying; Flying uses flight where it is unlocked. Quest requirements take priority." + }, + "ArrivalMode": { + "type": "string", + "enum": [ + "Auto", + "StayMounted", + "Land", + "Dismount" + ], + "description": "ARRIVAL PREFERENCE: What to do after reaching the destination. This does not change how the route is traveled. Quest requirements take priority." + }, "Mount": { "type": [ "boolean", "null" ], - "description": "Travel hint: If true, will mount regardless of distance to position. If false, will unmount. Ignored by SmartNav auto-decide when not set. For quest-mechanical mount requirements, use MountRequired/DismountRequired instead." + "description": "LEGACY COMPATIBILITY: Old per-movement mount hint retained for existing quests. Use TravelMode and ArrivalMode for new paths.", + "deprecated": true }, "MountRequired": { "type": "boolean", - "description": "Quest mechanic: the quest requires the player to be mounted (e.g. mounted escort, chocobo race). Takes priority over Mount and auto-decide." + "description": "QUEST REQUIREMENT: Use only when the quest mechanic itself requires being mounted, such as a mounted escort or race. Overrides TravelMode and ArrivalMode." }, "DismountRequired": { "type": "boolean", - "description": "Quest mechanic: the quest requires the player to be on foot (e.g. interaction that fails while mounted). Takes priority over Mount and auto-decide." + "description": "QUEST REQUIREMENT: Use only when the quest mechanic itself fails while mounted. Overrides TravelMode and ArrivalMode." }, "Fly": { "type": "boolean", - "description": "If true and flying is unlocked in a zone, will use a flight path" + "description": "LEGACY COMPATIBILITY: Old flight hint retained for existing quests. Use TravelMode for new paths.", + "deprecated": true }, "Land": { "type": "boolean", - "description": "If true and flying, will attempt to land on the ground" + "description": "LEGACY COMPATIBILITY: Old arrival hint retained for existing quests. Use ArrivalMode for new paths.", + "deprecated": true }, "Sprint": { "type": [ "boolean", "null" - ] + ], + "description": "GENERAL MOVEMENT: Controls Sprint independently of mount and flight behavior." }, "ItemId": { "type": [ diff --git a/QuestPaths/Questionable.QuestPaths/PathBundleSecret.cs b/QuestPaths/Questionable.QuestPaths/PathBundleSecret.cs index bf8d03b..51422e2 100644 --- a/QuestPaths/Questionable.QuestPaths/PathBundleSecret.cs +++ b/QuestPaths/Questionable.QuestPaths/PathBundleSecret.cs @@ -4,18 +4,18 @@ internal static class PathBundleSecret { private static readonly byte[] A = new byte[32] { - 73, 4, 92, 189, 214, 0, 53, 235, 105, 164, - 216, 243, 244, 200, 198, 26, 227, 227, 153, 121, - 241, 147, 166, 171, 63, 63, 121, 152, 52, 115, - 89, 201 + 153, 30, 158, 74, 159, 14, 199, 185, 40, 166, + 85, 6, 37, 167, 5, 38, 221, 72, 99, 65, + 160, 20, 49, 186, 80, 230, 97, 96, 19, 189, + 194, 228 }; private static readonly byte[] B = new byte[32] { - 243, 132, 180, 1, 83, 89, 202, 66, 33, 20, - 242, 184, 99, 15, 215, 2, 4, 125, 221, 134, - 186, 51, 132, 151, 141, 220, 230, 57, 252, 77, - 103, 57 + 30, 173, 221, 145, 116, 199, 45, 52, 72, 186, + 186, 181, 129, 66, 39, 24, 195, 19, 249, 56, + 14, 14, 134, 179, 100, 23, 222, 226, 221, 184, + 242, 90 }; internal static byte[] Key() diff --git a/Questionable.Model/Questionable.Model.Questing.Converter/ArrivalModeConverter.cs b/Questionable.Model/Questionable.Model.Questing.Converter/ArrivalModeConverter.cs new file mode 100644 index 0000000..6e79f23 --- /dev/null +++ b/Questionable.Model/Questionable.Model.Questing.Converter/ArrivalModeConverter.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using SmartNav.Model.Converter; + +namespace Questionable.Model.Questing.Converter; + +public sealed class ArrivalModeConverter : EnumConverter +{ + private static readonly Dictionary Values = new Dictionary + { + { + EArrivalMode.Auto, + "Auto" + }, + { + EArrivalMode.StayMounted, + "StayMounted" + }, + { + EArrivalMode.Land, + "Land" + }, + { + EArrivalMode.Dismount, + "Dismount" + } + }; + + public ArrivalModeConverter() + : base((IReadOnlyDictionary)Values) + { + } +} diff --git a/Questionable.Model/Questionable.Model.Questing.Converter/TravelModeConverter.cs b/Questionable.Model/Questionable.Model.Questing.Converter/TravelModeConverter.cs new file mode 100644 index 0000000..3fe5b27 --- /dev/null +++ b/Questionable.Model/Questionable.Model.Questing.Converter/TravelModeConverter.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using SmartNav.Model.Converter; + +namespace Questionable.Model.Questing.Converter; + +public sealed class TravelModeConverter : EnumConverter +{ + private static readonly Dictionary Values = new Dictionary + { + { + ETravelMode.Auto, + "Auto" + }, + { + ETravelMode.OnFoot, + "OnFoot" + }, + { + ETravelMode.GroundMount, + "GroundMount" + }, + { + ETravelMode.Flying, + "Flying" + } + }; + + public TravelModeConverter() + : base((IReadOnlyDictionary)Values) + { + } +} diff --git a/Questionable.Model/Questionable.Model.Questing/EArrivalMode.cs b/Questionable.Model/Questionable.Model.Questing/EArrivalMode.cs new file mode 100644 index 0000000..288cd7d --- /dev/null +++ b/Questionable.Model/Questionable.Model.Questing/EArrivalMode.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using Questionable.Model.Questing.Converter; + +namespace Questionable.Model.Questing; + +[JsonConverter(typeof(ArrivalModeConverter))] +public enum EArrivalMode +{ + Auto, + StayMounted, + Land, + Dismount +} diff --git a/Questionable.Model/Questionable.Model.Questing/ETravelMode.cs b/Questionable.Model/Questionable.Model.Questing/ETravelMode.cs new file mode 100644 index 0000000..91e9191 --- /dev/null +++ b/Questionable.Model/Questionable.Model.Questing/ETravelMode.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using Questionable.Model.Questing.Converter; + +namespace Questionable.Model.Questing; + +[JsonConverter(typeof(TravelModeConverter))] +public enum ETravelMode +{ + Auto, + OnFoot, + GroundMount, + Flying +} diff --git a/Questionable.Model/Questionable.Model.Questing/QuestStep.cs b/Questionable.Model/Questionable.Model.Questing/QuestStep.cs index c4b6394..fcfaa9a 100644 --- a/Questionable.Model/Questionable.Model.Questing/QuestStep.cs +++ b/Questionable.Model/Questionable.Model.Questing/QuestStep.cs @@ -45,6 +45,10 @@ public sealed class QuestStep public bool DisableNavmesh { get; set; } + public ETravelMode TravelMode { get; set; } + + public EArrivalMode ArrivalMode { get; set; } + public bool? Mount { get; set; } public bool MountRequired { get; set; } diff --git a/Questionable/Questionable.Controller.CombatModules/BossModModule.cs b/Questionable/Questionable.Controller.CombatModules/BossModModule.cs index 5587599..520bbb2 100644 --- a/Questionable/Questionable.Controller.CombatModules/BossModModule.cs +++ b/Questionable/Questionable.Controller.CombatModules/BossModModule.cs @@ -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) diff --git a/Questionable/Questionable.Controller.CombatModules/ItemUseModule.cs b/Questionable/Questionable.Controller.CombatModules/ItemUseModule.cs index 99a926f..1d9939d 100644 --- a/Questionable/Questionable.Controller.CombatModules/ItemUseModule.cs +++ b/Questionable/Questionable.Controller.CombatModules/ItemUseModule.cs @@ -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 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 diff --git a/Questionable/Questionable.Controller.Steps.Interactions/Dive.cs b/Questionable/Questionable.Controller.Steps.Interactions/Dive.cs index 08b327e..3599c5e 100644 --- a/Questionable/Questionable.Controller.Steps.Interactions/Dive.cs +++ b/Questionable/Questionable.Controller.Steps.Interactions/Dive.cs @@ -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(); diff --git a/Questionable/Questionable.Controller.Steps.Interactions/Duty.cs b/Questionable/Questionable.Controller.Steps.Interactions/Duty.cs index 0aa29fb..5b76c7f 100644 --- a/Questionable/Questionable.Controller.Steps.Interactions/Duty.cs +++ b/Questionable/Questionable.Controller.Steps.Interactions/Duty.cs @@ -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; } diff --git a/Questionable/Questionable.Controller.Steps.Interactions/EquipRecommended.cs b/Questionable/Questionable.Controller.Steps.Interactions/EquipRecommended.cs index 6f8029b..368af29 100644 --- a/Questionable/Questionable.Controller.Steps.Interactions/EquipRecommended.cs +++ b/Questionable/Questionable.Controller.Steps.Interactions/EquipRecommended.cs @@ -80,6 +80,8 @@ internal static class EquipRecommended private bool _checkedOrTriggeredEquipmentUpdate; + private List? _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 SnapshotRecommendedItemIds(RecommendEquipModule* recommendedEquipModule) { - Span> recommendedItems = RecommendEquipModule.Instance()->RecommendedItems; + List list = new List(); + Span> recommendedItems = recommendedEquipModule->RecommendedItems; for (int i = 0; i < recommendedItems.Length; i++) { Pointer 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 expectedItemIds) + { + InventoryManager* ptr = InventoryManager.Instance(); + if (ptr == null) + { + return false; + } + InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(InventoryType.EquippedItems); + if (inventoryContainer == null) + { + return false; + } + Dictionary dictionary = new Dictionary(); + 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() diff --git a/Questionable/Questionable.Controller.Steps.Interactions/Interact.cs b/Questionable/Questionable.Controller.Steps.Interactions/Interact.cs index 72500ee..a4b9502 100644 --- a/Questionable/Questionable.Controller.Steps.Interactions/Interact.cs +++ b/Questionable/Questionable.Controller.Steps.Interactions/Interact.cs @@ -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 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 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(); + } + List 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; } } diff --git a/Questionable/Questionable.Controller.Steps.Interactions/SinglePlayerDuty.cs b/Questionable/Questionable.Controller.Steps.Interactions/SinglePlayerDuty.cs index cb28962..e6d168e 100644 --- a/Questionable/Questionable.Controller.Steps.Interactions/SinglePlayerDuty.cs +++ b/Questionable/Questionable.Controller.Steps.Interactions/SinglePlayerDuty.cs @@ -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 CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { @@ -54,7 +54,12 @@ internal static class SinglePlayerDuty byte seqBefore = questProgressInfo?.Sequence ?? 0; IReadOnlyList varsBefore = questProgressInfo?.Variables.ToArray(); dutyRetryState.Reset(); - List dutyTasks = new List(); + List dutyTasks = new List + { + 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() { - 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; } diff --git a/Questionable/Questionable.Controller.Steps.Movement/MoveExecutor.cs b/Questionable/Questionable.Controller.Steps.Movement/MoveExecutor.cs index eaa3469..1280c36 100644 --- a/Questionable/Questionable.Controller.Steps.Movement/MoveExecutor.cs +++ b/Questionable/Questionable.Controller.Steps.Movement/MoveExecutor.cs @@ -130,10 +130,10 @@ internal sealed class MoveExecutor : TaskExecutor, 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, 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, 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, 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, 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 list = new List(num2); CollectionsMarshal.SetCount(list, num2); - CollectionsMarshal.AsSpan(list)[0] = vector; + Span 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, 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, 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; diff --git a/Questionable/Questionable.Controller.Steps.Movement/MoveTask.cs b/Questionable/Questionable.Controller.Steps.Movement/MoveTask.cs index 32ad580..1816d39 100644 --- a/Questionable/Questionable.Controller.Steps.Movement/MoveTask.cs +++ b/Questionable/Questionable.Controller.Steps.Movement/MoveTask.cs @@ -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; diff --git a/Questionable/Questionable.Controller.Steps.Movement/MoveTo.cs b/Questionable/Questionable.Controller.Steps.Movement/MoveTo.cs index 33e1172..f69d1b9 100644 --- a/Questionable/Questionable.Controller.Steps.Movement/MoveTo.cs +++ b/Questionable/Questionable.Controller.Steps.Movement/MoveTo.cs @@ -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(); + } } } } diff --git a/Questionable/Questionable.Controller.Steps.Shared/SmartNavStep.cs b/Questionable/Questionable.Controller.Steps.Shared/SmartNavStep.cs index b41e655..5fa93fb 100644 --- a/Questionable/Questionable.Controller.Steps.Shared/SmartNavStep.cs +++ b/Questionable/Questionable.Controller.Steps.Shared/SmartNavStep.cs @@ -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; } } diff --git a/Questionable/Questionable.Controller/CombatController.cs b/Questionable/Questionable.Controller/CombatController.cs index cb6b6f0..1aa0b29 100644 --- a/Questionable/Questionable.Controller/CombatController.cs +++ b/Questionable/Questionable.Controller/CombatController.cs @@ -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]) { diff --git a/Questionable/Questionable.Controller/MovementController.cs b/Questionable/Questionable.Controller/MovementController.cs index 7cac6b2..0ccaecd 100644 --- a/Questionable/Questionable.Controller/MovementController.cs +++ b/Questionable/Questionable.Controller/MovementController.cs @@ -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(); } diff --git a/Questionable/Questionable.Controller/QuestController.cs b/Questionable/Questionable.Controller/QuestController.cs index 47d0900..9c99277 100644 --- a/Questionable/Questionable.Controller/QuestController.cs +++ b/Questionable/Questionable.Controller/QuestController.cs @@ -1538,6 +1538,10 @@ internal sealed class QuestController : MiniTaskController { 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(); diff --git a/Questionable/Questionable.Controller/QuestPriorityResolver.cs b/Questionable/Questionable.Controller/QuestPriorityResolver.cs index ab95243..76796cf 100644 --- a/Questionable/Questionable.Controller/QuestPriorityResolver.cs +++ b/Questionable/Questionable.Controller/QuestPriorityResolver.cs @@ -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) diff --git a/Questionable/Questionable.Data/ChangelogData.cs b/Questionable/Questionable.Data/ChangelogData.cs index b76a110..5aa65b5 100644 --- a/Questionable/Questionable.Data/ChangelogData.cs +++ b/Questionable/Questionable.Data/ChangelogData.cs @@ -11,357 +11,372 @@ internal static class ChangelogData static ChangelogData() { - int num = 77; + int num = 78; List list = new List(num); CollectionsMarshal.SetCount(list, num); Span span = CollectionsMarshal.AsSpan(list); ref ChangelogEntry reference = ref span[0]; - DateOnly releaseDate = new DateOnly(2026, 8, 17); - int num2 = 5; + DateOnly releaseDate = new DateOnly(2026, 8, 21); + int num2 = 4; List list2 = new List(num2); CollectionsMarshal.SetCount(list2, num2); Span span2 = CollectionsMarshal.AsSpan(list2); ref ChangeEntry reference2 = ref span2[0]; - int num3 = 5; + int num3 = 4; List list3 = new List(num3); CollectionsMarshal.SetCount(list3, num3); Span span3 = CollectionsMarshal.AsSpan(list3); - span3[0] = "Added a fixed-height, scrollable list to Queue -> Saved Presets."; - span3[1] = "Added an always-on selected-step world preview to the debug Quest Editor, including targetability and required quest variables."; - span3[2] = "Added step duplication to the debug Quest Editor."; - span3[3] = "Added an option to abandon the active quest from the main Questionable window."; - span3[4] = "Added automatic continuation after manually completed duties."; + span3[0] = "Added target state and control-flag details to the debug overlay and Quest Editor previews."; + span3[1] = "Added a setting under \"Quest Battles\" to control BossMod AI usage during battles."; + span3[2] = "Added Travel Mode and Arrival Mode controls for quest routes, including on-foot, ground-mount, flying, landing, and dismount preferences."; + span3[3] = "Added SmartNav support for swimming, diving, and surfacing while routing through water."; reference2 = new ChangeEntry(EChangeCategory.Added, "Additions", list3); ref ChangeEntry reference3 = ref span2[1]; - num3 = 5; + num3 = 4; List list4 = new List(num3); CollectionsMarshal.SetCount(list4, num3); Span span4 = CollectionsMarshal.AsSpan(list4); - span4[0] = "LGB-derived data (NPC positions and zone boundaries) now ships with the plugin."; - span4[1] = "Automatically use reward items after quest complete as well as on quest accept."; - span4[2] = "Added copy and paste for stop conditions and blacklisted quests, and restored drag reordering for stop conditions."; - span4[3] = "Prioritized starting city-state quests needed to unlock the full city over class and job quests."; - span4[4] = "Improved Rotation Solver Reborn target handling to avoid unnecessary target changes."; + span4[0] = "Do not start duty until player exits the \"Occupied\" state."; + span4[1] = "Force starting city quests to execute in specific order."; + span4[2] = "Warp-triggering interactions now confirm prompts and wait for same-territory transitions."; + span4[3] = "Recommended-gear steps now wait for the requested equipment changes and stop automation if equipping fails."; reference3 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list4); ref ChangeEntry reference4 = ref span2[2]; - num3 = 16; + num3 = 2; List list5 = new List(num3); CollectionsMarshal.SetCount(list5, num3); Span span5 = CollectionsMarshal.AsSpan(list5); - span5[0] = "Fixed timeout state being triggered if character status is 'Occupied'."; - span5[1] = "Prevented DoGather exceptions when gathering slots are empty or the requested collectable is unavailable."; - span5[2] = "Fixed Custom Deliveries selecting or attempting to turn in an item that is no longer requested."; - span5[3] = "Fixed Custom Delivery travel into the Firmament getting stuck at the gatekeeper."; - span5[4] = "Fixed Custom Delivery navigation not recovering when pathfinding to the delivery NPC fails after gathering."; - span5[5] = "Fixed Smart Gear Upgrades unequipping gear when the current gearset contains stale or mismatched data."; - span5[6] = "Fixed Questionable selecting quests that were hidden in the in-game Journal."; - span5[7] = "Fixed 'Don't pick up class/job quests' preventing Questionable from continuing with an available MSQ."; - span5[8] = "Fixed required quest-variable baselines being lost on data reload or carried into a later quest sequence."; - span5[9] = "Fixed quest and event items appearing as unknown items in the debug Quest Editor."; - span5[10] = "Fixed rejected AutoDuty runs hanging instead of failing promptly."; - span5[11] = "Fixed aethernet travel failing when the local player was temporarily unavailable at startup."; - span5[12] = "Fixed Custom Deliveries unnecessarily repositioning near delivery NPCs or failing to unmount on direct approaches."; - span5[13] = "Fixed ignored quests being selected from the priority queue."; - span5[14] = "Fixed questing not resuming correctly after duties, including premature sequence transitions and Field Operations being treated as duties."; - span5[15] = "Fixed Fishing navigation around unlandable fishing markers and the initial cast after AutoHook setup."; + span5[0] = "Fixed landing & interaction range movement logic."; + span5[1] = "Fixed combat state dropping prematurely when item usage was interrupted by player rotation."; reference4 = new ChangeEntry(EChangeCategory.Fixed, "Resolved", list5); ref ChangeEntry reference5 = ref span2[3]; - num3 = 2; + num3 = 7; List list6 = new List(num3); CollectionsMarshal.SetCount(list6, num3); Span span6 = CollectionsMarshal.AsSpan(list6); - span6[0] = "Removed the background LGB scan and its out-of-process worker - there is no first-load disk or memory spike after a game patch anymore."; - span6[1] = "Removed the \"Clear LGB Caches\" button, as there are no LGB caches left to clear."; - reference5 = new ChangeEntry(EChangeCategory.Removed, "Retired", list6); - ref ChangeEntry reference6 = ref span2[4]; - num3 = 3; - List list7 = new List(num3); - CollectionsMarshal.SetCount(list7, num3); - Span span7 = CollectionsMarshal.AsSpan(list7); - span7[0] = "Updated Moonfire Faire (2026) quest 'Earning Your Water Wings' to pick up the subsequent event quest, 'Whoever Floats Your Raft'."; - span7[1] = "Added the Phantom Village aethernet destination and the final relic quest, 'All Too Fleeting'."; - span7[2] = "Added paths for the Amalj'aa daily quests 'Smothering Instinct' and 'Ravage the Ravagers'."; - reference6 = new ChangeEntry(EChangeCategory.QuestUpdates, "Quest Path Updates", list7); - reference = new ChangelogEntry("7.5.12", releaseDate, list2); - ref ChangelogEntry reference7 = ref span[1]; - DateOnly releaseDate2 = new DateOnly(2026, 8, 13); - num2 = 3; - List list8 = new List(num2); - CollectionsMarshal.SetCount(list8, num2); - Span span8 = CollectionsMarshal.AsSpan(list8); - ref ChangeEntry reference8 = ref span8[0]; - num3 = 1; + span6[0] = "In \"Hatchling Humiliation\", force dismount before Combat step."; + span6[1] = "Consolidate \"Elite Beating\" Combat events into a single step."; + span6[2] = "Force dismount & walking to Aether Current position for \"Waiting for the Wind to Change\"."; + span6[3] = "Add \"Jump\" step for \"Waiting for the Wind to Change\"."; + span6[4] = "Disable navmesh for territory boundary for \"A Great New Nation\"."; + span6[5] = "Fix various navigation issues with SmartNav for the Syrcus Tower questline."; + span6[6] = "Split the Glowing Chimera into a second combat wave for \"Chimerical Maintenance\" so combat automation remains active."; + reference5 = new ChangeEntry(EChangeCategory.QuestUpdates, "Quest Path Updates", list6); + reference = new ChangelogEntry("7.5.13", releaseDate, list2); + ref ChangelogEntry reference6 = ref span[1]; + DateOnly releaseDate2 = new DateOnly(2026, 8, 17); + num2 = 5; + List list7 = new List(num2); + CollectionsMarshal.SetCount(list7, num2); + Span span7 = CollectionsMarshal.AsSpan(list7); + ref ChangeEntry reference7 = ref span7[0]; + num3 = 5; + List list8 = new List(num3); + CollectionsMarshal.SetCount(list8, num3); + Span span8 = CollectionsMarshal.AsSpan(list8); + span8[0] = "Added a fixed-height, scrollable list to Queue -> Saved Presets."; + span8[1] = "Added an always-on selected-step world preview to the debug Quest Editor, including targetability and required quest variables."; + span8[2] = "Added step duplication to the debug Quest Editor."; + span8[3] = "Added an option to abandon the active quest from the main Questionable window."; + span8[4] = "Added automatic continuation after manually completed duties."; + reference7 = new ChangeEntry(EChangeCategory.Added, "Additions", list8); + ref ChangeEntry reference8 = ref span7[1]; + num3 = 5; List list9 = new List(num3); CollectionsMarshal.SetCount(list9, num3); - CollectionsMarshal.AsSpan(list9)[0] = "Added autodetection of your Grand Company based on player's starting city."; - reference8 = new ChangeEntry(EChangeCategory.Added, "Added", list9); - ref ChangeEntry reference9 = ref span8[1]; - num3 = 1; + Span span9 = CollectionsMarshal.AsSpan(list9); + span9[0] = "LGB-derived data (NPC positions and zone boundaries) now ships with the plugin."; + span9[1] = "Automatically use reward items after quest complete as well as on quest accept."; + span9[2] = "Added copy and paste for stop conditions and blacklisted quests, and restored drag reordering for stop conditions."; + span9[3] = "Prioritized starting city-state quests needed to unlock the full city over class and job quests."; + span9[4] = "Improved Rotation Solver Reborn target handling to avoid unnecessary target changes."; + reference8 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list9); + ref ChangeEntry reference9 = ref span7[2]; + num3 = 16; List list10 = new List(num3); CollectionsMarshal.SetCount(list10, num3); - CollectionsMarshal.AsSpan(list10)[0] = "Updated Moonfire Faire (2026) event paths to go around the pool."; - reference9 = new ChangeEntry(EChangeCategory.Changed, "Changed", list10); - ref ChangeEntry reference10 = ref span8[2]; - num3 = 3; + Span span10 = CollectionsMarshal.AsSpan(list10); + span10[0] = "Fixed timeout state being triggered if character status is 'Occupied'."; + span10[1] = "Prevented DoGather exceptions when gathering slots are empty or the requested collectable is unavailable."; + span10[2] = "Fixed Custom Deliveries selecting or attempting to turn in an item that is no longer requested."; + span10[3] = "Fixed Custom Delivery travel into the Firmament getting stuck at the gatekeeper."; + span10[4] = "Fixed Custom Delivery navigation not recovering when pathfinding to the delivery NPC fails after gathering."; + span10[5] = "Fixed Smart Gear Upgrades unequipping gear when the current gearset contains stale or mismatched data."; + span10[6] = "Fixed Questionable selecting quests that were hidden in the in-game Journal."; + span10[7] = "Fixed 'Don't pick up class/job quests' preventing Questionable from continuing with an available MSQ."; + span10[8] = "Fixed required quest-variable baselines being lost on data reload or carried into a later quest sequence."; + span10[9] = "Fixed quest and event items appearing as unknown items in the debug Quest Editor."; + span10[10] = "Fixed rejected AutoDuty runs hanging instead of failing promptly."; + span10[11] = "Fixed aethernet travel failing when the local player was temporarily unavailable at startup."; + span10[12] = "Fixed Custom Deliveries unnecessarily repositioning near delivery NPCs or failing to unmount on direct approaches."; + span10[13] = "Fixed ignored quests being selected from the priority queue."; + span10[14] = "Fixed questing not resuming correctly after duties, including premature sequence transitions and Field Operations being treated as duties."; + span10[15] = "Fixed Fishing navigation around unlandable fishing markers and the initial cast after AutoHook setup."; + reference9 = new ChangeEntry(EChangeCategory.Fixed, "Resolved", list10); + ref ChangeEntry reference10 = ref span7[3]; + num3 = 2; List list11 = new List(num3); CollectionsMarshal.SetCount(list11, num3); - Span span9 = CollectionsMarshal.AsSpan(list11); - span9[0] = "Fix an issue where Custom Deliveries were not working due to Questionable attempting to use old quest presets."; - span9[1] = "Fix race condition where Questionable is attempting to call Bossmod IPC before Bossmod had finished loading."; - span9[2] = "Fix Bossmod AI not working due to Questionable intializing an empty preset."; - reference10 = new ChangeEntry(EChangeCategory.Fixed, "Fixed", list11); - reference7 = new ChangelogEntry("7.5.11", releaseDate2, list8); - ref ChangelogEntry reference11 = ref span[2]; - DateOnly releaseDate3 = new DateOnly(2026, 8, 12); - num2 = 1; - List list12 = new List(num2); - CollectionsMarshal.SetCount(list12, num2); - ref ChangeEntry reference12 = ref CollectionsMarshal.AsSpan(list12)[0]; + Span span11 = CollectionsMarshal.AsSpan(list11); + span11[0] = "Removed the background LGB scan and its out-of-process worker - there is no first-load disk or memory spike after a game patch anymore."; + span11[1] = "Removed the \"Clear LGB Caches\" button, as there are no LGB caches left to clear."; + reference10 = new ChangeEntry(EChangeCategory.Removed, "Retired", list11); + ref ChangeEntry reference11 = ref span7[4]; + num3 = 3; + List list12 = new List(num3); + CollectionsMarshal.SetCount(list12, num3); + Span span12 = CollectionsMarshal.AsSpan(list12); + span12[0] = "Updated Moonfire Faire (2026) quest 'Earning Your Water Wings' to pick up the subsequent event quest, 'Whoever Floats Your Raft'."; + span12[1] = "Added the Phantom Village aethernet destination and the final relic quest, 'All Too Fleeting'."; + span12[2] = "Added paths for the Amalj'aa daily quests 'Smothering Instinct' and 'Ravage the Ravagers'."; + reference11 = new ChangeEntry(EChangeCategory.QuestUpdates, "Quest Path Updates", list12); + reference6 = new ChangelogEntry("7.5.12", releaseDate2, list7); + ref ChangelogEntry reference12 = ref span[2]; + DateOnly releaseDate3 = new DateOnly(2026, 8, 13); + num2 = 3; + List list13 = new List(num2); + CollectionsMarshal.SetCount(list13, num2); + Span span13 = CollectionsMarshal.AsSpan(list13); + ref ChangeEntry reference13 = ref span13[0]; num3 = 1; - List list13 = new List(num3); - CollectionsMarshal.SetCount(list13, num3); - CollectionsMarshal.AsSpan(list13)[0] = "This version was intended to roll back early changes made to 7.5.9 before it was ready for publishing."; - reference12 = new ChangeEntry(EChangeCategory.Removed, "Rollback", list13); - reference11 = new ChangelogEntry("7.5.10", releaseDate3, list12); - ref ChangelogEntry reference13 = ref span[3]; - DateOnly releaseDate4 = new DateOnly(2026, 8, 12); - num2 = 1; - List list14 = new List(num2); - CollectionsMarshal.SetCount(list14, num2); - ref ChangeEntry reference14 = ref CollectionsMarshal.AsSpan(list14)[0]; + List list14 = new List(num3); + CollectionsMarshal.SetCount(list14, num3); + CollectionsMarshal.AsSpan(list14)[0] = "Added autodetection of your Grand Company based on player's starting city."; + reference13 = new ChangeEntry(EChangeCategory.Added, "Added", list14); + ref ChangeEntry reference14 = ref span13[1]; num3 = 1; List list15 = new List(num3); CollectionsMarshal.SetCount(list15, num3); - CollectionsMarshal.AsSpan(list15)[0] = "Fixed an issue which causes the plugin to fail to load and Dalamud to crash in rare occasions."; - reference14 = new ChangeEntry(EChangeCategory.Fixed, "Fixed", list15); - reference13 = new ChangelogEntry("7.5.9", releaseDate4, list14); - ref ChangelogEntry reference15 = ref span[4]; + CollectionsMarshal.AsSpan(list15)[0] = "Updated Moonfire Faire (2026) event paths to go around the pool."; + reference14 = new ChangeEntry(EChangeCategory.Changed, "Changed", list15); + ref ChangeEntry reference15 = ref span13[2]; + num3 = 3; + List list16 = new List(num3); + CollectionsMarshal.SetCount(list16, num3); + Span span14 = CollectionsMarshal.AsSpan(list16); + span14[0] = "Fix an issue where Custom Deliveries were not working due to Questionable attempting to use old quest presets."; + span14[1] = "Fix race condition where Questionable is attempting to call Bossmod IPC before Bossmod had finished loading."; + span14[2] = "Fix Bossmod AI not working due to Questionable intializing an empty preset."; + reference15 = new ChangeEntry(EChangeCategory.Fixed, "Fixed", list16); + reference12 = new ChangelogEntry("7.5.11", releaseDate3, list13); + ref ChangelogEntry reference16 = ref span[3]; + DateOnly releaseDate4 = new DateOnly(2026, 8, 12); + num2 = 1; + List list17 = new List(num2); + CollectionsMarshal.SetCount(list17, num2); + ref ChangeEntry reference17 = ref CollectionsMarshal.AsSpan(list17)[0]; + num3 = 1; + List list18 = new List(num3); + CollectionsMarshal.SetCount(list18, num3); + CollectionsMarshal.AsSpan(list18)[0] = "This version was intended to roll back early changes made to 7.5.9 before it was ready for publishing."; + reference17 = new ChangeEntry(EChangeCategory.Removed, "Rollback", list18); + reference16 = new ChangelogEntry("7.5.10", releaseDate4, list17); + ref ChangelogEntry reference18 = ref span[4]; DateOnly releaseDate5 = new DateOnly(2026, 8, 12); num2 = 1; - List list16 = new List(num2); - CollectionsMarshal.SetCount(list16, num2); - ref ChangeEntry reference16 = ref CollectionsMarshal.AsSpan(list16)[0]; + List list19 = new List(num2); + CollectionsMarshal.SetCount(list19, num2); + ref ChangeEntry reference19 = ref CollectionsMarshal.AsSpan(list19)[0]; num3 = 1; - List list17 = new List(num3); - CollectionsMarshal.SetCount(list17, num3); - CollectionsMarshal.AsSpan(list17)[0] = "This version was intended to roll back early changes made to 7.5.9 before it was ready for publishing."; - reference16 = new ChangeEntry(EChangeCategory.Removed, "Rollback", list17); - reference15 = new ChangelogEntry("7.5.10", releaseDate5, list16); - ref ChangelogEntry reference17 = ref span[5]; + List list20 = new List(num3); + CollectionsMarshal.SetCount(list20, num3); + CollectionsMarshal.AsSpan(list20)[0] = "Fixed an issue which causes the plugin to fail to load and Dalamud to crash in rare occasions."; + reference19 = new ChangeEntry(EChangeCategory.Fixed, "Fixed", list20); + reference18 = new ChangelogEntry("7.5.9", releaseDate5, list19); + ref ChangelogEntry reference20 = ref span[5]; DateOnly releaseDate6 = new DateOnly(2026, 8, 12); num2 = 1; - List list18 = new List(num2); - CollectionsMarshal.SetCount(list18, num2); - ref ChangeEntry reference18 = ref CollectionsMarshal.AsSpan(list18)[0]; - num3 = 2; - List list19 = new List(num3); - CollectionsMarshal.SetCount(list19, num3); - Span span10 = CollectionsMarshal.AsSpan(list19); - span10[0] = "Move LGB scanning (NPC positions and zone boundaries) to an out-of-process worker to eliminate memory spikes on first load and after game patches"; - span10[1] = "Add Clear LGB Caches button in Advanced settings with cache size tooltip"; - reference18 = new ChangeEntry(EChangeCategory.Changed, "Changed", list19); - reference17 = new ChangelogEntry("7.5.7", releaseDate6, list18); - ref ChangelogEntry reference19 = ref span[6]; - DateOnly releaseDate7 = new DateOnly(2026, 8, 12); - num2 = 4; - List list20 = new List(num2); - CollectionsMarshal.SetCount(list20, num2); - Span span11 = CollectionsMarshal.AsSpan(list20); - ref ChangeEntry reference20 = ref span11[0]; - num3 = 35; - List list21 = new List(num3); - CollectionsMarshal.SetCount(list21, num3); - Span span12 = CollectionsMarshal.AsSpan(list21); - span12[0] = "Add SmartNav automatic cross-zone routing with real-time gil cost awareness"; - span12[1] = "Add WigglyNav as an alternative navigation provider with provider-aware navmesh rebuild"; - span12[2] = "Add Custom Delivery automation with crafting, gathering, fishing, vendor purchases, and job selection"; - span12[3] = "Add Attunement tab to Journal with aetheryte/aethernet/aether current status and batch attune"; - span12[4] = "Add Fishing automation with AutoHook preset management and bait vendor resolution"; - span12[5] = "Add allied society overcap protection with reputation-aware turn-in ordering"; - span12[6] = "Add quest chain graph with queue management in Journal"; - span12[7] = "Add Gathering Editor as a sub-panel to Quest Editor Window"; - span12[8] = "Add smart equip with three modes: Game Default, Smart (stat-aware), and None"; - span12[9] = "Add gear coffer opening from quest rewards with gearset switching"; - span12[10] = "Add automatic repair at quest boundaries and duty gates via self-repair and menders"; - span12[11] = "Add out-of-game notifications with per-event alert toggles and sound selection"; - span12[12] = "Add run-command-after-stop for executing a command when a stop condition fires"; - span12[13] = "Add reward redemption settings with per-type filtering and skip-tradable option"; - span12[14] = "Add UnlockLink reward type for unlock-link quest rewards"; - span12[15] = "Add teleport ticket support for cheaper cross-zone travel"; - span12[16] = "Add chocobo taxi routing for zones with taxi stands"; - span12[17] = "Add warp-based travel (elevators, ferries, zone transitions)"; - span12[18] = "Add Critical Encounter and Fishing quest step types"; - span12[19] = "Add auto-unsync for 4-man dungeons when safely overleveled"; - span12[20] = "Add death recovery system that auto-respawns and re-routes"; - span12[21] = "Add RetryStep system that re-navigates on interaction failures"; - span12[22] = "Add UnequipItem step executor"; - span12[23] = "Add unified stop condition system with QuestCount, Level, and Sequence conditions"; - span12[24] = "Add setup wizard with guided steps, re-openable via command"; - span12[25] = "Add BossModReborn support alongside BossMod"; - span12[26] = "Add WrathCombo integration with explicit combo state management"; - span12[27] = "Add per-feature control for external plugin pausing"; - span12[28] = "Add hot-reloading and in-memory quest testing to Quest Editor"; - span12[29] = "Add Navigation config tab with mount, flying, and gil reserve settings"; - span12[30] = "Add config backup before version migrations"; - span12[31] = "Add option to disable coffer opening (None mode)"; - span12[32] = "Add detection of installed-but-disabled plugins with Enable button"; - span12[33] = "Add plugin alternative listing (vnavmesh-cn, BossModReborn) with conflict warnings"; - span12[34] = "Add accepted quests with no path shown with live game sequence"; - reference20 = new ChangeEntry(EChangeCategory.Added, "Added", list21); - ref ChangeEntry reference21 = ref span11[1]; - num3 = 17; + List list21 = new List(num2); + CollectionsMarshal.SetCount(list21, num2); + ref ChangeEntry reference21 = ref CollectionsMarshal.AsSpan(list21)[0]; + num3 = 1; List list22 = new List(num3); CollectionsMarshal.SetCount(list22, num3); - Span span13 = CollectionsMarshal.AsSpan(list22); - span13[0] = "Redesign Configuration window with grouped sidebar navigation and themed cards"; - span13[1] = "Redesign Journal with sidebar navigation, accordion layouts, and tracker tables"; - span13[2] = "Redesign main quest window with themed banners, titles, and stop buttons"; - span13[3] = "Redesign FATE and Seasonal Duty selection windows with search and shared cycle UI"; - span13[4] = "Replace quest-path-based custom deliveries with dynamic automation controller"; - span13[5] = "Move path data to external repository for community contributions"; - span13[6] = "Install and enable required plugins directly from the Dependencies tab"; - span13[7] = "Faster startup with parallel data loading"; - span13[8] = "SmartNav automatically determines mount, fly, and teleport decisions (replaces manual shortcuts)"; - span13[9] = "Replace native aethernet teleportation with direct implementation (removes Lifestream dependency)"; - span13[10] = "Remove aether current and city aethernet quest presets (replaced by Attunement tab)"; - span13[11] = "Remove 'Stop after current quest' and quest-count/timer stop buttons from main window"; - span13[12] = "Remove 'View All Quest Paths Online' button from journal"; - span13[13] = "Remove Explanation dropdown from Quests and Duties tabs"; - span13[14] = "Virtualize quest, duty, and gathering journals for better performance"; - span13[15] = "Default custom delivery scrip overcap mode to Ignore"; - span13[16] = "Default quest journal groups to collapsed"; - reference21 = new ChangeEntry(EChangeCategory.Changed, "Changed", list22); - ref ChangeEntry reference22 = ref span11[2]; - num3 = 41; - List list23 = new List(num3); - CollectionsMarshal.SetCount(list23, num3); - Span span14 = CollectionsMarshal.AsSpan(list23); - span14[0] = "Fix zone transition hangs and re-routing after zone changes"; - span14[1] = "Fix flying route decisions based on mount ownership and flying unlock state"; - span14[2] = "Fix warp routing filtering by quest requirements and class level"; - span14[3] = "Fix zone boundary access respecting per-boundary quest requirements"; - span14[4] = "Fix custom delivery craft purchases, turn-in timeouts, and rank-up resume"; - span14[5] = "Fix custom delivery keeping queued deliveries when inventory is full"; - span14[6] = "Fix fishing approach rebuild on retry and no-progress timeout"; - span14[7] = "Fix smart equip ranking weapons by weapon damage and job-usable stats"; - span14[8] = "Fix repair retry when gear condition did not improve"; - span14[9] = "Fix gathering retry counter reset and node integrity checks"; - span14[10] = "Fix reward redemption for tradeable items and stale reward task completion"; - span14[11] = "Fix gear coffer gearset restore across interrupts and retries"; - span14[12] = "Fix AutoDuty leveling loop not continuing until target level reached"; - span14[13] = "Fix AutoDuty not persisting excessive LoopTimes values"; - span14[14] = "Fix quest editor schema validation, dirty tracking, and atomic save"; - span14[15] = "Fix death recovery respawn prompt clicking timing"; - span14[16] = "Fix same-zone teleport skipping when it gains no distance"; - span14[17] = "Fix stop condition removal and level-15 floor in StartLevelingMode IPC"; - span14[18] = "Fix stop conditions not firing during leveling mode"; - span14[19] = "Fix command-after-stop firing once per run instead of per frame"; - span14[20] = "Fix soft hyphens and escaped macros in game string/dialogue comparisons"; - span14[21] = "Fix skip aether current quest pickups once flying is unlocked"; - span14[22] = "Fix class quest prioritization and level-lock pickup logic"; - span14[23] = "Fix respecting skip setting for in-progress class quests over level 5"; - span14[24] = "Fix repeatable quest completion flag being checked while quest is accepted"; - span14[25] = "Fix priority quest pickup after shortcut-key strip"; - span14[26] = "Fix duty category classification"; - span14[27] = "Fix loading crash when InterruptHandler hook fails"; - span14[28] = "Fix shop opening on wrong tab during vendor purchases"; - span14[29] = "Fix aethernet destination selection and teleport retry on transient failures"; - span14[30] = "Fix combat targeting overpull and self-defense getting stuck on evading mobs"; - span14[31] = "Fix BossMod preset management during solo duties and between combat phases"; - span14[32] = "Fix WrathCombo rotation mode enum values"; - span14[33] = "Fix startup crash from Pictomancy initialization"; - span14[34] = "Fix emote retry when target NPC not found"; - span14[35] = "Fix interaction wait for missing objects instead of silently skipping"; - span14[36] = "Fix PurchaseItem vendor interaction retry when shop not open"; - span14[37] = "Fix GC shop cancel on handler reset"; - span14[38] = "Fix quest sequence window latching closed after first close"; - span14[39] = "Fix An Ill-conceived Venture locked without retainer entitlement"; - span14[40] = "Fix various UI text clipping and wrapping issues"; - reference22 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list23); - ref ChangeEntry reference23 = ref span11[3]; - num3 = 6; + CollectionsMarshal.AsSpan(list22)[0] = "This version was intended to roll back early changes made to 7.5.9 before it was ready for publishing."; + reference21 = new ChangeEntry(EChangeCategory.Removed, "Rollback", list22); + reference20 = new ChangelogEntry("7.5.8", releaseDate6, list21); + ref ChangelogEntry reference22 = ref span[6]; + DateOnly releaseDate7 = new DateOnly(2026, 8, 12); + num2 = 1; + List list23 = new List(num2); + CollectionsMarshal.SetCount(list23, num2); + ref ChangeEntry reference23 = ref CollectionsMarshal.AsSpan(list23)[0]; + num3 = 2; List list24 = new List(num3); CollectionsMarshal.SetCount(list24, num3); Span span15 = CollectionsMarshal.AsSpan(list24); - span15[0] = "Add Moonfire Faire (2026) quests"; - span15[1] = "Add 88 community-contributed quest ports"; - span15[2] = "Add all missing gathering paths"; - span15[3] = "Add AutoHook presets for ARR fishing quests"; - span15[4] = "Add Shadow of the First missing sequence"; - span15[5] = "Add Amaljaa, Sylph, and Ixal allied society NPC data"; - reference23 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list24); - reference19 = new ChangelogEntry("7.5.6", releaseDate7, list20); + span15[0] = "Move LGB scanning (NPC positions and zone boundaries) to an out-of-process worker to eliminate memory spikes on first load and after game patches"; + span15[1] = "Add Clear LGB Caches button in Advanced settings with cache size tooltip"; + reference23 = new ChangeEntry(EChangeCategory.Changed, "Changed", list24); + reference22 = new ChangelogEntry("7.5.7", releaseDate7, list23); ref ChangelogEntry reference24 = ref span[7]; - DateOnly releaseDate8 = new DateOnly(2026, 6, 25); - num2 = 1; + DateOnly releaseDate8 = new DateOnly(2026, 8, 12); + num2 = 4; List list25 = new List(num2); CollectionsMarshal.SetCount(list25, num2); - ref ChangeEntry reference25 = ref CollectionsMarshal.AsSpan(list25)[0]; - num3 = 1; + Span span16 = CollectionsMarshal.AsSpan(list25); + ref ChangeEntry reference25 = ref span16[0]; + num3 = 35; List list26 = new List(num3); CollectionsMarshal.SetCount(list26, num3); - CollectionsMarshal.AsSpan(list26)[0] = "Add Breaking Brick Mountains (2026) event quest"; - reference25 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list26); - reference24 = new ChangelogEntry("7.5.5", releaseDate8, list25); - ref ChangelogEntry reference26 = ref span[8]; - DateOnly releaseDate9 = new DateOnly(2026, 6, 6); - num2 = 1; - List list27 = new List(num2); - CollectionsMarshal.SetCount(list27, num2); - ref ChangeEntry reference27 = ref CollectionsMarshal.AsSpan(list27)[0]; - num3 = 1; + Span span17 = CollectionsMarshal.AsSpan(list26); + span17[0] = "Add SmartNav automatic cross-zone routing with real-time gil cost awareness"; + span17[1] = "Add WigglyNav as an alternative navigation provider with provider-aware navmesh rebuild"; + span17[2] = "Add Custom Delivery automation with crafting, gathering, fishing, vendor purchases, and job selection"; + span17[3] = "Add Attunement tab to Journal with aetheryte/aethernet/aether current status and batch attune"; + span17[4] = "Add Fishing automation with AutoHook preset management and bait vendor resolution"; + span17[5] = "Add allied society overcap protection with reputation-aware turn-in ordering"; + span17[6] = "Add quest chain graph with queue management in Journal"; + span17[7] = "Add Gathering Editor as a sub-panel to Quest Editor Window"; + span17[8] = "Add smart equip with three modes: Game Default, Smart (stat-aware), and None"; + span17[9] = "Add gear coffer opening from quest rewards with gearset switching"; + span17[10] = "Add automatic repair at quest boundaries and duty gates via self-repair and menders"; + span17[11] = "Add out-of-game notifications with per-event alert toggles and sound selection"; + span17[12] = "Add run-command-after-stop for executing a command when a stop condition fires"; + span17[13] = "Add reward redemption settings with per-type filtering and skip-tradable option"; + span17[14] = "Add UnlockLink reward type for unlock-link quest rewards"; + span17[15] = "Add teleport ticket support for cheaper cross-zone travel"; + span17[16] = "Add chocobo taxi routing for zones with taxi stands"; + span17[17] = "Add warp-based travel (elevators, ferries, zone transitions)"; + span17[18] = "Add Critical Encounter and Fishing quest step types"; + span17[19] = "Add auto-unsync for 4-man dungeons when safely overleveled"; + span17[20] = "Add death recovery system that auto-respawns and re-routes"; + span17[21] = "Add RetryStep system that re-navigates on interaction failures"; + span17[22] = "Add UnequipItem step executor"; + span17[23] = "Add unified stop condition system with QuestCount, Level, and Sequence conditions"; + span17[24] = "Add setup wizard with guided steps, re-openable via command"; + span17[25] = "Add BossModReborn support alongside BossMod"; + span17[26] = "Add WrathCombo integration with explicit combo state management"; + span17[27] = "Add per-feature control for external plugin pausing"; + span17[28] = "Add hot-reloading and in-memory quest testing to Quest Editor"; + span17[29] = "Add Navigation config tab with mount, flying, and gil reserve settings"; + span17[30] = "Add config backup before version migrations"; + span17[31] = "Add option to disable coffer opening (None mode)"; + span17[32] = "Add detection of installed-but-disabled plugins with Enable button"; + span17[33] = "Add plugin alternative listing (vnavmesh-cn, BossModReborn) with conflict warnings"; + span17[34] = "Add accepted quests with no path shown with live game sequence"; + reference25 = new ChangeEntry(EChangeCategory.Added, "Added", list26); + ref ChangeEntry reference26 = ref span16[1]; + num3 = 17; + List list27 = new List(num3); + CollectionsMarshal.SetCount(list27, num3); + Span span18 = CollectionsMarshal.AsSpan(list27); + span18[0] = "Redesign Configuration window with grouped sidebar navigation and themed cards"; + span18[1] = "Redesign Journal with sidebar navigation, accordion layouts, and tracker tables"; + span18[2] = "Redesign main quest window with themed banners, titles, and stop buttons"; + span18[3] = "Redesign FATE and Seasonal Duty selection windows with search and shared cycle UI"; + span18[4] = "Replace quest-path-based custom deliveries with dynamic automation controller"; + span18[5] = "Move path data to external repository for community contributions"; + span18[6] = "Install and enable required plugins directly from the Dependencies tab"; + span18[7] = "Faster startup with parallel data loading"; + span18[8] = "SmartNav automatically determines mount, fly, and teleport decisions (replaces manual shortcuts)"; + span18[9] = "Replace native aethernet teleportation with direct implementation (removes Lifestream dependency)"; + span18[10] = "Remove aether current and city aethernet quest presets (replaced by Attunement tab)"; + span18[11] = "Remove 'Stop after current quest' and quest-count/timer stop buttons from main window"; + span18[12] = "Remove 'View All Quest Paths Online' button from journal"; + span18[13] = "Remove Explanation dropdown from Quests and Duties tabs"; + span18[14] = "Virtualize quest, duty, and gathering journals for better performance"; + span18[15] = "Default custom delivery scrip overcap mode to Ignore"; + span18[16] = "Default quest journal groups to collapsed"; + reference26 = new ChangeEntry(EChangeCategory.Changed, "Changed", list27); + ref ChangeEntry reference27 = ref span16[2]; + num3 = 41; List list28 = new List(num3); CollectionsMarshal.SetCount(list28, num3); - CollectionsMarshal.AsSpan(list28)[0] = "Add Make It Rain (2026) seasonal quest"; - reference27 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list28); - reference26 = new ChangelogEntry("7.5.4", releaseDate9, list27); - ref ChangelogEntry reference28 = ref span[9]; - DateOnly releaseDate10 = new DateOnly(2026, 5, 12); + Span span19 = CollectionsMarshal.AsSpan(list28); + span19[0] = "Fix zone transition hangs and re-routing after zone changes"; + span19[1] = "Fix flying route decisions based on mount ownership and flying unlock state"; + span19[2] = "Fix warp routing filtering by quest requirements and class level"; + span19[3] = "Fix zone boundary access respecting per-boundary quest requirements"; + span19[4] = "Fix custom delivery craft purchases, turn-in timeouts, and rank-up resume"; + span19[5] = "Fix custom delivery keeping queued deliveries when inventory is full"; + span19[6] = "Fix fishing approach rebuild on retry and no-progress timeout"; + span19[7] = "Fix smart equip ranking weapons by weapon damage and job-usable stats"; + span19[8] = "Fix repair retry when gear condition did not improve"; + span19[9] = "Fix gathering retry counter reset and node integrity checks"; + span19[10] = "Fix reward redemption for tradeable items and stale reward task completion"; + span19[11] = "Fix gear coffer gearset restore across interrupts and retries"; + span19[12] = "Fix AutoDuty leveling loop not continuing until target level reached"; + span19[13] = "Fix AutoDuty not persisting excessive LoopTimes values"; + span19[14] = "Fix quest editor schema validation, dirty tracking, and atomic save"; + span19[15] = "Fix death recovery respawn prompt clicking timing"; + span19[16] = "Fix same-zone teleport skipping when it gains no distance"; + span19[17] = "Fix stop condition removal and level-15 floor in StartLevelingMode IPC"; + span19[18] = "Fix stop conditions not firing during leveling mode"; + span19[19] = "Fix command-after-stop firing once per run instead of per frame"; + span19[20] = "Fix soft hyphens and escaped macros in game string/dialogue comparisons"; + span19[21] = "Fix skip aether current quest pickups once flying is unlocked"; + span19[22] = "Fix class quest prioritization and level-lock pickup logic"; + span19[23] = "Fix respecting skip setting for in-progress class quests over level 5"; + span19[24] = "Fix repeatable quest completion flag being checked while quest is accepted"; + span19[25] = "Fix priority quest pickup after shortcut-key strip"; + span19[26] = "Fix duty category classification"; + span19[27] = "Fix loading crash when InterruptHandler hook fails"; + span19[28] = "Fix shop opening on wrong tab during vendor purchases"; + span19[29] = "Fix aethernet destination selection and teleport retry on transient failures"; + span19[30] = "Fix combat targeting overpull and self-defense getting stuck on evading mobs"; + span19[31] = "Fix BossMod preset management during solo duties and between combat phases"; + span19[32] = "Fix WrathCombo rotation mode enum values"; + span19[33] = "Fix startup crash from Pictomancy initialization"; + span19[34] = "Fix emote retry when target NPC not found"; + span19[35] = "Fix interaction wait for missing objects instead of silently skipping"; + span19[36] = "Fix PurchaseItem vendor interaction retry when shop not open"; + span19[37] = "Fix GC shop cancel on handler reset"; + span19[38] = "Fix quest sequence window latching closed after first close"; + span19[39] = "Fix An Ill-conceived Venture locked without retainer entitlement"; + span19[40] = "Fix various UI text clipping and wrapping issues"; + reference27 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list28); + ref ChangeEntry reference28 = ref span16[3]; + num3 = 6; + List list29 = new List(num3); + CollectionsMarshal.SetCount(list29, num3); + Span span20 = CollectionsMarshal.AsSpan(list29); + span20[0] = "Add Moonfire Faire (2026) quests"; + span20[1] = "Add 88 community-contributed quest ports"; + span20[2] = "Add all missing gathering paths"; + span20[3] = "Add AutoHook presets for ARR fishing quests"; + span20[4] = "Add Shadow of the First missing sequence"; + span20[5] = "Add Amaljaa, Sylph, and Ixal allied society NPC data"; + reference28 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list29); + reference24 = new ChangelogEntry("7.5.6", releaseDate8, list25); + ref ChangelogEntry reference29 = ref span[8]; + DateOnly releaseDate9 = new DateOnly(2026, 6, 25); num2 = 1; - List list29 = new List(num2); - CollectionsMarshal.SetCount(list29, num2); - ref ChangeEntry reference29 = ref CollectionsMarshal.AsSpan(list29)[0]; + List list30 = new List(num2); + CollectionsMarshal.SetCount(list30, num2); + ref ChangeEntry reference30 = ref CollectionsMarshal.AsSpan(list30)[0]; num3 = 1; - List list30 = new List(num3); - CollectionsMarshal.SetCount(list30, num3); - CollectionsMarshal.AsSpan(list30)[0] = "Add The Maiden's Rhapsody (2026) event quest"; - reference29 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list30); - reference28 = new ChangelogEntry("7.5.3", releaseDate10, list29); - ref ChangelogEntry reference30 = ref span[10]; - DateOnly releaseDate11 = new DateOnly(2026, 5, 1); + List list31 = new List(num3); + CollectionsMarshal.SetCount(list31, num3); + CollectionsMarshal.AsSpan(list31)[0] = "Add Breaking Brick Mountains (2026) event quest"; + reference30 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list31); + reference29 = new ChangelogEntry("7.5.5", releaseDate9, list30); + ref ChangelogEntry reference31 = ref span[9]; + DateOnly releaseDate10 = new DateOnly(2026, 6, 6); num2 = 1; - List list31 = new List(num2); - CollectionsMarshal.SetCount(list31, num2); - ref ChangeEntry reference31 = ref CollectionsMarshal.AsSpan(list31)[0]; + List list32 = new List(num2); + CollectionsMarshal.SetCount(list32, num2); + ref ChangeEntry reference32 = ref CollectionsMarshal.AsSpan(list32)[0]; num3 = 1; - List list32 = new List(num3); - CollectionsMarshal.SetCount(list32, num3); - CollectionsMarshal.AsSpan(list32)[0] = "Revert vnavmesh SimpleMove IPC temporary fix"; - reference31 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list32); - reference30 = new ChangelogEntry("7.5.2", releaseDate11, list31); - ref ChangelogEntry reference32 = ref span[11]; - DateOnly releaseDate12 = new DateOnly(2026, 4, 30); - num2 = 2; - List list33 = new List(num2); - CollectionsMarshal.SetCount(list33, num2); - Span span16 = CollectionsMarshal.AsSpan(list33); - ref ChangeEntry reference33 = ref span16[0]; - num3 = 2; - List list34 = new List(num3); - CollectionsMarshal.SetCount(list34, num3); - Span span17 = CollectionsMarshal.AsSpan(list34); - span17[0] = "Add Windurst: The Third Walk quests"; - span17[1] = "Add 7.5 MSQ"; - reference33 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list34); - ref ChangeEntry reference34 = ref span16[1]; + List list33 = new List(num3); + CollectionsMarshal.SetCount(list33, num3); + CollectionsMarshal.AsSpan(list33)[0] = "Add Make It Rain (2026) seasonal quest"; + reference32 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list33); + reference31 = new ChangelogEntry("7.5.4", releaseDate10, list32); + ref ChangelogEntry reference33 = ref span[10]; + DateOnly releaseDate11 = new DateOnly(2026, 5, 12); + num2 = 1; + List list34 = new List(num2); + CollectionsMarshal.SetCount(list34, num2); + ref ChangeEntry reference34 = ref CollectionsMarshal.AsSpan(list34)[0]; num3 = 1; List list35 = new List(num3); CollectionsMarshal.SetCount(list35, num3); - CollectionsMarshal.AsSpan(list35)[0] = "Use vnavmesh SimpleMove IPC to fix navmesh hanging the game (temporary fix)"; - reference34 = new ChangeEntry(EChangeCategory.Changed, "Fixes", list35); - reference32 = new ChangelogEntry("7.5.1", releaseDate12, list33); - ref ChangelogEntry reference35 = ref span[12]; - DateOnly releaseDate13 = new DateOnly(2026, 4, 29); + CollectionsMarshal.AsSpan(list35)[0] = "Add The Maiden's Rhapsody (2026) event quest"; + reference34 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list35); + reference33 = new ChangelogEntry("7.5.3", releaseDate11, list34); + ref ChangelogEntry reference35 = ref span[11]; + DateOnly releaseDate12 = new DateOnly(2026, 5, 1); num2 = 1; List list36 = new List(num2); CollectionsMarshal.SetCount(list36, num2); @@ -369,1010 +384,1043 @@ internal static class ChangelogData num3 = 1; List list37 = new List(num3); CollectionsMarshal.SetCount(list37, num3); - CollectionsMarshal.AsSpan(list37)[0] = "Api15"; + CollectionsMarshal.AsSpan(list37)[0] = "Revert vnavmesh SimpleMove IPC temporary fix"; reference36 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list37); - reference35 = new ChangelogEntry("7.5.0", releaseDate13, list36); - ref ChangelogEntry reference37 = ref span[13]; - DateOnly releaseDate14 = new DateOnly(2026, 3, 24); - num2 = 1; + reference35 = new ChangelogEntry("7.5.2", releaseDate12, list36); + ref ChangelogEntry reference37 = ref span[12]; + DateOnly releaseDate13 = new DateOnly(2026, 4, 30); + num2 = 2; List list38 = new List(num2); CollectionsMarshal.SetCount(list38, num2); - ref ChangeEntry reference38 = ref CollectionsMarshal.AsSpan(list38)[0]; + Span span21 = CollectionsMarshal.AsSpan(list38); + ref ChangeEntry reference38 = ref span21[0]; num3 = 2; List list39 = new List(num3); CollectionsMarshal.SetCount(list39, num3); - Span span18 = CollectionsMarshal.AsSpan(list39); - span18[0] = "Add duty handling to external and qst"; - span18[1] = "Fix duty farming NPC interact getting stuck after zone transition into duty"; - reference38 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list39); - reference37 = new ChangelogEntry("7.4.19", releaseDate14, list38); - ref ChangelogEntry reference39 = ref span[14]; - DateOnly releaseDate15 = new DateOnly(2026, 3, 24); - num2 = 3; - List list40 = new List(num2); - CollectionsMarshal.SetCount(list40, num2); - Span span19 = CollectionsMarshal.AsSpan(list40); - ref ChangeEntry reference40 = ref span19[0]; + Span span22 = CollectionsMarshal.AsSpan(list39); + span22[0] = "Add Windurst: The Third Walk quests"; + span22[1] = "Add 7.5 MSQ"; + reference38 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list39); + ref ChangeEntry reference39 = ref span21[1]; num3 = 1; - List list41 = new List(num3); - CollectionsMarshal.SetCount(list41, num3); - CollectionsMarshal.AsSpan(list41)[0] = "Add seasonal duty farming system (/qst duty)"; - reference40 = new ChangeEntry(EChangeCategory.Added, "New Features", list41); - ref ChangeEntry reference41 = ref span19[1]; + List list40 = new List(num3); + CollectionsMarshal.SetCount(list40, num3); + CollectionsMarshal.AsSpan(list40)[0] = "Use vnavmesh SimpleMove IPC to fix navmesh hanging the game (temporary fix)"; + reference39 = new ChangeEntry(EChangeCategory.Changed, "Fixes", list40); + reference37 = new ChangelogEntry("7.5.1", releaseDate13, list38); + ref ChangelogEntry reference40 = ref span[13]; + DateOnly releaseDate14 = new DateOnly(2026, 4, 29); + num2 = 1; + List list41 = new List(num2); + CollectionsMarshal.SetCount(list41, num2); + ref ChangeEntry reference41 = ref CollectionsMarshal.AsSpan(list41)[0]; num3 = 1; List list42 = new List(num3); CollectionsMarshal.SetCount(list42, num3); - CollectionsMarshal.AsSpan(list42)[0] = "Add Hatching-tide 2026 quest"; - reference41 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list42); - ref ChangeEntry reference42 = ref span19[2]; - num3 = 1; - List list43 = new List(num3); - CollectionsMarshal.SetCount(list43, num3); - CollectionsMarshal.AsSpan(list43)[0] = "Quest path fixes"; - reference42 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list43); - reference39 = new ChangelogEntry("7.4.18", releaseDate15, list40); - ref ChangelogEntry reference43 = ref span[15]; - DateOnly releaseDate16 = new DateOnly(2026, 3, 1); + CollectionsMarshal.AsSpan(list42)[0] = "Api15"; + reference41 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list42); + reference40 = new ChangelogEntry("7.5.0", releaseDate14, list41); + ref ChangelogEntry reference42 = ref span[14]; + DateOnly releaseDate15 = new DateOnly(2026, 3, 24); num2 = 1; - List list44 = new List(num2); - CollectionsMarshal.SetCount(list44, num2); - ref ChangeEntry reference44 = ref CollectionsMarshal.AsSpan(list44)[0]; + List list43 = new List(num2); + CollectionsMarshal.SetCount(list43, num2); + ref ChangeEntry reference43 = ref CollectionsMarshal.AsSpan(list43)[0]; + num3 = 2; + List list44 = new List(num3); + CollectionsMarshal.SetCount(list44, num3); + Span span23 = CollectionsMarshal.AsSpan(list44); + span23[0] = "Add duty handling to external and qst"; + span23[1] = "Fix duty farming NPC interact getting stuck after zone transition into duty"; + reference43 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list44); + reference42 = new ChangelogEntry("7.4.19", releaseDate15, list43); + ref ChangelogEntry reference44 = ref span[15]; + DateOnly releaseDate16 = new DateOnly(2026, 3, 24); + num2 = 3; + List list45 = new List(num2); + CollectionsMarshal.SetCount(list45, num2); + Span span24 = CollectionsMarshal.AsSpan(list45); + ref ChangeEntry reference45 = ref span24[0]; num3 = 1; - List list45 = new List(num3); - CollectionsMarshal.SetCount(list45, num3); - CollectionsMarshal.AsSpan(list45)[0] = "Quest path fixes"; - reference44 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list45); - reference43 = new ChangelogEntry("7.4.17", releaseDate16, list44); - ref ChangelogEntry reference45 = ref span[16]; - DateOnly releaseDate17 = new DateOnly(2026, 3, 1); - num2 = 1; - List list46 = new List(num2); - CollectionsMarshal.SetCount(list46, num2); - ref ChangeEntry reference46 = ref CollectionsMarshal.AsSpan(list46)[0]; + List list46 = new List(num3); + CollectionsMarshal.SetCount(list46, num3); + CollectionsMarshal.AsSpan(list46)[0] = "Add seasonal duty farming system (/qst duty)"; + reference45 = new ChangeEntry(EChangeCategory.Added, "New Features", list46); + ref ChangeEntry reference46 = ref span24[1]; num3 = 1; List list47 = new List(num3); CollectionsMarshal.SetCount(list47, num3); - CollectionsMarshal.AsSpan(list47)[0] = "Enable TextAdvance, PandorasBox, and Automaton IPC integration during FATE farming"; - reference46 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list47); - reference45 = new ChangelogEntry("7.4.16", releaseDate17, list46); - ref ChangelogEntry reference47 = ref span[17]; - DateOnly releaseDate18 = new DateOnly(2026, 2, 28); + CollectionsMarshal.AsSpan(list47)[0] = "Add Hatching-tide 2026 quest"; + reference46 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list47); + ref ChangeEntry reference47 = ref span24[2]; + num3 = 1; + List list48 = new List(num3); + CollectionsMarshal.SetCount(list48, num3); + CollectionsMarshal.AsSpan(list48)[0] = "Quest path fixes"; + reference47 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list48); + reference44 = new ChangelogEntry("7.4.18", releaseDate16, list45); + ref ChangelogEntry reference48 = ref span[16]; + DateOnly releaseDate17 = new DateOnly(2026, 3, 1); num2 = 1; - List list48 = new List(num2); - CollectionsMarshal.SetCount(list48, num2); - ref ChangeEntry reference48 = ref CollectionsMarshal.AsSpan(list48)[0]; - num3 = 2; - List list49 = new List(num3); - CollectionsMarshal.SetCount(list49, num3); - Span span20 = CollectionsMarshal.AsSpan(list49); - span20[0] = "Fix FATE transform interact task never completing after NPC dialogue"; - span20[1] = "Fix FATE farming loop not detecting completion when NPCs persist after FATE ends"; - reference48 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list49); - reference47 = new ChangelogEntry("7.4.15", releaseDate18, list48); - ref ChangelogEntry reference49 = ref span[18]; - DateOnly releaseDate19 = new DateOnly(2026, 2, 27); - num2 = 2; - List list50 = new List(num2); - CollectionsMarshal.SetCount(list50, num2); - Span span21 = CollectionsMarshal.AsSpan(list50); - ref ChangeEntry reference50 = ref span21[0]; - num3 = 2; - List list51 = new List(num3); - CollectionsMarshal.SetCount(list51, num3); - Span span22 = CollectionsMarshal.AsSpan(list51); - span22[0] = "Added prerequisite quest requirement to FATE farming definitions"; - span22[1] = "Show active FATE in main quest UI and disable quest start during FATE farming"; - reference50 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list51); - ref ChangeEntry reference51 = ref span21[1]; + List list49 = new List(num2); + CollectionsMarshal.SetCount(list49, num2); + ref ChangeEntry reference49 = ref CollectionsMarshal.AsSpan(list49)[0]; + num3 = 1; + List list50 = new List(num3); + CollectionsMarshal.SetCount(list50, num3); + CollectionsMarshal.AsSpan(list50)[0] = "Quest path fixes"; + reference49 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list50); + reference48 = new ChangelogEntry("7.4.17", releaseDate17, list49); + ref ChangelogEntry reference50 = ref span[17]; + DateOnly releaseDate18 = new DateOnly(2026, 3, 1); + num2 = 1; + List list51 = new List(num2); + CollectionsMarshal.SetCount(list51, num2); + ref ChangeEntry reference51 = ref CollectionsMarshal.AsSpan(list51)[0]; num3 = 1; List list52 = new List(num3); CollectionsMarshal.SetCount(list52, num3); - CollectionsMarshal.AsSpan(list52)[0] = "Fixed FATE farming buff expiry and added Curtain Call cleanup on stop"; + CollectionsMarshal.AsSpan(list52)[0] = "Enable TextAdvance, PandorasBox, and Automaton IPC integration during FATE farming"; reference51 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list52); - reference49 = new ChangelogEntry("7.4.14", releaseDate19, list50); - ref ChangelogEntry reference52 = ref span[19]; - DateOnly releaseDate20 = new DateOnly(2026, 2, 27); + reference50 = new ChangelogEntry("7.4.16", releaseDate18, list51); + ref ChangelogEntry reference52 = ref span[18]; + DateOnly releaseDate19 = new DateOnly(2026, 2, 28); num2 = 1; List list53 = new List(num2); CollectionsMarshal.SetCount(list53, num2); ref ChangeEntry reference53 = ref CollectionsMarshal.AsSpan(list53)[0]; - num3 = 1; + num3 = 2; List list54 = new List(num3); CollectionsMarshal.SetCount(list54, num3); - CollectionsMarshal.AsSpan(list54)[0] = "Fixed an issue where FATE actions were not performed during the Little Ladies Day (2026) FATE event"; + Span span25 = CollectionsMarshal.AsSpan(list54); + span25[0] = "Fix FATE transform interact task never completing after NPC dialogue"; + span25[1] = "Fix FATE farming loop not detecting completion when NPCs persist after FATE ends"; reference53 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list54); - reference52 = new ChangelogEntry("7.4.13", releaseDate20, list53); - ref ChangelogEntry reference54 = ref span[20]; - DateOnly releaseDate21 = new DateOnly(2026, 2, 26); - num2 = 3; + reference52 = new ChangelogEntry("7.4.15", releaseDate19, list53); + ref ChangelogEntry reference54 = ref span[19]; + DateOnly releaseDate20 = new DateOnly(2026, 2, 27); + num2 = 2; List list55 = new List(num2); CollectionsMarshal.SetCount(list55, num2); - Span span23 = CollectionsMarshal.AsSpan(list55); - ref ChangeEntry reference55 = ref span23[0]; - num3 = 3; + Span span26 = CollectionsMarshal.AsSpan(list55); + ref ChangeEntry reference55 = ref span26[0]; + num3 = 2; List list56 = new List(num3); CollectionsMarshal.SetCount(list56, num3); - Span span24 = CollectionsMarshal.AsSpan(list56); - span24[0] = "Add Saved Presets tab to quest priority window"; - span24[1] = "Add passive FATE handling and per npc ability handling"; - span24[2] = "Add FATE farming support"; - reference55 = new ChangeEntry(EChangeCategory.Added, "New Features", list56); - ref ChangeEntry reference56 = ref span23[1]; + Span span27 = CollectionsMarshal.AsSpan(list56); + span27[0] = "Added prerequisite quest requirement to FATE farming definitions"; + span27[1] = "Show active FATE in main quest UI and disable quest start during FATE farming"; + reference55 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list56); + ref ChangeEntry reference56 = ref span26[1]; num3 = 1; List list57 = new List(num3); CollectionsMarshal.SetCount(list57, num3); - CollectionsMarshal.AsSpan(list57)[0] = "Add Little Ladies' Day 2026 quests"; - reference56 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list57); - ref ChangeEntry reference57 = ref span23[2]; + CollectionsMarshal.AsSpan(list57)[0] = "Fixed FATE farming buff expiry and added Curtain Call cleanup on stop"; + reference56 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list57); + reference54 = new ChangelogEntry("7.4.14", releaseDate20, list55); + ref ChangelogEntry reference57 = ref span[20]; + DateOnly releaseDate21 = new DateOnly(2026, 2, 27); + num2 = 1; + List list58 = new List(num2); + CollectionsMarshal.SetCount(list58, num2); + ref ChangeEntry reference58 = ref CollectionsMarshal.AsSpan(list58)[0]; num3 = 1; - List list58 = new List(num3); - CollectionsMarshal.SetCount(list58, num3); - CollectionsMarshal.AsSpan(list58)[0] = "Quest path fixes"; - reference57 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list58); - reference54 = new ChangelogEntry("7.4.12", releaseDate21, list55); - ref ChangelogEntry reference58 = ref span[21]; - DateOnly releaseDate22 = new DateOnly(2026, 2, 2); - num2 = 2; - List list59 = new List(num2); - CollectionsMarshal.SetCount(list59, num2); - Span span25 = CollectionsMarshal.AsSpan(list59); - ref ChangeEntry reference59 = ref span25[0]; - num3 = 1; - List list60 = new List(num3); - CollectionsMarshal.SetCount(list60, num3); - CollectionsMarshal.AsSpan(list60)[0] = "Add Valentione's Day 2026 quest (The Icing on the Cake)"; - reference59 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list60); - ref ChangeEntry reference60 = ref span25[1]; - num3 = 2; + List list59 = new List(num3); + CollectionsMarshal.SetCount(list59, num3); + CollectionsMarshal.AsSpan(list59)[0] = "Fixed an issue where FATE actions were not performed during the Little Ladies Day (2026) FATE event"; + reference58 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list59); + reference57 = new ChangelogEntry("7.4.13", releaseDate21, list58); + ref ChangelogEntry reference59 = ref span[21]; + DateOnly releaseDate22 = new DateOnly(2026, 2, 26); + num2 = 3; + List list60 = new List(num2); + CollectionsMarshal.SetCount(list60, num2); + Span span28 = CollectionsMarshal.AsSpan(list60); + ref ChangeEntry reference60 = ref span28[0]; + num3 = 3; List list61 = new List(num3); CollectionsMarshal.SetCount(list61, num3); - Span span26 = CollectionsMarshal.AsSpan(list61); - span26[0] = "Updated Duty journal to include missing duty types"; - span26[1] = "Added various missing sequences to quests"; - reference60 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list61); - reference58 = new ChangelogEntry("7.4.11", releaseDate22, list59); - ref ChangelogEntry reference61 = ref span[22]; - DateOnly releaseDate23 = new DateOnly(2026, 1, 18); - num2 = 2; - List list62 = new List(num2); - CollectionsMarshal.SetCount(list62, num2); - Span span27 = CollectionsMarshal.AsSpan(list62); - ref ChangeEntry reference62 = ref span27[0]; - num3 = 5; + Span span29 = CollectionsMarshal.AsSpan(list61); + span29[0] = "Add Saved Presets tab to quest priority window"; + span29[1] = "Add passive FATE handling and per npc ability handling"; + span29[2] = "Add FATE farming support"; + reference60 = new ChangeEntry(EChangeCategory.Added, "New Features", list61); + ref ChangeEntry reference61 = ref span28[1]; + num3 = 1; + List list62 = new List(num3); + CollectionsMarshal.SetCount(list62, num3); + CollectionsMarshal.AsSpan(list62)[0] = "Add Little Ladies' Day 2026 quests"; + reference61 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list62); + ref ChangeEntry reference62 = ref span28[2]; + num3 = 1; List list63 = new List(num3); CollectionsMarshal.SetCount(list63, num3); - Span span28 = CollectionsMarshal.AsSpan(list63); - span28[0] = "Added quest blacklisting"; - span28[1] = "Added MSQ Priority config"; - span28[2] = "Added Quest priority persistence config"; - span28[3] = "Added Duties tab to Journal"; - span28[4] = "Added GC shop handling and chocobo naming for chocobo quests"; - reference62 = new ChangeEntry(EChangeCategory.Added, "Major Features", list63); - ref ChangeEntry reference63 = ref span27[1]; - num3 = 5; - List list64 = new List(num3); - CollectionsMarshal.SetCount(list64, num3); - Span span29 = CollectionsMarshal.AsSpan(list64); - span29[0] = "Removed PandorasBox dependency and added QTE handling"; - span29[1] = "Removed CBT dependency and added Snipe handling"; - span29[2] = "Added drag reordering to Stop condition quests"; - span29[3] = "Ignore item level requirements if using Unsync config"; - span29[4] = "Setting a Stop quest to Off no longer removes it from the list"; - reference63 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list64); - reference61 = new ChangelogEntry("7.4.10", releaseDate23, list62); - ref ChangelogEntry reference64 = ref span[23]; - DateOnly releaseDate24 = new DateOnly(2025, 12, 31); - num2 = 3; - List list65 = new List(num2); - CollectionsMarshal.SetCount(list65, num2); - Span span30 = CollectionsMarshal.AsSpan(list65); - ref ChangeEntry reference65 = ref span30[0]; + CollectionsMarshal.AsSpan(list63)[0] = "Quest path fixes"; + reference62 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list63); + reference59 = new ChangelogEntry("7.4.12", releaseDate22, list60); + ref ChangelogEntry reference63 = ref span[22]; + DateOnly releaseDate23 = new DateOnly(2026, 2, 2); + num2 = 2; + List list64 = new List(num2); + CollectionsMarshal.SetCount(list64, num2); + Span span30 = CollectionsMarshal.AsSpan(list64); + ref ChangeEntry reference64 = ref span30[0]; num3 = 1; + List list65 = new List(num3); + CollectionsMarshal.SetCount(list65, num3); + CollectionsMarshal.AsSpan(list65)[0] = "Add Valentione's Day 2026 quest (The Icing on the Cake)"; + reference64 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list65); + ref ChangeEntry reference65 = ref span30[1]; + num3 = 2; List list66 = new List(num3); CollectionsMarshal.SetCount(list66, num3); - CollectionsMarshal.AsSpan(list66)[0] = "Add Heavensturn (2026) quests"; - reference65 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list66); - ref ChangeEntry reference66 = ref span30[1]; - num3 = 1; - List list67 = new List(num3); - CollectionsMarshal.SetCount(list67, num3); - CollectionsMarshal.AsSpan(list67)[0] = "Added missing quest sequences"; - reference66 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list67); - ref ChangeEntry reference67 = ref span30[2]; - num3 = 1; + Span span31 = CollectionsMarshal.AsSpan(list66); + span31[0] = "Updated Duty journal to include missing duty types"; + span31[1] = "Added various missing sequences to quests"; + reference65 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list66); + reference63 = new ChangelogEntry("7.4.11", releaseDate23, list64); + ref ChangelogEntry reference66 = ref span[23]; + DateOnly releaseDate24 = new DateOnly(2026, 1, 18); + num2 = 2; + List list67 = new List(num2); + CollectionsMarshal.SetCount(list67, num2); + Span span32 = CollectionsMarshal.AsSpan(list67); + ref ChangeEntry reference67 = ref span32[0]; + num3 = 5; List list68 = new List(num3); CollectionsMarshal.SetCount(list68, num3); - CollectionsMarshal.AsSpan(list68)[0] = "Fixed leveling mode not restarting properly"; - reference67 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list68); - reference64 = new ChangelogEntry("7.4.9", releaseDate24, list65); - ref ChangelogEntry reference68 = ref span[24]; - DateOnly releaseDate25 = new DateOnly(2025, 12, 29); - num2 = 1; - List list69 = new List(num2); - CollectionsMarshal.SetCount(list69, num2); - ref ChangeEntry reference69 = ref CollectionsMarshal.AsSpan(list69)[0]; - num3 = 2; - List list70 = new List(num3); - CollectionsMarshal.SetCount(list70, num3); - Span span31 = CollectionsMarshal.AsSpan(list70); - span31[0] = "Fixed infinite teleport loop when multiple quests compete for priority"; - span31[1] = "Fixed leveling mode enabling for quest duties"; - reference69 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list70); - reference68 = new ChangelogEntry("7.4.8", releaseDate25, list69); - ref ChangelogEntry reference70 = ref span[25]; - DateOnly releaseDate26 = new DateOnly(2025, 12, 28); - num2 = 4; - List list71 = new List(num2); - CollectionsMarshal.SetCount(list71, num2); - Span span32 = CollectionsMarshal.AsSpan(list71); - ref ChangeEntry reference71 = ref span32[0]; + Span span33 = CollectionsMarshal.AsSpan(list68); + span33[0] = "Added quest blacklisting"; + span33[1] = "Added MSQ Priority config"; + span33[2] = "Added Quest priority persistence config"; + span33[3] = "Added Duties tab to Journal"; + span33[4] = "Added GC shop handling and chocobo naming for chocobo quests"; + reference67 = new ChangeEntry(EChangeCategory.Added, "Major Features", list68); + ref ChangeEntry reference68 = ref span32[1]; + num3 = 5; + List list69 = new List(num3); + CollectionsMarshal.SetCount(list69, num3); + Span span34 = CollectionsMarshal.AsSpan(list69); + span34[0] = "Removed PandorasBox dependency and added QTE handling"; + span34[1] = "Removed CBT dependency and added Snipe handling"; + span34[2] = "Added drag reordering to Stop condition quests"; + span34[3] = "Ignore item level requirements if using Unsync config"; + span34[4] = "Setting a Stop quest to Off no longer removes it from the list"; + reference68 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list69); + reference66 = new ChangelogEntry("7.4.10", releaseDate24, list67); + ref ChangelogEntry reference69 = ref span[24]; + DateOnly releaseDate25 = new DateOnly(2025, 12, 31); + num2 = 3; + List list70 = new List(num2); + CollectionsMarshal.SetCount(list70, num2); + Span span35 = CollectionsMarshal.AsSpan(list70); + ref ChangeEntry reference70 = ref span35[0]; + num3 = 1; + List list71 = new List(num3); + CollectionsMarshal.SetCount(list71, num3); + CollectionsMarshal.AsSpan(list71)[0] = "Add Heavensturn (2026) quests"; + reference70 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list71); + ref ChangeEntry reference71 = ref span35[1]; num3 = 1; List list72 = new List(num3); CollectionsMarshal.SetCount(list72, num3); - CollectionsMarshal.AsSpan(list72)[0] = "Added leveling mode when underleveled for MSQ"; - reference71 = new ChangeEntry(EChangeCategory.Added, "Major Features", list72); - ref ChangeEntry reference72 = ref span32[1]; - num3 = 3; + CollectionsMarshal.AsSpan(list72)[0] = "Added missing quest sequences"; + reference71 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list72); + ref ChangeEntry reference72 = ref span35[2]; + num3 = 1; List list73 = new List(num3); CollectionsMarshal.SetCount(list73, num3); - Span span33 = CollectionsMarshal.AsSpan(list73); - span33[0] = "Added missing dungeons to Duties"; - span33[1] = "Added Normal Raids and Alliance Raids to duties tab"; - span33[2] = "Added Pause/Stop modes to stop conditions"; - reference72 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list73); - ref ChangeEntry reference73 = ref span32[2]; - num3 = 1; - List list74 = new List(num3); - CollectionsMarshal.SetCount(list74, num3); - CollectionsMarshal.AsSpan(list74)[0] = "Added leveling mode IPC: IsLevelingModeEnabled, SetLevelingModeEnabled, GetMsqLevelLockInfo, StartLevelingMode, StopLevelingMode"; - reference73 = new ChangeEntry(EChangeCategory.Added, "IPC Changes", list74); - ref ChangeEntry reference74 = ref span32[3]; - num3 = 1; + CollectionsMarshal.AsSpan(list73)[0] = "Fixed leveling mode not restarting properly"; + reference72 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list73); + reference69 = new ChangelogEntry("7.4.9", releaseDate25, list70); + ref ChangelogEntry reference73 = ref span[25]; + DateOnly releaseDate26 = new DateOnly(2025, 12, 29); + num2 = 1; + List list74 = new List(num2); + CollectionsMarshal.SetCount(list74, num2); + ref ChangeEntry reference74 = ref CollectionsMarshal.AsSpan(list74)[0]; + num3 = 2; List list75 = new List(num3); CollectionsMarshal.SetCount(list75, num3); - CollectionsMarshal.AsSpan(list75)[0] = "Fixed UI appearing in duties when debuff or interrupts occur"; + Span span36 = CollectionsMarshal.AsSpan(list75); + span36[0] = "Fixed infinite teleport loop when multiple quests compete for priority"; + span36[1] = "Fixed leveling mode enabling for quest duties"; reference74 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list75); - reference70 = new ChangelogEntry("7.4.7", releaseDate26, list71); + reference73 = new ChangelogEntry("7.4.8", releaseDate26, list74); ref ChangelogEntry reference75 = ref span[26]; - DateOnly releaseDate27 = new DateOnly(2025, 12, 23); - num2 = 2; + DateOnly releaseDate27 = new DateOnly(2025, 12, 28); + num2 = 4; List list76 = new List(num2); CollectionsMarshal.SetCount(list76, num2); - Span span34 = CollectionsMarshal.AsSpan(list76); - ref ChangeEntry reference76 = ref span34[0]; + Span span37 = CollectionsMarshal.AsSpan(list76); + ref ChangeEntry reference76 = ref span37[0]; num3 = 1; List list77 = new List(num3); CollectionsMarshal.SetCount(list77, num3); - CollectionsMarshal.AsSpan(list77)[0] = "Added Cinema Mode to not skip cutscenes"; + CollectionsMarshal.AsSpan(list77)[0] = "Added leveling mode when underleveled for MSQ"; reference76 = new ChangeEntry(EChangeCategory.Added, "Major Features", list77); - ref ChangeEntry reference77 = ref span34[1]; - num3 = 2; + ref ChangeEntry reference77 = ref span37[1]; + num3 = 3; List list78 = new List(num3); CollectionsMarshal.SetCount(list78, num3); - Span span35 = CollectionsMarshal.AsSpan(list78); - span35[0] = "Added handling for Unsync (Party) to Party Watchdog and configuration to disable if in party"; - span35[1] = "Stop conditions now act as a true stop"; + Span span38 = CollectionsMarshal.AsSpan(list78); + span38[0] = "Added missing dungeons to Duties"; + span38[1] = "Added Normal Raids and Alliance Raids to duties tab"; + span38[2] = "Added Pause/Stop modes to stop conditions"; reference77 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list78); - reference75 = new ChangelogEntry("7.4.6", releaseDate27, list76); - ref ChangelogEntry reference78 = ref span[27]; - DateOnly releaseDate28 = new DateOnly(2025, 12, 22); - num2 = 1; - List list79 = new List(num2); - CollectionsMarshal.SetCount(list79, num2); - ref ChangeEntry reference79 = ref CollectionsMarshal.AsSpan(list79)[0]; - num3 = 2; + ref ChangeEntry reference78 = ref span37[2]; + num3 = 1; + List list79 = new List(num3); + CollectionsMarshal.SetCount(list79, num3); + CollectionsMarshal.AsSpan(list79)[0] = "Added leveling mode IPC: IsLevelingModeEnabled, SetLevelingModeEnabled, GetMsqLevelLockInfo, StartLevelingMode, StopLevelingMode"; + reference78 = new ChangeEntry(EChangeCategory.Added, "IPC Changes", list79); + ref ChangeEntry reference79 = ref span37[3]; + num3 = 1; List list80 = new List(num3); CollectionsMarshal.SetCount(list80, num3); - Span span36 = CollectionsMarshal.AsSpan(list80); - span36[0] = "Dive adjustments"; - span36[1] = "Logging message adjustments"; - reference79 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list80); - reference78 = new ChangelogEntry("7.4.5", releaseDate28, list79); - ref ChangelogEntry reference80 = ref span[28]; - DateOnly releaseDate29 = new DateOnly(2025, 12, 21); + CollectionsMarshal.AsSpan(list80)[0] = "Fixed UI appearing in duties when debuff or interrupts occur"; + reference79 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list80); + reference75 = new ChangelogEntry("7.4.7", releaseDate27, list76); + ref ChangelogEntry reference80 = ref span[27]; + DateOnly releaseDate28 = new DateOnly(2025, 12, 23); num2 = 2; List list81 = new List(num2); CollectionsMarshal.SetCount(list81, num2); - Span span37 = CollectionsMarshal.AsSpan(list81); - ref ChangeEntry reference81 = ref span37[0]; + Span span39 = CollectionsMarshal.AsSpan(list81); + ref ChangeEntry reference81 = ref span39[0]; num3 = 1; List list82 = new List(num3); CollectionsMarshal.SetCount(list82, num3); - CollectionsMarshal.AsSpan(list82)[0] = "Changelog only shows once per update now"; - reference81 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list82); - ref ChangeEntry reference82 = ref span37[1]; - num3 = 1; + CollectionsMarshal.AsSpan(list82)[0] = "Added Cinema Mode to not skip cutscenes"; + reference81 = new ChangeEntry(EChangeCategory.Added, "Major Features", list82); + ref ChangeEntry reference82 = ref span39[1]; + num3 = 2; List list83 = new List(num3); CollectionsMarshal.SetCount(list83, num3); - CollectionsMarshal.AsSpan(list83)[0] = "Fixed gathering paths loading"; - reference82 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list83); - reference80 = new ChangelogEntry("7.4.4", releaseDate29, list81); - ref ChangelogEntry reference83 = ref span[29]; - DateOnly releaseDate30 = new DateOnly(2025, 12, 21); + Span span40 = CollectionsMarshal.AsSpan(list83); + span40[0] = "Added handling for Unsync (Party) to Party Watchdog and configuration to disable if in party"; + span40[1] = "Stop conditions now act as a true stop"; + reference82 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list83); + reference80 = new ChangelogEntry("7.4.6", releaseDate28, list81); + ref ChangelogEntry reference83 = ref span[28]; + DateOnly releaseDate29 = new DateOnly(2025, 12, 22); num2 = 1; List list84 = new List(num2); CollectionsMarshal.SetCount(list84, num2); ref ChangeEntry reference84 = ref CollectionsMarshal.AsSpan(list84)[0]; - num3 = 1; + num3 = 2; List list85 = new List(num3); CollectionsMarshal.SetCount(list85, num3); - CollectionsMarshal.AsSpan(list85)[0] = "Fixed changelog version checks"; - reference84 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list85); - reference83 = new ChangelogEntry("7.4.3", releaseDate30, list84); - ref ChangelogEntry reference85 = ref span[30]; - DateOnly releaseDate31 = new DateOnly(2025, 12, 20); + Span span41 = CollectionsMarshal.AsSpan(list85); + span41[0] = "Dive adjustments"; + span41[1] = "Logging message adjustments"; + reference84 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list85); + reference83 = new ChangelogEntry("7.4.5", releaseDate29, list84); + ref ChangelogEntry reference85 = ref span[29]; + DateOnly releaseDate30 = new DateOnly(2025, 12, 21); num2 = 2; List list86 = new List(num2); CollectionsMarshal.SetCount(list86, num2); - Span span38 = CollectionsMarshal.AsSpan(list86); - ref ChangeEntry reference86 = ref span38[0]; + Span span42 = CollectionsMarshal.AsSpan(list86); + ref ChangeEntry reference86 = ref span42[0]; num3 = 1; List list87 = new List(num3); CollectionsMarshal.SetCount(list87, num3); - CollectionsMarshal.AsSpan(list87)[0] = "Add 7.4 Starlight Celebration (2025) quest"; - reference86 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list87); - ref ChangeEntry reference87 = ref span38[1]; + CollectionsMarshal.AsSpan(list87)[0] = "Changelog only shows once per update now"; + reference86 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list87); + ref ChangeEntry reference87 = ref span42[1]; num3 = 1; List list88 = new List(num3); CollectionsMarshal.SetCount(list88, num3); - CollectionsMarshal.AsSpan(list88)[0] = "Fixed 7.4 MSQ"; + CollectionsMarshal.AsSpan(list88)[0] = "Fixed gathering paths loading"; reference87 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list88); - reference85 = new ChangelogEntry("7.4.2", releaseDate31, list86); - ref ChangelogEntry reference88 = ref span[31]; - DateOnly releaseDate32 = new DateOnly(2025, 12, 19); + reference85 = new ChangelogEntry("7.4.4", releaseDate30, list86); + ref ChangelogEntry reference88 = ref span[30]; + DateOnly releaseDate31 = new DateOnly(2025, 12, 21); num2 = 1; List list89 = new List(num2); CollectionsMarshal.SetCount(list89, num2); ref ChangeEntry reference89 = ref CollectionsMarshal.AsSpan(list89)[0]; - num3 = 2; + num3 = 1; List list90 = new List(num3); CollectionsMarshal.SetCount(list90, num3); - Span span39 = CollectionsMarshal.AsSpan(list90); - span39[0] = "Add 7.4 MSQ"; - span39[1] = "Add 7.4 Arcadion Raid quests"; - reference89 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list90); - reference88 = new ChangelogEntry("7.4.1", releaseDate32, list89); - ref ChangelogEntry reference90 = ref span[32]; - DateOnly releaseDate33 = new DateOnly(2025, 12, 17); - num2 = 1; + CollectionsMarshal.AsSpan(list90)[0] = "Fixed changelog version checks"; + reference89 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list90); + reference88 = new ChangelogEntry("7.4.3", releaseDate31, list89); + ref ChangelogEntry reference90 = ref span[31]; + DateOnly releaseDate32 = new DateOnly(2025, 12, 20); + num2 = 2; List list91 = new List(num2); CollectionsMarshal.SetCount(list91, num2); - CollectionsMarshal.AsSpan(list91)[0] = new ChangeEntry(EChangeCategory.Changed, "Api 14 update"); - reference90 = new ChangelogEntry("7.4.0", releaseDate33, list91); - ref ChangelogEntry reference91 = ref span[33]; - DateOnly releaseDate34 = new DateOnly(2025, 12, 6); - num2 = 2; - List list92 = new List(num2); - CollectionsMarshal.SetCount(list92, num2); - Span span40 = CollectionsMarshal.AsSpan(list92); - ref ChangeEntry reference92 = ref span40[0]; - num3 = 4; + Span span43 = CollectionsMarshal.AsSpan(list91); + ref ChangeEntry reference91 = ref span43[0]; + num3 = 1; + List list92 = new List(num3); + CollectionsMarshal.SetCount(list92, num3); + CollectionsMarshal.AsSpan(list92)[0] = "Add 7.4 Starlight Celebration (2025) quest"; + reference91 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list92); + ref ChangeEntry reference92 = ref span43[1]; + num3 = 1; List list93 = new List(num3); CollectionsMarshal.SetCount(list93, num3); - Span span41 = CollectionsMarshal.AsSpan(list93); - span41[0] = "Added reloading and rebuilding to movement system"; - span41[1] = "Improved interrupts and refresh states to allow continuation of questing"; - span41[2] = "Added player input detection to stop automation when manually moving character"; - span41[3] = "Added various missing quest sequences"; - reference92 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list93); - ref ChangeEntry reference93 = ref span40[1]; - num3 = 1; - List list94 = new List(num3); - CollectionsMarshal.SetCount(list94, num3); - CollectionsMarshal.AsSpan(list94)[0] = "Fixed reset task state to prevent stuck interactions after interruption"; - reference93 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list94); - reference91 = new ChangelogEntry("7.38.9", releaseDate34, list92); - ref ChangelogEntry reference94 = ref span[34]; - DateOnly releaseDate35 = new DateOnly(2025, 11, 29); - num2 = 2; - List list95 = new List(num2); - CollectionsMarshal.SetCount(list95, num2); - Span span42 = CollectionsMarshal.AsSpan(list95); - ref ChangeEntry reference95 = ref span42[0]; - num3 = 3; - List list96 = new List(num3); - CollectionsMarshal.SetCount(list96, num3); - Span span43 = CollectionsMarshal.AsSpan(list96); - span43[0] = "Movement update with automatic retrying if character can't reach target position"; - span43[1] = "Added Hunt mob data"; - span43[2] = "Refactored commands"; - reference95 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list96); - ref ChangeEntry reference96 = ref span42[1]; - num3 = 3; - List list97 = new List(num3); - CollectionsMarshal.SetCount(list97, num3); - Span span44 = CollectionsMarshal.AsSpan(list97); - span44[0] = "Fixed quest (Way of the Archer)"; - span44[1] = "Fixed quest (Spirithold Broken)"; - span44[2] = "Fixed quest (It's Probably Not Pirates)"; - reference96 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list97); - reference94 = new ChangelogEntry("7.38.8", releaseDate35, list95); - ref ChangelogEntry reference97 = ref span[35]; - DateOnly releaseDate36 = new DateOnly(2025, 11, 25); - num2 = 2; - List list98 = new List(num2); - CollectionsMarshal.SetCount(list98, num2); - Span span45 = CollectionsMarshal.AsSpan(list98); - ref ChangeEntry reference98 = ref span45[0]; + CollectionsMarshal.AsSpan(list93)[0] = "Fixed 7.4 MSQ"; + reference92 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list93); + reference90 = new ChangelogEntry("7.4.2", releaseDate32, list91); + ref ChangelogEntry reference93 = ref span[32]; + DateOnly releaseDate33 = new DateOnly(2025, 12, 19); + num2 = 1; + List list94 = new List(num2); + CollectionsMarshal.SetCount(list94, num2); + ref ChangeEntry reference94 = ref CollectionsMarshal.AsSpan(list94)[0]; num3 = 2; + List list95 = new List(num3); + CollectionsMarshal.SetCount(list95, num3); + Span span44 = CollectionsMarshal.AsSpan(list95); + span44[0] = "Add 7.4 MSQ"; + span44[1] = "Add 7.4 Arcadion Raid quests"; + reference94 = new ChangeEntry(EChangeCategory.QuestUpdates, "New Quest Paths", list95); + reference93 = new ChangelogEntry("7.4.1", releaseDate33, list94); + ref ChangelogEntry reference95 = ref span[33]; + DateOnly releaseDate34 = new DateOnly(2025, 12, 17); + num2 = 1; + List list96 = new List(num2); + CollectionsMarshal.SetCount(list96, num2); + CollectionsMarshal.AsSpan(list96)[0] = new ChangeEntry(EChangeCategory.Changed, "Api 14 update"); + reference95 = new ChangelogEntry("7.4.0", releaseDate34, list96); + ref ChangelogEntry reference96 = ref span[34]; + DateOnly releaseDate35 = new DateOnly(2025, 12, 6); + num2 = 2; + List list97 = new List(num2); + CollectionsMarshal.SetCount(list97, num2); + Span span45 = CollectionsMarshal.AsSpan(list97); + ref ChangeEntry reference97 = ref span45[0]; + num3 = 4; + List list98 = new List(num3); + CollectionsMarshal.SetCount(list98, num3); + Span span46 = CollectionsMarshal.AsSpan(list98); + span46[0] = "Added reloading and rebuilding to movement system"; + span46[1] = "Improved interrupts and refresh states to allow continuation of questing"; + span46[2] = "Added player input detection to stop automation when manually moving character"; + span46[3] = "Added various missing quest sequences"; + reference97 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list98); + ref ChangeEntry reference98 = ref span45[1]; + num3 = 1; List list99 = new List(num3); CollectionsMarshal.SetCount(list99, num3); - Span span46 = CollectionsMarshal.AsSpan(list99); - span46[0] = "Added individual sequence stop condition for each quest"; - span46[1] = "Added Trials to Duties tab in config"; - reference98 = new ChangeEntry(EChangeCategory.Added, "Major features", list99); - ref ChangeEntry reference99 = ref span45[1]; - num3 = 1; - List list100 = new List(num3); - CollectionsMarshal.SetCount(list100, num3); - CollectionsMarshal.AsSpan(list100)[0] = "Added IPC for stop conditions: GetQuestSequenceStopCondition, SetQuestSequenceStopCondition, RemoveQuestSequenceStopCondition, GetAllQuestSequenceStopConditions"; - reference99 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list100); - reference97 = new ChangelogEntry("7.38.7", releaseDate36, list98); - ref ChangelogEntry reference100 = ref span[36]; - DateOnly releaseDate37 = new DateOnly(2025, 11, 25); - num2 = 3; - List list101 = new List(num2); - CollectionsMarshal.SetCount(list101, num2); - Span span47 = CollectionsMarshal.AsSpan(list101); - ref ChangeEntry reference101 = ref span47[0]; - num3 = 2; + CollectionsMarshal.AsSpan(list99)[0] = "Fixed reset task state to prevent stuck interactions after interruption"; + reference98 = new ChangeEntry(EChangeCategory.Fixed, "Fixes", list99); + reference96 = new ChangelogEntry("7.38.9", releaseDate35, list97); + ref ChangelogEntry reference99 = ref span[35]; + DateOnly releaseDate36 = new DateOnly(2025, 11, 29); + num2 = 2; + List list100 = new List(num2); + CollectionsMarshal.SetCount(list100, num2); + Span span47 = CollectionsMarshal.AsSpan(list100); + ref ChangeEntry reference100 = ref span47[0]; + num3 = 3; + List list101 = new List(num3); + CollectionsMarshal.SetCount(list101, num3); + Span span48 = CollectionsMarshal.AsSpan(list101); + span48[0] = "Movement update with automatic retrying if character can't reach target position"; + span48[1] = "Added Hunt mob data"; + span48[2] = "Refactored commands"; + reference100 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list101); + ref ChangeEntry reference101 = ref span47[1]; + num3 = 3; List list102 = new List(num3); CollectionsMarshal.SetCount(list102, num3); - Span span48 = CollectionsMarshal.AsSpan(list102); - span48[0] = "Updated Allied Society journal text"; - span48[1] = "Improved Allied Society rank handling"; - reference101 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list102); - ref ChangeEntry reference102 = ref span47[1]; - num3 = 1; - List list103 = new List(num3); - CollectionsMarshal.SetCount(list103, num3); - CollectionsMarshal.AsSpan(list103)[0] = "Added IPC for Allied Society: AddAlliedSocietyOptimalQuests, GetAlliedSocietyOptimalQuests"; - reference102 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list103); - ref ChangeEntry reference103 = ref span47[2]; - num3 = 1; + Span span49 = CollectionsMarshal.AsSpan(list102); + span49[0] = "Fixed quest (Way of the Archer)"; + span49[1] = "Fixed quest (Spirithold Broken)"; + span49[2] = "Fixed quest (It's Probably Not Pirates)"; + reference101 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list102); + reference99 = new ChangelogEntry("7.38.8", releaseDate36, list100); + ref ChangelogEntry reference102 = ref span[36]; + DateOnly releaseDate37 = new DateOnly(2025, 11, 25); + num2 = 2; + List list103 = new List(num2); + CollectionsMarshal.SetCount(list103, num2); + Span span50 = CollectionsMarshal.AsSpan(list103); + ref ChangeEntry reference103 = ref span50[0]; + num3 = 2; List list104 = new List(num3); CollectionsMarshal.SetCount(list104, num3); - CollectionsMarshal.AsSpan(list104)[0] = "Fixed quest (We Come in Peace)"; - reference103 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list104); - reference100 = new ChangelogEntry("7.38.6", releaseDate37, list101); - ref ChangelogEntry reference104 = ref span[37]; - DateOnly releaseDate38 = new DateOnly(2025, 11, 24); - num2 = 2; - List list105 = new List(num2); - CollectionsMarshal.SetCount(list105, num2); - Span span49 = CollectionsMarshal.AsSpan(list105); - ref ChangeEntry reference105 = ref span49[0]; - num3 = 1; - List list106 = new List(num3); - CollectionsMarshal.SetCount(list106, num3); - CollectionsMarshal.AsSpan(list106)[0] = "Added Allied Society daily allowance tracker with bulk quest adding buttons"; - reference105 = new ChangeEntry(EChangeCategory.Added, "Major features", list106); - ref ChangeEntry reference106 = ref span49[1]; + Span span51 = CollectionsMarshal.AsSpan(list104); + span51[0] = "Added individual sequence stop condition for each quest"; + span51[1] = "Added Trials to Duties tab in config"; + reference103 = new ChangeEntry(EChangeCategory.Added, "Major features", list104); + ref ChangeEntry reference104 = ref span50[1]; num3 = 1; + List list105 = new List(num3); + CollectionsMarshal.SetCount(list105, num3); + CollectionsMarshal.AsSpan(list105)[0] = "Added IPC for stop conditions: GetQuestSequenceStopCondition, SetQuestSequenceStopCondition, RemoveQuestSequenceStopCondition, GetAllQuestSequenceStopConditions"; + reference104 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list105); + reference102 = new ChangelogEntry("7.38.7", releaseDate37, list103); + ref ChangelogEntry reference105 = ref span[37]; + DateOnly releaseDate38 = new DateOnly(2025, 11, 25); + num2 = 3; + List list106 = new List(num2); + CollectionsMarshal.SetCount(list106, num2); + Span span52 = CollectionsMarshal.AsSpan(list106); + ref ChangeEntry reference106 = ref span52[0]; + num3 = 2; List list107 = new List(num3); CollectionsMarshal.SetCount(list107, num3); - CollectionsMarshal.AsSpan(list107)[0] = "Added IPC for Allied Society: GetRemainingAllowances, GetTimeUntilReset, GetAvailableQuestIds, GetAllAvailableQuestCounts, IsMaxRank, GetCurrentRank, GetSocietiesWithAvailableQuests"; - reference106 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list107); - reference104 = new ChangelogEntry("7.38.5", releaseDate38, list105); - ref ChangelogEntry reference107 = ref span[38]; - DateOnly releaseDate39 = new DateOnly(2025, 11, 23); - num2 = 2; - List list108 = new List(num2); - CollectionsMarshal.SetCount(list108, num2); - Span span50 = CollectionsMarshal.AsSpan(list108); - ref ChangeEntry reference108 = ref span50[0]; + Span span53 = CollectionsMarshal.AsSpan(list107); + span53[0] = "Updated Allied Society journal text"; + span53[1] = "Improved Allied Society rank handling"; + reference106 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list107); + ref ChangeEntry reference107 = ref span52[1]; + num3 = 1; + List list108 = new List(num3); + CollectionsMarshal.SetCount(list108, num3); + CollectionsMarshal.AsSpan(list108)[0] = "Added IPC for Allied Society: AddAlliedSocietyOptimalQuests, GetAlliedSocietyOptimalQuests"; + reference107 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list108); + ref ChangeEntry reference108 = ref span52[2]; num3 = 1; List list109 = new List(num3); CollectionsMarshal.SetCount(list109, num3); - CollectionsMarshal.AsSpan(list109)[0] = "Explicitly declare support for BMR singleplayer duty (The Rematch)"; - reference108 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list109); - ref ChangeEntry reference109 = ref span50[1]; - num3 = 8; - List list110 = new List(num3); - CollectionsMarshal.SetCount(list110, num3); - Span span51 = CollectionsMarshal.AsSpan(list110); - span51[0] = "Fixed quest (Microbrewing) to not get stuck near ramp"; - span51[1] = "Fixed quest (The Illuminated Land) where pathing would kill the player due to fall damage"; - span51[2] = "Fixed quest (It's Probably Not Pirates) improper pathing and removed unneeded step"; - span51[3] = "Fixed quest (The Black Wolf's Ultimatum) not exiting landing area"; - span51[4] = "Fixed quest (Magiteknical Failure) from not interacting with NPC due to being mounted"; - span51[5] = "Fixed quest (We Come in Peace) shortcut navigation"; - span51[6] = "Fixed quest (Poisoned Hearts) where incorrect pathing caused the player to die"; - span51[7] = "Fixed quests (Savage Snares) and (An Apple a Day) not detecting kills"; - reference109 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list110); - reference107 = new ChangelogEntry("7.38.4", releaseDate39, list108); - ref ChangelogEntry reference110 = ref span[39]; - DateOnly releaseDate40 = new DateOnly(2025, 11, 23); - num2 = 3; - List list111 = new List(num2); - CollectionsMarshal.SetCount(list111, num2); - Span span52 = CollectionsMarshal.AsSpan(list111); - ref ChangeEntry reference111 = ref span52[0]; - num3 = 2; + CollectionsMarshal.AsSpan(list109)[0] = "Fixed quest (We Come in Peace)"; + reference108 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list109); + reference105 = new ChangelogEntry("7.38.6", releaseDate38, list106); + ref ChangelogEntry reference109 = ref span[38]; + DateOnly releaseDate39 = new DateOnly(2025, 11, 24); + num2 = 2; + List list110 = new List(num2); + CollectionsMarshal.SetCount(list110, num2); + Span span54 = CollectionsMarshal.AsSpan(list110); + ref ChangeEntry reference110 = ref span54[0]; + num3 = 1; + List list111 = new List(num3); + CollectionsMarshal.SetCount(list111, num3); + CollectionsMarshal.AsSpan(list111)[0] = "Added Allied Society daily allowance tracker with bulk quest adding buttons"; + reference110 = new ChangeEntry(EChangeCategory.Added, "Major features", list111); + ref ChangeEntry reference111 = ref span54[1]; + num3 = 1; List list112 = new List(num3); CollectionsMarshal.SetCount(list112, num3); - Span span53 = CollectionsMarshal.AsSpan(list112); - span53[0] = "Added RequireHq to crafting InteractionType"; - span53[1] = "Mark GC quests as Locked if rank not achieved"; - reference111 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list112); - ref ChangeEntry reference112 = ref span52[1]; - num3 = 2; - List list113 = new List(num3); - CollectionsMarshal.SetCount(list113, num3); - Span span54 = CollectionsMarshal.AsSpan(list113); - span54[0] = "Added IPC for stop conditions: GetStopConditionsEnabled, SetStopConditionsEnabled, GetStopQuestList, AddStopQuest, RemoveStopQuest, ClearStopQuests, GetLevelStopCondition, SetLevelStopCondition, GetSequenceStopCondition, SetSequenceStopCondition"; - span54[1] = "Added IPC for priority quests: GetPriorityQuests, RemovePriorityQuest, ReorderPriorityQuest, GetAvailablePresets, GetPresetQuests, AddPresetToPriority, IsPresetAvailable, IsQuestInPriority, GetQuestPriorityIndex, HasAvailablePriorityQuests"; - reference112 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list113); - ref ChangeEntry reference113 = ref span52[2]; - num3 = 3; + CollectionsMarshal.AsSpan(list112)[0] = "Added IPC for Allied Society: GetRemainingAllowances, GetTimeUntilReset, GetAvailableQuestIds, GetAllAvailableQuestCounts, IsMaxRank, GetCurrentRank, GetSocietiesWithAvailableQuests"; + reference111 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list112); + reference109 = new ChangelogEntry("7.38.5", releaseDate39, list110); + ref ChangelogEntry reference112 = ref span[39]; + DateOnly releaseDate40 = new DateOnly(2025, 11, 23); + num2 = 2; + List list113 = new List(num2); + CollectionsMarshal.SetCount(list113, num2); + Span span55 = CollectionsMarshal.AsSpan(list113); + ref ChangeEntry reference113 = ref span55[0]; + num3 = 1; List list114 = new List(num3); CollectionsMarshal.SetCount(list114, num3); - Span span55 = CollectionsMarshal.AsSpan(list114); - span55[0] = "Fixed line breaks not working in dialog strings"; - span55[1] = "Fixed quest (Labor of Love)"; - span55[2] = "Fixed quest (Sea of Sorrow)"; - reference113 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list114); - reference110 = new ChangelogEntry("7.38.3", releaseDate40, list111); - ref ChangelogEntry reference114 = ref span[40]; - DateOnly releaseDate41 = new DateOnly(2025, 11, 18); + CollectionsMarshal.AsSpan(list114)[0] = "Explicitly declare support for BMR singleplayer duty (The Rematch)"; + reference113 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list114); + ref ChangeEntry reference114 = ref span55[1]; + num3 = 8; + List list115 = new List(num3); + CollectionsMarshal.SetCount(list115, num3); + Span span56 = CollectionsMarshal.AsSpan(list115); + span56[0] = "Fixed quest (Microbrewing) to not get stuck near ramp"; + span56[1] = "Fixed quest (The Illuminated Land) where pathing would kill the player due to fall damage"; + span56[2] = "Fixed quest (It's Probably Not Pirates) improper pathing and removed unneeded step"; + span56[3] = "Fixed quest (The Black Wolf's Ultimatum) not exiting landing area"; + span56[4] = "Fixed quest (Magiteknical Failure) from not interacting with NPC due to being mounted"; + span56[5] = "Fixed quest (We Come in Peace) shortcut navigation"; + span56[6] = "Fixed quest (Poisoned Hearts) where incorrect pathing caused the player to die"; + span56[7] = "Fixed quests (Savage Snares) and (An Apple a Day) not detecting kills"; + reference114 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list115); + reference112 = new ChangelogEntry("7.38.4", releaseDate40, list113); + ref ChangelogEntry reference115 = ref span[40]; + DateOnly releaseDate41 = new DateOnly(2025, 11, 23); num2 = 3; - List list115 = new List(num2); - CollectionsMarshal.SetCount(list115, num2); - Span span56 = CollectionsMarshal.AsSpan(list115); - ref ChangeEntry reference115 = ref span56[0]; + List list116 = new List(num2); + CollectionsMarshal.SetCount(list116, num2); + Span span57 = CollectionsMarshal.AsSpan(list116); + ref ChangeEntry reference116 = ref span57[0]; num3 = 2; - List list116 = new List(num3); - CollectionsMarshal.SetCount(list116, num3); - Span span57 = CollectionsMarshal.AsSpan(list116); - span57[0] = "Auto Duty unsync options for each duty (Duty Support, Unsync Solo, Unsync Party)"; - span57[1] = "Added Auto Duty unsync options to quest schema and updated quests using old unsync method"; - reference115 = new ChangeEntry(EChangeCategory.Added, "Major features", list116); - ref ChangeEntry reference116 = ref span56[1]; - num3 = 3; List list117 = new List(num3); CollectionsMarshal.SetCount(list117, num3); Span span58 = CollectionsMarshal.AsSpan(list117); - span58[0] = "Added IPC for duty sync handling: GetDefaultDutyMode, SetDefaultDutyMode"; - span58[1] = "Added IPC for duty mode overrides: GetDutyModeOverride, SetDutyModeOverride"; - span58[2] = "Added IPC for clearing overrides: ClearDutyModeOverride, ClearAllDutyModeOverrides"; - reference116 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list117); - span56[2] = new ChangeEntry(EChangeCategory.Fixed, "Fixed quest (Constant Cravings)"); - reference114 = new ChangelogEntry("7.38.2", releaseDate41, list115); - ref ChangelogEntry reference117 = ref span[41]; - DateOnly releaseDate42 = new DateOnly(2025, 11, 18); - num2 = 3; - List list118 = new List(num2); - CollectionsMarshal.SetCount(list118, num2); - Span span59 = CollectionsMarshal.AsSpan(list118); - ref ChangeEntry reference118 = ref span59[0]; - num3 = 1; + span58[0] = "Added RequireHq to crafting InteractionType"; + span58[1] = "Mark GC quests as Locked if rank not achieved"; + reference116 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list117); + ref ChangeEntry reference117 = ref span57[1]; + num3 = 2; + List list118 = new List(num3); + CollectionsMarshal.SetCount(list118, num3); + Span span59 = CollectionsMarshal.AsSpan(list118); + span59[0] = "Added IPC for stop conditions: GetStopConditionsEnabled, SetStopConditionsEnabled, GetStopQuestList, AddStopQuest, RemoveStopQuest, ClearStopQuests, GetLevelStopCondition, SetLevelStopCondition, GetSequenceStopCondition, SetSequenceStopCondition"; + span59[1] = "Added IPC for priority quests: GetPriorityQuests, RemovePriorityQuest, ReorderPriorityQuest, GetAvailablePresets, GetPresetQuests, AddPresetToPriority, IsPresetAvailable, IsQuestInPriority, GetQuestPriorityIndex, HasAvailablePriorityQuests"; + reference117 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list118); + ref ChangeEntry reference118 = ref span57[2]; + num3 = 3; List list119 = new List(num3); CollectionsMarshal.SetCount(list119, num3); - CollectionsMarshal.AsSpan(list119)[0] = "Added new fields to quest schema"; - reference118 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list119); - ref ChangeEntry reference119 = ref span59[1]; - num3 = 3; - List list120 = new List(num3); - CollectionsMarshal.SetCount(list120, num3); - Span span60 = CollectionsMarshal.AsSpan(list120); - span60[0] = "A Faerie Tale Come True"; - span60[1] = "Constant Cravings"; - span60[2] = "A Bridge Too Full"; - reference119 = new ChangeEntry(EChangeCategory.QuestUpdates, "Added new quest paths", list120); - ref ChangeEntry reference120 = ref span59[2]; - num3 = 3; + Span span60 = CollectionsMarshal.AsSpan(list119); + span60[0] = "Fixed line breaks not working in dialog strings"; + span60[1] = "Fixed quest (Labor of Love)"; + span60[2] = "Fixed quest (Sea of Sorrow)"; + reference118 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list119); + reference115 = new ChangelogEntry("7.38.3", releaseDate41, list116); + ref ChangelogEntry reference119 = ref span[41]; + DateOnly releaseDate42 = new DateOnly(2025, 11, 18); + num2 = 3; + List list120 = new List(num2); + CollectionsMarshal.SetCount(list120, num2); + Span span61 = CollectionsMarshal.AsSpan(list120); + ref ChangeEntry reference120 = ref span61[0]; + num3 = 2; List list121 = new List(num3); CollectionsMarshal.SetCount(list121, num3); - Span span61 = CollectionsMarshal.AsSpan(list121); - span61[0] = "Fixed various quest schemas"; - span61[1] = "Fixed changelog bullet point encoding"; - span61[2] = "Fixed item use to wait until item is used before next action"; - reference120 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list121); - reference117 = new ChangelogEntry("7.38.1", releaseDate42, list118); - ref ChangelogEntry reference121 = ref span[42]; - DateOnly releaseDate43 = new DateOnly(2025, 11, 17); - num2 = 5; - List list122 = new List(num2); - CollectionsMarshal.SetCount(list122, num2); - Span span62 = CollectionsMarshal.AsSpan(list122); - ref ChangeEntry reference122 = ref span62[0]; - num3 = 2; - List list123 = new List(num3); - CollectionsMarshal.SetCount(list123, num3); - Span span63 = CollectionsMarshal.AsSpan(list123); - span63[0] = "Quest sequence window to show expected sequences in each quest (with quest searching)"; - span63[1] = "Changelog"; - reference122 = new ChangeEntry(EChangeCategory.Added, "Major features", list123); - ref ChangeEntry reference123 = ref span62[1]; - num3 = 2; + Span span62 = CollectionsMarshal.AsSpan(list121); + span62[0] = "Auto Duty unsync options for each duty (Duty Support, Unsync Solo, Unsync Party)"; + span62[1] = "Added Auto Duty unsync options to quest schema and updated quests using old unsync method"; + reference120 = new ChangeEntry(EChangeCategory.Added, "Major features", list121); + ref ChangeEntry reference121 = ref span61[1]; + num3 = 3; + List list122 = new List(num3); + CollectionsMarshal.SetCount(list122, num3); + Span span63 = CollectionsMarshal.AsSpan(list122); + span63[0] = "Added IPC for duty sync handling: GetDefaultDutyMode, SetDefaultDutyMode"; + span63[1] = "Added IPC for duty mode overrides: GetDutyModeOverride, SetDutyModeOverride"; + span63[2] = "Added IPC for clearing overrides: ClearDutyModeOverride, ClearAllDutyModeOverrides"; + reference121 = new ChangeEntry(EChangeCategory.Added, "IPC changes", list122); + span61[2] = new ChangeEntry(EChangeCategory.Fixed, "Fixed quest (Constant Cravings)"); + reference119 = new ChangelogEntry("7.38.2", releaseDate42, list120); + ref ChangelogEntry reference122 = ref span[42]; + DateOnly releaseDate43 = new DateOnly(2025, 11, 18); + num2 = 3; + List list123 = new List(num2); + CollectionsMarshal.SetCount(list123, num2); + Span span64 = CollectionsMarshal.AsSpan(list123); + ref ChangeEntry reference123 = ref span64[0]; + num3 = 1; List list124 = new List(num3); CollectionsMarshal.SetCount(list124, num3); - Span span64 = CollectionsMarshal.AsSpan(list124); - span64[0] = "Updated quest schemas"; - span64[1] = "Added search bar to preferred mounts and capitalization to mirror game mount names"; + CollectionsMarshal.AsSpan(list124)[0] = "Added new fields to quest schema"; reference123 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list124); - ref ChangeEntry reference124 = ref span62[2]; + ref ChangeEntry reference124 = ref span64[1]; num3 = 3; List list125 = new List(num3); CollectionsMarshal.SetCount(list125, num3); Span span65 = CollectionsMarshal.AsSpan(list125); - span65[0] = "Renamed IsQuestCompleted → IsQuestComplete"; - span65[1] = "Renamed IsQuestAvailable → IsReadyToAcceptQuest"; - span65[2] = "Added GetCurrentTask IPC"; - reference124 = new ChangeEntry(EChangeCategory.Changed, "IPC changes", list125); - span62[3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added all Hildibrand quests"); - span62[4] = new ChangeEntry(EChangeCategory.Fixed, "Fixed credits/cutscenes playback"); - reference121 = new ChangelogEntry("7.38.0", releaseDate43, list122); - ref ChangelogEntry reference125 = ref span[43]; - DateOnly releaseDate44 = new DateOnly(2025, 11, 8); - num2 = 1; - List list126 = new List(num2); - CollectionsMarshal.SetCount(list126, num2); - CollectionsMarshal.AsSpan(list126)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Fall Guys quest (Just Crowning Around)"); - reference125 = new ChangelogEntry("6.38", releaseDate44, list126); - ref ChangelogEntry reference126 = ref span[44]; - DateOnly releaseDate45 = new DateOnly(2025, 11, 8); - num2 = 1; + span65[0] = "A Faerie Tale Come True"; + span65[1] = "Constant Cravings"; + span65[2] = "A Bridge Too Full"; + reference124 = new ChangeEntry(EChangeCategory.QuestUpdates, "Added new quest paths", list125); + ref ChangeEntry reference125 = ref span64[2]; + num3 = 3; + List list126 = new List(num3); + CollectionsMarshal.SetCount(list126, num3); + Span span66 = CollectionsMarshal.AsSpan(list126); + span66[0] = "Fixed various quest schemas"; + span66[1] = "Fixed changelog bullet point encoding"; + span66[2] = "Fixed item use to wait until item is used before next action"; + reference125 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list126); + reference122 = new ChangelogEntry("7.38.1", releaseDate43, list123); + ref ChangelogEntry reference126 = ref span[43]; + DateOnly releaseDate44 = new DateOnly(2025, 11, 17); + num2 = 5; List list127 = new List(num2); CollectionsMarshal.SetCount(list127, num2); - CollectionsMarshal.AsSpan(list127)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Cosmic Exploration and various unlock quests"); - reference126 = new ChangelogEntry("6.37", releaseDate45, list127); - ref ChangelogEntry reference127 = ref span[45]; - DateOnly releaseDate46 = new DateOnly(2025, 11, 2); - num2 = 1; - List list128 = new List(num2); - CollectionsMarshal.SetCount(list128, num2); - CollectionsMarshal.AsSpan(list128)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy Rank 6 quest (With High Spirits)"); - reference127 = new ChangelogEntry("6.36", releaseDate46, list128); - ref ChangelogEntry reference128 = ref span[46]; - DateOnly releaseDate47 = new DateOnly(2025, 10, 28); - num2 = 1; - List list129 = new List(num2); - CollectionsMarshal.SetCount(list129, num2); - CollectionsMarshal.AsSpan(list129)[0] = new ChangeEntry(EChangeCategory.Fixed, "Fixed level 3 MSQ handling if character started on non-XP buff world"); - reference128 = new ChangelogEntry("6.35", releaseDate47, list129); - ref ChangelogEntry reference129 = ref span[47]; - DateOnly releaseDate48 = new DateOnly(2025, 10, 23); - num2 = 2; - List list130 = new List(num2); - CollectionsMarshal.SetCount(list130, num2); - Span span66 = CollectionsMarshal.AsSpan(list130); - span66[0] = new ChangeEntry(EChangeCategory.Added, "Added clear priority quests on logout and on completion config settings"); - span66[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed priority quest importing to respect import order"); - reference129 = new ChangelogEntry("6.34", releaseDate48, list130); - ref ChangelogEntry reference130 = ref span[48]; - DateOnly releaseDate49 = new DateOnly(2025, 10, 23); + Span span67 = CollectionsMarshal.AsSpan(list127); + ref ChangeEntry reference127 = ref span67[0]; + num3 = 2; + List list128 = new List(num3); + CollectionsMarshal.SetCount(list128, num3); + Span span68 = CollectionsMarshal.AsSpan(list128); + span68[0] = "Quest sequence window to show expected sequences in each quest (with quest searching)"; + span68[1] = "Changelog"; + reference127 = new ChangeEntry(EChangeCategory.Added, "Major features", list128); + ref ChangeEntry reference128 = ref span67[1]; + num3 = 2; + List list129 = new List(num3); + CollectionsMarshal.SetCount(list129, num3); + Span span69 = CollectionsMarshal.AsSpan(list129); + span69[0] = "Updated quest schemas"; + span69[1] = "Added search bar to preferred mounts and capitalization to mirror game mount names"; + reference128 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list129); + ref ChangeEntry reference129 = ref span67[2]; + num3 = 3; + List list130 = new List(num3); + CollectionsMarshal.SetCount(list130, num3); + Span span70 = CollectionsMarshal.AsSpan(list130); + span70[0] = "Renamed IsQuestCompleted → IsQuestComplete"; + span70[1] = "Renamed IsQuestAvailable → IsReadyToAcceptQuest"; + span70[2] = "Added GetCurrentTask IPC"; + reference129 = new ChangeEntry(EChangeCategory.Changed, "IPC changes", list130); + span67[3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added all Hildibrand quests"); + span67[4] = new ChangeEntry(EChangeCategory.Fixed, "Fixed credits/cutscenes playback"); + reference126 = new ChangelogEntry("7.38.0", releaseDate44, list127); + ref ChangelogEntry reference130 = ref span[44]; + DateOnly releaseDate45 = new DateOnly(2025, 11, 8); num2 = 1; List list131 = new List(num2); CollectionsMarshal.SetCount(list131, num2); - CollectionsMarshal.AsSpan(list131)[0] = new ChangeEntry(EChangeCategory.Fixed, "Fixed RSR combat module"); - reference130 = new ChangelogEntry("6.33", releaseDate49, list131); - ref ChangelogEntry reference131 = ref span[49]; - DateOnly releaseDate50 = new DateOnly(2025, 10, 23); + CollectionsMarshal.AsSpan(list131)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Fall Guys quest (Just Crowning Around)"); + reference130 = new ChangelogEntry("6.38", releaseDate45, list131); + ref ChangelogEntry reference131 = ref span[45]; + DateOnly releaseDate46 = new DateOnly(2025, 11, 8); num2 = 1; List list132 = new List(num2); CollectionsMarshal.SetCount(list132, num2); - CollectionsMarshal.AsSpan(list132)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy Rank 5 quest (Forged in Corn)"); - reference131 = new ChangelogEntry("6.32", releaseDate50, list132); - ref ChangelogEntry reference132 = ref span[50]; - DateOnly releaseDate51 = new DateOnly(2025, 10, 21); + CollectionsMarshal.AsSpan(list132)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Cosmic Exploration and various unlock quests"); + reference131 = new ChangelogEntry("6.37", releaseDate46, list132); + ref ChangelogEntry reference132 = ref span[46]; + DateOnly releaseDate47 = new DateOnly(2025, 11, 2); num2 = 1; List list133 = new List(num2); CollectionsMarshal.SetCount(list133, num2); - CollectionsMarshal.AsSpan(list133)[0] = new ChangeEntry(EChangeCategory.Changed, "Added checks for moogle and allied society quests when using add all available quests"); - reference132 = new ChangelogEntry("6.31", releaseDate51, list133); - ref ChangelogEntry reference133 = ref span[51]; - DateOnly releaseDate52 = new DateOnly(2025, 10, 21); + CollectionsMarshal.AsSpan(list133)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy Rank 6 quest (With High Spirits)"); + reference132 = new ChangelogEntry("6.36", releaseDate47, list133); + ref ChangelogEntry reference133 = ref span[47]; + DateOnly releaseDate48 = new DateOnly(2025, 10, 28); num2 = 1; List list134 = new List(num2); CollectionsMarshal.SetCount(list134, num2); - CollectionsMarshal.AsSpan(list134)[0] = new ChangeEntry(EChangeCategory.Added, "Added button to journal that allows adding all available quests to priority"); - reference133 = new ChangelogEntry("6.30", releaseDate52, list134); - ref ChangelogEntry reference134 = ref span[52]; - DateOnly releaseDate53 = new DateOnly(2025, 10, 20); + CollectionsMarshal.AsSpan(list134)[0] = new ChangeEntry(EChangeCategory.Fixed, "Fixed level 3 MSQ handling if character started on non-XP buff world"); + reference133 = new ChangelogEntry("6.35", releaseDate48, list134); + ref ChangelogEntry reference134 = ref span[48]; + DateOnly releaseDate49 = new DateOnly(2025, 10, 23); num2 = 2; List list135 = new List(num2); CollectionsMarshal.SetCount(list135, num2); - Span span67 = CollectionsMarshal.AsSpan(list135); - ref ChangeEntry reference135 = ref span67[0]; - num3 = 2; - List list136 = new List(num3); - CollectionsMarshal.SetCount(list136, num3); - Span span68 = CollectionsMarshal.AsSpan(list136); - span68[0] = "Added item count to combat handling rework"; - span68[1] = "Updated Pandora conflicting features"; - reference135 = new ChangeEntry(EChangeCategory.Changed, "Combat handling improvements", list136); - span67[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed quest to purchase Gysahl Greens if not in inventory"); - reference134 = new ChangelogEntry("6.29", releaseDate53, list135); - ref ChangelogEntry reference136 = ref span[53]; - DateOnly releaseDate54 = new DateOnly(2025, 10, 19); + Span span71 = CollectionsMarshal.AsSpan(list135); + span71[0] = new ChangeEntry(EChangeCategory.Added, "Added clear priority quests on logout and on completion config settings"); + span71[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed priority quest importing to respect import order"); + reference134 = new ChangelogEntry("6.34", releaseDate49, list135); + ref ChangelogEntry reference135 = ref span[49]; + DateOnly releaseDate50 = new DateOnly(2025, 10, 23); + num2 = 1; + List list136 = new List(num2); + CollectionsMarshal.SetCount(list136, num2); + CollectionsMarshal.AsSpan(list136)[0] = new ChangeEntry(EChangeCategory.Fixed, "Fixed RSR combat module"); + reference135 = new ChangelogEntry("6.33", releaseDate50, list136); + ref ChangelogEntry reference136 = ref span[50]; + DateOnly releaseDate51 = new DateOnly(2025, 10, 23); num2 = 1; List list137 = new List(num2); CollectionsMarshal.SetCount(list137, num2); - CollectionsMarshal.AsSpan(list137)[0] = new ChangeEntry(EChangeCategory.Changed, "Reworked kill count combat handling - combat and enemy kills are now processed instantly"); - reference136 = new ChangelogEntry("6.28", releaseDate54, list137); - ref ChangelogEntry reference137 = ref span[54]; - DateOnly releaseDate55 = new DateOnly(2025, 10, 18); - num2 = 2; + CollectionsMarshal.AsSpan(list137)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy Rank 5 quest (Forged in Corn)"); + reference136 = new ChangelogEntry("6.32", releaseDate51, list137); + ref ChangelogEntry reference137 = ref span[51]; + DateOnly releaseDate52 = new DateOnly(2025, 10, 21); + num2 = 1; List list138 = new List(num2); CollectionsMarshal.SetCount(list138, num2); - Span span69 = CollectionsMarshal.AsSpan(list138); - span69[0] = new ChangeEntry(EChangeCategory.Changed, "Improved Aether Current checking logic"); - span69[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed Chocobo Taxi Stand CheckSkip error and Patch 7.3 Fantasia unlock quest date/time"); - reference137 = new ChangelogEntry("6.27", releaseDate55, list138); - ref ChangelogEntry reference138 = ref span[55]; - DateOnly releaseDate56 = new DateOnly(2025, 10, 18); + CollectionsMarshal.AsSpan(list138)[0] = new ChangeEntry(EChangeCategory.Changed, "Added checks for moogle and allied society quests when using add all available quests"); + reference137 = new ChangelogEntry("6.31", releaseDate52, list138); + ref ChangelogEntry reference138 = ref span[52]; + DateOnly releaseDate53 = new DateOnly(2025, 10, 21); num2 = 1; List list139 = new List(num2); CollectionsMarshal.SetCount(list139, num2); - CollectionsMarshal.AsSpan(list139)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 4 quests"); - reference138 = new ChangelogEntry("6.26", releaseDate56, list139); - ref ChangelogEntry reference139 = ref span[56]; - DateOnly releaseDate57 = new DateOnly(2025, 10, 17); - num2 = 1; + CollectionsMarshal.AsSpan(list139)[0] = new ChangeEntry(EChangeCategory.Added, "Added button to journal that allows adding all available quests to priority"); + reference138 = new ChangelogEntry("6.30", releaseDate53, list139); + ref ChangelogEntry reference139 = ref span[53]; + DateOnly releaseDate54 = new DateOnly(2025, 10, 20); + num2 = 2; List list140 = new List(num2); CollectionsMarshal.SetCount(list140, num2); - CollectionsMarshal.AsSpan(list140)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added All Saints' Wake 2025 quests and 7.35 Yok Huy rank 4 quests"); - reference139 = new ChangelogEntry("6.25", releaseDate57, list140); - ref ChangelogEntry reference140 = ref span[57]; - DateOnly releaseDate58 = new DateOnly(2025, 10, 16); - num2 = 1; - List list141 = new List(num2); - CollectionsMarshal.SetCount(list141, num2); - CollectionsMarshal.AsSpan(list141)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 4 quests and Deep Dungeon quest"); - reference140 = new ChangelogEntry("6.24", releaseDate58, list141); - ref ChangelogEntry reference141 = ref span[58]; - DateOnly releaseDate59 = new DateOnly(2025, 10, 13); + Span span72 = CollectionsMarshal.AsSpan(list140); + ref ChangeEntry reference140 = ref span72[0]; + num3 = 2; + List list141 = new List(num3); + CollectionsMarshal.SetCount(list141, num3); + Span span73 = CollectionsMarshal.AsSpan(list141); + span73[0] = "Added item count to combat handling rework"; + span73[1] = "Updated Pandora conflicting features"; + reference140 = new ChangeEntry(EChangeCategory.Changed, "Combat handling improvements", list141); + span72[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed quest to purchase Gysahl Greens if not in inventory"); + reference139 = new ChangelogEntry("6.29", releaseDate54, list140); + ref ChangelogEntry reference141 = ref span[54]; + DateOnly releaseDate55 = new DateOnly(2025, 10, 19); num2 = 1; List list142 = new List(num2); CollectionsMarshal.SetCount(list142, num2); - CollectionsMarshal.AsSpan(list142)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 3 quest (Larder Logistics)"); - reference141 = new ChangelogEntry("6.23", releaseDate59, list142); - ref ChangelogEntry reference142 = ref span[59]; - DateOnly releaseDate60 = new DateOnly(2025, 10, 12); - num2 = 3; + CollectionsMarshal.AsSpan(list142)[0] = new ChangeEntry(EChangeCategory.Changed, "Reworked kill count combat handling - combat and enemy kills are now processed instantly"); + reference141 = new ChangelogEntry("6.28", releaseDate55, list142); + ref ChangelogEntry reference142 = ref span[55]; + DateOnly releaseDate56 = new DateOnly(2025, 10, 18); + num2 = 2; List list143 = new List(num2); CollectionsMarshal.SetCount(list143, num2); - Span span70 = CollectionsMarshal.AsSpan(list143); - span70[0] = new ChangeEntry(EChangeCategory.Changed, "Prevent disabled or Locked quests from being started as 'Start as next quest'"); - span70[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 3 quests"); - span70[2] = new ChangeEntry(EChangeCategory.Fixed, "Fixed Yok Huy quest and journal quest chain priority issues"); - reference142 = new ChangelogEntry("6.22", releaseDate60, list143); - ref ChangelogEntry reference143 = ref span[60]; - DateOnly releaseDate61 = new DateOnly(2025, 10, 12); - num2 = 2; + Span span74 = CollectionsMarshal.AsSpan(list143); + span74[0] = new ChangeEntry(EChangeCategory.Changed, "Improved Aether Current checking logic"); + span74[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed Chocobo Taxi Stand CheckSkip error and Patch 7.3 Fantasia unlock quest date/time"); + reference142 = new ChangelogEntry("6.27", releaseDate56, list143); + ref ChangelogEntry reference143 = ref span[56]; + DateOnly releaseDate57 = new DateOnly(2025, 10, 18); + num2 = 1; List list144 = new List(num2); CollectionsMarshal.SetCount(list144, num2); - Span span71 = CollectionsMarshal.AsSpan(list144); - span71[0] = new ChangeEntry(EChangeCategory.Added, "Added expansion abbreviation to journal window"); - span71[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 3 quests"); - reference143 = new ChangelogEntry("6.21", releaseDate61, list144); - ref ChangelogEntry reference144 = ref span[61]; - DateOnly releaseDate62 = new DateOnly(2025, 10, 10); - num2 = 2; + CollectionsMarshal.AsSpan(list144)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 4 quests"); + reference143 = new ChangelogEntry("6.26", releaseDate57, list144); + ref ChangelogEntry reference144 = ref span[57]; + DateOnly releaseDate58 = new DateOnly(2025, 10, 17); + num2 = 1; List list145 = new List(num2); CollectionsMarshal.SetCount(list145, num2); - Span span72 = CollectionsMarshal.AsSpan(list145); - span72[0] = new ChangeEntry(EChangeCategory.Changed, "Allow completed repeatable quests to be used with 'Add quest and requirements to priority' feature"); - span72[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 1 quest (A Work of Cart)"); - reference144 = new ChangelogEntry("6.20", releaseDate62, list145); - ref ChangelogEntry reference145 = ref span[62]; - DateOnly releaseDate63 = new DateOnly(2025, 10, 9); - num2 = 3; + CollectionsMarshal.AsSpan(list145)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added All Saints' Wake 2025 quests and 7.35 Yok Huy rank 4 quests"); + reference144 = new ChangelogEntry("6.25", releaseDate58, list145); + ref ChangelogEntry reference145 = ref span[58]; + DateOnly releaseDate59 = new DateOnly(2025, 10, 16); + num2 = 1; List list146 = new List(num2); CollectionsMarshal.SetCount(list146, num2); - Span span73 = CollectionsMarshal.AsSpan(list146); - span73[0] = new ChangeEntry(EChangeCategory.Added, "Added config to batch Allied Society quest turn-ins"); - span73[1] = new ChangeEntry(EChangeCategory.Changed, "Repeatable quests now show correct availability state in journal"); - span73[2] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 2 quests"); - reference145 = new ChangelogEntry("6.19", releaseDate63, list146); - ref ChangelogEntry reference146 = ref span[63]; - DateOnly releaseDate64 = new DateOnly(2025, 10, 9); - num2 = 2; + CollectionsMarshal.AsSpan(list146)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 4 quests and Deep Dungeon quest"); + reference145 = new ChangelogEntry("6.24", releaseDate59, list146); + ref ChangelogEntry reference146 = ref span[59]; + DateOnly releaseDate60 = new DateOnly(2025, 10, 13); + num2 = 1; List list147 = new List(num2); CollectionsMarshal.SetCount(list147, num2); - Span span74 = CollectionsMarshal.AsSpan(list147); - span74[0] = new ChangeEntry(EChangeCategory.Changed, "Show once completed quests with improved state display"); - span74[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy daily quest and improvements to various Yok Huy quests"); - reference146 = new ChangelogEntry("6.18", releaseDate64, list147); - ref ChangelogEntry reference147 = ref span[64]; - DateOnly releaseDate65 = new DateOnly(2025, 10, 8); - num2 = 1; + CollectionsMarshal.AsSpan(list147)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 3 quest (Larder Logistics)"); + reference146 = new ChangelogEntry("6.23", releaseDate60, list147); + ref ChangelogEntry reference147 = ref span[60]; + DateOnly releaseDate61 = new DateOnly(2025, 10, 12); + num2 = 3; List list148 = new List(num2); CollectionsMarshal.SetCount(list148, num2); - CollectionsMarshal.AsSpan(list148)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 1 and rank 2 quests"); - reference147 = new ChangelogEntry("6.17", releaseDate65, list148); - ref ChangelogEntry reference148 = ref span[65]; - DateOnly releaseDate66 = new DateOnly(2025, 10, 8); - num2 = 1; + Span span75 = CollectionsMarshal.AsSpan(list148); + span75[0] = new ChangeEntry(EChangeCategory.Changed, "Prevent disabled or Locked quests from being started as 'Start as next quest'"); + span75[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 3 quests"); + span75[2] = new ChangeEntry(EChangeCategory.Fixed, "Fixed Yok Huy quest and journal quest chain priority issues"); + reference147 = new ChangelogEntry("6.22", releaseDate61, list148); + ref ChangelogEntry reference148 = ref span[61]; + DateOnly releaseDate62 = new DateOnly(2025, 10, 12); + num2 = 2; List list149 = new List(num2); CollectionsMarshal.SetCount(list149, num2); - CollectionsMarshal.AsSpan(list149)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Deep Dungeon quest (Faerie Tale)"); - reference148 = new ChangelogEntry("6.16", releaseDate66, list149); - ref ChangelogEntry reference149 = ref span[66]; - DateOnly releaseDate67 = new DateOnly(2025, 10, 8); + Span span76 = CollectionsMarshal.AsSpan(list149); + span76[0] = new ChangeEntry(EChangeCategory.Added, "Added expansion abbreviation to journal window"); + span76[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 3 quests"); + reference148 = new ChangelogEntry("6.21", releaseDate62, list149); + ref ChangelogEntry reference149 = ref span[62]; + DateOnly releaseDate63 = new DateOnly(2025, 10, 10); num2 = 2; List list150 = new List(num2); CollectionsMarshal.SetCount(list150, num2); - Span span75 = CollectionsMarshal.AsSpan(list150); - span75[0] = new ChangeEntry(EChangeCategory.Changed, "Dalamud cleanup"); - span75[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed quest level requirement check log spam"); - reference149 = new ChangelogEntry("6.15", releaseDate67, list150); - ref ChangelogEntry reference150 = ref span[67]; - DateOnly releaseDate68 = new DateOnly(2025, 10, 8); - num2 = 1; + Span span77 = CollectionsMarshal.AsSpan(list150); + span77[0] = new ChangeEntry(EChangeCategory.Changed, "Allow completed repeatable quests to be used with 'Add quest and requirements to priority' feature"); + span77[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 1 quest (A Work of Cart)"); + reference149 = new ChangelogEntry("6.20", releaseDate63, list150); + ref ChangelogEntry reference150 = ref span[63]; + DateOnly releaseDate64 = new DateOnly(2025, 10, 9); + num2 = 3; List list151 = new List(num2); CollectionsMarshal.SetCount(list151, num2); - CollectionsMarshal.AsSpan(list151)[0] = new ChangeEntry(EChangeCategory.Fixed, "Fixed abandoned quest check logic if quest were MSQ"); - reference150 = new ChangelogEntry("6.14", releaseDate68, list151); - ref ChangelogEntry reference151 = ref span[68]; - DateOnly releaseDate69 = new DateOnly(2025, 10, 8); + Span span78 = CollectionsMarshal.AsSpan(list151); + span78[0] = new ChangeEntry(EChangeCategory.Added, "Added config to batch Allied Society quest turn-ins"); + span78[1] = new ChangeEntry(EChangeCategory.Changed, "Repeatable quests now show correct availability state in journal"); + span78[2] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 2 quests"); + reference150 = new ChangelogEntry("6.19", releaseDate64, list151); + ref ChangelogEntry reference151 = ref span[64]; + DateOnly releaseDate65 = new DateOnly(2025, 10, 9); num2 = 2; List list152 = new List(num2); CollectionsMarshal.SetCount(list152, num2); - Span span76 = CollectionsMarshal.AsSpan(list152); - ref ChangeEntry reference152 = ref span76[0]; - num3 = 3; - List list153 = new List(num3); - CollectionsMarshal.SetCount(list153, num3); - Span span77 = CollectionsMarshal.AsSpan(list153); - span77[0] = "Context menu option to add required quests and their chain to priority list"; - span77[1] = "AetheryteShortcut to multiple quests"; - span77[2] = "Artisan as a recommended plugin/dependency"; - reference152 = new ChangeEntry(EChangeCategory.Added, "Quest improvements", list153); - span76[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed abandoned quest check and priority list issues"); - reference151 = new ChangelogEntry("6.13", releaseDate69, list152); - ref ChangelogEntry reference153 = ref span[69]; - DateOnly releaseDate70 = new DateOnly(2025, 10, 7); - num2 = 4; + Span span79 = CollectionsMarshal.AsSpan(list152); + span79[0] = new ChangeEntry(EChangeCategory.Changed, "Show once completed quests with improved state display"); + span79[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy daily quest and improvements to various Yok Huy quests"); + reference151 = new ChangelogEntry("6.18", releaseDate65, list152); + ref ChangelogEntry reference152 = ref span[65]; + DateOnly releaseDate66 = new DateOnly(2025, 10, 8); + num2 = 1; + List list153 = new List(num2); + CollectionsMarshal.SetCount(list153, num2); + CollectionsMarshal.AsSpan(list153)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 1 and rank 2 quests"); + reference152 = new ChangelogEntry("6.17", releaseDate66, list153); + ref ChangelogEntry reference153 = ref span[66]; + DateOnly releaseDate67 = new DateOnly(2025, 10, 8); + num2 = 1; List list154 = new List(num2); CollectionsMarshal.SetCount(list154, num2); - Span span78 = CollectionsMarshal.AsSpan(list154); - ref ChangeEntry reference154 = ref span78[0]; - num3 = 4; - List list155 = new List(num3); - CollectionsMarshal.SetCount(list155, num3); - Span span79 = CollectionsMarshal.AsSpan(list155); - span79[0] = "FATE combat handling with auto level syncing"; - span79[1] = "Start accepted quests from journal with 'Start as next quest'"; - span79[2] = "Update quest tracking when quests are hidden or prioritised in game"; - span79[3] = "QuestMap as a recommended plugin/dependency"; - reference154 = new ChangeEntry(EChangeCategory.Added, "FATE and quest tracking", list155); - ref ChangeEntry reference155 = ref span78[1]; - num3 = 3; - List list156 = new List(num3); - CollectionsMarshal.SetCount(list156, num3); - Span span80 = CollectionsMarshal.AsSpan(list156); - span80[0] = "Always prioritise next quest during teleportation/zone transitions"; - span80[1] = "Improved accepted quest logic with abandoned quest detection"; - span80[2] = "Show quests without quest paths as Locked"; - reference155 = new ChangeEntry(EChangeCategory.Changed, "Quest prioritisation improvements", list156); - span78[2] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Deep Dungeon, Hildibrand, Yok Huy, Monster Hunter Wilds Collab, and Doman Enclave quests"); - span78[3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed accepted/active quest display and Hildibrand quest issues"); - reference153 = new ChangelogEntry("6.12", releaseDate70, list154); - ref ChangelogEntry reference156 = ref span[70]; - DateOnly releaseDate71 = new DateOnly(2025, 10, 3); + CollectionsMarshal.AsSpan(list154)[0] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Deep Dungeon quest (Faerie Tale)"); + reference153 = new ChangelogEntry("6.16", releaseDate67, list154); + ref ChangelogEntry reference154 = ref span[67]; + DateOnly releaseDate68 = new DateOnly(2025, 10, 8); + num2 = 2; + List list155 = new List(num2); + CollectionsMarshal.SetCount(list155, num2); + Span span80 = CollectionsMarshal.AsSpan(list155); + span80[0] = new ChangeEntry(EChangeCategory.Changed, "Dalamud cleanup"); + span80[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed quest level requirement check log spam"); + reference154 = new ChangelogEntry("6.15", releaseDate68, list155); + ref ChangelogEntry reference155 = ref span[68]; + DateOnly releaseDate69 = new DateOnly(2025, 10, 8); num2 = 1; + List list156 = new List(num2); + CollectionsMarshal.SetCount(list156, num2); + CollectionsMarshal.AsSpan(list156)[0] = new ChangeEntry(EChangeCategory.Fixed, "Fixed abandoned quest check logic if quest were MSQ"); + reference155 = new ChangelogEntry("6.14", releaseDate69, list156); + ref ChangelogEntry reference156 = ref span[69]; + DateOnly releaseDate70 = new DateOnly(2025, 10, 8); + num2 = 2; List list157 = new List(num2); CollectionsMarshal.SetCount(list157, num2); - CollectionsMarshal.AsSpan(list157)[0] = new ChangeEntry(EChangeCategory.Changed, "Added remaining checks for quest priority to prevent infinite teleport looping"); - reference156 = new ChangelogEntry("6.11", releaseDate71, list157); - ref ChangelogEntry reference157 = ref span[71]; - DateOnly releaseDate72 = new DateOnly(2025, 10, 2); - num2 = 1; - List list158 = new List(num2); - CollectionsMarshal.SetCount(list158, num2); - ref ChangeEntry reference158 = ref CollectionsMarshal.AsSpan(list158)[0]; - num3 = 2; - List list159 = new List(num3); - CollectionsMarshal.SetCount(list159, num3); - Span span81 = CollectionsMarshal.AsSpan(list159); - span81[0] = "Don't show quests as available if player doesn't meet level requirements"; - span81[1] = "Updated 'required for MSQ' text in Crystal Tower quest preset window"; - reference158 = new ChangeEntry(EChangeCategory.Changed, "Quest window improvements", list159); - reference157 = new ChangelogEntry("6.10", releaseDate72, list158); - ref ChangelogEntry reference159 = ref span[72]; - DateOnly releaseDate73 = new DateOnly(2025, 9, 21); - num2 = 5; - List list160 = new List(num2); - CollectionsMarshal.SetCount(list160, num2); - Span span82 = CollectionsMarshal.AsSpan(list160); - ref ChangeEntry reference160 = ref span82[0]; + Span span81 = CollectionsMarshal.AsSpan(list157); + ref ChangeEntry reference157 = ref span81[0]; + num3 = 3; + List list158 = new List(num3); + CollectionsMarshal.SetCount(list158, num3); + Span span82 = CollectionsMarshal.AsSpan(list158); + span82[0] = "Context menu option to add required quests and their chain to priority list"; + span82[1] = "AetheryteShortcut to multiple quests"; + span82[2] = "Artisan as a recommended plugin/dependency"; + reference157 = new ChangeEntry(EChangeCategory.Added, "Quest improvements", list158); + span81[1] = new ChangeEntry(EChangeCategory.Fixed, "Fixed abandoned quest check and priority list issues"); + reference156 = new ChangelogEntry("6.13", releaseDate70, list157); + ref ChangelogEntry reference158 = ref span[70]; + DateOnly releaseDate71 = new DateOnly(2025, 10, 7); + num2 = 4; + List list159 = new List(num2); + CollectionsMarshal.SetCount(list159, num2); + Span span83 = CollectionsMarshal.AsSpan(list159); + ref ChangeEntry reference159 = ref span83[0]; num3 = 4; + List list160 = new List(num3); + CollectionsMarshal.SetCount(list160, num3); + Span span84 = CollectionsMarshal.AsSpan(list160); + span84[0] = "FATE combat handling with auto level syncing"; + span84[1] = "Start accepted quests from journal with 'Start as next quest'"; + span84[2] = "Update quest tracking when quests are hidden or prioritised in game"; + span84[3] = "QuestMap as a recommended plugin/dependency"; + reference159 = new ChangeEntry(EChangeCategory.Added, "FATE and quest tracking", list160); + ref ChangeEntry reference160 = ref span83[1]; + num3 = 3; List list161 = new List(num3); CollectionsMarshal.SetCount(list161, num3); - Span span83 = CollectionsMarshal.AsSpan(list161); - span83[0] = "Reworked event quest handling - automatically displays when events are active"; - span83[1] = "Reworked journal system with improved filtering and display"; - span83[2] = "Reworked Priority Quests tab (Manual Priority and Quest Presets)"; - span83[3] = "Quest path viewer site (https://wigglymuffin.github.io/FFXIV-Tools/)"; - reference160 = new ChangeEntry(EChangeCategory.Added, "Major system reworks", list161); - ref ChangeEntry reference161 = ref span82[1]; - num3 = 4; - List list162 = new List(num3); - CollectionsMarshal.SetCount(list162, num3); - Span span84 = CollectionsMarshal.AsSpan(list162); - span84[0] = "Questionable.IsQuestCompleted"; - span84[1] = "Questionable.IsQuestAvailable"; - span84[2] = "Questionable.IsQuestAccepted"; - span84[3] = "Questionable.IsQuestUnobtainable"; - reference161 = new ChangeEntry(EChangeCategory.Added, "New IPC commands", list162); - ref ChangeEntry reference162 = ref span82[2]; - num3 = 5; - List list163 = new List(num3); - CollectionsMarshal.SetCount(list163, num3); - Span span85 = CollectionsMarshal.AsSpan(list163); - span85[0] = "Improved JSON quest validation with specific error reasons"; - span85[1] = "Added stop at sequence stop condition"; - span85[2] = "Improved Pandora plugin conflict detection"; - span85[3] = "Improved DialogueChoices regex matching"; - span85[4] = "Improved refresh checker for all quest states"; - reference162 = new ChangeEntry(EChangeCategory.Changed, "Various improvements", list163); - span82[3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.31 Occult Crescent quests"); - span82[4] = new ChangeEntry(EChangeCategory.Fixed, "Fixed cutscene crashes, Single Player Duty triggers, and various quest issues"); - reference159 = new ChangelogEntry("6.9", releaseDate73, list160); - ref ChangelogEntry reference163 = ref span[73]; - DateOnly releaseDate74 = new DateOnly(2025, 9, 2); - num2 = 4; - List list164 = new List(num2); - CollectionsMarshal.SetCount(list164, num2); - Span span86 = CollectionsMarshal.AsSpan(list164); - ref ChangeEntry reference164 = ref span86[0]; - num3 = 3; - List list165 = new List(num3); - CollectionsMarshal.SetCount(list165, num3); - Span span87 = CollectionsMarshal.AsSpan(list165); - span87[0] = "Help commands and priority quest command"; - span87[1] = "Prevent 'CompleteQuest' step setting"; - span87[2] = "Duty counts and controls in 'Quest Battles' tab"; - reference164 = new ChangeEntry(EChangeCategory.Added, "Command and UI improvements", list165); - span86[1] = new ChangeEntry(EChangeCategory.Changed, "Improved 'Clear All' buttons to require CTRL being held"); - span86[2] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Zodiac quests and 7.31 Cosmic/Occult Crescent quests"); - span86[3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed Fishing for Friendship and Cosmic Exploration quests"); - reference163 = new ChangelogEntry("6.8", releaseDate74, list164); - ref ChangelogEntry reference165 = ref span[74]; - DateOnly releaseDate75 = new DateOnly(2025, 8, 27); - num2 = 4; - List list166 = new List(num2); - CollectionsMarshal.SetCount(list166, num2); - Span span88 = CollectionsMarshal.AsSpan(list166); - ref ChangeEntry reference166 = ref span88[0]; + Span span85 = CollectionsMarshal.AsSpan(list161); + span85[0] = "Always prioritise next quest during teleportation/zone transitions"; + span85[1] = "Improved accepted quest logic with abandoned quest detection"; + span85[2] = "Show quests without quest paths as Locked"; + reference160 = new ChangeEntry(EChangeCategory.Changed, "Quest prioritisation improvements", list161); + span83[2] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Deep Dungeon, Hildibrand, Yok Huy, Monster Hunter Wilds Collab, and Doman Enclave quests"); + span83[3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed accepted/active quest display and Hildibrand quest issues"); + reference158 = new ChangelogEntry("6.12", releaseDate71, list159); + ref ChangelogEntry reference161 = ref span[71]; + DateOnly releaseDate72 = new DateOnly(2025, 10, 3); + num2 = 1; + List list162 = new List(num2); + CollectionsMarshal.SetCount(list162, num2); + CollectionsMarshal.AsSpan(list162)[0] = new ChangeEntry(EChangeCategory.Changed, "Added remaining checks for quest priority to prevent infinite teleport looping"); + reference161 = new ChangelogEntry("6.11", releaseDate72, list162); + ref ChangelogEntry reference162 = ref span[72]; + DateOnly releaseDate73 = new DateOnly(2025, 10, 2); + num2 = 1; + List list163 = new List(num2); + CollectionsMarshal.SetCount(list163, num2); + ref ChangeEntry reference163 = ref CollectionsMarshal.AsSpan(list163)[0]; num3 = 2; + List list164 = new List(num3); + CollectionsMarshal.SetCount(list164, num3); + Span span86 = CollectionsMarshal.AsSpan(list164); + span86[0] = "Don't show quests as available if player doesn't meet level requirements"; + span86[1] = "Updated 'required for MSQ' text in Crystal Tower quest preset window"; + reference163 = new ChangeEntry(EChangeCategory.Changed, "Quest window improvements", list164); + reference162 = new ChangelogEntry("6.10", releaseDate73, list163); + ref ChangelogEntry reference164 = ref span[73]; + DateOnly releaseDate74 = new DateOnly(2025, 9, 21); + num2 = 5; + List list165 = new List(num2); + CollectionsMarshal.SetCount(list165, num2); + Span span87 = CollectionsMarshal.AsSpan(list165); + ref ChangeEntry reference165 = ref span87[0]; + num3 = 4; + List list166 = new List(num3); + CollectionsMarshal.SetCount(list166, num3); + Span span88 = CollectionsMarshal.AsSpan(list166); + span88[0] = "Reworked event quest handling - automatically displays when events are active"; + span88[1] = "Reworked journal system with improved filtering and display"; + span88[2] = "Reworked Priority Quests tab (Manual Priority and Quest Presets)"; + span88[3] = "Quest path viewer site (https://wigglymuffin.github.io/FFXIV-Tools/)"; + reference165 = new ChangeEntry(EChangeCategory.Added, "Major system reworks", list166); + ref ChangeEntry reference166 = ref span87[1]; + num3 = 4; List list167 = new List(num3); CollectionsMarshal.SetCount(list167, num3); Span span89 = CollectionsMarshal.AsSpan(list167); - span89[0] = "Icon to 'Clear All' button in stop conditions"; - span89[1] = "Duty counts and 'Enable All' button in 'Duties' tab"; - reference166 = new ChangeEntry(EChangeCategory.Added, "UI improvements", list167); - span88[1] = new ChangeEntry(EChangeCategory.Changed, "Renamed 'Clear' button to 'Clear All' in priority window"); - span88[2] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Rising 2025 Event Quests"); - span88[3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed clipboard assigning blacklist to whitelist in 'Duties' tab"); - reference165 = new ChangelogEntry("6.7", releaseDate75, list166); - ref ChangelogEntry reference167 = ref span[75]; - DateOnly releaseDate76 = new DateOnly(2025, 8, 25); - num2 = 2; - List list168 = new List(num2); - CollectionsMarshal.SetCount(list168, num2); - Span span90 = CollectionsMarshal.AsSpan(list168); - ref ChangeEntry reference168 = ref span90[0]; + span89[0] = "Questionable.IsQuestCompleted"; + span89[1] = "Questionable.IsQuestAvailable"; + span89[2] = "Questionable.IsQuestAccepted"; + span89[3] = "Questionable.IsQuestUnobtainable"; + reference166 = new ChangeEntry(EChangeCategory.Added, "New IPC commands", list167); + ref ChangeEntry reference167 = ref span87[2]; + num3 = 5; + List list168 = new List(num3); + CollectionsMarshal.SetCount(list168, num3); + Span span90 = CollectionsMarshal.AsSpan(list168); + span90[0] = "Improved JSON quest validation with specific error reasons"; + span90[1] = "Added stop at sequence stop condition"; + span90[2] = "Improved Pandora plugin conflict detection"; + span90[3] = "Improved DialogueChoices regex matching"; + span90[4] = "Improved refresh checker for all quest states"; + reference167 = new ChangeEntry(EChangeCategory.Changed, "Various improvements", list168); + span87[3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.31 Occult Crescent quests"); + span87[4] = new ChangeEntry(EChangeCategory.Fixed, "Fixed cutscene crashes, Single Player Duty triggers, and various quest issues"); + reference164 = new ChangelogEntry("6.9", releaseDate74, list165); + ref ChangelogEntry reference168 = ref span[74]; + DateOnly releaseDate75 = new DateOnly(2025, 9, 2); + num2 = 4; + List list169 = new List(num2); + CollectionsMarshal.SetCount(list169, num2); + Span span91 = CollectionsMarshal.AsSpan(list169); + ref ChangeEntry reference169 = ref span91[0]; + num3 = 3; + List list170 = new List(num3); + CollectionsMarshal.SetCount(list170, num3); + Span span92 = CollectionsMarshal.AsSpan(list170); + span92[0] = "Help commands and priority quest command"; + span92[1] = "Prevent 'CompleteQuest' step setting"; + span92[2] = "Duty counts and controls in 'Quest Battles' tab"; + reference169 = new ChangeEntry(EChangeCategory.Added, "Command and UI improvements", list170); + span91[1] = new ChangeEntry(EChangeCategory.Changed, "Improved 'Clear All' buttons to require CTRL being held"); + span91[2] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Zodiac quests and 7.31 Cosmic/Occult Crescent quests"); + span91[3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed Fishing for Friendship and Cosmic Exploration quests"); + reference168 = new ChangelogEntry("6.8", releaseDate75, list169); + ref ChangelogEntry reference170 = ref span[75]; + DateOnly releaseDate76 = new DateOnly(2025, 8, 27); + num2 = 4; + List list171 = new List(num2); + CollectionsMarshal.SetCount(list171, num2); + Span span93 = CollectionsMarshal.AsSpan(list171); + ref ChangeEntry reference171 = ref span93[0]; num3 = 2; - List list169 = new List(num3); - CollectionsMarshal.SetCount(list169, num3); - Span span91 = CollectionsMarshal.AsSpan(list169); - span91[0] = "Missing emotes to schema and emote handler"; - span91[1] = "Improved stop conditions with 'Clear All' button"; - reference168 = new ChangeEntry(EChangeCategory.Added, "Emote support and stop conditions", list169); - span90[1] = new ChangeEntry(EChangeCategory.Changed, "Stop at level functionality"); - reference167 = new ChangelogEntry("6.6", releaseDate76, list168); - ref ChangelogEntry reference169 = ref span[76]; + List list172 = new List(num3); + CollectionsMarshal.SetCount(list172, num3); + Span span94 = CollectionsMarshal.AsSpan(list172); + span94[0] = "Icon to 'Clear All' button in stop conditions"; + span94[1] = "Duty counts and 'Enable All' button in 'Duties' tab"; + reference171 = new ChangeEntry(EChangeCategory.Added, "UI improvements", list172); + span93[1] = new ChangeEntry(EChangeCategory.Changed, "Renamed 'Clear' button to 'Clear All' in priority window"); + span93[2] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Rising 2025 Event Quests"); + span93[3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed clipboard assigning blacklist to whitelist in 'Duties' tab"); + reference170 = new ChangelogEntry("6.7", releaseDate76, list171); + ref ChangelogEntry reference172 = ref span[76]; DateOnly releaseDate77 = new DateOnly(2025, 8, 25); num2 = 2; - List list170 = new List(num2); - CollectionsMarshal.SetCount(list170, num2); - Span span92 = CollectionsMarshal.AsSpan(list170); - span92[0] = new ChangeEntry(EChangeCategory.Fixed, "Potential fix to single/solo duties softlocking"); - span92[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added San d'Oria: The Second Walk and various side quests"); - reference169 = new ChangelogEntry("6.5", releaseDate77, list170); + List list173 = new List(num2); + CollectionsMarshal.SetCount(list173, num2); + Span span95 = CollectionsMarshal.AsSpan(list173); + ref ChangeEntry reference173 = ref span95[0]; + num3 = 2; + List list174 = new List(num3); + CollectionsMarshal.SetCount(list174, num3); + Span span96 = CollectionsMarshal.AsSpan(list174); + span96[0] = "Missing emotes to schema and emote handler"; + span96[1] = "Improved stop conditions with 'Clear All' button"; + reference173 = new ChangeEntry(EChangeCategory.Added, "Emote support and stop conditions", list174); + span95[1] = new ChangeEntry(EChangeCategory.Changed, "Stop at level functionality"); + reference172 = new ChangelogEntry("6.6", releaseDate77, list173); + ref ChangelogEntry reference174 = ref span[77]; + DateOnly releaseDate78 = new DateOnly(2025, 8, 25); + num2 = 2; + List list175 = new List(num2); + CollectionsMarshal.SetCount(list175, num2); + Span span97 = CollectionsMarshal.AsSpan(list175); + span97[0] = new ChangeEntry(EChangeCategory.Fixed, "Potential fix to single/solo duties softlocking"); + span97[1] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added San d'Oria: The Second Walk and various side quests"); + reference174 = new ChangelogEntry("6.5", releaseDate78, list175); Changelogs = list; } } diff --git a/Questionable/Questionable.External/BossModIpc.cs b/Questionable/Questionable.External/BossModIpc.cs index 42a5ae9..927ac97 100644 --- a/Questionable/Questionable.External/BossModIpc.cs +++ b/Questionable/Questionable.External/BossModIpc.cs @@ -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(); } diff --git a/Questionable/Questionable.Functions/QuestFunctions.cs b/Questionable/Questionable.Functions/QuestFunctions.cs index 43020ef..24cd2ea 100644 --- a/Questionable/Questionable.Functions/QuestFunctions.cs +++ b/Questionable/Questionable.Functions/QuestFunctions.cs @@ -23,6 +23,22 @@ namespace Questionable.Functions; internal sealed class QuestFunctions { + private static readonly Dictionary ComingToStartingCityQuests = new Dictionary + { + { + 1, + new QuestId(107) + }, + { + 2, + new QuestId(39) + }, + { + 3, + new QuestId(594) + } + }; + private static readonly Dictionary CloseToHomeQuests = new Dictionary { { @@ -95,6 +111,56 @@ internal sealed class QuestFunctions private static readonly HashSet RemovedQuestIds = new HashSet { 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() + .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 logger) { _questRegistry = questRegistry; diff --git a/Questionable/Questionable.Navigation/SmartNavReRouteService.cs b/Questionable/Questionable.Navigation/SmartNavReRouteService.cs index 95e2281..476be71 100644 --- a/Questionable/Questionable.Navigation/SmartNavReRouteService.cs +++ b/Questionable/Questionable.Navigation/SmartNavReRouteService.cs @@ -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 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), + }; } } diff --git a/Questionable/Questionable.Navigation/SmartNavRouteEnqueuer.cs b/Questionable/Questionable.Navigation/SmartNavRouteEnqueuer.cs index 7544768..5d07c74 100644 --- a/Questionable/Questionable.Navigation/SmartNavRouteEnqueuer.cs +++ b/Questionable/Questionable.Navigation/SmartNavRouteEnqueuer.cs @@ -53,7 +53,27 @@ internal sealed class SmartNavRouteEnqueuer(NavRouter navRouter, PlayerNavStateB { return false; } - List list = instructionBuilder.Build(routeResult, territoryId).TakeWhile((NavInstruction x) => !(x is NavInstruction.Move move) || !move.IsFinal).ToList(); + List 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; diff --git a/Questionable/Questionable.Navigation/SmartNavTaskMapper.cs b/Questionable/Questionable.Navigation/SmartNavTaskMapper.cs index f948a82..30e0024 100644 --- a/Questionable/Questionable.Navigation/SmartNavTaskMapper.cs +++ b/Questionable/Questionable.Navigation/SmartNavTaskMapper.cs @@ -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 MapInstructions(IReadOnlyList instructions, QuestStep? step = null) + private const float SurfaceExitRise = 3f; + + public IEnumerable MapInstructions(IReadOnlyList 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, + }; + } } diff --git a/Questionable/Questionable.Windows.ConfigComponents/SinglePlayerDutyConfigComponent.cs b/Questionable/Questionable.Windows.ConfigComponents/SinglePlayerDutyConfigComponent.cs index f32c6cc..3f6ea18 100644 --- a/Questionable/Questionable.Windows.ConfigComponents/SinglePlayerDutyConfigComponent.cs +++ b/Questionable/Questionable.Windows.ConfigComponents/SinglePlayerDutyConfigComponent.cs @@ -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)); diff --git a/Questionable/Questionable.Windows.QuestComponents/QuestSequenceComponent.cs b/Questionable/Questionable.Windows.QuestComponents/QuestSequenceComponent.cs index 8e9e530..2057e45 100644 --- a/Questionable/Questionable.Windows.QuestComponents/QuestSequenceComponent.cs +++ b/Questionable/Questionable.Windows.QuestComponents/QuestSequenceComponent.cs @@ -338,30 +338,27 @@ internal sealed class QuestSequenceComponent ImGui.Spacing(); try { - Lumina.Excel.Sheets.Quest? quest = _dataManager.GetExcelSheet()?.GetRowOrDefault((uint)(questId.Value + 65536)); - if (!quest.HasValue) + if (!(_dataManager.GetExcelSheet()?.GetRowOrDefault((uint)(questId.Value + 65536))).HasValue) { DrawErrorState("Quest Not Found in Game Data", "Unable to load quest information from Lumina."); return; } - List 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 todoParamsStruct) => todoParamsStruct.ToDoCompleteSeq)).Distinct() - orderby result - select result).ToList(); - if (list.Count == 0) + List 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 hashSet = new HashSet(); Dictionary dictionary = new Dictionary(); 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) { diff --git a/Questionable/Questionable/Configuration.cs b/Questionable/Questionable/Configuration.cs index bf41434..a68b464 100644 --- a/Questionable/Questionable/Configuration.cs +++ b/Questionable/Questionable/Configuration.cs @@ -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; } diff --git a/Questionable/Questionable/QuestionablePlugin.cs b/Questionable/Questionable/QuestionablePlugin.cs index d07e074..984d39e 100644 --- a/Questionable/Questionable/QuestionablePlugin.cs +++ b/Questionable/Questionable/QuestionablePlugin.cs @@ -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(); serviceCollection.AddSingleton((IServiceProvider sp) => new LgbZoneBoundarySource(sp.GetRequiredService>(), sp.GetRequiredService())); ((IServiceCollection)serviceCollection).AddSingleton((Func)((IServiceProvider sp) => sp.GetRequiredService())); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); ((IServiceCollection)serviceCollection).AddSingleton((Func)((IServiceProvider sp) => sp.GetRequiredService())); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/SeasonalDutyPaths/Questionable.SeasonalDutyPaths.SeasonalDutyBundle b/SeasonalDutyPaths/Questionable.SeasonalDutyPaths.SeasonalDutyBundle index 6f54328..14e88a9 100644 Binary files a/SeasonalDutyPaths/Questionable.SeasonalDutyPaths.SeasonalDutyBundle and b/SeasonalDutyPaths/Questionable.SeasonalDutyPaths.SeasonalDutyBundle differ diff --git a/SeasonalDutyPaths/Questionable.SeasonalDutyPaths/PathBundleSecret.cs b/SeasonalDutyPaths/Questionable.SeasonalDutyPaths/PathBundleSecret.cs index 26096e8..75baa78 100644 --- a/SeasonalDutyPaths/Questionable.SeasonalDutyPaths/PathBundleSecret.cs +++ b/SeasonalDutyPaths/Questionable.SeasonalDutyPaths/PathBundleSecret.cs @@ -4,18 +4,18 @@ internal static class PathBundleSecret { private static readonly byte[] A = new byte[32] { - 192, 237, 68, 154, 123, 161, 164, 81, 179, 101, - 208, 145, 185, 118, 184, 66, 42, 173, 202, 110, - 201, 113, 73, 242, 250, 58, 135, 189, 143, 92, - 151, 77 + 14, 238, 168, 186, 100, 48, 138, 129, 114, 53, + 75, 217, 99, 224, 198, 248, 103, 129, 59, 33, + 149, 44, 226, 149, 187, 253, 141, 40, 151, 199, + 127, 185 }; private static readonly byte[] B = new byte[32] { - 239, 73, 204, 3, 150, 201, 87, 43, 137, 223, - 14, 253, 125, 35, 109, 186, 32, 12, 130, 132, - 201, 118, 255, 168, 253, 104, 107, 98, 213, 28, - 83, 69 + 87, 34, 127, 49, 238, 14, 221, 143, 224, 157, + 207, 147, 247, 122, 30, 185, 79, 187, 117, 57, + 242, 252, 100, 251, 176, 253, 30, 79, 135, 124, + 73, 92 }; internal static byte[] Key() diff --git a/SmartNav.Data/SmartNav.Data.chocobo-taxi-stands.bin b/SmartNav.Data/SmartNav.Data.chocobo-taxi-stands.bin index 668da49..2e5921a 100644 Binary files a/SmartNav.Data/SmartNav.Data.chocobo-taxi-stands.bin and b/SmartNav.Data/SmartNav.Data.chocobo-taxi-stands.bin differ diff --git a/SmartNav.Data/SmartNav.Data.csproj b/SmartNav.Data/SmartNav.Data.csproj index 3eaf940..1c280f5 100644 --- a/SmartNav.Data/SmartNav.Data.csproj +++ b/SmartNav.Data/SmartNav.Data.csproj @@ -20,6 +20,7 @@ + @@ -27,6 +28,7 @@ + diff --git a/SmartNav.Data/SmartNav.Data.derived-arrivals.bin b/SmartNav.Data/SmartNav.Data.derived-arrivals.bin index fc16578..4dc1789 100644 Binary files a/SmartNav.Data/SmartNav.Data.derived-arrivals.bin and b/SmartNav.Data/SmartNav.Data.derived-arrivals.bin differ diff --git a/SmartNav.Data/SmartNav.Data.npc-position-cache.bin b/SmartNav.Data/SmartNav.Data.npc-position-cache.bin index e0d05ff..69bff09 100644 Binary files a/SmartNav.Data/SmartNav.Data.npc-position-cache.bin and b/SmartNav.Data/SmartNav.Data.npc-position-cache.bin differ diff --git a/SmartNav.Data/SmartNav.Data.teleport-tickets.bin b/SmartNav.Data/SmartNav.Data.teleport-tickets.bin index b11869b..dbd4946 100644 Binary files a/SmartNav.Data/SmartNav.Data.teleport-tickets.bin and b/SmartNav.Data/SmartNav.Data.teleport-tickets.bin differ diff --git a/SmartNav.Data/SmartNav.Data.warp-destinations.bin b/SmartNav.Data/SmartNav.Data.warp-destinations.bin index 9315cb2..428fa7f 100644 Binary files a/SmartNav.Data/SmartNav.Data.warp-destinations.bin and b/SmartNav.Data/SmartNav.Data.warp-destinations.bin differ diff --git a/SmartNav.Data/SmartNav.Data.water-cache.bin b/SmartNav.Data/SmartNav.Data.water-cache.bin new file mode 100644 index 0000000..edc07db Binary files /dev/null and b/SmartNav.Data/SmartNav.Data.water-cache.bin differ diff --git a/SmartNav.Data/SmartNav.Data.zone-boundary-cache.bin b/SmartNav.Data/SmartNav.Data.zone-boundary-cache.bin index 2b3bd58..fcc2b5b 100644 Binary files a/SmartNav.Data/SmartNav.Data.zone-boundary-cache.bin and b/SmartNav.Data/SmartNav.Data.zone-boundary-cache.bin differ diff --git a/SmartNav.Data/SmartNav.Data.zone-sub-regions.bin b/SmartNav.Data/SmartNav.Data.zone-sub-regions.bin index e82fa48..70d31ad 100644 Binary files a/SmartNav.Data/SmartNav.Data.zone-sub-regions.bin and b/SmartNav.Data/SmartNav.Data.zone-sub-regions.bin differ diff --git a/SmartNav.Data/SmartNav.Data/AssemblyWaterMapLoader.cs b/SmartNav.Data/SmartNav.Data/AssemblyWaterMapLoader.cs new file mode 100644 index 0000000..4e0ff4b --- /dev/null +++ b/SmartNav.Data/SmartNav.Data/AssemblyWaterMapLoader.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.IO; +using SmartNav.Model.Navigation; + +namespace SmartNav.Data; + +public static class AssemblyWaterMapLoader +{ + public static (string? GameVersion, IReadOnlyDictionary Maps) GetWaterMaps(string? overrideDirectory = null) + { + using Stream stream = SmartNavResources.TryOpen("water-cache.bin", overrideDirectory); + if (stream == null) + { + return (GameVersion: null, Maps: new Dictionary()); + } + (string GameVersion, List Maps) tuple = WaterCacheFormat.Read(stream); + string item = tuple.GameVersion; + List item2 = tuple.Maps; + Dictionary dictionary = new Dictionary(item2.Count); + foreach (TerritoryWaterMap item3 in item2) + { + if (!dictionary.TryAdd(item3.TerritoryId, item3)) + { + throw new InvalidDataException($"water cache lists territory {item3.TerritoryId} twice"); + } + } + return (GameVersion: item, Maps: dictionary); + } +} diff --git a/SmartNav.Data/SmartNav.Data/DataBundleSecret.cs b/SmartNav.Data/SmartNav.Data/DataBundleSecret.cs index bc2854f..996e626 100644 --- a/SmartNav.Data/SmartNav.Data/DataBundleSecret.cs +++ b/SmartNav.Data/SmartNav.Data/DataBundleSecret.cs @@ -4,18 +4,18 @@ internal static class DataBundleSecret { private static readonly byte[] A = new byte[32] { - 13, 38, 21, 255, 71, 40, 19, 213, 79, 201, - 20, 24, 146, 227, 159, 156, 142, 23, 138, 144, - 119, 132, 179, 149, 190, 134, 23, 209, 204, 192, - 234, 63 + 71, 215, 20, 29, 12, 252, 253, 194, 59, 69, + 18, 55, 34, 247, 239, 216, 228, 201, 157, 67, + 207, 24, 144, 24, 62, 38, 55, 197, 249, 132, + 62, 40 }; private static readonly byte[] B = new byte[32] { - 252, 191, 81, 239, 79, 239, 196, 247, 66, 179, - 73, 171, 175, 76, 172, 241, 89, 23, 110, 4, - 105, 193, 2, 137, 162, 77, 122, 136, 209, 181, - 229, 197 + 63, 81, 210, 107, 86, 21, 96, 76, 152, 72, + 178, 235, 191, 217, 62, 127, 34, 130, 14, 224, + 171, 239, 111, 52, 2, 7, 1, 25, 82, 57, + 94, 195 }; internal static byte[] Key() diff --git a/SmartNav.Data/SmartNav.Data/SmartNavResources.cs b/SmartNav.Data/SmartNav.Data/SmartNavResources.cs index 6c76efe..93902b2 100644 --- a/SmartNav.Data/SmartNav.Data/SmartNavResources.cs +++ b/SmartNav.Data/SmartNav.Data/SmartNavResources.cs @@ -45,6 +45,24 @@ internal static class SmartNavResources return JsonSerializer.Deserialize(readOnlySpan); } + public static Stream? TryOpen(string fileName, string? overrideDirectory) + { + if (overrideDirectory != null) + { + string path = Path.Combine(overrideDirectory, fileName); + if (File.Exists(path)) + { + return File.OpenRead(path); + } + } + byte[] array = Unseal("SmartNav.Data." + fileName); + if (array != null) + { + return new MemoryStream(array, writable: false); + } + return null; + } + public static Stream OpenSealed(string fileName) { return new MemoryStream(Unseal("SmartNav.Data." + fileName) ?? throw new InvalidOperationException("embedded resource '" + fileName + "' is missing"), writable: false); diff --git a/SmartNav.Model/SmartNav.Model.Navigation/TerritoryWaterMap.cs b/SmartNav.Model/SmartNav.Model.Navigation/TerritoryWaterMap.cs new file mode 100644 index 0000000..4ea9410 --- /dev/null +++ b/SmartNav.Model/SmartNav.Model.Navigation/TerritoryWaterMap.cs @@ -0,0 +1,188 @@ +using System; +using System.Numerics; + +namespace SmartNav.Model.Navigation; + +public sealed class TerritoryWaterMap +{ + public const int GridSize = 256; + + public const int CellCount = 65536; + + public const float CellSize = 8f; + + public const float WorldMin = -1024f; + + public const float WorldMax = 1024f; + + private const float SurfaceYScale = 32f; + + private const float SurfaceYBias = 1024f; + + private readonly byte[] _classes; + + private readonly ushort[] _surfaceY; + + public ushort TerritoryId { get; } + + public ReadOnlySpan Classes => _classes; + + public ReadOnlySpan SurfaceYRaw => _surfaceY; + + public bool HasWater => ((ReadOnlySpan)_classes.AsSpan()).ContainsAnyExcept((byte)0); + + public TerritoryWaterMap(ushort territoryId) + : this(territoryId, new byte[65536], new ushort[65536]) + { + } + + public TerritoryWaterMap(ushort territoryId, byte[] classes, ushort[] surfaceY) + { + ArgumentNullException.ThrowIfNull(classes, "classes"); + ArgumentNullException.ThrowIfNull(surfaceY, "surfaceY"); + if (classes.Length != 65536) + { + throw new ArgumentException($"expected {65536} class entries, got {classes.Length}", "classes"); + } + if (surfaceY.Length != 65536) + { + throw new ArgumentException($"expected {65536} surface Y entries, got {surfaceY.Length}", "surfaceY"); + } + TerritoryId = territoryId; + _classes = classes; + _surfaceY = surfaceY; + } + + public static ushort EncodeSurfaceY(float y) + { + if (float.IsNaN(y)) + { + throw new ArgumentException("surface Y is NaN", "y"); + } + float num = MathF.Round((y + 1024f) * 32f); + if (num <= 0f) + { + return 0; + } + if (num >= 65535f) + { + return ushort.MaxValue; + } + return (ushort)num; + } + + public static float DecodeSurfaceY(ushort raw) + { + return (float)(int)raw / 32f - 1024f; + } + + public static float CellCenterCoordinate(int colOrRow) + { + return -1024f + ((float)colOrRow + 0.5f) * 8f; + } + + public static bool TryGetCell(float x, float z, out int col, out int row) + { + col = (int)MathF.Floor((x - -1024f) / 8f); + row = (int)MathF.Floor((z - -1024f) / 8f); + if (col < 0 || col >= 256 || row < 0 || row >= 256) + { + col = -1; + row = -1; + return false; + } + return true; + } + + public void SetCell(int col, int row, WaterCellClass cellClass, float surfaceY) + { + int num = IndexOf(col, row); + _classes[num] = (byte)cellClass; + _surfaceY[num] = EncodeSurfaceY(surfaceY); + } + + public WaterCellClass GetCell(int col, int row) + { + return (WaterCellClass)_classes[IndexOf(col, row)]; + } + + public WaterCellClass ClassifyAt(float x, float z) + { + if (!TryGetCell(x, z, out var col, out var row)) + { + return WaterCellClass.None; + } + return (WaterCellClass)_classes[row * 256 + col]; + } + + public bool TryGetSurfaceY(float x, float z, out float surfaceY) + { + surfaceY = 0f; + if (!TryGetCell(x, z, out var col, out var row)) + { + return false; + } + int num = row * 256 + col; + if (_classes[num] == 0) + { + return false; + } + surfaceY = DecodeSurfaceY(_surfaceY[num]); + return true; + } + + public bool TryFindNearestDivable(float x, float z, float maxRadiusYalms, out Vector3 entryCellCenter) + { + entryCellCenter = default(Vector3); + if (float.IsNaN(maxRadiusYalms) || maxRadiusYalms < 0f || !TryGetCell(x, z, out var col, out var row)) + { + return false; + } + float num = MathF.Ceiling(maxRadiusYalms / 8f) + 1f; + int num2 = ((num >= 256f) ? 256 : ((int)num)); + float num3 = float.MaxValue; + int num4 = -1; + int num5 = -1; + for (int i = 0; i <= num2 && (num4 < 0 || !(((float)i - 0.5f) * 8f > num3)); i++) + { + int num6 = col - i; + int num7 = col + i; + int num8 = row - i; + int num9 = row + i; + for (int j = Math.Max(num8, 0); j <= Math.Min(num9, 255); j++) + { + int num10 = ((j == num8 || j == num9) ? 1 : Math.Max(num7 - num6, 1)); + for (int k = num6; k <= num7; k += num10) + { + if (k >= 0 && k < 256 && _classes[j * 256 + k] == 2) + { + float num11 = CellCenterCoordinate(k) - x; + float num12 = CellCenterCoordinate(j) - z; + float num13 = MathF.Sqrt(num11 * num11 + num12 * num12); + if (!(num13 > maxRadiusYalms) && !(num13 >= num3)) + { + num3 = num13; + num4 = k; + num5 = j; + } + } + } + } + } + if (num4 < 0) + { + return false; + } + entryCellCenter = new Vector3(CellCenterCoordinate(num4), DecodeSurfaceY(_surfaceY[num5 * 256 + num4]), CellCenterCoordinate(num5)); + return true; + } + + private static int IndexOf(int col, int row) + { + ArgumentOutOfRangeException.ThrowIfNegative(col, "col"); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(col, 256, "col"); + ArgumentOutOfRangeException.ThrowIfNegative(row, "row"); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(row, 256, "row"); + return row * 256 + col; + } +} diff --git a/SmartNav.Model/SmartNav.Model.Navigation/WaterCacheFormat.cs b/SmartNav.Model/SmartNav.Model.Navigation/WaterCacheFormat.cs new file mode 100644 index 0000000..6e98f06 --- /dev/null +++ b/SmartNav.Model/SmartNav.Model.Navigation/WaterCacheFormat.cs @@ -0,0 +1,122 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Text; + +namespace SmartNav.Model.Navigation; + +public static class WaterCacheFormat +{ + public const uint FileMagic = 1129469015u; + + public const int FileFormatVersion = 1; + + public const string FileName = "water-cache.bin"; + + private const int MaxTerritoryCount = 65536; + + private const int ClassBytes = 65536; + + private const int SurfaceBytes = 131072; + + private const int PayloadBytes = 196608; + + public static void Write(Stream stream, string gameVersion, IReadOnlyList maps) + { + ArgumentNullException.ThrowIfNull(stream, "stream"); + ArgumentNullException.ThrowIfNull(gameVersion, "gameVersion"); + ArgumentNullException.ThrowIfNull(maps, "maps"); + List list = new List(maps.Count); + foreach (TerritoryWaterMap map in maps) + { + if (map.HasWater) + { + list.Add(map); + } + } + list.Sort((TerritoryWaterMap left, TerritoryWaterMap right) => left.TerritoryId.CompareTo(right.TerritoryId)); + using BinaryWriter binaryWriter = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); + binaryWriter.Write(1129469015u); + binaryWriter.Write(1); + binaryWriter.Write(gameVersion); + binaryWriter.Write(list.Count); + foreach (TerritoryWaterMap item in list) + { + byte[] array = new byte[196608]; + item.Classes.CopyTo(array.AsSpan(0, 65536)); + ReadOnlySpan surfaceYRaw = item.SurfaceYRaw; + for (int num = 0; num < surfaceYRaw.Length; num++) + { + BinaryPrimitives.WriteUInt16LittleEndian(array.AsSpan(65536 + num * 2), surfaceYRaw[num]); + } + using MemoryStream memoryStream = new MemoryStream(); + using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true)) + { + deflateStream.Write(array); + } + binaryWriter.Write(item.TerritoryId); + binaryWriter.Write((int)memoryStream.Length); + binaryWriter.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); + } + } + + public static (string GameVersion, List Maps) Read(Stream stream) + { + ArgumentNullException.ThrowIfNull(stream, "stream"); + using BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true); + uint num = binaryReader.ReadUInt32(); + if (num != 1129469015) + { + throw new InvalidDataException($"water cache has magic 0x{num:X8}, expected 0x{1129469015:X8}"); + } + int num2 = binaryReader.ReadInt32(); + if (num2 != 1) + { + throw new InvalidDataException($"water cache has format version {num2}, expected {1}"); + } + string item = binaryReader.ReadString(); + int num3 = binaryReader.ReadInt32(); + if (num3 < 0 || num3 > 65536) + { + throw new InvalidDataException($"water cache declares {num3} territories"); + } + List list = new List(num3); + for (int i = 0; i < num3; i++) + { + ushort num4 = binaryReader.ReadUInt16(); + int num5 = binaryReader.ReadInt32(); + if (num5 < 0) + { + throw new InvalidDataException($"territory {num4} declares {num5} compressed bytes"); + } + byte[] array = binaryReader.ReadBytes(num5); + if (array.Length != num5) + { + throw new InvalidDataException($"territory {num4} is truncated: {array.Length} of {num5} bytes"); + } + byte[] array2 = new byte[196608]; + using (DeflateStream deflateStream = new DeflateStream(new MemoryStream(array, writable: false), CompressionMode.Decompress)) + { + deflateStream.ReadExactly(array2); + } + byte[] subArray = array2[..65536]; + byte[] array3 = subArray; + foreach (byte b in array3) + { + if (b > 2) + { + throw new InvalidDataException($"territory {num4} has cell class {b}, expected 0..{2}"); + } + } + ushort[] array4 = new ushort[65536]; + for (int k = 0; k < array4.Length; k++) + { + array4[k] = BinaryPrimitives.ReadUInt16LittleEndian(array2.AsSpan(65536 + k * 2)); + } + list.Add(new TerritoryWaterMap(num4, subArray, array4)); + } + return (GameVersion: item, Maps: list); + } +} diff --git a/SmartNav.Model/SmartNav.Model.Navigation/WaterCellClass.cs b/SmartNav.Model/SmartNav.Model.Navigation/WaterCellClass.cs new file mode 100644 index 0000000..2009ac9 --- /dev/null +++ b/SmartNav.Model/SmartNav.Model.Navigation/WaterCellClass.cs @@ -0,0 +1,8 @@ +namespace SmartNav.Model.Navigation; + +public enum WaterCellClass : byte +{ + None, + SwimOnly, + Divable +} diff --git a/SmartNav.Model/SmartNav.Model/MovementData.cs b/SmartNav.Model/SmartNav.Model/MovementData.cs index f0736d6..358522a 100644 --- a/SmartNav.Model/SmartNav.Model/MovementData.cs +++ b/SmartNav.Model/SmartNav.Model/MovementData.cs @@ -56,9 +56,25 @@ public static class MovementData public const float DefaultMountGroundDistance = 20f; + public const float MinMountTimeSavedSeconds = 2f; + public const float DefaultMountFlyDistance = 15f; public const float DefaultStopDistance = 3f; + + public static float MountGroundDistanceFor(int mountSpeedLevel) + { + return 2f / (1f / 6f - 1f / Speeds.GetMountGroundSpeed(mountSpeedLevel)); + } + } + + public static class Water + { + public const float EjectDepth = 20.6f; + + public const float EntryDepth = 25f; + + public const float SwimDepth = 0.6f; } public static class Timings @@ -74,5 +90,9 @@ public static class MovementData public const float SprintDurationInCombat = 10f; public const float SprintCooldown = 60f; + + public const float DiveEntryDuration = 4f; + + public const float SurfaceExitDuration = 1f; } } diff --git a/SmartNav/SmartNav.Data/WaterMapService.cs b/SmartNav/SmartNav.Data/WaterMapService.cs new file mode 100644 index 0000000..30c6a25 --- /dev/null +++ b/SmartNav/SmartNav.Data/WaterMapService.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using Microsoft.Extensions.Logging; +using SmartNav.Model.Navigation; + +namespace SmartNav.Data; + +public sealed class WaterMapService +{ + private readonly Lazy> _maps; + + public WaterMapService(SmartNavDataOptions dataOptions, ILogger logger) + { + _maps = new Lazy>(delegate + { + try + { + var (text, readOnlyDictionary) = AssemblyWaterMapLoader.GetWaterMaps(dataOptions.DevSourceDirectory?.FullName); + logger.LogInformation("WaterMapService: {Count} territories with water maps loaded (game version {GameVersion})", readOnlyDictionary.Count, text ?? ""); + return readOnlyDictionary; + } + catch (Exception exception) + { + logger.LogError(exception, "Water cache could not be loaded - routing proceeds without water data"); + return new Dictionary(); + } + }, LazyThreadSafetyMode.ExecutionAndPublication); + } + + internal WaterMapService(IReadOnlyDictionary maps) + { + _maps = new Lazy>(maps); + } + + public bool TryGetMap(uint territoryId, [NotNullWhen(true)] out TerritoryWaterMap? map) + { + if (territoryId > 65535) + { + map = null; + return false; + } + return _maps.Value.TryGetValue((ushort)territoryId, out map); + } +} diff --git a/SmartNav/SmartNav/CostCalculator.cs b/SmartNav/SmartNav/CostCalculator.cs index 3f84671..36a2d59 100644 --- a/SmartNav/SmartNav/CostCalculator.cs +++ b/SmartNav/SmartNav/CostCalculator.cs @@ -4,9 +4,53 @@ using SmartNav.Model; namespace SmartNav; -public sealed class CostCalculator(ITerritoryInfo territoryInfo) +public sealed class CostCalculator(ITerritoryInfo territoryInfo, WaterRoutePlanner waterPlanner) { public float EstimateWithinZoneTravelTime(Vector3 from, Vector3 to, uint territoryId, PlayerNavState playerState) + { + WaterPlan waterPlan = waterPlanner.Plan(territoryId, from, to); + if (waterPlan == null) + { + return EstimateDry(from, to, territoryId, playerState); + } + bool flag = playerState.MountUnlocked && playerState.IsFlyingUnlocked(territoryId); + switch (waterPlan.Kind) + { + case WaterPlanKind.Submerged: + return Vector3.Distance(from, to) / 6f; + case WaterPlanKind.SurfaceOut: + return AscentTime(from, waterPlan.SurfacePoint.Value) + EstimateDry(waterPlan.SurfacePoint.Value, to, territoryId, playerState); + case WaterPlanKind.DiveIn: + if (!flag) + { + return float.PositiveInfinity; + } + return EstimateDry(from, waterPlan.EntryPoint.Value, territoryId, playerState) + DescentTime(waterPlan.EntryPoint.Value, to); + case WaterPlanKind.SurfaceOutDiveIn: + if (!flag) + { + return float.PositiveInfinity; + } + return AscentTime(from, waterPlan.SurfacePoint.Value) + EstimateDry(waterPlan.SurfacePoint.Value, waterPlan.EntryPoint.Value, territoryId, playerState) + DescentTime(waterPlan.EntryPoint.Value, to); + default: + throw new ArgumentOutOfRangeException("plan", waterPlan.Kind, "unknown water plan kind"); + } + } + + private static float AscentTime(Vector3 from, Vector3 surfacePoint) + { + return MathF.Max(0f, surfacePoint.Y - from.Y - 20.6f) / 6f + 1f; + } + + private static float DescentTime(Vector3 entryPoint, Vector3 to) + { + Vector3 vector = entryPoint; + vector.Y = entryPoint.Y - 25f; + Vector3 value = vector; + return 4f + Vector3.Distance(value, to) / 6f; + } + + private float EstimateDry(Vector3 from, Vector3 to, uint territoryId, PlayerNavState playerState) { if (!territoryInfo.CanUseMount(territoryId)) { diff --git a/SmartNav/SmartNav/NavGraphView.cs b/SmartNav/SmartNav/NavGraphView.cs index 5d2951b..fe03837 100644 --- a/SmartNav/SmartNav/NavGraphView.cs +++ b/SmartNav/SmartNav/NavGraphView.cs @@ -225,6 +225,15 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t } } + private void AddWithinZoneEdge(NavGraph queryGraph, NavNode from, NavNode to, uint territoryId, PlayerNavState playerState) + { + float num = costCalculator.EstimateWithinZoneTravelTime(from.Position, to.Position, territoryId, playerState); + if (float.IsFinite(num)) + { + queryGraph.AddEdge(new NavEdge(from.Id, to.Id, NavEdgeType.WithinZone, num, 0)); + } + } + private void AddWithinZoneEdgesFrom(NavGraph queryGraph, NavNode origin, PlayerNavState playerState) { foreach (string item in queryGraph.GetNodesInTerritory(origin.TerritoryId)) @@ -234,8 +243,7 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t NavNode node = queryGraph.GetNode(item); if (!(node == null) && AreNodesConnectedWithinZone(origin, node, origin.TerritoryId)) { - float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(origin.Position, node.Position, origin.TerritoryId, playerState); - queryGraph.AddEdge(new NavEdge(origin.Id, item, NavEdgeType.WithinZone, timeCostSeconds, 0)); + AddWithinZoneEdge(queryGraph, origin, node, origin.TerritoryId, playerState); } } } @@ -250,8 +258,7 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t NavNode node = queryGraph.GetNode(item); if (!(node == null) && AreNodesConnectedWithinZone(node, destination, destination.TerritoryId)) { - float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, destination.Position, destination.TerritoryId, playerState); - queryGraph.AddEdge(new NavEdge(item, destination.Id, NavEdgeType.WithinZone, timeCostSeconds, 0)); + AddWithinZoneEdge(queryGraph, node, destination, destination.TerritoryId, playerState); } } } @@ -348,10 +355,8 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t NavNode node = queryGraph.GetNode(item); if (!(node == null) && (!(node is NavNode.ZoneBoundaryNode) || string.CompareOrdinal(zoneBoundaryNode.Id, item) < 0) && AreNodesConnectedWithinZone(zoneBoundaryNode, node, zoneBoundaryNode.TerritoryId)) { - float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, zoneBoundaryNode.Position, zoneBoundaryNode.TerritoryId, playerState); - float timeCostSeconds2 = costCalculator.EstimateWithinZoneTravelTime(zoneBoundaryNode.Position, node.Position, zoneBoundaryNode.TerritoryId, playerState); - queryGraph.AddEdge(new NavEdge(item, zoneBoundaryNode.Id, NavEdgeType.WithinZone, timeCostSeconds, 0)); - queryGraph.AddEdge(new NavEdge(zoneBoundaryNode.Id, item, NavEdgeType.WithinZone, timeCostSeconds2, 0)); + AddWithinZoneEdge(queryGraph, node, zoneBoundaryNode, zoneBoundaryNode.TerritoryId, playerState); + AddWithinZoneEdge(queryGraph, zoneBoundaryNode, node, zoneBoundaryNode.TerritoryId, playerState); } } } @@ -373,10 +378,8 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t NavNode node = queryGraph.GetNode(item); if (!(node == null) && (!(node is NavNode.WarpNode) || string.CompareOrdinal(warpNode.Id, item) < 0) && !(node is NavNode.ZoneBoundaryNode) && AreNodesConnectedWithinZone(warpNode, node, warpNode.TerritoryId)) { - float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, warpNode.Position, warpNode.TerritoryId, playerState); - float timeCostSeconds2 = costCalculator.EstimateWithinZoneTravelTime(warpNode.Position, node.Position, warpNode.TerritoryId, playerState); - queryGraph.AddEdge(new NavEdge(item, warpNode.Id, NavEdgeType.WithinZone, timeCostSeconds, 0)); - queryGraph.AddEdge(new NavEdge(warpNode.Id, item, NavEdgeType.WithinZone, timeCostSeconds2, 0)); + AddWithinZoneEdge(queryGraph, node, warpNode, warpNode.TerritoryId, playerState); + AddWithinZoneEdge(queryGraph, warpNode, node, warpNode.TerritoryId, playerState); } } } @@ -403,10 +406,8 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t bool flag = ((node is NavNode.ZoneBoundaryNode || node is NavNode.WarpNode) ? true : false); if (!flag && AreNodesConnectedWithinZone(subRegionConnectorNode, node, subRegionConnectorNode.TerritoryId)) { - float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, subRegionConnectorNode.Position, subRegionConnectorNode.TerritoryId, playerState); - float timeCostSeconds2 = costCalculator.EstimateWithinZoneTravelTime(subRegionConnectorNode.Position, node.Position, subRegionConnectorNode.TerritoryId, playerState); - queryGraph.AddEdge(new NavEdge(item, subRegionConnectorNode.Id, NavEdgeType.WithinZone, timeCostSeconds, 0)); - queryGraph.AddEdge(new NavEdge(subRegionConnectorNode.Id, item, NavEdgeType.WithinZone, timeCostSeconds2, 0)); + AddWithinZoneEdge(queryGraph, node, subRegionConnectorNode, subRegionConnectorNode.TerritoryId, playerState); + AddWithinZoneEdge(queryGraph, subRegionConnectorNode, node, subRegionConnectorNode.TerritoryId, playerState); } } } diff --git a/SmartNav/SmartNav/NavInstruction.cs b/SmartNav/SmartNav/NavInstruction.cs index 8261171..09bdcf8 100644 --- a/SmartNav/SmartNav/NavInstruction.cs +++ b/SmartNav/SmartNav/NavInstruction.cs @@ -12,6 +12,12 @@ public abstract record NavInstruction public sealed record Move(ushort Territory, Vector3 Position, bool Fly, bool LandAtTarget, float? StopDistance, uint? DataId, bool? Mount, bool DisableNavmesh, bool AllowZoneTransition, string? TargetNodeId, string? ApproachNodeId, bool IsFinal) : NavInstruction(); + public sealed record DiveEntry(ushort Territory, Vector3 Position, string? TargetNodeId, string? ApproachNodeId) : NavInstruction(); + + public sealed record Swim(ushort Territory, Vector3 Position, float SurfaceY, float? StopDistance, string? TargetNodeId, string? ApproachNodeId, bool IsFinal) : NavInstruction(); + + public sealed record Surface(ushort Territory, Vector3 Position, string? TargetNodeId, string? ApproachNodeId) : NavInstruction(); + public sealed record Land : NavInstruction; public sealed record Unmount : NavInstruction; diff --git a/SmartNav/SmartNav/PlayerNavState.cs b/SmartNav/SmartNav/PlayerNavState.cs index 96462f1..a245a45 100644 --- a/SmartNav/SmartNav/PlayerNavState.cs +++ b/SmartNav/SmartNav/PlayerNavState.cs @@ -93,4 +93,42 @@ public sealed class PlayerNavState { return QuestSequenceChecker(questRowId); } + + public PlayerNavState WithTravelCapabilities(bool allowMount, bool allowFlight) + { + PlayerNavState obj = new PlayerNavState + { + CurrentTerritoryId = CurrentTerritoryId, + CurrentPosition = CurrentPosition, + Gil = Gil, + GilReserve = GilReserve, + UnlockedAetherytes = UnlockedAetherytes, + HomeAetheryte = HomeAetheryte, + FreeAetheryte = FreeAetheryte, + TeleportCosts = TeleportCosts + }; + IReadOnlySet flyingUnlockedTerritories; + if (!(allowMount && allowFlight)) + { + IReadOnlySet readOnlySet = new HashSet(); + flyingUnlockedTerritories = readOnlySet; + } + else + { + flyingUnlockedTerritories = FlyingUnlockedTerritories; + } + obj.FlyingUnlockedTerritories = flyingUnlockedTerritories; + obj.MountSpeedLevels = MountSpeedLevels; + obj.TeleportUnlocked = TeleportUnlocked; + obj.ReturnAvailable = ReturnAvailable; + obj.MountUnlocked = allowMount && MountUnlocked; + obj.MaxExpansion = MaxExpansion; + obj.CurrentLevel = CurrentLevel; + obj.CurrentMsqQuestId = CurrentMsqQuestId; + obj.PreferFreeTravel = PreferFreeTravel; + obj.QuestCompleteChecker = QuestCompleteChecker; + obj.QuestSequenceChecker = QuestSequenceChecker; + obj.AvailableTeleportTickets = AvailableTeleportTickets; + return obj; + } } diff --git a/SmartNav/SmartNav/PlayerNavStateBuilder.cs b/SmartNav/SmartNav/PlayerNavStateBuilder.cs index 683bc26..4839473 100644 --- a/SmartNav/SmartNav/PlayerNavStateBuilder.cs +++ b/SmartNav/SmartNav/PlayerNavStateBuilder.cs @@ -117,7 +117,7 @@ public class PlayerNavStateBuilder(IClientState clientState, IObjectTable object return hashSet; } - private unsafe Dictionary BuildMountSpeedLevels() + private Dictionary BuildMountSpeedLevels() { Dictionary dictionary = new Dictionary(); ExcelSheet excelSheet = dataManager.GetExcelSheet(); @@ -125,30 +125,51 @@ public class PlayerNavStateBuilder(IClientState clientState, IObjectTable object { return dictionary; } - UIState* ptr = UIState.Instance(); foreach (TerritoryType item in excelSheet) { if (item.RowId != 0 && item.MountSpeed.RowId != 0) { - MountSpeed value = item.MountSpeed.Value; - int num = 0; - if (value.Quest.RowId != 0 && ptr != null && ptr->IsUnlockLinkUnlockedOrQuestCompleted(value.Quest.RowId, 0)) + int unlockedMountSpeedLevel = GetUnlockedMountSpeedLevel(item.MountSpeed.Value); + if (unlockedMountSpeedLevel > 0) { - num++; - } - if (value.Unknown0 != 0 && ptr != null && ptr->IsUnlockLinkUnlockedOrQuestCompleted(value.Unknown0, 0)) - { - num++; - } - if (num > 0) - { - dictionary[item.RowId] = num; + dictionary[item.RowId] = unlockedMountSpeedLevel; } } } return dictionary; } + public (int Level, int MaxLevel)? GetTerritoryMountSpeed(uint territoryId) + { + TerritoryType? territoryType = dataManager.GetExcelSheet()?.GetRowOrDefault(territoryId); + if (!territoryType.HasValue || territoryType.Value.MountSpeed.RowId == 0) + { + return null; + } + MountSpeed value = territoryType.Value.MountSpeed.Value; + int item = ((value.Quest.RowId != 0) ? 1 : 0) + ((value.Unknown0 != 0) ? 1 : 0); + return (GetUnlockedMountSpeedLevel(value), item); + } + + private unsafe static int GetUnlockedMountSpeedLevel(MountSpeed mountSpeed) + { + UIState* ptr = UIState.Instance(); + if (ptr == null) + { + return 0; + } + int num = 0; + if (mountSpeed.Quest.RowId != 0 && ptr->IsUnlockLinkUnlockedOrQuestCompleted(mountSpeed.Quest.RowId, 0)) + { + num++; + } + if (mountSpeed.Unknown0 != 0 && ptr->IsUnlockLinkUnlockedOrQuestCompleted(mountSpeed.Unknown0, 0)) + { + num++; + } + return num; + } + private unsafe static int GetCurrentGil() { InventoryManager* ptr = InventoryManager.Instance(); diff --git a/SmartNav/SmartNav/ReRoutePolicy.cs b/SmartNav/SmartNav/ReRoutePolicy.cs index f2732a0..5c900a6 100644 --- a/SmartNav/SmartNav/ReRoutePolicy.cs +++ b/SmartNav/SmartNav/ReRoutePolicy.cs @@ -13,17 +13,23 @@ public sealed class ReRoutePolicy(NavRouter navRouter, PlayerNavStateBuilder pla private bool _hasDestination; + private bool _allowMount = true; + + private bool _allowFlight = true; + private int _reRouteCount; private readonly HashSet _penalizedTargets = new HashSet(); private const int MaxReRoutes = 3; - public void SetDestination(uint territoryId, Vector3 position) + public void SetDestination(uint territoryId, Vector3 position, bool allowMount = true, bool allowFlight = true) { - bool num = _hasDestination && _destinationTerritoryId == territoryId && Vector3.DistanceSquared(_destinationPosition, position) < 1f; + bool num = _hasDestination && _destinationTerritoryId == territoryId && Vector3.DistanceSquared(_destinationPosition, position) < 1f && _allowMount == allowMount && _allowFlight == allowFlight; _destinationTerritoryId = territoryId; _destinationPosition = position; + _allowMount = allowMount; + _allowFlight = allowFlight; _hasDestination = true; _reRouteCount = 0; _penalizedTargets.Clear(); @@ -68,6 +74,7 @@ public sealed class ReRoutePolicy(NavRouter navRouter, PlayerNavStateBuilder pla logger.LogWarning("Cannot build PlayerNavState for re-routing"); return new ReRouteDecision.GiveUp(); } + playerNavState = playerNavState.WithTravelCapabilities(_allowMount, _allowFlight); if (playerNavState.CurrentTerritoryId != _destinationTerritoryId && hasPreservedTasks) { _reRouteCount++; diff --git a/SmartNav/SmartNav/RouteInstructionBuilder.cs b/SmartNav/SmartNav/RouteInstructionBuilder.cs index 8d1c977..18d049a 100644 --- a/SmartNav/SmartNav/RouteInstructionBuilder.cs +++ b/SmartNav/SmartNav/RouteInstructionBuilder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Numerics; using Microsoft.Extensions.Logging; @@ -7,7 +8,7 @@ using SmartNav.Model.Navigation; namespace SmartNav; -public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerritoryInfo territoryInfo, ILogger logger) +public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerritoryInfo territoryInfo, WaterRoutePlanner waterPlanner, ILogger logger) { private const float BoundaryApproachMargin = 5f; @@ -17,7 +18,25 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit for (int i = 0; i < route.Segments.Count; i++) { RouteSegment routeSegment = route.Segments[i]; - if (routeSegment.EdgeType != NavEdgeType.WithinZone || i + 1 >= route.Segments.Count || route.Segments[i + 1].EdgeType != NavEdgeType.ZoneBoundary) + if (routeSegment.EdgeType == NavEdgeType.WithinZone && i + 1 < route.Segments.Count && route.Segments[i + 1].EdgeType == NavEdgeType.ZoneBoundary) + { + WaterPlan waterPlan = waterPlanner.Plan(routeSegment.From.TerritoryId, routeSegment.From.Position, routeSegment.To.Position); + if ((object)waterPlan != null && waterPlan.Kind == WaterPlanKind.SurfaceOut) + { + uint territoryId = routeSegment.From.TerritoryId; + list.Add(new NavInstruction.WaitForTerritory(new global::_003C_003Ez__ReadOnlySingleElementList(territoryId), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId) + ")")); + if (!disableNavmesh) + { + list.Add(new NavInstruction.WaitForNavmesh()); + } + list.Add(new NavInstruction.Surface((ushort)territoryId, waterPlan.SurfacePoint.Value, routeSegment.To.Id, routeSegment.From.Id)); + } + else if (waterPlan != null) + { + logger.LogWarning("SmartNav: water plan {Kind} on a within-zone segment collapsed into a boundary crossing in {Territory}; only the boundary move is emitted", waterPlan.Kind, territoryInfo.GetNameAndId(routeSegment.From.TerritoryId)); + } + } + else { BuildSegment(list, routeSegment, (i > 0) ? route.Segments[i - 1] : null, i, route.Segments.Count, destinationTerritory, disableNavmesh); } @@ -52,6 +71,39 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit return true; } + private static NavInstruction.Move BuildWithinZoneMove(RouteSegment seg, uint territory, bool isFinalSegment) + { + uint? num = ((!(seg.To is NavNode.WarpNode warpNode)) ? ((uint?)null) : warpNode.NpcDataId); + uint? dataId = num; + return new NavInstruction.Move((ushort)territory, seg.To.Position, seg.Fly, seg.Fly, isFinalSegment ? ((float?)null) : new float?(3f), dataId, null, DisableNavmesh: false, AllowZoneTransition: false, seg.To.Id, seg.From.Id, isFinalSegment); + } + + private static void EmitWaterLegs(List instructions, WaterPlan plan, RouteSegment seg, uint territory, bool isFinalSegment) + { + WaterPlanKind kind = plan.Kind; + if ((uint)kind > 3u) + { + throw new ArgumentOutOfRangeException("plan", plan.Kind, "unknown water plan kind"); + } + kind = plan.Kind; + if ((uint)(kind - 2) <= 1u) + { + instructions.Add(new NavInstruction.Surface((ushort)territory, plan.SurfacePoint.Value, seg.To.Id, seg.From.Id)); + } + if (plan.Kind == WaterPlanKind.SurfaceOut) + { + instructions.Add(BuildWithinZoneMove(seg, territory, isFinalSegment)); + return; + } + kind = plan.Kind; + if ((kind == WaterPlanKind.DiveIn || kind == WaterPlanKind.SurfaceOutDiveIn) ? true : false) + { + instructions.Add(new NavInstruction.Move((ushort)territory, plan.EntryPoint.Value, Fly: true, LandAtTarget: false, 3f, null, null, DisableNavmesh: false, AllowZoneTransition: false, seg.To.Id, seg.From.Id, IsFinal: false)); + instructions.Add(new NavInstruction.DiveEntry((ushort)territory, plan.EntryPoint.Value, seg.To.Id, seg.From.Id)); + } + instructions.Add(new NavInstruction.Swim((ushort)territory, seg.To.Position, plan.GoalSurfaceY.Value, isFinalSegment ? ((float?)null) : new float?(3f), seg.To.Id, seg.From.Id, isFinalSegment)); + } + private void BuildSegment(List instructions, RouteSegment seg, RouteSegment? previous, int index, int segmentCount, uint destinationTerritory, bool disableNavmesh) { uint territoryId4; @@ -79,15 +131,21 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit { uint territoryId = seg.From.TerritoryId; bool flag = index == segmentCount - 1 && seg.To.TerritoryId == destinationTerritory; - logger.LogTrace("SmartNav: Move within {Territory}{Final}", territoryInfo.GetNameAndId(territoryId), flag ? " (final)" : ""); + WaterPlan waterPlan = waterPlanner.Plan(territoryId, seg.From.Position, seg.To.Position); + logger.LogTrace("SmartNav: Move within {Territory}{Final}{Water}", territoryInfo.GetNameAndId(territoryId), flag ? " (final)" : "", (waterPlan == null) ? "" : $" (water: {waterPlan.Kind})"); instructions.Add(new NavInstruction.WaitForTerritory(new global::_003C_003Ez__ReadOnlySingleElementList(territoryId), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId) + ")")); if (!disableNavmesh) { instructions.Add(new NavInstruction.WaitForNavmesh()); } - uint? num = ((!(seg.To is NavNode.WarpNode warpNode)) ? ((uint?)null) : warpNode.NpcDataId); - uint? dataId = num; - instructions.Add(new NavInstruction.Move((ushort)territoryId, seg.To.Position, seg.Fly, seg.Fly, flag ? ((float?)null) : new float?(3f), dataId, null, DisableNavmesh: false, AllowZoneTransition: false, seg.To.Id, seg.From.Id, flag)); + if (waterPlan == null) + { + instructions.Add(BuildWithinZoneMove(seg, territoryId, flag)); + } + else + { + EmitWaterLegs(instructions, waterPlan, seg, territoryId, flag); + } break; } case NavEdgeType.ZoneBoundary: @@ -104,20 +162,20 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit { instructions.Add(new NavInstruction.Move((ushort)territoryId3, approach, Fly: true, LandAtTarget: true, 3f, null, null, DisableNavmesh: false, AllowZoneTransition: true, seg.From.Id, null, IsFinal: false)); instructions.Add(new NavInstruction.Move((ushort)territoryId3, seg.From.Position, Fly: false, LandAtTarget: false, 0f, null, null, DisableNavmesh: true, AllowZoneTransition: true, seg.From.Id, null, IsFinal: false)); - goto IL_04b4; + goto IL_04bb; } } instructions.Add(new NavInstruction.Move((ushort)territoryId3, seg.From.Position, seg.Fly, seg.Fly, 0f, null, null, DisableNavmesh: false, AllowZoneTransition: true, seg.From.Id, null, IsFinal: false)); - goto IL_04b4; + goto IL_04bb; } case NavEdgeType.Warp: { uint territoryId5 = seg.From.TerritoryId; uint territoryId6 = seg.To.TerritoryId; logger.LogTrace("SmartNav: Warp from {From} to {To}", territoryInfo.GetNameAndId(territoryId5), territoryInfo.GetNameAndId(territoryId6)); - if (seg.From is NavNode.WarpNode { NpcDataId: { } npcDataId } warpNode2) + if (seg.From is NavNode.WarpNode { NpcDataId: { } npcDataId } warpNode) { - instructions.Add(new NavInstruction.InteractWarp(npcDataId, warpNode2.WarpRowId, (ushort)territoryId5, (ushort)territoryId6)); + instructions.Add(new NavInstruction.InteractWarp(npcDataId, warpNode.WarpRowId, (ushort)territoryId5, (ushort)territoryId6)); break; } logger.LogWarning("SmartNav: warp segment {From} -> {To} has no interactable source NPC; segment dropped", territoryInfo.GetNameAndId(territoryId5), territoryInfo.GetNameAndId(territoryId6)); @@ -156,7 +214,7 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit logger.LogWarning("SmartNav: unhandled edge type {EdgeType}; segment dropped", seg.EdgeType); break; } - IL_04b4: + IL_04bb: instructions.Add(new NavInstruction.WaitForTerritory(new global::_003C_003Ez__ReadOnlySingleElementList(territoryId4), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId4) + ")")); break; } diff --git a/SmartNav/SmartNav/WaterPlan.cs b/SmartNav/SmartNav/WaterPlan.cs new file mode 100644 index 0000000..44cea9c --- /dev/null +++ b/SmartNav/SmartNav/WaterPlan.cs @@ -0,0 +1,5 @@ +using System.Numerics; + +namespace SmartNav; + +public sealed record WaterPlan(WaterPlanKind Kind, Vector3? SurfacePoint, Vector3? EntryPoint, float? GoalSurfaceY); diff --git a/SmartNav/SmartNav/WaterPlanKind.cs b/SmartNav/SmartNav/WaterPlanKind.cs new file mode 100644 index 0000000..9d33015 --- /dev/null +++ b/SmartNav/SmartNav/WaterPlanKind.cs @@ -0,0 +1,9 @@ +namespace SmartNav; + +public enum WaterPlanKind +{ + Submerged, + DiveIn, + SurfaceOut, + SurfaceOutDiveIn +} diff --git a/SmartNav/SmartNav/WaterRoutePlanner.cs b/SmartNav/SmartNav/WaterRoutePlanner.cs new file mode 100644 index 0000000..26c67ed --- /dev/null +++ b/SmartNav/SmartNav/WaterRoutePlanner.cs @@ -0,0 +1,66 @@ +using System; +using System.Numerics; +using SmartNav.Data; +using SmartNav.Model.Navigation; + +namespace SmartNav; + +public sealed class WaterRoutePlanner(WaterMapService waterMaps) +{ + private const float SameBodySurfaceTolerance = 0.5f; + + public bool IsSubmerged(uint territoryId, Vector3 point, out float surfaceY) + { + surfaceY = 0f; + if (!waterMaps.TryGetMap(territoryId, out TerritoryWaterMap map)) + { + return false; + } + return IsSubmerged(map, point, out surfaceY); + } + + public WaterPlan? Plan(uint territoryId, Vector3 from, Vector3 to) + { + if (!waterMaps.TryGetMap(territoryId, out TerritoryWaterMap map)) + { + return null; + } + float surfaceY; + bool flag = IsSubmerged(map, from, out surfaceY); + float surfaceY2; + bool flag2 = IsSubmerged(map, to, out surfaceY2); + if (!flag && !flag2) + { + return null; + } + Vector3? surfacePoint = (flag ? new Vector3?(new Vector3(from.X, surfaceY, from.Z)) : ((Vector3?)null)); + Vector3 value = new Vector3(to.X, surfaceY2, to.Z); + if (!flag) + { + return new WaterPlan(WaterPlanKind.DiveIn, null, value, surfaceY2); + } + if (!flag2) + { + return new WaterPlan(WaterPlanKind.SurfaceOut, surfacePoint, null, null); + } + if (MathF.Abs(surfaceY - surfaceY2) <= 0.5f) + { + return new WaterPlan(WaterPlanKind.Submerged, null, null, surfaceY2); + } + return new WaterPlan(WaterPlanKind.SurfaceOutDiveIn, surfacePoint, value, surfaceY2); + } + + private static bool IsSubmerged(TerritoryWaterMap map, Vector3 point, out float surfaceY) + { + surfaceY = 0f; + if (map.ClassifyAt(point.X, point.Z) != WaterCellClass.Divable) + { + return false; + } + if (map.TryGetSurfaceY(point.X, point.Z, out surfaceY)) + { + return point.Y <= surfaceY - 20.6f; + } + return false; + } +}