--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using Dalamud.Game;
+using Dalamud.Game.ClientState.Objects;
+using Dalamud.Game.ClientState.Objects.Types;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.System.Framework;
+using FFXIVClientStructs.FFXIV.Client.System.Memory;
+using FFXIVClientStructs.FFXIV.Client.System.String;
+using Lumina.Excel.GeneratedSheets;
+using Microsoft.Extensions.Logging;
+using Questionable.Model.V1;
+
+namespace Questionable;
+
+internal sealed unsafe class ChatFunctions
+{
+ private delegate void ProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4);
+
+ private readonly ReadOnlyDictionary<EEmote, string> _emoteCommands;
+
+ private readonly GameFunctions _gameFunctions;
+ private readonly ITargetManager _targetManager;
+ private readonly ILogger<ChatFunctions> _logger;
+ private readonly ProcessChatBoxDelegate _processChatBox;
+ private readonly delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString;
+
+ public ChatFunctions(ISigScanner sigScanner, IDataManager dataManager, GameFunctions gameFunctions,
+ ITargetManager targetManager, ILogger<ChatFunctions> logger)
+ {
+ _gameFunctions = gameFunctions;
+ _targetManager = targetManager;
+ _logger = logger;
+ _processChatBox =
+ Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText(Signatures.SendChat));
+ _sanitiseString =
+ (delegate* unmanaged<Utf8String*, int, IntPtr, void>)sigScanner.ScanText(Signatures.SanitiseString);
+
+ _emoteCommands = dataManager.GetExcelSheet<Emote>()!
+ .Where(x => x.RowId > 0)
+ .Where(x => x.TextCommand != null && x.TextCommand.Value != null)
+ .Select(x => (x.RowId, Command: x.TextCommand.Value!.Command?.ToString()))
+ .Where(x => x.Command != null && x.Command.StartsWith('/'))
+ .ToDictionary(x => (EEmote)x.RowId, x => x.Command!)
+ .AsReadOnly();
+ }
+
+ /// <summary>
+ /// <para>
+ /// Send a given message to the chat box. <b>This can send chat to the server.</b>
+ /// </para>
+ /// <para>
+ /// <b>This method is unsafe.</b> This method does no checking on your input and
+ /// may send content to the server that the normal client could not. You must
+ /// verify what you're sending and handle content and length to properly use
+ /// this.
+ /// </para>
+ /// </summary>
+ /// <param name="message">Message to send</param>
+ /// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
+ private void SendMessageUnsafe(byte[] message)
+ {
+ var uiModule = (IntPtr)Framework.Instance()->GetUiModule();
+
+ using var payload = new ChatPayload(message);
+ var mem1 = Marshal.AllocHGlobal(400);
+ Marshal.StructureToPtr(payload, mem1, false);
+
+ _processChatBox(uiModule, mem1, IntPtr.Zero, 0);
+
+ Marshal.FreeHGlobal(mem1);
+ }
+
+ /// <summary>
+ /// <para>
+ /// Send a given message to the chat box. <b>This can send chat to the server.</b>
+ /// </para>
+ /// <para>
+ /// This method is slightly less unsafe than <see cref="SendMessageUnsafe"/>. It
+ /// will throw exceptions for certain inputs that the client can't normally send,
+ /// but it is still possible to make mistakes. Use with caution.
+ /// </para>
+ /// </summary>
+ /// <param name="message">message to send</param>
+ /// <exception cref="ArgumentException">If <paramref name="message"/> is empty, longer than 500 bytes in UTF-8, or contains invalid characters.</exception>
+ /// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
+ private void SendMessage(string message)
+ {
+ _logger.LogDebug("Attempting to send chat message '{Message}'", message);
+ var bytes = Encoding.UTF8.GetBytes(message);
+ if (bytes.Length == 0)
+ throw new ArgumentException("message is empty", nameof(message));
+
+ if (bytes.Length > 500)
+ throw new ArgumentException("message is longer than 500 bytes", nameof(message));
+
+ if (message.Length != SanitiseText(message).Length)
+ throw new ArgumentException("message contained invalid characters", nameof(message));
+
+ SendMessageUnsafe(bytes);
+ }
+
+ /// <summary>
+ /// <para>
+ /// Sanitises a string by removing any invalid input.
+ /// </para>
+ /// <para>
+ /// The result of this method is safe to use with
+ /// <see cref="SendMessage"/>, provided that it is not empty or too
+ /// long.
+ /// </para>
+ /// </summary>
+ /// <param name="text">text to sanitise</param>
+ /// <returns>sanitised text</returns>
+ /// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
+ private string SanitiseText(string text)
+ {
+ var uText = Utf8String.FromString(text);
+
+ _sanitiseString(uText, 0x27F, IntPtr.Zero);
+ var sanitised = uText->ToString();
+
+ uText->Dtor();
+ IMemorySpace.Free(uText);
+
+ return sanitised;
+ }
+
+ public void ExecuteCommand(string command)
+ {
+ if (!command.StartsWith('/'))
+ return;
+
+ SendMessage(command);
+ }
+
+ public void UseEmote(uint dataId, EEmote emote)
+ {
+ GameObject? gameObject = _gameFunctions.FindObjectByDataId(dataId);
+ if (gameObject != null)
+ {
+ _targetManager.Target = gameObject;
+ ExecuteCommand($"{_emoteCommands[emote]} motion");
+ }
+ }
+
+ public void UseEmote(EEmote emote)
+ {
+ ExecuteCommand($"{_emoteCommands[emote]} motion");
+ }
+
+ private static class Signatures
+ {
+ internal const string SendChat = "48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9";
+ internal const string SanitiseString = "E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D";
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ [SuppressMessage("ReSharper", "PrivateFieldCanBeConvertedToLocalVariable")]
+ private readonly struct ChatPayload : IDisposable
+ {
+ [FieldOffset(0)] private readonly IntPtr textPtr;
+
+ [FieldOffset(16)] private readonly ulong textLen;
+
+ [FieldOffset(8)] private readonly ulong unk1;
+
+ [FieldOffset(24)] private readonly ulong unk2;
+
+ internal ChatPayload(byte[] stringBytes)
+ {
+ textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
+ Marshal.Copy(stringBytes, 0, textPtr, stringBytes.Length);
+ Marshal.WriteByte(textPtr + stringBytes.Length, 0);
+
+ textLen = (ulong)(stringBytes.Length + 1);
+
+ unk1 = 64;
+ unk2 = 0;
+ }
+
+ public void Dispose()
+ {
+ Marshal.FreeHGlobal(textPtr);
+ }
+ }
+}
private readonly NavmeshIpc _navmeshIpc;
private readonly IClientState _clientState;
private readonly GameFunctions _gameFunctions;
+ private readonly ChatFunctions _chatFunctions;
private readonly ICondition _condition;
private readonly ILogger<MovementController> _logger;
private CancellationTokenSource? _cancellationTokenSource;
private Task<List<Vector3>>? _pathfindTask;
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, GameFunctions gameFunctions,
- ICondition condition, ILogger<MovementController> logger)
+ ChatFunctions chatFunctions, ICondition condition, ILogger<MovementController> logger)
{
_navmeshIpc = navmeshIpc;
_clientState = clientState;
_gameFunctions = gameFunctions;
+ _chatFunctions = chatFunctions;
_condition = condition;
_logger = logger;
}
if (InputManager.IsAutoRunning())
{
_logger.LogInformation("Turning off auto-move");
- _gameFunctions.ExecuteCommand("/automove off");
+ _chatFunctions.ExecuteCommand("/automove off");
}
Destination = new DestinationData(dataId, to, stopDistance ?? (DefaultStopDistance - 0.2f), fly, sprint,
if (InputManager.IsAutoRunning())
{
_logger.LogInformation("Turning off auto-move [stop]");
- _gameFunctions.ExecuteCommand("/automove off");
+ _chatFunctions.ExecuteCommand("/automove off");
}
}
private readonly Dictionary<ushort, Quest> _quests = new();
- public QuestRegistry(DalamudPluginInterface pluginInterface, IDataManager dataManager, ILogger<QuestRegistry> logger)
+ public QuestRegistry(DalamudPluginInterface pluginInterface, IDataManager dataManager,
+ ILogger<QuestRegistry> logger)
{
_pluginInterface = pluginInterface;
_dataManager = dataManager;
internal static class WaitAtEnd
{
- internal sealed class Factory(IServiceProvider serviceProvider, IClientState clientState, ICondition condition) : ITaskFactory
+ internal sealed class Factory(IServiceProvider serviceProvider, IClientState clientState, ICondition condition)
+ : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
switch (step.InteractionType)
{
case EInteractionType.Combat:
- var notInCombat = new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
- return [
+ var notInCombat =
+ new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
+ return
+ [
serviceProvider.GetRequiredService<WaitDelay>(),
notInCombat,
serviceProvider.GetRequiredService<WaitDelay>(),
if (step.TerritoryId != step.TargetTerritoryId)
{
// interaction moves to a different territory
- waitInteraction = new WaitConditionTask(() => clientState.TerritoryType == step.TargetTerritoryId,
+ waitInteraction = new WaitConditionTask(
+ () => clientState.TerritoryType == step.TargetTerritoryId,
$"Wait(tp to territory: {step.TargetTerritoryId})");
}
else
return false;
}
- logger.LogInformation("Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...", distance, _territoryId);
+ logger.LogInformation(
+ "Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...",
+ distance, _territoryId);
}
else
logger.LogInformation("Want to use mount, trying (in territory {Id})...", _territoryId);
if (condition[ConditionFlag.InFlight])
{
gameFunctions.Unmount();
+ _continueAt = DateTime.Now.AddSeconds(1);
return true;
}
else
_unmountTriggered = gameFunctions.Unmount();
- _continueAt = DateTime.Now.AddSeconds(0.5);
+ _continueAt = DateTime.Now.AddSeconds(1);
return ETaskResult.StillRunning;
}
return true;
}
- logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", AetherCurrentId, DataId);
+ logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", AetherCurrentId,
+ DataId);
return false;
}
=> throw new InvalidOperationException();
}
- internal sealed class UseOnObject(GameFunctions gameFunctions) : AbstractDelayedTask
+ internal sealed class UseOnObject(ChatFunctions chatFunctions) : AbstractDelayedTask
{
public EEmote Emote { get; set; }
public uint DataId { get; set; }
protected override bool StartInternal()
{
- gameFunctions.UseEmote(DataId, Emote);
+ chatFunctions.UseEmote(DataId, Emote);
return true;
}
public override string ToString() => $"Emote({Emote} on {DataId})";
}
- internal sealed class Use(GameFunctions gameFunctions) : AbstractDelayedTask
+ internal sealed class Use(ChatFunctions chatFunctions) : AbstractDelayedTask
{
public EEmote Emote { get; set; }
protected override bool StartInternal()
{
- gameFunctions.UseEmote(Emote);
+ chatFunctions.UseEmote(Emote);
return true;
}
{
_needsUnmount = true;
gameFunctions.Unmount();
- _continueAt = DateTime.Now.AddSeconds(0.5);
+ _continueAt = DateTime.Now.AddSeconds(1);
return true;
}
if (condition[ConditionFlag.Mounted])
{
gameFunctions.Unmount();
- _continueAt = DateTime.Now.AddSeconds(0.5);
+ _continueAt = DateTime.Now.AddSeconds(1);
return ETaskResult.StillRunning;
}
else
ArgumentNullException.ThrowIfNull(step.ChatMessage);
- string? excelString = gameFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key);
+ string? excelString =
+ gameFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key);
ArgumentNullException.ThrowIfNull(excelString);
var unmount = serviceProvider.GetRequiredService<UnmountTask>();
=> throw new InvalidOperationException();
}
- internal sealed class UseChat(GameFunctions gameFunctions) : AbstractDelayedTask
+ internal sealed class UseChat(ChatFunctions chatFunctions) : AbstractDelayedTask
{
public string ChatMessage { get; set; } = null!;
protected override bool StartInternal()
{
- gameFunctions.ExecuteCommand($"/say {ChatMessage}");
+ chatFunctions.ExecuteCommand($"/say {ChatMessage}");
return true;
}
if (itemCount == _itemCount)
{
// TODO Better handling for game-provided errors, i.e. reacting to the 'Could not use' messages. UseItem() is successful in this case (and returns 0)
- logger.LogInformation("Attempted to use vesper bay aetheryte ticket, but it didn't consume an item - reattempting next frame");
+ logger.LogInformation(
+ "Attempted to use vesper bay aetheryte ticket, but it didn't consume an item - reattempting next frame");
_usedItem = false;
return ETaskResult.StillRunning;
}
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
-using System.Runtime.InteropServices;
-using System.Text;
using Dalamud.Game;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
-using FFXIVClientStructs.FFXIV.Client.System.Framework;
-using FFXIVClientStructs.FFXIV.Client.System.Memory;
-using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
internal sealed unsafe class GameFunctions
{
- private static class Signatures
- {
- internal const string SendChat = "48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9";
- internal const string SanitiseString = "E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D";
- }
-
- private delegate void ProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4);
-
- private readonly ProcessChatBoxDelegate _processChatBox;
- private readonly delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString;
private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet;
- private readonly ReadOnlyDictionary<EEmote, string> _emoteCommands;
private readonly ReadOnlyDictionary<uint, ushort> _contentFinderConditionToContentId;
private readonly IDataManager _dataManager;
private readonly Configuration _configuration;
private readonly ILogger<GameFunctions> _logger;
- public GameFunctions(IDataManager dataManager, IObjectTable objectTable, ISigScanner sigScanner,
- ITargetManager targetManager, ICondition condition, IClientState clientState, QuestRegistry questRegistry,
- IGameGui gameGui, Configuration configuration, ILogger<GameFunctions> logger)
+ public GameFunctions(IDataManager dataManager,
+ IObjectTable objectTable,
+ ITargetManager targetManager,
+ ICondition condition,
+ IClientState clientState,
+ QuestRegistry questRegistry,
+ IGameGui gameGui,
+ Configuration configuration,
+ ILogger<GameFunctions> logger)
{
_dataManager = dataManager;
_objectTable = objectTable;
_gameGui = gameGui;
_configuration = configuration;
_logger = logger;
- _processChatBox =
- Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText(Signatures.SendChat));
- _sanitiseString =
- (delegate* unmanaged<Utf8String*, int, IntPtr, void>)sigScanner.ScanText(Signatures.SanitiseString);
_territoryToAetherCurrentCompFlgSet = dataManager.GetExcelSheet<TerritoryType>()!
.Where(x => x.RowId > 0)
.Where(x => x.Unknown32 > 0)
.ToDictionary(x => (ushort)x.RowId, x => x.Unknown32)
.AsReadOnly();
- _emoteCommands = dataManager.GetExcelSheet<Emote>()!
- .Where(x => x.RowId > 0)
- .Where(x => x.TextCommand != null && x.TextCommand.Value != null)
- .Select(x => (x.RowId, Command: x.TextCommand.Value!.Command?.ToString()))
- .Where(x => x.Command != null && x.Command.StartsWith('/'))
- .ToDictionary(x => (EEmote)x.RowId, x => x.Command!)
- .AsReadOnly();
_contentFinderConditionToContentId = dataManager.GetExcelSheet<ContentFinderCondition>()!
.Where(x => x.RowId > 0 && x.Content > 0)
.ToDictionary(x => x.RowId, x => x.Content)
public bool TeleportAetheryte(uint aetheryteId)
{
+ _logger.LogDebug("Attempting to teleport to aetheryte {AetheryteId}", aetheryteId);
if (IsAetheryteUnlocked(aetheryteId, out var subIndex))
{
if (aetheryteId == PlayerState.Instance()->HomeAetheryteId &&
{
ReturnRequestedAt = DateTime.Now;
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 8))
+ {
+ _logger.LogInformation("Using 'return' for home aetheryte");
return true;
+ }
}
if (ActionManager.Instance()->GetActionStatus(ActionType.Action, 5) == 0)
+ {
// fallback if return isn't available or (more likely) on a different aetheryte
+ _logger.LogInformation("Teleporting to aetheryte {AetheryteId}", aetheryteId);
return Telepo.Instance()->Teleport(aetheryteId, subIndex);
+ }
}
return false;
playerState->IsAetherCurrentUnlocked(aetherCurrentId);
}
- public void ExecuteCommand(string command)
- {
- if (!command.StartsWith('/'))
- return;
-
- SendMessage(command);
- }
-
- #region SendMessage
-
- /// <summary>
- /// <para>
- /// Send a given message to the chat box. <b>This can send chat to the server.</b>
- /// </para>
- /// <para>
- /// <b>This method is unsafe.</b> This method does no checking on your input and
- /// may send content to the server that the normal client could not. You must
- /// verify what you're sending and handle content and length to properly use
- /// this.
- /// </para>
- /// </summary>
- /// <param name="message">Message to send</param>
- /// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
- private void SendMessageUnsafe(byte[] message)
- {
- var uiModule = (IntPtr)Framework.Instance()->GetUiModule();
-
- using var payload = new ChatPayload(message);
- var mem1 = Marshal.AllocHGlobal(400);
- Marshal.StructureToPtr(payload, mem1, false);
-
- _processChatBox(uiModule, mem1, IntPtr.Zero, 0);
-
- Marshal.FreeHGlobal(mem1);
- }
-
- /// <summary>
- /// <para>
- /// Send a given message to the chat box. <b>This can send chat to the server.</b>
- /// </para>
- /// <para>
- /// This method is slightly less unsafe than <see cref="SendMessageUnsafe"/>. It
- /// will throw exceptions for certain inputs that the client can't normally send,
- /// but it is still possible to make mistakes. Use with caution.
- /// </para>
- /// </summary>
- /// <param name="message">message to send</param>
- /// <exception cref="ArgumentException">If <paramref name="message"/> is empty, longer than 500 bytes in UTF-8, or contains invalid characters.</exception>
- /// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
- public void SendMessage(string message)
- {
- var bytes = Encoding.UTF8.GetBytes(message);
- if (bytes.Length == 0)
- {
- throw new ArgumentException("message is empty", nameof(message));
- }
-
- if (bytes.Length > 500)
- {
- throw new ArgumentException("message is longer than 500 bytes", nameof(message));
- }
-
- if (message.Length != SanitiseText(message).Length)
- {
- throw new ArgumentException("message contained invalid characters", nameof(message));
- }
-
- SendMessageUnsafe(bytes);
- }
-
- /// <summary>
- /// <para>
- /// Sanitises a string by removing any invalid input.
- /// </para>
- /// <para>
- /// The result of this method is safe to use with
- /// <see cref="SendMessage"/>, provided that it is not empty or too
- /// long.
- /// </para>
- /// </summary>
- /// <param name="text">text to sanitise</param>
- /// <returns>sanitised text</returns>
- /// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
- public string SanitiseText(string text)
- {
- var uText = Utf8String.FromString(text);
-
- _sanitiseString(uText, 0x27F, IntPtr.Zero);
- var sanitised = uText->ToString();
-
- uText->Dtor();
- IMemorySpace.Free(uText);
-
- return sanitised;
- }
-
- [StructLayout(LayoutKind.Explicit)]
- [SuppressMessage("ReSharper", "PrivateFieldCanBeConvertedToLocalVariable")]
- private readonly struct ChatPayload : IDisposable
- {
- [FieldOffset(0)] private readonly IntPtr textPtr;
-
- [FieldOffset(16)] private readonly ulong textLen;
-
- [FieldOffset(8)] private readonly ulong unk1;
-
- [FieldOffset(24)] private readonly ulong unk2;
-
- internal ChatPayload(byte[] stringBytes)
- {
- textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
- Marshal.Copy(stringBytes, 0, textPtr, stringBytes.Length);
- Marshal.WriteByte(textPtr + stringBytes.Length, 0);
-
- textLen = (ulong)(stringBytes.Length + 1);
-
- unk1 = 64;
- unk2 = 0;
- }
-
- public void Dispose()
- {
- Marshal.FreeHGlobal(textPtr);
- }
- }
-
- #endregion
-
public GameObject? FindObjectByDataId(uint dataId)
{
foreach (var gameObject in _objectTable)
return false;
}
- public void UseEmote(uint dataId, EEmote emote)
- {
- GameObject? gameObject = FindObjectByDataId(dataId);
- if (gameObject != null)
- {
- _targetManager.Target = gameObject;
- ExecuteCommand($"{_emoteCommands[emote]} motion");
- }
- }
-
- public void UseEmote(EEmote emote)
- {
- ExecuteCommand($"{_emoteCommands[emote]} motion");
- }
-
public bool IsObjectAtPosition(uint dataId, Vector3 position, float distance)
{
GameObject? gameObject = FindObjectByDataId(dataId);
{
if (ActionManager.Instance()->GetActionStatus(ActionType.Mount, _configuration.General.MountId) == 0)
{
+ _logger.LogDebug("Attempting to use preferred mount...");
if (ActionManager.Instance()->UseAction(ActionType.Mount, _configuration.General.MountId))
{
_logger.LogInformation("Using preferred mount");
{
if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 9) == 0)
{
+ _logger.LogDebug("Attempting to use mount roulette...");
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 9))
{
_logger.LogInformation("Using mount roulette");
if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0)
{
- _logger.LogInformation("Unmounting...");
- return ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
+ _logger.LogDebug("Attempting to unmount...");
+ if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23))
+ {
+ _logger.LogInformation("Unmounted");
+ return true;
+ }
+
+ return false;
}
else
{
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
- <Version>0.18</Version>
+ <Version>0.19</Version>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Dalamud.Extensions.MicrosoftLogging" Version="4.0.1" />
+ <PackageReference Include="Dalamud.Extensions.MicrosoftLogging" Version="4.0.1"/>
<PackageReference Include="DalamudPackager" Version="2.1.12"/>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" ExcludeAssets="runtime"/>
- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0"/>
<PackageReference Include="System.Text.Json" Version="8.0.3"/>
</ItemGroup>
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\LLib\LLib.csproj" />
- <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" />
- <ProjectReference Include="..\QuestPaths\QuestPaths.csproj" />
+ <ProjectReference Include="..\LLib\LLib.csproj"/>
+ <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj"/>
+ <ProjectReference Include="..\QuestPaths\QuestPaths.csproj"/>
</ItemGroup>
</Project>
serviceCollection.AddSingleton((Configuration?)pluginInterface.GetPluginConfig() ?? new Configuration());
serviceCollection.AddSingleton<GameFunctions>();
+ serviceCollection.AddSingleton<ChatFunctions>();
serviceCollection.AddSingleton<AetheryteData>();
serviceCollection.AddSingleton<TerritoryData>();
serviceCollection.AddSingleton<NavmeshIpc>();
serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();
serviceCollection.AddTaskWithFactory<UseItem.Factory, UseItem.UseOnGround, UseItem.UseOnObject, UseItem.Use>();
serviceCollection.AddTaskWithFactory<EquipItem.Factory, EquipItem.DoEquip>();
- serviceCollection.AddTaskWithFactory<SinglePlayerDuty.Factory, SinglePlayerDuty.DisableYesAlready, SinglePlayerDuty.RestoreYesAlready>();
+ serviceCollection
+ .AddTaskWithFactory<SinglePlayerDuty.Factory, SinglePlayerDuty.DisableYesAlready,
+ SinglePlayerDuty.RestoreYesAlready>();
serviceCollection
.AddTaskWithFactory<WaitAtEnd.Factory,
private readonly string[] _mountNames;
private readonly string[] _grandCompanyNames =
- ["None (manually pick quest)", "Maelstrom", "Twin Adder"/*, "Immortal Flames"*/];
+ ["None (manually pick quest)", "Maelstrom", "Twin Adder" /*, "Immortal Flames"*/];
[SuppressMessage("Performance", "CA1861", Justification = "One time initialization")]
public ConfigWindow(DalamudPluginInterface pluginInterface, Configuration configuration, IDataManager dataManager)
private readonly MovementController _movementController;
private readonly QuestController _questController;
private readonly GameFunctions _gameFunctions;
+ private readonly ChatFunctions _chatFunctions;
private readonly IClientState _clientState;
private readonly IFramework _framework;
private readonly ITargetManager _targetManager;
MovementController movementController,
QuestController questController,
GameFunctions gameFunctions,
+ ChatFunctions chatFunctions,
IClientState clientState,
IFramework framework,
ITargetManager targetManager,
_movementController = movementController;
_questController = questController;
_gameFunctions = gameFunctions;
+ _chatFunctions = chatFunctions;
_clientState = clientState;
_framework = framework;
_targetManager = targetManager;
if (ImGui.Button("Move to Flag"))
{
_movementController.Destination = null;
- _gameFunctions.ExecuteCommand(
+ _chatFunctions.ExecuteCommand(
$"/vnav {(_gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}");
}