forked from aly/qstbak
Compare commits
27 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2df81c5d15 | |||
| a9e1cdc5f1 | |||
| 63f975ff4f | |||
| 1cc65e495d | |||
| bb09805213 | |||
| 8461a561d3 | |||
| 5791858984 | |||
| bddffe21ae | |||
| 7ae14cf8b8 | |||
| a4175abacd | |||
| 8a7847ff37 | |||
| ada27cf05b | |||
| 5e1e1decc5 | |||
| 52daefcfd7 | |||
| ca577ad208 | |||
| ca78750ba5 | |||
| 44c67ab71b | |||
| 3e10cbbbf2 | |||
| 5e2d8f648b | |||
| 317800fb39 | |||
| ac85eea230 | |||
| 7177a5440c | |||
| bbc394c386 | |||
| 0336b9c9e9 | |||
| e5b98b3d57 | |||
| 411c0bbe76 | |||
| 98989e8a70 |
246 changed files with 157209 additions and 118389 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,2 +1,5 @@
|
||||||
AssemblyInfo.cs
|
AssemblyInfo.cs
|
||||||
Solution.sln
|
Solution.sln
|
||||||
|
.vs
|
||||||
|
**/bin/
|
||||||
|
**/obj/
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>GatheringPaths</AssemblyName>
|
<AssemblyName>GatheringPaths</AssemblyName>
|
||||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||||
<TargetFramework>netcoreapp9.0</TargetFramework>
|
<TargetFramework>netcoreapp1.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<LangVersion>12.0</LangVersion>
|
<LangVersion>12.0</LangVersion>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -10,6 +10,10 @@ public readonly struct ExtendedBaseParam : IExcelRow<ExtendedBaseParam>
|
||||||
|
|
||||||
public uint RowId => _003Crow_003EP;
|
public uint RowId => _003Crow_003EP;
|
||||||
|
|
||||||
|
public ExcelPage ExcelPage => _003Cpage_003EP;
|
||||||
|
|
||||||
|
public uint RowOffset => _003Coffset_003EP;
|
||||||
|
|
||||||
public BaseParam BaseParam => new BaseParam(_003Cpage_003EP, _003Coffset_003EP, _003Crow_003EP);
|
public BaseParam BaseParam => new BaseParam(_003Cpage_003EP, _003Coffset_003EP, _003Crow_003EP);
|
||||||
|
|
||||||
public unsafe Collection<ushort> EquipSlotCategoryPct => new Collection<ushort>(_003Cpage_003EP, _003Coffset_003EP, _003Coffset_003EP, (delegate*<ExcelPage, uint, uint, uint, ushort>)(&EquipSlotCategoryPctCtor), 23);
|
public unsafe Collection<ushort> EquipSlotCategoryPct => new Collection<ushort>(_003Cpage_003EP, _003Coffset_003EP, _003Coffset_003EP, (delegate*<ExcelPage, uint, uint, uint, ushort>)(&EquipSlotCategoryPctCtor), 23);
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ public sealed class GearStatsCalculator
|
||||||
num2--;
|
num2--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (i == 0 && !CanHaveOffhand.Contains(rowOrDefault.Value.ItemUICategory.RowId))
|
if (i == 0 && !((ReadOnlySpan<uint>)CanHaveOffhand).Contains(rowOrDefault.Value.ItemUICategory.RowId))
|
||||||
{
|
{
|
||||||
num += rowOrDefault.Value.LevelItem.RowId;
|
num += rowOrDefault.Value.LevelItem.RowId;
|
||||||
i++;
|
i++;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>LLib</AssemblyName>
|
<AssemblyName>LLib</AssemblyName>
|
||||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||||
<TargetFramework>netcoreapp9.0</TargetFramework>
|
<TargetFramework>netcoreapp1.0</TargetFramework>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ public static class DataManagerExtensions
|
||||||
|
|
||||||
public static string? GetString<T>(this IDataManager dataManager, string key, IPluginLog? pluginLog) where T : struct, IQuestDialogueText, IExcelRow<T>
|
public static string? GetString<T>(this IDataManager dataManager, string key, IPluginLog? pluginLog) where T : struct, IQuestDialogueText, IExcelRow<T>
|
||||||
{
|
{
|
||||||
string text = dataManager.GetSeString<T>(key)?.WithCertainMacroCodeReplacements();
|
return dataManager.GetSeString<T>(key)?.WithCertainMacroCodeReplacements();
|
||||||
pluginLog?.Verbose($"{typeof(T).Name}.{key} => {text}");
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Regex? GetRegex<T>(this IDataManager dataManager, string key, IPluginLog? pluginLog) where T : struct, IQuestDialogueText, IExcelRow<T>
|
public static Regex? GetRegex<T>(this IDataManager dataManager, string key, IPluginLog? pluginLog) where T : struct, IQuestDialogueText, IExcelRow<T>
|
||||||
|
|
@ -30,9 +28,7 @@ public static class DataManagerExtensions
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
string text = string.Join("", seString.Select((ReadOnlySePayload payload) => (payload.Type == ReadOnlySePayloadType.Text) ? Regex.Escape(payload.ToString()) : "(.*)"));
|
return new Regex(string.Join("", seString.Select((ReadOnlySePayload payload) => (payload.Type == ReadOnlySePayloadType.Text) ? Regex.Escape(payload.ToString()) : "(.*)")));
|
||||||
pluginLog?.Verbose($"{typeof(T).Name}.{key} => /{text}/");
|
|
||||||
return new Regex(text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReadOnlySeString? GetSeString<T>(this IDataManager dataManager, uint rowId, Func<T, ReadOnlySeString?> mapper) where T : struct, IExcelRow<T>
|
public static ReadOnlySeString? GetSeString<T>(this IDataManager dataManager, uint rowId, Func<T, ReadOnlySeString?> mapper) where T : struct, IExcelRow<T>
|
||||||
|
|
@ -49,9 +45,7 @@ public static class DataManagerExtensions
|
||||||
|
|
||||||
public static string? GetString<T>(this IDataManager dataManager, uint rowId, Func<T, ReadOnlySeString?> mapper, IPluginLog? pluginLog = null) where T : struct, IExcelRow<T>
|
public static string? GetString<T>(this IDataManager dataManager, uint rowId, Func<T, ReadOnlySeString?> mapper, IPluginLog? pluginLog = null) where T : struct, IExcelRow<T>
|
||||||
{
|
{
|
||||||
string text = dataManager.GetSeString(rowId, mapper)?.WithCertainMacroCodeReplacements();
|
return dataManager.GetSeString(rowId, mapper)?.WithCertainMacroCodeReplacements();
|
||||||
pluginLog?.Verbose($"{typeof(T).Name}.{rowId} => {text}");
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Regex? GetRegex<T>(this IDataManager dataManager, uint rowId, Func<T, ReadOnlySeString?> mapper, IPluginLog? pluginLog = null) where T : struct, IExcelRow<T>
|
public static Regex? GetRegex<T>(this IDataManager dataManager, uint rowId, Func<T, ReadOnlySeString?> mapper, IPluginLog? pluginLog = null) where T : struct, IExcelRow<T>
|
||||||
|
|
@ -61,9 +55,7 @@ public static class DataManagerExtensions
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Regex regex = seString.ToRegex();
|
return seString.ToRegex();
|
||||||
pluginLog?.Verbose($"{typeof(T).Name}.{rowId} => /{regex}/");
|
|
||||||
return regex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Regex? GetRegex<T>(this T excelRow, Func<T, ReadOnlySeString?> mapper, IPluginLog? pluginLog) where T : struct, IExcelRow<T>
|
public static Regex? GetRegex<T>(this T excelRow, Func<T, ReadOnlySeString?> mapper, IPluginLog? pluginLog) where T : struct, IExcelRow<T>
|
||||||
|
|
@ -74,9 +66,7 @@ public static class DataManagerExtensions
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Regex regex = text.ToRegex();
|
return text.ToRegex();
|
||||||
pluginLog?.Verbose($"{typeof(T).Name}.regex => /{regex}/");
|
|
||||||
return regex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Regex ToRegex(this ReadOnlySeString? text)
|
public static Regex ToRegex(this ReadOnlySeString? text)
|
||||||
|
|
@ -92,7 +82,7 @@ public static class DataManagerExtensions
|
||||||
ReadOnlySePayloadType.Text => payload.ToString(),
|
ReadOnlySePayloadType.Text => payload.ToString(),
|
||||||
ReadOnlySePayloadType.Macro => payload.MacroCode switch
|
ReadOnlySePayloadType.Macro => payload.MacroCode switch
|
||||||
{
|
{
|
||||||
MacroCode.NewLine => "",
|
MacroCode.NewLine => Environment.NewLine,
|
||||||
MacroCode.NonBreakingSpace => " ",
|
MacroCode.NonBreakingSpace => " ",
|
||||||
MacroCode.Hyphen => "-",
|
MacroCode.Hyphen => "-",
|
||||||
MacroCode.SoftHyphen => "",
|
MacroCode.SoftHyphen => "",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ public readonly struct QuestDialogueText : IQuestDialogueText, IExcelRow<QuestDi
|
||||||
{
|
{
|
||||||
public uint RowId => _003Crow_003EP;
|
public uint RowId => _003Crow_003EP;
|
||||||
|
|
||||||
|
public ExcelPage ExcelPage => _003Cpage_003EP;
|
||||||
|
|
||||||
|
public uint RowOffset => _003Coffset_003EP;
|
||||||
|
|
||||||
public ReadOnlySeString Key => _003Cpage_003EP.ReadString(_003Coffset_003EP, _003Coffset_003EP);
|
public ReadOnlySeString Key => _003Cpage_003EP.ReadString(_003Coffset_003EP, _003Coffset_003EP);
|
||||||
|
|
||||||
public ReadOnlySeString Value => _003Cpage_003EP.ReadString(_003Coffset_003EP + 4, _003Coffset_003EP);
|
public ReadOnlySeString Value => _003Cpage_003EP.ReadString(_003Coffset_003EP + 4, _003Coffset_003EP);
|
||||||
|
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>QuestPaths</AssemblyName>
|
<AssemblyName>QuestPaths</AssemblyName>
|
||||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||||
<TargetFramework>netcoreapp9.0</TargetFramework>
|
<TargetFramework>netcoreapp1.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<LangVersion>12.0</LangVersion>
|
<LangVersion>12.0</LangVersion>
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,9 @@
|
||||||
"CompletionQuestVariablesFlags": {
|
"CompletionQuestVariablesFlags": {
|
||||||
"$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json"
|
"$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json"
|
||||||
},
|
},
|
||||||
|
"RequiredQuestVariables": {
|
||||||
|
"$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-requiredvariables.json"
|
||||||
|
},
|
||||||
"Flying": {
|
"Flying": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
@ -257,14 +260,22 @@
|
||||||
"Item": {
|
"Item": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"InInventory": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"NotInInventory": {
|
"NotInInventory": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"BetterOrEqualItemEquipped": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip this step if a better or equal item (by item level) is already equipped"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"MinimumLevel": {
|
"MinimumLevel": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Skip this step if the player level is greater than or equal to this value. Useful for steps that should only be done once at low levels (e.g., early aetheryte attunements).",
|
"description": "Skip this step if the player level is greater than or equal to this value",
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 100
|
"maximum": 100
|
||||||
},
|
},
|
||||||
|
|
@ -277,6 +288,15 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"QuestsCompleted": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": [
|
||||||
|
"number",
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"NotNamePlateIconId": {
|
"NotNamePlateIconId": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
|
@ -1586,6 +1606,11 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"DutyMode": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Overrides the default duty mode for this specific duty. 0 = Duty Support, 1 = Unsync (Solo), 2 = Unsync (Party). If not specified, uses the global configuration setting.",
|
||||||
|
"enum": [0, 1, 2]
|
||||||
|
},
|
||||||
"TestedAutoDutyVersion": {
|
"TestedAutoDutyVersion": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^0\\.\\d+\\.\\d+\\.\\d+$"
|
"pattern": "^0\\.\\d+\\.\\d+\\.\\d+$"
|
||||||
|
|
@ -1605,7 +1630,8 @@
|
||||||
"required": [
|
"required": [
|
||||||
"ContentFinderConditionId",
|
"ContentFinderConditionId",
|
||||||
"Enabled"
|
"Enabled"
|
||||||
]
|
],
|
||||||
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"DataId": {
|
"DataId": {
|
||||||
"type": "null"
|
"type": "null"
|
||||||
|
|
@ -1763,6 +1789,10 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"ItemCount": {
|
"ItemCount": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"RequireHq": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If true, only HQ items will be counted towards completion."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
|
@ -11,20 +12,26 @@ public sealed class StringListOrValueConverter : JsonConverter<List<string>>
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonTokenType.String)
|
if (reader.TokenType == JsonTokenType.String)
|
||||||
{
|
{
|
||||||
return new List<string>(1) { reader.GetString() };
|
int num = 1;
|
||||||
|
List<string> list = new List<string>(num);
|
||||||
|
CollectionsMarshal.SetCount(list, num);
|
||||||
|
Span<string> span = CollectionsMarshal.AsSpan(list);
|
||||||
|
int index = 0;
|
||||||
|
span[index] = reader.GetString();
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
if (reader.TokenType != JsonTokenType.StartArray)
|
if (reader.TokenType != JsonTokenType.StartArray)
|
||||||
{
|
{
|
||||||
throw new JsonException();
|
throw new JsonException();
|
||||||
}
|
}
|
||||||
reader.Read();
|
reader.Read();
|
||||||
List<string> list = new List<string>();
|
List<string> list2 = new List<string>();
|
||||||
while (reader.TokenType != JsonTokenType.EndArray)
|
while (reader.TokenType != JsonTokenType.EndArray)
|
||||||
{
|
{
|
||||||
list.Add(reader.GetString());
|
list2.Add(reader.GetString());
|
||||||
reader.Read();
|
reader.Read();
|
||||||
}
|
}
|
||||||
return list;
|
return list2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, List<string>? value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, List<string>? value, JsonSerializerOptions options)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "https://git.carvel.li//liza/Questionable/raw/refs/heads/main/Questionable.Model/common-classjob.json",
|
"$id": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-classjob.json",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Gladiator",
|
"Gladiator",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "https://git.carvel.li//liza/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json",
|
"$id": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Quest Variables that dictate whether or not this step is skipped: null is don't check, positive values need to be set, negative values need to be unset",
|
"description": "Quest Variables that dictate whether or not this step is skipped: null is don't check, positive values need to be set, negative values need to be unset",
|
||||||
"items": {
|
"items": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-requiredvariables.json",
|
||||||
|
"type": "array",
|
||||||
|
"description": "Certain quests (primarily beast tribes/allied societies) have a RNG element to spawning targets, and the step should be skipped in its entirety if none of the sets below match",
|
||||||
|
"minItems": 6,
|
||||||
|
"maxItems": 6,
|
||||||
|
"items": {
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": [
|
||||||
|
"number",
|
||||||
|
"object"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"High": {
|
||||||
|
"type": [
|
||||||
|
"number",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 15
|
||||||
|
},
|
||||||
|
"Low": {
|
||||||
|
"type": [
|
||||||
|
"number",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "https://git.carvel.li//liza/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json",
|
"$id": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Position in the world",
|
"description": "Position in the world",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Questionable.Model.Questing.Converter;
|
|
||||||
|
|
||||||
public sealed class ElementIdConverter : JsonConverter<ElementId>
|
|
||||||
{
|
|
||||||
public override ElementId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
if (reader.TokenType == JsonTokenType.Number)
|
|
||||||
return new QuestId(reader.GetUInt16());
|
|
||||||
else
|
|
||||||
return ElementId.FromString(reader.GetString() ?? throw new JsonException());
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, ElementId value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
writer.WriteStringValue(value.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Questionable.Model.Questing.Converter;
|
|
||||||
|
|
||||||
public sealed class ElementIdListConverter : JsonConverter<List<ElementId>>
|
|
||||||
{
|
|
||||||
public override List<ElementId> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
if (reader.TokenType != JsonTokenType.StartArray)
|
|
||||||
throw new JsonException();
|
|
||||||
|
|
||||||
reader.Read();
|
|
||||||
|
|
||||||
List<ElementId> values = [];
|
|
||||||
while (reader.TokenType != JsonTokenType.EndArray)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (reader.TokenType == JsonTokenType.Number)
|
|
||||||
values.Add(new QuestId(reader.GetUInt16()));
|
|
||||||
else
|
|
||||||
values.Add(ElementId.FromString(reader.GetString() ?? throw new JsonException()));
|
|
||||||
|
|
||||||
reader.Read();
|
|
||||||
}
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, List<ElementId> value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
writer.WriteStartArray();
|
|
||||||
foreach (ElementId elementId in value)
|
|
||||||
{
|
|
||||||
writer.WriteStringValue(elementId.ToString());
|
|
||||||
}
|
|
||||||
writer.WriteEndArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Questionable.Model.Questing.Converter;
|
|
||||||
|
|
||||||
public sealed class QuestWorkConfigConverter : JsonConverter<QuestWorkValue>
|
|
||||||
{
|
|
||||||
public override QuestWorkValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
if (reader.TokenType == JsonTokenType.Number)
|
|
||||||
return new QuestWorkValue(reader.GetByte());
|
|
||||||
|
|
||||||
if (reader.TokenType != JsonTokenType.StartObject)
|
|
||||||
throw new JsonException();
|
|
||||||
|
|
||||||
byte? high = null, low = null;
|
|
||||||
EQuestWorkMode mode = EQuestWorkMode.Bitwise;
|
|
||||||
while (reader.Read())
|
|
||||||
{
|
|
||||||
switch (reader.TokenType)
|
|
||||||
{
|
|
||||||
case JsonTokenType.PropertyName:
|
|
||||||
string? propertyName = reader.GetString();
|
|
||||||
if (propertyName == null || !reader.Read())
|
|
||||||
throw new JsonException();
|
|
||||||
|
|
||||||
switch (propertyName)
|
|
||||||
{
|
|
||||||
case nameof(QuestWorkValue.High):
|
|
||||||
high = reader.GetByte();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case nameof(QuestWorkValue.Low):
|
|
||||||
low = reader.GetByte();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case nameof(QuestWorkValue.Mode):
|
|
||||||
mode = new QuestWorkModeConverter().Read(ref reader, typeof(EQuestWorkMode), options);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new JsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JsonTokenType.EndObject:
|
|
||||||
return new QuestWorkValue(high, low, mode);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new JsonException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new JsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, QuestWorkValue value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
writer.WriteStringValue(value.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,4 +11,6 @@ public class DutyOptions
|
||||||
public bool LowPriority { get; set; }
|
public bool LowPriority { get; set; }
|
||||||
|
|
||||||
public List<string> Notes { get; set; } = new List<string>();
|
public List<string> Notes { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
public EDutyMode? DutyMode { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Questionable.Model.Questing;
|
||||||
|
|
||||||
|
public enum EDutyMode
|
||||||
|
{
|
||||||
|
Support,
|
||||||
|
UnsyncSolo,
|
||||||
|
UnsyncParty
|
||||||
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ public abstract class ElementId : IComparable<ElementId>, IEquatable<ElementId>
|
||||||
if (value.StartsWith("A"))
|
if (value.StartsWith("A"))
|
||||||
{
|
{
|
||||||
value = value.Substring(1);
|
value = value.Substring(1);
|
||||||
string[] array = value.Split(new char[1] { 'x' });
|
string[] array = value.Split('x');
|
||||||
if (array.Length == 2)
|
if (array.Length == 2)
|
||||||
{
|
{
|
||||||
return new AlliedSocietyDailyId(byte.Parse(array[0], CultureInfo.InvariantCulture), byte.Parse(array[1], CultureInfo.InvariantCulture));
|
return new AlliedSocietyDailyId(byte.Parse(array[0], CultureInfo.InvariantCulture), byte.Parse(array[1], CultureInfo.InvariantCulture));
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ public sealed class QuestRoot
|
||||||
|
|
||||||
public string? Comment { get; set; }
|
public string? Comment { get; set; }
|
||||||
|
|
||||||
[JsonIgnore(/*Could not decode attribute arguments.*/)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
public bool? IsSeasonalQuest { get; set; }
|
public bool? IsSeasonalQuest { get; set; }
|
||||||
|
|
||||||
[JsonIgnore(/*Could not decode attribute arguments.*/)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
public DateTime? SeasonalQuestExpiry { get; set; }
|
public DateTime? SeasonalQuestExpiry { get; set; }
|
||||||
|
|
||||||
public List<QuestSequence> QuestSequence { get; set; } = new List<QuestSequence>();
|
public List<QuestSequence> QuestSequence { get; set; } = new List<QuestSequence>();
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ public sealed class QuestStep
|
||||||
|
|
||||||
public ushort TerritoryId { get; set; }
|
public ushort TerritoryId { get; set; }
|
||||||
|
|
||||||
[JsonIgnore(/*Could not decode attribute arguments.*/)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||||
public EInteractionType InteractionType { get; set; }
|
public EInteractionType InteractionType { get; set; }
|
||||||
|
|
||||||
public float? NpcWaitDistance { get; set; }
|
public float? NpcWaitDistance { get; set; }
|
||||||
|
|
@ -68,6 +68,8 @@ public sealed class QuestStep
|
||||||
|
|
||||||
public int? ItemCount { get; set; }
|
public int? ItemCount { get; set; }
|
||||||
|
|
||||||
|
public bool? RequireHq { get; set; }
|
||||||
|
|
||||||
public EEmote? Emote { get; set; }
|
public EEmote? Emote { get; set; }
|
||||||
|
|
||||||
public ChatMessage? ChatMessage { get; set; }
|
public ChatMessage? ChatMessage { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,15 @@ public sealed class QuestWorkValue(byte? high, byte? low, EQuestWorkMode mode)
|
||||||
{
|
{
|
||||||
if (High.HasValue && Low.HasValue)
|
if (High.HasValue && Low.HasValue)
|
||||||
{
|
{
|
||||||
return ((byte)(High << 4).Value + Low).ToString();
|
return ((byte)((High.Value << 4) + Low.Value)).ToString();
|
||||||
}
|
}
|
||||||
if (High.HasValue)
|
if (High.HasValue)
|
||||||
{
|
{
|
||||||
return High + "H";
|
return High.Value + "H";
|
||||||
}
|
}
|
||||||
if (Low.HasValue)
|
if (Low.HasValue)
|
||||||
{
|
{
|
||||||
return Low + "L";
|
return Low.Value + "L";
|
||||||
}
|
}
|
||||||
return "-";
|
return "-";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,8 @@ namespace Questionable.Model.Questing;
|
||||||
public sealed class SkipItemConditions
|
public sealed class SkipItemConditions
|
||||||
{
|
{
|
||||||
public bool NotInInventory { get; set; }
|
public bool NotInInventory { get; set; }
|
||||||
|
|
||||||
|
public bool InInventory { get; set; }
|
||||||
|
|
||||||
|
public bool BetterOrEqualItemEquipped { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,6 @@ public sealed class SkipStepConditions
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("{0}: {1}, {2}: {3}, {4}: {5}, {6}: {7}, {8}: {9}, {10}: {11}, {12}: {13}, {14}: {15}, {16}: {17}, {18}: {19}, {20}: {21}, {22}: {23}, {24}: {25}, {26}: {27}", "Never", Never, "CompletionQuestVariablesFlags", CompletionQuestVariablesFlags, "Flying", Flying, "Chocobo", Chocobo, "Diving", Diving, "NotTargetable", NotTargetable, "InTerritory", string.Join(" ", InTerritory), "NotInTerritory", string.Join(" ", NotInTerritory), "Item", Item, "QuestsAccepted", string.Join(" ", QuestsAccepted), "QuestsCompleted", string.Join(" ", QuestsCompleted), "NotNamePlateIconId", string.Join(" ", NotNamePlateIconId), "NearPosition", NearPosition, "ExtraCondition", ExtraCondition);
|
return $"{"Never"}: {Never}, {"CompletionQuestVariablesFlags"}: {CompletionQuestVariablesFlags}, {"Flying"}: {Flying}, {"Chocobo"}: {Chocobo}, {"Diving"}: {Diving}, {"NotTargetable"}: {NotTargetable}, {"InTerritory"}: {string.Join(" ", InTerritory)}, {"NotInTerritory"}: {string.Join(" ", NotInTerritory)}, {"Item"}: {Item}, {"QuestsAccepted"}: {string.Join(" ", QuestsAccepted)}, {"QuestsCompleted"}: {string.Join(" ", QuestsCompleted)}, {"NotNamePlateIconId"}: {string.Join(" ", NotNamePlateIconId)}, {"NearPosition"}: {NearPosition}, {"ExtraCondition"}: {ExtraCondition}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>Questionable.Model</AssemblyName>
|
<AssemblyName>Questionable.Model</AssemblyName>
|
||||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netcoreapp1.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<LangVersion>12.0</LangVersion>
|
<LangVersion>12.0</LangVersion>
|
||||||
|
|
@ -17,19 +17,14 @@
|
||||||
<None Remove="Questionable.Model.CommonAetheryte" />
|
<None Remove="Questionable.Model.CommonAetheryte" />
|
||||||
<None Remove="Questionable.Model.CommonClassJob" />
|
<None Remove="Questionable.Model.CommonClassJob" />
|
||||||
<None Remove="Questionable.Model.CommonCompletionFlags" />
|
<None Remove="Questionable.Model.CommonCompletionFlags" />
|
||||||
|
<None Remove="Questionable.Model.CommonRequiredVariables" />
|
||||||
<None Remove="Questionable.Model.CommonVector3" />
|
<None Remove="Questionable.Model.CommonVector3" />
|
||||||
<EmbeddedResource Include="Questionable.Model.CommonAethernetShard" LogicalName="Questionable.Model.CommonAethernetShard" />
|
<EmbeddedResource Include="Questionable.Model.CommonAethernetShard" LogicalName="Questionable.Model.CommonAethernetShard" />
|
||||||
<EmbeddedResource Include="Questionable.Model.CommonAetheryte" LogicalName="Questionable.Model.CommonAetheryte" />
|
<EmbeddedResource Include="Questionable.Model.CommonAetheryte" LogicalName="Questionable.Model.CommonAetheryte" />
|
||||||
<EmbeddedResource Include="Questionable.Model.CommonClassJob" LogicalName="Questionable.Model.CommonClassJob" />
|
<EmbeddedResource Include="Questionable.Model.CommonClassJob" LogicalName="Questionable.Model.CommonClassJob" />
|
||||||
<EmbeddedResource Include="Questionable.Model.CommonCompletionFlags" LogicalName="Questionable.Model.CommonCompletionFlags" />
|
<EmbeddedResource Include="Questionable.Model.CommonCompletionFlags" LogicalName="Questionable.Model.CommonCompletionFlags" />
|
||||||
|
<EmbeddedResource Include="Questionable.Model.CommonRequiredVariables" LogicalName="Questionable.Model.CommonRequiredVariables" />
|
||||||
<EmbeddedResource Include="Questionable.Model.CommonVector3" LogicalName="Questionable.Model.CommonVector3" />
|
<EmbeddedResource Include="Questionable.Model.CommonVector3" LogicalName="Questionable.Model.CommonVector3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup />
|
||||||
<Reference Include="System.Numerics.Vectors">
|
|
||||||
<HintPath>C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.32\System.Numerics.Vectors.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System.Text.Json">
|
|
||||||
<HintPath>C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.32\System.Text.Json.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -12,5 +12,7 @@ public static class AssemblyModelLoader
|
||||||
|
|
||||||
public static Stream CommonCompletionFlags => typeof(AssemblyModelLoader).Assembly.GetManifestResourceStream("Questionable.Model.CommonCompletionFlags");
|
public static Stream CommonCompletionFlags => typeof(AssemblyModelLoader).Assembly.GetManifestResourceStream("Questionable.Model.CommonCompletionFlags");
|
||||||
|
|
||||||
|
public static Stream CommonRequiredVariables => typeof(AssemblyModelLoader).Assembly.GetManifestResourceStream("Questionable.Model.CommonRequiredVariables");
|
||||||
|
|
||||||
public static Stream CommonVector3 => typeof(AssemblyModelLoader).Assembly.GetManifestResourceStream("Questionable.Model.CommonVector3");
|
public static Stream CommonVector3 => typeof(AssemblyModelLoader).Assembly.GetManifestResourceStream("Questionable.Model.CommonVector3");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
Questionable/--y__InlineArray2.cs
Normal file
8
Questionable/--y__InlineArray2.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Auto)]
|
||||||
|
[InlineArray(2)]
|
||||||
|
internal struct _003C_003Ey__InlineArray2<T>
|
||||||
|
{
|
||||||
|
}
|
||||||
8
Questionable/--y__InlineArray7.cs
Normal file
8
Questionable/--y__InlineArray7.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Auto)]
|
||||||
|
[InlineArray(7)]
|
||||||
|
internal struct _003C_003Ey__InlineArray7<T>
|
||||||
|
{
|
||||||
|
}
|
||||||
0
Questionable/-PrivateImplementationDetails-.cs
Normal file
0
Questionable/-PrivateImplementationDetails-.cs
Normal file
|
|
@ -29,6 +29,12 @@ internal sealed class ItemUseModule : ICombatModule
|
||||||
|
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
|
|
||||||
|
private bool _itemUsePending;
|
||||||
|
|
||||||
|
private DateTime _itemUsePendingUntil;
|
||||||
|
|
||||||
|
private int _lastKnownItemCount;
|
||||||
|
|
||||||
public ItemUseModule(IServiceProvider serviceProvider, ICondition condition, ILogger<ItemUseModule> logger)
|
public ItemUseModule(IServiceProvider serviceProvider, ICondition condition, ILogger<ItemUseModule> logger)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
|
@ -49,13 +55,19 @@ internal sealed class ItemUseModule : ICombatModule
|
||||||
return _delegate != null;
|
return _delegate != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Start(CombatController.CombatData combatData)
|
public unsafe bool Start(CombatController.CombatData combatData)
|
||||||
{
|
{
|
||||||
if (_delegate.Start(combatData))
|
if (_delegate.Start(combatData))
|
||||||
{
|
{
|
||||||
_combatData = combatData;
|
_combatData = combatData;
|
||||||
_isDoingRotation = true;
|
_isDoingRotation = true;
|
||||||
_continueAt = DateTime.Now;
|
_continueAt = DateTime.Now;
|
||||||
|
if (_combatData?.CombatItemUse != null)
|
||||||
|
{
|
||||||
|
InventoryManager* ptr = InventoryManager.Instance();
|
||||||
|
_lastKnownItemCount = ptr->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0);
|
||||||
|
_itemUsePending = false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -88,16 +100,29 @@ internal sealed class ItemUseModule : ICombatModule
|
||||||
{
|
{
|
||||||
if (_isDoingRotation)
|
if (_isDoingRotation)
|
||||||
{
|
{
|
||||||
if (InventoryManager.Instance()->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) == 0)
|
int inventoryItemCount = InventoryManager.Instance()->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0);
|
||||||
|
if (_itemUsePending)
|
||||||
{
|
{
|
||||||
_isDoingRotation = false;
|
if (DateTime.Now < _itemUsePendingUntil)
|
||||||
_delegate.Stop();
|
{
|
||||||
|
_logger.LogDebug("Item use pending; ignoring temporary inventory count={Count}", inventoryItemCount);
|
||||||
}
|
}
|
||||||
else if (ShouldUseItem(nextTarget))
|
else
|
||||||
|
{
|
||||||
|
_itemUsePending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!_itemUsePending && inventoryItemCount == 0)
|
||||||
{
|
{
|
||||||
_isDoingRotation = false;
|
_isDoingRotation = false;
|
||||||
_delegate.Stop();
|
_delegate.Stop();
|
||||||
_logger.LogInformation("Using item {ItemId}", _combatData.CombatItemUse.ItemId);
|
return;
|
||||||
|
}
|
||||||
|
_lastKnownItemCount = inventoryItemCount;
|
||||||
|
if (ShouldUseItem(nextTarget))
|
||||||
|
{
|
||||||
|
_itemUsePending = true;
|
||||||
|
_itemUsePendingUntil = DateTime.Now.AddSeconds(3.0);
|
||||||
AgentInventoryContext.Instance()->UseItem(_combatData.CombatItemUse.ItemId, InventoryType.Invalid, 0u, 0);
|
AgentInventoryContext.Instance()->UseItem(_combatData.CombatItemUse.ItemId, InventoryType.Invalid, 0u, 0);
|
||||||
_continueAt = DateTime.Now.AddSeconds(2.0);
|
_continueAt = DateTime.Now.AddSeconds(2.0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Questionable.Functions;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class AbandonDutyCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
|
||||||
|
public string CommandName => "abandon-duty";
|
||||||
|
|
||||||
|
public AbandonDutyCommandHandler(GameFunctions gameFunctions)
|
||||||
|
{
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
_gameFunctions.AbandonDuty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
|
using Questionable.Model.Common;
|
||||||
|
using Questionable.Model.Questing.Converter;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class AethernetCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly IDataManager _dataManager;
|
||||||
|
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
|
private readonly AetheryteFunctions _aetheryteFunctions;
|
||||||
|
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
|
public string CommandName => "aethernet";
|
||||||
|
|
||||||
|
public AethernetCommandHandler(IDataManager dataManager, IClientState clientState, AetheryteFunctions aetheryteFunctions, IChatGui chatGui)
|
||||||
|
{
|
||||||
|
_dataManager = dataManager;
|
||||||
|
_clientState = clientState;
|
||||||
|
_aetheryteFunctions = aetheryteFunctions;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
ushort territoryType = _clientState.TerritoryType;
|
||||||
|
Dictionary<EAetheryteLocation, string> values = AethernetShardConverter.Values;
|
||||||
|
AetheryteData aetheryteData = new AetheryteData(_dataManager);
|
||||||
|
HashSet<string> hashSet = new HashSet<string>();
|
||||||
|
Dictionary<string, List<(EAetheryteLocation, string, bool)>> dictionary = new Dictionary<string, List<(EAetheryteLocation, string, bool)>>();
|
||||||
|
EAetheryteLocation key;
|
||||||
|
string value;
|
||||||
|
foreach (KeyValuePair<EAetheryteLocation, string> item3 in values)
|
||||||
|
{
|
||||||
|
item3.Deconstruct(out key, out value);
|
||||||
|
EAetheryteLocation key2 = key;
|
||||||
|
string text = value;
|
||||||
|
if (aetheryteData.TerritoryIds.TryGetValue(key2, out var value2) && value2 == territoryType)
|
||||||
|
{
|
||||||
|
int num = text.IndexOf(']', StringComparison.Ordinal);
|
||||||
|
if (num > 0)
|
||||||
|
{
|
||||||
|
string item = text.Substring(1, num - 1);
|
||||||
|
hashSet.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hashSet.Count == 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print("No aethernet shards found in current zone.", "Questionable", 576);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<EAetheryteLocation, string> item4 in values)
|
||||||
|
{
|
||||||
|
item4.Deconstruct(out key, out value);
|
||||||
|
EAetheryteLocation eAetheryteLocation = key;
|
||||||
|
string text2 = value;
|
||||||
|
int num2 = text2.IndexOf(']', StringComparison.Ordinal);
|
||||||
|
if (num2 <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
string text3 = text2.Substring(1, num2 - 1);
|
||||||
|
if (hashSet.Contains(text3))
|
||||||
|
{
|
||||||
|
if (!dictionary.ContainsKey(text3))
|
||||||
|
{
|
||||||
|
dictionary[text3] = new List<(EAetheryteLocation, string, bool)>();
|
||||||
|
}
|
||||||
|
bool item2 = _aetheryteFunctions.IsAetheryteUnlocked(eAetheryteLocation);
|
||||||
|
dictionary[text3].Add((eAetheryteLocation, text2, item2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<string, List<(EAetheryteLocation, string, bool)>> item5 in dictionary.OrderBy<KeyValuePair<string, List<(EAetheryteLocation, string, bool)>>, string>((KeyValuePair<string, List<(EAetheryteLocation Location, string Name, bool Unlocked)>> x) => x.Key))
|
||||||
|
{
|
||||||
|
item5.Deconstruct(out value, out var value3);
|
||||||
|
string value4 = value;
|
||||||
|
List<(EAetheryteLocation, string, bool)> list = value3;
|
||||||
|
List<(EAetheryteLocation, string, bool)> list2 = list.Where<(EAetheryteLocation, string, bool)>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => x.Unlocked).ToList();
|
||||||
|
List<(EAetheryteLocation, string, bool)> list3 = list.Where<(EAetheryteLocation, string, bool)>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => !x.Unlocked).ToList();
|
||||||
|
_chatGui.Print($"Aethernet Shards in {value4} ({list.Count} total):", "Questionable", 576);
|
||||||
|
_chatGui.Print($" Unlocked: {list2.Count}", "Questionable", 576);
|
||||||
|
_chatGui.Print($" Missing: {list3.Count}", "Questionable", 576);
|
||||||
|
_chatGui.Print("", "Questionable", 576);
|
||||||
|
if (list3.Count > 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print("Missing/Unattuned Aethernet Shards:", "Questionable", 576);
|
||||||
|
foreach (var item6 in list3.OrderBy<(EAetheryteLocation, string, bool), string>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => x.Name))
|
||||||
|
{
|
||||||
|
_chatGui.Print(" " + item6.Item2, "Questionable", 576);
|
||||||
|
}
|
||||||
|
_chatGui.Print("", "Questionable", 576);
|
||||||
|
}
|
||||||
|
if (list2.Count > 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print("Unlocked Aethernet Shards:", "Questionable", 576);
|
||||||
|
foreach (var item7 in list2.OrderBy<(EAetheryteLocation, string, bool), string>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => x.Name))
|
||||||
|
{
|
||||||
|
_chatGui.Print(" " + item7.Item2, "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dictionary.Count > 1)
|
||||||
|
{
|
||||||
|
_chatGui.Print("", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class DebugCommandExecutor
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, IDebugCommandHandler> _handlers;
|
||||||
|
|
||||||
|
public DebugCommandExecutor(IEnumerable<IDebugCommandHandler> handlers)
|
||||||
|
{
|
||||||
|
_handlers = handlers.ToDictionary<IDebugCommandHandler, string>((IDebugCommandHandler h) => h.CommandName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryExecute(string command, string[] arguments)
|
||||||
|
{
|
||||||
|
if (_handlers.TryGetValue(command, out IDebugCommandHandler value))
|
||||||
|
{
|
||||||
|
value.Execute(arguments);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Questionable.Model;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
using Questionable.Windows;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class DebugOverlayCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly DebugOverlay _debugOverlay;
|
||||||
|
|
||||||
|
private readonly QuestRegistry _questRegistry;
|
||||||
|
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
|
public string CommandName => "do";
|
||||||
|
|
||||||
|
public DebugOverlayCommandHandler(DebugOverlay debugOverlay, QuestRegistry questRegistry, IChatGui chatGui)
|
||||||
|
{
|
||||||
|
_debugOverlay = debugOverlay;
|
||||||
|
_questRegistry = questRegistry;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
ElementId elementId;
|
||||||
|
if (!_debugOverlay.DrawConditions())
|
||||||
|
{
|
||||||
|
_chatGui.PrintError("You don't have the debug overlay enabled.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
else if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out elementId) && elementId != null)
|
||||||
|
{
|
||||||
|
if (_questRegistry.TryGetQuest(elementId, out Quest quest))
|
||||||
|
{
|
||||||
|
_debugOverlay.HighlightedQuest = quest.Id;
|
||||||
|
_chatGui.Print($"Set highlighted quest to {elementId} ({quest.Info.Name}).", "Questionable", 576);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_debugOverlay.HighlightedQuest = null;
|
||||||
|
_chatGui.Print("Cleared highlighted quest.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class FestivalsCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
|
public string CommandName => "festivals";
|
||||||
|
|
||||||
|
public FestivalsCommandHandler(IChatGui chatGui)
|
||||||
|
{
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
List<string> list = new List<string>();
|
||||||
|
for (byte b = 0; b < 4; b++)
|
||||||
|
{
|
||||||
|
GameMain.Festival festival = GameMain.Instance()->ActiveFestivals[b];
|
||||||
|
if (festival.Id == 0)
|
||||||
|
{
|
||||||
|
list.Add($"Slot {b}: None");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.Add($"Slot {b}: {festival.Id}({festival.Phase})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_chatGui.Print("Festival slots:", "Questionable", 576);
|
||||||
|
foreach (string item in list)
|
||||||
|
{
|
||||||
|
_chatGui.Print(" " + item, "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal interface IDebugCommandHandler
|
||||||
|
{
|
||||||
|
string CommandName { get; }
|
||||||
|
|
||||||
|
void Execute(string[] arguments);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using Questionable.Functions;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class MountIdCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
|
||||||
|
private readonly IDataManager _dataManager;
|
||||||
|
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
|
public string CommandName => "mountid";
|
||||||
|
|
||||||
|
public MountIdCommandHandler(GameFunctions gameFunctions, IDataManager dataManager, IChatGui chatGui)
|
||||||
|
{
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
_dataManager = dataManager;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
ushort? mountId = _gameFunctions.GetMountId();
|
||||||
|
if (mountId.HasValue)
|
||||||
|
{
|
||||||
|
Mount? rowOrDefault = _dataManager.GetExcelSheet<Mount>().GetRowOrDefault(mountId.Value);
|
||||||
|
_chatGui.Print($"Mount ID: {mountId}, Name: {rowOrDefault?.Singular}, Obtainable: {((rowOrDefault?.Order == -1) ? "No" : "Yes")}", "Questionable", 576);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chatGui.Print("You are not mounted.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Questionable.Functions;
|
||||||
|
using Questionable.Model;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class NextQuestCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly QuestController _questController;
|
||||||
|
|
||||||
|
private readonly QuestRegistry _questRegistry;
|
||||||
|
|
||||||
|
private readonly QuestFunctions _questFunctions;
|
||||||
|
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
|
public string CommandName => "next";
|
||||||
|
|
||||||
|
public NextQuestCommandHandler(QuestController questController, QuestRegistry questRegistry, QuestFunctions questFunctions, IChatGui chatGui)
|
||||||
|
{
|
||||||
|
_questController = questController;
|
||||||
|
_questRegistry = questRegistry;
|
||||||
|
_questFunctions = questFunctions;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null)
|
||||||
|
{
|
||||||
|
Quest quest;
|
||||||
|
if (_questFunctions.IsQuestLocked(elementId))
|
||||||
|
{
|
||||||
|
_chatGui.PrintError($"Quest {elementId} is locked.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
else if (_questRegistry.TryGetQuest(elementId, out quest))
|
||||||
|
{
|
||||||
|
_questController.SetNextQuest(quest);
|
||||||
|
_chatGui.Print($"Set next quest to {elementId} ({quest.Info.Name}).", "Questionable", 576);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_questController.SetNextQuest(null);
|
||||||
|
_chatGui.Print("Cleared next quest.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using Questionable.Functions;
|
||||||
|
using Questionable.Model;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class QuestKillsCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly QuestController _questController;
|
||||||
|
|
||||||
|
private readonly QuestFunctions _questFunctions;
|
||||||
|
|
||||||
|
private readonly IDataManager _dataManager;
|
||||||
|
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
|
public string CommandName => "quest-kills";
|
||||||
|
|
||||||
|
public QuestKillsCommandHandler(QuestController questController, QuestFunctions questFunctions, IDataManager dataManager, IObjectTable objectTable, IChatGui chatGui)
|
||||||
|
{
|
||||||
|
_questController = questController;
|
||||||
|
_questFunctions = questFunctions;
|
||||||
|
_dataManager = dataManager;
|
||||||
|
_objectTable = objectTable;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
(QuestController.QuestProgress, QuestController.ECurrentQuestType)? currentQuestDetails = _questController.CurrentQuestDetails;
|
||||||
|
if (!currentQuestDetails.HasValue)
|
||||||
|
{
|
||||||
|
_chatGui.PrintError("No active quest.", "Questionable", 576);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QuestController.QuestProgress item = currentQuestDetails.Value.Item1;
|
||||||
|
Questionable.Model.Quest quest = item.Quest;
|
||||||
|
QuestProgressInfo questProgressInfo = null;
|
||||||
|
if (quest.Id is QuestId elementId)
|
||||||
|
{
|
||||||
|
questProgressInfo = _questFunctions.GetQuestProgressInfo(elementId);
|
||||||
|
}
|
||||||
|
if (questProgressInfo == null)
|
||||||
|
{
|
||||||
|
_chatGui.PrintError("Unable to retrieve quest progress information.", "Questionable", 576);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QuestSequence questSequence = quest.FindSequence(item.Sequence);
|
||||||
|
if (questSequence == null)
|
||||||
|
{
|
||||||
|
_chatGui.PrintError($"Sequence {item.Sequence} not found for quest {quest.Id}.", "Questionable", 576);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QuestStep questStep = ((item.Step < questSequence.Steps.Count) ? questSequence.Steps[item.Step] : null);
|
||||||
|
if (questStep == null)
|
||||||
|
{
|
||||||
|
_chatGui.PrintError($"Step {item.Step} not found in sequence {item.Sequence}.", "Questionable", 576);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_chatGui.Print($"Quest: {quest.Info.Name} ({quest.Id})", "Questionable", 576);
|
||||||
|
_chatGui.Print($"Sequence: {item.Sequence}, Step: {item.Step}", "Questionable", 576);
|
||||||
|
_chatGui.Print("", "Questionable", 576);
|
||||||
|
_chatGui.Print("Quest Variables: " + string.Join(", ", questProgressInfo.Variables.Select((byte v, int i) => $"[{i}]={v}")), "Questionable", 576);
|
||||||
|
_chatGui.Print("", "Questionable", 576);
|
||||||
|
ExcelSheet<BNpcName> bnpcNameSheet = _dataManager.GetExcelSheet<BNpcName>();
|
||||||
|
HashSet<uint> hashSet = new HashSet<uint>(questStep.KillEnemyDataIds);
|
||||||
|
foreach (ComplexCombatData complexCombatDatum in questStep.ComplexCombatData)
|
||||||
|
{
|
||||||
|
hashSet.Add(complexCombatDatum.DataId);
|
||||||
|
}
|
||||||
|
if (hashSet.Count > 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print($"All Enemy DataIds Found: {hashSet.Count}", "Questionable", 576);
|
||||||
|
foreach (uint item3 in hashSet.OrderBy((uint x) => x))
|
||||||
|
{
|
||||||
|
(string Name, bool Found) tuple = GetEnemyName(item3);
|
||||||
|
var (value, _) = tuple;
|
||||||
|
if (tuple.Found)
|
||||||
|
{
|
||||||
|
_chatGui.Print($" - {value} (DataId: {item3})", "Questionable", 576);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chatGui.Print($" - DataId: {item3}", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_chatGui.Print("", "Questionable", 576);
|
||||||
|
}
|
||||||
|
if (questStep.ComplexCombatData.Count > 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print($"Complex Combat Data Entries: {questStep.ComplexCombatData.Count}", "Questionable", 576);
|
||||||
|
_chatGui.Print("Kill Progress:", "Questionable", 576);
|
||||||
|
if (questStep.ComplexCombatData.Count == 1 && hashSet.Count > 1)
|
||||||
|
{
|
||||||
|
ComplexCombatData complexCombatData = questStep.ComplexCombatData[0];
|
||||||
|
int num = -1;
|
||||||
|
byte? b = null;
|
||||||
|
for (int num2 = 0; num2 < complexCombatData.CompletionQuestVariablesFlags.Count; num2++)
|
||||||
|
{
|
||||||
|
QuestWorkValue questWorkValue = complexCombatData.CompletionQuestVariablesFlags[num2];
|
||||||
|
if (questWorkValue != null && questWorkValue.Low.HasValue)
|
||||||
|
{
|
||||||
|
num = num2;
|
||||||
|
b = questWorkValue.Low;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte b2 = (byte)(((num >= 0 && num < questProgressInfo.Variables.Count) ? questProgressInfo.Variables[num] : 0) & 0xF);
|
||||||
|
string value2 = (b.HasValue ? $" {b2}/{b}" : "");
|
||||||
|
string value3 = ((b.HasValue && b2 >= b) ? "✓" : "○");
|
||||||
|
foreach (uint item4 in hashSet.OrderBy((uint x) => x))
|
||||||
|
{
|
||||||
|
(string Name, bool Found) tuple3 = GetEnemyName(item4);
|
||||||
|
var (value4, _) = tuple3;
|
||||||
|
if (tuple3.Found)
|
||||||
|
{
|
||||||
|
_chatGui.Print($" {value3} Slay {value4}.{value2} (DataId: {item4})", "Questionable", 576);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chatGui.Print($" {value3} Slay enemy.{value2} (DataId: {item4})", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int num3 = 0; num3 < questStep.ComplexCombatData.Count; num3++)
|
||||||
|
{
|
||||||
|
ComplexCombatData complexCombatData2 = questStep.ComplexCombatData[num3];
|
||||||
|
int num4 = -1;
|
||||||
|
byte? b3 = null;
|
||||||
|
bool flag = false;
|
||||||
|
for (int num5 = 0; num5 < complexCombatData2.CompletionQuestVariablesFlags.Count; num5++)
|
||||||
|
{
|
||||||
|
QuestWorkValue questWorkValue2 = complexCombatData2.CompletionQuestVariablesFlags[num5];
|
||||||
|
if (questWorkValue2 != null)
|
||||||
|
{
|
||||||
|
if (questWorkValue2.Low.HasValue)
|
||||||
|
{
|
||||||
|
num4 = num5;
|
||||||
|
b3 = questWorkValue2.Low;
|
||||||
|
flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (questWorkValue2.High.HasValue)
|
||||||
|
{
|
||||||
|
num4 = num5;
|
||||||
|
b3 = questWorkValue2.High;
|
||||||
|
flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte b4 = (byte)((num4 >= 0 && num4 < questProgressInfo.Variables.Count) ? questProgressInfo.Variables[num4] : 0);
|
||||||
|
byte b5 = (flag ? ((byte)(b4 >> 4)) : ((byte)(b4 & 0xF)));
|
||||||
|
string value5;
|
||||||
|
if (complexCombatData2.NameId.HasValue)
|
||||||
|
{
|
||||||
|
BNpcName? bNpcName = bnpcNameSheet?.GetRowOrDefault(complexCombatData2.NameId.Value);
|
||||||
|
value5 = ((!bNpcName.HasValue || string.IsNullOrEmpty(bNpcName.Value.Singular.ToString())) ? "enemy" : bNpcName.Value.Singular.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(string Name, bool Found) tuple5 = GetEnemyName(complexCombatData2.DataId);
|
||||||
|
string item2 = tuple5.Name;
|
||||||
|
value5 = (tuple5.Found ? item2 : "enemy");
|
||||||
|
}
|
||||||
|
string value6 = (b3.HasValue ? $" {b5}/{b3}" : "");
|
||||||
|
string value7 = ((b3.HasValue && b5 >= b3) ? "✓" : "○");
|
||||||
|
string value8 = (complexCombatData2.NameId.HasValue ? $" (DataId: {complexCombatData2.DataId}, NameId: {complexCombatData2.NameId})" : $" (DataId: {complexCombatData2.DataId})");
|
||||||
|
_chatGui.Print($" {value7} Slay {value5}.{value6}{value8}", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_chatGui.Print("", "Questionable", 576);
|
||||||
|
}
|
||||||
|
else if (questStep.KillEnemyDataIds.Count == 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print("No kill enemy data for this step.", "Questionable", 576);
|
||||||
|
_chatGui.Print("", "Questionable", 576);
|
||||||
|
}
|
||||||
|
if (questStep.CompletionQuestVariablesFlags.Count <= 0 || !questStep.CompletionQuestVariablesFlags.Any((QuestWorkValue x) => x != null))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_chatGui.Print("Completion Flags (Debug):", "Questionable", 576);
|
||||||
|
for (int num6 = 0; num6 < questStep.CompletionQuestVariablesFlags.Count; num6++)
|
||||||
|
{
|
||||||
|
QuestWorkValue questWorkValue3 = questStep.CompletionQuestVariablesFlags[num6];
|
||||||
|
if (questWorkValue3 != null)
|
||||||
|
{
|
||||||
|
int num7 = ((num6 < questProgressInfo.Variables.Count) ? questProgressInfo.Variables[num6] : 0);
|
||||||
|
byte b6 = (byte)(num7 >> 4);
|
||||||
|
byte b7 = (byte)(num7 & 0xF);
|
||||||
|
string value9 = (((!questWorkValue3.High.HasValue || questWorkValue3.High == b6) && (!questWorkValue3.Low.HasValue || questWorkValue3.Low == b7)) ? " ✓" : " ✗");
|
||||||
|
_chatGui.Print($" [{num6}] Expected: H={questWorkValue3.High?.ToString(CultureInfo.InvariantCulture) ?? "any"} L={questWorkValue3.Low?.ToString(CultureInfo.InvariantCulture) ?? "any"} | Actual: H={b6.ToString(CultureInfo.InvariantCulture)} L={b7.ToString(CultureInfo.InvariantCulture)}{value9}", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(string Name, bool Found) GetEnemyName(uint dataId)
|
||||||
|
{
|
||||||
|
if (_objectTable.FirstOrDefault((IGameObject x) => x is IBattleNpc battleNpc2 && battleNpc2.BaseId == dataId) is IBattleNpc { NameId: not 0u } battleNpc)
|
||||||
|
{
|
||||||
|
BNpcName? bNpcName2 = bnpcNameSheet?.GetRowOrDefault(battleNpc.NameId);
|
||||||
|
if (bNpcName2.HasValue && !string.IsNullOrEmpty(bNpcName2.Value.Singular.ToString()))
|
||||||
|
{
|
||||||
|
return (Name: bNpcName2.Value.Singular.ToString(), Found: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (Name: string.Empty, Found: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Questionable.Windows;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class SequencesCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly QuestSequenceWindow _questSequenceWindow;
|
||||||
|
|
||||||
|
public string CommandName => "seq";
|
||||||
|
|
||||||
|
public SequencesCommandHandler(QuestSequenceWindow questSequenceWindow)
|
||||||
|
{
|
||||||
|
_questSequenceWindow = questSequenceWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
_questSequenceWindow.ToggleOrUncollapse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Questionable.Windows;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class SetupCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly OneTimeSetupWindow _oneTimeSetupWindow;
|
||||||
|
|
||||||
|
public string CommandName => "setup";
|
||||||
|
|
||||||
|
public SetupCommandHandler(OneTimeSetupWindow oneTimeSetupWindow)
|
||||||
|
{
|
||||||
|
_oneTimeSetupWindow = oneTimeSetupWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
_oneTimeSetupWindow.IsOpenAndUncollapsed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Questionable.Model;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class SimulateQuestCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly QuestController _questController;
|
||||||
|
|
||||||
|
private readonly QuestRegistry _questRegistry;
|
||||||
|
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
|
public string CommandName => "sim";
|
||||||
|
|
||||||
|
public SimulateQuestCommandHandler(QuestController questController, QuestRegistry questRegistry, IChatGui chatGui)
|
||||||
|
{
|
||||||
|
_questController = questController;
|
||||||
|
_questRegistry = questRegistry;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null)
|
||||||
|
{
|
||||||
|
if (_questRegistry.TryGetQuest(elementId, out Quest quest))
|
||||||
|
{
|
||||||
|
byte sequence = 0;
|
||||||
|
int step = 0;
|
||||||
|
if (arguments.Length >= 2 && byte.TryParse(arguments[1], out var result))
|
||||||
|
{
|
||||||
|
QuestSequence questSequence = quest.FindSequence(result);
|
||||||
|
if (questSequence != null)
|
||||||
|
{
|
||||||
|
sequence = questSequence.Sequence;
|
||||||
|
if (arguments.Length >= 3 && int.TryParse(arguments[2], out var result2) && questSequence.FindStep(result2) != null)
|
||||||
|
{
|
||||||
|
step = result2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_questController.SimulateQuest(quest, sequence, step);
|
||||||
|
_chatGui.Print($"Simulating quest {elementId} ({quest.Info.Name}).", "Questionable", 576);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_questController.SimulateQuest(null, 0, 0);
|
||||||
|
_chatGui.Print("Cleared simulated quest.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class TaxiCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly IDataManager _dataManager;
|
||||||
|
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
|
public string CommandName => "taxi";
|
||||||
|
|
||||||
|
public TaxiCommandHandler(IDataManager dataManager, IClientState clientState, IChatGui chatGui)
|
||||||
|
{
|
||||||
|
_dataManager = dataManager;
|
||||||
|
_clientState = clientState;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
List<string> list = new List<string>();
|
||||||
|
ExcelSheet<ChocoboTaxiStand> excelSheet = _dataManager.GetExcelSheet<ChocoboTaxiStand>();
|
||||||
|
UIState* ptr = UIState.Instance();
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
_chatGui.PrintError("UIState is null", "Questionable", 576);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 192; i++)
|
||||||
|
{
|
||||||
|
uint num = (uint)(i + 1179648);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (excelSheet.HasRow(num) && ptr->IsChocoboTaxiStandUnlocked(num))
|
||||||
|
{
|
||||||
|
string value = excelSheet.GetRow(num).PlaceName.ToString();
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
value = "Unknown";
|
||||||
|
}
|
||||||
|
list.Add($"{value} (ID: {i}, Row: 0x{num:X})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_chatGui.Print($"Unlocked taxi stands ({list.Count}):", "Questionable", 576);
|
||||||
|
if (list.Count == 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print(" (No unlocked taxi stands found)", "Questionable", 576);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (string item in list)
|
||||||
|
{
|
||||||
|
_chatGui.Print(" - " + item, "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Questionable.Functions;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.DebugCommands;
|
||||||
|
|
||||||
|
internal sealed class UnlockLinksCommandHandler : IDebugCommandHandler
|
||||||
|
{
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
|
private IReadOnlyList<uint> _previouslyUnlockedUnlockLinks = Array.Empty<uint>();
|
||||||
|
|
||||||
|
public string CommandName => "unlock-links";
|
||||||
|
|
||||||
|
public UnlockLinksCommandHandler(GameFunctions gameFunctions, IChatGui chatGui)
|
||||||
|
{
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(string[] arguments)
|
||||||
|
{
|
||||||
|
IReadOnlyList<uint> unlockLinks = _gameFunctions.GetUnlockLinks();
|
||||||
|
if (unlockLinks.Count >= 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print($"Saved {unlockLinks.Count} unlock links to log.", "Questionable", 576);
|
||||||
|
List<uint> list = unlockLinks.Except(_previouslyUnlockedUnlockLinks).ToList();
|
||||||
|
if (_previouslyUnlockedUnlockLinks.Count > 0 && list.Count > 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print("New unlock links: " + string.Join(", ", list), "Questionable", 576);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chatGui.PrintError("Could not query unlock links.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
_previouslyUnlockedUnlockLinks = unlockLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_previouslyUnlockedUnlockLinks = Array.Empty<uint>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -138,7 +138,7 @@ internal sealed class CraftworksSupplyController : IDisposable
|
||||||
address->Close(fireCallback: true);
|
address->Close(fireCallback: true);
|
||||||
if (addonById->NameString == "BankaCraftworksSupply")
|
if (addonById->NameString == "BankaCraftworksSupply")
|
||||||
{
|
{
|
||||||
_framework.RunOnTick((Action)InteractWithBankaCraftworksSupply, TimeSpan.FromMilliseconds(50L, 0L), 0, default(CancellationToken));
|
_framework.RunOnTick((Action)InteractWithBankaCraftworksSupply, TimeSpan.FromMilliseconds(50L), 0, default(CancellationToken));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,30 @@ internal sealed class CreditsController : IDisposable
|
||||||
|
|
||||||
private static bool _pendingDispose;
|
private static bool _pendingDispose;
|
||||||
|
|
||||||
|
private static int _handledConsecutiveCutscenes;
|
||||||
|
|
||||||
|
private static int _maxConsecutiveSkips = 5;
|
||||||
|
|
||||||
|
private static DateTime _lastHandledAt = DateTime.MinValue;
|
||||||
|
|
||||||
|
private static readonly TimeSpan _reentrancySuppress = TimeSpan.FromMilliseconds(500L);
|
||||||
|
|
||||||
|
private static long _lastHandledAddr;
|
||||||
|
|
||||||
|
private static readonly TimeSpan _consecutiveResetWindow = TimeSpan.FromSeconds(10L);
|
||||||
|
|
||||||
private readonly IAddonLifecycle _addonLifecycle;
|
private readonly IAddonLifecycle _addonLifecycle;
|
||||||
|
|
||||||
|
private readonly Configuration _configuration;
|
||||||
|
|
||||||
private readonly ILogger<CreditsController> _logger;
|
private readonly ILogger<CreditsController> _logger;
|
||||||
|
|
||||||
private static readonly object _lock = new object();
|
private static readonly object _lock = new object();
|
||||||
|
|
||||||
public CreditsController(IAddonLifecycle addonLifecycle, ILogger<CreditsController> logger)
|
public CreditsController(IAddonLifecycle addonLifecycle, Configuration configuration, ILogger<CreditsController> logger)
|
||||||
{
|
{
|
||||||
_addonLifecycle = addonLifecycle;
|
_addonLifecycle = addonLifecycle;
|
||||||
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
|
|
@ -63,70 +78,117 @@ internal sealed class CreditsController : IDisposable
|
||||||
|
|
||||||
private unsafe void CreditScrollPostSetup(AddonEvent type, AddonArgs args)
|
private unsafe void CreditScrollPostSetup(AddonEvent type, AddonArgs args)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Closing Credits sequence scroll post-setup");
|
HandleCutscene(delegate
|
||||||
if (args.Addon.Address == IntPtr.Zero)
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("CreditScrollPostSetup: Addon address is zero, skipping.");
|
_logger.LogInformation("CreditScrollPostSetup: attempting to close credits sequence (scroll).");
|
||||||
return;
|
|
||||||
}
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_registeredLifecycle != null)
|
|
||||||
{
|
|
||||||
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler);
|
|
||||||
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler);
|
|
||||||
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler);
|
|
||||||
_registeredLifecycle = null;
|
|
||||||
_instance = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
||||||
address->FireCallbackInt(-2);
|
address->FireCallbackInt(-2);
|
||||||
|
}, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args)
|
private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Closing Credits sequence post-setup");
|
HandleCutscene(delegate
|
||||||
if (args.Addon.Address == IntPtr.Zero)
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("CreditPostSetup: Addon address is zero, skipping.");
|
_logger.LogInformation("CreditPostSetup: attempting to close credits sequence.");
|
||||||
return;
|
|
||||||
}
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_registeredLifecycle != null)
|
|
||||||
{
|
|
||||||
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler);
|
|
||||||
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler);
|
|
||||||
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler);
|
|
||||||
_registeredLifecycle = null;
|
|
||||||
_instance = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
||||||
address->FireCallbackInt(-2);
|
address->FireCallbackInt(-2);
|
||||||
|
}, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void CreditPlayerPostSetup(AddonEvent type, AddonArgs args)
|
private unsafe void CreditPlayerPostSetup(AddonEvent type, AddonArgs args)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Closing CreditPlayer");
|
HandleCutscene(delegate
|
||||||
if (args.Addon.Address == IntPtr.Zero)
|
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("CreditPlayerPostSetup: attempting to close CreditPlayer.");
|
||||||
|
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
||||||
|
address->Close(fireCallback: true);
|
||||||
|
}, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCutscene(Action nativeAction, AddonArgs args)
|
||||||
|
{
|
||||||
|
if (_configuration.General.CinemaMode)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("HandleCutscene: Cinema Mode enabled, not skipping cutscene.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long num = ((args.Addon.Address == IntPtr.Zero) ? 0 : ((IntPtr)args.Addon.Address).ToInt64());
|
||||||
|
if (num == 0L)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("HandleCutscene: Addon address is zero, skipping.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_lastHandledAt != DateTime.MinValue && DateTime.Now - _lastHandledAt > _consecutiveResetWindow)
|
||||||
|
{
|
||||||
|
if (_handledConsecutiveCutscenes != 0)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("HandleCutscene: long pause detected ({Elapsed}), resetting consecutive counter (was {Was}).", DateTime.Now - _lastHandledAt, _handledConsecutiveCutscenes);
|
||||||
|
}
|
||||||
|
_handledConsecutiveCutscenes = 0;
|
||||||
|
}
|
||||||
|
if (DateTime.Now - _lastHandledAt < _reentrancySuppress && num == _lastHandledAddr)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("HandleCutscene: suppressed re-entrant invocation for same addon (addr=0x{Address:X}).", num);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_handledConsecutiveCutscenes >= _maxConsecutiveSkips)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("HandleCutscene: reached max consecutive skips ({MaxSkips}), unregistering listeners.", _maxConsecutiveSkips);
|
||||||
|
TryUnregisterAndClear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_lastHandledAt = DateTime.Now;
|
||||||
|
_lastHandledAddr = num;
|
||||||
|
_handledConsecutiveCutscenes++;
|
||||||
|
_logger.LogDebug("HandleCutscene: handling cutscene #{Count} (addr=0x{Address:X}).", _handledConsecutiveCutscenes, num);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nativeAction();
|
||||||
|
_logger.LogInformation("HandleCutscene: native action executed for addon at 0x{Address:X}.", num);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.LogError(exception, "HandleCutscene: exception while executing native action for addon at 0x{Address:X}.", num);
|
||||||
|
}
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_handledConsecutiveCutscenes >= _maxConsecutiveSkips && !_deferDisposeUntilCutsceneEnds)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("HandleCutscene: max handled reached and no defer active, unregistering now.");
|
||||||
|
TryUnregisterAndClear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("HandleCutscene: leaving listeners registered (handled {Count}/{Max}).", _handledConsecutiveCutscenes, _maxConsecutiveSkips);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryUnregisterAndClear()
|
||||||
{
|
{
|
||||||
if (_registeredLifecycle != null)
|
if (_registeredLifecycle != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler);
|
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler);
|
||||||
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler);
|
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler);
|
||||||
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler);
|
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler);
|
||||||
|
_logger.LogDebug("TryUnregisterAndClear: listeners unregistered successfully.");
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.LogError(exception, "TryUnregisterAndClear: exception while unregistering listeners.");
|
||||||
|
}
|
||||||
|
}
|
||||||
_registeredLifecycle = null;
|
_registeredLifecycle = null;
|
||||||
_instance = null;
|
_instance = null;
|
||||||
}
|
_handledConsecutiveCutscenes = 0;
|
||||||
}
|
_lastHandledAddr = 0L;
|
||||||
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
_lastHandledAt = DateTime.MinValue;
|
||||||
address->Close(fireCallback: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DeferDisposeUntilCutsceneEnds()
|
public static void DeferDisposeUntilCutsceneEnds()
|
||||||
|
|
@ -181,6 +243,9 @@ internal sealed class CreditsController : IDisposable
|
||||||
_registeredLifecycle = null;
|
_registeredLifecycle = null;
|
||||||
_instance = null;
|
_instance = null;
|
||||||
_pendingDispose = false;
|
_pendingDispose = false;
|
||||||
|
_handledConsecutiveCutscenes = 0;
|
||||||
|
_lastHandledAddr = 0L;
|
||||||
|
_lastHandledAt = DateTime.MinValue;
|
||||||
_logger.LogDebug("CreditsController listeners unregistered and disposed (primary instance).");
|
_logger.LogDebug("CreditsController listeners unregistered and disposed (primary instance).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Dalamud.Game.Addon.Lifecycle;
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||||
|
|
@ -58,6 +57,8 @@ internal sealed class InteractionUiController : IDisposable
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
private readonly ShopController _shopController;
|
private readonly ShopController _shopController;
|
||||||
|
|
||||||
private readonly BossModIpc _bossModIpc;
|
private readonly BossModIpc _bossModIpc;
|
||||||
|
|
@ -84,7 +85,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe InteractionUiController(IAddonLifecycle addonLifecycle, IDataManager dataManager, QuestFunctions questFunctions, AetheryteFunctions aetheryteFunctions, ExcelFunctions excelFunctions, QuestController questController, GatheringPointRegistry gatheringPointRegistry, QuestRegistry questRegistry, QuestData questData, TerritoryData territoryData, IGameGui gameGui, ITargetManager targetManager, IPluginLog pluginLog, IClientState clientState, ShopController shopController, BossModIpc bossModIpc, Configuration configuration, ILogger<InteractionUiController> logger)
|
public unsafe InteractionUiController(IAddonLifecycle addonLifecycle, IDataManager dataManager, QuestFunctions questFunctions, AetheryteFunctions aetheryteFunctions, ExcelFunctions excelFunctions, QuestController questController, GatheringPointRegistry gatheringPointRegistry, QuestRegistry questRegistry, QuestData questData, TerritoryData territoryData, IGameGui gameGui, ITargetManager targetManager, IPluginLog pluginLog, IClientState clientState, IObjectTable objectTable, ShopController shopController, BossModIpc bossModIpc, Configuration configuration, ILogger<InteractionUiController> logger)
|
||||||
{
|
{
|
||||||
_addonLifecycle = addonLifecycle;
|
_addonLifecycle = addonLifecycle;
|
||||||
_dataManager = dataManager;
|
_dataManager = dataManager;
|
||||||
|
|
@ -99,6 +100,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||||
_gameGui = gameGui;
|
_gameGui = gameGui;
|
||||||
_targetManager = targetManager;
|
_targetManager = targetManager;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_objectTable = objectTable;
|
||||||
_shopController = shopController;
|
_shopController = shopController;
|
||||||
_bossModIpc = bossModIpc;
|
_bossModIpc = bossModIpc;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
|
@ -683,7 +685,6 @@ internal sealed class InteractionUiController : IDisposable
|
||||||
if (aetheryte.HasValue)
|
if (aetheryte.HasValue)
|
||||||
{
|
{
|
||||||
EAetheryteLocation valueOrDefault = aetheryte.GetValueOrDefault();
|
EAetheryteLocation valueOrDefault = aetheryte.GetValueOrDefault();
|
||||||
Span<DialogueChoice> span2;
|
|
||||||
Span<DialogueChoice> span;
|
Span<DialogueChoice> span;
|
||||||
switch (_aetheryteFunctions.CanRegisterFreeOrFavoriteAetheryte(valueOrDefault))
|
switch (_aetheryteFunctions.CanRegisterFreeOrFavoriteAetheryte(valueOrDefault))
|
||||||
{
|
{
|
||||||
|
|
@ -693,12 +694,12 @@ internal sealed class InteractionUiController : IDisposable
|
||||||
int num2 = 1 + list.Count;
|
int num2 = 1 + list.Count;
|
||||||
List<DialogueChoice> list3 = new List<DialogueChoice>(num2);
|
List<DialogueChoice> list3 = new List<DialogueChoice>(num2);
|
||||||
CollectionsMarshal.SetCount(list3, num2);
|
CollectionsMarshal.SetCount(list3, num2);
|
||||||
span2 = CollectionsMarshal.AsSpan(list3);
|
Span<DialogueChoice> span3 = CollectionsMarshal.AsSpan(list3);
|
||||||
int num = 0;
|
int num = 0;
|
||||||
span = CollectionsMarshal.AsSpan(list);
|
span = CollectionsMarshal.AsSpan(list);
|
||||||
span.CopyTo(span2.Slice(num, span.Length));
|
span.CopyTo(span3.Slice(num, span.Length));
|
||||||
num += span.Length;
|
num += span.Length;
|
||||||
span2[num] = new DialogueChoice
|
span3[num] = new DialogueChoice
|
||||||
{
|
{
|
||||||
Type = EDialogChoiceType.YesNo,
|
Type = EDialogChoiceType.YesNo,
|
||||||
ExcelSheet = "Addon",
|
ExcelSheet = "Addon",
|
||||||
|
|
@ -716,7 +717,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||||
CollectionsMarshal.SetCount(list2, num);
|
CollectionsMarshal.SetCount(list2, num);
|
||||||
span = CollectionsMarshal.AsSpan(list2);
|
span = CollectionsMarshal.AsSpan(list2);
|
||||||
int num2 = 0;
|
int num2 = 0;
|
||||||
span2 = CollectionsMarshal.AsSpan(list);
|
Span<DialogueChoice> span2 = CollectionsMarshal.AsSpan(list);
|
||||||
span2.CopyTo(span.Slice(num2, span2.Length));
|
span2.CopyTo(span.Slice(num2, span2.Length));
|
||||||
num2 += span2.Length;
|
num2 += span2.Length;
|
||||||
span[num2] = new DialogueChoice
|
span[num2] = new DialogueChoice
|
||||||
|
|
@ -857,7 +858,10 @@ internal sealed class InteractionUiController : IDisposable
|
||||||
{
|
{
|
||||||
_logger.LogTrace("FindTargetTerritoryFromQuestStep (current): {CurrentTerritory}, {TargetTerritory}", questStep.TerritoryId, questStep.TargetTerritoryId);
|
_logger.LogTrace("FindTargetTerritoryFromQuestStep (current): {CurrentTerritory}, {TargetTerritory}", questStep.TerritoryId, questStep.TargetTerritoryId);
|
||||||
}
|
}
|
||||||
if (questStep != null && (questStep.TerritoryId != _clientState.TerritoryType || !questStep.TargetTerritoryId.HasValue) && questStep.InteractionType == EInteractionType.Gather && _gatheringPointRegistry.TryGetGatheringPointId(questStep.ItemsToGather[0].ItemId, ((EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId).GetValueOrDefault(), out GatheringPointId gatheringPointId) && _gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot gatheringRoot))
|
if (questStep != null && (questStep.TerritoryId != _clientState.TerritoryType || !questStep.TargetTerritoryId.HasValue) && questStep.InteractionType == EInteractionType.Gather)
|
||||||
|
{
|
||||||
|
IGameObject gameObject = _objectTable[0];
|
||||||
|
if (_gatheringPointRegistry.TryGetGatheringPointId(questStep.ItemsToGather[0].ItemId, ((EClassJob?)(gameObject as ICharacter)?.ClassJob.RowId).GetValueOrDefault(), out GatheringPointId gatheringPointId) && _gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot gatheringRoot))
|
||||||
{
|
{
|
||||||
foreach (QuestStep step in gatheringRoot.Steps)
|
foreach (QuestStep step in gatheringRoot.Steps)
|
||||||
{
|
{
|
||||||
|
|
@ -868,6 +872,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (questStep == null || !questStep.TargetTerritoryId.HasValue)
|
if (questStep == null || !questStep.TargetTerritoryId.HasValue)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("FindTargetTerritoryFromQuestStep: Checking previous step...");
|
_logger.LogTrace("FindTargetTerritoryFromQuestStep: Checking previous step...");
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ internal static class Mount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class MountEvaluator(GameFunctions gameFunctions, ICondition condition, TerritoryData territoryData, IClientState clientState, ILogger<MountEvaluator> logger)
|
internal sealed class MountEvaluator(GameFunctions gameFunctions, ICondition condition, TerritoryData territoryData, IClientState clientState, IObjectTable objectTable, ILogger<MountEvaluator> logger)
|
||||||
{
|
{
|
||||||
public unsafe MountResult EvaluateMountState(MountTask task, bool dryRun, ref DateTime retryAt)
|
public unsafe MountResult EvaluateMountState(MountTask task, bool dryRun, ref DateTime retryAt)
|
||||||
{
|
{
|
||||||
|
|
@ -71,7 +71,7 @@ internal static class Mount
|
||||||
}
|
}
|
||||||
if (task.MountIf == EMountIf.AwayFromPosition)
|
if (task.MountIf == EMountIf.AwayFromPosition)
|
||||||
{
|
{
|
||||||
float num = System.Numerics.Vector3.Distance((FFXIVClientStructs.FFXIV.Common.Math.Vector3)(clientState.LocalPlayer?.Position ?? ((System.Numerics.Vector3)FFXIVClientStructs.FFXIV.Common.Math.Vector3.Zero)), task.Position.GetValueOrDefault());
|
float num = System.Numerics.Vector3.Distance((FFXIVClientStructs.FFXIV.Common.Math.Vector3)(objectTable[0]?.Position ?? ((System.Numerics.Vector3)FFXIVClientStructs.FFXIV.Common.Math.Vector3.Zero)), task.Position.GetValueOrDefault());
|
||||||
if (task.TerritoryId == clientState.TerritoryType && num < 30f && !Conditions.Instance()->Diving)
|
if (task.TerritoryId == clientState.TerritoryType && num < 30f && !Conditions.Instance()->Diving)
|
||||||
{
|
{
|
||||||
logger.Log(logLevel, "Not using mount, as we're close to the target");
|
logger.Log(logLevel, "Not using mount, as we're close to the target");
|
||||||
|
|
@ -159,7 +159,7 @@ internal static class Mount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UnmountExecutor(ICondition condition, ILogger<UnmountTask> logger, GameFunctions gameFunctions, IClientState clientState) : TaskExecutor<UnmountTask>()
|
internal sealed class UnmountExecutor(ICondition condition, ILogger<UnmountTask> logger, GameFunctions gameFunctions, IObjectTable objectTable) : TaskExecutor<UnmountTask>()
|
||||||
{
|
{
|
||||||
private bool _unmountTriggered;
|
private bool _unmountTriggered;
|
||||||
|
|
||||||
|
|
@ -221,10 +221,9 @@ internal static class Mount
|
||||||
|
|
||||||
private unsafe bool IsUnmounting()
|
private unsafe bool IsUnmounting()
|
||||||
{
|
{
|
||||||
IPlayerCharacter localPlayer = clientState.LocalPlayer;
|
if (objectTable[0] is IPlayerCharacter playerCharacter)
|
||||||
if (localPlayer != null)
|
|
||||||
{
|
{
|
||||||
BattleChara* address = (BattleChara*)localPlayer.Address;
|
BattleChara* address = (BattleChara*)playerCharacter.Address;
|
||||||
return (address->Mount.Flags & 1) == 1;
|
return (address->Mount.Flags & 1) == 1;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ internal static class SendNotification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class Executor(NotificationMasterIpc notificationMasterIpc, IChatGui chatGui, Configuration configuration) : TaskExecutor<Task>()
|
internal sealed class Executor(IChatGui chatGui, Configuration configuration) : TaskExecutor<Task>()
|
||||||
{
|
{
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
|
|
@ -142,7 +142,6 @@ internal static class SendNotification
|
||||||
XivChatEntry chat = xivChatEntry;
|
XivChatEntry chat = xivChatEntry;
|
||||||
chatGui.Print(chat);
|
chatGui.Print(chat);
|
||||||
}
|
}
|
||||||
notificationMasterIpc.Notify(text2);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
|
@ -30,7 +31,7 @@ internal static class DoGather
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class GatherExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IGameGui gameGui, IClientState clientState, ICondition condition, ILogger<GatherExecutor> logger) : TaskExecutor<Task>()
|
internal sealed class GatherExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IGameGui gameGui, IObjectTable objectTable, ICondition condition, ILogger<GatherExecutor> logger) : TaskExecutor<Task>()
|
||||||
{
|
{
|
||||||
private bool _wasGathering;
|
private bool _wasGathering;
|
||||||
|
|
||||||
|
|
@ -268,7 +269,7 @@ internal static class DoGather
|
||||||
|
|
||||||
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
||||||
{
|
{
|
||||||
if (clientState.LocalPlayer?.ClassJob.RowId == 16)
|
if ((objectTable[0] as ICharacter)?.ClassJob.RowId == 16)
|
||||||
{
|
{
|
||||||
return minerAction;
|
return minerAction;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
@ -27,7 +28,7 @@ internal static class DoGatherCollectable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class GatherCollectableExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IClientState clientState, IGameGui gameGui, ILogger<GatherCollectableExecutor> logger) : TaskExecutor<Task>()
|
internal sealed class GatherCollectableExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IObjectTable objectTable, IGameGui gameGui, ILogger<GatherCollectableExecutor> logger) : TaskExecutor<Task>()
|
||||||
{
|
{
|
||||||
private Queue<EAction>? _actionQueue;
|
private Queue<EAction>? _actionQueue;
|
||||||
|
|
||||||
|
|
@ -137,42 +138,42 @@ internal static class DoGatherCollectable
|
||||||
|
|
||||||
private Queue<EAction> GetNextActions(NodeCondition nodeCondition)
|
private Queue<EAction> GetNextActions(NodeCondition nodeCondition)
|
||||||
{
|
{
|
||||||
uint currentGp = clientState.LocalPlayer.CurrentGp;
|
uint num = (objectTable[0] as ICharacter)?.CurrentGp ?? 0;
|
||||||
logger.LogTrace("Getting next actions (with {GP} GP, {MeticulousCollectability}~ meticulous, {ScourCollectability}~ scour)", currentGp, nodeCondition.CollectabilityFromMeticulous, nodeCondition.CollectabilityFromScour);
|
logger.LogTrace("Getting next actions (with {GP} GP, {MeticulousCollectability}~ meticulous, {ScourCollectability}~ scour)", num, nodeCondition.CollectabilityFromMeticulous, nodeCondition.CollectabilityFromScour);
|
||||||
Queue<EAction> queue = new Queue<EAction>();
|
Queue<EAction> queue = new Queue<EAction>();
|
||||||
uint num = nodeCondition.CollectabilityToGoal(base.Task.Request.Collectability);
|
uint num2 = nodeCondition.CollectabilityToGoal(base.Task.Request.Collectability);
|
||||||
if (num <= nodeCondition.CollectabilityFromMeticulous)
|
if (num2 <= nodeCondition.CollectabilityFromMeticulous)
|
||||||
{
|
{
|
||||||
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous", num, nodeCondition.CollectabilityFromMeticulous);
|
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous", num2, nodeCondition.CollectabilityFromMeticulous);
|
||||||
queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
if (num <= nodeCondition.CollectabilityFromScour)
|
if (num2 <= nodeCondition.CollectabilityFromScour)
|
||||||
{
|
{
|
||||||
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ scour", num, nodeCondition.CollectabilityFromScour);
|
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ scour", num2, nodeCondition.CollectabilityFromScour);
|
||||||
queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
if (!nodeCondition.ScrutinyActive && currentGp >= 200)
|
if (!nodeCondition.ScrutinyActive && num >= 200)
|
||||||
{
|
{
|
||||||
logger.LogTrace("Still missing {NeededCollectability} collectability, scrutiny inactive", num);
|
logger.LogTrace("Still missing {NeededCollectability} collectability, scrutiny inactive", num2);
|
||||||
queue.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist));
|
queue.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist));
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
if (nodeCondition.ScrutinyActive)
|
if (nodeCondition.ScrutinyActive)
|
||||||
{
|
{
|
||||||
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ meticulous", num, nodeCondition.CollectabilityFromMeticulous);
|
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ meticulous", num2, nodeCondition.CollectabilityFromMeticulous);
|
||||||
queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ scour", num, nodeCondition.CollectabilityFromScour);
|
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ scour", num2, nodeCondition.CollectabilityFromScour);
|
||||||
queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
||||||
{
|
{
|
||||||
if (clientState.LocalPlayer?.ClassJob.RowId == 16)
|
if ((objectTable[0] as ICharacter)?.ClassJob.RowId == 16)
|
||||||
{
|
{
|
||||||
return minerAction;
|
return minerAction;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Input;
|
using FFXIVClientStructs.FFXIV.Client.System.Input;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
|
|
@ -37,10 +36,12 @@ internal static class Dive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoDive(ICondition condition, ILogger<DoDive> logger) : AbstractDelayedTaskExecutor<Task>(TimeSpan.FromSeconds(5L))
|
internal sealed class DoDive(ICondition condition, ILogger<DoDive> logger) : AbstractDelayedTaskExecutor<Task>(TimeSpan.FromSeconds(5L)), IStoppableTaskExecutor, ITaskExecutor
|
||||||
{
|
{
|
||||||
private readonly Queue<(uint Type, nint Key)> _keysToPress = new Queue<(uint, nint)>();
|
private readonly Queue<(uint Type, nint Key)> _keysToPress = new Queue<(uint, nint)>();
|
||||||
|
|
||||||
|
private readonly HashSet<nint> _pressedKeys = new HashSet<nint>();
|
||||||
|
|
||||||
private int _attempts;
|
private int _attempts;
|
||||||
|
|
||||||
protected override bool StartInternal()
|
protected override bool StartInternal()
|
||||||
|
|
@ -67,11 +68,30 @@ internal static class Dive
|
||||||
}
|
}
|
||||||
logger.LogDebug("{Action} key {KeyCode:X2}", (result.Item1 == 256) ? "Pressing" : "Releasing", result.Item2);
|
logger.LogDebug("{Action} key {KeyCode:X2}", (result.Item1 == 256) ? "Pressing" : "Releasing", result.Item2);
|
||||||
NativeMethods.SendMessage((nint)Device.Instance()->hWnd, result.Item1, result.Item2, IntPtr.Zero);
|
NativeMethods.SendMessage((nint)Device.Instance()->hWnd, result.Item1, result.Item2, IntPtr.Zero);
|
||||||
|
if (result.Item1 == 256)
|
||||||
|
{
|
||||||
|
_pressedKeys.Add(result.Item2);
|
||||||
|
}
|
||||||
|
else if (result.Item1 == 257)
|
||||||
|
{
|
||||||
|
_pressedKeys.Remove(result.Item2);
|
||||||
|
}
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
return base.Update();
|
return base.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe void StopNow()
|
||||||
|
{
|
||||||
|
foreach (nint pressedKey in _pressedKeys)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Releasing stuck key {KeyCode:X2} on stop", pressedKey);
|
||||||
|
NativeMethods.SendMessage((nint)Device.Instance()->hWnd, 257u, pressedKey, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
_pressedKeys.Clear();
|
||||||
|
_keysToPress.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
public override bool ShouldInterruptOnDamage()
|
public override bool ShouldInterruptOnDamage()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -83,7 +103,7 @@ internal static class Dive
|
||||||
{
|
{
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
if (_attempts >= 3)
|
if (_attempts >= 5)
|
||||||
{
|
{
|
||||||
throw new TaskException("Please dive manually.");
|
throw new TaskException("Please dive manually.");
|
||||||
}
|
}
|
||||||
|
|
@ -94,18 +114,25 @@ internal static class Dive
|
||||||
|
|
||||||
private unsafe void Descend()
|
private unsafe void Descend()
|
||||||
{
|
{
|
||||||
UIInputData.Keybind keybind = default(UIInputData.Keybind);
|
Keybind* keybind = UIInputData.Instance()->GetKeybind(InputId.MOVE_DESCENT);
|
||||||
Utf8String* name = Utf8String.FromString("MOVE_DESCENT");
|
if (keybind == null)
|
||||||
UIInputData.Instance()->GetKeybindByName(name, (Keybind*)(&keybind));
|
{
|
||||||
logger.LogInformation("Dive keybind: {Key1} + {Modifier1}, {Key2} + {Modifier2}", keybind.Key, keybind.Modifier, keybind.AltKey, keybind.AltModifier);
|
throw new TaskException("Could not find descent keybind");
|
||||||
|
}
|
||||||
|
Span<KeySetting> keySettings = keybind->KeySettings;
|
||||||
|
SeVirtualKey key = keySettings[0].Key;
|
||||||
|
KeyModifierFlag keyModifier = keySettings[0].KeyModifier;
|
||||||
|
SeVirtualKey key2 = keySettings[1].Key;
|
||||||
|
KeyModifierFlag keyModifier2 = keySettings[1].KeyModifier;
|
||||||
|
logger.LogInformation("Dive keybind: {Key1} + {Modifier1}, {Key2} + {Modifier2}", key, keyModifier, key2, keyModifier2);
|
||||||
int num = 2;
|
int num = 2;
|
||||||
List<List<nint>> list = new List<List<nint>>(num);
|
List<List<nint>> list = new List<List<nint>>(num);
|
||||||
CollectionsMarshal.SetCount(list, num);
|
CollectionsMarshal.SetCount(list, num);
|
||||||
Span<List<nint>> span = CollectionsMarshal.AsSpan(list);
|
Span<List<nint>> span = CollectionsMarshal.AsSpan(list);
|
||||||
int num2 = 0;
|
int num2 = 0;
|
||||||
span[num2] = GetKeysToPress(keybind.Key, keybind.Modifier);
|
span[num2] = GetKeysToPress(key, keyModifier);
|
||||||
num2++;
|
num2++;
|
||||||
span[num2] = GetKeysToPress(keybind.AltKey, keybind.AltModifier);
|
span[num2] = GetKeysToPress(key2, keyModifier2);
|
||||||
List<nint> list2 = (from x in list
|
List<nint> list2 = (from x in list
|
||||||
where x != null
|
where x != null
|
||||||
select (x)).MinBy((List<nint> x) => x.Count);
|
select (x)).MinBy((List<nint> x) => x.Count);
|
||||||
|
|
@ -116,10 +143,12 @@ internal static class Dive
|
||||||
foreach (nint item in list2)
|
foreach (nint item in list2)
|
||||||
{
|
{
|
||||||
_keysToPress.Enqueue((256u, item));
|
_keysToPress.Enqueue((256u, item));
|
||||||
_keysToPress.Enqueue((0u, 0));
|
for (int num3 = 0; num3 < 15; num3++)
|
||||||
|
{
|
||||||
_keysToPress.Enqueue((0u, 0));
|
_keysToPress.Enqueue((0u, 0));
|
||||||
}
|
}
|
||||||
for (int num3 = 0; num3 < 5; num3++)
|
}
|
||||||
|
for (int num4 = 0; num4 < 5; num4++)
|
||||||
{
|
{
|
||||||
_keysToPress.Enqueue((0u, 0));
|
_keysToPress.Enqueue((0u, 0));
|
||||||
}
|
}
|
||||||
|
|
@ -142,18 +171,18 @@ internal static class Dive
|
||||||
public static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
|
public static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<nint>? GetKeysToPress(SeVirtualKey key, ModifierFlag modifier)
|
private static List<nint>? GetKeysToPress(SeVirtualKey key, KeyModifierFlag modifier)
|
||||||
{
|
{
|
||||||
List<nint> list = new List<nint>();
|
List<nint> list = new List<nint>();
|
||||||
if (modifier.HasFlag(ModifierFlag.Ctrl))
|
if ((modifier & KeyModifierFlag.Ctrl) != KeyModifierFlag.None)
|
||||||
{
|
{
|
||||||
list.Add(17);
|
list.Add(17);
|
||||||
}
|
}
|
||||||
if (modifier.HasFlag(ModifierFlag.Shift))
|
if ((modifier & KeyModifierFlag.Shift) != KeyModifierFlag.None)
|
||||||
{
|
{
|
||||||
list.Add(16);
|
list.Add(16);
|
||||||
}
|
}
|
||||||
if (modifier.HasFlag(ModifierFlag.Alt))
|
if ((modifier & KeyModifierFlag.Alt) != KeyModifierFlag.None)
|
||||||
{
|
{
|
||||||
list.Add(18);
|
list.Add(18);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
||||||
using LLib.Gear;
|
using LLib.Gear;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.Controller.Utils;
|
using Questionable.Controller.Utils;
|
||||||
|
|
@ -17,7 +19,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
||||||
|
|
||||||
internal static class Duty
|
internal static class Duty
|
||||||
{
|
{
|
||||||
internal sealed class Factory(AutoDutyIpc autoDutyIpc) : ITaskFactory
|
internal sealed class Factory(AutoDutyIpc autoDutyIpc, Configuration configuration) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
|
|
@ -26,37 +28,95 @@ internal static class Duty
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
ArgumentNullException.ThrowIfNull(step.DutyOptions, "step.DutyOptions");
|
ArgumentNullException.ThrowIfNull(step.DutyOptions, "step.DutyOptions");
|
||||||
uint contentFinderConditionId;
|
|
||||||
int dutyMode;
|
|
||||||
if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions))
|
if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions))
|
||||||
{
|
{
|
||||||
contentFinderConditionId = step.DutyOptions.ContentFinderConditionId;
|
AutoDutyIpc.DutyMode dutyMode = GetDutyMode(step.DutyOptions.ContentFinderConditionId, step.DutyOptions.DutyMode);
|
||||||
ElementId id = quest.Id;
|
if (dutyMode == AutoDutyIpc.DutyMode.UnsyncRegular && (step.DutyOptions.DutyMode == EDutyMode.UnsyncParty || (!step.DutyOptions.DutyMode.HasValue && configuration.Duties.DutyModeOverrides.TryGetValue(step.DutyOptions.ContentFinderConditionId, out var value) && value == EDutyMode.UnsyncParty) || (!step.DutyOptions.DutyMode.HasValue && !configuration.Duties.DutyModeOverrides.ContainsKey(step.DutyOptions.ContentFinderConditionId) && configuration.Duties.DefaultDutyMode == EDutyMode.UnsyncParty)))
|
||||||
if (id is QuestId)
|
|
||||||
{
|
{
|
||||||
ushort value = id.Value;
|
yield return new WaitForPartyTask();
|
||||||
if (value >= 357 && value <= 360)
|
|
||||||
{
|
|
||||||
dutyMode = 2;
|
|
||||||
goto IL_00b2;
|
|
||||||
}
|
}
|
||||||
}
|
yield return new StartAutoDutyTask(step.DutyOptions.ContentFinderConditionId, dutyMode);
|
||||||
dutyMode = 1;
|
|
||||||
goto IL_00b2;
|
|
||||||
}
|
|
||||||
if (!step.DutyOptions.LowPriority)
|
|
||||||
{
|
|
||||||
yield return new OpenDutyFinderTask(step.DutyOptions.ContentFinderConditionId);
|
|
||||||
}
|
|
||||||
yield break;
|
|
||||||
IL_00b2:
|
|
||||||
yield return new StartAutoDutyTask(contentFinderConditionId, (AutoDutyIpc.DutyMode)dutyMode);
|
|
||||||
yield return new WaitAutoDutyTask(step.DutyOptions.ContentFinderConditionId);
|
yield return new WaitAutoDutyTask(step.DutyOptions.ContentFinderConditionId);
|
||||||
if (!QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
if (!QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
||||||
{
|
{
|
||||||
yield return new WaitAtEnd.WaitNextStepOrSequence();
|
yield return new WaitAtEnd.WaitNextStepOrSequence();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (!step.DutyOptions.LowPriority)
|
||||||
|
{
|
||||||
|
yield return new OpenDutyFinderTask(step.DutyOptions.ContentFinderConditionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AutoDutyIpc.DutyMode GetDutyMode(uint cfcId, EDutyMode? stepDutyMode)
|
||||||
|
{
|
||||||
|
if (stepDutyMode.HasValue)
|
||||||
|
{
|
||||||
|
return ConvertToAutoDutyMode(stepDutyMode.Value);
|
||||||
|
}
|
||||||
|
if (configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value))
|
||||||
|
{
|
||||||
|
return ConvertToAutoDutyMode(value);
|
||||||
|
}
|
||||||
|
return ConvertToAutoDutyMode(configuration.Duties.DefaultDutyMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AutoDutyIpc.DutyMode ConvertToAutoDutyMode(EDutyMode mode)
|
||||||
|
{
|
||||||
|
return mode switch
|
||||||
|
{
|
||||||
|
EDutyMode.Support => AutoDutyIpc.DutyMode.Support,
|
||||||
|
EDutyMode.UnsyncSolo => AutoDutyIpc.DutyMode.UnsyncRegular,
|
||||||
|
EDutyMode.UnsyncParty => AutoDutyIpc.DutyMode.UnsyncRegular,
|
||||||
|
_ => AutoDutyIpc.DutyMode.Support,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record WaitForPartyTask : ITask
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "WaitForParty";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class WaitForPartyExecutor(IChatGui chatGui, ILogger<WaitForPartyExecutor> logger) : TaskExecutor<WaitForPartyTask>()
|
||||||
|
{
|
||||||
|
private DateTime _lastWarningTime = DateTime.MinValue;
|
||||||
|
|
||||||
|
protected override bool Start()
|
||||||
|
{
|
||||||
|
logger.LogInformation("Waiting for party members before starting duty...");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe override ETaskResult Update()
|
||||||
|
{
|
||||||
|
GroupManager* ptr = GroupManager.Instance();
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
byte memberCount = ptr->MainGroup.MemberCount;
|
||||||
|
bool isAlliance = ptr->MainGroup.IsAlliance;
|
||||||
|
if (memberCount > 1 || isAlliance)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Party detected with {MemberCount} members, proceeding with duty", memberCount);
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
if (DateTime.Now - _lastWarningTime > TimeSpan.FromSeconds(10L))
|
||||||
|
{
|
||||||
|
chatGui.Print("[Questionable] Waiting for party members before starting duty (Unsync Party mode)...", "Questionable", 576);
|
||||||
|
_lastWarningTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ShouldInterruptOnDamage()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed record StartAutoDutyTask(uint ContentFinderConditionId, AutoDutyIpc.DutyMode DutyMode) : ITask
|
internal sealed record StartAutoDutyTask(uint ContentFinderConditionId, AutoDutyIpc.DutyMode DutyMode) : ITask
|
||||||
|
|
@ -192,4 +252,124 @@ internal static class Duty
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed record StartLevelingModeTask(int RequiredLevel, string? QuestName) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"StartLevelingMode(target: Lv{RequiredLevel} for '{QuestName}')";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class StartLevelingModeExecutor(AutoDutyIpc autoDutyIpc, ICondition condition, ILogger<StartLevelingModeExecutor> logger) : TaskExecutor<StartLevelingModeTask>(), IStoppableTaskExecutor, ITaskExecutor
|
||||||
|
{
|
||||||
|
private DateTime _startTime;
|
||||||
|
|
||||||
|
protected override bool Start()
|
||||||
|
{
|
||||||
|
logger.LogInformation("Starting AutoDuty Leveling mode to reach level {RequiredLevel} for quest '{QuestName}'", base.Task.RequiredLevel, base.Task.QuestName);
|
||||||
|
if (condition[ConditionFlag.BoundByDuty] || condition[ConditionFlag.InDutyQueue])
|
||||||
|
{
|
||||||
|
logger.LogInformation("Already in duty or queue, skipping start");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!autoDutyIpc.IsStopped())
|
||||||
|
{
|
||||||
|
logger.LogInformation("AutoDuty is already running, waiting for it");
|
||||||
|
_startTime = DateTime.Now;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
autoDutyIpc.StartLevelingMode();
|
||||||
|
_startTime = DateTime.Now;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
|
{
|
||||||
|
bool flag = condition[ConditionFlag.BoundByDuty];
|
||||||
|
bool flag2 = condition[ConditionFlag.InDutyQueue];
|
||||||
|
if (flag || flag2)
|
||||||
|
{
|
||||||
|
logger.LogInformation("AutoDuty started successfully (inDuty={InDuty}, inQueue={InQueue})", flag, flag2);
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
if (!autoDutyIpc.IsStopped())
|
||||||
|
{
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
if (DateTime.Now - _startTime < TimeSpan.FromSeconds(10L))
|
||||||
|
{
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
logger.LogError("AutoDuty failed to start leveling mode after 10 seconds");
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopNow()
|
||||||
|
{
|
||||||
|
autoDutyIpc.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ShouldInterruptOnDamage()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record WaitLevelingModeTask(int RequiredLevel) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"WaitLevelingMode(until Lv{RequiredLevel})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class WaitLevelingModeExecutor(AutoDutyIpc autoDutyIpc, IObjectTable objectTable, ICondition condition, IChatGui chatGui, ILogger<WaitLevelingModeExecutor> logger) : TaskExecutor<WaitLevelingModeTask>(), IStoppableTaskExecutor, ITaskExecutor
|
||||||
|
{
|
||||||
|
private bool _wasInDuty;
|
||||||
|
|
||||||
|
private DateTime _lastStatusMessage = DateTime.MinValue;
|
||||||
|
|
||||||
|
protected override bool Start()
|
||||||
|
{
|
||||||
|
_wasInDuty = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
|
{
|
||||||
|
bool flag = condition[ConditionFlag.BoundByDuty];
|
||||||
|
bool flag2 = condition[ConditionFlag.InDutyQueue];
|
||||||
|
if (flag && !_wasInDuty)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Entered duty for leveling");
|
||||||
|
_wasInDuty = true;
|
||||||
|
}
|
||||||
|
byte b = objectTable.LocalPlayer?.Level ?? 0;
|
||||||
|
if (b >= base.Task.RequiredLevel)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Reached required level {RequiredLevel} (current: {CurrentLevel})", base.Task.RequiredLevel, b);
|
||||||
|
chatGui.Print($"Reached level {b}, can now continue MSQ.", "Questionable", 576);
|
||||||
|
autoDutyIpc.DisableLevelingMode();
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
if (autoDutyIpc.IsStopped() && !flag && !flag2 && _wasInDuty && DateTime.Now - _lastStatusMessage > TimeSpan.FromSeconds(30L))
|
||||||
|
{
|
||||||
|
int num = base.Task.RequiredLevel - b;
|
||||||
|
logger.LogInformation("Waiting for leveling mode to continue (current: {CurrentLevel}, need: {RequiredLevel}, {LevelsNeeded} more levels needed)", b, base.Task.RequiredLevel, num);
|
||||||
|
_lastStatusMessage = DateTime.Now;
|
||||||
|
}
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopNow()
|
||||||
|
{
|
||||||
|
autoDutyIpc.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ShouldInterruptOnDamage()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,6 @@ internal static class EquipItem
|
||||||
{
|
{
|
||||||
return new List<ushort>();
|
return new List<ushort>();
|
||||||
}
|
}
|
||||||
Span<ushort> span;
|
|
||||||
switch (item.Value.EquipSlotCategory.RowId)
|
switch (item.Value.EquipSlotCategory.RowId)
|
||||||
{
|
{
|
||||||
case 1u:
|
case 1u:
|
||||||
|
|
@ -179,9 +178,9 @@ internal static class EquipItem
|
||||||
int index = 1;
|
int index = 1;
|
||||||
List<ushort> list4 = new List<ushort>(index);
|
List<ushort> list4 = new List<ushort>(index);
|
||||||
CollectionsMarshal.SetCount(list4, index);
|
CollectionsMarshal.SetCount(list4, index);
|
||||||
span = CollectionsMarshal.AsSpan(list4);
|
Span<ushort> span4 = CollectionsMarshal.AsSpan(list4);
|
||||||
int num = 0;
|
int num = 0;
|
||||||
span[num] = (ushort)(item.Value.EquipSlotCategory.RowId - 1);
|
span4[num] = (ushort)(item.Value.EquipSlotCategory.RowId - 1);
|
||||||
return list4;
|
return list4;
|
||||||
}
|
}
|
||||||
case 12u:
|
case 12u:
|
||||||
|
|
@ -189,11 +188,11 @@ internal static class EquipItem
|
||||||
int num = 2;
|
int num = 2;
|
||||||
List<ushort> list3 = new List<ushort>(num);
|
List<ushort> list3 = new List<ushort>(num);
|
||||||
CollectionsMarshal.SetCount(list3, num);
|
CollectionsMarshal.SetCount(list3, num);
|
||||||
span = CollectionsMarshal.AsSpan(list3);
|
Span<ushort> span3 = CollectionsMarshal.AsSpan(list3);
|
||||||
int index = 0;
|
int index = 0;
|
||||||
span[index] = 11;
|
span3[index] = 11;
|
||||||
index++;
|
index++;
|
||||||
span[index] = 12;
|
span3[index] = 12;
|
||||||
return list3;
|
return list3;
|
||||||
}
|
}
|
||||||
case 13u:
|
case 13u:
|
||||||
|
|
@ -201,9 +200,9 @@ internal static class EquipItem
|
||||||
int index = 1;
|
int index = 1;
|
||||||
List<ushort> list2 = new List<ushort>(index);
|
List<ushort> list2 = new List<ushort>(index);
|
||||||
CollectionsMarshal.SetCount(list2, index);
|
CollectionsMarshal.SetCount(list2, index);
|
||||||
span = CollectionsMarshal.AsSpan(list2);
|
Span<ushort> span2 = CollectionsMarshal.AsSpan(list2);
|
||||||
int num = 0;
|
int num = 0;
|
||||||
span[num] = 0;
|
span2[num] = 0;
|
||||||
return list2;
|
return list2;
|
||||||
}
|
}
|
||||||
case 17u:
|
case 17u:
|
||||||
|
|
@ -211,7 +210,7 @@ internal static class EquipItem
|
||||||
int num = 1;
|
int num = 1;
|
||||||
List<ushort> list = new List<ushort>(num);
|
List<ushort> list = new List<ushort>(num);
|
||||||
CollectionsMarshal.SetCount(list, num);
|
CollectionsMarshal.SetCount(list, num);
|
||||||
span = CollectionsMarshal.AsSpan(list);
|
Span<ushort> span = CollectionsMarshal.AsSpan(list);
|
||||||
int index = 0;
|
int index = 0;
|
||||||
span[index] = 13;
|
span[index] = 13;
|
||||||
return list;
|
return list;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
|
@ -43,7 +44,7 @@ internal static class EquipRecommended
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoEquipRecommended(IClientState clientState, IChatGui chatGui, ICondition condition) : TaskExecutor<EquipTask>()
|
internal sealed class DoEquipRecommended(IObjectTable objectTable, IChatGui chatGui, ICondition condition) : TaskExecutor<EquipTask>()
|
||||||
{
|
{
|
||||||
private bool _checkedOrTriggeredEquipmentUpdate;
|
private bool _checkedOrTriggeredEquipmentUpdate;
|
||||||
|
|
||||||
|
|
@ -55,7 +56,11 @@ internal static class EquipRecommended
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
RecommendEquipModule.Instance()->SetupForClassJob((byte)clientState.LocalPlayer.ClassJob.RowId);
|
if (!(objectTable[0] is ICharacter character))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RecommendEquipModule.Instance()->SetupForClassJob((byte)character.ClassJob.RowId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -47,12 +48,17 @@ internal static class Jump
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class JumpBase<T>(MovementController movementController, IClientState clientState, IFramework framework) : TaskExecutor<T>() where T : class, IJumpTask
|
internal abstract class JumpBase<T>(MovementController movementController, IObjectTable objectTable, IFramework framework) : TaskExecutor<T>() where T : class, IJumpTask
|
||||||
{
|
{
|
||||||
protected unsafe override bool Start()
|
protected unsafe override bool Start()
|
||||||
{
|
{
|
||||||
|
IGameObject gameObject = _003CobjectTable_003EP[0];
|
||||||
|
if (gameObject == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
float num = base.Task.JumpDestination.CalculateStopDistance();
|
float num = base.Task.JumpDestination.CalculateStopDistance();
|
||||||
if ((_003CclientState_003EP.LocalPlayer.Position - base.Task.JumpDestination.Position).Length() <= num)
|
if ((gameObject.Position - base.Task.JumpDestination.Position).Length() <= num)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -94,8 +100,8 @@ internal static class Jump
|
||||||
|
|
||||||
internal sealed class DoSingleJump : JumpBase<SingleJumpTask>
|
internal sealed class DoSingleJump : JumpBase<SingleJumpTask>
|
||||||
{
|
{
|
||||||
public DoSingleJump(MovementController movementController, IClientState clientState, IFramework framework)
|
public DoSingleJump(MovementController movementController, IObjectTable objectTable, IFramework framework)
|
||||||
: base(movementController, clientState, framework)
|
: base(movementController, objectTable, framework)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,19 +116,19 @@ internal static class Jump
|
||||||
|
|
||||||
internal sealed class DoRepeatedJumps : JumpBase<RepeatedJumpTask>
|
internal sealed class DoRepeatedJumps : JumpBase<RepeatedJumpTask>
|
||||||
{
|
{
|
||||||
private readonly IClientState _clientState;
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
|
|
||||||
private int _attempts;
|
private int _attempts;
|
||||||
|
|
||||||
public DoRepeatedJumps(MovementController movementController, IClientState clientState, IFramework framework, ICondition condition, ILogger<DoRepeatedJumps> logger)
|
public DoRepeatedJumps(MovementController movementController, IObjectTable objectTable, IFramework framework, ICondition condition, ILogger<DoRepeatedJumps> logger)
|
||||||
{
|
{
|
||||||
_003Ccondition_003EP = condition;
|
_003Ccondition_003EP = condition;
|
||||||
_003Clogger_003EP = logger;
|
_003Clogger_003EP = logger;
|
||||||
_clientState = clientState;
|
_objectTable = objectTable;
|
||||||
_continueAt = DateTime.MinValue;
|
_continueAt = DateTime.MinValue;
|
||||||
base._002Ector(movementController, clientState, framework);
|
base._002Ector(movementController, objectTable, framework);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
|
|
@ -137,12 +143,17 @@ internal static class Jump
|
||||||
{
|
{
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
float num = base.Task.JumpDestination.CalculateStopDistance();
|
IGameObject gameObject = _objectTable[0];
|
||||||
if ((_clientState.LocalPlayer.Position - base.Task.JumpDestination.Position).Length() <= num || _clientState.LocalPlayer.Position.Y >= base.Task.JumpDestination.Position.Y - 0.5f)
|
if (gameObject == null)
|
||||||
{
|
{
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
_003Clogger_003EP.LogTrace("Y-Heights for jumps: player={A}, target={B}", _clientState.LocalPlayer.Position.Y, base.Task.JumpDestination.Position.Y - 0.5f);
|
float num = base.Task.JumpDestination.CalculateStopDistance();
|
||||||
|
if ((gameObject.Position - base.Task.JumpDestination.Position).Length() <= num || gameObject.Position.Y >= base.Task.JumpDestination.Position.Y - 0.5f)
|
||||||
|
{
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
_003Clogger_003EP.LogTrace("Y-Heights for jumps: player={A}, target={B}", gameObject.Position.Y, base.Task.JumpDestination.Position.Y - 0.5f);
|
||||||
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null))
|
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null))
|
||||||
{
|
{
|
||||||
_attempts++;
|
_attempts++;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
|
@ -29,7 +28,7 @@ internal static class SinglePlayerDuty
|
||||||
public const ushort Naadam = 688;
|
public const ushort Naadam = 688;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class Factory(BossModIpc bossModIpc, TerritoryData territoryData, ICondition condition, IClientState clientState) : ITaskFactory
|
internal sealed class Factory(BossModIpc bossModIpc, TerritoryData territoryData, 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)
|
||||||
{
|
{
|
||||||
|
|
@ -70,7 +69,7 @@ internal static class SinglePlayerDuty
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Vector3 vector = clientState.LocalPlayer?.Position ?? default(Vector3);
|
Vector3 vector = objectTable[0]?.Position ?? default(Vector3);
|
||||||
return (new Vector3(352.01f, -1.45f, 288.59f) - vector).Length() < 10f;
|
return (new Vector3(352.01f, -1.45f, 288.59f) - vector).Length() < 10f;
|
||||||
}, "Wait(moving to Ovoo)");
|
}, "Wait(moving to Ovoo)");
|
||||||
yield return new Mount.UnmountTask();
|
yield return new Mount.UnmountTask();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Movement;
|
namespace Questionable.Controller.Steps.Movement;
|
||||||
|
|
||||||
internal sealed class LandExecutor(IClientState clientState, ICondition condition, ILogger<LandExecutor> logger) : TaskExecutor<LandTask>()
|
internal sealed class LandExecutor(IObjectTable objectTable, ICondition condition, ILogger<LandExecutor> logger) : TaskExecutor<LandTask>()
|
||||||
{
|
{
|
||||||
private bool _landing;
|
private bool _landing;
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ internal sealed class LandExecutor(IClientState clientState, ICondition conditio
|
||||||
|
|
||||||
private unsafe bool AttemptLanding()
|
private unsafe bool AttemptLanding()
|
||||||
{
|
{
|
||||||
Character* ptr = (Character*)(clientState.LocalPlayer?.Address ?? 0);
|
Character* ptr = (Character*)(objectTable[0]?.Address ?? 0);
|
||||||
if (ptr != null && ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
|
if (ptr != null && ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Attempting to land");
|
logger.LogInformation("Attempting to land");
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using LLib;
|
using LLib;
|
||||||
|
|
@ -28,6 +29,8 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
private readonly ICondition _condition;
|
private readonly ICondition _condition;
|
||||||
|
|
||||||
private readonly Questionable.Controller.Steps.Common.Mount.MountEvaluator _mountEvaluator;
|
private readonly Questionable.Controller.Steps.Common.Mount.MountEvaluator _mountEvaluator;
|
||||||
|
|
@ -46,12 +49,13 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
|
|
||||||
private (Questionable.Controller.Steps.Common.Mount.MountExecutor Executor, Questionable.Controller.Steps.Common.Mount.MountTask Task)? _mountDuringMovement;
|
private (Questionable.Controller.Steps.Common.Mount.MountExecutor Executor, Questionable.Controller.Steps.Common.Mount.MountTask Task)? _mountDuringMovement;
|
||||||
|
|
||||||
public MoveExecutor(MovementController movementController, GameFunctions gameFunctions, ILogger<MoveExecutor> logger, IClientState clientState, ICondition condition, IDataManager dataManager, Questionable.Controller.Steps.Common.Mount.MountEvaluator mountEvaluator, IServiceProvider serviceProvider)
|
public MoveExecutor(MovementController movementController, GameFunctions gameFunctions, ILogger<MoveExecutor> logger, IClientState clientState, IObjectTable objectTable, ICondition condition, IDataManager dataManager, Questionable.Controller.Steps.Common.Mount.MountEvaluator mountEvaluator, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_movementController = movementController;
|
_movementController = movementController;
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_objectTable = objectTable;
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_mountEvaluator = mountEvaluator;
|
_mountEvaluator = mountEvaluator;
|
||||||
|
|
@ -95,7 +99,7 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
_canRestart = base.Task.RestartNavigation;
|
_canRestart = base.Task.RestartNavigation;
|
||||||
_destination = base.Task.Destination;
|
_destination = base.Task.Destination;
|
||||||
float num = base.Task.StopDistance ?? 3f;
|
float num = base.Task.StopDistance ?? 3f;
|
||||||
Vector3? vector = _clientState.LocalPlayer?.Position;
|
Vector3? vector = _objectTable[0]?.Position;
|
||||||
float num2 = ((!vector.HasValue) ? float.MaxValue : Vector3.Distance(vector.Value, _destination));
|
float num2 = ((!vector.HasValue) ? float.MaxValue : Vector3.Distance(vector.Value, _destination));
|
||||||
if (num2 > num)
|
if (num2 > num)
|
||||||
{
|
{
|
||||||
|
|
@ -170,7 +174,8 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
|
||||||
{
|
{
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
if (_canRestart && Vector3.Distance(_clientState.LocalPlayer.Position, _destination) > (base.Task.StopDistance ?? 3f) + 5f)
|
IGameObject gameObject = _objectTable[0];
|
||||||
|
if (_canRestart && gameObject != null && Vector3.Distance(gameObject.Position, _destination) > (base.Task.StopDistance ?? 3f) + 5f)
|
||||||
{
|
{
|
||||||
_canRestart = false;
|
_canRestart = false;
|
||||||
if (_clientState.TerritoryType == base.Task.TerritoryId)
|
if (_clientState.TerritoryType == base.Task.TerritoryId)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
|
|
@ -13,7 +14,7 @@ namespace Questionable.Controller.Steps.Movement;
|
||||||
|
|
||||||
internal static class MoveTo
|
internal static class MoveTo
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IClientState clientState, AetheryteData aetheryteData, TerritoryData territoryData, ILogger<Factory> logger) : ITaskFactory
|
internal sealed class Factory(IClientState clientState, IObjectTable objectTable, AetheryteData aetheryteData, TerritoryData territoryData, ILogger<Factory> logger) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
|
|
@ -62,7 +63,8 @@ internal static class MoveTo
|
||||||
|
|
||||||
private IEnumerable<ITask> CreateMoveTasks(QuestStep step, Vector3 destination)
|
private IEnumerable<ITask> CreateMoveTasks(QuestStep step, Vector3 destination)
|
||||||
{
|
{
|
||||||
if (step.InteractionType == EInteractionType.Jump && step.JumpDestination != null && (clientState.LocalPlayer.Position - step.JumpDestination.Position).Length() <= (step.JumpDestination.StopDistance ?? 1f))
|
IGameObject gameObject = objectTable[0];
|
||||||
|
if (step.InteractionType == EInteractionType.Jump && step.JumpDestination != null && gameObject != null && (gameObject.Position - step.JumpDestination.Position).Length() <= (step.JumpDestination.StopDistance ?? 1f))
|
||||||
{
|
{
|
||||||
logger.LogInformation("We're at the jump destination, skipping movement");
|
logger.LogInformation("We're at the jump destination, skipping movement");
|
||||||
yield break;
|
yield break;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using Questionable.Functions;
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Movement;
|
namespace Questionable.Controller.Steps.Movement;
|
||||||
|
|
||||||
internal sealed class WaitForNearDataIdExecutor(GameFunctions gameFunctions, IClientState clientState) : TaskExecutor<WaitForNearDataId>()
|
internal sealed class WaitForNearDataIdExecutor(GameFunctions gameFunctions, IObjectTable objectTable) : TaskExecutor<WaitForNearDataId>()
|
||||||
{
|
{
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
|
|
@ -14,7 +14,8 @@ internal sealed class WaitForNearDataIdExecutor(GameFunctions gameFunctions, ICl
|
||||||
public override ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId);
|
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId);
|
||||||
if (gameObject == null || (gameObject.Position - clientState.LocalPlayer.Position).Length() > base.Task.StopDistance)
|
IGameObject gameObject2 = objectTable[0];
|
||||||
|
if (gameObject == null || gameObject2 == null || (gameObject.Position - gameObject2.Position).Length() > base.Task.StopDistance)
|
||||||
{
|
{
|
||||||
throw new TaskException("Object not found or too far away, no position so we can't move");
|
throw new TaskException("Object not found or too far away, no position so we can't move");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
|
|
@ -50,7 +51,7 @@ internal static class AethernetShortcut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseAethernetShortcut(ILogger<UseAethernetShortcut> logger, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, AetheryteData aetheryteData, TerritoryData territoryData, LifestreamIpc lifestreamIpc, MovementController movementController, ICondition condition) : TaskExecutor<Task>()
|
internal sealed class UseAethernetShortcut(ILogger<UseAethernetShortcut> logger, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, IObjectTable objectTable, AetheryteData aetheryteData, TerritoryData territoryData, LifestreamIpc lifestreamIpc, MovementController movementController, ICondition condition) : TaskExecutor<Task>()
|
||||||
{
|
{
|
||||||
private bool _moving;
|
private bool _moving;
|
||||||
|
|
||||||
|
|
@ -98,7 +99,12 @@ internal static class AethernetShortcut
|
||||||
if (aetheryteFunctions.IsAetheryteUnlocked(base.Task.From) && aetheryteFunctions.IsAetheryteUnlocked(base.Task.To))
|
if (aetheryteFunctions.IsAetheryteUnlocked(base.Task.From) && aetheryteFunctions.IsAetheryteUnlocked(base.Task.To))
|
||||||
{
|
{
|
||||||
ushort territoryType = clientState.TerritoryType;
|
ushort territoryType = clientState.TerritoryType;
|
||||||
Vector3 playerPosition = clientState.LocalPlayer.Position;
|
IGameObject gameObject = objectTable[0];
|
||||||
|
if (gameObject == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Vector3 playerPosition = gameObject.Position;
|
||||||
if (aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) < aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.To))
|
if (aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) < aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.To))
|
||||||
{
|
{
|
||||||
if (aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) < (base.Task.From.IsFirmamentAetheryte() ? 11f : 4f))
|
if (aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) < (base.Task.From.IsFirmamentAetheryte() ? 11f : 4f))
|
||||||
|
|
@ -202,7 +208,7 @@ internal static class AethernetShortcut
|
||||||
DoTeleport();
|
DoTeleport();
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
Vector3? vector = clientState.LocalPlayer?.Position;
|
Vector3? vector = objectTable[0]?.Position;
|
||||||
if (!vector.HasValue)
|
if (!vector.HasValue)
|
||||||
{
|
{
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
|
|
@ -45,7 +46,7 @@ internal static class AetheryteShortcut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseAetheryteShortcut(ILogger<UseAetheryteShortcut> logger, AetheryteFunctions aetheryteFunctions, QuestFunctions questFunctions, IClientState clientState, IChatGui chatGui, ICondition condition, AetheryteData aetheryteData, ExtraConditionUtils extraConditionUtils) : TaskExecutor<Task>()
|
internal sealed class UseAetheryteShortcut(ILogger<UseAetheryteShortcut> logger, AetheryteFunctions aetheryteFunctions, QuestFunctions questFunctions, IClientState clientState, IObjectTable objectTable, IChatGui chatGui, ICondition condition, AetheryteData aetheryteData, ExtraConditionUtils extraConditionUtils) : TaskExecutor<Task>()
|
||||||
{
|
{
|
||||||
private bool _teleported;
|
private bool _teleported;
|
||||||
|
|
||||||
|
|
@ -116,14 +117,15 @@ internal static class AetheryteShortcut
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
IGameObject gameObject = objectTable[0];
|
||||||
NearPositionCondition nearPosition = skipAetheryteCondition.NearPosition;
|
NearPositionCondition nearPosition = skipAetheryteCondition.NearPosition;
|
||||||
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && Vector3.Distance(nearPosition.Position, clientState.LocalPlayer.Position) <= nearPosition.MaximumDistance)
|
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && gameObject != null && Vector3.Distance(nearPosition.Position, gameObject.Position) <= nearPosition.MaximumDistance)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte shortcut, as we're near the position");
|
logger.LogInformation("Skipping aetheryte shortcut, as we're near the position");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
NearPositionCondition notNearPosition = skipAetheryteCondition.NotNearPosition;
|
NearPositionCondition notNearPosition = skipAetheryteCondition.NotNearPosition;
|
||||||
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, clientState.LocalPlayer.Position))
|
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && gameObject != null && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, gameObject.Position))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte shortcut, as we're not near the position");
|
logger.LogInformation("Skipping aetheryte shortcut, as we're not near the position");
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -134,14 +136,17 @@ internal static class AetheryteShortcut
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (base.Task.ExpectedTerritoryId == territoryType && !skipAetheryteCondition.Never)
|
if (base.Task.ExpectedTerritoryId == territoryType)
|
||||||
|
{
|
||||||
|
IGameObject gameObject2 = objectTable[0];
|
||||||
|
if (gameObject2 != null && !skipAetheryteCondition.Never)
|
||||||
{
|
{
|
||||||
if (skipAetheryteCondition != null && skipAetheryteCondition.InSameTerritory)
|
if (skipAetheryteCondition != null && skipAetheryteCondition.InSameTerritory)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (InSameTerritory)");
|
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (InSameTerritory)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Vector3 position = clientState.LocalPlayer.Position;
|
Vector3 position = gameObject2.Position;
|
||||||
if (base.Task.Step.Position.HasValue && (position - base.Task.Step.Position.Value).Length() < base.Task.Step.CalculateActualStopDistance())
|
if (base.Task.Step.Position.HasValue && (position - base.Task.Step.Position.Value).Length() < base.Task.Step.CalculateActualStopDistance())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte teleport, we're near the target");
|
logger.LogInformation("Skipping aetheryte teleport, we're near the target");
|
||||||
|
|
@ -154,6 +159,7 @@ internal static class AetheryteShortcut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,7 +214,7 @@ internal static class AetheryteShortcut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class MoveAwayFromAetheryteExecutor(MoveExecutor moveExecutor, AetheryteData aetheryteData, IClientState clientState) : TaskExecutor<MoveAwayFromAetheryte>()
|
internal sealed class MoveAwayFromAetheryteExecutor(MoveExecutor moveExecutor, AetheryteData aetheryteData, IClientState clientState, IObjectTable objectTable) : TaskExecutor<MoveAwayFromAetheryte>()
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<EAetheryteLocation, List<Vector3>> AetherytesToMoveFrom;
|
private static readonly Dictionary<EAetheryteLocation, List<Vector3>> AetherytesToMoveFrom;
|
||||||
|
|
||||||
|
|
@ -219,7 +225,12 @@ internal static class AetheryteShortcut
|
||||||
|
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
Vector3 playerPosition = clientState.LocalPlayer.Position;
|
IGameObject gameObject = objectTable[0];
|
||||||
|
if (gameObject == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Vector3 playerPosition = gameObject.Position;
|
||||||
if (aetheryteData.CalculateDistance(playerPosition, clientState.TerritoryType, base.Task.TargetAetheryte) >= 20f)
|
if (aetheryteData.CalculateDistance(playerPosition, clientState.TerritoryType, base.Task.TargetAetheryte) >= 20f)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
|
@ -30,26 +31,26 @@ internal static class Craft
|
||||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2]
|
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2]
|
||||||
{
|
{
|
||||||
new Questionable.Controller.Steps.Common.Mount.UnmountTask(),
|
new Questionable.Controller.Steps.Common.Mount.UnmountTask(),
|
||||||
new CraftTask(step.ItemId.Value, step.ItemCount.Value)
|
new CraftTask(step.ItemId.Value, step.ItemCount.Value, step.RequireHq == true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed record CraftTask(uint ItemId, int ItemCount) : ITask
|
internal sealed record CraftTask(uint ItemId, int ItemCount, bool RequireHq) : ITask
|
||||||
{
|
{
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"Craft {ItemCount}x {ItemId} (with Artisan)";
|
return $"Craft {ItemCount}x {ItemId}{(RequireHq ? " (HQ)" : "")} (with Artisan)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoCraft(IDataManager dataManager, IClientState clientState, ArtisanIpc artisanIpc, ILogger<DoCraft> logger) : TaskExecutor<CraftTask>()
|
internal sealed class DoCraft(IDataManager dataManager, IObjectTable objectTable, ArtisanIpc artisanIpc, ILogger<DoCraft> logger) : TaskExecutor<CraftTask>()
|
||||||
{
|
{
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
if (HasRequestedItems())
|
if (HasRequestedItems())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Already own {ItemCount}x {ItemId}", base.Task.ItemCount, base.Task.ItemId);
|
logger.LogInformation("Already own {ItemCount}x {ItemId}{HqSuffix}", base.Task.ItemCount, base.Task.ItemId, base.Task.RequireHq ? " (HQ)" : "");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
RecipeLookup? rowOrDefault = dataManager.GetExcelSheet<RecipeLookup>().GetRowOrDefault(base.Task.ItemId);
|
RecipeLookup? rowOrDefault = dataManager.GetExcelSheet<RecipeLookup>().GetRowOrDefault(base.Task.ItemId);
|
||||||
|
|
@ -57,7 +58,7 @@ internal static class Craft
|
||||||
{
|
{
|
||||||
throw new TaskException($"Item {base.Task.ItemId} is not craftable");
|
throw new TaskException($"Item {base.Task.ItemId} is not craftable");
|
||||||
}
|
}
|
||||||
uint num = (EClassJob)clientState.LocalPlayer.ClassJob.RowId switch
|
uint num = (EClassJob?)(objectTable[0] as ICharacter)?.ClassJob.RowId switch
|
||||||
{
|
{
|
||||||
EClassJob.Carpenter => rowOrDefault.Value.CRP.RowId,
|
EClassJob.Carpenter => rowOrDefault.Value.CRP.RowId,
|
||||||
EClassJob.Blacksmith => rowOrDefault.Value.BSM.RowId,
|
EClassJob.Blacksmith => rowOrDefault.Value.BSM.RowId,
|
||||||
|
|
@ -88,7 +89,7 @@ internal static class Craft
|
||||||
throw new TaskException($"Unable to determine recipe for item {base.Task.ItemId}");
|
throw new TaskException($"Unable to determine recipe for item {base.Task.ItemId}");
|
||||||
}
|
}
|
||||||
int num2 = base.Task.ItemCount - GetOwnedItemCount();
|
int num2 = base.Task.ItemCount - GetOwnedItemCount();
|
||||||
logger.LogInformation("Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items", base.Task.ItemId, num, num2);
|
logger.LogInformation("Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items{HqSuffix}", base.Task.ItemId, num, num2, base.Task.RequireHq ? " (HQ required)" : "");
|
||||||
if (!artisanIpc.CraftItem((ushort)num, num2))
|
if (!artisanIpc.CraftItem((ushort)num, num2))
|
||||||
{
|
{
|
||||||
throw new TaskException($"Failed to start Artisan craft for recipe {num}");
|
throw new TaskException($"Failed to start Artisan craft for recipe {num}");
|
||||||
|
|
@ -128,7 +129,16 @@ internal static class Craft
|
||||||
private unsafe int GetOwnedItemCount()
|
private unsafe int GetOwnedItemCount()
|
||||||
{
|
{
|
||||||
InventoryManager* ptr = InventoryManager.Instance();
|
InventoryManager* ptr = InventoryManager.Instance();
|
||||||
return ptr->GetInventoryItemCount(base.Task.ItemId, isHq: false, checkEquipped: false, checkArmory: true, 0) + ptr->GetInventoryItemCount(base.Task.ItemId, isHq: true, checkEquipped: false, checkArmory: true, 0);
|
if (base.Task.RequireHq)
|
||||||
|
{
|
||||||
|
int inventoryItemCount = ptr->GetInventoryItemCount(base.Task.ItemId, isHq: true, checkEquipped: false, checkArmory: true, 0);
|
||||||
|
logger.LogTrace("HQ item count for {ItemId}: {Count}", base.Task.ItemId, inventoryItemCount);
|
||||||
|
return inventoryItemCount;
|
||||||
|
}
|
||||||
|
int inventoryItemCount2 = ptr->GetInventoryItemCount(base.Task.ItemId, isHq: false, checkEquipped: false, checkArmory: true, 0);
|
||||||
|
int inventoryItemCount3 = ptr->GetInventoryItemCount(base.Task.ItemId, isHq: true, checkEquipped: false, checkArmory: true, 0);
|
||||||
|
logger.LogTrace("Total item count for {ItemId}: {NqCount} NQ + {HqCount} HQ = {Total}", base.Task.ItemId, inventoryItemCount2, inventoryItemCount3, inventoryItemCount2 + inventoryItemCount3);
|
||||||
|
return inventoryItemCount2 + inventoryItemCount3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ShouldInterruptOnDamage()
|
public override bool ShouldInterruptOnDamage()
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,17 @@ internal sealed class ExtraConditionUtils
|
||||||
{
|
{
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
public ExtraConditionUtils(IClientState clientState)
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
|
public ExtraConditionUtils(IClientState clientState, IObjectTable objectTable)
|
||||||
{
|
{
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_objectTable = objectTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MatchesExtraCondition(EExtraSkipCondition skipCondition)
|
public bool MatchesExtraCondition(EExtraSkipCondition skipCondition)
|
||||||
{
|
{
|
||||||
Vector3? vector = _clientState.LocalPlayer?.Position;
|
Vector3? vector = _objectTable[0]?.Position;
|
||||||
if (vector.HasValue && _clientState.TerritoryType != 0)
|
if (vector.HasValue && _clientState.TerritoryType != 0)
|
||||||
{
|
{
|
||||||
return MatchesExtraCondition(skipCondition, vector.Value, _clientState.TerritoryType);
|
return MatchesExtraCondition(skipCondition, vector.Value, _clientState.TerritoryType);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
@ -42,7 +43,7 @@ internal static class Gather
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DelayedGatheringExecutor(GatheringPointRegistry gatheringPointRegistry, TerritoryData territoryData, IClientState clientState, IServiceProvider serviceProvider, ILogger<DelayedGatheringExecutor> logger) : TaskExecutor<DelayedGatheringTask>(), IExtraTaskCreator, ITaskExecutor
|
internal sealed class DelayedGatheringExecutor(GatheringPointRegistry gatheringPointRegistry, TerritoryData territoryData, IClientState clientState, IObjectTable objectTable, IServiceProvider serviceProvider, ILogger<DelayedGatheringExecutor> logger) : TaskExecutor<DelayedGatheringTask>(), IExtraTaskCreator, ITaskExecutor
|
||||||
{
|
{
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
|
|
@ -56,8 +57,8 @@ internal static class Gather
|
||||||
|
|
||||||
public IEnumerable<ITask> CreateExtraTasks()
|
public IEnumerable<ITask> CreateExtraTasks()
|
||||||
{
|
{
|
||||||
EClassJob rowId = (EClassJob)clientState.LocalPlayer.ClassJob.RowId;
|
EClassJob valueOrDefault = ((EClassJob?)(objectTable[0] as ICharacter)?.ClassJob.RowId).GetValueOrDefault();
|
||||||
if (!gatheringPointRegistry.TryGetGatheringPointId(base.Task.GatheredItem.ItemId, rowId, out GatheringPointId gatheringPointId))
|
if (!gatheringPointRegistry.TryGetGatheringPointId(base.Task.GatheredItem.ItemId, valueOrDefault, out GatheringPointId gatheringPointId))
|
||||||
{
|
{
|
||||||
throw new TaskException($"No gathering point found for item {base.Task.GatheredItem.ItemId}");
|
throw new TaskException($"No gathering point found for item {base.Task.GatheredItem.ItemId}");
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +70,7 @@ internal static class Gather
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
switch (rowId)
|
switch (valueOrDefault)
|
||||||
{
|
{
|
||||||
case EClassJob.Miner:
|
case EClassJob.Miner:
|
||||||
yield return new Questionable.Controller.Steps.Interactions.Action.TriggerStatusIfMissing(EStatus.Prospect, EAction.Prospect);
|
yield return new Questionable.Controller.Steps.Interactions.Action.TriggerStatusIfMissing(EStatus.Prospect, EAction.Prospect);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
@ -45,14 +46,18 @@ internal static class SkipCondition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class CheckSkip(ILogger<CheckSkip> logger, Configuration configuration, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, ICondition condition, ExtraConditionUtils extraConditionUtils, ClassJobUtils classJobUtils) : TaskExecutor<SkipTask>()
|
internal sealed class CheckSkip(ILogger<CheckSkip> logger, Configuration configuration, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, IObjectTable objectTable, ICondition condition, ExtraConditionUtils extraConditionUtils, ClassJobUtils classJobUtils) : TaskExecutor<SkipTask>()
|
||||||
{
|
{
|
||||||
protected override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
SkipStepConditions skipConditions = base.Task.SkipConditions;
|
SkipStepConditions skipConditions = base.Task.SkipConditions;
|
||||||
QuestStep step = base.Task.Step;
|
QuestStep step = base.Task.Step;
|
||||||
ElementId elementId = base.Task.ElementId;
|
ElementId elementId = base.Task.ElementId;
|
||||||
logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions));
|
ILogger<CheckSkip> logger = logger;
|
||||||
|
object[] array = new object[1];
|
||||||
|
object reference = skipConditions;
|
||||||
|
array[0] = string.Join(",", new ReadOnlySpan<object>(in reference));
|
||||||
|
logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", array);
|
||||||
if (CheckFlyingCondition(step, skipConditions))
|
if (CheckFlyingCondition(step, skipConditions))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -200,9 +205,10 @@ internal static class SkipCondition
|
||||||
if (skipConditions.NotTargetable && step != null && step.DataId.HasValue)
|
if (skipConditions.NotTargetable && step != null && step.DataId.HasValue)
|
||||||
{
|
{
|
||||||
IGameObject gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
|
IGameObject gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
|
||||||
|
IGameObject gameObject2 = objectTable[0];
|
||||||
if (gameObject == null)
|
if (gameObject == null)
|
||||||
{
|
{
|
||||||
if ((step.Position.GetValueOrDefault() - clientState.LocalPlayer.Position).Length() < 100f)
|
if (gameObject2 != null && (step.Position.GetValueOrDefault() - gameObject2.Position).Length() < 100f)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, object is not nearby (but we are)");
|
logger.LogInformation("Skipping step, object is not nearby (but we are)");
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -247,6 +253,22 @@ internal static class SkipCondition
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
item = skipConditions.Item;
|
||||||
|
if (item != null && item.InInventory && step != null && step.ItemId.HasValue)
|
||||||
|
{
|
||||||
|
InventoryManager* ptr2 = InventoryManager.Instance();
|
||||||
|
if (ptr2->GetInventoryItemCount(step.ItemId.Value, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0 || ptr2->GetInventoryItemCount(step.ItemId.Value, isHq: true, checkEquipped: true, checkArmory: true, 0) > 0)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Skipping step, item with itemId {ItemId} already in inventory", step.ItemId.Value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item = skipConditions.Item;
|
||||||
|
if (item != null && item.BetterOrEqualItemEquipped && step != null && step.ItemId.HasValue && IsBetterOrEqualItemEquipped(step.ItemId.Value))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Skipping step, better or equal item than {ItemId} is already equipped", step.ItemId.Value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,9 +346,10 @@ internal static class SkipCondition
|
||||||
|
|
||||||
private bool CheckLevelCondition(SkipStepConditions skipConditions)
|
private bool CheckLevelCondition(SkipStepConditions skipConditions)
|
||||||
{
|
{
|
||||||
if (skipConditions.MinimumLevel.HasValue && clientState.LocalPlayer != null && clientState.LocalPlayer.Level >= skipConditions.MinimumLevel.Value)
|
ICharacter character = objectTable[0] as ICharacter;
|
||||||
|
if (skipConditions.MinimumLevel.HasValue && character != null && character.Level >= skipConditions.MinimumLevel.Value)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as player level {CurrentLevel} >= minimum level {MinLevel}", clientState.LocalPlayer.Level, skipConditions.MinimumLevel.Value);
|
logger.LogInformation("Skipping step, as player level {CurrentLevel} >= minimum level {MinLevel}", character.Level, skipConditions.MinimumLevel.Value);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -391,9 +414,9 @@ internal static class SkipCondition
|
||||||
if (requiredCurrentJob != null && requiredCurrentJob.Count > 0)
|
if (requiredCurrentJob != null && requiredCurrentJob.Count > 0)
|
||||||
{
|
{
|
||||||
List<EClassJob> list = step.RequiredCurrentJob.SelectMany((EExtendedClassJob x) => classJobUtils.AsIndividualJobs(x, elementId)).ToList();
|
List<EClassJob> list = step.RequiredCurrentJob.SelectMany((EExtendedClassJob x) => classJobUtils.AsIndividualJobs(x, elementId)).ToList();
|
||||||
EClassJob rowId = (EClassJob)clientState.LocalPlayer.ClassJob.RowId;
|
EClassJob valueOrDefault = ((EClassJob?)(objectTable[0] as ICharacter)?.ClassJob.RowId).GetValueOrDefault();
|
||||||
logger.LogInformation("Checking current job {CurrentJob} against {ExpectedJobs}", rowId, string.Join(",", list));
|
logger.LogInformation("Checking current job {CurrentJob} against {ExpectedJobs}", valueOrDefault, string.Join(",", list));
|
||||||
if (!list.Contains(rowId))
|
if (!list.Contains(valueOrDefault))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as step requires a different job");
|
logger.LogInformation("Skipping step, as step requires a different job");
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -405,14 +428,15 @@ internal static class SkipCondition
|
||||||
|
|
||||||
private bool CheckPositionCondition(SkipStepConditions skipConditions)
|
private bool CheckPositionCondition(SkipStepConditions skipConditions)
|
||||||
{
|
{
|
||||||
|
IGameObject gameObject = objectTable[0];
|
||||||
NearPositionCondition nearPosition = skipConditions.NearPosition;
|
NearPositionCondition nearPosition = skipConditions.NearPosition;
|
||||||
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && Vector3.Distance(nearPosition.Position, clientState.LocalPlayer.Position) <= nearPosition.MaximumDistance)
|
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && gameObject != null && Vector3.Distance(nearPosition.Position, gameObject.Position) <= nearPosition.MaximumDistance)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as we're near the position");
|
logger.LogInformation("Skipping step, as we're near the position");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
NearPositionCondition notNearPosition = skipConditions.NotNearPosition;
|
NearPositionCondition notNearPosition = skipConditions.NotNearPosition;
|
||||||
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, clientState.LocalPlayer.Position))
|
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && gameObject != null && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, gameObject.Position))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as we're not near the position");
|
logger.LogInformation("Skipping step, as we're not near the position");
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -461,6 +485,39 @@ internal static class SkipCondition
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe bool IsBetterOrEqualItemEquipped(uint itemId)
|
||||||
|
{
|
||||||
|
if (objectTable[0] == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InventoryManager* ptr = InventoryManager.Instance();
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint itemLevel = gameFunctions.GetItemLevel(itemId);
|
||||||
|
if (itemLevel == 0)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Could not get item level for item {ItemId}, skipping equip step", itemId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ptr->GetInventoryItemCount(itemId, isHq: false, checkEquipped: true, checkArmory: true, 0) == 0 && ptr->GetInventoryItemCount(itemId, isHq: true, checkEquipped: true, checkArmory: true, 0) == 0)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Item {ItemId} not found in inventory, skipping equip step", itemId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
InventoryItem* inventorySlot = ptr->GetInventoryContainer(InventoryType.EquippedItems)->GetInventorySlot(0);
|
||||||
|
if (inventorySlot == null || inventorySlot->ItemId == 0)
|
||||||
|
{
|
||||||
|
logger.LogDebug("No item equipped in main hand slot, should equip item {ItemId}", itemId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint itemLevel2 = gameFunctions.GetItemLevel(inventorySlot->ItemId);
|
||||||
|
logger.LogDebug("Comparing equipped item level {EquippedLevel} with target item level {TargetLevel}", itemLevel2, itemLevel);
|
||||||
|
return itemLevel2 >= itemLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public override ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
return ETaskResult.SkipRemainingTasksForStep;
|
return ETaskResult.SkipRemainingTasksForStep;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
using LLib.GameData;
|
using LLib.GameData;
|
||||||
|
|
@ -31,11 +32,11 @@ internal static class SwitchClassJob
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class SwitchClassJobExecutor(IClientState clientState) : AbstractDelayedTaskExecutor<Task>()
|
internal sealed class SwitchClassJobExecutor(IObjectTable objectTable) : AbstractDelayedTaskExecutor<Task>()
|
||||||
{
|
{
|
||||||
protected unsafe override bool StartInternal()
|
protected unsafe override bool StartInternal()
|
||||||
{
|
{
|
||||||
if (clientState.LocalPlayer.ClassJob.RowId == (uint)base.Task.ClassJob)
|
if ((objectTable[0] as ICharacter)?.ClassJob.RowId == (uint?)base.Task.ClassJob)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Questionable.Controller.Steps.Shared;
|
||||||
|
|
||||||
internal static class WaitAtEnd
|
internal static class WaitAtEnd
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IClientState clientState, ICondition condition, TerritoryData territoryData, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc) : ITaskFactory
|
internal sealed class Factory(IClientState clientState, IObjectTable objectTable, ICondition condition, TerritoryData territoryData, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
|
|
@ -129,10 +129,10 @@ internal static class WaitAtEnd
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Vector3 lastPosition = step.Position ?? clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
Vector3 lastPosition = step.Position ?? objectTable[0]?.Position ?? Vector3.Zero;
|
||||||
task = new WaitCondition.Task(delegate
|
task = new WaitCondition.Task(delegate
|
||||||
{
|
{
|
||||||
Vector3? vector = clientState.LocalPlayer?.Position;
|
Vector3? vector = objectTable[0]?.Position;
|
||||||
return vector.HasValue && (lastPosition - vector.Value).Length() > 2f;
|
return vector.HasValue && (lastPosition - vector.Value).Length() > 2f;
|
||||||
}, "Wait(tp away from " + lastPosition.ToString("G", CultureInfo.InvariantCulture) + ")");
|
}, "Wait(tp away from " + lastPosition.ToString("G", CultureInfo.InvariantCulture) + ")");
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,9 @@ internal sealed class TaskCreator
|
||||||
int index = 1;
|
int index = 1;
|
||||||
List<ITask> list3 = new List<ITask>(index);
|
List<ITask> list3 = new List<ITask>(index);
|
||||||
CollectionsMarshal.SetCount(list3, index);
|
CollectionsMarshal.SetCount(list3, index);
|
||||||
Span<ITask> span = CollectionsMarshal.AsSpan(list3);
|
Span<ITask> span2 = CollectionsMarshal.AsSpan(list3);
|
||||||
int num = 0;
|
int num = 0;
|
||||||
span[num] = new WaitAtEnd.WaitNextStepOrSequence();
|
span2[num] = new WaitAtEnd.WaitNextStepOrSequence();
|
||||||
list2 = list3;
|
list2 = list3;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -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)}");
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,32 @@ using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
||||||
using LLib.GameData;
|
using LLib.GameData;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Data;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Controller.Utils;
|
namespace Questionable.Controller.Utils;
|
||||||
|
|
||||||
internal sealed class PartyWatchDog : IDisposable
|
internal sealed class PartyWatchdog : IDisposable
|
||||||
{
|
{
|
||||||
private readonly QuestController _questController;
|
private readonly QuestController _questController;
|
||||||
|
|
||||||
|
private readonly Configuration _configuration;
|
||||||
|
|
||||||
|
private readonly TerritoryData _territoryData;
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
private readonly IChatGui _chatGui;
|
private readonly IChatGui _chatGui;
|
||||||
|
|
||||||
private readonly ILogger<PartyWatchDog> _logger;
|
private readonly ILogger<PartyWatchdog> _logger;
|
||||||
|
|
||||||
private ushort? _uncheckedTeritoryId;
|
private ushort? _uncheckedTeritoryId;
|
||||||
|
|
||||||
public PartyWatchDog(QuestController questController, IClientState clientState, IChatGui chatGui, ILogger<PartyWatchDog> logger)
|
public PartyWatchdog(QuestController questController, Configuration configuration, TerritoryData territoryData, IClientState clientState, IChatGui chatGui, ILogger<PartyWatchdog> logger)
|
||||||
{
|
{
|
||||||
_questController = questController;
|
_questController = questController;
|
||||||
|
_configuration = configuration;
|
||||||
|
_territoryData = territoryData;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
@ -29,6 +37,8 @@ internal sealed class PartyWatchDog : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void TerritoryChanged(ushort newTerritoryId)
|
private unsafe void TerritoryChanged(ushort newTerritoryId)
|
||||||
|
{
|
||||||
|
if (!_configuration.Advanced.DisablePartyWatchdog)
|
||||||
{
|
{
|
||||||
switch ((ETerritoryIntendedUse)GameMain.Instance()->CurrentTerritoryIntendedUseId)
|
switch ((ETerritoryIntendedUse)GameMain.Instance()->CurrentTerritoryIntendedUseId)
|
||||||
{
|
{
|
||||||
|
|
@ -97,25 +107,47 @@ internal sealed class PartyWatchDog : IDisposable
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe void Update()
|
public unsafe void Update()
|
||||||
{
|
{
|
||||||
if (_uncheckedTeritoryId != _clientState.TerritoryType || GameMain.Instance()->TerritoryLoadState != 2)
|
if (_configuration.Advanced.DisablePartyWatchdog || _uncheckedTeritoryId != _clientState.TerritoryType || GameMain.Instance()->TerritoryLoadState != 2)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GroupManager* ptr = GroupManager.Instance();
|
GroupManager* ptr = GroupManager.Instance();
|
||||||
if (ptr != null)
|
if (ptr == null)
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
byte memberCount = ptr->MainGroup.MemberCount;
|
byte memberCount = ptr->MainGroup.MemberCount;
|
||||||
bool isAlliance = ptr->MainGroup.IsAlliance;
|
bool isAlliance = ptr->MainGroup.IsAlliance;
|
||||||
_logger.LogDebug("Territory {TerritoryId} with {MemberCount} members, alliance: {IsInAlliance}", _uncheckedTeritoryId, memberCount, isAlliance);
|
_logger.LogDebug("Territory {TerritoryId} with {MemberCount} members, alliance: {IsInAlliance}", _uncheckedTeritoryId, memberCount, isAlliance);
|
||||||
if (memberCount > 1 || isAlliance)
|
if (memberCount > 1 || isAlliance)
|
||||||
|
{
|
||||||
|
if (!IsDutyConfiguredForParty(_uncheckedTeritoryId.Value))
|
||||||
{
|
{
|
||||||
StopIfRunning("Other party members present");
|
StopIfRunning("Other party members present");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Party detected but duty is configured for Unsync (Party) mode, continuing");
|
||||||
|
}
|
||||||
|
}
|
||||||
_uncheckedTeritoryId = null;
|
_uncheckedTeritoryId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsDutyConfiguredForParty(ushort territoryId)
|
||||||
|
{
|
||||||
|
if (!_territoryData.TryGetContentFinderConditionIdForTerritory(territoryId, out var cfcId))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value))
|
||||||
|
{
|
||||||
|
return value == EDutyMode.UnsyncParty;
|
||||||
|
}
|
||||||
|
return _configuration.Duties.DefaultDutyMode == EDutyMode.UnsyncParty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StopIfRunning(string reason)
|
private void StopIfRunning(string reason)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@ using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
|
@ -323,12 +321,13 @@ internal sealed class CombatController : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
IGameObject localPlayer = _objectTable[0];
|
||||||
IGameObject gameObject = (from x in _objectTable
|
IGameObject gameObject = (from x in _objectTable
|
||||||
select new
|
select new
|
||||||
{
|
{
|
||||||
GameObject = x,
|
GameObject = x,
|
||||||
Priority = GetKillPriority(x).Priority,
|
Priority = GetKillPriority(x).Priority,
|
||||||
Distance = Vector3.Distance(x.Position, _clientState.LocalPlayer.Position)
|
Distance = Vector3.Distance(x.Position, localPlayer?.Position ?? Vector3.Zero)
|
||||||
} into x
|
} into x
|
||||||
where x.Priority > 0
|
where x.Priority > 0
|
||||||
orderby x.Priority descending, x.Distance
|
orderby x.Priority descending, x.Distance
|
||||||
|
|
@ -355,7 +354,8 @@ internal sealed class CombatController : IDisposable
|
||||||
}
|
}
|
||||||
if (gameObject is IBattleNpc battleNpc && battleNpc.StatusFlags.HasFlag(StatusFlags.InCombat))
|
if (gameObject is IBattleNpc battleNpc && battleNpc.StatusFlags.HasFlag(StatusFlags.InCombat))
|
||||||
{
|
{
|
||||||
if (gameObject.TargetObjectId == _clientState.LocalPlayer?.GameObjectId)
|
IGameObject? gameObject2 = _objectTable[0];
|
||||||
|
if (gameObject.TargetObjectId == gameObject2?.GameObjectId)
|
||||||
{
|
{
|
||||||
return (Priority: num.Value + 150, Reason: text + "/Targeted");
|
return (Priority: num.Value + 150, Reason: text + "/Targeted");
|
||||||
}
|
}
|
||||||
|
|
@ -393,11 +393,12 @@ internal sealed class CombatController : IDisposable
|
||||||
}
|
}
|
||||||
List<ComplexCombatData> complexCombatDatas = _currentFight.Data.ComplexCombatDatas;
|
List<ComplexCombatData> complexCombatDatas = _currentFight.Data.ComplexCombatDatas;
|
||||||
GameObject* address = (GameObject*)gameObject.Address;
|
GameObject* address = (GameObject*)gameObject.Address;
|
||||||
if (address->FateId != 0 && _currentFight.Data.SpawnType != EEnemySpawnType.FateEnemies && gameObject.TargetObjectId != _clientState.LocalPlayer?.GameObjectId)
|
IGameObject gameObject2 = _objectTable[0];
|
||||||
|
if (address->FateId != 0 && _currentFight.Data.SpawnType != EEnemySpawnType.FateEnemies && gameObject.TargetObjectId != gameObject2?.GameObjectId)
|
||||||
{
|
{
|
||||||
return (Priority: null, Reason: "FATE mob");
|
return (Priority: null, Reason: "FATE mob");
|
||||||
}
|
}
|
||||||
Vector3 value = _clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
Vector3 value = gameObject2?.Position ?? Vector3.Zero;
|
||||||
bool flag = _currentFight.Data.SpawnType != EEnemySpawnType.FinishCombatIfAny && (_currentFight.Data.SpawnType != EEnemySpawnType.OverworldEnemies || !(Vector3.Distance(value, battleNpc.Position) >= 50f)) && _currentFight.Data.SpawnType != EEnemySpawnType.FateEnemies;
|
bool flag = _currentFight.Data.SpawnType != EEnemySpawnType.FinishCombatIfAny && (_currentFight.Data.SpawnType != EEnemySpawnType.OverworldEnemies || !(Vector3.Distance(value, battleNpc.Position) >= 50f)) && _currentFight.Data.SpawnType != EEnemySpawnType.FateEnemies;
|
||||||
if (complexCombatDatas.Count > 0)
|
if (complexCombatDatas.Count > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -430,6 +431,7 @@ internal sealed class CombatController : IDisposable
|
||||||
|
|
||||||
private void SetTarget(IGameObject? target)
|
private void SetTarget(IGameObject? target)
|
||||||
{
|
{
|
||||||
|
IGameObject gameObject = _objectTable[0];
|
||||||
if (target == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
if (_targetManager.Target != null)
|
if (_targetManager.Target != null)
|
||||||
|
|
@ -438,9 +440,9 @@ internal sealed class CombatController : IDisposable
|
||||||
_targetManager.Target = null;
|
_targetManager.Target = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Vector3.Distance(_clientState.LocalPlayer.Position, target.Position) > 55f)
|
else if (gameObject != null && Vector3.Distance(gameObject.Position, target.Position) > 55f)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Moving to target, distance: {Distance:N2}", Vector3.Distance(_clientState.LocalPlayer.Position, target.Position));
|
_logger.LogInformation("Moving to target, distance: {Distance:N2}", Vector3.Distance(gameObject.Position, target.Position));
|
||||||
MoveToTarget(target);
|
MoveToTarget(target);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -468,39 +470,45 @@ internal sealed class CombatController : IDisposable
|
||||||
|
|
||||||
private void MoveToTarget(IGameObject gameObject)
|
private void MoveToTarget(IGameObject gameObject)
|
||||||
{
|
{
|
||||||
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
|
IGameObject gameObject2 = _objectTable[0];
|
||||||
if (localPlayer == null)
|
if (gameObject2 == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float num = localPlayer.HitboxRadius + gameObject.HitboxRadius;
|
float num = gameObject2.HitboxRadius + gameObject.HitboxRadius;
|
||||||
float num2 = Vector3.Distance(localPlayer.Position, gameObject.Position);
|
float num2 = Vector3.Distance(gameObject2.Position, gameObject.Position);
|
||||||
byte? b = localPlayer.ClassJob.ValueNullable?.Role;
|
ICharacter character = gameObject2 as ICharacter;
|
||||||
bool flag;
|
bool flag = character != null;
|
||||||
|
bool flag2;
|
||||||
|
if (flag)
|
||||||
|
{
|
||||||
|
byte? b = character.ClassJob.ValueNullable?.Role;
|
||||||
if (b.HasValue)
|
if (b.HasValue)
|
||||||
{
|
{
|
||||||
byte valueOrDefault = b.GetValueOrDefault();
|
byte valueOrDefault = b.GetValueOrDefault();
|
||||||
if ((uint)(valueOrDefault - 3) <= 1u)
|
if ((uint)(valueOrDefault - 3) <= 1u)
|
||||||
{
|
{
|
||||||
flag = true;
|
flag2 = true;
|
||||||
goto IL_008e;
|
goto IL_00a3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flag = false;
|
flag2 = false;
|
||||||
goto IL_008e;
|
goto IL_00a3;
|
||||||
IL_008e:
|
}
|
||||||
|
goto IL_00a7;
|
||||||
|
IL_00a7:
|
||||||
float num3 = (flag ? 20f : 2.9f);
|
float num3 = (flag ? 20f : 2.9f);
|
||||||
bool flag2 = num2 - num >= num3;
|
bool flag3 = num2 - num >= num3;
|
||||||
bool flag3 = IsInLineOfSight(gameObject);
|
bool flag4 = IsInLineOfSight(gameObject);
|
||||||
if (flag2 || !flag3)
|
if (flag3 || !flag4)
|
||||||
{
|
{
|
||||||
bool flag4 = num2 - num > 5f;
|
bool flag5 = num2 - num > 5f;
|
||||||
if (!flag2 && !flag3)
|
if (!flag3 && !flag4)
|
||||||
{
|
{
|
||||||
num3 = Math.Min(num3, num2) / 2f;
|
num3 = Math.Min(num3, num2) / 2f;
|
||||||
flag4 = true;
|
flag5 = true;
|
||||||
}
|
}
|
||||||
if (!flag4)
|
if (!flag5)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Moving to {TargetName} ({BaseId}) to attack", gameObject.Name, gameObject.BaseId);
|
_logger.LogInformation("Moving to {TargetName} ({BaseId}) to attack", gameObject.Name, gameObject.BaseId);
|
||||||
MovementController movementController = _movementController;
|
MovementController movementController = _movementController;
|
||||||
|
|
@ -518,11 +526,20 @@ internal sealed class CombatController : IDisposable
|
||||||
_movementController.NavigateTo(EMovementType.Combat, null, gameObject.Position, fly: false, sprint: false, num3 + num - 0.25f, float.MaxValue);
|
_movementController.NavigateTo(EMovementType.Combat, null, gameObject.Position, fly: false, sprint: false, num3 + num - 0.25f, float.MaxValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
IL_00a3:
|
||||||
|
flag = flag2;
|
||||||
|
goto IL_00a7;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal unsafe bool IsInLineOfSight(IGameObject target)
|
internal unsafe bool IsInLineOfSight(IGameObject target)
|
||||||
{
|
{
|
||||||
Vector3 position = _clientState.LocalPlayer.Position;
|
IGameObject gameObject = _objectTable[0];
|
||||||
|
if (gameObject == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Vector3 position = gameObject.Position;
|
||||||
position.Y += 2f;
|
position.Y += 2f;
|
||||||
Vector3 position2 = target.Position;
|
Vector3 position2 = target.Position;
|
||||||
position2.Y += 2f;
|
position2.Y += 2f;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using Dalamud.Game.ClientState.Objects;
|
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
|
||||||
using Lumina.Excel;
|
|
||||||
using Lumina.Excel.Sheets;
|
|
||||||
using Questionable.Data;
|
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
|
||||||
using Questionable.Model.Common;
|
|
||||||
using Questionable.Model.Questing;
|
|
||||||
using Questionable.Model.Questing.Converter;
|
|
||||||
using Questionable.Windows;
|
using Questionable.Windows;
|
||||||
|
|
||||||
namespace Questionable.Controller;
|
namespace Questionable.Controller;
|
||||||
|
|
@ -33,67 +20,59 @@ internal sealed class CommandHandler : IDisposable
|
||||||
|
|
||||||
private readonly MovementController _movementController;
|
private readonly MovementController _movementController;
|
||||||
|
|
||||||
private readonly QuestRegistry _questRegistry;
|
|
||||||
|
|
||||||
private readonly ConfigWindow _configWindow;
|
private readonly ConfigWindow _configWindow;
|
||||||
|
|
||||||
private readonly DebugOverlay _debugOverlay;
|
|
||||||
|
|
||||||
private readonly OneTimeSetupWindow _oneTimeSetupWindow;
|
private readonly OneTimeSetupWindow _oneTimeSetupWindow;
|
||||||
|
|
||||||
private readonly QuestWindow _questWindow;
|
private readonly QuestWindow _questWindow;
|
||||||
|
|
||||||
private readonly QuestSelectionWindow _questSelectionWindow;
|
private readonly QuestSelectionWindow _questSelectionWindow;
|
||||||
|
|
||||||
|
private readonly QuestSequenceWindow _questSequenceWindow;
|
||||||
|
|
||||||
private readonly JournalProgressWindow _journalProgressWindow;
|
private readonly JournalProgressWindow _journalProgressWindow;
|
||||||
|
|
||||||
private readonly PriorityWindow _priorityWindow;
|
private readonly PriorityWindow _priorityWindow;
|
||||||
|
|
||||||
private readonly ITargetManager _targetManager;
|
private readonly ITargetManager _targetManager;
|
||||||
|
|
||||||
private readonly QuestFunctions _questFunctions;
|
|
||||||
|
|
||||||
private readonly GameFunctions _gameFunctions;
|
|
||||||
|
|
||||||
private readonly AetheryteFunctions _aetheryteFunctions;
|
|
||||||
|
|
||||||
private readonly IDataManager _dataManager;
|
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
private readonly IObjectTable _objectTable;
|
|
||||||
|
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
|
|
||||||
private IReadOnlyList<uint> _previouslyUnlockedUnlockLinks = Array.Empty<uint>();
|
private readonly ChangelogWindow _changelogWindow;
|
||||||
|
|
||||||
public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController, MovementController movementController, QuestRegistry questRegistry, ConfigWindow configWindow, DebugOverlay debugOverlay, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, QuestSelectionWindow questSelectionWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, ITargetManager targetManager, QuestFunctions questFunctions, GameFunctions gameFunctions, AetheryteFunctions aetheryteFunctions, IDataManager dataManager, IClientState clientState, IObjectTable objectTable, Configuration configuration)
|
public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController, MovementController movementController, QuestRegistry questRegistry, ConfigWindow configWindow, DebugOverlay debugOverlay, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, QuestSelectionWindow questSelectionWindow, QuestSequenceWindow questSequenceWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, ITargetManager targetManager, QuestFunctions questFunctions, GameFunctions gameFunctions, AetheryteFunctions aetheryteFunctions, IDataManager dataManager, IClientState clientState, IObjectTable objectTable, Configuration configuration, ChangelogWindow changelogWindow)
|
||||||
{
|
{
|
||||||
_commandManager = commandManager;
|
_commandManager = commandManager;
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
_questController = questController;
|
_questController = questController;
|
||||||
_movementController = movementController;
|
_movementController = movementController;
|
||||||
_questRegistry = questRegistry;
|
|
||||||
_configWindow = configWindow;
|
_configWindow = configWindow;
|
||||||
_debugOverlay = debugOverlay;
|
|
||||||
_oneTimeSetupWindow = oneTimeSetupWindow;
|
_oneTimeSetupWindow = oneTimeSetupWindow;
|
||||||
_questWindow = questWindow;
|
_questWindow = questWindow;
|
||||||
_questSelectionWindow = questSelectionWindow;
|
_questSelectionWindow = questSelectionWindow;
|
||||||
|
_questSequenceWindow = questSequenceWindow;
|
||||||
_journalProgressWindow = journalProgressWindow;
|
_journalProgressWindow = journalProgressWindow;
|
||||||
_priorityWindow = priorityWindow;
|
_priorityWindow = priorityWindow;
|
||||||
_targetManager = targetManager;
|
_targetManager = targetManager;
|
||||||
_questFunctions = questFunctions;
|
|
||||||
_gameFunctions = gameFunctions;
|
|
||||||
_aetheryteFunctions = aetheryteFunctions;
|
|
||||||
_dataManager = dataManager;
|
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_objectTable = objectTable;
|
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
_changelogWindow = changelogWindow;
|
||||||
_clientState.Logout += OnLogout;
|
_clientState.Logout += OnLogout;
|
||||||
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
|
ICommandManager commandManager2 = _commandManager;
|
||||||
{
|
CommandInfo commandInfo = new CommandInfo(ProcessCommand);
|
||||||
HelpMessage = string.Join(Environment.NewLine + "\t", "Opens the Questing window", "/qst help - displays simplified commands", "/qst help-all - displays all available commands", "/qst config - opens the configuration window", "/qst start - starts doing quests", "/qst stop - stops doing quests")
|
string separator = Environment.NewLine + "\t";
|
||||||
});
|
_003C_003Ey__InlineArray7<string> buffer = default(_003C_003Ey__InlineArray7<string>);
|
||||||
|
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<_003C_003Ey__InlineArray7<string>, string>(ref buffer, 0) = "Opens the Questing window";
|
||||||
|
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<_003C_003Ey__InlineArray7<string>, string>(ref buffer, 1) = "/qst help - displays simplified commands";
|
||||||
|
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<_003C_003Ey__InlineArray7<string>, string>(ref buffer, 2) = "/qst help-all - displays all available commands";
|
||||||
|
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<_003C_003Ey__InlineArray7<string>, string>(ref buffer, 3) = "/qst config - opens the configuration window";
|
||||||
|
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<_003C_003Ey__InlineArray7<string>, string>(ref buffer, 4) = "/qst changelog - opens the changelog window";
|
||||||
|
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<_003C_003Ey__InlineArray7<string>, string>(ref buffer, 5) = "/qst start - starts doing quests";
|
||||||
|
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<_003C_003Ey__InlineArray7<string>, string>(ref buffer, 6) = "/qst stop - stops doing quests";
|
||||||
|
commandInfo.HelpMessage = string.Join(separator, global::_003CPrivateImplementationDetails_003E.InlineArrayAsReadOnlySpan<_003C_003Ey__InlineArray7<string>, string>(in buffer, 7));
|
||||||
|
commandManager2.AddHandler("/qst", commandInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessCommand(string command, string arguments)
|
private void ProcessCommand(string command, string arguments)
|
||||||
|
|
@ -110,17 +89,19 @@ internal sealed class CommandHandler : IDisposable
|
||||||
_chatGui.Print("/qst help - displays simplified commands", "Questionable", 576);
|
_chatGui.Print("/qst help - displays simplified commands", "Questionable", 576);
|
||||||
_chatGui.Print("/qst help-all - displays all available commands", "Questionable", 576);
|
_chatGui.Print("/qst help-all - displays all available commands", "Questionable", 576);
|
||||||
_chatGui.Print("/qst config - opens the configuration window", "Questionable", 576);
|
_chatGui.Print("/qst config - opens the configuration window", "Questionable", 576);
|
||||||
|
_chatGui.Print("/qst changelog - opens the changelog window", "Questionable", 576);
|
||||||
_chatGui.Print("/qst start - starts doing quests", "Questionable", 576);
|
_chatGui.Print("/qst start - starts doing quests", "Questionable", 576);
|
||||||
_chatGui.Print("/qst stop - stops doing quests", "Questionable", 576);
|
_chatGui.Print("/qst stop - stops doing quests", "Questionable", 576);
|
||||||
_chatGui.Print("/qst reload - reload all quest data", "Questionable", 576);
|
_chatGui.Print("/qst reload - reload all quest data", "Questionable", 576);
|
||||||
break;
|
break;
|
||||||
case "help-all":
|
|
||||||
case "ha":
|
case "ha":
|
||||||
|
case "help-all":
|
||||||
_chatGui.Print("Available commands:", "Questionable", 576);
|
_chatGui.Print("Available commands:", "Questionable", 576);
|
||||||
_chatGui.Print("/qst - toggles the Questing window", "Questionable", 576);
|
_chatGui.Print("/qst - toggles the Questing window", "Questionable", 576);
|
||||||
_chatGui.Print("/qst help - displays available commands", "Questionable", 576);
|
_chatGui.Print("/qst help - displays available commands", "Questionable", 576);
|
||||||
_chatGui.Print("/qst help-all - displays all available commands", "Questionable", 576);
|
_chatGui.Print("/qst help-all - displays all available commands", "Questionable", 576);
|
||||||
_chatGui.Print("/qst config - opens the configuration window", "Questionable", 576);
|
_chatGui.Print("/qst config - opens the configuration window", "Questionable", 576);
|
||||||
|
_chatGui.Print("/qst changelog - opens the changelog window", "Questionable", 576);
|
||||||
_chatGui.Print("/qst start - starts doing quests", "Questionable", 576);
|
_chatGui.Print("/qst start - starts doing quests", "Questionable", 576);
|
||||||
_chatGui.Print("/qst stop - stops doing quests", "Questionable", 576);
|
_chatGui.Print("/qst stop - stops doing quests", "Questionable", 576);
|
||||||
_chatGui.Print("/qst reload - reload all quest data", "Questionable", 576);
|
_chatGui.Print("/qst reload - reload all quest data", "Questionable", 576);
|
||||||
|
|
@ -134,6 +115,10 @@ internal sealed class CommandHandler : IDisposable
|
||||||
case "config":
|
case "config":
|
||||||
_configWindow.ToggleOrUncollapse();
|
_configWindow.ToggleOrUncollapse();
|
||||||
break;
|
break;
|
||||||
|
case "cl":
|
||||||
|
case "changelog":
|
||||||
|
_changelogWindow.ToggleOrUncollapse();
|
||||||
|
break;
|
||||||
case "start":
|
case "start":
|
||||||
_questWindow.IsOpenAndUncollapsed = true;
|
_questWindow.IsOpenAndUncollapsed = true;
|
||||||
_questController.Start("Start command");
|
_questController.Start("Start command");
|
||||||
|
|
@ -173,441 +158,8 @@ internal sealed class CommandHandler : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void ProcessDebugCommand(string command, string arguments)
|
private void ProcessDebugCommand(string command, string arguments)
|
||||||
{
|
{
|
||||||
if (OpenSetupIfNeeded(arguments))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
string[] array = arguments.Split(' ');
|
|
||||||
string text = array[0];
|
|
||||||
if (text == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (text.Length)
|
|
||||||
{
|
|
||||||
case 4:
|
|
||||||
switch (text[0])
|
|
||||||
{
|
|
||||||
case 'n':
|
|
||||||
if (text == "next")
|
|
||||||
{
|
|
||||||
SetNextQuest(array.Skip(1).ToArray());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
{
|
|
||||||
if (!(text == "taxi"))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
List<string> list5 = new List<string>();
|
|
||||||
ExcelSheet<ChocoboTaxiStand> excelSheet = _dataManager.GetExcelSheet<ChocoboTaxiStand>();
|
|
||||||
UIState* ptr = UIState.Instance();
|
|
||||||
if (ptr == null)
|
|
||||||
{
|
|
||||||
_chatGui.PrintError("UIState is null", "Questionable", 576);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for (int num10 = 0; num10 < 192; num10++)
|
|
||||||
{
|
|
||||||
uint num11 = (uint)(num10 + 1179648);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (excelSheet.HasRow(num11) && ptr->IsChocoboTaxiStandUnlocked(num11))
|
|
||||||
{
|
|
||||||
string value14 = excelSheet.GetRow(num11).PlaceName.ToString();
|
|
||||||
if (string.IsNullOrEmpty(value14))
|
|
||||||
{
|
|
||||||
value14 = "Unknown";
|
|
||||||
}
|
|
||||||
list5.Add($"{value14} (ID: {num10}, Row: 0x{num11:X})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_chatGui.Print($"Unlocked taxi stands ({list5.Count}):", "Questionable", 576);
|
|
||||||
if (list5.Count == 0)
|
|
||||||
{
|
|
||||||
_chatGui.Print(" (No unlocked taxi stands found)", "Questionable", 576);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
foreach (string item5 in list5)
|
|
||||||
{
|
|
||||||
_chatGui.Print(" - " + item5, "Questionable", 576);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
switch (text[0])
|
|
||||||
{
|
|
||||||
case 'a':
|
|
||||||
if (text == "abandon-duty")
|
|
||||||
{
|
|
||||||
_gameFunctions.AbandonDuty();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
{
|
|
||||||
if (!(text == "unlock-links"))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
IReadOnlyList<uint> unlockLinks = _gameFunctions.GetUnlockLinks();
|
|
||||||
if (unlockLinks.Count >= 0)
|
|
||||||
{
|
|
||||||
_chatGui.Print($"Saved {unlockLinks.Count} unlock links to log.", "Questionable", 576);
|
|
||||||
List<uint> list6 = unlockLinks.Except(_previouslyUnlockedUnlockLinks).ToList();
|
|
||||||
if (_previouslyUnlockedUnlockLinks.Count > 0 && list6.Count > 0)
|
|
||||||
{
|
|
||||||
_chatGui.Print("New unlock links: " + string.Join(", ", list6), "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_chatGui.PrintError("Could not query unlock links.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
_previouslyUnlockedUnlockLinks = unlockLinks;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
switch (text[0])
|
|
||||||
{
|
|
||||||
case 'f':
|
|
||||||
{
|
|
||||||
if (!(text == "festivals"))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
List<string> list4 = new List<string>();
|
|
||||||
for (byte b8 = 0; b8 < 4; b8++)
|
|
||||||
{
|
|
||||||
GameMain.Festival festival = GameMain.Instance()->ActiveFestivals[b8];
|
|
||||||
if (festival.Id == 0)
|
|
||||||
{
|
|
||||||
list4.Add($"Slot {b8}: None");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
list4.Add($"Slot {b8}: {festival.Id}({festival.Phase})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_chatGui.Print("Festival slots:", "Questionable", 576);
|
|
||||||
{
|
|
||||||
foreach (string item6 in list4)
|
|
||||||
{
|
|
||||||
_chatGui.Print(" " + item6, "Questionable", 576);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'a':
|
|
||||||
{
|
|
||||||
if (!(text == "aethernet"))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ushort territoryType = _clientState.TerritoryType;
|
|
||||||
Dictionary<EAetheryteLocation, string> values = AethernetShardConverter.Values;
|
|
||||||
AetheryteData aetheryteData = new AetheryteData(_dataManager);
|
|
||||||
HashSet<string> hashSet2 = new HashSet<string>();
|
|
||||||
Dictionary<string, List<(EAetheryteLocation, string, bool)>> dictionary = new Dictionary<string, List<(EAetheryteLocation, string, bool)>>();
|
|
||||||
EAetheryteLocation key;
|
|
||||||
string value10;
|
|
||||||
foreach (KeyValuePair<EAetheryteLocation, string> item7 in values)
|
|
||||||
{
|
|
||||||
item7.Deconstruct(out key, out value10);
|
|
||||||
EAetheryteLocation key2 = key;
|
|
||||||
string text2 = value10;
|
|
||||||
if (aetheryteData.TerritoryIds.TryGetValue(key2, out var value11) && value11 == territoryType)
|
|
||||||
{
|
|
||||||
int num8 = text2.IndexOf(']', StringComparison.Ordinal);
|
|
||||||
if (num8 > 0)
|
|
||||||
{
|
|
||||||
string item3 = text2.Substring(1, num8 - 1);
|
|
||||||
hashSet2.Add(item3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hashSet2.Count == 0)
|
|
||||||
{
|
|
||||||
_chatGui.Print("No aethernet shards found in current zone.", "Questionable", 576);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
foreach (KeyValuePair<EAetheryteLocation, string> item8 in values)
|
|
||||||
{
|
|
||||||
item8.Deconstruct(out key, out value10);
|
|
||||||
EAetheryteLocation eAetheryteLocation = key;
|
|
||||||
string text3 = value10;
|
|
||||||
int num9 = text3.IndexOf(']', StringComparison.Ordinal);
|
|
||||||
if (num9 <= 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
string text4 = text3.Substring(1, num9 - 1);
|
|
||||||
if (hashSet2.Contains(text4))
|
|
||||||
{
|
|
||||||
if (!dictionary.ContainsKey(text4))
|
|
||||||
{
|
|
||||||
dictionary[text4] = new List<(EAetheryteLocation, string, bool)>();
|
|
||||||
}
|
|
||||||
bool item4 = _aetheryteFunctions.IsAetheryteUnlocked(eAetheryteLocation);
|
|
||||||
dictionary[text4].Add((eAetheryteLocation, text3, item4));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
foreach (KeyValuePair<string, List<(EAetheryteLocation, string, bool)>> item9 in dictionary.OrderBy<KeyValuePair<string, List<(EAetheryteLocation, string, bool)>>, string>((KeyValuePair<string, List<(EAetheryteLocation Location, string Name, bool Unlocked)>> x) => x.Key))
|
|
||||||
{
|
|
||||||
item9.Deconstruct(out value10, out var value12);
|
|
||||||
string value13 = value10;
|
|
||||||
List<(EAetheryteLocation, string, bool)> list = value12;
|
|
||||||
List<(EAetheryteLocation, string, bool)> list2 = list.Where<(EAetheryteLocation, string, bool)>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => x.Unlocked).ToList();
|
|
||||||
List<(EAetheryteLocation, string, bool)> list3 = list.Where<(EAetheryteLocation, string, bool)>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => !x.Unlocked).ToList();
|
|
||||||
_chatGui.Print($"Aethernet Shards in {value13} ({list.Count} total):", "Questionable", 576);
|
|
||||||
_chatGui.Print($" Unlocked: {list2.Count}", "Questionable", 576);
|
|
||||||
_chatGui.Print($" Missing: {list3.Count}", "Questionable", 576);
|
|
||||||
_chatGui.Print("", "Questionable", 576);
|
|
||||||
if (list3.Count > 0)
|
|
||||||
{
|
|
||||||
_chatGui.Print("Missing/Unattuned Aethernet Shards:", "Questionable", 576);
|
|
||||||
foreach (var item10 in list3.OrderBy<(EAetheryteLocation, string, bool), string>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => x.Name))
|
|
||||||
{
|
|
||||||
_chatGui.Print(" " + item10.Item2, "Questionable", 576);
|
|
||||||
}
|
|
||||||
_chatGui.Print("", "Questionable", 576);
|
|
||||||
}
|
|
||||||
if (list2.Count > 0)
|
|
||||||
{
|
|
||||||
_chatGui.Print("Unlocked Aethernet Shards:", "Questionable", 576);
|
|
||||||
foreach (var item11 in list2.OrderBy<(EAetheryteLocation, string, bool), string>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => x.Name))
|
|
||||||
{
|
|
||||||
_chatGui.Print(" " + item11.Item2, "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dictionary.Count > 1)
|
|
||||||
{
|
|
||||||
_chatGui.Print("", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
if (text == "setup")
|
|
||||||
{
|
|
||||||
_oneTimeSetupWindow.IsOpenAndUncollapsed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (text == "do")
|
|
||||||
{
|
|
||||||
ConfigureDebugOverlay(array.Skip(1).ToArray());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
if (text == "sim")
|
|
||||||
{
|
|
||||||
SetSimulatedQuest(array.Skip(1).ToArray());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
if (text == "mountid")
|
|
||||||
{
|
|
||||||
PrintMountId();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
{
|
|
||||||
if (!(text == "quest-kills"))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
(QuestController.QuestProgress, QuestController.ECurrentQuestType)? currentQuestDetails = _questController.CurrentQuestDetails;
|
|
||||||
if (!currentQuestDetails.HasValue)
|
|
||||||
{
|
|
||||||
_chatGui.PrintError("No active quest.", "Questionable", 576);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
QuestController.QuestProgress item = currentQuestDetails.Value.Item1;
|
|
||||||
Questionable.Model.Quest quest = item.Quest;
|
|
||||||
QuestProgressInfo questProgressInfo = null;
|
|
||||||
if (quest.Id is QuestId elementId)
|
|
||||||
{
|
|
||||||
questProgressInfo = _questFunctions.GetQuestProgressInfo(elementId);
|
|
||||||
}
|
|
||||||
if (questProgressInfo == null)
|
|
||||||
{
|
|
||||||
_chatGui.PrintError("Unable to retrieve quest progress information.", "Questionable", 576);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
QuestSequence questSequence = quest.FindSequence(item.Sequence);
|
|
||||||
if (questSequence == null)
|
|
||||||
{
|
|
||||||
_chatGui.PrintError($"Sequence {item.Sequence} not found for quest {quest.Id}.", "Questionable", 576);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
QuestStep questStep = ((item.Step < questSequence.Steps.Count) ? questSequence.Steps[item.Step] : null);
|
|
||||||
if (questStep == null)
|
|
||||||
{
|
|
||||||
_chatGui.PrintError($"Step {item.Step} not found in sequence {item.Sequence}.", "Questionable", 576);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_chatGui.Print($"Quest: {quest.Info.Name} ({quest.Id})", "Questionable", 576);
|
|
||||||
_chatGui.Print($"Sequence: {item.Sequence}, Step: {item.Step}", "Questionable", 576);
|
|
||||||
_chatGui.Print("", "Questionable", 576);
|
|
||||||
_chatGui.Print("Quest Variables: " + string.Join(", ", questProgressInfo.Variables.Select((byte v, int i) => $"[{i}]={v}")), "Questionable", 576);
|
|
||||||
_chatGui.Print("", "Questionable", 576);
|
|
||||||
ExcelSheet<BNpcName> bnpcNameSheet = _dataManager.GetExcelSheet<BNpcName>();
|
|
||||||
HashSet<uint> hashSet = new HashSet<uint>(questStep.KillEnemyDataIds);
|
|
||||||
foreach (ComplexCombatData complexCombatDatum in questStep.ComplexCombatData)
|
|
||||||
{
|
|
||||||
hashSet.Add(complexCombatDatum.DataId);
|
|
||||||
}
|
|
||||||
if (hashSet.Count > 0)
|
|
||||||
{
|
|
||||||
_chatGui.Print($"All Enemy DataIds Found: {hashSet.Count}", "Questionable", 576);
|
|
||||||
foreach (uint item12 in hashSet.OrderBy((uint x) => x))
|
|
||||||
{
|
|
||||||
(string Name, bool Found) tuple = GetEnemyName(item12);
|
|
||||||
var (value, _) = tuple;
|
|
||||||
if (tuple.Found)
|
|
||||||
{
|
|
||||||
_chatGui.Print($" - {value} (DataId: {item12})", "Questionable", 576);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_chatGui.Print($" - DataId: {item12}", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_chatGui.Print("", "Questionable", 576);
|
|
||||||
}
|
|
||||||
if (questStep.ComplexCombatData.Count > 0)
|
|
||||||
{
|
|
||||||
_chatGui.Print($"Complex Combat Data Entries: {questStep.ComplexCombatData.Count}", "Questionable", 576);
|
|
||||||
_chatGui.Print("Kill Progress:", "Questionable", 576);
|
|
||||||
if (questStep.ComplexCombatData.Count == 1 && hashSet.Count > 1)
|
|
||||||
{
|
|
||||||
ComplexCombatData complexCombatData = questStep.ComplexCombatData[0];
|
|
||||||
int num = -1;
|
|
||||||
byte? b = null;
|
|
||||||
for (int num2 = 0; num2 < complexCombatData.CompletionQuestVariablesFlags.Count; num2++)
|
|
||||||
{
|
|
||||||
QuestWorkValue questWorkValue = complexCombatData.CompletionQuestVariablesFlags[num2];
|
|
||||||
if (questWorkValue != null && questWorkValue.Low.HasValue)
|
|
||||||
{
|
|
||||||
num = num2;
|
|
||||||
b = questWorkValue.Low;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byte b2 = (byte)(((num >= 0 && num < questProgressInfo.Variables.Count) ? questProgressInfo.Variables[num] : 0) & 0xF);
|
|
||||||
string value2 = (b.HasValue ? $" {b2}/{b}" : "");
|
|
||||||
string value3 = ((b.HasValue && b2 >= b) ? "✓" : "○");
|
|
||||||
foreach (uint item13 in hashSet.OrderBy((uint x) => x))
|
|
||||||
{
|
|
||||||
(string Name, bool Found) tuple3 = GetEnemyName(item13);
|
|
||||||
var (value4, _) = tuple3;
|
|
||||||
if (tuple3.Found)
|
|
||||||
{
|
|
||||||
_chatGui.Print($" {value3} Slay {value4}.{value2} (DataId: {item13})", "Questionable", 576);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_chatGui.Print($" {value3} Slay enemy.{value2} (DataId: {item13})", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int num3 = 0; num3 < questStep.ComplexCombatData.Count; num3++)
|
|
||||||
{
|
|
||||||
ComplexCombatData complexCombatData2 = questStep.ComplexCombatData[num3];
|
|
||||||
int num4 = -1;
|
|
||||||
byte? b3 = null;
|
|
||||||
bool flag = false;
|
|
||||||
for (int num5 = 0; num5 < complexCombatData2.CompletionQuestVariablesFlags.Count; num5++)
|
|
||||||
{
|
|
||||||
QuestWorkValue questWorkValue2 = complexCombatData2.CompletionQuestVariablesFlags[num5];
|
|
||||||
if (questWorkValue2 != null)
|
|
||||||
{
|
|
||||||
if (questWorkValue2.Low.HasValue)
|
|
||||||
{
|
|
||||||
num4 = num5;
|
|
||||||
b3 = questWorkValue2.Low;
|
|
||||||
flag = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (questWorkValue2.High.HasValue)
|
|
||||||
{
|
|
||||||
num4 = num5;
|
|
||||||
b3 = questWorkValue2.High;
|
|
||||||
flag = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byte b4 = (byte)((num4 >= 0 && num4 < questProgressInfo.Variables.Count) ? questProgressInfo.Variables[num4] : 0);
|
|
||||||
byte b5 = (flag ? ((byte)(b4 >> 4)) : ((byte)(b4 & 0xF)));
|
|
||||||
string value5;
|
|
||||||
if (complexCombatData2.NameId.HasValue)
|
|
||||||
{
|
|
||||||
BNpcName? bNpcName = bnpcNameSheet?.GetRowOrDefault(complexCombatData2.NameId.Value);
|
|
||||||
value5 = ((!bNpcName.HasValue || string.IsNullOrEmpty(bNpcName.Value.Singular.ToString())) ? "enemy" : bNpcName.Value.Singular.ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
(string Name, bool Found) tuple5 = GetEnemyName(complexCombatData2.DataId);
|
|
||||||
string item2 = tuple5.Name;
|
|
||||||
value5 = (tuple5.Found ? item2 : "enemy");
|
|
||||||
}
|
|
||||||
string value6 = (b3.HasValue ? $" {b5}/{b3}" : "");
|
|
||||||
string value7 = ((b3.HasValue && b5 >= b3) ? "✓" : "○");
|
|
||||||
string value8 = (complexCombatData2.NameId.HasValue ? $" (DataId: {complexCombatData2.DataId}, NameId: {complexCombatData2.NameId})" : $" (DataId: {complexCombatData2.DataId})");
|
|
||||||
_chatGui.Print($" {value7} Slay {value5}.{value6}{value8}", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_chatGui.Print("", "Questionable", 576);
|
|
||||||
}
|
|
||||||
else if (questStep.KillEnemyDataIds.Count == 0)
|
|
||||||
{
|
|
||||||
_chatGui.Print("No kill enemy data for this step.", "Questionable", 576);
|
|
||||||
_chatGui.Print("", "Questionable", 576);
|
|
||||||
}
|
|
||||||
if (questStep.CompletionQuestVariablesFlags.Count <= 0 || !questStep.CompletionQuestVariablesFlags.Any((QuestWorkValue x) => x != null))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_chatGui.Print("Completion Flags (Debug):", "Questionable", 576);
|
|
||||||
for (int num6 = 0; num6 < questStep.CompletionQuestVariablesFlags.Count; num6++)
|
|
||||||
{
|
|
||||||
QuestWorkValue questWorkValue3 = questStep.CompletionQuestVariablesFlags[num6];
|
|
||||||
if (questWorkValue3 != null)
|
|
||||||
{
|
|
||||||
int num7 = ((num6 < questProgressInfo.Variables.Count) ? questProgressInfo.Variables[num6] : 0);
|
|
||||||
byte b6 = (byte)(num7 >> 4);
|
|
||||||
byte b7 = (byte)(num7 & 0xF);
|
|
||||||
string value9 = (((!questWorkValue3.High.HasValue || questWorkValue3.High == b6) && (!questWorkValue3.Low.HasValue || questWorkValue3.Low == b7)) ? " ✓" : " ✗");
|
|
||||||
_chatGui.Print($" [{num6}] Expected: H={questWorkValue3.High?.ToString(CultureInfo.InvariantCulture) ?? "any"} L={questWorkValue3.Low?.ToString(CultureInfo.InvariantCulture) ?? "any"} | Actual: H={b6.ToString(CultureInfo.InvariantCulture)} L={b7.ToString(CultureInfo.InvariantCulture)}{value9}", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 6:
|
|
||||||
case 8:
|
|
||||||
case 10:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool OpenSetupIfNeeded(string arguments)
|
private bool OpenSetupIfNeeded(string arguments)
|
||||||
|
|
@ -627,110 +179,8 @@ internal sealed class CommandHandler : IDisposable
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfigureDebugOverlay(string[] arguments)
|
|
||||||
{
|
|
||||||
ElementId elementId;
|
|
||||||
if (!_debugOverlay.DrawConditions())
|
|
||||||
{
|
|
||||||
_chatGui.PrintError("You don't have the debug overlay enabled.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
else if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out elementId) && elementId != null)
|
|
||||||
{
|
|
||||||
if (_questRegistry.TryGetQuest(elementId, out Questionable.Model.Quest quest))
|
|
||||||
{
|
|
||||||
_debugOverlay.HighlightedQuest = quest.Id;
|
|
||||||
_chatGui.Print($"Set highlighted quest to {elementId} ({quest.Info.Name}).", "Questionable", 576);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_debugOverlay.HighlightedQuest = null;
|
|
||||||
_chatGui.Print("Cleared highlighted quest.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetNextQuest(string[] arguments)
|
|
||||||
{
|
|
||||||
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null)
|
|
||||||
{
|
|
||||||
Questionable.Model.Quest quest;
|
|
||||||
if (_questFunctions.IsQuestLocked(elementId))
|
|
||||||
{
|
|
||||||
_chatGui.PrintError($"Quest {elementId} is locked.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
else if (_questRegistry.TryGetQuest(elementId, out quest))
|
|
||||||
{
|
|
||||||
_questController.SetNextQuest(quest);
|
|
||||||
_chatGui.Print($"Set next quest to {elementId} ({quest.Info.Name}).", "Questionable", 576);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_questController.SetNextQuest(null);
|
|
||||||
_chatGui.Print("Cleared next quest.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetSimulatedQuest(string[] arguments)
|
|
||||||
{
|
|
||||||
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null)
|
|
||||||
{
|
|
||||||
if (_questRegistry.TryGetQuest(elementId, out Questionable.Model.Quest quest))
|
|
||||||
{
|
|
||||||
byte sequence = 0;
|
|
||||||
int step = 0;
|
|
||||||
if (arguments.Length >= 2 && byte.TryParse(arguments[1], out var result))
|
|
||||||
{
|
|
||||||
QuestSequence questSequence = quest.FindSequence(result);
|
|
||||||
if (questSequence != null)
|
|
||||||
{
|
|
||||||
sequence = questSequence.Sequence;
|
|
||||||
if (arguments.Length >= 3 && int.TryParse(arguments[2], out var result2) && questSequence.FindStep(result2) != null)
|
|
||||||
{
|
|
||||||
step = result2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_questController.SimulateQuest(quest, sequence, step);
|
|
||||||
_chatGui.Print($"Simulating quest {elementId} ({quest.Info.Name}).", "Questionable", 576);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_questController.SimulateQuest(null, 0, 0);
|
|
||||||
_chatGui.Print("Cleared simulated quest.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PrintMountId()
|
|
||||||
{
|
|
||||||
ushort? mountId = _gameFunctions.GetMountId();
|
|
||||||
if (mountId.HasValue)
|
|
||||||
{
|
|
||||||
Mount? rowOrDefault = _dataManager.GetExcelSheet<Mount>().GetRowOrDefault(mountId.Value);
|
|
||||||
_chatGui.Print($"Mount ID: {mountId}, Name: {rowOrDefault?.Singular}, Obtainable: {((rowOrDefault?.Order == -1) ? "No" : "Yes")}", "Questionable", 576);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_chatGui.Print("You are not mounted.", "Questionable", 576);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLogout(int type, int code)
|
private void OnLogout(int type, int code)
|
||||||
{
|
{
|
||||||
_previouslyUnlockedUnlockLinks = Array.Empty<uint>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.Gui.ContextMenu;
|
using Dalamud.Game.Gui.ContextMenu;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
@ -43,9 +44,11 @@ internal sealed class ContextMenuController : IDisposable
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
private readonly ILogger<ContextMenuController> _logger;
|
private readonly ILogger<ContextMenuController> _logger;
|
||||||
|
|
||||||
public ContextMenuController(IContextMenu contextMenu, QuestController questController, GatheringPointRegistry gatheringPointRegistry, GatheringData gatheringData, QuestRegistry questRegistry, QuestData questData, GameFunctions gameFunctions, QuestFunctions questFunctions, IGameGui gameGui, IChatGui chatGui, IClientState clientState, ILogger<ContextMenuController> logger)
|
public ContextMenuController(IContextMenu contextMenu, QuestController questController, GatheringPointRegistry gatheringPointRegistry, GatheringData gatheringData, QuestRegistry questRegistry, QuestData questData, GameFunctions gameFunctions, QuestFunctions questFunctions, IGameGui gameGui, IChatGui chatGui, IClientState clientState, IObjectTable objectTable, ILogger<ContextMenuController> logger)
|
||||||
{
|
{
|
||||||
_contextMenu = contextMenu;
|
_contextMenu = contextMenu;
|
||||||
_questController = questController;
|
_questController = questController;
|
||||||
|
|
@ -58,6 +61,7 @@ internal sealed class ContextMenuController : IDisposable
|
||||||
_gameGui = gameGui;
|
_gameGui = gameGui;
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_objectTable = objectTable;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_contextMenu.OnMenuOpened += MenuOpened;
|
_contextMenu.OnMenuOpened += MenuOpened;
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +117,11 @@ internal sealed class ContextMenuController : IDisposable
|
||||||
|
|
||||||
private unsafe void AddContextMenuEntry(IMenuOpenedArgs args, uint itemId, uint npcId, EClassJob classJob, string verb)
|
private unsafe void AddContextMenuEntry(IMenuOpenedArgs args, uint itemId, uint npcId, EClassJob classJob, string verb)
|
||||||
{
|
{
|
||||||
EClassJob rowId = (EClassJob)_clientState.LocalPlayer.ClassJob.RowId;
|
if (!(_objectTable[0] is ICharacter { ClassJob: var classJob2 }))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EClassJob rowId = (EClassJob)classJob2.RowId;
|
||||||
bool flag = classJob != rowId;
|
bool flag = classJob != rowId;
|
||||||
if (flag)
|
if (flag)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,9 @@ internal sealed class GatheringPointRegistry : IDisposable
|
||||||
private void LoadGatheringPointsFromAssembly()
|
private void LoadGatheringPointsFromAssembly()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Loading gathering points from assembly");
|
_logger.LogInformation("Loading gathering points from assembly");
|
||||||
foreach (var (value, value2) in AssemblyGatheringLocationLoader.GetLocations())
|
IReadOnlyDictionary<ushort, GatheringRoot> locations = AssemblyGatheringLocationLoader.GetLocations();
|
||||||
|
_logger.LogInformation("AssemblyGatheringLocationLoader returned {Count} locations", locations.Count);
|
||||||
|
foreach (var (value, value2) in locations)
|
||||||
{
|
{
|
||||||
_gatheringPoints[new GatheringPointId(value)] = value2;
|
_gatheringPoints[new GatheringPointId(value)] = value2;
|
||||||
}
|
}
|
||||||
|
|
@ -72,11 +74,11 @@ internal sealed class GatheringPointRegistry : IDisposable
|
||||||
private void LoadGatheringPointsFromProjectDirectory()
|
private void LoadGatheringPointsFromProjectDirectory()
|
||||||
{
|
{
|
||||||
DirectoryInfo directoryInfo = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
|
DirectoryInfo directoryInfo = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
|
||||||
if (directoryInfo == null)
|
_logger.LogInformation("Looking for gathering paths in solution directory: {SolutionDirectory}", directoryInfo?.FullName ?? "(null)");
|
||||||
|
if (directoryInfo != null)
|
||||||
{
|
{
|
||||||
return;
|
|
||||||
}
|
|
||||||
DirectoryInfo directoryInfo2 = new DirectoryInfo(Path.Combine(directoryInfo.FullName, "GatheringPaths"));
|
DirectoryInfo directoryInfo2 = new DirectoryInfo(Path.Combine(directoryInfo.FullName, "GatheringPaths"));
|
||||||
|
_logger.LogInformation("Gathering paths project directory: {PathProjectDirectory}, Exists: {Exists}", directoryInfo2.FullName, directoryInfo2.Exists);
|
||||||
if (!directoryInfo2.Exists)
|
if (!directoryInfo2.Exists)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -87,13 +89,17 @@ internal sealed class GatheringPointRegistry : IDisposable
|
||||||
{
|
{
|
||||||
LoadFromDirectory(new DirectoryInfo(Path.Combine(directoryInfo2.FullName, value)));
|
LoadFromDirectory(new DirectoryInfo(Path.Combine(directoryInfo2.FullName, value)));
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_gatheringPoints.Clear();
|
_gatheringPoints.Clear();
|
||||||
_logger.LogError(exception, "Failed to load gathering points from project directory");
|
_logger.LogError(exception, "Failed to load gathering points from project directory");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_logger.LogWarning("Could not determine solution directory from assembly location: {AssemblyLocation}", _pluginInterface.AssemblyLocation.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadGatheringPointFromStream(string fileName, Stream stream)
|
private void LoadGatheringPointFromStream(string fileName, Stream stream)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
|
@ -14,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
|
||||||
{
|
{
|
||||||
|
|
@ -135,18 +131,21 @@ internal sealed class InterruptHandler : IDisposable
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
private readonly TerritoryData _territoryData;
|
private readonly TerritoryData _territoryData;
|
||||||
|
|
||||||
private readonly ILogger<InterruptHandler> _logger;
|
private readonly ILogger<InterruptHandler> _logger;
|
||||||
|
|
||||||
public event EventHandler? Interrupted;
|
public event EventHandler? Interrupted;
|
||||||
|
|
||||||
public unsafe InterruptHandler(IGameInteropProvider gameInteropProvider, IClientState clientState, TerritoryData territoryData, ILogger<InterruptHandler> logger)
|
public unsafe InterruptHandler(IGameInteropProvider gameInteropProvider, IClientState clientState, IObjectTable objectTable, TerritoryData territoryData, ILogger<InterruptHandler> logger)
|
||||||
{
|
{
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,11 +157,12 @@ internal sealed class InterruptHandler : IDisposable
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
IGameObject gameObject = _objectTable[0];
|
||||||
for (int i = 0; i < effectHeader->TargetCount; i++)
|
for (int i = 0; i < effectHeader->TargetCount; i++)
|
||||||
{
|
{
|
||||||
int num = (int)(effectTail[i] & 0xFFFFFFFFu);
|
int num = (int)(effectTail[i] & 0xFFFFFFFFu);
|
||||||
EffectEntry* ptr = effectArray + 8 * i;
|
EffectEntry* ptr = effectArray + 8 * i;
|
||||||
bool flag = (uint)num == _clientState.LocalPlayer?.GameObjectId;
|
bool flag = (uint)num == gameObject?.GameObjectId;
|
||||||
if (flag)
|
if (flag)
|
||||||
{
|
{
|
||||||
EActionEffectType type = ptr->Type;
|
EActionEffectType type = ptr->Type;
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,10 @@ internal abstract class MiniTaskController<T> : IDisposable
|
||||||
|
|
||||||
public void OnErrorToast(ref SeString message, ref bool isHandled)
|
public void OnErrorToast(ref SeString message, ref bool isHandled)
|
||||||
{
|
{
|
||||||
|
if (_taskQueue.AllTasksComplete)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_taskQueue.CurrentTaskExecutor is IToastAware toastAware && toastAware.OnErrorToast(message))
|
if (_taskQueue.CurrentTaskExecutor is IToastAware toastAware && toastAware.OnErrorToast(message))
|
||||||
{
|
{
|
||||||
isHandled = true;
|
isHandled = true;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Ipc.Exceptions;
|
using Dalamud.Plugin.Ipc.Exceptions;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Enums;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -73,6 +74,8 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
|
||||||
private readonly ChatFunctions _chatFunctions;
|
private readonly ChatFunctions _chatFunctions;
|
||||||
|
|
@ -83,12 +86,24 @@ 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;
|
||||||
|
|
||||||
private Task<List<Vector3>>? _pathfindTask;
|
private Task<List<Vector3>>? _pathfindTask;
|
||||||
|
|
||||||
|
private long _pathfindStartTime;
|
||||||
|
|
||||||
|
private Vector3? _lastKnownPosition;
|
||||||
|
|
||||||
|
private long _lastPositionUpdateTime;
|
||||||
|
|
||||||
|
private Vector3? _expectedPosition;
|
||||||
|
|
||||||
|
private bool _isTrackingPlayerInput;
|
||||||
|
|
||||||
public bool IsNavmeshReady
|
public bool IsNavmeshReady
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -138,32 +153,87 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
public int BuiltNavmeshPercent => _navmeshIpc.GetBuildProgress();
|
public int BuiltNavmeshPercent => _navmeshIpc.GetBuildProgress();
|
||||||
|
|
||||||
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, GameFunctions gameFunctions, ChatFunctions chatFunctions, ICondition condition, MovementOverrideController movementOverrideController, AetheryteData aetheryteData, ILogger<MovementController> logger)
|
public bool IsNavmeshPathfindInProgress => _navmeshIpc.IsPathfindInProgress;
|
||||||
|
|
||||||
|
public int NumQueuedPathfindRequests => _navmeshIpc.NumQueuedPathfindRequests;
|
||||||
|
|
||||||
|
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;
|
||||||
|
_objectTable = objectTable;
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
_chatFunctions = chatFunctions;
|
_chatFunctions = chatFunctions;
|
||||||
_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)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Pathfinding appears stuck: {QueuedRequests} queued requests, task running for {Duration}ms", _navmeshIpc.NumQueuedPathfindRequests, Environment.TickCount64 - _pathfindStartTime);
|
||||||
|
}
|
||||||
if (_pathfindTask.IsCompletedSuccessfully)
|
if (_pathfindTask.IsCompletedSuccessfully)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Pathfinding complete, got {Count} points", _pathfindTask.Result.Count);
|
_logger.LogInformation("Pathfinding complete, got {Count} points", _pathfindTask.Result.Count);
|
||||||
if (_pathfindTask.Result.Count == 0)
|
if (_pathfindTask.Result.Count == 0)
|
||||||
{
|
{
|
||||||
|
if (Destination.NavmeshCalculations == 1)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Initial pathfinding returned 0 points, attempting to find accessible destination");
|
||||||
|
Vector3? vector = TryFindAccessibleDestination(Destination.Position, Destination.IsFlying, Destination.Land);
|
||||||
|
if (vector.HasValue && Vector3.Distance(Destination.Position, vector.Value) < 30f)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Retrying pathfinding with adjusted destination: {AdjustedDestination}", vector.Value.ToString("G", CultureInfo.InvariantCulture));
|
||||||
|
Restart(Destination with
|
||||||
|
{
|
||||||
|
Position = vector.Value
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Destination.IsFlying && Destination.Land)
|
||||||
|
{
|
||||||
|
_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.CancelAfter(TimeSpan.FromSeconds(30L));
|
||||||
|
Vector3 vector2 = _objectTable[0]?.Position ?? Vector3.Zero;
|
||||||
|
if (Destination.IsFlying)
|
||||||
|
{
|
||||||
|
Vector3 vector3 = vector2;
|
||||||
|
vector3.Y = vector2.Y + 0.2f;
|
||||||
|
vector2 = vector3;
|
||||||
|
}
|
||||||
|
_pathfindStartTime = Environment.TickCount64;
|
||||||
|
_pathfindTask = _navmeshIpc.PathfindWithTolerance(vector2, Destination.Position, Destination.IsFlying, 10f, _cancellationTokenSource.Token);
|
||||||
|
Destination.NavmeshCalculations++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
ResetPathfinding();
|
ResetPathfinding();
|
||||||
throw new PathfindingFailedException();
|
throw new PathfindingFailedException();
|
||||||
}
|
}
|
||||||
List<Vector3> list = _pathfindTask.Result.Skip(1).ToList();
|
List<Vector3> list = _pathfindTask.Result.Skip(1).ToList();
|
||||||
Vector3 p = _clientState.LocalPlayer?.Position ?? list[0];
|
Vector3 p = _objectTable[0]?.Position ?? list[0];
|
||||||
if (Destination.IsFlying && !_condition[ConditionFlag.InFlight] && _condition[ConditionFlag.Mounted] && (IsOnFlightPath(p) || list.Any(IsOnFlightPath)))
|
if (Destination.IsFlying && !_condition[ConditionFlag.InFlight] && _condition[ConditionFlag.Mounted] && (IsOnFlightPath(p) || list.Any(IsOnFlightPath)))
|
||||||
{
|
{
|
||||||
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);
|
||||||
|
|
@ -174,11 +244,17 @@ 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);
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
|
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
|
||||||
|
_pathfindStartTime = Environment.TickCount64;
|
||||||
_pathfindTask = _navmeshIpc.Pathfind(list.Last(), Destination.Position, Destination.IsFlying, _cancellationTokenSource.Token);
|
_pathfindTask = _navmeshIpc.Pathfind(list.Last(), Destination.Position, Destination.IsFlying, _cancellationTokenSource.Token);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -187,6 +263,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)
|
||||||
|
|
@ -220,7 +297,7 @@ internal sealed class MovementController : IDisposable
|
||||||
Restart(Destination);
|
Restart(Destination);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Vector3 vector = _clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
Vector3 vector4 = _objectTable[0]?.Position ?? Vector3.Zero;
|
||||||
if (Destination.MovementType == EMovementType.Landing)
|
if (Destination.MovementType == EMovementType.Landing)
|
||||||
{
|
{
|
||||||
if (!_condition[ConditionFlag.InFlight])
|
if (!_condition[ConditionFlag.InFlight])
|
||||||
|
|
@ -228,9 +305,9 @@ internal sealed class MovementController : IDisposable
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ((vector - Destination.Position).Length() < Destination.StopDistance)
|
else if ((vector4 - Destination.Position).Length() < Destination.StopDistance)
|
||||||
{
|
{
|
||||||
if (vector.Y - Destination.Position.Y <= Destination.VerticalStopDistance)
|
if (vector4.Y - Destination.Position.Y <= Destination.VerticalStopDistance)
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
@ -239,7 +316,7 @@ internal sealed class MovementController : IDisposable
|
||||||
IGameObject gameObject = _gameFunctions.FindObjectByDataId(Destination.DataId.Value);
|
IGameObject gameObject = _gameFunctions.FindObjectByDataId(Destination.DataId.Value);
|
||||||
if ((gameObject is ICharacter || gameObject is IEventObj) ? true : false)
|
if ((gameObject is ICharacter || gameObject is IEventObj) ? true : false)
|
||||||
{
|
{
|
||||||
if (Math.Abs(vector.Y - gameObject.Position.Y) < 1.95f)
|
if (Math.Abs(vector4.Y - gameObject.Position.Y) < 1.95f)
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +327,7 @@ internal sealed class MovementController : IDisposable
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
else if (Math.Abs(vector.Y - gameObject.Position.Y) < 1.95f)
|
else if (Math.Abs(vector4.Y - gameObject.Position.Y) < 1.95f)
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
@ -268,14 +345,80 @@ internal sealed class MovementController : IDisposable
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
List<Vector3> waypoints = _navmeshIpc.GetWaypoints();
|
List<Vector3> waypoints = _navmeshIpc.GetWaypoints();
|
||||||
Vector3? vector2 = _clientState.LocalPlayer?.Position;
|
Vector3? vector5 = _objectTable[0]?.Position;
|
||||||
if (vector2.HasValue && (!Destination.ShouldRecalculateNavmesh() || !RecalculateNavmesh(waypoints, vector2.Value)) && !Destination.IsFlying && !_condition[ConditionFlag.Mounted] && !_gameFunctions.HasStatusPreventingSprint() && Destination.CanSprint)
|
if (vector5.HasValue && (!Destination.ShouldRecalculateNavmesh() || !RecalculateNavmesh(waypoints, vector5.Value)) && !Destination.IsFlying && !_condition[ConditionFlag.Mounted] && !_gameFunctions.HasStatusPreventingSprint() && Destination.CanSprint)
|
||||||
{
|
{
|
||||||
TriggerSprintIfNeeded(waypoints, vector2.Value);
|
TriggerSprintIfNeeded(waypoints, vector5.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
@ -319,6 +462,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)
|
||||||
{
|
{
|
||||||
|
|
@ -331,7 +479,7 @@ internal sealed class MovementController : IDisposable
|
||||||
Destination.NavmeshCalculations++;
|
Destination.NavmeshCalculations++;
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
|
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
|
||||||
Vector3 vector2 = _clientState.LocalPlayer.Position;
|
Vector3 vector2 = _objectTable[0]?.Position ?? Vector3.Zero;
|
||||||
if (fly && _aetheryteData.CalculateDistance(vector2, _clientState.TerritoryType, EAetheryteLocation.CoerthasCentralHighlandsCampDragonhead) < 11f)
|
if (fly && _aetheryteData.CalculateDistance(vector2, _clientState.TerritoryType, EAetheryteLocation.CoerthasCentralHighlandsCampDragonhead) < 11f)
|
||||||
{
|
{
|
||||||
Vector3 vector = vector2;
|
Vector3 vector = vector2;
|
||||||
|
|
@ -345,11 +493,25 @@ internal sealed class MovementController : IDisposable
|
||||||
vector.Y = vector2.Y + 0.2f;
|
vector.Y = vector2.Y + 0.2f;
|
||||||
vector2 = vector;
|
vector2 = vector;
|
||||||
}
|
}
|
||||||
|
_pathfindStartTime = Environment.TickCount64;
|
||||||
|
if (fly && land)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Using tolerance-based pathfinding for landing (tolerance: 5.0)");
|
||||||
|
_pathfindTask = _navmeshIpc.PathfindWithTolerance(vector2, to, fly, 5f, _cancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
_pathfindTask = _navmeshIpc.Pathfind(vector2, to, fly, _cancellationTokenSource.Token);
|
_pathfindTask = _navmeshIpc.Pathfind(vector2, to, fly, _cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
|
@ -362,6 +524,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()
|
||||||
|
|
@ -380,6 +543,51 @@ internal sealed class MovementController : IDisposable
|
||||||
_pathfindTask = null;
|
_pathfindTask = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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[] array2 = ((!flying) ? new float[3] { 1f, 2f, 3f } : new float[3] { 3f, 5f, 10f });
|
||||||
|
for (int i = 0; i < array.Length; i++)
|
||||||
|
{
|
||||||
|
float num = array[i];
|
||||||
|
float num2 = array2[Math.Min(i, array2.Length - 1)];
|
||||||
|
Vector3? vector = _navmeshIpc.FindNearestMeshPoint(target, num, num2);
|
||||||
|
if (vector.HasValue)
|
||||||
|
{
|
||||||
|
float num3 = Vector3.Distance(target, vector.Value);
|
||||||
|
if (num3 <= num * 1.5f)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Adjusted destination from {Original} to {Adjusted} (distance: {Distance:F2}, extent: {ExtentXZ}/{ExtentY})", target.ToString("G", CultureInfo.InvariantCulture), vector.Value.ToString("G", CultureInfo.InvariantCulture), num3, num, num2);
|
||||||
|
}
|
||||||
|
return vector.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(target, flying, num);
|
||||||
|
if (!pointOnFloor.HasValue)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float num4 = Vector3.Distance(target, pointOnFloor.Value);
|
||||||
|
if (num4 <= num * 1.5f)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Adjusted destination via floor point from {Original} to {Adjusted} (distance: {Distance:F2}, extent: {ExtentXZ})", target.ToString("G", CultureInfo.InvariantCulture), pointOnFloor.Value.ToString("G", CultureInfo.InvariantCulture), num4, num);
|
||||||
|
}
|
||||||
|
return pointOnFloor.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_logger.LogWarning("Could not find accessible mesh point near {Target} within max extent {MaxExtent}", target.ToString("G", CultureInfo.InvariantCulture), array[^1]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe bool RecalculateNavmesh(List<Vector3> navPoints, Vector3 start)
|
private unsafe bool RecalculateNavmesh(List<Vector3> navPoints, Vector3 start)
|
||||||
{
|
{
|
||||||
if (Destination == null)
|
if (Destination == null)
|
||||||
|
|
@ -410,17 +618,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;
|
||||||
|
|
@ -447,14 +690,14 @@ internal sealed class MovementController : IDisposable
|
||||||
bool flag2;
|
bool flag2;
|
||||||
switch (GameMain.Instance()->CurrentTerritoryIntendedUseId)
|
switch (GameMain.Instance()->CurrentTerritoryIntendedUseId)
|
||||||
{
|
{
|
||||||
case 0:
|
case TerritoryIntendedUse.Town:
|
||||||
case 7:
|
case TerritoryIntendedUse.BeforeTrialDung:
|
||||||
case 13:
|
case TerritoryIntendedUse.HousingOutdoor:
|
||||||
case 14:
|
case TerritoryIntendedUse.HousingIndoor:
|
||||||
case 15:
|
case TerritoryIntendedUse.SoloOverworldInstances:
|
||||||
case 19:
|
case TerritoryIntendedUse.ChocoboSquareOld:
|
||||||
case 23:
|
case TerritoryIntendedUse.GoldSaucer:
|
||||||
case 29:
|
case TerritoryIntendedUse.SoloDuty:
|
||||||
flag2 = true;
|
flag2 = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -476,6 +719,7 @@ internal sealed class MovementController : IDisposable
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
|
StopPlayerInputTracking();
|
||||||
_navmeshIpc.Stop();
|
_navmeshIpc.Stop();
|
||||||
ResetPathfinding();
|
ResetPathfinding();
|
||||||
Destination = null;
|
Destination = null;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Numerics;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.Gui.Toast;
|
using Dalamud.Game.Gui.Toast;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
@ -15,6 +16,7 @@ using Questionable.Controller.Steps;
|
||||||
using Questionable.Controller.Steps.Interactions;
|
using Questionable.Controller.Steps.Interactions;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.External;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
@ -84,6 +86,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
|
||||||
private readonly QuestFunctions _questFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
|
|
@ -96,6 +100,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
|
|
||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
|
|
||||||
|
private readonly JournalData _journalData;
|
||||||
|
|
||||||
private readonly IKeyState _keyState;
|
private readonly IKeyState _keyState;
|
||||||
|
|
||||||
private readonly IChatGui _chatGui;
|
private readonly IChatGui _chatGui;
|
||||||
|
|
@ -110,6 +116,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
|
|
||||||
private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent;
|
private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent;
|
||||||
|
|
||||||
|
private readonly AutoDutyIpc _autoDutyIpc;
|
||||||
|
|
||||||
private readonly ILogger<QuestController> _logger;
|
private readonly ILogger<QuestController> _logger;
|
||||||
|
|
||||||
private readonly object _progressLock = new object();
|
private readonly object _progressLock = new object();
|
||||||
|
|
@ -142,6 +150,16 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
|
|
||||||
private DateTime _lastAutoRefresh = DateTime.MinValue;
|
private DateTime _lastAutoRefresh = DateTime.MinValue;
|
||||||
|
|
||||||
|
private bool _lastEscDown;
|
||||||
|
|
||||||
|
private int _escPressCount;
|
||||||
|
|
||||||
|
private DateTime _lastEscPressTime = DateTime.MinValue;
|
||||||
|
|
||||||
|
private static readonly TimeSpan EscDoublePressWindow = TimeSpan.FromSeconds(1L);
|
||||||
|
|
||||||
|
private HashSet<string> _stopConditionsMetAtStart = new HashSet<string>();
|
||||||
|
|
||||||
private const char ClipboardSeparator = ';';
|
private const char ClipboardSeparator = ';';
|
||||||
|
|
||||||
public EAutomationType AutomationType
|
public EAutomationType AutomationType
|
||||||
|
|
@ -223,16 +241,18 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
|
|
||||||
public event AutomationTypeChangedEventHandler? AutomationTypeChanged;
|
public event AutomationTypeChangedEventHandler? AutomationTypeChanged;
|
||||||
|
|
||||||
public QuestController(IClientState clientState, GameFunctions gameFunctions, QuestFunctions questFunctions, MovementController movementController, CombatController combatController, GatheringController gatheringController, ILogger<QuestController> logger, QuestRegistry questRegistry, IKeyState keyState, IChatGui chatGui, ICondition condition, IToastGui toastGui, Configuration configuration, TaskCreator taskCreator, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent)
|
public QuestController(IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, QuestFunctions questFunctions, MovementController movementController, CombatController combatController, GatheringController gatheringController, ILogger<QuestController> logger, QuestRegistry questRegistry, JournalData journalData, IKeyState keyState, IChatGui chatGui, ICondition condition, IToastGui toastGui, Configuration configuration, TaskCreator taskCreator, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent, AutoDutyIpc autoDutyIpc)
|
||||||
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
|
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
|
||||||
{
|
{
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_objectTable = objectTable;
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
_questFunctions = questFunctions;
|
_questFunctions = questFunctions;
|
||||||
_movementController = movementController;
|
_movementController = movementController;
|
||||||
_combatController = combatController;
|
_combatController = combatController;
|
||||||
_gatheringController = gatheringController;
|
_gatheringController = gatheringController;
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
|
_journalData = journalData;
|
||||||
_keyState = keyState;
|
_keyState = keyState;
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
|
|
@ -240,11 +260,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_taskCreator = taskCreator;
|
_taskCreator = taskCreator;
|
||||||
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
|
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
|
||||||
|
_autoDutyIpc = autoDutyIpc;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reload()
|
public void Reload()
|
||||||
|
|
@ -300,40 +322,101 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
{
|
{
|
||||||
StopAllDueToConditionFailed("Logged out");
|
StopAllDueToConditionFailed("Logged out");
|
||||||
}
|
}
|
||||||
|
bool flag;
|
||||||
if (_condition[ConditionFlag.Unconscious])
|
if (_condition[ConditionFlag.Unconscious])
|
||||||
{
|
{
|
||||||
if ((!_condition[ConditionFlag.Unconscious] || !_condition[ConditionFlag.SufferingStatusAffliction63] || _clientState.TerritoryType != 1052) && !(_taskQueue.CurrentTaskExecutor is Duty.WaitAutoDutyExecutor) && !_taskQueue.AllTasksComplete)
|
if (!_condition[ConditionFlag.Unconscious] || !_condition[ConditionFlag.SufferingStatusAffliction63] || _clientState.TerritoryType != 1052)
|
||||||
|
{
|
||||||
|
ITaskExecutor currentTaskExecutor = _taskQueue.CurrentTaskExecutor;
|
||||||
|
flag = ((currentTaskExecutor is Duty.WaitAutoDutyExecutor || currentTaskExecutor is Duty.WaitLevelingModeExecutor) ? true : false);
|
||||||
|
if (!flag && !_taskQueue.AllTasksComplete)
|
||||||
{
|
{
|
||||||
StopAllDueToConditionFailed("HP = 0");
|
StopAllDueToConditionFailed("HP = 0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_configuration.General.UseEscToCancelQuesting && _keyState[VirtualKey.ESCAPE] && !_taskQueue.AllTasksComplete)
|
|
||||||
{
|
|
||||||
StopAllDueToConditionFailed("ESC pressed");
|
|
||||||
}
|
}
|
||||||
if (_configuration.Stop.Enabled && _configuration.Stop.LevelToStopAfter && _clientState.LocalPlayer != null)
|
else if (_configuration.General.UseEscToCancelQuesting)
|
||||||
{
|
{
|
||||||
int level = _clientState.LocalPlayer.Level;
|
if (_keyState[VirtualKey.ESCAPE] && !_lastEscDown)
|
||||||
if (level >= _configuration.Stop.TargetLevel && IsRunning)
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Reached level stop condition (level: {CurrentLevel}, target: {TargetLevel})", level, _configuration.Stop.TargetLevel);
|
DateTime now = DateTime.Now;
|
||||||
_chatGui.Print($"Reached or exceeded target level {_configuration.Stop.TargetLevel}.", "Questionable", 576);
|
if (now - _lastEscPressTime <= EscDoublePressWindow)
|
||||||
Stop($"Level stop condition reached [{level}]");
|
{
|
||||||
|
_escPressCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_escPressCount = 1;
|
||||||
|
}
|
||||||
|
_lastEscPressTime = now;
|
||||||
|
if (_escPressCount >= 2)
|
||||||
|
{
|
||||||
|
if (!_taskQueue.AllTasksComplete)
|
||||||
|
{
|
||||||
|
StopAllDueToConditionFailed("ESC pressed twice");
|
||||||
|
}
|
||||||
|
_escPressCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!_keyState[VirtualKey.ESCAPE] && DateTime.Now - _lastEscPressTime > EscDoublePressWindow)
|
||||||
|
{
|
||||||
|
_escPressCount = 0;
|
||||||
|
}
|
||||||
|
_lastEscDown = _keyState[VirtualKey.ESCAPE];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_lastEscDown = _keyState[VirtualKey.ESCAPE];
|
||||||
|
_escPressCount = 0;
|
||||||
|
_lastEscPressTime = DateTime.MinValue;
|
||||||
|
}
|
||||||
|
if (_configuration.Stop.Enabled && _startedQuest != null)
|
||||||
|
{
|
||||||
|
string text = _startedQuest.Quest.Id.ToString();
|
||||||
|
if (_configuration.Stop.LevelStopMode != Configuration.EStopConditionMode.Off && IsRunning && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel)
|
||||||
|
{
|
||||||
|
string item = $"level:{_configuration.Stop.TargetLevel}";
|
||||||
|
if (_configuration.Stop.LevelStopMode != Configuration.EStopConditionMode.Pause || !_stopConditionsMetAtStart.Contains(item))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Reached level stop condition (current: {CurrentLevel}, target: {TargetLevel}, mode: {Mode})", playerCharacter.Level, _configuration.Stop.TargetLevel, _configuration.Stop.LevelStopMode);
|
||||||
|
_chatGui.Print($"Character level {playerCharacter.Level} reached target level {_configuration.Stop.TargetLevel}.", "Questionable", 576);
|
||||||
|
Stop($"Level stop condition reached [{playerCharacter.Level}]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_configuration.Stop.Enabled && _configuration.Stop.SequenceToStopAfter && CurrentQuest != null)
|
if (_configuration.Stop.QuestSequences.TryGetValue(text, out var value) && value.HasValue)
|
||||||
{
|
{
|
||||||
int sequence = CurrentQuest.Sequence;
|
int sequence = _startedQuest.Sequence;
|
||||||
if (sequence >= _configuration.Stop.TargetSequence && IsRunning)
|
if (sequence >= value.Value && IsRunning)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Reached quest sequence stop condition (sequence: {CurrentSequence}, target: {TargetSequence})", sequence, _configuration.Stop.TargetSequence);
|
Configuration.EStopConditionMode valueOrDefault = _configuration.Stop.QuestStopModes.GetValueOrDefault(text, Configuration.EStopConditionMode.Pause);
|
||||||
_chatGui.Print($"Quest sequence {sequence} reached target sequence {_configuration.Stop.TargetSequence}.", "Questionable", 576);
|
string item2 = $"questseq:{text}:{sequence}";
|
||||||
Stop($"Sequence stop condition reached [{sequence}]");
|
if (valueOrDefault != Configuration.EStopConditionMode.Pause || !_stopConditionsMetAtStart.Contains(item2))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Reached quest-specific sequence stop condition (quest: {QuestId}, sequence: {CurrentSequence}, target: {TargetSequence})", _startedQuest.Quest.Id, sequence, value.Value);
|
||||||
|
_chatGui.Print($"Quest '{_startedQuest.Quest.Info.Name}' reached sequence {sequence}, configured stop sequence is {value.Value}.", "Questionable", 576);
|
||||||
|
Stop($"Quest-specific sequence stop condition reached [{text}@{sequence}]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool flag = AutomationType == EAutomationType.Automatic && (_taskQueue.AllTasksComplete || _taskQueue.CurrentTaskExecutor?.CurrentTask is WaitAtEnd.WaitQuestAccepted);
|
}
|
||||||
|
else if (_configuration.Stop.SequenceStopMode != Configuration.EStopConditionMode.Off && CurrentQuest != null)
|
||||||
|
{
|
||||||
|
int sequence2 = CurrentQuest.Sequence;
|
||||||
|
if (sequence2 >= _configuration.Stop.TargetSequence && IsRunning)
|
||||||
|
{
|
||||||
|
string item3 = $"sequence:{text}:{sequence2}";
|
||||||
|
if (_configuration.Stop.SequenceStopMode != Configuration.EStopConditionMode.Pause || !_stopConditionsMetAtStart.Contains(item3))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Reached global quest sequence stop condition (sequence: {CurrentSequence}, target: {TargetSequence}, mode: {Mode})", sequence2, _configuration.Stop.TargetSequence, _configuration.Stop.SequenceStopMode);
|
||||||
|
_chatGui.Print($"Quest sequence {sequence2} reached target sequence {_configuration.Stop.TargetSequence}.", "Questionable", 576);
|
||||||
|
Stop($"Sequence stop condition reached [{sequence2}]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flag = AutomationType == EAutomationType.Automatic && (_taskQueue.AllTasksComplete || _taskQueue.CurrentTaskExecutor?.CurrentTask is WaitAtEnd.WaitQuestAccepted);
|
||||||
bool flag2;
|
bool flag2;
|
||||||
if (flag)
|
if (flag)
|
||||||
{
|
{
|
||||||
|
|
@ -344,14 +427,14 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
if (step == 0 || step == 255)
|
if (step == 0 || step == 255)
|
||||||
{
|
{
|
||||||
flag2 = true;
|
flag2 = true;
|
||||||
goto IL_0422;
|
goto IL_0800;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flag2 = false;
|
flag2 = false;
|
||||||
goto IL_0422;
|
goto IL_0800;
|
||||||
}
|
}
|
||||||
goto IL_0426;
|
goto IL_0804;
|
||||||
IL_0426:
|
IL_0804:
|
||||||
if (flag && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15.0))
|
if (flag && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15.0))
|
||||||
{
|
{
|
||||||
lock (_progressLock)
|
lock (_progressLock)
|
||||||
|
|
@ -367,9 +450,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
UpdateCurrentTask();
|
UpdateCurrentTask();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
IL_0422:
|
IL_0800:
|
||||||
flag = flag2;
|
flag = flag2;
|
||||||
goto IL_0426;
|
goto IL_0804;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckAutoRefreshCondition()
|
private void CheckAutoRefreshCondition()
|
||||||
|
|
@ -383,12 +466,12 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
_lastProgressUpdate = DateTime.Now;
|
_lastProgressUpdate = DateTime.Now;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
|
IGameObject gameObject = _objectTable[0];
|
||||||
if (localPlayer == null)
|
if (gameObject == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Vector3 position = localPlayer.Position;
|
Vector3 position = gameObject.Position;
|
||||||
if (CurrentQuest == null)
|
if (CurrentQuest == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -421,7 +504,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
{
|
{
|
||||||
if (_configuration.General.AutoStepRefreshEnabled && AutomationType == EAutomationType.Automatic && IsRunning && CurrentQuest != null && _clientState.IsLoggedIn)
|
if (_configuration.General.AutoStepRefreshEnabled && AutomationType == EAutomationType.Automatic && IsRunning && CurrentQuest != null && _clientState.IsLoggedIn)
|
||||||
{
|
{
|
||||||
return _clientState.LocalPlayer != null;
|
return _objectTable[0] != null;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -450,7 +533,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
private bool HasWaitingTasks()
|
private bool HasWaitingTasks()
|
||||||
{
|
{
|
||||||
ITask task = _taskQueue.CurrentTaskExecutor?.CurrentTask;
|
ITask task = _taskQueue.CurrentTaskExecutor?.CurrentTask;
|
||||||
if (task is WaitAtEnd.WaitObjectAtPosition || task is WaitAtEnd.WaitForCompletionFlags)
|
if (task is WaitAtEnd.WaitObjectAtPosition || task is WaitAtEnd.WaitForCompletionFlags || task is Duty.StartLevelingModeTask || task is Duty.WaitLevelingModeTask)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -474,7 +557,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
|
|
||||||
private bool HasSystemConditionsPreventingRefresh()
|
private bool HasSystemConditionsPreventingRefresh()
|
||||||
{
|
{
|
||||||
if (_movementController.IsNavmeshReady && !_condition[ConditionFlag.InCombat] && !_condition[ConditionFlag.Unconscious] && !_condition[ConditionFlag.BoundByDuty] && !_condition[ConditionFlag.InDeepDungeon] && !_condition[ConditionFlag.WatchingCutscene] && !_condition[ConditionFlag.WatchingCutscene78] && !_condition[ConditionFlag.BetweenAreas] && !_condition[ConditionFlag.BetweenAreas51] && !_gameFunctions.IsOccupied() && !_movementController.IsPathfinding && !_movementController.IsPathRunning)
|
if (_movementController.IsNavmeshReady && !_condition[ConditionFlag.InCombat] && !_condition[ConditionFlag.Unconscious] && !_condition[ConditionFlag.BoundByDuty] && !_condition[ConditionFlag.InDutyQueue] && !_condition[ConditionFlag.InDeepDungeon] && !_condition[ConditionFlag.WatchingCutscene] && !_condition[ConditionFlag.WatchingCutscene78] && !_condition[ConditionFlag.BetweenAreas] && !_condition[ConditionFlag.BetweenAreas51] && !_gameFunctions.IsOccupied() && !_movementController.IsPathfinding && !_movementController.IsPathRunning)
|
||||||
{
|
{
|
||||||
return DateTime.Now < _safeAnimationEnd;
|
return DateTime.Now < _safeAnimationEnd;
|
||||||
}
|
}
|
||||||
|
|
@ -506,7 +589,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
int num = ManualPriorityQuests.RemoveAll((Quest q) => _questFunctions.IsQuestComplete(q.Id));
|
int num = ManualPriorityQuests.RemoveAll((Quest q) => _questFunctions.IsQuestComplete(q.Id));
|
||||||
if (num > 0)
|
if (num > 0)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Removed {Count} completed priority quest(s)", num);
|
_logger.LogInformation("Removed {Count} completed priority {QuestWord}", num, (num == 1) ? "quest" : "quests");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_pendingQuest != null)
|
if (_pendingQuest != null)
|
||||||
|
|
@ -575,6 +658,26 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
}
|
}
|
||||||
if (elementId == null || elementId.Value == 0)
|
if (elementId == null || elementId.Value == 0)
|
||||||
{
|
{
|
||||||
|
ITask task = _taskQueue.CurrentTaskExecutor?.CurrentTask;
|
||||||
|
bool flag = ((task is Duty.StartLevelingModeTask || task is Duty.WaitLevelingModeTask) ? true : false);
|
||||||
|
if (flag || _taskQueue.RemainingTasks.Any((ITask t) => (t is Duty.StartLevelingModeTask || t is Duty.WaitLevelingModeTask) ? true : false))
|
||||||
|
{
|
||||||
|
DebugState = "Leveling mode active";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(bool isLevelLocked, int levelsNeeded, int requiredLevel, string? questName) msqLevelLockInfo = _questFunctions.GetMsqLevelLockInfo();
|
||||||
|
bool item = msqLevelLockInfo.isLevelLocked;
|
||||||
|
int item2 = msqLevelLockInfo.levelsNeeded;
|
||||||
|
int item3 = msqLevelLockInfo.requiredLevel;
|
||||||
|
string item4 = msqLevelLockInfo.questName;
|
||||||
|
int currentPlayerLevel = (_objectTable[0] as IPlayerCharacter)?.Level ?? 0;
|
||||||
|
if (item && _autoDutyIpc.IsConfiguredToRunLevelingMode(currentPlayerLevel) && AutomationType == EAutomationType.Automatic && !_condition[ConditionFlag.BoundByDuty] && !_condition[ConditionFlag.InDutyQueue] && _taskQueue.AllTasksComplete)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("MSQ '{QuestName}' requires level {RequiredLevel}, current level is {CurrentLevel} ({LevelsNeeded} levels needed). Starting AutoDuty Leveling mode.", item4, item3, item3 - item2, item2);
|
||||||
|
_taskQueue.Enqueue(new Duty.StartLevelingModeTask(item3, item4));
|
||||||
|
_taskQueue.Enqueue(new Duty.WaitLevelingModeTask(item3));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_startedQuest != null)
|
if (_startedQuest != null)
|
||||||
{
|
{
|
||||||
switch (mainScenarioQuestState)
|
switch (mainScenarioQuestState)
|
||||||
|
|
@ -592,11 +695,16 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
}
|
}
|
||||||
questProgress = null;
|
questProgress = null;
|
||||||
}
|
}
|
||||||
|
else if (_startedQuest == null || _startedQuest.Quest.Id != elementId)
|
||||||
|
{
|
||||||
|
if (_startedQuest != null && !_taskQueue.AllTasksComplete)
|
||||||
|
{
|
||||||
|
_logger.LogTrace("Not switching from quest {CurrentQuestId} to {NewQuestId} because tasks are still running", _startedQuest.Quest.Id, elementId);
|
||||||
|
questProgress = _startedQuest;
|
||||||
|
b = _startedQuest.Sequence;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_startedQuest == null || _startedQuest.Quest.Id != elementId)
|
|
||||||
{
|
|
||||||
Quest quest;
|
|
||||||
if (_configuration.Stop.Enabled && _startedQuest != null && _configuration.Stop.QuestsToStopAfter.Contains(_startedQuest.Quest.Id) && _questFunctions.IsQuestComplete(_startedQuest.Quest.Id))
|
if (_configuration.Stop.Enabled && _startedQuest != null && _configuration.Stop.QuestsToStopAfter.Contains(_startedQuest.Quest.Id) && _questFunctions.IsQuestComplete(_startedQuest.Quest.Id))
|
||||||
{
|
{
|
||||||
ElementId id = _startedQuest.Quest.Id;
|
ElementId id = _startedQuest.Quest.Id;
|
||||||
|
|
@ -604,17 +712,29 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
_chatGui.Print("Completed quest '" + _startedQuest.Quest.Info.Name + "', which is configured as a stopping point.", "Questionable", 576);
|
_chatGui.Print("Completed quest '" + _startedQuest.Quest.Info.Name + "', which is configured as a stopping point.", "Questionable", 576);
|
||||||
_startedQuest = null;
|
_startedQuest = null;
|
||||||
Stop($"Stopping point [{id}] reached");
|
Stop($"Stopping point [{id}] reached");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (_questRegistry.TryGetQuest(elementId, out quest))
|
if (_questRegistry.TryGetQuest(elementId, out Quest quest))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("New quest: {QuestName}", quest.Info.Name);
|
_logger.LogInformation("New quest: {QuestName}", quest.Info.Name);
|
||||||
_startedQuest = new QuestProgress(quest, b);
|
_startedQuest = new QuestProgress(quest, b);
|
||||||
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.Level < quest.Info.Level)
|
if (_objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level < quest.Info.Level)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Stopping automation, player level ({PlayerLevel}) < quest level ({QuestLevel}", _clientState.LocalPlayer.Level, quest.Info.Level);
|
if (_autoDutyIpc.IsConfiguredToRunLevelingMode(playerCharacter.Level) && AutomationType == EAutomationType.Automatic && !_condition[ConditionFlag.BoundByDuty])
|
||||||
Stop("Quest level too high");
|
{
|
||||||
return;
|
_logger.LogInformation("Player level ({PlayerLevel}) < quest level ({QuestLevel}), starting AutoDuty Leveling mode", playerCharacter.Level, quest.Info.Level);
|
||||||
|
ClearTasksInternal();
|
||||||
|
_taskQueue.Enqueue(new Duty.StartLevelingModeTask(quest.Info.Level, quest.Info.Name));
|
||||||
|
_taskQueue.Enqueue(new Duty.WaitLevelingModeTask(quest.Info.Level));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Stopping automation, player level ({PlayerLevel}) < quest level ({QuestLevel})", playerCharacter.Level, quest.Info.Level);
|
||||||
|
Stop("Quest level too high");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (AutomationType == EAutomationType.SingleQuestB)
|
if (AutomationType == EAutomationType.SingleQuestB)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Single quest is finished");
|
_logger.LogInformation("Single quest is finished");
|
||||||
|
|
@ -622,63 +742,91 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
}
|
}
|
||||||
CheckNextTasks("Different Quest");
|
CheckNextTasks("Different Quest");
|
||||||
}
|
}
|
||||||
else if (_startedQuest != null)
|
return;
|
||||||
|
}
|
||||||
|
if (_startedQuest != null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("No active quest anymore? Not sure what happened...");
|
_logger.LogInformation("No active quest anymore? Not sure what happened...");
|
||||||
_startedQuest = null;
|
_startedQuest = null;
|
||||||
Stop("No active Quest");
|
Stop("No active Quest");
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
questProgress = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
questProgress = _startedQuest;
|
questProgress = _startedQuest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (questProgress == null)
|
if (questProgress == null)
|
||||||
{
|
{
|
||||||
|
ITask task = _taskQueue.CurrentTaskExecutor?.CurrentTask;
|
||||||
|
bool flag = ((task is Duty.StartLevelingModeTask || task is Duty.WaitLevelingModeTask) ? true : false);
|
||||||
|
if (flag || _taskQueue.RemainingTasks.Any((ITask t) => (t is Duty.StartLevelingModeTask || t is Duty.WaitLevelingModeTask) ? true : false))
|
||||||
|
{
|
||||||
|
DebugState = "Leveling mode active";
|
||||||
|
return;
|
||||||
|
}
|
||||||
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");
|
||||||
|
|
@ -689,6 +837,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public (QuestSequence? Sequence, QuestStep? Step, bool createTasks) GetNextStep()
|
public (QuestSequence? Sequence, QuestStep? Step, bool createTasks) GetNextStep()
|
||||||
{
|
{
|
||||||
|
|
@ -732,15 +881,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"))
|
||||||
{
|
{
|
||||||
|
|
@ -774,6 +929,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
_nextQuest = null;
|
_nextQuest = null;
|
||||||
_gatheringQuest = null;
|
_gatheringQuest = null;
|
||||||
_lastTaskUpdate = DateTime.Now;
|
_lastTaskUpdate = DateTime.Now;
|
||||||
|
_stopConditionsMetAtStart.Clear();
|
||||||
ResetAutoRefreshState();
|
ResetAutoRefreshState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -886,28 +1042,40 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
{
|
{
|
||||||
using (_logger.BeginScope("Q/" + label))
|
using (_logger.BeginScope("Q/" + label))
|
||||||
{
|
{
|
||||||
|
if (!CheckAndBlockForStopConditions())
|
||||||
|
{
|
||||||
|
RecordStopConditionsMetAtStart();
|
||||||
AutomationType = EAutomationType.Automatic;
|
AutomationType = EAutomationType.Automatic;
|
||||||
ExecuteNextStep();
|
ExecuteNextStep();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void StartGatheringQuest(string label)
|
public void StartGatheringQuest(string label)
|
||||||
{
|
{
|
||||||
using (_logger.BeginScope("GQ/" + label))
|
using (_logger.BeginScope("GQ/" + label))
|
||||||
{
|
{
|
||||||
|
if (!CheckAndBlockForStopConditions())
|
||||||
|
{
|
||||||
|
RecordStopConditionsMetAtStart();
|
||||||
AutomationType = EAutomationType.GatheringOnly;
|
AutomationType = EAutomationType.GatheringOnly;
|
||||||
ExecuteNextStep();
|
ExecuteNextStep();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void StartSingleQuest(string label)
|
public void StartSingleQuest(string label)
|
||||||
{
|
{
|
||||||
using (_logger.BeginScope("SQ/" + label))
|
using (_logger.BeginScope("SQ/" + label))
|
||||||
{
|
{
|
||||||
|
if (!CheckAndBlockForStopConditions())
|
||||||
|
{
|
||||||
|
RecordStopConditionsMetAtStart();
|
||||||
AutomationType = EAutomationType.SingleQuestA;
|
AutomationType = EAutomationType.SingleQuestA;
|
||||||
ExecuteNextStep();
|
ExecuteNextStep();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void StartSingleStep(string label)
|
public void StartSingleStep(string label)
|
||||||
{
|
{
|
||||||
|
|
@ -918,6 +1086,109 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RecordStopConditionsMetAtStart()
|
||||||
|
{
|
||||||
|
_stopConditionsMetAtStart.Clear();
|
||||||
|
if (!_configuration.Stop.Enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_configuration.Stop.LevelStopMode == Configuration.EStopConditionMode.Pause && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel)
|
||||||
|
{
|
||||||
|
_stopConditionsMetAtStart.Add($"level:{_configuration.Stop.TargetLevel}");
|
||||||
|
_logger.LogDebug("Recording level stop condition as already met at start: {Level}", _configuration.Stop.TargetLevel);
|
||||||
|
}
|
||||||
|
if (_configuration.Stop.SequenceStopMode == Configuration.EStopConditionMode.Pause && _startedQuest != null)
|
||||||
|
{
|
||||||
|
string text = _startedQuest.Quest.Id.ToString();
|
||||||
|
if (!_configuration.Stop.QuestSequences.ContainsKey(text))
|
||||||
|
{
|
||||||
|
int sequence = _startedQuest.Sequence;
|
||||||
|
if (sequence >= _configuration.Stop.TargetSequence)
|
||||||
|
{
|
||||||
|
_stopConditionsMetAtStart.Add($"sequence:{text}:{sequence}");
|
||||||
|
_logger.LogDebug("Recording global sequence stop condition as already met at start: quest {QuestId}, sequence {Sequence}", text, sequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (ElementId item in _configuration.Stop.QuestsToStopAfter)
|
||||||
|
{
|
||||||
|
string text2 = item.ToString();
|
||||||
|
if (_configuration.Stop.QuestStopModes.GetValueOrDefault(text2, Configuration.EStopConditionMode.Pause) == Configuration.EStopConditionMode.Pause && _configuration.Stop.QuestSequences.TryGetValue(text2, out var value) && value.HasValue && _questFunctions.IsQuestAccepted(item))
|
||||||
|
{
|
||||||
|
QuestProgressInfo questProgressInfo = _questFunctions.GetQuestProgressInfo(item);
|
||||||
|
if (questProgressInfo != null && questProgressInfo.Sequence >= value.Value)
|
||||||
|
{
|
||||||
|
_stopConditionsMetAtStart.Add($"questseq:{text2}:{questProgressInfo.Sequence}");
|
||||||
|
_logger.LogDebug("Recording quest sequence stop condition as already met at start: quest {QuestId}, sequence {Sequence}", text2, questProgressInfo.Sequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckAndBlockForStopConditions()
|
||||||
|
{
|
||||||
|
if (!_configuration.Stop.Enabled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_configuration.Stop.LevelStopMode == Configuration.EStopConditionMode.Stop && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Blocking start: Level stop condition already met (current: {CurrentLevel}, target: {TargetLevel})", playerCharacter.Level, _configuration.Stop.TargetLevel);
|
||||||
|
_chatGui.Print($"Cannot start: Character level {playerCharacter.Level} has reached target level {_configuration.Stop.TargetLevel}.", "Questionable", 576);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
foreach (ElementId item in _configuration.Stop.QuestsToStopAfter)
|
||||||
|
{
|
||||||
|
string key = item.ToString();
|
||||||
|
if (_configuration.Stop.QuestStopModes.GetValueOrDefault(key, Configuration.EStopConditionMode.Pause) != Configuration.EStopConditionMode.Stop)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (_configuration.Stop.QuestSequences.TryGetValue(key, out var value) && value.HasValue)
|
||||||
|
{
|
||||||
|
if (!_questFunctions.IsQuestAccepted(item))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QuestProgressInfo questProgressInfo = _questFunctions.GetQuestProgressInfo(item);
|
||||||
|
if (questProgressInfo != null && questProgressInfo.Sequence >= value.Value)
|
||||||
|
{
|
||||||
|
if (_questRegistry.TryGetQuest(item, out Quest quest))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Blocking start: Quest '{QuestName}' is at sequence {CurrentSequence}, stop sequence is {StopSequence}", quest.Info.Name, questProgressInfo.Sequence, value.Value);
|
||||||
|
_chatGui.Print($"Cannot start: Quest '{quest.Info.Name}' is at sequence {questProgressInfo.Sequence}, configured stop sequence is {value.Value}.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_questFunctions.IsQuestComplete(item))
|
||||||
|
{
|
||||||
|
if (_questRegistry.TryGetQuest(item, out Quest quest2))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Blocking start: Quest '{QuestName}' is already complete and configured as a stop condition", quest2.Info.Name);
|
||||||
|
_chatGui.Print("Cannot start: Quest '" + quest2.Info.Name + "' is complete and configured as a stopping point.", "Questionable", 576);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_configuration.Stop.SequenceStopMode == Configuration.EStopConditionMode.Stop && _startedQuest != null)
|
||||||
|
{
|
||||||
|
string key2 = _startedQuest.Quest.Id.ToString();
|
||||||
|
if (!_configuration.Stop.QuestSequences.ContainsKey(key2))
|
||||||
|
{
|
||||||
|
int sequence = _startedQuest.Sequence;
|
||||||
|
if (sequence >= _configuration.Stop.TargetSequence)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Blocking start: Global sequence stop condition already met (current: {CurrentSequence}, target: {TargetSequence})", sequence, _configuration.Stop.TargetSequence);
|
||||||
|
_chatGui.Print($"Cannot start: Quest sequence {sequence} has reached target sequence {_configuration.Stop.TargetSequence}.", "Questionable", 576);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void ExecuteNextStep()
|
private void ExecuteNextStep()
|
||||||
{
|
{
|
||||||
ClearTasksInternal();
|
ClearTasksInternal();
|
||||||
|
|
@ -928,6 +1199,23 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
var (questSequence, step, flag) = GetNextStep();
|
var (questSequence, step, flag) = GetNextStep();
|
||||||
if (CurrentQuest == null || questSequence == null)
|
if (CurrentQuest == null || questSequence == null)
|
||||||
{
|
{
|
||||||
|
_logger.LogDebug("ExecuteNextStep: No current quest or sequence. Checking leveling mode conditions.");
|
||||||
|
if (AutomationType == EAutomationType.Automatic && !_condition[ConditionFlag.BoundByDuty])
|
||||||
|
{
|
||||||
|
(bool isLevelLocked, int levelsNeeded, int requiredLevel, string? questName) msqLevelLockInfo = _questFunctions.GetMsqLevelLockInfo();
|
||||||
|
bool item = msqLevelLockInfo.isLevelLocked;
|
||||||
|
int item2 = msqLevelLockInfo.levelsNeeded;
|
||||||
|
int item3 = msqLevelLockInfo.requiredLevel;
|
||||||
|
string item4 = msqLevelLockInfo.questName;
|
||||||
|
int currentPlayerLevel = (_objectTable[0] as IPlayerCharacter)?.Level ?? 0;
|
||||||
|
if (item && _autoDutyIpc.IsConfiguredToRunLevelingMode(currentPlayerLevel))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("MSQ '{QuestName}' requires level {RequiredLevel}, current level is {CurrentLevel} ({LevelsNeeded} levels needed). Starting AutoDuty Leveling mode.", item4, item3, item3 - item2, item2);
|
||||||
|
_taskQueue.Enqueue(new Duty.StartLevelingModeTask(item3, item4));
|
||||||
|
_taskQueue.Enqueue(new Duty.WaitLevelingModeTask(item3));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (CurrentQuestDetails?.Progress.Quest.Id is SatisfactionSupplyNpcId && CurrentQuestDetails?.Progress.Sequence == 1)
|
if (CurrentQuestDetails?.Progress.Quest.Id is SatisfactionSupplyNpcId && CurrentQuestDetails?.Progress.Sequence == 1)
|
||||||
{
|
{
|
||||||
(QuestProgress, ECurrentQuestType)? currentQuestDetails = CurrentQuestDetails;
|
(QuestProgress, ECurrentQuestType)? currentQuestDetails = CurrentQuestDetails;
|
||||||
|
|
@ -939,23 +1227,23 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
_logger.LogInformation("Completed delivery quest");
|
_logger.LogInformation("Completed delivery quest");
|
||||||
SetGatheringQuest(null);
|
SetGatheringQuest(null);
|
||||||
Stop("Gathering quest complete");
|
Stop("Gathering quest complete");
|
||||||
goto IL_01dc;
|
goto IL_02d0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_logger.LogWarning("Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]", CurrentQuest?.Quest.Id, CurrentQuest?.Sequence, CurrentQuest?.Step);
|
_logger.LogWarning("Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]", CurrentQuest?.Quest.Id, CurrentQuest?.Sequence, CurrentQuest?.Step);
|
||||||
goto IL_01dc;
|
goto IL_02d0;
|
||||||
}
|
}
|
||||||
goto IL_01e8;
|
goto IL_02dc;
|
||||||
IL_01e8:
|
IL_02dc:
|
||||||
_movementController.Stop();
|
_movementController.Stop();
|
||||||
_combatController.Stop("Execute next step");
|
_combatController.Stop("Execute next step");
|
||||||
_gatheringController.Stop("Execute next step");
|
_gatheringController.Stop("Execute next step");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (ITask item in _taskCreator.CreateTasks(CurrentQuest.Quest, CurrentQuest.Sequence, questSequence, step))
|
foreach (ITask item5 in _taskCreator.CreateTasks(CurrentQuest.Quest, CurrentQuest.Sequence, questSequence, step))
|
||||||
{
|
{
|
||||||
_taskQueue.Enqueue(item);
|
_taskQueue.Enqueue(item5);
|
||||||
}
|
}
|
||||||
ResetAutoRefreshState();
|
ResetAutoRefreshState();
|
||||||
return;
|
return;
|
||||||
|
|
@ -967,12 +1255,12 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
Stop("Tasks failed to create");
|
Stop("Tasks failed to create");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IL_01dc:
|
IL_02d0:
|
||||||
if (CurrentQuest == null || !flag)
|
if (CurrentQuest == null || !flag)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
goto IL_01e8;
|
goto IL_02dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ToStatString()
|
public string ToStatString()
|
||||||
|
|
@ -1162,7 +1450,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||||
_logger.LogDebug("Excluding allied society quest {QuestId} from bulk add", quest.Id);
|
_logger.LogDebug("Excluding allied society quest {QuestId} from bulk add", quest.Id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (quest.Info is QuestInfo { MoogleDeliveryLevel: >0 })
|
if (quest.Info is QuestInfo questInfo && _journalData.MoogleDeliveryGenreId.HasValue && questInfo.JournalGenre == _journalData.MoogleDeliveryGenreId.Value)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Excluding moogle delivery quest {QuestId} from bulk add", quest.Id);
|
_logger.LogDebug("Excluding moogle delivery quest {QuestId} from bulk add", quest.Id);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1245,12 +1533,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ internal sealed class QuestRegistry
|
||||||
}
|
}
|
||||||
if (_questData.TryGetQuestInfo(elementId2, out IQuestInfo questInfo))
|
if (_questData.TryGetQuestInfo(elementId2, out IQuestInfo questInfo))
|
||||||
{
|
{
|
||||||
goto IL_01c8;
|
goto IL_01ae;
|
||||||
}
|
}
|
||||||
if (elementId2 is UnlockLinkId unlockLinkId)
|
if (elementId2 is UnlockLinkId unlockLinkId)
|
||||||
{
|
{
|
||||||
|
|
@ -147,18 +147,16 @@ internal sealed class QuestRegistry
|
||||||
text = $"Unlock Link {unlockLinkId.Value}";
|
text = $"Unlock Link {unlockLinkId.Value}";
|
||||||
}
|
}
|
||||||
questInfo = new UnlockLinkQuestInfo(unlockLinkId, text, 0u, dateTime);
|
questInfo = new UnlockLinkQuestInfo(unlockLinkId, text, 0u, dateTime);
|
||||||
_logger.LogDebug("Created UnlockLinkQuestInfo for {QuestId} from assembly", elementId2);
|
|
||||||
_questData.AddOrReplaceQuestInfo(questInfo);
|
_questData.AddOrReplaceQuestInfo(questInfo);
|
||||||
goto IL_01c8;
|
goto IL_01ae;
|
||||||
}
|
}
|
||||||
_logger.LogWarning("Not loading unknown quest {QuestId} from assembly: Quest not found in quest data", elementId2);
|
_logger.LogWarning("Not loading unknown quest {QuestId} from assembly: Quest not found in quest data", elementId2);
|
||||||
goto end_IL_003d;
|
goto end_IL_003d;
|
||||||
IL_01c8:
|
IL_01ae:
|
||||||
if (flag2 || flag3)
|
if (flag2 || flag3)
|
||||||
{
|
{
|
||||||
bool flag4 = flag ?? questInfo.IsSeasonalQuest;
|
bool isSeasonal = flag ?? questInfo.IsSeasonalQuest;
|
||||||
_questData.ApplySeasonalOverride(elementId2, flag4, dateTime);
|
_questData.ApplySeasonalOverride(elementId2, isSeasonal, dateTime);
|
||||||
_logger.LogDebug("Applied seasonal override for quest {QuestId} from assembly: IsSeasonal={IsSeasonal}, Expiry={Expiry}", elementId2, flag4, dateTime?.ToString("o") ?? "(null)");
|
|
||||||
}
|
}
|
||||||
IQuestInfo questInfo2 = _questData.GetQuestInfo(elementId2);
|
IQuestInfo questInfo2 = _questData.GetQuestInfo(elementId2);
|
||||||
Quest quest = new Quest
|
Quest quest = new Quest
|
||||||
|
|
|
||||||
1150
Questionable/Questionable.Data/ChangelogData.cs
Normal file
1150
Questionable/Questionable.Data/ChangelogData.cs
Normal file
File diff suppressed because it is too large
Load diff
68
Questionable/Questionable.Data/HuntMobData.cs
Normal file
68
Questionable/Questionable.Data/HuntMobData.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
|
namespace Questionable.Data;
|
||||||
|
|
||||||
|
internal sealed class HuntMobData
|
||||||
|
{
|
||||||
|
private readonly HashSet<uint> _aRankDataIds = new HashSet<uint>();
|
||||||
|
|
||||||
|
private readonly HashSet<uint> _sRankDataIds = new HashSet<uint>();
|
||||||
|
|
||||||
|
private readonly HashSet<uint> _allHuntMobDataIds = new HashSet<uint>();
|
||||||
|
|
||||||
|
public const float SafeDistance = 15f;
|
||||||
|
|
||||||
|
public IReadOnlySet<uint> ARankDataIds => _aRankDataIds;
|
||||||
|
|
||||||
|
public IReadOnlySet<uint> SRankDataIds => _sRankDataIds;
|
||||||
|
|
||||||
|
public IReadOnlySet<uint> AllHuntMobDataIds => _allHuntMobDataIds;
|
||||||
|
|
||||||
|
public HuntMobData(IDataManager dataManager)
|
||||||
|
{
|
||||||
|
ExcelSheet<NotoriousMonster> excelSheet = dataManager.GetExcelSheet<NotoriousMonster>();
|
||||||
|
if (excelSheet == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (NotoriousMonster item in excelSheet)
|
||||||
|
{
|
||||||
|
byte rank = item.Rank;
|
||||||
|
if ((uint)(rank - 2) > 1u)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
uint rowId = item.BNpcBase.RowId;
|
||||||
|
if (rowId != 0)
|
||||||
|
{
|
||||||
|
_allHuntMobDataIds.Add(rowId);
|
||||||
|
if (item.Rank == 2)
|
||||||
|
{
|
||||||
|
_aRankDataIds.Add(rowId);
|
||||||
|
}
|
||||||
|
else if (item.Rank == 3)
|
||||||
|
{
|
||||||
|
_sRankDataIds.Add(rowId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHuntMob(uint dataId)
|
||||||
|
{
|
||||||
|
return _allHuntMobDataIds.Contains(dataId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsARank(uint dataId)
|
||||||
|
{
|
||||||
|
return _aRankDataIds.Contains(dataId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSRank(uint dataId)
|
||||||
|
{
|
||||||
|
return _sRankDataIds.Contains(dataId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
|
|
@ -85,6 +86,12 @@ internal sealed class JournalData
|
||||||
|
|
||||||
private readonly ILogger<JournalData> _logger;
|
private readonly ILogger<JournalData> _logger;
|
||||||
|
|
||||||
|
public uint? SeasonalEventsCategoryId { get; }
|
||||||
|
|
||||||
|
public IReadOnlySet<uint> SeasonalEventGenreIds { get; }
|
||||||
|
|
||||||
|
public uint? MoogleDeliveryGenreId { get; }
|
||||||
|
|
||||||
public List<Genre> Genres { get; }
|
public List<Genre> Genres { get; }
|
||||||
|
|
||||||
public List<Category> Categories { get; }
|
public List<Category> Categories { get; }
|
||||||
|
|
@ -97,7 +104,38 @@ internal sealed class JournalData
|
||||||
{
|
{
|
||||||
JournalData journalData = this;
|
JournalData journalData = this;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
List<Genre> list = (from x in dataManager.GetExcelSheet<JournalGenre>()
|
ExcelSheet<JournalGenre> excelSheet = dataManager.GetExcelSheet<JournalGenre>();
|
||||||
|
JournalCategory journalCategory = dataManager.GetExcelSheet<JournalCategory>().FirstOrDefault((JournalCategory x) => x.Name.ToString().Equals("Seasonal Events", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (journalCategory.RowId != 0)
|
||||||
|
{
|
||||||
|
SeasonalEventsCategoryId = journalCategory.RowId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SeasonalEventsCategoryId = null;
|
||||||
|
_logger.LogWarning("Could not find 'Seasonal Events' JournalCategory - seasonal event detection may not work correctly");
|
||||||
|
}
|
||||||
|
if (SeasonalEventsCategoryId.HasValue)
|
||||||
|
{
|
||||||
|
SeasonalEventGenreIds = (from x in excelSheet
|
||||||
|
where x.RowId != 0 && x.JournalCategory.RowId == journalData.SeasonalEventsCategoryId.Value
|
||||||
|
select x.RowId).ToHashSet();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SeasonalEventGenreIds = new HashSet<uint>();
|
||||||
|
}
|
||||||
|
JournalGenre journalGenre = excelSheet.FirstOrDefault((JournalGenre x) => x.Name.ToString().Equals("Delivery Moogle Quests", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (journalGenre.RowId != 0)
|
||||||
|
{
|
||||||
|
MoogleDeliveryGenreId = journalGenre.RowId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MoogleDeliveryGenreId = null;
|
||||||
|
_logger.LogWarning("Could not find 'Delivery Moogle Quests' JournalGenre - moogle delivery quest detection may not work correctly");
|
||||||
|
}
|
||||||
|
List<Genre> list = (from x in excelSheet
|
||||||
where x.RowId != 0 && x.Icon > 0
|
where x.RowId != 0 && x.Icon > 0
|
||||||
select new Genre(x, questData.GetAllByJournalGenre(x.RowId))).ToList();
|
select new Genre(x, questData.GetAllByJournalGenre(x.RowId))).ToList();
|
||||||
QuestRedo row = dataManager.GetExcelSheet<QuestRedo>().GetRow(1u);
|
QuestRedo row = dataManager.GetExcelSheet<QuestRedo>().GetRow(1u);
|
||||||
|
|
@ -120,9 +158,7 @@ internal sealed class JournalData
|
||||||
select new Category(x, journalData.Genres.Where((Genre y) => y.CategoryId == x.RowId).ToList())).ToList();
|
select new Category(x, journalData.Genres.Where((Genre y) => y.CategoryId == x.RowId).ToList())).ToList();
|
||||||
Sections = (from x in dataManager.GetExcelSheet<JournalSection>()
|
Sections = (from x in dataManager.GetExcelSheet<JournalSection>()
|
||||||
select new Section(x, journalData.Categories.Where((Category y) => y.SectionId == x.RowId).ToList())).ToList();
|
select new Section(x, journalData.Categories.Where((Category y) => y.SectionId == x.RowId).ToList())).ToList();
|
||||||
_logger.LogDebug("Resolving OtherQuests section id...");
|
|
||||||
OtherQuestsSectionRowId = GetOtherQuestsSectionRowId(dataManager);
|
OtherQuestsSectionRowId = GetOtherQuestsSectionRowId(dataManager);
|
||||||
_logger.LogDebug("Resolved OtherQuestsSectionRowId = {Id}", OtherQuestsSectionRowId);
|
|
||||||
int? otherQuestsSectionRowId = OtherQuestsSectionRowId;
|
int? otherQuestsSectionRowId = OtherQuestsSectionRowId;
|
||||||
if (otherQuestsSectionRowId.HasValue)
|
if (otherQuestsSectionRowId.HasValue)
|
||||||
{
|
{
|
||||||
|
|
@ -132,6 +168,7 @@ internal sealed class JournalData
|
||||||
if (section != null)
|
if (section != null)
|
||||||
{
|
{
|
||||||
int num = 0;
|
int num = 0;
|
||||||
|
{
|
||||||
foreach (Category category in section.Categories)
|
foreach (Category category in section.Categories)
|
||||||
{
|
{
|
||||||
foreach (Genre genre in category.Genres)
|
foreach (Genre genre in category.Genres)
|
||||||
|
|
@ -140,12 +177,10 @@ internal sealed class JournalData
|
||||||
num++;
|
num++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_logger.LogInformation("Marked {Count} genres as under 'Other Quests' (section id {Id})", num, valueOrDefault);
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning("OtherQuestsSectionRowId {Id} found but matching Section not present in constructed Sections", valueOrDefault);
|
|
||||||
}
|
}
|
||||||
|
_logger.LogWarning("OtherQuestsSectionRowId = {Id} found, but matching Section not present in constructed Sections", valueOrDefault);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -33,6 +33,8 @@ internal sealed class TerritoryData
|
||||||
|
|
||||||
private readonly ImmutableDictionary<uint, ContentFinderConditionData> _contentFinderConditions;
|
private readonly ImmutableDictionary<uint, ContentFinderConditionData> _contentFinderConditions;
|
||||||
|
|
||||||
|
private readonly ImmutableDictionary<uint, uint> _territoryToContentFinderCondition;
|
||||||
|
|
||||||
private readonly ImmutableDictionary<(ElementId QuestId, byte Index), uint> _questBattlesToContentFinderCondition;
|
private readonly ImmutableDictionary<(ElementId QuestId, byte Index), uint> _questBattlesToContentFinderCondition;
|
||||||
|
|
||||||
public TerritoryData(IDataManager dataManager)
|
public TerritoryData(IDataManager dataManager)
|
||||||
|
|
@ -67,6 +69,9 @@ internal sealed class TerritoryData
|
||||||
return flag && x.ContentType.RowId != 6;
|
return flag && x.ContentType.RowId != 6;
|
||||||
})
|
})
|
||||||
select new ContentFinderConditionData(x, dataManager.Language)).ToImmutableDictionary((ContentFinderConditionData x) => x.ContentFinderConditionId, (ContentFinderConditionData x) => x);
|
select new ContentFinderConditionData(x, dataManager.Language)).ToImmutableDictionary((ContentFinderConditionData x) => x.ContentFinderConditionId, (ContentFinderConditionData x) => x);
|
||||||
|
_territoryToContentFinderCondition = (from x in dataManager.GetExcelSheet<ContentFinderCondition>()
|
||||||
|
where x.RowId != 0 && x.TerritoryType.RowId != 0
|
||||||
|
group x by x.TerritoryType.RowId).ToImmutableDictionary((IGrouping<uint, ContentFinderCondition> g) => g.Key, (IGrouping<uint, ContentFinderCondition> g) => g.First().RowId);
|
||||||
_questBattlesToContentFinderCondition = (from x in (from x in dataManager.GetExcelSheet<Quest>()
|
_questBattlesToContentFinderCondition = (from x in (from x in dataManager.GetExcelSheet<Quest>()
|
||||||
where x.RowId != 0 && x.IssuerLocation.RowId != 0
|
where x.RowId != 0 && x.IssuerLocation.RowId != 0
|
||||||
select x).SelectMany(GetQuestBattles)
|
select x).SelectMany(GetQuestBattles)
|
||||||
|
|
@ -138,6 +143,11 @@ internal sealed class TerritoryData
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetContentFinderConditionIdForTerritory(ushort territoryId, out uint cfcId)
|
||||||
|
{
|
||||||
|
return _territoryToContentFinderCondition.TryGetValue(territoryId, out cfcId);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<(ElementId QuestId, byte Index, ContentFinderConditionData Data)> GetAllQuestsWithQuestBattles()
|
public IEnumerable<(ElementId QuestId, byte Index, ContentFinderConditionData Data)> GetAllQuestsWithQuestBattles()
|
||||||
{
|
{
|
||||||
return _questBattlesToContentFinderCondition.Select<KeyValuePair<(ElementId, byte), uint>, (ElementId, byte, ContentFinderConditionData)>((KeyValuePair<(ElementId QuestId, byte Index), uint> x) => (QuestId: x.Key.QuestId, Index: x.Key.Index, _contentFinderConditions[x.Value]));
|
return _questBattlesToContentFinderCondition.Select<KeyValuePair<(ElementId, byte), uint>, (ElementId, byte, ContentFinderConditionData)>((KeyValuePair<(ElementId QuestId, byte Index), uint> x) => (QuestId: x.Key.QuestId, Index: x.Key.Index, _contentFinderConditions[x.Value]));
|
||||||
|
|
@ -149,7 +159,7 @@ internal sealed class TerritoryData
|
||||||
{
|
{
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
return string.Concat(name[0].ToString().ToUpper(CultureInfo.InvariantCulture), name.AsSpan(1));
|
return string.Concat(name[0].ToString().ToUpper(CultureInfo.InvariantCulture).AsSpan(), name.AsSpan(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<(ElementId QuestId, byte Index, uint QuestBattleId)> GetQuestBattles(Quest quest)
|
private static IEnumerable<(ElementId QuestId, byte Index, uint QuestBattleId)> GetQuestBattles(Quest quest)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,12 @@ internal sealed class AutoDutyIpc
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<object> _stop;
|
private readonly ICallGateSubscriber<object> _stop;
|
||||||
|
|
||||||
|
private bool _loggedContentHasPathQueryWarning;
|
||||||
|
|
||||||
|
private bool _loggedLevelingModeWarning;
|
||||||
|
|
||||||
|
public const int MinimumLevelForLevelingMode = 15;
|
||||||
|
|
||||||
public AutoDutyIpc(IDalamudPluginInterface pluginInterface, Configuration configuration, TerritoryData territoryData, ILogger<AutoDutyIpc> logger)
|
public AutoDutyIpc(IDalamudPluginInterface pluginInterface, Configuration configuration, TerritoryData territoryData, ILogger<AutoDutyIpc> logger)
|
||||||
{
|
{
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
|
@ -43,6 +49,8 @@ internal sealed class AutoDutyIpc
|
||||||
_run = pluginInterface.GetIpcSubscriber<uint, int, bool, object>("AutoDuty.Run");
|
_run = pluginInterface.GetIpcSubscriber<uint, int, bool, object>("AutoDuty.Run");
|
||||||
_isStopped = pluginInterface.GetIpcSubscriber<bool>("AutoDuty.IsStopped");
|
_isStopped = pluginInterface.GetIpcSubscriber<bool>("AutoDuty.IsStopped");
|
||||||
_stop = pluginInterface.GetIpcSubscriber<object>("AutoDuty.Stop");
|
_stop = pluginInterface.GetIpcSubscriber<object>("AutoDuty.Stop");
|
||||||
|
_loggedContentHasPathQueryWarning = false;
|
||||||
|
_loggedLevelingModeWarning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsConfiguredToRunContent(DutyOptions? dutyOptions)
|
public bool IsConfiguredToRunContent(DutyOptions? dutyOptions)
|
||||||
|
|
@ -70,6 +78,24 @@ internal sealed class AutoDutyIpc
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsConfiguredToRunLevelingMode()
|
||||||
|
{
|
||||||
|
return _configuration.Duties.RunLevelingModeWhenUnderleveled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsConfiguredToRunLevelingMode(int currentPlayerLevel)
|
||||||
|
{
|
||||||
|
if (!_configuration.Duties.RunLevelingModeWhenUnderleveled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentPlayerLevel < 15)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool HasPath(uint cfcId)
|
public bool HasPath(uint cfcId)
|
||||||
{
|
{
|
||||||
if (!_territoryData.TryGetContentFinderCondition(cfcId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
|
if (!_territoryData.TryGetContentFinderCondition(cfcId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
|
||||||
|
|
@ -81,8 +107,12 @@ internal sealed class AutoDutyIpc
|
||||||
return _contentHasPath.InvokeFunc(contentFinderConditionData.TerritoryId);
|
return _contentHasPath.InvokeFunc(contentFinderConditionData.TerritoryId);
|
||||||
}
|
}
|
||||||
catch (IpcError ipcError)
|
catch (IpcError ipcError)
|
||||||
|
{
|
||||||
|
if (!_loggedContentHasPathQueryWarning)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Unable to query AutoDuty for path in territory {TerritoryType}: {Message}", contentFinderConditionData.TerritoryId, ipcError.Message);
|
_logger.LogWarning("Unable to query AutoDuty for path in territory {TerritoryType}: {Message}", contentFinderConditionData.TerritoryId, ipcError.Message);
|
||||||
|
_loggedContentHasPathQueryWarning = true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +125,7 @@ internal sealed class AutoDutyIpc
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_setConfig.InvokeAction("leveling", "None");
|
||||||
_setConfig.InvokeAction("Unsynced", $"{dutyMode == DutyMode.UnsyncRegular}");
|
_setConfig.InvokeAction("Unsynced", $"{dutyMode == DutyMode.UnsyncRegular}");
|
||||||
ICallGateSubscriber<string, string, object> setConfig = _setConfig;
|
ICallGateSubscriber<string, string, object> setConfig = _setConfig;
|
||||||
setConfig.InvokeAction("dutyModeEnum", dutyMode switch
|
setConfig.InvokeAction("dutyModeEnum", dutyMode switch
|
||||||
|
|
@ -111,6 +142,40 @@ internal sealed class AutoDutyIpc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool StartLevelingMode()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!IsStopped())
|
||||||
|
{
|
||||||
|
_logger.LogDebug("AutoDuty is already running, not starting leveling mode again");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_logger.LogInformation("Starting AutoDuty Leveling mode (Support) - AutoDuty will select the best dungeon");
|
||||||
|
_setConfig.InvokeAction("leveling", "Support");
|
||||||
|
_run.InvokeAction(0u, 1, !_configuration.Advanced.DisableAutoDutyBareMode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (IpcError ipcError)
|
||||||
|
{
|
||||||
|
if (!_loggedLevelingModeWarning)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Unable to start AutoDuty Leveling mode: {Message}", ipcError.Message);
|
||||||
|
_loggedLevelingModeWarning = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
if (!_loggedLevelingModeWarning)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(exception, "Unable to start AutoDuty Leveling mode");
|
||||||
|
_loggedLevelingModeWarning = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsStopped()
|
public bool IsStopped()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -123,16 +188,36 @@ internal sealed class AutoDutyIpc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DisableLevelingMode()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Disabling AutoDuty leveling mode");
|
||||||
|
_setConfig.InvokeAction("leveling", "None");
|
||||||
|
}
|
||||||
|
catch (IpcError ipcError)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Unable to disable AutoDuty leveling mode: {Message}", ipcError.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Calling AutoDuty.Stop");
|
_logger.LogInformation("Calling AutoDuty.Stop");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_setConfig.InvokeAction("leveling", "None");
|
||||||
|
}
|
||||||
|
catch (IpcError)
|
||||||
|
{
|
||||||
|
}
|
||||||
_stop.InvokeAction();
|
_stop.InvokeAction();
|
||||||
}
|
}
|
||||||
catch (IpcError ipcError)
|
catch (IpcError ipcError2)
|
||||||
{
|
{
|
||||||
throw new TaskException("Unable to stop AutoDuty: " + ipcError.Message, ipcError);
|
throw new TaskException("Unable to stop AutoDuty: " + ipcError2.Message, ipcError2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,18 @@ namespace Questionable.External;
|
||||||
|
|
||||||
internal sealed class NavmeshIpc
|
internal sealed class NavmeshIpc
|
||||||
{
|
{
|
||||||
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
|
|
||||||
private readonly ILogger<NavmeshIpc> _logger;
|
private readonly ILogger<NavmeshIpc> _logger;
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<bool> _isNavReady;
|
private readonly ICallGateSubscriber<bool> _isNavReady;
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<Vector3, Vector3, bool, CancellationToken, Task<List<Vector3>>> _navPathfind;
|
private readonly ICallGateSubscriber<Vector3, Vector3, bool, CancellationToken, Task<List<Vector3>>> _navPathfind;
|
||||||
|
|
||||||
|
private readonly ICallGateSubscriber<bool> _navPathfindInProgress;
|
||||||
|
|
||||||
|
private readonly ICallGateSubscriber<int> _navNumQueuedRequests;
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<List<Vector3>, bool, object> _pathMoveTo;
|
private readonly ICallGateSubscriber<List<Vector3>, bool, object> _pathMoveTo;
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<object> _pathStop;
|
private readonly ICallGateSubscriber<object> _pathStop;
|
||||||
|
|
@ -29,8 +35,34 @@ internal sealed class NavmeshIpc
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<Vector3, bool, float, Vector3?> _queryPointOnFloor;
|
private readonly ICallGateSubscriber<Vector3, bool, float, Vector3?> _queryPointOnFloor;
|
||||||
|
|
||||||
|
private readonly ICallGateSubscriber<Vector3, float, float, Vector3?> _queryNearestPoint;
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<float> _buildProgress;
|
private readonly ICallGateSubscriber<float> _buildProgress;
|
||||||
|
|
||||||
|
private readonly ICallGateSubscriber<bool> _navReload;
|
||||||
|
|
||||||
|
private readonly ICallGateSubscriber<bool> _navRebuild;
|
||||||
|
|
||||||
|
public bool IsNavmeshAvailable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_isNavReady.InvokeFunc();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (IpcNotReadyError)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (IpcError)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsReady
|
public bool IsReady
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -61,22 +93,62 @@ internal sealed class NavmeshIpc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsPathfindInProgress
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _navPathfindInProgress.InvokeFunc();
|
||||||
|
}
|
||||||
|
catch (IpcError)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int NumQueuedPathfindRequests
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _navNumQueuedRequests.InvokeFunc();
|
||||||
|
}
|
||||||
|
catch (IpcError)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public NavmeshIpc(IDalamudPluginInterface pluginInterface, ILogger<NavmeshIpc> logger)
|
public NavmeshIpc(IDalamudPluginInterface pluginInterface, ILogger<NavmeshIpc> logger)
|
||||||
{
|
{
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_isNavReady = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Nav.IsReady");
|
_isNavReady = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Nav.IsReady");
|
||||||
_navPathfind = pluginInterface.GetIpcSubscriber<Vector3, Vector3, bool, CancellationToken, Task<List<Vector3>>>("vnavmesh.Nav.PathfindCancelable");
|
_navPathfind = pluginInterface.GetIpcSubscriber<Vector3, Vector3, bool, CancellationToken, Task<List<Vector3>>>("vnavmesh.Nav.PathfindCancelable");
|
||||||
|
_navPathfindInProgress = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Nav.PathfindInProgress");
|
||||||
|
_navNumQueuedRequests = pluginInterface.GetIpcSubscriber<int>("vnavmesh.Nav.PathfindNumQueued");
|
||||||
_pathMoveTo = pluginInterface.GetIpcSubscriber<List<Vector3>, bool, object>("vnavmesh.Path.MoveTo");
|
_pathMoveTo = pluginInterface.GetIpcSubscriber<List<Vector3>, bool, object>("vnavmesh.Path.MoveTo");
|
||||||
_pathStop = pluginInterface.GetIpcSubscriber<object>("vnavmesh.Path.Stop");
|
_pathStop = pluginInterface.GetIpcSubscriber<object>("vnavmesh.Path.Stop");
|
||||||
_pathIsRunning = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Path.IsRunning");
|
_pathIsRunning = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Path.IsRunning");
|
||||||
_pathListWaypoints = pluginInterface.GetIpcSubscriber<List<Vector3>>("vnavmesh.Path.ListWaypoints");
|
_pathListWaypoints = pluginInterface.GetIpcSubscriber<List<Vector3>>("vnavmesh.Path.ListWaypoints");
|
||||||
_pathSetTolerance = pluginInterface.GetIpcSubscriber<float, object>("vnavmesh.Path.SetTolerance");
|
_pathSetTolerance = pluginInterface.GetIpcSubscriber<float, object>("vnavmesh.Path.SetTolerance");
|
||||||
_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");
|
||||||
_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()
|
||||||
{
|
{
|
||||||
|
if (!IsNavmeshAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_pathStop.InvokeAction();
|
_pathStop.InvokeAction();
|
||||||
|
|
@ -87,8 +159,44 @@ internal sealed class NavmeshIpc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reload()
|
||||||
|
{
|
||||||
|
if (!IsNavmeshAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_navReload.InvokeFunc();
|
||||||
|
}
|
||||||
|
catch (IpcError exception)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(exception, "Could not reload navmesh");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rebuild()
|
||||||
|
{
|
||||||
|
if (!IsNavmeshAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
if (!IsNavmeshAvailable)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new List<Vector3>());
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_pathSetTolerance.InvokeAction(0.25f);
|
_pathSetTolerance.InvokeAction(0.25f);
|
||||||
|
|
@ -101,8 +209,30 @@ internal sealed class NavmeshIpc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<List<Vector3>> PathfindWithTolerance(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly, float tolerance, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!IsNavmeshAvailable)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new List<Vector3>());
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pathSetTolerance.InvokeAction(tolerance);
|
||||||
|
return _pluginInterface.GetIpcSubscriber<Vector3, Vector3, bool, float, CancellationToken, Task<List<Vector3>>>("vnavmesh.Nav.PathfindWithToleranceCancelable").InvokeFunc(localPlayerPosition, targetPosition, fly, tolerance, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (IpcError exception)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(exception, "Could not pathfind with tolerance via navmesh");
|
||||||
|
return Task.FromException<List<Vector3>>(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void MoveTo(List<Vector3> position, bool fly)
|
public void MoveTo(List<Vector3> position, bool fly)
|
||||||
{
|
{
|
||||||
|
if (!IsNavmeshAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
Stop();
|
Stop();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -126,6 +256,30 @@ internal sealed class NavmeshIpc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vector3? GetPointOnFloor(Vector3 position, bool unlandable, float halfExtentXZ)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _queryPointOnFloor.InvokeFunc(position, unlandable, halfExtentXZ);
|
||||||
|
}
|
||||||
|
catch (IpcError)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3? FindNearestMeshPoint(Vector3 position, float halfExtentXZ, float halfExtentY)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _queryNearestPoint.InvokeFunc(position, halfExtentXZ, halfExtentY);
|
||||||
|
}
|
||||||
|
catch (IpcError)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<Vector3> GetWaypoints()
|
public List<Vector3> GetWaypoints()
|
||||||
{
|
{
|
||||||
if (IsPathRunning)
|
if (IsPathRunning)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
@ -10,31 +11,37 @@ internal sealed class TextAdvanceIpc : IDisposable
|
||||||
{
|
{
|
||||||
public sealed class ExternalTerritoryConfig
|
public sealed class ExternalTerritoryConfig
|
||||||
{
|
{
|
||||||
public bool? EnableQuestAccept = true;
|
public bool? EnableQuestAccept;
|
||||||
|
|
||||||
public bool? EnableQuestComplete = true;
|
public bool? EnableQuestComplete;
|
||||||
|
|
||||||
public bool? EnableRewardPick = true;
|
public bool? EnableRewardPick;
|
||||||
|
|
||||||
public bool? EnableRequestHandin = true;
|
public bool? EnableRequestHandin;
|
||||||
|
|
||||||
public bool? EnableCutsceneEsc = true;
|
public bool? EnableCutsceneEsc;
|
||||||
|
|
||||||
public bool? EnableCutsceneSkipConfirm = true;
|
public bool? EnableCutsceneSkipConfirm;
|
||||||
|
|
||||||
public bool? EnableTalkSkip = true;
|
public bool? EnableTalkSkip;
|
||||||
|
|
||||||
public bool? EnableRequestFill = true;
|
public bool? EnableRequestFill;
|
||||||
|
|
||||||
public bool? EnableAutoInteract = false;
|
public bool? EnableAutoInteract;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isExternalControlActivated;
|
private bool _isExternalControlActivated;
|
||||||
|
|
||||||
|
private bool _lastCinemaModeState;
|
||||||
|
|
||||||
|
private bool _lastInCutsceneState;
|
||||||
|
|
||||||
private readonly QuestController _questController;
|
private readonly QuestController _questController;
|
||||||
|
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
|
|
||||||
|
private readonly ICondition _condition;
|
||||||
|
|
||||||
private readonly IFramework _framework;
|
private readonly IFramework _framework;
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<bool> _isInExternalControl;
|
private readonly ICallGateSubscriber<bool> _isInExternalControl;
|
||||||
|
|
@ -45,13 +52,12 @@ internal sealed class TextAdvanceIpc : IDisposable
|
||||||
|
|
||||||
private readonly string _pluginName;
|
private readonly string _pluginName;
|
||||||
|
|
||||||
private readonly ExternalTerritoryConfig _externalTerritoryConfig = new ExternalTerritoryConfig();
|
public TextAdvanceIpc(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, Configuration configuration, ICondition condition)
|
||||||
|
|
||||||
public TextAdvanceIpc(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, Configuration configuration)
|
|
||||||
{
|
{
|
||||||
_framework = framework;
|
_framework = framework;
|
||||||
_questController = questController;
|
_questController = questController;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
_condition = condition;
|
||||||
_isInExternalControl = pluginInterface.GetIpcSubscriber<bool>("TextAdvance.IsInExternalControl");
|
_isInExternalControl = pluginInterface.GetIpcSubscriber<bool>("TextAdvance.IsInExternalControl");
|
||||||
_enableExternalControl = pluginInterface.GetIpcSubscriber<string, ExternalTerritoryConfig, bool>("TextAdvance.EnableExternalControl");
|
_enableExternalControl = pluginInterface.GetIpcSubscriber<string, ExternalTerritoryConfig, bool>("TextAdvance.EnableExternalControl");
|
||||||
_disableExternalControl = pluginInterface.GetIpcSubscriber<string, bool>("TextAdvance.DisableExternalControl");
|
_disableExternalControl = pluginInterface.GetIpcSubscriber<string, bool>("TextAdvance.DisableExternalControl");
|
||||||
|
|
@ -71,16 +77,53 @@ internal sealed class TextAdvanceIpc : IDisposable
|
||||||
private void OnUpdate(IFramework framework)
|
private void OnUpdate(IFramework framework)
|
||||||
{
|
{
|
||||||
bool flag = _questController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual;
|
bool flag = _questController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual;
|
||||||
if (_configuration.General.ConfigureTextAdvance && flag)
|
if (!_configuration.General.ConfigureTextAdvance || !flag)
|
||||||
{
|
{
|
||||||
if (!_isInExternalControl.InvokeFunc() && _enableExternalControl.InvokeFunc(_pluginName, _externalTerritoryConfig))
|
if (_isExternalControlActivated && (_disableExternalControl.InvokeFunc(_pluginName) || !_isInExternalControl.InvokeFunc()))
|
||||||
{
|
|
||||||
_isExternalControlActivated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_isExternalControlActivated && (_disableExternalControl.InvokeFunc(_pluginName) || !_isInExternalControl.InvokeFunc()))
|
|
||||||
{
|
{
|
||||||
_isExternalControlActivated = false;
|
_isExternalControlActivated = false;
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool cinemaMode = _configuration.General.CinemaMode;
|
||||||
|
bool flag2 = _condition[ConditionFlag.OccupiedInCutSceneEvent] || _condition[ConditionFlag.WatchingCutscene] || _condition[ConditionFlag.WatchingCutscene78];
|
||||||
|
bool flag3 = cinemaMode != _lastCinemaModeState || (cinemaMode && flag2 != _lastInCutsceneState);
|
||||||
|
if (!_isExternalControlActivated)
|
||||||
|
{
|
||||||
|
if (!_isInExternalControl.InvokeFunc())
|
||||||
|
{
|
||||||
|
ExternalTerritoryConfig arg = CreateExternalTerritoryConfig(cinemaMode, flag2);
|
||||||
|
if (_enableExternalControl.InvokeFunc(_pluginName, arg))
|
||||||
|
{
|
||||||
|
_isExternalControlActivated = true;
|
||||||
|
_lastCinemaModeState = cinemaMode;
|
||||||
|
_lastInCutsceneState = flag2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (flag3)
|
||||||
|
{
|
||||||
|
ExternalTerritoryConfig arg2 = CreateExternalTerritoryConfig(cinemaMode, flag2);
|
||||||
|
_enableExternalControl.InvokeFunc(_pluginName, arg2);
|
||||||
|
_lastCinemaModeState = cinemaMode;
|
||||||
|
_lastInCutsceneState = flag2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExternalTerritoryConfig CreateExternalTerritoryConfig(bool cinemaMode, bool inCutscene)
|
||||||
|
{
|
||||||
|
bool flag = cinemaMode && inCutscene;
|
||||||
|
return new ExternalTerritoryConfig
|
||||||
|
{
|
||||||
|
EnableQuestAccept = true,
|
||||||
|
EnableQuestComplete = true,
|
||||||
|
EnableRewardPick = true,
|
||||||
|
EnableRequestHandin = true,
|
||||||
|
EnableCutsceneEsc = !cinemaMode,
|
||||||
|
EnableCutsceneSkipConfirm = !cinemaMode,
|
||||||
|
EnableTalkSkip = !flag,
|
||||||
|
EnableRequestFill = true,
|
||||||
|
EnableAutoInteract = false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,16 @@ internal sealed class AetheryteFunctions
|
||||||
|
|
||||||
private readonly IDataManager _dataManager;
|
private readonly IDataManager _dataManager;
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
|
public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
|
||||||
|
|
||||||
public AetheryteFunctions(IServiceProvider serviceProvider, ILogger<AetheryteFunctions> logger, IDataManager dataManager, IClientState clientState)
|
public AetheryteFunctions(IServiceProvider serviceProvider, ILogger<AetheryteFunctions> logger, IDataManager dataManager, IObjectTable objectTable)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_dataManager = dataManager;
|
_dataManager = dataManager;
|
||||||
_clientState = clientState;
|
_objectTable = objectTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
|
public unsafe bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
|
||||||
|
|
@ -114,7 +114,7 @@ internal sealed class AetheryteFunctions
|
||||||
|
|
||||||
public unsafe AetheryteRegistrationResult CanRegisterFreeOrFavoriteAetheryte(EAetheryteLocation aetheryteLocation)
|
public unsafe AetheryteRegistrationResult CanRegisterFreeOrFavoriteAetheryte(EAetheryteLocation aetheryteLocation)
|
||||||
{
|
{
|
||||||
if (_clientState.LocalPlayer == null)
|
if (_objectTable[0] == null)
|
||||||
{
|
{
|
||||||
return AetheryteRegistrationResult.NotPossible;
|
return AetheryteRegistrationResult.NotPossible;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue