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",
|
||||
"Instruction",
|
||||
"AcceptQuest",
|
||||
"CompleteQuest"
|
||||
"CompleteQuest",
|
||||
"InitiateLeve"
|
||||
]
|
||||
},
|
||||
"Disabled": {
|
||||
@ -326,6 +327,10 @@
|
||||
"ItemId": {
|
||||
"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": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
|
@ -28,5 +28,6 @@ public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>
|
||||
{ EInteractionType.Instruction, "Instruction" },
|
||||
{ EInteractionType.AcceptQuest, "AcceptQuest" },
|
||||
{ EInteractionType.CompleteQuest, "CompleteQuest" },
|
||||
{ EInteractionType.InitiateLeve, "InitiateLeve" },
|
||||
};
|
||||
}
|
||||
|
@ -26,7 +26,12 @@ public enum EAction
|
||||
MeticulousBotanist = 22188,
|
||||
ScrutinyBotanist = 22189,
|
||||
|
||||
|
||||
SharpVision1 = 235,
|
||||
SharpVision2 = 237,
|
||||
SharpVision3 = 295,
|
||||
FieldMastery1 = 218,
|
||||
FieldMastery2 = 220,
|
||||
FieldMastery3 = 294,
|
||||
}
|
||||
|
||||
public static class EActionExtensions
|
||||
|
@ -32,4 +32,7 @@ public enum EInteractionType
|
||||
|
||||
AcceptQuest,
|
||||
CompleteQuest,
|
||||
AcceptLeve,
|
||||
InitiateLeve,
|
||||
CompleteLeve,
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
public sealed class GatheredItem
|
||||
{
|
||||
public uint ItemId { get; set; }
|
||||
public uint AlternativeItemId { get; set; }
|
||||
public int ItemCount { 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)
|
||||
{
|
||||
var questWork = _questFunctions.GetQuestEx(questId);
|
||||
if (questWork != null && QuestWorkUtils.MatchesQuestWork(condition.CompletionQuestVariablesFlags,
|
||||
questWork.Value))
|
||||
var questWork = _questFunctions.GetQuestProgressInfo(questId);
|
||||
if (questWork != null && QuestWorkUtils.MatchesQuestWork(condition.CompletionQuestVariablesFlags, questWork))
|
||||
{
|
||||
_logger.LogInformation("Complex combat condition fulfilled: QuestWork matches");
|
||||
_currentFight.Data.CompletedComplexDatas.Add(i);
|
||||
|
@ -7,12 +7,17 @@ using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
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.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib;
|
||||
using LLib.GameUI;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
@ -34,6 +39,7 @@ internal sealed class GameUiController : IDisposable
|
||||
private readonly QuestData _questData;
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly ITargetManager _targetManager;
|
||||
private readonly IFramework _framework;
|
||||
private readonly ILogger<GameUiController> _logger;
|
||||
private readonly Regex _returnRegex;
|
||||
|
||||
@ -48,7 +54,9 @@ internal sealed class GameUiController : IDisposable
|
||||
QuestData questData,
|
||||
IGameGui gameGui,
|
||||
ITargetManager targetManager,
|
||||
IPluginLog pluginLog, ILogger<GameUiController> logger)
|
||||
IFramework framework,
|
||||
IPluginLog pluginLog,
|
||||
ILogger<GameUiController> logger)
|
||||
{
|
||||
_addonLifecycle = addonLifecycle;
|
||||
_dataManager = dataManager;
|
||||
@ -60,6 +68,7 @@ internal sealed class GameUiController : IDisposable
|
||||
_questData = questData;
|
||||
_gameGui = gameGui;
|
||||
_targetManager = targetManager;
|
||||
_framework = framework;
|
||||
_logger = logger;
|
||||
|
||||
_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, "MultipleHelpWindow", MultipleHelpWindowPostSetup);
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "JournalResult", JournalResultPostSetup);
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GuildLeve", GuildLevePostSetup);
|
||||
}
|
||||
|
||||
internal unsafe void HandleCurrentDialogueChoices()
|
||||
@ -225,7 +236,41 @@ internal sealed class GameUiController : IDisposable
|
||||
private int? HandleListChoice(string? actualPrompt, List<string?> answers, bool checkAllSteps)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
_logger.LogDebug("No dialogue choices to check");
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var (quest, dialogueChoice) in dialogueChoices)
|
||||
{
|
||||
@ -344,7 +409,7 @@ internal sealed class GameUiController : IDisposable
|
||||
i, answers[i], actualPrompt);
|
||||
|
||||
// ensure we only open the dialog once
|
||||
if (quest.Id is SatisfactionSupplyNpcId)
|
||||
if (quest?.Id is SatisfactionSupplyNpcId)
|
||||
{
|
||||
if (_questController.GatheringQuest == null ||
|
||||
_questController.GatheringQuest.Sequence == 255)
|
||||
@ -403,6 +468,16 @@ internal sealed class GameUiController : IDisposable
|
||||
return;
|
||||
|
||||
_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;
|
||||
if (currentQuest != null && CheckQuestYesNo(addonSelectYesno, currentQuest, actualPrompt, checkAllSteps))
|
||||
@ -437,6 +512,20 @@ internal sealed class GameUiController : IDisposable
|
||||
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))
|
||||
return true;
|
||||
|
||||
@ -515,22 +604,24 @@ internal sealed class GameUiController : IDisposable
|
||||
|
||||
QuestStep? step = sequence.FindStep(currentQuest.Step);
|
||||
if (step != null)
|
||||
_logger.LogTrace("Current step: {CurrentTerritory}, {TargetTerritory}", step.TerritoryId,
|
||||
_logger.LogTrace("FindTargetTerritoryFromQuestStep (current): {CurrentTerritory}, {TargetTerritory}",
|
||||
step.TerritoryId,
|
||||
step.TargetTerritoryId);
|
||||
|
||||
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));
|
||||
|
||||
if (step != null)
|
||||
_logger.LogTrace("Previous step: {CurrentTerritory}, {TargetTerritory}", step.TerritoryId,
|
||||
_logger.LogTrace("FindTargetTerritoryFromQuestStep (previous): {CurrentTerritory}, {TargetTerritory}",
|
||||
step.TerritoryId,
|
||||
step.TargetTerritoryId);
|
||||
}
|
||||
|
||||
if (step == null || step.TargetTerritoryId == null)
|
||||
{
|
||||
_logger.LogTrace("TravelYesNo: Not found");
|
||||
_logger.LogTrace("FindTargetTerritoryFromQuestStep: Not found");
|
||||
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)
|
||||
return null;
|
||||
@ -701,6 +859,8 @@ internal sealed class GameUiController : IDisposable
|
||||
|
||||
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, "MultipleHelpWindow", MultipleHelpWindowPostSetup);
|
||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "ContentsTutorial", ContentsTutorialPostSetup);
|
||||
@ -714,5 +874,5 @@ internal sealed class GameUiController : IDisposable
|
||||
_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.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps;
|
||||
@ -17,6 +19,7 @@ using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.GatheringPaths;
|
||||
using Questionable.Model.Gathering;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller;
|
||||
|
||||
@ -119,6 +122,16 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
||||
|
||||
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>()
|
||||
.With(_currentRequest.Root.TerritoryId, MountTask.EMountIf.Always));
|
||||
if (currentNode.Locations.Count > 1)
|
||||
@ -142,7 +155,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MoveToLandingLocation>()
|
||||
.With(_currentRequest.Root.TerritoryId, currentNode));
|
||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
|
||||
.With(currentNode.DataId, true));
|
||||
.With(currentNode.DataId, null, EInteractionType.None, true));
|
||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
|
||||
.With(_currentRequest.Data, currentNode));
|
||||
if (_currentRequest.Data.Collectability > 0)
|
||||
@ -195,6 +208,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
||||
public sealed record GatheringRequest(
|
||||
GatheringPointId GatheringPointId,
|
||||
uint ItemId,
|
||||
uint AlternativeItemId,
|
||||
int Quantity,
|
||||
ushort Collectability = 0);
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
@ -37,6 +37,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
private QuestProgress? _nextQuest;
|
||||
private QuestProgress? _simulatedQuest;
|
||||
private QuestProgress? _gatheringQuest;
|
||||
private QuestProgress? _pendingQuest;
|
||||
private EAutomationType _automationType;
|
||||
|
||||
/// <summary>
|
||||
@ -101,6 +102,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
public QuestProgress? NextQuest => _nextQuest;
|
||||
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 void Reload()
|
||||
@ -112,6 +118,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
_startedQuest = null;
|
||||
_nextQuest = null;
|
||||
_gatheringQuest = null;
|
||||
_pendingQuest = null;
|
||||
_simulatedQuest = null;
|
||||
_safeAnimationEnd = DateTime.MinValue;
|
||||
|
||||
@ -188,6 +195,20 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
{
|
||||
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 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",
|
||||
_nextQuest.Quest.Id);
|
||||
|
||||
// if (_nextQuest.Quest.Id is LeveId)
|
||||
// _startedQuest = _nextQuest;
|
||||
|
||||
_nextQuest = null;
|
||||
}
|
||||
}
|
||||
@ -315,7 +340,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
var sequence = q.FindSequence(questToRun.Sequence);
|
||||
if (sequence == null)
|
||||
{
|
||||
DebugState = "Sequence not found";
|
||||
DebugState = $"Sequence {sequence} not found";
|
||||
Stop("Unknown sequence");
|
||||
return;
|
||||
}
|
||||
@ -457,6 +482,12 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
_gatheringQuest = null;
|
||||
}
|
||||
|
||||
public void SetPendingQuest(QuestProgress? quest)
|
||||
{
|
||||
_logger.LogInformation("PendingQuest: {QuestId}", quest?.Quest.Id);
|
||||
_pendingQuest = quest;
|
||||
}
|
||||
|
||||
protected override void UpdateCurrentTask()
|
||||
{
|
||||
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})";
|
||||
}
|
||||
|
||||
public bool HasCurrentTaskMatching<T>() =>
|
||||
_currentTask is T;
|
||||
public bool HasCurrentTaskMatching<T>([NotNullWhen(true)] out T? task)
|
||||
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;
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
@ -26,19 +25,21 @@ internal sealed class QuestRegistry
|
||||
private readonly QuestValidator _questValidator;
|
||||
private readonly JsonSchemaValidator _jsonSchemaValidator;
|
||||
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();
|
||||
|
||||
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
|
||||
QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator,
|
||||
ILogger<QuestRegistry> logger)
|
||||
ILogger<QuestRegistry> logger, LeveData leveData)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
_questData = questData;
|
||||
_questValidator = questValidator;
|
||||
_jsonSchemaValidator = jsonSchemaValidator;
|
||||
_logger = logger;
|
||||
_leveData = leveData;
|
||||
_reloadDataIpc = _pluginInterface.GetIpcProvider<object>("Questionable.ReloadData");
|
||||
}
|
||||
|
||||
@ -89,11 +90,14 @@ internal sealed class QuestRegistry
|
||||
|
||||
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()
|
||||
{
|
||||
Id = questId,
|
||||
Root = questRoot,
|
||||
Info = _questData.GetQuestInfo(questId),
|
||||
Info = questInfo,
|
||||
ReadOnly = true,
|
||||
};
|
||||
_quests[quest.Id] = quest;
|
||||
@ -143,11 +147,15 @@ internal sealed class QuestRegistry
|
||||
var questNode = JsonNode.Parse(stream)!;
|
||||
_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
|
||||
{
|
||||
Id = questId,
|
||||
Root = questNode.Deserialize<QuestRoot>()!,
|
||||
Info = _questData.GetQuestInfo(questId),
|
||||
Root = questRoot,
|
||||
Info = questInfo,
|
||||
ReadOnly = false,
|
||||
};
|
||||
_quests[quest.Id] = quest;
|
||||
|
@ -1,11 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib.GameData;
|
||||
using LLib.GameUI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model.Gathering;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Gathering;
|
||||
|
||||
@ -13,13 +20,17 @@ internal sealed class DoGather(
|
||||
GatheringController gatheringController,
|
||||
GameFunctions gameFunctions,
|
||||
IGameGui gameGui,
|
||||
ICondition condition) : ITask
|
||||
IClientState clientState,
|
||||
ICondition condition,
|
||||
ILogger<DoGather> logger) : ITask
|
||||
{
|
||||
private const uint StatusGatheringRateUp = 218;
|
||||
|
||||
private GatheringController.GatheringRequest _currentRequest = null!;
|
||||
private GatheringNode _currentNode = null!;
|
||||
private bool _wasGathering;
|
||||
private List<SlotInfo>? _slots;
|
||||
|
||||
private SlotInfo? _slotToGather;
|
||||
private Queue<EAction>? _actionQueue;
|
||||
|
||||
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode)
|
||||
{
|
||||
@ -45,17 +56,44 @@ internal sealed class DoGather(
|
||||
|
||||
_wasGathering = true;
|
||||
|
||||
if (gameGui.TryGetAddonByName("Gathering", out AtkUnitBase* atkUnitBase))
|
||||
if (gameGui.TryGetAddonByName("Gathering", out AddonGathering* addonGathering))
|
||||
{
|
||||
if (gatheringController.HasRequestedItems())
|
||||
{
|
||||
atkUnitBase->FireCallbackInt(-1);
|
||||
addonGathering->FireCallbackInt(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_slots ??= ReadSlots(atkUnitBase);
|
||||
var slot = _slots.Single(x => x.ItemId == _currentRequest.ItemId);
|
||||
atkUnitBase->FireCallbackInt(slot.Index);
|
||||
var slots = ReadSlots(addonGathering);
|
||||
if (_currentRequest.Collectability > 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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>();
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
@ -76,14 +114,122 @@ internal sealed class DoGather(
|
||||
if (itemId == 0)
|
||||
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);
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
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 (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags) && _combatData.ElementId is QuestId questId)
|
||||
{
|
||||
var questWork = questFunctions.GetQuestEx(questId);
|
||||
var questWork = questFunctions.GetQuestProgressInfo(questId);
|
||||
if (questWork == null)
|
||||
return ETaskResult.StillRunning;
|
||||
|
||||
if (QuestWorkUtils.MatchesQuestWork(_completionQuestVariableFlags, questWork.Value))
|
||||
if (QuestWorkUtils.MatchesQuestWork(_completionQuestVariableFlags, questWork))
|
||||
return ETaskResult.TaskComplete;
|
||||
else
|
||||
return ETaskResult.StillRunning;
|
||||
|
@ -19,7 +19,8 @@ internal static class Interact
|
||||
{
|
||||
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)
|
||||
yield break;
|
||||
@ -34,7 +35,7 @@ internal static class Interact
|
||||
yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
|
||||
|
||||
yield return serviceProvider.GetRequiredService<DoInteract>()
|
||||
.With(step.DataId.Value,
|
||||
.With(step.DataId.Value, quest, step.InteractionType,
|
||||
step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
|
||||
}
|
||||
|
||||
@ -50,11 +51,15 @@ internal static class Interact
|
||||
private DateTime _continueAt = DateTime.MinValue;
|
||||
|
||||
private uint DataId { get; set; }
|
||||
public Quest? Quest { get; private set; }
|
||||
public EInteractionType InteractionType { 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;
|
||||
Quest = quest;
|
||||
InteractionType = interactionType;
|
||||
SkipMarkerCheck = skipMarkerCheck;
|
||||
return this;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ internal static class SinglePlayerDuty
|
||||
[
|
||||
serviceProvider.GetRequiredService<DisableYesAlready>(),
|
||||
serviceProvider.GetRequiredService<Interact.DoInteract>()
|
||||
.With(step.DataId.Value, true),
|
||||
.With(step.DataId.Value, quest, EInteractionType.None, true),
|
||||
serviceProvider.GetRequiredService<RestoreYesAlready>()
|
||||
];
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ internal static class UseItem
|
||||
yield return serviceProvider.GetRequiredService<Move.MoveInternal>()
|
||||
.With(territoryId, destination, dataId: npcId, sprint: false);
|
||||
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))
|
||||
{
|
||||
QuestWork? questWork = questFunctions.GetQuestEx(questId);
|
||||
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(questId);
|
||||
if (questWork != null &&
|
||||
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value))
|
||||
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork))
|
||||
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()
|
||||
{
|
||||
return gatheringController.Start(new GatheringController.GatheringRequest(_gatheringPointId,
|
||||
_gatheredItem.ItemId, _gatheredItem.ItemCount, _gatheredItem.Collectability));
|
||||
_gatheredItem.ItemId, _gatheredItem.AlternativeItemId, _gatheredItem.ItemCount,
|
||||
_gatheredItem.Collectability));
|
||||
}
|
||||
|
||||
public ETaskResult Update()
|
||||
|
@ -158,12 +158,12 @@ internal static class SkipCondition
|
||||
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.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value))
|
||||
if (QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as quest variables match (step is complete)");
|
||||
return true;
|
||||
@ -172,7 +172,7 @@ internal static class SkipCondition
|
||||
|
||||
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)");
|
||||
return true;
|
||||
@ -181,8 +181,7 @@ internal static class SkipCondition
|
||||
|
||||
if (Step is { RequiredQuestVariables: { } requiredQuestVariables } && questWork != null)
|
||||
{
|
||||
if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork.Value,
|
||||
logger))
|
||||
if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork, logger))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as required variables do not match");
|
||||
return true;
|
||||
|
@ -179,9 +179,9 @@ internal static class WaitAtEnd
|
||||
|
||||
public ETaskResult Update()
|
||||
{
|
||||
QuestWork? questWork = questFunctions.GetQuestEx(Quest);
|
||||
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(Quest);
|
||||
return questWork != null &&
|
||||
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value)
|
||||
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork)
|
||||
? ETaskResult.TaskComplete
|
||||
: ETaskResult.StillRunning;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
for (int i = 0; i < questProgressInfo.Variables.Count; ++i)
|
||||
{
|
||||
QuestWorkValue? check = completionQuestVariablesFlags[i];
|
||||
if (check == null)
|
||||
@ -28,8 +29,8 @@ internal static class QuestWorkUtils
|
||||
|
||||
EQuestWorkMode mode = check.Mode;
|
||||
|
||||
byte actualHigh = (byte)(questWork.Variables[i] >> 4);
|
||||
byte actualLow = (byte)(questWork.Variables[i] & 0xF);
|
||||
byte actualHigh = (byte)(questProgressInfo.Variables[i] >> 4);
|
||||
byte actualLow = (byte)(questProgressInfo.Variables[i] & 0xF);
|
||||
|
||||
byte? checkHigh = check.High;
|
||||
byte? checkLow = check.Low;
|
||||
@ -60,7 +61,7 @@ internal static class QuestWorkUtils
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
|
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)),
|
||||
..dataManager.GetExcelSheet<SatisfactionNpc>()!
|
||||
.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);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ internal sealed class ExcelFunctions
|
||||
_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);
|
||||
if (isRegex)
|
||||
@ -33,9 +33,9 @@ internal sealed class ExcelFunctions
|
||||
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 =
|
||||
_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}";
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(excelSheetName);
|
||||
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
|
||||
if (excelSheet == null)
|
||||
{
|
||||
|
@ -344,6 +344,17 @@ internal sealed unsafe class GameFunctions
|
||||
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()
|
||||
{
|
||||
if (_condition[ConditionFlag.Mounted])
|
||||
@ -503,4 +514,16 @@ internal sealed unsafe class GameFunctions
|
||||
|
||||
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 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;
|
||||
_questData = questData;
|
||||
@ -117,6 +118,14 @@ internal sealed unsafe class QuestFunctions
|
||||
|
||||
case 1: // normal quest
|
||||
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;
|
||||
}
|
||||
|
||||
@ -189,23 +198,23 @@ internal sealed unsafe class QuestFunctions
|
||||
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
||||
}
|
||||
|
||||
public QuestWork? GetQuestEx(QuestId questId)
|
||||
{
|
||||
QuestWork* questWork = QuestManager.Instance()->GetQuestById(questId.Value);
|
||||
return questWork != null ? *questWork : null;
|
||||
}
|
||||
|
||||
public bool IsReadyToAcceptQuest(ElementId elementId)
|
||||
public QuestProgressInfo? GetQuestProgressInfo(ElementId elementId)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsReadyToAcceptQuest(questId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return true;
|
||||
{
|
||||
QuestWork* questWork = QuestManager.Instance()->GetQuestById(questId.Value);
|
||||
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
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsReadyToAcceptQuest(QuestId questId)
|
||||
public bool IsReadyToAcceptQuest(ElementId questId)
|
||||
{
|
||||
_questRegistry.TryGetQuest(questId, out var quest);
|
||||
if (quest is { Info.IsRepeatable: true })
|
||||
@ -239,6 +248,8 @@ internal sealed unsafe class QuestFunctions
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestAccepted(questId);
|
||||
else if (elementId is LeveId leveId)
|
||||
return IsQuestAccepted(leveId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
@ -251,10 +262,24 @@ internal sealed unsafe class QuestFunctions
|
||||
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)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestComplete(questId);
|
||||
else if (elementId is LeveId leveId)
|
||||
return IsQuestComplete(leveId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
@ -267,10 +292,17 @@ internal sealed unsafe class QuestFunctions
|
||||
return QuestManager.IsQuestComplete(questId.Value);
|
||||
}
|
||||
|
||||
public bool IsQuestComplete(LeveId leveId)
|
||||
{
|
||||
return QuestManager.Instance()->IsLevequestComplete(leveId.Value);
|
||||
}
|
||||
|
||||
public bool IsQuestLocked(ElementId elementId, ElementId? extraCompletedQuest = null)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestLocked(questId, extraCompletedQuest);
|
||||
else if (elementId is LeveId leveId)
|
||||
return IsQuestLocked(leveId);
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
@ -295,6 +327,17 @@ internal sealed unsafe class QuestFunctions
|
||||
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)
|
||||
{
|
||||
if (questInfo.PreviousQuests.Count == 0)
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Game.Text;
|
||||
using LLib.GameData;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Model;
|
||||
@ -13,6 +15,7 @@ public interface IQuestInfo
|
||||
public ushort Level { get; }
|
||||
public EBeastTribe BeastTribe { get; }
|
||||
public bool IsMainScenarioQuest { get; }
|
||||
public IReadOnlyList<EClassJob> ClassJobs { get; }
|
||||
|
||||
public string SimplifiedName => Name
|
||||
.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.Linq;
|
||||
using Dalamud.Game.Text;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using JetBrains.Annotations;
|
||||
using LLib.GameData;
|
||||
using Questionable.Model.Questing;
|
||||
using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
|
||||
|
||||
@ -53,6 +52,7 @@ internal sealed class QuestInfo : IQuestInfo
|
||||
PreviousInstanceContentJoin = (QuestJoin)quest.InstanceContentJoin;
|
||||
GrandCompany = (GrandCompany)quest.GrandCompany.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 GrandCompany GrandCompany { get; }
|
||||
public EBeastTribe BeastTribe { get; }
|
||||
public IReadOnlyList<EClassJob> ClassJobs { get; }
|
||||
|
||||
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
|
||||
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;
|
||||
|
||||
namespace Questionable.Model;
|
||||
@ -20,4 +22,9 @@ internal sealed class SatisfactionSupplyInfo : IQuestInfo
|
||||
public ushort Level { get; }
|
||||
public EBeastTribe BeastTribe => EBeastTribe.None;
|
||||
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.Gathering;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Controller.Steps.Leves;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
@ -103,6 +104,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton<AetherCurrentData>();
|
||||
serviceCollection.AddSingleton<AetheryteData>();
|
||||
serviceCollection.AddSingleton<GatheringData>();
|
||||
serviceCollection.AddSingleton<LeveData>();
|
||||
serviceCollection.AddSingleton<JournalData>();
|
||||
serviceCollection.AddSingleton<QuestData>();
|
||||
serviceCollection.AddSingleton<TerritoryData>();
|
||||
@ -143,12 +145,17 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.SingleJump, Jump.RepeatedJumps>();
|
||||
serviceCollection.AddTaskWithFactory<Dive.Factory, Dive.DoDive>();
|
||||
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<TurnInDelivery.Factory, TurnInDelivery.SatisfactionSupplyTurnIn>();
|
||||
serviceCollection
|
||||
.AddTaskWithFactory<SinglePlayerDuty.Factory, SinglePlayerDuty.DisableYesAlready,
|
||||
SinglePlayerDuty.RestoreYesAlready>();
|
||||
serviceCollection
|
||||
.AddTaskWithFactory<InitiateLeve.Factory, InitiateLeve.OpenJournal, InitiateLeve.Initiate,
|
||||
InitiateLeve.SelectDifficulty>();
|
||||
|
||||
serviceCollection
|
||||
.AddTaskWithFactory<WaitAtEnd.Factory,
|
||||
|
@ -14,6 +14,7 @@ using ImGuiNET;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Windows.QuestComponents;
|
||||
@ -156,12 +157,9 @@ internal sealed class ActiveQuestComponent
|
||||
}
|
||||
}
|
||||
|
||||
private QuestWork? DrawQuestWork(QuestController.QuestProgress currentQuest)
|
||||
private QuestProgressInfo? DrawQuestWork(QuestController.QuestProgress currentQuest)
|
||||
{
|
||||
if (currentQuest.Quest.Id is not QuestId questId)
|
||||
return null;
|
||||
|
||||
var questWork = _questFunctions.GetQuestEx(questId);
|
||||
var questWork = _questFunctions.GetQuestProgressInfo(currentQuest.Quest.Id);
|
||||
if (questWork != null)
|
||||
{
|
||||
Vector4 color;
|
||||
@ -175,34 +173,12 @@ internal sealed class ActiveQuestComponent
|
||||
}
|
||||
|
||||
using var styleColor = ImRaii.PushColor(ImGuiCol.Text, color);
|
||||
|
||||
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()}");
|
||||
ImGui.Text($"{questWork}");
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
string copy = "";
|
||||
for (int i = 0; i < 6; ++i)
|
||||
copy += qw.Variables[i] + " ";
|
||||
|
||||
copy = copy.Trim();
|
||||
ImGui.SetClipboardText(copy);
|
||||
_chatGui.Print($"Copied '{copy}' to clipboard");
|
||||
ImGui.SetClipboardText(questWork.ToString());
|
||||
_chatGui.Print($"Copied '{questWork}' to clipboard");
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
@ -213,7 +189,7 @@ internal sealed class ActiveQuestComponent
|
||||
ImGui.PopFont();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (currentQuest.Quest.Id is QuestId)
|
||||
{
|
||||
using var disabled = ImRaii.Disabled();
|
||||
|
||||
@ -227,13 +203,13 @@ internal sealed class ActiveQuestComponent
|
||||
}
|
||||
|
||||
private void DrawQuestButtons(QuestController.QuestProgress currentQuest, QuestStep? currentStep,
|
||||
QuestWork? questWork)
|
||||
QuestProgressInfo? questProgressInfo)
|
||||
{
|
||||
ImGui.BeginDisabled(_questController.IsRunning);
|
||||
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 (questWork == null)
|
||||
if (questProgressInfo == null)
|
||||
_questController.SetNextQuest(currentQuest.Quest);
|
||||
|
||||
_questController.ExecuteNextStep(QuestController.EAutomationType.Automatic);
|
||||
@ -261,7 +237,7 @@ internal sealed class ActiveQuestComponent
|
||||
bool colored = currentStep != null
|
||||
&& !lastStep
|
||||
&& currentStep.InteractionType == EInteractionType.Instruction
|
||||
&& _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
|
||||
&& _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>(out _);
|
||||
|
||||
ImGui.BeginDisabled(lastStep);
|
||||
if (colored)
|
||||
|
@ -9,8 +9,11 @@ using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using ImGuiNET;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller;
|
||||
@ -93,16 +96,37 @@ internal sealed class CreationUtilsComponent
|
||||
break;
|
||||
|
||||
case 1:
|
||||
_questRegistry.TryGetQuest(questManager->NormalQuests[trackedQuest.Index].QuestId,
|
||||
out var quest);
|
||||
//_questRegistry.TryGetQuest(questManager->NormalQuests[trackedQuest.Index].QuestId,
|
||||
// out var quest);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#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)
|
||||
{
|
||||
ImGui.Separator();
|
||||
|
@ -223,7 +223,8 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
ImGui.SameLine();
|
||||
|
||||
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.IsQuestLocked(quest.QuestId) &&
|
||||
(quest.IsRepeatable || !_questFunctions.IsQuestAcceptedOrComplete(quest.QuestId)))
|
||||
|
Loading…
Reference in New Issue
Block a user