Compare commits

..

1 commit
main ... main

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

3
.gitignore vendored
View file

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

View file

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>FatePaths</AssemblyName>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<TargetFramework>netcoreapp1.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<LangVersion>12.0</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="Questionable.FatePaths.FateDefinitionSchema" />
<EmbeddedResource Include="Questionable.FatePaths.FateDefinitionSchema" LogicalName="Questionable.FatePaths.FateDefinitionSchema" />
</ItemGroup>
<ItemGroup>
<Reference Include="Questionable.Model">
<HintPath>..\..\Questionable.Model.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View file

@ -1,186 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/FatePaths/fatedefinition-v1.json",
"title": "FATE Definition V1",
"description": "A FATE farming definition",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"const": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/FatePaths/fatedefinition-v1.json"
},
"Name": {
"description": "Display name of the FATE",
"type": "string"
},
"Description": {
"description": "Description of the FATE activity",
"type": "string"
},
"TerritoryId": {
"description": "Territory ID where the FATE takes place",
"type": "integer",
"minimum": 1
},
"Aetheryte": {
"description": "Nearest aetheryte for teleporting",
"$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-aetheryte.json"
},
"Position": {
"description": "Position to navigate to for the FATE",
"$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json"
},
"Targets": {
"description": "List of FATE targets and actions to perform on them",
"type": "array",
"items": {
"type": "object",
"properties": {
"DataId": {
"type": "integer",
"minimum": 1
},
"Action": {
"type": "string"
}
},
"required": [
"DataId",
"Action"
],
"additionalProperties": false
},
"minItems": 1
},
"RequiredQuestId": {
"description": "Quest ID that must be completed before this FATE can be farmed",
"type": "integer",
"minimum": 1
},
"RequiredStatusId": {
"description": "Status effect required to participate in the FATE",
"type": "string"
},
"TransformNpcDataId": {
"description": "NPC to interact with to obtain the required status",
"type": "integer",
"minimum": 1
},
"TransformNpcPosition": {
"description": "Position of the transform NPC",
"$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json"
},
"StopAction": {
"description": "Action to use when stopping FATE farming (e.g. to remove a transformation debuff). Only used if the player has RequiredStatusId.",
"type": "string"
},
"EventExpiry": {
"description": "If this is a seasonal/event FATE, the date and time (in UTC) when it is no longer available. Date-only values are treated as ending at daily reset (14:59:59 UTC).",
"format": "date-time",
"type": [
"string",
"null"
]
},
"TransformDialogueChoices": {
"description": "Dialogue choices when interacting with the transform NPC",
"type": "array",
"items": {
"type": "object",
"properties": {
"Type": {
"type": "string",
"enum": [
"YesNo",
"List"
]
},
"ExcelSheet": {
"type": "string"
}
},
"required": [
"Type"
],
"allOf": [
{
"if": {
"properties": {
"Type": {
"const": "YesNo"
}
}
},
"then": {
"properties": {
"Prompt": {
"type": [
"string",
"integer"
]
},
"PromptIsRegularExpression": {
"type": "boolean"
},
"Yes": {
"type": "boolean",
"default": true
}
},
"required": [
"Prompt",
"Yes"
]
}
},
{
"if": {
"properties": {
"Type": {
"const": "List"
}
}
},
"then": {
"properties": {
"Prompt": {
"type": [
"string",
"integer",
"null"
]
},
"PromptIsRegularExpression": {
"type": "boolean"
},
"Answer": {
"type": [
"string",
"integer"
]
},
"AnswerIsRegularExpression": {
"type": "boolean"
}
},
"required": [
"Prompt",
"Answer"
]
}
}
]
}
}
},
"required": [
"$schema",
"Name",
"Description",
"TerritoryId",
"Aetheryte",
"Position",
"Targets"
],
"additionalProperties": false
}

View file

@ -1,105 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Runtime.InteropServices;
using Questionable.Model.Common;
using Questionable.Model.Questing;
namespace Questionable.FatePaths;
public static class AssemblyFateDefinitionLoader
{
private static Dictionary<ushort, FateDefinition>? _definitions;
public static Stream FateDefinitionSchema => typeof(AssemblyFateDefinitionLoader).Assembly.GetManifestResourceStream("Questionable.FatePaths.FateDefinitionSchema");
public static IReadOnlyDictionary<ushort, FateDefinition> GetDefinitions()
{
if (_definitions == null)
{
_definitions = new Dictionary<ushort, FateDefinition>();
LoadDefinitions();
}
return _definitions ?? throw new InvalidOperationException("fate definition data is not initialized");
}
private static void AddDefinition(ushort id, FateDefinition definition)
{
_definitions[id] = definition;
}
private static void LoadDefinitions()
{
LoadDefinition0();
}
private static void LoadDefinition0()
{
FateDefinition obj = new FateDefinition
{
Name = "Little Ladies' Day (2026)",
Description = "Cheer Rhythm FATE in Ul'dah - Steps of Nald",
TerritoryId = 130,
Aetheryte = EAetheryteLocation.Uldah,
Position = new Vector3(-38.322124f, 3.9999998f, -144.23155f)
};
int num = 4;
List<FateActionTarget> list = new List<FateActionTarget>(num);
CollectionsMarshal.SetCount(list, num);
Span<FateActionTarget> span = CollectionsMarshal.AsSpan(list);
span[0] = new FateActionTarget
{
DataId = 18862u,
Action = EAction.CheerRhythmYellow
};
span[1] = new FateActionTarget
{
DataId = 18860u,
Action = EAction.CheerRhythmBlue
};
span[2] = new FateActionTarget
{
DataId = 18861u,
Action = EAction.CheerRhythmGreen
};
span[3] = new FateActionTarget
{
DataId = 18859u,
Action = EAction.CheerRhythmRed
};
obj.Targets = list;
obj.RequiredStatusId = EStatus.FaceInTheCrowd;
obj.TransformNpcDataId = 1055771u;
obj.TransformNpcPosition = new Vector3(-37.369385f, 5.0000005f, -130.14423f);
num = 3;
List<DialogueChoice> list2 = new List<DialogueChoice>(num);
CollectionsMarshal.SetCount(list2, num);
Span<DialogueChoice> span2 = CollectionsMarshal.AsSpan(list2);
span2[0] = new DialogueChoice
{
Type = EDialogChoiceType.List,
ExcelSheet = "custom/009/FesPdy2026FateDisguise_00951",
Prompt = new ExcelRef("TEXT_FESPDY2026FATEDISGUISE_00951_Q1_000_000"),
Answer = new ExcelRef("TEXT_FESPDY2026FATEDISGUISE_00951_A1_000_001")
};
span2[1] = new DialogueChoice
{
Type = EDialogChoiceType.List,
ExcelSheet = "custom/009/FesPdy2026FateDisguise_00951",
Prompt = new ExcelRef("TEXT_FESPDY2026FATEDISGUISE_00951_Q2_000_000"),
Answer = new ExcelRef("TEXT_FESPDY2026FATEDISGUISE_00951_A2_100_004")
};
span2[2] = new DialogueChoice
{
Type = EDialogChoiceType.YesNo,
ExcelSheet = "custom/009/FesPdy2026FateDisguise_00951",
Prompt = new ExcelRef("TEXT_FESPDY2026FATEDISGUISE_00951_CONFIRM_100_004")
};
obj.TransformDialogueChoices = list2;
obj.RequiredQuestId = (ushort)5444;
obj.StopAction = EAction.CurtainCall;
obj.EventExpiry = new DateTime(2026, 3, 12, 14, 59, 0, DateTimeKind.Utc);
AddDefinition(1, 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

@ -1,12 +0,0 @@
namespace LLib.Shop.Model;
public sealed class GrandCompanyItem
{
public required int Index { get; init; }
public required uint ItemId { get; init; }
public required uint IconId { get; init; }
public required uint SealCost { get; init; }
}

View file

@ -1,288 +0,0 @@
using System;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using LLib.Shop.Model;
namespace LLib.Shop;
public sealed class GrandCompanyShop : IDisposable
{
private sealed class GrandCompanyPurchaseRequest
{
public required uint ItemId { get; init; }
public required int RankTabIndex { get; init; }
public required int CategoryTabIndex { get; init; }
}
private const string AddonName = "GrandCompanyExchange";
private const string SelectYesNoAddonName = "SelectYesno";
private readonly IPluginLog _pluginLog;
private readonly IGameGui _gameGui;
private readonly IAddonLifecycle _addonLifecycle;
private int _navigationStep;
private DateTime _lastActionTime = DateTime.MinValue;
private GrandCompanyPurchaseRequest? _pendingPurchase;
public bool IsOpen { get; private set; }
public bool IsPurchaseInProgress => _pendingPurchase != null;
public bool IsAwaitingConfirmation { get; private set; }
public event EventHandler<PurchaseCompletedEventArgs>? PurchaseCompleted;
public event EventHandler? ShopOpened;
public event EventHandler? ShopClosed;
public GrandCompanyShop(IPluginLog pluginLog, IGameGui gameGui, IAddonLifecycle addonLifecycle)
{
_pluginLog = pluginLog;
_gameGui = gameGui;
_addonLifecycle = addonLifecycle;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GrandCompanyExchange", OnShopPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PreFinalize, "GrandCompanyExchange", OnShopPreFinalize);
_addonLifecycle.RegisterListener(AddonEvent.PostUpdate, "SelectYesno", OnSelectYesNoPostUpdate);
}
private void OnShopPostSetup(AddonEvent type, AddonArgs args)
{
IsOpen = true;
_navigationStep = 0;
_pluginLog.Debug("[GCShop] Shop opened");
this.ShopOpened?.Invoke(this, EventArgs.Empty);
}
private void OnShopPreFinalize(AddonEvent type, AddonArgs args)
{
IsOpen = false;
_pendingPurchase = null;
IsAwaitingConfirmation = false;
_pluginLog.Debug("[GCShop] Shop closed");
this.ShopClosed?.Invoke(this, EventArgs.Empty);
}
private unsafe void OnSelectYesNoPostUpdate(AddonEvent type, AddonArgs args)
{
if (_pendingPurchase != null && IsAwaitingConfirmation)
{
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
if (LAddon.IsAddonReady(address))
{
_pluginLog.Information("[GCShop] Confirming purchase dialog");
ClickYes(address);
address->Close(fireCallback: true);
IsAwaitingConfirmation = false;
}
}
}
public bool StartPurchase(uint itemId, int rankTabIndex, int categoryTabIndex)
{
if (!IsOpen || _pendingPurchase != null)
{
return false;
}
_pendingPurchase = new GrandCompanyPurchaseRequest
{
ItemId = itemId,
RankTabIndex = rankTabIndex,
CategoryTabIndex = categoryTabIndex
};
_navigationStep = 0;
_lastActionTime = DateTime.MinValue;
_pluginLog.Information($"[GCShop] Starting purchase of item {itemId}");
return true;
}
public void CancelPurchase()
{
_pendingPurchase = null;
IsAwaitingConfirmation = false;
_navigationStep = 0;
}
public unsafe bool ProcessPurchase()
{
if (_pendingPurchase == null || !IsOpen)
{
return false;
}
if ((DateTime.Now - _lastActionTime).TotalMilliseconds < 600.0)
{
return false;
}
if (InventoryManager.Instance()->GetInventoryItemCount(_pendingPurchase.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0)
{
_pluginLog.Information($"[GCShop] Item {_pendingPurchase.ItemId} already in inventory");
uint itemId = _pendingPurchase.ItemId;
_pendingPurchase = null;
this.PurchaseCompleted?.Invoke(this, new PurchaseCompletedEventArgs(itemId, success: true));
return true;
}
if (!_gameGui.TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonPtr) || !LAddon.IsAddonReady(addonPtr))
{
return false;
}
switch (_navigationStep)
{
case 0:
FireCallback(addonPtr, 1, _pendingPurchase.RankTabIndex);
_navigationStep++;
_lastActionTime = DateTime.Now;
break;
case 1:
FireCallback(addonPtr, 2, _pendingPurchase.CategoryTabIndex);
_navigationStep++;
_lastActionTime = DateTime.Now;
break;
case 2:
{
GrandCompanyItem grandCompanyItem = FindItemInShop(addonPtr, _pendingPurchase.ItemId);
if (grandCompanyItem != null)
{
_pluginLog.Information($"[GCShop] Found item {grandCompanyItem.ItemId} at index {grandCompanyItem.Index}, purchasing...");
FirePurchaseCallback(addonPtr, grandCompanyItem);
IsAwaitingConfirmation = true;
_navigationStep++;
_lastActionTime = DateTime.Now;
}
else
{
_pluginLog.Warning($"[GCShop] Item {_pendingPurchase.ItemId} not found in shop");
_lastActionTime = DateTime.Now;
}
break;
}
case 3:
if (!IsAwaitingConfirmation && InventoryManager.Instance()->GetInventoryItemCount(_pendingPurchase.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0)
{
_pluginLog.Information($"[GCShop] Purchase of {_pendingPurchase.ItemId} completed");
uint itemId2 = _pendingPurchase.ItemId;
_pendingPurchase = null;
_navigationStep = 0;
this.PurchaseCompleted?.Invoke(this, new PurchaseCompletedEventArgs(itemId2, success: true));
return true;
}
break;
}
return false;
}
public unsafe void CloseShop()
{
if (IsOpen && _gameGui.TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonPtr))
{
addonPtr->Close(fireCallback: true);
}
}
public unsafe static int GetCompanySeals()
{
return (int)InventoryManager.Instance()->GetCompanySeals(PlayerState.Instance()->GrandCompany);
}
private unsafe static GrandCompanyItem? FindItemInShop(AtkUnitBase* addon, uint targetItemId)
{
int atkValueInt = GetAtkValueInt(addon, 1);
if (atkValueInt <= 0)
{
return null;
}
for (int i = 0; i < atkValueInt; i++)
{
int atkValueInt2 = GetAtkValueInt(addon, 317 + i);
if (atkValueInt2 == (int)targetItemId)
{
return new GrandCompanyItem
{
Index = i,
ItemId = (uint)atkValueInt2,
IconId = (uint)GetAtkValueInt(addon, 167 + i),
SealCost = (uint)GetAtkValueInt(addon, 67 + i)
};
}
}
return null;
}
private unsafe void FireCallback(AtkUnitBase* addon, params int[] args)
{
AtkValue* ptr = stackalloc AtkValue[args.Length];
for (int i = 0; i < args.Length; i++)
{
ptr[i].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr[i].Int = args[i];
}
addon->FireCallback((uint)args.Length, ptr);
}
private unsafe void FirePurchaseCallback(AtkUnitBase* addon, GrandCompanyItem item)
{
AtkValue* ptr = stackalloc AtkValue[9];
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr->Int = 0;
ptr[1].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr[1].Int = item.Index;
ptr[2].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr[2].Int = 1;
ptr[3].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr[3].Int = 0;
ptr[4].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr[4].Int = 0;
ptr[5].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr[5].Int = 0;
ptr[6].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.UInt;
ptr[6].UInt = item.ItemId;
ptr[7].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.UInt;
ptr[7].UInt = item.IconId;
ptr[8].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.UInt;
ptr[8].UInt = item.SealCost;
addon->FireCallback(9u, ptr);
}
private unsafe static void ClickYes(AtkUnitBase* addon)
{
AtkValue* ptr = stackalloc AtkValue[1];
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr->Int = 0;
addon->FireCallback(1u, ptr);
}
private unsafe static int GetAtkValueInt(AtkUnitBase* addon, int index)
{
if (addon == null || addon->AtkValues == null || index < 0 || index >= addon->AtkValuesCount)
{
return -1;
}
AtkValue atkValue = addon->AtkValues[index];
return atkValue.Type switch
{
FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int => atkValue.Int,
FFXIVClientStructs.FFXIV.Component.GUI.ValueType.UInt => (int)atkValue.UInt,
FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Bool => (atkValue.Byte != 0) ? 1 : 0,
_ => -1,
};
}
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "GrandCompanyExchange", OnShopPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PreFinalize, "GrandCompanyExchange", OnShopPreFinalize);
_addonLifecycle.UnregisterListener(AddonEvent.PostUpdate, "SelectYesno", OnSelectYesNoPostUpdate);
}
}

View file

@ -1,16 +0,0 @@
using System;
namespace LLib.Shop;
public sealed class PurchaseCompletedEventArgs : EventArgs
{
public uint ItemId { get; }
public bool Success { get; }
public PurchaseCompletedEventArgs(uint itemId, bool success)
{
ItemId = itemId;
Success = success;
}
}

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

@ -90,21 +90,6 @@
"description": "The data id of the NPC/Object/Aetheryte/Aether Current",
"exclusiveMinimum": 0
},
"DataIds": {
"type": "array",
"description": "Multiple data ids to search for (e.g. for clearing randomly spawned objects)",
"items": {
"type": "integer",
"exclusiveMinimum": 0
}
},
"WaypointPositions": {
"type": "array",
"description": "Exit/waypoint positions to walk to after clearing all DataIds objects (e.g. doors between zones in a looping duty)",
"items": {
"$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json"
}
},
"Position": {
"$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json"
},
@ -120,9 +105,6 @@
"type": "boolean",
"description": "Most interactions with objects are checked for a Y (height) difference of 2 in-game units. If set to true, the game won't attempt to get any closer if the height difference is larger than this."
},
"IgnoreQuestMarker": {
"type": "boolean"
},
"RestartNavigationIfCancelled": {
"type": "boolean",
"description": "For some specific loading screen transitions (e.g. when entering/leaving the water through the portals in the ruby sea), setting this to 'false' means it won't re-attempt to move to the portal after the loading animation"
@ -171,8 +153,7 @@
"Instruction",
"AcceptQuest",
"CompleteQuest",
"InitiateLeve",
"FateAction"
"InitiateLeve"
]
},
"Disabled": {
@ -238,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": [
@ -279,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
},
@ -307,15 +277,6 @@
]
}
},
"QuestsCompleted": {
"type": "array",
"items": {
"type": [
"number",
"string"
]
}
},
"NotNamePlateIconId": {
"type": "array",
"items": {
@ -603,26 +564,6 @@
"properties": {
"PickUpItemId": {
"type": "number"
},
"GCPurchase": {
"type": "object",
"description": "Configuration for purchasing an item from the Grand Company Exchange shop",
"properties": {
"ItemId": {
"type": "number",
"description": "The item ID to purchase from the GC shop"
},
"RankTab": {
"type": "integer",
"description": "The rank tab index (0-based)"
},
"CategoryTab": {
"type": "integer",
"description": "The category tab index (0-based)"
}
},
"required": ["ItemId"],
"additionalProperties": false
}
},
"required": [
@ -639,9 +580,8 @@
}
},
"then": {
"anyOf": [
{ "required": ["Position"] },
{ "required": ["DataId"] }
"required": [
"Position"
]
}
},
@ -810,34 +750,6 @@
"additionalProperties": false
}
},
"FateActionTargets": {
"description": "List of targets with per-target actions for FateAction steps",
"type": "array",
"items": {
"type": "object",
"properties": {
"DataId": {
"description": "The NPC data id to use the action on",
"type": "integer"
},
"Action": {
"description": "The action to use on this target",
"type": "string",
"enum": [
"Cheer Rhythm: Red",
"Cheer Rhythm: Blue",
"Cheer Rhythm: Green",
"Cheer Rhythm: Yellow"
]
}
},
"required": [
"DataId",
"Action"
],
"additionalProperties": false
}
},
"CombatItemUse": {
"description": "Unlike the 'AfterItemUse' condition that is used for spawning an enemy in the first place, interacting with an item at a certain stage of combat is required",
"type": "object",
@ -1428,14 +1340,7 @@
"Shrouded Luminescence",
"Big Sneeze",
"Trickster's Treat",
"Treater's Trick",
"Cheer Rhythm: Red",
"Cheer Rhythm: Blue",
"Cheer Rhythm: Green",
"Cheer Rhythm: Yellow",
"Pruning Pirouette",
"Roaring Eggscapade",
"The Spriganator"
"Treater's Trick"
]
}
},
@ -1444,20 +1349,6 @@
]
}
},
{
"if": {
"properties": {
"InteractionType": {
"const": "FateAction"
}
}
},
"then": {
"required": [
"FateActionTargets"
]
}
},
{
"if": {
"properties": {
@ -1695,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+$"
@ -1719,8 +1605,7 @@
"required": [
"ContentFinderConditionId",
"Enabled"
],
"additionalProperties": false
]
},
"DataId": {
"type": "null"
@ -1878,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,24 +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);
CollectionsMarshal.AsSpan(list)[0] = 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,40 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Questionable.Model.Common.Converter;
public sealed class VectorListConverter : JsonConverter<List<Vector3>>
{
private static readonly VectorConverter ItemConverter = new VectorConverter();
public override List<Vector3> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray)
{
throw new JsonException();
}
List<Vector3> list = new List<Vector3>();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
{
return list;
}
list.Add(ItemConverter.Read(ref reader, typeof(Vector3), options));
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, List<Vector3> value, JsonSerializerOptions options)
{
writer.WriteStartArray();
foreach (Vector3 item in value)
{
ItemConverter.Write(writer, item, options);
}
writer.WriteEndArray();
}
}

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

@ -202,38 +202,6 @@ public sealed class ActionConverter : EnumConverter<EAction>
{
EAction.TreatersTrick,
"Treater's Trick"
},
{
EAction.CheerRhythmRed,
"Cheer Rhythm: Red"
},
{
EAction.CheerRhythmBlue,
"Cheer Rhythm: Blue"
},
{
EAction.CheerRhythmGreen,
"Cheer Rhythm: Green"
},
{
EAction.CheerRhythmYellow,
"Cheer Rhythm: Yellow"
},
{
EAction.PruningPirouette,
"Pruning Pirouette"
},
{
EAction.RoaringEggscapade,
"Roaring Eggscapade"
},
{
EAction.TheSpriganator,
"The Spriganator"
},
{
EAction.CurtainCall,
"Curtain Call"
}
};

View file

@ -126,10 +126,6 @@ public sealed class InteractionTypeConverter : EnumConverter<EInteractionType>
{
EInteractionType.CompleteQuest,
"CompleteQuest"
},
{
EInteractionType.FateAction,
"FateAction"
}
};

View file

@ -5,17 +5,11 @@ namespace Questionable.Model.Questing.Converter;
public sealed class StatusConverter : EnumConverter<EStatus>
{
private static readonly Dictionary<EStatus, string> Values = new Dictionary<EStatus, string>
private static readonly Dictionary<EStatus, string> Values = new Dictionary<EStatus, string> {
{
{
EStatus.Hidden,
"Hidden"
},
{
EStatus.FaceInTheCrowd,
"FaceInTheCrowd"
}
};
EStatus.Hidden,
"Hidden"
} };
public StatusConverter()
: base((IReadOnlyDictionary<EStatus, string>)Values)

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

@ -62,14 +62,6 @@ public enum EAction
BigSneeze = 1765,
TrickstersTreat = 44517,
TreatersTrick = 44518,
CheerRhythmRed = 44501,
CheerRhythmBlue = 44502,
CheerRhythmGreen = 44503,
CheerRhythmYellow = 44504,
PruningPirouette = 45127,
RoaringEggscapade = 42039,
TheSpriganator = 42038,
CurtainCall = 11063,
Prospect = 227,
CollectMiner = 240,
LuckOfTheMountaineer = 4081,

View file

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

View file

@ -35,6 +35,5 @@ public enum EInteractionType
UnlockTaxiStand,
Instruction,
AcceptQuest,
CompleteQuest,
FateAction
CompleteQuest
}

View file

@ -10,7 +10,6 @@ public enum EStatus : uint
GatheringRateUp = 218u,
Prospect = 225u,
Hidden = 614u,
FaceInTheCrowd = 1494u,
Eukrasia = 2606u,
Jog = 4209u
}

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

@ -1,8 +0,0 @@
namespace Questionable.Model.Questing;
public sealed class FateActionTarget
{
public uint DataId { get; set; }
public EAction Action { get; set; }
}

View file

@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text.Json.Serialization;
using Questionable.Model.Common;
using Questionable.Model.Common.Converter;
namespace Questionable.Model.Questing;
public sealed class FateDefinition
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public ushort TerritoryId { get; set; }
public EAetheryteLocation Aetheryte { get; set; }
[JsonConverter(typeof(VectorConverter))]
public Vector3 Position { get; set; }
public List<FateActionTarget> Targets { get; set; } = new List<FateActionTarget>();
public EStatus? RequiredStatusId { get; set; }
public uint? TransformNpcDataId { get; set; }
[JsonConverter(typeof(VectorConverter))]
public Vector3? TransformNpcPosition { get; set; }
public List<DialogueChoice>? TransformDialogueChoices { get; set; }
public ushort? RequiredQuestId { get; set; }
public EAction? StopAction { get; set; }
public DateTime? EventExpiry { get; set; }
}

View file

@ -1,10 +0,0 @@
namespace Questionable.Model.Questing;
public sealed class GCPurchaseInfo
{
public uint ItemId { get; set; }
public int RankTab { get; set; }
public int CategoryTab { get; set; }
}

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

@ -15,11 +15,6 @@ public sealed class QuestStep
public uint? DataId { get; set; }
public List<uint> DataIds { get; set; } = new List<uint>();
[JsonConverter(typeof(VectorListConverter))]
public List<Vector3> WaypointPositions { get; set; } = new List<Vector3>();
[JsonConverter(typeof(VectorConverter))]
public Vector3? Position { get; set; }
@ -27,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; }
@ -54,8 +49,6 @@ public sealed class QuestStep
public bool? RestartNavigationIfCancelled { get; set; }
public bool IgnoreQuestMarker { get; set; }
public string? Comment { get; set; }
public EAetheryteLocation? Aetheryte { get; set; }
@ -75,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; }
@ -95,8 +86,6 @@ public sealed class QuestStep
public List<ComplexCombatData> ComplexCombatData { get; set; } = new List<ComplexCombatData>();
public List<FateActionTarget> FateActionTargets { get; set; } = new List<FateActionTarget>();
public CombatItemUse? CombatItemUse { get; set; }
public float? CombatDelaySecondsAtStart { get; set; }
@ -127,8 +116,6 @@ public sealed class QuestStep
public PurchaseMenu? PurchaseMenu { get; set; }
public GCPurchaseInfo? GCPurchase { get; set; }
[JsonConverter(typeof(ElementIdConverter))]
public ElementId? PickUpQuestId { 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

@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text.Json.Serialization;
using Questionable.Model.Common;
using Questionable.Model.Common.Converter;
using Questionable.Model.Questing.Converter;
namespace Questionable.Model.Questing;
public sealed class SeasonalDutyDefinition
{
public string Name { get; set; } = string.Empty;
public ushort TerritoryId { get; set; }
public EAetheryteLocation Aetheryte { get; set; }
public AethernetShortcut? AethernetShortcut { get; set; }
public uint NpcDataId { get; set; }
[JsonConverter(typeof(VectorConverter))]
public Vector3 NpcPosition { get; set; }
public List<DialogueChoice> DialogueChoices { get; set; } = new List<DialogueChoice>();
public ushort DutyTerritoryId { get; set; }
public List<uint> DataIds { get; set; } = new List<uint>();
[JsonConverter(typeof(VectorListConverter))]
public List<Vector3> WaypointPositions { get; set; } = new List<Vector3>();
[JsonConverter(typeof(ActionConverter))]
public EAction Action { get; set; }
public float StopDistance { get; set; }
public ushort? RequiredQuestId { get; set; }
public DateTime? EventExpiry { get; set; }
}

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,28 +0,0 @@
using Questionable.Model.Questing;
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)
{
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null)
{
_questSequenceWindow.OpenForQuest(elementId);
}
else
{
_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

@ -1,63 +0,0 @@
using System;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
using FFXIVClientStructs.FFXIV.Common.Lua;
using Microsoft.Extensions.Logging;
namespace Questionable.Controller.GameUi;
internal sealed class AutoSnipeHandler : IDisposable
{
private unsafe delegate ulong EnqueueSnipeTaskDelegate(EventSceneModuleImplBase* scene, lua_State* state);
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly ILogger<AutoSnipeHandler> _logger;
private readonly Hook<EnqueueSnipeTaskDelegate>? _enqueueSnipeTaskHook;
public unsafe AutoSnipeHandler(QuestController questController, Configuration configuration, IGameInteropProvider gameInteropProvider, ILogger<AutoSnipeHandler> logger)
{
_questController = questController;
_configuration = configuration;
_logger = logger;
try
{
_enqueueSnipeTaskHook = gameInteropProvider.HookFromSignature<EnqueueSnipeTaskDelegate>("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 50 48 8B F9 48 8D 4C 24 ??", EnqueueSnipeTask);
_enqueueSnipeTaskHook.Enable();
_logger.LogInformation("AutoSnipeHandler enabled");
}
catch (Exception exception)
{
_logger.LogError(exception, "Failed to initialize AutoSnipeHandler");
_enqueueSnipeTaskHook = null;
}
}
private unsafe ulong EnqueueSnipeTask(EventSceneModuleImplBase* scene, lua_State* state)
{
if (_configuration.General.AutoSnipe && _questController.IsRunning)
{
_logger.LogDebug("Auto-completing snipe task");
TValue* top = state->top;
top->tt = 3;
top->value.n = 1.0;
state->top++;
return 1uL;
}
return _enqueueSnipeTaskHook.Original(scene, state);
}
public void Dispose()
{
if (_enqueueSnipeTaskHook != null)
{
_enqueueSnipeTaskHook.Disable();
_enqueueSnipeTaskHook.Dispose();
_logger.LogInformation("AutoSnipeHandler disabled");
}
}
}

View file

@ -1,149 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Model.Questing;
namespace Questionable.Controller.GameUi;
internal sealed class ChocoboNameHandler : IDisposable
{
private static readonly HashSet<ElementId> ChocoboQuestIds = new HashSet<ElementId>
{
new QuestId(700),
new QuestId(701),
new QuestId(702)
};
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly IAddonLifecycle _addonLifecycle;
private readonly ILogger<ChocoboNameHandler> _logger;
private bool _awaitingConfirmation;
public ChocoboNameHandler(QuestController questController, Configuration configuration, IAddonLifecycle addonLifecycle, ILogger<ChocoboNameHandler> logger)
{
_questController = questController;
_configuration = configuration;
_addonLifecycle = addonLifecycle;
_logger = logger;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "InputString", OnInputStringSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", OnSelectYesNoSetup);
}
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", OnSelectYesNoSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "InputString", OnInputStringSetup);
}
private bool IsChocoboQuestActive()
{
QuestController.QuestProgress currentQuest = _questController.CurrentQuest;
if (currentQuest != null)
{
return ChocoboQuestIds.Contains(currentQuest.Quest.Id);
}
return false;
}
private unsafe void OnInputStringSetup(AddonEvent type, AddonArgs args)
{
if (_questController.IsRunning && IsChocoboQuestActive())
{
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
if (LAddon.IsAddonReady(address))
{
string validatedChocoboName = GetValidatedChocoboName(_configuration.Advanced.ChocoboName);
_logger.LogInformation("Entering chocobo name: {Name}", validatedChocoboName);
FireInputStringCallback(address, validatedChocoboName);
_awaitingConfirmation = true;
}
}
}
private unsafe void OnSelectYesNoSetup(AddonEvent type, AddonArgs args)
{
if (_questController.IsRunning && _awaitingConfirmation)
{
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
if (LAddon.IsAddonReady(address))
{
_logger.LogInformation("Confirming chocobo name");
ClickYes(address);
_awaitingConfirmation = false;
}
}
}
private static string GetValidatedChocoboName(string? configuredName)
{
if (string.IsNullOrWhiteSpace(configuredName))
{
return "Kweh";
}
string text = configuredName.Trim();
if (text.Length < 2 || text.Length > 20)
{
return "Kweh";
}
string text2 = text.Substring(0, 1).ToUpperInvariant();
string text3;
if (text.Length <= 1)
{
text3 = string.Empty;
}
else
{
string text4 = text;
text3 = text4.Substring(1, text4.Length - 1).ToLowerInvariant();
}
return text2 + text3;
}
private unsafe void FireInputStringCallback(AtkUnitBase* addon, string text)
{
AtkComponentNode* componentNodeById = addon->GetComponentNodeById(9u);
if (componentNodeById == null)
{
_logger.LogWarning("Could not find text input node in InputString addon");
return;
}
AtkComponentTextInput* asAtkComponentTextInput = componentNodeById->GetAsAtkComponentTextInput();
if (asAtkComponentTextInput == null)
{
_logger.LogWarning("Node is not a text input component");
return;
}
asAtkComponentTextInput->SetText(text);
AtkValue* ptr = stackalloc AtkValue[2];
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr->Int = 0;
byte[] bytes = Encoding.UTF8.GetBytes(text);
nint num = Marshal.AllocHGlobal(bytes.Length + 1);
Marshal.Copy(bytes, 0, num, bytes.Length);
Marshal.WriteByte(num + bytes.Length, 0);
ptr[1].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.String;
ptr[1].String = (byte*)num;
addon->FireCallback(2u, ptr);
Marshal.FreeHGlobal(num);
}
private unsafe static void ClickYes(AtkUnitBase* addon)
{
AtkValue* ptr = stackalloc AtkValue[1];
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr->Int = 0;
addon->FireCallback(1u, ptr);
}
}

View file

@ -97,12 +97,12 @@ internal sealed class CraftworksSupplyController : IDisposable
{
return;
}
ushort blockedParentId = address->BlockedParentId;
if (blockedParentId == 0)
ushort contextMenuParentId = address->ContextMenuParentId;
if (contextMenuParentId == 0)
{
return;
}
AtkUnitBase* addonById = AtkStage.Instance()->RaptureAtkUnitManager->GetAddonById(blockedParentId);
AtkUnitBase* addonById = AtkStage.Instance()->RaptureAtkUnitManager->GetAddonById(contextMenuParentId);
if (addonById->NameString == "BankaCraftworksSupply")
{
_logger.LogInformation("Picking item for {AddonName}", addonById->NameString);
@ -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
if (_registeredLifecycle != null)
{
_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;
}
}
_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

@ -1,152 +0,0 @@
using System;
using Dalamud.Plugin.Services;
using LLib.Shop;
using Microsoft.Extensions.Logging;
using Questionable.Model.Questing;
namespace Questionable.Controller.GameUi;
internal sealed class GCShopHandler : IDisposable
{
private enum GCShopState
{
Inactive,
Purchasing,
Completed
}
private readonly QuestController _questController;
private readonly GrandCompanyShop _gcShop;
private readonly IObjectTable _objectTable;
private readonly IFramework _framework;
private readonly ILogger<GCShopHandler> _logger;
private GCShopState _state;
private GCPurchaseInfo? _currentPurchase;
private DateTime _lastActionTime = DateTime.MinValue;
private bool _purchaseCompletedThisStep;
public bool IsActive => _state != GCShopState.Inactive;
public GCShopHandler(QuestController questController, GrandCompanyShop gcShop, IObjectTable objectTable, IFramework framework, ILogger<GCShopHandler> logger)
{
_questController = questController;
_gcShop = gcShop;
_objectTable = objectTable;
_framework = framework;
_logger = logger;
_gcShop.PurchaseCompleted += OnPurchaseCompleted;
_framework.Update += OnFrameworkUpdate;
}
public void Dispose()
{
_framework.Update -= OnFrameworkUpdate;
_gcShop.PurchaseCompleted -= OnPurchaseCompleted;
}
private void OnFrameworkUpdate(IFramework framework)
{
if (_objectTable.LocalPlayer == null)
{
return;
}
if (!_questController.IsRunning)
{
if (_state != GCShopState.Inactive)
{
Reset();
}
return;
}
CheckForGCPurchase();
if (_state == GCShopState.Purchasing)
{
HandlePurchasing();
}
else if (_state == GCShopState.Completed)
{
_logger.LogInformation("GC shop purchase completed!");
_state = GCShopState.Inactive;
_lastActionTime = DateTime.MinValue;
}
}
private void CheckForGCPurchase()
{
QuestController.QuestProgress currentQuest = _questController.CurrentQuest;
if (currentQuest == null)
{
if (_currentPurchase != null)
{
Reset();
}
return;
}
GCPurchaseInfo gCPurchaseInfo = (currentQuest.Quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step))?.GCPurchase;
if (gCPurchaseInfo == null)
{
if (_currentPurchase != null)
{
_currentPurchase = null;
_purchaseCompletedThisStep = false;
}
if (_gcShop.IsOpen)
{
_logger.LogDebug("GC shop is open but current step has no GCPurchase. Quest={QuestId}, Seq={Sequence}, Step={Step}", currentQuest.Quest.Id, currentQuest.Sequence, currentQuest.Step);
}
}
else
{
if (_currentPurchase != gCPurchaseInfo)
{
_currentPurchase = gCPurchaseInfo;
_purchaseCompletedThisStep = false;
}
if (_gcShop.IsOpen && _state == GCShopState.Inactive && !_purchaseCompletedThisStep)
{
_logger.LogInformation("GC shop is open. Starting purchase of item {ItemId}.", gCPurchaseInfo.ItemId);
_state = GCShopState.Purchasing;
}
}
}
private void HandlePurchasing()
{
if (_gcShop.IsOpen && _currentPurchase != null && !((DateTime.Now - _lastActionTime).TotalMilliseconds < 500.0))
{
if (!_gcShop.IsPurchaseInProgress)
{
_gcShop.StartPurchase(_currentPurchase.ItemId, _currentPurchase.RankTab, _currentPurchase.CategoryTab);
_lastActionTime = DateTime.Now;
}
_gcShop.ProcessPurchase();
}
}
private void OnPurchaseCompleted(object? sender, PurchaseCompletedEventArgs e)
{
if (_state == GCShopState.Purchasing && _currentPurchase != null && _questController.IsRunning && e.Success && e.ItemId == _currentPurchase.ItemId)
{
_logger.LogInformation("GC purchase of item {ItemId} completed successfully", e.ItemId);
_gcShop.CloseShop();
_purchaseCompletedThisStep = true;
_state = GCShopState.Completed;
}
}
private void Reset()
{
_state = GCShopState.Inactive;
_currentPurchase = null;
_lastActionTime = DateTime.MinValue;
_purchaseCompletedThisStep = false;
}
}

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,14 +58,8 @@ internal sealed class InteractionUiController : IDisposable
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly ShopController _shopController;
private readonly FateController _fateController;
private readonly SeasonalDutyController _seasonalDutyController;
private readonly BossModIpc _bossModIpc;
private readonly Configuration _configuration;
@ -81,7 +76,7 @@ internal sealed class InteractionUiController : IDisposable
{
get
{
if (!_isInitialCheck && !_questController.IsRunning && !_fateController.IsRunning && !_seasonalDutyController.IsRunning)
if (!_isInitialCheck && !_questController.IsRunning)
{
return _territoryData.IsQuestBattleInstance(_clientState.TerritoryType);
}
@ -89,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, FateController fateController, SeasonalDutyController seasonalDutyController, 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;
@ -104,10 +99,7 @@ internal sealed class InteractionUiController : IDisposable
_gameGui = gameGui;
_targetManager = targetManager;
_clientState = clientState;
_objectTable = objectTable;
_shopController = shopController;
_fateController = fateController;
_seasonalDutyController = seasonalDutyController;
_bossModIpc = bossModIpc;
_configuration = configuration;
_logger = logger;
@ -121,7 +113,6 @@ internal sealed class InteractionUiController : IDisposable
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "DifficultySelectYesNo", DifficultySelectYesNoPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "EasterMowingResult", EasterMowingResultPostSetup);
if (_gameGui.TryGetAddonByName<AtkUnitBase>("RhythmAction", out var addonPtr))
{
addonPtr->Close(fireCallback: true);
@ -373,7 +364,9 @@ internal sealed class InteractionUiController : IDisposable
int num = 1;
List<EAetheryteLocation> list2 = new List<EAetheryteLocation>(num);
CollectionsMarshal.SetCount(list2, num);
CollectionsMarshal.AsSpan(list2)[0] = valueOrDefault;
Span<EAetheryteLocation> span = CollectionsMarshal.AsSpan(list2);
int index = 0;
span[index] = valueOrDefault;
source = list2;
}
}
@ -459,24 +452,6 @@ internal sealed class InteractionUiController : IDisposable
}
}
}
if (_fateController.IsRunning)
{
List<DialogueChoice> list4 = _fateController.CurrentFate?.TransformDialogueChoices;
if (list4 != null)
{
_logger.LogInformation("Adding {Count} dialogue choices from active FATE", list4.Count);
list.AddRange(list4.Select((DialogueChoice x) => new DialogueChoiceInfo(null, x)));
}
}
if (_seasonalDutyController.IsRunning)
{
List<DialogueChoice> list5 = _seasonalDutyController.CurrentDuty?.DialogueChoices;
if (list5 != null && list5.Count > 0)
{
_logger.LogInformation("Adding {Count} dialogue choices from active seasonal duty", list5.Count);
list.AddRange(list5.Select((DialogueChoice x) => new DialogueChoiceInfo(null, x)));
}
}
if (list.Count == 0)
{
_logger.LogDebug("No dialogue choices to check");
@ -664,55 +639,12 @@ internal sealed class InteractionUiController : IDisposable
return;
}
QuestController.QuestProgress simulatedQuest = _questController.SimulatedQuest;
if (simulatedQuest != null && HandleTravelYesNo(addonSelectYesno, simulatedQuest, text))
if (simulatedQuest == null || !HandleTravelYesNo(addonSelectYesno, simulatedQuest, text))
{
return;
}
QuestController.QuestProgress nextQuest = _questController.NextQuest;
if (nextQuest != null && CheckQuestYesNo(addonSelectYesno, nextQuest, text, checkAllSteps))
{
return;
}
if (_fateController.IsRunning)
{
List<DialogueChoice> list = _fateController.CurrentFate?.TransformDialogueChoices;
if (list != null)
QuestController.QuestProgress nextQuest = _questController.NextQuest;
if (nextQuest != null)
{
foreach (DialogueChoice item in list)
{
if (item.Type == EDialogChoiceType.YesNo)
{
StringOrRegex stringOrRegex = ResolveReference(null, item.ExcelSheet, item.Prompt, item.PromptIsRegularExpression);
if (stringOrRegex != null && IsMatch(text, stringOrRegex))
{
_logger.LogInformation("FATE: Returning {YesNo} for '{Prompt}'", item.Yes ? "Yes" : "No", text);
addonSelectYesno->AtkUnitBase.FireCallbackInt((!item.Yes) ? 1 : 0);
return;
}
}
}
}
}
if (!_seasonalDutyController.IsRunning)
{
return;
}
List<DialogueChoice> list2 = _seasonalDutyController.CurrentDuty?.DialogueChoices;
if (list2 == null || list2.Count <= 0)
{
return;
}
foreach (DialogueChoice item2 in list2)
{
if (item2.Type == EDialogChoiceType.YesNo)
{
StringOrRegex stringOrRegex2 = ResolveReference(null, item2.ExcelSheet, item2.Prompt, item2.PromptIsRegularExpression);
if (stringOrRegex2 != null && IsMatch(text, stringOrRegex2))
{
_logger.LogInformation("Seasonal duty: Returning {YesNo} for '{Prompt}'", item2.Yes ? "Yes" : "No", text);
addonSelectYesno->AtkUnitBase.FireCallbackInt((!item2.Yes) ? 1 : 0);
break;
}
CheckQuestYesNo(addonSelectYesno, nextQuest, text, checkAllSteps);
}
}
}
@ -751,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))
{
@ -760,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",
@ -783,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
@ -924,18 +857,14 @@ 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)
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))
{
IGameObject gameObject = _objectTable[0];
if (_gatheringPointRegistry.TryGetGatheringPointId(questStep.ItemsToGather[0].ItemId, ((EClassJob?)(gameObject as ICharacter)?.ClassJob.RowId).GetValueOrDefault(), out GatheringPointId gatheringPointId) && _gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot gatheringRoot))
foreach (QuestStep step in gatheringRoot.Steps)
{
foreach (QuestStep step in gatheringRoot.Steps)
if (step.TerritoryId == _clientState.TerritoryType && step.TargetTerritoryId.HasValue)
{
if (step.TerritoryId == _clientState.TerritoryType && step.TargetTerritoryId.HasValue)
{
_logger.LogTrace("FindTargetTerritoryFromQuestStep (gathering): {CurrentTerritory}, {TargetTerritory}", step.TerritoryId, step.TargetTerritoryId);
return step.TargetTerritoryId;
}
_logger.LogTrace("FindTargetTerritoryFromQuestStep (gathering): {CurrentTerritory}, {TargetTerritory}", step.TerritoryId, step.TargetTerritoryId);
return step.TargetTerritoryId;
}
}
}
@ -1052,35 +981,6 @@ internal sealed class InteractionUiController : IDisposable
}
}
private unsafe void EasterMowingResultPostSetup(AddonEvent type, AddonArgs args)
{
if (_seasonalDutyController.IsRunning || ShouldHandleUiInteractions)
{
_logger.LogInformation("Dismissing EasterMowingResult");
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
AtkValue* values = stackalloc AtkValue[3]
{
new AtkValue
{
Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int,
Int = -1
},
new AtkValue
{
Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int,
Int = 1
},
new AtkValue
{
Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int,
Int = 1
}
};
address->FireCallback(3u, values);
address->Close(fireCallback: true);
}
}
private StringOrRegex? ResolveReference(Questionable.Model.Quest? quest, string? excelSheet, ExcelRef? excelRef, bool isRegExp)
{
if (excelRef == null)
@ -1104,7 +1004,6 @@ internal sealed class InteractionUiController : IDisposable
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "EasterMowingResult", EasterMowingResultPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "DifficultySelectYesNo", DifficultySelectYesNoPostSetup);

View file

@ -1,113 +0,0 @@
using System;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
using FFXIVClientStructs.Interop;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Utils;
namespace Questionable.Controller.GameUi;
internal sealed class QteController : IDisposable
{
private readonly Configuration _configuration;
private readonly IGameGui _gameGui;
private readonly IGameConfig _gameConfig;
private readonly IFramework _framework;
private readonly ILogger<QteController> _logger;
private long _throttler = Environment.TickCount64;
private readonly Random _random = new Random();
private bool _hasDirectChat;
private bool _directChatWasEnabled;
public QteController(Configuration configuration, IGameGui gameGui, IGameConfig gameConfig, IFramework framework, ILogger<QteController> logger)
{
_configuration = configuration;
_gameGui = gameGui;
_gameConfig = gameConfig;
_framework = framework;
_logger = logger;
_framework.Update += OnUpdate;
}
private unsafe void OnUpdate(IFramework framework)
{
AtkUnitBase* addonPtr;
if (!_configuration.General.AutoSolveQte)
{
EnableDirectChatIfNeeded();
}
else if (_gameGui.TryGetAddonByName<AtkUnitBase>("QTE", out addonPtr) && LAddon.IsAddonReady(addonPtr) && addonPtr->IsVisible)
{
DisableDirectChatIfNeeded();
if (Environment.TickCount64 >= _throttler)
{
if (IsChatLogFocused())
{
WindowsKeypress.SendKeypress(WindowsKeypress.LimitedKeys.Escape);
}
WindowsKeypress.SendKeypress(WindowsKeypress.LimitedKeys.A);
_throttler = Environment.TickCount64 + _random.Next(25, 50);
}
}
else
{
EnableDirectChatIfNeeded();
}
}
private unsafe bool IsChatLogFocused()
{
AtkStage* ptr = AtkStage.Instance();
if (ptr == null)
{
return false;
}
Span<Pointer<AtkUnitBase>> entries = ptr->RaptureAtkUnitManager->AtkUnitManager.FocusedUnitsList.Entries;
for (int i = 0; i < entries.Length; i++)
{
Pointer<AtkUnitBase> pointer = entries[i];
if (pointer.Value != null && pointer.Value->NameString == "ChatLog")
{
return true;
}
}
return false;
}
private void EnableDirectChatIfNeeded()
{
if (_hasDirectChat)
{
_gameConfig.UiControl.Set("DirectChat", _directChatWasEnabled);
_hasDirectChat = false;
}
}
private void DisableDirectChatIfNeeded()
{
if (!_hasDirectChat && _gameConfig.UiControl.TryGetBool("DirectChat", out var value))
{
_directChatWasEnabled = value;
if (value)
{
_gameConfig.UiControl.Set("DirectChat", value: false);
}
_hasDirectChat = true;
}
}
public void Dispose()
{
_framework.Update -= OnUpdate;
EnableDirectChatIfNeeded();
}
}

View file

@ -54,37 +54,68 @@ internal sealed class MovementOverrideController
List<IBlacklistedLocation> list = new List<IBlacklistedLocation>(num);
CollectionsMarshal.SetCount(list, num);
Span<IBlacklistedLocation> span = CollectionsMarshal.AsSpan(list);
span[0] = new BlacklistedArea(1191, new Vector3(-223.0412f, 31.937134f, -584.03906f), 5f, 7.75f);
span[1] = new BlacklistedPoint(128, new Vector3(2f, 40.25f, 36.5f), new Vector3(0.25f, 40.25f, 36.5f));
span[2] = new BlacklistedPoint(132, new Vector3(29f, -8f, 120.5f), new Vector3(28.265165f, -8.000001f, 120.149734f));
span[3] = new BlacklistedPoint(132, new Vector3(28.25f, -8f, 125f), new Vector3(27.372725f, -8.200001f, 125.55859f));
span[4] = new BlacklistedPoint(132, new Vector3(32.25f, -8f, 126.5f), new Vector3(32.022232f, -8.200011f, 126.86095f));
span[5] = new BlacklistedPoint(205, new Vector3(26.75f, 0.5f, 20.75f), new Vector3(27.179117f, 0.26728272f, 19.714373f));
span[6] = new BlacklistedPoint(130, new Vector3(59.5f, 4.25f, -118f), new Vector3(60.551353f, 4f, -119.76446f));
span[7] = new BlacklistedPoint(145, new Vector3(-139.75f, -32.25f, 75.25f), new Vector3(-139.57748f, -33.785175f, 77.87906f));
span[8] = new BlacklistedPoint(146, new Vector3(-201.75f, 10.5f, -265.5f), new Vector3(-203.75235f, 10.130764f, -265.15314f));
span[9] = new BlacklistedArea(135, new Vector3(156.11499f, 15.518433f, 673.21277f), 0.5f, 5f);
span[10] = new BlacklistedPoint(139, new Vector3(366f, -2.5f, 95.5f), new Vector3(362.65973f, -3.4f, 96.6896f), 2f);
span[11] = new BlacklistedPoint(155, new Vector3(-478.75f, 149.25f, -305.75f), new Vector3(-476.1802f, 149.06573f, -304.7811f));
span[12] = new BlacklistedPoint(351, new Vector3(3.25f, 0.75f, 8.5f), new Vector3(4f, 0f, 9.5f));
span[13] = new BlacklistedPoint(418, new Vector3(-136.75f, 2.75f, 9f), new Vector3(-138.66408f, 2.0333426f, 8.860787f), 1f);
span[14] = new BlacklistedPoint(401, new Vector3(-14.75f, -136.75f, 515.75f), new Vector3(-17.631899f, -137.39148f, 512.6676f), 2f);
span[15] = new BlacklistedPoint(397, new Vector3(-93.75f, 87.75f, -715.5f), new Vector3(-87.78183f, 87.188995f, -713.3343f), 2f);
span[16] = new BlacklistedPoint(400, new Vector3(384f, -74f, 648.75f), new Vector3(386.0543f, -72.409454f, 652.0184f), 3f);
span[17] = new BlacklistedPoint(399, new Vector3(-514.4851f, 149.63762f, -480.58087f), new Vector3(-528.78656f, 151.17374f, -473.07077f), 5f, RecalculateNavmesh: true);
span[18] = new BlacklistedPoint(399, new Vector3(-534.5f, 153f, -476.75f), new Vector3(-528.78656f, 151.17374f, -473.07077f), 5f, RecalculateNavmesh: true);
span[19] = new BlacklistedPoint(478, new Vector3(14.5f, 215.25f, -101.5f), new Vector3(18.133032f, 215.44998f, -107.83075f), 5f);
span[20] = new BlacklistedPoint(478, new Vector3(11f, 215.5f, -104.5f), new Vector3(18.133032f, 215.44998f, -107.83075f), 5f);
span[21] = new BlacklistedPoint(1189, new Vector3(574f, -142.25f, 504.25f), new Vector3(574.44183f, -142.12766f, 507.60065f));
span[22] = new BlacklistedPoint(814, new Vector3(-324f, 348.75f, -181.75f), new Vector3(-322.75076f, 347.0529f, -177.69328f), 3f);
span[23] = new BlacklistedPoint(956, new Vector3(6.25f, -27.75f, -41.5f), new Vector3(5.0831127f, -28.213453f, -42.239136f));
span[24] = new BlacklistedPoint(1189, new Vector3(-115.75f, -213.75f, 336.5f), new Vector3(-112.40265f, -215.01514f, 339.0067f), 2f);
span[25] = new BlacklistedPoint(1190, new Vector3(-292.29004f, 18.598045f, -133.83907f), new Vector3(-288.20895f, 18.652182f, -132.67445f), 4f);
span[26] = new BlacklistedPoint(1191, new Vector3(-108f, 29.25f, -350.75f), new Vector3(-107.56289f, 29.008266f, -348.80087f));
span[27] = new BlacklistedPoint(1191, new Vector3(-105.75f, 29.75f, -351f), new Vector3(-105.335304f, 29.017048f, -348.85077f));
span[28] = new BlacklistedPoint(1186, new Vector3(284.25f, 50.75f, 171.25f), new Vector3(284.25f, 50.75f, 166.25f));
span[29] = new BlacklistedPoint(1186, new Vector3(283.75f, 50.75f, 167.25f), new Vector3(284.25f, 50.75f, 166.25f));
span[30] = new BlacklistedPoint(1186, new Vector3(287.75f, 51.25f, 172f), new Vector3(288.875f, 50.75f, 166.25f));
int num2 = 0;
span[num2] = new BlacklistedArea(1191, new Vector3(-223.0412f, 31.937134f, -584.03906f), 5f, 7.75f);
num2++;
span[num2] = new BlacklistedPoint(128, new Vector3(2f, 40.25f, 36.5f), new Vector3(0.25f, 40.25f, 36.5f));
num2++;
span[num2] = new BlacklistedPoint(132, new Vector3(29f, -8f, 120.5f), new Vector3(28.265165f, -8.000001f, 120.149734f));
num2++;
span[num2] = new BlacklistedPoint(132, new Vector3(28.25f, -8f, 125f), new Vector3(27.372725f, -8.200001f, 125.55859f));
num2++;
span[num2] = new BlacklistedPoint(132, new Vector3(32.25f, -8f, 126.5f), new Vector3(32.022232f, -8.200011f, 126.86095f));
num2++;
span[num2] = new BlacklistedPoint(205, new Vector3(26.75f, 0.5f, 20.75f), new Vector3(27.179117f, 0.26728272f, 19.714373f));
num2++;
span[num2] = new BlacklistedPoint(130, new Vector3(59.5f, 4.25f, -118f), new Vector3(60.551353f, 4f, -119.76446f));
num2++;
span[num2] = new BlacklistedPoint(145, new Vector3(-139.75f, -32.25f, 75.25f), new Vector3(-139.57748f, -33.785175f, 77.87906f));
num2++;
span[num2] = new BlacklistedPoint(146, new Vector3(-201.75f, 10.5f, -265.5f), new Vector3(-203.75235f, 10.130764f, -265.15314f));
num2++;
span[num2] = new BlacklistedArea(135, new Vector3(156.11499f, 15.518433f, 673.21277f), 0.5f, 5f);
num2++;
span[num2] = new BlacklistedPoint(139, new Vector3(366f, -2.5f, 95.5f), new Vector3(362.65973f, -3.4f, 96.6896f), 2f);
num2++;
span[num2] = new BlacklistedPoint(155, new Vector3(-478.75f, 149.25f, -305.75f), new Vector3(-476.1802f, 149.06573f, -304.7811f));
num2++;
span[num2] = new BlacklistedPoint(351, new Vector3(3.25f, 0.75f, 8.5f), new Vector3(4f, 0f, 9.5f));
num2++;
span[num2] = new BlacklistedPoint(418, new Vector3(-136.75f, 2.75f, 9f), new Vector3(-138.66408f, 2.0333426f, 8.860787f), 1f);
num2++;
span[num2] = new BlacklistedPoint(401, new Vector3(-14.75f, -136.75f, 515.75f), new Vector3(-17.631899f, -137.39148f, 512.6676f), 2f);
num2++;
span[num2] = new BlacklistedPoint(397, new Vector3(-93.75f, 87.75f, -715.5f), new Vector3(-87.78183f, 87.188995f, -713.3343f), 2f);
num2++;
span[num2] = new BlacklistedPoint(400, new Vector3(384f, -74f, 648.75f), new Vector3(386.0543f, -72.409454f, 652.0184f), 3f);
num2++;
span[num2] = new BlacklistedPoint(399, new Vector3(-514.4851f, 149.63762f, -480.58087f), new Vector3(-528.78656f, 151.17374f, -473.07077f), 5f, RecalculateNavmesh: true);
num2++;
span[num2] = new BlacklistedPoint(399, new Vector3(-534.5f, 153f, -476.75f), new Vector3(-528.78656f, 151.17374f, -473.07077f), 5f, RecalculateNavmesh: true);
num2++;
span[num2] = new BlacklistedPoint(478, new Vector3(14.5f, 215.25f, -101.5f), new Vector3(18.133032f, 215.44998f, -107.83075f), 5f);
num2++;
span[num2] = new BlacklistedPoint(478, new Vector3(11f, 215.5f, -104.5f), new Vector3(18.133032f, 215.44998f, -107.83075f), 5f);
num2++;
span[num2] = new BlacklistedPoint(1189, new Vector3(574f, -142.25f, 504.25f), new Vector3(574.44183f, -142.12766f, 507.60065f));
num2++;
span[num2] = new BlacklistedPoint(814, new Vector3(-324f, 348.75f, -181.75f), new Vector3(-322.75076f, 347.0529f, -177.69328f), 3f);
num2++;
span[num2] = new BlacklistedPoint(956, new Vector3(6.25f, -27.75f, -41.5f), new Vector3(5.0831127f, -28.213453f, -42.239136f));
num2++;
span[num2] = new BlacklistedPoint(1189, new Vector3(-115.75f, -213.75f, 336.5f), new Vector3(-112.40265f, -215.01514f, 339.0067f), 2f);
num2++;
span[num2] = new BlacklistedPoint(1190, new Vector3(-292.29004f, 18.598045f, -133.83907f), new Vector3(-288.20895f, 18.652182f, -132.67445f), 4f);
num2++;
span[num2] = new BlacklistedPoint(1191, new Vector3(-108f, 29.25f, -350.75f), new Vector3(-107.56289f, 29.008266f, -348.80087f));
num2++;
span[num2] = new BlacklistedPoint(1191, new Vector3(-105.75f, 29.75f, -351f), new Vector3(-105.335304f, 29.017048f, -348.85077f));
num2++;
span[num2] = new BlacklistedPoint(1186, new Vector3(284.25f, 50.75f, 171.25f), new Vector3(284.25f, 50.75f, 166.25f));
num2++;
span[num2] = new BlacklistedPoint(1186, new Vector3(283.75f, 50.75f, 167.25f), new Vector3(284.25f, 50.75f, 166.25f));
num2++;
span[num2] = new BlacklistedPoint(1186, new Vector3(287.75f, 51.25f, 172f), new Vector3(288.875f, 50.75f, 166.25f));
BlacklistedLocations = list;
}
}

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

@ -10,14 +10,14 @@ namespace Questionable.Controller.Steps.Common;
internal static class SendNotification
{
internal sealed class Factory(Configuration configuration, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc, TerritoryData territoryData) : SimpleTaskFactory()
internal sealed class Factory(AutomatonIpc automatonIpc, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc, TerritoryData territoryData) : SimpleTaskFactory()
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
switch (step.InteractionType)
{
case EInteractionType.Snipe:
if (!configuration.General.AutoSnipe)
if (!automatonIpc.IsAutoSnipeEnabled)
{
return new Task(step.InteractionType, step.Comment);
}
@ -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

@ -22,10 +22,6 @@ internal static class Action
{
return Array.Empty<ITask>();
}
if (step.DataIds.Count > 0)
{
return Array.Empty<ITask>();
}
ArgumentNullException.ThrowIfNull(step.Action, "step.Action");
ITask task = OnObject(step.DataId, quest, step.Action.Value, step.CompletionQuestVariablesFlags);
if (step.Action.Value.RequiresMount())
@ -90,9 +86,6 @@ internal static class Action
_continueAt = DateTime.Now.AddSeconds(0.5);
return true;
}
logger.LogInformation("Object {DataId} is untargetable, using action {Action} without target", base.Task.DataId, base.Task.Action);
_usedAction = gameFunctions.UseAction(base.Task.Action);
_continueAt = DateTime.Now.AddSeconds(0.5);
return true;
}
_usedAction = gameFunctions.UseAction(base.Task.Action);
@ -111,18 +104,11 @@ internal static class Action
if (base.Task.DataId.HasValue)
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId.Value);
if (gameObject == null)
if (gameObject == null || !gameObject.IsTargetable)
{
return ETaskResult.StillRunning;
}
if (gameObject.IsTargetable)
{
_usedAction = gameFunctions.UseAction(gameObject, base.Task.Action);
}
else
{
_usedAction = gameFunctions.UseAction(base.Task.Action);
}
_usedAction = gameFunctions.UseAction(gameObject, base.Task.Action);
_continueAt = DateTime.Now.AddSeconds(0.5);
}
else

View file

@ -1,347 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Interactions;
internal static class ClearObjectsWithAction
{
internal sealed class Factory : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.Action)
{
return Array.Empty<ITask>();
}
if (step.DataIds.Count == 0)
{
return Array.Empty<ITask>();
}
if (!step.Action.HasValue)
{
return Array.Empty<ITask>();
}
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(new ClearTask(step.DataIds, step.Action.Value, step.TerritoryId, step.CalculateActualStopDistance(), step.WaypointPositions));
}
}
internal sealed record ClearTask(List<uint> DataIds, EAction Action, ushort TerritoryId, float StopDistance, List<Vector3> WaypointPositions) : ITask
{
public bool ShouldRedoOnInterrupt()
{
return true;
}
public override string ToString()
{
return $"ClearObjects({Action}, {DataIds.Count} types)";
}
}
internal sealed class ClearTaskExecutor(MovementController movementController, GameFunctions gameFunctions, IClientState clientState, IObjectTable objectTable, ILogger<ClearTaskExecutor> logger) : TaskExecutor<ClearTask>()
{
private enum State
{
FindingObject,
MovingToObject,
UsingAction,
WaitingAfterAction,
MovingToWaypoint,
WaitingForTransition
}
private const float VisitedPositionRadius = 5f;
private State _state;
private DateTime _noObjectFoundSince = DateTime.MaxValue;
private DateTime _waitUntil = DateTime.MinValue;
private DateTime _waypointReachedAt = DateTime.MaxValue;
private Vector3 _lastPlayerPosition;
private readonly List<Vector3> _visitedPositions = new List<Vector3>();
private readonly List<int> _triedWaypointIndices = new List<int>();
protected override bool Start()
{
if (clientState.TerritoryType != base.Task.TerritoryId)
{
return true;
}
return TryFindAndMove();
}
private bool IsVisitedPosition(Vector3 position)
{
foreach (Vector3 visitedPosition in _visitedPositions)
{
if (Vector3.Distance(visitedPosition, position) <= 5f)
{
return true;
}
}
return false;
}
private IGameObject? FindNearestUnvisitedObject()
{
IGameObject gameObject = objectTable[0];
if (gameObject == null)
{
return null;
}
IGameObject result = null;
float num = float.MaxValue;
foreach (IGameObject item in objectTable)
{
ObjectKind objectKind = item.ObjectKind;
if ((objectKind == ObjectKind.Player || objectKind - 8 <= ObjectKind.BattleNpc || objectKind == ObjectKind.Housing) ? true : false)
{
continue;
}
bool flag = false;
foreach (uint dataId in base.Task.DataIds)
{
if (item.BaseId == dataId)
{
flag = true;
break;
}
}
if (flag && !IsVisitedPosition(item.Position))
{
float num2 = Vector3.Distance(gameObject.Position, item.Position);
if (num2 < num)
{
result = item;
num = num2;
}
}
}
return result;
}
private bool AnyMatchingObjectsExist()
{
foreach (IGameObject item in objectTable)
{
ObjectKind objectKind = item.ObjectKind;
if ((objectKind == ObjectKind.Player || objectKind - 8 <= ObjectKind.BattleNpc || objectKind == ObjectKind.Housing) ? true : false)
{
continue;
}
foreach (uint dataId in base.Task.DataIds)
{
if (item.BaseId == dataId)
{
return true;
}
}
}
return false;
}
private bool TryFindAndMove()
{
IGameObject gameObject = FindNearestUnvisitedObject();
if (gameObject == null)
{
_state = State.FindingObject;
if (_noObjectFoundSince == DateTime.MaxValue)
{
_noObjectFoundSince = DateTime.Now;
}
return true;
}
_noObjectFoundSince = DateTime.MaxValue;
Vector3 position = gameObject.Position;
logger.LogInformation("Moving to object {DataId} at {Position}", gameObject.BaseId, position);
movementController.NavigateTo(EMovementType.Quest, gameObject.BaseId, position, fly: false, sprint: true, base.Task.StopDistance);
_state = State.MovingToObject;
return true;
}
private (Vector3 Position, int Index)? FindNextWaypoint()
{
IGameObject gameObject = objectTable[0];
if (gameObject == null || base.Task.WaypointPositions.Count == 0)
{
return null;
}
(Vector3, int)? result = null;
float num = float.MaxValue;
for (int i = 0; i < base.Task.WaypointPositions.Count; i++)
{
if (!_triedWaypointIndices.Contains(i))
{
float num2 = Vector3.Distance(gameObject.Position, base.Task.WaypointPositions[i]);
if (num2 < num)
{
result = (base.Task.WaypointPositions[i], i);
num = num2;
}
}
}
return result;
}
private void NavigateToNextWaypoint()
{
(Vector3, int)? tuple = FindNextWaypoint();
if (!tuple.HasValue)
{
logger.LogInformation("All waypoints tried without transition, resetting");
_triedWaypointIndices.Clear();
tuple = FindNextWaypoint();
if (!tuple.HasValue)
{
logger.LogWarning("No waypoint positions configured, waiting for transition");
_state = State.WaitingForTransition;
_lastPlayerPosition = objectTable[0]?.Position ?? Vector3.Zero;
return;
}
}
_triedWaypointIndices.Add(tuple.Value.Item2);
logger.LogInformation("Moving to waypoint {Index} at {Position}", tuple.Value.Item2, tuple.Value.Item1);
movementController.NavigateTo(EMovementType.Quest, null, tuple.Value.Item1, fly: false, sprint: true, 0f);
_state = State.MovingToWaypoint;
}
public override ETaskResult Update()
{
if (clientState.TerritoryType != base.Task.TerritoryId)
{
return ETaskResult.TaskComplete;
}
switch (_state)
{
case State.FindingObject:
if (_noObjectFoundSince != DateTime.MaxValue && DateTime.Now > _noObjectFoundSince.AddSeconds(3.0))
{
if (AnyMatchingObjectsExist())
{
logger.LogInformation("Matching objects still exist, clearing visited positions and retrying");
_visitedPositions.Clear();
_noObjectFoundSince = DateTime.MaxValue;
}
else
{
logger.LogInformation("No matching objects remain, moving to waypoint");
NavigateToNextWaypoint();
}
return ETaskResult.StillRunning;
}
TryFindAndMove();
return ETaskResult.StillRunning;
case State.MovingToObject:
{
if (movementController.IsPathfinding || movementController.IsPathRunning)
{
return ETaskResult.StillRunning;
}
DateTime movementStartedAt = movementController.MovementStartedAt;
if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2.0) >= DateTime.Now)
{
return ETaskResult.StillRunning;
}
IGameObject gameObject2 = FindNearestUnvisitedObject();
IGameObject gameObject3 = objectTable[0];
if (gameObject2 != null && gameObject3 != null && Vector3.Distance(gameObject3.Position, gameObject2.Position) <= 5f)
{
_visitedPositions.Add(gameObject2.Position);
_state = State.UsingAction;
}
else
{
logger.LogInformation("Target no longer valid at destination, finding next");
TryFindAndMove();
}
return ETaskResult.StillRunning;
}
case State.UsingAction:
if (gameFunctions.UseAction(base.Task.Action))
{
logger.LogInformation("Used action {Action}", base.Task.Action);
_waitUntil = DateTime.Now.AddSeconds(1.0);
_state = State.WaitingAfterAction;
}
return ETaskResult.StillRunning;
case State.WaitingAfterAction:
if (DateTime.Now < _waitUntil)
{
return ETaskResult.StillRunning;
}
TryFindAndMove();
return ETaskResult.StillRunning;
case State.MovingToWaypoint:
{
if (movementController.IsPathfinding || movementController.IsPathRunning)
{
return ETaskResult.StillRunning;
}
DateTime movementStartedAt2 = movementController.MovementStartedAt;
if (movementStartedAt2 == DateTime.MaxValue || movementStartedAt2.AddSeconds(2.0) >= DateTime.Now)
{
return ETaskResult.StillRunning;
}
logger.LogInformation("Reached waypoint, waiting for zone transition");
_state = State.WaitingForTransition;
_lastPlayerPosition = objectTable[0]?.Position ?? Vector3.Zero;
_waypointReachedAt = DateTime.Now;
return ETaskResult.StillRunning;
}
case State.WaitingForTransition:
{
IGameObject gameObject = objectTable[0];
if (gameObject != null)
{
float num = Vector3.Distance(_lastPlayerPosition, gameObject.Position);
if (num > 50f)
{
logger.LogInformation("Zone transition detected (moved {Distance}), resuming plant clearing", num);
_visitedPositions.Clear();
_triedWaypointIndices.Clear();
_noObjectFoundSince = DateTime.MaxValue;
_state = State.FindingObject;
return ETaskResult.StillRunning;
}
}
if (FindNearestUnvisitedObject() != null)
{
logger.LogInformation("New objects detected, resuming plant clearing");
_visitedPositions.Clear();
_triedWaypointIndices.Clear();
_noObjectFoundSince = DateTime.MaxValue;
TryFindAndMove();
return ETaskResult.StillRunning;
}
if (_waypointReachedAt != DateTime.MaxValue && DateTime.Now > _waypointReachedAt.AddSeconds(3.0))
{
logger.LogInformation("No transition at this waypoint, trying next");
NavigateToNextWaypoint();
}
return ETaskResult.StillRunning;
}
default:
return ETaskResult.TaskComplete;
}
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -6,10 +6,10 @@ 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;
using Questionable.Controller.Utils;
using Questionable.Model;
using Questionable.Model.Questing;
@ -37,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,31 +66,12 @@ internal static class Dive
return ETaskResult.StillRunning;
}
logger.LogDebug("{Action} key {KeyCode:X2}", (result.Item1 == 256) ? "Pressing" : "Releasing", result.Item2);
WindowsKeypress.SendMessage((nint)Device.Instance()->hWnd, result.Item1, result.Item2);
if (result.Item1 == 256)
{
_pressedKeys.Add(result.Item2);
}
else if (result.Item1 == 257)
{
_pressedKeys.Remove(result.Item2);
}
NativeMethods.SendMessage((nint)Device.Instance()->hWnd, result.Item1, result.Item2, IntPtr.Zero);
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);
WindowsKeypress.SendMessage((nint)Device.Instance()->hWnd, 257u, pressedKey);
}
_pressedKeys.Clear();
_keysToPress.Clear();
}
public override bool ShouldInterruptOnDamage()
{
return false;
@ -104,7 +83,7 @@ internal static class Dive
{
return ETaskResult.TaskComplete;
}
if (_attempts >= 5)
if (_attempts >= 3)
{
throw new TaskException("Please dive manually.");
}
@ -115,23 +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);
span[0] = GetKeysToPress(key, keyModifier);
span[1] = GetKeysToPress(key2, keyModifier2);
int num2 = 0;
span[num2] = GetKeysToPress(keybind.Key, keybind.Modifier);
num2++;
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);
@ -142,10 +116,8 @@ internal static class Dive
foreach (nint item in list2)
{
_keysToPress.Enqueue((256u, item));
for (int num2 = 0; num2 < 15; num2++)
{
_keysToPress.Enqueue((0u, 0));
}
_keysToPress.Enqueue((0u, 0));
_keysToPress.Enqueue((0u, 0));
}
for (int num3 = 0; num3 < 5; num3++)
{
@ -159,18 +131,29 @@ internal static class Dive
}
}
private static List<nint>? GetKeysToPress(SeVirtualKey key, KeyModifierFlag modifier)
private static class NativeMethods
{
public const uint WM_KEYUP = 257u;
public const uint WM_KEYDOWN = 256u;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
public static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
}
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,94 +26,36 @@ 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();
}
yield return new StartAutoDutyTask(step.DutyOptions.ContentFinderConditionId, dutyMode);
yield return new WaitAutoDutyTask(step.DutyOptions.ContentFinderConditionId);
if (!QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
{
yield return new WaitAtEnd.WaitNextStepOrSequence();
ushort value = id.Value;
if (value >= 357 && value <= 360)
{
dutyMode = 2;
goto IL_00b2;
}
}
dutyMode = 1;
goto IL_00b2;
}
else if (!step.DutyOptions.LowPriority)
if (!step.DutyOptions.LowPriority)
{
yield return new OpenDutyFinderTask(step.DutyOptions.ContentFinderConditionId);
}
}
private AutoDutyIpc.DutyMode GetDutyMode(uint cfcId, EDutyMode? stepDutyMode)
{
if (stepDutyMode.HasValue)
yield break;
IL_00b2:
yield return new StartAutoDutyTask(contentFinderConditionId, (AutoDutyIpc.DutyMode)dutyMode);
yield return new WaitAutoDutyTask(step.DutyOptions.ContentFinderConditionId);
if (!QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
{
return ConvertToAutoDutyMode(stepDutyMode.Value);
yield return new WaitAtEnd.WaitNextStepOrSequence();
}
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;
}
}
@ -135,28 +75,25 @@ internal static class Duty
{
throw new TaskException("Failed to get territory ID for content finder condition");
}
if (base.Task.DutyMode != AutoDutyIpc.DutyMode.UnsyncRegular)
InventoryManager* intPtr = InventoryManager.Instance();
if (intPtr == null)
{
InventoryManager* intPtr = InventoryManager.Instance();
if (intPtr == null)
throw new TaskException("Inventory unavailable");
}
InventoryContainer* inventoryContainer = intPtr->GetInventoryContainer(InventoryType.EquippedItems);
if (inventoryContainer == null)
{
throw new TaskException("Equipped items unavailable");
}
short num = gearStatsCalculator.CalculateAverageItemLevel(inventoryContainer);
if (contentFinderConditionData.RequiredItemLevel > num)
{
string text = $"Could not use AutoDuty to queue for {contentFinderConditionData.Name}, required item level: {contentFinderConditionData.RequiredItemLevel}, current item level: {num}.";
if (!sendNotificationExecutor.Start(new SendNotification.Task(EInteractionType.Duty, text)))
{
throw new TaskException("Inventory unavailable");
}
InventoryContainer* inventoryContainer = intPtr->GetInventoryContainer(InventoryType.EquippedItems);
if (inventoryContainer == null)
{
throw new TaskException("Equipped items unavailable");
}
short num = gearStatsCalculator.CalculateAverageItemLevel(inventoryContainer);
if (contentFinderConditionData.RequiredItemLevel > num)
{
string text = $"Could not use AutoDuty to queue for {contentFinderConditionData.Name}, required item level: {contentFinderConditionData.RequiredItemLevel}, current item level: {num}.";
if (!sendNotificationExecutor.Start(new SendNotification.Task(EInteractionType.Duty, text)))
{
chatGui.PrintError(text, "Questionable", 576);
}
return false;
chatGui.PrintError(text, "Questionable", 576);
}
return false;
}
autoDutyIpc.StartInstance(base.Task.ContentFinderConditionId, base.Task.DutyMode);
return true;
@ -255,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:
@ -175,10 +176,12 @@ internal static class EquipItem
case 10u:
case 11u:
{
int num = 1;
List<ushort> list4 = new List<ushort>(num);
CollectionsMarshal.SetCount(list4, num);
CollectionsMarshal.AsSpan(list4)[0] = (ushort)(item.Value.EquipSlotCategory.RowId - 1);
int index = 1;
List<ushort> list4 = new List<ushort>(index);
CollectionsMarshal.SetCount(list4, index);
span = CollectionsMarshal.AsSpan(list4);
int num = 0;
span[num] = (ushort)(item.Value.EquipSlotCategory.RowId - 1);
return list4;
}
case 12u:
@ -186,17 +189,21 @@ internal static class EquipItem
int num = 2;
List<ushort> list3 = new List<ushort>(num);
CollectionsMarshal.SetCount(list3, num);
Span<ushort> span = CollectionsMarshal.AsSpan(list3);
span[0] = 11;
span[1] = 12;
span = CollectionsMarshal.AsSpan(list3);
int index = 0;
span[index] = 11;
index++;
span[index] = 12;
return list3;
}
case 13u:
{
int num = 1;
List<ushort> list2 = new List<ushort>(num);
CollectionsMarshal.SetCount(list2, num);
CollectionsMarshal.AsSpan(list2)[0] = 0;
int index = 1;
List<ushort> list2 = new List<ushort>(index);
CollectionsMarshal.SetCount(list2, index);
span = CollectionsMarshal.AsSpan(list2);
int num = 0;
span[num] = 0;
return list2;
}
case 17u:
@ -204,7 +211,9 @@ internal static class EquipItem
int num = 1;
List<ushort> list = new List<ushort>(num);
CollectionsMarshal.SetCount(list, num);
CollectionsMarshal.AsSpan(list)[0] = 13;
span = CollectionsMarshal.AsSpan(list);
int index = 0;
span[index] = 13;
return list;
}
default:

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

@ -1,83 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Types;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Interactions;
internal static class FateAction
{
internal sealed class Factory : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.FateAction)
{
return Array.Empty<ITask>();
}
if (step.FateActionTargets.Count == 0)
{
throw new InvalidOperationException("FateAction step requires FateActionTargets");
}
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2]
{
new Mount.UnmountTask(),
new UseOnTargets(step.FateActionTargets)
});
}
}
internal sealed record UseOnTargets(IList<FateActionTarget> Targets) : ITask
{
public bool ShouldRedoOnInterrupt()
{
return true;
}
public override string ToString()
{
return $"FateAction({Targets.Count} targets)";
}
}
internal sealed class UseOnTargetsExecutor(GameFunctions gameFunctions, ILogger<UseOnTargets> logger) : TaskExecutor<UseOnTargets>()
{
private DateTime _nextActionAt = DateTime.MinValue;
protected override bool Start()
{
logger.LogInformation("Starting FateAction: {Count} targets [{Targets}]", base.Task.Targets.Count, string.Join(", ", base.Task.Targets.Select((FateActionTarget t) => $"{t.DataId}→{t.Action}")));
return true;
}
public override ETaskResult Update()
{
if (DateTime.Now < _nextActionAt)
{
return ETaskResult.StillRunning;
}
foreach (FateActionTarget target in base.Task.Targets)
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId, null, warnIfMissing: false);
if (gameObject != null && gameObject.IsTargetable)
{
bool flag = gameFunctions.UseAction(gameObject, target.Action);
_nextActionAt = (flag ? DateTime.Now.AddSeconds(2.5) : DateTime.Now.AddSeconds(0.5));
return ETaskResult.StillRunning;
}
}
_nextActionAt = DateTime.Now.AddSeconds(0.25);
return ETaskResult.StillRunning;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -1,182 +0,0 @@
using System;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Objects.Types;
using Microsoft.Extensions.Logging;
using Questionable.Functions;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Interactions;
internal static class FateFarming
{
internal sealed record WaitForFateTargets(IReadOnlyList<FateActionTarget> Targets) : ITask
{
public override string ToString()
{
return $"WaitForFateTargets({Targets.Count} targets)";
}
}
internal sealed class WaitForFateTargetsExecutor(GameFunctions gameFunctions, ILogger<WaitForFateTargetsExecutor> logger) : TaskExecutor<WaitForFateTargets>()
{
private DateTime _nextPollAt = DateTime.MinValue;
private bool _loggedWaitingForFate;
protected override bool Start()
{
logger.LogInformation("Waiting for FATE targets to appear ({Count} targets)", base.Task.Targets.Count);
_loggedWaitingForFate = false;
return true;
}
public override ETaskResult Update()
{
if (DateTime.Now < _nextPollAt)
{
return ETaskResult.StillRunning;
}
ushort currentFateId = gameFunctions.GetCurrentFateId();
if (currentFateId == 0)
{
if (!_loggedWaitingForFate)
{
logger.LogInformation("No active FATE yet, waiting for FATE to start before checking targets");
_loggedWaitingForFate = true;
}
_nextPollAt = DateTime.Now.AddSeconds(1.0);
return ETaskResult.StillRunning;
}
_loggedWaitingForFate = false;
foreach (FateActionTarget target in base.Task.Targets)
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId, null, warnIfMissing: false);
if (gameObject != null && gameObject.IsTargetable)
{
logger.LogInformation("FATE {FateId} active and target {DataId} is targetable", currentFateId, target.DataId);
return ETaskResult.TaskComplete;
}
}
_nextPollAt = DateTime.Now.AddSeconds(1.0);
return ETaskResult.StillRunning;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record SyncFateLevel : ITask
{
public override string ToString()
{
return "SyncFateLevel";
}
}
internal sealed class SyncFateLevelExecutor(GameFunctions gameFunctions, ILogger<SyncFateLevelExecutor> logger) : TaskExecutor<SyncFateLevel>()
{
protected override bool Start()
{
logger.LogInformation("Syncing to FATE level");
return true;
}
public override ETaskResult Update()
{
ushort currentFateId = gameFunctions.GetCurrentFateId();
if (currentFateId == 0)
{
logger.LogDebug("No active FATE to sync to, skipping");
return ETaskResult.TaskComplete;
}
gameFunctions.SyncToFate(currentFateId);
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record FateActionLoop(IReadOnlyList<FateActionTarget> Targets, EStatus? RequiredStatusId = null) : ITask
{
public bool ShouldRedoOnInterrupt()
{
return true;
}
public override string ToString()
{
return $"FateActionLoop({Targets.Count} targets)";
}
}
internal sealed class FateActionLoopExecutor(GameFunctions gameFunctions, ILogger<FateActionLoopExecutor> logger) : TaskExecutor<FateActionLoop>()
{
private DateTime _nextActionAt = DateTime.MinValue;
private ushort _trackedFateId;
private bool _fateWasActive;
protected override bool Start()
{
_trackedFateId = gameFunctions.GetCurrentFateId();
logger.LogInformation("Starting FATE action loop with {Count} targets, tracking FATE {FateId}", base.Task.Targets.Count, _trackedFateId);
return true;
}
public override ETaskResult Update()
{
if (DateTime.Now < _nextActionAt)
{
return ETaskResult.StillRunning;
}
if (base.Task.RequiredStatusId.HasValue && !gameFunctions.HasStatus(base.Task.RequiredStatusId.Value))
{
logger.LogInformation("Required status {StatusId} lost during FATE action loop, ending cycle to re-apply", base.Task.RequiredStatusId.Value);
return ETaskResult.TaskComplete;
}
bool flag = gameFunctions.GetCurrentFateId() != 0;
if (_fateWasActive && !flag)
{
logger.LogInformation("FATE {FateId} is no longer active, cycle complete", _trackedFateId);
return ETaskResult.TaskComplete;
}
_fateWasActive = flag;
if (_trackedFateId == 0)
{
_trackedFateId = gameFunctions.GetCurrentFateId();
if (_trackedFateId != 0)
{
logger.LogInformation("Now tracking FATE {FateId}", _trackedFateId);
}
}
if (_trackedFateId != 0 && !gameFunctions.IsFateStillActive(_trackedFateId))
{
logger.LogInformation("FATE {FateId} is no longer running, cycle complete", _trackedFateId);
return ETaskResult.TaskComplete;
}
foreach (FateActionTarget target in base.Task.Targets)
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId, null, warnIfMissing: false);
if (gameObject != null && gameObject.IsTargetable)
{
bool flag2 = gameFunctions.UseAction(gameObject, target.Action);
_nextActionAt = (flag2 ? DateTime.Now.AddSeconds(2.5) : DateTime.Now.AddSeconds(0.5));
return ETaskResult.StillRunning;
}
}
_nextActionAt = DateTime.Now.AddSeconds(0.25);
return ETaskResult.StillRunning;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -11,6 +11,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Shared;
using Questionable.Controller.Utils;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
@ -19,7 +20,7 @@ namespace Questionable.Controller.Steps.Interactions;
internal static class Interact
{
internal sealed class Factory(Configuration configuration) : ITaskFactory
internal sealed class Factory(AutomatonIpc automatonIpc, Configuration configuration) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -40,7 +41,7 @@ internal static class Interact
}
else if (step.InteractionType == EInteractionType.Snipe)
{
if (!configuration.General.AutoSnipe)
if (!automatonIpc.IsAutoSnipeEnabled)
{
yield break;
}
@ -64,7 +65,7 @@ internal static class Interact
uint value = step.DataId.Value;
EInteractionType interactionType2 = step.InteractionType;
int skipMarkerCheck;
if (!step.IgnoreQuestMarker && !step.TargetTerritoryId.HasValue && !(quest.Id is SatisfactionSupplyNpcId))
if (!step.TargetTerritoryId.HasValue && !(quest.Id is SatisfactionSupplyNpcId))
{
SkipConditions skipConditions = step.SkipConditions;
if (skipConditions != null)
@ -72,22 +73,21 @@ internal static class Interact
SkipStepConditions stepIf = skipConditions.StepIf;
if (stepIf != null && stepIf.Never)
{
goto IL_025f;
goto IL_0247;
}
}
if (step.InteractionType != EInteractionType.PurchaseItem)
{
skipMarkerCheck = ((step.DataId == 1052475) ? 1 : 0);
goto IL_0260;
goto IL_0248;
}
}
goto IL_025f;
IL_0260:
yield return new Task(value, quest, interactionType2, (byte)skipMarkerCheck != 0, step.PickUpItemId ?? step.GCPurchase?.ItemId, step.TaxiStandId, step.SkipConditions?.StepIf, step.CompletionQuestVariablesFlags);
yield break;
IL_025f:
goto IL_0247;
IL_0247:
skipMarkerCheck = 1;
goto IL_0260;
goto IL_0248;
IL_0248:
yield return new Task(value, quest, interactionType2, (byte)skipMarkerCheck != 0, step.PickUpItemId, step.TaxiStandId, step.SkipConditions?.StepIf, step.CompletionQuestVariablesFlags);
}
}
@ -107,13 +107,11 @@ internal static class Interact
public SkipStepConditions? SkipConditions { get; init; }
public EStatus? CompletionStatusId { get; init; }
public List<QuestWorkValue?> CompletionQuestVariablesFlags { get; }
public bool HasCompletionQuestVariablesFlags { get; }
public Task(uint DataId, Quest? Quest, EInteractionType InteractionType, bool SkipMarkerCheck = false, uint? PickUpItemId = null, byte? TaxiStandId = null, SkipStepConditions? SkipConditions = null, List<QuestWorkValue?>? CompletionQuestVariablesFlags = null, EStatus? CompletionStatusId = null)
public Task(uint DataId, Quest? Quest, EInteractionType InteractionType, bool SkipMarkerCheck = false, uint? PickUpItemId = null, byte? TaxiStandId = null, SkipStepConditions? SkipConditions = null, List<QuestWorkValue?>? CompletionQuestVariablesFlags = null)
{
this.DataId = DataId;
this.Quest = Quest;
@ -122,7 +120,6 @@ internal static class Interact
this.PickUpItemId = PickUpItemId;
this.TaxiStandId = TaxiStandId;
this.SkipConditions = SkipConditions;
this.CompletionStatusId = CompletionStatusId;
this.CompletionQuestVariablesFlags = CompletionQuestVariablesFlags ?? new List<QuestWorkValue>();
HasCompletionQuestVariablesFlags = Quest != null && CompletionQuestVariablesFlags != null && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags);
base._002Ector();
@ -139,7 +136,7 @@ internal static class Interact
}
[CompilerGenerated]
public void Deconstruct(out uint DataId, out Quest? Quest, out EInteractionType InteractionType, out bool SkipMarkerCheck, out uint? PickUpItemId, out byte? TaxiStandId, out SkipStepConditions? SkipConditions, out List<QuestWorkValue?>? CompletionQuestVariablesFlags, out EStatus? CompletionStatusId)
public void Deconstruct(out uint DataId, out Quest? Quest, out EInteractionType InteractionType, out bool SkipMarkerCheck, out uint? PickUpItemId, out byte? TaxiStandId, out SkipStepConditions? SkipConditions, out List<QuestWorkValue?>? CompletionQuestVariablesFlags)
{
DataId = this.DataId;
Quest = this.Quest;
@ -149,7 +146,6 @@ internal static class Interact
TaxiStandId = this.TaxiStandId;
SkipConditions = this.SkipConditions;
CompletionQuestVariablesFlags = this.CompletionQuestVariablesFlags;
CompletionStatusId = this.CompletionStatusId;
}
}
@ -177,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)
{
@ -228,20 +220,11 @@ internal static class Interact
}
_needsUnmount = false;
}
EStatus? completionStatusId = base.Task.CompletionStatusId;
if (completionStatusId.HasValue)
{
EStatus valueOrDefault = completionStatusId.GetValueOrDefault();
if (gameFunctions.HasStatus(valueOrDefault))
{
return ETaskResult.TaskComplete;
}
}
uint? pickUpItemId = base.Task.PickUpItemId;
if (pickUpItemId.HasValue)
{
uint valueOrDefault2 = pickUpItemId.GetValueOrDefault();
if (InventoryManager.Instance()->GetInventoryItemCount(valueOrDefault2, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0)
uint valueOrDefault = pickUpItemId.GetValueOrDefault();
if (InventoryManager.Instance()->GetInventoryItemCount(valueOrDefault, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0)
{
return ETaskResult.TaskComplete;
}
@ -251,8 +234,8 @@ internal static class Interact
byte? taxiStandId = base.Task.TaxiStandId;
if (taxiStandId.HasValue)
{
byte valueOrDefault3 = taxiStandId.GetValueOrDefault();
if (UIState.Instance()->IsChocoboTaxiStandUnlocked((uint)(valueOrDefault3 + 1179648)))
byte valueOrDefault2 = taxiStandId.GetValueOrDefault();
if (UIState.Instance()->IsChocoboTaxiStandUnlocked((uint)(valueOrDefault2 + 1179648)))
{
return ETaskResult.TaskComplete;
}
@ -276,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;
}
@ -67,7 +61,9 @@ internal static class Jump
int num2 = 1;
List<Vector3> list = new List<Vector3>(num2);
CollectionsMarshal.SetCount(list, num2);
CollectionsMarshal.AsSpan(list)[0] = base.Task.JumpDestination.Position;
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
int index = 0;
span[index] = base.Task.JumpDestination.Position;
movementController.NavigateTo(EMovementType.Quest, dataId, list, fly: false, sprint: false, base.Task.JumpDestination.StopDistance ?? num);
_003Cframework_003EP.RunOnTick(delegate
{
@ -98,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)
{
}
}
@ -114,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()
@ -141,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;
@ -87,7 +83,9 @@ internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskE
int num = 1;
List<Vector3> list = new List<Vector3>(num);
CollectionsMarshal.SetCount(list, num);
CollectionsMarshal.AsSpan(list)[0] = _destination;
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
int index = 0;
span[index] = _destination;
movementController.NavigateTo(EMovementType.Quest, dataId, list, base.Task.Fly, base.Task.Sprint ?? (!_mountDuringMovement.HasValue), base.Task.StopDistance, base.Task.IgnoreDistanceToObject ? new float?(float.MaxValue) : ((float?)null), base.Task.Land);
};
}
@ -97,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)
{
@ -172,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,14 +13,10 @@ 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)
{
if (step.DataIds.Count > 0)
{
return Array.Empty<ITask>();
}
if (step.Position.HasValue)
{
return CreateMoveTasks(step, step.Position.Value);
@ -30,10 +25,6 @@ internal static class MoveTo
{
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(new WaitForNearDataId(step.DataId.Value, step.StopDistance.Value));
}
if (step != null && step.DataId.HasValue && !step.Position.HasValue)
{
return CreateMoveToObjectTasks(step);
}
EAetheryteLocation valueOrDefault = default(EAetheryteLocation);
bool flag;
if (step != null)
@ -46,13 +37,13 @@ internal static class MoveTo
{
valueOrDefault = aetheryte.GetValueOrDefault();
flag = true;
goto IL_00e2;
goto IL_00a3;
}
}
}
flag = false;
goto IL_00e2;
IL_00e2:
goto IL_00a3;
IL_00a3:
if (flag)
{
return CreateMoveTasks(step, aetheryteData.Locations[valueOrDefault]);
@ -69,20 +60,9 @@ internal static class MoveTo
return Array.Empty<ITask>();
}
private IEnumerable<ITask> CreateMoveToObjectTasks(QuestStep step)
{
yield return new WaitCondition.Task(() => clientState.TerritoryType == step.TerritoryId, "Wait(territory: " + territoryData.GetNameAndId(step.TerritoryId) + ")");
if (!step.DisableNavmesh)
{
yield return new WaitNavmesh.Task();
}
yield return new MoveToObject(step);
}
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

@ -1,16 +0,0 @@
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Movement;
internal sealed record MoveToObject(QuestStep Step) : ITask
{
public bool ShouldRedoOnInterrupt()
{
return true;
}
public override string ToString()
{
return $"MoveToObject({Step.DataId})";
}
}

View file

@ -1,127 +0,0 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Functions;
using Questionable.Model;
namespace Questionable.Controller.Steps.Movement;
internal sealed class MoveToObjectExecutor(MovementController movementController, GameFunctions gameFunctions, IClientState clientState, IObjectTable objectTable, ILogger<MoveToObjectExecutor> logger) : TaskExecutor<MoveToObject>()
{
private DateTime _startedLookingAt = DateTime.MaxValue;
private Vector3 _destination;
private bool _moving;
protected override bool Start()
{
if (!base.Task.Step.DataId.HasValue)
{
logger.LogWarning("MoveToObject task has no DataId");
return false;
}
if (clientState.TerritoryType != base.Task.Step.TerritoryId)
{
logger.LogInformation("Not in correct territory yet, waiting");
return true;
}
_startedLookingAt = DateTime.Now;
return TryStartMovement();
}
private IGameObject? FindDistantObjectByDataId(uint dataId)
{
IGameObject gameObject = objectTable[0];
if (gameObject == null)
{
return null;
}
float num = base.Task.Step.CalculateActualStopDistance();
IGameObject result = null;
float num2 = float.MaxValue;
foreach (IGameObject item in objectTable)
{
ObjectKind objectKind = item.ObjectKind;
bool flag = ((objectKind == ObjectKind.Player || objectKind - 8 <= ObjectKind.BattleNpc || objectKind == ObjectKind.Housing) ? true : false);
if (!flag && item.BaseId == dataId)
{
float num3 = Vector3.Distance(gameObject.Position, item.Position);
if (!(num3 <= num) && num3 < num2)
{
result = item;
num2 = num3;
}
}
}
return result;
}
private bool TryStartMovement()
{
IGameObject gameObject = FindDistantObjectByDataId(base.Task.Step.DataId.Value);
if (gameObject == null)
{
gameObject = gameFunctions.FindObjectByDataId(base.Task.Step.DataId.Value, null, warnIfMissing: false);
if (gameObject == null)
{
logger.LogInformation("Object {DataId} not found yet, waiting", base.Task.Step.DataId);
return true;
}
IGameObject gameObject2 = objectTable[0];
float num = base.Task.Step.CalculateActualStopDistance();
if (gameObject2 != null && Vector3.Distance(gameObject2.Position, gameObject.Position) <= num)
{
logger.LogInformation("Only nearby objects with DataId {DataId} remain, skipping", base.Task.Step.DataId);
return true;
}
}
_destination = gameObject.Position;
logger.LogInformation("Moving to object {DataId} at {Position}", base.Task.Step.DataId, _destination);
movementController.NavigateTo(EMovementType.Quest, base.Task.Step.DataId, _destination, fly: false, base.Task.Step.Sprint ?? true, base.Task.Step.CalculateActualStopDistance());
_moving = true;
return true;
}
public override ETaskResult Update()
{
if (clientState.TerritoryType != base.Task.Step.TerritoryId)
{
return ETaskResult.StillRunning;
}
if (!_moving)
{
if (_startedLookingAt != DateTime.MaxValue && DateTime.Now > _startedLookingAt.AddSeconds(3.0))
{
logger.LogInformation("Object {DataId} not found after timeout, skipping step", base.Task.Step.DataId);
return ETaskResult.TaskComplete;
}
if (!TryStartMovement())
{
return ETaskResult.TaskComplete;
}
if (!_moving)
{
return ETaskResult.StillRunning;
}
}
if (movementController.IsPathfinding || movementController.IsPathRunning)
{
return ETaskResult.StillRunning;
}
DateTime movementStartedAt = movementController.MovementStartedAt;
if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2.0) >= DateTime.Now)
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}

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))
@ -119,10 +113,14 @@ internal static class AethernetShortcut
List<Vector3> list = new List<Vector3>(num);
CollectionsMarshal.SetCount(list, num);
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
span[0] = new Vector3(0f, 8.442986f, 9f);
span[1] = new Vector3(9f, 8.442986f, 0f);
span[2] = new Vector3(-9f, 8.442986f, 0f);
span[3] = new Vector3(0f, 8.442986f, -9f);
int num2 = 0;
span[num2] = new Vector3(0f, 8.442986f, 9f);
num2++;
span[num2] = new Vector3(9f, 8.442986f, 0f);
num2++;
span[num2] = new Vector3(-9f, 8.442986f, 0f);
num2++;
span[num2] = new Vector3(0f, 8.442986f, -9f);
Vector3 to = list.MinBy((Vector3 x) => Vector3.Distance(playerPosition, x));
_moving = true;
movementController.NavigateTo(EMovementType.Quest, (uint)base.Task.From, to, fly: false, sprint: true, 0.25f);
@ -204,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,27 +134,23 @@ internal static class AetheryteShortcut
return true;
}
}
if (base.Task.ExpectedTerritoryId == territoryType)
if (base.Task.ExpectedTerritoryId == territoryType && !skipAetheryteCondition.Never)
{
IGameObject gameObject2 = objectTable[0];
if (gameObject2 != null && !skipAetheryteCondition.Never)
if (skipAetheryteCondition != null && skipAetheryteCondition.InSameTerritory)
{
if (skipAetheryteCondition != null && skipAetheryteCondition.InSameTerritory)
{
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (InSameTerritory)");
return true;
}
Vector3 position = gameObject2.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");
return true;
}
if (aetheryteData.CalculateDistance(position, territoryType, base.Task.TargetAetheryte) < 20f || (base.Task.Step.AethernetShortcut != null && (aetheryteData.CalculateDistance(position, territoryType, base.Task.Step.AethernetShortcut.From) < 20f || aetheryteData.CalculateDistance(position, territoryType, base.Task.Step.AethernetShortcut.To) < 20f)))
{
logger.LogInformation("Skipping aetheryte teleport");
return true;
}
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (InSameTerritory)");
return true;
}
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");
return true;
}
if (aetheryteData.CalculateDistance(position, territoryType, base.Task.TargetAetheryte) < 20f || (base.Task.Step.AethernetShortcut != null && (aetheryteData.CalculateDistance(position, territoryType, base.Task.Step.AethernetShortcut.From) < 20f || aetheryteData.CalculateDistance(position, territoryType, base.Task.Step.AethernetShortcut.To) < 20f)))
{
logger.LogInformation("Skipping aetheryte teleport");
return true;
}
}
}
@ -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;
@ -257,10 +246,14 @@ internal static class AetheryteShortcut
List<Vector3> list = new List<Vector3>(num);
CollectionsMarshal.SetCount(list, num);
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
span[0] = new Vector3(0f, 8.8f, 15.5f);
span[1] = new Vector3(0f, 8.8f, -15.5f);
span[2] = new Vector3(15.5f, 8.8f, 0f);
span[3] = new Vector3(-15.5f, 8.8f, 0f);
int num2 = 0;
span[num2] = new Vector3(0f, 8.8f, 15.5f);
num2++;
span[num2] = new Vector3(0f, 8.8f, -15.5f);
num2++;
span[num2] = new Vector3(15.5f, 8.8f, 0f);
num2++;
span[num2] = new Vector3(-15.5f, 8.8f, 0f);
dictionary.Add(EAetheryteLocation.SolutionNine, list);
AetherytesToMoveFrom = dictionary;
}

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