muffin v7.5.13
This commit is contained in:
parent
911ed68baa
commit
acf3e3cb18
69 changed files with 2731 additions and 1425 deletions
Binary file not shown.
|
|
@ -4,18 +4,18 @@ internal static class PathBundleSecret
|
||||||
{
|
{
|
||||||
private static readonly byte[] A = new byte[32]
|
private static readonly byte[] A = new byte[32]
|
||||||
{
|
{
|
||||||
48, 212, 251, 149, 233, 68, 48, 175, 219, 236,
|
183, 148, 243, 175, 81, 166, 60, 18, 24, 50,
|
||||||
217, 100, 130, 200, 255, 152, 233, 112, 177, 94,
|
233, 21, 123, 58, 154, 174, 171, 214, 240, 163,
|
||||||
93, 198, 133, 46, 110, 244, 154, 244, 247, 13,
|
19, 222, 77, 126, 21, 146, 142, 237, 152, 128,
|
||||||
145, 158
|
16, 0
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly byte[] B = new byte[32]
|
private static readonly byte[] B = new byte[32]
|
||||||
{
|
{
|
||||||
79, 60, 140, 255, 167, 84, 206, 132, 105, 228,
|
167, 25, 83, 195, 35, 102, 138, 74, 178, 77,
|
||||||
71, 209, 251, 6, 250, 62, 38, 86, 249, 66,
|
83, 219, 116, 213, 10, 227, 200, 219, 186, 181,
|
||||||
102, 234, 109, 58, 57, 229, 131, 131, 42, 66,
|
158, 22, 1, 102, 179, 218, 15, 30, 54, 210,
|
||||||
172, 97
|
214, 232
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static byte[] Key()
|
internal static byte[] Key()
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -4,18 +4,18 @@ internal static class PathBundleSecret
|
||||||
{
|
{
|
||||||
private static readonly byte[] A = new byte[32]
|
private static readonly byte[] A = new byte[32]
|
||||||
{
|
{
|
||||||
83, 53, 162, 177, 116, 210, 154, 147, 32, 246,
|
219, 178, 133, 86, 37, 102, 10, 221, 178, 245,
|
||||||
109, 49, 21, 10, 185, 40, 3, 223, 158, 234,
|
197, 51, 203, 134, 231, 122, 11, 25, 197, 240,
|
||||||
82, 124, 250, 209, 185, 174, 77, 93, 58, 15,
|
44, 220, 234, 91, 41, 93, 186, 183, 17, 72,
|
||||||
16, 246
|
109, 31
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly byte[] B = new byte[32]
|
private static readonly byte[] B = new byte[32]
|
||||||
{
|
{
|
||||||
11, 0, 215, 132, 153, 74, 202, 77, 15, 86,
|
107, 156, 123, 202, 221, 77, 66, 210, 234, 31,
|
||||||
243, 43, 187, 26, 168, 53, 42, 2, 79, 60,
|
174, 55, 4, 171, 94, 88, 13, 248, 215, 191,
|
||||||
154, 215, 216, 52, 147, 70, 38, 49, 232, 46,
|
115, 73, 208, 117, 230, 149, 32, 243, 105, 23,
|
||||||
225, 130
|
154, 250
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static byte[] Key()
|
internal static byte[] Key()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
|
|
||||||
namespace LLib.ImGui;
|
namespace LLib.ImGui;
|
||||||
|
|
||||||
[Obfuscation(Feature = "-rename", Exclude = false, ApplyToMembers = true)]
|
|
||||||
public abstract class LWindow : Window
|
public abstract class LWindow : Window
|
||||||
{
|
{
|
||||||
private bool _initializedConfig;
|
private bool _initializedConfig;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace LLib.ImGui;
|
namespace LLib.ImGui;
|
||||||
|
|
||||||
[Obfuscation(Feature = "-rename", Exclude = false, ApplyToMembers = true)]
|
|
||||||
public class WindowConfig
|
public class WindowConfig
|
||||||
{
|
{
|
||||||
public bool IsPinned { get; set; }
|
public bool IsPinned { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -120,33 +119,7 @@ public sealed class DalamudLoggerProvider : ILoggerProvider, IDisposable, ISuppo
|
||||||
|
|
||||||
public ILogger CreateLogger(string categoryName)
|
public ILogger CreateLogger(string categoryName)
|
||||||
{
|
{
|
||||||
string name = _categoryNameFormatter(categoryName);
|
return new DalamudLogger(_categoryNameFormatter(categoryName), this, _pluginLog);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
|
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -185,34 +185,58 @@
|
||||||
"description": "If true, will go to the position in a straight line instead of using pathfinding",
|
"description": "If true, will go to the position in a straight line instead of using pathfinding",
|
||||||
"type": "boolean"
|
"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": {
|
"Mount": {
|
||||||
"type": [
|
"type": [
|
||||||
"boolean",
|
"boolean",
|
||||||
"null"
|
"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": {
|
"MountRequired": {
|
||||||
"type": "boolean",
|
"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": {
|
"DismountRequired": {
|
||||||
"type": "boolean",
|
"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": {
|
"Fly": {
|
||||||
"type": "boolean",
|
"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": {
|
"Land": {
|
||||||
"type": "boolean",
|
"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": {
|
"Sprint": {
|
||||||
"type": [
|
"type": [
|
||||||
"boolean",
|
"boolean",
|
||||||
"null"
|
"null"
|
||||||
]
|
],
|
||||||
|
"description": "GENERAL MOVEMENT: Controls Sprint independently of mount and flight behavior."
|
||||||
},
|
},
|
||||||
"ItemId": {
|
"ItemId": {
|
||||||
"type": [
|
"type": [
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,18 @@ internal static class PathBundleSecret
|
||||||
{
|
{
|
||||||
private static readonly byte[] A = new byte[32]
|
private static readonly byte[] A = new byte[32]
|
||||||
{
|
{
|
||||||
73, 4, 92, 189, 214, 0, 53, 235, 105, 164,
|
153, 30, 158, 74, 159, 14, 199, 185, 40, 166,
|
||||||
216, 243, 244, 200, 198, 26, 227, 227, 153, 121,
|
85, 6, 37, 167, 5, 38, 221, 72, 99, 65,
|
||||||
241, 147, 166, 171, 63, 63, 121, 152, 52, 115,
|
160, 20, 49, 186, 80, 230, 97, 96, 19, 189,
|
||||||
89, 201
|
194, 228
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly byte[] B = new byte[32]
|
private static readonly byte[] B = new byte[32]
|
||||||
{
|
{
|
||||||
243, 132, 180, 1, 83, 89, 202, 66, 33, 20,
|
30, 173, 221, 145, 116, 199, 45, 52, 72, 186,
|
||||||
242, 184, 99, 15, 215, 2, 4, 125, 221, 134,
|
186, 181, 129, 66, 39, 24, 195, 19, 249, 56,
|
||||||
186, 51, 132, 151, 141, 220, 230, 57, 252, 77,
|
14, 14, 134, 179, 100, 23, 222, 226, 221, 184,
|
||||||
103, 57
|
242, 90
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static byte[] Key()
|
internal static byte[] Key()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using SmartNav.Model.Converter;
|
||||||
|
|
||||||
|
namespace Questionable.Model.Questing.Converter;
|
||||||
|
|
||||||
|
public sealed class ArrivalModeConverter : EnumConverter<EArrivalMode>
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<EArrivalMode, string> Values = new Dictionary<EArrivalMode, string>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
EArrivalMode.Auto,
|
||||||
|
"Auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EArrivalMode.StayMounted,
|
||||||
|
"StayMounted"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EArrivalMode.Land,
|
||||||
|
"Land"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EArrivalMode.Dismount,
|
||||||
|
"Dismount"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public ArrivalModeConverter()
|
||||||
|
: base((IReadOnlyDictionary<EArrivalMode, string>)Values)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using SmartNav.Model.Converter;
|
||||||
|
|
||||||
|
namespace Questionable.Model.Questing.Converter;
|
||||||
|
|
||||||
|
public sealed class TravelModeConverter : EnumConverter<ETravelMode>
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<ETravelMode, string> Values = new Dictionary<ETravelMode, string>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
ETravelMode.Auto,
|
||||||
|
"Auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ETravelMode.OnFoot,
|
||||||
|
"OnFoot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ETravelMode.GroundMount,
|
||||||
|
"GroundMount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ETravelMode.Flying,
|
||||||
|
"Flying"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public TravelModeConverter()
|
||||||
|
: base((IReadOnlyDictionary<ETravelMode, string>)Values)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -45,6 +45,10 @@ public sealed class QuestStep
|
||||||
|
|
||||||
public bool DisableNavmesh { get; set; }
|
public bool DisableNavmesh { get; set; }
|
||||||
|
|
||||||
|
public ETravelMode TravelMode { get; set; }
|
||||||
|
|
||||||
|
public EArrivalMode ArrivalMode { get; set; }
|
||||||
|
|
||||||
public bool? Mount { get; set; }
|
public bool? Mount { get; set; }
|
||||||
|
|
||||||
public bool MountRequired { get; set; }
|
public bool MountRequired { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -56,19 +56,19 @@ internal sealed class BossModModule : ICombatModule, IDisposable
|
||||||
if ((uint)(valueOrDefault - 3) <= 1u)
|
if ((uint)(valueOrDefault - 3) <= 1u)
|
||||||
{
|
{
|
||||||
flag = true;
|
flag = true;
|
||||||
goto IL_00bd;
|
goto IL_00be;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flag = false;
|
flag = false;
|
||||||
goto IL_00bd;
|
goto IL_00be;
|
||||||
}
|
}
|
||||||
goto IL_012e;
|
goto IL_012f;
|
||||||
IL_00bd:
|
IL_00be:
|
||||||
bool flag2 = flag;
|
bool flag2 = flag;
|
||||||
_logger.LogDebug("BossModModule.Start: isRanged={IsRanged}, job role={Role}", flag2, localPlayer.ClassJob.ValueNullable?.Role);
|
_logger.LogDebug("BossModModule.Start: isRanged={IsRanged}, job role={Role}", flag2, localPlayer.ClassJob.ValueNullable?.Role);
|
||||||
_bossModIpc.SetRangeStrategy(BossModIpc.EPreset.Active, flag2);
|
_bossModIpc.SetRangeStrategy(BossModIpc.EPreset.Active, flag2);
|
||||||
goto IL_012e;
|
goto IL_012f;
|
||||||
IL_012e:
|
IL_012f:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (IpcError exception)
|
catch (IpcError exception)
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,28 @@ internal sealed class ItemUseModule : ICombatModule
|
||||||
|
|
||||||
private long _continueAt;
|
private long _continueAt;
|
||||||
|
|
||||||
|
private bool _isWaitingToUseItem;
|
||||||
|
|
||||||
private bool _itemUsePending;
|
private bool _itemUsePending;
|
||||||
|
|
||||||
private long _itemUsePendingUntil;
|
private long _itemUsePendingUntil;
|
||||||
|
|
||||||
|
private int _itemCountBeforeUse;
|
||||||
|
|
||||||
public ICombatModule? Delegate => _delegate;
|
public ICombatModule? Delegate => _delegate;
|
||||||
|
|
||||||
|
public bool IsItemUseInProgress
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_isWaitingToUseItem || _itemUsePending)
|
||||||
|
{
|
||||||
|
return Environment.TickCount64 < _itemUsePendingUntil;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ItemUseModule(IServiceProvider serviceProvider, ICondition condition, ILogger<ItemUseModule> logger)
|
public ItemUseModule(IServiceProvider serviceProvider, ICondition condition, ILogger<ItemUseModule> logger)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
|
@ -62,6 +78,7 @@ internal sealed class ItemUseModule : ICombatModule
|
||||||
_combatData = combatData;
|
_combatData = combatData;
|
||||||
_isDoingRotation = true;
|
_isDoingRotation = true;
|
||||||
_continueAt = Environment.TickCount64;
|
_continueAt = Environment.TickCount64;
|
||||||
|
_isWaitingToUseItem = false;
|
||||||
_itemUsePending = false;
|
_itemUsePending = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -73,11 +90,13 @@ internal sealed class ItemUseModule : ICombatModule
|
||||||
if (_isDoingRotation)
|
if (_isDoingRotation)
|
||||||
{
|
{
|
||||||
_delegate.Stop();
|
_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;
|
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))))
|
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 (inventoryItemCount < _itemCountBeforeUse)
|
||||||
if (_itemUsePending)
|
|
||||||
{
|
{
|
||||||
if (Environment.TickCount64 < _itemUsePendingUntil)
|
_itemUsePending = false;
|
||||||
{
|
return;
|
||||||
_logger.LogDebug("Item use pending; ignoring temporary inventory count={Count}", inventoryItemCount);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_itemUsePending = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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);
|
_delegate.Update(nextTarget);
|
||||||
}
|
}
|
||||||
else if (ShouldUseItem(nextTarget))
|
else if (ShouldUseItem(nextTarget))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Attempting to use item {ItemId}", _combatData.CombatItemUse.ItemId);
|
_logger.LogDebug("Pausing rotation before using item {ItemId}", _combatData.CombatItemUse.ItemId);
|
||||||
_itemUsePending = true;
|
_isDoingRotation = false;
|
||||||
_itemUsePendingUntil = Environment.TickCount64 + 3000;
|
_isWaitingToUseItem = true;
|
||||||
AgentInventoryContext.Instance()->UseItem(_combatData.CombatItemUse.ItemId, InventoryType.Invalid, 0u, 0);
|
_delegate.Stop();
|
||||||
_continueAt = Environment.TickCount64 + 2000;
|
_continueAt = Environment.TickCount64 + 3000;
|
||||||
|
_itemUsePendingUntil = Environment.TickCount64 + 4000;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_delegate.Update(nextTarget);
|
_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])
|
else if (_condition[ConditionFlag.Casting])
|
||||||
{
|
{
|
||||||
long num = Environment.TickCount64 + 500;
|
long num2 = Environment.TickCount64 + 500;
|
||||||
if (num > _continueAt)
|
if (num2 > _continueAt)
|
||||||
{
|
{
|
||||||
_continueAt = num;
|
_continueAt = num2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ internal static class Dive
|
||||||
|
|
||||||
internal sealed class Task : ITask
|
internal sealed class Task : ITask
|
||||||
{
|
{
|
||||||
|
public bool SmartNavRouted { get; init; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return "Dive";
|
return "Dive";
|
||||||
|
|
@ -51,6 +53,10 @@ internal static class Dive
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (base.Task.SmartNavRouted && !condition[ConditionFlag.Swimming])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (condition[ConditionFlag.Mounted] || condition[ConditionFlag.Swimming])
|
if (condition[ConditionFlag.Mounted] || condition[ConditionFlag.Swimming])
|
||||||
{
|
{
|
||||||
Descend();
|
Descend();
|
||||||
|
|
|
||||||
|
|
@ -80,14 +80,14 @@ internal static class Duty
|
||||||
|
|
||||||
private EDutyMode GetEffectiveEDutyMode(uint cfcId, EDutyMode? registryDutyMode)
|
private EDutyMode GetEffectiveEDutyMode(uint cfcId, EDutyMode? registryDutyMode)
|
||||||
{
|
{
|
||||||
if (registryDutyMode.HasValue)
|
|
||||||
{
|
|
||||||
return registryDutyMode.Value;
|
|
||||||
}
|
|
||||||
if (configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value))
|
if (configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value))
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
if (registryDutyMode.HasValue)
|
||||||
|
{
|
||||||
|
return registryDutyMode.Value;
|
||||||
|
}
|
||||||
EDutyMode defaultDutyMode = configuration.Duties.DefaultDutyMode;
|
EDutyMode defaultDutyMode = configuration.Duties.DefaultDutyMode;
|
||||||
if (defaultDutyMode == EDutyMode.Support && configuration.Duties.AutoUnsyncOverleveled && IsSafelyOverleveled(cfcId))
|
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()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|
@ -392,7 +392,7 @@ internal static class Duty
|
||||||
{
|
{
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
bossModIpc.EnableAi(BossModIpc.EPreset.Active);
|
bossModIpc.EnableAi(BossModIpc.EPreset.Active, base.Task.ActivateAi);
|
||||||
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
|
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
|
||||||
bool flag;
|
bool flag;
|
||||||
if (localPlayer != null)
|
if (localPlayer != null)
|
||||||
|
|
@ -404,18 +404,18 @@ internal static class Duty
|
||||||
if ((uint)(valueOrDefault - 3) <= 1u)
|
if ((uint)(valueOrDefault - 3) <= 1u)
|
||||||
{
|
{
|
||||||
flag = true;
|
flag = true;
|
||||||
goto IL_0077;
|
goto IL_0082;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flag = false;
|
flag = false;
|
||||||
goto IL_0077;
|
goto IL_0082;
|
||||||
}
|
}
|
||||||
goto IL_0087;
|
goto IL_0092;
|
||||||
IL_0077:
|
IL_0082:
|
||||||
bool isRanged = flag;
|
bool isRanged = flag;
|
||||||
bossModIpc.SetRangeStrategy(BossModIpc.EPreset.Active, isRanged);
|
bossModIpc.SetRangeStrategy(BossModIpc.EPreset.Active, isRanged);
|
||||||
goto IL_0087;
|
goto IL_0092;
|
||||||
IL_0087:
|
IL_0092:
|
||||||
logger.LogDebug("Enabled BossMod Active preset for AutoDuty run");
|
logger.LogDebug("Enabled BossMod Active preset for AutoDuty run");
|
||||||
return true;
|
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()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|
@ -475,7 +475,7 @@ internal static class Duty
|
||||||
{
|
{
|
||||||
protected override bool Start()
|
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)");
|
logger.LogDebug("Enabled BossMod Passive preset for AutoDuty run (hazard avoidance)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ internal static class EquipRecommended
|
||||||
|
|
||||||
private bool _checkedOrTriggeredEquipmentUpdate;
|
private bool _checkedOrTriggeredEquipmentUpdate;
|
||||||
|
|
||||||
|
private List<uint>? _gameRecommendedItemIds;
|
||||||
|
|
||||||
private long _timeoutAt;
|
private long _timeoutAt;
|
||||||
|
|
||||||
private List<(int Slot, BestItemRef Item)>? _smartUpgrades;
|
private List<(int Slot, BestItemRef Item)>? _smartUpgrades;
|
||||||
|
|
@ -177,8 +179,7 @@ internal static class EquipRecommended
|
||||||
{
|
{
|
||||||
if (!TryStartMove(pendingMove))
|
if (!TryStartMove(pendingMove))
|
||||||
{
|
{
|
||||||
chatGui.Print($"Equip move {_smartMoveCursor + 1}/{smartMoves.Count} rejected; skipping equip step.", "Questionable", 576);
|
throw EquipFailure($"Equip move {_smartMoveCursor + 1}/{smartMoves.Count} was rejected");
|
||||||
return ETaskResult.TaskComplete;
|
|
||||||
}
|
}
|
||||||
_currentMoveStarted = true;
|
_currentMoveStarted = true;
|
||||||
_timeoutAt = Environment.TickCount64 + 2000;
|
_timeoutAt = Environment.TickCount64 + 2000;
|
||||||
|
|
@ -196,8 +197,7 @@ internal static class EquipRecommended
|
||||||
}
|
}
|
||||||
if (Environment.TickCount64 >= _timeoutAt)
|
if (Environment.TickCount64 >= _timeoutAt)
|
||||||
{
|
{
|
||||||
chatGui.Print($"Equip move {_smartMoveCursor + 1}/{smartMoves.Count} timed out; skipping equip step.", "Questionable", 576);
|
throw EquipFailure($"Equip move {_smartMoveCursor + 1}/{smartMoves.Count} timed out");
|
||||||
return ETaskResult.TaskComplete;
|
|
||||||
}
|
}
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
@ -227,11 +227,11 @@ internal static class EquipRecommended
|
||||||
{
|
{
|
||||||
return ETaskResult.TaskComplete;
|
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()
|
private ETaskResult DirectEquipPhase()
|
||||||
|
|
@ -247,20 +247,18 @@ internal static class EquipRecommended
|
||||||
{
|
{
|
||||||
return ETaskResult.TaskComplete;
|
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];
|
var (num, item) = smartUpgrades[_directEquipCursor];
|
||||||
if (!_directEquipStarted)
|
if (!_directEquipStarted)
|
||||||
{
|
{
|
||||||
if (!TryStartDirectEquip(item, num))
|
if (!TryStartDirectEquip(item, num))
|
||||||
{
|
{
|
||||||
chatGui.Print($"Direct equip for slot {num} rejected, skipping.", "Questionable", 576);
|
throw EquipFailure($"Direct equip for slot {num} was rejected");
|
||||||
_directEquipCursor++;
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
}
|
}
|
||||||
_directEquipStarted = true;
|
_directEquipStarted = true;
|
||||||
_timeoutAt = Environment.TickCount64 + 2000;
|
_timeoutAt = Environment.TickCount64 + 2000;
|
||||||
|
|
@ -274,8 +272,7 @@ internal static class EquipRecommended
|
||||||
}
|
}
|
||||||
if (Environment.TickCount64 >= _timeoutAt)
|
if (Environment.TickCount64 >= _timeoutAt)
|
||||||
{
|
{
|
||||||
_directEquipCursor++;
|
throw EquipFailure($"Direct equip for slot {num} timed out");
|
||||||
_directEquipStarted = false;
|
|
||||||
}
|
}
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
@ -366,7 +363,8 @@ internal static class EquipRecommended
|
||||||
}
|
}
|
||||||
if (!_checkedOrTriggeredEquipmentUpdate)
|
if (!_checkedOrTriggeredEquipmentUpdate)
|
||||||
{
|
{
|
||||||
if (IsAllRecommendedGearEquipped())
|
_gameRecommendedItemIds = SnapshotRecommendedItemIds(ptr);
|
||||||
|
if (AreAllItemsEquipped(_gameRecommendedItemIds))
|
||||||
{
|
{
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
@ -376,15 +374,15 @@ internal static class EquipRecommended
|
||||||
_checkedOrTriggeredEquipmentUpdate = true;
|
_checkedOrTriggeredEquipmentUpdate = true;
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
if (IsAllRecommendedGearEquipped())
|
if (_gameRecommendedItemIds != null && AreAllItemsEquipped(_gameRecommendedItemIds))
|
||||||
{
|
{
|
||||||
return ETaskResult.TaskComplete;
|
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)
|
private unsafe bool AreUpgradesApplied(IReadOnlyList<(int Slot, BestItemRef Item)> upgrades)
|
||||||
|
|
@ -621,19 +619,70 @@ internal static class EquipRecommended
|
||||||
return (byte)classJobLevel;
|
return (byte)classJobLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe bool IsAllRecommendedGearEquipped()
|
private unsafe static List<uint> SnapshotRecommendedItemIds(RecommendEquipModule* recommendedEquipModule)
|
||||||
{
|
{
|
||||||
Span<Pointer<InventoryItem>> recommendedItems = RecommendEquipModule.Instance()->RecommendedItems;
|
List<uint> list = new List<uint>();
|
||||||
|
Span<Pointer<InventoryItem>> recommendedItems = recommendedEquipModule->RecommendedItems;
|
||||||
for (int i = 0; i < recommendedItems.Length; i++)
|
for (int i = 0; i < recommendedItems.Length; i++)
|
||||||
{
|
{
|
||||||
Pointer<InventoryItem> pointer = recommendedItems[i];
|
Pointer<InventoryItem> pointer = recommendedItems[i];
|
||||||
InventoryItem* value = pointer.Value;
|
InventoryItem* value = pointer.Value;
|
||||||
if (value != null && value->ItemId != 0 && !InventoryHelper.IsItemEquipped(value->ItemId))
|
if (value != null && value->ItemId != 0)
|
||||||
{
|
{
|
||||||
return false;
|
list.Add(((ItemHandle)value->ItemId).Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe static bool AreAllItemsEquipped(IReadOnlyList<uint> expectedItemIds)
|
||||||
|
{
|
||||||
|
InventoryManager* ptr = InventoryManager.Instance();
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(InventoryType.EquippedItems);
|
||||||
|
if (inventoryContainer == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Dictionary<uint, int> dictionary = new Dictionary<uint, int>();
|
||||||
|
foreach (uint expectedItemId in expectedItemIds)
|
||||||
|
{
|
||||||
|
dictionary[expectedItemId] = dictionary.GetValueOrDefault(expectedItemId) + 1;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < inventoryContainer->Size; i++)
|
||||||
|
{
|
||||||
|
if (dictionary.Count <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i);
|
||||||
|
if (inventorySlot == null || inventorySlot->ItemId == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
uint id = ItemHandle.FromInventorySlot(inventorySlot).Id;
|
||||||
|
if (dictionary.TryGetValue(id, out var value))
|
||||||
|
{
|
||||||
|
if (value == 1)
|
||||||
|
{
|
||||||
|
dictionary.Remove(id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dictionary[id] = value - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dictionary.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskException EquipFailure(string message)
|
||||||
|
{
|
||||||
|
chatGui.PrintError(message + "; stopping automation.", "Questionable", 576);
|
||||||
|
return new TaskException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ShouldInterruptOnDamage()
|
public override bool ShouldInterruptOnDamage()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
|
@ -16,12 +17,13 @@ using Questionable.Controller.Utils;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
using SmartNav.Data;
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Interactions;
|
namespace Questionable.Controller.Steps.Interactions;
|
||||||
|
|
||||||
internal static class Interact
|
internal static class Interact
|
||||||
{
|
{
|
||||||
internal sealed class Factory(Configuration configuration) : ITaskFactory
|
internal sealed class Factory(Configuration configuration, WarpDataService warpDataService) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
|
|
@ -63,6 +65,25 @@ internal static class Interact
|
||||||
{
|
{
|
||||||
yield return new WaitAtEnd.WaitDelay();
|
yield return new WaitAtEnd.WaitDelay();
|
||||||
}
|
}
|
||||||
|
ushort? targetTerritoryId = step.TargetTerritoryId;
|
||||||
|
List<uint> list;
|
||||||
|
if (targetTerritoryId.HasValue)
|
||||||
|
{
|
||||||
|
ushort targetTerritoryId2 = targetTerritoryId.GetValueOrDefault();
|
||||||
|
list = (from x in warpDataService.Warps
|
||||||
|
where x.NpcDataId == step.DataId && x.SourceTerritoryId == step.TerritoryId && x.DestTerritoryId == targetTerritoryId2
|
||||||
|
select x.RowId).Distinct().Take(2).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list = new List<uint>();
|
||||||
|
}
|
||||||
|
List<uint> list2 = list;
|
||||||
|
if (list2.Count == 1)
|
||||||
|
{
|
||||||
|
yield return new WarpInteract.Task(step.DataId.Value, list2[0], step.TerritoryId, step.TargetTerritoryId.Value);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
uint value = step.DataId.Value;
|
uint value = step.DataId.Value;
|
||||||
EInteractionType interactionType2 = step.InteractionType;
|
EInteractionType interactionType2 = step.InteractionType;
|
||||||
int skipMarkerCheck;
|
int skipMarkerCheck;
|
||||||
|
|
@ -74,20 +95,17 @@ internal static class Interact
|
||||||
SkipStepConditions stepIf = skipConditions.StepIf;
|
SkipStepConditions stepIf = skipConditions.StepIf;
|
||||||
if (stepIf != null && stepIf.Never)
|
if (stepIf != null && stepIf.Never)
|
||||||
{
|
{
|
||||||
goto IL_024a;
|
goto IL_03d7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (step.InteractionType != EInteractionType.PurchaseItem)
|
if (step.InteractionType != EInteractionType.PurchaseItem)
|
||||||
{
|
{
|
||||||
skipMarkerCheck = ((step.DataId == 1052475) ? 1 : 0);
|
skipMarkerCheck = ((step.DataId == 1052475) ? 1 : 0);
|
||||||
goto IL_024b;
|
goto IL_03d8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
goto IL_024a;
|
goto IL_03d7;
|
||||||
IL_024a:
|
IL_03d8:
|
||||||
skipMarkerCheck = 1;
|
|
||||||
goto IL_024b;
|
|
||||||
IL_024b:
|
|
||||||
uint? pickUpItemId = step.PickUpItemId ?? step.GCPurchase?.ItemId;
|
uint? pickUpItemId = step.PickUpItemId ?? step.GCPurchase?.ItemId;
|
||||||
byte? taxiStandId = step.TaxiStandId;
|
byte? taxiStandId = step.TaxiStandId;
|
||||||
SkipStepConditions? skipConditions2 = step.SkipConditions?.StepIf;
|
SkipStepConditions? skipConditions2 = step.SkipConditions?.StepIf;
|
||||||
|
|
@ -95,6 +113,10 @@ internal static class Interact
|
||||||
uint? purchaseItemId = ((step.InteractionType == EInteractionType.PurchaseItem) ? step.ItemId : ((uint?)null));
|
uint? purchaseItemId = ((step.InteractionType == EInteractionType.PurchaseItem) ? step.ItemId : ((uint?)null));
|
||||||
int purchaseItemCount = ((step.InteractionType == EInteractionType.PurchaseItem) ? step.ItemCount.GetValueOrDefault() : 0);
|
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 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ internal static class SinglePlayerDuty
|
||||||
public const ushort Naadam = 688;
|
public const ushort Naadam = 688;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class Factory(BossModIpc bossModIpc, WrathComboModule wrathComboModule, RotationSolverRebornModule rsrModule, Configuration configuration, TerritoryData territoryData, QuestFunctions questFunctions, DutyRetryState dutyRetryState, ICondition condition, IClientState clientState, IObjectTable objectTable) : ITaskFactory
|
internal sealed class Factory(BossModIpc bossModIpc, WrathComboModule wrathComboModule, RotationSolverRebornModule rsrModule, Configuration configuration, TerritoryData territoryData, QuestFunctions questFunctions, GameFunctions gameFunctions, DutyRetryState dutyRetryState, ICondition condition, IClientState clientState, IObjectTable objectTable) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
|
|
@ -54,7 +54,12 @@ internal static class SinglePlayerDuty
|
||||||
byte seqBefore = questProgressInfo?.Sequence ?? 0;
|
byte seqBefore = questProgressInfo?.Sequence ?? 0;
|
||||||
IReadOnlyList<byte> varsBefore = questProgressInfo?.Variables.ToArray();
|
IReadOnlyList<byte> varsBefore = questProgressInfo?.Variables.ToArray();
|
||||||
dutyRetryState.Reset();
|
dutyRetryState.Reset();
|
||||||
List<ITask> dutyTasks = new List<ITask>();
|
List<ITask> dutyTasks = new List<ITask>
|
||||||
|
{
|
||||||
|
new StartSinglePlayerDuty(contentFinderConditionData.ContentFinderConditionId),
|
||||||
|
new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(10L)),
|
||||||
|
new WaitCondition.Task(() => !gameFunctions.IsOccupied(), "Wait(solo duty ready)")
|
||||||
|
};
|
||||||
if (wrathComboModule.IsAvailable())
|
if (wrathComboModule.IsAvailable())
|
||||||
{
|
{
|
||||||
dutyTasks.Add(new Duty.EnableWrathForDutyTask());
|
dutyTasks.Add(new Duty.EnableWrathForDutyTask());
|
||||||
|
|
@ -65,14 +70,12 @@ internal static class SinglePlayerDuty
|
||||||
}
|
}
|
||||||
if (configuration.General.CombatModule == Configuration.ECombatModule.BossMod && bossModIpc.IsSupported())
|
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())
|
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());
|
dutyTasks.Add(new EnableAi());
|
||||||
if (contentFinderConditionData.TerritoryId == 1052)
|
if (contentFinderConditionData.TerritoryId == 1052)
|
||||||
{
|
{
|
||||||
|
|
@ -132,8 +135,6 @@ internal static class SinglePlayerDuty
|
||||||
|
|
||||||
internal sealed class StartSinglePlayerDutyExecutor(ICondition condition) : TaskExecutor<StartSinglePlayerDuty>()
|
internal sealed class StartSinglePlayerDutyExecutor(ICondition condition) : TaskExecutor<StartSinglePlayerDuty>()
|
||||||
{
|
{
|
||||||
private long _enteredAtMs;
|
|
||||||
|
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -149,14 +150,6 @@ internal static class SinglePlayerDuty
|
||||||
{
|
{
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
if (_enteredAtMs == 0L)
|
|
||||||
{
|
|
||||||
_enteredAtMs = Environment.TickCount64;
|
|
||||||
}
|
|
||||||
if (Environment.TickCount64 - _enteredAtMs < 2000)
|
|
||||||
{
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,10 +130,10 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float num2 = base.Task.StopDistance ?? 3f;
|
float stopDistance = base.Task.StopDistance ?? 3f;
|
||||||
Vector3? vector = _objectTable.LocalPlayer?.Position;
|
Vector3? vector = _objectTable.LocalPlayer?.Position;
|
||||||
float num3 = ((!vector.HasValue) ? float.MaxValue : Vector3.Distance(vector.Value, _destination));
|
float num2 = ((!vector.HasValue) ? float.MaxValue : Vector3.Distance(vector.Value, _destination));
|
||||||
if (num3 > num2)
|
if (!vector.HasValue || !IsWithinInteractionRange(vector.Value, _destination, stopDistance, GetVerticalStopDistance()))
|
||||||
{
|
{
|
||||||
PrepareMovementIfNeeded();
|
PrepareMovementIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +176,7 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
else if (!base.Task.DisableNavmesh)
|
else if (!base.Task.DisableNavmesh)
|
||||||
{
|
{
|
||||||
bool flag = _gameFunctions.IsFlyingUnlocked(base.Task.TerritoryId);
|
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
|
base.Task = base.Task with
|
||||||
{
|
{
|
||||||
|
|
@ -184,8 +184,8 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
Land = true
|
Land = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
float num4 = (base.Task.Fly ? _configuration.Navigation.MountFlyDistance : _configuration.Navigation.MountGroundDistance);
|
float num3 = (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);
|
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);
|
Questionable.Controller.Steps.Common.Mount.MountTask mountTask3 = new Questionable.Controller.Steps.Common.Mount.MountTask(base.Task.TerritoryId, mountIf, _destination);
|
||||||
long retryAtMs = 0L;
|
long retryAtMs = 0L;
|
||||||
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask)? tuple;
|
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask)? tuple;
|
||||||
|
|
@ -244,7 +244,7 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
}
|
}
|
||||||
IPlayerCharacter localPlayer = _objectTable.LocalPlayer;
|
IPlayerCharacter localPlayer = _objectTable.LocalPlayer;
|
||||||
float num = base.Task.StopDistance ?? 3f;
|
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])
|
if (_condition[ConditionFlag.InFlight])
|
||||||
{
|
{
|
||||||
|
|
@ -255,20 +255,25 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
}
|
}
|
||||||
if (!_movementController.IsNavmeshReady)
|
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;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
Vector3 position = localPlayer.Position;
|
Vector3 destination = _destination;
|
||||||
position.Y = localPlayer.Position.Y - 100f;
|
destination.Y = localPlayer.Position.Y;
|
||||||
Vector3 vector = position;
|
Vector3 vector = destination;
|
||||||
|
destination = _destination;
|
||||||
|
destination.Y = _destination.Y - 100f;
|
||||||
|
Vector3 vector2 = destination;
|
||||||
_descendAttempted = true;
|
_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;
|
MovementController movementController = _movementController;
|
||||||
uint? dataId = base.Task.DataId;
|
uint? dataId = base.Task.DataId;
|
||||||
int num2 = 1;
|
int num2 = 2;
|
||||||
List<Vector3> list = new List<Vector3>(num2);
|
List<Vector3> list = new List<Vector3>(num2);
|
||||||
CollectionsMarshal.SetCount(list, num2);
|
CollectionsMarshal.SetCount(list, num2);
|
||||||
CollectionsMarshal.AsSpan(list)[0] = vector;
|
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
|
||||||
|
span[0] = vector;
|
||||||
|
span[1] = vector2;
|
||||||
movementController.NavigateTo(EMovementType.Landing, dataId, list, fly: true, sprint: false, null);
|
movementController.NavigateTo(EMovementType.Landing, dataId, list, fly: true, sprint: false, null);
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
@ -276,22 +281,29 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
_logger.LogDebug("Landed outside stop distance, closing gap on foot");
|
_logger.LogDebug("Landed outside stop distance, closing gap on foot");
|
||||||
MovementController movementController2 = _movementController;
|
MovementController movementController2 = _movementController;
|
||||||
uint? dataId2 = base.Task.DataId;
|
uint? dataId2 = base.Task.DataId;
|
||||||
Vector3 destination = _destination;
|
Vector3 destination2 = _destination;
|
||||||
float? stopDistance = base.Task.StopDistance;
|
float? stopDistance = base.Task.StopDistance;
|
||||||
bool smartNavRouted = base.Task.SmartNavRouted;
|
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;
|
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);
|
IGameObject gameObject = _gameFunctions.FindObjectByDataId(base.Task.DataId.Value, null, warnIfMissing: false);
|
||||||
if (gameObject != null)
|
if (gameObject != null)
|
||||||
{
|
{
|
||||||
float num3 = Vector3.Distance(localPlayer.Position, gameObject.Position);
|
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;
|
_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);
|
_movementController.NavigateTo(EMovementType.Quest, base.Task.DataId, gameObject.Position, fly: false, sprint: false, base.Task.StopDistance);
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
@ -316,13 +328,31 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
}
|
}
|
||||||
if (base.Task.SmartNavRouted && !_adjustedToObject && localPlayer != null && _clientState.TerritoryType == base.Task.TerritoryId && Vector3.Distance(localPlayer.Position, _destination) > num + 5f)
|
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);
|
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", num4, num);
|
_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 {num4:F1}y from target (stop distance {num:F1}y)");
|
throw new MovementController.PathfindingFailedException($"SmartNav movement ended {num5:F1}y from target (stop distance {num:F1}y)");
|
||||||
}
|
}
|
||||||
return ETaskResult.TaskComplete;
|
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()
|
private ETaskResult? UpdateMountState()
|
||||||
{
|
{
|
||||||
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask)? mountBeforeMovement = _mountBeforeMovement;
|
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask)? mountBeforeMovement = _mountBeforeMovement;
|
||||||
|
|
|
||||||
|
|
@ -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
|
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)
|
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()
|
public bool ShouldRedoOnInterrupt()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,14 @@ internal static class MoveTo
|
||||||
yield return new LandTask();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ internal static class SmartNavStep
|
||||||
{
|
{
|
||||||
yield break;
|
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);
|
RouteResult routeResult = navRouter.FindRoute(playerNavState, step.TerritoryId, vector.Value);
|
||||||
if (routeResult.Segments.Count == 0)
|
if (routeResult.Segments.Count == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -37,10 +41,10 @@ internal static class SmartNavStep
|
||||||
}
|
}
|
||||||
HandledRouting = true;
|
HandledRouting = true;
|
||||||
logger.LogDebug("SmartNav routing to {Territory} with {Count} segments", territoryData.GetNameAndId(step.TerritoryId), routeResult.Segments.Count);
|
logger.LogDebug("SmartNav routing to {Territory} with {Count} segments", territoryData.GetNameAndId(step.TerritoryId), routeResult.Segments.Count);
|
||||||
reRouteService.SetDestination(step.TerritoryId, vector.Value);
|
reRouteService.SetDestination(step.TerritoryId, vector.Value, step.TravelMode);
|
||||||
foreach (ITask item in taskMapper.MapInstructions(instructionBuilder.Build(routeResult, step.TerritoryId, step.DisableNavmesh), step))
|
foreach (ITask item3 in taskMapper.MapInstructions(instructionBuilder.Build(routeResult, step.TerritoryId, step.DisableNavmesh), step, step.TravelMode))
|
||||||
{
|
{
|
||||||
yield return item;
|
yield return item3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,10 @@ internal sealed class CombatController : IDisposable
|
||||||
SetTarget(gameObject3);
|
SetTarget(gameObject3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (_currentFight.Module is ItemUseModule { IsItemUseInProgress: not false })
|
||||||
|
{
|
||||||
|
return EStatus.InCombat;
|
||||||
|
}
|
||||||
if (_condition[ConditionFlag.InCombat])
|
if (_condition[ConditionFlag.InCombat])
|
||||||
{
|
{
|
||||||
_wasInCombat = true;
|
_wasInCombat = true;
|
||||||
|
|
@ -362,43 +366,39 @@ internal sealed class CombatController : IDisposable
|
||||||
int num = 0;
|
int num = 0;
|
||||||
float num2 = float.MaxValue;
|
float num2 = float.MaxValue;
|
||||||
bool flag5 = _logger.IsEnabled(LogLevel.Debug);
|
bool flag5 = _logger.IsEnabled(LogLevel.Debug);
|
||||||
foreach (IGameObject item in _objectTable)
|
foreach (IGameObject item2 in _objectTable)
|
||||||
{
|
{
|
||||||
var (num3, text) = GetKillPriority(item, localPlayer);
|
int item = GetKillPriority(item2, localPlayer).Priority;
|
||||||
if (num3 <= 0)
|
if (item <= 0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (_losIgnoreList.TryGetValue(item.GameObjectId, out var value2))
|
if (_losIgnoreList.TryGetValue(item2.GameObjectId, out var value2))
|
||||||
{
|
{
|
||||||
if (Environment.TickCount64 < value2)
|
if (Environment.TickCount64 < value2)
|
||||||
{
|
{
|
||||||
if (flag5)
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
_losIgnoreList.Remove(item.GameObjectId);
|
_losIgnoreList.Remove(item2.GameObjectId);
|
||||||
if (flag5)
|
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);
|
float num3 = Vector3.Distance(item2.Position, value);
|
||||||
if (flag5)
|
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);
|
gameObject = item2;
|
||||||
}
|
num = item;
|
||||||
if (num3 > num || (num3 == num && num4 < num2))
|
num2 = num3;
|
||||||
{
|
|
||||||
gameObject = item;
|
|
||||||
num = num3;
|
|
||||||
num2 = num4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ulong? num5 = gameObject?.GameObjectId;
|
ulong? num4 = gameObject?.GameObjectId;
|
||||||
if (num5 != _lastTargetId)
|
if (num4 != _lastTargetId)
|
||||||
{
|
{
|
||||||
if (gameObject != null)
|
if (gameObject != null)
|
||||||
{
|
{
|
||||||
|
|
@ -408,7 +408,7 @@ internal sealed class CombatController : IDisposable
|
||||||
{
|
{
|
||||||
_logger.LogDebug("No valid target found");
|
_logger.LogDebug("No valid target found");
|
||||||
}
|
}
|
||||||
_lastTargetId = num5;
|
_lastTargetId = num4;
|
||||||
}
|
}
|
||||||
if (gameObject != null && num <= 15 && !_condition[ConditionFlag.InCombat])
|
if (gameObject != null && num <= 15 && !_condition[ConditionFlag.InCombat])
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -416,7 +416,7 @@ internal sealed class MovementController : IDisposable
|
||||||
}
|
}
|
||||||
else if ((vector4 - Destination.Position).Length() < Destination.StopDistance)
|
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();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1538,6 +1538,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (_startedQuest != null && QuestFunctions.IsStartingCityOpeningQuest(_startedQuest.Quest.Id))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
ElementId elementId = (from x in _questFunctions.GetNextPriorityQuestsThatCanBeAccepted()
|
ElementId elementId = (from x in _questFunctions.GetNextPriorityQuestsThatCanBeAccepted()
|
||||||
where x.IsAvailable
|
where x.IsAvailable
|
||||||
select x.QuestId).FirstOrDefault();
|
select x.QuestId).FirstOrDefault();
|
||||||
|
|
|
||||||
|
|
@ -103,15 +103,10 @@ internal sealed class QuestPriorityResolver
|
||||||
QuestResolution valueOrDefault2 = questResolution.GetValueOrDefault();
|
QuestResolution valueOrDefault2 = questResolution.GetValueOrDefault();
|
||||||
return ApplyQuestRedirects(valueOrDefault2);
|
return ApplyQuestRedirects(valueOrDefault2);
|
||||||
}
|
}
|
||||||
uint territoryType = _clientState.TerritoryType;
|
QuestId activeStartingCityOpeningQuest = _questFunctions.GetActiveStartingCityOpeningQuest();
|
||||||
bool flag2 = territoryType - 181 <= 2;
|
if ((object)activeStartingCityOpeningQuest != null && (_questFunctions.IsQuestAccepted(activeStartingCityOpeningQuest) || (flag && _configuration.General.MsqPriority != Configuration.EMsqPriorityMode.Manual)))
|
||||||
if (flag2 && flag && _configuration.General.MsqPriority != Configuration.EMsqPriorityMode.Manual)
|
|
||||||
{
|
{
|
||||||
ElementId currentQuest = resolvedMsqQuest.CurrentQuest;
|
return QuestResolution.ForQuest(EQuestResolutionType.MsqImmediate, activeStartingCityOpeningQuest, _questFunctions.GetStartingCityOpeningSequence(activeStartingCityOpeningQuest), resolvedMsqQuest.State, $"Starting city opening MSQ {activeStartingCityOpeningQuest}");
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
questResolution = TryResolveInProgressClassQuest(valueOrDefault, resolvedMsqQuest.State);
|
questResolution = TryResolveInProgressClassQuest(valueOrDefault, resolvedMsqQuest.State);
|
||||||
if (questResolution.HasValue)
|
if (questResolution.HasValue)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -391,7 +391,7 @@ internal sealed class BossModIpc : IDisposable
|
||||||
AddTransientStrategy(preset, "BossMod.Autorotation.MiscAI.StayCloseToTarget", "Range", isRanged ? "15" : "3");
|
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))
|
if (!CreateShellPreset(preset))
|
||||||
{
|
{
|
||||||
|
|
@ -411,6 +411,10 @@ internal sealed class BossModIpc : IDisposable
|
||||||
_logger.LogWarning("Unable to set BossMod preset {Preset}: {Message}", preset, ipcError.Message);
|
_logger.LogWarning("Unable to set BossMod preset {Preset}: {Message}", preset, ipcError.Message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!activateAi)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
string aiCommand = GetAiCommand();
|
string aiCommand = GetAiCommand();
|
||||||
if (aiCommand != null)
|
if (aiCommand != null)
|
||||||
{
|
{
|
||||||
|
|
@ -424,7 +428,7 @@ internal sealed class BossModIpc : IDisposable
|
||||||
|
|
||||||
public void EnableQuestBattleAi()
|
public void EnableQuestBattleAi()
|
||||||
{
|
{
|
||||||
EnableAi(EPreset.Active);
|
EnableAi(EPreset.Active, _configuration.SinglePlayerDuties.EnableBossModAi);
|
||||||
ConfigureSoloDutyZone();
|
ConfigureSoloDutyZone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,22 @@ namespace Questionable.Functions;
|
||||||
|
|
||||||
internal sealed class QuestFunctions
|
internal sealed class QuestFunctions
|
||||||
{
|
{
|
||||||
|
private static readonly Dictionary<byte, QuestId> ComingToStartingCityQuests = new Dictionary<byte, QuestId>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
1,
|
||||||
|
new QuestId(107)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
2,
|
||||||
|
new QuestId(39)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
3,
|
||||||
|
new QuestId(594)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<ushort, EClassJob> CloseToHomeQuests = new Dictionary<ushort, EClassJob>
|
private static readonly Dictionary<ushort, EClassJob> CloseToHomeQuests = new Dictionary<ushort, EClassJob>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|
@ -95,6 +111,56 @@ internal sealed class QuestFunctions
|
||||||
|
|
||||||
private static readonly HashSet<ushort> RemovedQuestIds = new HashSet<ushort> { 487, 1428, 1429 };
|
private static readonly HashSet<ushort> RemovedQuestIds = new HashSet<ushort> { 487, 1428, 1429 };
|
||||||
|
|
||||||
|
public static bool IsStartingCityOpeningQuest(ElementId elementId)
|
||||||
|
{
|
||||||
|
if (elementId is QuestId questId)
|
||||||
|
{
|
||||||
|
if (!ComingToStartingCityQuests.ContainsValue(questId))
|
||||||
|
{
|
||||||
|
return CloseToHomeQuests.ContainsKey(questId.Value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuestId? GetActiveStartingCityOpeningQuest()
|
||||||
|
{
|
||||||
|
QuestId questId = ComingToStartingCityQuests.Values.Concat(CloseToHomeQuests.Keys.Select((ushort x) => new QuestId(x))).Where(IsQuestAccepted).Cast<QuestId>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
if (questId != null)
|
||||||
|
{
|
||||||
|
return questId;
|
||||||
|
}
|
||||||
|
EClassJob startingClass = ((EClassJob?)_objectTable.LocalPlayer?.ClassJob.RowId).GetValueOrDefault();
|
||||||
|
ushort num = (from x in CloseToHomeQuests
|
||||||
|
where x.Value == startingClass
|
||||||
|
select x.Key).FirstOrDefault();
|
||||||
|
if (num != 0)
|
||||||
|
{
|
||||||
|
QuestId questId2 = new QuestId(num);
|
||||||
|
if (IsReadyToAcceptQuest(questId2, ignoreLevel: true))
|
||||||
|
{
|
||||||
|
return questId2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ComingToStartingCityQuests.TryGetValue(PlayerStateHelper.GetStartTown(), out QuestId value) && IsReadyToAcceptQuest(value, ignoreLevel: true))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte GetStartingCityOpeningSequence(QuestId questId)
|
||||||
|
{
|
||||||
|
byte questSequence = QuestManager.GetQuestSequence(questId.Value);
|
||||||
|
if (ComingToStartingCityQuests.ContainsValue(questId) && IsQuestAccepted(questId) && questSequence == 0)
|
||||||
|
{
|
||||||
|
return byte.MaxValue;
|
||||||
|
}
|
||||||
|
return questSequence;
|
||||||
|
}
|
||||||
|
|
||||||
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, JournalData journalData, AetheryteFunctions aetheryteFunctions, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, AlliedSocietyData alliedSocietyData, Configuration configuration, IDataManager dataManager, IObjectTable objectTable, IGameGui gameGui, ILogger<QuestFunctions> logger)
|
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, JournalData journalData, AetheryteFunctions aetheryteFunctions, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, AlliedSocietyData alliedSocietyData, Configuration configuration, IDataManager dataManager, IObjectTable objectTable, IGameGui gameGui, ILogger<QuestFunctions> logger)
|
||||||
{
|
{
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,28 @@ using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.CustomDelivery;
|
using Questionable.Controller.CustomDelivery;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
|
using Questionable.Controller.Steps.Interactions;
|
||||||
using Questionable.Controller.Steps.Movement;
|
using Questionable.Controller.Steps.Movement;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
using SmartNav;
|
using SmartNav;
|
||||||
|
|
||||||
namespace Questionable.Navigation;
|
namespace Questionable.Navigation;
|
||||||
|
|
||||||
internal sealed class SmartNavReRouteService(ReRoutePolicy reRoutePolicy, SmartNavTaskMapper taskMapper, Configuration configuration, ILogger<SmartNavReRouteService> logger)
|
internal sealed class SmartNavReRouteService(ReRoutePolicy reRoutePolicy, SmartNavTaskMapper taskMapper, Configuration configuration, ILogger<SmartNavReRouteService> logger)
|
||||||
{
|
{
|
||||||
public void SetDestination(uint territoryId, Vector3 position)
|
private ETravelMode _travelMode;
|
||||||
|
|
||||||
|
public void SetDestination(uint territoryId, Vector3 position, ETravelMode travelMode = ETravelMode.Auto)
|
||||||
{
|
{
|
||||||
reRoutePolicy.SetDestination(territoryId, position);
|
_travelMode = travelMode;
|
||||||
|
var (allowMount, allowFlight) = GetCapabilities(travelMode);
|
||||||
|
reRoutePolicy.SetDestination(territoryId, position, allowMount, allowFlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearDestination()
|
public void ClearDestination()
|
||||||
{
|
{
|
||||||
|
_travelMode = ETravelMode.Auto;
|
||||||
reRoutePolicy.ClearDestination();
|
reRoutePolicy.ClearDestination();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +58,7 @@ internal sealed class SmartNavReRouteService(ReRoutePolicy reRoutePolicy, SmartN
|
||||||
if (reRouteDecision is ReRouteDecision.Replan replan)
|
if (reRouteDecision is ReRouteDecision.Replan replan)
|
||||||
{
|
{
|
||||||
taskQueue.Reset();
|
taskQueue.Reset();
|
||||||
foreach (ITask item2 in taskMapper.MapInstructions(replan.Instructions))
|
foreach (ITask item2 in taskMapper.MapInstructions(replan.Instructions, null, _travelMode))
|
||||||
{
|
{
|
||||||
taskQueue.Enqueue(item2);
|
taskQueue.Enqueue(item2);
|
||||||
}
|
}
|
||||||
|
|
@ -79,10 +86,33 @@ internal sealed class SmartNavReRouteService(ReRoutePolicy reRoutePolicy, SmartN
|
||||||
|
|
||||||
private static bool IsMovementTask(ITask task)
|
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),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,27 @@ internal sealed class SmartNavRouteEnqueuer(NavRouter navRouter, PlayerNavStateB
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
List<NavInstruction> list = instructionBuilder.Build(routeResult, territoryId).TakeWhile((NavInstruction x) => !(x is NavInstruction.Move move) || !move.IsFinal).ToList();
|
List<NavInstruction> list = instructionBuilder.Build(routeResult, territoryId).TakeWhile(delegate(NavInstruction x)
|
||||||
|
{
|
||||||
|
if (x is NavInstruction.Move move)
|
||||||
|
{
|
||||||
|
if (move.IsFinal)
|
||||||
|
{
|
||||||
|
goto IL_0026;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (x is NavInstruction.Swim { IsFinal: not false })
|
||||||
|
{
|
||||||
|
goto IL_0026;
|
||||||
|
}
|
||||||
|
bool flag = false;
|
||||||
|
goto IL_002c;
|
||||||
|
IL_0026:
|
||||||
|
flag = true;
|
||||||
|
goto IL_002c;
|
||||||
|
IL_002c:
|
||||||
|
return !flag;
|
||||||
|
}).ToList();
|
||||||
if (list.Count == 0)
|
if (list.Count == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -120,6 +140,18 @@ internal sealed class SmartNavRouteEnqueuer(NavRouter navRouter, PlayerNavStateB
|
||||||
{
|
{
|
||||||
return false;
|
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)
|
if (instruction is NavInstruction.AethernetHop)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Numerics;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
|
using Questionable.Controller.Steps.Interactions;
|
||||||
using Questionable.Controller.Steps.Movement;
|
using Questionable.Controller.Steps.Movement;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
|
@ -15,13 +16,22 @@ namespace Questionable.Navigation;
|
||||||
|
|
||||||
internal sealed class SmartNavTaskMapper(IClientState clientState, TerritoryData territoryData)
|
internal sealed class SmartNavTaskMapper(IClientState clientState, TerritoryData territoryData)
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> MapInstructions(IReadOnlyList<NavInstruction> instructions, QuestStep? step = null)
|
private const float SurfaceExitRise = 3f;
|
||||||
|
|
||||||
|
public IEnumerable<ITask> MapInstructions(IReadOnlyList<NavInstruction> instructions, QuestStep? step = null, ETravelMode travelMode = ETravelMode.Auto)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < instructions.Count; i++)
|
for (int i = 0; i < instructions.Count; i++)
|
||||||
{
|
{
|
||||||
foreach (ITask item in Map(instructions[i], NextTravelInstruction(instructions, i), step))
|
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.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 (!(instruction is NavInstruction.Surface surface))
|
||||||
if ((object)waitForTerritory == null)
|
|
||||||
{
|
{
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
yield return new TicketTeleport.Task(useTeleportTicket.ItemId, useTeleportTicket.Territory);
|
yield return new WaitCondition.Task(() => waitForTerritory.Acceptable.Contains(clientState.TerritoryType), waitForTerritory.Description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
yield return new WarpInteract.Task(interactWarp.NpcDataId, interactWarp.WarpRowId, interactWarp.FromTerritory, interactWarp.ToTerritory);
|
yield return new Mount.UnmountTask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
yield return new WaitNavmesh.Task();
|
yield return new LandTask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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
|
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
|
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)
|
else if (move.IsFinal && step != null)
|
||||||
{
|
{
|
||||||
Vector3 destination = step.Position ?? move.Position;
|
Vector3 destination4 = step.Position ?? move.Position;
|
||||||
yield return new MoveTask(step, destination)with
|
MoveTask moveTask = new MoveTask(step, destination4)with
|
||||||
{
|
{
|
||||||
Fly = (move.Fly || step.Fly == true),
|
Fly = (move.Fly || step.Fly == true)
|
||||||
Land = (move.Fly || step.Land == true),
|
|
||||||
SmartNavRouted = true,
|
|
||||||
RouteTargetNodeId = move.TargetNodeId,
|
|
||||||
RouteApproachNodeId = move.ApproachNodeId
|
|
||||||
};
|
};
|
||||||
|
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)
|
if (step != null)
|
||||||
{
|
{
|
||||||
bool? fly = step.Fly;
|
bool? fly = step.Fly;
|
||||||
|
|
@ -116,22 +191,30 @@ internal sealed class SmartNavTaskMapper(IClientState clientState, TerritoryData
|
||||||
yield return new LandTask();
|
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
|
else
|
||||||
{
|
{
|
||||||
NavInstruction.Move move2 = move;
|
NavInstruction.Move move2 = move;
|
||||||
ushort territory = move2.Territory;
|
ushort territory4 = move2.Territory;
|
||||||
Vector3 position = move2.Position;
|
Vector3 position3 = move2.Position;
|
||||||
bool? mount = move2.Mount;
|
bool? mount2 = move2.Mount;
|
||||||
float? stopDistance = move2.StopDistance;
|
float? stopDistance2 = move2.StopDistance;
|
||||||
uint? dataId = move2.DataId;
|
uint? dataId = move2.DataId;
|
||||||
bool disableNavmesh = move2.DisableNavmesh;
|
bool disableNavmesh = move2.DisableNavmesh;
|
||||||
bool fly2 = move2.Fly;
|
bool flag = move2.Fly;
|
||||||
bool landAtTarget = move2.LandAtTarget;
|
bool flag2 = move2.LandAtTarget;
|
||||||
bool allowZoneTransition = move2.AllowZoneTransition;
|
bool allowZoneTransition = move2.AllowZoneTransition;
|
||||||
string targetNodeId = move2.TargetNodeId;
|
string approachNodeId = move2.TargetNodeId;
|
||||||
string approachNodeId = move2.ApproachNodeId;
|
string targetNodeId = 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);
|
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
|
else
|
||||||
|
|
@ -147,4 +230,50 @@ internal sealed class SmartNavTaskMapper(IClientState clientState, TerritoryData
|
||||||
yield return new AetheryteTeleport.MoveAwayFromAetheryte(teleport.Target);
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,13 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||||
}
|
}
|
||||||
using (ImRaii.Disabled(!value))
|
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();
|
ImGui.Spacing();
|
||||||
int currentItem = (int)base.Configuration.SinglePlayerDuties.RetryDifficulty;
|
int currentItem = (int)base.Configuration.SinglePlayerDuties.RetryDifficulty;
|
||||||
ImGui.SetNextItemWidth(MathF.Min(ImGui.GetContentRegionAvail().X * 0.4f, 260f));
|
ImGui.SetNextItemWidth(MathF.Min(ImGui.GetContentRegionAvail().X * 0.4f, 260f));
|
||||||
|
|
|
||||||
|
|
@ -338,30 +338,27 @@ internal sealed class QuestSequenceComponent
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Lumina.Excel.Sheets.Quest? quest = _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>()?.GetRowOrDefault((uint)(questId.Value + 65536));
|
if (!(_dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>()?.GetRowOrDefault((uint)(questId.Value + 65536))).HasValue)
|
||||||
if (!quest.HasValue)
|
|
||||||
{
|
{
|
||||||
DrawErrorState("Quest Not Found in Game Data", "Unable to load quest information from Lumina.");
|
DrawErrorState("Quest Not Found in Game Data", "Unable to load quest information from Lumina.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<int> list = (from result in quest.Value.TodoParams.Where((Lumina.Excel.Sheets.Quest.TodoParamsStruct todoParamsStruct) => todoParamsStruct.ToDoCompleteSeq > 0 && todoParamsStruct.ToDoCompleteSeq < byte.MaxValue).Select((Func<Lumina.Excel.Sheets.Quest.TodoParamsStruct, int>)((Lumina.Excel.Sheets.Quest.TodoParamsStruct todoParamsStruct) => todoParamsStruct.ToDoCompleteSeq)).Distinct()
|
List<int> completionSequences = GameQuestSequences.GetCompletionSequences(_dataManager, questId);
|
||||||
orderby result
|
if (completionSequences.Count == 0)
|
||||||
select result).ToList();
|
|
||||||
if (list.Count == 0)
|
|
||||||
{
|
{
|
||||||
DrawErrorState("No Sequence Data Available", "This quest may not have traditional sequences.");
|
DrawErrorState("No Sequence Data Available", "This quest may not have traditional sequences.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int num8 = list.Max();
|
int num8 = completionSequences.Where((int num13) => num13 != 255).DefaultIfEmpty(0).Max();
|
||||||
int num9 = num8 + 1;
|
int num9 = num8 + 1;
|
||||||
Questionable.Model.Quest quest2;
|
Questionable.Model.Quest quest;
|
||||||
bool flag = _questRegistry.TryGetQuest(questElementId, out quest2);
|
bool flag = _questRegistry.TryGetQuest(questElementId, out quest);
|
||||||
HashSet<int> hashSet = new HashSet<int>();
|
HashSet<int> hashSet = new HashSet<int>();
|
||||||
Dictionary<int, (int, string)> dictionary = new Dictionary<int, (int, string)>();
|
Dictionary<int, (int, string)> dictionary = new Dictionary<int, (int, string)>();
|
||||||
bool flag2 = false;
|
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)
|
if (item.Sequence == byte.MaxValue)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
|
||||||
using Dalamud.Configuration;
|
using Dalamud.Configuration;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
|
@ -14,7 +13,6 @@ using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable;
|
namespace Questionable;
|
||||||
|
|
||||||
[Obfuscation(Feature = "-rename", Exclude = false, ApplyToMembers = true)]
|
|
||||||
internal sealed class Configuration : IPluginConfiguration
|
internal sealed class Configuration : IPluginConfiguration
|
||||||
{
|
{
|
||||||
internal sealed class GeneralConfiguration
|
internal sealed class GeneralConfiguration
|
||||||
|
|
@ -160,6 +158,8 @@ internal sealed class Configuration : IPluginConfiguration
|
||||||
{
|
{
|
||||||
public bool RunSoloInstancesWithBossMod { get; set; }
|
public bool RunSoloInstancesWithBossMod { get; set; }
|
||||||
|
|
||||||
|
public bool EnableBossModAi { get; set; } = true;
|
||||||
|
|
||||||
public ERetryDifficulty RetryDifficulty { get; set; }
|
public ERetryDifficulty RetryDifficulty { get; set; }
|
||||||
|
|
||||||
public int MaxRetries { get; set; }
|
public int MaxRetries { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -53,7 +52,6 @@ using SmartNav.Data;
|
||||||
|
|
||||||
namespace Questionable;
|
namespace Questionable;
|
||||||
|
|
||||||
[Obfuscation(Feature = "-ctrl flow", Exclude = false, ApplyToMembers = true)]
|
|
||||||
public sealed class QuestionablePlugin : IAsyncDalamudPlugin, IAsyncDisposable
|
public sealed class QuestionablePlugin : IAsyncDalamudPlugin, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private ServiceProvider? _serviceProvider;
|
private ServiceProvider? _serviceProvider;
|
||||||
|
|
@ -326,6 +324,8 @@ public sealed class QuestionablePlugin : IAsyncDalamudPlugin, IAsyncDisposable
|
||||||
serviceCollection.AddSingleton<TeleportTicketService>();
|
serviceCollection.AddSingleton<TeleportTicketService>();
|
||||||
serviceCollection.AddSingleton((IServiceProvider sp) => new LgbZoneBoundarySource(sp.GetRequiredService<ILogger<LgbZoneBoundarySource>>(), sp.GetRequiredService<SmartNavDataOptions>()));
|
serviceCollection.AddSingleton((IServiceProvider sp) => new LgbZoneBoundarySource(sp.GetRequiredService<ILogger<LgbZoneBoundarySource>>(), sp.GetRequiredService<SmartNavDataOptions>()));
|
||||||
((IServiceCollection)serviceCollection).AddSingleton((Func<IServiceProvider, IZoneBoundarySource>)((IServiceProvider sp) => sp.GetRequiredService<LgbZoneBoundarySource>()));
|
((IServiceCollection)serviceCollection).AddSingleton((Func<IServiceProvider, IZoneBoundarySource>)((IServiceProvider sp) => sp.GetRequiredService<LgbZoneBoundarySource>()));
|
||||||
|
serviceCollection.AddSingleton<WaterMapService>();
|
||||||
|
serviceCollection.AddSingleton<WaterRoutePlanner>();
|
||||||
((IServiceCollection)serviceCollection).AddSingleton((Func<IServiceProvider, ITerritoryInfo>)((IServiceProvider sp) => sp.GetRequiredService<TerritoryData>()));
|
((IServiceCollection)serviceCollection).AddSingleton((Func<IServiceProvider, ITerritoryInfo>)((IServiceProvider sp) => sp.GetRequiredService<TerritoryData>()));
|
||||||
serviceCollection.AddSingleton<IPlayerContext, QuestionablePlayerContext>();
|
serviceCollection.AddSingleton<IPlayerContext, QuestionablePlayerContext>();
|
||||||
serviceCollection.AddSingleton<CostCalculator>();
|
serviceCollection.AddSingleton<CostCalculator>();
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -4,18 +4,18 @@ internal static class PathBundleSecret
|
||||||
{
|
{
|
||||||
private static readonly byte[] A = new byte[32]
|
private static readonly byte[] A = new byte[32]
|
||||||
{
|
{
|
||||||
192, 237, 68, 154, 123, 161, 164, 81, 179, 101,
|
14, 238, 168, 186, 100, 48, 138, 129, 114, 53,
|
||||||
208, 145, 185, 118, 184, 66, 42, 173, 202, 110,
|
75, 217, 99, 224, 198, 248, 103, 129, 59, 33,
|
||||||
201, 113, 73, 242, 250, 58, 135, 189, 143, 92,
|
149, 44, 226, 149, 187, 253, 141, 40, 151, 199,
|
||||||
151, 77
|
127, 185
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly byte[] B = new byte[32]
|
private static readonly byte[] B = new byte[32]
|
||||||
{
|
{
|
||||||
239, 73, 204, 3, 150, 201, 87, 43, 137, 223,
|
87, 34, 127, 49, 238, 14, 221, 143, 224, 157,
|
||||||
14, 253, 125, 35, 109, 186, 32, 12, 130, 132,
|
207, 147, 247, 122, 30, 185, 79, 187, 117, 57,
|
||||||
201, 118, 255, 168, 253, 104, 107, 98, 213, 28,
|
242, 252, 100, 251, 176, 253, 30, 79, 135, 124,
|
||||||
83, 69
|
73, 92
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static byte[] Key()
|
internal static byte[] Key()
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -20,6 +20,7 @@
|
||||||
<None Remove="SmartNav.Data.derived-arrivals.bin" />
|
<None Remove="SmartNav.Data.derived-arrivals.bin" />
|
||||||
<None Remove="SmartNav.Data.npc-position-cache.bin" />
|
<None Remove="SmartNav.Data.npc-position-cache.bin" />
|
||||||
<None Remove="SmartNav.Data.zone-boundary-cache.bin" />
|
<None Remove="SmartNav.Data.zone-boundary-cache.bin" />
|
||||||
|
<None Remove="SmartNav.Data.water-cache.bin" />
|
||||||
<EmbeddedResource Include="SmartNav.Data.warp-destinations.bin" LogicalName="SmartNav.Data.warp-destinations.bin" />
|
<EmbeddedResource Include="SmartNav.Data.warp-destinations.bin" LogicalName="SmartNav.Data.warp-destinations.bin" />
|
||||||
<EmbeddedResource Include="SmartNav.Data.teleport-tickets.bin" LogicalName="SmartNav.Data.teleport-tickets.bin" />
|
<EmbeddedResource Include="SmartNav.Data.teleport-tickets.bin" LogicalName="SmartNav.Data.teleport-tickets.bin" />
|
||||||
<EmbeddedResource Include="SmartNav.Data.chocobo-taxi-stands.bin" LogicalName="SmartNav.Data.chocobo-taxi-stands.bin" />
|
<EmbeddedResource Include="SmartNav.Data.chocobo-taxi-stands.bin" LogicalName="SmartNav.Data.chocobo-taxi-stands.bin" />
|
||||||
|
|
@ -27,6 +28,7 @@
|
||||||
<EmbeddedResource Include="SmartNav.Data.derived-arrivals.bin" LogicalName="SmartNav.Data.derived-arrivals.bin" />
|
<EmbeddedResource Include="SmartNav.Data.derived-arrivals.bin" LogicalName="SmartNav.Data.derived-arrivals.bin" />
|
||||||
<EmbeddedResource Include="SmartNav.Data.npc-position-cache.bin" LogicalName="SmartNav.Data.npc-position-cache.bin" />
|
<EmbeddedResource Include="SmartNav.Data.npc-position-cache.bin" LogicalName="SmartNav.Data.npc-position-cache.bin" />
|
||||||
<EmbeddedResource Include="SmartNav.Data.zone-boundary-cache.bin" LogicalName="SmartNav.Data.zone-boundary-cache.bin" />
|
<EmbeddedResource Include="SmartNav.Data.zone-boundary-cache.bin" LogicalName="SmartNav.Data.zone-boundary-cache.bin" />
|
||||||
|
<EmbeddedResource Include="SmartNav.Data.water-cache.bin" LogicalName="SmartNav.Data.water-cache.bin" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="SmartNav.Model">
|
<Reference Include="SmartNav.Model">
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
SmartNav.Data/SmartNav.Data.water-cache.bin
Normal file
BIN
SmartNav.Data/SmartNav.Data.water-cache.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
29
SmartNav.Data/SmartNav.Data/AssemblyWaterMapLoader.cs
Normal file
29
SmartNav.Data/SmartNav.Data/AssemblyWaterMapLoader.cs
Normal file
|
|
@ -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<ushort, TerritoryWaterMap> Maps) GetWaterMaps(string? overrideDirectory = null)
|
||||||
|
{
|
||||||
|
using Stream stream = SmartNavResources.TryOpen("water-cache.bin", overrideDirectory);
|
||||||
|
if (stream == null)
|
||||||
|
{
|
||||||
|
return (GameVersion: null, Maps: new Dictionary<ushort, TerritoryWaterMap>());
|
||||||
|
}
|
||||||
|
(string GameVersion, List<TerritoryWaterMap> Maps) tuple = WaterCacheFormat.Read(stream);
|
||||||
|
string item = tuple.GameVersion;
|
||||||
|
List<TerritoryWaterMap> item2 = tuple.Maps;
|
||||||
|
Dictionary<ushort, TerritoryWaterMap> dictionary = new Dictionary<ushort, TerritoryWaterMap>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,18 +4,18 @@ internal static class DataBundleSecret
|
||||||
{
|
{
|
||||||
private static readonly byte[] A = new byte[32]
|
private static readonly byte[] A = new byte[32]
|
||||||
{
|
{
|
||||||
13, 38, 21, 255, 71, 40, 19, 213, 79, 201,
|
71, 215, 20, 29, 12, 252, 253, 194, 59, 69,
|
||||||
20, 24, 146, 227, 159, 156, 142, 23, 138, 144,
|
18, 55, 34, 247, 239, 216, 228, 201, 157, 67,
|
||||||
119, 132, 179, 149, 190, 134, 23, 209, 204, 192,
|
207, 24, 144, 24, 62, 38, 55, 197, 249, 132,
|
||||||
234, 63
|
62, 40
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly byte[] B = new byte[32]
|
private static readonly byte[] B = new byte[32]
|
||||||
{
|
{
|
||||||
252, 191, 81, 239, 79, 239, 196, 247, 66, 179,
|
63, 81, 210, 107, 86, 21, 96, 76, 152, 72,
|
||||||
73, 171, 175, 76, 172, 241, 89, 23, 110, 4,
|
178, 235, 191, 217, 62, 127, 34, 130, 14, 224,
|
||||||
105, 193, 2, 137, 162, 77, 122, 136, 209, 181,
|
171, 239, 111, 52, 2, 7, 1, 25, 82, 57,
|
||||||
229, 197
|
94, 195
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static byte[] Key()
|
internal static byte[] Key()
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,24 @@ internal static class SmartNavResources
|
||||||
return JsonSerializer.Deserialize<T>(readOnlySpan);
|
return JsonSerializer.Deserialize<T>(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)
|
public static Stream OpenSealed(string fileName)
|
||||||
{
|
{
|
||||||
return new MemoryStream(Unseal("SmartNav.Data." + fileName) ?? throw new InvalidOperationException("embedded resource '" + fileName + "' is missing"), writable: false);
|
return new MemoryStream(Unseal("SmartNav.Data." + fileName) ?? throw new InvalidOperationException("embedded resource '" + fileName + "' is missing"), writable: false);
|
||||||
|
|
|
||||||
188
SmartNav.Model/SmartNav.Model.Navigation/TerritoryWaterMap.cs
Normal file
188
SmartNav.Model/SmartNav.Model.Navigation/TerritoryWaterMap.cs
Normal file
|
|
@ -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<byte> Classes => _classes;
|
||||||
|
|
||||||
|
public ReadOnlySpan<ushort> SurfaceYRaw => _surfaceY;
|
||||||
|
|
||||||
|
public bool HasWater => ((ReadOnlySpan<byte>)_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
122
SmartNav.Model/SmartNav.Model.Navigation/WaterCacheFormat.cs
Normal file
122
SmartNav.Model/SmartNav.Model.Navigation/WaterCacheFormat.cs
Normal file
|
|
@ -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<TerritoryWaterMap> maps)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(stream, "stream");
|
||||||
|
ArgumentNullException.ThrowIfNull(gameVersion, "gameVersion");
|
||||||
|
ArgumentNullException.ThrowIfNull(maps, "maps");
|
||||||
|
List<TerritoryWaterMap> list = new List<TerritoryWaterMap>(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<ushort> 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<TerritoryWaterMap> 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<TerritoryWaterMap> list = new List<TerritoryWaterMap>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace SmartNav.Model.Navigation;
|
||||||
|
|
||||||
|
public enum WaterCellClass : byte
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
SwimOnly,
|
||||||
|
Divable
|
||||||
|
}
|
||||||
|
|
@ -56,9 +56,25 @@ public static class MovementData
|
||||||
|
|
||||||
public const float DefaultMountGroundDistance = 20f;
|
public const float DefaultMountGroundDistance = 20f;
|
||||||
|
|
||||||
|
public const float MinMountTimeSavedSeconds = 2f;
|
||||||
|
|
||||||
public const float DefaultMountFlyDistance = 15f;
|
public const float DefaultMountFlyDistance = 15f;
|
||||||
|
|
||||||
public const float DefaultStopDistance = 3f;
|
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
|
public static class Timings
|
||||||
|
|
@ -74,5 +90,9 @@ public static class MovementData
|
||||||
public const float SprintDurationInCombat = 10f;
|
public const float SprintDurationInCombat = 10f;
|
||||||
|
|
||||||
public const float SprintCooldown = 60f;
|
public const float SprintCooldown = 60f;
|
||||||
|
|
||||||
|
public const float DiveEntryDuration = 4f;
|
||||||
|
|
||||||
|
public const float SurfaceExitDuration = 1f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
SmartNav/SmartNav.Data/WaterMapService.cs
Normal file
46
SmartNav/SmartNav.Data/WaterMapService.cs
Normal file
|
|
@ -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<IReadOnlyDictionary<ushort, TerritoryWaterMap>> _maps;
|
||||||
|
|
||||||
|
public WaterMapService(SmartNavDataOptions dataOptions, ILogger<WaterMapService> logger)
|
||||||
|
{
|
||||||
|
_maps = new Lazy<IReadOnlyDictionary<ushort, TerritoryWaterMap>>(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 ?? "<none>");
|
||||||
|
return readOnlyDictionary;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
logger.LogError(exception, "Water cache could not be loaded - routing proceeds without water data");
|
||||||
|
return new Dictionary<ushort, TerritoryWaterMap>();
|
||||||
|
}
|
||||||
|
}, LazyThreadSafetyMode.ExecutionAndPublication);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WaterMapService(IReadOnlyDictionary<ushort, TerritoryWaterMap> maps)
|
||||||
|
{
|
||||||
|
_maps = new Lazy<IReadOnlyDictionary<ushort, TerritoryWaterMap>>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,9 +4,53 @@ using SmartNav.Model;
|
||||||
|
|
||||||
namespace SmartNav;
|
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)
|
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))
|
if (!territoryInfo.CanUseMount(territoryId))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
private void AddWithinZoneEdgesFrom(NavGraph queryGraph, NavNode origin, PlayerNavState playerState)
|
||||||
{
|
{
|
||||||
foreach (string item in queryGraph.GetNodesInTerritory(origin.TerritoryId))
|
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);
|
NavNode node = queryGraph.GetNode(item);
|
||||||
if (!(node == null) && AreNodesConnectedWithinZone(origin, node, origin.TerritoryId))
|
if (!(node == null) && AreNodesConnectedWithinZone(origin, node, origin.TerritoryId))
|
||||||
{
|
{
|
||||||
float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(origin.Position, node.Position, origin.TerritoryId, playerState);
|
AddWithinZoneEdge(queryGraph, origin, node, origin.TerritoryId, playerState);
|
||||||
queryGraph.AddEdge(new NavEdge(origin.Id, item, NavEdgeType.WithinZone, timeCostSeconds, 0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,8 +258,7 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
||||||
NavNode node = queryGraph.GetNode(item);
|
NavNode node = queryGraph.GetNode(item);
|
||||||
if (!(node == null) && AreNodesConnectedWithinZone(node, destination, destination.TerritoryId))
|
if (!(node == null) && AreNodesConnectedWithinZone(node, destination, destination.TerritoryId))
|
||||||
{
|
{
|
||||||
float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, destination.Position, destination.TerritoryId, playerState);
|
AddWithinZoneEdge(queryGraph, node, destination, destination.TerritoryId, playerState);
|
||||||
queryGraph.AddEdge(new NavEdge(item, destination.Id, NavEdgeType.WithinZone, timeCostSeconds, 0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -348,10 +355,8 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
||||||
NavNode node = queryGraph.GetNode(item);
|
NavNode node = queryGraph.GetNode(item);
|
||||||
if (!(node == null) && (!(node is NavNode.ZoneBoundaryNode) || string.CompareOrdinal(zoneBoundaryNode.Id, item) < 0) && AreNodesConnectedWithinZone(zoneBoundaryNode, node, zoneBoundaryNode.TerritoryId))
|
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);
|
AddWithinZoneEdge(queryGraph, node, zoneBoundaryNode, zoneBoundaryNode.TerritoryId, playerState);
|
||||||
float timeCostSeconds2 = costCalculator.EstimateWithinZoneTravelTime(zoneBoundaryNode.Position, node.Position, zoneBoundaryNode.TerritoryId, playerState);
|
AddWithinZoneEdge(queryGraph, zoneBoundaryNode, node, 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -373,10 +378,8 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
||||||
NavNode node = queryGraph.GetNode(item);
|
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))
|
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);
|
AddWithinZoneEdge(queryGraph, node, warpNode, warpNode.TerritoryId, playerState);
|
||||||
float timeCostSeconds2 = costCalculator.EstimateWithinZoneTravelTime(warpNode.Position, node.Position, warpNode.TerritoryId, playerState);
|
AddWithinZoneEdge(queryGraph, warpNode, node, 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -403,10 +406,8 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
||||||
bool flag = ((node is NavNode.ZoneBoundaryNode || node is NavNode.WarpNode) ? true : false);
|
bool flag = ((node is NavNode.ZoneBoundaryNode || node is NavNode.WarpNode) ? true : false);
|
||||||
if (!flag && AreNodesConnectedWithinZone(subRegionConnectorNode, node, subRegionConnectorNode.TerritoryId))
|
if (!flag && AreNodesConnectedWithinZone(subRegionConnectorNode, node, subRegionConnectorNode.TerritoryId))
|
||||||
{
|
{
|
||||||
float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, subRegionConnectorNode.Position, subRegionConnectorNode.TerritoryId, playerState);
|
AddWithinZoneEdge(queryGraph, node, subRegionConnectorNode, subRegionConnectorNode.TerritoryId, playerState);
|
||||||
float timeCostSeconds2 = costCalculator.EstimateWithinZoneTravelTime(subRegionConnectorNode.Position, node.Position, subRegionConnectorNode.TerritoryId, playerState);
|
AddWithinZoneEdge(queryGraph, subRegionConnectorNode, node, 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 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 Land : NavInstruction;
|
||||||
|
|
||||||
public sealed record Unmount : NavInstruction;
|
public sealed record Unmount : NavInstruction;
|
||||||
|
|
|
||||||
|
|
@ -93,4 +93,42 @@ public sealed class PlayerNavState
|
||||||
{
|
{
|
||||||
return QuestSequenceChecker(questRowId);
|
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<uint> flyingUnlockedTerritories;
|
||||||
|
if (!(allowMount && allowFlight))
|
||||||
|
{
|
||||||
|
IReadOnlySet<uint> readOnlySet = new HashSet<uint>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ public class PlayerNavStateBuilder(IClientState clientState, IObjectTable object
|
||||||
return hashSet;
|
return hashSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe Dictionary<uint, int> BuildMountSpeedLevels()
|
private Dictionary<uint, int> BuildMountSpeedLevels()
|
||||||
{
|
{
|
||||||
Dictionary<uint, int> dictionary = new Dictionary<uint, int>();
|
Dictionary<uint, int> dictionary = new Dictionary<uint, int>();
|
||||||
ExcelSheet<TerritoryType> excelSheet = dataManager.GetExcelSheet<TerritoryType>();
|
ExcelSheet<TerritoryType> excelSheet = dataManager.GetExcelSheet<TerritoryType>();
|
||||||
|
|
@ -125,30 +125,51 @@ public class PlayerNavStateBuilder(IClientState clientState, IObjectTable object
|
||||||
{
|
{
|
||||||
return dictionary;
|
return dictionary;
|
||||||
}
|
}
|
||||||
UIState* ptr = UIState.Instance();
|
|
||||||
foreach (TerritoryType item in excelSheet)
|
foreach (TerritoryType item in excelSheet)
|
||||||
{
|
{
|
||||||
if (item.RowId != 0 && item.MountSpeed.RowId != 0)
|
if (item.RowId != 0 && item.MountSpeed.RowId != 0)
|
||||||
{
|
{
|
||||||
MountSpeed value = item.MountSpeed.Value;
|
int unlockedMountSpeedLevel = GetUnlockedMountSpeedLevel(item.MountSpeed.Value);
|
||||||
int num = 0;
|
if (unlockedMountSpeedLevel > 0)
|
||||||
if (value.Quest.RowId != 0 && ptr != null && ptr->IsUnlockLinkUnlockedOrQuestCompleted(value.Quest.RowId, 0))
|
|
||||||
{
|
{
|
||||||
num++;
|
dictionary[item.RowId] = unlockedMountSpeedLevel;
|
||||||
}
|
|
||||||
if (value.Unknown0 != 0 && ptr != null && ptr->IsUnlockLinkUnlockedOrQuestCompleted(value.Unknown0, 0))
|
|
||||||
{
|
|
||||||
num++;
|
|
||||||
}
|
|
||||||
if (num > 0)
|
|
||||||
{
|
|
||||||
dictionary[item.RowId] = num;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dictionary;
|
return dictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public (int Level, int MaxLevel)? GetTerritoryMountSpeed(uint territoryId)
|
||||||
|
{
|
||||||
|
TerritoryType? territoryType = dataManager.GetExcelSheet<TerritoryType>()?.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()
|
private unsafe static int GetCurrentGil()
|
||||||
{
|
{
|
||||||
InventoryManager* ptr = InventoryManager.Instance();
|
InventoryManager* ptr = InventoryManager.Instance();
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,23 @@ public sealed class ReRoutePolicy(NavRouter navRouter, PlayerNavStateBuilder pla
|
||||||
|
|
||||||
private bool _hasDestination;
|
private bool _hasDestination;
|
||||||
|
|
||||||
|
private bool _allowMount = true;
|
||||||
|
|
||||||
|
private bool _allowFlight = true;
|
||||||
|
|
||||||
private int _reRouteCount;
|
private int _reRouteCount;
|
||||||
|
|
||||||
private readonly HashSet<string> _penalizedTargets = new HashSet<string>();
|
private readonly HashSet<string> _penalizedTargets = new HashSet<string>();
|
||||||
|
|
||||||
private const int MaxReRoutes = 3;
|
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;
|
_destinationTerritoryId = territoryId;
|
||||||
_destinationPosition = position;
|
_destinationPosition = position;
|
||||||
|
_allowMount = allowMount;
|
||||||
|
_allowFlight = allowFlight;
|
||||||
_hasDestination = true;
|
_hasDestination = true;
|
||||||
_reRouteCount = 0;
|
_reRouteCount = 0;
|
||||||
_penalizedTargets.Clear();
|
_penalizedTargets.Clear();
|
||||||
|
|
@ -68,6 +74,7 @@ public sealed class ReRoutePolicy(NavRouter navRouter, PlayerNavStateBuilder pla
|
||||||
logger.LogWarning("Cannot build PlayerNavState for re-routing");
|
logger.LogWarning("Cannot build PlayerNavState for re-routing");
|
||||||
return new ReRouteDecision.GiveUp();
|
return new ReRouteDecision.GiveUp();
|
||||||
}
|
}
|
||||||
|
playerNavState = playerNavState.WithTravelCapabilities(_allowMount, _allowFlight);
|
||||||
if (playerNavState.CurrentTerritoryId != _destinationTerritoryId && hasPreservedTasks)
|
if (playerNavState.CurrentTerritoryId != _destinationTerritoryId && hasPreservedTasks)
|
||||||
{
|
{
|
||||||
_reRouteCount++;
|
_reRouteCount++;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -7,7 +8,7 @@ using SmartNav.Model.Navigation;
|
||||||
|
|
||||||
namespace SmartNav;
|
namespace SmartNav;
|
||||||
|
|
||||||
public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerritoryInfo territoryInfo, ILogger<RouteInstructionBuilder> logger)
|
public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerritoryInfo territoryInfo, WaterRoutePlanner waterPlanner, ILogger<RouteInstructionBuilder> logger)
|
||||||
{
|
{
|
||||||
private const float BoundaryApproachMargin = 5f;
|
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++)
|
for (int i = 0; i < route.Segments.Count; i++)
|
||||||
{
|
{
|
||||||
RouteSegment routeSegment = route.Segments[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<uint>(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);
|
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;
|
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<NavInstruction> 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<NavInstruction> instructions, RouteSegment seg, RouteSegment? previous, int index, int segmentCount, uint destinationTerritory, bool disableNavmesh)
|
private void BuildSegment(List<NavInstruction> instructions, RouteSegment seg, RouteSegment? previous, int index, int segmentCount, uint destinationTerritory, bool disableNavmesh)
|
||||||
{
|
{
|
||||||
uint territoryId4;
|
uint territoryId4;
|
||||||
|
|
@ -79,15 +131,21 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit
|
||||||
{
|
{
|
||||||
uint territoryId = seg.From.TerritoryId;
|
uint territoryId = seg.From.TerritoryId;
|
||||||
bool flag = index == segmentCount - 1 && seg.To.TerritoryId == destinationTerritory;
|
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<uint>(territoryId), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId) + ")"));
|
instructions.Add(new NavInstruction.WaitForTerritory(new global::_003C_003Ez__ReadOnlySingleElementList<uint>(territoryId), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId) + ")"));
|
||||||
if (!disableNavmesh)
|
if (!disableNavmesh)
|
||||||
{
|
{
|
||||||
instructions.Add(new NavInstruction.WaitForNavmesh());
|
instructions.Add(new NavInstruction.WaitForNavmesh());
|
||||||
}
|
}
|
||||||
uint? num = ((!(seg.To is NavNode.WarpNode warpNode)) ? ((uint?)null) : warpNode.NpcDataId);
|
if (waterPlan == null)
|
||||||
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));
|
instructions.Add(BuildWithinZoneMove(seg, territoryId, flag));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EmitWaterLegs(instructions, waterPlan, seg, territoryId, flag);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NavEdgeType.ZoneBoundary:
|
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, 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));
|
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));
|
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:
|
case NavEdgeType.Warp:
|
||||||
{
|
{
|
||||||
uint territoryId5 = seg.From.TerritoryId;
|
uint territoryId5 = seg.From.TerritoryId;
|
||||||
uint territoryId6 = seg.To.TerritoryId;
|
uint territoryId6 = seg.To.TerritoryId;
|
||||||
logger.LogTrace("SmartNav: Warp from {From} to {To}", territoryInfo.GetNameAndId(territoryId5), territoryInfo.GetNameAndId(territoryId6));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
logger.LogWarning("SmartNav: warp segment {From} -> {To} has no interactable source NPC; segment dropped", territoryInfo.GetNameAndId(territoryId5), territoryInfo.GetNameAndId(territoryId6));
|
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);
|
logger.LogWarning("SmartNav: unhandled edge type {EdgeType}; segment dropped", seg.EdgeType);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
IL_04b4:
|
IL_04bb:
|
||||||
instructions.Add(new NavInstruction.WaitForTerritory(new global::_003C_003Ez__ReadOnlySingleElementList<uint>(territoryId4), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId4) + ")"));
|
instructions.Add(new NavInstruction.WaitForTerritory(new global::_003C_003Ez__ReadOnlySingleElementList<uint>(territoryId4), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId4) + ")"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
SmartNav/SmartNav/WaterPlan.cs
Normal file
5
SmartNav/SmartNav/WaterPlan.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace SmartNav;
|
||||||
|
|
||||||
|
public sealed record WaterPlan(WaterPlanKind Kind, Vector3? SurfacePoint, Vector3? EntryPoint, float? GoalSurfaceY);
|
||||||
9
SmartNav/SmartNav/WaterPlanKind.cs
Normal file
9
SmartNav/SmartNav/WaterPlanKind.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace SmartNav;
|
||||||
|
|
||||||
|
public enum WaterPlanKind
|
||||||
|
{
|
||||||
|
Submerged,
|
||||||
|
DiveIn,
|
||||||
|
SurfaceOut,
|
||||||
|
SurfaceOutDiveIn
|
||||||
|
}
|
||||||
66
SmartNav/SmartNav/WaterRoutePlanner.cs
Normal file
66
SmartNav/SmartNav/WaterRoutePlanner.cs
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue