Compare commits

..

1 commit
main ... main

Author SHA1 Message Date
Simon Latusek
b6b2980285 json writing 2025-11-03 11:51:35 +01:00
246 changed files with 117498 additions and 156318 deletions

3
.gitignore vendored
View file

@ -1,5 +1,2 @@
AssemblyInfo.cs
Solution.sln
.vs
**/bin/
**/obj/

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>GatheringPaths</AssemblyName>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<TargetFramework>netcoreapp1.0</TargetFramework>
<TargetFramework>netcoreapp9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<LangVersion>12.0</LangVersion>

View file

@ -10,10 +10,6 @@ public readonly struct ExtendedBaseParam : IExcelRow<ExtendedBaseParam>
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 unsafe Collection<ushort> EquipSlotCategoryPct => new Collection<ushort>(_003Cpage_003EP, _003Coffset_003EP, _003Coffset_003EP, (delegate*<ExcelPage, uint, uint, uint, ushort>)(&EquipSlotCategoryPctCtor), 23);

View file

@ -207,7 +207,7 @@ public sealed class GearStatsCalculator
num2--;
continue;
}
if (i == 0 && !((ReadOnlySpan<uint>)CanHaveOffhand).Contains(rowOrDefault.Value.ItemUICategory.RowId))
if (i == 0 && !CanHaveOffhand.Contains(rowOrDefault.Value.ItemUICategory.RowId))
{
num += rowOrDefault.Value.LevelItem.RowId;
i++;

View file

@ -33,7 +33,7 @@ public abstract class LWindow : Window
}
}
protected new bool IsPinned
protected bool IsPinned
{
get
{
@ -45,7 +45,7 @@ public abstract class LWindow : Window
}
}
protected new bool IsClickthrough
protected bool IsClickthrough
{
get
{

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>LLib</AssemblyName>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<TargetFramework>netcoreapp1.0</TargetFramework>
<TargetFramework>netcoreapp9.0</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup>

View file

@ -18,7 +18,9 @@ public static class DataManagerExtensions
public static string? GetString<T>(this IDataManager dataManager, string key, IPluginLog? pluginLog) where T : struct, IQuestDialogueText, IExcelRow<T>
{
return dataManager.GetSeString<T>(key)?.WithCertainMacroCodeReplacements();
string text = 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>
@ -28,7 +30,9 @@ public static class DataManagerExtensions
{
return null;
}
return new Regex(string.Join("", seString.Select((ReadOnlySePayload payload) => (payload.Type == ReadOnlySePayloadType.Text) ? Regex.Escape(payload.ToString()) : "(.*)")));
string text = 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>
@ -45,7 +49,9 @@ 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>
{
return dataManager.GetSeString(rowId, mapper)?.WithCertainMacroCodeReplacements();
string text = 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>
@ -55,7 +61,9 @@ public static class DataManagerExtensions
{
return null;
}
return seString.ToRegex();
Regex regex = 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>
@ -66,7 +74,9 @@ public static class DataManagerExtensions
{
return null;
}
return text.ToRegex();
Regex regex = text.ToRegex();
pluginLog?.Verbose($"{typeof(T).Name}.regex => /{regex}/");
return regex;
}
public static Regex ToRegex(this ReadOnlySeString? text)
@ -82,7 +92,7 @@ public static class DataManagerExtensions
ReadOnlySePayloadType.Text => payload.ToString(),
ReadOnlySePayloadType.Macro => payload.MacroCode switch
{
MacroCode.NewLine => Environment.NewLine,
MacroCode.NewLine => "",
MacroCode.NonBreakingSpace => " ",
MacroCode.Hyphen => "-",
MacroCode.SoftHyphen => "",

View file

@ -8,10 +8,6 @@ public readonly struct QuestDialogueText : IQuestDialogueText, IExcelRow<QuestDi
{
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 Value => _003Cpage_003EP.ReadString(_003Coffset_003EP + 4, _003Coffset_003EP);

View file

@ -1,19 +0,0 @@
<?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>

View file

@ -1,6 +0,0 @@
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";
}

View file

@ -1,16 +0,0 @@
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";
}

View file

@ -1,146 +0,0 @@
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;
}
}

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>QuestPaths</AssemblyName>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<TargetFramework>netcoreapp1.0</TargetFramework>
<TargetFramework>netcoreapp9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<LangVersion>12.0</LangVersion>

View file

@ -219,9 +219,6 @@
"CompletionQuestVariablesFlags": {
"$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": {
"type": "string",
"enum": [
@ -260,22 +257,14 @@
"Item": {
"type": "object",
"properties": {
"InInventory": {
"type": "boolean"
},
"NotInInventory": {
"type": "boolean"
},
"BetterOrEqualItemEquipped": {
"type": "boolean",
"description": "Skip this step if a better or equal item (by item level) is already equipped"
}
},
"additionalProperties": false
}
},
"MinimumLevel": {
"type": "integer",
"description": "Skip this step if the player level is greater than or equal to this value",
"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).",
"minimum": 1,
"maximum": 100
},
@ -288,15 +277,6 @@
]
}
},
"QuestsCompleted": {
"type": "array",
"items": {
"type": [
"number",
"string"
]
}
},
"NotNamePlateIconId": {
"type": "array",
"items": {
@ -1606,11 +1586,6 @@
"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": {
"type": "string",
"pattern": "^0\\.\\d+\\.\\d+\\.\\d+$"
@ -1630,8 +1605,7 @@
"required": [
"ContentFinderConditionId",
"Enabled"
],
"additionalProperties": false
]
},
"DataId": {
"type": "null"
@ -1789,10 +1763,6 @@
"properties": {
"ItemCount": {
"type": "number"
},
"RequireHq": {
"type": "boolean",
"description": "If true, only HQ items will be counted towards completion."
}
},
"required": [

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;
@ -12,26 +11,20 @@ public sealed class StringListOrValueConverter : JsonConverter<List<string>>
{
if (reader.TokenType == JsonTokenType.String)
{
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;
return new List<string>(1) { reader.GetString() };
}
if (reader.TokenType != JsonTokenType.StartArray)
{
throw new JsonException();
}
reader.Read();
List<string> list2 = new List<string>();
List<string> list = new List<string>();
while (reader.TokenType != JsonTokenType.EndArray)
{
list2.Add(reader.GetString());
list.Add(reader.GetString());
reader.Read();
}
return list2;
return list;
}
public override void Write(Utf8JsonWriter writer, List<string>? value, JsonSerializerOptions options)

View file

@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-classjob.json",
"$id": "https://git.carvel.li//liza/Questionable/raw/refs/heads/main/Questionable.Model/common-classjob.json",
"type": "string",
"enum": [
"Gladiator",

View file

@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json",
"$id": "https://git.carvel.li//liza/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json",
"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",
"items": {

View file

@ -1,40 +0,0 @@
{
"$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
}
}
}

View file

@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json",
"$id": "https://git.carvel.li//liza/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json",
"type": "object",
"description": "Position in the world",
"properties": {

View file

@ -0,0 +1,21 @@
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());
}
}

View file

@ -0,0 +1,41 @@
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();
}
}

View file

@ -0,0 +1,63 @@
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());
}
}

View file

@ -11,6 +11,4 @@ public class DutyOptions
public bool LowPriority { get; set; }
public List<string> Notes { get; set; } = new List<string>();
public EDutyMode? DutyMode { get; set; }
}

View file

@ -1,8 +0,0 @@
namespace Questionable.Model.Questing;
public enum EDutyMode
{
Support,
UnsyncSolo,
UnsyncParty
}

View file

@ -95,7 +95,7 @@ public abstract class ElementId : IComparable<ElementId>, IEquatable<ElementId>
if (value.StartsWith("A"))
{
value = value.Substring(1);
string[] array = value.Split('x');
string[] array = value.Split(new char[1] { 'x' });
if (array.Length == 2)
{
return new AlliedSocietyDailyId(byte.Parse(array[0], CultureInfo.InvariantCulture), byte.Parse(array[1], CultureInfo.InvariantCulture));

View file

@ -16,10 +16,10 @@ public sealed class QuestRoot
public string? Comment { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonIgnore(/*Could not decode attribute arguments.*/)]
public bool? IsSeasonalQuest { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonIgnore(/*Could not decode attribute arguments.*/)]
public DateTime? SeasonalQuestExpiry { get; set; }
public List<QuestSequence> QuestSequence { get; set; } = new List<QuestSequence>();

View file

@ -22,7 +22,7 @@ public sealed class QuestStep
public ushort TerritoryId { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[JsonIgnore(/*Could not decode attribute arguments.*/)]
public EInteractionType InteractionType { get; set; }
public float? NpcWaitDistance { get; set; }
@ -68,8 +68,6 @@ public sealed class QuestStep
public int? ItemCount { get; set; }
public bool? RequireHq { get; set; }
public EEmote? Emote { get; set; }
public ChatMessage? ChatMessage { get; set; }

View file

@ -21,15 +21,15 @@ public sealed class QuestWorkValue(byte? high, byte? low, EQuestWorkMode mode)
{
if (High.HasValue && Low.HasValue)
{
return ((byte)((High.Value << 4) + Low.Value)).ToString();
return ((byte)(High << 4).Value + Low).ToString();
}
if (High.HasValue)
{
return High.Value + "H";
return High + "H";
}
if (Low.HasValue)
{
return Low.Value + "L";
return Low + "L";
}
return "-";
}

View file

@ -3,8 +3,4 @@ namespace Questionable.Model.Questing;
public sealed class SkipItemConditions
{
public bool NotInInventory { get; set; }
public bool InInventory { get; set; }
public bool BetterOrEqualItemEquipped { get; set; }
}

View file

@ -61,6 +61,6 @@ public sealed class SkipStepConditions
public override string ToString()
{
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}";
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);
}
}

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>Questionable.Model</AssemblyName>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<TargetFramework>netcoreapp1.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<LangVersion>12.0</LangVersion>
@ -17,14 +17,19 @@
<None Remove="Questionable.Model.CommonAetheryte" />
<None Remove="Questionable.Model.CommonClassJob" />
<None Remove="Questionable.Model.CommonCompletionFlags" />
<None Remove="Questionable.Model.CommonRequiredVariables" />
<None Remove="Questionable.Model.CommonVector3" />
<EmbeddedResource Include="Questionable.Model.CommonAethernetShard" LogicalName="Questionable.Model.CommonAethernetShard" />
<EmbeddedResource Include="Questionable.Model.CommonAetheryte" LogicalName="Questionable.Model.CommonAetheryte" />
<EmbeddedResource Include="Questionable.Model.CommonClassJob" LogicalName="Questionable.Model.CommonClassJob" />
<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" />
</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>

View file

@ -12,7 +12,5 @@ public static class AssemblyModelLoader
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");
}

View file

@ -1,8 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Auto)]
[InlineArray(2)]
internal struct _003C_003Ey__InlineArray2<T>
{
}

View file

@ -1,8 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Auto)]
[InlineArray(7)]
internal struct _003C_003Ey__InlineArray7<T>
{
}

View file

@ -29,12 +29,6 @@ internal sealed class ItemUseModule : ICombatModule
private DateTime _continueAt;
private bool _itemUsePending;
private DateTime _itemUsePendingUntil;
private int _lastKnownItemCount;
public ItemUseModule(IServiceProvider serviceProvider, ICondition condition, ILogger<ItemUseModule> logger)
{
_serviceProvider = serviceProvider;
@ -55,19 +49,13 @@ internal sealed class ItemUseModule : ICombatModule
return _delegate != null;
}
public unsafe bool Start(CombatController.CombatData combatData)
public bool Start(CombatController.CombatData combatData)
{
if (_delegate.Start(combatData))
{
_combatData = combatData;
_isDoingRotation = true;
_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 false;
@ -100,29 +88,16 @@ internal sealed class ItemUseModule : ICombatModule
{
if (_isDoingRotation)
{
int inventoryItemCount = InventoryManager.Instance()->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0);
if (_itemUsePending)
{
if (DateTime.Now < _itemUsePendingUntil)
{
_logger.LogDebug("Item use pending; ignoring temporary inventory count={Count}", inventoryItemCount);
}
else
{
_itemUsePending = false;
}
}
if (!_itemUsePending && inventoryItemCount == 0)
if (InventoryManager.Instance()->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) == 0)
{
_isDoingRotation = false;
_delegate.Stop();
return;
}
_lastKnownItemCount = inventoryItemCount;
if (ShouldUseItem(nextTarget))
else if (ShouldUseItem(nextTarget))
{
_itemUsePending = true;
_itemUsePendingUntil = DateTime.Now.AddSeconds(3.0);
_isDoingRotation = false;
_delegate.Stop();
_logger.LogInformation("Using item {ItemId}", _combatData.CombatItemUse.ItemId);
AgentInventoryContext.Instance()->UseItem(_combatData.CombatItemUse.ItemId, InventoryType.Invalid, 0u, 0);
_continueAt = DateTime.Now.AddSeconds(2.0);
}

View file

@ -1,20 +0,0 @@
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();
}
}

View file

@ -1,116 +0,0 @@
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);
}
}
}
}

View file

@ -1,25 +0,0 @@
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;
}
}

View file

@ -1,50 +0,0 @@
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);
}
}
}

View file

@ -1,39 +0,0 @@
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);
}
}
}

View file

@ -1,8 +0,0 @@
namespace Questionable.Controller.DebugCommands;
internal interface IDebugCommandHandler
{
string CommandName { get; }
void Execute(string[] arguments);
}

View file

@ -1,37 +0,0 @@
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);
}
}
}

View file

@ -1,53 +0,0 @@
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);
}
}
}

View file

@ -1,220 +0,0 @@
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);
}
}
}

View file

@ -1,20 +0,0 @@
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();
}
}

View file

@ -1,20 +0,0 @@
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;
}
}

View file

@ -1,58 +0,0 @@
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);
}
}
}

View file

@ -1,66 +0,0 @@
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);
}
}
}

View file

@ -1,48 +0,0 @@
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>();
}
}

View file

@ -138,7 +138,7 @@ internal sealed class CraftworksSupplyController : IDisposable
address->Close(fireCallback: true);
if (addonById->NameString == "BankaCraftworksSupply")
{
_framework.RunOnTick((Action)InteractWithBankaCraftworksSupply, TimeSpan.FromMilliseconds(50L), 0, default(CancellationToken));
_framework.RunOnTick((Action)InteractWithBankaCraftworksSupply, TimeSpan.FromMilliseconds(50L, 0L), 0, default(CancellationToken));
}
}
else

View file

@ -38,30 +38,15 @@ internal sealed class CreditsController : IDisposable
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 Configuration _configuration;
private readonly ILogger<CreditsController> _logger;
private static readonly object _lock = new object();
public CreditsController(IAddonLifecycle addonLifecycle, Configuration configuration, ILogger<CreditsController> logger)
public CreditsController(IAddonLifecycle addonLifecycle, ILogger<CreditsController> logger)
{
_addonLifecycle = addonLifecycle;
_configuration = configuration;
_logger = logger;
lock (_lock)
{
@ -78,117 +63,70 @@ internal sealed class CreditsController : IDisposable
private unsafe void CreditScrollPostSetup(AddonEvent type, AddonArgs args)
{
HandleCutscene(delegate
_logger.LogInformation("Closing Credits sequence scroll post-setup");
if (args.Addon.Address == IntPtr.Zero)
{
_logger.LogInformation("CreditScrollPostSetup: attempting to close credits sequence (scroll).");
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
address->FireCallbackInt(-2);
}, args);
}
private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args)
{
HandleCutscene(delegate
{
_logger.LogInformation("CreditPostSetup: attempting to close credits sequence.");
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
address->FireCallbackInt(-2);
}, args);
}
private unsafe void CreditPlayerPostSetup(AddonEvent type, AddonArgs args)
{
HandleCutscene(delegate
{
_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.");
_logger.LogInformation("CreditScrollPostSetup: Addon address is zero, skipping.");
return;
}
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)
{
try
{
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler);
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler);
_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;
_instance = null;
_handledConsecutiveCutscenes = 0;
_lastHandledAddr = 0L;
_lastHandledAt = DateTime.MinValue;
}
}
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
address->FireCallbackInt(-2);
}
private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args)
{
_logger.LogInformation("Closing Credits sequence post-setup");
if (args.Addon.Address == IntPtr.Zero)
{
_logger.LogInformation("CreditPostSetup: Addon address is zero, skipping.");
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;
address->FireCallbackInt(-2);
}
private unsafe void CreditPlayerPostSetup(AddonEvent type, AddonArgs args)
{
_logger.LogInformation("Closing CreditPlayer");
if (args.Addon.Address == IntPtr.Zero)
{
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;
address->Close(fireCallback: true);
}
public static void DeferDisposeUntilCutsceneEnds()
@ -243,9 +181,6 @@ internal sealed class CreditsController : IDisposable
_registeredLifecycle = null;
_instance = null;
_pendingDispose = false;
_handledConsecutiveCutscenes = 0;
_lastHandledAddr = 0L;
_lastHandledAt = DateTime.MinValue;
_logger.LogDebug("CreditsController listeners unregistered and disposed (primary instance).");
}
}

View file

@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
@ -57,8 +58,6 @@ internal sealed class InteractionUiController : IDisposable
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly ShopController _shopController;
private readonly BossModIpc _bossModIpc;
@ -85,7 +84,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, IObjectTable objectTable, 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, ShopController shopController, BossModIpc bossModIpc, Configuration configuration, ILogger<InteractionUiController> logger)
{
_addonLifecycle = addonLifecycle;
_dataManager = dataManager;
@ -100,7 +99,6 @@ internal sealed class InteractionUiController : IDisposable
_gameGui = gameGui;
_targetManager = targetManager;
_clientState = clientState;
_objectTable = objectTable;
_shopController = shopController;
_bossModIpc = bossModIpc;
_configuration = configuration;
@ -685,6 +683,7 @@ internal sealed class InteractionUiController : IDisposable
if (aetheryte.HasValue)
{
EAetheryteLocation valueOrDefault = aetheryte.GetValueOrDefault();
Span<DialogueChoice> span2;
Span<DialogueChoice> span;
switch (_aetheryteFunctions.CanRegisterFreeOrFavoriteAetheryte(valueOrDefault))
{
@ -694,12 +693,12 @@ internal sealed class InteractionUiController : IDisposable
int num2 = 1 + list.Count;
List<DialogueChoice> list3 = new List<DialogueChoice>(num2);
CollectionsMarshal.SetCount(list3, num2);
Span<DialogueChoice> span3 = CollectionsMarshal.AsSpan(list3);
span2 = CollectionsMarshal.AsSpan(list3);
int num = 0;
span = CollectionsMarshal.AsSpan(list);
span.CopyTo(span3.Slice(num, span.Length));
span.CopyTo(span2.Slice(num, span.Length));
num += span.Length;
span3[num] = new DialogueChoice
span2[num] = new DialogueChoice
{
Type = EDialogChoiceType.YesNo,
ExcelSheet = "Addon",
@ -717,7 +716,7 @@ internal sealed class InteractionUiController : IDisposable
CollectionsMarshal.SetCount(list2, num);
span = CollectionsMarshal.AsSpan(list2);
int num2 = 0;
Span<DialogueChoice> span2 = CollectionsMarshal.AsSpan(list);
span2 = CollectionsMarshal.AsSpan(list);
span2.CopyTo(span.Slice(num2, span2.Length));
num2 += span2.Length;
span[num2] = new DialogueChoice
@ -858,10 +857,7 @@ internal sealed class InteractionUiController : IDisposable
{
_logger.LogTrace("FindTargetTerritoryFromQuestStep (current): {CurrentTerritory}, {TargetTerritory}", questStep.TerritoryId, questStep.TargetTerritoryId);
}
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))
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))
{
foreach (QuestStep step in gatheringRoot.Steps)
{
@ -872,7 +868,6 @@ internal sealed class InteractionUiController : IDisposable
}
}
}
}
if (questStep == null || !questStep.TargetTerritoryId.HasValue)
{
_logger.LogTrace("FindTargetTerritoryFromQuestStep: Checking previous step...");

View file

@ -50,7 +50,7 @@ internal static class Mount
}
}
internal sealed class MountEvaluator(GameFunctions gameFunctions, ICondition condition, TerritoryData territoryData, IClientState clientState, IObjectTable objectTable, ILogger<MountEvaluator> logger)
internal sealed class MountEvaluator(GameFunctions gameFunctions, ICondition condition, TerritoryData territoryData, IClientState clientState, ILogger<MountEvaluator> logger)
{
public unsafe MountResult EvaluateMountState(MountTask task, bool dryRun, ref DateTime retryAt)
{
@ -71,7 +71,7 @@ internal static class Mount
}
if (task.MountIf == EMountIf.AwayFromPosition)
{
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());
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());
if (task.TerritoryId == clientState.TerritoryType && num < 30f && !Conditions.Instance()->Diving)
{
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, IObjectTable objectTable) : TaskExecutor<UnmountTask>()
internal sealed class UnmountExecutor(ICondition condition, ILogger<UnmountTask> logger, GameFunctions gameFunctions, IClientState clientState) : TaskExecutor<UnmountTask>()
{
private bool _unmountTriggered;
@ -221,9 +221,10 @@ internal static class Mount
private unsafe bool IsUnmounting()
{
if (objectTable[0] is IPlayerCharacter playerCharacter)
IPlayerCharacter localPlayer = clientState.LocalPlayer;
if (localPlayer != null)
{
BattleChara* address = (BattleChara*)playerCharacter.Address;
BattleChara* address = (BattleChara*)localPlayer.Address;
return (address->Mount.Flags & 1) == 1;
}
return false;

View file

@ -59,7 +59,7 @@ internal static class SendNotification
}
}
internal sealed class Executor(IChatGui chatGui, Configuration configuration) : TaskExecutor<Task>()
internal sealed class Executor(NotificationMasterIpc notificationMasterIpc, IChatGui chatGui, Configuration configuration) : TaskExecutor<Task>()
{
protected override bool Start()
{
@ -142,6 +142,7 @@ internal static class SendNotification
XivChatEntry chat = xivChatEntry;
chatGui.Print(chat);
}
notificationMasterIpc.Notify(text2);
return true;
}

View file

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
@ -31,7 +30,7 @@ internal static class DoGather
}
}
internal sealed class GatherExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IGameGui gameGui, IObjectTable objectTable, ICondition condition, ILogger<GatherExecutor> logger) : TaskExecutor<Task>()
internal sealed class GatherExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IGameGui gameGui, IClientState clientState, ICondition condition, ILogger<GatherExecutor> logger) : TaskExecutor<Task>()
{
private bool _wasGathering;
@ -269,7 +268,7 @@ internal static class DoGather
private EAction PickAction(EAction minerAction, EAction botanistAction)
{
if ((objectTable[0] as ICharacter)?.ClassJob.RowId == 16)
if (clientState.LocalPlayer?.ClassJob.RowId == 16)
{
return minerAction;
}

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
@ -28,7 +27,7 @@ internal static class DoGatherCollectable
}
}
internal sealed class GatherCollectableExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IObjectTable objectTable, IGameGui gameGui, ILogger<GatherCollectableExecutor> logger) : TaskExecutor<Task>()
internal sealed class GatherCollectableExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IClientState clientState, IGameGui gameGui, ILogger<GatherCollectableExecutor> logger) : TaskExecutor<Task>()
{
private Queue<EAction>? _actionQueue;
@ -138,42 +137,42 @@ internal static class DoGatherCollectable
private Queue<EAction> GetNextActions(NodeCondition nodeCondition)
{
uint num = (objectTable[0] as ICharacter)?.CurrentGp ?? 0;
logger.LogTrace("Getting next actions (with {GP} GP, {MeticulousCollectability}~ meticulous, {ScourCollectability}~ scour)", num, nodeCondition.CollectabilityFromMeticulous, nodeCondition.CollectabilityFromScour);
uint currentGp = clientState.LocalPlayer.CurrentGp;
logger.LogTrace("Getting next actions (with {GP} GP, {MeticulousCollectability}~ meticulous, {ScourCollectability}~ scour)", currentGp, nodeCondition.CollectabilityFromMeticulous, nodeCondition.CollectabilityFromScour);
Queue<EAction> queue = new Queue<EAction>();
uint num2 = nodeCondition.CollectabilityToGoal(base.Task.Request.Collectability);
if (num2 <= nodeCondition.CollectabilityFromMeticulous)
uint num = nodeCondition.CollectabilityToGoal(base.Task.Request.Collectability);
if (num <= nodeCondition.CollectabilityFromMeticulous)
{
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous", num2, nodeCondition.CollectabilityFromMeticulous);
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous", num, nodeCondition.CollectabilityFromMeticulous);
queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
return queue;
}
if (num2 <= nodeCondition.CollectabilityFromScour)
if (num <= nodeCondition.CollectabilityFromScour)
{
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ scour", num2, nodeCondition.CollectabilityFromScour);
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ scour", num, nodeCondition.CollectabilityFromScour);
queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
return queue;
}
if (!nodeCondition.ScrutinyActive && num >= 200)
if (!nodeCondition.ScrutinyActive && currentGp >= 200)
{
logger.LogTrace("Still missing {NeededCollectability} collectability, scrutiny inactive", num2);
logger.LogTrace("Still missing {NeededCollectability} collectability, scrutiny inactive", num);
queue.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist));
return queue;
}
if (nodeCondition.ScrutinyActive)
{
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ meticulous", num2, nodeCondition.CollectabilityFromMeticulous);
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ meticulous", num, nodeCondition.CollectabilityFromMeticulous);
queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
return queue;
}
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ scour", num2, nodeCondition.CollectabilityFromScour);
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ scour", num, nodeCondition.CollectabilityFromScour);
queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
return queue;
}
private EAction PickAction(EAction minerAction, EAction botanistAction)
{
if ((objectTable[0] as ICharacter)?.ClassJob.RowId == 16)
if (clientState.LocalPlayer?.ClassJob.RowId == 16)
{
return minerAction;
}

View file

@ -6,6 +6,7 @@ using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.System.Input;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
@ -36,12 +37,10 @@ internal static class Dive
}
}
internal sealed class DoDive(ICondition condition, ILogger<DoDive> logger) : AbstractDelayedTaskExecutor<Task>(TimeSpan.FromSeconds(5L)), IStoppableTaskExecutor, ITaskExecutor
internal sealed class DoDive(ICondition condition, ILogger<DoDive> logger) : AbstractDelayedTaskExecutor<Task>(TimeSpan.FromSeconds(5L))
{
private readonly Queue<(uint Type, nint Key)> _keysToPress = new Queue<(uint, nint)>();
private readonly HashSet<nint> _pressedKeys = new HashSet<nint>();
private int _attempts;
protected override bool StartInternal()
@ -68,30 +67,11 @@ internal static class Dive
}
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);
if (result.Item1 == 256)
{
_pressedKeys.Add(result.Item2);
}
else if (result.Item1 == 257)
{
_pressedKeys.Remove(result.Item2);
}
return ETaskResult.StillRunning;
}
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()
{
return false;
@ -103,7 +83,7 @@ internal static class Dive
{
return ETaskResult.TaskComplete;
}
if (_attempts >= 5)
if (_attempts >= 3)
{
throw new TaskException("Please dive manually.");
}
@ -114,25 +94,18 @@ internal static class Dive
private unsafe void Descend()
{
Keybind* keybind = UIInputData.Instance()->GetKeybind(InputId.MOVE_DESCENT);
if (keybind == null)
{
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);
UIInputData.Keybind keybind = default(UIInputData.Keybind);
Utf8String* name = Utf8String.FromString("MOVE_DESCENT");
UIInputData.Instance()->GetKeybindByName(name, (Keybind*)(&keybind));
logger.LogInformation("Dive keybind: {Key1} + {Modifier1}, {Key2} + {Modifier2}", keybind.Key, keybind.Modifier, keybind.AltKey, keybind.AltModifier);
int num = 2;
List<List<nint>> list = new List<List<nint>>(num);
CollectionsMarshal.SetCount(list, num);
Span<List<nint>> span = CollectionsMarshal.AsSpan(list);
int num2 = 0;
span[num2] = GetKeysToPress(key, keyModifier);
span[num2] = GetKeysToPress(keybind.Key, keybind.Modifier);
num2++;
span[num2] = GetKeysToPress(key2, keyModifier2);
span[num2] = GetKeysToPress(keybind.AltKey, keybind.AltModifier);
List<nint> list2 = (from x in list
where x != null
select (x)).MinBy((List<nint> x) => x.Count);
@ -143,12 +116,10 @@ internal static class Dive
foreach (nint item in list2)
{
_keysToPress.Enqueue((256u, item));
for (int num3 = 0; num3 < 15; num3++)
{
_keysToPress.Enqueue((0u, 0));
_keysToPress.Enqueue((0u, 0));
}
}
for (int num4 = 0; num4 < 5; num4++)
for (int num3 = 0; num3 < 5; num3++)
{
_keysToPress.Enqueue((0u, 0));
}
@ -171,18 +142,18 @@ internal static class Dive
public static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
}
private static List<nint>? GetKeysToPress(SeVirtualKey key, KeyModifierFlag modifier)
private static List<nint>? GetKeysToPress(SeVirtualKey key, ModifierFlag modifier)
{
List<nint> list = new List<nint>();
if ((modifier & KeyModifierFlag.Ctrl) != KeyModifierFlag.None)
if (modifier.HasFlag(ModifierFlag.Ctrl))
{
list.Add(17);
}
if ((modifier & KeyModifierFlag.Shift) != KeyModifierFlag.None)
if (modifier.HasFlag(ModifierFlag.Shift))
{
list.Add(16);
}
if ((modifier & KeyModifierFlag.Alt) != KeyModifierFlag.None)
if (modifier.HasFlag(ModifierFlag.Alt))
{
list.Add(18);
}

View file

@ -3,9 +3,7 @@ using System.Collections.Generic;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
using LLib.Gear;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Shared;
using Questionable.Controller.Utils;
@ -19,7 +17,7 @@ namespace Questionable.Controller.Steps.Interactions;
internal static class Duty
{
internal sealed class Factory(AutoDutyIpc autoDutyIpc, Configuration configuration) : ITaskFactory
internal sealed class Factory(AutoDutyIpc autoDutyIpc) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -28,95 +26,37 @@ internal static class Duty
yield break;
}
ArgumentNullException.ThrowIfNull(step.DutyOptions, "step.DutyOptions");
uint contentFinderConditionId;
int dutyMode;
if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions))
{
AutoDutyIpc.DutyMode dutyMode = GetDutyMode(step.DutyOptions.ContentFinderConditionId, step.DutyOptions.DutyMode);
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)))
contentFinderConditionId = step.DutyOptions.ContentFinderConditionId;
ElementId id = quest.Id;
if (id is QuestId)
{
yield return new WaitForPartyTask();
ushort value = id.Value;
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);
if (!QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
{
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
@ -252,124 +192,4 @@ internal static class Duty
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;
}
}
}

