forked from liza/Questionable
Automatic weekly custom delivery turn in + some gathering cleanup
This commit is contained in:
parent
837ee7b368
commit
139250c4a4
@ -79,7 +79,7 @@
|
||||
"Y": 257.4255,
|
||||
"Z": -669.3115
|
||||
},
|
||||
"MinimumAngle": -65,
|
||||
"MinimumAngle": -30,
|
||||
"MaximumAngle": 5
|
||||
}
|
||||
]
|
||||
@ -128,4 +128,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
2
LLib
2
LLib
@ -1 +1 @@
|
||||
Subproject commit 9db9f95b8cd3f36262b5b4b14f12b7331d3c7279
|
||||
Subproject commit 43c3dba112c202e2d0ff1a6909020c2b83e20dc3
|
@ -155,6 +155,10 @@ public static class RoslynShortcuts
|
||||
.AsSyntaxNodeOrToken(),
|
||||
Assignment(nameof(DialogueChoice.Answer), dialogueChoice.Answer, emptyChoice.Answer)
|
||||
.AsSyntaxNodeOrToken(),
|
||||
Assignment(nameof(DialogueChoice.AnswerIsRegularExpression),
|
||||
dialogueChoice.AnswerIsRegularExpression,
|
||||
emptyChoice.AnswerIsRegularExpression)
|
||||
.AsSyntaxNodeOrToken(),
|
||||
Assignment(nameof(DialogueChoice.DataId), dialogueChoice.DataId, emptyChoice.DataId)
|
||||
.AsSyntaxNodeOrToken()))));
|
||||
}
|
||||
@ -359,6 +363,9 @@ public static class RoslynShortcuts
|
||||
.AsSyntaxNodeOrToken(),
|
||||
Assignment(nameof(GatheredItem.Collectability), gatheredItem.Collectability,
|
||||
emptyItem.Collectability)
|
||||
.AsSyntaxNodeOrToken(),
|
||||
Assignment(nameof(GatheredItem.ClassJob), gatheredItem.ClassJob,
|
||||
emptyItem.ClassJob)
|
||||
.AsSyntaxNodeOrToken()))));
|
||||
}
|
||||
else if (value is GatheringNodeGroup nodeGroup)
|
||||
|
@ -25,7 +25,33 @@
|
||||
},
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 478,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/003/CtsSfsCharacter1_00386",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER1_00386_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER1_00386_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/003/CtsSfsCharacter1_00386",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER1_00386_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER1_00386_TOPMENU_000_003"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -15,7 +15,33 @@
|
||||
"TerritoryId": 478,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Idyllshire"
|
||||
"AetheryteShortcut": "Idyllshire",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/005/CtsSfsCharacter4_00541",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER4_00541_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER4_00541_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/005/CtsSfsCharacter4_00541",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER4_00541_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER4_00541_TOPMENU_000_004"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -15,7 +15,33 @@
|
||||
"TerritoryId": 613,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Ruby Sea - Tamamizu"
|
||||
"AetheryteShortcut": "Ruby Sea - Tamamizu",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/004/CtsSfsCharacter3_00481",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER3_00481_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER3_00481_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 613,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/004/CtsSfsCharacter3_00481",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER3_00481_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER3_00481_TOPMENU_000_004"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -15,7 +15,33 @@
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Rhalgr's Reach"
|
||||
"AetheryteShortcut": "Rhalgr's Reach",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/004/CtsSfsCharacter2_00434",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER2_00434_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER2_00434_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/004/CtsSfsCharacter2_00434",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER2_00434_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER2_00434_TOPMENU_000_003"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -18,6 +18,32 @@
|
||||
"AethernetShortcut": [
|
||||
"[Ishgard] Aetheryte Plaza",
|
||||
"[Ishgard] Firmament"
|
||||
],
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/007/CtsSfsCharacter7_00710",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER7_00710_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER7_00710_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/007/CtsSfsCharacter7_00710",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER7_00710_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER7_00710_TOPMENU_000_004"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -18,6 +18,32 @@
|
||||
"AethernetShortcut": [
|
||||
"[Ishgard] Aetheryte Plaza",
|
||||
"[Ishgard] Firmament"
|
||||
],
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/006/CtsSfsCharacter6_00674",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER6_00674_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER6_00674_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/006/CtsSfsCharacter6_00674",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER6_00674_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER6_00674_TOPMENU_000_003"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -15,7 +15,33 @@
|
||||
"TerritoryId": 820,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Eulmore"
|
||||
"AetheryteShortcut": "Eulmore",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/006/CtsSfsCharacter5_00640",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER5_00640_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER5_00640_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/006/CtsSfsCharacter5_00640",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER5_00640_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER5_00640_TOPMENU_000_004"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -19,6 +19,32 @@
|
||||
"AethernetShortcut": [
|
||||
"[Old Sharlayan] Aetheryte Plaza",
|
||||
"[Old Sharlayan] The Leveilleur Estate"
|
||||
],
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/007/CtsSfsCharacter8_00773",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER8_00773_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER8_00773_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/007/CtsSfsCharacter8_00773",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER8_00773_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER8_00773_TOPMENU_000_004"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -16,7 +16,33 @@
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Il Mheg - Lydha Lran",
|
||||
"Fly": true
|
||||
"Fly": true,
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/008/CtsSfsCharacter9_00815",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER9_00815_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER9_00815_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/008/CtsSfsCharacter9_00815",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER9_00815_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER9_00815_TOPMENU_000_004"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -25,7 +25,33 @@
|
||||
"Z": -65.14081
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/008/CtsSfsCharacter10_00842",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER10_00842_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER10_00842_TOPMENU_000_001",
|
||||
"AnswerIsRegularExpression": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 1,
|
||||
"Steps": [
|
||||
{
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "None",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"ExcelSheet": "custom/008/CtsSfsCharacter10_00842",
|
||||
"Prompt": "TEXT_CTSSFSCHARACTER10_00842_TOPMENU_000_000",
|
||||
"Answer": "TEXT_CTSSFSCHARACTER10_00842_TOPMENU_000_004"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -13,14 +13,7 @@
|
||||
"Z": -68.40625
|
||||
},
|
||||
"TerritoryId": 963,
|
||||
"InteractionType": "AcceptQuest",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
"Prompt": "TEXT_AKTKMM103_04753_Q1_000_000",
|
||||
"Answer": "TEXT_AKTKMM103_04753_A1_000_001"
|
||||
}
|
||||
]
|
||||
"InteractionType": "AcceptQuest"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -101,6 +101,7 @@
|
||||
"type": "string",
|
||||
"description": "What to do at the position",
|
||||
"enum": [
|
||||
"None",
|
||||
"Interact",
|
||||
"WalkTo",
|
||||
"AttuneAethernetShard",
|
||||
|
@ -7,6 +7,7 @@ public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>
|
||||
{
|
||||
private static readonly Dictionary<EInteractionType, string> Values = new()
|
||||
{
|
||||
{ EInteractionType.None, "None" },
|
||||
{ EInteractionType.Interact, "Interact" },
|
||||
{ EInteractionType.WalkTo, "WalkTo" },
|
||||
{ EInteractionType.AttuneAethernetShard, "AttuneAethernetShard" },
|
||||
|
@ -16,6 +16,7 @@ public sealed class DialogueChoice
|
||||
|
||||
[JsonConverter(typeof(ExcelRefConverter))]
|
||||
public ExcelRef? Answer { get; set; }
|
||||
public bool AnswerIsRegularExpression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set, only applies when focusing the given target id.
|
||||
|
@ -6,6 +6,7 @@ namespace Questionable.Model.Questing;
|
||||
[JsonConverter(typeof(InteractionTypeConverter))]
|
||||
public enum EInteractionType
|
||||
{
|
||||
None,
|
||||
Interact,
|
||||
WalkTo,
|
||||
AttuneAethernetShard,
|
||||
|
@ -5,4 +5,9 @@ public sealed class GatheredItem
|
||||
public uint ItemId { get; set; }
|
||||
public int ItemCount { get; set; }
|
||||
public ushort Collectability { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Either miner or botanist; null if it is irrelevant (prefers current class/job, then any unlocked ones).
|
||||
/// </summary>
|
||||
public uint? ClassJob { get; set; }
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using FFXIVClientStructs.FFXIV.Common.Math;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.CombatModules;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller;
|
||||
@ -26,7 +27,7 @@ internal sealed class CombatController : IDisposable
|
||||
private readonly IObjectTable _objectTable;
|
||||
private readonly ICondition _condition;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly ILogger<CombatController> _logger;
|
||||
|
||||
private CurrentFight? _currentFight;
|
||||
@ -39,7 +40,7 @@ internal sealed class CombatController : IDisposable
|
||||
IObjectTable objectTable,
|
||||
ICondition condition,
|
||||
IClientState clientState,
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
ILogger<CombatController> logger)
|
||||
{
|
||||
_combatModules = combatModules.ToList();
|
||||
@ -48,7 +49,7 @@ internal sealed class CombatController : IDisposable
|
||||
_objectTable = objectTable;
|
||||
_condition = condition;
|
||||
_clientState = clientState;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_logger = logger;
|
||||
|
||||
_clientState.TerritoryChanged += TerritoryChanged;
|
||||
@ -168,9 +169,9 @@ internal sealed class CombatController : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
if (QuestWorkUtils.HasCompletionFlags(condition.CompletionQuestVariablesFlags) && _currentFight.Data.QuestElementId is QuestId questId)
|
||||
if (QuestWorkUtils.HasCompletionFlags(condition.CompletionQuestVariablesFlags) && _currentFight.Data.ElementId is QuestId questId)
|
||||
{
|
||||
var questWork = _gameFunctions.GetQuestEx(questId);
|
||||
var questWork = _questFunctions.GetQuestEx(questId);
|
||||
if (questWork != null && QuestWorkUtils.MatchesQuestWork(condition.CompletionQuestVariablesFlags,
|
||||
questWork.Value))
|
||||
{
|
||||
@ -303,7 +304,7 @@ internal sealed class CombatController : IDisposable
|
||||
|
||||
public sealed class CombatData
|
||||
{
|
||||
public required ElementId QuestElementId { get; init; }
|
||||
public required ElementId ElementId { get; init; }
|
||||
public required EEnemySpawnType SpawnType { get; init; }
|
||||
public required List<uint> KillEnemyDataIds { get; init; }
|
||||
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using Questionable.Windows;
|
||||
@ -23,7 +24,7 @@ internal sealed class CommandHandler : IDisposable
|
||||
private readonly QuestWindow _questWindow;
|
||||
private readonly QuestSelectionWindow _questSelectionWindow;
|
||||
private readonly ITargetManager _targetManager;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
|
||||
public CommandHandler(
|
||||
ICommandManager commandManager,
|
||||
@ -37,7 +38,7 @@ internal sealed class CommandHandler : IDisposable
|
||||
QuestWindow questWindow,
|
||||
QuestSelectionWindow questSelectionWindow,
|
||||
ITargetManager targetManager,
|
||||
GameFunctions gameFunctions)
|
||||
QuestFunctions questFunctions)
|
||||
{
|
||||
_commandManager = commandManager;
|
||||
_chatGui = chatGui;
|
||||
@ -50,7 +51,7 @@ internal sealed class CommandHandler : IDisposable
|
||||
_questWindow = questWindow;
|
||||
_questSelectionWindow = questSelectionWindow;
|
||||
_targetManager = targetManager;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
|
||||
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
|
||||
{
|
||||
@ -149,7 +150,7 @@ internal sealed class CommandHandler : IDisposable
|
||||
{
|
||||
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null)
|
||||
{
|
||||
if (_gameFunctions.IsQuestLocked(questId))
|
||||
if (_questFunctions.IsQuestLocked(questId))
|
||||
_chatGui.PrintError($"[Questionable] Quest {questId} is locked.");
|
||||
else if (_questRegistry.TryGetQuest(questId, out Quest? quest))
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using LLib.GameData;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.GameStructs;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
@ -21,6 +22,8 @@ internal sealed class ContextMenuController : IDisposable
|
||||
private readonly GatheringData _gatheringData;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestData _questData;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly IChatGui _chatGui;
|
||||
private readonly IClientState _clientState;
|
||||
@ -32,6 +35,8 @@ internal sealed class ContextMenuController : IDisposable
|
||||
GatheringData gatheringData,
|
||||
QuestRegistry questRegistry,
|
||||
QuestData questData,
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
IGameGui gameGui,
|
||||
IChatGui chatGui,
|
||||
IClientState clientState,
|
||||
@ -42,6 +47,8 @@ internal sealed class ContextMenuController : IDisposable
|
||||
_gatheringData = gatheringData;
|
||||
_questRegistry = questRegistry;
|
||||
_questData = questData;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_gameGui = gameGui;
|
||||
_chatGui = chatGui;
|
||||
_clientState = clientState;
|
||||
@ -52,7 +59,7 @@ internal sealed class ContextMenuController : IDisposable
|
||||
|
||||
private void MenuOpened(IMenuOpenedArgs args)
|
||||
{
|
||||
uint itemId = (uint) _gameGui.HoveredItem;
|
||||
uint itemId = (uint)_gameGui.HoveredItem;
|
||||
if (itemId == 0)
|
||||
return;
|
||||
|
||||
@ -62,43 +69,66 @@ internal sealed class ContextMenuController : IDisposable
|
||||
if (itemId >= 500_000)
|
||||
itemId -= 500_000;
|
||||
|
||||
if (!_gatheringData.TryGetGatheringPointId(itemId, (EClassJob)_clientState.LocalPlayer!.ClassJob.Id, out _))
|
||||
if (_gatheringData.TryGetCustomDeliveryNpc(itemId, out uint npcId))
|
||||
{
|
||||
AddContextMenuEntry(args, itemId, npcId, EClassJob.Miner, "Mine");
|
||||
AddContextMenuEntry(args, itemId, npcId, EClassJob.Botanist, "Harvest");
|
||||
}
|
||||
}
|
||||
|
||||
private void AddContextMenuEntry(IMenuOpenedArgs args, uint itemId, uint npcId, EClassJob classJob, string verb)
|
||||
{
|
||||
EClassJob currentClassJob = (EClassJob)_clientState.LocalPlayer!.ClassJob.Id;
|
||||
if (classJob != currentClassJob && currentClassJob is EClassJob.Miner or EClassJob.Botanist)
|
||||
return;
|
||||
|
||||
if (!_gatheringData.TryGetGatheringPointId(itemId, classJob, out _))
|
||||
{
|
||||
_logger.LogInformation("No gathering point found for current job.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gatheringData.TryGetCustomDeliveryNpc(itemId, out uint npcId))
|
||||
ushort collectability = _gatheringData.GetRecommendedCollectability(itemId);
|
||||
int quantityToGather = collectability > 0 ? 6 : int.MaxValue;
|
||||
if (collectability == 0)
|
||||
return;
|
||||
|
||||
unsafe
|
||||
{
|
||||
ushort collectability = _gatheringData.GetRecommendedCollectability(itemId);
|
||||
int quantityToGather = collectability > 0 ? 6 : int.MaxValue;
|
||||
if (collectability == 0)
|
||||
return;
|
||||
|
||||
unsafe
|
||||
var agentSatisfactionSupply = AgentSatisfactionSupply.Instance();
|
||||
if (agentSatisfactionSupply->IsAgentActive())
|
||||
{
|
||||
var agentSatisfactionSupply = AgentSatisfactionSupply.Instance();
|
||||
if (agentSatisfactionSupply->IsAgentActive())
|
||||
{
|
||||
quantityToGather = Math.Min(agentSatisfactionSupply->RemainingAllowances,
|
||||
((AgentSatisfactionSupply2*)agentSatisfactionSupply)->TurnInsToNextRank);
|
||||
}
|
||||
quantityToGather = Math.Min(agentSatisfactionSupply->RemainingAllowances,
|
||||
((AgentSatisfactionSupply2*)agentSatisfactionSupply)->TurnInsToNextRank);
|
||||
}
|
||||
|
||||
args.AddMenuItem(new MenuItem
|
||||
{
|
||||
Prefix = SeIconChar.Hyadelyn,
|
||||
PrefixColor = 52,
|
||||
Name = "Gather with Questionable",
|
||||
OnClicked = _ => StartGathering(npcId, itemId, quantityToGather, collectability),
|
||||
IsEnabled = quantityToGather > 0,
|
||||
});
|
||||
}
|
||||
|
||||
string lockedReasonn = string.Empty;
|
||||
if (!_questFunctions.IsClassJobUnlocked(classJob))
|
||||
lockedReasonn = $"{classJob} not unlocked";
|
||||
else if (quantityToGather == 0)
|
||||
lockedReasonn = "No allowances";
|
||||
else if (_gameFunctions.IsOccupied())
|
||||
lockedReasonn = "Can't be used while interacting";
|
||||
|
||||
string name = $"{verb} with Questionable";
|
||||
if (!string.IsNullOrEmpty(lockedReasonn))
|
||||
name += $" ({lockedReasonn})";
|
||||
|
||||
args.AddMenuItem(new MenuItem
|
||||
{
|
||||
Prefix = SeIconChar.Hyadelyn,
|
||||
PrefixColor = 52,
|
||||
Name = name,
|
||||
OnClicked = _ => StartGathering(npcId, itemId, quantityToGather, collectability, classJob),
|
||||
IsEnabled = string.IsNullOrEmpty(lockedReasonn),
|
||||
});
|
||||
}
|
||||
|
||||
private void StartGathering(uint npcId, uint itemId, int quantity, ushort collectability)
|
||||
private void StartGathering(uint npcId, uint itemId, int quantity, ushort collectability, EClassJob classJob)
|
||||
{
|
||||
var info = (SatisfactionSupplyInfo)_questData.GetAllByIssuerDataId(npcId).Single(x => x is SatisfactionSupplyInfo);
|
||||
var info = (SatisfactionSupplyInfo)_questData.GetAllByIssuerDataId(npcId)
|
||||
.Single(x => x is SatisfactionSupplyInfo);
|
||||
if (_questRegistry.TryGetQuest(info.QuestId, out Quest? quest))
|
||||
{
|
||||
var step = quest.FindSequence(0)!.FindStep(0)!;
|
||||
@ -108,7 +138,8 @@ internal sealed class ContextMenuController : IDisposable
|
||||
{
|
||||
ItemId = itemId,
|
||||
ItemCount = quantity,
|
||||
Collectability = collectability
|
||||
Collectability = collectability,
|
||||
ClassJob = (uint)classJob,
|
||||
}
|
||||
];
|
||||
_questController.SetGatheringQuest(quest);
|
||||
|
@ -14,6 +14,8 @@ using LLib.GameUI;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using Quest = Questionable.Model.Quest;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
@ -25,6 +27,8 @@ internal sealed class GameUiController : IDisposable
|
||||
private readonly IAddonLifecycle _addonLifecycle;
|
||||
private readonly IDataManager _dataManager;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly ExcelFunctions _excelFunctions;
|
||||
private readonly QuestController _questController;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestData _questData;
|
||||
@ -33,13 +37,24 @@ internal sealed class GameUiController : IDisposable
|
||||
private readonly ILogger<GameUiController> _logger;
|
||||
private readonly Regex _returnRegex;
|
||||
|
||||
public GameUiController(IAddonLifecycle addonLifecycle, IDataManager dataManager, GameFunctions gameFunctions,
|
||||
QuestController questController, QuestRegistry questRegistry, QuestData questData, IGameGui gameGui,
|
||||
ITargetManager targetManager, IPluginLog pluginLog, ILogger<GameUiController> logger)
|
||||
public GameUiController(
|
||||
IAddonLifecycle addonLifecycle,
|
||||
IDataManager dataManager,
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
ExcelFunctions excelFunctions,
|
||||
QuestController questController,
|
||||
QuestRegistry questRegistry,
|
||||
QuestData questData,
|
||||
IGameGui gameGui,
|
||||
ITargetManager targetManager,
|
||||
IPluginLog pluginLog, ILogger<GameUiController> logger)
|
||||
{
|
||||
_addonLifecycle = addonLifecycle;
|
||||
_dataManager = dataManager;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_excelFunctions = excelFunctions;
|
||||
_questController = questController;
|
||||
_questRegistry = questRegistry;
|
||||
_questData = questData;
|
||||
@ -188,7 +203,7 @@ internal sealed class GameUiController : IDisposable
|
||||
{
|
||||
// it is possible for this to be a quest selection
|
||||
string questName = quest.Info.Name;
|
||||
int questSelection = answers.FindIndex(x => GameStringEquals(questName, x));
|
||||
int questSelection = answers.FindIndex(x => GameFunctions.GameStringEquals(questName, x));
|
||||
if (questSelection >= 0)
|
||||
{
|
||||
addonSelectIconString->AtkUnitBase.FireCallbackInt(questSelection);
|
||||
@ -210,7 +225,7 @@ internal sealed class GameUiController : IDisposable
|
||||
private int? HandleListChoice(string? actualPrompt, List<string?> answers, bool checkAllSteps)
|
||||
{
|
||||
List<DialogueChoiceInfo> dialogueChoices = [];
|
||||
var currentQuest = _questController.SimulatedQuest ?? _questController.StartedQuest;
|
||||
var currentQuest = _questController.SimulatedQuest ?? _questController.GatheringQuest ?? _questController.StartedQuest;
|
||||
if (currentQuest != null)
|
||||
{
|
||||
var quest = currentQuest.Quest;
|
||||
@ -260,9 +275,9 @@ internal sealed class GameUiController : IDisposable
|
||||
var target = _targetManager.Target;
|
||||
if (target != null)
|
||||
{
|
||||
foreach (var questInfo in _questData.GetAllByIssuerDataId(target.DataId))
|
||||
foreach (var questInfo in _questData.GetAllByIssuerDataId(target.DataId).Where(x => x.QuestId is QuestId))
|
||||
{
|
||||
if (_gameFunctions.IsReadyToAcceptQuest(questInfo.QuestId) &&
|
||||
if (_questFunctions.IsReadyToAcceptQuest(questInfo.QuestId) &&
|
||||
_questRegistry.TryGetQuest(questInfo.QuestId, out Quest? knownQuest))
|
||||
{
|
||||
var questChoices = knownQuest.FindSequence(0)?.Steps
|
||||
@ -300,8 +315,10 @@ internal sealed class GameUiController : IDisposable
|
||||
continue;
|
||||
}
|
||||
|
||||
string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
|
||||
string? excelAnswer = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer);
|
||||
string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt, false)
|
||||
?.GetString();
|
||||
StringOrRegex? excelAnswer = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer,
|
||||
dialogueChoice.AnswerIsRegularExpression);
|
||||
|
||||
if (actualPrompt == null && !string.IsNullOrEmpty(excelPrompt))
|
||||
{
|
||||
@ -309,7 +326,8 @@ internal sealed class GameUiController : IDisposable
|
||||
continue;
|
||||
}
|
||||
|
||||
if (actualPrompt != null && (excelPrompt == null || !GameStringEquals(actualPrompt, excelPrompt)))
|
||||
if (actualPrompt != null &&
|
||||
(excelPrompt == null || !GameFunctions.GameStringEquals(actualPrompt, excelPrompt)))
|
||||
{
|
||||
_logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}, actualPrompt: {ActualPrompt}",
|
||||
excelPrompt, actualPrompt);
|
||||
@ -320,10 +338,22 @@ internal sealed class GameUiController : IDisposable
|
||||
{
|
||||
_logger.LogTrace("Checking if {ActualAnswer} == {ExpectedAnswer}",
|
||||
answers[i], excelAnswer);
|
||||
if (GameStringEquals(answers[i], excelAnswer))
|
||||
if (IsMatch(answers[i], excelAnswer))
|
||||
{
|
||||
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}'",
|
||||
i, answers[i], actualPrompt);
|
||||
|
||||
// ensure we only open the dialog once
|
||||
if (quest.Id is SatisfactionSupplyNpcId)
|
||||
{
|
||||
if (_questController.GatheringQuest == null ||
|
||||
_questController.GatheringQuest.Sequence == 255)
|
||||
return null;
|
||||
|
||||
_questController.GatheringQuest.SetSequence(1);
|
||||
_questController.ExecuteNextStep(QuestController.EAutomationType.CurrentQuestOnly);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@ -333,13 +363,24 @@ internal sealed class GameUiController : IDisposable
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsMatch(string? actualAnswer, StringOrRegex? expectedAnswer)
|
||||
{
|
||||
if (actualAnswer == null && expectedAnswer == null)
|
||||
return true;
|
||||
|
||||
if (actualAnswer == null || expectedAnswer == null)
|
||||
return false;
|
||||
|
||||
return expectedAnswer.IsMatch(actualAnswer);
|
||||
}
|
||||
|
||||
private int? HandleInstanceListChoice(string? actualPrompt)
|
||||
{
|
||||
if (!_questController.IsRunning)
|
||||
return null;
|
||||
|
||||
string? expectedPrompt = _gameFunctions.GetDialogueTextByRowId("Addon", 2090);
|
||||
if (GameStringEquals(actualPrompt, expectedPrompt))
|
||||
string? expectedPrompt = _excelFunctions.GetDialogueTextByRowId("Addon", 2090, false).GetString();
|
||||
if (GameFunctions.GameStringEquals(actualPrompt, expectedPrompt))
|
||||
{
|
||||
_logger.LogInformation("Selecting no prefered instance as answer for '{Prompt}'", actualPrompt);
|
||||
return 0; // any instance
|
||||
@ -419,8 +460,9 @@ internal sealed class GameUiController : IDisposable
|
||||
continue;
|
||||
}
|
||||
|
||||
string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
|
||||
if (excelPrompt == null || !GameStringEquals(actualPrompt, excelPrompt))
|
||||
string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt, false)
|
||||
?.GetString();
|
||||
if (excelPrompt == null || !GameFunctions.GameStringEquals(actualPrompt, excelPrompt))
|
||||
{
|
||||
_logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}, actualPrompt: {ActualPrompt}",
|
||||
excelPrompt, actualPrompt);
|
||||
@ -506,13 +548,13 @@ internal sealed class GameUiController : IDisposable
|
||||
string? excelName = entry.Name?.ToString();
|
||||
string? excelQuestion = entry.Question?.ToString();
|
||||
|
||||
if (excelQuestion != null && GameStringEquals(excelQuestion, actualPrompt))
|
||||
if (excelQuestion != null && GameFunctions.GameStringEquals(excelQuestion, actualPrompt))
|
||||
{
|
||||
warpId = entry.RowId;
|
||||
warpText = excelQuestion;
|
||||
return true;
|
||||
}
|
||||
else if (excelName != null && GameStringEquals(excelName, actualPrompt))
|
||||
else if (excelName != null && GameFunctions.GameStringEquals(excelName, actualPrompt))
|
||||
{
|
||||
warpId = entry.RowId;
|
||||
warpText = excelName;
|
||||
@ -642,31 +684,17 @@ internal sealed class GameUiController : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures characters like '-' are handled equally in both strings.
|
||||
/// </summary>
|
||||
public static bool GameStringEquals(string? a, string? b)
|
||||
{
|
||||
if (a == null)
|
||||
return b == null;
|
||||
|
||||
if (b == null)
|
||||
return false;
|
||||
|
||||
return a.ReplaceLineEndings().Replace('\u2013', '-') == b.ReplaceLineEndings().Replace('\u2013', '-');
|
||||
}
|
||||
|
||||
private string? ResolveReference(Quest quest, string? excelSheet, ExcelRef? excelRef)
|
||||
private StringOrRegex? ResolveReference(Quest quest, string? excelSheet, ExcelRef? excelRef, bool isRegExp)
|
||||
{
|
||||
if (excelRef == null)
|
||||
return null;
|
||||
|
||||
if (excelRef.Type == ExcelRef.EType.Key)
|
||||
return _gameFunctions.GetDialogueText(quest, excelSheet, excelRef.AsKey());
|
||||
return _excelFunctions.GetDialogueText(quest, excelSheet, excelRef.AsKey(), isRegExp);
|
||||
else if (excelRef.Type == ExcelRef.EType.RowId)
|
||||
return _gameFunctions.GetDialogueTextByRowId(excelSheet, excelRef.AsRowId());
|
||||
return _excelFunctions.GetDialogueTextByRowId(excelSheet, excelRef.AsRowId(), isRegExp);
|
||||
else if (excelRef.Type == ExcelRef.EType.RawString)
|
||||
return excelRef.AsRawString();
|
||||
return new StringOrRegex(excelRef.AsRawString());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using Questionable.Controller.Steps.Gathering;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.GatheringPaths;
|
||||
using Questionable.Model.Gathering;
|
||||
|
||||
|
@ -17,6 +17,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.NavigationOverrides;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Common.Converter;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
|
||||
namespace Questionable.Controller;
|
||||
|
@ -5,10 +5,12 @@ using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
@ -18,6 +20,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
{
|
||||
private readonly IClientState _clientState;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly MovementController _movementController;
|
||||
private readonly CombatController _combatController;
|
||||
private readonly GatheringController _gatheringController;
|
||||
@ -46,6 +49,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
public QuestController(
|
||||
IClientState clientState,
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
MovementController movementController,
|
||||
CombatController combatController,
|
||||
GatheringController gatheringController,
|
||||
@ -61,6 +65,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
{
|
||||
_clientState = clientState;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_movementController = movementController;
|
||||
_combatController = combatController;
|
||||
_gatheringController = gatheringController;
|
||||
@ -78,7 +83,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
{
|
||||
if (_simulatedQuest != null)
|
||||
return (_simulatedQuest, ECurrentQuestType.Simulated);
|
||||
else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
|
||||
else if (_nextQuest != null && _questFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
|
||||
return (_nextQuest, ECurrentQuestType.Next);
|
||||
else if (_gatheringQuest != null)
|
||||
return (_gatheringQuest, ECurrentQuestType.Gathering);
|
||||
@ -177,7 +182,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
UpdateCurrentTask();
|
||||
}
|
||||
|
||||
private void UpdateCurrentQuest()
|
||||
private unsafe void UpdateCurrentQuest()
|
||||
{
|
||||
lock (_progressLock)
|
||||
{
|
||||
@ -188,9 +193,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
// if the quest is accepted, we no longer track it
|
||||
bool canUseNextQuest;
|
||||
if (_nextQuest.Quest.Info.IsRepeatable)
|
||||
canUseNextQuest = !_gameFunctions.IsQuestAccepted(_nextQuest.Quest.Id);
|
||||
canUseNextQuest = !_questFunctions.IsQuestAccepted(_nextQuest.Quest.Id);
|
||||
else
|
||||
canUseNextQuest = !_gameFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.Id);
|
||||
canUseNextQuest = !_questFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.Id);
|
||||
|
||||
if (!canUseNextQuest)
|
||||
{
|
||||
@ -207,7 +212,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
currentSequence = _simulatedQuest.Sequence;
|
||||
questToRun = _simulatedQuest;
|
||||
}
|
||||
else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
|
||||
else if (_nextQuest != null && _questFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
|
||||
{
|
||||
questToRun = _nextQuest;
|
||||
currentSequence = _nextQuest.Sequence; // by definition, this should always be 0
|
||||
@ -226,11 +231,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
_taskQueue.Count == 0 &&
|
||||
_automationType == EAutomationType.Automatic)
|
||||
ExecuteNextStep(_automationType);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
(ElementId? currentQuestId, currentSequence) = _gameFunctions.GetCurrentQuest();
|
||||
(ElementId? currentQuestId, currentSequence) = _questFunctions.GetCurrentQuest();
|
||||
if (currentQuestId == null || currentQuestId.Value == 0)
|
||||
{
|
||||
if (_startedQuest != null)
|
||||
@ -276,7 +280,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gameFunctions.IsOccupied())
|
||||
if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(questToRun.Quest))
|
||||
{
|
||||
DebugState = "Occupied";
|
||||
return;
|
||||
@ -303,7 +307,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
if (questToRun.Sequence != currentSequence)
|
||||
{
|
||||
questToRun.SetSequence(currentSequence);
|
||||
Stop($"New sequence {questToRun == _startedQuest}/{_gameFunctions.GetCurrentQuestInternal()}",
|
||||
Stop($"New sequence {questToRun == _startedQuest}/{_questFunctions.GetCurrentQuestInternal()}",
|
||||
continueIfAutomatic: true);
|
||||
}
|
||||
|
||||
@ -455,7 +459,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
|
||||
protected override void UpdateCurrentTask()
|
||||
{
|
||||
if (_gameFunctions.IsOccupied())
|
||||
if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(CurrentQuest?.Quest))
|
||||
return;
|
||||
|
||||
base.UpdateCurrentTask();
|
||||
@ -469,7 +473,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
|
||||
protected override void OnNextStep(ILastTask task)
|
||||
{
|
||||
IncreaseStepCount(task.QuestElementId, task.Sequence, true);
|
||||
IncreaseStepCount(task.ElementId, task.Sequence, true);
|
||||
}
|
||||
|
||||
public void ExecuteNextStep(EAutomationType automatic)
|
||||
@ -484,7 +488,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
if (CurrentQuest == null || seq == null || step == null)
|
||||
{
|
||||
if (CurrentQuestDetails?.Progress.Quest.Id is SatisfactionSupplyNpcId &&
|
||||
CurrentQuestDetails?.Progress.Sequence == 0 &&
|
||||
CurrentQuestDetails?.Progress.Sequence == 1 &&
|
||||
CurrentQuestDetails?.Progress.Step == 255 &&
|
||||
CurrentQuestDetails?.Type == ECurrentQuestType.Gathering)
|
||||
{
|
||||
@ -590,7 +594,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
}
|
||||
}
|
||||
|
||||
public void Skip(ElementId questQuestElementId, byte currentQuestSequence)
|
||||
public void Skip(ElementId elementId, byte currentQuestSequence)
|
||||
{
|
||||
lock (_progressLock)
|
||||
{
|
||||
@ -609,13 +613,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
if (_taskQueue.Count == 0)
|
||||
{
|
||||
Stop("Skip");
|
||||
IncreaseStepCount(questQuestElementId, currentQuestSequence);
|
||||
IncreaseStepCount(elementId, currentQuestSequence);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop("SkipNx");
|
||||
IncreaseStepCount(questQuestElementId, currentQuestSequence);
|
||||
IncreaseStepCount(elementId, currentQuestSequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -657,7 +661,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
foreach (var id in priorityQuests)
|
||||
{
|
||||
var questId = new QuestId(id);
|
||||
if (_gameFunctions.IsReadyToAcceptQuest(questId) && _questRegistry.TryGetQuest(questId, out var quest))
|
||||
if (_questFunctions.IsReadyToAcceptQuest(questId) && _questRegistry.TryGetQuest(questId, out var quest))
|
||||
{
|
||||
SetNextQuest(quest);
|
||||
_chatGui.Print(
|
||||
|
@ -5,6 +5,7 @@ using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
|
||||
namespace Questionable.Controller.Steps.Common;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
@ -26,32 +27,32 @@ internal static class NextQuest
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, GameFunctions gameFunctions, ILogger<SetQuest> logger) : ITask
|
||||
internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger<SetQuest> logger) : ITask
|
||||
{
|
||||
public ElementId NextQuestElementId { get; set; } = null!;
|
||||
public ElementId CurrentQuestElementId { get; set; } = null!;
|
||||
public ElementId NextQuestId { get; set; } = null!;
|
||||
public ElementId CurrentQuestId { get; set; } = null!;
|
||||
|
||||
public ITask With(ElementId nextQuestElementId, ElementId currentQuestElementId)
|
||||
public ITask With(ElementId nextQuestId, ElementId currentQuestId)
|
||||
{
|
||||
NextQuestElementId = nextQuestElementId;
|
||||
CurrentQuestElementId = currentQuestElementId;
|
||||
NextQuestId = nextQuestId;
|
||||
CurrentQuestId = currentQuestId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool Start()
|
||||
{
|
||||
if (gameFunctions.IsQuestLocked(NextQuestElementId, CurrentQuestElementId))
|
||||
if (questFunctions.IsQuestLocked(NextQuestId, CurrentQuestId))
|
||||
{
|
||||
logger.LogInformation("Can't set next quest to {QuestId}, quest is locked", NextQuestElementId);
|
||||
logger.LogInformation("Can't set next quest to {QuestId}, quest is locked", NextQuestId);
|
||||
}
|
||||
else if (questRegistry.TryGetQuest(NextQuestElementId, out Quest? quest))
|
||||
else if (questRegistry.TryGetQuest(NextQuestId, out Quest? quest))
|
||||
{
|
||||
logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", NextQuestElementId, quest.Info.Name);
|
||||
logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", NextQuestId, quest.Info.Name);
|
||||
questController.SetNextQuest(quest);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation("Next quest with id {QuestId} not found", NextQuestElementId);
|
||||
logger.LogInformation("Next quest with id {QuestId} not found", NextQuestId);
|
||||
questController.SetNextQuest(null);
|
||||
}
|
||||
|
||||
@ -60,6 +61,6 @@ internal static class NextQuest
|
||||
|
||||
public ETaskResult Update() => ETaskResult.TaskComplete;
|
||||
|
||||
public override string ToString() => $"SetNextQuest({NextQuestElementId})";
|
||||
public override string ToString() => $"SetNextQuest({NextQuestId})";
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
|
||||
namespace Questionable.Controller.Steps.Common;
|
||||
|
||||
|
@ -5,6 +5,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib.GameData;
|
||||
using LLib.GameUI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model.Gathering;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
|
@ -8,6 +8,7 @@ using GatheringPathRenderer;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model.Gathering;
|
||||
|
||||
namespace Questionable.Controller.Steps.Gathering;
|
||||
|
83
Questionable/Controller/Steps/Gathering/TurnInDelivery.cs
Normal file
83
Questionable/Controller/Steps/Gathering/TurnInDelivery.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib.GameUI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
namespace Questionable.Controller.Steps.Gathering;
|
||||
|
||||
internal static class TurnInDelivery
|
||||
{
|
||||
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
|
||||
{
|
||||
public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (quest.Id is not SatisfactionSupplyNpcId || sequence.Sequence != 1)
|
||||
return null;
|
||||
|
||||
return serviceProvider.GetRequiredService<SatisfactionSupplyTurnIn>();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : ITask
|
||||
{
|
||||
private ushort? _remainingAllowances;
|
||||
|
||||
public bool Start() => true;
|
||||
|
||||
public unsafe ETaskResult Update()
|
||||
{
|
||||
AgentSatisfactionSupply* agentSatisfactionSupply = AgentSatisfactionSupply.Instance();
|
||||
if (agentSatisfactionSupply == null || !agentSatisfactionSupply->IsAgentActive())
|
||||
return _remainingAllowances == null ? ETaskResult.StillRunning : ETaskResult.TaskComplete;
|
||||
|
||||
var addonId = agentSatisfactionSupply->GetAddonId();
|
||||
if (addonId == 0)
|
||||
return _remainingAllowances == null ? ETaskResult.StillRunning : ETaskResult.TaskComplete;
|
||||
|
||||
AtkUnitBase* addon = LAddon.GetAddonById(addonId);
|
||||
if (addon == null || !LAddon.IsAddonReady(addon))
|
||||
return ETaskResult.StillRunning;
|
||||
|
||||
ushort remainingAllowances = agentSatisfactionSupply->RemainingAllowances;
|
||||
if (remainingAllowances == 0)
|
||||
{
|
||||
logger.LogInformation("No remaining weekly allowances");
|
||||
addon->FireCallbackInt(0);
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
if (InventoryManager.Instance()->GetInventoryItemCount(agentSatisfactionSupply->Items[1].Id,
|
||||
minCollectability: (short)agentSatisfactionSupply->Items[1].Collectability1) == 0)
|
||||
{
|
||||
logger.LogInformation("Inventory has no {ItemId}", agentSatisfactionSupply->Items[1].Id);
|
||||
addon->FireCallbackInt(0);
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
// we should at least wait until we have less allowances
|
||||
if (_remainingAllowances == remainingAllowances)
|
||||
return ETaskResult.StillRunning;
|
||||
|
||||
// try turning it in...
|
||||
logger.LogInformation("Attempting turn-in (remaining allowances: {RemainingAllowances})",
|
||||
remainingAllowances);
|
||||
_remainingAllowances = remainingAllowances;
|
||||
|
||||
var pickGatheringItem = stackalloc AtkValue[]
|
||||
{
|
||||
new() { Type = ValueType.Int, Int = 1 },
|
||||
new() { Type = ValueType.Int, Int = 1 }
|
||||
};
|
||||
addon->FireCallback(2, pickGatheringItem);
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
public override string ToString() => "WeeklyDeliveryTurnIn";
|
||||
}
|
||||
}
|
@ -4,6 +4,6 @@ namespace Questionable.Controller.Steps;
|
||||
|
||||
internal interface ILastTask : ITask
|
||||
{
|
||||
public ElementId QuestElementId { get; }
|
||||
public ElementId ElementId { get; }
|
||||
public int Sequence { get; }
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
|
@ -3,6 +3,7 @@ using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
@ -78,19 +79,19 @@ internal static class Combat
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HandleCombat(CombatController combatController, GameFunctions gameFunctions) : ITask
|
||||
internal sealed class HandleCombat(CombatController combatController, QuestFunctions questFunctions) : ITask
|
||||
{
|
||||
private bool _isLastStep;
|
||||
private CombatController.CombatData _combatData = null!;
|
||||
private IList<QuestWorkValue?> _completionQuestVariableFlags = null!;
|
||||
|
||||
public ITask With(ElementId questElementId, bool isLastStep, EEnemySpawnType enemySpawnType, IList<uint> killEnemyDataIds,
|
||||
public ITask With(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType, IList<uint> killEnemyDataIds,
|
||||
IList<QuestWorkValue?> completionQuestVariablesFlags, IList<ComplexCombatData> complexCombatData)
|
||||
{
|
||||
_isLastStep = isLastStep;
|
||||
_combatData = new CombatController.CombatData
|
||||
{
|
||||
QuestElementId = questElementId,
|
||||
ElementId = elementId,
|
||||
SpawnType = enemySpawnType,
|
||||
KillEnemyDataIds = killEnemyDataIds.ToList(),
|
||||
ComplexCombatDatas = complexCombatData.ToList(),
|
||||
@ -107,9 +108,9 @@ internal static class Combat
|
||||
return ETaskResult.StillRunning;
|
||||
|
||||
// if our quest step has any completion flags, we need to check if they are set
|
||||
if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags) && _combatData.QuestElementId is QuestId questId)
|
||||
if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags) && _combatData.ElementId is QuestId questId)
|
||||
{
|
||||
var questWork = gameFunctions.GetQuestEx(questId);
|
||||
var questWork = questFunctions.GetQuestEx(questId);
|
||||
if (questWork == null)
|
||||
return ETaskResult.StillRunning;
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
|
@ -7,6 +7,7 @@ using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
@ -9,7 +10,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class Say
|
||||
{
|
||||
internal sealed class Factory(IServiceProvider serviceProvider, GameFunctions gameFunctions) : ITaskFactory
|
||||
internal sealed class Factory(IServiceProvider serviceProvider, ExcelFunctions excelFunctions) : ITaskFactory
|
||||
{
|
||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
@ -20,7 +21,7 @@ internal static class Say
|
||||
ArgumentNullException.ThrowIfNull(step.ChatMessage);
|
||||
|
||||
string? excelString =
|
||||
gameFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key);
|
||||
excelFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key, false).GetString();
|
||||
ArgumentNullException.ThrowIfNull(excelString);
|
||||
|
||||
var unmount = serviceProvider.GetRequiredService<UnmountTask>();
|
||||
|
@ -12,6 +12,7 @@ using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
@ -103,7 +104,7 @@ internal static class UseItem
|
||||
yield return serviceProvider.GetRequiredService<AetheryteShortcut.UseAetheryteShortcut>()
|
||||
.With(null, EAetheryteLocation.Limsa, territoryId);
|
||||
yield return serviceProvider.GetRequiredService<AethernetShortcut.UseAethernetShortcut>()
|
||||
.With(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist, null);
|
||||
.With(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
|
||||
yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
|
||||
yield return serviceProvider.GetRequiredService<Move.MoveInternal>()
|
||||
.With(territoryId, destination, dataId: npcId, sprint: false);
|
||||
@ -112,7 +113,7 @@ internal static class UseItem
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class UseItemBase(GameFunctions gameFunctions, ICondition condition, ILogger logger) : ITask
|
||||
internal abstract class UseItemBase(QuestFunctions questFunctions, ICondition condition, ILogger logger) : ITask
|
||||
{
|
||||
private bool _usedItem;
|
||||
private DateTime _continueAt;
|
||||
@ -144,7 +145,7 @@ internal static class UseItem
|
||||
{
|
||||
if (QuestId is QuestId questId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
||||
{
|
||||
QuestWork? questWork = gameFunctions.GetQuestEx(questId);
|
||||
QuestWork? questWork = questFunctions.GetQuestEx(questId);
|
||||
if (questWork != null &&
|
||||
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value))
|
||||
return ETaskResult.TaskComplete;
|
||||
@ -196,11 +197,9 @@ internal static class UseItem
|
||||
}
|
||||
|
||||
|
||||
internal sealed class UseOnGround(GameFunctions gameFunctions, ICondition condition, ILogger<UseOnGround> logger)
|
||||
: UseItemBase(gameFunctions, condition, logger)
|
||||
internal sealed class UseOnGround(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<UseOnGround> logger)
|
||||
: UseItemBase(questFunctions, condition, logger)
|
||||
{
|
||||
private readonly GameFunctions _gameFunctions = gameFunctions;
|
||||
|
||||
public uint DataId { get; set; }
|
||||
|
||||
public ITask With(ElementId? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
||||
@ -212,19 +211,18 @@ internal static class UseItem
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override bool UseItem() => _gameFunctions.UseItemOnGround(DataId, ItemId);
|
||||
protected override bool UseItem() => gameFunctions.UseItemOnGround(DataId, ItemId);
|
||||
|
||||
public override string ToString() => $"UseItem({ItemId} on ground at {DataId})";
|
||||
}
|
||||
|
||||
internal sealed class UseOnPosition(
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
ICondition condition,
|
||||
ILogger<UseOnPosition> logger)
|
||||
: UseItemBase(gameFunctions, condition, logger)
|
||||
: UseItemBase(questFunctions, condition, logger)
|
||||
{
|
||||
private readonly GameFunctions _gameFunctions = gameFunctions;
|
||||
|
||||
public Vector3 Position { get; set; }
|
||||
|
||||
public ITask With(ElementId? questId, Vector3 position, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
||||
@ -236,17 +234,15 @@ internal static class UseItem
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override bool UseItem() => _gameFunctions.UseItemOnPosition(Position, ItemId);
|
||||
protected override bool UseItem() => gameFunctions.UseItemOnPosition(Position, ItemId);
|
||||
|
||||
public override string ToString() =>
|
||||
$"UseItem({ItemId} on ground at {Position.ToString("G", CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
|
||||
internal sealed class UseOnObject(GameFunctions gameFunctions, ICondition condition, ILogger<UseOnObject> logger)
|
||||
: UseItemBase(gameFunctions, condition, logger)
|
||||
internal sealed class UseOnObject(QuestFunctions questFunctions, GameFunctions gameFunctions, ICondition condition, ILogger<UseOnObject> logger)
|
||||
: UseItemBase(questFunctions, condition, logger)
|
||||
{
|
||||
private readonly GameFunctions _gameFunctions = gameFunctions;
|
||||
|
||||
public uint DataId { get; set; }
|
||||
|
||||
public ITask With(ElementId? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||
@ -260,16 +256,14 @@ internal static class UseItem
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override bool UseItem() => _gameFunctions.UseItem(DataId, ItemId);
|
||||
protected override bool UseItem() => gameFunctions.UseItem(DataId, ItemId);
|
||||
|
||||
public override string ToString() => $"UseItem({ItemId} on {DataId})";
|
||||
}
|
||||
|
||||
internal sealed class Use(GameFunctions gameFunctions, ICondition condition, ILogger<Use> logger)
|
||||
: UseItemBase(gameFunctions, condition, logger)
|
||||
internal sealed class Use(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<Use> logger)
|
||||
: UseItemBase(questFunctions, condition, logger)
|
||||
{
|
||||
private readonly GameFunctions _gameFunctions = gameFunctions;
|
||||
|
||||
public ITask With(ElementId? questId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
||||
{
|
||||
QuestId = questId;
|
||||
@ -278,7 +272,7 @@ internal static class UseItem
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override bool UseItem() => _gameFunctions.UseItem(ItemId);
|
||||
protected override bool UseItem() => gameFunctions.UseItem(ItemId);
|
||||
|
||||
public override string ToString() => $"UseItem({ItemId})";
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Common.Converter;
|
||||
|
@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
|
@ -28,14 +28,25 @@ internal static class GatheringRequiredItems
|
||||
{
|
||||
foreach (var requiredGatheredItems in step.RequiredGatheredItems)
|
||||
{
|
||||
if (!gatheringData.TryGetGatheringPointId(requiredGatheredItems.ItemId,
|
||||
(EClassJob)clientState.LocalPlayer!.ClassJob.Id, out var gatheringPointId))
|
||||
EClassJob currentClassJob = (EClassJob)clientState.LocalPlayer!.ClassJob.Id;
|
||||
EClassJob classJob = currentClassJob;
|
||||
if (requiredGatheredItems.ClassJob != null)
|
||||
classJob = (EClassJob)requiredGatheredItems.ClassJob.Value;
|
||||
|
||||
if (!gatheringData.TryGetGatheringPointId(requiredGatheredItems.ItemId, classJob,
|
||||
out var gatheringPointId))
|
||||
throw new TaskException($"No gathering point found for item {requiredGatheredItems.ItemId}");
|
||||
|
||||
if (!AssemblyGatheringLocationLoader.GetLocations()
|
||||
.TryGetValue(gatheringPointId, out GatheringRoot? gatheringRoot))
|
||||
throw new TaskException($"No path found for gathering point {gatheringPointId}");
|
||||
|
||||
if (classJob != currentClassJob)
|
||||
{
|
||||
yield return serviceProvider.GetRequiredService<SwitchClassJob>()
|
||||
.With(classJob);
|
||||
}
|
||||
|
||||
if (HasRequiredItems(requiredGatheredItems))
|
||||
continue;
|
||||
|
||||
@ -71,7 +82,8 @@ internal static class GatheringRequiredItems
|
||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||
return inventoryManager != null &&
|
||||
inventoryManager->GetInventoryItemCount(requiredGatheredItems.ItemId,
|
||||
minCollectability: (short)requiredGatheredItems.Collectability) >= requiredGatheredItems.ItemCount;
|
||||
minCollectability: (short)requiredGatheredItems.Collectability) >=
|
||||
requiredGatheredItems.ItemCount;
|
||||
}
|
||||
|
||||
public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
|
@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.NavigationOverrides;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
|
@ -10,6 +10,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
@ -41,17 +42,18 @@ internal static class SkipCondition
|
||||
internal sealed class CheckSkip(
|
||||
ILogger<CheckSkip> logger,
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
IClientState clientState) : ITask
|
||||
{
|
||||
public QuestStep Step { get; set; } = null!;
|
||||
public SkipStepConditions SkipConditions { get; set; } = null!;
|
||||
public ElementId QuestElementId { get; set; } = null!;
|
||||
public ElementId ElementId { get; set; } = null!;
|
||||
|
||||
public ITask With(QuestStep step, SkipStepConditions skipConditions, ElementId questElementId)
|
||||
public ITask With(QuestStep step, SkipStepConditions skipConditions, ElementId elementId)
|
||||
{
|
||||
Step = step;
|
||||
SkipConditions = skipConditions;
|
||||
QuestElementId = questElementId;
|
||||
ElementId = elementId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -95,14 +97,14 @@ internal static class SkipCondition
|
||||
}
|
||||
|
||||
if (SkipConditions.QuestsCompleted.Count > 0 &&
|
||||
SkipConditions.QuestsCompleted.All(gameFunctions.IsQuestComplete))
|
||||
SkipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
|
||||
{
|
||||
logger.LogInformation("Skipping step, all prequisite quests are complete");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SkipConditions.QuestsAccepted.Count > 0 &&
|
||||
SkipConditions.QuestsAccepted.All(gameFunctions.IsQuestAccepted))
|
||||
SkipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
|
||||
{
|
||||
logger.LogInformation("Skipping step, all prequisite quests are accepted");
|
||||
return true;
|
||||
@ -156,9 +158,9 @@ internal static class SkipCondition
|
||||
return true;
|
||||
}
|
||||
|
||||
if (QuestElementId is QuestId questId)
|
||||
if (ElementId is QuestId questId)
|
||||
{
|
||||
QuestWork? questWork = gameFunctions.GetQuestEx(questId);
|
||||
QuestWork? questWork = questFunctions.GetQuestEx(questId);
|
||||
if (QuestWorkUtils.HasCompletionFlags(Step.CompletionQuestVariablesFlags) && questWork != null)
|
||||
{
|
||||
if (QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value))
|
||||
@ -198,13 +200,13 @@ internal static class SkipCondition
|
||||
}
|
||||
}
|
||||
|
||||
if (Step.PickUpQuestId != null && gameFunctions.IsQuestAcceptedOrComplete(Step.PickUpQuestId))
|
||||
if (Step.PickUpQuestId != null && questFunctions.IsQuestAcceptedOrComplete(Step.PickUpQuestId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as we have already picked up the relevant quest");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Step.TurnInQuestId != null && gameFunctions.IsQuestComplete(Step.TurnInQuestId))
|
||||
if (Step.TurnInQuestId != null && questFunctions.IsQuestComplete(Step.TurnInQuestId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as we have already completed the relevant quest");
|
||||
return true;
|
||||
|
44
Questionable/Controller/Steps/Shared/SwitchClassJob.cs
Normal file
44
Questionable/Controller/Steps/Shared/SwitchClassJob.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using LLib.GameData;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
|
||||
namespace Questionable.Controller.Steps.Shared;
|
||||
|
||||
internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayedTask
|
||||
{
|
||||
private EClassJob _classJob;
|
||||
|
||||
public ITask With(EClassJob classJob)
|
||||
{
|
||||
_classJob = classJob;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override unsafe bool StartInternal()
|
||||
{
|
||||
if (clientState.LocalPlayer!.ClassJob.Id == (uint)_classJob)
|
||||
return false;
|
||||
|
||||
var gearsetModule = RaptureGearsetModule.Instance();
|
||||
if (gearsetModule != null)
|
||||
{
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
var gearset = gearsetModule->GetGearset(i);
|
||||
if (gearset->ClassJob == (byte)_classJob)
|
||||
{
|
||||
gearsetModule->EquipGearset(gearset->Id, gearset->BannerIndex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new TaskException($"No gearset found for {_classJob}");
|
||||
}
|
||||
|
||||
protected override ETaskResult UpdateInternal() => ETaskResult.TaskComplete;
|
||||
|
||||
public override string ToString() => $"SwitchJob({_classJob})";
|
||||
}
|
@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
@ -160,7 +161,7 @@ internal static class WaitAtEnd
|
||||
public override string ToString() => "Wait(next step or sequence)";
|
||||
}
|
||||
|
||||
internal sealed class WaitForCompletionFlags(GameFunctions gameFunctions) : ITask
|
||||
internal sealed class WaitForCompletionFlags(QuestFunctions questFunctions) : ITask
|
||||
{
|
||||
public QuestId Quest { get; set; } = null!;
|
||||
public QuestStep Step { get; set; } = null!;
|
||||
@ -178,7 +179,7 @@ internal static class WaitAtEnd
|
||||
|
||||
public ETaskResult Update()
|
||||
{
|
||||
QuestWork? questWork = gameFunctions.GetQuestEx(Quest);
|
||||
QuestWork? questWork = questFunctions.GetQuestEx(Quest);
|
||||
return questWork != null &&
|
||||
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value)
|
||||
? ETaskResult.TaskComplete
|
||||
@ -214,13 +215,13 @@ internal static class WaitAtEnd
|
||||
$"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})";
|
||||
}
|
||||
|
||||
internal sealed class WaitQuestAccepted(GameFunctions gameFunctions) : ITask
|
||||
internal sealed class WaitQuestAccepted(QuestFunctions questFunctions) : ITask
|
||||
{
|
||||
public ElementId QuestElementId { get; set; } = null!;
|
||||
public ElementId ElementId { get; set; } = null!;
|
||||
|
||||
public ITask With(ElementId questElementId)
|
||||
public ITask With(ElementId elementId)
|
||||
{
|
||||
QuestElementId = questElementId;
|
||||
ElementId = elementId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -228,21 +229,21 @@ internal static class WaitAtEnd
|
||||
|
||||
public ETaskResult Update()
|
||||
{
|
||||
return gameFunctions.IsQuestAccepted(QuestElementId)
|
||||
return questFunctions.IsQuestAccepted(ElementId)
|
||||
? ETaskResult.TaskComplete
|
||||
: ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
public override string ToString() => $"WaitQuestAccepted({QuestElementId})";
|
||||
public override string ToString() => $"WaitQuestAccepted({ElementId})";
|
||||
}
|
||||
|
||||
internal sealed class WaitQuestCompleted(GameFunctions gameFunctions) : ITask
|
||||
internal sealed class WaitQuestCompleted(QuestFunctions questFunctions) : ITask
|
||||
{
|
||||
public ElementId QuestElementId { get; set; } = null!;
|
||||
public ElementId ElementId { get; set; } = null!;
|
||||
|
||||
public ITask With(ElementId questElementId)
|
||||
public ITask With(ElementId elementId)
|
||||
{
|
||||
QuestElementId = questElementId;
|
||||
ElementId = elementId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -250,15 +251,15 @@ internal static class WaitAtEnd
|
||||
|
||||
public ETaskResult Update()
|
||||
{
|
||||
return gameFunctions.IsQuestComplete(QuestElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
|
||||
return questFunctions.IsQuestComplete(ElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
public override string ToString() => $"WaitQuestComplete({QuestElementId})";
|
||||
public override string ToString() => $"WaitQuestComplete({ElementId})";
|
||||
}
|
||||
|
||||
internal sealed class NextStep(ElementId questElementId, int sequence) : ILastTask
|
||||
internal sealed class NextStep(ElementId elementId, int sequence) : ILastTask
|
||||
{
|
||||
public ElementId QuestElementId { get; } = questElementId;
|
||||
public ElementId ElementId { get; } = elementId;
|
||||
public int Sequence { get; } = sequence;
|
||||
|
||||
public bool Start() => true;
|
||||
@ -270,7 +271,7 @@ internal static class WaitAtEnd
|
||||
|
||||
internal sealed class EndAutomation : ILastTask
|
||||
{
|
||||
public ElementId QuestElementId => throw new InvalidOperationException();
|
||||
public ElementId ElementId => throw new InvalidOperationException();
|
||||
public int Sequence => throw new InvalidOperationException();
|
||||
|
||||
public bool Start() => true;
|
||||
|
@ -16,7 +16,7 @@ using Lumina.Excel.GeneratedSheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable;
|
||||
namespace Questionable.Functions;
|
||||
|
||||
internal sealed unsafe class ChatFunctions
|
||||
{
|
101
Questionable/Functions/ExcelFunctions.cs
Normal file
101
Questionable/Functions/ExcelFunctions.cs
Normal file
@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using LLib;
|
||||
using Lumina.Excel.CustomSheets;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Model;
|
||||
using Quest = Questionable.Model.Quest;
|
||||
using GimmickYesNo = Lumina.Excel.GeneratedSheets2.GimmickYesNo;
|
||||
|
||||
namespace Questionable.Functions;
|
||||
|
||||
internal sealed class ExcelFunctions
|
||||
{
|
||||
private readonly IDataManager _dataManager;
|
||||
private readonly ILogger<ExcelFunctions> _logger;
|
||||
|
||||
public ExcelFunctions(IDataManager dataManager, ILogger<ExcelFunctions> logger)
|
||||
{
|
||||
_dataManager = dataManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public StringOrRegex GetDialogueText(Quest currentQuest, string? excelSheetName, string key, bool isRegex)
|
||||
{
|
||||
var seString = GetRawDialogueText(currentQuest, excelSheetName, key);
|
||||
if (isRegex)
|
||||
return new StringOrRegex(seString.ToRegex());
|
||||
else
|
||||
return new StringOrRegex(seString?.ToDalamudString().ToString());
|
||||
}
|
||||
|
||||
public SeString? GetRawDialogueText(Quest currentQuest, string? excelSheetName, string key)
|
||||
{
|
||||
if (excelSheetName == null)
|
||||
{
|
||||
var questRow =
|
||||
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.Id.Value +
|
||||
0x10000);
|
||||
if (questRow == null)
|
||||
{
|
||||
_logger.LogError("Could not find quest row for {QuestId}", currentQuest.Id);
|
||||
return null;
|
||||
}
|
||||
|
||||
excelSheetName = $"quest/{(currentQuest.Id.Value / 100):000}/{questRow.Id}";
|
||||
}
|
||||
|
||||
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
|
||||
if (excelSheet == null)
|
||||
{
|
||||
_logger.LogError("Unknown excel sheet '{SheetName}'", excelSheetName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return excelSheet.FirstOrDefault(x => x.Key == key)?.Value;
|
||||
}
|
||||
|
||||
public StringOrRegex GetDialogueTextByRowId(string? excelSheet, uint rowId, bool isRegex)
|
||||
{
|
||||
var seString = GetRawDialogueTextByRowId(excelSheet, rowId);
|
||||
if (isRegex)
|
||||
return new StringOrRegex(seString.ToRegex());
|
||||
else
|
||||
return new StringOrRegex(seString?.ToDalamudString().ToString());
|
||||
}
|
||||
|
||||
public SeString? GetRawDialogueTextByRowId(string? excelSheet, uint rowId)
|
||||
{
|
||||
if (excelSheet == "GimmickYesNo")
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<GimmickYesNo>()!.GetRow(rowId);
|
||||
return questRow?.Unknown0;
|
||||
}
|
||||
else if (excelSheet == "Warp")
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<Warp>()!.GetRow(rowId);
|
||||
return questRow?.Name;
|
||||
}
|
||||
else if (excelSheet is "Addon")
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<Addon>()!.GetRow(rowId);
|
||||
return questRow?.Text;
|
||||
}
|
||||
else if (excelSheet is "EventPathMove")
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<EventPathMove>()!.GetRow(rowId);
|
||||
return questRow?.Unknown10;
|
||||
}
|
||||
else if (excelSheet is "ContentTalk" or null)
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<ContentTalk>()!.GetRow(rowId);
|
||||
return questRow?.Text;
|
||||
}
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(excelSheet), $"Unsupported excel sheet {excelSheet}");
|
||||
}
|
||||
}
|
@ -1,16 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
@ -18,60 +14,51 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib.GameUI;
|
||||
using Lumina.Excel.CustomSheets;
|
||||
using Lumina.Excel.GeneratedSheets2;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
using Action = Lumina.Excel.GeneratedSheets2.Action;
|
||||
using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
|
||||
using ContentFinderCondition = Lumina.Excel.GeneratedSheets.ContentFinderCondition;
|
||||
using ContentTalk = Lumina.Excel.GeneratedSheets.ContentTalk;
|
||||
using EventPathMove = Lumina.Excel.GeneratedSheets.EventPathMove;
|
||||
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
|
||||
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||
using Quest = Questionable.Model.Quest;
|
||||
using TerritoryType = Lumina.Excel.GeneratedSheets.TerritoryType;
|
||||
|
||||
namespace Questionable;
|
||||
namespace Questionable.Functions;
|
||||
|
||||
internal sealed unsafe class GameFunctions
|
||||
{
|
||||
private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet;
|
||||
private readonly ReadOnlyDictionary<uint, ushort> _contentFinderConditionToContentId;
|
||||
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly IDataManager _dataManager;
|
||||
private readonly IObjectTable _objectTable;
|
||||
private readonly ITargetManager _targetManager;
|
||||
private readonly ICondition _condition;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestData _questData;
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly ILogger<GameFunctions> _logger;
|
||||
|
||||
public GameFunctions(IDataManager dataManager,
|
||||
public GameFunctions(
|
||||
QuestFunctions questFunctions,
|
||||
IDataManager dataManager,
|
||||
IObjectTable objectTable,
|
||||
ITargetManager targetManager,
|
||||
ICondition condition,
|
||||
IClientState clientState,
|
||||
QuestRegistry questRegistry,
|
||||
QuestData questData,
|
||||
IGameGui gameGui,
|
||||
Configuration configuration,
|
||||
ILogger<GameFunctions> logger)
|
||||
{
|
||||
_questFunctions = questFunctions;
|
||||
_dataManager = dataManager;
|
||||
_objectTable = objectTable;
|
||||
_targetManager = targetManager;
|
||||
_condition = condition;
|
||||
_clientState = clientState;
|
||||
_questRegistry = questRegistry;
|
||||
_questData = questData;
|
||||
_gameGui = gameGui;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
@ -89,289 +76,6 @@ internal sealed unsafe class GameFunctions
|
||||
|
||||
public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
|
||||
|
||||
public (ElementId? CurrentQuest, byte Sequence) GetCurrentQuest()
|
||||
{
|
||||
var (currentQuest, sequence) = GetCurrentQuestInternal();
|
||||
PlayerState* playerState = PlayerState.Instance();
|
||||
|
||||
if (currentQuest == null || currentQuest.Value == 0)
|
||||
{
|
||||
if (_clientState.TerritoryType == 181) // Starting in Limsa
|
||||
return (new QuestId(107), 0);
|
||||
if (_clientState.TerritoryType == 182) // Starting in Ul'dah
|
||||
return (new QuestId(594), 0);
|
||||
if (_clientState.TerritoryType == 183) // Starting in Gridania
|
||||
return (new QuestId(39), 0);
|
||||
return default;
|
||||
}
|
||||
else if (currentQuest.Value == 681)
|
||||
{
|
||||
// if we have already picked up the GC quest, just return the progress for it
|
||||
if (IsQuestAccepted(currentQuest) || IsQuestComplete(currentQuest))
|
||||
return (currentQuest, sequence);
|
||||
|
||||
// The company you keep...
|
||||
return _configuration.General.GrandCompany switch
|
||||
{
|
||||
GrandCompany.TwinAdder => (new QuestId(680), 0),
|
||||
GrandCompany.Maelstrom => (new QuestId(681), 0),
|
||||
_ => default
|
||||
};
|
||||
}
|
||||
else if (currentQuest.Value == 3856 && !playerState->IsMountUnlocked(1)) // we come in peace
|
||||
{
|
||||
ushort chocoboQuest = (GrandCompany)playerState->GrandCompany switch
|
||||
{
|
||||
GrandCompany.TwinAdder => 700,
|
||||
GrandCompany.Maelstrom => 701,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if (chocoboQuest != 0 && !QuestManager.IsQuestComplete(chocoboQuest))
|
||||
return (new QuestId(chocoboQuest), QuestManager.GetQuestSequence(chocoboQuest));
|
||||
}
|
||||
else if (currentQuest.Value == 801)
|
||||
{
|
||||
// skeletons in her closet, finish 'broadening horizons' to unlock the white wolf gate
|
||||
QuestId broadeningHorizons = new QuestId(802);
|
||||
if (IsQuestAccepted(broadeningHorizons))
|
||||
return (broadeningHorizons, QuestManager.GetQuestSequence(broadeningHorizons.Value));
|
||||
}
|
||||
|
||||
return (currentQuest, sequence);
|
||||
}
|
||||
|
||||
public (ElementId? CurrentQuest, byte Sequence) GetCurrentQuestInternal()
|
||||
{
|
||||
var questManager = QuestManager.Instance();
|
||||
if (questManager != null)
|
||||
{
|
||||
// always prioritize accepting MSQ quests, to make sure we don't turn in one MSQ quest and then go off to do
|
||||
// side quests until the end of time.
|
||||
var msqQuest = GetMainScenarioQuest(questManager);
|
||||
if (msqQuest.CurrentQuest is { Value: not 0 } && _questRegistry.IsKnownQuest(msqQuest.CurrentQuest))
|
||||
return msqQuest;
|
||||
|
||||
// Use the quests in the same order as they're shown in the to-do list, e.g. if the MSQ is the first item,
|
||||
// do the MSQ; if a side quest is the first item do that side quest.
|
||||
//
|
||||
// If no quests are marked as 'priority', accepting a new quest adds it to the top of the list.
|
||||
for (int i = questManager->TrackedQuests.Length - 1; i >= 0; --i)
|
||||
{
|
||||
ElementId currentQuest;
|
||||
var trackedQuest = questManager->TrackedQuests[i];
|
||||
switch (trackedQuest.QuestType)
|
||||
{
|
||||
default:
|
||||
continue;
|
||||
|
||||
case 1: // normal quest
|
||||
currentQuest = new QuestId(questManager->NormalQuests[trackedQuest.Index].QuestId);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_questRegistry.IsKnownQuest(currentQuest))
|
||||
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
||||
}
|
||||
|
||||
// if we know no quest of those currently in the to-do list, just do MSQ
|
||||
return msqQuest;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private (QuestId? CurrentQuest, byte Sequence) GetMainScenarioQuest(QuestManager* questManager)
|
||||
{
|
||||
if (QuestManager.IsQuestComplete(3759)) // Memories Rekindled
|
||||
{
|
||||
AgentInterface* questRedoHud = AgentModule.Instance()->GetAgentByInternalId(AgentId.QuestRedoHud);
|
||||
if (questRedoHud != null && questRedoHud->IsAgentActive())
|
||||
{
|
||||
// there's surely better ways to check this, but the one in the OOB Plugin was even less reliable
|
||||
if (_gameGui.TryGetAddonByName<AtkUnitBase>("QuestRedoHud", out var addon) &&
|
||||
addon->AtkValuesCount == 4 &&
|
||||
// 0 seems to be active,
|
||||
// 1 seems to be paused,
|
||||
// 2 is unknown, but it happens e.g. before the quest 'Alzadaal's Legacy'
|
||||
// 3 seems to be having /ng+ open while active,
|
||||
// 4 seems to be when (a) suspending the chapter, or (b) having turned in a quest
|
||||
addon->AtkValues[0].UInt is 0 or 2 or 3 or 4)
|
||||
{
|
||||
// redoHud+44 is chapter
|
||||
// redoHud+46 is quest
|
||||
ushort questId = MemoryHelper.Read<ushort>((nint)questRedoHud + 46);
|
||||
return (new QuestId(questId), QuestManager.GetQuestSequence(questId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scenarioTree = AgentScenarioTree.Instance();
|
||||
if (scenarioTree == null)
|
||||
return default;
|
||||
|
||||
if (scenarioTree->Data == null)
|
||||
return default;
|
||||
|
||||
QuestId currentQuest = new QuestId(scenarioTree->Data->CurrentScenarioQuest);
|
||||
if (currentQuest.Value == 0)
|
||||
return default;
|
||||
|
||||
// if the MSQ is hidden, we generally ignore it
|
||||
if (IsQuestAccepted(currentQuest) && questManager->GetQuestById(currentQuest.Value)->IsHidden)
|
||||
return default;
|
||||
|
||||
// it can sometimes happen (although this isn't reliably reproducible) that the quest returned here
|
||||
// is one you've just completed.
|
||||
if (!IsReadyToAcceptQuest(currentQuest))
|
||||
return default;
|
||||
|
||||
// if we're not at a high enough level to continue, we also ignore it
|
||||
var currentLevel = _clientState.LocalPlayer?.Level ?? 0;
|
||||
if (currentLevel != 0 &&
|
||||
_questRegistry.TryGetQuest(currentQuest, out Quest? quest)
|
||||
&& quest.Info.Level > currentLevel)
|
||||
return default;
|
||||
|
||||
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
||||
}
|
||||
|
||||
public QuestWork? GetQuestEx(QuestId questId)
|
||||
{
|
||||
QuestWork* questWork = QuestManager.Instance()->GetQuestById(questId.Value);
|
||||
return questWork != null ? *questWork : null;
|
||||
}
|
||||
|
||||
public bool IsReadyToAcceptQuest(ElementId elementId)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsReadyToAcceptQuest(questId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return true;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public bool IsReadyToAcceptQuest(QuestId questId)
|
||||
{
|
||||
_questRegistry.TryGetQuest(questId, out var quest);
|
||||
if (quest is { Info.IsRepeatable: true })
|
||||
{
|
||||
if (IsQuestAccepted(questId))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsQuestAcceptedOrComplete(questId))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsQuestLocked(questId))
|
||||
return false;
|
||||
|
||||
// if we're not at a high enough level to continue, we also ignore it
|
||||
var currentLevel = _clientState.LocalPlayer?.Level ?? 0;
|
||||
if (currentLevel != 0 && quest != null && quest.Info.Level > currentLevel)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsQuestAcceptedOrComplete(ElementId questElementId)
|
||||
{
|
||||
return IsQuestComplete(questElementId) || IsQuestAccepted(questElementId);
|
||||
}
|
||||
|
||||
public bool IsQuestAccepted(ElementId elementId)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestAccepted(questId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public bool IsQuestAccepted(QuestId questId)
|
||||
{
|
||||
QuestManager* questManager = QuestManager.Instance();
|
||||
return questManager->IsQuestAccepted(questId.Value);
|
||||
}
|
||||
|
||||
public bool IsQuestComplete(ElementId elementId)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestComplete(questId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
[SuppressMessage("Performance", "CA1822")]
|
||||
public bool IsQuestComplete(QuestId questId)
|
||||
{
|
||||
return QuestManager.IsQuestComplete(questId.Value);
|
||||
}
|
||||
|
||||
public bool IsQuestLocked(ElementId elementId, ElementId? extraCompletedQuest = null)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestLocked(questId, extraCompletedQuest);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public bool IsQuestLocked(QuestId questId, ElementId? extraCompletedQuest = null)
|
||||
{
|
||||
var questInfo = (QuestInfo) _questData.GetQuestInfo(questId);
|
||||
if (questInfo.QuestLocks.Count > 0)
|
||||
{
|
||||
var completedQuests = questInfo.QuestLocks.Count(x => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
|
||||
if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.All && questInfo.QuestLocks.Count == completedQuests)
|
||||
return true;
|
||||
else if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (questInfo.GrandCompany != GrandCompany.None && questInfo.GrandCompany != GetGrandCompany())
|
||||
return true;
|
||||
|
||||
return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
|
||||
}
|
||||
|
||||
private bool HasCompletedPreviousQuests(QuestInfo questInfo, ElementId? extraCompletedQuest)
|
||||
{
|
||||
if (questInfo.PreviousQuests.Count == 0)
|
||||
return true;
|
||||
|
||||
var completedQuests = questInfo.PreviousQuests.Count(x => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
|
||||
if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.All &&
|
||||
questInfo.PreviousQuests.Count == completedQuests)
|
||||
return true;
|
||||
else if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasCompletedPreviousInstances(QuestInfo questInfo)
|
||||
{
|
||||
if (questInfo.PreviousInstanceContent.Count == 0)
|
||||
return true;
|
||||
|
||||
var completedInstances = questInfo.PreviousInstanceContent.Count(x => UIState.IsInstanceContentCompleted(x));
|
||||
if (questInfo.PreviousInstanceContentJoin == QuestInfo.QuestJoin.All &&
|
||||
questInfo.PreviousInstanceContent.Count == completedInstances)
|
||||
return true;
|
||||
else if (questInfo.PreviousInstanceContentJoin == QuestInfo.QuestJoin.AtLeastOne && completedInstances > 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
|
||||
{
|
||||
subIndex = 0;
|
||||
@ -383,7 +87,7 @@ internal sealed unsafe class GameFunctions
|
||||
public bool IsAetheryteUnlocked(EAetheryteLocation aetheryteLocation)
|
||||
{
|
||||
if (aetheryteLocation == EAetheryteLocation.IshgardFirmament)
|
||||
return IsQuestComplete(new QuestId(3672));
|
||||
return _questFunctions.IsQuestComplete(new QuestId(3672));
|
||||
return IsAetheryteUnlocked((uint)aetheryteLocation, out _);
|
||||
}
|
||||
|
||||
@ -431,7 +135,7 @@ internal sealed unsafe class GameFunctions
|
||||
if (_configuration.Advanced.NeverFly)
|
||||
return false;
|
||||
|
||||
if (IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted])
|
||||
if (_questFunctions.IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted])
|
||||
{
|
||||
BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
|
||||
if (battleChara != null && battleChara->Mount.MountId == 198) // special quest amaro, not the normal one
|
||||
@ -718,61 +422,18 @@ internal sealed unsafe class GameFunctions
|
||||
contentFinderConditionId);
|
||||
}
|
||||
|
||||
public string? GetDialogueText(Quest currentQuest, string? excelSheetName, string key)
|
||||
/// <summary>
|
||||
/// Ensures characters like '-' are handled equally in both strings.
|
||||
/// </summary>
|
||||
public static bool GameStringEquals(string? a, string? b)
|
||||
{
|
||||
if (excelSheetName == null)
|
||||
{
|
||||
var questRow =
|
||||
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.Id.Value +
|
||||
0x10000);
|
||||
if (questRow == null)
|
||||
{
|
||||
_logger.LogError("Could not find quest row for {QuestId}", currentQuest.Id);
|
||||
return null;
|
||||
}
|
||||
if (a == null)
|
||||
return b == null;
|
||||
|
||||
excelSheetName = $"quest/{(currentQuest.Id.Value / 100):000}/{questRow.Id}";
|
||||
}
|
||||
if (b == null)
|
||||
return false;
|
||||
|
||||
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
|
||||
if (excelSheet == null)
|
||||
{
|
||||
_logger.LogError("Unknown excel sheet '{SheetName}'", excelSheetName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return excelSheet.FirstOrDefault(x => x.Key == key)?.Value?.ToDalamudString().ToString();
|
||||
}
|
||||
|
||||
public string? GetDialogueTextByRowId(string? excelSheet, uint rowId)
|
||||
{
|
||||
if (excelSheet == "GimmickYesNo")
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<GimmickYesNo>()!.GetRow(rowId);
|
||||
return questRow?.Unknown0?.ToString();
|
||||
}
|
||||
else if (excelSheet == "Warp")
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<Warp>()!.GetRow(rowId);
|
||||
return questRow?.Name?.ToString();
|
||||
}
|
||||
else if (excelSheet is "Addon")
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<Addon>()!.GetRow(rowId);
|
||||
return questRow?.Text?.ToString();
|
||||
}
|
||||
else if (excelSheet is "EventPathMove")
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<EventPathMove>()!.GetRow(rowId);
|
||||
return questRow?.Unknown10?.ToString();
|
||||
}
|
||||
else if (excelSheet is "ContentTalk" or null)
|
||||
{
|
||||
var questRow = _dataManager.GetExcelSheet<ContentTalk>()!.GetRow(rowId);
|
||||
return questRow?.Text?.ToString();
|
||||
}
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(excelSheet), $"Unsupported excel sheet {excelSheet}");
|
||||
return a.ReplaceLineEndings().Replace('\u2013', '-') == b.ReplaceLineEndings().Replace('\u2013', '-');
|
||||
}
|
||||
|
||||
public bool IsOccupied()
|
||||
@ -792,15 +453,28 @@ internal sealed unsafe class GameFunctions
|
||||
_condition[ConditionFlag.Jumping61] || _condition[ConditionFlag.Gathering42];
|
||||
}
|
||||
|
||||
public bool IsOccupiedWithCustomDeliveryNpc(Quest? currentQuest)
|
||||
{
|
||||
// not a supply quest?
|
||||
if (currentQuest is not { Info: SatisfactionSupplyInfo })
|
||||
return false;
|
||||
|
||||
if (_targetManager.Target == null || _targetManager.Target.DataId != currentQuest.Info.IssuerDataId)
|
||||
return false;
|
||||
|
||||
if (!AgentSatisfactionSupply.Instance()->IsAgentActive())
|
||||
return false;
|
||||
|
||||
var flags = _condition.AsReadOnlySet();
|
||||
return flags.Count == 2 &&
|
||||
flags.Contains(ConditionFlag.NormalConditions) &&
|
||||
flags.Contains(ConditionFlag.OccupiedInQuestEvent);
|
||||
}
|
||||
|
||||
public bool IsLoadingScreenVisible()
|
||||
{
|
||||
return _gameGui.TryGetAddonByName("FadeMiddle", out AtkUnitBase* fade) &&
|
||||
LAddon.IsAddonReady(fade) &&
|
||||
fade->IsVisible;
|
||||
}
|
||||
|
||||
public GrandCompany GetGrandCompany()
|
||||
{
|
||||
return (GrandCompany)PlayerState.Instance()->GrandCompany;
|
||||
}
|
||||
}
|
346
Questionable/Functions/QuestFunctions.cs
Normal file
346
Questionable/Functions/QuestFunctions.cs
Normal file
@ -0,0 +1,346 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib.GameData;
|
||||
using LLib.GameUI;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
|
||||
using Quest = Questionable.Model.Quest;
|
||||
|
||||
namespace Questionable.Functions;
|
||||
|
||||
internal sealed unsafe class QuestFunctions
|
||||
{
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestData _questData;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly IDataManager _dataManager;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly IGameGui _gameGui;
|
||||
|
||||
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, Configuration configuration, IDataManager dataManager, IClientState clientState, IGameGui gameGui)
|
||||
{
|
||||
_questRegistry = questRegistry;
|
||||
_questData = questData;
|
||||
_configuration = configuration;
|
||||
_dataManager = dataManager;
|
||||
_clientState = clientState;
|
||||
_gameGui = gameGui;
|
||||
}
|
||||
|
||||
public (ElementId? CurrentQuest, byte Sequence) GetCurrentQuest()
|
||||
{
|
||||
var (currentQuest, sequence) = GetCurrentQuestInternal();
|
||||
PlayerState* playerState = PlayerState.Instance();
|
||||
|
||||
if (currentQuest == null || currentQuest.Value == 0)
|
||||
{
|
||||
if (_clientState.TerritoryType == 181) // Starting in Limsa
|
||||
return (new QuestId(107), 0);
|
||||
if (_clientState.TerritoryType == 182) // Starting in Ul'dah
|
||||
return (new QuestId(594), 0);
|
||||
if (_clientState.TerritoryType == 183) // Starting in Gridania
|
||||
return (new QuestId(39), 0);
|
||||
return default;
|
||||
}
|
||||
else if (currentQuest.Value == 681)
|
||||
{
|
||||
// if we have already picked up the GC quest, just return the progress for it
|
||||
if (IsQuestAccepted(currentQuest) || IsQuestComplete(currentQuest))
|
||||
return (currentQuest, sequence);
|
||||
|
||||
// The company you keep...
|
||||
return _configuration.General.GrandCompany switch
|
||||
{
|
||||
GrandCompany.TwinAdder => (new QuestId(680), 0),
|
||||
GrandCompany.Maelstrom => (new QuestId(681), 0),
|
||||
_ => default
|
||||
};
|
||||
}
|
||||
else if (currentQuest.Value == 3856 && !playerState->IsMountUnlocked(1)) // we come in peace
|
||||
{
|
||||
ushort chocoboQuest = (GrandCompany)playerState->GrandCompany switch
|
||||
{
|
||||
GrandCompany.TwinAdder => 700,
|
||||
GrandCompany.Maelstrom => 701,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if (chocoboQuest != 0 && !QuestManager.IsQuestComplete(chocoboQuest))
|
||||
return (new QuestId(chocoboQuest), QuestManager.GetQuestSequence(chocoboQuest));
|
||||
}
|
||||
else if (currentQuest.Value == 801)
|
||||
{
|
||||
// skeletons in her closet, finish 'broadening horizons' to unlock the white wolf gate
|
||||
QuestId broadeningHorizons = new QuestId(802);
|
||||
if (IsQuestAccepted(broadeningHorizons))
|
||||
return (broadeningHorizons, QuestManager.GetQuestSequence(broadeningHorizons.Value));
|
||||
}
|
||||
|
||||
return (currentQuest, sequence);
|
||||
}
|
||||
|
||||
public (ElementId? CurrentQuest, byte Sequence) GetCurrentQuestInternal()
|
||||
{
|
||||
var questManager = QuestManager.Instance();
|
||||
if (questManager != null)
|
||||
{
|
||||
// always prioritize accepting MSQ quests, to make sure we don't turn in one MSQ quest and then go off to do
|
||||
// side quests until the end of time.
|
||||
var msqQuest = GetMainScenarioQuest(questManager);
|
||||
if (msqQuest.CurrentQuest is { Value: not 0 } && _questRegistry.IsKnownQuest(msqQuest.CurrentQuest))
|
||||
return msqQuest;
|
||||
|
||||
// Use the quests in the same order as they're shown in the to-do list, e.g. if the MSQ is the first item,
|
||||
// do the MSQ; if a side quest is the first item do that side quest.
|
||||
//
|
||||
// If no quests are marked as 'priority', accepting a new quest adds it to the top of the list.
|
||||
for (int i = questManager->TrackedQuests.Length - 1; i >= 0; --i)
|
||||
{
|
||||
ElementId currentQuest;
|
||||
var trackedQuest = questManager->TrackedQuests[i];
|
||||
switch (trackedQuest.QuestType)
|
||||
{
|
||||
default:
|
||||
continue;
|
||||
|
||||
case 1: // normal quest
|
||||
currentQuest = new QuestId(questManager->NormalQuests[trackedQuest.Index].QuestId);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_questRegistry.IsKnownQuest(currentQuest))
|
||||
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
||||
}
|
||||
|
||||
// if we know no quest of those currently in the to-do list, just do MSQ
|
||||
return msqQuest;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private (QuestId? CurrentQuest, byte Sequence) GetMainScenarioQuest(QuestManager* questManager)
|
||||
{
|
||||
if (QuestManager.IsQuestComplete(3759)) // Memories Rekindled
|
||||
{
|
||||
AgentInterface* questRedoHud = AgentModule.Instance()->GetAgentByInternalId(AgentId.QuestRedoHud);
|
||||
if (questRedoHud != null && questRedoHud->IsAgentActive())
|
||||
{
|
||||
// there's surely better ways to check this, but the one in the OOB Plugin was even less reliable
|
||||
if (_gameGui.TryGetAddonByName<AtkUnitBase>("QuestRedoHud", out var addon) &&
|
||||
addon->AtkValuesCount == 4 &&
|
||||
// 0 seems to be active,
|
||||
// 1 seems to be paused,
|
||||
// 2 is unknown, but it happens e.g. before the quest 'Alzadaal's Legacy'
|
||||
// 3 seems to be having /ng+ open while active,
|
||||
// 4 seems to be when (a) suspending the chapter, or (b) having turned in a quest
|
||||
addon->AtkValues[0].UInt is 0 or 2 or 3 or 4)
|
||||
{
|
||||
// redoHud+44 is chapter
|
||||
// redoHud+46 is quest
|
||||
ushort questId = MemoryHelper.Read<ushort>((nint)questRedoHud + 46);
|
||||
return (new QuestId(questId), QuestManager.GetQuestSequence(questId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scenarioTree = AgentScenarioTree.Instance();
|
||||
if (scenarioTree == null)
|
||||
return default;
|
||||
|
||||
if (scenarioTree->Data == null)
|
||||
return default;
|
||||
|
||||
QuestId currentQuest = new QuestId(scenarioTree->Data->CurrentScenarioQuest);
|
||||
if (currentQuest.Value == 0)
|
||||
return default;
|
||||
|
||||
// if the MSQ is hidden, we generally ignore it
|
||||
if (IsQuestAccepted(currentQuest) && questManager->GetQuestById(currentQuest.Value)->IsHidden)
|
||||
return default;
|
||||
|
||||
// it can sometimes happen (although this isn't reliably reproducible) that the quest returned here
|
||||
// is one you've just completed.
|
||||
if (!IsReadyToAcceptQuest(currentQuest))
|
||||
return default;
|
||||
|
||||
// if we're not at a high enough level to continue, we also ignore it
|
||||
var currentLevel = _clientState.LocalPlayer?.Level ?? 0;
|
||||
if (currentLevel != 0 &&
|
||||
_questRegistry.TryGetQuest(currentQuest, out Quest? quest)
|
||||
&& quest.Info.Level > currentLevel)
|
||||
return default;
|
||||
|
||||
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
||||
}
|
||||
|
||||
public QuestWork? GetQuestEx(QuestId questId)
|
||||
{
|
||||
QuestWork* questWork = QuestManager.Instance()->GetQuestById(questId.Value);
|
||||
return questWork != null ? *questWork : null;
|
||||
}
|
||||
|
||||
public bool IsReadyToAcceptQuest(ElementId elementId)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsReadyToAcceptQuest(questId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return true;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public bool IsReadyToAcceptQuest(QuestId questId)
|
||||
{
|
||||
_questRegistry.TryGetQuest(questId, out var quest);
|
||||
if (quest is { Info.IsRepeatable: true })
|
||||
{
|
||||
if (IsQuestAccepted(questId))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsQuestAcceptedOrComplete(questId))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsQuestLocked(questId))
|
||||
return false;
|
||||
|
||||
// if we're not at a high enough level to continue, we also ignore it
|
||||
var currentLevel = _clientState.LocalPlayer?.Level ?? 0;
|
||||
if (currentLevel != 0 && quest != null && quest.Info.Level > currentLevel)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsQuestAcceptedOrComplete(ElementId elementId)
|
||||
{
|
||||
return IsQuestComplete(elementId) || IsQuestAccepted(elementId);
|
||||
}
|
||||
|
||||
public bool IsQuestAccepted(ElementId elementId)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestAccepted(questId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public bool IsQuestAccepted(QuestId questId)
|
||||
{
|
||||
QuestManager* questManager = QuestManager.Instance();
|
||||
return questManager->IsQuestAccepted(questId.Value);
|
||||
}
|
||||
|
||||
public bool IsQuestComplete(ElementId elementId)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestComplete(questId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
[SuppressMessage("Performance", "CA1822")]
|
||||
public bool IsQuestComplete(QuestId questId)
|
||||
{
|
||||
return QuestManager.IsQuestComplete(questId.Value);
|
||||
}
|
||||
|
||||
public bool IsQuestLocked(ElementId elementId, ElementId? extraCompletedQuest = null)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestLocked(questId, extraCompletedQuest);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public bool IsQuestLocked(QuestId questId, ElementId? extraCompletedQuest = null)
|
||||
{
|
||||
var questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
|
||||
if (questInfo.QuestLocks.Count > 0)
|
||||
{
|
||||
var completedQuests = questInfo.QuestLocks.Count(x => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
|
||||
if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.All && questInfo.QuestLocks.Count == completedQuests)
|
||||
return true;
|
||||
else if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (questInfo.GrandCompany != GrandCompany.None && questInfo.GrandCompany != GetGrandCompany())
|
||||
return true;
|
||||
|
||||
return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
|
||||
}
|
||||
|
||||
private bool HasCompletedPreviousQuests(QuestInfo questInfo, ElementId? extraCompletedQuest)
|
||||
{
|
||||
if (questInfo.PreviousQuests.Count == 0)
|
||||
return true;
|
||||
|
||||
var completedQuests = questInfo.PreviousQuests.Count(x => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
|
||||
if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.All &&
|
||||
questInfo.PreviousQuests.Count == completedQuests)
|
||||
return true;
|
||||
else if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasCompletedPreviousInstances(QuestInfo questInfo)
|
||||
{
|
||||
if (questInfo.PreviousInstanceContent.Count == 0)
|
||||
return true;
|
||||
|
||||
var completedInstances = questInfo.PreviousInstanceContent.Count(x => UIState.IsInstanceContentCompleted(x));
|
||||
if (questInfo.PreviousInstanceContentJoin == QuestInfo.QuestJoin.All &&
|
||||
questInfo.PreviousInstanceContent.Count == completedInstances)
|
||||
return true;
|
||||
else if (questInfo.PreviousInstanceContentJoin == QuestInfo.QuestJoin.AtLeastOne && completedInstances > 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsClassJobUnlocked(EClassJob classJob)
|
||||
{
|
||||
var classJobRow = _dataManager.GetExcelSheet<ClassJob>()!.GetRow((uint)classJob)!;
|
||||
var questId = (ushort)classJobRow.UnlockQuest.Row;
|
||||
if (questId != 0)
|
||||
return IsQuestComplete(new QuestId(questId));
|
||||
|
||||
PlayerState* playerState = PlayerState.Instance();
|
||||
return playerState != null && playerState->ClassJobLevels[classJobRow.ExpArrayIndex] > 0;
|
||||
}
|
||||
|
||||
public bool IsJobUnlocked(EClassJob classJob)
|
||||
{
|
||||
var classJobRow = _dataManager.GetExcelSheet<ClassJob>()!.GetRow((uint)classJob)!;
|
||||
return IsClassJobUnlocked((EClassJob)classJobRow.ClassJobParent.Row);
|
||||
}
|
||||
|
||||
public GrandCompany GetGrandCompany()
|
||||
{
|
||||
return (GrandCompany)PlayerState.Instance()->GrandCompany;
|
||||
}
|
||||
}
|
43
Questionable/Model/StringOrRegex.cs
Normal file
43
Questionable/Model/StringOrRegex.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Questionable.Functions;
|
||||
|
||||
namespace Questionable.Model;
|
||||
|
||||
internal sealed class StringOrRegex
|
||||
{
|
||||
private readonly Regex? _regex;
|
||||
private readonly string? _stringValue;
|
||||
|
||||
public StringOrRegex(Regex? regex)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(regex);
|
||||
_regex = regex;
|
||||
_stringValue = null;
|
||||
}
|
||||
|
||||
public StringOrRegex(string? str)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(str);
|
||||
_regex = null;
|
||||
_stringValue = str;
|
||||
}
|
||||
|
||||
public bool IsMatch(string other)
|
||||
{
|
||||
if (_regex != null)
|
||||
return _regex.IsMatch(other);
|
||||
else
|
||||
return GameFunctions.GameStringEquals(_stringValue, other);
|
||||
}
|
||||
|
||||
public string? GetString()
|
||||
{
|
||||
if (_stringValue == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
return _stringValue;
|
||||
}
|
||||
|
||||
public override string? ToString() => _regex?.ToString() ?? _stringValue;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<Project Sdk="Dalamud.NET.Sdk/10.0.0">
|
||||
<PropertyGroup>
|
||||
<Version>2.1</Version>
|
||||
<Version>2.2</Version>
|
||||
<OutputPath>dist</OutputPath>
|
||||
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||
<Platforms>x64</Platforms>
|
||||
|
@ -17,6 +17,7 @@ using Questionable.Controller.Steps.Gathering;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Validation;
|
||||
using Questionable.Validation.Validators;
|
||||
using Questionable.Windows;
|
||||
@ -47,50 +48,58 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
IContextMenu contextMenu)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pluginInterface);
|
||||
ArgumentNullException.ThrowIfNull(chatGui);
|
||||
try
|
||||
{
|
||||
ServiceCollection serviceCollection = new();
|
||||
serviceCollection.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace)
|
||||
.ClearProviders()
|
||||
.AddDalamudLogger(pluginLog, t => t[(t.LastIndexOf('.') + 1)..]));
|
||||
serviceCollection.AddSingleton<IDalamudPlugin>(this);
|
||||
serviceCollection.AddSingleton(pluginInterface);
|
||||
serviceCollection.AddSingleton(clientState);
|
||||
serviceCollection.AddSingleton(targetManager);
|
||||
serviceCollection.AddSingleton(framework);
|
||||
serviceCollection.AddSingleton(gameGui);
|
||||
serviceCollection.AddSingleton(dataManager);
|
||||
serviceCollection.AddSingleton(sigScanner);
|
||||
serviceCollection.AddSingleton(objectTable);
|
||||
serviceCollection.AddSingleton(pluginLog);
|
||||
serviceCollection.AddSingleton(condition);
|
||||
serviceCollection.AddSingleton(chatGui);
|
||||
serviceCollection.AddSingleton(commandManager);
|
||||
serviceCollection.AddSingleton(addonLifecycle);
|
||||
serviceCollection.AddSingleton(keyState);
|
||||
serviceCollection.AddSingleton(contextMenu);
|
||||
serviceCollection.AddSingleton(new WindowSystem(nameof(Questionable)));
|
||||
serviceCollection.AddSingleton((Configuration?)pluginInterface.GetPluginConfig() ?? new Configuration());
|
||||
|
||||
ServiceCollection serviceCollection = new();
|
||||
serviceCollection.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace)
|
||||
.ClearProviders()
|
||||
.AddDalamudLogger(pluginLog, t => t[(t.LastIndexOf('.') + 1)..]));
|
||||
serviceCollection.AddSingleton<IDalamudPlugin>(this);
|
||||
serviceCollection.AddSingleton(pluginInterface);
|
||||
serviceCollection.AddSingleton(clientState);
|
||||
serviceCollection.AddSingleton(targetManager);
|
||||
serviceCollection.AddSingleton(framework);
|
||||
serviceCollection.AddSingleton(gameGui);
|
||||
serviceCollection.AddSingleton(dataManager);
|
||||
serviceCollection.AddSingleton(sigScanner);
|
||||
serviceCollection.AddSingleton(objectTable);
|
||||
serviceCollection.AddSingleton(pluginLog);
|
||||
serviceCollection.AddSingleton(condition);
|
||||
serviceCollection.AddSingleton(chatGui);
|
||||
serviceCollection.AddSingleton(commandManager);
|
||||
serviceCollection.AddSingleton(addonLifecycle);
|
||||
serviceCollection.AddSingleton(keyState);
|
||||
serviceCollection.AddSingleton(contextMenu);
|
||||
serviceCollection.AddSingleton(new WindowSystem(nameof(Questionable)));
|
||||
serviceCollection.AddSingleton((Configuration?)pluginInterface.GetPluginConfig() ?? new Configuration());
|
||||
AddBasicFunctionsAndData(serviceCollection);
|
||||
AddTaskFactories(serviceCollection);
|
||||
AddControllers(serviceCollection);
|
||||
AddWindows(serviceCollection);
|
||||
AddQuestValidators(serviceCollection);
|
||||
|
||||
AddBasicFunctionsAndData(serviceCollection);
|
||||
AddTaskFactories(serviceCollection);
|
||||
AddControllers(serviceCollection);
|
||||
AddWindows(serviceCollection);
|
||||
AddQuestValidators(serviceCollection);
|
||||
serviceCollection.AddSingleton<CommandHandler>();
|
||||
serviceCollection.AddSingleton<DalamudInitializer>();
|
||||
|
||||
serviceCollection.AddSingleton<CommandHandler>();
|
||||
serviceCollection.AddSingleton<DalamudInitializer>();
|
||||
|
||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
_serviceProvider.GetRequiredService<QuestRegistry>().Reload();
|
||||
_serviceProvider.GetRequiredService<CommandHandler>();
|
||||
_serviceProvider.GetRequiredService<ContextMenuController>();
|
||||
_serviceProvider.GetRequiredService<DalamudInitializer>();
|
||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
Initialize(_serviceProvider);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
chatGui.PrintError("Unable to load plugin, check /xllog for details", "Questionable");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddBasicFunctionsAndData(ServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddSingleton<ExcelFunctions>();
|
||||
serviceCollection.AddSingleton<GameFunctions>();
|
||||
serviceCollection.AddSingleton<ChatFunctions>();
|
||||
serviceCollection.AddSingleton<QuestFunctions>();
|
||||
|
||||
serviceCollection.AddSingleton<AetherCurrentData>();
|
||||
serviceCollection.AddSingleton<AetheryteData>();
|
||||
serviceCollection.AddSingleton<GatheringData>();
|
||||
@ -110,6 +119,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddTransient<MoveToLandingLocation>();
|
||||
serviceCollection.AddTransient<DoGather>();
|
||||
serviceCollection.AddTransient<DoGatherCollectable>();
|
||||
serviceCollection.AddTransient<SwitchClassJob>();
|
||||
|
||||
// task factories
|
||||
serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
|
||||
@ -135,6 +145,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();
|
||||
serviceCollection.AddTaskWithFactory<UseItem.Factory, UseItem.UseOnGround, UseItem.UseOnObject, UseItem.Use, UseItem.UseOnPosition>();
|
||||
serviceCollection.AddTaskWithFactory<EquipItem.Factory, EquipItem.DoEquip>();
|
||||
serviceCollection.AddTaskWithFactory<TurnInDelivery.Factory, TurnInDelivery.SatisfactionSupplyTurnIn>();
|
||||
serviceCollection
|
||||
.AddTaskWithFactory<SinglePlayerDuty.Factory, SinglePlayerDuty.DisableYesAlready,
|
||||
SinglePlayerDuty.RestoreYesAlready>();
|
||||
@ -192,10 +203,19 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton<IQuestValidator, NextQuestValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, CompletionFlagsValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, AethernetShortcutValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, DialogueChoiceValidator>();
|
||||
serviceCollection.AddSingleton<JsonSchemaValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator>(sp => sp.GetRequiredService<JsonSchemaValidator>());
|
||||
}
|
||||
|
||||
private static void Initialize(IServiceProvider serviceProvider)
|
||||
{
|
||||
serviceProvider.GetRequiredService<QuestRegistry>().Reload();
|
||||
serviceProvider.GetRequiredService<CommandHandler>();
|
||||
serviceProvider.GetRequiredService<ContextMenuController>();
|
||||
serviceProvider.GetRequiredService<DalamudInitializer>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_serviceProvider?.Dispose();
|
||||
|
@ -16,4 +16,5 @@ public enum EIssueType
|
||||
UnexpectedAcceptQuestStep,
|
||||
UnexpectedCompleteQuestStep,
|
||||
InvalidAethernetShortcut,
|
||||
InvalidExcelRef,
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ internal sealed class QuestValidator
|
||||
: LogLevel.Information;
|
||||
_logger.Log(level,
|
||||
"Validation failed: {QuestId} ({QuestName}) / {QuestSequence} / {QuestStep} - {Description}",
|
||||
issue.QuestId, quest.Info.Name, issue.Sequence, issue.Step, issue.Description);
|
||||
issue.ElementId, quest.Info.Name, issue.Sequence, issue.Step, issue.Description);
|
||||
if (issue.Type == EIssueType.QuestDisabled && quest.Info.BeastTribe != EBeastTribe.None)
|
||||
{
|
||||
disabledTribeQuests.TryAdd(quest.Info.BeastTribe, 0);
|
||||
@ -70,12 +70,12 @@ internal sealed class QuestValidator
|
||||
|
||||
var disabledQuests = issues
|
||||
.Where(x => x.Type == EIssueType.QuestDisabled)
|
||||
.Select(x => x.QuestId)
|
||||
.Select(x => x.ElementId)
|
||||
.ToList();
|
||||
|
||||
_validationIssues = issues
|
||||
.Where(x => !disabledQuests.Contains(x.QuestId) || x.Type == EIssueType.QuestDisabled)
|
||||
.OrderBy(x => x.QuestId)
|
||||
.Where(x => !disabledQuests.Contains(x.ElementId) || x.Type == EIssueType.QuestDisabled)
|
||||
.OrderBy(x => x.ElementId)
|
||||
.ThenBy(x => x.Sequence)
|
||||
.ThenBy(x => x.Step)
|
||||
.ThenBy(x => x.Description)
|
||||
@ -95,7 +95,7 @@ internal sealed class QuestValidator
|
||||
.OrderBy(x => x.Key)
|
||||
.Select(x => new ValidationIssue
|
||||
{
|
||||
QuestId = null,
|
||||
ElementId = null,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
BeastTribe = x.Key,
|
||||
|
@ -5,7 +5,7 @@ namespace Questionable.Validation;
|
||||
|
||||
internal sealed record ValidationIssue
|
||||
{
|
||||
public required ElementId? QuestId { get; init; }
|
||||
public required ElementId? ElementId { get; init; }
|
||||
public required byte? Sequence { get; init; }
|
||||
public required int? Step { get; init; }
|
||||
public EBeastTribe BeastTribe { get; init; } = EBeastTribe.None;
|
||||
|
@ -24,7 +24,7 @@ internal sealed class AethernetShortcutValidator : IQuestValidator
|
||||
.Cast<ValidationIssue>();
|
||||
}
|
||||
|
||||
private ValidationIssue? Validate(ElementId questElementId, int sequenceNo, int stepId, AethernetShortcut? aethernetShortcut)
|
||||
private ValidationIssue? Validate(ElementId elementId, int sequenceNo, int stepId, AethernetShortcut? aethernetShortcut)
|
||||
{
|
||||
if (aethernetShortcut == null)
|
||||
return null;
|
||||
@ -35,7 +35,7 @@ internal sealed class AethernetShortcutValidator : IQuestValidator
|
||||
{
|
||||
return new ValidationIssue
|
||||
{
|
||||
QuestId = questElementId,
|
||||
ElementId = elementId,
|
||||
Sequence = (byte)sequenceNo,
|
||||
Step = stepId,
|
||||
Type = EIssueType.InvalidAethernetShortcut,
|
||||
|
@ -18,7 +18,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = 0,
|
||||
Step = null,
|
||||
Type = EIssueType.MissingSequence0,
|
||||
@ -37,7 +37,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = (byte)sequence.Sequence,
|
||||
Step = null,
|
||||
Type = EIssueType.InstantQuestWithMultipleSteps,
|
||||
@ -73,7 +73,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
{
|
||||
return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = (byte)sequenceNo,
|
||||
Step = null,
|
||||
Type = EIssueType.MissingSequence,
|
||||
@ -85,7 +85,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
{
|
||||
return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = (byte)sequenceNo,
|
||||
Step = null,
|
||||
Type = EIssueType.DuplicateSequence,
|
||||
|
@ -45,7 +45,7 @@ internal sealed class CompletionFlagsValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = (byte)sequence.Sequence,
|
||||
Step = i,
|
||||
Type = EIssueType.DuplicateCompletionFlags,
|
||||
|
@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Validation.Validators;
|
||||
|
||||
internal sealed class DialogueChoiceValidator : IQuestValidator
|
||||
{
|
||||
private readonly ExcelFunctions _excelFunctions;
|
||||
|
||||
public DialogueChoiceValidator(ExcelFunctions excelFunctions)
|
||||
{
|
||||
_excelFunctions = excelFunctions;
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationIssue> Validate(Quest quest)
|
||||
{
|
||||
foreach (var x in quest.AllSteps())
|
||||
{
|
||||
if (x.Step.DialogueChoices.Count == 0)
|
||||
continue;
|
||||
|
||||
foreach (var dialogueChoice in x.Step.DialogueChoices)
|
||||
{
|
||||
ExcelRef? prompt = dialogueChoice.Prompt;
|
||||
if (prompt != null)
|
||||
{
|
||||
ValidationIssue? promptIssue = Validate(quest, x.Sequence, x.StepId, dialogueChoice.ExcelSheet,
|
||||
prompt, "Prompt");
|
||||
if (promptIssue != null)
|
||||
yield return promptIssue;
|
||||
}
|
||||
|
||||
ExcelRef? answer = dialogueChoice.Answer;
|
||||
if (answer != null)
|
||||
{
|
||||
ValidationIssue? answerIssue = Validate(quest, x.Sequence, x.StepId, dialogueChoice.ExcelSheet,
|
||||
answer, "Answer");
|
||||
if (answerIssue != null)
|
||||
yield return answerIssue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ValidationIssue? Validate(Quest quest, QuestSequence sequence, int stepId, string? excelSheet,
|
||||
ExcelRef excelRef, string label)
|
||||
{
|
||||
if (excelRef.Type == ExcelRef.EType.Key)
|
||||
{
|
||||
if (_excelFunctions.GetRawDialogueText(quest, excelSheet, excelRef.AsKey()) == null)
|
||||
{
|
||||
return new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = (byte)sequence.Sequence,
|
||||
Step = stepId,
|
||||
Type = EIssueType.InvalidExcelRef,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = $"{label} invalid: {excelSheet} → {excelRef.AsKey()}",
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (excelRef.Type == ExcelRef.EType.RowId)
|
||||
{
|
||||
if (_excelFunctions.GetRawDialogueTextByRowId(excelSheet, excelRef.AsRowId()) == null)
|
||||
{
|
||||
return new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = (byte)sequence.Sequence,
|
||||
Step = stepId,
|
||||
Type = EIssueType.InvalidExcelRef,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = $"{label} invalid: {excelSheet} → {excelRef.AsRowId()}",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSchema,
|
||||
@ -47,7 +47,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(ElementId questElementId, JsonNode questNode) => _questNodes[questElementId] = questNode;
|
||||
public void Enqueue(ElementId elementId, JsonNode questNode) => _questNodes[elementId] = questNode;
|
||||
|
||||
public void Reset() => _questNodes.Clear();
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ internal sealed class NextQuestValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = (byte)invalidNextQuest.Sequence.Sequence,
|
||||
Step = invalidNextQuest.StepId,
|
||||
Type = EIssueType.InvalidNextQuestId,
|
||||
|
@ -11,7 +11,7 @@ internal sealed class QuestDisabledValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.QuestDisabled,
|
||||
|
@ -21,7 +21,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = (byte)accept.Sequence.Sequence,
|
||||
Step = accept.StepId,
|
||||
Type = EIssueType.UnexpectedAcceptQuestStep,
|
||||
@ -35,7 +35,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = 0,
|
||||
Step = null,
|
||||
Type = EIssueType.MissingQuestAccept,
|
||||
@ -53,7 +53,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = (byte)complete.Sequence.Sequence,
|
||||
Step = complete.StepId,
|
||||
Type = EIssueType.UnexpectedCompleteQuestStep,
|
||||
@ -67,7 +67,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.Id,
|
||||
ElementId = quest.Id,
|
||||
Sequence = 255,
|
||||
Step = null,
|
||||
Type = EIssueType.MissingQuestComplete,
|
||||
|
@ -12,6 +12,7 @@ using ImGuiNET;
|
||||
using LLib.ImGui;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Windows.QuestComponents;
|
||||
|
||||
@ -21,7 +22,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
||||
{
|
||||
private readonly JournalData _journalData;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly UiUtils _uiUtils;
|
||||
private readonly QuestTooltipComponent _questTooltipComponent;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
@ -37,7 +38,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
||||
|
||||
public JournalProgressWindow(JournalData journalData,
|
||||
QuestRegistry questRegistry,
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
UiUtils uiUtils,
|
||||
QuestTooltipComponent questTooltipComponent,
|
||||
IDalamudPluginInterface pluginInterface,
|
||||
@ -47,7 +48,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
||||
{
|
||||
_journalData = journalData;
|
||||
_questRegistry = questRegistry;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_uiUtils = uiUtils;
|
||||
_questTooltipComponent = questTooltipComponent;
|
||||
_pluginInterface = pluginInterface;
|
||||
@ -327,7 +328,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
||||
{
|
||||
int available = genre.Quests.Count(x =>
|
||||
_questRegistry.TryGetQuest(x.QuestId, out var quest) && !quest.Root.Disabled);
|
||||
int completed = genre.Quests.Count(x => _gameFunctions.IsQuestComplete(x.QuestId));
|
||||
int completed = genre.Quests.Count(x => _questFunctions.IsQuestComplete(x.QuestId));
|
||||
_genreCounts[genre] = (available, completed);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ using Dalamud.Interface.Utility.Raii;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Windows.QuestComponents;
|
||||
@ -18,26 +19,26 @@ internal sealed class ARealmRebornComponent
|
||||
private static readonly QuestId[] RequiredAllianceRaidQuests =
|
||||
[new(1709), new(1200), new(1201), new(1202), new(1203), new(1474), new(494), new(495)];
|
||||
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly QuestData _questData;
|
||||
private readonly TerritoryData _territoryData;
|
||||
private readonly UiUtils _uiUtils;
|
||||
|
||||
public ARealmRebornComponent(GameFunctions gameFunctions, QuestData questData, TerritoryData territoryData,
|
||||
public ARealmRebornComponent(QuestFunctions questFunctions, QuestData questData, TerritoryData territoryData,
|
||||
UiUtils uiUtils)
|
||||
{
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_questData = questData;
|
||||
_territoryData = territoryData;
|
||||
_uiUtils = uiUtils;
|
||||
}
|
||||
|
||||
public bool ShouldDraw => !_gameFunctions.IsQuestAcceptedOrComplete(ATimeForEveryPurpose) &&
|
||||
_gameFunctions.IsQuestComplete(TheUltimateWeapon);
|
||||
public bool ShouldDraw => !_questFunctions.IsQuestAcceptedOrComplete(ATimeForEveryPurpose) &&
|
||||
_questFunctions.IsQuestComplete(TheUltimateWeapon);
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if (!_gameFunctions.IsQuestAcceptedOrComplete(GoodIntentions))
|
||||
if (!_questFunctions.IsQuestAcceptedOrComplete(GoodIntentions))
|
||||
DrawPrimals();
|
||||
|
||||
DrawAllianceRaids();
|
||||
@ -63,7 +64,7 @@ internal sealed class ARealmRebornComponent
|
||||
|
||||
private void DrawAllianceRaids()
|
||||
{
|
||||
bool complete = _gameFunctions.IsQuestComplete(RequiredAllianceRaidQuests.Last());
|
||||
bool complete = _questFunctions.IsQuestComplete(RequiredAllianceRaidQuests.Last());
|
||||
bool hover = _uiUtils.ChecklistItem("Crystal Tower Raids", complete);
|
||||
if (complete || !hover)
|
||||
return;
|
||||
|
@ -13,6 +13,7 @@ using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
||||
using ImGuiNET;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Windows.QuestComponents;
|
||||
@ -24,6 +25,7 @@ internal sealed class ActiveQuestComponent
|
||||
private readonly CombatController _combatController;
|
||||
private readonly GatheringController _gatheringController;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
private readonly Configuration _configuration;
|
||||
@ -36,6 +38,7 @@ internal sealed class ActiveQuestComponent
|
||||
CombatController combatController,
|
||||
GatheringController gatheringController,
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
ICommandManager commandManager,
|
||||
IDalamudPluginInterface pluginInterface,
|
||||
Configuration configuration,
|
||||
@ -47,6 +50,7 @@ internal sealed class ActiveQuestComponent
|
||||
_combatController = combatController;
|
||||
_gatheringController = gatheringController;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_commandManager = commandManager;
|
||||
_pluginInterface = pluginInterface;
|
||||
_configuration = configuration;
|
||||
@ -116,6 +120,12 @@ internal sealed class ActiveQuestComponent
|
||||
ImGui.TextUnformatted(
|
||||
$"Simulated Quest: {Shorten(currentQuest.Quest.Info.Name)} / {currentQuest.Sequence} / {currentQuest.Step}");
|
||||
}
|
||||
else if (currentQuestType == QuestController.ECurrentQuestType.Gathering)
|
||||
{
|
||||
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedGold);
|
||||
ImGui.TextUnformatted(
|
||||
$"Gathering: {Shorten(currentQuest.Quest.Info.Name)} / {currentQuest.Sequence} / {currentQuest.Step}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var startedQuest = _questController.StartedQuest;
|
||||
@ -154,7 +164,7 @@ internal sealed class ActiveQuestComponent
|
||||
if (currentQuest.Quest.Id is not QuestId questId)
|
||||
return null;
|
||||
|
||||
var questWork = _gameFunctions.GetQuestEx(questId);
|
||||
var questWork = _questFunctions.GetQuestEx(questId);
|
||||
if (questWork != null)
|
||||
{
|
||||
Vector4 color;
|
||||
|
@ -15,6 +15,7 @@ using ImGuiNET;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
@ -26,6 +27,7 @@ internal sealed class CreationUtilsComponent
|
||||
{
|
||||
private readonly MovementController _movementController;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly TerritoryData _territoryData;
|
||||
private readonly QuestData _questData;
|
||||
private readonly QuestSelectionWindow _questSelectionWindow;
|
||||
@ -35,13 +37,22 @@ internal sealed class CreationUtilsComponent
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly ILogger<CreationUtilsComponent> _logger;
|
||||
|
||||
public CreationUtilsComponent(MovementController movementController, GameFunctions gameFunctions,
|
||||
TerritoryData territoryData, QuestData questData, QuestSelectionWindow questSelectionWindow,
|
||||
IClientState clientState, ITargetManager targetManager, ICondition condition, IGameGui gameGui,
|
||||
public CreationUtilsComponent(
|
||||
MovementController movementController,
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
TerritoryData territoryData,
|
||||
QuestData questData,
|
||||
QuestSelectionWindow questSelectionWindow,
|
||||
IClientState clientState,
|
||||
ITargetManager targetManager,
|
||||
ICondition condition,
|
||||
IGameGui gameGui,
|
||||
ILogger<CreationUtilsComponent> logger)
|
||||
{
|
||||
_movementController = movementController;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_territoryData = territoryData;
|
||||
_questData = questData;
|
||||
_questSelectionWindow = questSelectionWindow;
|
||||
@ -65,7 +76,7 @@ internal sealed class CreationUtilsComponent
|
||||
ImGui.Text(SeIconChar.BotanistSprout.ToIconString());
|
||||
}
|
||||
|
||||
var q = _gameFunctions.GetCurrentQuest();
|
||||
var q = _questFunctions.GetCurrentQuest();
|
||||
ImGui.Text($"Current Quest: {q.CurrentQuest} → {q.Sequence}");
|
||||
|
||||
#if false
|
||||
|
@ -5,6 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using ImGuiNET;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
@ -15,20 +16,20 @@ internal sealed class QuestTooltipComponent
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestData _questData;
|
||||
private readonly TerritoryData _territoryData;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly UiUtils _uiUtils;
|
||||
|
||||
public QuestTooltipComponent(
|
||||
QuestRegistry questRegistry,
|
||||
QuestData questData,
|
||||
TerritoryData territoryData,
|
||||
GameFunctions gameFunctions,
|
||||
QuestFunctions questFunctions,
|
||||
UiUtils uiUtils)
|
||||
{
|
||||
_questRegistry = questRegistry;
|
||||
_questData = questData;
|
||||
_territoryData = territoryData;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_uiUtils = uiUtils;
|
||||
}
|
||||
|
||||
@ -161,7 +162,7 @@ internal sealed class QuestTooltipComponent
|
||||
_ => "None",
|
||||
};
|
||||
|
||||
GrandCompany currentGrandCompany = _gameFunctions.GetGrandCompany();
|
||||
GrandCompany currentGrandCompany = ~_questFunctions.GetGrandCompany();
|
||||
_uiUtils.ChecklistItem($"Grand Company: {gcName}", quest.GrandCompany == currentGrandCompany);
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using ImGuiNET;
|
||||
using Questionable.Controller;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
|
||||
namespace Questionable.Windows.QuestComponents;
|
||||
|
||||
|
@ -12,12 +12,12 @@ using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using ImGuiNET;
|
||||
using LLib.GameUI;
|
||||
using LLib.ImGui;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using Questionable.Windows.QuestComponents;
|
||||
@ -30,7 +30,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
private readonly QuestData _questData;
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly IChatGui _chatGui;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly QuestController _questController;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
@ -43,16 +43,24 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
private List<IQuestInfo> _offeredQuests = [];
|
||||
private bool _onlyAvailableQuests = true;
|
||||
|
||||
public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions,
|
||||
QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface,
|
||||
TerritoryData territoryData, IClientState clientState, UiUtils uiUtils,
|
||||
public QuestSelectionWindow(
|
||||
QuestData questData,
|
||||
IGameGui gameGui,
|
||||
IChatGui chatGui,
|
||||
QuestFunctions questFunctions,
|
||||
QuestController questController,
|
||||
QuestRegistry questRegistry,
|
||||
IDalamudPluginInterface pluginInterface,
|
||||
TerritoryData territoryData,
|
||||
IClientState clientState,
|
||||
UiUtils uiUtils,
|
||||
QuestTooltipComponent questTooltipComponent)
|
||||
: base($"Quest Selection{WindowId}")
|
||||
{
|
||||
_questData = questData;
|
||||
_gameGui = gameGui;
|
||||
_chatGui = chatGui;
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_questController = questController;
|
||||
_questRegistry = questRegistry;
|
||||
_pluginInterface = pluginInterface;
|
||||
@ -82,7 +90,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
{
|
||||
var answers = GameUiController.GetChoices(addonSelectIconString);
|
||||
_offeredQuests = _quests
|
||||
.Where(x => answers.Any(y => GameUiController.GameStringEquals(x.Name, y)))
|
||||
.Where(x => answers.Any(y => GameFunctions.GameStringEquals(x.Name, y)))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
@ -216,9 +224,9 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
|
||||
if (knownQuest != null &&
|
||||
knownQuest.FindSequence(0)?.LastStep()?.InteractionType == EInteractionType.AcceptQuest &&
|
||||
!_gameFunctions.IsQuestAccepted(quest.QuestId) &&
|
||||
!_gameFunctions.IsQuestLocked(quest.QuestId) &&
|
||||
(quest.IsRepeatable || !_gameFunctions.IsQuestAcceptedOrComplete(quest.QuestId)))
|
||||
!_questFunctions.IsQuestAccepted(quest.QuestId) &&
|
||||
!_questFunctions.IsQuestLocked(quest.QuestId) &&
|
||||
(quest.IsRepeatable || !_questFunctions.IsQuestAcceptedOrComplete(quest.QuestId)))
|
||||
{
|
||||
ImGui.BeginDisabled(_questController.NextQuest != null || _questController.SimulatedQuest != null);
|
||||
|
||||
|
@ -56,11 +56,11 @@ internal sealed class QuestValidationWindow : LWindow
|
||||
ImGui.TableNextRow();
|
||||
|
||||
if (ImGui.TableNextColumn())
|
||||
ImGui.TextUnformatted(validationIssue.QuestId?.ToString() ?? string.Empty);
|
||||
ImGui.TextUnformatted(validationIssue.ElementId?.ToString() ?? string.Empty);
|
||||
|
||||
if (ImGui.TableNextColumn())
|
||||
ImGui.TextUnformatted(validationIssue.QuestId != null
|
||||
? _questData.GetQuestInfo(validationIssue.QuestId).Name
|
||||
ImGui.TextUnformatted(validationIssue.ElementId != null
|
||||
? _questData.GetQuestInfo(validationIssue.ElementId).Name
|
||||
: validationIssue.BeastTribe.ToString());
|
||||
|
||||
if (ImGui.TableNextColumn())
|
||||
|
@ -63,6 +63,11 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
|
||||
|
||||
public void SaveWindowConfig() => _pluginInterface.SavePluginConfig(_configuration);
|
||||
|
||||
public override void PreOpenCheck()
|
||||
{
|
||||
IsOpen |= _questController.IsRunning;
|
||||
}
|
||||
|
||||
public override bool DrawConditions()
|
||||
{
|
||||
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null || _clientState.IsPvPExcludingDen)
|
||||
|
@ -4,28 +4,29 @@ using Dalamud.Interface.Colors;
|
||||
using Dalamud.Plugin;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using ImGuiNET;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Windows;
|
||||
|
||||
internal sealed class UiUtils
|
||||
{
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
|
||||
public UiUtils(GameFunctions gameFunctions, IDalamudPluginInterface pluginInterface)
|
||||
public UiUtils(QuestFunctions questFunctions, IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
_gameFunctions = gameFunctions;
|
||||
_questFunctions = questFunctions;
|
||||
_pluginInterface = pluginInterface;
|
||||
}
|
||||
|
||||
public (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(ElementId questElementId)
|
||||
public (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(ElementId elementId)
|
||||
{
|
||||
if (_gameFunctions.IsQuestAccepted(questElementId))
|
||||
if (_questFunctions.IsQuestAccepted(elementId))
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Active");
|
||||
else if (_gameFunctions.IsQuestAcceptedOrComplete(questElementId))
|
||||
else if (_questFunctions.IsQuestAcceptedOrComplete(elementId))
|
||||
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
|
||||
else if (_gameFunctions.IsQuestLocked(questElementId))
|
||||
else if (_questFunctions.IsQuestLocked(elementId))
|
||||
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
|
||||
else
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Available");
|
||||
|
Loading…
Reference in New Issue
Block a user