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,
|
"Y": 257.4255,
|
||||||
"Z": -669.3115
|
"Z": -669.3115
|
||||||
},
|
},
|
||||||
"MinimumAngle": -65,
|
"MinimumAngle": -30,
|
||||||
"MaximumAngle": 5
|
"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(),
|
.AsSyntaxNodeOrToken(),
|
||||||
Assignment(nameof(DialogueChoice.Answer), dialogueChoice.Answer, emptyChoice.Answer)
|
Assignment(nameof(DialogueChoice.Answer), dialogueChoice.Answer, emptyChoice.Answer)
|
||||||
.AsSyntaxNodeOrToken(),
|
.AsSyntaxNodeOrToken(),
|
||||||
|
Assignment(nameof(DialogueChoice.AnswerIsRegularExpression),
|
||||||
|
dialogueChoice.AnswerIsRegularExpression,
|
||||||
|
emptyChoice.AnswerIsRegularExpression)
|
||||||
|
.AsSyntaxNodeOrToken(),
|
||||||
Assignment(nameof(DialogueChoice.DataId), dialogueChoice.DataId, emptyChoice.DataId)
|
Assignment(nameof(DialogueChoice.DataId), dialogueChoice.DataId, emptyChoice.DataId)
|
||||||
.AsSyntaxNodeOrToken()))));
|
.AsSyntaxNodeOrToken()))));
|
||||||
}
|
}
|
||||||
@ -359,6 +363,9 @@ public static class RoslynShortcuts
|
|||||||
.AsSyntaxNodeOrToken(),
|
.AsSyntaxNodeOrToken(),
|
||||||
Assignment(nameof(GatheredItem.Collectability), gatheredItem.Collectability,
|
Assignment(nameof(GatheredItem.Collectability), gatheredItem.Collectability,
|
||||||
emptyItem.Collectability)
|
emptyItem.Collectability)
|
||||||
|
.AsSyntaxNodeOrToken(),
|
||||||
|
Assignment(nameof(GatheredItem.ClassJob), gatheredItem.ClassJob,
|
||||||
|
emptyItem.ClassJob)
|
||||||
.AsSyntaxNodeOrToken()))));
|
.AsSyntaxNodeOrToken()))));
|
||||||
}
|
}
|
||||||
else if (value is GatheringNodeGroup nodeGroup)
|
else if (value is GatheringNodeGroup nodeGroup)
|
||||||
|
@ -25,7 +25,33 @@
|
|||||||
},
|
},
|
||||||
"StopDistance": 5,
|
"StopDistance": 5,
|
||||||
"TerritoryId": 478,
|
"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,
|
"TerritoryId": 478,
|
||||||
"InteractionType": "Interact",
|
"InteractionType": "Interact",
|
||||||
"RequiredGatheredItems": [],
|
"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,
|
"TerritoryId": 613,
|
||||||
"InteractionType": "Interact",
|
"InteractionType": "Interact",
|
||||||
"RequiredGatheredItems": [],
|
"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,
|
"TerritoryId": 635,
|
||||||
"InteractionType": "Interact",
|
"InteractionType": "Interact",
|
||||||
"RequiredGatheredItems": [],
|
"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": [
|
"AethernetShortcut": [
|
||||||
"[Ishgard] Aetheryte Plaza",
|
"[Ishgard] Aetheryte Plaza",
|
||||||
"[Ishgard] Firmament"
|
"[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": [
|
"AethernetShortcut": [
|
||||||
"[Ishgard] Aetheryte Plaza",
|
"[Ishgard] Aetheryte Plaza",
|
||||||
"[Ishgard] Firmament"
|
"[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,
|
"TerritoryId": 820,
|
||||||
"InteractionType": "Interact",
|
"InteractionType": "Interact",
|
||||||
"RequiredGatheredItems": [],
|
"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": [
|
"AethernetShortcut": [
|
||||||
"[Old Sharlayan] Aetheryte Plaza",
|
"[Old Sharlayan] Aetheryte Plaza",
|
||||||
"[Old Sharlayan] The Leveilleur Estate"
|
"[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",
|
"InteractionType": "Interact",
|
||||||
"RequiredGatheredItems": [],
|
"RequiredGatheredItems": [],
|
||||||
"AetheryteShortcut": "Il Mheg - Lydha Lran",
|
"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
|
"Z": -65.14081
|
||||||
},
|
},
|
||||||
"TerritoryId": 956,
|
"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
|
"Z": -68.40625
|
||||||
},
|
},
|
||||||
"TerritoryId": 963,
|
"TerritoryId": 963,
|
||||||
"InteractionType": "AcceptQuest",
|
"InteractionType": "AcceptQuest"
|
||||||
"DialogueChoices": [
|
|
||||||
{
|
|
||||||
"Type": "List",
|
|
||||||
"Prompt": "TEXT_AKTKMM103_04753_Q1_000_000",
|
|
||||||
"Answer": "TEXT_AKTKMM103_04753_A1_000_001"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -101,6 +101,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "What to do at the position",
|
"description": "What to do at the position",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
"None",
|
||||||
"Interact",
|
"Interact",
|
||||||
"WalkTo",
|
"WalkTo",
|
||||||
"AttuneAethernetShard",
|
"AttuneAethernetShard",
|
||||||
|
@ -7,6 +7,7 @@ public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>
|
|||||||
{
|
{
|
||||||
private static readonly Dictionary<EInteractionType, string> Values = new()
|
private static readonly Dictionary<EInteractionType, string> Values = new()
|
||||||
{
|
{
|
||||||
|
{ EInteractionType.None, "None" },
|
||||||
{ EInteractionType.Interact, "Interact" },
|
{ EInteractionType.Interact, "Interact" },
|
||||||
{ EInteractionType.WalkTo, "WalkTo" },
|
{ EInteractionType.WalkTo, "WalkTo" },
|
||||||
{ EInteractionType.AttuneAethernetShard, "AttuneAethernetShard" },
|
{ EInteractionType.AttuneAethernetShard, "AttuneAethernetShard" },
|
||||||
|
@ -16,6 +16,7 @@ public sealed class DialogueChoice
|
|||||||
|
|
||||||
[JsonConverter(typeof(ExcelRefConverter))]
|
[JsonConverter(typeof(ExcelRefConverter))]
|
||||||
public ExcelRef? Answer { get; set; }
|
public ExcelRef? Answer { get; set; }
|
||||||
|
public bool AnswerIsRegularExpression { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If set, only applies when focusing the given target id.
|
/// If set, only applies when focusing the given target id.
|
||||||
|
@ -6,6 +6,7 @@ namespace Questionable.Model.Questing;
|
|||||||
[JsonConverter(typeof(InteractionTypeConverter))]
|
[JsonConverter(typeof(InteractionTypeConverter))]
|
||||||
public enum EInteractionType
|
public enum EInteractionType
|
||||||
{
|
{
|
||||||
|
None,
|
||||||
Interact,
|
Interact,
|
||||||
WalkTo,
|
WalkTo,
|
||||||
AttuneAethernetShard,
|
AttuneAethernetShard,
|
||||||
|
@ -5,4 +5,9 @@ public sealed class GatheredItem
|
|||||||
public uint ItemId { get; set; }
|
public uint ItemId { get; set; }
|
||||||
public int ItemCount { get; set; }
|
public int ItemCount { get; set; }
|
||||||
public ushort Collectability { 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 Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.CombatModules;
|
using Questionable.Controller.CombatModules;
|
||||||
using Questionable.Controller.Utils;
|
using Questionable.Controller.Utils;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Controller;
|
namespace Questionable.Controller;
|
||||||
@ -26,7 +27,7 @@ internal sealed class CombatController : IDisposable
|
|||||||
private readonly IObjectTable _objectTable;
|
private readonly IObjectTable _objectTable;
|
||||||
private readonly ICondition _condition;
|
private readonly ICondition _condition;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly ILogger<CombatController> _logger;
|
private readonly ILogger<CombatController> _logger;
|
||||||
|
|
||||||
private CurrentFight? _currentFight;
|
private CurrentFight? _currentFight;
|
||||||
@ -39,7 +40,7 @@ internal sealed class CombatController : IDisposable
|
|||||||
IObjectTable objectTable,
|
IObjectTable objectTable,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
GameFunctions gameFunctions,
|
QuestFunctions questFunctions,
|
||||||
ILogger<CombatController> logger)
|
ILogger<CombatController> logger)
|
||||||
{
|
{
|
||||||
_combatModules = combatModules.ToList();
|
_combatModules = combatModules.ToList();
|
||||||
@ -48,7 +49,7 @@ internal sealed class CombatController : IDisposable
|
|||||||
_objectTable = objectTable;
|
_objectTable = objectTable;
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_gameFunctions = gameFunctions;
|
_questFunctions = questFunctions;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_clientState.TerritoryChanged += TerritoryChanged;
|
_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,
|
if (questWork != null && QuestWorkUtils.MatchesQuestWork(condition.CompletionQuestVariablesFlags,
|
||||||
questWork.Value))
|
questWork.Value))
|
||||||
{
|
{
|
||||||
@ -303,7 +304,7 @@ internal sealed class CombatController : IDisposable
|
|||||||
|
|
||||||
public sealed class CombatData
|
public sealed class CombatData
|
||||||
{
|
{
|
||||||
public required ElementId QuestElementId { get; init; }
|
public required ElementId ElementId { get; init; }
|
||||||
public required EEnemySpawnType SpawnType { get; init; }
|
public required EEnemySpawnType SpawnType { get; init; }
|
||||||
public required List<uint> KillEnemyDataIds { get; init; }
|
public required List<uint> KillEnemyDataIds { get; init; }
|
||||||
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
|
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
|
||||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
|||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using Questionable.Windows;
|
using Questionable.Windows;
|
||||||
@ -23,7 +24,7 @@ internal sealed class CommandHandler : IDisposable
|
|||||||
private readonly QuestWindow _questWindow;
|
private readonly QuestWindow _questWindow;
|
||||||
private readonly QuestSelectionWindow _questSelectionWindow;
|
private readonly QuestSelectionWindow _questSelectionWindow;
|
||||||
private readonly ITargetManager _targetManager;
|
private readonly ITargetManager _targetManager;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
|
|
||||||
public CommandHandler(
|
public CommandHandler(
|
||||||
ICommandManager commandManager,
|
ICommandManager commandManager,
|
||||||
@ -37,7 +38,7 @@ internal sealed class CommandHandler : IDisposable
|
|||||||
QuestWindow questWindow,
|
QuestWindow questWindow,
|
||||||
QuestSelectionWindow questSelectionWindow,
|
QuestSelectionWindow questSelectionWindow,
|
||||||
ITargetManager targetManager,
|
ITargetManager targetManager,
|
||||||
GameFunctions gameFunctions)
|
QuestFunctions questFunctions)
|
||||||
{
|
{
|
||||||
_commandManager = commandManager;
|
_commandManager = commandManager;
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
@ -50,7 +51,7 @@ internal sealed class CommandHandler : IDisposable
|
|||||||
_questWindow = questWindow;
|
_questWindow = questWindow;
|
||||||
_questSelectionWindow = questSelectionWindow;
|
_questSelectionWindow = questSelectionWindow;
|
||||||
_targetManager = targetManager;
|
_targetManager = targetManager;
|
||||||
_gameFunctions = gameFunctions;
|
_questFunctions = questFunctions;
|
||||||
|
|
||||||
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
|
_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 (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.");
|
_chatGui.PrintError($"[Questionable] Quest {questId} is locked.");
|
||||||
else if (_questRegistry.TryGetQuest(questId, out Quest? quest))
|
else if (_questRegistry.TryGetQuest(questId, out Quest? quest))
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||||||
using LLib.GameData;
|
using LLib.GameData;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.GameStructs;
|
using Questionable.GameStructs;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -21,6 +22,8 @@ internal sealed class ContextMenuController : IDisposable
|
|||||||
private readonly GatheringData _gatheringData;
|
private readonly GatheringData _gatheringData;
|
||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
private readonly QuestData _questData;
|
private readonly QuestData _questData;
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
private readonly IChatGui _chatGui;
|
private readonly IChatGui _chatGui;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
@ -32,6 +35,8 @@ internal sealed class ContextMenuController : IDisposable
|
|||||||
GatheringData gatheringData,
|
GatheringData gatheringData,
|
||||||
QuestRegistry questRegistry,
|
QuestRegistry questRegistry,
|
||||||
QuestData questData,
|
QuestData questData,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
IGameGui gameGui,
|
IGameGui gameGui,
|
||||||
IChatGui chatGui,
|
IChatGui chatGui,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
@ -42,6 +47,8 @@ internal sealed class ContextMenuController : IDisposable
|
|||||||
_gatheringData = gatheringData;
|
_gatheringData = gatheringData;
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
_questFunctions = questFunctions;
|
||||||
_gameGui = gameGui;
|
_gameGui = gameGui;
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
@ -52,7 +59,7 @@ internal sealed class ContextMenuController : IDisposable
|
|||||||
|
|
||||||
private void MenuOpened(IMenuOpenedArgs args)
|
private void MenuOpened(IMenuOpenedArgs args)
|
||||||
{
|
{
|
||||||
uint itemId = (uint) _gameGui.HoveredItem;
|
uint itemId = (uint)_gameGui.HoveredItem;
|
||||||
if (itemId == 0)
|
if (itemId == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -62,43 +69,66 @@ internal sealed class ContextMenuController : IDisposable
|
|||||||
if (itemId >= 500_000)
|
if (itemId >= 500_000)
|
||||||
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.");
|
_logger.LogInformation("No gathering point found for current job.");
|
||||||
return;
|
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);
|
var agentSatisfactionSupply = AgentSatisfactionSupply.Instance();
|
||||||
int quantityToGather = collectability > 0 ? 6 : int.MaxValue;
|
if (agentSatisfactionSupply->IsAgentActive())
|
||||||
if (collectability == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
{
|
||||||
var agentSatisfactionSupply = AgentSatisfactionSupply.Instance();
|
quantityToGather = Math.Min(agentSatisfactionSupply->RemainingAllowances,
|
||||||
if (agentSatisfactionSupply->IsAgentActive())
|
((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))
|
if (_questRegistry.TryGetQuest(info.QuestId, out Quest? quest))
|
||||||
{
|
{
|
||||||
var step = quest.FindSequence(0)!.FindStep(0)!;
|
var step = quest.FindSequence(0)!.FindStep(0)!;
|
||||||
@ -108,7 +138,8 @@ internal sealed class ContextMenuController : IDisposable
|
|||||||
{
|
{
|
||||||
ItemId = itemId,
|
ItemId = itemId,
|
||||||
ItemCount = quantity,
|
ItemCount = quantity,
|
||||||
Collectability = collectability
|
Collectability = collectability,
|
||||||
|
ClassJob = (uint)classJob,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
_questController.SetGatheringQuest(quest);
|
_questController.SetGatheringQuest(quest);
|
||||||
|
@ -14,6 +14,8 @@ using LLib.GameUI;
|
|||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using Quest = Questionable.Model.Quest;
|
using Quest = Questionable.Model.Quest;
|
||||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||||
@ -25,6 +27,8 @@ internal sealed class GameUiController : IDisposable
|
|||||||
private readonly IAddonLifecycle _addonLifecycle;
|
private readonly IAddonLifecycle _addonLifecycle;
|
||||||
private readonly IDataManager _dataManager;
|
private readonly IDataManager _dataManager;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly QuestFunctions _questFunctions;
|
||||||
|
private readonly ExcelFunctions _excelFunctions;
|
||||||
private readonly QuestController _questController;
|
private readonly QuestController _questController;
|
||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
private readonly QuestData _questData;
|
private readonly QuestData _questData;
|
||||||
@ -33,13 +37,24 @@ internal sealed class GameUiController : IDisposable
|
|||||||
private readonly ILogger<GameUiController> _logger;
|
private readonly ILogger<GameUiController> _logger;
|
||||||
private readonly Regex _returnRegex;
|
private readonly Regex _returnRegex;
|
||||||
|
|
||||||
public GameUiController(IAddonLifecycle addonLifecycle, IDataManager dataManager, GameFunctions gameFunctions,
|
public GameUiController(
|
||||||
QuestController questController, QuestRegistry questRegistry, QuestData questData, IGameGui gameGui,
|
IAddonLifecycle addonLifecycle,
|
||||||
ITargetManager targetManager, IPluginLog pluginLog, ILogger<GameUiController> logger)
|
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;
|
_addonLifecycle = addonLifecycle;
|
||||||
_dataManager = dataManager;
|
_dataManager = dataManager;
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
|
_questFunctions = questFunctions;
|
||||||
|
_excelFunctions = excelFunctions;
|
||||||
_questController = questController;
|
_questController = questController;
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
@ -188,7 +203,7 @@ internal sealed class GameUiController : IDisposable
|
|||||||
{
|
{
|
||||||
// it is possible for this to be a quest selection
|
// it is possible for this to be a quest selection
|
||||||
string questName = quest.Info.Name;
|
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)
|
if (questSelection >= 0)
|
||||||
{
|
{
|
||||||
addonSelectIconString->AtkUnitBase.FireCallbackInt(questSelection);
|
addonSelectIconString->AtkUnitBase.FireCallbackInt(questSelection);
|
||||||
@ -210,7 +225,7 @@ internal sealed class GameUiController : IDisposable
|
|||||||
private int? HandleListChoice(string? actualPrompt, List<string?> answers, bool checkAllSteps)
|
private int? HandleListChoice(string? actualPrompt, List<string?> answers, bool checkAllSteps)
|
||||||
{
|
{
|
||||||
List<DialogueChoiceInfo> dialogueChoices = [];
|
List<DialogueChoiceInfo> dialogueChoices = [];
|
||||||
var currentQuest = _questController.SimulatedQuest ?? _questController.StartedQuest;
|
var currentQuest = _questController.SimulatedQuest ?? _questController.GatheringQuest ?? _questController.StartedQuest;
|
||||||
if (currentQuest != null)
|
if (currentQuest != null)
|
||||||
{
|
{
|
||||||
var quest = currentQuest.Quest;
|
var quest = currentQuest.Quest;
|
||||||
@ -260,9 +275,9 @@ internal sealed class GameUiController : IDisposable
|
|||||||
var target = _targetManager.Target;
|
var target = _targetManager.Target;
|
||||||
if (target != null)
|
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))
|
_questRegistry.TryGetQuest(questInfo.QuestId, out Quest? knownQuest))
|
||||||
{
|
{
|
||||||
var questChoices = knownQuest.FindSequence(0)?.Steps
|
var questChoices = knownQuest.FindSequence(0)?.Steps
|
||||||
@ -300,8 +315,10 @@ internal sealed class GameUiController : IDisposable
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
|
string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt, false)
|
||||||
string? excelAnswer = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer);
|
?.GetString();
|
||||||
|
StringOrRegex? excelAnswer = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer,
|
||||||
|
dialogueChoice.AnswerIsRegularExpression);
|
||||||
|
|
||||||
if (actualPrompt == null && !string.IsNullOrEmpty(excelPrompt))
|
if (actualPrompt == null && !string.IsNullOrEmpty(excelPrompt))
|
||||||
{
|
{
|
||||||
@ -309,7 +326,8 @@ internal sealed class GameUiController : IDisposable
|
|||||||
continue;
|
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}",
|
_logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}, actualPrompt: {ActualPrompt}",
|
||||||
excelPrompt, actualPrompt);
|
excelPrompt, actualPrompt);
|
||||||
@ -320,10 +338,22 @@ internal sealed class GameUiController : IDisposable
|
|||||||
{
|
{
|
||||||
_logger.LogTrace("Checking if {ActualAnswer} == {ExpectedAnswer}",
|
_logger.LogTrace("Checking if {ActualAnswer} == {ExpectedAnswer}",
|
||||||
answers[i], excelAnswer);
|
answers[i], excelAnswer);
|
||||||
if (GameStringEquals(answers[i], excelAnswer))
|
if (IsMatch(answers[i], excelAnswer))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}'",
|
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}'",
|
||||||
i, answers[i], actualPrompt);
|
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;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,13 +363,24 @@ internal sealed class GameUiController : IDisposable
|
|||||||
return null;
|
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)
|
private int? HandleInstanceListChoice(string? actualPrompt)
|
||||||
{
|
{
|
||||||
if (!_questController.IsRunning)
|
if (!_questController.IsRunning)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
string? expectedPrompt = _gameFunctions.GetDialogueTextByRowId("Addon", 2090);
|
string? expectedPrompt = _excelFunctions.GetDialogueTextByRowId("Addon", 2090, false).GetString();
|
||||||
if (GameStringEquals(actualPrompt, expectedPrompt))
|
if (GameFunctions.GameStringEquals(actualPrompt, expectedPrompt))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Selecting no prefered instance as answer for '{Prompt}'", actualPrompt);
|
_logger.LogInformation("Selecting no prefered instance as answer for '{Prompt}'", actualPrompt);
|
||||||
return 0; // any instance
|
return 0; // any instance
|
||||||
@ -419,8 +460,9 @@ internal sealed class GameUiController : IDisposable
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
|
string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt, false)
|
||||||
if (excelPrompt == null || !GameStringEquals(actualPrompt, excelPrompt))
|
?.GetString();
|
||||||
|
if (excelPrompt == null || !GameFunctions.GameStringEquals(actualPrompt, excelPrompt))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}, actualPrompt: {ActualPrompt}",
|
_logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}, actualPrompt: {ActualPrompt}",
|
||||||
excelPrompt, actualPrompt);
|
excelPrompt, actualPrompt);
|
||||||
@ -506,13 +548,13 @@ internal sealed class GameUiController : IDisposable
|
|||||||
string? excelName = entry.Name?.ToString();
|
string? excelName = entry.Name?.ToString();
|
||||||
string? excelQuestion = entry.Question?.ToString();
|
string? excelQuestion = entry.Question?.ToString();
|
||||||
|
|
||||||
if (excelQuestion != null && GameStringEquals(excelQuestion, actualPrompt))
|
if (excelQuestion != null && GameFunctions.GameStringEquals(excelQuestion, actualPrompt))
|
||||||
{
|
{
|
||||||
warpId = entry.RowId;
|
warpId = entry.RowId;
|
||||||
warpText = excelQuestion;
|
warpText = excelQuestion;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (excelName != null && GameStringEquals(excelName, actualPrompt))
|
else if (excelName != null && GameFunctions.GameStringEquals(excelName, actualPrompt))
|
||||||
{
|
{
|
||||||
warpId = entry.RowId;
|
warpId = entry.RowId;
|
||||||
warpText = excelName;
|
warpText = excelName;
|
||||||
@ -642,31 +684,17 @@ internal sealed class GameUiController : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private StringOrRegex? ResolveReference(Quest quest, string? excelSheet, ExcelRef? excelRef, bool isRegExp)
|
||||||
/// 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)
|
|
||||||
{
|
{
|
||||||
if (excelRef == null)
|
if (excelRef == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (excelRef.Type == ExcelRef.EType.Key)
|
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)
|
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)
|
else if (excelRef.Type == ExcelRef.EType.RawString)
|
||||||
return excelRef.AsRawString();
|
return new StringOrRegex(excelRef.AsRawString());
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using Questionable.Controller.Steps.Gathering;
|
|||||||
using Questionable.Controller.Steps.Interactions;
|
using Questionable.Controller.Steps.Interactions;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.GatheringPaths;
|
using Questionable.GatheringPaths;
|
||||||
using Questionable.Model.Gathering;
|
using Questionable.Model.Gathering;
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.NavigationOverrides;
|
using Questionable.Controller.NavigationOverrides;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Common;
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Common.Converter;
|
using Questionable.Model.Common.Converter;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
|
|
||||||
namespace Questionable.Controller;
|
namespace Questionable.Controller;
|
||||||
|
@ -5,10 +5,12 @@ using Dalamud.Game.ClientState.Conditions;
|
|||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
@ -18,6 +20,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
{
|
{
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly MovementController _movementController;
|
private readonly MovementController _movementController;
|
||||||
private readonly CombatController _combatController;
|
private readonly CombatController _combatController;
|
||||||
private readonly GatheringController _gatheringController;
|
private readonly GatheringController _gatheringController;
|
||||||
@ -46,6 +49,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
public QuestController(
|
public QuestController(
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
CombatController combatController,
|
CombatController combatController,
|
||||||
GatheringController gatheringController,
|
GatheringController gatheringController,
|
||||||
@ -61,6 +65,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
{
|
{
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
|
_questFunctions = questFunctions;
|
||||||
_movementController = movementController;
|
_movementController = movementController;
|
||||||
_combatController = combatController;
|
_combatController = combatController;
|
||||||
_gatheringController = gatheringController;
|
_gatheringController = gatheringController;
|
||||||
@ -78,7 +83,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
{
|
{
|
||||||
if (_simulatedQuest != null)
|
if (_simulatedQuest != null)
|
||||||
return (_simulatedQuest, ECurrentQuestType.Simulated);
|
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);
|
return (_nextQuest, ECurrentQuestType.Next);
|
||||||
else if (_gatheringQuest != null)
|
else if (_gatheringQuest != null)
|
||||||
return (_gatheringQuest, ECurrentQuestType.Gathering);
|
return (_gatheringQuest, ECurrentQuestType.Gathering);
|
||||||
@ -177,7 +182,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
UpdateCurrentTask();
|
UpdateCurrentTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCurrentQuest()
|
private unsafe void UpdateCurrentQuest()
|
||||||
{
|
{
|
||||||
lock (_progressLock)
|
lock (_progressLock)
|
||||||
{
|
{
|
||||||
@ -188,9 +193,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
// if the quest is accepted, we no longer track it
|
// if the quest is accepted, we no longer track it
|
||||||
bool canUseNextQuest;
|
bool canUseNextQuest;
|
||||||
if (_nextQuest.Quest.Info.IsRepeatable)
|
if (_nextQuest.Quest.Info.IsRepeatable)
|
||||||
canUseNextQuest = !_gameFunctions.IsQuestAccepted(_nextQuest.Quest.Id);
|
canUseNextQuest = !_questFunctions.IsQuestAccepted(_nextQuest.Quest.Id);
|
||||||
else
|
else
|
||||||
canUseNextQuest = !_gameFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.Id);
|
canUseNextQuest = !_questFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.Id);
|
||||||
|
|
||||||
if (!canUseNextQuest)
|
if (!canUseNextQuest)
|
||||||
{
|
{
|
||||||
@ -207,7 +212,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
currentSequence = _simulatedQuest.Sequence;
|
currentSequence = _simulatedQuest.Sequence;
|
||||||
questToRun = _simulatedQuest;
|
questToRun = _simulatedQuest;
|
||||||
}
|
}
|
||||||
else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
|
else if (_nextQuest != null && _questFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
|
||||||
{
|
{
|
||||||
questToRun = _nextQuest;
|
questToRun = _nextQuest;
|
||||||
currentSequence = _nextQuest.Sequence; // by definition, this should always be 0
|
currentSequence = _nextQuest.Sequence; // by definition, this should always be 0
|
||||||
@ -226,11 +231,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
_taskQueue.Count == 0 &&
|
_taskQueue.Count == 0 &&
|
||||||
_automationType == EAutomationType.Automatic)
|
_automationType == EAutomationType.Automatic)
|
||||||
ExecuteNextStep(_automationType);
|
ExecuteNextStep(_automationType);
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
(ElementId? currentQuestId, currentSequence) = _gameFunctions.GetCurrentQuest();
|
(ElementId? currentQuestId, currentSequence) = _questFunctions.GetCurrentQuest();
|
||||||
if (currentQuestId == null || currentQuestId.Value == 0)
|
if (currentQuestId == null || currentQuestId.Value == 0)
|
||||||
{
|
{
|
||||||
if (_startedQuest != null)
|
if (_startedQuest != null)
|
||||||
@ -276,7 +280,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_gameFunctions.IsOccupied())
|
if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(questToRun.Quest))
|
||||||
{
|
{
|
||||||
DebugState = "Occupied";
|
DebugState = "Occupied";
|
||||||
return;
|
return;
|
||||||
@ -303,7 +307,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
if (questToRun.Sequence != currentSequence)
|
if (questToRun.Sequence != currentSequence)
|
||||||
{
|
{
|
||||||
questToRun.SetSequence(currentSequence);
|
questToRun.SetSequence(currentSequence);
|
||||||
Stop($"New sequence {questToRun == _startedQuest}/{_gameFunctions.GetCurrentQuestInternal()}",
|
Stop($"New sequence {questToRun == _startedQuest}/{_questFunctions.GetCurrentQuestInternal()}",
|
||||||
continueIfAutomatic: true);
|
continueIfAutomatic: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +459,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
|
|
||||||
protected override void UpdateCurrentTask()
|
protected override void UpdateCurrentTask()
|
||||||
{
|
{
|
||||||
if (_gameFunctions.IsOccupied())
|
if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(CurrentQuest?.Quest))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
base.UpdateCurrentTask();
|
base.UpdateCurrentTask();
|
||||||
@ -469,7 +473,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
|
|
||||||
protected override void OnNextStep(ILastTask task)
|
protected override void OnNextStep(ILastTask task)
|
||||||
{
|
{
|
||||||
IncreaseStepCount(task.QuestElementId, task.Sequence, true);
|
IncreaseStepCount(task.ElementId, task.Sequence, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExecuteNextStep(EAutomationType automatic)
|
public void ExecuteNextStep(EAutomationType automatic)
|
||||||
@ -484,7 +488,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
if (CurrentQuest == null || seq == null || step == null)
|
if (CurrentQuest == null || seq == null || step == null)
|
||||||
{
|
{
|
||||||
if (CurrentQuestDetails?.Progress.Quest.Id is SatisfactionSupplyNpcId &&
|
if (CurrentQuestDetails?.Progress.Quest.Id is SatisfactionSupplyNpcId &&
|
||||||
CurrentQuestDetails?.Progress.Sequence == 0 &&
|
CurrentQuestDetails?.Progress.Sequence == 1 &&
|
||||||
CurrentQuestDetails?.Progress.Step == 255 &&
|
CurrentQuestDetails?.Progress.Step == 255 &&
|
||||||
CurrentQuestDetails?.Type == ECurrentQuestType.Gathering)
|
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)
|
lock (_progressLock)
|
||||||
{
|
{
|
||||||
@ -609,13 +613,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
if (_taskQueue.Count == 0)
|
if (_taskQueue.Count == 0)
|
||||||
{
|
{
|
||||||
Stop("Skip");
|
Stop("Skip");
|
||||||
IncreaseStepCount(questQuestElementId, currentQuestSequence);
|
IncreaseStepCount(elementId, currentQuestSequence);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Stop("SkipNx");
|
Stop("SkipNx");
|
||||||
IncreaseStepCount(questQuestElementId, currentQuestSequence);
|
IncreaseStepCount(elementId, currentQuestSequence);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -657,7 +661,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
foreach (var id in priorityQuests)
|
foreach (var id in priorityQuests)
|
||||||
{
|
{
|
||||||
var questId = new QuestId(id);
|
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);
|
SetNextQuest(quest);
|
||||||
_chatGui.Print(
|
_chatGui.Print(
|
||||||
|
@ -5,6 +5,7 @@ using Dalamud.Plugin.Services;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Common;
|
namespace Questionable.Controller.Steps.Common;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
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 NextQuestId { get; set; } = null!;
|
||||||
public ElementId CurrentQuestElementId { 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;
|
NextQuestId = nextQuestId;
|
||||||
CurrentQuestElementId = currentQuestElementId;
|
CurrentQuestId = currentQuestId;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Start()
|
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);
|
questController.SetNextQuest(quest);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogInformation("Next quest with id {QuestId} not found", NextQuestElementId);
|
logger.LogInformation("Next quest with id {QuestId} not found", NextQuestId);
|
||||||
questController.SetNextQuest(null);
|
questController.SetNextQuest(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +61,6 @@ internal static class NextQuest
|
|||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.TaskComplete;
|
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.Game.ClientState.Conditions;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Functions;
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Common;
|
namespace Questionable.Controller.Steps.Common;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
|
|||||||
using LLib.GameData;
|
using LLib.GameData;
|
||||||
using LLib.GameUI;
|
using LLib.GameUI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model.Gathering;
|
using Questionable.Model.Gathering;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ using GatheringPathRenderer;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model.Gathering;
|
using Questionable.Model.Gathering;
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.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
|
internal interface ILastTask : ITask
|
||||||
{
|
{
|
||||||
public ElementId QuestElementId { get; }
|
public ElementId ElementId { get; }
|
||||||
public int Sequence { get; }
|
public int Sequence { get; }
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.Game;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ using Dalamud.Plugin.Services;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Common;
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Common;
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.Controller.Utils;
|
using Questionable.Controller.Utils;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
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 bool _isLastStep;
|
||||||
private CombatController.CombatData _combatData = null!;
|
private CombatController.CombatData _combatData = null!;
|
||||||
private IList<QuestWorkValue?> _completionQuestVariableFlags = 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)
|
IList<QuestWorkValue?> completionQuestVariablesFlags, IList<ComplexCombatData> complexCombatData)
|
||||||
{
|
{
|
||||||
_isLastStep = isLastStep;
|
_isLastStep = isLastStep;
|
||||||
_combatData = new CombatController.CombatData
|
_combatData = new CombatController.CombatData
|
||||||
{
|
{
|
||||||
QuestElementId = questElementId,
|
ElementId = elementId,
|
||||||
SpawnType = enemySpawnType,
|
SpawnType = enemySpawnType,
|
||||||
KillEnemyDataIds = killEnemyDataIds.ToList(),
|
KillEnemyDataIds = killEnemyDataIds.ToList(),
|
||||||
ComplexCombatDatas = complexCombatData.ToList(),
|
ComplexCombatDatas = complexCombatData.ToList(),
|
||||||
@ -107,9 +108,9 @@ internal static class Combat
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
// if our quest step has any completion flags, we need to check if they are set
|
// 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)
|
if (questWork == null)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using Dalamud.Plugin.Services;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Say
|
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)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -20,7 +21,7 @@ internal static class Say
|
|||||||
ArgumentNullException.ThrowIfNull(step.ChatMessage);
|
ArgumentNullException.ThrowIfNull(step.ChatMessage);
|
||||||
|
|
||||||
string? excelString =
|
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);
|
ArgumentNullException.ThrowIfNull(excelString);
|
||||||
|
|
||||||
var unmount = serviceProvider.GetRequiredService<UnmountTask>();
|
var unmount = serviceProvider.GetRequiredService<UnmountTask>();
|
||||||
|
@ -12,6 +12,7 @@ using Questionable.Controller.Steps.Common;
|
|||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.Controller.Utils;
|
using Questionable.Controller.Utils;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Common;
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -103,7 +104,7 @@ internal static class UseItem
|
|||||||
yield return serviceProvider.GetRequiredService<AetheryteShortcut.UseAetheryteShortcut>()
|
yield return serviceProvider.GetRequiredService<AetheryteShortcut.UseAetheryteShortcut>()
|
||||||
.With(null, EAetheryteLocation.Limsa, territoryId);
|
.With(null, EAetheryteLocation.Limsa, territoryId);
|
||||||
yield return serviceProvider.GetRequiredService<AethernetShortcut.UseAethernetShortcut>()
|
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<WaitAtEnd.WaitDelay>();
|
||||||
yield return serviceProvider.GetRequiredService<Move.MoveInternal>()
|
yield return serviceProvider.GetRequiredService<Move.MoveInternal>()
|
||||||
.With(territoryId, destination, dataId: npcId, sprint: false);
|
.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 bool _usedItem;
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
@ -144,7 +145,7 @@ internal static class UseItem
|
|||||||
{
|
{
|
||||||
if (QuestId is QuestId questId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
if (QuestId is QuestId questId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
||||||
{
|
{
|
||||||
QuestWork? questWork = gameFunctions.GetQuestEx(questId);
|
QuestWork? questWork = questFunctions.GetQuestEx(questId);
|
||||||
if (questWork != null &&
|
if (questWork != null &&
|
||||||
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value))
|
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value))
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
@ -196,11 +197,9 @@ internal static class UseItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal sealed class UseOnGround(GameFunctions gameFunctions, ICondition condition, ILogger<UseOnGround> logger)
|
internal sealed class UseOnGround(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<UseOnGround> logger)
|
||||||
: UseItemBase(gameFunctions, condition, logger)
|
: UseItemBase(questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
private readonly GameFunctions _gameFunctions = gameFunctions;
|
|
||||||
|
|
||||||
public uint DataId { get; set; }
|
public uint DataId { get; set; }
|
||||||
|
|
||||||
public ITask With(ElementId? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
public ITask With(ElementId? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
||||||
@ -212,19 +211,18 @@ internal static class UseItem
|
|||||||
return this;
|
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})";
|
public override string ToString() => $"UseItem({ItemId} on ground at {DataId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseOnPosition(
|
internal sealed class UseOnPosition(
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<UseOnPosition> logger)
|
ILogger<UseOnPosition> logger)
|
||||||
: UseItemBase(gameFunctions, condition, logger)
|
: UseItemBase(questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
private readonly GameFunctions _gameFunctions = gameFunctions;
|
|
||||||
|
|
||||||
public Vector3 Position { get; set; }
|
public Vector3 Position { get; set; }
|
||||||
|
|
||||||
public ITask With(ElementId? questId, Vector3 position, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
public ITask With(ElementId? questId, Vector3 position, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
||||||
@ -236,17 +234,15 @@ internal static class UseItem
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool UseItem() => _gameFunctions.UseItemOnPosition(Position, ItemId);
|
protected override bool UseItem() => gameFunctions.UseItemOnPosition(Position, ItemId);
|
||||||
|
|
||||||
public override string ToString() =>
|
public override string ToString() =>
|
||||||
$"UseItem({ItemId} on ground at {Position.ToString("G", CultureInfo.InvariantCulture)})";
|
$"UseItem({ItemId} on ground at {Position.ToString("G", CultureInfo.InvariantCulture)})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseOnObject(GameFunctions gameFunctions, ICondition condition, ILogger<UseOnObject> logger)
|
internal sealed class UseOnObject(QuestFunctions questFunctions, GameFunctions gameFunctions, ICondition condition, ILogger<UseOnObject> logger)
|
||||||
: UseItemBase(gameFunctions, condition, logger)
|
: UseItemBase(questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
private readonly GameFunctions _gameFunctions = gameFunctions;
|
|
||||||
|
|
||||||
public uint DataId { get; set; }
|
public uint DataId { get; set; }
|
||||||
|
|
||||||
public ITask With(ElementId? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
public ITask With(ElementId? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
@ -260,16 +256,14 @@ internal static class UseItem
|
|||||||
return this;
|
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})";
|
public override string ToString() => $"UseItem({ItemId} on {DataId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class Use(GameFunctions gameFunctions, ICondition condition, ILogger<Use> logger)
|
internal sealed class Use(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<Use> logger)
|
||||||
: UseItemBase(gameFunctions, condition, logger)
|
: UseItemBase(questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
private readonly GameFunctions _gameFunctions = gameFunctions;
|
|
||||||
|
|
||||||
public ITask With(ElementId? questId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
public ITask With(ElementId? questId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
||||||
{
|
{
|
||||||
QuestId = questId;
|
QuestId = questId;
|
||||||
@ -278,7 +272,7 @@ internal static class UseItem
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool UseItem() => _gameFunctions.UseItem(ItemId);
|
protected override bool UseItem() => gameFunctions.UseItem(ItemId);
|
||||||
|
|
||||||
public override string ToString() => $"UseItem({ItemId})";
|
public override string ToString() => $"UseItem({ItemId})";
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Common;
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Common.Converter;
|
using Questionable.Model.Common.Converter;
|
||||||
|
@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Common;
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
@ -28,14 +28,25 @@ internal static class GatheringRequiredItems
|
|||||||
{
|
{
|
||||||
foreach (var requiredGatheredItems in step.RequiredGatheredItems)
|
foreach (var requiredGatheredItems in step.RequiredGatheredItems)
|
||||||
{
|
{
|
||||||
if (!gatheringData.TryGetGatheringPointId(requiredGatheredItems.ItemId,
|
EClassJob currentClassJob = (EClassJob)clientState.LocalPlayer!.ClassJob.Id;
|
||||||
(EClassJob)clientState.LocalPlayer!.ClassJob.Id, out var gatheringPointId))
|
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}");
|
throw new TaskException($"No gathering point found for item {requiredGatheredItems.ItemId}");
|
||||||
|
|
||||||
if (!AssemblyGatheringLocationLoader.GetLocations()
|
if (!AssemblyGatheringLocationLoader.GetLocations()
|
||||||
.TryGetValue(gatheringPointId, out GatheringRoot? gatheringRoot))
|
.TryGetValue(gatheringPointId, out GatheringRoot? gatheringRoot))
|
||||||
throw new TaskException($"No path found for gathering point {gatheringPointId}");
|
throw new TaskException($"No path found for gathering point {gatheringPointId}");
|
||||||
|
|
||||||
|
if (classJob != currentClassJob)
|
||||||
|
{
|
||||||
|
yield return serviceProvider.GetRequiredService<SwitchClassJob>()
|
||||||
|
.With(classJob);
|
||||||
|
}
|
||||||
|
|
||||||
if (HasRequiredItems(requiredGatheredItems))
|
if (HasRequiredItems(requiredGatheredItems))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -71,7 +82,8 @@ internal static class GatheringRequiredItems
|
|||||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||||
return inventoryManager != null &&
|
return inventoryManager != null &&
|
||||||
inventoryManager->GetInventoryItemCount(requiredGatheredItems.ItemId,
|
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)
|
public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
|
@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Questionable.Controller.NavigationOverrides;
|
using Questionable.Controller.NavigationOverrides;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Utils;
|
using Questionable.Controller.Utils;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Common;
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -41,17 +42,18 @@ internal static class SkipCondition
|
|||||||
internal sealed class CheckSkip(
|
internal sealed class CheckSkip(
|
||||||
ILogger<CheckSkip> logger,
|
ILogger<CheckSkip> logger,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
IClientState clientState) : ITask
|
IClientState clientState) : ITask
|
||||||
{
|
{
|
||||||
public QuestStep Step { get; set; } = null!;
|
public QuestStep Step { get; set; } = null!;
|
||||||
public SkipStepConditions SkipConditions { 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;
|
Step = step;
|
||||||
SkipConditions = skipConditions;
|
SkipConditions = skipConditions;
|
||||||
QuestElementId = questElementId;
|
ElementId = elementId;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,14 +97,14 @@ internal static class SkipCondition
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.QuestsCompleted.Count > 0 &&
|
if (SkipConditions.QuestsCompleted.Count > 0 &&
|
||||||
SkipConditions.QuestsCompleted.All(gameFunctions.IsQuestComplete))
|
SkipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, all prequisite quests are complete");
|
logger.LogInformation("Skipping step, all prequisite quests are complete");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.QuestsAccepted.Count > 0 &&
|
if (SkipConditions.QuestsAccepted.Count > 0 &&
|
||||||
SkipConditions.QuestsAccepted.All(gameFunctions.IsQuestAccepted))
|
SkipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, all prequisite quests are accepted");
|
logger.LogInformation("Skipping step, all prequisite quests are accepted");
|
||||||
return true;
|
return true;
|
||||||
@ -156,9 +158,9 @@ internal static class SkipCondition
|
|||||||
return true;
|
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.HasCompletionFlags(Step.CompletionQuestVariablesFlags) && questWork != null)
|
||||||
{
|
{
|
||||||
if (QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value))
|
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");
|
logger.LogInformation("Skipping step, as we have already picked up the relevant quest");
|
||||||
return true;
|
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");
|
logger.LogInformation("Skipping step, as we have already completed the relevant quest");
|
||||||
return true;
|
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.Steps.Common;
|
||||||
using Questionable.Controller.Utils;
|
using Questionable.Controller.Utils;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
@ -160,7 +161,7 @@ internal static class WaitAtEnd
|
|||||||
public override string ToString() => "Wait(next step or sequence)";
|
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 QuestId Quest { get; set; } = null!;
|
||||||
public QuestStep Step { get; set; } = null!;
|
public QuestStep Step { get; set; } = null!;
|
||||||
@ -178,7 +179,7 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
QuestWork? questWork = gameFunctions.GetQuestEx(Quest);
|
QuestWork? questWork = questFunctions.GetQuestEx(Quest);
|
||||||
return questWork != null &&
|
return questWork != null &&
|
||||||
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value)
|
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
@ -214,13 +215,13 @@ internal static class WaitAtEnd
|
|||||||
$"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})";
|
$"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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,21 +229,21 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
return gameFunctions.IsQuestAccepted(QuestElementId)
|
return questFunctions.IsQuestAccepted(ElementId)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: 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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,15 +251,15 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
public ETaskResult Update()
|
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 int Sequence { get; } = sequence;
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
@ -270,7 +271,7 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
internal sealed class EndAutomation : ILastTask
|
internal sealed class EndAutomation : ILastTask
|
||||||
{
|
{
|
||||||
public ElementId QuestElementId => throw new InvalidOperationException();
|
public ElementId ElementId => throw new InvalidOperationException();
|
||||||
public int Sequence => throw new InvalidOperationException();
|
public int Sequence => throw new InvalidOperationException();
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
@ -16,7 +16,7 @@ using Lumina.Excel.GeneratedSheets;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable;
|
namespace Questionable.Functions;
|
||||||
|
|
||||||
internal sealed unsafe class ChatFunctions
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Memory;
|
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
|
||||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
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.Client.UI.Agent;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using LLib.GameUI;
|
using LLib.GameUI;
|
||||||
using Lumina.Excel.CustomSheets;
|
|
||||||
using Lumina.Excel.GeneratedSheets2;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller;
|
|
||||||
using Questionable.Data;
|
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Common;
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using Action = Lumina.Excel.GeneratedSheets2.Action;
|
using Action = Lumina.Excel.GeneratedSheets2.Action;
|
||||||
using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
|
using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
|
||||||
using ContentFinderCondition = Lumina.Excel.GeneratedSheets.ContentFinderCondition;
|
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 ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||||
using Quest = Questionable.Model.Quest;
|
using Quest = Questionable.Model.Quest;
|
||||||
using TerritoryType = Lumina.Excel.GeneratedSheets.TerritoryType;
|
using TerritoryType = Lumina.Excel.GeneratedSheets.TerritoryType;
|
||||||
|
|
||||||
namespace Questionable;
|
namespace Questionable.Functions;
|
||||||
|
|
||||||
internal sealed unsafe class GameFunctions
|
internal sealed unsafe class GameFunctions
|
||||||
{
|
{
|
||||||
private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet;
|
private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet;
|
||||||
private readonly ReadOnlyDictionary<uint, ushort> _contentFinderConditionToContentId;
|
private readonly ReadOnlyDictionary<uint, ushort> _contentFinderConditionToContentId;
|
||||||
|
|
||||||
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly IDataManager _dataManager;
|
private readonly IDataManager _dataManager;
|
||||||
private readonly IObjectTable _objectTable;
|
private readonly IObjectTable _objectTable;
|
||||||
private readonly ITargetManager _targetManager;
|
private readonly ITargetManager _targetManager;
|
||||||
private readonly ICondition _condition;
|
private readonly ICondition _condition;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly QuestRegistry _questRegistry;
|
|
||||||
private readonly QuestData _questData;
|
|
||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
private readonly ILogger<GameFunctions> _logger;
|
private readonly ILogger<GameFunctions> _logger;
|
||||||
|
|
||||||
public GameFunctions(IDataManager dataManager,
|
public GameFunctions(
|
||||||
|
QuestFunctions questFunctions,
|
||||||
|
IDataManager dataManager,
|
||||||
IObjectTable objectTable,
|
IObjectTable objectTable,
|
||||||
ITargetManager targetManager,
|
ITargetManager targetManager,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
QuestRegistry questRegistry,
|
|
||||||
QuestData questData,
|
|
||||||
IGameGui gameGui,
|
IGameGui gameGui,
|
||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
ILogger<GameFunctions> logger)
|
ILogger<GameFunctions> logger)
|
||||||
{
|
{
|
||||||
|
_questFunctions = questFunctions;
|
||||||
_dataManager = dataManager;
|
_dataManager = dataManager;
|
||||||
_objectTable = objectTable;
|
_objectTable = objectTable;
|
||||||
_targetManager = targetManager;
|
_targetManager = targetManager;
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_questRegistry = questRegistry;
|
|
||||||
_questData = questData;
|
|
||||||
_gameGui = gameGui;
|
_gameGui = gameGui;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -89,289 +76,6 @@ internal sealed unsafe class GameFunctions
|
|||||||
|
|
||||||
public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
|
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)
|
public bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
|
||||||
{
|
{
|
||||||
subIndex = 0;
|
subIndex = 0;
|
||||||
@ -383,7 +87,7 @@ internal sealed unsafe class GameFunctions
|
|||||||
public bool IsAetheryteUnlocked(EAetheryteLocation aetheryteLocation)
|
public bool IsAetheryteUnlocked(EAetheryteLocation aetheryteLocation)
|
||||||
{
|
{
|
||||||
if (aetheryteLocation == EAetheryteLocation.IshgardFirmament)
|
if (aetheryteLocation == EAetheryteLocation.IshgardFirmament)
|
||||||
return IsQuestComplete(new QuestId(3672));
|
return _questFunctions.IsQuestComplete(new QuestId(3672));
|
||||||
return IsAetheryteUnlocked((uint)aetheryteLocation, out _);
|
return IsAetheryteUnlocked((uint)aetheryteLocation, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,7 +135,7 @@ internal sealed unsafe class GameFunctions
|
|||||||
if (_configuration.Advanced.NeverFly)
|
if (_configuration.Advanced.NeverFly)
|
||||||
return false;
|
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);
|
BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
|
||||||
if (battleChara != null && battleChara->Mount.MountId == 198) // special quest amaro, not the normal one
|
if (battleChara != null && battleChara->Mount.MountId == 198) // special quest amaro, not the normal one
|
||||||
@ -718,61 +422,18 @@ internal sealed unsafe class GameFunctions
|
|||||||
contentFinderConditionId);
|
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)
|
if (a == null)
|
||||||
{
|
return b == 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}";
|
if (b == null)
|
||||||
}
|
return false;
|
||||||
|
|
||||||
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
|
return a.ReplaceLineEndings().Replace('\u2013', '-') == b.ReplaceLineEndings().Replace('\u2013', '-');
|
||||||
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}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsOccupied()
|
public bool IsOccupied()
|
||||||
@ -792,15 +453,28 @@ internal sealed unsafe class GameFunctions
|
|||||||
_condition[ConditionFlag.Jumping61] || _condition[ConditionFlag.Gathering42];
|
_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()
|
public bool IsLoadingScreenVisible()
|
||||||
{
|
{
|
||||||
return _gameGui.TryGetAddonByName("FadeMiddle", out AtkUnitBase* fade) &&
|
return _gameGui.TryGetAddonByName("FadeMiddle", out AtkUnitBase* fade) &&
|
||||||
LAddon.IsAddonReady(fade) &&
|
LAddon.IsAddonReady(fade) &&
|
||||||
fade->IsVisible;
|
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">
|
<Project Sdk="Dalamud.NET.Sdk/10.0.0">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>2.1</Version>
|
<Version>2.2</Version>
|
||||||
<OutputPath>dist</OutputPath>
|
<OutputPath>dist</OutputPath>
|
||||||
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||||
<Platforms>x64</Platforms>
|
<Platforms>x64</Platforms>
|
||||||
|
@ -17,6 +17,7 @@ using Questionable.Controller.Steps.Gathering;
|
|||||||
using Questionable.Controller.Steps.Interactions;
|
using Questionable.Controller.Steps.Interactions;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Validation;
|
using Questionable.Validation;
|
||||||
using Questionable.Validation.Validators;
|
using Questionable.Validation.Validators;
|
||||||
using Questionable.Windows;
|
using Questionable.Windows;
|
||||||
@ -47,50 +48,58 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
IContextMenu contextMenu)
|
IContextMenu contextMenu)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(pluginInterface);
|
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();
|
AddBasicFunctionsAndData(serviceCollection);
|
||||||
serviceCollection.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace)
|
AddTaskFactories(serviceCollection);
|
||||||
.ClearProviders()
|
AddControllers(serviceCollection);
|
||||||
.AddDalamudLogger(pluginLog, t => t[(t.LastIndexOf('.') + 1)..]));
|
AddWindows(serviceCollection);
|
||||||
serviceCollection.AddSingleton<IDalamudPlugin>(this);
|
AddQuestValidators(serviceCollection);
|
||||||
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);
|
serviceCollection.AddSingleton<CommandHandler>();
|
||||||
AddTaskFactories(serviceCollection);
|
serviceCollection.AddSingleton<DalamudInitializer>();
|
||||||
AddControllers(serviceCollection);
|
|
||||||
AddWindows(serviceCollection);
|
|
||||||
AddQuestValidators(serviceCollection);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<CommandHandler>();
|
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
serviceCollection.AddSingleton<DalamudInitializer>();
|
Initialize(_serviceProvider);
|
||||||
|
}
|
||||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
catch (Exception)
|
||||||
_serviceProvider.GetRequiredService<QuestRegistry>().Reload();
|
{
|
||||||
_serviceProvider.GetRequiredService<CommandHandler>();
|
chatGui.PrintError("Unable to load plugin, check /xllog for details", "Questionable");
|
||||||
_serviceProvider.GetRequiredService<ContextMenuController>();
|
throw;
|
||||||
_serviceProvider.GetRequiredService<DalamudInitializer>();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddBasicFunctionsAndData(ServiceCollection serviceCollection)
|
private static void AddBasicFunctionsAndData(ServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
|
serviceCollection.AddSingleton<ExcelFunctions>();
|
||||||
serviceCollection.AddSingleton<GameFunctions>();
|
serviceCollection.AddSingleton<GameFunctions>();
|
||||||
serviceCollection.AddSingleton<ChatFunctions>();
|
serviceCollection.AddSingleton<ChatFunctions>();
|
||||||
|
serviceCollection.AddSingleton<QuestFunctions>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<AetherCurrentData>();
|
serviceCollection.AddSingleton<AetherCurrentData>();
|
||||||
serviceCollection.AddSingleton<AetheryteData>();
|
serviceCollection.AddSingleton<AetheryteData>();
|
||||||
serviceCollection.AddSingleton<GatheringData>();
|
serviceCollection.AddSingleton<GatheringData>();
|
||||||
@ -110,6 +119,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
serviceCollection.AddTransient<MoveToLandingLocation>();
|
serviceCollection.AddTransient<MoveToLandingLocation>();
|
||||||
serviceCollection.AddTransient<DoGather>();
|
serviceCollection.AddTransient<DoGather>();
|
||||||
serviceCollection.AddTransient<DoGatherCollectable>();
|
serviceCollection.AddTransient<DoGatherCollectable>();
|
||||||
|
serviceCollection.AddTransient<SwitchClassJob>();
|
||||||
|
|
||||||
// task factories
|
// task factories
|
||||||
serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
|
serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
|
||||||
@ -135,6 +145,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();
|
serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();
|
||||||
serviceCollection.AddTaskWithFactory<UseItem.Factory, UseItem.UseOnGround, UseItem.UseOnObject, UseItem.Use, UseItem.UseOnPosition>();
|
serviceCollection.AddTaskWithFactory<UseItem.Factory, UseItem.UseOnGround, UseItem.UseOnObject, UseItem.Use, UseItem.UseOnPosition>();
|
||||||
serviceCollection.AddTaskWithFactory<EquipItem.Factory, EquipItem.DoEquip>();
|
serviceCollection.AddTaskWithFactory<EquipItem.Factory, EquipItem.DoEquip>();
|
||||||
|
serviceCollection.AddTaskWithFactory<TurnInDelivery.Factory, TurnInDelivery.SatisfactionSupplyTurnIn>();
|
||||||
serviceCollection
|
serviceCollection
|
||||||
.AddTaskWithFactory<SinglePlayerDuty.Factory, SinglePlayerDuty.DisableYesAlready,
|
.AddTaskWithFactory<SinglePlayerDuty.Factory, SinglePlayerDuty.DisableYesAlready,
|
||||||
SinglePlayerDuty.RestoreYesAlready>();
|
SinglePlayerDuty.RestoreYesAlready>();
|
||||||
@ -192,10 +203,19 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
serviceCollection.AddSingleton<IQuestValidator, NextQuestValidator>();
|
serviceCollection.AddSingleton<IQuestValidator, NextQuestValidator>();
|
||||||
serviceCollection.AddSingleton<IQuestValidator, CompletionFlagsValidator>();
|
serviceCollection.AddSingleton<IQuestValidator, CompletionFlagsValidator>();
|
||||||
serviceCollection.AddSingleton<IQuestValidator, AethernetShortcutValidator>();
|
serviceCollection.AddSingleton<IQuestValidator, AethernetShortcutValidator>();
|
||||||
|
serviceCollection.AddSingleton<IQuestValidator, DialogueChoiceValidator>();
|
||||||
serviceCollection.AddSingleton<JsonSchemaValidator>();
|
serviceCollection.AddSingleton<JsonSchemaValidator>();
|
||||||
serviceCollection.AddSingleton<IQuestValidator>(sp => sp.GetRequiredService<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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_serviceProvider?.Dispose();
|
_serviceProvider?.Dispose();
|
||||||
|
@ -16,4 +16,5 @@ public enum EIssueType
|
|||||||
UnexpectedAcceptQuestStep,
|
UnexpectedAcceptQuestStep,
|
||||||
UnexpectedCompleteQuestStep,
|
UnexpectedCompleteQuestStep,
|
||||||
InvalidAethernetShortcut,
|
InvalidAethernetShortcut,
|
||||||
|
InvalidExcelRef,
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ internal sealed class QuestValidator
|
|||||||
: LogLevel.Information;
|
: LogLevel.Information;
|
||||||
_logger.Log(level,
|
_logger.Log(level,
|
||||||
"Validation failed: {QuestId} ({QuestName}) / {QuestSequence} / {QuestStep} - {Description}",
|
"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)
|
if (issue.Type == EIssueType.QuestDisabled && quest.Info.BeastTribe != EBeastTribe.None)
|
||||||
{
|
{
|
||||||
disabledTribeQuests.TryAdd(quest.Info.BeastTribe, 0);
|
disabledTribeQuests.TryAdd(quest.Info.BeastTribe, 0);
|
||||||
@ -70,12 +70,12 @@ internal sealed class QuestValidator
|
|||||||
|
|
||||||
var disabledQuests = issues
|
var disabledQuests = issues
|
||||||
.Where(x => x.Type == EIssueType.QuestDisabled)
|
.Where(x => x.Type == EIssueType.QuestDisabled)
|
||||||
.Select(x => x.QuestId)
|
.Select(x => x.ElementId)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
_validationIssues = issues
|
_validationIssues = issues
|
||||||
.Where(x => !disabledQuests.Contains(x.QuestId) || x.Type == EIssueType.QuestDisabled)
|
.Where(x => !disabledQuests.Contains(x.ElementId) || x.Type == EIssueType.QuestDisabled)
|
||||||
.OrderBy(x => x.QuestId)
|
.OrderBy(x => x.ElementId)
|
||||||
.ThenBy(x => x.Sequence)
|
.ThenBy(x => x.Sequence)
|
||||||
.ThenBy(x => x.Step)
|
.ThenBy(x => x.Step)
|
||||||
.ThenBy(x => x.Description)
|
.ThenBy(x => x.Description)
|
||||||
@ -95,7 +95,7 @@ internal sealed class QuestValidator
|
|||||||
.OrderBy(x => x.Key)
|
.OrderBy(x => x.Key)
|
||||||
.Select(x => new ValidationIssue
|
.Select(x => new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = null,
|
ElementId = null,
|
||||||
Sequence = null,
|
Sequence = null,
|
||||||
Step = null,
|
Step = null,
|
||||||
BeastTribe = x.Key,
|
BeastTribe = x.Key,
|
||||||
|
@ -5,7 +5,7 @@ namespace Questionable.Validation;
|
|||||||
|
|
||||||
internal sealed record ValidationIssue
|
internal sealed record ValidationIssue
|
||||||
{
|
{
|
||||||
public required ElementId? QuestId { get; init; }
|
public required ElementId? ElementId { get; init; }
|
||||||
public required byte? Sequence { get; init; }
|
public required byte? Sequence { get; init; }
|
||||||
public required int? Step { get; init; }
|
public required int? Step { get; init; }
|
||||||
public EBeastTribe BeastTribe { get; init; } = EBeastTribe.None;
|
public EBeastTribe BeastTribe { get; init; } = EBeastTribe.None;
|
||||||
|
@ -24,7 +24,7 @@ internal sealed class AethernetShortcutValidator : IQuestValidator
|
|||||||
.Cast<ValidationIssue>();
|
.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)
|
if (aethernetShortcut == null)
|
||||||
return null;
|
return null;
|
||||||
@ -35,7 +35,7 @@ internal sealed class AethernetShortcutValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
return new ValidationIssue
|
return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = questElementId,
|
ElementId = elementId,
|
||||||
Sequence = (byte)sequenceNo,
|
Sequence = (byte)sequenceNo,
|
||||||
Step = stepId,
|
Step = stepId,
|
||||||
Type = EIssueType.InvalidAethernetShortcut,
|
Type = EIssueType.InvalidAethernetShortcut,
|
||||||
|
@ -18,7 +18,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
yield return new ValidationIssue
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = 0,
|
Sequence = 0,
|
||||||
Step = null,
|
Step = null,
|
||||||
Type = EIssueType.MissingSequence0,
|
Type = EIssueType.MissingSequence0,
|
||||||
@ -37,7 +37,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
|||||||
|
|
||||||
yield return new ValidationIssue
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = (byte)sequence.Sequence,
|
Sequence = (byte)sequence.Sequence,
|
||||||
Step = null,
|
Step = null,
|
||||||
Type = EIssueType.InstantQuestWithMultipleSteps,
|
Type = EIssueType.InstantQuestWithMultipleSteps,
|
||||||
@ -73,7 +73,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
return new ValidationIssue
|
return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = (byte)sequenceNo,
|
Sequence = (byte)sequenceNo,
|
||||||
Step = null,
|
Step = null,
|
||||||
Type = EIssueType.MissingSequence,
|
Type = EIssueType.MissingSequence,
|
||||||
@ -85,7 +85,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
return new ValidationIssue
|
return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = (byte)sequenceNo,
|
Sequence = (byte)sequenceNo,
|
||||||
Step = null,
|
Step = null,
|
||||||
Type = EIssueType.DuplicateSequence,
|
Type = EIssueType.DuplicateSequence,
|
||||||
|
@ -45,7 +45,7 @@ internal sealed class CompletionFlagsValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
yield return new ValidationIssue
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = (byte)sequence.Sequence,
|
Sequence = (byte)sequence.Sequence,
|
||||||
Step = i,
|
Step = i,
|
||||||
Type = EIssueType.DuplicateCompletionFlags,
|
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
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = null,
|
Sequence = null,
|
||||||
Step = null,
|
Step = null,
|
||||||
Type = EIssueType.InvalidJsonSchema,
|
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();
|
public void Reset() => _questNodes.Clear();
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ internal sealed class NextQuestValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
yield return new ValidationIssue
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = (byte)invalidNextQuest.Sequence.Sequence,
|
Sequence = (byte)invalidNextQuest.Sequence.Sequence,
|
||||||
Step = invalidNextQuest.StepId,
|
Step = invalidNextQuest.StepId,
|
||||||
Type = EIssueType.InvalidNextQuestId,
|
Type = EIssueType.InvalidNextQuestId,
|
||||||
|
@ -11,7 +11,7 @@ internal sealed class QuestDisabledValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
yield return new ValidationIssue
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = null,
|
Sequence = null,
|
||||||
Step = null,
|
Step = null,
|
||||||
Type = EIssueType.QuestDisabled,
|
Type = EIssueType.QuestDisabled,
|
||||||
|
@ -21,7 +21,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
yield return new ValidationIssue
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = (byte)accept.Sequence.Sequence,
|
Sequence = (byte)accept.Sequence.Sequence,
|
||||||
Step = accept.StepId,
|
Step = accept.StepId,
|
||||||
Type = EIssueType.UnexpectedAcceptQuestStep,
|
Type = EIssueType.UnexpectedAcceptQuestStep,
|
||||||
@ -35,7 +35,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
yield return new ValidationIssue
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = 0,
|
Sequence = 0,
|
||||||
Step = null,
|
Step = null,
|
||||||
Type = EIssueType.MissingQuestAccept,
|
Type = EIssueType.MissingQuestAccept,
|
||||||
@ -53,7 +53,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
yield return new ValidationIssue
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = (byte)complete.Sequence.Sequence,
|
Sequence = (byte)complete.Sequence.Sequence,
|
||||||
Step = complete.StepId,
|
Step = complete.StepId,
|
||||||
Type = EIssueType.UnexpectedCompleteQuestStep,
|
Type = EIssueType.UnexpectedCompleteQuestStep,
|
||||||
@ -67,7 +67,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
|||||||
{
|
{
|
||||||
yield return new ValidationIssue
|
yield return new ValidationIssue
|
||||||
{
|
{
|
||||||
QuestId = quest.Id,
|
ElementId = quest.Id,
|
||||||
Sequence = 255,
|
Sequence = 255,
|
||||||
Step = null,
|
Step = null,
|
||||||
Type = EIssueType.MissingQuestComplete,
|
Type = EIssueType.MissingQuestComplete,
|
||||||
|
@ -12,6 +12,7 @@ using ImGuiNET;
|
|||||||
using LLib.ImGui;
|
using LLib.ImGui;
|
||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Windows.QuestComponents;
|
using Questionable.Windows.QuestComponents;
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
|||||||
{
|
{
|
||||||
private readonly JournalData _journalData;
|
private readonly JournalData _journalData;
|
||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly UiUtils _uiUtils;
|
private readonly UiUtils _uiUtils;
|
||||||
private readonly QuestTooltipComponent _questTooltipComponent;
|
private readonly QuestTooltipComponent _questTooltipComponent;
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
@ -37,7 +38,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
|||||||
|
|
||||||
public JournalProgressWindow(JournalData journalData,
|
public JournalProgressWindow(JournalData journalData,
|
||||||
QuestRegistry questRegistry,
|
QuestRegistry questRegistry,
|
||||||
GameFunctions gameFunctions,
|
QuestFunctions questFunctions,
|
||||||
UiUtils uiUtils,
|
UiUtils uiUtils,
|
||||||
QuestTooltipComponent questTooltipComponent,
|
QuestTooltipComponent questTooltipComponent,
|
||||||
IDalamudPluginInterface pluginInterface,
|
IDalamudPluginInterface pluginInterface,
|
||||||
@ -47,7 +48,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
|||||||
{
|
{
|
||||||
_journalData = journalData;
|
_journalData = journalData;
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
_gameFunctions = gameFunctions;
|
_questFunctions = questFunctions;
|
||||||
_uiUtils = uiUtils;
|
_uiUtils = uiUtils;
|
||||||
_questTooltipComponent = questTooltipComponent;
|
_questTooltipComponent = questTooltipComponent;
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
@ -327,7 +328,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
|||||||
{
|
{
|
||||||
int available = genre.Quests.Count(x =>
|
int available = genre.Quests.Count(x =>
|
||||||
_questRegistry.TryGetQuest(x.QuestId, out var quest) && !quest.Root.Disabled);
|
_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);
|
_genreCounts[genre] = (available, completed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ using Dalamud.Interface.Utility.Raii;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Common.Math;
|
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Windows.QuestComponents;
|
namespace Questionable.Windows.QuestComponents;
|
||||||
@ -18,26 +19,26 @@ internal sealed class ARealmRebornComponent
|
|||||||
private static readonly QuestId[] RequiredAllianceRaidQuests =
|
private static readonly QuestId[] RequiredAllianceRaidQuests =
|
||||||
[new(1709), new(1200), new(1201), new(1202), new(1203), new(1474), new(494), new(495)];
|
[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 QuestData _questData;
|
||||||
private readonly TerritoryData _territoryData;
|
private readonly TerritoryData _territoryData;
|
||||||
private readonly UiUtils _uiUtils;
|
private readonly UiUtils _uiUtils;
|
||||||
|
|
||||||
public ARealmRebornComponent(GameFunctions gameFunctions, QuestData questData, TerritoryData territoryData,
|
public ARealmRebornComponent(QuestFunctions questFunctions, QuestData questData, TerritoryData territoryData,
|
||||||
UiUtils uiUtils)
|
UiUtils uiUtils)
|
||||||
{
|
{
|
||||||
_gameFunctions = gameFunctions;
|
_questFunctions = questFunctions;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
_territoryData = territoryData;
|
_territoryData = territoryData;
|
||||||
_uiUtils = uiUtils;
|
_uiUtils = uiUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShouldDraw => !_gameFunctions.IsQuestAcceptedOrComplete(ATimeForEveryPurpose) &&
|
public bool ShouldDraw => !_questFunctions.IsQuestAcceptedOrComplete(ATimeForEveryPurpose) &&
|
||||||
_gameFunctions.IsQuestComplete(TheUltimateWeapon);
|
_questFunctions.IsQuestComplete(TheUltimateWeapon);
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
if (!_gameFunctions.IsQuestAcceptedOrComplete(GoodIntentions))
|
if (!_questFunctions.IsQuestAcceptedOrComplete(GoodIntentions))
|
||||||
DrawPrimals();
|
DrawPrimals();
|
||||||
|
|
||||||
DrawAllianceRaids();
|
DrawAllianceRaids();
|
||||||
@ -63,7 +64,7 @@ internal sealed class ARealmRebornComponent
|
|||||||
|
|
||||||
private void DrawAllianceRaids()
|
private void DrawAllianceRaids()
|
||||||
{
|
{
|
||||||
bool complete = _gameFunctions.IsQuestComplete(RequiredAllianceRaidQuests.Last());
|
bool complete = _questFunctions.IsQuestComplete(RequiredAllianceRaidQuests.Last());
|
||||||
bool hover = _uiUtils.ChecklistItem("Crystal Tower Raids", complete);
|
bool hover = _uiUtils.ChecklistItem("Crystal Tower Raids", complete);
|
||||||
if (complete || !hover)
|
if (complete || !hover)
|
||||||
return;
|
return;
|
||||||
|
@ -13,6 +13,7 @@ using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
|||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Windows.QuestComponents;
|
namespace Questionable.Windows.QuestComponents;
|
||||||
@ -24,6 +25,7 @@ internal sealed class ActiveQuestComponent
|
|||||||
private readonly CombatController _combatController;
|
private readonly CombatController _combatController;
|
||||||
private readonly GatheringController _gatheringController;
|
private readonly GatheringController _gatheringController;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly ICommandManager _commandManager;
|
private readonly ICommandManager _commandManager;
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
@ -36,6 +38,7 @@ internal sealed class ActiveQuestComponent
|
|||||||
CombatController combatController,
|
CombatController combatController,
|
||||||
GatheringController gatheringController,
|
GatheringController gatheringController,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
ICommandManager commandManager,
|
ICommandManager commandManager,
|
||||||
IDalamudPluginInterface pluginInterface,
|
IDalamudPluginInterface pluginInterface,
|
||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
@ -47,6 +50,7 @@ internal sealed class ActiveQuestComponent
|
|||||||
_combatController = combatController;
|
_combatController = combatController;
|
||||||
_gatheringController = gatheringController;
|
_gatheringController = gatheringController;
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
|
_questFunctions = questFunctions;
|
||||||
_commandManager = commandManager;
|
_commandManager = commandManager;
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
@ -116,6 +120,12 @@ internal sealed class ActiveQuestComponent
|
|||||||
ImGui.TextUnformatted(
|
ImGui.TextUnformatted(
|
||||||
$"Simulated Quest: {Shorten(currentQuest.Quest.Info.Name)} / {currentQuest.Sequence} / {currentQuest.Step}");
|
$"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
|
else
|
||||||
{
|
{
|
||||||
var startedQuest = _questController.StartedQuest;
|
var startedQuest = _questController.StartedQuest;
|
||||||
@ -154,7 +164,7 @@ internal sealed class ActiveQuestComponent
|
|||||||
if (currentQuest.Quest.Id is not QuestId questId)
|
if (currentQuest.Quest.Id is not QuestId questId)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var questWork = _gameFunctions.GetQuestEx(questId);
|
var questWork = _questFunctions.GetQuestEx(questId);
|
||||||
if (questWork != null)
|
if (questWork != null)
|
||||||
{
|
{
|
||||||
Vector4 color;
|
Vector4 color;
|
||||||
|
@ -15,6 +15,7 @@ using ImGuiNET;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Common;
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -26,6 +27,7 @@ internal sealed class CreationUtilsComponent
|
|||||||
{
|
{
|
||||||
private readonly MovementController _movementController;
|
private readonly MovementController _movementController;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly TerritoryData _territoryData;
|
private readonly TerritoryData _territoryData;
|
||||||
private readonly QuestData _questData;
|
private readonly QuestData _questData;
|
||||||
private readonly QuestSelectionWindow _questSelectionWindow;
|
private readonly QuestSelectionWindow _questSelectionWindow;
|
||||||
@ -35,13 +37,22 @@ internal sealed class CreationUtilsComponent
|
|||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
private readonly ILogger<CreationUtilsComponent> _logger;
|
private readonly ILogger<CreationUtilsComponent> _logger;
|
||||||
|
|
||||||
public CreationUtilsComponent(MovementController movementController, GameFunctions gameFunctions,
|
public CreationUtilsComponent(
|
||||||
TerritoryData territoryData, QuestData questData, QuestSelectionWindow questSelectionWindow,
|
MovementController movementController,
|
||||||
IClientState clientState, ITargetManager targetManager, ICondition condition, IGameGui gameGui,
|
GameFunctions gameFunctions,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
|
TerritoryData territoryData,
|
||||||
|
QuestData questData,
|
||||||
|
QuestSelectionWindow questSelectionWindow,
|
||||||
|
IClientState clientState,
|
||||||
|
ITargetManager targetManager,
|
||||||
|
ICondition condition,
|
||||||
|
IGameGui gameGui,
|
||||||
ILogger<CreationUtilsComponent> logger)
|
ILogger<CreationUtilsComponent> logger)
|
||||||
{
|
{
|
||||||
_movementController = movementController;
|
_movementController = movementController;
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
|
_questFunctions = questFunctions;
|
||||||
_territoryData = territoryData;
|
_territoryData = territoryData;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
_questSelectionWindow = questSelectionWindow;
|
_questSelectionWindow = questSelectionWindow;
|
||||||
@ -65,7 +76,7 @@ internal sealed class CreationUtilsComponent
|
|||||||
ImGui.Text(SeIconChar.BotanistSprout.ToIconString());
|
ImGui.Text(SeIconChar.BotanistSprout.ToIconString());
|
||||||
}
|
}
|
||||||
|
|
||||||
var q = _gameFunctions.GetCurrentQuest();
|
var q = _questFunctions.GetCurrentQuest();
|
||||||
ImGui.Text($"Current Quest: {q.CurrentQuest} → {q.Sequence}");
|
ImGui.Text($"Current Quest: {q.CurrentQuest} → {q.Sequence}");
|
||||||
|
|
||||||
#if false
|
#if false
|
||||||
|
@ -5,6 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
@ -15,20 +16,20 @@ internal sealed class QuestTooltipComponent
|
|||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
private readonly QuestData _questData;
|
private readonly QuestData _questData;
|
||||||
private readonly TerritoryData _territoryData;
|
private readonly TerritoryData _territoryData;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly UiUtils _uiUtils;
|
private readonly UiUtils _uiUtils;
|
||||||
|
|
||||||
public QuestTooltipComponent(
|
public QuestTooltipComponent(
|
||||||
QuestRegistry questRegistry,
|
QuestRegistry questRegistry,
|
||||||
QuestData questData,
|
QuestData questData,
|
||||||
TerritoryData territoryData,
|
TerritoryData territoryData,
|
||||||
GameFunctions gameFunctions,
|
QuestFunctions questFunctions,
|
||||||
UiUtils uiUtils)
|
UiUtils uiUtils)
|
||||||
{
|
{
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
_territoryData = territoryData;
|
_territoryData = territoryData;
|
||||||
_gameFunctions = gameFunctions;
|
_questFunctions = questFunctions;
|
||||||
_uiUtils = uiUtils;
|
_uiUtils = uiUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +162,7 @@ internal sealed class QuestTooltipComponent
|
|||||||
_ => "None",
|
_ => "None",
|
||||||
};
|
};
|
||||||
|
|
||||||
GrandCompany currentGrandCompany = _gameFunctions.GetGrandCompany();
|
GrandCompany currentGrandCompany = ~_questFunctions.GetGrandCompany();
|
||||||
_uiUtils.ChecklistItem($"Grand Company: {gcName}", quest.GrandCompany == currentGrandCompany);
|
_uiUtils.ChecklistItem($"Grand Company: {gcName}", quest.GrandCompany == currentGrandCompany);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
|
using Questionable.Functions;
|
||||||
|
|
||||||
namespace Questionable.Windows.QuestComponents;
|
namespace Questionable.Windows.QuestComponents;
|
||||||
|
|
||||||
|
@ -12,12 +12,12 @@ using Dalamud.Plugin;
|
|||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using LLib.GameUI;
|
using LLib.GameUI;
|
||||||
using LLib.ImGui;
|
using LLib.ImGui;
|
||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using Questionable.Windows.QuestComponents;
|
using Questionable.Windows.QuestComponents;
|
||||||
@ -30,7 +30,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
private readonly QuestData _questData;
|
private readonly QuestData _questData;
|
||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
private readonly IChatGui _chatGui;
|
private readonly IChatGui _chatGui;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly QuestController _questController;
|
private readonly QuestController _questController;
|
||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
@ -43,16 +43,24 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
private List<IQuestInfo> _offeredQuests = [];
|
private List<IQuestInfo> _offeredQuests = [];
|
||||||
private bool _onlyAvailableQuests = true;
|
private bool _onlyAvailableQuests = true;
|
||||||
|
|
||||||
public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions,
|
public QuestSelectionWindow(
|
||||||
QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface,
|
QuestData questData,
|
||||||
TerritoryData territoryData, IClientState clientState, UiUtils uiUtils,
|
IGameGui gameGui,
|
||||||
|
IChatGui chatGui,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
|
QuestController questController,
|
||||||
|
QuestRegistry questRegistry,
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
TerritoryData territoryData,
|
||||||
|
IClientState clientState,
|
||||||
|
UiUtils uiUtils,
|
||||||
QuestTooltipComponent questTooltipComponent)
|
QuestTooltipComponent questTooltipComponent)
|
||||||
: base($"Quest Selection{WindowId}")
|
: base($"Quest Selection{WindowId}")
|
||||||
{
|
{
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
_gameGui = gameGui;
|
_gameGui = gameGui;
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
_gameFunctions = gameFunctions;
|
_questFunctions = questFunctions;
|
||||||
_questController = questController;
|
_questController = questController;
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
@ -82,7 +90,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
{
|
{
|
||||||
var answers = GameUiController.GetChoices(addonSelectIconString);
|
var answers = GameUiController.GetChoices(addonSelectIconString);
|
||||||
_offeredQuests = _quests
|
_offeredQuests = _quests
|
||||||
.Where(x => answers.Any(y => GameUiController.GameStringEquals(x.Name, y)))
|
.Where(x => answers.Any(y => GameFunctions.GameStringEquals(x.Name, y)))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -216,9 +224,9 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
|
|
||||||
if (knownQuest != null &&
|
if (knownQuest != null &&
|
||||||
knownQuest.FindSequence(0)?.LastStep()?.InteractionType == EInteractionType.AcceptQuest &&
|
knownQuest.FindSequence(0)?.LastStep()?.InteractionType == EInteractionType.AcceptQuest &&
|
||||||
!_gameFunctions.IsQuestAccepted(quest.QuestId) &&
|
!_questFunctions.IsQuestAccepted(quest.QuestId) &&
|
||||||
!_gameFunctions.IsQuestLocked(quest.QuestId) &&
|
!_questFunctions.IsQuestLocked(quest.QuestId) &&
|
||||||
(quest.IsRepeatable || !_gameFunctions.IsQuestAcceptedOrComplete(quest.QuestId)))
|
(quest.IsRepeatable || !_questFunctions.IsQuestAcceptedOrComplete(quest.QuestId)))
|
||||||
{
|
{
|
||||||
ImGui.BeginDisabled(_questController.NextQuest != null || _questController.SimulatedQuest != null);
|
ImGui.BeginDisabled(_questController.NextQuest != null || _questController.SimulatedQuest != null);
|
||||||
|
|
||||||
|
@ -56,11 +56,11 @@ internal sealed class QuestValidationWindow : LWindow
|
|||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
|
|
||||||
if (ImGui.TableNextColumn())
|
if (ImGui.TableNextColumn())
|
||||||
ImGui.TextUnformatted(validationIssue.QuestId?.ToString() ?? string.Empty);
|
ImGui.TextUnformatted(validationIssue.ElementId?.ToString() ?? string.Empty);
|
||||||
|
|
||||||
if (ImGui.TableNextColumn())
|
if (ImGui.TableNextColumn())
|
||||||
ImGui.TextUnformatted(validationIssue.QuestId != null
|
ImGui.TextUnformatted(validationIssue.ElementId != null
|
||||||
? _questData.GetQuestInfo(validationIssue.QuestId).Name
|
? _questData.GetQuestInfo(validationIssue.ElementId).Name
|
||||||
: validationIssue.BeastTribe.ToString());
|
: validationIssue.BeastTribe.ToString());
|
||||||
|
|
||||||
if (ImGui.TableNextColumn())
|
if (ImGui.TableNextColumn())
|
||||||
|
@ -63,6 +63,11 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
|
|||||||
|
|
||||||
public void SaveWindowConfig() => _pluginInterface.SavePluginConfig(_configuration);
|
public void SaveWindowConfig() => _pluginInterface.SavePluginConfig(_configuration);
|
||||||
|
|
||||||
|
public override void PreOpenCheck()
|
||||||
|
{
|
||||||
|
IsOpen |= _questController.IsRunning;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool DrawConditions()
|
public override bool DrawConditions()
|
||||||
{
|
{
|
||||||
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null || _clientState.IsPvPExcludingDen)
|
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null || _clientState.IsPvPExcludingDen)
|
||||||
|
@ -4,28 +4,29 @@ using Dalamud.Interface.Colors;
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Windows;
|
namespace Questionable.Windows;
|
||||||
|
|
||||||
internal sealed class UiUtils
|
internal sealed class UiUtils
|
||||||
{
|
{
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
|
|
||||||
public UiUtils(GameFunctions gameFunctions, IDalamudPluginInterface pluginInterface)
|
public UiUtils(QuestFunctions questFunctions, IDalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
_gameFunctions = gameFunctions;
|
_questFunctions = questFunctions;
|
||||||
_pluginInterface = pluginInterface;
|
_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");
|
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Active");
|
||||||
else if (_gameFunctions.IsQuestAcceptedOrComplete(questElementId))
|
else if (_questFunctions.IsQuestAcceptedOrComplete(elementId))
|
||||||
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
|
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
|
||||||
else if (_gameFunctions.IsQuestLocked(questElementId))
|
else if (_questFunctions.IsQuestLocked(elementId))
|
||||||
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
|
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
|
||||||
else
|
else
|
||||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Available");
|
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Available");
|
||||||
|
Loading…
Reference in New Issue
Block a user