View file

@ -161,6 +161,7 @@ internal static class EquipItem
{
return new List<ushort>();
}
Span<ushort> span;
switch (item.Value.EquipSlotCategory.RowId)
{
case 1u:
@ -178,9 +179,9 @@ internal static class EquipItem
int index = 1;
List<ushort> list4 = new List<ushort>(index);
CollectionsMarshal.SetCount(list4, index);
Span<ushort> span4 = CollectionsMarshal.AsSpan(list4);
span = CollectionsMarshal.AsSpan(list4);
int num = 0;
span4[num] = (ushort)(item.Value.EquipSlotCategory.RowId - 1);
span[num] = (ushort)(item.Value.EquipSlotCategory.RowId - 1);
return list4;
}
case 12u:
@ -188,11 +189,11 @@ internal static class EquipItem
int num = 2;
List<ushort> list3 = new List<ushort>(num);
CollectionsMarshal.SetCount(list3, num);
Span<ushort> span3 = CollectionsMarshal.AsSpan(list3);
span = CollectionsMarshal.AsSpan(list3);
int index = 0;
span3[index] = 11;
span[index] = 11;
index++;
span3[index] = 12;
span[index] = 12;
return list3;
}
case 13u:
@ -200,9 +201,9 @@ internal static class EquipItem
int index = 1;
List<ushort> list2 = new List<ushort>(index);
CollectionsMarshal.SetCount(list2, index);
Span<ushort> span2 = CollectionsMarshal.AsSpan(list2);
span = CollectionsMarshal.AsSpan(list2);
int num = 0;
span2[num] = 0;
span[num] = 0;
return list2;
}
case 17u:
@ -210,7 +211,7 @@ internal static class EquipItem
int num = 1;
List<ushort> list = new List<ushort>(num);
CollectionsMarshal.SetCount(list, num);
Span<ushort> span = CollectionsMarshal.AsSpan(list);
span = CollectionsMarshal.AsSpan(list);
int index = 0;
span[index] = 13;
return list;

View file

@ -1,6 +1,5 @@
using System;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
@ -44,7 +43,7 @@ internal static class EquipRecommended
}
}
internal sealed class DoEquipRecommended(IObjectTable objectTable, IChatGui chatGui, ICondition condition) : TaskExecutor<EquipTask>()
internal sealed class DoEquipRecommended(IClientState clientState, IChatGui chatGui, ICondition condition) : TaskExecutor<EquipTask>()
{
private bool _checkedOrTriggeredEquipmentUpdate;
@ -56,11 +55,7 @@ internal static class EquipRecommended
{
return false;
}
if (!(objectTable[0] is ICharacter character))
{
return false;
}
RecommendEquipModule.Instance()->SetupForClassJob((byte)character.ClassJob.RowId);
RecommendEquipModule.Instance()->SetupForClassJob((byte)clientState.LocalPlayer.ClassJob.RowId);
return true;
}

View file

@ -173,10 +173,6 @@ internal static class Interact
protected override bool Start()
{
InteractionType = base.Task.InteractionType;
_interactionState = EInteractionState.None;
_needsUnmount = false;
delayedFinalCheck = false;
_continueAt = DateTime.MinValue;
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId);
if (gameObject == null)
{
@ -263,7 +259,6 @@ internal static class Interact
{
if (base.ProgressContext.WasInterrupted())
{
logger.LogDebug("Interaction with {DataId} was interrupted", base.Task.DataId);
return ETaskResult.StillRunning;
}
if (base.ProgressContext.WasSuccessful() || _interactionState == EInteractionState.InteractionConfirmed)

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.Logging;
@ -48,17 +47,12 @@ internal static class Jump
}
}
internal abstract class JumpBase<T>(MovementController movementController, IObjectTable objectTable, IFramework framework) : TaskExecutor<T>() where T : class, IJumpTask
internal abstract class JumpBase<T>(MovementController movementController, IClientState clientState, IFramework framework) : TaskExecutor<T>() where T : class, IJumpTask
{
protected unsafe override bool Start()
{
IGameObject gameObject = _003CobjectTable_003EP[0];
if (gameObject == null)
{
return false;
}
float num = base.Task.JumpDestination.CalculateStopDistance();
if ((gameObject.Position - base.Task.JumpDestination.Position).Length() <= num)
if ((_003CclientState_003EP.LocalPlayer.Position - base.Task.JumpDestination.Position).Length() <= num)
{
return false;
}
@ -100,8 +94,8 @@ internal static class Jump
internal sealed class DoSingleJump : JumpBase<SingleJumpTask>
{
public DoSingleJump(MovementController movementController, IObjectTable objectTable, IFramework framework)
: base(movementController, objectTable, framework)
public DoSingleJump(MovementController movementController, IClientState clientState, IFramework framework)
: base(movementController, clientState, framework)
{
}
}
@ -116,19 +110,19 @@ internal static class Jump
internal sealed class DoRepeatedJumps : JumpBase<RepeatedJumpTask>
{
private readonly IObjectTable _objectTable;
private readonly IClientState _clientState;
private DateTime _continueAt;
private int _attempts;
public DoRepeatedJumps(MovementController movementController, IObjectTable objectTable, IFramework framework, ICondition condition, ILogger<DoRepeatedJumps> logger)
public DoRepeatedJumps(MovementController movementController, IClientState clientState, IFramework framework, ICondition condition, ILogger<DoRepeatedJumps> logger)
{
_003Ccondition_003EP = condition;
_003Clogger_003EP = logger;
_objectTable = objectTable;
_clientState = clientState;
_continueAt = DateTime.MinValue;
base._002Ector(movementController, objectTable, framework);
base._002Ector(movementController, clientState, framework);
}
protected override bool Start()
@ -143,17 +137,12 @@ internal static class Jump
{
return ETaskResult.StillRunning;
}
IGameObject gameObject = _objectTable[0];
if (gameObject == null)
{
return ETaskResult.TaskComplete;
}
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)
if ((_clientState.LocalPlayer.Position - base.Task.JumpDestination.Position).Length() <= num || _clientState.LocalPlayer.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);
_003Clogger_003EP.LogTrace("Y-Heights for jumps: player={A}, target={B}", _clientState.LocalPlayer.Position.Y, base.Task.JumpDestination.Position.Y - 0.5f);
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null))
{
_attempts++;

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
@ -28,7 +29,7 @@ internal static class SinglePlayerDuty
public const ushort Naadam = 688;
}
internal sealed class Factory(BossModIpc bossModIpc, TerritoryData territoryData, ICondition condition, IClientState clientState, IObjectTable objectTable) : ITaskFactory
internal sealed class Factory(BossModIpc bossModIpc, TerritoryData territoryData, ICondition condition, IClientState clientState) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -69,7 +70,7 @@ internal static class SinglePlayerDuty
{
return true;
}
Vector3 vector = objectTable[0]?.Position ?? default(Vector3);
Vector3 vector = clientState.LocalPlayer?.Position ?? default(Vector3);
return (new Vector3(352.01f, -1.45f, 288.59f) - vector).Length() < 10f;
}, "Wait(moving to Ovoo)");
yield return new Mount.UnmountTask();

View file

@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging;
namespace Questionable.Controller.Steps.Movement;
internal sealed class LandExecutor(IObjectTable objectTable, ICondition condition, ILogger<LandExecutor> logger) : TaskExecutor<LandTask>()
internal sealed class LandExecutor(IClientState clientState, ICondition condition, ILogger<LandExecutor> logger) : TaskExecutor<LandTask>()
{
private bool _landing;
@ -45,7 +45,7 @@ internal sealed class LandExecutor(IObjectTable objectTable, ICondition conditio
private unsafe bool AttemptLanding()
{
Character* ptr = (Character*)(objectTable[0]?.Address ?? 0);
Character* ptr = (Character*)(clientState.LocalPlayer?.Address ?? 0);
if (ptr != null && ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
{
logger.LogInformation("Attempting to land");

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using LLib;
@ -29,8 +28,6 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly ICondition _condition;
private readonly Questionable.Controller.Steps.Common.Mount.MountEvaluator _mountEvaluator;
@ -49,13 +46,12 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
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, IObjectTable objectTable, ICondition condition, IDataManager dataManager, Questionable.Controller.Steps.Common.Mount.MountEvaluator mountEvaluator, IServiceProvider serviceProvider)
public MoveExecutor(MovementController movementController, GameFunctions gameFunctions, ILogger<MoveExecutor> logger, IClientState clientState, ICondition condition, IDataManager dataManager, Questionable.Controller.Steps.Common.Mount.MountEvaluator mountEvaluator, IServiceProvider serviceProvider)
{
_movementController = movementController;
_gameFunctions = gameFunctions;
_logger = logger;
_clientState = clientState;
_objectTable = objectTable;
_condition = condition;
_serviceProvider = serviceProvider;
_mountEvaluator = mountEvaluator;
@ -99,7 +95,7 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
_canRestart = base.Task.RestartNavigation;
_destination = base.Task.Destination;
float num = base.Task.StopDistance ?? 3f;
Vector3? vector = _objectTable[0]?.Position;
Vector3? vector = _clientState.LocalPlayer?.Position;
float num2 = ((!vector.HasValue) ? float.MaxValue : Vector3.Distance(vector.Value, _destination));
if (num2 > num)
{
@ -174,8 +170,7 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
{
return ETaskResult.StillRunning;
}
IGameObject gameObject = _objectTable[0];
if (_canRestart && gameObject != null && Vector3.Distance(gameObject.Position, _destination) > (base.Task.StopDistance ?? 3f) + 5f)
if (_canRestart && Vector3.Distance(_clientState.LocalPlayer.Position, _destination) > (base.Task.StopDistance ?? 3f) + 5f)
{
_canRestart = false;
if (_clientState.TerritoryType == base.Task.TerritoryId)

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
@ -14,7 +13,7 @@ namespace Questionable.Controller.Steps.Movement;
internal static class MoveTo
{
internal sealed class Factory(IClientState clientState, IObjectTable objectTable, AetheryteData aetheryteData, TerritoryData territoryData, ILogger<Factory> logger) : ITaskFactory
internal sealed class Factory(IClientState clientState, AetheryteData aetheryteData, TerritoryData territoryData, ILogger<Factory> logger) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -63,8 +62,7 @@ internal static class MoveTo
private IEnumerable<ITask> CreateMoveTasks(QuestStep step, Vector3 destination)
{
IGameObject gameObject = objectTable[0];
if (step.InteractionType == EInteractionType.Jump && step.JumpDestination != null && gameObject != null && (gameObject.Position - step.JumpDestination.Position).Length() <= (step.JumpDestination.StopDistance ?? 1f))
if (step.InteractionType == EInteractionType.Jump && step.JumpDestination != null && (clientState.LocalPlayer.Position - step.JumpDestination.Position).Length() <= (step.JumpDestination.StopDistance ?? 1f))
{
logger.LogInformation("We're at the jump destination, skipping movement");
yield break;

View file

@ -4,7 +4,7 @@ using Questionable.Functions;
namespace Questionable.Controller.Steps.Movement;
internal sealed class WaitForNearDataIdExecutor(GameFunctions gameFunctions, IObjectTable objectTable) : TaskExecutor<WaitForNearDataId>()
internal sealed class WaitForNearDataIdExecutor(GameFunctions gameFunctions, IClientState clientState) : TaskExecutor<WaitForNearDataId>()
{
protected override bool Start()
{
@ -14,8 +14,7 @@ internal sealed class WaitForNearDataIdExecutor(GameFunctions gameFunctions, IOb
public override ETaskResult Update()
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId);
IGameObject gameObject2 = objectTable[0];
if (gameObject == null || gameObject2 == null || (gameObject.Position - gameObject2.Position).Length() > base.Task.StopDistance)
if (gameObject == null || (gameObject.Position - clientState.LocalPlayer.Position).Length() > base.Task.StopDistance)
{
throw new TaskException("Object not found or too far away, no position so we can't move");
}

View file

@ -4,7 +4,6 @@ using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
@ -51,7 +50,7 @@ internal static class AethernetShortcut
}
}
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>()
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>()
{
private bool _moving;
@ -99,12 +98,7 @@ internal static class AethernetShortcut
if (aetheryteFunctions.IsAetheryteUnlocked(base.Task.From) && aetheryteFunctions.IsAetheryteUnlocked(base.Task.To))
{
ushort territoryType = clientState.TerritoryType;
IGameObject gameObject = objectTable[0];
if (gameObject == null)
{
return false;
}
Vector3 playerPosition = gameObject.Position;
Vector3 playerPosition = clientState.LocalPlayer.Position;
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))
@ -208,7 +202,7 @@ internal static class AethernetShortcut
DoTeleport();
return ETaskResult.StillRunning;
}
Vector3? vector = objectTable[0]?.Position;
Vector3? vector = clientState.LocalPlayer?.Position;
if (!vector.HasValue)
{
return ETaskResult.StillRunning;

View file

@ -4,7 +4,6 @@ using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
@ -46,7 +45,7 @@ internal static class AetheryteShortcut
}
}
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>()
internal sealed class UseAetheryteShortcut(ILogger<UseAetheryteShortcut> logger, AetheryteFunctions aetheryteFunctions, QuestFunctions questFunctions, IClientState clientState, IChatGui chatGui, ICondition condition, AetheryteData aetheryteData, ExtraConditionUtils extraConditionUtils) : TaskExecutor<Task>()
{
private bool _teleported;
@ -117,15 +116,14 @@ internal static class AetheryteShortcut
return true;
}
}
IGameObject gameObject = objectTable[0];
NearPositionCondition nearPosition = skipAetheryteCondition.NearPosition;
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && gameObject != null && Vector3.Distance(nearPosition.Position, gameObject.Position) <= nearPosition.MaximumDistance)
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && Vector3.Distance(nearPosition.Position, clientState.LocalPlayer.Position) <= nearPosition.MaximumDistance)
{
logger.LogInformation("Skipping aetheryte shortcut, as we're near the position");
return true;
}
NearPositionCondition notNearPosition = skipAetheryteCondition.NotNearPosition;
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && gameObject != null && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, gameObject.Position))
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, clientState.LocalPlayer.Position))
{
logger.LogInformation("Skipping aetheryte shortcut, as we're not near the position");
return true;
@ -136,17 +134,14 @@ internal static class AetheryteShortcut
return true;
}
}
if (base.Task.ExpectedTerritoryId == territoryType)
{
IGameObject gameObject2 = objectTable[0];
if (gameObject2 != null && !skipAetheryteCondition.Never)
if (base.Task.ExpectedTerritoryId == territoryType && !skipAetheryteCondition.Never)
{
if (skipAetheryteCondition != null && skipAetheryteCondition.InSameTerritory)
{
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (InSameTerritory)");
return true;
}
Vector3 position = gameObject2.Position;
Vector3 position = clientState.LocalPlayer.Position;
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");
@ -159,7 +154,6 @@ internal static class AetheryteShortcut
}
}
}
}
return false;
}
@ -214,7 +208,7 @@ internal static class AetheryteShortcut
}
}
internal sealed class MoveAwayFromAetheryteExecutor(MoveExecutor moveExecutor, AetheryteData aetheryteData, IClientState clientState, IObjectTable objectTable) : TaskExecutor<MoveAwayFromAetheryte>()
internal sealed class MoveAwayFromAetheryteExecutor(MoveExecutor moveExecutor, AetheryteData aetheryteData, IClientState clientState) : TaskExecutor<MoveAwayFromAetheryte>()
{
private static readonly Dictionary<EAetheryteLocation, List<Vector3>> AetherytesToMoveFrom;
@ -225,12 +219,7 @@ internal static class AetheryteShortcut
protected override bool Start()
{
IGameObject gameObject = objectTable[0];
if (gameObject == null)
{
return false;
}
Vector3 playerPosition = gameObject.Position;
Vector3 playerPosition = clientState.LocalPlayer.Position;
if (aetheryteData.CalculateDistance(playerPosition, clientState.TerritoryType, base.Task.TargetAetheryte) >= 20f)
{
return false;

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
@ -31,26 +30,26 @@ internal static class Craft
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2]
{
new Questionable.Controller.Steps.Common.Mount.UnmountTask(),
new CraftTask(step.ItemId.Value, step.ItemCount.Value, step.RequireHq == true)
new CraftTask(step.ItemId.Value, step.ItemCount.Value)
});
}
}
internal sealed record CraftTask(uint ItemId, int ItemCount, bool RequireHq) : ITask
internal sealed record CraftTask(uint ItemId, int ItemCount) : ITask
{
public override string ToString()
{
return $"Craft {ItemCount}x {ItemId}{(RequireHq ? " (HQ)" : "")} (with Artisan)";
return $"Craft {ItemCount}x {ItemId} (with Artisan)";
}
}
internal sealed class DoCraft(IDataManager dataManager, IObjectTable objectTable, ArtisanIpc artisanIpc, ILogger<DoCraft> logger) : TaskExecutor<CraftTask>()
internal sealed class DoCraft(IDataManager dataManager, IClientState clientState, ArtisanIpc artisanIpc, ILogger<DoCraft> logger) : TaskExecutor<CraftTask>()
{
protected override bool Start()
{
if (HasRequestedItems())
{
logger.LogInformation("Already own {ItemCount}x {ItemId}{HqSuffix}", base.Task.ItemCount, base.Task.ItemId, base.Task.RequireHq ? " (HQ)" : "");
logger.LogInformation("Already own {ItemCount}x {ItemId}", base.Task.ItemCount, base.Task.ItemId);
return false;
}
RecipeLookup? rowOrDefault = dataManager.GetExcelSheet<RecipeLookup>().GetRowOrDefault(base.Task.ItemId);
@ -58,7 +57,7 @@ internal static class Craft
{
throw new TaskException($"Item {base.Task.ItemId} is not craftable");
}
uint num = (EClassJob?)(objectTable[0] as ICharacter)?.ClassJob.RowId switch
uint num = (EClassJob)clientState.LocalPlayer.ClassJob.RowId switch
{
EClassJob.Carpenter => rowOrDefault.Value.CRP.RowId,
EClassJob.Blacksmith => rowOrDefault.Value.BSM.RowId,
@ -89,7 +88,7 @@ internal static class Craft
throw new TaskException($"Unable to determine recipe for item {base.Task.ItemId}");
}
int num2 = base.Task.ItemCount - GetOwnedItemCount();
logger.LogInformation("Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items{HqSuffix}", base.Task.ItemId, num, num2, base.Task.RequireHq ? " (HQ required)" : "");
logger.LogInformation("Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items", base.Task.ItemId, num, num2);
if (!artisanIpc.CraftItem((ushort)num, num2))
{
throw new TaskException($"Failed to start Artisan craft for recipe {num}");
@ -129,16 +128,7 @@ internal static class Craft
private unsafe int GetOwnedItemCount()
{
InventoryManager* ptr = InventoryManager.Instance();
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;
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);
}
public override bool ShouldInterruptOnDamage()

View file

@ -9,17 +9,14 @@ internal sealed class ExtraConditionUtils
{
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
public ExtraConditionUtils(IClientState clientState, IObjectTable objectTable)
public ExtraConditionUtils(IClientState clientState)
{
_clientState = clientState;
_objectTable = objectTable;
}
public bool MatchesExtraCondition(EExtraSkipCondition skipCondition)
{
Vector3? vector = _objectTable[0]?.Position;
Vector3? vector = _clientState.LocalPlayer?.Position;
if (vector.HasValue && _clientState.TerritoryType != 0)
{
return MatchesExtraCondition(skipCondition, vector.Value, _clientState.TerritoryType);

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
@ -43,7 +42,7 @@ internal static class Gather
}
}
internal sealed class DelayedGatheringExecutor(GatheringPointRegistry gatheringPointRegistry, TerritoryData territoryData, IClientState clientState, IObjectTable objectTable, IServiceProvider serviceProvider, ILogger<DelayedGatheringExecutor> logger) : TaskExecutor<DelayedGatheringTask>(), IExtraTaskCreator, ITaskExecutor
internal sealed class DelayedGatheringExecutor(GatheringPointRegistry gatheringPointRegistry, TerritoryData territoryData, IClientState clientState, IServiceProvider serviceProvider, ILogger<DelayedGatheringExecutor> logger) : TaskExecutor<DelayedGatheringTask>(), IExtraTaskCreator, ITaskExecutor
{
protected override bool Start()
{
@ -57,8 +56,8 @@ internal static class Gather
public IEnumerable<ITask> CreateExtraTasks()
{
EClassJob valueOrDefault = ((EClassJob?)(objectTable[0] as ICharacter)?.ClassJob.RowId).GetValueOrDefault();
if (!gatheringPointRegistry.TryGetGatheringPointId(base.Task.GatheredItem.ItemId, valueOrDefault, out GatheringPointId gatheringPointId))
EClassJob rowId = (EClassJob)clientState.LocalPlayer.ClassJob.RowId;
if (!gatheringPointRegistry.TryGetGatheringPointId(base.Task.GatheredItem.ItemId, rowId, out GatheringPointId gatheringPointId))
{
throw new TaskException($"No gathering point found for item {base.Task.GatheredItem.ItemId}");
}
@ -70,7 +69,7 @@ internal static class Gather
{
yield break;
}
switch (valueOrDefault)
switch (rowId)
{
case EClassJob.Miner:
yield return new Questionable.Controller.Steps.Interactions.Action.TriggerStatusIfMissing(EStatus.Prospect, EAction.Prospect);

View file

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
@ -46,18 +45,14 @@ internal static class SkipCondition
}
}
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>()
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>()
{
protected override bool Start()
{
SkipStepConditions skipConditions = base.Task.SkipConditions;
QuestStep step = base.Task.Step;
ElementId elementId = base.Task.ElementId;
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);
logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions));
if (CheckFlyingCondition(step, skipConditions))
{
return true;
@ -205,10 +200,9 @@ internal static class SkipCondition
if (skipConditions.NotTargetable && step != null && step.DataId.HasValue)
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
IGameObject gameObject2 = objectTable[0];
if (gameObject == null)
{
if (gameObject2 != null && (step.Position.GetValueOrDefault() - gameObject2.Position).Length() < 100f)
if ((step.Position.GetValueOrDefault() - clientState.LocalPlayer.Position).Length() < 100f)
{
logger.LogInformation("Skipping step, object is not nearby (but we are)");
return true;
@ -253,22 +247,6 @@ internal static class SkipCondition
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;
}
@ -346,10 +324,9 @@ internal static class SkipCondition
private bool CheckLevelCondition(SkipStepConditions skipConditions)
{
ICharacter character = objectTable[0] as ICharacter;
if (skipConditions.MinimumLevel.HasValue && character != null && character.Level >= skipConditions.MinimumLevel.Value)
if (skipConditions.MinimumLevel.HasValue && clientState.LocalPlayer != null && clientState.LocalPlayer.Level >= skipConditions.MinimumLevel.Value)
{
logger.LogInformation("Skipping step, as player level {CurrentLevel} >= minimum level {MinLevel}", character.Level, skipConditions.MinimumLevel.Value);
logger.LogInformation("Skipping step, as player level {CurrentLevel} >= minimum level {MinLevel}", clientState.LocalPlayer.Level, skipConditions.MinimumLevel.Value);
return true;
}
return false;
@ -414,9 +391,9 @@ internal static class SkipCondition
if (requiredCurrentJob != null && requiredCurrentJob.Count > 0)
{
List<EClassJob> list = step.RequiredCurrentJob.SelectMany((EExtendedClassJob x) => classJobUtils.AsIndividualJobs(x, elementId)).ToList();
EClassJob valueOrDefault = ((EClassJob?)(objectTable[0] as ICharacter)?.ClassJob.RowId).GetValueOrDefault();
logger.LogInformation("Checking current job {CurrentJob} against {ExpectedJobs}", valueOrDefault, string.Join(",", list));
if (!list.Contains(valueOrDefault))
EClassJob rowId = (EClassJob)clientState.LocalPlayer.ClassJob.RowId;
logger.LogInformation("Checking current job {CurrentJob} against {ExpectedJobs}", rowId, string.Join(",", list));
if (!list.Contains(rowId))
{
logger.LogInformation("Skipping step, as step requires a different job");
return true;
@ -428,15 +405,14 @@ internal static class SkipCondition
private bool CheckPositionCondition(SkipStepConditions skipConditions)
{
IGameObject gameObject = objectTable[0];
NearPositionCondition nearPosition = skipConditions.NearPosition;
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && gameObject != null && Vector3.Distance(nearPosition.Position, gameObject.Position) <= nearPosition.MaximumDistance)
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && Vector3.Distance(nearPosition.Position, clientState.LocalPlayer.Position) <= nearPosition.MaximumDistance)
{
logger.LogInformation("Skipping step, as we're near the position");
return true;
}
NearPositionCondition notNearPosition = skipConditions.NotNearPosition;
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && gameObject != null && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, gameObject.Position))
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, clientState.LocalPlayer.Position))
{
logger.LogInformation("Skipping step, as we're not near the position");
return true;
@ -485,39 +461,6 @@ internal static class SkipCondition
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()
{
return ETaskResult.SkipRemainingTasksForStep;

View file

@ -1,5 +1,4 @@
using System.Linq;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using LLib.GameData;
@ -32,11 +31,11 @@ internal static class SwitchClassJob
}
}
internal sealed class SwitchClassJobExecutor(IObjectTable objectTable) : AbstractDelayedTaskExecutor<Task>()
internal sealed class SwitchClassJobExecutor(IClientState clientState) : AbstractDelayedTaskExecutor<Task>()
{
protected unsafe override bool StartInternal()
{
if ((objectTable[0] as ICharacter)?.ClassJob.RowId == (uint?)base.Task.ClassJob)
if (clientState.LocalPlayer.ClassJob.RowId == (uint)base.Task.ClassJob)
{
return false;
}

View file

@ -17,7 +17,7 @@ namespace Questionable.Controller.Steps.Shared;
internal static class WaitAtEnd
{
internal sealed class Factory(IClientState clientState, IObjectTable objectTable, ICondition condition, TerritoryData territoryData, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc) : ITaskFactory
internal sealed class Factory(IClientState clientState, ICondition condition, TerritoryData territoryData, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -129,10 +129,10 @@ internal static class WaitAtEnd
}
else
{
Vector3 lastPosition = step.Position ?? objectTable[0]?.Position ?? Vector3.Zero;
Vector3 lastPosition = step.Position ?? clientState.LocalPlayer?.Position ?? Vector3.Zero;
task = new WaitCondition.Task(delegate
{
Vector3? vector = objectTable[0]?.Position;
Vector3? vector = clientState.LocalPlayer?.Position;
return vector.HasValue && (lastPosition - vector.Value).Length() > 2f;
}, "Wait(tp away from " + lastPosition.ToString("G", CultureInfo.InvariantCulture) + ")");
}
@ -196,40 +196,15 @@ internal static class WaitAtEnd
}
}
internal sealed class WaitNextStepOrSequenceExecutor(QuestController questController) : TaskExecutor<WaitNextStepOrSequence>()
internal sealed class WaitNextStepOrSequenceExecutor : TaskExecutor<WaitNextStepOrSequence>
{
private ElementId? _questId;
private byte _initialSequence;
private int _initialStep;
protected override bool Start()
{
QuestController.QuestProgress currentQuest = questController.CurrentQuest;
if (currentQuest != null)
{
_questId = currentQuest.Quest.Id;
_initialSequence = currentQuest.Sequence;
_initialStep = currentQuest.Step;
}
return true;
}
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;
}

View file

@ -53,9 +53,9 @@ internal sealed class TaskCreator
int index = 1;
List<ITask> list3 = new List<ITask>(index);
CollectionsMarshal.SetCount(list3, index);
Span<ITask> span2 = CollectionsMarshal.AsSpan(list3);
Span<ITask> span = CollectionsMarshal.AsSpan(list3);
int num = 0;
span2[num] = new WaitAtEnd.WaitNextStepOrSequence();
span[num] = new WaitAtEnd.WaitNextStepOrSequence();
list2 = list3;
}
else

View file

@ -33,7 +33,6 @@ internal abstract class TaskExecutor<T> : ITaskExecutor where T : class, ITask
if (task is T task2)
{
Task = task2;
ProgressContext = null;
return Start();
}
throw new TaskException($"Unable to cast {task.GetType()} to {typeof(T)}");

View file

@ -4,32 +4,24 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
using LLib.GameData;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Model.Questing;
namespace Questionable.Controller.Utils;
internal sealed class PartyWatchdog : IDisposable
internal sealed class PartyWatchDog : IDisposable
{
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly TerritoryData _territoryData;
private readonly IClientState _clientState;
private readonly IChatGui _chatGui;
private readonly ILogger<PartyWatchdog> _logger;
private readonly ILogger<PartyWatchDog> _logger;
private ushort? _uncheckedTeritoryId;
public PartyWatchdog(QuestController questController, Configuration configuration, TerritoryData territoryData, IClientState clientState, IChatGui chatGui, ILogger<PartyWatchdog> logger)
public PartyWatchDog(QuestController questController, IClientState clientState, IChatGui chatGui, ILogger<PartyWatchDog> logger)
{
_questController = questController;
_configuration = configuration;
_territoryData = territoryData;
_clientState = clientState;
_chatGui = chatGui;
_logger = logger;
@ -37,8 +29,6 @@ internal sealed class PartyWatchdog : IDisposable
}
private unsafe void TerritoryChanged(ushort newTerritoryId)
{
if (!_configuration.Advanced.DisablePartyWatchdog)
{
switch ((ETerritoryIntendedUse)GameMain.Instance()->CurrentTerritoryIntendedUseId)
{
@ -107,47 +97,25 @@ internal sealed class PartyWatchdog : IDisposable
break;
}
}
}
public unsafe void Update()
{
if (_configuration.Advanced.DisablePartyWatchdog || _uncheckedTeritoryId != _clientState.TerritoryType || GameMain.Instance()->TerritoryLoadState != 2)
if (_uncheckedTeritoryId != _clientState.TerritoryType || GameMain.Instance()->TerritoryLoadState != 2)
{
return;
}
GroupManager* ptr = GroupManager.Instance();
if (ptr == null)
if (ptr != null)
{
return;
}
byte memberCount = ptr->MainGroup.MemberCount;
bool isAlliance = ptr->MainGroup.IsAlliance;
_logger.LogDebug("Territory {TerritoryId} with {MemberCount} members, alliance: {IsInAlliance}", _uncheckedTeritoryId, memberCount, isAlliance);
if (memberCount > 1 || isAlliance)
{
if (!IsDutyConfiguredForParty(_uncheckedTeritoryId.Value))
{
StopIfRunning("Other party members present");
}
else
{
_logger.LogInformation("Party detected but duty is configured for Unsync (Party) mode, continuing");
}
}
_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)

View file

@ -4,7 +4,9 @@ using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
@ -321,13 +323,12 @@ internal sealed class CombatController : IDisposable
}
}
}
IGameObject localPlayer = _objectTable[0];
IGameObject gameObject = (from x in _objectTable
select new
{
GameObject = x,
Priority = GetKillPriority(x).Priority,
Distance = Vector3.Distance(x.Position, localPlayer?.Position ?? Vector3.Zero)
Distance = Vector3.Distance(x.Position, _clientState.LocalPlayer.Position)
} into x
where x.Priority > 0
orderby x.Priority descending, x.Distance
@ -354,8 +355,7 @@ internal sealed class CombatController : IDisposable
}
if (gameObject is IBattleNpc battleNpc && battleNpc.StatusFlags.HasFlag(StatusFlags.InCombat))
{
IGameObject? gameObject2 = _objectTable[0];
if (gameObject.TargetObjectId == gameObject2?.GameObjectId)
if (gameObject.TargetObjectId == _clientState.LocalPlayer?.GameObjectId)
{
return (Priority: num.Value + 150, Reason: text + "/Targeted");
}
@ -393,12 +393,11 @@ internal sealed class CombatController : IDisposable
}
List<ComplexCombatData> complexCombatDatas = _currentFight.Data.ComplexCombatDatas;
GameObject* address = (GameObject*)gameObject.Address;
IGameObject gameObject2 = _objectTable[0];
if (address->FateId != 0 && _currentFight.Data.SpawnType != EEnemySpawnType.FateEnemies && gameObject.TargetObjectId != gameObject2?.GameObjectId)
if (address->FateId != 0 && _currentFight.Data.SpawnType != EEnemySpawnType.FateEnemies && gameObject.TargetObjectId != _clientState.LocalPlayer?.GameObjectId)
{
return (Priority: null, Reason: "FATE mob");
}
Vector3 value = gameObject2?.Position ?? Vector3.Zero;
Vector3 value = _clientState.LocalPlayer?.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;
if (complexCombatDatas.Count > 0)
{
@ -431,7 +430,6 @@ internal sealed class CombatController : IDisposable
private void SetTarget(IGameObject? target)
{
IGameObject gameObject = _objectTable[0];
if (target == null)
{
if (_targetManager.Target != null)
@ -440,9 +438,9 @@ internal sealed class CombatController : IDisposable
_targetManager.Target = null;
}
}
else if (gameObject != null && Vector3.Distance(gameObject.Position, target.Position) > 55f)
else if (Vector3.Distance(_clientState.LocalPlayer.Position, target.Position) > 55f)
{
_logger.LogInformation("Moving to target, distance: {Distance:N2}", Vector3.Distance(gameObject.Position, target.Position));
_logger.LogInformation("Moving to target, distance: {Distance:N2}", Vector3.Distance(_clientState.LocalPlayer.Position, target.Position));
MoveToTarget(target);
}
else
@ -470,45 +468,39 @@ internal sealed class CombatController : IDisposable
private void MoveToTarget(IGameObject gameObject)
{
IGameObject gameObject2 = _objectTable[0];
if (gameObject2 == null)
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
if (localPlayer == null)
{
return;
}
float num = gameObject2.HitboxRadius + gameObject.HitboxRadius;
float num2 = Vector3.Distance(gameObject2.Position, gameObject.Position);
ICharacter character = gameObject2 as ICharacter;
bool flag = character != null;
bool flag2;
if (flag)
{
byte? b = character.ClassJob.ValueNullable?.Role;
float num = localPlayer.HitboxRadius + gameObject.HitboxRadius;
float num2 = Vector3.Distance(localPlayer.Position, gameObject.Position);
byte? b = localPlayer.ClassJob.ValueNullable?.Role;
bool flag;
if (b.HasValue)
{
byte valueOrDefault = b.GetValueOrDefault();
if ((uint)(valueOrDefault - 3) <= 1u)
{
flag2 = true;
goto IL_00a3;
flag = true;
goto IL_008e;
}
}
flag2 = false;
goto IL_00a3;
}
goto IL_00a7;
IL_00a7:
flag = false;
goto IL_008e;
IL_008e:
float num3 = (flag ? 20f : 2.9f);
bool flag3 = num2 - num >= num3;
bool flag4 = IsInLineOfSight(gameObject);
if (flag3 || !flag4)
bool flag2 = num2 - num >= num3;
bool flag3 = IsInLineOfSight(gameObject);
if (flag2 || !flag3)
{
bool flag5 = num2 - num > 5f;
if (!flag3 && !flag4)
bool flag4 = num2 - num > 5f;
if (!flag2 && !flag3)
{
num3 = Math.Min(num3, num2) / 2f;
flag5 = true;
flag4 = true;
}
if (!flag5)
if (!flag4)
{
_logger.LogInformation("Moving to {TargetName} ({BaseId}) to attack", gameObject.Name, gameObject.BaseId);
MovementController movementController = _movementController;
@ -526,20 +518,11 @@ internal sealed class CombatController : IDisposable
_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)
{
IGameObject gameObject = _objectTable[0];
if (gameObject == null)
{
return false;
}
Vector3 position = gameObject.Position;
Vector3 position = _clientState.LocalPlayer.Position;
position.Y += 2f;
Vector3 position2 = target.Position;
position2.Y += 2f;

View file

@ -1,7 +1,20 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command;
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.Model;
using Questionable.Model.Common;
using Questionable.Model.Questing;
using Questionable.Model.Questing.Converter;
using Questionable.Windows;
namespace Questionable.Controller;
@ -20,59 +33,67 @@ internal sealed class CommandHandler : IDisposable
private readonly MovementController _movementController;
private readonly QuestRegistry _questRegistry;
private readonly ConfigWindow _configWindow;
private readonly DebugOverlay _debugOverlay;
private readonly OneTimeSetupWindow _oneTimeSetupWindow;
private readonly QuestWindow _questWindow;
private readonly QuestSelectionWindow _questSelectionWindow;
private readonly QuestSequenceWindow _questSequenceWindow;
private readonly JournalProgressWindow _journalProgressWindow;
private readonly PriorityWindow _priorityWindow;
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 IObjectTable _objectTable;
private readonly Configuration _configuration;
private readonly ChangelogWindow _changelogWindow;
private IReadOnlyList<uint> _previouslyUnlockedUnlockLinks = Array.Empty<uint>();
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)
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)
{
_commandManager = commandManager;
_chatGui = chatGui;
_questController = questController;
_movementController = movementController;
_questRegistry = questRegistry;
_configWindow = configWindow;
_debugOverlay = debugOverlay;
_oneTimeSetupWindow = oneTimeSetupWindow;
_questWindow = questWindow;
_questSelectionWindow = questSelectionWindow;
_questSequenceWindow = questSequenceWindow;
_journalProgressWindow = journalProgressWindow;
_priorityWindow = priorityWindow;
_targetManager = targetManager;
_questFunctions = questFunctions;
_gameFunctions = gameFunctions;
_aetheryteFunctions = aetheryteFunctions;
_dataManager = dataManager;
_clientState = clientState;
_objectTable = objectTable;
_configuration = configuration;
_changelogWindow = changelogWindow;
_clientState.Logout += OnLogout;
ICommandManager commandManager2 = _commandManager;
CommandInfo commandInfo = new CommandInfo(ProcessCommand);
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);
_commandManager.AddHandler("/qst", 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")
});
}
private void ProcessCommand(string command, string arguments)
@ -89,19 +110,17 @@ internal sealed class CommandHandler : IDisposable
_chatGui.Print("/qst help - displays simplified 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 changelog - opens the changelog window", "Questionable", 576);
_chatGui.Print("/qst start - starts doing quests", "Questionable", 576);
_chatGui.Print("/qst stop - stops doing quests", "Questionable", 576);
_chatGui.Print("/qst reload - reload all quest data", "Questionable", 576);
break;
case "ha":
case "help-all":
case "ha":
_chatGui.Print("Available commands:", "Questionable", 576);
_chatGui.Print("/qst - toggles the Questing window", "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 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 stop - stops doing quests", "Questionable", 576);
_chatGui.Print("/qst reload - reload all quest data", "Questionable", 576);
@ -115,10 +134,6 @@ internal sealed class CommandHandler : IDisposable
case "config":
_configWindow.ToggleOrUncollapse();
break;
case "cl":
case "changelog":
_changelogWindow.ToggleOrUncollapse();
break;
case "start":
_questWindow.IsOpenAndUncollapsed = true;
_questController.Start("Start command");
@ -158,8 +173,441 @@ internal sealed class CommandHandler : IDisposable
}
}
private void ProcessDebugCommand(string command, string arguments)
private unsafe 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)
@ -179,8 +627,110 @@ internal sealed class CommandHandler : IDisposable
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)
{
_previouslyUnlockedUnlockLinks = Array.Empty<uint>();
}
public void Dispose()

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services;
@ -44,11 +43,9 @@ internal sealed class ContextMenuController : IDisposable
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
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, IObjectTable objectTable, 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)
{
_contextMenu = contextMenu;
_questController = questController;
@ -61,7 +58,6 @@ internal sealed class ContextMenuController : IDisposable
_gameGui = gameGui;
_chatGui = chatGui;
_clientState = clientState;
_objectTable = objectTable;
_logger = logger;
_contextMenu.OnMenuOpened += MenuOpened;
}
@ -117,11 +113,7 @@ internal sealed class ContextMenuController : IDisposable
private unsafe void AddContextMenuEntry(IMenuOpenedArgs args, uint itemId, uint npcId, EClassJob classJob, string verb)
{
if (!(_objectTable[0] is ICharacter { ClassJob: var classJob2 }))
{
return;
}
EClassJob rowId = (EClassJob)classJob2.RowId;
EClassJob rowId = (EClassJob)_clientState.LocalPlayer.ClassJob.RowId;
bool flag = classJob != rowId;
if (flag)
{

View file

@ -61,9 +61,7 @@ internal sealed class GatheringPointRegistry : IDisposable
private void LoadGatheringPointsFromAssembly()
{
_logger.LogInformation("Loading gathering points from assembly");
IReadOnlyDictionary<ushort, GatheringRoot> locations = AssemblyGatheringLocationLoader.GetLocations();
_logger.LogInformation("AssemblyGatheringLocationLoader returned {Count} locations", locations.Count);
foreach (var (value, value2) in locations)
foreach (var (value, value2) in AssemblyGatheringLocationLoader.GetLocations())
{
_gatheringPoints[new GatheringPointId(value)] = value2;
}
@ -74,11 +72,11 @@ internal sealed class GatheringPointRegistry : IDisposable
private void LoadGatheringPointsFromProjectDirectory()
{
DirectoryInfo directoryInfo = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
_logger.LogInformation("Looking for gathering paths in solution directory: {SolutionDirectory}", directoryInfo?.FullName ?? "(null)");
if (directoryInfo != null)
if (directoryInfo == null)
{
return;
}
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)
{
return;
@ -89,17 +87,13 @@ internal sealed class GatheringPointRegistry : IDisposable
{
LoadFromDirectory(new DirectoryInfo(Path.Combine(directoryInfo2.FullName, value)));
}
return;
}
catch (Exception exception)
{
_gatheringPoints.Clear();
_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)
{

View file

@ -1,6 +1,5 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
@ -15,6 +14,11 @@ internal sealed class InterruptHandler : IDisposable
{
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)]
private struct EffectEntry
{
@ -131,21 +135,18 @@ internal sealed class InterruptHandler : IDisposable
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly TerritoryData _territoryData;
private readonly ILogger<InterruptHandler> _logger;
public event EventHandler? Interrupted;
public unsafe InterruptHandler(IGameInteropProvider gameInteropProvider, IClientState clientState, IObjectTable objectTable, TerritoryData territoryData, ILogger<InterruptHandler> logger)
public unsafe InterruptHandler(IGameInteropProvider gameInteropProvider, IClientState clientState, TerritoryData territoryData, ILogger<InterruptHandler> logger)
{
_clientState = clientState;
_objectTable = objectTable;
_territoryData = territoryData;
_logger = logger;
_processActionEffectHook = gameInteropProvider.HookFromAddress<ProcessActionEffect>(ActionEffectHandler.Addresses.Receive.Value, HandleProcessActionEffect);
_processActionEffectHook = gameInteropProvider.HookFromSignature<ProcessActionEffect>("40 ?? 56 57 41 ?? 41 ?? 41 ?? 48 ?? ?? ?? ?? ?? ?? ?? 48", HandleProcessActionEffect);
_processActionEffectHook.Enable();
}
@ -157,12 +158,11 @@ internal sealed class InterruptHandler : IDisposable
{
return;
}
IGameObject gameObject = _objectTable[0];
for (int i = 0; i < effectHeader->TargetCount; i++)
{
int num = (int)(effectTail[i] & 0xFFFFFFFFu);
EffectEntry* ptr = effectArray + 8 * i;
bool flag = (uint)num == gameObject?.GameObjectId;
bool flag = (uint)num == _clientState.LocalPlayer?.GameObjectId;
if (flag)
{
EActionEffectType type = ptr->Type;

View file

@ -215,10 +215,6 @@ internal abstract class MiniTaskController<T> : IDisposable
public void OnErrorToast(ref SeString message, ref bool isHandled)
{
if (_taskQueue.AllTasksComplete)
{
return;
}
if (_taskQueue.CurrentTaskExecutor is IToastAware toastAware && toastAware.OnErrorToast(message))
{
isHandled = true;

View file

@ -13,7 +13,6 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Ipc.Exceptions;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Enums;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using Microsoft.Extensions.Logging;
@ -74,8 +73,6 @@ internal sealed class MovementController : IDisposable
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly GameFunctions _gameFunctions;
private readonly ChatFunctions _chatFunctions;
@ -86,24 +83,12 @@ internal sealed class MovementController : IDisposable
private readonly AetheryteData _aetheryteData;
private readonly Configuration _configuration;
private readonly ILogger<MovementController> _logger;
private CancellationTokenSource? _cancellationTokenSource;
private Task<List<Vector3>>? _pathfindTask;
private long _pathfindStartTime;
private Vector3? _lastKnownPosition;
private long _lastPositionUpdateTime;
private Vector3? _expectedPosition;
private bool _isTrackingPlayerInput;
public bool IsNavmeshReady
{
get
@ -153,87 +138,32 @@ internal sealed class MovementController : IDisposable
public int BuiltNavmeshPercent => _navmeshIpc.GetBuildProgress();
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)
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, GameFunctions gameFunctions, ChatFunctions chatFunctions, ICondition condition, MovementOverrideController movementOverrideController, AetheryteData aetheryteData, ILogger<MovementController> logger)
{
_navmeshIpc = navmeshIpc;
_clientState = clientState;
_objectTable = objectTable;
_gameFunctions = gameFunctions;
_chatFunctions = chatFunctions;
_condition = condition;
_movementOverrideController = movementOverrideController;
_aetheryteData = aetheryteData;
_configuration = configuration;
_logger = logger;
}
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.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)
{
_logger.LogInformation("Pathfinding complete, got {Count} points", _pathfindTask.Result.Count);
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();
throw new PathfindingFailedException();
}
List<Vector3> list = _pathfindTask.Result.Skip(1).ToList();
Vector3 p = _objectTable[0]?.Position ?? list[0];
Vector3 p = _clientState.LocalPlayer?.Position ?? list[0];
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);
@ -244,17 +174,11 @@ internal sealed class MovementController : IDisposable
(list, _) = tuple;
if (tuple.Item2 && Destination.ShouldRecalculateNavmesh())
{
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready for recalculation");
return;
}
Destination.NavmeshCalculations++;
Destination.PartialRoute.AddRange(list);
_logger.LogInformation("Running navmesh recalculation with fudged point ({From} to {To})", list.Last(), Destination.Position);
_cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
_pathfindStartTime = Environment.TickCount64;
_pathfindTask = _navmeshIpc.Pathfind(list.Last(), Destination.Position, Destination.IsFlying, _cancellationTokenSource.Token);
return;
}
@ -263,7 +187,6 @@ internal sealed class MovementController : IDisposable
_logger.LogInformation("Navigating via route: [{Route}]", string.Join(" → ", _pathfindTask.Result.Select((Vector3 x) => x.ToString("G", CultureInfo.InvariantCulture))));
_navmeshIpc.MoveTo(list, Destination.IsFlying);
MovementStartedAt = DateTime.Now;
StartPlayerInputTracking();
ResetPathfinding();
}
else if (_pathfindTask.IsCompleted)
@ -297,7 +220,7 @@ internal sealed class MovementController : IDisposable
Restart(Destination);
return;
}
Vector3 vector4 = _objectTable[0]?.Position ?? Vector3.Zero;
Vector3 vector = _clientState.LocalPlayer?.Position ?? Vector3.Zero;
if (Destination.MovementType == EMovementType.Landing)
{
if (!_condition[ConditionFlag.InFlight])
@ -305,9 +228,9 @@ internal sealed class MovementController : IDisposable
Stop();
}
}
else if ((vector4 - Destination.Position).Length() < Destination.StopDistance)
else if ((vector - Destination.Position).Length() < Destination.StopDistance)
{
if (vector4.Y - Destination.Position.Y <= Destination.VerticalStopDistance)
if (vector.Y - Destination.Position.Y <= Destination.VerticalStopDistance)
{
Stop();
}
@ -316,7 +239,7 @@ internal sealed class MovementController : IDisposable
IGameObject gameObject = _gameFunctions.FindObjectByDataId(Destination.DataId.Value);
if ((gameObject is ICharacter || gameObject is IEventObj) ? true : false)
{
if (Math.Abs(vector4.Y - gameObject.Position.Y) < 1.95f)
if (Math.Abs(vector.Y - gameObject.Position.Y) < 1.95f)
{
Stop();
}
@ -327,7 +250,7 @@ internal sealed class MovementController : IDisposable
{
Stop();
}
else if (Math.Abs(vector4.Y - gameObject.Position.Y) < 1.95f)
else if (Math.Abs(vector.Y - gameObject.Position.Y) < 1.95f)
{
Stop();
}
@ -345,80 +268,14 @@ internal sealed class MovementController : IDisposable
else
{
List<Vector3> waypoints = _navmeshIpc.GetWaypoints();
Vector3? vector5 = _objectTable[0]?.Position;
if (vector5.HasValue && (!Destination.ShouldRecalculateNavmesh() || !RecalculateNavmesh(waypoints, vector5.Value)) && !Destination.IsFlying && !_condition[ConditionFlag.Mounted] && !_gameFunctions.HasStatusPreventingSprint() && Destination.CanSprint)
Vector3? vector2 = _clientState.LocalPlayer?.Position;
if (vector2.HasValue && (!Destination.ShouldRecalculateNavmesh() || !RecalculateNavmesh(waypoints, vector2.Value)) && !Destination.IsFlying && !_condition[ConditionFlag.Mounted] && !_gameFunctions.HasStatusPreventingSprint() && Destination.CanSprint)
{
TriggerSprintIfNeeded(waypoints, vector5.Value);
TriggerSprintIfNeeded(waypoints, vector2.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)
{
Stop();
@ -462,11 +319,6 @@ 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)
{
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready, cannot start navigation to {Position}", to.ToString("G", CultureInfo.InvariantCulture));
return;
}
fly |= _condition[ConditionFlag.Diving];
if (fly && land)
{
@ -479,7 +331,7 @@ internal sealed class MovementController : IDisposable
Destination.NavmeshCalculations++;
_cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
Vector3 vector2 = _objectTable[0]?.Position ?? Vector3.Zero;
Vector3 vector2 = _clientState.LocalPlayer.Position;
if (fly && _aetheryteData.CalculateDistance(vector2, _clientState.TerritoryType, EAetheryteLocation.CoerthasCentralHighlandsCampDragonhead) < 11f)
{
Vector3 vector = vector2;
@ -493,25 +345,11 @@ internal sealed class MovementController : IDisposable
vector.Y = vector2.Y + 0.2f;
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);
}
}
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];
if (fly && land && to.Count > 0)
{
@ -524,7 +362,6 @@ internal sealed class MovementController : IDisposable
_logger.LogInformation("Moving to {Destination}", Destination);
_navmeshIpc.MoveTo(to, fly);
MovementStartedAt = DateTime.Now;
StartPlayerInputTracking();
}
public void ResetPathfinding()
@ -543,51 +380,6 @@ internal sealed class MovementController : IDisposable
_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)
{
if (Destination == null)
@ -618,52 +410,17 @@ internal sealed class MovementController : IDisposable
if (Math.Abs((double)num - Destination.LastWaypoint.Distance2DAtLastUpdate) < 0.5)
{
int navmeshCalculations = Destination.NavmeshCalculations;
switch (navmeshCalculations)
if (navmeshCalculations % 6 == 1)
{
case 1:
case 7:
_logger.LogWarning("Jumping to try and resolve navmesh problem (n = {Calculations}) at {Position}", navmeshCalculations, Destination.Position.ToString("G", CultureInfo.InvariantCulture));
_logger.LogWarning("Jumping to try and resolve navmesh problem (n = {Calculations})", navmeshCalculations);
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
Destination.NavmeshCalculations++;
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
break;
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("Navmesh not ready after reload (n = {Calculations})", navmeshCalculations);
return false;
}
_logger.LogInformation("Navmesh ready after reload, restarting navigation (n = {Calculations})", navmeshCalculations);
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)
else
{
_logger.LogWarning("Navmesh not ready after rebuild (n = {Calculations})", navmeshCalculations);
return false;
}
_logger.LogInformation("Navmesh ready after rebuild, restarting navigation (n = {Calculations})", navmeshCalculations);
_logger.LogWarning("Recalculating navmesh (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;
return true;
@ -690,14 +447,14 @@ internal sealed class MovementController : IDisposable
bool flag2;
switch (GameMain.Instance()->CurrentTerritoryIntendedUseId)
{
case TerritoryIntendedUse.Town:
case TerritoryIntendedUse.BeforeTrialDung:
case TerritoryIntendedUse.HousingOutdoor:
case TerritoryIntendedUse.HousingIndoor:
case TerritoryIntendedUse.SoloOverworldInstances:
case TerritoryIntendedUse.ChocoboSquareOld:
case TerritoryIntendedUse.GoldSaucer:
case TerritoryIntendedUse.SoloDuty:
case 0:
case 7:
case 13:
case 14:
case 15:
case 19:
case 23:
case 29:
flag2 = true;
break;
default:
@ -719,7 +476,6 @@ internal sealed class MovementController : IDisposable
public void Stop()
{
StopPlayerInputTracking();
_navmeshIpc.Stop();
ResetPathfinding();
Destination = null;

View file

@ -6,7 +6,6 @@ using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Gui.Toast;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
@ -16,7 +15,6 @@ using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Shared;
using Questionable.Data;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
@ -86,8 +84,6 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly GameFunctions _gameFunctions;
private readonly QuestFunctions _questFunctions;
@ -100,8 +96,6 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private readonly QuestRegistry _questRegistry;
private readonly JournalData _journalData;
private readonly IKeyState _keyState;
private readonly IChatGui _chatGui;
@ -116,8 +110,6 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent;
private readonly AutoDutyIpc _autoDutyIpc;
private readonly ILogger<QuestController> _logger;
private readonly object _progressLock = new object();
@ -150,16 +142,6 @@ internal sealed class QuestController : MiniTaskController<QuestController>
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 = ';';
public EAutomationType AutomationType
@ -241,18 +223,16 @@ internal sealed class QuestController : MiniTaskController<QuestController>
public event AutomationTypeChangedEventHandler? AutomationTypeChanged;
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)
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)
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
{
_clientState = clientState;
_objectTable = objectTable;
_gameFunctions = gameFunctions;
_questFunctions = questFunctions;
_movementController = movementController;
_combatController = combatController;
_gatheringController = gatheringController;
_questRegistry = questRegistry;
_journalData = journalData;
_keyState = keyState;
_chatGui = chatGui;
_condition = condition;
@ -260,13 +240,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_configuration = configuration;
_taskCreator = taskCreator;
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
_autoDutyIpc = autoDutyIpc;
_logger = logger;
_toastGui.ErrorToast += base.OnErrorToast;
_toastGui.Toast += OnNormalToast;
_condition.ConditionChange += OnConditionChange;
_clientState.Logout += OnLogout;
_movementController.PlayerInputDetected += OnPlayerInputDetected;
}
public void Reload()
@ -322,101 +300,40 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
StopAllDueToConditionFailed("Logged out");
}
bool flag;
if (_condition[ConditionFlag.Unconscious])
{
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)
if ((!_condition[ConditionFlag.Unconscious] || !_condition[ConditionFlag.SufferingStatusAffliction63] || _clientState.TerritoryType != 1052) && !(_taskQueue.CurrentTaskExecutor is Duty.WaitAutoDutyExecutor) && !_taskQueue.AllTasksComplete)
{
StopAllDueToConditionFailed("HP = 0");
}
}
else if (_configuration.General.UseEscToCancelQuesting && _keyState[VirtualKey.ESCAPE] && !_taskQueue.AllTasksComplete)
{
StopAllDueToConditionFailed("ESC pressed");
}
else if (_configuration.General.UseEscToCancelQuesting)
if (_configuration.Stop.Enabled && _configuration.Stop.LevelToStopAfter && _clientState.LocalPlayer != null)
{
if (_keyState[VirtualKey.ESCAPE] && !_lastEscDown)
int level = _clientState.LocalPlayer.Level;
if (level >= _configuration.Stop.TargetLevel && IsRunning)
{
DateTime now = DateTime.Now;
if (now - _lastEscPressTime <= EscDoublePressWindow)
{
_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}]");
_logger.LogInformation("Reached level stop condition (level: {CurrentLevel}, target: {TargetLevel})", level, _configuration.Stop.TargetLevel);
_chatGui.Print($"Reached or exceeded target level {_configuration.Stop.TargetLevel}.", "Questionable", 576);
Stop($"Level stop condition reached [{level}]");
return;
}
}
if (_configuration.Stop.QuestSequences.TryGetValue(text, out var value) && value.HasValue)
if (_configuration.Stop.Enabled && _configuration.Stop.SequenceToStopAfter && CurrentQuest != null)
{
int sequence = _startedQuest.Sequence;
if (sequence >= value.Value && IsRunning)
int sequence = CurrentQuest.Sequence;
if (sequence >= _configuration.Stop.TargetSequence && IsRunning)
{
Configuration.EStopConditionMode valueOrDefault = _configuration.Stop.QuestStopModes.GetValueOrDefault(text, Configuration.EStopConditionMode.Pause);
string item2 = $"questseq:{text}:{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}]");
_logger.LogInformation("Reached quest sequence stop condition (sequence: {CurrentSequence}, target: {TargetSequence})", sequence, _configuration.Stop.TargetSequence);
_chatGui.Print($"Quest sequence {sequence} reached target sequence {_configuration.Stop.TargetSequence}.", "Questionable", 576);
Stop($"Sequence stop condition reached [{sequence}]");
return;
}
}
}
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 flag = AutomationType == EAutomationType.Automatic && (_taskQueue.AllTasksComplete || _taskQueue.CurrentTaskExecutor?.CurrentTask is WaitAtEnd.WaitQuestAccepted);
bool flag2;
if (flag)
{
@ -427,14 +344,14 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (step == 0 || step == 255)
{
flag2 = true;
goto IL_0800;
goto IL_0422;
}
}
flag2 = false;
goto IL_0800;
goto IL_0422;
}
goto IL_0804;
IL_0804:
goto IL_0426;
IL_0426:
if (flag && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15.0))
{
lock (_progressLock)
@ -450,9 +367,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>
UpdateCurrentTask();
}
return;
IL_0800:
IL_0422:
flag = flag2;
goto IL_0804;
goto IL_0426;
}
private void CheckAutoRefreshCondition()
@ -466,12 +383,12 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_lastProgressUpdate = DateTime.Now;
return;
}
IGameObject gameObject = _objectTable[0];
if (gameObject == null)
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
if (localPlayer == null)
{
return;
}
Vector3 position = gameObject.Position;
Vector3 position = localPlayer.Position;
if (CurrentQuest == null)
{
return;
@ -504,7 +421,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
if (_configuration.General.AutoStepRefreshEnabled && AutomationType == EAutomationType.Automatic && IsRunning && CurrentQuest != null && _clientState.IsLoggedIn)
{
return _objectTable[0] != null;
return _clientState.LocalPlayer != null;
}
return false;
}
@ -533,7 +450,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private bool HasWaitingTasks()
{
ITask task = _taskQueue.CurrentTaskExecutor?.CurrentTask;
if (task is WaitAtEnd.WaitObjectAtPosition || task is WaitAtEnd.WaitForCompletionFlags || task is Duty.StartLevelingModeTask || task is Duty.WaitLevelingModeTask)
if (task is WaitAtEnd.WaitObjectAtPosition || task is WaitAtEnd.WaitForCompletionFlags)
{
return true;
}
@ -557,7 +474,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private bool HasSystemConditionsPreventingRefresh()
{
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)
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)
{
return DateTime.Now < _safeAnimationEnd;
}
@ -589,7 +506,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
int num = ManualPriorityQuests.RemoveAll((Quest q) => _questFunctions.IsQuestComplete(q.Id));
if (num > 0)
{
_logger.LogInformation("Removed {Count} completed priority {QuestWord}", num, (num == 1) ? "quest" : "quests");
_logger.LogInformation("Removed {Count} completed priority quest(s)", num);
}
}
if (_pendingQuest != null)
@ -658,26 +575,6 @@ internal sealed class QuestController : MiniTaskController<QuestController>
}
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)
{
switch (mainScenarioQuestState)
@ -695,16 +592,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
}
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
{
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))
{
ElementId id = _startedQuest.Quest.Id;
@ -712,29 +604,17 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_chatGui.Print("Completed quest '" + _startedQuest.Quest.Info.Name + "', which is configured as a stopping point.", "Questionable", 576);
_startedQuest = null;
Stop($"Stopping point [{id}] reached");
return;
}
if (_questRegistry.TryGetQuest(elementId, out Quest quest))
else if (_questRegistry.TryGetQuest(elementId, out quest))
{
_logger.LogInformation("New quest: {QuestName}", quest.Info.Name);
_startedQuest = new QuestProgress(quest, b);
if (_objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level < quest.Info.Level)
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.Level < quest.Info.Level)
{
if (_autoDutyIpc.IsConfiguredToRunLevelingMode(playerCharacter.Level) && AutomationType == EAutomationType.Automatic && !_condition[ConditionFlag.BoundByDuty])
{
_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);
_logger.LogInformation("Stopping automation, player level ({PlayerLevel}) < quest level ({QuestLevel}", _clientState.LocalPlayer.Level, quest.Info.Level);
Stop("Quest level too high");
return;
}
}
else
{
if (AutomationType == EAutomationType.SingleQuestB)
{
_logger.LogInformation("Single quest is finished");
@ -742,91 +622,63 @@ internal sealed class QuestController : MiniTaskController<QuestController>
}
CheckNextTasks("Different Quest");
}
return;
}
if (_startedQuest != null)
else if (_startedQuest != null)
{
_logger.LogInformation("No active quest anymore? Not sure what happened...");
_startedQuest = null;
Stop("No active Quest");
}
return;
}
questProgress = null;
}
}
else
{
questProgress = _startedQuest;
}
}
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";
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;
}
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))
if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(questProgress.Quest))
{
DebugState = "Occupied";
return;
}
else if (_movementController.IsPathfinding)
if (_movementController.IsPathfinding)
{
DebugState = "Pathfinding is running";
return;
}
else if (_movementController.IsPathRunning)
if (_movementController.IsPathRunning)
{
DebugState = "Path is running";
return;
}
else if (DateTime.Now < _safeAnimationEnd)
if (DateTime.Now < _safeAnimationEnd)
{
DebugState = "Waiting for Animation";
return;
}
else if (questProgress.Sequence != b)
if (questProgress.Sequence != b)
{
questProgress.SetSequence(b);
CheckNextTasks($"New sequence {questProgress == _startedQuest}/{_questFunctions.GetCurrentQuestInternal(allowNewMsq: true)}");
}
else
{
QuestSequence questSequence2 = questProgress.Quest.FindSequence(questProgress.Sequence);
if (questSequence2 == null)
QuestSequence questSequence = questProgress.Quest.FindSequence(questProgress.Sequence);
if (questSequence == null)
{
DebugState = $"Sequence {questProgress.Sequence} not found";
Stop("Unknown sequence");
}
else if (questSequence2.Steps.Count > 0 && questProgress.Step >= questSequence2.Steps.Count)
else if (questProgress.Step == 255)
{
DebugState = "Step completed";
if (!_taskQueue.AllTasksComplete)
{
CheckNextTasks("Step complete");
}
}
else if (questSequence.Steps.Count > 0 && questProgress.Step >= questSequence.Steps.Count)
{
DebugState = "Step not found";
Stop("Unknown step");
@ -837,7 +689,6 @@ internal sealed class QuestController : MiniTaskController<QuestController>
}
}
}
}
public (QuestSequence? Sequence, QuestStep? Step, bool createTasks) GetNextStep()
{
@ -881,21 +732,15 @@ 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.LogInformation("Increasing step count from {CurrentValue}", CurrentQuest.Step);
bool num = CurrentQuest.Step + 1 >= questSequence.Steps.Count;
if (num)
{
CurrentQuest.SetStep(255);
}
else
if (CurrentQuest.Step + 1 < questSequence.Steps.Count)
{
CurrentQuest.SetStep(CurrentQuest.Step + 1);
}
ResetAutoRefreshState();
if (num)
else
{
_logger.LogInformation("Completed last step in sequence, waiting for game to update sequence");
return;
CurrentQuest.SetStep(255);
}
ResetAutoRefreshState();
}
using (_logger.BeginScope("IncStepCt"))
{
@ -929,7 +774,6 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_nextQuest = null;
_gatheringQuest = null;
_lastTaskUpdate = DateTime.Now;
_stopConditionsMetAtStart.Clear();
ResetAutoRefreshState();
}
}
@ -1042,40 +886,28 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
using (_logger.BeginScope("Q/" + label))
{
if (!CheckAndBlockForStopConditions())
{
RecordStopConditionsMetAtStart();
AutomationType = EAutomationType.Automatic;
ExecuteNextStep();
}
}
}
public void StartGatheringQuest(string label)
{
using (_logger.BeginScope("GQ/" + label))
{
if (!CheckAndBlockForStopConditions())
{
RecordStopConditionsMetAtStart();
AutomationType = EAutomationType.GatheringOnly;
ExecuteNextStep();
}
}
}
public void StartSingleQuest(string label)
{
using (_logger.BeginScope("SQ/" + label))
{
if (!CheckAndBlockForStopConditions())
{
RecordStopConditionsMetAtStart();
AutomationType = EAutomationType.SingleQuestA;
ExecuteNextStep();
}
}
}
public void StartSingleStep(string label)
{
@ -1086,109 +918,6 @@ 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()
{
ClearTasksInternal();
@ -1199,23 +928,6 @@ internal sealed class QuestController : MiniTaskController<QuestController>
var (questSequence, step, flag) = GetNextStep();
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)
{
(QuestProgress, ECurrentQuestType)? currentQuestDetails = CurrentQuestDetails;
@ -1227,23 +939,23 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_logger.LogInformation("Completed delivery quest");
SetGatheringQuest(null);
Stop("Gathering quest complete");
goto IL_02d0;
goto IL_01dc;
}
}
}
_logger.LogWarning("Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]", CurrentQuest?.Quest.Id, CurrentQuest?.Sequence, CurrentQuest?.Step);
goto IL_02d0;
goto IL_01dc;
}
goto IL_02dc;
IL_02dc:
goto IL_01e8;
IL_01e8:
_movementController.Stop();
_combatController.Stop("Execute next step");
_gatheringController.Stop("Execute next step");
try
{
foreach (ITask item5 in _taskCreator.CreateTasks(CurrentQuest.Quest, CurrentQuest.Sequence, questSequence, step))
foreach (ITask item in _taskCreator.CreateTasks(CurrentQuest.Quest, CurrentQuest.Sequence, questSequence, step))
{
_taskQueue.Enqueue(item5);
_taskQueue.Enqueue(item);
}
ResetAutoRefreshState();
return;
@ -1255,12 +967,12 @@ internal sealed class QuestController : MiniTaskController<QuestController>
Stop("Tasks failed to create");
return;
}
IL_02d0:
IL_01dc:
if (CurrentQuest == null || !flag)
{
return;
}
goto IL_02dc;
goto IL_01e8;
}
public string ToStatString()
@ -1450,7 +1162,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_logger.LogDebug("Excluding allied society quest {QuestId} from bulk add", quest.Id);
return false;
}
if (quest.Info is QuestInfo questInfo && _journalData.MoogleDeliveryGenreId.HasValue && questInfo.JournalGenre == _journalData.MoogleDeliveryGenreId.Value)
if (quest.Info is QuestInfo { MoogleDeliveryLevel: >0 })
{
_logger.LogDebug("Excluding moogle delivery quest {QuestId} from bulk add", quest.Id);
return false;
@ -1533,23 +1245,12 @@ 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()
{
_toastGui.ErrorToast -= base.OnErrorToast;
_toastGui.Toast -= OnNormalToast;
_condition.ConditionChange -= OnConditionChange;
_clientState.Logout -= OnLogout;
_movementController.PlayerInputDetected -= OnPlayerInputDetected;
base.Dispose();
}
}

