muffin v7.38.9
This commit is contained in:
parent
ada27cf05b
commit
8a7847ff37
21 changed files with 1296 additions and 689 deletions
|
|
@ -33,7 +33,7 @@ public abstract class LWindow : Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool IsPinned
|
protected new bool IsPinned
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -45,7 +45,7 @@ public abstract class LWindow : Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool IsClickthrough
|
protected new bool IsClickthrough
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
|
||||||
19
NotificationMasterAPI/NotificationMasterAPI.csproj
Normal file
19
NotificationMasterAPI/NotificationMasterAPI.csproj
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyName>NotificationMasterAPI</AssemblyName>
|
||||||
|
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||||
|
<TargetFramework>netcoreapp9.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<LangVersion>12.0</LangVersion>
|
||||||
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup />
|
||||||
|
<ItemGroup />
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Dalamud">
|
||||||
|
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
6
NotificationMasterAPI/NotificationMasterAPI/Data.cs
Normal file
6
NotificationMasterAPI/NotificationMasterAPI/Data.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace NotificationMasterAPI;
|
||||||
|
|
||||||
|
public static class Data
|
||||||
|
{
|
||||||
|
public const string MFAudioFormats = "*.3g2;*.3gp;*.3gp2;*.3gpp;*.asf;*.wma;*.wmv;*.aac;*.adts;*.avi;*.mp3;*.m4a;*.m4v;*.mov;*.mp4;*.sami;*.smi;*.wav;*.aiff";
|
||||||
|
}
|
||||||
16
NotificationMasterAPI/NotificationMasterAPI/NMAPINames.cs
Normal file
16
NotificationMasterAPI/NotificationMasterAPI/NMAPINames.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
namespace NotificationMasterAPI;
|
||||||
|
|
||||||
|
public static class NMAPINames
|
||||||
|
{
|
||||||
|
public const string DisplayToastNotification = "NotificationMasterAPI.DisplayToastNotification";
|
||||||
|
|
||||||
|
public const string FlashTaskbarIcon = "NotificationMasterAPI.FlashTaskbarIcon";
|
||||||
|
|
||||||
|
public const string PlaySound = "NotificationMasterAPI.PlaySound";
|
||||||
|
|
||||||
|
public const string BringGameForeground = "NotificationMasterAPI.BringGameForeground";
|
||||||
|
|
||||||
|
public const string StopSound = "NotificationMasterAPI.StopSound";
|
||||||
|
|
||||||
|
public const string Active = "NotificationMasterAPI.Active";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
using System;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Ipc.Exceptions;
|
||||||
|
|
||||||
|
namespace NotificationMasterAPI;
|
||||||
|
|
||||||
|
public class NotificationMasterApi
|
||||||
|
{
|
||||||
|
private IDalamudPluginInterface PluginInterface;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance of NotificationMaster API. You do not need to check if NotificationMaster plugin is installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dalamudPluginInterface">Plugin interface reference</param>
|
||||||
|
public NotificationMasterApi(IDalamudPluginInterface dalamudPluginInterface)
|
||||||
|
{
|
||||||
|
PluginInterface = dalamudPluginInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Validate()
|
||||||
|
{
|
||||||
|
if (PluginInterface == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("NotificationMaster API was called before it was initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if IPC is ready. You DO NOT need to call this method before invoking any of API functions unless you specifically want to check if plugin is installed and ready to accept requests.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool IsIPCReady()
|
||||||
|
{
|
||||||
|
Validate();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PluginInterface.GetIpcSubscriber<object>("NotificationMasterAPI.Active").InvokeAction();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (IpcNotReadyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays tray notification. This function does not throws an exception or displays an error if NotificationMaster is not installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text of tray notification</param>
|
||||||
|
/// <returns>Whether operation succeed.</returns>
|
||||||
|
public bool DisplayTrayNotification(string text)
|
||||||
|
{
|
||||||
|
return DisplayTrayNotification(null, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays tray notification. This function does not throws an exception or displays an error if NotificationMaster is not installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="title">Title of tray notification</param>
|
||||||
|
/// <param name="text">Text of tray notification</param>
|
||||||
|
/// <returns>Whether operation succeed.</returns>
|
||||||
|
public bool DisplayTrayNotification(string? title, string text)
|
||||||
|
{
|
||||||
|
Validate();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return PluginInterface.GetIpcSubscriber<string, string, string, bool>("NotificationMasterAPI.DisplayToastNotification").InvokeFunc(PluginInterface.InternalName, title, text);
|
||||||
|
}
|
||||||
|
catch (IpcNotReadyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flashes game's taskbar icon. This function does not throws an exception or displays an error if NotificationMaster is not installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether operation succeeded</returns>
|
||||||
|
public bool FlashTaskbarIcon()
|
||||||
|
{
|
||||||
|
Validate();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return PluginInterface.GetIpcSubscriber<string, bool>("NotificationMasterAPI.FlashTaskbarIcon").InvokeFunc(PluginInterface.InternalName);
|
||||||
|
}
|
||||||
|
catch (IpcNotReadyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to bring game's window foreground. Due to Windows inconsistencies, it's not guaranteed to work. This function does not throws an exception or displays an error if NotificationMaster is not installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether operation succeeded</returns>
|
||||||
|
public bool TryBringGameForeground()
|
||||||
|
{
|
||||||
|
Validate();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return PluginInterface.GetIpcSubscriber<string, bool>("NotificationMasterAPI.BringGameForeground").InvokeFunc(PluginInterface.InternalName);
|
||||||
|
}
|
||||||
|
catch (IpcNotReadyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins to play a sound file. If another sound file is already playing, stops previous file and begins playing specified. This function does not throws an exception or displays an error if NotificationMaster is not installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathOnDisk">Path to local file. Can not be web URL. See <see cref="F:NotificationMasterAPI.Data.MFAudioFormats" /> for supported formats.</param>
|
||||||
|
/// <param name="volume">Volume between 0.0 and 1.0</param>
|
||||||
|
/// <param name="repeat">Whether to repeat sound file.</param>
|
||||||
|
/// <param name="stopOnGameFocus">Whether to stop file once game is focused. </param>
|
||||||
|
/// <returns>Whether operation succeeded</returns>
|
||||||
|
public bool PlaySound(string pathOnDisk, float volume = 1f, bool repeat = false, bool stopOnGameFocus = true)
|
||||||
|
{
|
||||||
|
Validate();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return PluginInterface.GetIpcSubscriber<string, string, float, bool, bool, bool>("NotificationMasterAPI.PlaySound").InvokeFunc(PluginInterface.InternalName, pathOnDisk, volume, repeat, stopOnGameFocus);
|
||||||
|
}
|
||||||
|
catch (IpcNotReadyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops playing sound. This function does not throws an exception or displays an error if NotificationMaster is not installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether operation succeeded</returns>
|
||||||
|
public bool StopSound()
|
||||||
|
{
|
||||||
|
Validate();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return PluginInterface.GetIpcSubscriber<string, bool>("NotificationMasterAPI.StopSound").InvokeFunc(PluginInterface.InternalName);
|
||||||
|
}
|
||||||
|
catch (IpcNotReadyError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -173,6 +173,10 @@ internal static class Interact
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
InteractionType = base.Task.InteractionType;
|
InteractionType = base.Task.InteractionType;
|
||||||
|
_interactionState = EInteractionState.None;
|
||||||
|
_needsUnmount = false;
|
||||||
|
delayedFinalCheck = false;
|
||||||
|
_continueAt = DateTime.MinValue;
|
||||||
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId);
|
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId);
|
||||||
if (gameObject == null)
|
if (gameObject == null)
|
||||||
{
|
{
|
||||||
|
|
@ -259,6 +263,7 @@ internal static class Interact
|
||||||
{
|
{
|
||||||
if (base.ProgressContext.WasInterrupted())
|
if (base.ProgressContext.WasInterrupted())
|
||||||
{
|
{
|
||||||
|
logger.LogDebug("Interaction with {DataId} was interrupted", base.Task.DataId);
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
if (base.ProgressContext.WasSuccessful() || _interactionState == EInteractionState.InteractionConfirmed)
|
if (base.ProgressContext.WasSuccessful() || _interactionState == EInteractionState.InteractionConfirmed)
|
||||||
|
|
|
||||||
|
|
@ -196,15 +196,40 @@ internal static class WaitAtEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitNextStepOrSequenceExecutor : TaskExecutor<WaitNextStepOrSequence>
|
internal sealed class WaitNextStepOrSequenceExecutor(QuestController questController) : TaskExecutor<WaitNextStepOrSequence>()
|
||||||
{
|
{
|
||||||
|
private ElementId? _questId;
|
||||||
|
|
||||||
|
private byte _initialSequence;
|
||||||
|
|
||||||
|
private int _initialStep;
|
||||||
|
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
|
QuestController.QuestProgress currentQuest = questController.CurrentQuest;
|
||||||
|
if (currentQuest != null)
|
||||||
|
{
|
||||||
|
_questId = currentQuest.Quest.Id;
|
||||||
|
_initialSequence = currentQuest.Sequence;
|
||||||
|
_initialStep = currentQuest.Step;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
|
if (_questId != null)
|
||||||
|
{
|
||||||
|
QuestController.QuestProgress currentQuest = questController.CurrentQuest;
|
||||||
|
if (currentQuest == null || currentQuest.Quest.Id != _questId)
|
||||||
|
{
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
if (currentQuest.Sequence != _initialSequence || currentQuest.Step != _initialStep)
|
||||||
|
{
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
}
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ internal abstract class TaskExecutor<T> : ITaskExecutor where T : class, ITask
|
||||||
if (task is T task2)
|
if (task is T task2)
|
||||||
{
|
{
|
||||||
Task = task2;
|
Task = task2;
|
||||||
|
ProgressContext = null;
|
||||||
return Start();
|
return Start();
|
||||||
}
|
}
|
||||||
throw new TaskException($"Unable to cast {task.GetType()} to {typeof(T)}");
|
throw new TaskException($"Unable to cast {task.GetType()} to {typeof(T)}");
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,6 @@ internal sealed class InterruptHandler : IDisposable
|
||||||
{
|
{
|
||||||
private unsafe delegate void ProcessActionEffect(uint sourceId, Character* sourceCharacter, Vector3* pos, EffectHeader* effectHeader, EffectEntry* effectArray, ulong* effectTail);
|
private unsafe delegate void ProcessActionEffect(uint sourceId, Character* sourceCharacter, Vector3* pos, EffectHeader* effectHeader, EffectEntry* effectArray, ulong* effectTail);
|
||||||
|
|
||||||
private static class Signatures
|
|
||||||
{
|
|
||||||
internal const string ActionEffect = "40 ?? 56 57 41 ?? 41 ?? 41 ?? 48 ?? ?? ?? ?? ?? ?? ?? 48";
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
private struct EffectEntry
|
private struct EffectEntry
|
||||||
{
|
{
|
||||||
|
|
@ -150,7 +145,7 @@ internal sealed class InterruptHandler : IDisposable
|
||||||
_objectTable = objectTable;
|
_objectTable = objectTable;
|
||||||
_territoryData = territoryData;
|
_territoryData = territoryData;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_processActionEffectHook = gameInteropProvider.HookFromSignature<ProcessActionEffect>("40 ?? 56 57 41 ?? 41 ?? 41 ?? 48 ?? ?? ?? ?? ?? ?? ?? 48", HandleProcessActionEffect);
|
_processActionEffectHook = gameInteropProvider.HookFromAddress<ProcessActionEffect>(ActionEffectHandler.Addresses.Receive.Value, HandleProcessActionEffect);
|
||||||
_processActionEffectHook.Enable();
|
_processActionEffectHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
private readonly AetheryteData _aetheryteData;
|
private readonly AetheryteData _aetheryteData;
|
||||||
|
|
||||||
|
private readonly Configuration _configuration;
|
||||||
|
|
||||||
private readonly ILogger<MovementController> _logger;
|
private readonly ILogger<MovementController> _logger;
|
||||||
|
|
||||||
private CancellationTokenSource? _cancellationTokenSource;
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
|
|
@ -93,6 +95,14 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
private long _pathfindStartTime;
|
private long _pathfindStartTime;
|
||||||
|
|
||||||
|
private Vector3? _lastKnownPosition;
|
||||||
|
|
||||||
|
private long _lastPositionUpdateTime;
|
||||||
|
|
||||||
|
private Vector3? _expectedPosition;
|
||||||
|
|
||||||
|
private bool _isTrackingPlayerInput;
|
||||||
|
|
||||||
public bool IsNavmeshReady
|
public bool IsNavmeshReady
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -146,7 +156,9 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
public int NumQueuedPathfindRequests => _navmeshIpc.NumQueuedPathfindRequests;
|
public int NumQueuedPathfindRequests => _navmeshIpc.NumQueuedPathfindRequests;
|
||||||
|
|
||||||
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, ChatFunctions chatFunctions, ICondition condition, MovementOverrideController movementOverrideController, AetheryteData aetheryteData, ILogger<MovementController> logger)
|
public event EventHandler? PlayerInputDetected;
|
||||||
|
|
||||||
|
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, ChatFunctions chatFunctions, ICondition condition, MovementOverrideController movementOverrideController, AetheryteData aetheryteData, Configuration configuration, ILogger<MovementController> logger)
|
||||||
{
|
{
|
||||||
_navmeshIpc = navmeshIpc;
|
_navmeshIpc = navmeshIpc;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
|
@ -156,11 +168,19 @@ internal sealed class MovementController : IDisposable
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
_movementOverrideController = movementOverrideController;
|
_movementOverrideController = movementOverrideController;
|
||||||
_aetheryteData = aetheryteData;
|
_aetheryteData = aetheryteData;
|
||||||
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Update()
|
public unsafe void Update()
|
||||||
{
|
{
|
||||||
|
if (IsPathRunning && _isTrackingPlayerInput && DetectPlayerInputInterference())
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Player input detected during automatic movement, raising event to stop automation");
|
||||||
|
this.PlayerInputDetected?.Invoke(this, EventArgs.Empty);
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_pathfindTask != null && Destination != null)
|
if (_pathfindTask != null && Destination != null)
|
||||||
{
|
{
|
||||||
if (!_pathfindTask.IsCompleted && Environment.TickCount64 - _pathfindStartTime > 30000 && _navmeshIpc.NumQueuedPathfindRequests > 5)
|
if (!_pathfindTask.IsCompleted && Environment.TickCount64 - _pathfindStartTime > 30000 && _navmeshIpc.NumQueuedPathfindRequests > 5)
|
||||||
|
|
@ -188,6 +208,11 @@ internal sealed class MovementController : IDisposable
|
||||||
if (Destination.IsFlying && Destination.Land)
|
if (Destination.IsFlying && Destination.Land)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Adjusted destination failed, trying tolerance-based pathfinding");
|
_logger.LogWarning("Adjusted destination failed, trying tolerance-based pathfinding");
|
||||||
|
if (!IsNavmeshReady)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Navmesh not ready for tolerance-based pathfinding");
|
||||||
|
return;
|
||||||
|
}
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
|
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
|
||||||
Vector3 vector2 = _objectTable[0]?.Position ?? Vector3.Zero;
|
Vector3 vector2 = _objectTable[0]?.Position ?? Vector3.Zero;
|
||||||
|
|
@ -218,6 +243,11 @@ internal sealed class MovementController : IDisposable
|
||||||
(list, _) = tuple;
|
(list, _) = tuple;
|
||||||
if (tuple.Item2 && Destination.ShouldRecalculateNavmesh())
|
if (tuple.Item2 && Destination.ShouldRecalculateNavmesh())
|
||||||
{
|
{
|
||||||
|
if (!IsNavmeshReady)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Navmesh not ready for recalculation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
Destination.NavmeshCalculations++;
|
Destination.NavmeshCalculations++;
|
||||||
Destination.PartialRoute.AddRange(list);
|
Destination.PartialRoute.AddRange(list);
|
||||||
_logger.LogInformation("Running navmesh recalculation with fudged point ({From} to {To})", list.Last(), Destination.Position);
|
_logger.LogInformation("Running navmesh recalculation with fudged point ({From} to {To})", list.Last(), Destination.Position);
|
||||||
|
|
@ -232,6 +262,7 @@ internal sealed class MovementController : IDisposable
|
||||||
_logger.LogInformation("Navigating via route: [{Route}]", string.Join(" → ", _pathfindTask.Result.Select((Vector3 x) => x.ToString("G", CultureInfo.InvariantCulture))));
|
_logger.LogInformation("Navigating via route: [{Route}]", string.Join(" → ", _pathfindTask.Result.Select((Vector3 x) => x.ToString("G", CultureInfo.InvariantCulture))));
|
||||||
_navmeshIpc.MoveTo(list, Destination.IsFlying);
|
_navmeshIpc.MoveTo(list, Destination.IsFlying);
|
||||||
MovementStartedAt = DateTime.Now;
|
MovementStartedAt = DateTime.Now;
|
||||||
|
StartPlayerInputTracking();
|
||||||
ResetPathfinding();
|
ResetPathfinding();
|
||||||
}
|
}
|
||||||
else if (_pathfindTask.IsCompleted)
|
else if (_pathfindTask.IsCompleted)
|
||||||
|
|
@ -321,6 +352,72 @@ internal sealed class MovementController : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StartPlayerInputTracking()
|
||||||
|
{
|
||||||
|
IGameObject gameObject = _objectTable[0];
|
||||||
|
if (gameObject != null)
|
||||||
|
{
|
||||||
|
_lastKnownPosition = gameObject.Position;
|
||||||
|
_expectedPosition = gameObject.Position;
|
||||||
|
_lastPositionUpdateTime = Environment.TickCount64;
|
||||||
|
_isTrackingPlayerInput = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DetectPlayerInputInterference()
|
||||||
|
{
|
||||||
|
if (!_configuration.General.StopOnPlayerInput)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!_isTrackingPlayerInput || !_lastKnownPosition.HasValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IGameObject gameObject = _objectTable[0];
|
||||||
|
if (gameObject == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Vector3 position = gameObject.Position;
|
||||||
|
long tickCount = Environment.TickCount64;
|
||||||
|
if (tickCount - _lastPositionUpdateTime < 100)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<Vector3> waypoints = _navmeshIpc.GetWaypoints();
|
||||||
|
if (waypoints.Count > 0)
|
||||||
|
{
|
||||||
|
_expectedPosition = waypoints[0];
|
||||||
|
}
|
||||||
|
if (_expectedPosition.HasValue)
|
||||||
|
{
|
||||||
|
Vector3 vector = Vector3.Normalize(_expectedPosition.Value - _lastKnownPosition.Value);
|
||||||
|
Vector3 value = position - _lastKnownPosition.Value;
|
||||||
|
if (value.Length() > 0.1f)
|
||||||
|
{
|
||||||
|
Vector3 vector2 = Vector3.Normalize(value);
|
||||||
|
float num = Vector3.Dot(vector, vector2);
|
||||||
|
if (num < 0.7f)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Player movement detected: alignment={Alignment:F2}, actual={Actual}, expected={Expected}", num, value.ToString("G", CultureInfo.InvariantCulture), vector.ToString("G", CultureInfo.InvariantCulture));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_lastKnownPosition = position;
|
||||||
|
_lastPositionUpdateTime = tickCount;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopPlayerInputTracking()
|
||||||
|
{
|
||||||
|
_isTrackingPlayerInput = false;
|
||||||
|
_lastKnownPosition = null;
|
||||||
|
_expectedPosition = null;
|
||||||
|
_lastPositionUpdateTime = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
private void Restart(DestinationData destination)
|
private void Restart(DestinationData destination)
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
|
|
@ -364,6 +461,11 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
public void NavigateTo(EMovementType type, uint? dataId, Vector3 to, bool fly, bool sprint, float? stopDistance = null, float? verticalStopDistance = null, bool land = false)
|
public void NavigateTo(EMovementType type, uint? dataId, Vector3 to, bool fly, bool sprint, float? stopDistance = null, float? verticalStopDistance = null, bool land = false)
|
||||||
{
|
{
|
||||||
|
if (!IsNavmeshReady)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Navmesh not ready, cannot start navigation to {Position}", to.ToString("G", CultureInfo.InvariantCulture));
|
||||||
|
return;
|
||||||
|
}
|
||||||
fly |= _condition[ConditionFlag.Diving];
|
fly |= _condition[ConditionFlag.Diving];
|
||||||
if (fly && land)
|
if (fly && land)
|
||||||
{
|
{
|
||||||
|
|
@ -404,6 +506,11 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
public void NavigateTo(EMovementType type, uint? dataId, List<Vector3> to, bool fly, bool sprint, float? stopDistance, float? verticalStopDistance = null, bool land = false)
|
public void NavigateTo(EMovementType type, uint? dataId, List<Vector3> to, bool fly, bool sprint, float? stopDistance, float? verticalStopDistance = null, bool land = false)
|
||||||
{
|
{
|
||||||
|
if (!IsNavmeshReady)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Navmesh not ready, cannot start navigation to {Position}", to.Last().ToString("G", CultureInfo.InvariantCulture));
|
||||||
|
return;
|
||||||
|
}
|
||||||
fly |= _condition[ConditionFlag.Diving];
|
fly |= _condition[ConditionFlag.Diving];
|
||||||
if (fly && land && to.Count > 0)
|
if (fly && land && to.Count > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -416,6 +523,7 @@ internal sealed class MovementController : IDisposable
|
||||||
_logger.LogInformation("Moving to {Destination}", Destination);
|
_logger.LogInformation("Moving to {Destination}", Destination);
|
||||||
_navmeshIpc.MoveTo(to, fly);
|
_navmeshIpc.MoveTo(to, fly);
|
||||||
MovementStartedAt = DateTime.Now;
|
MovementStartedAt = DateTime.Now;
|
||||||
|
StartPlayerInputTracking();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetPathfinding()
|
public void ResetPathfinding()
|
||||||
|
|
@ -436,6 +544,11 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
private Vector3? TryFindAccessibleDestination(Vector3 target, bool flying, bool landing)
|
private Vector3? TryFindAccessibleDestination(Vector3 target, bool flying, bool landing)
|
||||||
{
|
{
|
||||||
|
if (!IsNavmeshReady)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Navmesh not ready, cannot find accessible destination");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
float[] array = ((!(flying && landing)) ? ((!flying) ? new float[3] { 1f, 3f, 5f } : new float[3] { 2f, 5f, 10f }) : new float[3] { 5f, 10f, 15f });
|
float[] array = ((!(flying && landing)) ? ((!flying) ? new float[3] { 1f, 3f, 5f } : new float[3] { 2f, 5f, 10f }) : new float[3] { 5f, 10f, 15f });
|
||||||
float[] array2 = ((!flying) ? new float[3] { 1f, 2f, 3f } : new float[3] { 3f, 5f, 10f });
|
float[] array2 = ((!flying) ? new float[3] { 1f, 2f, 3f } : new float[3] { 3f, 5f, 10f });
|
||||||
for (int i = 0; i < array.Length; i++)
|
for (int i = 0; i < array.Length; i++)
|
||||||
|
|
@ -504,17 +617,52 @@ internal sealed class MovementController : IDisposable
|
||||||
if (Math.Abs((double)num - Destination.LastWaypoint.Distance2DAtLastUpdate) < 0.5)
|
if (Math.Abs((double)num - Destination.LastWaypoint.Distance2DAtLastUpdate) < 0.5)
|
||||||
{
|
{
|
||||||
int navmeshCalculations = Destination.NavmeshCalculations;
|
int navmeshCalculations = Destination.NavmeshCalculations;
|
||||||
if (navmeshCalculations % 6 == 1)
|
switch (navmeshCalculations)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Jumping to try and resolve navmesh problem (n = {Calculations})", navmeshCalculations);
|
case 1:
|
||||||
|
case 7:
|
||||||
|
_logger.LogWarning("Jumping to try and resolve navmesh problem (n = {Calculations}) at {Position}", navmeshCalculations, Destination.Position.ToString("G", CultureInfo.InvariantCulture));
|
||||||
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
|
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
|
||||||
Destination.NavmeshCalculations++;
|
Destination.NavmeshCalculations++;
|
||||||
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
|
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
|
||||||
}
|
break;
|
||||||
else
|
case 5:
|
||||||
|
_logger.LogWarning("Reloading navmesh (n = {Calculations}) at {Position}", navmeshCalculations, Destination.Position.ToString("G", CultureInfo.InvariantCulture));
|
||||||
|
_navmeshIpc.Reload();
|
||||||
|
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
if (!IsNavmeshReady)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Recalculating navmesh (n = {Calculations})", navmeshCalculations);
|
_logger.LogWarning("Navmesh not ready after reload (n = {Calculations})", navmeshCalculations);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_logger.LogInformation("Navmesh ready after reload, restarting navigation (n = {Calculations})", navmeshCalculations);
|
||||||
Restart(Destination);
|
Restart(Destination);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
_logger.LogWarning("Rebuilding navmesh (n = {Calculations}) at {Position}", navmeshCalculations, Destination.Position.ToString("G", CultureInfo.InvariantCulture));
|
||||||
|
_navmeshIpc.Rebuild();
|
||||||
|
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
if (!IsNavmeshReady)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Navmesh not ready after rebuild (n = {Calculations})", navmeshCalculations);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_logger.LogInformation("Navmesh ready after rebuild, restarting navigation (n = {Calculations})", navmeshCalculations);
|
||||||
|
Restart(Destination);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!IsNavmeshReady)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Navmesh not ready for recalculation (n = {Calculations})", navmeshCalculations);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_logger.LogWarning("Recalculating navmesh (n = {Calculations}) at {Position}", navmeshCalculations, Destination.Position.ToString("G", CultureInfo.InvariantCulture));
|
||||||
|
Restart(Destination);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
Destination.NavmeshCalculations = navmeshCalculations + 1;
|
Destination.NavmeshCalculations = navmeshCalculations + 1;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -570,6 +718,7 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
|
StopPlayerInputTracking();
|
||||||
_navmeshIpc.Stop();
|
_navmeshIpc.Stop();
|
||||||
ResetPathfinding();
|
ResetPathfinding();
|
||||||
Destination = null;
|
Destination = null;
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
_toastGui.Toast += OnNormalToast;
|
_toastGui.Toast += OnNormalToast;
|
||||||
_condition.ConditionChange += OnConditionChange;
|
_condition.ConditionChange += OnConditionChange;
|
||||||
_clientState.Logout += OnLogout;
|
_clientState.Logout += OnLogout;
|
||||||
|
_movementController.PlayerInputDetected += OnPlayerInputDetected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reload()
|
public void Reload()
|
||||||
|
|
@ -683,48 +684,63 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
{
|
{
|
||||||
DebugState = "No quest active";
|
DebugState = "No quest active";
|
||||||
Stop("No quest active");
|
Stop("No quest active");
|
||||||
|
}
|
||||||
|
else if (questProgress.Step == 255)
|
||||||
|
{
|
||||||
|
DebugState = $"Waiting for sequence update (current: {questProgress.Sequence})";
|
||||||
|
if (!_taskQueue.AllTasksComplete)
|
||||||
|
{
|
||||||
|
DebugState = "Step 255 - processing interrupted tasks";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (this.CurrentQuest == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(questProgress.Quest))
|
TimeSpan timeSpan = DateTime.Now - this.CurrentQuest.StepProgress.StartedAt;
|
||||||
|
if (timeSpan > TimeSpan.FromSeconds(3L))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Step 255 with no tasks for {WaitTime:F1}s, retrying step to ensure completion (quest: {QuestId}, sequence: {Sequence})", timeSpan.TotalSeconds, questProgress.Quest.Id, questProgress.Sequence);
|
||||||
|
QuestSequence questSequence = questProgress.Quest.FindSequence(questProgress.Sequence);
|
||||||
|
if (questSequence != null && questSequence.Steps.Count > 0)
|
||||||
|
{
|
||||||
|
this.CurrentQuest.SetStep(questSequence.Steps.Count - 1);
|
||||||
|
CheckNextTasks("Retry last step at 255");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(questProgress.Quest))
|
||||||
{
|
{
|
||||||
DebugState = "Occupied";
|
DebugState = "Occupied";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (_movementController.IsPathfinding)
|
else if (_movementController.IsPathfinding)
|
||||||
{
|
{
|
||||||
DebugState = "Pathfinding is running";
|
DebugState = "Pathfinding is running";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (_movementController.IsPathRunning)
|
else if (_movementController.IsPathRunning)
|
||||||
{
|
{
|
||||||
DebugState = "Path is running";
|
DebugState = "Path is running";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (DateTime.Now < _safeAnimationEnd)
|
else if (DateTime.Now < _safeAnimationEnd)
|
||||||
{
|
{
|
||||||
DebugState = "Waiting for Animation";
|
DebugState = "Waiting for Animation";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (questProgress.Sequence != b)
|
else if (questProgress.Sequence != b)
|
||||||
{
|
{
|
||||||
questProgress.SetSequence(b);
|
questProgress.SetSequence(b);
|
||||||
CheckNextTasks($"New sequence {questProgress == _startedQuest}/{_questFunctions.GetCurrentQuestInternal(allowNewMsq: true)}");
|
CheckNextTasks($"New sequence {questProgress == _startedQuest}/{_questFunctions.GetCurrentQuestInternal(allowNewMsq: true)}");
|
||||||
}
|
}
|
||||||
QuestSequence questSequence = questProgress.Quest.FindSequence(questProgress.Sequence);
|
else
|
||||||
if (questSequence == null)
|
{
|
||||||
|
QuestSequence questSequence2 = questProgress.Quest.FindSequence(questProgress.Sequence);
|
||||||
|
if (questSequence2 == null)
|
||||||
{
|
{
|
||||||
DebugState = $"Sequence {questProgress.Sequence} not found";
|
DebugState = $"Sequence {questProgress.Sequence} not found";
|
||||||
Stop("Unknown sequence");
|
Stop("Unknown sequence");
|
||||||
}
|
}
|
||||||
else if (questProgress.Step == 255)
|
else if (questSequence2.Steps.Count > 0 && questProgress.Step >= questSequence2.Steps.Count)
|
||||||
{
|
|
||||||
DebugState = "Step completed";
|
|
||||||
if (!_taskQueue.AllTasksComplete)
|
|
||||||
{
|
|
||||||
CheckNextTasks("Step complete");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (questSequence.Steps.Count > 0 && questProgress.Step >= questSequence.Steps.Count)
|
|
||||||
{
|
{
|
||||||
DebugState = "Step not found";
|
DebugState = "Step not found";
|
||||||
Stop("Unknown step");
|
Stop("Unknown step");
|
||||||
|
|
@ -735,6 +751,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public (QuestSequence? Sequence, QuestStep? Step, bool createTasks) GetNextStep()
|
public (QuestSequence? Sequence, QuestStep? Step, bool createTasks) GetNextStep()
|
||||||
{
|
{
|
||||||
|
|
@ -778,15 +795,21 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
_logger.LogWarning("Ignoring 'increase step count' for different sequence (expected {ExpectedSequence}, but we are at {CurrentSequence}", sequence, questSequence.Sequence);
|
_logger.LogWarning("Ignoring 'increase step count' for different sequence (expected {ExpectedSequence}, but we are at {CurrentSequence}", sequence, questSequence.Sequence);
|
||||||
}
|
}
|
||||||
_logger.LogInformation("Increasing step count from {CurrentValue}", CurrentQuest.Step);
|
_logger.LogInformation("Increasing step count from {CurrentValue}", CurrentQuest.Step);
|
||||||
if (CurrentQuest.Step + 1 < questSequence.Steps.Count)
|
bool num = CurrentQuest.Step + 1 >= questSequence.Steps.Count;
|
||||||
{
|
if (num)
|
||||||
CurrentQuest.SetStep(CurrentQuest.Step + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
CurrentQuest.SetStep(255);
|
CurrentQuest.SetStep(255);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentQuest.SetStep(CurrentQuest.Step + 1);
|
||||||
|
}
|
||||||
ResetAutoRefreshState();
|
ResetAutoRefreshState();
|
||||||
|
if (num)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Completed last step in sequence, waiting for game to update sequence");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
using (_logger.BeginScope("IncStepCt"))
|
using (_logger.BeginScope("IncStepCt"))
|
||||||
{
|
{
|
||||||
|
|
@ -1291,12 +1314,23 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPlayerInputDetected(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (AutomationType != EAutomationType.Manual && IsRunning)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Player input detected during movement, stopping quest automation");
|
||||||
|
_chatGui.Print("Player input detected - stopping quest automation.", "Questionable", 576);
|
||||||
|
Stop("Player input detected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
_toastGui.ErrorToast -= base.OnErrorToast;
|
_toastGui.ErrorToast -= base.OnErrorToast;
|
||||||
_toastGui.Toast -= OnNormalToast;
|
_toastGui.Toast -= OnNormalToast;
|
||||||
_condition.ConditionChange -= OnConditionChange;
|
_condition.ConditionChange -= OnConditionChange;
|
||||||
_clientState.Logout -= OnLogout;
|
_clientState.Logout -= OnLogout;
|
||||||
|
_movementController.PlayerInputDetected -= OnPlayerInputDetected;
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -39,6 +39,10 @@ internal sealed class NavmeshIpc
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<float> _buildProgress;
|
private readonly ICallGateSubscriber<float> _buildProgress;
|
||||||
|
|
||||||
|
private readonly ICallGateSubscriber<bool> _navReload;
|
||||||
|
|
||||||
|
private readonly ICallGateSubscriber<bool> _navRebuild;
|
||||||
|
|
||||||
public bool IsReady
|
public bool IsReady
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -115,6 +119,8 @@ internal sealed class NavmeshIpc
|
||||||
_queryPointOnFloor = pluginInterface.GetIpcSubscriber<Vector3, bool, float, Vector3?>("vnavmesh.Query.Mesh.PointOnFloor");
|
_queryPointOnFloor = pluginInterface.GetIpcSubscriber<Vector3, bool, float, Vector3?>("vnavmesh.Query.Mesh.PointOnFloor");
|
||||||
_queryNearestPoint = pluginInterface.GetIpcSubscriber<Vector3, float, float, Vector3?>("vnavmesh.Query.Mesh.NearestPoint");
|
_queryNearestPoint = pluginInterface.GetIpcSubscriber<Vector3, float, float, Vector3?>("vnavmesh.Query.Mesh.NearestPoint");
|
||||||
_buildProgress = pluginInterface.GetIpcSubscriber<float>("vnavmesh.Nav.BuildProgress");
|
_buildProgress = pluginInterface.GetIpcSubscriber<float>("vnavmesh.Nav.BuildProgress");
|
||||||
|
_navReload = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Nav.Reload");
|
||||||
|
_navRebuild = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Nav.Rebuild");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
|
|
@ -129,6 +135,30 @@ internal sealed class NavmeshIpc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reload()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_navReload.InvokeFunc();
|
||||||
|
}
|
||||||
|
catch (IpcError exception)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(exception, "Could not reload navmesh");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rebuild()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_navRebuild.InvokeFunc();
|
||||||
|
}
|
||||||
|
catch (IpcError exception)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(exception, "Could not rebuild navmesh");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task<List<Vector3>> Pathfind(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly, CancellationToken cancellationToken)
|
public Task<List<Vector3>> Pathfind(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -147,8 +177,8 @@ internal sealed class NavmeshIpc
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_pathSetTolerance.InvokeAction(0.25f);
|
_pathSetTolerance.InvokeAction(tolerance);
|
||||||
return _pluginInterface.GetIpcSubscriber<Vector3, Vector3, bool, float, CancellationToken, Task<List<Vector3>>>("vnavmesh.Nav.PathfindWithTolerance").InvokeFunc(localPlayerPosition, targetPosition, fly, tolerance, cancellationToken);
|
return _pluginInterface.GetIpcSubscriber<Vector3, Vector3, bool, float, Task<List<Vector3>>>("vnavmesh.Nav.PathfindWithTolerance").InvokeFunc(localPlayerPosition, targetPosition, fly, tolerance);
|
||||||
}
|
}
|
||||||
catch (IpcError exception)
|
catch (IpcError exception)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
@ -21,11 +22,6 @@ internal sealed class ChatFunctions
|
||||||
{
|
{
|
||||||
private delegate void ProcessChatBoxDelegate(nint uiModule, nint message, nint unused, byte a4);
|
private delegate void ProcessChatBoxDelegate(nint uiModule, nint message, nint unused, byte a4);
|
||||||
|
|
||||||
private static class Signatures
|
|
||||||
{
|
|
||||||
internal const string SendChat = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F2 48 8B F9 45 84 C9";
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
private readonly struct ChatPayload : IDisposable
|
private readonly struct ChatPayload : IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -72,7 +68,7 @@ internal sealed class ChatFunctions
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
_targetManager = targetManager;
|
_targetManager = targetManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_processChatBox = Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F2 48 8B F9 45 84 C9"));
|
_processChatBox = Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(UIModule.Addresses.ProcessChatBoxEntry.Value);
|
||||||
_emoteCommands = (from x in dataManager.GetExcelSheet<Emote>()
|
_emoteCommands = (from x in dataManager.GetExcelSheet<Emote>()
|
||||||
where x.RowId != 0
|
where x.RowId != 0
|
||||||
where x.TextCommand.IsValid
|
where x.TextCommand.IsValid
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Fate;
|
using FFXIVClientStructs.FFXIV.Client.Game.Fate;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
|
@ -34,11 +35,6 @@ internal sealed class GameFunctions
|
||||||
{
|
{
|
||||||
private delegate void AbandonDutyDelegate(bool a1);
|
private delegate void AbandonDutyDelegate(bool a1);
|
||||||
|
|
||||||
private static class Signatures
|
|
||||||
{
|
|
||||||
internal const string AbandonDuty = "E8 ?? ?? ?? ?? 41 B2 01 EB 39";
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly QuestFunctions _questFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
|
|
||||||
private readonly IDataManager _dataManager;
|
private readonly IDataManager _dataManager;
|
||||||
|
|
@ -74,7 +70,7 @@ internal sealed class GameFunctions
|
||||||
_gameGui = gameGui;
|
_gameGui = gameGui;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_abandonDuty = Marshal.GetDelegateForFunctionPointer<AbandonDutyDelegate>(sigScanner.ScanText("E8 ?? ?? ?? ?? 41 B2 01 EB 39"));
|
_abandonDuty = Marshal.GetDelegateForFunctionPointer<AbandonDutyDelegate>(EventFramework.Addresses.LeaveCurrentContent.Value);
|
||||||
_territoryToAetherCurrentCompFlgSet = (from x in dataManager.GetExcelSheet<TerritoryType>()
|
_territoryToAetherCurrentCompFlgSet = (from x in dataManager.GetExcelSheet<TerritoryType>()
|
||||||
where x.RowId != 0
|
where x.RowId != 0
|
||||||
where x.AetherCurrentCompFlgSet.RowId != 0
|
where x.AetherCurrentCompFlgSet.RowId != 0
|
||||||
|
|
|
||||||
5
Questionable/Questionable.Functions/GameSignatures.cs
Normal file
5
Questionable/Questionable.Functions/GameSignatures.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
namespace Questionable.Functions;
|
||||||
|
|
||||||
|
internal static class GameSignatures
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -183,22 +183,28 @@ internal sealed class GeneralConfigComponent : ConfigComponent
|
||||||
base.Configuration.General.UseEscToCancelQuesting = v2;
|
base.Configuration.General.UseEscToCancelQuesting = v2;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
bool v3 = base.Configuration.General.ShowIncompleteSeasonalEvents;
|
bool v3 = base.Configuration.General.StopOnPlayerInput;
|
||||||
if (ImGui.Checkbox("Show details for incomplete seasonal events", ref v3))
|
if (ImGui.Checkbox("Stop automation when manually moving character", ref v3))
|
||||||
{
|
{
|
||||||
base.Configuration.General.ShowIncompleteSeasonalEvents = v3;
|
base.Configuration.General.StopOnPlayerInput = v3;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
bool v4 = base.Configuration.General.HideSeasonalEventsFromJournalProgress;
|
bool v4 = base.Configuration.General.ShowIncompleteSeasonalEvents;
|
||||||
if (ImGui.Checkbox("Hide Seasonal Events from Journal Progress", ref v4))
|
if (ImGui.Checkbox("Show details for incomplete seasonal events", ref v4))
|
||||||
{
|
{
|
||||||
base.Configuration.General.HideSeasonalEventsFromJournalProgress = v4;
|
base.Configuration.General.ShowIncompleteSeasonalEvents = v4;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
bool v5 = base.Configuration.General.ShowChangelogOnUpdate;
|
bool v5 = base.Configuration.General.HideSeasonalEventsFromJournalProgress;
|
||||||
if (ImGui.Checkbox("Show changelog window when plugin updates", ref v5))
|
if (ImGui.Checkbox("Hide Seasonal Events from Journal Progress", ref v5))
|
||||||
{
|
{
|
||||||
base.Configuration.General.ShowChangelogOnUpdate = v5;
|
base.Configuration.General.HideSeasonalEventsFromJournalProgress = v5;
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
bool v6 = base.Configuration.General.ShowChangelogOnUpdate;
|
||||||
|
if (ImGui.Checkbox("Show changelog window when plugin updates", ref v6))
|
||||||
|
{
|
||||||
|
base.Configuration.General.ShowChangelogOnUpdate = v6;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,16 +212,16 @@ internal sealed class GeneralConfigComponent : ConfigComponent
|
||||||
ImGui.Text("Questing");
|
ImGui.Text("Questing");
|
||||||
using (ImRaii.PushIndent())
|
using (ImRaii.PushIndent())
|
||||||
{
|
{
|
||||||
bool v6 = base.Configuration.General.ConfigureTextAdvance;
|
bool v7 = base.Configuration.General.ConfigureTextAdvance;
|
||||||
if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v6))
|
if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v7))
|
||||||
{
|
{
|
||||||
base.Configuration.General.ConfigureTextAdvance = v6;
|
base.Configuration.General.ConfigureTextAdvance = v7;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
bool v7 = base.Configuration.General.SkipLowPriorityDuties;
|
bool v8 = base.Configuration.General.SkipLowPriorityDuties;
|
||||||
if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v7))
|
if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v8))
|
||||||
{
|
{
|
||||||
base.Configuration.General.SkipLowPriorityDuties = v7;
|
base.Configuration.General.SkipLowPriorityDuties = v8;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -243,10 +249,10 @@ internal sealed class GeneralConfigComponent : ConfigComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
bool v8 = base.Configuration.General.AutoStepRefreshEnabled;
|
bool v9 = base.Configuration.General.AutoStepRefreshEnabled;
|
||||||
if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v8))
|
if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v9))
|
||||||
{
|
{
|
||||||
base.Configuration.General.AutoStepRefreshEnabled = v8;
|
base.Configuration.General.AutoStepRefreshEnabled = v9;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -262,20 +268,20 @@ internal sealed class GeneralConfigComponent : ConfigComponent
|
||||||
ImGui.Text("This helps resume automated quest completion when interruptions occur.");
|
ImGui.Text("This helps resume automated quest completion when interruptions occur.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
using (ImRaii.Disabled(!v8))
|
using (ImRaii.Disabled(!v9))
|
||||||
{
|
{
|
||||||
ImGui.Indent();
|
ImGui.Indent();
|
||||||
int v9 = base.Configuration.General.AutoStepRefreshDelaySeconds;
|
int v10 = base.Configuration.General.AutoStepRefreshDelaySeconds;
|
||||||
ImGui.SetNextItemWidth(150f);
|
ImGui.SetNextItemWidth(150f);
|
||||||
if (ImGui.SliderInt("Refresh delay (seconds)", ref v9, 10, 180))
|
if (ImGui.SliderInt("Refresh delay (seconds)", ref v10, 10, 180))
|
||||||
{
|
{
|
||||||
base.Configuration.General.AutoStepRefreshDelaySeconds = v9;
|
base.Configuration.General.AutoStepRefreshDelaySeconds = v10;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
Vector4 col = new Vector4(0.7f, 0.7f, 0.7f, 1f);
|
Vector4 col = new Vector4(0.7f, 0.7f, 0.7f, 1f);
|
||||||
ImU8String text2 = new ImU8String(77, 1);
|
ImU8String text2 = new ImU8String(77, 1);
|
||||||
text2.AppendLiteral("Quest steps will refresh automatically after ");
|
text2.AppendLiteral("Quest steps will refresh automatically after ");
|
||||||
text2.AppendFormatted(v9);
|
text2.AppendFormatted(v10);
|
||||||
text2.AppendLiteral(" seconds if no progress is made.");
|
text2.AppendLiteral(" seconds if no progress is made.");
|
||||||
ImGui.TextColored(in col, text2);
|
ImGui.TextColored(in col, text2);
|
||||||
ImGui.Unindent();
|
ImGui.Unindent();
|
||||||
|
|
@ -283,16 +289,16 @@ internal sealed class GeneralConfigComponent : ConfigComponent
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.Text("Priority Quest Management");
|
ImGui.Text("Priority Quest Management");
|
||||||
bool v10 = base.Configuration.General.ClearPriorityQuestsOnLogout;
|
bool v11 = base.Configuration.General.ClearPriorityQuestsOnLogout;
|
||||||
if (ImGui.Checkbox("Clear priority quests on character logout", ref v10))
|
if (ImGui.Checkbox("Clear priority quests on character logout", ref v11))
|
||||||
{
|
{
|
||||||
base.Configuration.General.ClearPriorityQuestsOnLogout = v10;
|
base.Configuration.General.ClearPriorityQuestsOnLogout = v11;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
bool v11 = base.Configuration.General.ClearPriorityQuestsOnCompletion;
|
bool v12 = base.Configuration.General.ClearPriorityQuestsOnCompletion;
|
||||||
if (ImGui.Checkbox("Remove priority quests when completed", ref v11))
|
if (ImGui.Checkbox("Remove priority quests when completed", ref v12))
|
||||||
{
|
{
|
||||||
base.Configuration.General.ClearPriorityQuestsOnCompletion = v11;
|
base.Configuration.General.ClearPriorityQuestsOnCompletion = v12;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ internal sealed class PluginConfigComponent : ConfigComponent
|
||||||
|
|
||||||
private static readonly IReadOnlyList<PluginInfo> RequiredPlugins = new global::_003C_003Ez__ReadOnlyArray<PluginInfo>(new PluginInfo[3]
|
private static readonly IReadOnlyList<PluginInfo> RequiredPlugins = new global::_003C_003Ez__ReadOnlyArray<PluginInfo>(new PluginInfo[3]
|
||||||
{
|
{
|
||||||
new PluginInfo("vnavmesh", "vnavmesh", "vnavmesh handles the navigation within a zone, moving\nyour character to the next quest-related objective.", new Uri("https://github.com/awgil/ffxiv_navmesh/"), new Uri("https://puni.sh/api/repository/veyn")),
|
|
||||||
new PluginInfo("Lifestream", "Lifestream", "Used to travel to aethernet shards in cities.", new Uri("https://github.com/NightmareXIV/Lifestream"), new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")),
|
new PluginInfo("Lifestream", "Lifestream", "Used to travel to aethernet shards in cities.", new Uri("https://github.com/NightmareXIV/Lifestream"), new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")),
|
||||||
new PluginInfo("TextAdvance", "TextAdvance", "Automatically accepts and turns in quests, skips cutscenes\nand dialogue.", new Uri("https://github.com/NightmareXIV/TextAdvance"), new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json"))
|
new PluginInfo("TextAdvance", "TextAdvance", "Automatically accepts and turns in quests, skips cutscenes\nand dialogue.", new Uri("https://github.com/NightmareXIV/TextAdvance"), new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")),
|
||||||
|
new PluginInfo("vnavmesh", "vnavmesh", "vnavmesh handles the navigation within a zone, moving\nyour character to the next quest-related objective.", new Uri("https://github.com/awgil/ffxiv_navmesh/"), new Uri("https://puni.sh/api/repository/veyn"))
|
||||||
});
|
});
|
||||||
|
|
||||||
private static readonly ReadOnlyDictionary<Configuration.ECombatModule, PluginInfo> CombatPlugins = new Dictionary<Configuration.ECombatModule, PluginInfo>
|
private static readonly ReadOnlyDictionary<Configuration.ECombatModule, PluginInfo> CombatPlugins = new Dictionary<Configuration.ECombatModule, PluginInfo>
|
||||||
|
|
@ -36,13 +36,13 @@ internal sealed class PluginConfigComponent : ConfigComponent
|
||||||
Questionable.Configuration.ECombatModule.BossMod,
|
Questionable.Configuration.ECombatModule.BossMod,
|
||||||
new PluginInfo("Boss Mod (VBM)", "BossMod", string.Empty, new Uri("https://github.com/awgil/ffxiv_bossmod"), new Uri("https://puni.sh/api/repository/veyn"))
|
new PluginInfo("Boss Mod (VBM)", "BossMod", string.Empty, new Uri("https://github.com/awgil/ffxiv_bossmod"), new Uri("https://puni.sh/api/repository/veyn"))
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Questionable.Configuration.ECombatModule.WrathCombo,
|
|
||||||
new PluginInfo("Wrath Combo", "WrathCombo", string.Empty, new Uri("https://github.com/PunishXIV/WrathCombo"), new Uri("https://puni.sh/api/plugins"))
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Questionable.Configuration.ECombatModule.RotationSolverReborn,
|
Questionable.Configuration.ECombatModule.RotationSolverReborn,
|
||||||
new PluginInfo("Rotation Solver Reborn", "RotationSolver", string.Empty, new Uri("https://github.com/FFXIV-CombatReborn/RotationSolverReborn"), new Uri("https://raw.githubusercontent.com/FFXIV-CombatReborn/CombatRebornRepo/main/pluginmaster.json"))
|
new PluginInfo("Rotation Solver Reborn", "RotationSolver", string.Empty, new Uri("https://github.com/FFXIV-CombatReborn/RotationSolverReborn"), new Uri("https://raw.githubusercontent.com/FFXIV-CombatReborn/CombatRebornRepo/main/pluginmaster.json"))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Questionable.Configuration.ECombatModule.WrathCombo,
|
||||||
|
new PluginInfo("Wrath Combo", "WrathCombo", string.Empty, new Uri("https://github.com/PunishXIV/WrathCombo"), new Uri("https://puni.sh/api/plugins"))
|
||||||
}
|
}
|
||||||
}.AsReadOnly();
|
}.AsReadOnly();
|
||||||
|
|
||||||
|
|
@ -66,9 +66,10 @@ internal sealed class PluginConfigComponent : ConfigComponent
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_uiUtils = uiUtils;
|
_uiUtils = uiUtils;
|
||||||
_commandManager = commandManager;
|
_commandManager = commandManager;
|
||||||
PluginInfo[] obj = new PluginInfo[5]
|
PluginInfo[] obj = new PluginInfo[6]
|
||||||
{
|
{
|
||||||
new PluginInfo("Artisan", "Artisan", "Handles automatic crafting for quests that require\ncrafted items.", new Uri("https://github.com/PunishXIV/Artisan"), new Uri("https://puni.sh/api/plugins")),
|
new PluginInfo("Artisan", "Artisan", "Handles automatic crafting for quests that require\ncrafted items.", new Uri("https://github.com/PunishXIV/Artisan"), new Uri("https://puni.sh/api/plugins")),
|
||||||
|
new PluginInfo("AutoDuty", "AutoDuty", "Automatically handles dungeon and trial completion during\nMain Scenario Quest progression.", new Uri("https://github.com/erdelf/AutoDuty"), new Uri("https://puni.sh/api/repository/erdelf")),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|
@ -82,7 +83,8 @@ internal sealed class PluginConfigComponent : ConfigComponent
|
||||||
Span<PluginDetailInfo> span = CollectionsMarshal.AsSpan(list);
|
Span<PluginDetailInfo> span = CollectionsMarshal.AsSpan(list);
|
||||||
int index = 0;
|
int index = 0;
|
||||||
span[index] = new PluginDetailInfo("'Sniper no sniping' enabled", "Automatically completes sniping tasks introduced in Stormblood", () => automatonIpc.IsAutoSnipeEnabled);
|
span[index] = new PluginDetailInfo("'Sniper no sniping' enabled", "Automatically completes sniping tasks introduced in Stormblood", () => automatonIpc.IsAutoSnipeEnabled);
|
||||||
obj[1] = new PluginInfo("CBT (formerly known as Automaton)", "Automaton", "Automaton is a collection of automation-related tweaks.", websiteUri, dalamudRepositoryUri, "/cbt", list);
|
obj[2] = new PluginInfo("CBT (formerly known as Automaton)", "Automaton", "Automaton is a collection of automation-related tweaks.", websiteUri, dalamudRepositoryUri, "/cbt", list);
|
||||||
|
obj[3] = new PluginInfo("NotificationMaster", "NotificationMaster", "Sends a configurable out-of-game notification if a quest\nrequires manual actions.", new Uri("https://github.com/NightmareXIV/NotificationMaster"), null);
|
||||||
Uri websiteUri2 = new Uri("https://github.com/PunishXIV/PandorasBox");
|
Uri websiteUri2 = new Uri("https://github.com/PunishXIV/PandorasBox");
|
||||||
Uri dalamudRepositoryUri2 = new Uri("https://puni.sh/api/plugins");
|
Uri dalamudRepositoryUri2 = new Uri("https://puni.sh/api/plugins");
|
||||||
index = 1;
|
index = 1;
|
||||||
|
|
@ -91,9 +93,8 @@ internal sealed class PluginConfigComponent : ConfigComponent
|
||||||
span = CollectionsMarshal.AsSpan(list2);
|
span = CollectionsMarshal.AsSpan(list2);
|
||||||
num = 0;
|
num = 0;
|
||||||
span[num] = new PluginDetailInfo("'Auto Active Time Maneuver' enabled", "Automatically completes active time maneuvers in\nsingle player instances, trials and raids\"", () => pandorasBoxIpc.IsAutoActiveTimeManeuverEnabled);
|
span[num] = new PluginDetailInfo("'Auto Active Time Maneuver' enabled", "Automatically completes active time maneuvers in\nsingle player instances, trials and raids\"", () => pandorasBoxIpc.IsAutoActiveTimeManeuverEnabled);
|
||||||
obj[2] = new PluginInfo("Pandora's Box", "PandorasBox", "Pandora's Box is a collection of tweaks.", websiteUri2, dalamudRepositoryUri2, "/pandora", list2);
|
obj[4] = new PluginInfo("Pandora's Box", "PandorasBox", "Pandora's Box is a collection of tweaks.", websiteUri2, dalamudRepositoryUri2, "/pandora", list2);
|
||||||
obj[3] = new PluginInfo("QuestMap", "QuestMap", "Displays quest objectives and markers on the map for\nbetter navigation and tracking.", new Uri("https://github.com/rreminy/QuestMap"), null);
|
obj[5] = new PluginInfo("QuestMap", "QuestMap", "Displays quest objectives and markers on the map for\nbetter navigation and tracking.", new Uri("https://github.com/rreminy/QuestMap"), null);
|
||||||
obj[4] = new PluginInfo("NotificationMaster", "NotificationMaster", "Sends a configurable out-of-game notification if a quest\nrequires manual actions.", new Uri("https://github.com/NightmareXIV/NotificationMaster"), null);
|
|
||||||
_recommendedPlugins = new global::_003C_003Ez__ReadOnlyArray<PluginInfo>(obj);
|
_recommendedPlugins = new global::_003C_003Ez__ReadOnlyArray<PluginInfo>(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,8 +148,8 @@ internal sealed class PluginConfigComponent : ConfigComponent
|
||||||
_pluginInterface.SavePluginConfig(_configuration);
|
_pluginInterface.SavePluginConfig(_configuration);
|
||||||
}
|
}
|
||||||
allRequiredInstalled &= DrawCombatPlugin(Questionable.Configuration.ECombatModule.BossMod, checklistPadding);
|
allRequiredInstalled &= DrawCombatPlugin(Questionable.Configuration.ECombatModule.BossMod, checklistPadding);
|
||||||
allRequiredInstalled &= DrawCombatPlugin(Questionable.Configuration.ECombatModule.WrathCombo, checklistPadding);
|
|
||||||
allRequiredInstalled &= DrawCombatPlugin(Questionable.Configuration.ECombatModule.RotationSolverReborn, checklistPadding);
|
allRequiredInstalled &= DrawCombatPlugin(Questionable.Configuration.ECombatModule.RotationSolverReborn, checklistPadding);
|
||||||
|
allRequiredInstalled &= DrawCombatPlugin(Questionable.Configuration.ECombatModule.WrathCombo, checklistPadding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility.Signatures;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using LLib.GameData;
|
using LLib.GameData;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
@ -84,12 +84,9 @@ internal sealed class GatheringJournalComponent
|
||||||
|
|
||||||
private string _searchText = string.Empty;
|
private string _searchText = string.Empty;
|
||||||
|
|
||||||
[Signature("48 89 5C 24 ?? 57 48 83 EC 20 8B D9 8B F9")]
|
private static bool IsGatheringItemGathered(uint item)
|
||||||
private GetIsGatheringItemGatheredDelegate _getIsGatheringItemGathered;
|
|
||||||
|
|
||||||
private bool IsGatheringItemGathered(uint item)
|
|
||||||
{
|
{
|
||||||
return _getIsGatheringItemGathered((ushort)item) != 0;
|
return QuestManager.IsGatheringItemGathered((ushort)item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GatheringJournalComponent(IDataManager dataManager, IDalamudPluginInterface pluginInterface, UiUtils uiUtils, IGameInteropProvider gameInteropProvider, GatheringPointRegistry gatheringPointRegistry)
|
public GatheringJournalComponent(IDataManager dataManager, IDalamudPluginInterface pluginInterface, UiUtils uiUtils, IGameInteropProvider gameInteropProvider, GatheringPointRegistry gatheringPointRegistry)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ internal sealed class Configuration : IPluginConfiguration
|
||||||
|
|
||||||
public bool AutoStepRefreshEnabled { get; set; } = true;
|
public bool AutoStepRefreshEnabled { get; set; } = true;
|
||||||
|
|
||||||
public int AutoStepRefreshDelaySeconds { get; set; } = 10;
|
public int AutoStepRefreshDelaySeconds { get; set; } = 60;
|
||||||
|
|
||||||
public bool HideSeasonalEventsFromJournalProgress { get; set; }
|
public bool HideSeasonalEventsFromJournalProgress { get; set; }
|
||||||
|
|
||||||
|
|
@ -43,6 +43,8 @@ internal sealed class Configuration : IPluginConfiguration
|
||||||
public bool ClearPriorityQuestsOnCompletion { get; set; }
|
public bool ClearPriorityQuestsOnCompletion { get; set; }
|
||||||
|
|
||||||
public bool ShowChangelogOnUpdate { get; set; } = true;
|
public bool ShowChangelogOnUpdate { get; set; } = true;
|
||||||
|
|
||||||
|
public bool StopOnPlayerInput { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class StopConfiguration
|
internal sealed class StopConfiguration
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue