1
0
Fork 0
forked from aly/qstbak

Compare commits

..

4 commits
main ... main

Author SHA1 Message Date
0336b9c9e9 muffin v7.38.1 2025-11-18 10:16:09 +10:00
e5b98b3d57 muffin v7.38 2025-11-17 11:31:27 +10:00
411c0bbe76 muffin v6.38 2025-11-09 09:25:53 +10:00
98989e8a70 muffin v6.37 2025-11-09 04:17:00 +10:00
47 changed files with 23001 additions and 18274 deletions

3
.gitignore vendored
View file

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

View file

@ -14,22 +14,22 @@
<ItemGroup />
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Dalamud.dll</HintPath>
</Reference>
<Reference Include="Lumina">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Lumina.dll</HintPath>
</Reference>
<Reference Include="FFXIVClientStructs">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\FFXIVClientStructs.dll</HintPath>
</Reference>
<Reference Include="Dalamud.Bindings.ImGui">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Dalamud.Bindings.ImGui.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Dalamud.Bindings.ImGui.dll</HintPath>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
</Reference>
<Reference Include="InteropGenerator.Runtime">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\InteropGenerator.Runtime.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\InteropGenerator.Runtime.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View file

@ -219,6 +219,9 @@
"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": [
@ -257,14 +260,22 @@
"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. Useful for steps that should only be done once at low levels (e.g., early aetheryte attunements).",
"description": "Skip this step if the player level is greater than or equal to this value",
"minimum": 1,
"maximum": 100
},
@ -277,6 +288,15 @@
]
}
},
"QuestsCompleted": {
"type": "array",
"items": {
"type": [
"number",
"string"
]
}
},
"NotNamePlateIconId": {
"type": "array",
"items": {

File diff suppressed because it is too large Load diff

View file

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

View file

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

@ -0,0 +1,40 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-requiredvariables.json",
"type": "array",
"description": "Certain quests (primarily beast tribes/allied societies) have a RNG element to spawning targets, and the step should be skipped in its entirety if none of the sets below match",
"minItems": 6,
"maxItems": 6,
"items": {
"type": [
"array",
"null"
],
"items": {
"type": [
"number",
"object"
],
"properties": {
"High": {
"type": [
"number",
"null"
],
"minimum": 0,
"maximum": 15
},
"Low": {
"type": [
"number",
"null"
],
"minimum": 0,
"maximum": 15
}
},
"minimum": 0,
"maximum": 255
}
}
}

View file

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

View file

@ -1,21 +0,0 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Questionable.Model.Questing.Converter;
public sealed class ElementIdConverter : JsonConverter<ElementId>
{
public override ElementId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Number)
return new QuestId(reader.GetUInt16());
else
return ElementId.FromString(reader.GetString() ?? throw new JsonException());
}
public override void Write(Utf8JsonWriter writer, ElementId value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}

View file

@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Questionable.Model.Questing.Converter;
public sealed class ElementIdListConverter : JsonConverter<List<ElementId>>
{
public override List<ElementId> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new JsonException();
reader.Read();
List<ElementId> values = [];
while (reader.TokenType != JsonTokenType.EndArray)
{
if (reader.TokenType == JsonTokenType.Number)
values.Add(new QuestId(reader.GetUInt16()));
else
values.Add(ElementId.FromString(reader.GetString() ?? throw new JsonException()));
reader.Read();
}
return values;
}
public override void Write(Utf8JsonWriter writer, List<ElementId> value, JsonSerializerOptions options)
{
writer.WriteStartArray();
foreach (ElementId elementId in value)
{
writer.WriteStringValue(elementId.ToString());
}
writer.WriteEndArray();
}
}

View file

@ -1,63 +0,0 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Questionable.Model.Questing.Converter;
public sealed class QuestWorkConfigConverter : JsonConverter<QuestWorkValue>
{
public override QuestWorkValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Number)
return new QuestWorkValue(reader.GetByte());
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
byte? high = null, low = null;
EQuestWorkMode mode = EQuestWorkMode.Bitwise;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
string? propertyName = reader.GetString();
if (propertyName == null || !reader.Read())
throw new JsonException();
switch (propertyName)
{
case nameof(QuestWorkValue.High):
high = reader.GetByte();
break;
case nameof(QuestWorkValue.Low):
low = reader.GetByte();
break;
case nameof(QuestWorkValue.Mode):
mode = new QuestWorkModeConverter().Read(ref reader, typeof(EQuestWorkMode), options);
break;
default:
throw new JsonException();
}
break;
case JsonTokenType.EndObject:
return new QuestWorkValue(high, low, mode);
default:
throw new JsonException();
}
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, QuestWorkValue value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}

View file

@ -3,4 +3,8 @@ 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

@ -17,11 +17,13 @@
<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>

View file

@ -12,5 +12,7 @@ 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

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

View file

@ -38,6 +38,18 @@ 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, 0L);
private static long _lastHandledAddr;
private static readonly TimeSpan _consecutiveResetWindow = TimeSpan.FromSeconds(10L);
private readonly IAddonLifecycle _addonLifecycle;
private readonly ILogger<CreditsController> _logger;
@ -57,76 +69,119 @@ internal sealed class CreditsController : IDisposable
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler);
_logger.LogDebug("CreditsController: registered listeners and ready to skip up to {MaxSkips} successive cutscenes.", _maxConsecutiveSkips);
}
}
}
private unsafe void CreditScrollPostSetup(AddonEvent type, AddonArgs args)
{
_logger.LogInformation("Closing Credits sequence scroll post-setup");
if (args.Addon.Address == IntPtr.Zero)
HandleCutscene(delegate
{
_logger.LogInformation("CreditScrollPostSetup: 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;
}
}
_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)
{
_logger.LogInformation("Closing Credits sequence post-setup");
if (args.Addon.Address == IntPtr.Zero)
HandleCutscene(delegate
{
_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;
}
}
_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)
{
_logger.LogInformation("Closing CreditPlayer");
if (args.Addon.Address == IntPtr.Zero)
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)
{
long num = ((args.Addon.Address == IntPtr.Zero) ? 0 : ((IntPtr)args.Addon.Address).ToInt64());
if (num == 0L)
{
_logger.LogInformation("HandleCutscene: Addon address is zero, skipping.");
return;
}
lock (_lock)
{
if (_lastHandledAt != DateTime.MinValue && DateTime.Now - _lastHandledAt > _consecutiveResetWindow)
{
if (_handledConsecutiveCutscenes != 0)
{
_logger.LogDebug("HandleCutscene: long pause detected ({Elapsed}), resetting consecutive counter (was {Was}).", DateTime.Now - _lastHandledAt, _handledConsecutiveCutscenes);
}
_handledConsecutiveCutscenes = 0;
}
if (DateTime.Now - _lastHandledAt < _reentrancySuppress && num == _lastHandledAddr)
{
_logger.LogDebug("HandleCutscene: suppressed re-entrant invocation for same addon (addr=0x{Address:X}).", num);
return;
}
if (_handledConsecutiveCutscenes >= _maxConsecutiveSkips)
{
_logger.LogInformation("HandleCutscene: reached max consecutive skips ({MaxSkips}), unregistering listeners.", _maxConsecutiveSkips);
TryUnregisterAndClear();
return;
}
_lastHandledAt = DateTime.Now;
_lastHandledAddr = num;
_handledConsecutiveCutscenes++;
_logger.LogDebug("HandleCutscene: handling cutscene #{Count} (addr=0x{Address:X}).", _handledConsecutiveCutscenes, num);
}
try
{
nativeAction();
_logger.LogInformation("HandleCutscene: native action executed for addon at 0x{Address:X}.", num);
}
catch (Exception exception)
{
_logger.LogError(exception, "HandleCutscene: exception while executing native action for addon at 0x{Address:X}.", num);
}
lock (_lock)
{
if (_handledConsecutiveCutscenes >= _maxConsecutiveSkips && !_deferDisposeUntilCutsceneEnds)
{
_logger.LogDebug("HandleCutscene: max handled reached and no defer active, unregistering now.");
TryUnregisterAndClear();
}
else
{
_logger.LogDebug("HandleCutscene: leaving listeners registered (handled {Count}/{Max}).", _handledConsecutiveCutscenes, _maxConsecutiveSkips);
}
}
}
private void TryUnregisterAndClear()
{
if (_registeredLifecycle != null)
{
try
{
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler);
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler);
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler);
_logger.LogDebug("TryUnregisterAndClear: listeners unregistered successfully.");
}
catch (Exception exception)
{
_logger.LogError(exception, "TryUnregisterAndClear: exception while unregistering listeners.");
}
}
_registeredLifecycle = null;
_instance = null;
}
}
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
address->Close(fireCallback: true);
_handledConsecutiveCutscenes = 0;
_lastHandledAddr = 0L;
_lastHandledAt = DateTime.MinValue;
}
public static void DeferDisposeUntilCutsceneEnds()
@ -181,6 +236,9 @@ 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

@ -247,6 +247,22 @@ internal static class SkipCondition
return true;
}
}
item = skipConditions.Item;
if (item != null && item.InInventory && step != null && step.ItemId.HasValue)
{
InventoryManager* ptr2 = InventoryManager.Instance();
if (ptr2->GetInventoryItemCount(step.ItemId.Value, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0 || ptr2->GetInventoryItemCount(step.ItemId.Value, isHq: true, checkEquipped: true, checkArmory: true, 0) > 0)
{
logger.LogInformation("Skipping step, item with itemId {ItemId} already in inventory", step.ItemId.Value);
return true;
}
}
item = skipConditions.Item;
if (item != null && item.BetterOrEqualItemEquipped && step != null && step.ItemId.HasValue && IsBetterOrEqualItemEquipped(step.ItemId.Value))
{
logger.LogInformation("Skipping step, better or equal item than {ItemId} is already equipped", step.ItemId.Value);
return true;
}
return false;
}
@ -461,6 +477,39 @@ internal static class SkipCondition
return false;
}
private unsafe bool IsBetterOrEqualItemEquipped(uint itemId)
{
if (clientState.LocalPlayer == null)
{
return false;
}
InventoryManager* ptr = InventoryManager.Instance();
if (ptr == null)
{
return false;
}
uint itemLevel = gameFunctions.GetItemLevel(itemId);
if (itemLevel == 0)
{
logger.LogWarning("Could not get item level for item {ItemId}, skipping equip step", itemId);
return true;
}
if (ptr->GetInventoryItemCount(itemId, isHq: false, checkEquipped: true, checkArmory: true, 0) == 0 && ptr->GetInventoryItemCount(itemId, isHq: true, checkEquipped: true, checkArmory: true, 0) == 0)
{
logger.LogInformation("Item {ItemId} not found in inventory, skipping equip step", itemId);
return true;
}
InventoryItem* inventorySlot = ptr->GetInventoryContainer(InventoryType.EquippedItems)->GetInventorySlot(0);
if (inventorySlot == null || inventorySlot->ItemId == 0)
{
logger.LogDebug("No item equipped in main hand slot, should equip item {ItemId}", itemId);
return false;
}
uint itemLevel2 = gameFunctions.GetItemLevel(inventorySlot->ItemId);
logger.LogDebug("Comparing equipped item level {EquippedLevel} with target item level {TargetLevel}", itemLevel2, itemLevel);
return itemLevel2 >= itemLevel;
}
public override ETaskResult Update()
{
return ETaskResult.SkipRemainingTasksForStep;

View file

@ -45,6 +45,8 @@ internal sealed class CommandHandler : IDisposable
private readonly QuestSelectionWindow _questSelectionWindow;
private readonly QuestSequenceWindow _questSequenceWindow;
private readonly JournalProgressWindow _journalProgressWindow;
private readonly PriorityWindow _priorityWindow;
@ -65,9 +67,11 @@ internal sealed class CommandHandler : IDisposable
private readonly Configuration _configuration;
private readonly ChangelogWindow _changelogWindow;
private IReadOnlyList<uint> _previouslyUnlockedUnlockLinks = Array.Empty<uint>();
public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController, MovementController movementController, QuestRegistry questRegistry, ConfigWindow configWindow, DebugOverlay debugOverlay, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, QuestSelectionWindow questSelectionWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, ITargetManager targetManager, QuestFunctions questFunctions, GameFunctions gameFunctions, AetheryteFunctions aetheryteFunctions, IDataManager dataManager, IClientState clientState, IObjectTable objectTable, Configuration configuration)
public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController, MovementController movementController, QuestRegistry questRegistry, ConfigWindow configWindow, DebugOverlay debugOverlay, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, QuestSelectionWindow questSelectionWindow, QuestSequenceWindow questSequenceWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, ITargetManager targetManager, QuestFunctions questFunctions, GameFunctions gameFunctions, AetheryteFunctions aetheryteFunctions, IDataManager dataManager, IClientState clientState, IObjectTable objectTable, Configuration configuration, ChangelogWindow changelogWindow)
{
_commandManager = commandManager;
_chatGui = chatGui;
@ -79,6 +83,7 @@ internal sealed class CommandHandler : IDisposable
_oneTimeSetupWindow = oneTimeSetupWindow;
_questWindow = questWindow;
_questSelectionWindow = questSelectionWindow;
_questSequenceWindow = questSequenceWindow;
_journalProgressWindow = journalProgressWindow;
_priorityWindow = priorityWindow;
_targetManager = targetManager;
@ -89,10 +94,11 @@ internal sealed class CommandHandler : IDisposable
_clientState = clientState;
_objectTable = objectTable;
_configuration = configuration;
_changelogWindow = changelogWindow;
_clientState.Logout += OnLogout;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{
HelpMessage = string.Join(Environment.NewLine + "\t", "Opens the Questing window", "/qst help - displays simplified commands", "/qst help-all - displays all available commands", "/qst config - opens the configuration window", "/qst start - starts doing quests", "/qst stop - stops doing quests")
HelpMessage = string.Join(Environment.NewLine + "\t", "Opens the Questing window", "/qst help - displays simplified commands", "/qst help-all - displays all available commands", "/qst config - opens the configuration window", "/qst changelog - opens the changelog window", "/qst start - starts doing quests", "/qst stop - stops doing quests")
});
}
@ -110,17 +116,19 @@ internal sealed class CommandHandler : IDisposable
_chatGui.Print("/qst help - displays simplified commands", "Questionable", 576);
_chatGui.Print("/qst help-all - displays all available commands", "Questionable", 576);
_chatGui.Print("/qst config - opens the configuration window", "Questionable", 576);
_chatGui.Print("/qst changelog - opens the changelog window", "Questionable", 576);
_chatGui.Print("/qst start - starts doing quests", "Questionable", 576);
_chatGui.Print("/qst stop - stops doing quests", "Questionable", 576);
_chatGui.Print("/qst reload - reload all quest data", "Questionable", 576);
break;
case "help-all":
case "ha":
case "help-all":
_chatGui.Print("Available commands:", "Questionable", 576);
_chatGui.Print("/qst - toggles the Questing window", "Questionable", 576);
_chatGui.Print("/qst help - displays available commands", "Questionable", 576);
_chatGui.Print("/qst help-all - displays all available commands", "Questionable", 576);
_chatGui.Print("/qst config - opens the configuration window", "Questionable", 576);
_chatGui.Print("/qst changelog - opens the changelog window", "Questionable", 576);
_chatGui.Print("/qst start - starts doing quests", "Questionable", 576);
_chatGui.Print("/qst stop - stops doing quests", "Questionable", 576);
_chatGui.Print("/qst reload - reload all quest data", "Questionable", 576);
@ -134,6 +142,10 @@ internal sealed class CommandHandler : IDisposable
case "config":
_configWindow.ToggleOrUncollapse();
break;
case "cl":
case "changelog":
_changelogWindow.ToggleOrUncollapse();
break;
case "start":
_questWindow.IsOpenAndUncollapsed = true;
_questController.Start("Start command");
@ -245,6 +257,25 @@ internal sealed class CommandHandler : IDisposable
}
}
break;
case 3:
switch (text[1])
{
default:
return;
case 'i':
if (text == "sim")
{
SetSimulatedQuest(array.Skip(1).ToArray());
}
return;
case 'e':
break;
}
if (!(text == "seq"))
{
break;
}
goto IL_0209;
case 12:
switch (text[0])
{
@ -282,11 +313,15 @@ internal sealed class CommandHandler : IDisposable
case 9:
switch (text[0])
{
default:
return;
case 's':
break;
case 'f':
{
if (!(text == "festivals"))
{
break;
return;
}
List<string> list4 = new List<string>();
for (byte b8 = 0; b8 < 4; b8++)
@ -307,14 +342,14 @@ internal sealed class CommandHandler : IDisposable
{
_chatGui.Print(" " + item6, "Questionable", 576);
}
break;
return;
}
}
case 'a':
{
if (!(text == "aethernet"))
{
break;
return;
}
ushort territoryType = _clientState.TerritoryType;
Dictionary<EAetheryteLocation, string> values = AethernetShardConverter.Values;
@ -341,7 +376,7 @@ internal sealed class CommandHandler : IDisposable
if (hashSet2.Count == 0)
{
_chatGui.Print("No aethernet shards found in current zone.", "Questionable", 576);
break;
return;
}
foreach (KeyValuePair<EAetheryteLocation, string> item8 in values)
{
@ -398,11 +433,15 @@ internal sealed class CommandHandler : IDisposable
_chatGui.Print("", "Questionable", 576);
}
}
return;
}
}
}
if (!(text == "sequences"))
{
break;
}
}
}
break;
goto IL_0209;
case 5:
if (text == "setup")
{
@ -415,12 +454,6 @@ internal sealed class CommandHandler : IDisposable
ConfigureDebugOverlay(array.Skip(1).ToArray());
}
break;
case 3:
if (text == "sim")
{
SetSimulatedQuest(array.Skip(1).ToArray());
}
break;
case 7:
if (text == "mountid")
{
@ -607,6 +640,9 @@ internal sealed class CommandHandler : IDisposable
case 8:
case 10:
break;
IL_0209:
_questSequenceWindow.ToggleOrUncollapse();
break;
}
}

View file

@ -142,6 +142,14 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private DateTime _lastAutoRefresh = DateTime.MinValue;
private bool _lastEscDown;
private int _escPressCount;
private DateTime _lastEscPressTime = DateTime.MinValue;
private static readonly TimeSpan EscDoublePressWindow = TimeSpan.FromSeconds(1L);
private const char ClipboardSeparator = ';';
public EAutomationType AutomationType
@ -307,9 +315,40 @@ internal sealed class QuestController : MiniTaskController<QuestController>
StopAllDueToConditionFailed("HP = 0");
}
}
else if (_configuration.General.UseEscToCancelQuesting && _keyState[VirtualKey.ESCAPE] && !_taskQueue.AllTasksComplete)
else if (_configuration.General.UseEscToCancelQuesting)
{
StopAllDueToConditionFailed("ESC pressed");
if (_keyState[VirtualKey.ESCAPE] && !_lastEscDown)
{
DateTime now = DateTime.Now;
if (now - _lastEscPressTime <= EscDoublePressWindow)
{
_escPressCount++;
}
else
{
_escPressCount = 1;
}
_lastEscPressTime = now;
if (_escPressCount >= 2)
{
if (!_taskQueue.AllTasksComplete)
{
StopAllDueToConditionFailed("ESC pressed twice");
}
_escPressCount = 0;
}
}
if (!_keyState[VirtualKey.ESCAPE] && DateTime.Now - _lastEscPressTime > EscDoublePressWindow)
{
_escPressCount = 0;
}
_lastEscDown = _keyState[VirtualKey.ESCAPE];
}
else
{
_lastEscDown = _keyState[VirtualKey.ESCAPE];
_escPressCount = 0;
_lastEscPressTime = DateTime.MinValue;
}
if (_configuration.Stop.Enabled && _configuration.Stop.LevelToStopAfter && _clientState.LocalPlayer != null)
{
@ -344,14 +383,14 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (step == 0 || step == 255)
{
flag2 = true;
goto IL_0422;
goto IL_04f5;
}
}
flag2 = false;
goto IL_0422;
goto IL_04f5;
}
goto IL_0426;
IL_0426:
goto IL_04f9;
IL_04f9:
if (flag && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15.0))
{
lock (_progressLock)
@ -367,9 +406,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>
UpdateCurrentTask();
}
return;
IL_0422:
IL_04f5:
flag = flag2;
goto IL_0426;
goto IL_04f9;
}
private void CheckAutoRefreshCondition()

View file

@ -0,0 +1,629 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Questionable.Model.Changelog;
namespace Questionable.Data;
internal static class ChangelogData
{
public static readonly List<ChangelogEntry> Changelogs;
static ChangelogData()
{
int num = 36;
List<ChangelogEntry> list = new List<ChangelogEntry>(num);
CollectionsMarshal.SetCount(list, num);
Span<ChangelogEntry> span = CollectionsMarshal.AsSpan(list);
int num2 = 0;
ref ChangelogEntry reference = ref span[num2];
DateOnly releaseDate = new DateOnly(2025, 11, 18);
int num3 = 3;
List<ChangeEntry> list2 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list2, num3);
Span<ChangeEntry> span2 = CollectionsMarshal.AsSpan(list2);
int num4 = 0;
ref ChangeEntry reference2 = ref span2[num4];
int num5 = 1;
List<string> list3 = new List<string>(num5);
CollectionsMarshal.SetCount(list3, num5);
Span<string> span3 = CollectionsMarshal.AsSpan(list3);
int index = 0;
span3[index] = "Added new fields to quest schema";
reference2 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list3);
num4++;
ref ChangeEntry reference3 = ref span2[num4];
index = 3;
List<string> list4 = new List<string>(index);
CollectionsMarshal.SetCount(list4, index);
span3 = CollectionsMarshal.AsSpan(list4);
num5 = 0;
span3[num5] = "A Faerie Tale Come True";
num5++;
span3[num5] = "Constant Cravings";
num5++;
span3[num5] = "A Bridge Too Full";
reference3 = new ChangeEntry(EChangeCategory.QuestUpdates, "Added new quest paths", list4);
num4++;
ref ChangeEntry reference4 = ref span2[num4];
num5 = 3;
List<string> list5 = new List<string>(num5);
CollectionsMarshal.SetCount(list5, num5);
span3 = CollectionsMarshal.AsSpan(list5);
index = 0;
span3[index] = "Fixed various quest schemas";
index++;
span3[index] = "Fixed changelog bullet point encoding";
index++;
span3[index] = "Fixed item use to wait until item is used before next action";
reference4 = new ChangeEntry(EChangeCategory.Fixed, "Bug fixes", list5);
reference = new ChangelogEntry("7.38.1", releaseDate, list2);
num2++;
ref ChangelogEntry reference5 = ref span[num2];
DateOnly releaseDate2 = new DateOnly(2025, 11, 17);
num4 = 5;
List<ChangeEntry> list6 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list6, num4);
span2 = CollectionsMarshal.AsSpan(list6);
num3 = 0;
ref ChangeEntry reference6 = ref span2[num3];
index = 2;
List<string> list7 = new List<string>(index);
CollectionsMarshal.SetCount(list7, index);
span3 = CollectionsMarshal.AsSpan(list7);
num5 = 0;
span3[num5] = "Quest sequence window to show expected sequences in each quest (with quest searching)";
num5++;
span3[num5] = "Changelog";
reference6 = new ChangeEntry(EChangeCategory.Added, "Major features", list7);
num3++;
ref ChangeEntry reference7 = ref span2[num3];
num5 = 2;
List<string> list8 = new List<string>(num5);
CollectionsMarshal.SetCount(list8, num5);
span3 = CollectionsMarshal.AsSpan(list8);
index = 0;
span3[index] = "Updated quest schemas";
index++;
span3[index] = "Added search bar to preferred mounts and capitalization to mirror game mount names";
reference7 = new ChangeEntry(EChangeCategory.Changed, "Improvements", list8);
num3++;
ref ChangeEntry reference8 = ref span2[num3];
index = 3;
List<string> list9 = new List<string>(index);
CollectionsMarshal.SetCount(list9, index);
span3 = CollectionsMarshal.AsSpan(list9);
num5 = 0;
span3[num5] = "Renamed IsQuestCompleted → IsQuestComplete";
num5++;
span3[num5] = "Renamed IsQuestAvailable → IsReadyToAcceptQuest";
num5++;
span3[num5] = "Added GetCurrentTask IPC";
reference8 = new ChangeEntry(EChangeCategory.Changed, "IPC changes", list9);
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added all Hildibrand quests");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed credits/cutscenes playback");
reference5 = new ChangelogEntry("7.38.0", releaseDate2, list6);
num2++;
ref ChangelogEntry reference9 = ref span[num2];
DateOnly releaseDate3 = new DateOnly(2025, 11, 8);
num3 = 1;
List<ChangeEntry> list10 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list10, num3);
span2 = CollectionsMarshal.AsSpan(list10);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Fall Guys quest (Just Crowning Around)");
reference9 = new ChangelogEntry("6.38", releaseDate3, list10);
num2++;
ref ChangelogEntry reference10 = ref span[num2];
DateOnly releaseDate4 = new DateOnly(2025, 11, 8);
num4 = 1;
List<ChangeEntry> list11 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list11, num4);
span2 = CollectionsMarshal.AsSpan(list11);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Cosmic Exploration and various unlock quests");
reference10 = new ChangelogEntry("6.37", releaseDate4, list11);
num2++;
ref ChangelogEntry reference11 = ref span[num2];
DateOnly releaseDate5 = new DateOnly(2025, 11, 2);
num3 = 1;
List<ChangeEntry> list12 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list12, num3);
span2 = CollectionsMarshal.AsSpan(list12);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy Rank 6 quest (With High Spirits)");
reference11 = new ChangelogEntry("6.36", releaseDate5, list12);
num2++;
ref ChangelogEntry reference12 = ref span[num2];
DateOnly releaseDate6 = new DateOnly(2025, 10, 28);
num4 = 1;
List<ChangeEntry> list13 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list13, num4);
span2 = CollectionsMarshal.AsSpan(list13);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed level 3 MSQ handling if character started on non-XP buff world");
reference12 = new ChangelogEntry("6.35", releaseDate6, list13);
num2++;
ref ChangelogEntry reference13 = ref span[num2];
DateOnly releaseDate7 = new DateOnly(2025, 10, 23);
num3 = 2;
List<ChangeEntry> list14 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list14, num3);
span2 = CollectionsMarshal.AsSpan(list14);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.Added, "Added clear priority quests on logout and on completion config settings");
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.Fixed, "Fixed priority quest importing to respect import order");
reference13 = new ChangelogEntry("6.34", releaseDate7, list14);
num2++;
ref ChangelogEntry reference14 = ref span[num2];
DateOnly releaseDate8 = new DateOnly(2025, 10, 23);
num4 = 1;
List<ChangeEntry> list15 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list15, num4);
span2 = CollectionsMarshal.AsSpan(list15);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed RSR combat module");
reference14 = new ChangelogEntry("6.33", releaseDate8, list15);
num2++;
ref ChangelogEntry reference15 = ref span[num2];
DateOnly releaseDate9 = new DateOnly(2025, 10, 23);
num3 = 1;
List<ChangeEntry> list16 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list16, num3);
span2 = CollectionsMarshal.AsSpan(list16);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy Rank 5 quest (Forged in Corn)");
reference15 = new ChangelogEntry("6.32", releaseDate9, list16);
num2++;
ref ChangelogEntry reference16 = ref span[num2];
DateOnly releaseDate10 = new DateOnly(2025, 10, 21);
num4 = 1;
List<ChangeEntry> list17 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list17, num4);
span2 = CollectionsMarshal.AsSpan(list17);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.Changed, "Added checks for moogle and allied society quests when using add all available quests");
reference16 = new ChangelogEntry("6.31", releaseDate10, list17);
num2++;
ref ChangelogEntry reference17 = ref span[num2];
DateOnly releaseDate11 = new DateOnly(2025, 10, 21);
num3 = 1;
List<ChangeEntry> list18 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list18, num3);
span2 = CollectionsMarshal.AsSpan(list18);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.Added, "Added button to journal that allows adding all available quests to priority");
reference17 = new ChangelogEntry("6.30", releaseDate11, list18);
num2++;
ref ChangelogEntry reference18 = ref span[num2];
DateOnly releaseDate12 = new DateOnly(2025, 10, 20);
num4 = 2;
List<ChangeEntry> list19 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list19, num4);
span2 = CollectionsMarshal.AsSpan(list19);
num3 = 0;
ref ChangeEntry reference19 = ref span2[num3];
num5 = 2;
List<string> list20 = new List<string>(num5);
CollectionsMarshal.SetCount(list20, num5);
span3 = CollectionsMarshal.AsSpan(list20);
index = 0;
span3[index] = "Added item count to combat handling rework";
index++;
span3[index] = "Updated Pandora conflicting features";
reference19 = new ChangeEntry(EChangeCategory.Changed, "Combat handling improvements", list20);
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed quest to purchase Gysahl Greens if not in inventory");
reference18 = new ChangelogEntry("6.29", releaseDate12, list19);
num2++;
ref ChangelogEntry reference20 = ref span[num2];
DateOnly releaseDate13 = new DateOnly(2025, 10, 19);
num3 = 1;
List<ChangeEntry> list21 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list21, num3);
span2 = CollectionsMarshal.AsSpan(list21);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.Changed, "Reworked kill count combat handling - combat and enemy kills are now processed instantly");
reference20 = new ChangelogEntry("6.28", releaseDate13, list21);
num2++;
ref ChangelogEntry reference21 = ref span[num2];
DateOnly releaseDate14 = new DateOnly(2025, 10, 18);
num4 = 2;
List<ChangeEntry> list22 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list22, num4);
span2 = CollectionsMarshal.AsSpan(list22);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.Changed, "Improved Aether Current checking logic");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed Chocobo Taxi Stand CheckSkip error and Patch 7.3 Fantasia unlock quest date/time");
reference21 = new ChangelogEntry("6.27", releaseDate14, list22);
num2++;
ref ChangelogEntry reference22 = ref span[num2];
DateOnly releaseDate15 = new DateOnly(2025, 10, 18);
num3 = 1;
List<ChangeEntry> list23 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list23, num3);
span2 = CollectionsMarshal.AsSpan(list23);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 4 quests");
reference22 = new ChangelogEntry("6.26", releaseDate15, list23);
num2++;
ref ChangelogEntry reference23 = ref span[num2];
DateOnly releaseDate16 = new DateOnly(2025, 10, 17);
num4 = 1;
List<ChangeEntry> list24 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list24, num4);
span2 = CollectionsMarshal.AsSpan(list24);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added All Saints' Wake 2025 quests and 7.35 Yok Huy rank 4 quests");
reference23 = new ChangelogEntry("6.25", releaseDate16, list24);
num2++;
ref ChangelogEntry reference24 = ref span[num2];
DateOnly releaseDate17 = new DateOnly(2025, 10, 16);
num3 = 1;
List<ChangeEntry> list25 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list25, num3);
span2 = CollectionsMarshal.AsSpan(list25);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 4 quests and Deep Dungeon quest");
reference24 = new ChangelogEntry("6.24", releaseDate17, list25);
num2++;
ref ChangelogEntry reference25 = ref span[num2];
DateOnly releaseDate18 = new DateOnly(2025, 10, 13);
num4 = 1;
List<ChangeEntry> list26 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list26, num4);
span2 = CollectionsMarshal.AsSpan(list26);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 3 quest (Larder Logistics)");
reference25 = new ChangelogEntry("6.23", releaseDate18, list26);
num2++;
ref ChangelogEntry reference26 = ref span[num2];
DateOnly releaseDate19 = new DateOnly(2025, 10, 12);
num3 = 3;
List<ChangeEntry> list27 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list27, num3);
span2 = CollectionsMarshal.AsSpan(list27);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.Changed, "Prevent disabled or Locked quests from being started as 'Start as next quest'");
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 3 quests");
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.Fixed, "Fixed Yok Huy quest and journal quest chain priority issues");
reference26 = new ChangelogEntry("6.22", releaseDate19, list27);
num2++;
ref ChangelogEntry reference27 = ref span[num2];
DateOnly releaseDate20 = new DateOnly(2025, 10, 12);
num4 = 2;
List<ChangeEntry> list28 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list28, num4);
span2 = CollectionsMarshal.AsSpan(list28);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.Added, "Added expansion abbreviation to journal window");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 3 quests");
reference27 = new ChangelogEntry("6.21", releaseDate20, list28);
num2++;
ref ChangelogEntry reference28 = ref span[num2];
DateOnly releaseDate21 = new DateOnly(2025, 10, 10);
num3 = 2;
List<ChangeEntry> list29 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list29, num3);
span2 = CollectionsMarshal.AsSpan(list29);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.Changed, "Allow completed repeatable quests to be used with 'Add quest and requirements to priority' feature");
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 1 quest (A Work of Cart)");
reference28 = new ChangelogEntry("6.20", releaseDate21, list29);
num2++;
ref ChangelogEntry reference29 = ref span[num2];
DateOnly releaseDate22 = new DateOnly(2025, 10, 9);
num4 = 3;
List<ChangeEntry> list30 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list30, num4);
span2 = CollectionsMarshal.AsSpan(list30);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.Added, "Added config to batch Allied Society quest turn-ins");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.Changed, "Repeatable quests now show correct availability state in journal");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 2 quests");
reference29 = new ChangelogEntry("6.19", releaseDate22, list30);
num2++;
ref ChangelogEntry reference30 = ref span[num2];
DateOnly releaseDate23 = new DateOnly(2025, 10, 9);
num3 = 2;
List<ChangeEntry> list31 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list31, num3);
span2 = CollectionsMarshal.AsSpan(list31);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.Changed, "Show once completed quests with improved state display");
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy daily quest and improvements to various Yok Huy quests");
reference30 = new ChangelogEntry("6.18", releaseDate23, list31);
num2++;
ref ChangelogEntry reference31 = ref span[num2];
DateOnly releaseDate24 = new DateOnly(2025, 10, 8);
num4 = 1;
List<ChangeEntry> list32 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list32, num4);
span2 = CollectionsMarshal.AsSpan(list32);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Yok Huy rank 1 and rank 2 quests");
reference31 = new ChangelogEntry("6.17", releaseDate24, list32);
num2++;
ref ChangelogEntry reference32 = ref span[num2];
DateOnly releaseDate25 = new DateOnly(2025, 10, 8);
num3 = 1;
List<ChangeEntry> list33 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list33, num3);
span2 = CollectionsMarshal.AsSpan(list33);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Deep Dungeon quest (Faerie Tale)");
reference32 = new ChangelogEntry("6.16", releaseDate25, list33);
num2++;
ref ChangelogEntry reference33 = ref span[num2];
DateOnly releaseDate26 = new DateOnly(2025, 10, 8);
num4 = 2;
List<ChangeEntry> list34 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list34, num4);
span2 = CollectionsMarshal.AsSpan(list34);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.Changed, "Dalamud cleanup");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed quest level requirement check log spam");
reference33 = new ChangelogEntry("6.15", releaseDate26, list34);
num2++;
ref ChangelogEntry reference34 = ref span[num2];
DateOnly releaseDate27 = new DateOnly(2025, 10, 8);
num3 = 1;
List<ChangeEntry> list35 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list35, num3);
span2 = CollectionsMarshal.AsSpan(list35);
num4 = 0;
span2[num4] = new ChangeEntry(EChangeCategory.Fixed, "Fixed abandoned quest check logic if quest were MSQ");
reference34 = new ChangelogEntry("6.14", releaseDate27, list35);
num2++;
ref ChangelogEntry reference35 = ref span[num2];
DateOnly releaseDate28 = new DateOnly(2025, 10, 8);
num4 = 2;
List<ChangeEntry> list36 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list36, num4);
span2 = CollectionsMarshal.AsSpan(list36);
num3 = 0;
ref ChangeEntry reference36 = ref span2[num3];
index = 3;
List<string> list37 = new List<string>(index);
CollectionsMarshal.SetCount(list37, index);
span3 = CollectionsMarshal.AsSpan(list37);
num5 = 0;
span3[num5] = "Context menu option to add required quests and their chain to priority list";
num5++;
span3[num5] = "AetheryteShortcut to multiple quests";
num5++;
span3[num5] = "Artisan as a recommended plugin/dependency";
reference36 = new ChangeEntry(EChangeCategory.Added, "Quest improvements", list37);
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed abandoned quest check and priority list issues");
reference35 = new ChangelogEntry("6.13", releaseDate28, list36);
num2++;
ref ChangelogEntry reference37 = ref span[num2];
DateOnly releaseDate29 = new DateOnly(2025, 10, 7);
num3 = 4;
List<ChangeEntry> list38 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list38, num3);
span2 = CollectionsMarshal.AsSpan(list38);
num4 = 0;
ref ChangeEntry reference38 = ref span2[num4];
num5 = 4;
List<string> list39 = new List<string>(num5);
CollectionsMarshal.SetCount(list39, num5);
span3 = CollectionsMarshal.AsSpan(list39);
index = 0;
span3[index] = "FATE combat handling with auto level syncing";
index++;
span3[index] = "Start accepted quests from journal with 'Start as next quest'";
index++;
span3[index] = "Update quest tracking when quests are hidden or prioritised in game";
index++;
span3[index] = "QuestMap as a recommended plugin/dependency";
reference38 = new ChangeEntry(EChangeCategory.Added, "FATE and quest tracking", list39);
num4++;
ref ChangeEntry reference39 = ref span2[num4];
index = 3;
List<string> list40 = new List<string>(index);
CollectionsMarshal.SetCount(list40, index);
span3 = CollectionsMarshal.AsSpan(list40);
num5 = 0;
span3[num5] = "Always prioritise next quest during teleportation/zone transitions";
num5++;
span3[num5] = "Improved accepted quest logic with abandoned quest detection";
num5++;
span3[num5] = "Show quests without quest paths as Locked";
reference39 = new ChangeEntry(EChangeCategory.Changed, "Quest prioritisation improvements", list40);
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.35 Deep Dungeon, Hildibrand, Yok Huy, Monster Hunter Wilds Collab, and Doman Enclave quests");
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.Fixed, "Fixed accepted/active quest display and Hildibrand quest issues");
reference37 = new ChangelogEntry("6.12", releaseDate29, list38);
num2++;
ref ChangelogEntry reference40 = ref span[num2];
DateOnly releaseDate30 = new DateOnly(2025, 10, 3);
num4 = 1;
List<ChangeEntry> list41 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list41, num4);
span2 = CollectionsMarshal.AsSpan(list41);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.Changed, "Added remaining checks for quest priority to prevent infinite teleport looping");
reference40 = new ChangelogEntry("6.11", releaseDate30, list41);
num2++;
ref ChangelogEntry reference41 = ref span[num2];
DateOnly releaseDate31 = new DateOnly(2025, 10, 2);
num3 = 1;
List<ChangeEntry> list42 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list42, num3);
span2 = CollectionsMarshal.AsSpan(list42);
num4 = 0;
ref ChangeEntry reference42 = ref span2[num4];
num5 = 2;
List<string> list43 = new List<string>(num5);
CollectionsMarshal.SetCount(list43, num5);
span3 = CollectionsMarshal.AsSpan(list43);
index = 0;
span3[index] = "Don't show quests as available if player doesn't meet level requirements";
index++;
span3[index] = "Updated 'required for MSQ' text in Crystal Tower quest preset window";
reference42 = new ChangeEntry(EChangeCategory.Changed, "Quest window improvements", list43);
reference41 = new ChangelogEntry("6.10", releaseDate31, list42);
num2++;
ref ChangelogEntry reference43 = ref span[num2];
DateOnly releaseDate32 = new DateOnly(2025, 9, 21);
num4 = 5;
List<ChangeEntry> list44 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list44, num4);
span2 = CollectionsMarshal.AsSpan(list44);
num3 = 0;
ref ChangeEntry reference44 = ref span2[num3];
index = 4;
List<string> list45 = new List<string>(index);
CollectionsMarshal.SetCount(list45, index);
span3 = CollectionsMarshal.AsSpan(list45);
num5 = 0;
span3[num5] = "Reworked event quest handling - automatically displays when events are active";
num5++;
span3[num5] = "Reworked journal system with improved filtering and display";
num5++;
span3[num5] = "Reworked Priority Quests tab (Manual Priority and Quest Presets)";
num5++;
span3[num5] = "Quest path viewer site (https://wigglymuffin.github.io/FFXIV-Tools/)";
reference44 = new ChangeEntry(EChangeCategory.Added, "Major system reworks", list45);
num3++;
ref ChangeEntry reference45 = ref span2[num3];
num5 = 4;
List<string> list46 = new List<string>(num5);
CollectionsMarshal.SetCount(list46, num5);
span3 = CollectionsMarshal.AsSpan(list46);
index = 0;
span3[index] = "Questionable.IsQuestCompleted";
index++;
span3[index] = "Questionable.IsQuestAvailable";
index++;
span3[index] = "Questionable.IsQuestAccepted";
index++;
span3[index] = "Questionable.IsQuestUnobtainable";
reference45 = new ChangeEntry(EChangeCategory.Added, "New IPC commands", list46);
num3++;
ref ChangeEntry reference46 = ref span2[num3];
index = 5;
List<string> list47 = new List<string>(index);
CollectionsMarshal.SetCount(list47, index);
span3 = CollectionsMarshal.AsSpan(list47);
num5 = 0;
span3[num5] = "Improved JSON quest validation with specific error reasons";
num5++;
span3[num5] = "Added stop at sequence stop condition";
num5++;
span3[num5] = "Improved Pandora plugin conflict detection";
num5++;
span3[num5] = "Improved DialogueChoices regex matching";
num5++;
span3[num5] = "Improved refresh checker for all quest states";
reference46 = new ChangeEntry(EChangeCategory.Changed, "Various improvements", list47);
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added 7.31 Occult Crescent quests");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed cutscene crashes, Single Player Duty triggers, and various quest issues");
reference43 = new ChangelogEntry("6.9", releaseDate32, list44);
num2++;
ref ChangelogEntry reference47 = ref span[num2];
DateOnly releaseDate33 = new DateOnly(2025, 9, 2);
num3 = 4;
List<ChangeEntry> list48 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list48, num3);
span2 = CollectionsMarshal.AsSpan(list48);
num4 = 0;
ref ChangeEntry reference48 = ref span2[num4];
num5 = 4;
List<string> list49 = new List<string>(num5);
CollectionsMarshal.SetCount(list49, num5);
span3 = CollectionsMarshal.AsSpan(list49);
index = 0;
span3[index] = "Help commands and priority quest command";
index++;
span3[index] = "Prevent 'CompleteQuest' step setting";
index++;
span3[index] = "Duty counts and controls in 'Quest Battles' tab";
index++;
span3[index] = "'Refresh quest timer' setting (WIP)";
reference48 = new ChangeEntry(EChangeCategory.Added, "Command and UI improvements", list49);
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.Changed, "Improved 'Clear All' buttons to require CTRL being held");
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Zodiac quests and 7.31 Cosmic/Occult Crescent quests");
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.Fixed, "Fixed Fishing for Friendship and Cosmic Exploration quests");
reference47 = new ChangelogEntry("6.8", releaseDate33, list48);
num2++;
ref ChangelogEntry reference49 = ref span[num2];
DateOnly releaseDate34 = new DateOnly(2025, 8, 27);
num4 = 4;
List<ChangeEntry> list50 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list50, num4);
span2 = CollectionsMarshal.AsSpan(list50);
num3 = 0;
ref ChangeEntry reference50 = ref span2[num3];
index = 2;
List<string> list51 = new List<string>(index);
CollectionsMarshal.SetCount(list51, index);
span3 = CollectionsMarshal.AsSpan(list51);
num5 = 0;
span3[num5] = "Icon to 'Clear All' button in stop conditions";
num5++;
span3[num5] = "Duty counts and 'Enable All' button in 'Duties' tab";
reference50 = new ChangeEntry(EChangeCategory.Added, "UI improvements", list51);
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.Changed, "Renamed 'Clear' button to 'Clear All' in priority window");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added Rising 2025 Event Quests");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Fixed clipboard assigning blacklist to whitelist in 'Duties' tab");
reference49 = new ChangelogEntry("6.7", releaseDate34, list50);
num2++;
ref ChangelogEntry reference51 = ref span[num2];
DateOnly releaseDate35 = new DateOnly(2025, 8, 25);
num3 = 2;
List<ChangeEntry> list52 = new List<ChangeEntry>(num3);
CollectionsMarshal.SetCount(list52, num3);
span2 = CollectionsMarshal.AsSpan(list52);
num4 = 0;
ref ChangeEntry reference52 = ref span2[num4];
num5 = 2;
List<string> list53 = new List<string>(num5);
CollectionsMarshal.SetCount(list53, num5);
span3 = CollectionsMarshal.AsSpan(list53);
index = 0;
span3[index] = "Missing emotes to schema and emote handler";
index++;
span3[index] = "Improved stop conditions with 'Clear All' button";
reference52 = new ChangeEntry(EChangeCategory.Added, "Emote support and stop conditions", list53);
num4++;
span2[num4] = new ChangeEntry(EChangeCategory.Changed, "Stop at level functionality");
reference51 = new ChangelogEntry("6.6", releaseDate35, list52);
num2++;
ref ChangelogEntry reference53 = ref span[num2];
DateOnly releaseDate36 = new DateOnly(2025, 8, 25);
num4 = 2;
List<ChangeEntry> list54 = new List<ChangeEntry>(num4);
CollectionsMarshal.SetCount(list54, num4);
span2 = CollectionsMarshal.AsSpan(list54);
num3 = 0;
span2[num3] = new ChangeEntry(EChangeCategory.Fixed, "Potential fix to single/solo duties softlocking");
num3++;
span2[num3] = new ChangeEntry(EChangeCategory.QuestUpdates, "Added San d'Oria: The Second Walk and various side quests");
reference53 = new ChangelogEntry("6.5", releaseDate36, list54);
Changelogs = list;
}
}

View file

@ -120,9 +120,7 @@ internal sealed class JournalData
select new Category(x, journalData.Genres.Where((Genre y) => y.CategoryId == x.RowId).ToList())).ToList();
Sections = (from x in dataManager.GetExcelSheet<JournalSection>()
select new Section(x, journalData.Categories.Where((Category y) => y.SectionId == x.RowId).ToList())).ToList();
_logger.LogDebug("Resolving OtherQuests section id...");
OtherQuestsSectionRowId = GetOtherQuestsSectionRowId(dataManager);
_logger.LogDebug("Resolved OtherQuestsSectionRowId = {Id}", OtherQuestsSectionRowId);
int? otherQuestsSectionRowId = OtherQuestsSectionRowId;
if (otherQuestsSectionRowId.HasValue)
{
@ -140,11 +138,11 @@ internal sealed class JournalData
num++;
}
}
_logger.LogInformation("Marked {Count} genres as under 'Other Quests' (section id {Id})", num, valueOrDefault);
_logger.LogInformation("Marked {Count} genres as under 'Other Quests' (OtherQuestsSectionRowId={Id})", num, valueOrDefault);
}
else
{
_logger.LogWarning("OtherQuestsSectionRowId {Id} found but matching Section not present in constructed Sections", valueOrDefault);
_logger.LogWarning("OtherQuestsSectionRowId = {Id} found, but matching Section not present in constructed Sections", valueOrDefault);
}
}
else

View file

@ -33,6 +33,8 @@ internal sealed class AutoDutyIpc
private readonly ICallGateSubscriber<object> _stop;
private bool _loggedContentHasPathQueryWarning;
public AutoDutyIpc(IDalamudPluginInterface pluginInterface, Configuration configuration, TerritoryData territoryData, ILogger<AutoDutyIpc> logger)
{
_configuration = configuration;
@ -43,6 +45,7 @@ internal sealed class AutoDutyIpc
_run = pluginInterface.GetIpcSubscriber<uint, int, bool, object>("AutoDuty.Run");
_isStopped = pluginInterface.GetIpcSubscriber<bool>("AutoDuty.IsStopped");
_stop = pluginInterface.GetIpcSubscriber<object>("AutoDuty.Stop");
_loggedContentHasPathQueryWarning = false;
}
public bool IsConfiguredToRunContent(DutyOptions? dutyOptions)
@ -81,8 +84,12 @@ internal sealed class AutoDutyIpc
return _contentHasPath.InvokeFunc(contentFinderConditionData.TerritoryId);
}
catch (IpcError ipcError)
{
if (!_loggedContentHasPathQueryWarning)
{
_logger.LogWarning("Unable to query AutoDuty for path in territory {TerritoryType}: {Message}", contentFinderConditionData.TerritoryId, ipcError.Message);
_loggedContentHasPathQueryWarning = true;
}
return false;
}
}

View file

@ -5,6 +5,7 @@ using System.Numerics;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Questionable.Controller;
using Questionable.Controller.Steps;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
@ -30,12 +31,21 @@ internal sealed class QuestionableIpc : IDisposable
public required ushort TerritoryId { get; init; }
}
public sealed class TaskData
{
public required string TaskName { get; init; }
public required int RemainingTaskCount { get; init; }
}
private const string IpcIsRunning = "Questionable.IsRunning";
private const string IpcGetCurrentQuestId = "Questionable.GetCurrentQuestId";
private const string IpcGetCurrentStepData = "Questionable.GetCurrentStepData";
private const string IpcGetCurrentTask = "Questionable.GetCurrentTask";
private const string IpcGetCurrentlyActiveEventQuests = "Questionable.GetCurrentlyActiveEventQuests";
private const string IpcStartQuest = "Questionable.StartQuest";
@ -44,9 +54,9 @@ internal sealed class QuestionableIpc : IDisposable
private const string IpcIsQuestLocked = "Questionable.IsQuestLocked";
private const string IpcIsQuestCompleted = "Questionable.IsQuestCompleted";
private const string IpcIsQuestComplete = "Questionable.IsQuestComplete";
private const string IpcIsQuestAvailable = "Questionable.IsQuestAvailable";
private const string IpcIsReadyToAcceptQuest = "Questionable.IsReadyToAcceptQuest";
private const string IpcIsQuestAccepted = "Questionable.IsQuestAccepted";
@ -76,6 +86,8 @@ internal sealed class QuestionableIpc : IDisposable
private readonly ICallGateProvider<StepData?> _getCurrentStepData;
private readonly ICallGateProvider<TaskData?> _getCurrentTask;
private readonly ICallGateProvider<List<string>> _getCurrentlyActiveEventQuests;
private readonly ICallGateProvider<string, bool> _startQuest;
@ -84,9 +96,9 @@ internal sealed class QuestionableIpc : IDisposable
private readonly ICallGateProvider<string, bool> _isQuestLocked;
private readonly ICallGateProvider<string, bool> _isQuestCompleted;
private readonly ICallGateProvider<string, bool> _IsQuestComplete;
private readonly ICallGateProvider<string, bool> _isQuestAvailable;
private readonly ICallGateProvider<string, bool> _IsReadyToAcceptQuest;
private readonly ICallGateProvider<string, bool> _isQuestAccepted;
@ -115,6 +127,8 @@ internal sealed class QuestionableIpc : IDisposable
_getCurrentQuestId.RegisterFunc(() => questController.CurrentQuest?.Quest.Id.ToString());
_getCurrentStepData = pluginInterface.GetIpcProvider<StepData>("Questionable.GetCurrentStepData");
_getCurrentStepData.RegisterFunc(GetStepData);
_getCurrentTask = pluginInterface.GetIpcProvider<TaskData>("Questionable.GetCurrentTask");
_getCurrentTask.RegisterFunc(GetCurrentTask);
_getCurrentlyActiveEventQuests = pluginInterface.GetIpcProvider<List<string>>("Questionable.GetCurrentlyActiveEventQuests");
_getCurrentlyActiveEventQuests.RegisterFunc(() => (from q in eventInfoComponent.GetCurrentlyActiveEventQuests()
select q.ToString()).ToList());
@ -124,10 +138,10 @@ internal sealed class QuestionableIpc : IDisposable
_startSingleQuest.RegisterFunc((string questId) => questionableIpc.StartQuest(questId, single: true));
_isQuestLocked = pluginInterface.GetIpcProvider<string, bool>("Questionable.IsQuestLocked");
_isQuestLocked.RegisterFunc(IsQuestLocked);
_isQuestCompleted = pluginInterface.GetIpcProvider<string, bool>("Questionable.IsQuestCompleted");
_isQuestCompleted.RegisterFunc(IsQuestCompleted);
_isQuestAvailable = pluginInterface.GetIpcProvider<string, bool>("Questionable.IsQuestAvailable");
_isQuestAvailable.RegisterFunc(IsQuestAvailable);
_IsQuestComplete = pluginInterface.GetIpcProvider<string, bool>("Questionable.IsQuestComplete");
_IsQuestComplete.RegisterFunc(IsQuestComplete);
_IsReadyToAcceptQuest = pluginInterface.GetIpcProvider<string, bool>("Questionable.IsReadyToAcceptQuest");
_IsReadyToAcceptQuest.RegisterFunc(IsReadyToAcceptQuest);
_isQuestAccepted = pluginInterface.GetIpcProvider<string, bool>("Questionable.IsQuestAccepted");
_isQuestAccepted.RegisterFunc(IsQuestAccepted);
_isQuestUnobtainable = pluginInterface.GetIpcProvider<string, bool>("Questionable.IsQuestUnobtainable");
@ -190,6 +204,22 @@ internal sealed class QuestionableIpc : IDisposable
};
}
private TaskData? GetCurrentTask()
{
ITask task = _questController.TaskQueue.CurrentTaskExecutor?.CurrentTask;
if (task == null)
{
return null;
}
int remainingTaskCount = _questController.TaskQueue.RemainingTasks.Count();
string taskName = task.ToString() ?? "Unknown";
return new TaskData
{
TaskName = taskName,
RemainingTaskCount = remainingTaskCount
};
}
private bool IsQuestLocked(string questId)
{
if (ElementId.TryFromString(questId, out ElementId elementId) && elementId != null && _questRegistry.TryGetQuest(elementId, out Quest _))
@ -199,7 +229,7 @@ internal sealed class QuestionableIpc : IDisposable
return true;
}
private bool IsQuestCompleted(string questId)
private bool IsQuestComplete(string questId)
{
if (ElementId.TryFromString(questId, out ElementId elementId) && elementId != null)
{
@ -208,7 +238,7 @@ internal sealed class QuestionableIpc : IDisposable
return false;
}
private bool IsQuestAvailable(string questId)
private bool IsReadyToAcceptQuest(string questId)
{
if (ElementId.TryFromString(questId, out ElementId elementId) && elementId != null)
{
@ -275,12 +305,13 @@ internal sealed class QuestionableIpc : IDisposable
_importQuestPriority.UnregisterFunc();
_isQuestUnobtainable.UnregisterFunc();
_isQuestAccepted.UnregisterFunc();
_isQuestAvailable.UnregisterFunc();
_isQuestCompleted.UnregisterFunc();
_IsReadyToAcceptQuest.UnregisterFunc();
_IsQuestComplete.UnregisterFunc();
_isQuestLocked.UnregisterFunc();
_startSingleQuest.UnregisterFunc();
_startQuest.UnregisterFunc();
_getCurrentlyActiveEventQuests.UnregisterFunc();
_getCurrentTask.UnregisterFunc();
_getCurrentStepData.UnregisterFunc();
_getCurrentQuestId.UnregisterFunc();
_isRunning.UnregisterFunc();

View file

@ -587,6 +587,11 @@ internal sealed class GameFunctions
return ptr->CurrentFate->FateId;
}
public uint GetItemLevel(uint itemId)
{
return (_dataManager.GetExcelSheet<Item>()?.GetRowOrDefault(itemId))?.LevelItem.RowId ?? 0;
}
private unsafe void ExecuteCommand(string command)
{
try

View file

@ -64,6 +64,8 @@ internal sealed class QuestFunctions
private ElementId? _lastLoggedNotReadyQuest;
private ElementId? _lastLoggedAcceptedHiddenMsq;
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, AetheryteFunctions aetheryteFunctions, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, AlliedSocietyData alliedSocietyData, AetheryteData aetheryteData, Configuration configuration, IDataManager dataManager, IClientState clientState, IGameGui gameGui, IAetheryteList aetheryteList, ILogger<QuestFunctions> logger)
{
_questRegistry = questRegistry;
@ -437,8 +439,12 @@ internal sealed class QuestFunctions
}
QuestManager* ptr2 = QuestManager.Instance();
if (IsQuestAccepted(questId) && ptr2->GetQuestById(questId.Value)->IsHidden)
{
if (_lastLoggedAcceptedHiddenMsq != questId)
{
_logger.LogInformation("GetMainScenarioQuest: Quest {QuestId} is accepted but hidden", questId);
_lastLoggedAcceptedHiddenMsq = questId;
}
return (QuestReference.NoQuest(MainScenarioQuestState.Available), "Quest accepted but hidden");
}
if (IsQuestComplete(questId))
@ -461,6 +467,7 @@ internal sealed class QuestFunctions
_logger.LogTrace("GetMainScenarioQuest: In loading screen");
return (QuestReference.NoQuest(MainScenarioQuestState.LoadingScreen), "In loading screen");
}
_lastLoggedAcceptedHiddenMsq = null;
return (new QuestReference(questId, QuestManager.GetQuestSequence(questId.Value), MainScenarioQuestState.Available), item);
}
@ -1052,9 +1059,8 @@ internal sealed class QuestFunctions
}
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
{
_logger.LogDebug("Quest {QuestId} seasonal expiry raw={ExpiryRaw} Kind={Kind} TimeOfDay={TimeOfDay}", questId, valueOrDefault.ToString("o"), valueOrDefault.Kind, valueOrDefault.TimeOfDay);
_logger.LogDebug("Quest {QuestId} normalized expiryUtc={ExpiryUtc:o} treatedAsDailyReset={TreatedAsDailyReset}", questId, dateTime2, flag2);
_logger.LogTrace("Quest {QuestId} expiry check: nowUtc={Now:o}, expiryUtc={Expiry:o}, expired={Expired}", questId, DateTime.UtcNow, dateTime2, DateTime.UtcNow > dateTime2);
_logger.LogDebug("Quest {QuestId} seasonal expiry raw={ExpiryRaw} Kind={Kind} TimeOfDay={TimeOfDay} treatedAsDailyReset={TreatedAsDailyReset}", questId, valueOrDefault.ToString("o"), valueOrDefault.Kind, valueOrDefault.TimeOfDay, flag2);
_logger.LogDebug("Quest {QuestId} expiry check: nowUtc={Now:o}, expiryUtc={Expiry:o}, expired={Expired}", questId, DateTime.UtcNow, dateTime2, DateTime.UtcNow > dateTime2);
}
if (DateTime.UtcNow > dateTime2)
{

View file

@ -0,0 +1,32 @@
using System.Numerics;
namespace Questionable.Model.Changelog;
internal static class ChangeCategoryExtensions
{
public static string ToDisplayString(this EChangeCategory category)
{
return category switch
{
EChangeCategory.Added => "Added",
EChangeCategory.Changed => "Changed",
EChangeCategory.Fixed => "Fixed",
EChangeCategory.Removed => "Removed",
EChangeCategory.QuestUpdates => "Quest Updates",
_ => category.ToString(),
};
}
public static Vector4 GetColor(this EChangeCategory category)
{
return category switch
{
EChangeCategory.Added => new Vector4(0.3f, 0.8f, 0.4f, 1f),
EChangeCategory.Changed => new Vector4(0.4f, 0.6f, 0.9f, 1f),
EChangeCategory.Fixed => new Vector4(0.9f, 0.6f, 0.3f, 1f),
EChangeCategory.Removed => new Vector4(0.9f, 0.3f, 0.3f, 1f),
EChangeCategory.QuestUpdates => new Vector4(0.8f, 0.8f, 0.4f, 1f),
_ => new Vector4(0.7f, 0.7f, 0.7f, 1f),
};
}
}

View file

@ -0,0 +1,5 @@
using System.Collections.Generic;
namespace Questionable.Model.Changelog;
internal sealed record ChangeEntry(EChangeCategory Category, string Description, List<string>? SubItems = null);

View file

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Questionable.Model.Changelog;
internal sealed record ChangelogEntry(string Version, DateOnly ReleaseDate, List<ChangeEntry> Changes)
{
public bool IsNewVersion(string? lastViewedVersion)
{
if (string.IsNullOrEmpty(lastViewedVersion))
{
return true;
}
return CompareVersions(Version, lastViewedVersion) > 0;
}
private static int CompareVersions(string version1, string version2)
{
int result;
int[] array = (from p in version1.Split('.')
select int.TryParse(p, out result) ? result : 0).ToArray();
int[] array2 = (from p in version2.Split('.')
select int.TryParse(p, out result) ? result : 0).ToArray();
int num = Math.Max(array.Length, array2.Length);
for (int num2 = 0; num2 < num; num2++)
{
int num3 = ((num2 < array.Length) ? array[num2] : 0);
int num4 = ((num2 < array2.Length) ? array2[num2] : 0);
if (num3 != num4)
{
return num3.CompareTo(num4);
}
}
return 0;
}
}

View file

@ -0,0 +1,10 @@
namespace Questionable.Model.Changelog;
internal enum EChangeCategory
{
Added,
Changed,
Fixed,
Removed,
QuestUpdates
}

View file

@ -34,6 +34,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-aetheryte.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonAetheryte).AsTask().Result);
SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-classjob.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonClassJob).AsTask().Result);
SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonCompletionFlags).AsTask().Result);
SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-requiredvariables.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonRequiredVariables).AsTask().Result);
SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonVector3).AsTask().Result);
SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/QuestPaths/quest-v1.json"), JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result);
}
@ -67,6 +68,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
RegisterLocalIfExistsFromPath(Find("common-aetheryte.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-aetheryte.json");
RegisterLocalIfExistsFromPath(Find("common-classjob.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-classjob.json");
RegisterLocalIfExistsFromPath(Find("common-completionflags.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json");
RegisterLocalIfExistsFromPath(Find("common-requiredvariables.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-requiredvariables.json");
RegisterLocalIfExistsFromPath(Find("common-vector3.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json");
RegisterLocalIfExistsFromPath(Find("quest-v1.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/QuestPaths/quest-v1.json");
static void RegisterLocalIfExistsFromPath(string? path, string registrationUri)

View file

@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Questionable.Model.Changelog;
namespace Questionable.Windows.ChangelogComponents;
internal static class ChangelogCategoryComponent
{
public static void DrawCategoryGroup(EChangeCategory category, List<ChangeEntry> changes, float baseX)
{
DrawCategoryHeader(category switch
{
EChangeCategory.Added => FontAwesomeIcon.Plus,
EChangeCategory.Changed => FontAwesomeIcon.Edit,
EChangeCategory.Fixed => FontAwesomeIcon.Wrench,
EChangeCategory.Removed => FontAwesomeIcon.Minus,
EChangeCategory.QuestUpdates => FontAwesomeIcon.Map,
_ => FontAwesomeIcon.Circle,
}, category switch
{
EChangeCategory.Added => new Vector4(0.5f, 1f, 0.6f, 1f),
EChangeCategory.Changed => new Vector4(0.6f, 0.85f, 1f, 1f),
EChangeCategory.Fixed => new Vector4(1f, 0.85f, 0.5f, 1f),
EChangeCategory.Removed => new Vector4(1f, 0.5f, 0.5f, 1f),
EChangeCategory.QuestUpdates => new Vector4(1f, 1f, 0.6f, 1f),
_ => new Vector4(0.9f, 0.9f, 0.9f, 1f),
}, category.ToDisplayString(), baseX);
ImGui.Spacing();
float changeIndent = 28f;
foreach (ChangeEntry change in changes)
{
DrawChange(change, baseX, changeIndent);
}
}
private static void DrawCategoryHeader(FontAwesomeIcon icon, Vector4 color, string text, float baseX)
{
float cursorPosY = ImGui.GetCursorPosY();
ImGui.SetCursorPosX(baseX);
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextColored(in color, icon.ToIconString());
}
ImGui.SameLine();
ImGui.SetCursorPosY(cursorPosY);
using (ImRaii.PushColor(ImGuiCol.Text, color))
{
ImGui.TextUnformatted(text);
}
}
private static void DrawChange(ChangeEntry change, float baseX, float changeIndent)
{
ImGui.SetCursorPosX(baseX + changeIndent);
float cursorPosY = ImGui.GetCursorPosY();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextColored(new Vector4(0.95f, 0.95f, 1f, 1f), FontAwesomeIcon.CaretRight.ToIconString());
}
ImGui.SameLine();
ImGui.SetCursorPosY(cursorPosY);
float num = ImGui.GetContentRegionAvail().X - 8f;
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + num);
ImGui.TextColored(new Vector4(0.95f, 0.95f, 1f, 1f), change.Description);
ImGui.PopTextWrapPos();
if (change.SubItems != null && change.SubItems.Count > 0)
{
DrawSubItems(change.SubItems, baseX, changeIndent);
}
}
private static void DrawSubItems(List<string> subItems, float baseX, float changeIndent)
{
float num = changeIndent + 20f;
foreach (string subItem in subItems)
{
ImGui.SetCursorPosX(baseX + num);
float cursorPosY = ImGui.GetCursorPosY();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextColored(new Vector4(0.85f, 0.85f, 0.92f, 1f), FontAwesomeIcon.AngleRight.ToIconString());
}
ImGui.SameLine();
ImGui.SetCursorPosY(cursorPosY);
float num2 = ImGui.GetContentRegionAvail().X - 8f;
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + num2);
ImGui.TextColored(new Vector4(0.85f, 0.85f, 0.92f, 1f), subItem);
ImGui.PopTextWrapPos();
}
}
}

View file

@ -0,0 +1,151 @@
using System;
using System.Globalization;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Questionable.Model.Changelog;
namespace Questionable.Windows.ChangelogComponents;
internal sealed class ChangelogEntryComponent
{
private readonly Configuration _configuration;
private readonly int _windowOpenCount;
private readonly float _animationTime;
public ChangelogEntryComponent(Configuration configuration, int windowOpenCount, float animationTime)
{
_configuration = configuration;
_windowOpenCount = windowOpenCount;
_animationTime = animationTime;
}
public void Draw(ChangelogEntry changelog, bool isLatest, bool hasSetInitialState)
{
bool isNew = changelog.IsNewVersion(_configuration.LastViewedChangelogVersion);
ImGui.GetWindowDrawList();
string text = changelog.ReleaseDate.ToString("MMMM dd, yyyy", CultureInfo.InvariantCulture);
if (!hasSetInitialState)
{
ImGui.SetNextItemOpen(isLatest, ImGuiCond.Always);
}
string text2 = "v" + changelog.Version;
float fontSize = ImGui.GetFontSize();
float num = fontSize;
Vector2 versionSize;
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
versionSize = ImGui.CalcTextSize(text2);
}
Vector2 dateSize = ImGui.CalcTextSize(text);
float scaledIconFontSize = fontSize * 0.85f;
float x = ImGui.GetContentRegionAvail().X;
bool flag;
using (ImRaii.PushColor(ImGuiCol.Header, new Vector4(0.2f, 0.18f, 0.25f, 0.6f)))
{
using (ImRaii.PushColor(ImGuiCol.HeaderHovered, new Vector4(0.3f, 0.25f, 0.35f, 0.8f)))
{
using (ImRaii.PushColor(ImGuiCol.HeaderActive, new Vector4(0.25f, 0.22f, 0.3f, 1f)))
{
Vector2 cursorPos = ImGui.GetCursorPos();
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
ImU8String label = new ImU8String(19, 2);
label.AppendLiteral("###header_");
label.AppendFormatted(changelog.Version);
label.AppendLiteral("_changes_");
label.AppendFormatted(_windowOpenCount);
flag = ImGui.CollapsingHeader(label);
Vector2 cursorPos2 = ImGui.GetCursorPos();
float verticalOffset = (cursorPos2.Y - cursorPos.Y - num) * 0.5f - 1f;
DrawVersionText(text2, versionSize, cursorPos, verticalOffset);
DrawNewBadge(isNew, cursorScreenPos, x);
DrawDateWithIcon(text, dateSize, scaledIconFontSize, cursorScreenPos, cursorPos, x, verticalOffset, fontSize);
ImGui.SetCursorPos(cursorPos2);
}
}
}
if (flag)
{
DrawChangelogContent(changelog);
}
}
private static void DrawVersionText(string versionText, Vector2 versionSize, Vector2 headerStartPos, float verticalOffset)
{
float x = headerStartPos.X + ImGui.GetStyle().FramePadding.X * 2f + 20f;
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGui.SetCursorPos(new Vector2(x, headerStartPos.Y + verticalOffset));
ImGui.TextColored(new Vector4(0.95f, 0.95f, 0.95f, 1f), versionText);
}
}
private void DrawNewBadge(bool isNew, Vector2 headerStartScreenPos, float availableWidth)
{
if (isNew)
{
float num = 2.5f;
float w = (MathF.Sin(_animationTime * num) + 1f) * 0.5f * 0.15f;
float frameHeight = ImGui.GetFrameHeight();
float frameRounding = ImGui.GetStyle().FrameRounding;
ImGui.GetWindowDrawList().AddRectFilled(headerStartScreenPos, headerStartScreenPos + new Vector2(availableWidth, frameHeight), ImGui.ColorConvertFloat4ToU32(new Vector4(0.5f, 1f, 0.6f, w)), frameRounding);
}
}
private static void DrawDateWithIcon(string dateText, Vector2 dateSize, float scaledIconFontSize, Vector2 headerStartScreenPos, Vector2 headerStartPos, float availableWidth, float verticalOffset, float defaultFontSize)
{
string text = FontAwesomeIcon.Calendar.ToIconString();
Vector2 vector;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
vector = ImGui.CalcTextSize(text);
}
float num = vector.X * (scaledIconFontSize / defaultFontSize);
float num2 = num + 4f + dateSize.X;
float num3 = availableWidth - num2 - 12f;
Vector2 pos = headerStartScreenPos + new Vector2(num3, verticalOffset);
ImGui.GetWindowDrawList().AddText(UiBuilder.IconFont, scaledIconFontSize, pos, ImGui.ColorConvertFloat4ToU32(new Vector4(0.65f, 0.6f, 0.8f, 1f)), text);
ImGui.SetCursorPos(new Vector2(num3 + num + 4f, headerStartPos.Y + verticalOffset));
ImGui.TextColored(new Vector4(0.95f, 0.95f, 1f, 1f), dateText);
}
private static void DrawChangelogContent(ChangelogEntry changelog)
{
Vector2 cursorPos = ImGui.GetCursorPos();
Vector2 vector = new Vector2(16f, 8f);
float x = ImGui.GetContentRegionAvail().X;
IOrderedEnumerable<IGrouping<EChangeCategory, ChangeEntry>> orderedEnumerable = from changeEntry in changelog.Changes
group changeEntry by changeEntry.Category into g
orderby g.Key
select g;
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
windowDrawList.ChannelsSplit(2);
windowDrawList.ChannelsSetCurrent(1);
float baseX = cursorPos.X + vector.X;
ImGui.SetCursorPos(cursorPos + new Vector2(vector.X, vector.Y));
bool flag = true;
foreach (IGrouping<EChangeCategory, ChangeEntry> item in orderedEnumerable)
{
if (!flag)
{
ImGui.Spacing();
}
ChangelogCategoryComponent.DrawCategoryGroup(item.Key, item.ToList(), baseX);
flag = false;
}
Vector2 cursorPos2 = ImGui.GetCursorPos();
windowDrawList.ChannelsSetCurrent(0);
ImGui.SetCursorPos(cursorPos);
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
ImGui.SetCursorPos(cursorPos2 + new Vector2(0f, vector.Y));
Vector2 cursorScreenPos2 = ImGui.GetCursorScreenPos();
windowDrawList.AddRectFilled(cursorScreenPos, new Vector2(cursorScreenPos.X + x, cursorScreenPos2.Y), ImGui.ColorConvertFloat4ToU32(new Vector4(0.1f, 0.08f, 0.14f, 0.5f)), 4f);
windowDrawList.AddRect(cursorScreenPos, new Vector2(cursorScreenPos.X + x, cursorScreenPos2.Y), ImGui.ColorConvertFloat4ToU32(new Vector4(0.55f, 0.45f, 0.75f, 0.15f)), 4f, ImDrawFlags.None, 1f);
windowDrawList.ChannelsMerge();
ImGui.SetCursorPos(new Vector2(cursorPos.X, cursorPos2.Y + vector.Y + 2f));
}
}

View file

@ -0,0 +1,36 @@
using System;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii;
namespace Questionable.Windows.ChangelogComponents;
internal sealed class ChangelogFooterComponent
{
public static void Draw(Action onConfirm)
{
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float x = ImGui.GetContentRegionAvail().X;
windowDrawList.AddRectFilledMultiColor(cursorScreenPos, cursorScreenPos + new Vector2(x, 2f), ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, 0.4f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, 1f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, 1f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, 0.4f)));
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 8f);
float num = 120f;
float num2 = (ImGui.GetContentRegionAvail().X - num) * 0.5f;
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num2);
using (ImRaii.PushColor(ImGuiCol.Button, new Vector4(0.55f, 0.45f, 0.75f, 0.6f)))
{
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, new Vector4(0.65f, 0.55f, 0.85f, 0.8f)))
{
using (ImRaii.PushColor(ImGuiCol.ButtonActive, new Vector4(0.75f, 0.55f, 0.95f, 1f)))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.CheckCircle, "Got It!"))
{
onConfirm();
}
}
}
}
}
}

View file

@ -0,0 +1,251 @@
using System;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
namespace Questionable.Windows.ChangelogComponents;
internal sealed class ChangelogHeaderComponent
{
private float _animationTime;
public void Reset()
{
_animationTime = 0f;
}
public void Draw(Action<bool> onCloseClicked)
{
_animationTime += ImGui.GetIO().DeltaTime;
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float x = ImGui.GetContentRegionAvail().X;
float headerHeight = 120f;
DrawBackground(windowDrawList, cursorScreenPos, x, headerHeight);
DrawAnimatedBorder(windowDrawList, cursorScreenPos, x, headerHeight);
DrawSparkles(windowDrawList, cursorScreenPos, x, headerHeight);
DrawCloseButton(windowDrawList, cursorScreenPos, x, onCloseClicked);
DrawHeaderContent(cursorScreenPos, x, headerHeight);
DrawAccentLine(windowDrawList, cursorScreenPos, x, headerHeight);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 8f);
}
private void DrawBackground(ImDrawListPtr drawList, Vector2 headerStartPos, float contentWidth, float headerHeight)
{
float num = (MathF.Sin(_animationTime * 1.5f) + 1f) * 0.5f * 0.03f;
uint num2 = ImGui.ColorConvertFloat4ToU32(new Vector4(0.15f + num, 0.1f + num, 0.2f + num, 0.95f));
uint num3 = ImGui.ColorConvertFloat4ToU32(new Vector4(0.08f, 0.05f, 0.12f, 0.95f));
drawList.AddRectFilledMultiColor(headerStartPos, headerStartPos + new Vector2(contentWidth, headerHeight), num2, num2, num3, num3);
}
private void DrawAnimatedBorder(ImDrawListPtr drawList, Vector2 headerStartPos, float contentWidth, float headerHeight)
{
float num = 100f;
float num2 = 250f;
float num3 = _animationTime * num % num2;
float num4 = MathF.Ceiling(contentWidth / num2) + 2f;
for (int i = 0; (float)i < num4; i++)
{
float num5 = (float)i * num2 - num3;
float num6 = num5 + num2;
if (!(num6 < 0f) && !(num5 > contentWidth))
{
float num7 = MathF.Max(0f, num5);
float num8 = MathF.Min(contentWidth, num6);
float num9 = (num7 - num5) / num2;
float num10 = (num8 - num5) / num2;
float num11 = (MathF.Sin(num9 * (float)Math.PI * 2f - (float)Math.PI / 2f) + 1f) * 0.5f;
float num12 = (MathF.Sin(num10 * (float)Math.PI * 2f - (float)Math.PI / 2f) + 1f) * 0.5f;
Vector4 value = new Vector4(0.75f, 0.55f, 0.95f, num11 * 0.8f);
Vector4 value2 = new Vector4(0.55f, 0.75f, 0.95f, num11 * 0.8f);
Vector4 value3 = new Vector4(0.75f, 0.55f, 0.95f, num12 * 0.8f);
Vector4 value4 = new Vector4(0.55f, 0.75f, 0.95f, num12 * 0.8f);
float num13 = (num9 + num10) * 0.5f;
Vector4 input = Vector4.Lerp(value, value2, num13);
Vector4 input2 = Vector4.Lerp(value3, value4, num13 + 0.2f);
drawList.AddRectFilledMultiColor(headerStartPos + new Vector2(num7, 0f), headerStartPos + new Vector2(num8, 3f), ImGui.ColorConvertFloat4ToU32(input), ImGui.ColorConvertFloat4ToU32(input2), ImGui.ColorConvertFloat4ToU32(input2), ImGui.ColorConvertFloat4ToU32(input));
}
}
}
private void DrawSparkles(ImDrawListPtr drawList, Vector2 headerStartPos, float contentWidth, float headerHeight)
{
int num = 12;
for (int i = 0; i < num; i++)
{
float num2 = (float)i * 123.456f;
float num3 = 0.8f + (float)(i % 3) * 0.15f;
float num4 = _animationTime * num3;
float x = num2 * 12.345f % 1f * contentWidth;
float y = (MathF.Sin(num4 + num2) + 1f) * 0.5f * headerHeight;
Vector2 pos = headerStartPos + new Vector2(x, y);
float num5 = (MathF.Sin(num4 * 3f + num2 * 2f) + 1f) * 0.5f;
float alpha = num5 * 0.6f;
float size = 2f + num5 * 1.5f;
DrawSparkleShape(drawList, pos, size, alpha, i % 4, num4);
}
}
private static void DrawSparkleShape(ImDrawListPtr drawList, Vector2 pos, float size, float alpha, int type, float time)
{
switch (type)
{
case 0:
DrawCrossSparkle(drawList, pos, size, alpha);
break;
case 1:
DrawDiamondSparkle(drawList, pos, size, alpha);
break;
case 2:
DrawStarSparkle(drawList, pos, size, alpha, time);
break;
default:
DrawCircleSparkle(drawList, pos, size, alpha);
break;
}
}
private static void DrawCrossSparkle(ImDrawListPtr drawList, Vector2 pos, float size, float alpha)
{
float num = size * 0.8f;
float thickness = MathF.Max(1f, size * 0.3f);
drawList.AddLine(pos - new Vector2(num, 0f), pos + new Vector2(num, 0f), ImGui.ColorConvertFloat4ToU32(new Vector4(0.9f, 0.8f, 1f, alpha)), thickness);
drawList.AddLine(pos - new Vector2(0f, num), pos + new Vector2(0f, num), ImGui.ColorConvertFloat4ToU32(new Vector4(0.9f, 0.8f, 1f, alpha)), thickness);
drawList.AddCircleFilled(pos, size * 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, alpha * 0.2f)));
}
private static void DrawDiamondSparkle(ImDrawListPtr drawList, Vector2 pos, float size, float alpha)
{
float num = size * 0.7f;
Vector2 p = pos + new Vector2(0f, 0f - num);
Vector2 p2 = pos + new Vector2(num, 0f);
Vector2 p3 = pos + new Vector2(0f, num);
Vector2 p4 = pos + new Vector2(0f - num, 0f);
drawList.AddQuadFilled(p, p2, p3, p4, ImGui.ColorConvertFloat4ToU32(new Vector4(0.9f, 0.8f, 1f, alpha * 0.8f)));
drawList.AddCircleFilled(pos, size * 1.6f, ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, alpha * 0.25f)));
}
private static void DrawStarSparkle(ImDrawListPtr drawList, Vector2 pos, float size, float alpha, float time)
{
for (int i = 0; i < 4; i++)
{
float num = (float)i * (float)Math.PI * 0.5f;
float num2 = size * 0.3f;
float num3 = size * 0.9f;
float x = num - 0.3f;
float x2 = num + 0.3f;
Vector2 p = pos + new Vector2(MathF.Cos(x) * num2, MathF.Sin(x) * num2);
Vector2 p2 = pos + new Vector2(MathF.Cos(num) * num3, MathF.Sin(num) * num3);
Vector2 p3 = pos + new Vector2(MathF.Cos(x2) * num2, MathF.Sin(x2) * num2);
drawList.AddTriangleFilled(p, p2, p3, ImGui.ColorConvertFloat4ToU32(new Vector4(0.9f, 0.8f, 1f, alpha)));
}
drawList.AddCircleFilled(pos, size * 1.7f, ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, alpha * 0.3f)));
}
private static void DrawCircleSparkle(ImDrawListPtr drawList, Vector2 pos, float size, float alpha)
{
drawList.AddCircleFilled(pos, size, ImGui.ColorConvertFloat4ToU32(new Vector4(0.9f, 0.8f, 1f, alpha)));
drawList.AddCircleFilled(pos, size * 1.8f, ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, alpha * 0.3f)));
}
private void DrawCloseButton(ImDrawListPtr drawList, Vector2 headerStartPos, float contentWidth, Action<bool> onCloseClicked)
{
float num = 32f;
Vector2 vector = headerStartPos + new Vector2(contentWidth - num - 8f, 8f);
Vector2 center = vector + new Vector2(num * 0.5f, num * 0.5f);
Vector2 mousePos = ImGui.GetMousePos();
bool flag = mousePos.X >= vector.X && mousePos.X <= vector.X + num && mousePos.Y >= vector.Y && mousePos.Y <= vector.Y + num;
float w = (flag ? 0.9f : 0.5f);
float num2 = (flag ? ((MathF.Sin(_animationTime * 4f) + 1f) * 0.5f * 0.1f) : 0f);
uint col = (flag ? ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f + num2, 0.55f, 0.95f, w)) : ImGui.ColorConvertFloat4ToU32(new Vector4(0.55f, 0.45f, 0.75f, w)));
drawList.AddCircleFilled(center, num * 0.4f, col);
DrawAnimatedCross(drawList, center, flag);
ImGui.SetCursorScreenPos(vector);
ImGui.InvisibleButton("CloseButton", new Vector2(num, num));
if (ImGui.IsItemClicked())
{
onCloseClicked(obj: false);
}
}
private void DrawAnimatedCross(ImDrawListPtr drawList, Vector2 center, bool isHovered)
{
float thickness = 2f;
float w = (isHovered ? 1f : 0.8f);
float x = (isHovered ? (MathF.Sin(_animationTime * 6f) * 0.1f) : 0f);
float num = MathF.Cos(x);
float num2 = MathF.Sin(x);
float num3 = 8f * 0.5f;
Vector2 vector = new Vector2((0f - num3) * num - (0f - num3) * num2, (0f - num3) * num2 + (0f - num3) * num);
Vector2 vector2 = new Vector2(num3 * num - num3 * num2, num3 * num2 + num3 * num);
Vector2 vector3 = new Vector2((0f - num3) * num - num3 * num2, (0f - num3) * num2 + num3 * num);
Vector2 vector4 = new Vector2(num3 * num - (0f - num3) * num2, num3 * num2 + (0f - num3) * num);
drawList.AddLine(center + vector, center + vector2, ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, w)), thickness);
drawList.AddLine(center + vector3, center + vector4, ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, w)), thickness);
}
private void DrawHeaderContent(Vector2 headerStartPos, float contentWidth, float headerHeight)
{
float fadeIn = MathF.Min(1f, _animationTime * 2f);
ImGui.SetCursorScreenPos(headerStartPos);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 30f);
float centerX = contentWidth * 0.5f;
DrawIcon(centerX, fadeIn);
DrawTitle(centerX, fadeIn);
DrawSubtitle(centerX);
}
private void DrawIcon(float centerX, float fadeIn)
{
float num = MathF.Sin(_animationTime * 2f) * 3f;
float num2 = MathF.Sin(_animationTime * 1.5f + (float)Math.PI / 2f) * 1.5f;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
string text = FontAwesomeIcon.FileAlt.ToIconString();
ImGui.SetCursorPosX(centerX - ImGui.CalcTextSize(text).X * 0.5f + num2);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + num);
float w = (MathF.Sin(_animationTime * 3f) + 1f) * 0.5f * 0.3f;
Vector4 input = ((_animationTime * 0.5f % 1f < 0.5f) ? new Vector4(0.75f, 0.55f, 0.95f, w) : new Vector4(0.55f, 0.75f, 0.95f, w));
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
ImGui.GetWindowDrawList().AddText(UiBuilder.IconFont, ImGui.GetFontSize() * 1.2f, cursorScreenPos + new Vector2(2f, 2f), ImGui.ColorConvertFloat4ToU32(input), text);
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, fadeIn);
ImGui.TextColored(new Vector4(0.75f, 0.55f, 0.95f, 1f), text);
ImGui.PopStyleVar();
}
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - num + 4f);
}
private void DrawTitle(float centerX, float fadeIn)
{
float num = MathF.Max(0f, 1f - (_animationTime - 0.1f) * 3f);
float num2 = num * num * (3f - 2f * num) * 20f;
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
string text = "What's New in Questionable?";
ImGui.SetCursorPosX(centerX - ImGui.CalcTextSize(text).X * 0.5f - num2);
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, fadeIn);
ImGui.TextColored(new Vector4(0.95f, 0.95f, 1f, 1f), text);
ImGui.PopStyleVar();
}
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 6f);
}
private void DrawSubtitle(float centerX)
{
float val = MathF.Max(0f, MathF.Min(1f, (_animationTime - 0.3f) * 2f));
string text = "Stay up to date with the latest features and improvements";
ImGui.SetCursorPosX(centerX - ImGui.CalcTextSize(text).X * 0.5f);
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, val);
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.8f, 0.9f), text);
ImGui.PopStyleVar();
}
private void DrawAccentLine(ImDrawListPtr drawList, Vector2 headerStartPos, float contentWidth, float headerHeight)
{
ImGui.SetCursorScreenPos(headerStartPos + new Vector2(0f, headerHeight));
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float num = (MathF.Sin(_animationTime * 2f + (float)Math.PI) + 1f) * 0.5f;
drawList.AddRectFilledMultiColor(cursorScreenPos, cursorScreenPos + new Vector2(contentWidth, 2f), ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, 0.6f + num * 0.4f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, 0.2f + num * 0.2f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, 0.2f + num * 0.2f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.75f, 0.55f, 0.95f, 0.6f + num * 0.4f)));
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
@ -38,6 +39,10 @@ internal sealed class GeneralConfigComponent : ConfigComponent
private readonly string[] _classJobNames;
private string _mountSearchText = string.Empty;
private bool _mountComboJustOpened;
public GeneralConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration, IDataManager dataManager, ClassJobUtils classJobUtils, QuestRegistry questRegistry, TerritoryData territoryData)
: base(pluginInterface, configuration)
{
@ -45,10 +50,9 @@ internal sealed class GeneralConfigComponent : ConfigComponent
_territoryData = territoryData;
List<(uint, string)> source = (from x in dataManager.GetExcelSheet<Mount>()
where x.RowId != 0 && x.Icon > 0
select (MountId: x.RowId, Name: x.Singular.ToString()) into x
select (MountId: x.RowId, Name: CapitalizeName(x.Singular.ExtractText())) into x
where !string.IsNullOrEmpty(x.Name)
orderby x.Name
select x).ToList();
select x).OrderBy<(uint, string), string>(((uint MountId, string Name) x) => x.Name, StringComparer.OrdinalIgnoreCase).ToList();
_mountIds = DefaultMounts.Select<(uint, string), uint>(((uint Id, string Name) x) => x.Id).Concat(source.Select<(uint, string), uint>(((uint MountId, string Name) x) => x.MountId)).ToArray();
_mountNames = DefaultMounts.Select<(uint, string), string>(((uint Id, string Name) x) => x.Name).Concat(source.Select<(uint, string), string>(((uint MountId, string Name) x) => x.Name)).ToArray();
List<EClassJob> sortedClassJobs = classJobUtils.SortedClassJobs.Select(((EClassJob ClassJob, int Category) x) => x.ClassJob).ToList();
@ -62,6 +66,27 @@ internal sealed class GeneralConfigComponent : ConfigComponent
_classJobNames = DefaultClassJobs.Select<(EClassJob, string), string>(((EClassJob ClassJob, string Name) x) => x.Name).Concat(list.Select((EClassJob x) => x.ToFriendlyString())).ToArray();
}
private static string CapitalizeName(string text)
{
if (string.IsNullOrEmpty(text))
{
return text;
}
if (string.Equals(text, text.ToLowerInvariant(), StringComparison.Ordinal))
{
return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(text);
}
string[] array = text.Split(' ');
for (int i = 0; i < array.Length; i++)
{
if (array[i].Length > 0 && char.IsLower(array[i][0]))
{
array[i] = char.ToUpperInvariant(array[i][0]) + array[i].Substring(1);
}
}
return string.Join(" ", array);
}
public override void DrawTab()
{
using ImRaii.IEndObject endObject = ImRaii.TabItem("General###General");
@ -75,34 +100,71 @@ internal sealed class GeneralConfigComponent : ConfigComponent
base.Configuration.General.CombatModule = (Configuration.ECombatModule)currentItem;
Save();
}
int currentItem2 = Array.FindIndex(_mountIds, (uint x) => x == base.Configuration.General.MountId);
if (currentItem2 == -1)
int num = Array.FindIndex(_mountIds, (uint x) => x == base.Configuration.General.MountId);
if (num == -1)
{
currentItem2 = 0;
base.Configuration.General.MountId = _mountIds[currentItem2];
num = 0;
base.Configuration.General.MountId = _mountIds[num];
Save();
}
if (ImGui.Combo("Preferred Mount", ref currentItem2, in _mountNames, _mountNames.Length))
string text = ((num >= 0 && num < _mountNames.Length) ? _mountNames[num] : "Unknown");
if (ImGui.BeginCombo("Preferred Mount", text, ImGuiComboFlags.HeightLarge))
{
base.Configuration.General.MountId = _mountIds[currentItem2];
if (!_mountComboJustOpened)
{
ImGui.SetKeyboardFocusHere();
_mountComboJustOpened = true;
}
ImGui.SetNextItemWidth(-1f);
ImGui.InputTextWithHint("##MountSearch", "Search mounts...", ref _mountSearchText, 256);
ImGui.Separator();
using (ImRaii.IEndObject endObject2 = ImRaii.Child("##MountScrollArea", new Vector2(0f, 300f), border: false))
{
if (endObject2)
{
string value = _mountSearchText.ToUpperInvariant();
for (int num2 = 0; num2 < _mountNames.Length; num2++)
{
if (string.IsNullOrEmpty(_mountSearchText) || _mountNames[num2].ToUpperInvariant().Contains(value, StringComparison.Ordinal))
{
bool flag = num2 == num;
if (ImGui.Selectable(_mountNames[num2], flag))
{
base.Configuration.General.MountId = _mountIds[num2];
Save();
_mountSearchText = string.Empty;
ImGui.CloseCurrentPopup();
}
if (flag)
{
ImGui.SetItemDefaultFocus();
}
}
}
}
}
ImGui.EndCombo();
}
else
{
_mountComboJustOpened = false;
}
int currentItem2 = (int)base.Configuration.General.GrandCompany;
if (ImGui.Combo("Preferred Grand Company", ref currentItem2, in _grandCompanyNames, _grandCompanyNames.Length))
{
base.Configuration.General.GrandCompany = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)currentItem2;
Save();
}
int currentItem3 = (int)base.Configuration.General.GrandCompany;
if (ImGui.Combo("Preferred Grand Company", ref currentItem3, in _grandCompanyNames, _grandCompanyNames.Length))
{
base.Configuration.General.GrandCompany = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)currentItem3;
Save();
}
int currentItem4 = Array.IndexOf(_classJobIds, base.Configuration.General.CombatJob);
if (currentItem4 == -1)
int currentItem3 = Array.IndexOf(_classJobIds, base.Configuration.General.CombatJob);
if (currentItem3 == -1)
{
base.Configuration.General.CombatJob = EClassJob.Adventurer;
Save();
currentItem4 = 0;
currentItem3 = 0;
}
if (ImGui.Combo("Preferred Combat Job", ref currentItem4, in _classJobNames, _classJobNames.Length))
if (ImGui.Combo("Preferred Combat Job", ref currentItem3, in _classJobNames, _classJobNames.Length))
{
base.Configuration.General.CombatJob = _classJobIds[currentItem4];
base.Configuration.General.CombatJob = _classJobIds[currentItem3];
Save();
}
ImGui.Separator();
@ -116,7 +178,7 @@ internal sealed class GeneralConfigComponent : ConfigComponent
Save();
}
bool v2 = base.Configuration.General.UseEscToCancelQuesting;
if (ImGui.Checkbox("Use ESC to cancel questing/movement", ref v2))
if (ImGui.Checkbox("Double tap ESC to cancel questing/movement", ref v2))
{
base.Configuration.General.UseEscToCancelQuesting = v2;
Save();
@ -133,21 +195,27 @@ internal sealed class GeneralConfigComponent : ConfigComponent
base.Configuration.General.HideSeasonalEventsFromJournalProgress = v4;
Save();
}
bool v5 = base.Configuration.General.ShowChangelogOnUpdate;
if (ImGui.Checkbox("Show changelog window when plugin updates", ref v5))
{
base.Configuration.General.ShowChangelogOnUpdate = v5;
Save();
}
}
ImGui.Separator();
ImGui.Text("Questing");
using (ImRaii.PushIndent())
{
bool v5 = base.Configuration.General.ConfigureTextAdvance;
if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v5))
bool v6 = base.Configuration.General.ConfigureTextAdvance;
if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v6))
{
base.Configuration.General.ConfigureTextAdvance = v5;
base.Configuration.General.ConfigureTextAdvance = v6;
Save();
}
bool v6 = base.Configuration.General.SkipLowPriorityDuties;
if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v6))
bool v7 = base.Configuration.General.SkipLowPriorityDuties;
if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v7))
{
base.Configuration.General.SkipLowPriorityDuties = v6;
base.Configuration.General.SkipLowPriorityDuties = v7;
Save();
}
ImGui.SameLine();
@ -167,18 +235,18 @@ internal sealed class GeneralConfigComponent : ConfigComponent
{
if (_territoryData.TryGetContentFinderCondition(lowPriorityContentFinderConditionQuest.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
{
ImU8String text = new ImU8String(0, 1);
text.AppendFormatted(contentFinderConditionData.Name);
ImGui.BulletText(text);
ImU8String text2 = new ImU8String(0, 1);
text2.AppendFormatted(contentFinderConditionData.Name);
ImGui.BulletText(text2);
}
}
}
}
ImGui.Spacing();
bool v7 = base.Configuration.General.AutoStepRefreshEnabled;
if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v7))
bool v8 = base.Configuration.General.AutoStepRefreshEnabled;
if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v8))
{
base.Configuration.General.AutoStepRefreshEnabled = v7;
base.Configuration.General.AutoStepRefreshEnabled = v8;
Save();
}
ImGui.SameLine();
@ -194,37 +262,37 @@ internal sealed class GeneralConfigComponent : ConfigComponent
ImGui.Text("This helps resume automated quest completion when interruptions occur.");
}
}
using (ImRaii.Disabled(!v7))
using (ImRaii.Disabled(!v8))
{
ImGui.Indent();
int v8 = base.Configuration.General.AutoStepRefreshDelaySeconds;
int v9 = base.Configuration.General.AutoStepRefreshDelaySeconds;
ImGui.SetNextItemWidth(150f);
if (ImGui.SliderInt("Refresh delay (seconds)", ref v8, 10, 180))
if (ImGui.SliderInt("Refresh delay (seconds)", ref v9, 10, 180))
{
base.Configuration.General.AutoStepRefreshDelaySeconds = v8;
base.Configuration.General.AutoStepRefreshDelaySeconds = v9;
Save();
}
Vector4 col = new Vector4(0.7f, 0.7f, 0.7f, 1f);
ImU8String text = new ImU8String(77, 1);
text.AppendLiteral("Quest steps will refresh automatically after ");
text.AppendFormatted(v8);
text.AppendLiteral(" seconds if no progress is made.");
ImGui.TextColored(in col, text);
ImU8String text2 = new ImU8String(77, 1);
text2.AppendLiteral("Quest steps will refresh automatically after ");
text2.AppendFormatted(v9);
text2.AppendLiteral(" seconds if no progress is made.");
ImGui.TextColored(in col, text2);
ImGui.Unindent();
}
ImGui.Spacing();
ImGui.Separator();
ImGui.Text("Priority Quest Management");
bool v9 = base.Configuration.General.ClearPriorityQuestsOnLogout;
if (ImGui.Checkbox("Clear priority quests on character logout", ref v9))
bool v10 = base.Configuration.General.ClearPriorityQuestsOnLogout;
if (ImGui.Checkbox("Clear priority quests on character logout", ref v10))
{
base.Configuration.General.ClearPriorityQuestsOnLogout = v9;
base.Configuration.General.ClearPriorityQuestsOnLogout = v10;
Save();
}
bool v10 = base.Configuration.General.ClearPriorityQuestsOnCompletion;
if (ImGui.Checkbox("Remove priority quests when completed", ref v10))
bool v11 = base.Configuration.General.ClearPriorityQuestsOnCompletion;
if (ImGui.Checkbox("Remove priority quests when completed", ref v11))
{
base.Configuration.General.ClearPriorityQuestsOnCompletion = v10;
base.Configuration.General.ClearPriorityQuestsOnCompletion = v11;
Save();
}
ImGui.SameLine();

View file

@ -52,7 +52,7 @@ internal sealed class ActiveQuestComponent
public event EventHandler? Reload;
[GeneratedRegex("\\s\\s+", RegexOptions.IgnoreCase, "en-US")]
[GeneratedCode("System.Text.RegularExpressions.Generator", "9.0.12.47515")]
[GeneratedCode("System.Text.RegularExpressions.Generator", "9.0.13.1716")]
private static Regex MultipleWhitespaceRegex()
{
return _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0.Instance;

View file

@ -0,0 +1,595 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin.Services;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Controller;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Windows.QuestComponents;
internal sealed class QuestSequenceComponent
{
private readonly QuestData _questData;
private readonly QuestRegistry _questRegistry;
private readonly QuestFunctions _questFunctions;
private readonly IDataManager _dataManager;
private readonly ILogger<QuestSequenceComponent> _logger;
private string _questLookupInput = string.Empty;
private ElementId? _lookedUpQuestId;
private int _hoveredSequenceId = -1;
public QuestSequenceComponent(QuestData questData, QuestRegistry questRegistry, QuestFunctions questFunctions, IDataManager dataManager, ILogger<QuestSequenceComponent> logger)
{
_questData = questData;
_questRegistry = questRegistry;
_questFunctions = questFunctions;
_dataManager = dataManager;
_logger = logger;
}
public static void DrawCustomHeader(ref bool isOpen)
{
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float x = ImGui.GetContentRegionAvail().X;
float num = ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y * 1.5f;
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
windowDrawList.AddRectFilledMultiColor(cursorScreenPos, cursorScreenPos + new Vector2(x, num), ImGui.ColorConvertFloat4ToU32(new Vector4(0.22f, 0.2f, 0.28f, 0.85f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.18f, 0.16f, 0.24f, 0.85f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.18f, 0.16f, 0.24f, 0.85f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.22f, 0.2f, 0.28f, 0.85f)));
windowDrawList.AddLine(cursorScreenPos + new Vector2(0f, num), cursorScreenPos + new Vector2(x, num), ImGui.ColorConvertFloat4ToU32(new Vector4(0.7f, 0.6f, 0.9f, 0.5f)), 2f);
float textLineHeight = ImGui.GetTextLineHeight();
float num2 = (num - textLineHeight) * 0.5f;
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + num2);
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 10f);
ImGui.TextColored(new Vector4(0.98f, 0.98f, 1f, 1f), "Quest Sequence Viewer");
ImGui.SameLine(x - 35f);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - num2 + ImGui.GetStyle().ItemSpacing.Y);
using (ImRaii.PushColor(ImGuiCol.Button, Vector4.Zero))
{
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, new Vector4(0.8f, 0.3f, 0.3f, 0.4f)))
{
using (ImRaii.PushColor(ImGuiCol.ButtonActive, new Vector4(0.9f, 0.2f, 0.2f, 0.6f)))
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.Times))
{
isOpen = false;
}
}
}
}
ImGui.SetCursorPosY(cursorScreenPos.Y + num - ImGui.GetCursorScreenPos().Y + ImGui.GetCursorPosY());
}
public static void DrawWindowBorder()
{
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
Vector2 windowPos = ImGui.GetWindowPos();
Vector2 windowSize = ImGui.GetWindowSize();
windowDrawList.AddRect(windowPos - new Vector2(1f, 1f), windowPos + windowSize + new Vector2(1f, 1f), ImGui.ColorConvertFloat4ToU32(new Vector4(0.7f, 0.6f, 0.9f, 0.15f)), 5f, ImDrawFlags.None, 3f);
windowDrawList.AddRect(windowPos, windowPos + windowSize, ImGui.ColorConvertFloat4ToU32(new Vector4(0.7f, 0.6f, 0.9f, 0.4f)), 4f, ImDrawFlags.None, 1.5f);
}
public void DrawCurrentQuestTab()
{
QuestReference currentQuest = _questFunctions.GetCurrentQuest();
(ElementId, byte) tuple = (currentQuest.CurrentQuest, currentQuest.Sequence);
if (tuple.Item1 != null)
{
DrawQuestSequences(tuple.Item1, tuple.Item2);
}
else
{
DrawEmptyState(FontAwesomeIcon.QuestionCircle, "No Active Quest", "Accept a quest to view its sequence information.", new Vector4(0.6f, 0.5f, 0.8f, 0.7f));
}
}
public void DrawQuestLookupTab()
{
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float y = ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y * 3f;
float x = ImGui.GetContentRegionAvail().X;
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
windowDrawList.AddRectFilledMultiColor(cursorScreenPos, cursorScreenPos + new Vector2(x, y), ImGui.ColorConvertFloat4ToU32(new Vector4(0.12f, 0.1f, 0.16f, 0.6f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.1f, 0.08f, 0.14f, 0.6f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.1f, 0.08f, 0.14f, 0.6f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.12f, 0.1f, 0.16f, 0.6f)));
windowDrawList.AddRect(cursorScreenPos, cursorScreenPos + new Vector2(x, y), ImGui.ColorConvertFloat4ToU32(new Vector4(0.6f, 0.5f, 0.8f, 0.2f)), 4f, ImDrawFlags.None, 1f);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetStyle().ItemSpacing.Y * 1.5f);
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 10f);
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextColored(new Vector4(0.7f, 0.6f, 0.9f, 1f), FontAwesomeIcon.Search.ToIconString());
}
ImGui.SameLine();
float x2 = ImGui.GetStyle().ItemSpacing.X;
float num = 10f;
float num2 = 10f;
float num3 = ImGui.CalcTextSize("Search").X + ImGui.CalcTextSize(FontAwesomeIcon.Search.ToIconString()).X + ImGui.GetStyle().FramePadding.X * 4f + x2;
ImGui.SetNextItemWidth(x - num3 - x2 * 3f - num - num2);
using (ImRaii.PushColor(ImGuiCol.FrameBg, new Vector4(0.08f, 0.06f, 0.12f, 0.8f)))
{
using (ImRaii.PushColor(ImGuiCol.FrameBgHovered, new Vector4(0.12f, 0.1f, 0.18f, 0.9f)))
{
using (ImRaii.PushColor(ImGuiCol.FrameBgActive, new Vector4(0.15f, 0.13f, 0.22f, 1f)))
{
bool flag = ImGui.InputTextWithHint("##QuestLookup", "Enter quest ID...", ref _questLookupInput, 10, ImGuiInputTextFlags.EnterReturnsTrue);
ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Button, new Vector4(0.25f, 0.22f, 0.32f, 0.8f)))
{
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, new Vector4(0.35f, 0.3f, 0.45f, 0.9f)))
{
using (ImRaii.PushColor(ImGuiCol.ButtonActive, new Vector4(0.4f, 0.35f, 0.5f, 1f)))
{
bool flag2 = ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Search, "Search");
if (flag || flag2)
{
if (ushort.TryParse(_questLookupInput, out var result))
{
_lookedUpQuestId = new QuestId(result);
}
else
{
_lookedUpQuestId = null;
}
}
}
}
}
}
}
}
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetStyle().ItemSpacing.Y * 1.5f);
ImGui.Spacing();
ImGui.Spacing();
if (_lookedUpQuestId != null)
{
DrawQuestSequences(_lookedUpQuestId, null);
}
else
{
DrawEmptyState(FontAwesomeIcon.Search, "Enter a Quest ID", "Use the search box above to look up any quest by its ID.", new Vector4(0.6f, 0.5f, 0.8f, 0.7f));
}
}
private static void DrawEmptyState(FontAwesomeIcon icon, string title, string message, Vector4 iconColor)
{
float y = ImGui.GetContentRegionAvail().Y;
float x = ImGui.GetContentRegionAvail().X;
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + y * 0.25f);
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
ImGui.PushFont(UiBuilder.IconFont);
string text = icon.ToIconString();
Vector2 vector = ImGui.CalcTextSize(text);
vector *= 2.5f;
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
Vector2 vector2 = new Vector2(x * 0.5f, 0f) + cursorScreenPos + new Vector2(0f, vector.Y * 0.5f);
for (int num = 3; num > 0; num--)
{
Vector4 col = new Vector4(iconColor.X, iconColor.Y, iconColor.Z, iconColor.W * 0.1f * (float)num);
ImGui.SetCursorScreenPos(vector2 - vector * 0.5f - new Vector2(num * 2, num * 2));
ImGui.TextColored(in col, text);
}
ImGui.SetCursorScreenPos(vector2 - vector * 0.5f);
ImGui.TextColored(in iconColor, text);
ImGui.PopFont();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + vector.Y * 0.5f + 20f);
Vector2 vector3 = ImGui.CalcTextSize(title);
Vector2 vector4 = new Vector2((x - vector3.X) * 0.5f, 0f) + ImGui.GetCursorScreenPos();
windowDrawList.AddText(vector4 + new Vector2(1f, 1f), ImGui.ColorConvertFloat4ToU32(new Vector4(0f, 0f, 0f, 0.5f)), title);
ImGui.SetCursorPosX((x - vector3.X) * 0.5f);
ImGui.TextColored(new Vector4(0.95f, 0.95f, 1f, 1f), title);
ImGui.Spacing();
ImGui.Spacing();
ImGui.SetCursorPosX((x - ImGui.CalcTextSize(message).X) * 0.5f);
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.8f, 1f), message);
}
private void DrawQuestSequences(ElementId questElementId, byte? currentSequence)
{
if (!(questElementId is QuestId questId))
{
DrawErrorState("Invalid Quest ID Type", "This viewer only supports regular quest IDs.");
return;
}
if (!_questData.TryGetQuestInfo(questElementId, out IQuestInfo questInfo))
{
DrawErrorState($"Quest {questId.Value} Not Found", "This quest ID does not exist in the game data.");
return;
}
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float num = ImGui.GetFrameHeightWithSpacing() * 1.8f;
float x = ImGui.GetContentRegionAvail().X;
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
windowDrawList.AddRectFilledMultiColor(cursorScreenPos, cursorScreenPos + new Vector2(x, num), ImGui.ColorConvertFloat4ToU32(new Vector4(0.12f, 0.1f, 0.16f, 0.7f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.1f, 0.08f, 0.14f, 0.7f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.1f, 0.08f, 0.14f, 0.7f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.12f, 0.1f, 0.16f, 0.7f)));
windowDrawList.AddRect(cursorScreenPos, cursorScreenPos + new Vector2(x, num), ImGui.ColorConvertFloat4ToU32(new Vector4(0.6f, 0.5f, 0.8f, 0.25f)), 4f, ImDrawFlags.None, 1f);
float textLineHeight = ImGui.GetTextLineHeight();
float num2 = (num - textLineHeight) * 0.5f;
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + num2);
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 10f);
ImGui.PushFont(UiBuilder.IconFont);
ImGui.TextColored(new Vector4(0.7f, 0.6f, 0.9f, 1f), FontAwesomeIcon.Book.ToIconString());
ImGui.PopFont();
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.98f, 0.98f, 1f, 1f), questInfo.Name);
ImGui.SameLine();
Vector4 col = new Vector4(0.7f, 0.7f, 0.8f, 1f);
ImU8String text = new ImU8String(6, 1);
text.AppendLiteral("(ID: ");
text.AppendFormatted(questId.Value);
text.AppendLiteral(")");
ImGui.TextColored(in col, text);
if (currentSequence.HasValue)
{
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.5f, 0.5f, 0.6f, 1f), "—");
ImGui.SameLine();
float num3 = (float)ImGui.GetTime();
float num4 = 0.85f + MathF.Sin(num3 * 3f) * 0.15f;
col = new Vector4(0.5f * num4, 1f * num4, 0.6f * num4, 1f);
text = new ImU8String(17, 1);
text.AppendLiteral("Active: Sequence ");
text.AppendFormatted(currentSequence.Value);
ImGui.TextColored(in col, text);
}
ImGui.SetCursorPosY(cursorScreenPos.Y + num - ImGui.GetCursorScreenPos().Y + ImGui.GetCursorPosY());
ImGui.Spacing();
try
{
Lumina.Excel.Sheets.Quest? quest = _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>()?.GetRowOrDefault((uint)(questId.Value + 65536));
if (!quest.HasValue)
{
DrawErrorState("Quest Not Found in Game Data", "Unable to load quest information from Lumina.");
return;
}
List<int> list = (from result in quest.Value.TodoParams.Where((Lumina.Excel.Sheets.Quest.TodoParamsStruct todoParamsStruct) => todoParamsStruct.ToDoCompleteSeq > 0 && todoParamsStruct.ToDoCompleteSeq < byte.MaxValue).Select((Func<Lumina.Excel.Sheets.Quest.TodoParamsStruct, int>)((Lumina.Excel.Sheets.Quest.TodoParamsStruct todoParamsStruct) => todoParamsStruct.ToDoCompleteSeq)).Distinct()
orderby result
select result).ToList();
if (list.Count == 0)
{
DrawErrorState("No Sequence Data Available", "This quest may not have traditional sequences.");
return;
}
int num5 = list.Max();
int num6 = num5 + 1;
Questionable.Model.Quest quest2;
bool flag = _questRegistry.TryGetQuest(questElementId, out quest2);
HashSet<int> hashSet = new HashSet<int>();
Dictionary<int, (int, string)> dictionary = new Dictionary<int, (int, string)>();
bool flag2 = false;
if (flag && quest2 != null && quest2.Root.QuestSequence.Count > 0)
{
foreach (QuestSequence item in quest2.Root.QuestSequence)
{
if (item.Sequence == byte.MaxValue)
{
flag2 = true;
dictionary[255] = (item.Steps.Count, item.Comment);
}
else
{
hashSet.Add(item.Sequence);
dictionary[item.Sequence] = (item.Steps.Count, item.Comment);
}
}
}
int num7 = hashSet.Count + (flag2 ? 1 : 0);
int num8 = num6 + 1;
float num9 = (flag ? ((float)num7 / (float)num8 * 100f) : 0f);
Vector4 coverageColor = ((num9 >= 100f) ? new Vector4(0.5f, 1f, 0.6f, 1f) : ((num9 >= 50f) ? new Vector4(1f, 0.9f, 0.5f, 1f) : ((num9 < 1f) ? new Vector4(0.6f, 0.5f, 0.8f, 0.7f) : new Vector4(1f, 0.5f, 0.5f, 1f))));
DrawSummaryCards(num6, num5, flag, num9, coverageColor, num7, num8);
ImGui.Spacing();
ImGui.Spacing();
DrawSequenceGrid(num5, currentSequence, hashSet, dictionary, flag2);
}
catch (Exception ex)
{
DrawErrorState("Error Loading Quest Data", ex.Message + "\nCheck logs for details.");
_logger.LogWarning(ex, "Failed to read quest sequence data from Lumina for {QuestId}", questId);
}
}
private static void DrawErrorState(string title, string message)
{
float y = ImGui.GetContentRegionAvail().Y;
float x = ImGui.GetContentRegionAvail().X;
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + y * 0.25f);
float num = (float)ImGui.GetTime();
float num2 = 0.7f + MathF.Sin(num * 2f) * 0.3f;
ImGui.PushFont(UiBuilder.IconFont);
string text = FontAwesomeIcon.ExclamationTriangle.ToIconString();
Vector2 vector = ImGui.CalcTextSize(text);
ImGui.SetCursorPosX((x - (vector * 2f).X) * 0.5f);
ImGui.TextColored(new Vector4(1f * num2, 0.4f * num2, 0.4f * num2, 1f), text);
ImGui.PopFont();
ImGui.Spacing();
ImGui.Spacing();
ImGui.SetCursorPosX((x - ImGui.CalcTextSize(title).X) * 0.5f);
ImGui.TextColored(new Vector4(1f, 0.5f, 0.5f, 1f), title);
ImGui.Spacing();
ImGui.SetCursorPosX((x - ImGui.CalcTextSize(message).X) * 0.5f);
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.8f, 1f), message);
}
private static void DrawSummaryCards(int totalSequences, int maxSeq, bool hasQuestPath, float coverage, Vector4 coverageColor, int implementedCount, int expectedCount)
{
using ImRaii.IEndObject endObject = ImRaii.Table("SummaryCards", 2, ImGuiTableFlags.None);
if (!endObject)
{
return;
}
ImGui.TableSetupColumn("GameData", ImGuiTableColumnFlags.WidthStretch, 0.4f);
ImGui.TableSetupColumn("QuestPath", ImGuiTableColumnFlags.WidthStretch, 0.6f);
ImGui.TableNextRow();
ImGui.TableNextColumn();
DrawCard(80f, new Vector4(0.6f, 0.85f, 1f, 1f), FontAwesomeIcon.Gamepad, "Expected Sequences", delegate
{
ImGui.Spacing();
ImGui.Indent(10f);
Vector4 col = new Vector4(0.9f, 0.9f, 0.98f, 1f);
ImU8String text = new ImU8String(6, 0);
text.AppendLiteral("Count:");
ImGui.TextColored(in col, text);
ImGui.SameLine();
col = new Vector4(0.7f, 0.9f, 1f, 1f);
text = new ImU8String(0, 1);
text.AppendFormatted(totalSequences);
ImGui.TextColored(in col, text);
ImGui.Spacing();
col = new Vector4(0.9f, 0.9f, 0.98f, 1f);
text = new ImU8String(6, 0);
text.AppendLiteral("Range:");
ImGui.TextColored(in col, text);
ImGui.SameLine();
col = new Vector4(0.7f, 0.9f, 1f, 1f);
text = new ImU8String(9, 1);
text.AppendLiteral("0 → ");
text.AppendFormatted(maxSeq);
text.AppendLiteral(", 255");
ImGui.TextColored(in col, text);
ImGui.Unindent(10f);
});
ImGui.TableNextColumn();
DrawCard(80f, new Vector4(0.5f, 1f, 0.6f, 1f), FontAwesomeIcon.Code, "Implementation Status", delegate
{
if (hasQuestPath)
{
ImGui.Indent(10f);
ImGui.TextColored(new Vector4(0.9f, 0.9f, 0.98f, 1f), "Progress:");
ImGui.SameLine();
ref Vector4 col = ref coverageColor;
ImU8String text = new ImU8String(0, 1);
text.AppendFormatted(implementedCount);
ImGui.TextColored(in col, text);
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.8f, 1f), "/");
ImGui.SameLine();
Vector4 col2 = new Vector4(0.9f, 0.9f, 0.98f, 1f);
text = new ImU8String(0, 1);
text.AppendFormatted(expectedCount);
ImGui.TextColored(in col2, text);
ImGui.SameLine();
ref Vector4 col3 = ref coverageColor;
text = new ImU8String(3, 1);
text.AppendLiteral("(");
text.AppendFormatted(coverage, "F0");
text.AppendLiteral("%)");
ImGui.TextColored(in col3, text);
ImGui.Spacing();
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float x = ImGui.GetContentRegionAvail().X - 10f;
using (ImRaii.PushColor(ImGuiCol.PlotHistogram, coverageColor))
{
using (ImRaii.PushColor(ImGuiCol.FrameBg, new Vector4(0.08f, 0.06f, 0.12f, 0.8f)))
{
ImGui.ProgressBar(coverage / 100f, new Vector2(x, 16f), "");
}
}
ImGui.GetWindowDrawList().AddRect(cursorScreenPos, cursorScreenPos + new Vector2(x, 16f), ImGui.ColorConvertFloat4ToU32(new Vector4(0.6f, 0.5f, 0.8f, 0.3f)), 2f);
ImGui.Unindent(10f);
}
else
{
ImGui.Spacing();
ImGui.Indent(10f);
ImGui.PushFont(UiBuilder.IconFont);
ImGui.TextColored(new Vector4(0.6f, 0.5f, 0.8f, 0.8f), FontAwesomeIcon.Ban.ToIconString());
ImGui.PopFont();
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.6f, 0.5f, 0.8f, 0.8f), "Not Implemented");
ImGui.Spacing();
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.8f, 1f), "No quest path data available");
ImGui.Unindent(10f);
}
});
}
private static void DrawCard(float height, Vector4 accentColor, FontAwesomeIcon icon, string title, System.Action content)
{
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float x = ImGui.GetContentRegionAvail().X;
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
windowDrawList.AddRectFilledMultiColor(cursorScreenPos, cursorScreenPos + new Vector2(x, height), ImGui.ColorConvertFloat4ToU32(new Vector4(0.12f, 0.1f, 0.16f, 0.7f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.1f, 0.08f, 0.14f, 0.7f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.1f, 0.08f, 0.14f, 0.7f)), ImGui.ColorConvertFloat4ToU32(new Vector4(0.12f, 0.1f, 0.16f, 0.7f)));
windowDrawList.AddRect(cursorScreenPos, cursorScreenPos + new Vector2(x, height), ImGui.ColorConvertFloat4ToU32(new Vector4(0.6f, 0.5f, 0.8f, 0.2f)), 4f, ImDrawFlags.None, 1f);
windowDrawList.AddLine(cursorScreenPos, cursorScreenPos + new Vector2(x, 0f), ImGui.ColorConvertFloat4ToU32(accentColor), 3f);
float num = cursorScreenPos.Y + 10f;
ImGui.SetCursorScreenPos(new Vector2(cursorScreenPos.X + 10f, num));
ImGui.PushFont(UiBuilder.IconFont);
ImGui.TextColored(in accentColor, icon.ToIconString());
ImGui.PopFont();
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.98f, 0.98f, 1f, 1f), title);
ImGui.SetCursorScreenPos(new Vector2(cursorScreenPos.X + 10f, num + 24f));
content();
ImGui.SetCursorScreenPos(new Vector2(cursorScreenPos.X, cursorScreenPos.Y + height + 4f));
}
private void DrawSequenceGrid(int maxSeq, byte? currentSequence, HashSet<int> implementedSequences, Dictionary<int, (int StepCount, string? Comment)> sequenceDetails, bool hasEndSequence)
{
int val = maxSeq + 2;
float x = ImGui.GetContentRegionAvail().X;
int val2 = Math.Max(4, Math.Min(8, (int)(x / 100f)));
val2 = Math.Min(val2, val);
float y = ImGui.GetContentRegionAvail().Y;
using (ImRaii.PushColor(ImGuiCol.ChildBg, new Vector4(0.08f, 0.06f, 0.12f, 0.6f)))
{
using (ImRaii.PushColor(ImGuiCol.Border, new Vector4(0.6f, 0.5f, 0.8f, 0.2f)))
{
using ImRaii.IEndObject endObject = ImRaii.Child("SequenceGrid", new Vector2(-1f, y), border: true, ImGuiWindowFlags.None);
if (!ImRaii.IEndObject.op_True(endObject))
{
return;
}
using (ImRaii.PushColor(ImGuiCol.TableBorderStrong, new Vector4(0.6f, 0.5f, 0.8f, 0.2f)))
{
using (ImRaii.PushColor(ImGuiCol.TableBorderLight, new Vector4(0.6f, 0.5f, 0.8f, 0.15f)))
{
using ImRaii.IEndObject endObject2 = ImRaii.Table("SequenceGridTable", val2, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingStretchSame);
if (!ImRaii.IEndObject.op_True(endObject2))
{
return;
}
int num = 0;
for (int i = 0; i <= maxSeq; i++)
{
if (num % val2 == 0)
{
ImGui.TableNextRow();
}
ImGui.TableNextColumn();
DrawSequenceCell(i, currentSequence, implementedSequences, sequenceDetails);
num++;
}
if (num % val2 == 0)
{
ImGui.TableNextRow();
}
ImGui.TableNextColumn();
DrawSequenceCell(255, currentSequence, implementedSequences, sequenceDetails, hasEndSequence);
for (num++; num % val2 != 0; num++)
{
ImGui.TableNextColumn();
}
}
}
}
}
}
private void DrawSequenceCell(int sequenceId, byte? currentSequence, HashSet<int> implementedSequences, Dictionary<int, (int StepCount, string? Comment)> sequenceDetails, bool? forcedImplemented = null)
{
bool flag = currentSequence.HasValue && sequenceId == currentSequence.Value;
bool flag2 = forcedImplemented ?? implementedSequences.Contains(sequenceId);
bool flag3 = _hoveredSequenceId == sequenceId;
string text = ((sequenceId == 255) ? "255" : $"{sequenceId}");
Vector4 input;
Vector4 input2;
if (flag)
{
input = (flag3 ? new Vector4(0.18f, 0.55f, 0.25f, 0.8f) : new Vector4(0.15f, 0.48f, 0.22f, 0.7f));
input2 = new Vector4(0.5f, 1f, 0.6f, flag3 ? 0.8f : 0.5f);
}
else if (flag2)
{
input = (flag3 ? new Vector4(0.14f, 0.38f, 0.18f, 0.6f) : new Vector4(0.12f, 0.34f, 0.16f, 0.5f));
input2 = new Vector4(0.5f, 1f, 0.6f, flag3 ? 0.5f : 0.25f);
}
else
{
input = (flag3 ? new Vector4(0.5f, 0.18f, 0.18f, 0.6f) : new Vector4(0.45f, 0.15f, 0.15f, 0.5f));
input2 = new Vector4(1f, 0.5f, 0.5f, flag3 ? 0.6f : 0.3f);
}
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
Vector2 vector = new Vector2(ImGui.GetContentRegionAvail().X, 75f);
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
windowDrawList.AddRectFilled(cursorScreenPos, cursorScreenPos + vector, ImGui.ColorConvertFloat4ToU32(input), 3f);
if (flag3)
{
windowDrawList.AddRect(cursorScreenPos - new Vector2(1f, 1f), cursorScreenPos + vector + new Vector2(1f, 1f), ImGui.ColorConvertFloat4ToU32(new Vector4(input2.X, input2.Y, input2.Z, input2.W * 0.3f)), 3f, ImDrawFlags.None, 3f);
}
windowDrawList.AddRect(cursorScreenPos, cursorScreenPos + vector, ImGui.ColorConvertFloat4ToU32(input2), 3f, ImDrawFlags.None, flag3 ? 2f : 1.5f);
float x = vector.X;
Vector2 vector2 = ImGui.CalcTextSize(text);
Vector4 input3 = (flag ? new Vector4(0.98f, 0.98f, 1f, 1f) : new Vector4(0.9f, 0.9f, 0.98f, 1f));
float y = cursorScreenPos.Y + (vector.Y - vector2.Y) * 0.5f;
windowDrawList.AddText(new Vector2(cursorScreenPos.X + (x - vector2.X) * 0.5f, y), ImGui.ColorConvertFloat4ToU32(input3), text);
ImGui.SetCursorScreenPos(cursorScreenPos);
ImU8String strId = new ImU8String(5, 1);
strId.AppendLiteral("##Seq");
strId.AppendFormatted(sequenceId);
ImGui.InvisibleButton(strId, vector);
if (ImGui.IsItemHovered())
{
_hoveredSequenceId = sequenceId;
using (ImRaii.Tooltip())
{
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(0.98f, 0.98f, 1f, 1f)))
{
strId = new ImU8String(9, 1);
strId.AppendLiteral("Sequence ");
strId.AppendFormatted(sequenceId);
ImGui.Text(strId);
}
ImGui.Separator();
if (flag2 && sequenceDetails.TryGetValue(sequenceId, out (int, string) value))
{
ImGui.PushFont(UiBuilder.IconFont);
ImGui.TextColored(new Vector4(0.5f, 1f, 0.6f, 1f), FontAwesomeIcon.CheckCircle.ToIconString());
ImGui.PopFont();
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.5f, 1f, 0.6f, 1f), "Implemented");
ImGui.Spacing();
Vector4 col = new Vector4(0.9f, 0.9f, 0.98f, 1f);
strId = new ImU8String(6, 0);
strId.AppendLiteral("Steps:");
ImGui.TextColored(in col, strId);
ImGui.SameLine();
col = new Vector4(0.7f, 0.9f, 1f, 1f);
strId = new ImU8String(0, 1);
strId.AppendFormatted(value.Item1);
ImGui.TextColored(in col, strId);
if (!string.IsNullOrEmpty(value.Item2))
{
ImGui.Separator();
ImGui.PushTextWrapPos(250f);
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.8f, 1f), value.Item2);
ImGui.PopTextWrapPos();
}
}
else if (!flag2)
{
ImGui.PushFont(UiBuilder.IconFont);
ImGui.TextColored(new Vector4(1f, 0.5f, 0.5f, 1f), FontAwesomeIcon.TimesCircle.ToIconString());
ImGui.PopFont();
ImGui.SameLine();
ImGui.TextColored(new Vector4(1f, 0.5f, 0.5f, 1f), "Not Implemented");
ImGui.Spacing();
ImGui.PushTextWrapPos(250f);
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.8f, 1f), "This sequence is missing from the quest path.");
ImGui.PopTextWrapPos();
}
if (flag)
{
ImGui.Separator();
ImGui.TextColored(new Vector4(1f, 0.85f, 0.5f, 1f), "► Currently Active");
}
}
}
else if (_hoveredSequenceId == sequenceId)
{
_hoveredSequenceId = -1;
}
ImGui.SetCursorScreenPos(cursorScreenPos + new Vector2(0f, vector.Y));
}
}

View file

@ -0,0 +1,132 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using LLib.ImGui;
using Questionable.Data;
using Questionable.Model.Changelog;
using Questionable.Windows.ChangelogComponents;
namespace Questionable.Windows;
internal sealed class ChangelogWindow : LWindow
{
private readonly Configuration _configuration;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly ChangelogHeaderComponent _headerComponent;
private readonly ChangelogFooterComponent _footerComponent;
private int _windowOpenCount;
private bool _hasSetInitialState;
private float _headerAnimationTime;
public ChangelogWindow(Configuration configuration, IDalamudPluginInterface pluginInterface)
: base("Questionable Changelog###QuestionableChangelog", ImGuiWindowFlags.NoTitleBar)
{
_configuration = configuration;
_pluginInterface = pluginInterface;
_headerComponent = new ChangelogHeaderComponent();
_footerComponent = new ChangelogFooterComponent();
base.Size = new Vector2(900f, 650f);
base.SizeCondition = ImGuiCond.FirstUseEver;
base.SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(700f, 500f),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
};
}
public override void DrawContent()
{
_headerComponent.Draw(delegate(bool isOpen)
{
base.IsOpen = isOpen;
});
DrawChangelogEntries();
ChangelogFooterComponent.Draw(delegate
{
MarkAllAsRead();
base.IsOpen = false;
});
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
Vector2 windowPos = ImGui.GetWindowPos();
Vector2 windowSize = ImGui.GetWindowSize();
windowDrawList.AddRect(windowPos, windowPos + windowSize, ImGui.ColorConvertFloat4ToU32(new Vector4(0.65f, 0.55f, 0.85f, 0.3f)), 0f, ImDrawFlags.None, 2f);
}
public override void OnOpen()
{
base.OnOpen();
_windowOpenCount++;
_hasSetInitialState = false;
_headerAnimationTime = 0f;
_headerComponent.Reset();
}
private void DrawChangelogEntries()
{
_headerAnimationTime += ImGui.GetIO().DeltaTime;
using (ImRaii.PushColor(ImGuiCol.ScrollbarBg, new Vector4(0.1f, 0.08f, 0.14f, 0.6f)))
{
using (ImRaii.PushColor(ImGuiCol.ScrollbarGrab, new Vector4(0.55f, 0.45f, 0.75f, 0.4f)))
{
using (ImRaii.PushColor(ImGuiCol.ScrollbarGrabHovered, new Vector4(0.65f, 0.55f, 0.85f, 0.6f)))
{
using (ImRaii.PushColor(ImGuiCol.ScrollbarGrabActive, new Vector4(0.75f, 0.55f, 0.95f, 0.8f)))
{
float num = ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y * 2f;
using ImRaii.IEndObject endObject = ImRaii.Child("ChangelogScroll", new Vector2(0f, 0f - num), border: false, ImGuiWindowFlags.NoScrollbar);
if (!endObject)
{
return;
}
using ImRaii.IEndObject endObject2 = ImRaii.Child("ChangelogScrollInner", Vector2.Zero, border: false);
if (!endObject2)
{
return;
}
List<ChangelogEntry> changelogs = ChangelogData.Changelogs;
ChangelogEntry changelogEntry = changelogs.FirstOrDefault();
if (changelogs.Count == 0)
{
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 40f);
Vector2 vector = ImGui.CalcTextSize("No changelog entries available.");
ImGui.SetCursorPosX((ImGui.GetContentRegionAvail().X - vector.X) * 0.5f);
ImGui.TextDisabled("No changelog entries available.");
return;
}
ChangelogEntryComponent changelogEntryComponent = new ChangelogEntryComponent(_configuration, _windowOpenCount, _headerAnimationTime);
foreach (ChangelogEntry item in changelogs)
{
changelogEntryComponent.Draw(item, item == changelogEntry, _hasSetInitialState);
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float num2 = ImGui.GetContentRegionAvail().X * 0.5f;
float num3 = (ImGui.GetContentRegionAvail().X - num2) * 0.5f;
ImGui.GetWindowDrawList().AddLine(cursorScreenPos + new Vector2(num3, 0f), cursorScreenPos + new Vector2(num3 + num2, 0f), ImGui.ColorConvertFloat4ToU32(new Vector4(0.55f, 0.45f, 0.75f, 0.1f)), 1f);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 4f);
}
_hasSetInitialState = true;
}
}
}
}
}
private void MarkAllAsRead()
{
string text = ChangelogData.Changelogs.FirstOrDefault()?.Version;
if (text != null)
{
_configuration.LastViewedChangelogVersion = text;
_pluginInterface.SavePluginConfig(_configuration);
}
}
}

View file

@ -1,5 +1,8 @@
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using LLib.ImGui;
using Questionable.Windows.ConfigComponents;
@ -28,7 +31,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
public WindowConfig WindowConfig => _configuration.ConfigWindowConfig;
public ConfigWindow(IDalamudPluginInterface pluginInterface, GeneralConfigComponent generalConfigComponent, PluginConfigComponent pluginConfigComponent, DutyConfigComponent dutyConfigComponent, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent, StopConditionComponent stopConditionComponent, NotificationConfigComponent notificationConfigComponent, DebugConfigComponent debugConfigComponent, Configuration configuration)
public ConfigWindow(IDalamudPluginInterface pluginInterface, GeneralConfigComponent generalConfigComponent, PluginConfigComponent pluginConfigComponent, DutyConfigComponent dutyConfigComponent, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent, StopConditionComponent stopConditionComponent, NotificationConfigComponent notificationConfigComponent, DebugConfigComponent debugConfigComponent, Configuration configuration, ChangelogWindow changelogWindow)
: base("Config - Questionable###QuestionableConfig", ImGuiWindowFlags.AlwaysAutoResize)
{
_pluginInterface = pluginInterface;
@ -40,6 +43,21 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
_notificationConfigComponent = notificationConfigComponent;
_debugConfigComponent = debugConfigComponent;
_configuration = configuration;
base.TitleBarButtons.Add(new TitleBarButton
{
Icon = FontAwesomeIcon.FileAlt,
IconOffset = new Vector2(1.5f, 1f),
Click = delegate
{
changelogWindow.IsOpenAndUncollapsed = true;
},
ShowTooltip = delegate
{
ImGui.BeginTooltip();
ImGui.Text("View Changelog");
ImGui.EndTooltip();
}
});
}
public override void DrawContent()

View file

@ -0,0 +1,84 @@
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using LLib.ImGui;
using Questionable.Windows.QuestComponents;
namespace Questionable.Windows;
internal sealed class QuestSequenceWindow : LWindow, IPersistableWindowConfig
{
private readonly IDalamudPluginInterface _pluginInterface;
private readonly Configuration _configuration;
private readonly QuestSequenceComponent _questSequenceComponent;
public WindowConfig WindowConfig => _configuration.QuestSequenceWindowConfig;
public QuestSequenceWindow(IDalamudPluginInterface pluginInterface, Configuration configuration, QuestSequenceComponent questSequenceComponent)
: base("Quest Sequence Viewer###QuestionableQuestSequenceViewer", ImGuiWindowFlags.NoTitleBar)
{
_pluginInterface = pluginInterface;
_configuration = configuration;
_questSequenceComponent = questSequenceComponent;
base.Size = new Vector2(950f, 700f);
base.SizeCondition = ImGuiCond.FirstUseEver;
base.SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(750f, 550f),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
};
}
public void SaveWindowConfig()
{
_pluginInterface.SavePluginConfig(_configuration);
}
public override void DrawContent()
{
bool isOpen = base.IsOpen;
QuestSequenceComponent.DrawCustomHeader(ref isOpen);
base.IsOpen = isOpen;
ImGui.Spacing();
ImGui.Spacing();
using (ImRaii.PushColor(ImGuiCol.Tab, new Vector4(0.15f, 0.13f, 0.2f, 0.7f)))
{
using (ImRaii.PushColor(ImGuiCol.TabHovered, new Vector4(0.35f, 0.3f, 0.45f, 0.9f)))
{
using (ImRaii.PushColor(ImGuiCol.TabActive, new Vector4(0.28f, 0.24f, 0.35f, 1f)))
{
using (ImRaii.PushColor(ImGuiCol.TabUnfocused, new Vector4(0.13f, 0.11f, 0.18f, 0.6f)))
{
using (ImRaii.PushColor(ImGuiCol.TabUnfocusedActive, new Vector4(0.25f, 0.22f, 0.3f, 0.95f)))
{
using ImRaii.IEndObject endObject = ImRaii.TabBar("QuestSequenceTabs", ImGuiTabBarFlags.None);
if (!endObject)
{
return;
}
using (ImRaii.IEndObject endObject2 = ImRaii.TabItem("Current Quest"))
{
if (endObject2)
{
ImGui.Spacing();
_questSequenceComponent.DrawCurrentQuestTab();
}
}
using ImRaii.IEndObject endObject3 = ImRaii.TabItem("Quest Lookup");
if (endObject3)
{
ImGui.Spacing();
_questSequenceComponent.DrawQuestLookupTab();
}
}
}
}
}
}
QuestSequenceComponent.DrawWindowBorder();
}
}

View file

@ -51,7 +51,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
public bool IsMinimized { get; set; }
public QuestWindow(IDalamudPluginInterface pluginInterface, QuestController questController, IClientState clientState, Configuration configuration, TerritoryData territoryData, ActiveQuestComponent activeQuestComponent, ARealmRebornComponent aRealmRebornComponent, EventInfoComponent eventInfoComponent, CreationUtilsComponent creationUtilsComponent, QuickAccessButtonsComponent quickAccessButtonsComponent, RemainingTasksComponent remainingTasksComponent, IFramework framework, InteractionUiController interactionUiController, ConfigWindow configWindow)
: base("Questionable v" + PluginVersion.ToString(2) + "###Questionable", ImGuiWindowFlags.AlwaysAutoResize)
: base("Questionable v" + PluginVersion.ToString(3) + "###Questionable", ImGuiWindowFlags.AlwaysAutoResize)
{
QuestWindow questWindow = this;
_pluginInterface = pluginInterface;

View file

@ -21,19 +21,19 @@
</ItemGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Dalamud.dll</HintPath>
</Reference>
<Reference Include="LLib">
<HintPath>..\..\LLib.dll</HintPath>
</Reference>
<Reference Include="FFXIVClientStructs">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\FFXIVClientStructs.dll</HintPath>
</Reference>
<Reference Include="Questionable.Model">
<HintPath>..\..\Questionable.Model.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.Abstractions">
<HintPath>..\..\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
@ -45,19 +45,19 @@
<HintPath>..\..\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Dalamud.Bindings.ImGui">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Dalamud.Bindings.ImGui.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Dalamud.Bindings.ImGui.dll</HintPath>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
</Reference>
<Reference Include="Lumina">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\Lumina.dll</HintPath>
</Reference>
<Reference Include="JsonSchema.Net">
<HintPath>..\..\JsonSchema.Net.dll</HintPath>
</Reference>
<Reference Include="InteropGenerator.Runtime">
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\InteropGenerator.Runtime.dll</HintPath>
<HintPath>..\..\..\..\..\ffxiv\alyssile-xivl\addon\Hooks\dev\InteropGenerator.Runtime.dll</HintPath>
</Reference>
<Reference Include="NotificationMasterAPI">
<HintPath>..\..\NotificationMasterAPI.dll</HintPath>

View file

@ -41,6 +41,8 @@ internal sealed class Configuration : IPluginConfiguration
public bool ClearPriorityQuestsOnLogout { get; set; }
public bool ClearPriorityQuestsOnCompletion { get; set; }
public bool ShowChangelogOnUpdate { get; set; } = true;
}
internal sealed class StopConfiguration
@ -149,6 +151,8 @@ internal sealed class Configuration : IPluginConfiguration
public int PluginSetupCompleteVersion { get; set; }
public string? LastViewedChangelogVersion { get; set; }
public GeneralConfiguration General { get; } = new GeneralConfiguration();
public StopConfiguration Stop { get; } = new StopConfiguration();
@ -167,6 +171,8 @@ internal sealed class Configuration : IPluginConfiguration
public WindowConfig QuestValidationWindowConfig { get; set; } = new WindowConfig();
public WindowConfig QuestSequenceWindowConfig { get; set; } = new WindowConfig();
internal bool IsPluginSetupComplete()
{
return PluginSetupCompleteVersion == 5;

View file

@ -37,7 +37,7 @@ internal sealed class DalamudInitializer : IDisposable
private readonly ILogger<DalamudInitializer> _logger;
public DalamudInitializer(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, MovementController movementController, WindowSystem windowSystem, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, DebugOverlay debugOverlay, ConfigWindow configWindow, QuestSelectionWindow questSelectionWindow, QuestValidationWindow questValidationWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, IToastGui toastGui, Configuration configuration, PartyWatchDog partyWatchDog, ILogger<DalamudInitializer> logger)
public DalamudInitializer(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, MovementController movementController, WindowSystem windowSystem, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, DebugOverlay debugOverlay, ConfigWindow configWindow, QuestSelectionWindow questSelectionWindow, QuestSequenceWindow questSequenceWindow, QuestValidationWindow questValidationWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, ChangelogWindow changelogWindow, IToastGui toastGui, Configuration configuration, PartyWatchDog partyWatchDog, ILogger<DalamudInitializer> logger)
{
_pluginInterface = pluginInterface;
_framework = framework;
@ -56,9 +56,11 @@ internal sealed class DalamudInitializer : IDisposable
_windowSystem.AddWindow(configWindow);
_windowSystem.AddWindow(debugOverlay);
_windowSystem.AddWindow(questSelectionWindow);
_windowSystem.AddWindow(questSequenceWindow);
_windowSystem.AddWindow(questValidationWindow);
_windowSystem.AddWindow(journalProgressWindow);
_windowSystem.AddWindow(priorityWindow);
_windowSystem.AddWindow(changelogWindow);
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenMainUi += ToggleQuestWindow;
_pluginInterface.UiBuilder.OpenConfigUi += _configWindow.Toggle;

View file

@ -1,8 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Dalamud.Extensions.MicrosoftLogging;
using Dalamud.Game;
using Dalamud.Game.ClientState.Objects;
@ -27,7 +24,6 @@ using Questionable.Controller.Utils;
using Questionable.Data;
using Questionable.External;
using Questionable.Functions;
using Questionable.QuestPaths;
using Questionable.Validation;
using Questionable.Validation.Validators;
using Questionable.Windows;
@ -43,10 +39,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
private readonly ServiceProvider? _serviceProvider;
public QuestionablePlugin(IDalamudPluginInterface pluginInterface, IClientState clientState, ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager, ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui, ICommandManager commandManager, IAddonLifecycle addonLifecycle, IKeyState keyState, IContextMenu contextMenu, IToastGui toastGui, IGameInteropProvider gameInteropProvider, IAetheryteList aetheryteList)
{
File.WriteAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Quests.json"), JsonSerializer.Serialize(AssemblyQuestLoader.GetQuests().Select(pair => (pair.Key.GetType().Name + ": " + pair.Key.ToString(), pair.Value)).ToList(), new JsonSerializerOptions()
{ WriteIndented = true, IgnoreReadOnlyProperties = true, IncludeFields = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, }));
{
ArgumentNullException.ThrowIfNull(pluginInterface, "pluginInterface");
ArgumentNullException.ThrowIfNull(chatGui, "chatGui");
try
@ -255,6 +248,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddSingleton<ConfigWindow>();
serviceCollection.AddSingleton<DebugOverlay>();
serviceCollection.AddSingleton<QuestSelectionWindow>();
serviceCollection.AddSingleton<QuestSequenceWindow>();
serviceCollection.AddSingleton<QuestValidationWindow>();
serviceCollection.AddSingleton<JournalProgressWindow>();
serviceCollection.AddSingleton<PriorityWindow>();
@ -265,6 +259,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddSingleton<StopConditionComponent>();
serviceCollection.AddSingleton<NotificationConfigComponent>();
serviceCollection.AddSingleton<DebugConfigComponent>();
serviceCollection.AddSingleton<ChangelogWindow>();
serviceCollection.AddSingleton<QuestSequenceComponent>();
}
private static void AddQuestValidators(ServiceCollection serviceCollection)
@ -299,6 +295,16 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceProvider.GetRequiredService<QuestionableIpc>();
serviceProvider.GetRequiredService<DalamudInitializer>();
serviceProvider.GetRequiredService<TextAdvanceIpc>();
ChangelogWindow requiredService = serviceProvider.GetRequiredService<ChangelogWindow>();
Configuration requiredService2 = serviceProvider.GetRequiredService<Configuration>();
if (requiredService2.IsPluginSetupComplete() && requiredService2.General.ShowChangelogOnUpdate)
{
string text = ChangelogData.Changelogs.FirstOrDefault()?.Version;
if (text != null && (string.IsNullOrEmpty(requiredService2.LastViewedChangelogVersion) || string.CompareOrdinal(text, requiredService2.LastViewedChangelogVersion) > 0))
{
requiredService.IsOpenAndUncollapsed = true;
}
}
}
public void Dispose()

View file

@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
namespace System.Text.RegularExpressions.Generated;
[GeneratedCode("System.Text.RegularExpressions.Generator", "9.0.12.47515")]
[GeneratedCode("System.Text.RegularExpressions.Generator", "9.0.13.1716")]
[SkipLocalsInit]
internal sealed class _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0 : Regex
{

View file

@ -3,7 +3,7 @@ using System.CodeDom.Compiler;
namespace System.Text.RegularExpressions.Generated;
[GeneratedCode("System.Text.RegularExpressions.Generator", "9.0.12.47515")]
[GeneratedCode("System.Text.RegularExpressions.Generator", "9.0.13.1716")]
internal static class _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__Utilities
{
internal static readonly TimeSpan s_defaultTimeout = ((AppContext.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") is TimeSpan timeSpan) ? timeSpan : Regex.InfiniteMatchTimeout);