using System; using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.Command; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.UI; using Lumina.Excel; using Lumina.Excel.Sheets; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; using Questionable.Windows; namespace Questionable.Controller; internal sealed class CommandHandler : IDisposable { public const string MessageTag = "Questionable"; public const ushort TagColor = 576; private readonly ICommandManager _commandManager; private readonly IChatGui _chatGui; private readonly QuestController _questController; private readonly MovementController _movementController; private readonly QuestRegistry _questRegistry; private readonly ConfigWindow _configWindow; private readonly DebugOverlay _debugOverlay; private readonly OneTimeSetupWindow _oneTimeSetupWindow; private readonly QuestWindow _questWindow; private readonly QuestSelectionWindow _questSelectionWindow; private readonly JournalProgressWindow _journalProgressWindow; private readonly PriorityWindow _priorityWindow; private readonly ITargetManager _targetManager; private readonly QuestFunctions _questFunctions; private readonly GameFunctions _gameFunctions; private readonly IDataManager _dataManager; private readonly IClientState _clientState; private readonly Configuration _configuration; private IReadOnlyList _previouslyUnlockedUnlockLinks = Array.Empty(); public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController, MovementController movementController, QuestRegistry questRegistry, ConfigWindow configWindow, DebugOverlay debugOverlay, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, QuestSelectionWindow questSelectionWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, ITargetManager targetManager, QuestFunctions questFunctions, GameFunctions gameFunctions, IDataManager dataManager, IClientState clientState, Configuration configuration) { _commandManager = commandManager; _chatGui = chatGui; _questController = questController; _movementController = movementController; _questRegistry = questRegistry; _configWindow = configWindow; _debugOverlay = debugOverlay; _oneTimeSetupWindow = oneTimeSetupWindow; _questWindow = questWindow; _questSelectionWindow = questSelectionWindow; _journalProgressWindow = journalProgressWindow; _priorityWindow = priorityWindow; _targetManager = targetManager; _questFunctions = questFunctions; _gameFunctions = gameFunctions; _dataManager = dataManager; _clientState = clientState; _configuration = configuration; _clientState.Logout += OnLogout; _commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand) { HelpMessage = string.Join(Environment.NewLine + "\t", "Opens the Questing window", "/qst help - displays simplified commands", "/qst help-all - displays all available commands", "/qst config - opens the configuration window", "/qst start - starts doing quests", "/qst stop - stops doing quests") }); } private void ProcessCommand(string command, string arguments) { if (!OpenSetupIfNeeded(arguments)) { string[] array = arguments.Split(' '); switch (array[0]) { case "h": case "help": _chatGui.Print("Available commands:", "Questionable", 576); _chatGui.Print("/qst - toggles the Questing window", "Questionable", 576); _chatGui.Print("/qst help - displays simplified commands", "Questionable", 576); _chatGui.Print("/qst help-all - displays all available commands", "Questionable", 576); _chatGui.Print("/qst config - opens the configuration window", "Questionable", 576); _chatGui.Print("/qst start - starts doing quests", "Questionable", 576); _chatGui.Print("/qst stop - stops doing quests", "Questionable", 576); _chatGui.Print("/qst reload - reload all quest data", "Questionable", 576); break; case "ha": case "help-all": _chatGui.Print("Available commands:", "Questionable", 576); _chatGui.Print("/qst - toggles the Questing window", "Questionable", 576); _chatGui.Print("/qst help - displays available commands", "Questionable", 576); _chatGui.Print("/qst help-all - displays all available commands", "Questionable", 576); _chatGui.Print("/qst config - opens the configuration window", "Questionable", 576); _chatGui.Print("/qst start - starts doing quests", "Questionable", 576); _chatGui.Print("/qst stop - stops doing quests", "Questionable", 576); _chatGui.Print("/qst reload - reload all quest data", "Questionable", 576); _chatGui.Print("/qst do - highlights the specified quest in the debug overlay (requires debug overlay to be enabled)", "Questionable", 576); _chatGui.Print("/qst do - clears the highlighted quest in the debug overlay (requires debug overlay to be enabled)", "Questionable", 576); _chatGui.Print("/qst next - sets the next quest to do (or clears it if no questId is specified)", "Questionable", 576); _chatGui.Print("/qst sim [sequence] [step] - simulates the specified quest (or clears it if no questId is specified)", "Questionable", 576); _chatGui.Print("/qst which - shows all quests starting with your selected target", "Questionable", 576); _chatGui.Print("/qst zone - shows all quests starting in the current zone (only includes quests with a known quest path, and currently visible unaccepted quests)", "Questionable", 576); _chatGui.Print("/qst journal - toggles the Journal Progress window", "Questionable", 576); _chatGui.Print("/qst priority - toggles the Priority window", "Questionable", 576); _chatGui.Print("/qst mountid - prints information about your current mount", "Questionable", 576); _chatGui.Print("/qst handle-interrupt - makes Questionable handle queued interrupts immediately (useful if you manually start combat)", "Questionable", 576); break; case "c": case "config": _configWindow.ToggleOrUncollapse(); break; case "start": _questWindow.IsOpenAndUncollapsed = true; _questController.Start("Start command"); break; case "stop": _movementController.Stop(); _questController.Stop("Stop command"); break; case "reload": _questWindow.Reload(); break; case "do": ConfigureDebugOverlay(array.Skip(1).ToArray()); break; case "next": SetNextQuest(array.Skip(1).ToArray()); break; case "sim": SetSimulatedQuest(array.Skip(1).ToArray()); break; case "which": _questSelectionWindow.OpenForTarget(_targetManager.Target); break; case "z": case "zone": _questSelectionWindow.OpenForCurrentZone(); break; case "j": case "journal": _journalProgressWindow.ToggleOrUncollapse(); break; case "p": case "priority": _priorityWindow.ToggleOrUncollapse(); break; case "mountid": PrintMountId(); break; case "handle-interrupt": _questController.InterruptQueueWithCombat(); break; case "": _questWindow.ToggleOrUncollapse(); break; default: _chatGui.PrintError("Unknown subcommand " + array[0], "Questionable", 576); break; } } } private unsafe void ProcessDebugCommand(string command, string arguments) { if (OpenSetupIfNeeded(arguments)) { return; } switch (arguments.Split(' ')[0]) { case "abandon-duty": _gameFunctions.AbandonDuty(); break; case "unlock-links": { IReadOnlyList unlockLinks = _gameFunctions.GetUnlockLinks(); if (unlockLinks.Count >= 0) { _chatGui.Print($"Saved {unlockLinks.Count} unlock links to log.", "Questionable", 576); List list2 = unlockLinks.Except(_previouslyUnlockedUnlockLinks).ToList(); if (_previouslyUnlockedUnlockLinks.Count > 0 && list2.Count > 0) { _chatGui.Print("New unlock links: " + string.Join(", ", list2), "Questionable", 576); } } else { _chatGui.PrintError("Could not query unlock links.", "Questionable", 576); } _previouslyUnlockedUnlockLinks = unlockLinks; break; } case "taxi": { List list3 = new List(); ExcelSheet excelSheet = _dataManager.GetExcelSheet(); UIState* ptr = UIState.Instance(); for (byte b2 = 0; b2 < ptr->UnlockedChocoboTaxiStandsBitmask.Length * 8; b2++) { if (ptr->IsChocoboTaxiStandUnlocked(b2)) { list3.Add($"{excelSheet.GetRow((uint)(b2 + 1179648)).PlaceName} ({b2})"); } } _chatGui.Print("Unlocked taxi stands:", "Questionable", 576); { foreach (string item in list3) { _chatGui.Print("- " + item, "Questionable", 576); } break; } } case "festivals": { List list = new List(); for (byte b = 0; b < 4; b++) { GameMain.Festival festival = GameMain.Instance()->ActiveFestivals[b]; if (festival.Id != 0) { list.Add($"{festival.Id}({festival.Phase})"); } } _chatGui.Print("Active festivals: " + string.Join(", ", list), "Questionable", 576); break; } } } private bool OpenSetupIfNeeded(string arguments) { if (!_configuration.IsPluginSetupComplete()) { if (string.IsNullOrEmpty(arguments)) { _oneTimeSetupWindow.IsOpenAndUncollapsed = true; } else { _chatGui.PrintError("Please complete the one-time setup first.", "Questionable", 576); } return true; } return false; } private void ConfigureDebugOverlay(string[] arguments) { ElementId elementId; if (!_debugOverlay.DrawConditions()) { _chatGui.PrintError("You don't have the debug overlay enabled.", "Questionable", 576); } else if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out elementId) && elementId != null) { if (_questRegistry.TryGetQuest(elementId, out Questionable.Model.Quest quest)) { _debugOverlay.HighlightedQuest = quest.Id; _chatGui.Print($"Set highlighted quest to {elementId} ({quest.Info.Name}).", "Questionable", 576); } else { _chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576); } } else { _debugOverlay.HighlightedQuest = null; _chatGui.Print("Cleared highlighted quest.", "Questionable", 576); } } private void SetNextQuest(string[] arguments) { if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null) { Questionable.Model.Quest quest; if (_questFunctions.IsQuestLocked(elementId)) { _chatGui.PrintError($"Quest {elementId} is locked.", "Questionable", 576); } else if (_questRegistry.TryGetQuest(elementId, out quest)) { _questController.SetNextQuest(quest); _chatGui.Print($"Set next quest to {elementId} ({quest.Info.Name}).", "Questionable", 576); } else { _chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576); } } else { _questController.SetNextQuest(null); _chatGui.Print("Cleared next quest.", "Questionable", 576); } } private void SetSimulatedQuest(string[] arguments) { if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null) { if (_questRegistry.TryGetQuest(elementId, out Questionable.Model.Quest quest)) { byte sequence = 0; int step = 0; if (arguments.Length >= 2 && byte.TryParse(arguments[1], out var result)) { QuestSequence questSequence = quest.FindSequence(result); if (questSequence != null) { sequence = questSequence.Sequence; if (arguments.Length >= 3 && int.TryParse(arguments[2], out var result2) && questSequence.FindStep(result2) != null) { step = result2; } } } _questController.SimulateQuest(quest, sequence, step); _chatGui.Print($"Simulating quest {elementId} ({quest.Info.Name}).", "Questionable", 576); } else { _chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576); } } else { _questController.SimulateQuest(null, 0, 0); _chatGui.Print("Cleared simulated quest.", "Questionable", 576); } } private void PrintMountId() { ushort? mountId = _gameFunctions.GetMountId(); if (mountId.HasValue) { Mount? rowOrDefault = _dataManager.GetExcelSheet().GetRowOrDefault(mountId.Value); _chatGui.Print($"Mount ID: {mountId}, Name: {rowOrDefault?.Singular}, Obtainable: {((rowOrDefault?.Order == -1) ? "No" : "Yes")}", "Questionable", 576); } else { _chatGui.Print("You are not mounted.", "Questionable", 576); } } private void OnLogout(int type, int code) { _previouslyUnlockedUnlockLinks = Array.Empty(); } public void Dispose() { _commandManager.RemoveHandler("/qst"); _clientState.Logout -= OnLogout; } }