Gathering leves proof-of-concept
This commit is contained in:
parent
41abceb89a
commit
a7af485369
0
GatheringPaths/7.x - Dawntrail/Yak T'el/970.md
Normal file
0
GatheringPaths/7.x - Dawntrail/Yak T'el/970.md
Normal file
115
GatheringPaths/7.x - Dawntrail/Yak T'el/970__MIN.json
Normal file
115
GatheringPaths/7.x - Dawntrail/Yak T'el/970__MIN.json
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
|
||||||
|
"Author": "liza",
|
||||||
|
"TerritoryId": 1189,
|
||||||
|
"Groups": [
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 34721,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 663.934,
|
||||||
|
"Y": 25.09505,
|
||||||
|
"Z": -87.81284
|
||||||
|
},
|
||||||
|
"MinimumAngle": -30,
|
||||||
|
"MaximumAngle": 45
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 34722,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 652.5192,
|
||||||
|
"Y": 21.87234,
|
||||||
|
"Z": -111.9597
|
||||||
|
},
|
||||||
|
"MinimumAngle": 195,
|
||||||
|
"MaximumAngle": 310
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 34723,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 605.4673,
|
||||||
|
"Y": 22.40212,
|
||||||
|
"Z": -91.82993
|
||||||
|
},
|
||||||
|
"MinimumAngle": 220,
|
||||||
|
"MaximumAngle": 330
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 34724,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 547.7242,
|
||||||
|
"Y": 17.74087,
|
||||||
|
"Z": -106.2755
|
||||||
|
},
|
||||||
|
"MinimumAngle": 45,
|
||||||
|
"MaximumAngle": 180
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 34725,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 534.3469,
|
||||||
|
"Y": 18.59627,
|
||||||
|
"Z": -78.46846
|
||||||
|
},
|
||||||
|
"MinimumAngle": -20,
|
||||||
|
"MaximumAngle": 55
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 34726,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 485.1973,
|
||||||
|
"Y": 17.44523,
|
||||||
|
"Z": -79.501
|
||||||
|
},
|
||||||
|
"MinimumAngle": -100,
|
||||||
|
"MaximumAngle": 35
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||||
|
"Author": "liza",
|
||||||
|
"QuestSequence": [
|
||||||
|
{
|
||||||
|
"Sequence": 1,
|
||||||
|
"Steps": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 664.32874,
|
||||||
|
"Y": 24.373428,
|
||||||
|
"Z": -85.7219
|
||||||
|
},
|
||||||
|
"TerritoryId": 1189,
|
||||||
|
"InteractionType": "InitiateLeve",
|
||||||
|
"AetheryteShortcut": "Yak T'el - Mamook",
|
||||||
|
"Fly": true,
|
||||||
|
"SkipConditions": {
|
||||||
|
"AetheryteShortcutIf": {
|
||||||
|
"InSameTerritory": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TerritoryId": 1189,
|
||||||
|
"InteractionType": "None",
|
||||||
|
"RequiredGatheredItems": [
|
||||||
|
{
|
||||||
|
"ItemId": 2003552,
|
||||||
|
"AlternativeItemId": 2003553,
|
||||||
|
"ItemCount": 999
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$.0": "41635 → 970"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -121,7 +121,8 @@
|
|||||||
"Dive",
|
"Dive",
|
||||||
"Instruction",
|
"Instruction",
|
||||||
"AcceptQuest",
|
"AcceptQuest",
|
||||||
"CompleteQuest"
|
"CompleteQuest",
|
||||||
|
"InitiateLeve"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Disabled": {
|
"Disabled": {
|
||||||
@ -326,6 +327,10 @@
|
|||||||
"ItemId": {
|
"ItemId": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
"AlternativeItemId": {
|
||||||
|
"description": "For leves that allow you to gather two items with different chance percentage, this is the preferred item if the gathering chance is 100% (after buffs)",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"ItemCount": {
|
"ItemCount": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"exclusiveMinimum": 0
|
"exclusiveMinimum": 0
|
||||||
|
@ -28,5 +28,6 @@ public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>
|
|||||||
{ EInteractionType.Instruction, "Instruction" },
|
{ EInteractionType.Instruction, "Instruction" },
|
||||||
{ EInteractionType.AcceptQuest, "AcceptQuest" },
|
{ EInteractionType.AcceptQuest, "AcceptQuest" },
|
||||||
{ EInteractionType.CompleteQuest, "CompleteQuest" },
|
{ EInteractionType.CompleteQuest, "CompleteQuest" },
|
||||||
|
{ EInteractionType.InitiateLeve, "InitiateLeve" },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,12 @@ public enum EAction
|
|||||||
MeticulousBotanist = 22188,
|
MeticulousBotanist = 22188,
|
||||||
ScrutinyBotanist = 22189,
|
ScrutinyBotanist = 22189,
|
||||||
|
|
||||||
|
SharpVision1 = 235,
|
||||||
|
SharpVision2 = 237,
|
||||||
|
SharpVision3 = 295,
|
||||||
|
FieldMastery1 = 218,
|
||||||
|
FieldMastery2 = 220,
|
||||||
|
FieldMastery3 = 294,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EActionExtensions
|
public static class EActionExtensions
|
||||||
|
@ -32,4 +32,7 @@ public enum EInteractionType
|
|||||||
|
|
||||||
AcceptQuest,
|
AcceptQuest,
|
||||||
CompleteQuest,
|
CompleteQuest,
|
||||||
|
AcceptLeve,
|
||||||
|
InitiateLeve,
|
||||||
|
CompleteLeve,
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
public sealed class GatheredItem
|
public sealed class GatheredItem
|
||||||
{
|
{
|
||||||
public uint ItemId { get; set; }
|
public uint ItemId { get; set; }
|
||||||
|
public uint AlternativeItemId { get; set; }
|
||||||
public int ItemCount { get; set; }
|
public int ItemCount { get; set; }
|
||||||
public ushort Collectability { get; set; }
|
public ushort Collectability { get; set; }
|
||||||
|
|
||||||
|
@ -171,9 +171,8 @@ internal sealed class CombatController : IDisposable
|
|||||||
|
|
||||||
if (QuestWorkUtils.HasCompletionFlags(condition.CompletionQuestVariablesFlags) && _currentFight.Data.ElementId is QuestId questId)
|
if (QuestWorkUtils.HasCompletionFlags(condition.CompletionQuestVariablesFlags) && _currentFight.Data.ElementId is QuestId questId)
|
||||||
{
|
{
|
||||||
var questWork = _questFunctions.GetQuestEx(questId);
|
var questWork = _questFunctions.GetQuestProgressInfo(questId);
|
||||||
if (questWork != null && QuestWorkUtils.MatchesQuestWork(condition.CompletionQuestVariablesFlags,
|
if (questWork != null && QuestWorkUtils.MatchesQuestWork(condition.CompletionQuestVariablesFlags, questWork))
|
||||||
questWork.Value))
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Complex combat condition fulfilled: QuestWork matches");
|
_logger.LogInformation("Complex combat condition fulfilled: QuestWork matches");
|
||||||
_currentFight.Data.CompletedComplexDatas.Add(i);
|
_currentFight.Data.CompletedComplexDatas.Add(i);
|
||||||
|
@ -7,12 +7,17 @@ using Dalamud.Game.Addon.Lifecycle;
|
|||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using LLib;
|
using LLib;
|
||||||
using LLib.GameUI;
|
using LLib.GameUI;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Controller.Steps.Interactions;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
@ -34,6 +39,7 @@ internal sealed class GameUiController : IDisposable
|
|||||||
private readonly QuestData _questData;
|
private readonly QuestData _questData;
|
||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
private readonly ITargetManager _targetManager;
|
private readonly ITargetManager _targetManager;
|
||||||
|
private readonly IFramework _framework;
|
||||||
private readonly ILogger<GameUiController> _logger;
|
private readonly ILogger<GameUiController> _logger;
|
||||||
private readonly Regex _returnRegex;
|
private readonly Regex _returnRegex;
|
||||||
|
|
||||||
@ -48,7 +54,9 @@ internal sealed class GameUiController : IDisposable
|
|||||||
QuestData questData,
|
QuestData questData,
|
||||||
IGameGui gameGui,
|
IGameGui gameGui,
|
||||||
ITargetManager targetManager,
|
ITargetManager targetManager,
|
||||||
IPluginLog pluginLog, ILogger<GameUiController> logger)
|
IFramework framework,
|
||||||
|
IPluginLog pluginLog,
|
||||||
|
ILogger<GameUiController> logger)
|
||||||
{
|
{
|
||||||
_addonLifecycle = addonLifecycle;
|
_addonLifecycle = addonLifecycle;
|
||||||
_dataManager = dataManager;
|
_dataManager = dataManager;
|
||||||
@ -60,6 +68,7 @@ internal sealed class GameUiController : IDisposable
|
|||||||
_questData = questData;
|
_questData = questData;
|
||||||
_gameGui = gameGui;
|
_gameGui = gameGui;
|
||||||
_targetManager = targetManager;
|
_targetManager = targetManager;
|
||||||
|
_framework = framework;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_returnRegex = _dataManager.GetExcelSheet<Addon>()!.GetRow(196)!.GetRegex(addon => addon.Text, pluginLog)!;
|
_returnRegex = _dataManager.GetExcelSheet<Addon>()!.GetRow(196)!.GetRegex(addon => addon.Text, pluginLog)!;
|
||||||
@ -75,6 +84,8 @@ internal sealed class GameUiController : IDisposable
|
|||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "ContentsTutorial", ContentsTutorialPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "ContentsTutorial", ContentsTutorialPostSetup);
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "MultipleHelpWindow", MultipleHelpWindowPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "MultipleHelpWindow", MultipleHelpWindowPostSetup);
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
||||||
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "JournalResult", JournalResultPostSetup);
|
||||||
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GuildLeve", GuildLevePostSetup);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal unsafe void HandleCurrentDialogueChoices()
|
internal unsafe void HandleCurrentDialogueChoices()
|
||||||
@ -225,7 +236,41 @@ 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.GatheringQuest ?? _questController.StartedQuest;
|
|
||||||
|
// levequest choices have some vague sort of priority
|
||||||
|
if (_questController.HasCurrentTaskMatching<Interact.DoInteract>(out var interact) &&
|
||||||
|
interact.Quest != null &&
|
||||||
|
interact.InteractionType is EInteractionType.AcceptLeve or EInteractionType.CompleteLeve)
|
||||||
|
{
|
||||||
|
if (interact.InteractionType == EInteractionType.AcceptLeve)
|
||||||
|
{
|
||||||
|
dialogueChoices.Add(new DialogueChoiceInfo(interact.Quest,
|
||||||
|
new DialogueChoice
|
||||||
|
{
|
||||||
|
Type = EDialogChoiceType.List,
|
||||||
|
ExcelSheet = "leve/GuildleveAssignment",
|
||||||
|
Prompt = new ExcelRef("TEXT_GUILDLEVEASSIGNMENT_SELECT_MENU_TITLE"),
|
||||||
|
Answer = new ExcelRef("TEXT_GUILDLEVEASSIGNMENT_SELECT_MENU_01"),
|
||||||
|
}));
|
||||||
|
interact.InteractionType = EInteractionType.None;
|
||||||
|
}
|
||||||
|
else if (interact.InteractionType == EInteractionType.CompleteLeve)
|
||||||
|
{
|
||||||
|
dialogueChoices.Add(new DialogueChoiceInfo(interact.Quest,
|
||||||
|
new DialogueChoice
|
||||||
|
{
|
||||||
|
Type = EDialogChoiceType.List,
|
||||||
|
ExcelSheet = "leve/GuildleveAssignment",
|
||||||
|
Prompt = new ExcelRef("TEXT_GUILDLEVEASSIGNMENT_SELECT_MENU_TITLE"),
|
||||||
|
Answer = new ExcelRef("TEXT_GUILDLEVEASSIGNMENT_SELECT_MENU_REWARD"),
|
||||||
|
}));
|
||||||
|
interact.InteractionType = EInteractionType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentQuest = _questController.SimulatedQuest ??
|
||||||
|
_questController.GatheringQuest ??
|
||||||
|
_questController.StartedQuest;
|
||||||
if (currentQuest != null)
|
if (currentQuest != null)
|
||||||
{
|
{
|
||||||
var quest = currentQuest.Quest;
|
var quest = currentQuest.Quest;
|
||||||
@ -291,10 +336,30 @@ internal sealed class GameUiController : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_questController.NextQuest == null)
|
||||||
|
{
|
||||||
|
// make sure to always close the leve dialogue
|
||||||
|
if (_questData.GetAllByIssuerDataId(target.DataId).Any(x => x.QuestId is LeveId))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Adding close leve dialogue as option");
|
||||||
|
dialogueChoices.Add(new DialogueChoiceInfo(null,
|
||||||
|
new DialogueChoice
|
||||||
|
{
|
||||||
|
Type = EDialogChoiceType.List,
|
||||||
|
ExcelSheet = "leve/GuildleveAssignment",
|
||||||
|
Prompt = new ExcelRef("TEXT_GUILDLEVEASSIGNMENT_SELECT_MENU_TITLE"),
|
||||||
|
Answer = new ExcelRef("TEXT_GUILDLEVEASSIGNMENT_SELECT_MENU_07"),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dialogueChoices.Count == 0)
|
if (dialogueChoices.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("No dialogue choices to check");
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var (quest, dialogueChoice) in dialogueChoices)
|
foreach (var (quest, dialogueChoice) in dialogueChoices)
|
||||||
{
|
{
|
||||||
@ -344,7 +409,7 @@ internal sealed class GameUiController : IDisposable
|
|||||||
i, answers[i], actualPrompt);
|
i, answers[i], actualPrompt);
|
||||||
|
|
||||||
// ensure we only open the dialog once
|
// ensure we only open the dialog once
|
||||||
if (quest.Id is SatisfactionSupplyNpcId)
|
if (quest?.Id is SatisfactionSupplyNpcId)
|
||||||
{
|
{
|
||||||
if (_questController.GatheringQuest == null ||
|
if (_questController.GatheringQuest == null ||
|
||||||
_questController.GatheringQuest.Sequence == 255)
|
_questController.GatheringQuest.Sequence == 255)
|
||||||
@ -403,6 +468,16 @@ internal sealed class GameUiController : IDisposable
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_logger.LogTrace("Prompt: '{Prompt}'", actualPrompt);
|
_logger.LogTrace("Prompt: '{Prompt}'", actualPrompt);
|
||||||
|
var director = UIState.Instance()->DirectorTodo.Director;
|
||||||
|
if (director != null && director->EventHandlerInfo != null &&
|
||||||
|
director->EventHandlerInfo->EventId.ContentId == EventHandlerType.GatheringLeveDirector &&
|
||||||
|
director->Sequence == 254)
|
||||||
|
{
|
||||||
|
// just close the dialogue for 'do you want to return to next settlement', should prolly be different for
|
||||||
|
// ARR territories
|
||||||
|
addonSelectYesno->AtkUnitBase.FireCallbackInt(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var currentQuest = _questController.StartedQuest;
|
var currentQuest = _questController.StartedQuest;
|
||||||
if (currentQuest != null && CheckQuestYesNo(addonSelectYesno, currentQuest, actualPrompt, checkAllSteps))
|
if (currentQuest != null && CheckQuestYesNo(addonSelectYesno, currentQuest, actualPrompt, checkAllSteps))
|
||||||
@ -437,6 +512,20 @@ internal sealed class GameUiController : IDisposable
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentQuest.Quest.Id is LeveId)
|
||||||
|
{
|
||||||
|
var dialogueChoice = new DialogueChoice
|
||||||
|
{
|
||||||
|
Type = EDialogChoiceType.YesNo,
|
||||||
|
ExcelSheet = "Addon",
|
||||||
|
Prompt = new ExcelRef(608),
|
||||||
|
Yes = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (HandleDefaultYesNo(addonSelectYesno, quest, [dialogueChoice], actualPrompt))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (HandleTravelYesNo(addonSelectYesno, currentQuest, actualPrompt))
|
if (HandleTravelYesNo(addonSelectYesno, currentQuest, actualPrompt))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -515,22 +604,24 @@ internal sealed class GameUiController : IDisposable
|
|||||||
|
|
||||||
QuestStep? step = sequence.FindStep(currentQuest.Step);
|
QuestStep? step = sequence.FindStep(currentQuest.Step);
|
||||||
if (step != null)
|
if (step != null)
|
||||||
_logger.LogTrace("Current step: {CurrentTerritory}, {TargetTerritory}", step.TerritoryId,
|
_logger.LogTrace("FindTargetTerritoryFromQuestStep (current): {CurrentTerritory}, {TargetTerritory}",
|
||||||
|
step.TerritoryId,
|
||||||
step.TargetTerritoryId);
|
step.TargetTerritoryId);
|
||||||
|
|
||||||
if (step == null || step.TargetTerritoryId == null)
|
if (step == null || step.TargetTerritoryId == null)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("TravelYesNo: Checking previous step...");
|
_logger.LogTrace("FindTargetTerritoryFromQuestStep: Checking previous step...");
|
||||||
step = sequence.FindStep(currentQuest.Step == 255 ? (sequence.Steps.Count - 1) : (currentQuest.Step - 1));
|
step = sequence.FindStep(currentQuest.Step == 255 ? (sequence.Steps.Count - 1) : (currentQuest.Step - 1));
|
||||||
|
|
||||||
if (step != null)
|
if (step != null)
|
||||||
_logger.LogTrace("Previous step: {CurrentTerritory}, {TargetTerritory}", step.TerritoryId,
|
_logger.LogTrace("FindTargetTerritoryFromQuestStep (previous): {CurrentTerritory}, {TargetTerritory}",
|
||||||
|
step.TerritoryId,
|
||||||
step.TargetTerritoryId);
|
step.TargetTerritoryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step == null || step.TargetTerritoryId == null)
|
if (step == null || step.TargetTerritoryId == null)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("TravelYesNo: Not found");
|
_logger.LogTrace("FindTargetTerritoryFromQuestStep: Not found");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,7 +775,74 @@ internal sealed class GameUiController : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringOrRegex? ResolveReference(Quest quest, string? excelSheet, ExcelRef? excelRef, bool isRegExp)
|
private unsafe void JournalResultPostSetup(AddonEvent type, AddonArgs args)
|
||||||
|
{
|
||||||
|
if (_questController.IsRunning)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Checking for quest name of journal result");
|
||||||
|
AddonJournalResult* addon = (AddonJournalResult*)args.Addon;
|
||||||
|
|
||||||
|
string questName = addon->AtkTextNode250->NodeText.ToString();
|
||||||
|
if (_questController.CurrentQuest != null &&
|
||||||
|
GameFunctions.GameStringEquals(_questController.CurrentQuest.Quest.Info.Name, questName))
|
||||||
|
addon->FireCallbackInt(0);
|
||||||
|
else
|
||||||
|
addon->FireCallbackInt(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void GuildLevePostSetup(AddonEvent type, AddonArgs args)
|
||||||
|
{
|
||||||
|
var target = _targetManager.Target;
|
||||||
|
if (target == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_questController is { IsRunning: true, NextQuest: { Quest.Id: LeveId } nextQuest } &&
|
||||||
|
_questFunctions.IsReadyToAcceptQuest(nextQuest.Quest.Id))
|
||||||
|
{
|
||||||
|
var addon = (AddonGuildLeve*)args.Addon;
|
||||||
|
/*
|
||||||
|
var atkValues = addon->AtkValues;
|
||||||
|
|
||||||
|
var availableLeves = _questData.GetAllByIssuerDataId(target.DataId);
|
||||||
|
List<(int, IQuestInfo)> offeredLeves = [];
|
||||||
|
for (int i = 0; i <= 20; ++i) // 3 leves per group, 1 label for group
|
||||||
|
{
|
||||||
|
string? leveName = atkValues[626 + i * 2].ReadAtkString();
|
||||||
|
if (leveName == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var questInfo = availableLeves.FirstOrDefault(x => GameFunctions.GameStringEquals(x.Name, leveName));
|
||||||
|
if (questInfo == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
offeredLeves.Add((i, questInfo));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (i, questInfo) in offeredLeves)
|
||||||
|
_logger.LogInformation("Leve {Index} = {Id}, {Name}", i, questInfo.QuestId, questInfo.Name);
|
||||||
|
*/
|
||||||
|
|
||||||
|
_framework.RunOnTick(() =>
|
||||||
|
{
|
||||||
|
_questController.SetPendingQuest(nextQuest);
|
||||||
|
_questController.SetNextQuest(null);
|
||||||
|
|
||||||
|
var agent = UIModule.Instance()->GetAgentModule()->GetAgentByInternalId(AgentId.LeveQuest);
|
||||||
|
var returnValue = stackalloc AtkValue[1];
|
||||||
|
var selectQuest = stackalloc AtkValue[]
|
||||||
|
{
|
||||||
|
new() { Type = ValueType.Int, Int = 3 },
|
||||||
|
new() { Type = ValueType.UInt, UInt = nextQuest.Quest.Id.Value }
|
||||||
|
};
|
||||||
|
agent->ReceiveEvent(returnValue, selectQuest, 2, 0);
|
||||||
|
addon->Close(true);
|
||||||
|
}, TimeSpan.FromMilliseconds(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringOrRegex? ResolveReference(Quest? quest, string? excelSheet, ExcelRef? excelRef, bool isRegExp)
|
||||||
{
|
{
|
||||||
if (excelRef == null)
|
if (excelRef == null)
|
||||||
return null;
|
return null;
|
||||||
@ -701,6 +859,8 @@ internal sealed class GameUiController : IDisposable
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "GuildLeve", GuildLevePostSetup);
|
||||||
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "JournalResult", JournalResultPostSetup);
|
||||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
||||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "MultipleHelpWindow", MultipleHelpWindowPostSetup);
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "MultipleHelpWindow", MultipleHelpWindowPostSetup);
|
||||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "ContentsTutorial", ContentsTutorialPostSetup);
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "ContentsTutorial", ContentsTutorialPostSetup);
|
||||||
@ -714,5 +874,5 @@ internal sealed class GameUiController : IDisposable
|
|||||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed record DialogueChoiceInfo(Quest Quest, DialogueChoice DialogueChoice);
|
private sealed record DialogueChoiceInfo(Quest? Quest, DialogueChoice DialogueChoice);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ using Dalamud.Game.ClientState.Conditions;
|
|||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
@ -17,6 +19,7 @@ using Questionable.External;
|
|||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.GatheringPaths;
|
using Questionable.GatheringPaths;
|
||||||
using Questionable.Model.Gathering;
|
using Questionable.Model.Gathering;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Controller;
|
namespace Questionable.Controller;
|
||||||
|
|
||||||
@ -119,6 +122,16 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
|
|
||||||
var currentNode = _currentRequest.Nodes[_currentRequest.CurrentIndex++ % _currentRequest.Nodes.Count];
|
var currentNode = _currentRequest.Nodes[_currentRequest.CurrentIndex++ % _currentRequest.Nodes.Count];
|
||||||
|
|
||||||
|
var director = UIState.Instance()->DirectorTodo.Director;
|
||||||
|
if (director != null && director->EventHandlerInfo != null &&
|
||||||
|
director->EventHandlerInfo->EventId.ContentId == EventHandlerType.GatheringLeveDirector)
|
||||||
|
{
|
||||||
|
if (director->Sequence == 254)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_taskQueue.Enqueue(new WaitAtEnd.WaitDelay());
|
||||||
|
}
|
||||||
|
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MountTask>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MountTask>()
|
||||||
.With(_currentRequest.Root.TerritoryId, MountTask.EMountIf.Always));
|
.With(_currentRequest.Root.TerritoryId, MountTask.EMountIf.Always));
|
||||||
if (currentNode.Locations.Count > 1)
|
if (currentNode.Locations.Count > 1)
|
||||||
@ -142,7 +155,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MoveToLandingLocation>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MoveToLandingLocation>()
|
||||||
.With(_currentRequest.Root.TerritoryId, currentNode));
|
.With(_currentRequest.Root.TerritoryId, currentNode));
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
|
||||||
.With(currentNode.DataId, true));
|
.With(currentNode.DataId, null, EInteractionType.None, true));
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
|
||||||
.With(_currentRequest.Data, currentNode));
|
.With(_currentRequest.Data, currentNode));
|
||||||
if (_currentRequest.Data.Collectability > 0)
|
if (_currentRequest.Data.Collectability > 0)
|
||||||
@ -195,6 +208,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
public sealed record GatheringRequest(
|
public sealed record GatheringRequest(
|
||||||
GatheringPointId GatheringPointId,
|
GatheringPointId GatheringPointId,
|
||||||
uint ItemId,
|
uint ItemId,
|
||||||
|
uint AlternativeItemId,
|
||||||
int Quantity,
|
int Quantity,
|
||||||
ushort Collectability = 0);
|
ushort Collectability = 0);
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
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;
|
||||||
@ -37,6 +37,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
private QuestProgress? _nextQuest;
|
private QuestProgress? _nextQuest;
|
||||||
private QuestProgress? _simulatedQuest;
|
private QuestProgress? _simulatedQuest;
|
||||||
private QuestProgress? _gatheringQuest;
|
private QuestProgress? _gatheringQuest;
|
||||||
|
private QuestProgress? _pendingQuest;
|
||||||
private EAutomationType _automationType;
|
private EAutomationType _automationType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -101,6 +102,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
public QuestProgress? NextQuest => _nextQuest;
|
public QuestProgress? NextQuest => _nextQuest;
|
||||||
public QuestProgress? GatheringQuest => _gatheringQuest;
|
public QuestProgress? GatheringQuest => _gatheringQuest;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used when accepting leves, as there's a small delay
|
||||||
|
/// </summary>
|
||||||
|
public QuestProgress? PendingQuest => _pendingQuest;
|
||||||
|
|
||||||
public string? DebugState { get; private set; }
|
public string? DebugState { get; private set; }
|
||||||
|
|
||||||
public void Reload()
|
public void Reload()
|
||||||
@ -112,6 +118,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
_startedQuest = null;
|
_startedQuest = null;
|
||||||
_nextQuest = null;
|
_nextQuest = null;
|
||||||
_gatheringQuest = null;
|
_gatheringQuest = null;
|
||||||
|
_pendingQuest = null;
|
||||||
_simulatedQuest = null;
|
_simulatedQuest = null;
|
||||||
_safeAnimationEnd = DateTime.MinValue;
|
_safeAnimationEnd = DateTime.MinValue;
|
||||||
|
|
||||||
@ -188,6 +195,20 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
{
|
{
|
||||||
DebugState = null;
|
DebugState = null;
|
||||||
|
|
||||||
|
if (_pendingQuest != null)
|
||||||
|
{
|
||||||
|
if (!_questFunctions.IsQuestAccepted(_pendingQuest.Quest.Id))
|
||||||
|
{
|
||||||
|
DebugState = $"Waiting for Leve {_pendingQuest.Quest.Id}";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_startedQuest = _pendingQuest;
|
||||||
|
_pendingQuest = null;
|
||||||
|
Stop("Pending quest accepted", continueIfAutomatic: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (_simulatedQuest == null && _nextQuest != null)
|
if (_simulatedQuest == null && _nextQuest != null)
|
||||||
{
|
{
|
||||||
// if the quest is accepted, we no longer track it
|
// if the quest is accepted, we no longer track it
|
||||||
@ -201,6 +222,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Next quest {QuestId} accepted or completed",
|
_logger.LogInformation("Next quest {QuestId} accepted or completed",
|
||||||
_nextQuest.Quest.Id);
|
_nextQuest.Quest.Id);
|
||||||
|
|
||||||
|
// if (_nextQuest.Quest.Id is LeveId)
|
||||||
|
// _startedQuest = _nextQuest;
|
||||||
|
|
||||||
_nextQuest = null;
|
_nextQuest = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,7 +340,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
var sequence = q.FindSequence(questToRun.Sequence);
|
var sequence = q.FindSequence(questToRun.Sequence);
|
||||||
if (sequence == null)
|
if (sequence == null)
|
||||||
{
|
{
|
||||||
DebugState = "Sequence not found";
|
DebugState = $"Sequence {sequence} not found";
|
||||||
Stop("Unknown sequence");
|
Stop("Unknown sequence");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -457,6 +482,12 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
_gatheringQuest = null;
|
_gatheringQuest = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetPendingQuest(QuestProgress? quest)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("PendingQuest: {QuestId}", quest?.Quest.Id);
|
||||||
|
_pendingQuest = quest;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UpdateCurrentTask()
|
protected override void UpdateCurrentTask()
|
||||||
{
|
{
|
||||||
if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(CurrentQuest?.Quest))
|
if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(CurrentQuest?.Quest))
|
||||||
@ -555,8 +586,20 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
return _currentTask == null ? $"- (+{_taskQueue.Count})" : $"{_currentTask} (+{_taskQueue.Count})";
|
return _currentTask == null ? $"- (+{_taskQueue.Count})" : $"{_currentTask} (+{_taskQueue.Count})";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasCurrentTaskMatching<T>() =>
|
public bool HasCurrentTaskMatching<T>([NotNullWhen(true)] out T? task)
|
||||||
_currentTask is T;
|
where T : class, ITask
|
||||||
|
{
|
||||||
|
if (_currentTask is T t)
|
||||||
|
{
|
||||||
|
task = t;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
task = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsRunning => _currentTask != null || _taskQueue.Count > 0;
|
public bool IsRunning => _currentTask != null || _taskQueue.Count > 0;
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@ -26,19 +25,21 @@ internal sealed class QuestRegistry
|
|||||||
private readonly QuestValidator _questValidator;
|
private readonly QuestValidator _questValidator;
|
||||||
private readonly JsonSchemaValidator _jsonSchemaValidator;
|
private readonly JsonSchemaValidator _jsonSchemaValidator;
|
||||||
private readonly ILogger<QuestRegistry> _logger;
|
private readonly ILogger<QuestRegistry> _logger;
|
||||||
private readonly ICallGateProvider<object> _reloadDataIpc;
|
private readonly LeveData _leveData;
|
||||||
|
|
||||||
|
private readonly ICallGateProvider<object> _reloadDataIpc;
|
||||||
private readonly Dictionary<ElementId, Quest> _quests = new();
|
private readonly Dictionary<ElementId, Quest> _quests = new();
|
||||||
|
|
||||||
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
|
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
|
||||||
QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator,
|
QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator,
|
||||||
ILogger<QuestRegistry> logger)
|
ILogger<QuestRegistry> logger, LeveData leveData)
|
||||||
{
|
{
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
_questValidator = questValidator;
|
_questValidator = questValidator;
|
||||||
_jsonSchemaValidator = jsonSchemaValidator;
|
_jsonSchemaValidator = jsonSchemaValidator;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_leveData = leveData;
|
||||||
_reloadDataIpc = _pluginInterface.GetIpcProvider<object>("Questionable.ReloadData");
|
_reloadDataIpc = _pluginInterface.GetIpcProvider<object>("Questionable.ReloadData");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,11 +90,14 @@ internal sealed class QuestRegistry
|
|||||||
|
|
||||||
foreach ((ElementId questId, QuestRoot questRoot) in AssemblyQuestLoader.GetQuests())
|
foreach ((ElementId questId, QuestRoot questRoot) in AssemblyQuestLoader.GetQuests())
|
||||||
{
|
{
|
||||||
|
var questInfo = _questData.GetQuestInfo(questId);
|
||||||
|
if (questInfo is LeveInfo leveInfo)
|
||||||
|
_leveData.AddQuestSteps(leveInfo, questRoot);
|
||||||
Quest quest = new()
|
Quest quest = new()
|
||||||
{
|
{
|
||||||
Id = questId,
|
Id = questId,
|
||||||
Root = questRoot,
|
Root = questRoot,
|
||||||
Info = _questData.GetQuestInfo(questId),
|
Info = questInfo,
|
||||||
ReadOnly = true,
|
ReadOnly = true,
|
||||||
};
|
};
|
||||||
_quests[quest.Id] = quest;
|
_quests[quest.Id] = quest;
|
||||||
@ -143,11 +147,15 @@ internal sealed class QuestRegistry
|
|||||||
var questNode = JsonNode.Parse(stream)!;
|
var questNode = JsonNode.Parse(stream)!;
|
||||||
_jsonSchemaValidator.Enqueue(questId, questNode);
|
_jsonSchemaValidator.Enqueue(questId, questNode);
|
||||||
|
|
||||||
|
var questRoot = questNode.Deserialize<QuestRoot>()!;
|
||||||
|
var questInfo = _questData.GetQuestInfo(questId);
|
||||||
|
if (questInfo is LeveInfo leveInfo)
|
||||||
|
_leveData.AddQuestSteps(leveInfo, questRoot);
|
||||||
Quest quest = new Quest
|
Quest quest = new Quest
|
||||||
{
|
{
|
||||||
Id = questId,
|
Id = questId,
|
||||||
Root = questNode.Deserialize<QuestRoot>()!,
|
Root = questRoot,
|
||||||
Info = _questData.GetQuestInfo(questId),
|
Info = questInfo,
|
||||||
ReadOnly = false,
|
ReadOnly = false,
|
||||||
};
|
};
|
||||||
_quests[quest.Id] = quest;
|
_quests[quest.Id] = quest;
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Memory;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using LLib.GameData;
|
||||||
using LLib.GameUI;
|
using LLib.GameUI;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model.Gathering;
|
using Questionable.Model.Gathering;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Gathering;
|
namespace Questionable.Controller.Steps.Gathering;
|
||||||
|
|
||||||
@ -13,13 +20,17 @@ internal sealed class DoGather(
|
|||||||
GatheringController gatheringController,
|
GatheringController gatheringController,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
IGameGui gameGui,
|
IGameGui gameGui,
|
||||||
ICondition condition) : ITask
|
IClientState clientState,
|
||||||
|
ICondition condition,
|
||||||
|
ILogger<DoGather> logger) : ITask
|
||||||
{
|
{
|
||||||
|
private const uint StatusGatheringRateUp = 218;
|
||||||
|
|
||||||
private GatheringController.GatheringRequest _currentRequest = null!;
|
private GatheringController.GatheringRequest _currentRequest = null!;
|
||||||
private GatheringNode _currentNode = null!;
|
private GatheringNode _currentNode = null!;
|
||||||
private bool _wasGathering;
|
private bool _wasGathering;
|
||||||
private List<SlotInfo>? _slots;
|
private SlotInfo? _slotToGather;
|
||||||
|
private Queue<EAction>? _actionQueue;
|
||||||
|
|
||||||
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode)
|
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode)
|
||||||
{
|
{
|
||||||
@ -45,17 +56,44 @@ internal sealed class DoGather(
|
|||||||
|
|
||||||
_wasGathering = true;
|
_wasGathering = true;
|
||||||
|
|
||||||
if (gameGui.TryGetAddonByName("Gathering", out AtkUnitBase* atkUnitBase))
|
if (gameGui.TryGetAddonByName("Gathering", out AddonGathering* addonGathering))
|
||||||
{
|
{
|
||||||
if (gatheringController.HasRequestedItems())
|
if (gatheringController.HasRequestedItems())
|
||||||
{
|
{
|
||||||
atkUnitBase->FireCallbackInt(-1);
|
addonGathering->FireCallbackInt(-1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_slots ??= ReadSlots(atkUnitBase);
|
var slots = ReadSlots(addonGathering);
|
||||||
var slot = _slots.Single(x => x.ItemId == _currentRequest.ItemId);
|
if (_currentRequest.Collectability > 0)
|
||||||
atkUnitBase->FireCallbackInt(slot.Index);
|
{
|
||||||
|
var slot = slots.Single(x => x.ItemId == _currentRequest.ItemId);
|
||||||
|
addonGathering->FireCallbackInt(slot.Index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NodeCondition nodeCondition = new NodeCondition(
|
||||||
|
addonGathering->AtkValues[110].UInt,
|
||||||
|
addonGathering->AtkValues[111].UInt);
|
||||||
|
|
||||||
|
if (_actionQueue != null && _actionQueue.TryPeek(out EAction nextAction))
|
||||||
|
{
|
||||||
|
if (gameFunctions.UseAction(nextAction))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Used action {Action} on node", nextAction);
|
||||||
|
_actionQueue.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
_actionQueue = GetNextActions(nodeCondition, slots);
|
||||||
|
if (_actionQueue.Count == 0)
|
||||||
|
{
|
||||||
|
var slot = _slotToGather ?? slots.Single(x => x.ItemId == _currentRequest.ItemId);
|
||||||
|
addonGathering->FireCallbackInt(slot.Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,9 +103,9 @@ internal sealed class DoGather(
|
|||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe List<SlotInfo> ReadSlots(AtkUnitBase* atkUnitBase)
|
private unsafe List<SlotInfo> ReadSlots(AddonGathering* addonGathering)
|
||||||
{
|
{
|
||||||
var atkValues = atkUnitBase->AtkValues;
|
var atkValues = addonGathering->AtkValues;
|
||||||
List<SlotInfo> slots = new List<SlotInfo>();
|
List<SlotInfo> slots = new List<SlotInfo>();
|
||||||
for (int i = 0; i < 8; ++i)
|
for (int i = 0; i < 8; ++i)
|
||||||
{
|
{
|
||||||
@ -76,14 +114,122 @@ internal sealed class DoGather(
|
|||||||
if (itemId == 0)
|
if (itemId == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var slot = new SlotInfo(i, itemId);
|
AtkComponentCheckBox* atkCheckbox = addonGathering->GatheredItemComponentCheckbox[i].Value;
|
||||||
|
|
||||||
|
AtkTextNode* atkGatheringChance = atkCheckbox->UldManager.SearchNodeById(10)->GetAsAtkTextNode();
|
||||||
|
if (!int.TryParse(atkGatheringChance->NodeText.ToString(), out int gatheringChance))
|
||||||
|
gatheringChance = 0;
|
||||||
|
|
||||||
|
AtkTextNode* atkBoonChance = atkCheckbox->UldManager.SearchNodeById(16)->GetAsAtkTextNode();
|
||||||
|
if (!int.TryParse(atkBoonChance->NodeText.ToString(), out int boonChance))
|
||||||
|
boonChance = 0;
|
||||||
|
|
||||||
|
AtkComponentNode* atkImage = atkCheckbox->UldManager.SearchNodeById(31)->GetAsAtkComponentNode();
|
||||||
|
AtkTextNode* atkQuantity = atkImage->Component->UldManager.SearchNodeById(7)->GetAsAtkTextNode();
|
||||||
|
if (!atkQuantity->IsVisible() || !int.TryParse(atkQuantity->NodeText.ToString(), out int quantity))
|
||||||
|
quantity = 1;
|
||||||
|
|
||||||
|
var slot = new SlotInfo(i, itemId, gatheringChance, boonChance, quantity);
|
||||||
slots.Add(slot);
|
slots.Add(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Queue<EAction> GetNextActions(NodeCondition nodeCondition, List<SlotInfo> slots)
|
||||||
|
{
|
||||||
|
uint gp = clientState.LocalPlayer!.CurrentGp;
|
||||||
|
Queue<EAction> actions = new();
|
||||||
|
|
||||||
|
if (!gameFunctions.HasStatus(StatusGatheringRateUp))
|
||||||
|
{
|
||||||
|
// do we have an alternative item? only happens for 'evaluation' leve quests
|
||||||
|
if (_currentRequest.AlternativeItemId != 0)
|
||||||
|
{
|
||||||
|
var alternativeSlot = slots.Single(x => x.ItemId == _currentRequest.AlternativeItemId);
|
||||||
|
|
||||||
|
if (alternativeSlot.GatheringChance == 100)
|
||||||
|
{
|
||||||
|
_slotToGather = alternativeSlot;
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternativeSlot.GatheringChance > 0)
|
||||||
|
{
|
||||||
|
if (alternativeSlot.GatheringChance >= 95 &&
|
||||||
|
CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
|
||||||
|
{
|
||||||
|
_slotToGather = alternativeSlot;
|
||||||
|
actions.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternativeSlot.GatheringChance >= 85 &&
|
||||||
|
CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
|
||||||
|
{
|
||||||
|
_slotToGather = alternativeSlot;
|
||||||
|
actions.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternativeSlot.GatheringChance >= 50 &&
|
||||||
|
CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
|
||||||
|
{
|
||||||
|
_slotToGather = alternativeSlot;
|
||||||
|
actions.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var slot = slots.Single(x => x.ItemId == _currentRequest.ItemId);
|
||||||
|
if (slot.GatheringChance > 0 && slot.GatheringChance < 100)
|
||||||
|
{
|
||||||
|
if (slot.GatheringChance >= 95 &&
|
||||||
|
CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
|
||||||
|
{
|
||||||
|
actions.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot.GatheringChance >= 85 &&
|
||||||
|
CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
|
||||||
|
{
|
||||||
|
actions.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot.GatheringChance >= 50 &&
|
||||||
|
CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
|
||||||
|
{
|
||||||
|
actions.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
||||||
|
{
|
||||||
|
if ((EClassJob?)clientState.LocalPlayer?.ClassJob.Id == EClassJob.Miner)
|
||||||
|
return minerAction;
|
||||||
|
else
|
||||||
|
return botanistAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool CanUseAction(EAction minerAction, EAction botanistAction)
|
||||||
|
{
|
||||||
|
EAction action = PickAction(minerAction, botanistAction);
|
||||||
|
return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() => "DoGather";
|
public override string ToString() => "DoGather";
|
||||||
|
|
||||||
private sealed record SlotInfo(int Index, uint ItemId);
|
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
|
||||||
|
|
||||||
|
private sealed record NodeCondition(
|
||||||
|
uint CurrentIntegrity,
|
||||||
|
uint MaxIntegrity);
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ internal static class Combat
|
|||||||
ArgumentNullException.ThrowIfNull(step.DataId);
|
ArgumentNullException.ThrowIfNull(step.DataId);
|
||||||
|
|
||||||
yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
|
yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
|
||||||
.With(step.DataId.Value, true);
|
.With(step.DataId.Value, quest, EInteractionType.None, true);
|
||||||
yield return CreateTask(quest, sequence, step);
|
yield return CreateTask(quest, sequence, step);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -110,11 +110,11 @@ internal static class Combat
|
|||||||
// 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.ElementId is QuestId questId)
|
if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags) && _combatData.ElementId is QuestId questId)
|
||||||
{
|
{
|
||||||
var questWork = questFunctions.GetQuestEx(questId);
|
var questWork = questFunctions.GetQuestProgressInfo(questId);
|
||||||
if (questWork == null)
|
if (questWork == null)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
if (QuestWorkUtils.MatchesQuestWork(_completionQuestVariableFlags, questWork.Value))
|
if (QuestWorkUtils.MatchesQuestWork(_completionQuestVariableFlags, questWork))
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
else
|
else
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
@ -19,7 +19,8 @@ internal static class Interact
|
|||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (step.InteractionType is EInteractionType.AcceptQuest or EInteractionType.CompleteQuest)
|
if (step.InteractionType is EInteractionType.AcceptQuest or EInteractionType.CompleteQuest
|
||||||
|
or EInteractionType.AcceptLeve or EInteractionType.CompleteLeve)
|
||||||
{
|
{
|
||||||
if (step.Emote != null)
|
if (step.Emote != null)
|
||||||
yield break;
|
yield break;
|
||||||
@ -34,7 +35,7 @@ internal static class Interact
|
|||||||
yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
|
yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
|
||||||
|
|
||||||
yield return serviceProvider.GetRequiredService<DoInteract>()
|
yield return serviceProvider.GetRequiredService<DoInteract>()
|
||||||
.With(step.DataId.Value,
|
.With(step.DataId.Value, quest, step.InteractionType,
|
||||||
step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
|
step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,11 +51,15 @@ internal static class Interact
|
|||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
private uint DataId { get; set; }
|
private uint DataId { get; set; }
|
||||||
|
public Quest? Quest { get; private set; }
|
||||||
|
public EInteractionType InteractionType { get; set; }
|
||||||
private bool SkipMarkerCheck { get; set; }
|
private bool SkipMarkerCheck { get; set; }
|
||||||
|
|
||||||
public ITask With(uint dataId, bool skipMarkerCheck)
|
public DoInteract With(uint dataId, Quest? quest, EInteractionType interactionType, bool skipMarkerCheck)
|
||||||
{
|
{
|
||||||
DataId = dataId;
|
DataId = dataId;
|
||||||
|
Quest = quest;
|
||||||
|
InteractionType = interactionType;
|
||||||
SkipMarkerCheck = skipMarkerCheck;
|
SkipMarkerCheck = skipMarkerCheck;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ internal static class SinglePlayerDuty
|
|||||||
[
|
[
|
||||||
serviceProvider.GetRequiredService<DisableYesAlready>(),
|
serviceProvider.GetRequiredService<DisableYesAlready>(),
|
||||||
serviceProvider.GetRequiredService<Interact.DoInteract>()
|
serviceProvider.GetRequiredService<Interact.DoInteract>()
|
||||||
.With(step.DataId.Value, true),
|
.With(step.DataId.Value, quest, EInteractionType.None, true),
|
||||||
serviceProvider.GetRequiredService<RestoreYesAlready>()
|
serviceProvider.GetRequiredService<RestoreYesAlready>()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ internal static class UseItem
|
|||||||
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);
|
||||||
yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
|
yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
|
||||||
.With(npcId, true);
|
.With(npcId, null, EInteractionType.None, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,9 +145,9 @@ internal static class UseItem
|
|||||||
{
|
{
|
||||||
if (QuestId is QuestId questId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
if (QuestId is QuestId questId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
||||||
{
|
{
|
||||||
QuestWork? questWork = questFunctions.GetQuestEx(questId);
|
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(questId);
|
||||||
if (questWork != null &&
|
if (questWork != null &&
|
||||||
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value))
|
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork))
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
122
Questionable/Controller/Steps/Leves/InitiateLeve.cs
Normal file
122
Questionable/Controller/Steps/Leves/InitiateLeve.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using LLib.GameUI;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Questionable.Controller.Steps.Common;
|
||||||
|
using Questionable.Model;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.Steps.Leves;
|
||||||
|
|
||||||
|
internal static class InitiateLeve
|
||||||
|
{
|
||||||
|
internal sealed class Factory(IServiceProvider serviceProvider, ICondition condition) : ITaskFactory
|
||||||
|
{
|
||||||
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
|
{
|
||||||
|
if (step.InteractionType != EInteractionType.InitiateLeve)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
yield return serviceProvider.GetRequiredService<OpenJournal>().With(quest.Id);
|
||||||
|
yield return serviceProvider.GetRequiredService<Initiate>().With(quest.Id);
|
||||||
|
yield return serviceProvider.GetRequiredService<SelectDifficulty>();
|
||||||
|
yield return new WaitConditionTask(() => condition[ConditionFlag.BoundByDuty], "Wait(BoundByDuty)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed unsafe class OpenJournal : ITask
|
||||||
|
{
|
||||||
|
private ElementId _elementId = null!;
|
||||||
|
private uint _questType;
|
||||||
|
|
||||||
|
public ITask With(ElementId elementId)
|
||||||
|
{
|
||||||
|
_elementId = elementId;
|
||||||
|
_questType = _elementId is LeveId ? 2u : 1u;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Start()
|
||||||
|
{
|
||||||
|
AgentQuestJournal.Instance()->OpenForQuest(_elementId.Value, _questType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ETaskResult Update()
|
||||||
|
{
|
||||||
|
AgentQuestJournal* agentQuestJournal = AgentQuestJournal.Instance();
|
||||||
|
if (!agentQuestJournal->IsAgentActive())
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
|
return agentQuestJournal->SelectedQuestId == _elementId.Value &&
|
||||||
|
agentQuestJournal->SelectedQuestType == _questType
|
||||||
|
? ETaskResult.TaskComplete
|
||||||
|
: ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"OpenJournal({_elementId})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed unsafe class Initiate(IGameGui gameGui) : ITask
|
||||||
|
{
|
||||||
|
private ElementId _elementId = null!;
|
||||||
|
|
||||||
|
public ITask With(ElementId elementId)
|
||||||
|
{
|
||||||
|
_elementId = elementId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Start() => true;
|
||||||
|
|
||||||
|
public ETaskResult Update()
|
||||||
|
{
|
||||||
|
if (gameGui.TryGetAddonByName("JournalDetail", out AtkUnitBase* addonJournalDetail))
|
||||||
|
{
|
||||||
|
var pickQuest = stackalloc AtkValue[]
|
||||||
|
{
|
||||||
|
new() { Type = ValueType.Int, Int = 4 },
|
||||||
|
new() { Type = ValueType.UInt, Int = _elementId.Value }
|
||||||
|
};
|
||||||
|
addonJournalDetail->FireCallback(2, pickQuest);
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"InitiateLeve({_elementId})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed unsafe class SelectDifficulty(IGameGui gameGui) : ITask
|
||||||
|
{
|
||||||
|
public bool Start() => true;
|
||||||
|
|
||||||
|
public ETaskResult Update()
|
||||||
|
{
|
||||||
|
if (gameGui.TryGetAddonByName("GuildLeveDifficulty", out AtkUnitBase* addon))
|
||||||
|
{
|
||||||
|
// atkvalues: 1 → default difficulty, 2 → min, 3 → max
|
||||||
|
|
||||||
|
|
||||||
|
var pickDifficulty = stackalloc AtkValue[]
|
||||||
|
{
|
||||||
|
new() { Type = ValueType.Int, Int = 0 },
|
||||||
|
new() { Type = ValueType.Int, Int = addon->AtkValues[1].Int }
|
||||||
|
};
|
||||||
|
addon->FireCallback(2, pickDifficulty, true);
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -105,7 +105,8 @@ internal static class GatheringRequiredItems
|
|||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
return gatheringController.Start(new GatheringController.GatheringRequest(_gatheringPointId,
|
return gatheringController.Start(new GatheringController.GatheringRequest(_gatheringPointId,
|
||||||
_gatheredItem.ItemId, _gatheredItem.ItemCount, _gatheredItem.Collectability));
|
_gatheredItem.ItemId, _gatheredItem.AlternativeItemId, _gatheredItem.ItemCount,
|
||||||
|
_gatheredItem.Collectability));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
|
@ -158,12 +158,12 @@ internal static class SkipCondition
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ElementId is QuestId questId)
|
if (ElementId is QuestId || ElementId is LeveId)
|
||||||
{
|
{
|
||||||
QuestWork? questWork = questFunctions.GetQuestEx(questId);
|
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(ElementId);
|
||||||
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))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as quest variables match (step is complete)");
|
logger.LogInformation("Skipping step, as quest variables match (step is complete)");
|
||||||
return true;
|
return true;
|
||||||
@ -172,7 +172,7 @@ internal static class SkipCondition
|
|||||||
|
|
||||||
if (Step is { SkipConditions.StepIf: { } conditions } && questWork != null)
|
if (Step is { SkipConditions.StepIf: { } conditions } && questWork != null)
|
||||||
{
|
{
|
||||||
if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork.Value))
|
if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as quest variables match (step can be skipped)");
|
logger.LogInformation("Skipping step, as quest variables match (step can be skipped)");
|
||||||
return true;
|
return true;
|
||||||
@ -181,8 +181,7 @@ internal static class SkipCondition
|
|||||||
|
|
||||||
if (Step is { RequiredQuestVariables: { } requiredQuestVariables } && questWork != null)
|
if (Step is { RequiredQuestVariables: { } requiredQuestVariables } && questWork != null)
|
||||||
{
|
{
|
||||||
if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork.Value,
|
if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork, logger))
|
||||||
logger))
|
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as required variables do not match");
|
logger.LogInformation("Skipping step, as required variables do not match");
|
||||||
return true;
|
return true;
|
||||||
|
@ -179,9 +179,9 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
QuestWork? questWork = questFunctions.GetQuestEx(Quest);
|
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(Quest);
|
||||||
return questWork != null &&
|
return questWork != null &&
|
||||||
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value)
|
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Controller.Utils;
|
namespace Questionable.Controller.Utils;
|
||||||
@ -15,12 +16,12 @@ internal static class QuestWorkUtils
|
|||||||
return completionQuestVariablesFlags.Count == 6 && completionQuestVariablesFlags.Any(x => x != null && (x.High != 0 || x.Low != 0));
|
return completionQuestVariablesFlags.Count == 6 && completionQuestVariablesFlags.Any(x => x != null && (x.High != 0 || x.Low != 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool MatchesQuestWork(IList<QuestWorkValue?> completionQuestVariablesFlags, QuestWork questWork)
|
public static bool MatchesQuestWork(IList<QuestWorkValue?> completionQuestVariablesFlags, QuestProgressInfo questProgressInfo)
|
||||||
{
|
{
|
||||||
if (!HasCompletionFlags(completionQuestVariablesFlags))
|
if (!HasCompletionFlags(completionQuestVariablesFlags) || questProgressInfo.Variables.Count != 6)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (int i = 0; i < 6; ++i)
|
for (int i = 0; i < questProgressInfo.Variables.Count; ++i)
|
||||||
{
|
{
|
||||||
QuestWorkValue? check = completionQuestVariablesFlags[i];
|
QuestWorkValue? check = completionQuestVariablesFlags[i];
|
||||||
if (check == null)
|
if (check == null)
|
||||||
@ -28,8 +29,8 @@ internal static class QuestWorkUtils
|
|||||||
|
|
||||||
EQuestWorkMode mode = check.Mode;
|
EQuestWorkMode mode = check.Mode;
|
||||||
|
|
||||||
byte actualHigh = (byte)(questWork.Variables[i] >> 4);
|
byte actualHigh = (byte)(questProgressInfo.Variables[i] >> 4);
|
||||||
byte actualLow = (byte)(questWork.Variables[i] & 0xF);
|
byte actualLow = (byte)(questProgressInfo.Variables[i] & 0xF);
|
||||||
|
|
||||||
byte? checkHigh = check.High;
|
byte? checkHigh = check.High;
|
||||||
byte? checkLow = check.Low;
|
byte? checkLow = check.Low;
|
||||||
@ -60,7 +61,7 @@ internal static class QuestWorkUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool MatchesRequiredQuestWorkConfig(List<List<QuestWorkValue>?> requiredQuestVariables,
|
public static bool MatchesRequiredQuestWorkConfig(List<List<QuestWorkValue>?> requiredQuestVariables,
|
||||||
QuestWork questWork, ILogger<SkipCondition.CheckSkip> logger)
|
QuestProgressInfo questWork, ILogger<SkipCondition.CheckSkip> logger)
|
||||||
{
|
{
|
||||||
if (requiredQuestVariables.Count != 6 || requiredQuestVariables.All(x => x == null || x.Count == 0))
|
if (requiredQuestVariables.Count != 6 || requiredQuestVariables.All(x => x == null || x.Count == 0))
|
||||||
{
|
{
|
||||||
|
133
Questionable/Data/LeveData.cs
Normal file
133
Questionable/Data/LeveData.cs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||||
|
using LLib.GameData;
|
||||||
|
using Questionable.Model;
|
||||||
|
using Questionable.Model.Common;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
namespace Questionable.Data;
|
||||||
|
|
||||||
|
internal sealed class LeveData
|
||||||
|
{
|
||||||
|
private static readonly List<LeveStepData> Leves =
|
||||||
|
[
|
||||||
|
new(EAetheryteLocation.Tuliyollal, 1048390, new(15.243713f, -14.000001f, 85.83191f)),
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly AetheryteData _aetheryteData;
|
||||||
|
|
||||||
|
public LeveData(AetheryteData aetheryteData)
|
||||||
|
{
|
||||||
|
_aetheryteData = aetheryteData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddQuestSteps(LeveInfo leveInfo, QuestRoot questRoot)
|
||||||
|
{
|
||||||
|
LeveStepData leveStepData = Leves.Single(x => x.IssuerDataId == leveInfo.IssuerDataId);
|
||||||
|
|
||||||
|
QuestSequence? startSequence = questRoot.QuestSequence.FirstOrDefault(x => x.Sequence == 0);
|
||||||
|
if (startSequence == null)
|
||||||
|
{
|
||||||
|
questRoot.QuestSequence.Add(new QuestSequence
|
||||||
|
{
|
||||||
|
Sequence = 0,
|
||||||
|
Steps =
|
||||||
|
[
|
||||||
|
new QuestStep
|
||||||
|
{
|
||||||
|
DataId = leveStepData.IssuerDataId,
|
||||||
|
Position = leveStepData.IssuerPosition,
|
||||||
|
TerritoryId = _aetheryteData.TerritoryIds[leveStepData.AetheryteLocation],
|
||||||
|
InteractionType = EInteractionType.AcceptLeve,
|
||||||
|
AetheryteShortcut = leveStepData.AetheryteLocation,
|
||||||
|
SkipConditions = new()
|
||||||
|
{
|
||||||
|
AetheryteShortcutIf = new()
|
||||||
|
{
|
||||||
|
InSameTerritory = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QuestSequence? endSequence = questRoot.QuestSequence.FirstOrDefault(x => x.Sequence == 255);
|
||||||
|
if (endSequence == null)
|
||||||
|
{
|
||||||
|
questRoot.QuestSequence.Add(new QuestSequence
|
||||||
|
{
|
||||||
|
Sequence = 255,
|
||||||
|
Steps =
|
||||||
|
[
|
||||||
|
new QuestStep
|
||||||
|
{
|
||||||
|
DataId = leveStepData.GetTurnInDataId(leveInfo),
|
||||||
|
Position = leveStepData.GetTurnInPosition(leveInfo),
|
||||||
|
TerritoryId = _aetheryteData.TerritoryIds[leveStepData.AetheryteLocation],
|
||||||
|
InteractionType = EInteractionType.CompleteLeve,
|
||||||
|
AetheryteShortcut = leveStepData.AetheryteLocation,
|
||||||
|
SkipConditions = new()
|
||||||
|
{
|
||||||
|
AetheryteShortcutIf = new()
|
||||||
|
{
|
||||||
|
InSameTerritory = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class LeveStepData
|
||||||
|
{
|
||||||
|
private readonly uint? _turnInDataId;
|
||||||
|
private readonly Vector3? _turnInPosition;
|
||||||
|
private readonly uint? _gathererTurnInDataId;
|
||||||
|
private readonly Vector3? _gathererTurnInPosition;
|
||||||
|
private readonly uint? _crafterTurnInDataId;
|
||||||
|
private readonly Vector3? _crafterTurnInPosition;
|
||||||
|
|
||||||
|
public LeveStepData(EAetheryteLocation aetheryteLocation, uint issuerDataId, Vector3 issuerPosition,
|
||||||
|
uint? turnInDataId = null, Vector3? turnInPosition = null,
|
||||||
|
uint? gathererTurnInDataId = null, Vector3? gathererTurnInPosition = null,
|
||||||
|
uint? crafterTurnInDataId = null, Vector3? crafterTurnInPosition = null)
|
||||||
|
{
|
||||||
|
_turnInDataId = turnInDataId;
|
||||||
|
_turnInPosition = turnInPosition;
|
||||||
|
_gathererTurnInDataId = gathererTurnInDataId;
|
||||||
|
_gathererTurnInPosition = gathererTurnInPosition;
|
||||||
|
_crafterTurnInDataId = crafterTurnInDataId;
|
||||||
|
_crafterTurnInPosition = crafterTurnInPosition;
|
||||||
|
AetheryteLocation = aetheryteLocation;
|
||||||
|
IssuerDataId = issuerDataId;
|
||||||
|
IssuerPosition = issuerPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EAetheryteLocation AetheryteLocation { get; }
|
||||||
|
public uint IssuerDataId { get; }
|
||||||
|
public Vector3 IssuerPosition { get; }
|
||||||
|
|
||||||
|
public uint GetTurnInDataId(LeveInfo leveInfo)
|
||||||
|
{
|
||||||
|
if (leveInfo.ClassJobs.Any(x => x.IsGatherer()))
|
||||||
|
return _gathererTurnInDataId ?? _turnInDataId ?? IssuerDataId;
|
||||||
|
else if (leveInfo.ClassJobs.Any(x => x.IsCrafter()))
|
||||||
|
return _crafterTurnInDataId ?? _turnInDataId ?? IssuerDataId;
|
||||||
|
else
|
||||||
|
return _turnInDataId ?? IssuerDataId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetTurnInPosition(LeveInfo leveInfo)
|
||||||
|
{
|
||||||
|
if (leveInfo.ClassJobs.Any(x => x.IsGatherer()))
|
||||||
|
return _gathererTurnInPosition ?? _turnInPosition ?? IssuerPosition;
|
||||||
|
else if (leveInfo.ClassJobs.Any(x => x.IsCrafter()))
|
||||||
|
return _crafterTurnInPosition ?? _turnInPosition ?? IssuerPosition;
|
||||||
|
else
|
||||||
|
return _turnInPosition ?? IssuerPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,11 @@ internal sealed class QuestData
|
|||||||
.Select(x => new QuestInfo(x)),
|
.Select(x => new QuestInfo(x)),
|
||||||
..dataManager.GetExcelSheet<SatisfactionNpc>()!
|
..dataManager.GetExcelSheet<SatisfactionNpc>()!
|
||||||
.Where(x => x.RowId > 0)
|
.Where(x => x.RowId > 0)
|
||||||
.Select(x => new SatisfactionSupplyInfo(x))
|
.Select(x => new SatisfactionSupplyInfo(x)),
|
||||||
|
..dataManager.GetExcelSheet<Leve>()!
|
||||||
|
.Where(x => x.RowId > 0)
|
||||||
|
.Where(x => x.LevelLevemete.Row != 0)
|
||||||
|
.Select(x => new LeveInfo(x)),
|
||||||
];
|
];
|
||||||
_quests = quests.ToDictionary(x => x.QuestId, x => x);
|
_quests = quests.ToDictionary(x => x.QuestId, x => x);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ internal sealed class ExcelFunctions
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringOrRegex GetDialogueText(Quest currentQuest, string? excelSheetName, string key, bool isRegex)
|
public StringOrRegex GetDialogueText(Quest? currentQuest, string? excelSheetName, string key, bool isRegex)
|
||||||
{
|
{
|
||||||
var seString = GetRawDialogueText(currentQuest, excelSheetName, key);
|
var seString = GetRawDialogueText(currentQuest, excelSheetName, key);
|
||||||
if (isRegex)
|
if (isRegex)
|
||||||
@ -33,9 +33,9 @@ internal sealed class ExcelFunctions
|
|||||||
return new StringOrRegex(seString?.ToDalamudString().ToString());
|
return new StringOrRegex(seString?.ToDalamudString().ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SeString? GetRawDialogueText(Quest currentQuest, string? excelSheetName, string key)
|
public SeString? GetRawDialogueText(Quest? currentQuest, string? excelSheetName, string key)
|
||||||
{
|
{
|
||||||
if (excelSheetName == null)
|
if (currentQuest != null && excelSheetName == null)
|
||||||
{
|
{
|
||||||
var questRow =
|
var questRow =
|
||||||
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.Id.Value +
|
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.Id.Value +
|
||||||
@ -49,6 +49,7 @@ internal sealed class ExcelFunctions
|
|||||||
excelSheetName = $"quest/{(currentQuest.Id.Value / 100):000}/{questRow.Id}";
|
excelSheetName = $"quest/{(currentQuest.Id.Value / 100):000}/{questRow.Id}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(excelSheetName);
|
||||||
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
|
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
|
||||||
if (excelSheet == null)
|
if (excelSheet == null)
|
||||||
{
|
{
|
||||||
|
@ -344,6 +344,17 @@ internal sealed unsafe class GameFunctions
|
|||||||
statusManager->HasStatus(2730);
|
statusManager->HasStatus(2730);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasStatus(uint statusId)
|
||||||
|
{
|
||||||
|
var localPlayer = _clientState.LocalPlayer;
|
||||||
|
if (localPlayer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var battleChara = (BattleChara*)localPlayer.Address;
|
||||||
|
StatusManager* statusManager = battleChara->GetStatusManager();
|
||||||
|
return statusManager->HasStatus(statusId);
|
||||||
|
}
|
||||||
|
|
||||||
public bool Mount()
|
public bool Mount()
|
||||||
{
|
{
|
||||||
if (_condition[ConditionFlag.Mounted])
|
if (_condition[ConditionFlag.Mounted])
|
||||||
@ -503,4 +514,16 @@ internal sealed unsafe class GameFunctions
|
|||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if false
|
||||||
|
private byte ExecuteCommand(int id, int a, int b, int c, int d)
|
||||||
|
{
|
||||||
|
// Initiate Leve: 804 1794 [1] 0 0 // with [1] = extra difficulty levels
|
||||||
|
// 705 2 1794 0 0
|
||||||
|
// 801 0 0 0 0
|
||||||
|
// Abandon: 805 1794 0 0 0
|
||||||
|
// Retry button: 803 1794 0 0 0
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,8 @@ internal sealed unsafe class QuestFunctions
|
|||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
|
|
||||||
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, Configuration configuration, IDataManager dataManager, IClientState clientState, IGameGui gameGui)
|
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, Configuration configuration,
|
||||||
|
IDataManager dataManager, IClientState clientState, IGameGui gameGui)
|
||||||
{
|
{
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
@ -117,6 +118,14 @@ internal sealed unsafe class QuestFunctions
|
|||||||
|
|
||||||
case 1: // normal quest
|
case 1: // normal quest
|
||||||
currentQuest = new QuestId(questManager->NormalQuests[trackedQuest.Index].QuestId);
|
currentQuest = new QuestId(questManager->NormalQuests[trackedQuest.Index].QuestId);
|
||||||
|
if (_questRegistry.IsKnownQuest(currentQuest))
|
||||||
|
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // leve
|
||||||
|
currentQuest = new LeveId(questManager->LeveQuests[trackedQuest.Index].LeveId);
|
||||||
|
if (_questRegistry.IsKnownQuest(currentQuest))
|
||||||
|
return (currentQuest, questManager->GetLeveQuestById(currentQuest.Value)->Sequence);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,23 +198,23 @@ internal sealed unsafe class QuestFunctions
|
|||||||
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public QuestWork? GetQuestEx(QuestId questId)
|
public QuestProgressInfo? GetQuestProgressInfo(ElementId elementId)
|
||||||
{
|
|
||||||
QuestWork* questWork = QuestManager.Instance()->GetQuestById(questId.Value);
|
|
||||||
return questWork != null ? *questWork : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsReadyToAcceptQuest(ElementId elementId)
|
|
||||||
{
|
{
|
||||||
if (elementId is QuestId questId)
|
if (elementId is QuestId questId)
|
||||||
return IsReadyToAcceptQuest(questId);
|
{
|
||||||
else if (elementId is SatisfactionSupplyNpcId)
|
QuestWork* questWork = QuestManager.Instance()->GetQuestById(questId.Value);
|
||||||
return true;
|
return questWork != null ? new QuestProgressInfo(*questWork) : null;
|
||||||
|
}
|
||||||
|
else if (elementId is LeveId leveId)
|
||||||
|
{
|
||||||
|
LeveWork* leveWork = QuestManager.Instance()->GetLeveQuestById(leveId.Value);
|
||||||
|
return leveWork != null ? new QuestProgressInfo(*leveWork) : null;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsReadyToAcceptQuest(QuestId questId)
|
public bool IsReadyToAcceptQuest(ElementId questId)
|
||||||
{
|
{
|
||||||
_questRegistry.TryGetQuest(questId, out var quest);
|
_questRegistry.TryGetQuest(questId, out var quest);
|
||||||
if (quest is { Info.IsRepeatable: true })
|
if (quest is { Info.IsRepeatable: true })
|
||||||
@ -239,6 +248,8 @@ internal sealed unsafe class QuestFunctions
|
|||||||
{
|
{
|
||||||
if (elementId is QuestId questId)
|
if (elementId is QuestId questId)
|
||||||
return IsQuestAccepted(questId);
|
return IsQuestAccepted(questId);
|
||||||
|
else if (elementId is LeveId leveId)
|
||||||
|
return IsQuestAccepted(leveId);
|
||||||
else if (elementId is SatisfactionSupplyNpcId)
|
else if (elementId is SatisfactionSupplyNpcId)
|
||||||
return false;
|
return false;
|
||||||
else
|
else
|
||||||
@ -251,10 +262,24 @@ internal sealed unsafe class QuestFunctions
|
|||||||
return questManager->IsQuestAccepted(questId.Value);
|
return questManager->IsQuestAccepted(questId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsQuestAccepted(LeveId leveId)
|
||||||
|
{
|
||||||
|
QuestManager* questManager = QuestManager.Instance();
|
||||||
|
foreach (var leveQuest in questManager->LeveQuests)
|
||||||
|
{
|
||||||
|
if (leveQuest.LeveId == leveId.Value)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsQuestComplete(ElementId elementId)
|
public bool IsQuestComplete(ElementId elementId)
|
||||||
{
|
{
|
||||||
if (elementId is QuestId questId)
|
if (elementId is QuestId questId)
|
||||||
return IsQuestComplete(questId);
|
return IsQuestComplete(questId);
|
||||||
|
else if (elementId is LeveId leveId)
|
||||||
|
return IsQuestComplete(leveId);
|
||||||
else if (elementId is SatisfactionSupplyNpcId)
|
else if (elementId is SatisfactionSupplyNpcId)
|
||||||
return false;
|
return false;
|
||||||
else
|
else
|
||||||
@ -267,10 +292,17 @@ internal sealed unsafe class QuestFunctions
|
|||||||
return QuestManager.IsQuestComplete(questId.Value);
|
return QuestManager.IsQuestComplete(questId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsQuestComplete(LeveId leveId)
|
||||||
|
{
|
||||||
|
return QuestManager.Instance()->IsLevequestComplete(leveId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsQuestLocked(ElementId elementId, ElementId? extraCompletedQuest = null)
|
public bool IsQuestLocked(ElementId elementId, ElementId? extraCompletedQuest = null)
|
||||||
{
|
{
|
||||||
if (elementId is QuestId questId)
|
if (elementId is QuestId questId)
|
||||||
return IsQuestLocked(questId, extraCompletedQuest);
|
return IsQuestLocked(questId, extraCompletedQuest);
|
||||||
|
else if (elementId is LeveId leveId)
|
||||||
|
return IsQuestLocked(leveId);
|
||||||
else if (elementId is SatisfactionSupplyNpcId)
|
else if (elementId is SatisfactionSupplyNpcId)
|
||||||
return false;
|
return false;
|
||||||
else
|
else
|
||||||
@ -295,6 +327,17 @@ internal sealed unsafe class QuestFunctions
|
|||||||
return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
|
return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsQuestLocked(LeveId leveId)
|
||||||
|
{
|
||||||
|
// this only checks for the current class
|
||||||
|
IQuestInfo questInfo = _questData.GetQuestInfo(leveId);
|
||||||
|
if (!questInfo.ClassJobs.Contains((EClassJob)_clientState.LocalPlayer!.ClassJob.Id) ||
|
||||||
|
questInfo.Level > _clientState.LocalPlayer.Level)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return !IsQuestAccepted(leveId) && QuestManager.Instance()->NumLeveAllowances == 0;
|
||||||
|
}
|
||||||
|
|
||||||
private bool HasCompletedPreviousQuests(QuestInfo questInfo, ElementId? extraCompletedQuest)
|
private bool HasCompletedPreviousQuests(QuestInfo questInfo, ElementId? extraCompletedQuest)
|
||||||
{
|
{
|
||||||
if (questInfo.PreviousQuests.Count == 0)
|
if (questInfo.PreviousQuests.Count == 0)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
|
using LLib.GameData;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Model;
|
namespace Questionable.Model;
|
||||||
@ -13,6 +15,7 @@ public interface IQuestInfo
|
|||||||
public ushort Level { get; }
|
public ushort Level { get; }
|
||||||
public EBeastTribe BeastTribe { get; }
|
public EBeastTribe BeastTribe { get; }
|
||||||
public bool IsMainScenarioQuest { get; }
|
public bool IsMainScenarioQuest { get; }
|
||||||
|
public IReadOnlyList<EClassJob> ClassJobs { get; }
|
||||||
|
|
||||||
public string SimplifiedName => Name
|
public string SimplifiedName => Name
|
||||||
.Replace(".", "", StringComparison.Ordinal)
|
.Replace(".", "", StringComparison.Ordinal)
|
||||||
|
27
Questionable/Model/LeveInfo.cs
Normal file
27
Questionable/Model/LeveInfo.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using LLib.GameData;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
namespace Questionable.Model;
|
||||||
|
|
||||||
|
internal sealed class LeveInfo : IQuestInfo
|
||||||
|
{
|
||||||
|
public LeveInfo(Leve leve)
|
||||||
|
{
|
||||||
|
QuestId = new LeveId((ushort)leve.RowId);
|
||||||
|
Name = leve.Name;
|
||||||
|
Level = leve.ClassJobLevel;
|
||||||
|
IssuerDataId = leve.LevelLevemete.Value!.Object;
|
||||||
|
ClassJobs = QuestInfoUtils.AsList(leve.ClassJobCategory.Value!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ElementId QuestId { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public uint IssuerDataId { get; }
|
||||||
|
public bool IsRepeatable => true;
|
||||||
|
public ushort Level { get; }
|
||||||
|
public EBeastTribe BeastTribe => EBeastTribe.None;
|
||||||
|
public bool IsMainScenarioQuest => false;
|
||||||
|
public IReadOnlyList<EClassJob> ClassJobs { get; }
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Game.Text;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using LLib.GameData;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
|
using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
|
||||||
|
|
||||||
@ -53,6 +52,7 @@ internal sealed class QuestInfo : IQuestInfo
|
|||||||
PreviousInstanceContentJoin = (QuestJoin)quest.InstanceContentJoin;
|
PreviousInstanceContentJoin = (QuestJoin)quest.InstanceContentJoin;
|
||||||
GrandCompany = (GrandCompany)quest.GrandCompany.Row;
|
GrandCompany = (GrandCompany)quest.GrandCompany.Row;
|
||||||
BeastTribe = (EBeastTribe)quest.BeastTribe.Row;
|
BeastTribe = (EBeastTribe)quest.BeastTribe.Row;
|
||||||
|
ClassJobs = QuestInfoUtils.AsList(quest.ClassJobCategory0.Value!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -73,6 +73,7 @@ internal sealed class QuestInfo : IQuestInfo
|
|||||||
public bool CompletesInstantly { get; }
|
public bool CompletesInstantly { get; }
|
||||||
public GrandCompany GrandCompany { get; }
|
public GrandCompany GrandCompany { get; }
|
||||||
public EBeastTribe BeastTribe { get; }
|
public EBeastTribe BeastTribe { get; }
|
||||||
|
public IReadOnlyList<EClassJob> ClassJobs { get; }
|
||||||
|
|
||||||
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
|
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
|
||||||
public enum QuestJoin : byte
|
public enum QuestJoin : byte
|
||||||
|
70
Questionable/Model/QuestInfoUtils.cs
Normal file
70
Questionable/Model/QuestInfoUtils.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using LLib.GameData;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Questionable.Model;
|
||||||
|
|
||||||
|
internal static class QuestInfoUtils
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<uint, IReadOnlyList<EClassJob>> CachedClassJobs = new();
|
||||||
|
|
||||||
|
internal static IReadOnlyList<EClassJob> AsList(ClassJobCategory classJobCategory)
|
||||||
|
{
|
||||||
|
if (CachedClassJobs.TryGetValue(classJobCategory.RowId, out IReadOnlyList<EClassJob>? classJobs))
|
||||||
|
return classJobs;
|
||||||
|
|
||||||
|
classJobs = new Dictionary<EClassJob, bool>
|
||||||
|
{
|
||||||
|
{ EClassJob.Adventurer, classJobCategory.ADV },
|
||||||
|
{ EClassJob.Gladiator, classJobCategory.GLA },
|
||||||
|
{ EClassJob.Pugilist, classJobCategory.PGL },
|
||||||
|
{ EClassJob.Marauder, classJobCategory.MRD },
|
||||||
|
{ EClassJob.Lancer, classJobCategory.LNC },
|
||||||
|
{ EClassJob.Archer, classJobCategory.ARC },
|
||||||
|
{ EClassJob.Conjurer, classJobCategory.CNJ },
|
||||||
|
{ EClassJob.Thaumaturge, classJobCategory.THM },
|
||||||
|
{ EClassJob.Carpenter, classJobCategory.CRP },
|
||||||
|
{ EClassJob.Blacksmith, classJobCategory.BSM },
|
||||||
|
{ EClassJob.Armorer, classJobCategory.ARM },
|
||||||
|
{ EClassJob.Goldsmith, classJobCategory.GSM },
|
||||||
|
{ EClassJob.Leatherworker, classJobCategory.LTW },
|
||||||
|
{ EClassJob.Weaver, classJobCategory.WVR },
|
||||||
|
{ EClassJob.Alchemist, classJobCategory.ALC },
|
||||||
|
{ EClassJob.Culinarian, classJobCategory.CUL },
|
||||||
|
{ EClassJob.Miner, classJobCategory.MIN },
|
||||||
|
{ EClassJob.Botanist, classJobCategory.BTN },
|
||||||
|
{ EClassJob.Fisher, classJobCategory.FSH },
|
||||||
|
{ EClassJob.Paladin, classJobCategory.PLD },
|
||||||
|
{ EClassJob.Monk, classJobCategory.MNK },
|
||||||
|
{ EClassJob.Warrior, classJobCategory.WAR },
|
||||||
|
{ EClassJob.Dragoon, classJobCategory.DRG },
|
||||||
|
{ EClassJob.Bard, classJobCategory.BRD },
|
||||||
|
{ EClassJob.WhiteMage, classJobCategory.WHM },
|
||||||
|
{ EClassJob.BlackMage, classJobCategory.BLM },
|
||||||
|
{ EClassJob.Arcanist, classJobCategory.ACN },
|
||||||
|
{ EClassJob.Summoner, classJobCategory.SMN },
|
||||||
|
{ EClassJob.Scholar, classJobCategory.SCH },
|
||||||
|
{ EClassJob.Rogue, classJobCategory.ROG },
|
||||||
|
{ EClassJob.Ninja, classJobCategory.NIN },
|
||||||
|
{ EClassJob.Machinist, classJobCategory.MCH },
|
||||||
|
{ EClassJob.DarkKnight, classJobCategory.DRK },
|
||||||
|
{ EClassJob.Astrologian, classJobCategory.AST },
|
||||||
|
{ EClassJob.Samurai, classJobCategory.SAM },
|
||||||
|
{ EClassJob.RedMage, classJobCategory.RDM },
|
||||||
|
{ EClassJob.BlueMage, classJobCategory.BLU },
|
||||||
|
{ EClassJob.Gunbreaker, classJobCategory.GNB },
|
||||||
|
{ EClassJob.Dancer, classJobCategory.DNC },
|
||||||
|
{ EClassJob.Reaper, classJobCategory.RPR },
|
||||||
|
{ EClassJob.Sage, classJobCategory.SGE },
|
||||||
|
{ EClassJob.Viper, classJobCategory.VPR },
|
||||||
|
{ EClassJob.Pictomancer, classJobCategory.PCT }
|
||||||
|
}
|
||||||
|
.Where(y => y.Value)
|
||||||
|
.Select(y => y.Key)
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
CachedClassJobs[classJobCategory.RowId] = classJobs;
|
||||||
|
return classJobs;
|
||||||
|
}
|
||||||
|
}
|
57
Questionable/Model/QuestProgressInfo.cs
Normal file
57
Questionable/Model/QuestProgressInfo.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
||||||
|
using LLib.GameData;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
namespace Questionable.Model;
|
||||||
|
|
||||||
|
internal sealed class QuestProgressInfo
|
||||||
|
{
|
||||||
|
private readonly string _asString;
|
||||||
|
|
||||||
|
public QuestProgressInfo(QuestWork questWork)
|
||||||
|
{
|
||||||
|
Id = new QuestId(questWork.QuestId);
|
||||||
|
Sequence = questWork.Sequence;
|
||||||
|
Flags = questWork.Flags;
|
||||||
|
Variables = [..questWork.Variables.ToArray()];
|
||||||
|
IsHidden = questWork.IsHidden;
|
||||||
|
|
||||||
|
var qw = questWork.Variables;
|
||||||
|
string vars = "";
|
||||||
|
for (int i = 0; i < qw.Length; ++i)
|
||||||
|
{
|
||||||
|
vars += qw[i] + " ";
|
||||||
|
if (i % 2 == 1)
|
||||||
|
vars += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// For combat quests, a sequence to kill 3 enemies works a bit like this:
|
||||||
|
// Trigger enemies → 0
|
||||||
|
// Kill first enemy → 1
|
||||||
|
// Kill second enemy → 2
|
||||||
|
// Last enemy → increase sequence, reset variable to 0
|
||||||
|
// The order in which enemies are killed doesn't seem to matter.
|
||||||
|
// If multiple waves spawn, this continues to count up (e.g. 1 enemy from wave 1, 2 enemies from wave 2, 1 from wave 3) would count to 3 then 0
|
||||||
|
_asString = $"QW: {vars.Trim()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuestProgressInfo(LeveWork leveWork)
|
||||||
|
{
|
||||||
|
Id = new LeveId(leveWork.LeveId);
|
||||||
|
Sequence = leveWork.Sequence;
|
||||||
|
Flags = leveWork.Flags;
|
||||||
|
Variables = [0, 0, 0, 0, 0, 0];
|
||||||
|
IsHidden = leveWork.IsHidden;
|
||||||
|
|
||||||
|
_asString = $"Seed: {leveWork.LeveSeed}, Flags: {Flags:X}, Class: {(EClassJob)leveWork.ClearClass}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ElementId Id { get; }
|
||||||
|
public byte Sequence { get; }
|
||||||
|
public ushort Flags { get; init; }
|
||||||
|
public List<byte> Variables { get; }
|
||||||
|
public bool IsHidden { get; }
|
||||||
|
|
||||||
|
public override string ToString() => _asString;
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
using Lumina.Excel.GeneratedSheets;
|
using System.Collections.Generic;
|
||||||
|
using LLib.GameData;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Model;
|
namespace Questionable.Model;
|
||||||
@ -20,4 +22,9 @@ internal sealed class SatisfactionSupplyInfo : IQuestInfo
|
|||||||
public ushort Level { get; }
|
public ushort Level { get; }
|
||||||
public EBeastTribe BeastTribe => EBeastTribe.None;
|
public EBeastTribe BeastTribe => EBeastTribe.None;
|
||||||
public bool IsMainScenarioQuest => false;
|
public bool IsMainScenarioQuest => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We don't have collectables implemented for any other class.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<EClassJob> ClassJobs { get; } = [EClassJob.Miner, EClassJob.Botanist];
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using Questionable.Controller.Steps.Shared;
|
|||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Controller.Steps.Gathering;
|
using Questionable.Controller.Steps.Gathering;
|
||||||
using Questionable.Controller.Steps.Interactions;
|
using Questionable.Controller.Steps.Interactions;
|
||||||
|
using Questionable.Controller.Steps.Leves;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
@ -103,6 +104,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
serviceCollection.AddSingleton<AetherCurrentData>();
|
serviceCollection.AddSingleton<AetherCurrentData>();
|
||||||
serviceCollection.AddSingleton<AetheryteData>();
|
serviceCollection.AddSingleton<AetheryteData>();
|
||||||
serviceCollection.AddSingleton<GatheringData>();
|
serviceCollection.AddSingleton<GatheringData>();
|
||||||
|
serviceCollection.AddSingleton<LeveData>();
|
||||||
serviceCollection.AddSingleton<JournalData>();
|
serviceCollection.AddSingleton<JournalData>();
|
||||||
serviceCollection.AddSingleton<QuestData>();
|
serviceCollection.AddSingleton<QuestData>();
|
||||||
serviceCollection.AddSingleton<TerritoryData>();
|
serviceCollection.AddSingleton<TerritoryData>();
|
||||||
@ -143,12 +145,17 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.SingleJump, Jump.RepeatedJumps>();
|
serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.SingleJump, Jump.RepeatedJumps>();
|
||||||
serviceCollection.AddTaskWithFactory<Dive.Factory, Dive.DoDive>();
|
serviceCollection.AddTaskWithFactory<Dive.Factory, Dive.DoDive>();
|
||||||
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.AddTaskWithFactory<TurnInDelivery.Factory, TurnInDelivery.SatisfactionSupplyTurnIn>();
|
||||||
serviceCollection
|
serviceCollection
|
||||||
.AddTaskWithFactory<SinglePlayerDuty.Factory, SinglePlayerDuty.DisableYesAlready,
|
.AddTaskWithFactory<SinglePlayerDuty.Factory, SinglePlayerDuty.DisableYesAlready,
|
||||||
SinglePlayerDuty.RestoreYesAlready>();
|
SinglePlayerDuty.RestoreYesAlready>();
|
||||||
|
serviceCollection
|
||||||
|
.AddTaskWithFactory<InitiateLeve.Factory, InitiateLeve.OpenJournal, InitiateLeve.Initiate,
|
||||||
|
InitiateLeve.SelectDifficulty>();
|
||||||
|
|
||||||
serviceCollection
|
serviceCollection
|
||||||
.AddTaskWithFactory<WaitAtEnd.Factory,
|
.AddTaskWithFactory<WaitAtEnd.Factory,
|
||||||
|
@ -14,6 +14,7 @@ using ImGuiNET;
|
|||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Windows.QuestComponents;
|
namespace Questionable.Windows.QuestComponents;
|
||||||
@ -156,18 +157,15 @@ internal sealed class ActiveQuestComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private QuestWork? DrawQuestWork(QuestController.QuestProgress currentQuest)
|
private QuestProgressInfo? DrawQuestWork(QuestController.QuestProgress currentQuest)
|
||||||
{
|
{
|
||||||
if (currentQuest.Quest.Id is not QuestId questId)
|
var questWork = _questFunctions.GetQuestProgressInfo(currentQuest.Quest.Id);
|
||||||
return null;
|
|
||||||
|
|
||||||
var questWork = _questFunctions.GetQuestEx(questId);
|
|
||||||
if (questWork != null)
|
if (questWork != null)
|
||||||
{
|
{
|
||||||
Vector4 color;
|
Vector4 color;
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
var ptr =ImGui.GetStyleColorVec4(ImGuiCol.TextDisabled);
|
var ptr = ImGui.GetStyleColorVec4(ImGuiCol.TextDisabled);
|
||||||
if (ptr != null)
|
if (ptr != null)
|
||||||
color = *ptr;
|
color = *ptr;
|
||||||
else
|
else
|
||||||
@ -175,34 +173,12 @@ internal sealed class ActiveQuestComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
using var styleColor = ImRaii.PushColor(ImGuiCol.Text, color);
|
using var styleColor = ImRaii.PushColor(ImGuiCol.Text, color);
|
||||||
|
ImGui.Text($"{questWork}");
|
||||||
var qw = questWork.Value;
|
|
||||||
string vars = "";
|
|
||||||
for (int i = 0; i < 6; ++i)
|
|
||||||
{
|
|
||||||
vars += qw.Variables[i] + " ";
|
|
||||||
if (i % 2 == 1)
|
|
||||||
vars += " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
// For combat quests, a sequence to kill 3 enemies works a bit like this:
|
|
||||||
// Trigger enemies → 0
|
|
||||||
// Kill first enemy → 1
|
|
||||||
// Kill second enemy → 2
|
|
||||||
// Last enemy → increase sequence, reset variable to 0
|
|
||||||
// The order in which enemies are killed doesn't seem to matter.
|
|
||||||
// If multiple waves spawn, this continues to count up (e.g. 1 enemy from wave 1, 2 enemies from wave 2, 1 from wave 3) would count to 3 then 0
|
|
||||||
ImGui.Text($"QW: {vars.Trim()}");
|
|
||||||
|
|
||||||
if (ImGui.IsItemClicked())
|
if (ImGui.IsItemClicked())
|
||||||
{
|
{
|
||||||
string copy = "";
|
ImGui.SetClipboardText(questWork.ToString());
|
||||||
for (int i = 0; i < 6; ++i)
|
_chatGui.Print($"Copied '{questWork}' to clipboard");
|
||||||
copy += qw.Variables[i] + " ";
|
|
||||||
|
|
||||||
copy = copy.Trim();
|
|
||||||
ImGui.SetClipboardText(copy);
|
|
||||||
_chatGui.Print($"Copied '{copy}' to clipboard");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
@ -213,7 +189,7 @@ internal sealed class ActiveQuestComponent
|
|||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (currentQuest.Quest.Id is QuestId)
|
||||||
{
|
{
|
||||||
using var disabled = ImRaii.Disabled();
|
using var disabled = ImRaii.Disabled();
|
||||||
|
|
||||||
@ -227,13 +203,13 @@ internal sealed class ActiveQuestComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void DrawQuestButtons(QuestController.QuestProgress currentQuest, QuestStep? currentStep,
|
private void DrawQuestButtons(QuestController.QuestProgress currentQuest, QuestStep? currentStep,
|
||||||
QuestWork? questWork)
|
QuestProgressInfo? questProgressInfo)
|
||||||
{
|
{
|
||||||
ImGui.BeginDisabled(_questController.IsRunning);
|
ImGui.BeginDisabled(_questController.IsRunning);
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
|
||||||
{
|
{
|
||||||
// if we haven't accepted this quest, mark it as next quest so that we can optionally use aetherytes to travel
|
// if we haven't accepted this quest, mark it as next quest so that we can optionally use aetherytes to travel
|
||||||
if (questWork == null)
|
if (questProgressInfo == null)
|
||||||
_questController.SetNextQuest(currentQuest.Quest);
|
_questController.SetNextQuest(currentQuest.Quest);
|
||||||
|
|
||||||
_questController.ExecuteNextStep(QuestController.EAutomationType.Automatic);
|
_questController.ExecuteNextStep(QuestController.EAutomationType.Automatic);
|
||||||
@ -261,7 +237,7 @@ internal sealed class ActiveQuestComponent
|
|||||||
bool colored = currentStep != null
|
bool colored = currentStep != null
|
||||||
&& !lastStep
|
&& !lastStep
|
||||||
&& currentStep.InteractionType == EInteractionType.Instruction
|
&& currentStep.InteractionType == EInteractionType.Instruction
|
||||||
&& _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
|
&& _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>(out _);
|
||||||
|
|
||||||
ImGui.BeginDisabled(lastStep);
|
ImGui.BeginDisabled(lastStep);
|
||||||
if (colored)
|
if (colored)
|
||||||
|
@ -9,8 +9,11 @@ using Dalamud.Interface;
|
|||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
@ -93,16 +96,37 @@ internal sealed class CreationUtilsComponent
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
_questRegistry.TryGetQuest(questManager->NormalQuests[trackedQuest.Index].QuestId,
|
//_questRegistry.TryGetQuest(questManager->NormalQuests[trackedQuest.Index].QuestId,
|
||||||
out var quest);
|
// out var quest);
|
||||||
ImGui.Text(
|
ImGui.Text(
|
||||||
$"Tracked quest: {questManager->NormalQuests[trackedQuest.Index].QuestId}, {trackedQuest.Index}: {quest?.Info.Name}");
|
$"Quest: {questManager->NormalQuests[trackedQuest.Index].QuestId}, {trackedQuest.Index}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
ImGui.Text($"Leve: {questManager->LeveQuests[trackedQuest.Index].LeveId}, {trackedQuest.Index}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if false
|
||||||
|
var director = UIState.Instance()->DirectorTodo.Director;
|
||||||
|
if (director != null)
|
||||||
|
{
|
||||||
|
ImGui.Text($"Director: {director->ContentId}");
|
||||||
|
ImGui.Text($"Seq: {director->Sequence}");
|
||||||
|
ImGui.Text($"Ico: {director->IconId}");
|
||||||
|
if (director->EventHandlerInfo != null)
|
||||||
|
{
|
||||||
|
ImGui.Text($" EHI: {director->EventHandlerInfo->EventId.ContentId}");
|
||||||
|
ImGui.Text($" EHI: {director->EventHandlerInfo->EventId.Id}");
|
||||||
|
ImGui.Text($" EHI: {director->EventHandlerInfo->EventId.EntryId}");
|
||||||
|
ImGui.Text($" EHI: {director->EventHandlerInfo->Flags}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (_targetManager.Target != null)
|
if (_targetManager.Target != null)
|
||||||
{
|
{
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
@ -223,7 +223,8 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
if (knownQuest != null &&
|
if (knownQuest != null &&
|
||||||
knownQuest.FindSequence(0)?.LastStep()?.InteractionType == EInteractionType.AcceptQuest &&
|
knownQuest.FindSequence(0)?.LastStep()?.InteractionType is EInteractionType.AcceptQuest
|
||||||
|
or EInteractionType.AcceptLeve &&
|
||||||
!_questFunctions.IsQuestAccepted(quest.QuestId) &&
|
!_questFunctions.IsQuestAccepted(quest.QuestId) &&
|
||||||
!_questFunctions.IsQuestLocked(quest.QuestId) &&
|
!_questFunctions.IsQuestLocked(quest.QuestId) &&
|
||||||
(quest.IsRepeatable || !_questFunctions.IsQuestAcceptedOrComplete(quest.QuestId)))
|
(quest.IsRepeatable || !_questFunctions.IsQuestAcceptedOrComplete(quest.QuestId)))
|
||||||
|
Loading…
Reference in New Issue
Block a user