View file

@ -131,7 +131,7 @@ internal sealed class QuestRegistry
}
if (_questData.TryGetQuestInfo(elementId2, out IQuestInfo questInfo))
{
goto IL_01ae;
goto IL_01c8;
}
if (elementId2 is UnlockLinkId unlockLinkId)
{
@ -147,16 +147,18 @@ internal sealed class QuestRegistry
text = $"Unlock Link {unlockLinkId.Value}";
}
questInfo = new UnlockLinkQuestInfo(unlockLinkId, text, 0u, dateTime);
_logger.LogDebug("Created UnlockLinkQuestInfo for {QuestId} from assembly", elementId2);
_questData.AddOrReplaceQuestInfo(questInfo);
goto IL_01ae;
goto IL_01c8;
}
_logger.LogWarning("Not loading unknown quest {QuestId} from assembly: Quest not found in quest data", elementId2);
goto end_IL_003d;
IL_01ae:
IL_01c8:
if (flag2 || flag3)
{
bool isSeasonal = flag ?? questInfo.IsSeasonalQuest;
_questData.ApplySeasonalOverride(elementId2, isSeasonal, dateTime);
bool flag4 = flag ?? questInfo.IsSeasonalQuest;
_questData.ApplySeasonalOverride(elementId2, flag4, 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);
Quest quest = new Quest

File diff suppressed because it is too large Load diff

View file

@ -1,68 +0,0 @@
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);
}
}

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Plugin.Services;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model;
@ -86,12 +85,6 @@ internal sealed class JournalData
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<Category> Categories { get; }
@ -104,38 +97,7 @@ internal sealed class JournalData
{
JournalData journalData = this;
_logger = logger;
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
List<Genre> list = (from x in dataManager.GetExcelSheet<JournalGenre>()
where x.RowId != 0 && x.Icon > 0
select new Genre(x, questData.GetAllByJournalGenre(x.RowId))).ToList();
QuestRedo row = dataManager.GetExcelSheet<QuestRedo>().GetRow(1u);
@ -158,7 +120,9 @@ internal sealed class JournalData
select new Category(x, journalData.Genres.Where((Genre y) => y.CategoryId == x.RowId).ToList())).ToList();
Sections = (from x in dataManager.GetExcelSheet<JournalSection>()
select new Section(x, journalData.Categories.Where((Category y) => y.SectionId == x.RowId).ToList())).ToList();
_logger.LogDebug("Resolving OtherQuests section id...");
OtherQuestsSectionRowId = GetOtherQuestsSectionRowId(dataManager);
_logger.LogDebug("Resolved OtherQuestsSectionRowId = {Id}", OtherQuestsSectionRowId);
int? otherQuestsSectionRowId = OtherQuestsSectionRowId;
if (otherQuestsSectionRowId.HasValue)
{
@ -168,7 +132,6 @@ internal sealed class JournalData
if (section != null)
{
int num = 0;
{
foreach (Category category in section.Categories)
{
foreach (Genre genre in category.Genres)
@ -177,10 +140,12 @@ internal sealed class JournalData
num++;
}
}
return;
_logger.LogInformation("Marked {Count} genres as under 'Other Quests' (section id {Id})", num, valueOrDefault);
}
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
{

File diff suppressed because it is too large Load diff

View file

@ -33,8 +33,6 @@ internal sealed class TerritoryData
private readonly ImmutableDictionary<uint, ContentFinderConditionData> _contentFinderConditions;
private readonly ImmutableDictionary<uint, uint> _territoryToContentFinderCondition;
private readonly ImmutableDictionary<(ElementId QuestId, byte Index), uint> _questBattlesToContentFinderCondition;
public TerritoryData(IDataManager dataManager)
@ -69,9 +67,6 @@ internal sealed class TerritoryData
return flag && x.ContentType.RowId != 6;
})
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>()
where x.RowId != 0 && x.IssuerLocation.RowId != 0
select x).SelectMany(GetQuestBattles)
@ -143,11 +138,6 @@ internal sealed class TerritoryData
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()
{
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]));
@ -159,7 +149,7 @@ internal sealed class TerritoryData
{
return name;
}
return string.Concat(name[0].ToString().ToUpper(CultureInfo.InvariantCulture).AsSpan(), name.AsSpan(1));
return string.Concat(name[0].ToString().ToUpper(CultureInfo.InvariantCulture), name.AsSpan(1));
}
private static IEnumerable<(ElementId QuestId, byte Index, uint QuestBattleId)> GetQuestBattles(Quest quest)

View file

@ -33,12 +33,6 @@ internal sealed class AutoDutyIpc
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)
{
_configuration = configuration;
@ -49,8 +43,6 @@ internal sealed class AutoDutyIpc
_run = pluginInterface.GetIpcSubscriber<uint, int, bool, object>("AutoDuty.Run");
_isStopped = pluginInterface.GetIpcSubscriber<bool>("AutoDuty.IsStopped");
_stop = pluginInterface.GetIpcSubscriber<object>("AutoDuty.Stop");
_loggedContentHasPathQueryWarning = false;
_loggedLevelingModeWarning = false;
}
public bool IsConfiguredToRunContent(DutyOptions? dutyOptions)
@ -78,24 +70,6 @@ internal sealed class AutoDutyIpc
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)
{
if (!_territoryData.TryGetContentFinderCondition(cfcId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
@ -107,12 +81,8 @@ internal sealed class AutoDutyIpc
return _contentHasPath.InvokeFunc(contentFinderConditionData.TerritoryId);
}
catch (IpcError ipcError)
{
if (!_loggedContentHasPathQueryWarning)
{
_logger.LogWarning("Unable to query AutoDuty for path in territory {TerritoryType}: {Message}", contentFinderConditionData.TerritoryId, ipcError.Message);
_loggedContentHasPathQueryWarning = true;
}
return false;
}
}
@ -125,7 +95,6 @@ internal sealed class AutoDutyIpc
}
try
{
_setConfig.InvokeAction("leveling", "None");
_setConfig.InvokeAction("Unsynced", $"{dutyMode == DutyMode.UnsyncRegular}");
ICallGateSubscriber<string, string, object> setConfig = _setConfig;
setConfig.InvokeAction("dutyModeEnum", dutyMode switch
@ -142,40 +111,6 @@ 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()
{
try
@ -188,36 +123,16 @@ 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()
{
try
{
_logger.LogInformation("Calling AutoDuty.Stop");
try
{
_setConfig.InvokeAction("leveling", "None");
}
catch (IpcError)
{
}
_stop.InvokeAction();
}
catch (IpcError ipcError2)
catch (IpcError ipcError)
{
throw new TaskException("Unable to stop AutoDuty: " + ipcError2.Message, ipcError2);
throw new TaskException("Unable to stop AutoDuty: " + ipcError.Message, ipcError);
}
}
}

View file

@ -11,18 +11,12 @@ namespace Questionable.External;
internal sealed class NavmeshIpc
{
private readonly IDalamudPluginInterface _pluginInterface;
private readonly ILogger<NavmeshIpc> _logger;
private readonly ICallGateSubscriber<bool> _isNavReady;
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<object> _pathStop;
@ -35,34 +29,8 @@ internal sealed class NavmeshIpc
private readonly ICallGateSubscriber<Vector3, bool, float, Vector3?> _queryPointOnFloor;
private readonly ICallGateSubscriber<Vector3, float, float, Vector3?> _queryNearestPoint;
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
{
get
@ -93,62 +61,22 @@ 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)
{
_pluginInterface = pluginInterface;
_logger = logger;
_isNavReady = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Nav.IsReady");
_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");
_pathStop = pluginInterface.GetIpcSubscriber<object>("vnavmesh.Path.Stop");
_pathIsRunning = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Path.IsRunning");
_pathListWaypoints = pluginInterface.GetIpcSubscriber<List<Vector3>>("vnavmesh.Path.ListWaypoints");
_pathSetTolerance = pluginInterface.GetIpcSubscriber<float, object>("vnavmesh.Path.SetTolerance");
_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");
_navReload = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Nav.Reload");
_navRebuild = pluginInterface.GetIpcSubscriber<bool>("vnavmesh.Nav.Rebuild");
}
public void Stop()
{
if (!IsNavmeshAvailable)
{
return;
}
try
{
_pathStop.InvokeAction();
@ -159,44 +87,8 @@ 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)
{
if (!IsNavmeshAvailable)
{
return Task.FromResult(new List<Vector3>());
}
try
{
_pathSetTolerance.InvokeAction(0.25f);
@ -209,30 +101,8 @@ 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)
{
if (!IsNavmeshAvailable)
{
return;
}
Stop();
try
{
@ -256,30 +126,6 @@ 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()
{
if (IsPathRunning)

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
using System;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Services;
@ -11,37 +10,31 @@ internal sealed class TextAdvanceIpc : IDisposable
{
public sealed class ExternalTerritoryConfig
{
public bool? EnableQuestAccept;
public bool? EnableQuestAccept = true;
public bool? EnableQuestComplete;
public bool? EnableQuestComplete = true;
public bool? EnableRewardPick;
public bool? EnableRewardPick = true;
public bool? EnableRequestHandin;
public bool? EnableRequestHandin = true;
public bool? EnableCutsceneEsc;
public bool? EnableCutsceneEsc = true;
public bool? EnableCutsceneSkipConfirm;
public bool? EnableCutsceneSkipConfirm = true;
public bool? EnableTalkSkip;
public bool? EnableTalkSkip = true;
public bool? EnableRequestFill;
public bool? EnableRequestFill = true;
public bool? EnableAutoInteract;
public bool? EnableAutoInteract = false;
}
private bool _isExternalControlActivated;
private bool _lastCinemaModeState;
private bool _lastInCutsceneState;
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly ICondition _condition;
private readonly IFramework _framework;
private readonly ICallGateSubscriber<bool> _isInExternalControl;
@ -52,12 +45,13 @@ internal sealed class TextAdvanceIpc : IDisposable
private readonly string _pluginName;
public TextAdvanceIpc(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, Configuration configuration, ICondition condition)
private readonly ExternalTerritoryConfig _externalTerritoryConfig = new ExternalTerritoryConfig();
public TextAdvanceIpc(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, Configuration configuration)
{
_framework = framework;
_questController = questController;
_configuration = configuration;
_condition = condition;
_isInExternalControl = pluginInterface.GetIpcSubscriber<bool>("TextAdvance.IsInExternalControl");
_enableExternalControl = pluginInterface.GetIpcSubscriber<string, ExternalTerritoryConfig, bool>("TextAdvance.EnableExternalControl");
_disableExternalControl = pluginInterface.GetIpcSubscriber<string, bool>("TextAdvance.DisableExternalControl");
@ -77,53 +71,16 @@ internal sealed class TextAdvanceIpc : IDisposable
private void OnUpdate(IFramework framework)
{
bool flag = _questController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual;
if (!_configuration.General.ConfigureTextAdvance || !flag)
if (_configuration.General.ConfigureTextAdvance && flag)
{
if (_isExternalControlActivated && (_disableExternalControl.InvokeFunc(_pluginName) || !_isInExternalControl.InvokeFunc()))
if (!_isInExternalControl.InvokeFunc() && _enableExternalControl.InvokeFunc(_pluginName, _externalTerritoryConfig))
{
_isExternalControlActivated = true;
}
}
else if (_isExternalControlActivated && (_disableExternalControl.InvokeFunc(_pluginName) || !_isInExternalControl.InvokeFunc()))
{
_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
};
}
}

View file

@ -22,16 +22,16 @@ internal sealed class AetheryteFunctions
private readonly IDataManager _dataManager;
private readonly IObjectTable _objectTable;
private readonly IClientState _clientState;
public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
public AetheryteFunctions(IServiceProvider serviceProvider, ILogger<AetheryteFunctions> logger, IDataManager dataManager, IObjectTable objectTable)
public AetheryteFunctions(IServiceProvider serviceProvider, ILogger<AetheryteFunctions> logger, IDataManager dataManager, IClientState clientState)
{
_serviceProvider = serviceProvider;
_logger = logger;
_dataManager = dataManager;
_objectTable = objectTable;
_clientState = clientState;
}
public unsafe bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
@ -114,7 +114,7 @@ internal sealed class AetheryteFunctions
public unsafe AetheryteRegistrationResult CanRegisterFreeOrFavoriteAetheryte(EAetheryteLocation aetheryteLocation)
{
if (_objectTable[0] == null)
if (_clientState.LocalPlayer == null)
{
return AetheryteRegistrationResult.NotPossible;
}

Some files were not shown because too many files have changed in this diff Show more