Use ServiceHost/ILogger

This commit is contained in:
Liza 2024-06-08 21:16:57 +02:00
parent fe49d5354f
commit 78357dc288
Signed by: liza
GPG Key ID: 7199F8D727D55F67
35 changed files with 519 additions and 304 deletions

View File

@ -290,7 +290,7 @@ dotnet_diagnostic.CA1806.severity = warning
dotnet_diagnostic.CA1810.severity = warning dotnet_diagnostic.CA1810.severity = warning
# CA1812: Avoid uninstantiated internal classes # CA1812: Avoid uninstantiated internal classes
dotnet_diagnostic.CA1812.severity = warning dotnet_diagnostic.CA1812.severity = suggestion
# CA1813: Avoid unsealed attributes # CA1813: Avoid unsealed attributes
dotnet_diagnostic.CA1813.severity = warning dotnet_diagnostic.CA1813.severity = warning
@ -392,7 +392,7 @@ dotnet_diagnostic.CA1846.severity = warning
dotnet_diagnostic.CA1847.severity = warning dotnet_diagnostic.CA1847.severity = warning
# CA1848: Use the LoggerMessage delegates # CA1848: Use the LoggerMessage delegates
dotnet_diagnostic.CA1848.severity = warning dotnet_diagnostic.CA1848.severity = suggestion
# CA1849: Call async methods when in an async method # CA1849: Call async methods when in an async method
dotnet_diagnostic.CA1849.severity = warning dotnet_diagnostic.CA1849.severity = warning

View File

@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI; using LLib.GameUI;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.Logging;
using Questionable.Model.V1; using Questionable.Model.V1;
using Quest = Questionable.Model.Quest; using Quest = Questionable.Model.Quest;
@ -21,17 +22,17 @@ internal sealed class GameUiController : IDisposable
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly QuestController _questController; private readonly QuestController _questController;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly IPluginLog _pluginLog; private readonly ILogger<GameUiController> _logger;
public GameUiController(IAddonLifecycle addonLifecycle, IDataManager dataManager, GameFunctions gameFunctions, public GameUiController(IAddonLifecycle addonLifecycle, IDataManager dataManager, GameFunctions gameFunctions,
QuestController questController, IGameGui gameGui, IPluginLog pluginLog) QuestController questController, IGameGui gameGui, ILogger<GameUiController> logger)
{ {
_addonLifecycle = addonLifecycle; _addonLifecycle = addonLifecycle;
_dataManager = dataManager; _dataManager = dataManager;
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
_questController = questController; _questController = questController;
_gameGui = gameGui; _gameGui = gameGui;
_pluginLog = pluginLog; _logger = logger;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
@ -45,26 +46,26 @@ internal sealed class GameUiController : IDisposable
{ {
if (_gameGui.TryGetAddonByName("SelectString", out AddonSelectString* addonSelectString)) if (_gameGui.TryGetAddonByName("SelectString", out AddonSelectString* addonSelectString))
{ {
_pluginLog.Information("SelectString window is open"); _logger.LogInformation("SelectString window is open");
SelectStringPostSetup(addonSelectString, true); SelectStringPostSetup(addonSelectString, true);
} }
if (_gameGui.TryGetAddonByName("CutSceneSelectString", if (_gameGui.TryGetAddonByName("CutSceneSelectString",
out AddonCutSceneSelectString* addonCutSceneSelectString)) out AddonCutSceneSelectString* addonCutSceneSelectString))
{ {
_pluginLog.Information("CutSceneSelectString window is open"); _logger.LogInformation("CutSceneSelectString window is open");
CutsceneSelectStringPostSetup(addonCutSceneSelectString, true); CutsceneSelectStringPostSetup(addonCutSceneSelectString, true);
} }
if (_gameGui.TryGetAddonByName("SelectIconString", out AddonSelectIconString* addonSelectIconString)) if (_gameGui.TryGetAddonByName("SelectIconString", out AddonSelectIconString* addonSelectIconString))
{ {
_pluginLog.Information("SelectIconString window is open"); _logger.LogInformation("SelectIconString window is open");
SelectIconStringPostSetup(addonSelectIconString, true); SelectIconStringPostSetup(addonSelectIconString, true);
} }
if (_gameGui.TryGetAddonByName("SelectYesno", out AddonSelectYesno* addonSelectYesno)) if (_gameGui.TryGetAddonByName("SelectYesno", out AddonSelectYesno* addonSelectYesno))
{ {
_pluginLog.Information("SelectYesno window is open"); _logger.LogInformation("SelectYesno window is open");
SelectYesnoPostSetup(addonSelectYesno, true); SelectYesnoPostSetup(addonSelectYesno, true);
} }
} }
@ -151,7 +152,7 @@ internal sealed class GameUiController : IDisposable
var currentQuest = _questController.CurrentQuest; var currentQuest = _questController.CurrentQuest;
if (currentQuest == null) if (currentQuest == null)
{ {
_pluginLog.Information("Ignoring list choice, no active quest"); _logger.LogInformation("Ignoring list choice, no active quest");
return null; return null;
} }
@ -167,7 +168,7 @@ internal sealed class GameUiController : IDisposable
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step); var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step == null) if (step == null)
{ {
_pluginLog.Information("Ignoring list choice, no active step"); _logger.LogInformation("Ignoring list choice, no active step");
return null; return null;
} }
@ -178,7 +179,7 @@ internal sealed class GameUiController : IDisposable
{ {
if (dialogueChoice.Answer == null) if (dialogueChoice.Answer == null)
{ {
_pluginLog.Information("Ignoring entry in DialogueChoices, no answer"); _logger.LogInformation("Ignoring entry in DialogueChoices, no answer");
continue; continue;
} }
@ -212,28 +213,31 @@ internal sealed class GameUiController : IDisposable
if (actualPrompt == null && !string.IsNullOrEmpty(excelPrompt)) if (actualPrompt == null && !string.IsNullOrEmpty(excelPrompt))
{ {
_pluginLog.Information($"Unexpected excelPrompt: {excelPrompt}"); _logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}", excelPrompt);
continue; continue;
} }
if (actualPrompt != null && (excelPrompt == null || !GameStringEquals(actualPrompt, excelPrompt))) if (actualPrompt != null && (excelPrompt == null || !GameStringEquals(actualPrompt, excelPrompt)))
{ {
_pluginLog.Information($"Unexpected excelPrompt: {excelPrompt}, actualPrompt: {actualPrompt}"); _logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}, actualPrompt: {ActualPrompt}",
excelPrompt, actualPrompt);
continue; continue;
} }
for (int i = 0; i < answers.Count; ++i) for (int i = 0; i < answers.Count; ++i)
{ {
_pluginLog.Verbose($"Checking if {answers[i]} == {excelAnswer}"); _logger.LogTrace("Checking if {ActualAnswer} == {ExpectedAnswer}",
answers[i], excelAnswer);
if (GameStringEquals(answers[i], excelAnswer)) if (GameStringEquals(answers[i], excelAnswer))
{ {
_pluginLog.Information($"Returning {i}: '{answers[i]}' for '{actualPrompt}'"); _logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}'",
i, answers[i], actualPrompt);
return i; return i;
} }
} }
} }
_pluginLog.Information($"No matching answer found for {actualPrompt}."); _logger.LogInformation("No matching answer found for {Prompt}.", actualPrompt);
return null; return null;
} }
@ -249,7 +253,7 @@ internal sealed class GameUiController : IDisposable
if (actualPrompt == null) if (actualPrompt == null)
return; return;
_pluginLog.Verbose($"Prompt: '{actualPrompt}'"); _logger.LogTrace("Prompt: '{Prompt}'", actualPrompt);
var currentQuest = _questController.CurrentQuest; var currentQuest = _questController.CurrentQuest;
if (currentQuest == null) if (currentQuest == null)
@ -277,7 +281,7 @@ internal sealed class GameUiController : IDisposable
private unsafe bool HandleDefaultYesNo(AddonSelectYesno* addonSelectYesno, Quest quest, private unsafe bool HandleDefaultYesNo(AddonSelectYesno* addonSelectYesno, Quest quest,
IList<DialogueChoice> dialogueChoices, string actualPrompt, bool checkAllSteps) IList<DialogueChoice> dialogueChoices, string actualPrompt, bool checkAllSteps)
{ {
_pluginLog.Verbose($"DefaultYesNo: Choice count: {dialogueChoices.Count}"); _logger.LogTrace("DefaultYesNo: Choice count: {Count}", dialogueChoices.Count);
foreach (var dialogueChoice in dialogueChoices) foreach (var dialogueChoice in dialogueChoices)
{ {
string? excelPrompt; string? excelPrompt;
@ -325,21 +329,22 @@ internal sealed class GameUiController : IDisposable
bool increaseStepCount = true; bool increaseStepCount = true;
QuestStep? step = sequence.FindStep(currentQuest.Step); QuestStep? step = sequence.FindStep(currentQuest.Step);
if (step != null) if (step != null)
_pluginLog.Verbose($"Current step: {step.TerritoryId}, {step.TargetTerritoryId}"); _logger.LogTrace("Current step: {CurrentTerritory}, {TargetTerritory}", step.TerritoryId,
step.TargetTerritoryId);
if (step == null || step.TargetTerritoryId == null) if (step == null || step.TargetTerritoryId == null)
{ {
_pluginLog.Verbose("TravelYesNo: Checking previous step..."); _logger.LogTrace("TravelYesNo: 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));
increaseStepCount = false; increaseStepCount = false;
if (step != null) if (step != null)
_pluginLog.Verbose($"Previous step: {step.TerritoryId}, {step.TargetTerritoryId}"); _logger.LogTrace("Previous step: {CurrentTerritory}, {TargetTerritory}", step.TerritoryId, step.TargetTerritoryId);
} }
if (step == null || step.TargetTerritoryId == null) if (step == null || step.TargetTerritoryId == null)
{ {
_pluginLog.Verbose("TravelYesNo: Not found"); _logger.LogTrace("TravelYesNo: Not found");
return; return;
} }
@ -351,11 +356,11 @@ internal sealed class GameUiController : IDisposable
string? excelPrompt = entry.Question?.ToString(); string? excelPrompt = entry.Question?.ToString();
if (excelPrompt == null || !GameStringEquals(excelPrompt, actualPrompt)) if (excelPrompt == null || !GameStringEquals(excelPrompt, actualPrompt))
{ {
_pluginLog.Information($"Ignoring prompt '{excelPrompt}'"); _logger.LogDebug("Ignoring prompt '{Prompt}'", excelPrompt);
continue; continue;
} }
_pluginLog.Information($"Using warp {entry.RowId}, {excelPrompt}"); _logger.LogInformation("Using warp {Id}, {Prompt}", entry.RowId, excelPrompt);
addonSelectYesno->AtkUnitBase.FireCallbackInt(0); addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
if (increaseStepCount) if (increaseStepCount)
_questController.IncreaseStepCount(); _questController.IncreaseStepCount();
@ -365,7 +370,7 @@ internal sealed class GameUiController : IDisposable
private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args) private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args)
{ {
_pluginLog.Information("Closing Credits sequence"); _logger.LogInformation("Closing Credits sequence");
AtkUnitBase* addon = (AtkUnitBase*)args.Addon; AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
addon->FireCallbackInt(-2); addon->FireCallbackInt(-2);
} }
@ -374,7 +379,7 @@ internal sealed class GameUiController : IDisposable
{ {
if (_questController.CurrentQuest?.Quest.QuestId == 4526) if (_questController.CurrentQuest?.Quest.QuestId == 4526)
{ {
_pluginLog.Information("Closing Unending Codex"); _logger.LogInformation("Closing Unending Codex");
AtkUnitBase* addon = (AtkUnitBase*)args.Addon; AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
addon->FireCallbackInt(-2); addon->FireCallbackInt(-2);
} }

View File

@ -12,6 +12,7 @@ using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.Game.Control;
using Microsoft.Extensions.Logging;
using Questionable.External; using Questionable.External;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
@ -26,18 +27,18 @@ internal sealed class MovementController : IDisposable
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly IPluginLog _pluginLog; private readonly ILogger<MovementController> _logger;
private CancellationTokenSource? _cancellationTokenSource; private CancellationTokenSource? _cancellationTokenSource;
private Task<List<Vector3>>? _pathfindTask; private Task<List<Vector3>>? _pathfindTask;
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, GameFunctions gameFunctions, public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, GameFunctions gameFunctions,
ICondition condition, IPluginLog pluginLog) ICondition condition, ILogger<MovementController> logger)
{ {
_navmeshIpc = navmeshIpc; _navmeshIpc = navmeshIpc;
_clientState = clientState; _clientState = clientState;
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
_condition = condition; _condition = condition;
_pluginLog = pluginLog; _logger = logger;
} }
public bool IsNavmeshReady => _navmeshIpc.IsReady; public bool IsNavmeshReady => _navmeshIpc.IsReady;
@ -51,9 +52,8 @@ internal sealed class MovementController : IDisposable
{ {
if (_pathfindTask.IsCompletedSuccessfully) if (_pathfindTask.IsCompletedSuccessfully)
{ {
_pluginLog.Information( _logger.LogInformation("Pathfinding complete, route: [{Route}]",
string.Create(CultureInfo.InvariantCulture, string.Join(" → ", _pathfindTask.Result.Select(x => x.ToString("G", CultureInfo.InvariantCulture))));
$"Pathfinding complete, route: [{string.Join(" ", _pathfindTask.Result.Select(x => x.ToString()))}]"));
var navPoints = _pathfindTask.Result.Skip(1).ToList(); var navPoints = _pathfindTask.Result.Skip(1).ToList();
Vector3 start = _clientState.LocalPlayer?.Position ?? navPoints[0]; Vector3 start = _clientState.LocalPlayer?.Position ?? navPoints[0];
@ -83,7 +83,7 @@ internal sealed class MovementController : IDisposable
if (actualDistance > 100f && if (actualDistance > 100f &&
ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 4) == 0) ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 4) == 0)
{ {
_pluginLog.Information("Triggering Sprint"); _logger.LogInformation("Triggering Sprint");
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 4); ActionManager.Instance()->UseAction(ActionType.GeneralAction, 4);
} }
} }
@ -94,7 +94,7 @@ internal sealed class MovementController : IDisposable
} }
else if (_pathfindTask.IsCompleted) else if (_pathfindTask.IsCompleted)
{ {
_pluginLog.Information("Unable to complete pathfinding task"); _logger.LogWarning("Unable to complete pathfinding task");
ResetPathfinding(); ResetPathfinding();
} }
} }
@ -170,7 +170,7 @@ internal sealed class MovementController : IDisposable
{ {
fly |= _condition[ConditionFlag.Diving]; fly |= _condition[ConditionFlag.Diving];
PrepareNavigation(type, dataId, to, fly, sprint, stopDistance); PrepareNavigation(type, dataId, to, fly, sprint, stopDistance);
_pluginLog.Information($"Pathfinding to {Destination}"); _logger.LogInformation("Pathfinding to {Destination}", Destination);
_cancellationTokenSource = new(); _cancellationTokenSource = new();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10)); _cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
@ -183,7 +183,7 @@ internal sealed class MovementController : IDisposable
fly |= _condition[ConditionFlag.Diving]; fly |= _condition[ConditionFlag.Diving];
PrepareNavigation(type, dataId, to.Last(), fly, sprint, stopDistance); PrepareNavigation(type, dataId, to.Last(), fly, sprint, stopDistance);
_pluginLog.Information($"Moving to {Destination}"); _logger.LogInformation("Moving to {Destination}", Destination);
_navmeshIpc.MoveTo(to, fly); _navmeshIpc.MoveTo(to, fly);
} }

View File

@ -0,0 +1,38 @@
using System.Numerics;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using Questionable.Model;
namespace Questionable.Controller;
internal sealed class NavigationShortcutController
{
private readonly IGameGui _gameGui;
private readonly MovementController _movementController;
private readonly GameFunctions _gameFunctions;
public NavigationShortcutController(IGameGui gameGui, MovementController movementController,
GameFunctions gameFunctions)
{
_gameGui = gameGui;
_movementController = movementController;
_gameFunctions = gameFunctions;
}
public unsafe void HandleNavigationShortcut()
{
var inputData = UIInputData.Instance();
if (inputData == null)
return;
if (inputData->IsGameWindowFocused &&
inputData->UIFilteredMouseButtonReleasedFlags.HasFlag(MouseButtonFlags.LBUTTON) &&
inputData->GetKeyState(SeVirtualKey.MENU).HasFlag(KeyStateFlags.Down) &&
_gameGui.ScreenToWorld(new Vector2(inputData->CursorXPosition, inputData->CursorYPosition),
out Vector3 worldPos))
{
_movementController.NavigateTo(EMovementType.Shortcut, null, worldPos,
_gameFunctions.IsFlyingUnlockedInCurrentZone(), true);
}
}
}

View File

@ -1,15 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Numerics; using System.Numerics;
using System.Text.Json;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions; using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.Logging;
using Questionable.Data; using Questionable.Data;
using Questionable.External; using Questionable.External;
using Questionable.Model; using Questionable.Model;
@ -20,39 +16,34 @@ namespace Questionable.Controller;
internal sealed class QuestController internal sealed class QuestController
{ {
private readonly DalamudPluginInterface _pluginInterface;
private readonly IDataManager _dataManager;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly MovementController _movementController; private readonly MovementController _movementController;
private readonly IPluginLog _pluginLog; private readonly ILogger<QuestController> _logger;
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly AetheryteData _aetheryteData; private readonly AetheryteData _aetheryteData;
private readonly LifestreamIpc _lifestreamIpc; private readonly LifestreamIpc _lifestreamIpc;
private readonly TerritoryData _territoryData; private readonly TerritoryData _territoryData;
private readonly Dictionary<ushort, Quest> _quests = new(); private readonly QuestRegistry _questRegistry;
public QuestController(DalamudPluginInterface pluginInterface, IDataManager dataManager, IClientState clientState, public QuestController(IClientState clientState, GameFunctions gameFunctions, MovementController movementController,
GameFunctions gameFunctions, MovementController movementController, IPluginLog pluginLog, ICondition condition, ILogger<QuestController> logger, ICondition condition, IChatGui chatGui, IFramework framework,
IChatGui chatGui, IFramework framework, AetheryteData aetheryteData, LifestreamIpc lifestreamIpc) AetheryteData aetheryteData, LifestreamIpc lifestreamIpc, TerritoryData territoryData,
QuestRegistry questRegistry)
{ {
_pluginInterface = pluginInterface;
_dataManager = dataManager;
_clientState = clientState; _clientState = clientState;
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
_movementController = movementController; _movementController = movementController;
_pluginLog = pluginLog; _logger = logger;
_condition = condition; _condition = condition;
_chatGui = chatGui; _chatGui = chatGui;
_framework = framework; _framework = framework;
_aetheryteData = aetheryteData; _aetheryteData = aetheryteData;
_lifestreamIpc = lifestreamIpc; _lifestreamIpc = lifestreamIpc;
_territoryData = new TerritoryData(dataManager); _territoryData = territoryData;
_questRegistry = questRegistry;
Reload();
_gameFunctions.QuestController = this;
} }
@ -62,88 +53,10 @@ internal sealed class QuestController
public void Reload() public void Reload()
{ {
_quests.Clear();
CurrentQuest = null; CurrentQuest = null;
DebugState = null; DebugState = null;
#if RELEASE _questRegistry.Reload();
_pluginLog.Information("Loading quests from assembly");
QuestPaths.AssemblyQuestLoader.LoadQuestsFromEmbeddedResources(LoadQuestFromStream);
#else
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation?.Directory?.Parent?.Parent;
if (solutionDirectory != null)
{
DirectoryInfo pathProjectDirectory =
new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "QuestPaths"));
if (pathProjectDirectory.Exists)
{
LoadFromDirectory(new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "Shadowbringers")));
LoadFromDirectory(new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "Endwalker")));
}
}
#endif
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
foreach (var (questId, quest) in _quests)
{
var questData =
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Quest>()!.GetRow((uint)questId + 0x10000);
if (questData == null)
continue;
quest.Name = questData.Name.ToString();
}
}
private void LoadQuestFromStream(string fileName, Stream stream)
{
_pluginLog.Verbose($"Loading quest from '{fileName}'");
var (questId, name) = ExtractQuestDataFromName(fileName);
Quest quest = new Quest
{
QuestId = questId,
Name = name,
Data = JsonSerializer.Deserialize<QuestData>(stream)!,
};
_quests[questId] = quest;
}
public bool IsKnownQuest(ushort questId) => _quests.ContainsKey(questId);
private void LoadFromDirectory(DirectoryInfo directory)
{
if (!directory.Exists)
{
_pluginLog.Information($"Not loading quests from {directory} (doesn't exist)");
return;
}
_pluginLog.Information($"Loading quests from {directory}");
foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
{
try
{
using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
LoadQuestFromStream(fileInfo.Name, stream);
}
catch (Exception e)
{
throw new InvalidDataException($"Unable to load file {fileInfo.FullName}", e);
}
}
foreach (DirectoryInfo childDirectory in directory.GetDirectories())
LoadFromDirectory(childDirectory);
}
private static (ushort QuestId, string Name) ExtractQuestDataFromName(string resourceName)
{
string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
name = name.Substring(name.LastIndexOf('.') + 1);
string[] parts = name.Split('_', 2);
return (ushort.Parse(parts[0], CultureInfo.InvariantCulture), parts[1]);
} }
public void Update() public void Update()
@ -158,7 +71,7 @@ internal sealed class QuestController
} }
else if (CurrentQuest == null || CurrentQuest.Quest.QuestId != currentQuestId) else if (CurrentQuest == null || CurrentQuest.Quest.QuestId != currentQuestId)
{ {
if (_quests.TryGetValue(currentQuestId, out var quest)) if (_questRegistry.TryGetQuest(currentQuestId, out var quest))
CurrentQuest = new QuestProgress(quest, currentSequence, 0); CurrentQuest = new QuestProgress(quest, currentSequence, 0);
else if (CurrentQuest != null) else if (CurrentQuest != null)
CurrentQuest = null; CurrentQuest = null;
@ -244,7 +157,7 @@ internal sealed class QuestController
(QuestSequence? seq, QuestStep? step) = GetNextStep(); (QuestSequence? seq, QuestStep? step) = GetNextStep();
if (CurrentQuest == null || seq == null || step == null) if (CurrentQuest == null || seq == null || step == null)
{ {
_pluginLog.Warning("Unable to retrieve next quest step, not increasing step count"); _logger.LogWarning("Unable to retrieve next quest step, not increasing step count");
return; return;
} }
@ -271,7 +184,7 @@ internal sealed class QuestController
(QuestSequence? seq, QuestStep? step) = GetNextStep(); (QuestSequence? seq, QuestStep? step) = GetNextStep();
if (CurrentQuest == null || seq == null || step == null) if (CurrentQuest == null || seq == null || step == null)
{ {
_pluginLog.Warning("Unable to retrieve next quest step, not increasing dialogue choice count"); _logger.LogWarning("Unable to retrieve next quest step, not increasing dialogue choice count");
return; return;
} }
@ -292,13 +205,13 @@ internal sealed class QuestController
(QuestSequence? seq, QuestStep? step) = GetNextStep(); (QuestSequence? seq, QuestStep? step) = GetNextStep();
if (CurrentQuest == null || seq == null || step == null) if (CurrentQuest == null || seq == null || step == null)
{ {
_pluginLog.Warning("Could not retrieve next quest step, not doing anything"); _logger.LogWarning("Could not retrieve next quest step, not doing anything");
return; return;
} }
if (step.Disabled) if (step.Disabled)
{ {
_pluginLog.Information("Skipping step, as it is disabled"); _logger.LogInformation("Skipping step, as it is disabled");
IncreaseStepCount(); IncreaseStepCount();
return; return;
} }
@ -315,14 +228,14 @@ internal sealed class QuestController
(_aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.From) < 20 || (_aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.From) < 20 ||
_aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.To) < 20))) _aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.To) < 20)))
{ {
_pluginLog.Information("Skipping aetheryte teleport"); _logger.LogInformation("Skipping aetheryte teleport");
skipTeleport = true; skipTeleport = true;
} }
} }
if (skipTeleport) if (skipTeleport)
{ {
_pluginLog.Information("Marking aetheryte shortcut as used"); _logger.LogInformation("Marking aetheryte shortcut as used");
CurrentQuest = CurrentQuest with CurrentQuest = CurrentQuest with
{ {
StepProgress = CurrentQuest.StepProgress with { AetheryteShortcutUsed = true } StepProgress = CurrentQuest.StepProgress with { AetheryteShortcutUsed = true }
@ -332,12 +245,12 @@ internal sealed class QuestController
{ {
if (!_gameFunctions.IsAetheryteUnlocked(step.AetheryteShortcut.Value)) if (!_gameFunctions.IsAetheryteUnlocked(step.AetheryteShortcut.Value))
{ {
_pluginLog.Error($"Aetheryte {step.AetheryteShortcut.Value} is not unlocked."); _logger.LogError("Aetheryte {Aetheryte} is not unlocked.", step.AetheryteShortcut.Value);
_chatGui.Print($"[Questionable] Aetheryte {step.AetheryteShortcut.Value} is not unlocked."); _chatGui.Print($"[Questionable] Aetheryte {step.AetheryteShortcut.Value} is not unlocked.");
} }
else if (_gameFunctions.TeleportAetheryte(step.AetheryteShortcut.Value)) else if (_gameFunctions.TeleportAetheryte(step.AetheryteShortcut.Value))
{ {
_pluginLog.Information("Travelling via aetheryte..."); _logger.LogInformation("Travelling via aetheryte...");
CurrentQuest = CurrentQuest with CurrentQuest = CurrentQuest with
{ {
StepProgress = CurrentQuest.StepProgress with { AetheryteShortcutUsed = true } StepProgress = CurrentQuest.StepProgress with { AetheryteShortcutUsed = true }
@ -345,7 +258,7 @@ internal sealed class QuestController
} }
else else
{ {
_pluginLog.Warning("Unable to teleport to aetheryte"); _logger.LogWarning("Unable to teleport to aetheryte");
_chatGui.Print("[Questionable] Unable to teleport to aetheryte."); _chatGui.Print("[Questionable] Unable to teleport to aetheryte.");
} }
@ -355,12 +268,12 @@ internal sealed class QuestController
if (!step.SkipIf.Contains(ESkipCondition.Never)) if (!step.SkipIf.Contains(ESkipCondition.Never))
{ {
_pluginLog.Information($"Checking skip conditions; {string.Join(",", step.SkipIf)}"); _logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", step.SkipIf));
if (step.SkipIf.Contains(ESkipCondition.FlyingUnlocked) && if (step.SkipIf.Contains(ESkipCondition.FlyingUnlocked) &&
_gameFunctions.IsFlyingUnlocked(step.TerritoryId)) _gameFunctions.IsFlyingUnlocked(step.TerritoryId))
{ {
_pluginLog.Information("Skipping step, as flying is unlocked"); _logger.LogInformation("Skipping step, as flying is unlocked");
IncreaseStepCount(); IncreaseStepCount();
return; return;
} }
@ -368,7 +281,7 @@ internal sealed class QuestController
if (step.SkipIf.Contains(ESkipCondition.FlyingLocked) && if (step.SkipIf.Contains(ESkipCondition.FlyingLocked) &&
!_gameFunctions.IsFlyingUnlocked(step.TerritoryId)) !_gameFunctions.IsFlyingUnlocked(step.TerritoryId))
{ {
_pluginLog.Information("Skipping step, as flying is locked"); _logger.LogInformation("Skipping step, as flying is locked");
IncreaseStepCount(); IncreaseStepCount();
return; return;
} }
@ -380,7 +293,7 @@ internal sealed class QuestController
} && } &&
_gameFunctions.IsAetheryteUnlocked((EAetheryteLocation)step.DataId.Value)) _gameFunctions.IsAetheryteUnlocked((EAetheryteLocation)step.DataId.Value))
{ {
_pluginLog.Information("Skipping step, as aetheryte/aethernet shard is unlocked"); _logger.LogInformation("Skipping step, as aetheryte/aethernet shard is unlocked");
IncreaseStepCount(); IncreaseStepCount();
return; return;
} }
@ -388,7 +301,7 @@ internal sealed class QuestController
if (step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } && if (step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } &&
_gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value)) _gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value))
{ {
_pluginLog.Information("Skipping step, as current is unlocked"); _logger.LogInformation("Skipping step, as current is unlocked");
IncreaseStepCount(); IncreaseStepCount();
return; return;
} }
@ -396,7 +309,7 @@ internal sealed class QuestController
QuestWork? questWork = _gameFunctions.GetQuestEx(CurrentQuest.Quest.QuestId); QuestWork? questWork = _gameFunctions.GetQuestEx(CurrentQuest.Quest.QuestId);
if (questWork != null && step.MatchesQuestVariables(questWork.Value)) if (questWork != null && step.MatchesQuestVariables(questWork.Value))
{ {
_pluginLog.Information("Skipping step, as quest variables match"); _logger.LogInformation("Skipping step, as quest variables match");
IncreaseStepCount(); IncreaseStepCount();
return; return;
} }
@ -418,7 +331,7 @@ internal sealed class QuestController
{ {
if (_aetheryteData.CalculateDistance(playerPosition, territoryType, from) < 11) if (_aetheryteData.CalculateDistance(playerPosition, territoryType, from) < 11)
{ {
_pluginLog.Information($"Using lifestream to teleport to {to}"); _logger.LogInformation("Using lifestream to teleport to {Destination}", to);
_lifestreamIpc.Teleport(to); _lifestreamIpc.Teleport(to);
CurrentQuest = CurrentQuest with CurrentQuest = CurrentQuest with
{ {
@ -427,7 +340,7 @@ internal sealed class QuestController
} }
else else
{ {
_pluginLog.Information("Moving to aethernet shortcut"); _logger.LogInformation("Moving to aethernet shortcut");
_movementController.NavigateTo(EMovementType.Quest, (uint)from, _aetheryteData.Locations[from], _movementController.NavigateTo(EMovementType.Quest, (uint)from, _aetheryteData.Locations[from],
false, true, false, true,
AetheryteConverter.IsLargeAetheryte(from) ? 10.9f : 6.9f); AetheryteConverter.IsLargeAetheryte(from) ? 10.9f : 6.9f);
@ -437,14 +350,16 @@ internal sealed class QuestController
} }
} }
else else
_pluginLog.Warning( _logger.LogWarning(
$"Aethernet shortcut not unlocked (from: {step.AethernetShortcut.From}, to: {step.AethernetShortcut.To}), walking manually"); "Aethernet shortcut not unlocked (from: {FromAetheryte}, to: {ToAetheryte}), walking manually",
step.AethernetShortcut.From, step.AethernetShortcut.To);
} }
if (step.TargetTerritoryId.HasValue && step.TerritoryId != step.TargetTerritoryId && step.TargetTerritoryId == _clientState.TerritoryType) if (step.TargetTerritoryId.HasValue && step.TerritoryId != step.TargetTerritoryId &&
step.TargetTerritoryId == _clientState.TerritoryType)
{ {
// we assume whatever e.g. interaction, walkto etc. we have will trigger the zone transition // we assume whatever e.g. interaction, walkto etc. we have will trigger the zone transition
_pluginLog.Information("Zone transition, skipping rest of step"); _logger.LogInformation("Zone transition, skipping rest of step");
IncreaseStepCount(); IncreaseStepCount();
return; return;
} }
@ -453,7 +368,7 @@ internal sealed class QuestController
(_clientState.LocalPlayer!.Position - step.JumpDestination.Position).Length() <= (_clientState.LocalPlayer!.Position - step.JumpDestination.Position).Length() <=
(step.JumpDestination.StopDistance ?? 1f)) (step.JumpDestination.StopDistance ?? 1f))
{ {
_pluginLog.Information("We're at the jump destination, skipping movement"); _logger.LogInformation("We're at the jump destination, skipping movement");
} }
else if (step.Position != null) else if (step.Position != null)
{ {
@ -468,7 +383,7 @@ internal sealed class QuestController
if (step.Mount == true && !_gameFunctions.HasStatusPreventingSprintOrMount()) if (step.Mount == true && !_gameFunctions.HasStatusPreventingSprintOrMount())
{ {
_pluginLog.Information("Step explicitly wants a mount, trying to mount..."); _logger.LogInformation("Step explicitly wants a mount, trying to mount...");
if (!_condition[ConditionFlag.Mounted] && !_condition[ConditionFlag.InCombat] && if (!_condition[ConditionFlag.Mounted] && !_condition[ConditionFlag.InCombat] &&
_territoryData.CanUseMount(_clientState.TerritoryType)) _territoryData.CanUseMount(_clientState.TerritoryType))
{ {
@ -478,7 +393,7 @@ internal sealed class QuestController
} }
else if (step.Mount == false) else if (step.Mount == false)
{ {
_pluginLog.Information("Step explicitly wants no mount, trying to unmount..."); _logger.LogInformation("Step explicitly wants no mount, trying to unmount...");
if (_condition[ConditionFlag.Mounted]) if (_condition[ConditionFlag.Mounted])
{ {
_gameFunctions.Unmount(); _gameFunctions.Unmount();
@ -499,7 +414,7 @@ internal sealed class QuestController
if (actualDistance > distance) if (actualDistance > distance)
{ {
_movementController.NavigateTo(EMovementType.Quest, step.DataId, step.Position.Value, _movementController.NavigateTo(EMovementType.Quest, step.DataId, step.Position.Value,
fly: step.Fly == true && _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType), fly: step.Fly == true && _gameFunctions.IsFlyingUnlockedInCurrentZone(),
sprint: step.Sprint != false, sprint: step.Sprint != false,
stopDistance: distance); stopDistance: distance);
return; return;
@ -511,7 +426,7 @@ internal sealed class QuestController
if (actualDistance > distance) if (actualDistance > distance)
{ {
_movementController.NavigateTo(EMovementType.Quest, step.DataId, [step.Position.Value], _movementController.NavigateTo(EMovementType.Quest, step.DataId, [step.Position.Value],
fly: step.Fly == true && _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType), fly: step.Fly == true && _gameFunctions.IsFlyingUnlockedInCurrentZone(),
sprint: step.Sprint != false, sprint: step.Sprint != false,
stopDistance: distance); stopDistance: distance);
return; return;
@ -524,12 +439,12 @@ internal sealed class QuestController
if (gameObject == null || if (gameObject == null ||
(gameObject.Position - _clientState.LocalPlayer!.Position).Length() > step.StopDistance) (gameObject.Position - _clientState.LocalPlayer!.Position).Length() > step.StopDistance)
{ {
_pluginLog.Warning("Object not found or too far away, no position so we can't move"); _logger.LogWarning("Object not found or too far away, no position so we can't move");
return; return;
} }
} }
_pluginLog.Information($"Running logic for {step.InteractionType}"); _logger.LogInformation("Running logic for {InteractionType}", step.InteractionType);
switch (step.InteractionType) switch (step.InteractionType)
{ {
case EInteractionType.Interact: case EInteractionType.Interact:
@ -538,7 +453,7 @@ internal sealed class QuestController
GameObject? gameObject = _gameFunctions.FindObjectByDataId(step.DataId.Value); GameObject? gameObject = _gameFunctions.FindObjectByDataId(step.DataId.Value);
if (gameObject == null) if (gameObject == null)
{ {
_pluginLog.Warning($"No game object with dataId {step.DataId}"); _logger.LogWarning("No game object with dataId {DataId}", step.DataId);
return; return;
} }
@ -555,7 +470,7 @@ internal sealed class QuestController
IncreaseStepCount(); IncreaseStepCount();
} }
else else
_pluginLog.Warning("Not interacting on current step, DataId is null"); _logger.LogWarning("Not interacting on current step, DataId is null");
break; break;
@ -584,8 +499,10 @@ internal sealed class QuestController
case EInteractionType.AttuneAetherCurrent: case EInteractionType.AttuneAetherCurrent:
if (step.DataId != null) if (step.DataId != null)
{ {
_pluginLog.Information( _logger.LogInformation(
$"{step.AetherCurrentId} → {_gameFunctions.IsAetherCurrentUnlocked(step.AetherCurrentId.GetValueOrDefault())}"); "{AetherCurrentId} is unlocked = {Unlocked}",
step.AetherCurrentId,
_gameFunctions.IsAetherCurrentUnlocked(step.AetherCurrentId.GetValueOrDefault()));
if (step.AetherCurrentId == null || if (step.AetherCurrentId == null ||
!_gameFunctions.IsAetherCurrentUnlocked(step.AetherCurrentId.Value)) !_gameFunctions.IsAetherCurrentUnlocked(step.AetherCurrentId.Value))
_gameFunctions.InteractWith(step.DataId.Value); _gameFunctions.InteractWith(step.DataId.Value);
@ -662,7 +579,8 @@ internal sealed class QuestController
if (step.ChatMessage != null) if (step.ChatMessage != null)
{ {
string? excelString = _gameFunctions.GetDialogueText(CurrentQuest.Quest, step.ChatMessage.ExcelSheet, string? excelString = _gameFunctions.GetDialogueText(CurrentQuest.Quest,
step.ChatMessage.ExcelSheet,
step.ChatMessage.Key); step.ChatMessage.Key);
if (excelString == null) if (excelString == null)
return; return;
@ -722,7 +640,7 @@ internal sealed class QuestController
break; break;
default: default:
_pluginLog.Warning($"Action '{step.InteractionType}' is not implemented"); _logger.LogWarning("Action '{InteractionType}' is not implemented", step.InteractionType);
break; break;
} }
} }

View File

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Text.Json;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.V1;
namespace Questionable.Controller;
internal sealed class QuestRegistry
{
private readonly DalamudPluginInterface _pluginInterface;
private readonly IDataManager _dataManager;
private readonly ILogger<QuestRegistry> _logger;
private readonly Dictionary<ushort, Quest> _quests = new();
public QuestRegistry(DalamudPluginInterface pluginInterface, IDataManager dataManager, ILogger<QuestRegistry> logger)
{
_pluginInterface = pluginInterface;
_dataManager = dataManager;
_logger = logger;
}
public void Reload()
{
_quests.Clear();
#if RELEASE
_logger.LogInformation("Loading quests from assembly");
QuestPaths.AssemblyQuestLoader.LoadQuestsFromEmbeddedResources(LoadQuestFromStream);
#else
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation?.Directory?.Parent?.Parent;
if (solutionDirectory != null)
{
DirectoryInfo pathProjectDirectory =
new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "QuestPaths"));
if (pathProjectDirectory.Exists)
{
LoadFromDirectory(new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "Shadowbringers")));
LoadFromDirectory(new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "Endwalker")));
}
}
#endif
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
foreach (var (questId, quest) in _quests)
{
var questData =
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Quest>()!.GetRow((uint)questId + 0x10000);
if (questData == null)
continue;
quest.Name = questData.Name.ToString();
}
}
private void LoadQuestFromStream(string fileName, Stream stream)
{
_logger.LogTrace("Loading quest from '{FileName}'", fileName);
var (questId, name) = ExtractQuestDataFromName(fileName);
Quest quest = new Quest
{
QuestId = questId,
Name = name,
Data = JsonSerializer.Deserialize<QuestData>(stream)!,
};
_quests[questId] = quest;
}
private void LoadFromDirectory(DirectoryInfo directory)
{
if (!directory.Exists)
{
_logger.LogInformation("Not loading quests from {DirectoryName} (doesn't exist)", directory);
return;
}
_logger.LogInformation("Loading quests from {DirectoryName}", directory);
foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
{
try
{
using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
LoadQuestFromStream(fileInfo.Name, stream);
}
catch (Exception e)
{
throw new InvalidDataException($"Unable to load file {fileInfo.FullName}", e);
}
}
foreach (DirectoryInfo childDirectory in directory.GetDirectories())
LoadFromDirectory(childDirectory);
}
private static (ushort QuestId, string Name) ExtractQuestDataFromName(string resourceName)
{
string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
name = name.Substring(name.LastIndexOf('.') + 1);
string[] parts = name.Split('_', 2);
return (ushort.Parse(parts[0], CultureInfo.InvariantCulture), parts[1]);
}
public bool IsKnownQuest(ushort questId) => _quests.ContainsKey(questId);
public bool TryGetQuest(ushort questId, [NotNullWhen(true)] out Quest? quest)
=> _quests.TryGetValue(questId, out quest);
}

View File

@ -0,0 +1,65 @@
using System;
using Dalamud.Game.Command;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Questionable.Controller;
using Questionable.Windows;
namespace Questionable;
internal sealed class DalamudInitializer : IDisposable
{
private readonly DalamudPluginInterface _pluginInterface;
private readonly IFramework _framework;
private readonly ICommandManager _commandManager;
private readonly QuestController _questController;
private readonly MovementController _movementController;
private readonly NavigationShortcutController _navigationShortcutController;
private readonly WindowSystem _windowSystem;
private readonly DebugWindow _debugWindow;
public DalamudInitializer(DalamudPluginInterface pluginInterface, IFramework framework,
ICommandManager commandManager, QuestController questController, MovementController movementController,
GameUiController gameUiController, NavigationShortcutController navigationShortcutController, WindowSystem windowSystem, DebugWindow debugWindow)
{
_pluginInterface = pluginInterface;
_framework = framework;
_commandManager = commandManager;
_questController = questController;
_movementController = movementController;
_navigationShortcutController = navigationShortcutController;
_windowSystem = windowSystem;
_debugWindow = debugWindow;
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenMainUi += _debugWindow.Toggle;
_framework.Update += FrameworkUpdate;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{
HelpMessage = "Opens the Questing window"
});
_framework.RunOnTick(gameUiController.HandleCurrentDialogueChoices, TimeSpan.FromMilliseconds(200));
}
private void FrameworkUpdate(IFramework framework)
{
_questController.Update();
_navigationShortcutController.HandleNavigationShortcut();
_movementController.Update();
}
private void ProcessCommand(string command, string arguments)
{
_debugWindow.Toggle();
}
public void Dispose()
{
_commandManager.RemoveHandler("/qst");
_framework.Update -= FrameworkUpdate;
_pluginInterface.UiBuilder.OpenMainUi -= _debugWindow.Toggle;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
}
}

View File

@ -22,6 +22,7 @@ using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Lumina.Excel.CustomSheets; using Lumina.Excel.CustomSheets;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.Logging;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Model.V1; using Questionable.Model.V1;
using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara; using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
@ -51,17 +52,20 @@ internal sealed unsafe class GameFunctions
private readonly ITargetManager _targetManager; private readonly ITargetManager _targetManager;
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly IPluginLog _pluginLog; private readonly QuestRegistry _questRegistry;
private readonly ILogger<GameFunctions> _logger;
public GameFunctions(IDataManager dataManager, IObjectTable objectTable, ISigScanner sigScanner, public GameFunctions(IDataManager dataManager, IObjectTable objectTable, ISigScanner sigScanner,
ITargetManager targetManager, ICondition condition, IClientState clientState, IPluginLog pluginLog) ITargetManager targetManager, ICondition condition, IClientState clientState, QuestRegistry questRegistry,
ILogger<GameFunctions> logger)
{ {
_dataManager = dataManager; _dataManager = dataManager;
_objectTable = objectTable; _objectTable = objectTable;
_targetManager = targetManager; _targetManager = targetManager;
_condition = condition; _condition = condition;
_clientState = clientState; _clientState = clientState;
_pluginLog = pluginLog; _questRegistry = questRegistry;
_logger = logger;
_processChatBox = _processChatBox =
Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText(Signatures.SendChat)); Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText(Signatures.SendChat));
_sanitiseString = _sanitiseString =
@ -85,9 +89,6 @@ internal sealed unsafe class GameFunctions
.AsReadOnly(); .AsReadOnly();
} }
// FIXME
public QuestController QuestController { private get; set; } = null!;
public (ushort CurrentQuest, byte Sequence) GetCurrentQuest() public (ushort CurrentQuest, byte Sequence) GetCurrentQuest()
{ {
ushort currentQuest; ushort currentQuest;
@ -108,7 +109,7 @@ internal sealed unsafe class GameFunctions
break; break;
} }
if (QuestController.IsKnownQuest(currentQuest)) if (_questRegistry.IsKnownQuest(currentQuest))
return (currentQuest, QuestManager.GetQuestSequence(currentQuest)); return (currentQuest, QuestManager.GetQuestSequence(currentQuest));
} }
} }
@ -190,6 +191,8 @@ internal sealed unsafe class GameFunctions
playerState->IsAetherCurrentZoneComplete(aetherCurrentCompFlgSet); playerState->IsAetherCurrentZoneComplete(aetherCurrentCompFlgSet);
} }
public bool IsFlyingUnlockedInCurrentZone() => IsFlyingUnlocked(_clientState.TerritoryType);
public bool IsAetherCurrentUnlocked(uint aetherCurrentId) public bool IsAetherCurrentUnlocked(uint aetherCurrentId)
{ {
var playerState = PlayerState.Instance(); var playerState = PlayerState.Instance();
@ -335,7 +338,7 @@ internal sealed unsafe class GameFunctions
} }
} }
_pluginLog.Warning($"Could not find GameObject with dataId {dataId}"); _logger.LogWarning("Could not find GameObject with dataId {DataId}", dataId);
return null; return null;
} }
@ -344,7 +347,7 @@ internal sealed unsafe class GameFunctions
GameObject? gameObject = FindObjectByDataId(dataId); GameObject? gameObject = FindObjectByDataId(dataId);
if (gameObject != null) if (gameObject != null)
{ {
_pluginLog.Information($"Setting target with {dataId} to {gameObject.ObjectId}"); _logger.LogInformation("Setting target with {DataId} to {ObjectId}", dataId, gameObject.ObjectId);
_targetManager.Target = gameObject; _targetManager.Target = gameObject;
TargetSystem.Instance()->InteractWithObject( TargetSystem.Instance()->InteractWithObject(
@ -400,7 +403,7 @@ internal sealed unsafe class GameFunctions
public bool HasStatusPreventingSprintOrMount() public bool HasStatusPreventingSprintOrMount()
{ {
if (_condition[ConditionFlag.Swimming] && !IsFlyingUnlocked(_clientState.TerritoryType)) if (_condition[ConditionFlag.Swimming] && !IsFlyingUnlockedInCurrentZone())
return true; return true;
// company chocobo is locked // company chocobo is locked
@ -428,7 +431,7 @@ internal sealed unsafe class GameFunctions
{ {
if (ActionManager.Instance()->GetActionStatus(ActionType.Mount, 71) == 0) if (ActionManager.Instance()->GetActionStatus(ActionType.Mount, 71) == 0)
{ {
_pluginLog.Information("Using SDS Fenrir as mount"); _logger.LogInformation("Using SDS Fenrir as mount");
ActionManager.Instance()->UseAction(ActionType.Mount, 71); ActionManager.Instance()->UseAction(ActionType.Mount, 71);
} }
} }
@ -436,7 +439,7 @@ internal sealed unsafe class GameFunctions
{ {
if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 9) == 0) if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 9) == 0)
{ {
_pluginLog.Information("Using mount roulette"); _logger.LogInformation("Using mount roulette");
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 9); ActionManager.Instance()->UseAction(ActionType.GeneralAction, 9);
} }
} }
@ -449,11 +452,11 @@ internal sealed unsafe class GameFunctions
{ {
if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0) if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0)
{ {
_pluginLog.Information("Unmounting..."); _logger.LogInformation("Unmounting...");
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23); ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
} }
else else
_pluginLog.Warning("Can't unmount right now?"); _logger.LogWarning("Can't unmount right now?");
return true; return true;
} }
@ -468,11 +471,12 @@ internal sealed unsafe class GameFunctions
if (UIState.IsInstanceContentUnlocked(contentId)) if (UIState.IsInstanceContentUnlocked(contentId))
AgentContentsFinder.Instance()->OpenRegularDuty(contentFinderConditionId); AgentContentsFinder.Instance()->OpenRegularDuty(contentFinderConditionId);
else else
_pluginLog.Error( _logger.LogError(
$"Trying to access a locked duty (cf: {contentFinderConditionId}, content: {contentId})"); "Trying to access a locked duty (cf: {ContentFinderId}, content: {ContentId})",
contentFinderConditionId, contentId);
} }
else else
_pluginLog.Error($"Could not find content for content finder condition (cf: {contentFinderConditionId})"); _logger.LogError("Could not find content for content finder condition (cf: {ContentFinderId})", contentFinderConditionId);
} }
public string? GetDialogueText(Quest currentQuest, string? excelSheetName, string key) public string? GetDialogueText(Quest currentQuest, string? excelSheetName, string key)
@ -482,7 +486,7 @@ internal sealed unsafe class GameFunctions
var questRow = _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId + 0x10000); var questRow = _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId + 0x10000);
if (questRow == null) if (questRow == null)
{ {
_pluginLog.Error($"Could not find quest row for {currentQuest.QuestId}"); _logger.LogError("Could not find quest row for {QuestId}", currentQuest.QuestId);
return null; return null;
} }
@ -492,7 +496,7 @@ internal sealed unsafe class GameFunctions
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName); var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
if (excelSheet == null) if (excelSheet == null)
{ {
_pluginLog.Error($"Unknown excel sheet '{excelSheetName}'"); _logger.LogError("Unknown excel sheet '{SheetName}'", excelSheetName);
return null; return null;
} }

View File

@ -0,0 +1,10 @@
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global",
Justification = "Properties are used for serialization",
Scope = "namespaceanddescendants",
Target = "Questionable.Model.V1")]
[assembly: SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global",
Justification = "Properties are used for serialization",
Scope = "namespaceanddescendants",
Target = "Questionable.Model.V1")]

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(AethernetShortcutConverter))] [JsonConverter(typeof(AethernetShortcutConverter))]
public sealed class AethernetShortcut internal sealed class AethernetShortcut
{ {
public EAetheryteLocation From { get; set; } public EAetheryteLocation From { get; set; }
public EAetheryteLocation To { get; set; } public EAetheryteLocation To { get; set; }

View File

@ -1,6 +1,9 @@
namespace Questionable.Model.V1; using JetBrains.Annotations;
public sealed class ChatMessage namespace Questionable.Model.V1;
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
internal sealed class ChatMessage
{ {
public string? ExcelSheet { get; set; } public string? ExcelSheet { get; set; }
public string Key { get; set; } = null!; public string Key { get; set; } = null!;

View File

@ -6,7 +6,7 @@ using System.Text.Json.Serialization;
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
public sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut> internal sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut>
{ {
private static readonly Dictionary<EAetheryteLocation, string> EnumToString = new() private static readonly Dictionary<EAetheryteLocation, string> EnumToString = new()
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
public sealed class AetheryteConverter() : EnumConverter<EAetheryteLocation>(Values) internal sealed class AetheryteConverter() : EnumConverter<EAetheryteLocation>(Values)
{ {
private static readonly Dictionary<EAetheryteLocation, string> Values = new() private static readonly Dictionary<EAetheryteLocation, string> Values = new()
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
public sealed class DialogueChoiceTypeConverter() : EnumConverter<EDialogChoiceType>(Values) internal sealed class DialogueChoiceTypeConverter() : EnumConverter<EDialogChoiceType>(Values)
{ {
private static readonly Dictionary<EDialogChoiceType, string> Values = new() private static readonly Dictionary<EDialogChoiceType, string> Values = new()
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
public sealed class EmoteConverter() : EnumConverter<EEmote>(Values) internal sealed class EmoteConverter() : EnumConverter<EEmote>(Values)
{ {
private static readonly Dictionary<EEmote, string> Values = new() private static readonly Dictionary<EEmote, string> Values = new()
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
public sealed class EnemySpawnTypeConverter() : EnumConverter<EEnemySpawnType>(Values) internal sealed class EnemySpawnTypeConverter() : EnumConverter<EEnemySpawnType>(Values)
{ {
private static readonly Dictionary<EEnemySpawnType, string> Values = new() private static readonly Dictionary<EEnemySpawnType, string> Values = new()
{ {

View File

@ -7,7 +7,7 @@ using System.Text.Json.Serialization;
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
public abstract class EnumConverter<T> : JsonConverter<T> internal abstract class EnumConverter<T> : JsonConverter<T>
where T : Enum where T : Enum
{ {
private readonly ReadOnlyDictionary<T, string> _enumToString; private readonly ReadOnlyDictionary<T, string> _enumToString;

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>(Values) internal sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>(Values)
{ {
private static readonly Dictionary<EInteractionType, string> Values = new() private static readonly Dictionary<EInteractionType, string> Values = new()
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
public sealed class SkipConditionConverter() : EnumConverter<ESkipCondition>(Values) internal sealed class SkipConditionConverter() : EnumConverter<ESkipCondition>(Values)
{ {
private static readonly Dictionary<ESkipCondition, string> Values = new() private static readonly Dictionary<ESkipCondition, string> Values = new()
{ {

View File

@ -5,7 +5,7 @@ using System.Text.Json.Serialization;
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
public class VectorConverter : JsonConverter<Vector3> internal sealed class VectorConverter : JsonConverter<Vector3>
{ {
public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {

View File

@ -1,14 +1,16 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Questionable.Model.V1.Converter; using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
public class DialogueChoice [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
internal sealed class DialogueChoice
{ {
[JsonConverter(typeof(DialogueChoiceTypeConverter))] [JsonConverter(typeof(DialogueChoiceTypeConverter))]
public EDialogChoiceType Type { get; set; } public EDialogChoiceType Type { get; set; }
public string? ExcelSheet { get; set; } public string? ExcelSheet { get; set; }
public string? Prompt { get; set; } = null!; public string? Prompt { get; set; }
public bool Yes { get; set; } = true; public bool Yes { get; set; } = true;
public string? Answer { get; set; } public string? Answer { get; set; }
} }

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(AetheryteConverter))] [JsonConverter(typeof(AetheryteConverter))]
public enum EAetheryteLocation internal enum EAetheryteLocation
{ {
None = 0, None = 0,

View File

@ -1,6 +1,6 @@
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
public enum EDialogChoiceType internal enum EDialogChoiceType
{ {
None, None,
YesNo, YesNo,

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(EmoteConverter))] [JsonConverter(typeof(EmoteConverter))]
public enum EEmote internal enum EEmote
{ {
None = 0, None = 0,

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(EnemySpawnTypeConverter))] [JsonConverter(typeof(EnemySpawnTypeConverter))]
public enum EEnemySpawnType internal enum EEnemySpawnType
{ {
None = 0, None = 0,
AfterInteraction, AfterInteraction,

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(InteractionTypeConverter))] [JsonConverter(typeof(InteractionTypeConverter))]
public enum EInteractionType internal enum EInteractionType
{ {
Interact, Interact,
WalkTo, WalkTo,

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(SkipConditionConverter))] [JsonConverter(typeof(SkipConditionConverter))]
public enum ESkipCondition internal enum ESkipCondition
{ {
None, None,
Never, Never,

View File

@ -1,10 +1,12 @@
using System.Numerics; using System.Numerics;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Questionable.Model.V1.Converter; using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
public sealed class JumpDestination [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
internal sealed class JumpDestination
{ {
[JsonConverter(typeof(VectorConverter))] [JsonConverter(typeof(VectorConverter))]
public Vector3 Position { get; set; } public Vector3 Position { get; set; }

View File

@ -1,8 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
public class QuestData [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
internal sealed class QuestData
{ {
public required string Author { get; set; } public required string Author { get; set; }
public List<string> Contributors { get; set; } = new(); public List<string> Contributors { get; set; } = new();

View File

@ -1,8 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
public class QuestSequence [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
internal sealed class QuestSequence
{ {
public required int Sequence { get; set; } public required int Sequence { get; set; }
public string? Comment { get; set; } public string? Comment { get; set; }

View File

@ -1,12 +1,15 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions; using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using JetBrains.Annotations;
using Questionable.Model.V1.Converter; using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
public class QuestStep [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
internal sealed class QuestStep
{ {
public EInteractionType InteractionType { get; set; } public EInteractionType InteractionType { get; set; }

View File

@ -23,7 +23,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Dalamud.Extensions.MicrosoftLogging" Version="3.0.0" />
<PackageReference Include="DalamudPackager" Version="2.1.12"/> <PackageReference Include="DalamudPackager" Version="2.1.12"/>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" ExcludeAssets="runtime"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.3"/> <PackageReference Include="System.Text.Json" Version="8.0.3"/>
</ItemGroup> </ItemGroup>

View File

@ -1,113 +1,84 @@
using System; using System;
using System.Linq; using System.Diagnostics.CodeAnalysis;
using System.Numerics; using Dalamud.Extensions.MicrosoftLogging;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Data; using Questionable.Data;
using Questionable.External; using Questionable.External;
using Questionable.Model;
using Questionable.Windows; using Questionable.Windows;
namespace Questionable; namespace Questionable;
[SuppressMessage("ReSharper", "UnusedType.Global")]
public sealed class QuestionablePlugin : IDalamudPlugin public sealed class QuestionablePlugin : IDalamudPlugin
{ {
private readonly WindowSystem _windowSystem = new(nameof(Questionable)); private readonly ServiceProvider? _serviceProvider;
private readonly DalamudPluginInterface _pluginInterface; public QuestionablePlugin(DalamudPluginInterface pluginInterface,
private readonly IClientState _clientState; IClientState clientState,
private readonly IFramework _framework; ITargetManager targetManager,
private readonly IGameGui _gameGui; IFramework framework,
private readonly ICommandManager _commandManager; IGameGui gameGui,
private readonly GameFunctions _gameFunctions; IDataManager dataManager,
private readonly QuestController _questController; ISigScanner sigScanner,
private readonly MovementController _movementController; IObjectTable objectTable,
private readonly GameUiController _gameUiController; IPluginLog pluginLog,
private readonly Configuration _configuration; ICondition condition,
IChatGui chatGui,
public QuestionablePlugin(DalamudPluginInterface pluginInterface, IClientState clientState, ICommandManager commandManager,
ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager, IAddonLifecycle addonLifecycle)
ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui,
ICommandManager commandManager, IAddonLifecycle addonLifecycle)
{ {
ArgumentNullException.ThrowIfNull(pluginInterface); ArgumentNullException.ThrowIfNull(pluginInterface);
ArgumentNullException.ThrowIfNull(sigScanner);
ArgumentNullException.ThrowIfNull(dataManager);
ArgumentNullException.ThrowIfNull(objectTable);
_pluginInterface = pluginInterface; ServiceCollection serviceCollection = new();
_clientState = clientState; serviceCollection.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace)
_framework = framework; .ClearProviders()
_gameGui = gameGui; .AddDalamudLogger(pluginLog));
_commandManager = commandManager; serviceCollection.AddSingleton<IDalamudPlugin>(this);
_gameFunctions = new GameFunctions(dataManager, objectTable, sigScanner, targetManager, condition, clientState, serviceCollection.AddSingleton(pluginInterface);
pluginLog); serviceCollection.AddSingleton(clientState);
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration(); serviceCollection.AddSingleton(targetManager);
serviceCollection.AddSingleton(framework);
serviceCollection.AddSingleton(gameGui);
serviceCollection.AddSingleton(dataManager);
serviceCollection.AddSingleton(sigScanner);
serviceCollection.AddSingleton(objectTable);
serviceCollection.AddSingleton(condition);
serviceCollection.AddSingleton(chatGui);
serviceCollection.AddSingleton(commandManager);
serviceCollection.AddSingleton(addonLifecycle);
serviceCollection.AddSingleton(new WindowSystem(nameof(Questionable)));
serviceCollection.AddSingleton((Configuration?)pluginInterface.GetPluginConfig() ?? new Configuration());
AetheryteData aetheryteData = new AetheryteData(dataManager); serviceCollection.AddSingleton<GameFunctions>();
NavmeshIpc navmeshIpc = new NavmeshIpc(pluginInterface); serviceCollection.AddSingleton<AetheryteData>();
LifestreamIpc lifestreamIpc = new LifestreamIpc(pluginInterface, aetheryteData); serviceCollection.AddSingleton<TerritoryData>();
_movementController = serviceCollection.AddSingleton<NavmeshIpc>();
new MovementController(navmeshIpc, clientState, _gameFunctions, condition, pluginLog); serviceCollection.AddSingleton<LifestreamIpc>();
_questController = new QuestController(pluginInterface, dataManager, _clientState, _gameFunctions,
_movementController, pluginLog, condition, chatGui, framework, aetheryteData, lifestreamIpc);
_gameUiController =
new GameUiController(addonLifecycle, dataManager, _gameFunctions, _questController, gameGui, pluginLog);
_windowSystem.AddWindow(new DebugWindow(pluginInterface, _movementController, _questController, _gameFunctions, serviceCollection.AddSingleton<MovementController>();
clientState, framework, targetManager, _gameUiController, _configuration)); serviceCollection.AddSingleton<QuestRegistry>();
serviceCollection.AddSingleton<QuestController>();
serviceCollection.AddSingleton<GameUiController>();
serviceCollection.AddSingleton<NavigationShortcutController>();
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw; serviceCollection.AddSingleton<DebugWindow>();
_framework.Update += FrameworkUpdate; serviceCollection.AddSingleton<DalamudInitializer>();
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand));
_framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(), TimeSpan.FromMilliseconds(200)); _serviceProvider = serviceCollection.BuildServiceProvider();
_serviceProvider.GetRequiredService<QuestRegistry>().Reload();
_serviceProvider.GetRequiredService<DebugWindow>();
_serviceProvider.GetRequiredService<DalamudInitializer>();
} }
private void FrameworkUpdate(IFramework framework)
{
_questController.Update();
HandleNavigationShortcut();
_movementController.Update();
}
private void ProcessCommand(string command, string arguments)
{
_windowSystem.Windows.Single(x => x is DebugWindow).Toggle();
}
private unsafe void HandleNavigationShortcut()
{
var inputData = UIInputData.Instance();
if (inputData == null)
return;
if (inputData->IsGameWindowFocused &&
inputData->UIFilteredMouseButtonReleasedFlags.HasFlag(MouseButtonFlags.LBUTTON) &&
inputData->GetKeyState(SeVirtualKey.MENU).HasFlag(KeyStateFlags.Down) &&
_gameGui.ScreenToWorld(new Vector2(inputData->CursorXPosition, inputData->CursorYPosition),
out Vector3 worldPos))
{
_movementController.NavigateTo(EMovementType.Shortcut, null, worldPos,
_gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType), true);
}
}
public void Dispose() public void Dispose()
{ {
_commandManager.RemoveHandler("/qst"); _serviceProvider?.Dispose();
_framework.Update -= FrameworkUpdate;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_gameUiController.Dispose();
_movementController.Dispose();
} }
} }

View File

@ -18,9 +18,10 @@ using Questionable.Model.V1;
namespace Questionable.Windows; namespace Questionable.Windows;
internal sealed class DebugWindow : LWindow, IPersistableWindowConfig internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposable
{ {
private readonly DalamudPluginInterface _pluginInterface; private readonly DalamudPluginInterface _pluginInterface;
private readonly WindowSystem _windowSystem;
private readonly MovementController _movementController; private readonly MovementController _movementController;
private readonly QuestController _questController; private readonly QuestController _questController;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
@ -30,12 +31,14 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
private readonly GameUiController _gameUiController; private readonly GameUiController _gameUiController;
private readonly Configuration _configuration; private readonly Configuration _configuration;
public DebugWindow(DalamudPluginInterface pluginInterface, MovementController movementController, public DebugWindow(DalamudPluginInterface pluginInterface, WindowSystem windowSystem,
QuestController questController, GameFunctions gameFunctions, IClientState clientState, IFramework framework, MovementController movementController, QuestController questController, GameFunctions gameFunctions,
ITargetManager targetManager, GameUiController gameUiController, Configuration configuration) IClientState clientState, IFramework framework, ITargetManager targetManager, GameUiController gameUiController,
Configuration configuration)
: base("Questionable", ImGuiWindowFlags.AlwaysAutoResize) : base("Questionable", ImGuiWindowFlags.AlwaysAutoResize)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_windowSystem = windowSystem;
_movementController = movementController; _movementController = movementController;
_questController = questController; _questController = questController;
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
@ -51,6 +54,8 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
MinimumSize = new Vector2(200, 30), MinimumSize = new Vector2(200, 30),
MaximumSize = default MaximumSize = default
}; };
_windowSystem.AddWindow(this);
} }
public WindowConfig WindowConfig => _configuration.DebugWindowConfig; public WindowConfig WindowConfig => _configuration.DebugWindowConfig;
@ -128,7 +133,7 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
ImGui.Separator(); ImGui.Separator();
ImGui.Text( ImGui.Text(
$"Current TerritoryId: {_clientState.TerritoryType}, Flying: {(_gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType) ? "Yes" : "No")}"); $"Current TerritoryId: {_clientState.TerritoryType}, Flying: {(_gameFunctions.IsFlyingUnlockedInCurrentZone() ? "Yes" : "No")}");
var q = _gameFunctions.GetCurrentQuest(); var q = _gameFunctions.GetCurrentQuest();
ImGui.Text($"Current Quest: {q.CurrentQuest} → {q.Sequence}"); ImGui.Text($"Current Quest: {q.CurrentQuest} → {q.Sequence}");
@ -169,7 +174,7 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
if (ImGui.Button("Move to Target")) if (ImGui.Button("Move to Target"))
{ {
_movementController.NavigateTo(EMovementType.DebugWindow, _targetManager.Target.DataId, _movementController.NavigateTo(EMovementType.DebugWindow, _targetManager.Target.DataId,
_targetManager.Target.Position, _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType), _targetManager.Target.Position, _gameFunctions.IsFlyingUnlockedInCurrentZone(),
true); true);
} }
} }
@ -234,7 +239,7 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
map->FlagMapMarker.TerritoryId != _clientState.TerritoryType); map->FlagMapMarker.TerritoryId != _clientState.TerritoryType);
if (ImGui.Button("Move to Flag")) if (ImGui.Button("Move to Flag"))
_gameFunctions.ExecuteCommand( _gameFunctions.ExecuteCommand(
$"/vnav {(_gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType) ? "flyflag" : "moveflag")}"); $"/vnav {(_gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}");
ImGui.EndDisabled(); ImGui.EndDisabled();
ImGui.SameLine(); ImGui.SameLine();
@ -251,4 +256,9 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
TimeSpan.FromMilliseconds(200)); TimeSpan.FromMilliseconds(200));
} }
} }
public void Dispose()
{
_windowSystem.RemoveWindow(this);
}
} }

View File

@ -2,12 +2,36 @@
"version": 1, "version": 1,
"dependencies": { "dependencies": {
"net8.0-windows7.0": { "net8.0-windows7.0": {
"Dalamud.Extensions.MicrosoftLogging": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "jWK3r/cZUXN8H9vHf78gEzeRmMk4YAbCUYzLcTqUAcega8unUiFGwYy+iOjVYJ9urnr9r+hk+vBi1y9wyv+e7Q==",
"dependencies": {
"Microsoft.Extensions.Logging": "8.0.0"
}
},
"DalamudPackager": { "DalamudPackager": {
"type": "Direct", "type": "Direct",
"requested": "[2.1.12, )", "requested": "[2.1.12, )",
"resolved": "2.1.12", "resolved": "2.1.12",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
}, },
"JetBrains.Annotations": {
"type": "Direct",
"requested": "[2023.3.0, )",
"resolved": "2023.3.0",
"contentHash": "PHfnvdBUdGaTVG9bR/GEfxgTwWM0Z97Y6X3710wiljELBISipSfF5okn/vz+C2gfO+ihoEyVPjaJwn8ZalVukA=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
}
},
"System.Text.Json": { "System.Text.Json": {
"type": "Direct", "type": "Direct",
"requested": "[8.0.3, )", "requested": "[8.0.3, )",
@ -17,6 +41,43 @@
"System.Text.Encodings.Web": "8.0.0" "System.Text.Encodings.Web": "8.0.0"
} }
}, },
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
},
"Microsoft.Extensions.Logging": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
}
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
},
"System.Text.Encodings.Web": { "System.Text.Encodings.Web": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.0", "resolved": "8.0.0",