muffin v7.4.12

This commit is contained in:
alydev 2026-02-27 22:50:51 +10:00
parent e3e5a401c3
commit 0f9f445830
38 changed files with 13646 additions and 10442 deletions

View file

@ -0,0 +1,681 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Questionable.Controller;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Windows.QuestComponents;
internal sealed class SavedPresetsComponent
{
private sealed class PresetExportData
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<string> QuestIds { get; set; } = new List<string>();
}
private readonly QuestController _questController;
private readonly QuestFunctions _questFunctions;
private readonly QuestData _questData;
private readonly QuestRegistry _questRegistry;
private readonly Configuration _configuration;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly IChatGui _chatGui;
private readonly UiUtils _uiUtils;
private readonly QuestTooltipComponent _questTooltipComponent;
private readonly ILogger<SavedPresetsComponent> _logger;
private string _newPresetName = string.Empty;
private string _newPresetDescription = string.Empty;
private bool _showSaveDialog;
private string? _presetToDelete;
private string? _expandedPreset;
private string? _renamingPresetKey;
private string _renamePresetName = string.Empty;
private string _renamePresetDescription = string.Empty;
private const string PresetClipboardPrefix = "qst:preset:";
private const string AllPresetsClipboardPrefix = "qst:presets:";
public SavedPresetsComponent(QuestController questController, QuestFunctions questFunctions, QuestData questData, QuestRegistry questRegistry, Configuration configuration, IDalamudPluginInterface pluginInterface, IChatGui chatGui, UiUtils uiUtils, QuestTooltipComponent questTooltipComponent, ILogger<SavedPresetsComponent> logger)
{
_questController = questController;
_questFunctions = questFunctions;
_questData = questData;
_questRegistry = questRegistry;
_configuration = configuration;
_pluginInterface = pluginInterface;
_chatGui = chatGui;
_uiUtils = uiUtils;
_questTooltipComponent = questTooltipComponent;
_logger = logger;
}
public void Draw()
{
ImGui.TextWrapped("Save your current priority queue as a preset for quick access later. Saved presets are stored in your configuration and persist between sessions.");
ImGui.Spacing();
DrawSaveSection();
ImGui.Spacing();
using (ImRaii.IEndObject endObject = ImRaii.Child("SavedPresetsList", new Vector2(-1f, -27f), border: true))
{
if (endObject)
{
DrawSavedPresets();
}
}
DrawBottomButtons();
}
private void DrawSaveSection()
{
int count = _questController.ManualPriorityQuests.Count;
if (_showSaveDialog)
{
ImGui.Text("Save Current Priority Queue as Preset:");
ImGui.SetNextItemWidth(200f);
ImGui.InputTextWithHint("##PresetName", "Preset name...", ref _newPresetName, 64);
ImGui.SameLine();
ImGui.SetNextItemWidth(250f);
ImGui.InputTextWithHint("##PresetDescription", "Description (optional)...", ref _newPresetDescription, 256);
ImGui.SameLine();
using (ImRaii.Disabled(string.IsNullOrWhiteSpace(_newPresetName) || count == 0))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Save, "Save"))
{
SaveCurrentPreset();
}
}
ImGui.SameLine();
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Times, "Cancel"))
{
_showSaveDialog = false;
_newPresetName = string.Empty;
_newPresetDescription = string.Empty;
}
if (count == 0)
{
ImGui.TextColored(ImGuiColors.DalamudYellow, "Add quests to the priority queue first.");
}
return;
}
using (ImRaii.Disabled(count == 0))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Plus, $"Save Current Queue ({count} quests)"))
{
_showSaveDialog = true;
}
}
if (count == 0 && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Add quests to the priority queue first to save them as a preset.");
}
}
private void DrawSavedPresets()
{
Dictionary<string, Configuration.SavedQuestPreset> savedPresets = _configuration.General.SavedPresets;
if (savedPresets.Count == 0)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
ImGui.TextWrapped("No saved presets yet. Save your current priority queue using the button above.");
ImGui.PopStyleColor();
return;
}
foreach (var (key, preset) in savedPresets.OrderBy((KeyValuePair<string, Configuration.SavedQuestPreset> x) => x.Value.Name))
{
DrawPresetEntry(key, preset);
}
if (_presetToDelete != null)
{
if (savedPresets.Remove(_presetToDelete))
{
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Deleted saved preset '{PresetName}'", _presetToDelete);
}
_presetToDelete = null;
}
}
private void DrawPresetEntry(string key, Configuration.SavedQuestPreset preset)
{
using (ImRaii.PushId(key))
{
List<ElementId> availableQuests = GetAvailableQuests(preset.QuestIds);
List<ElementId> completedQuests = GetCompletedQuests(preset.QuestIds);
List<ElementId> alreadyPriorityQuests = GetAlreadyPriorityQuests(preset.QuestIds);
string text = preset.Name;
List<string> list = new List<string>();
if (availableQuests.Count > 0)
{
list.Add($"{availableQuests.Count} available");
}
if (alreadyPriorityQuests.Count > 0)
{
list.Add($"{alreadyPriorityQuests.Count} priority");
}
if (completedQuests.Count > 0)
{
list.Add($"{completedQuests.Count} completed");
}
if (list.Count > 0)
{
text = text + " (" + string.Join(", ", list) + ")";
}
bool flag = _expandedPreset == key;
ImU8String label = new ImU8String(3, 2);
label.AppendFormatted(text);
label.AppendLiteral("###");
label.AppendFormatted(key);
if (ImGui.CollapsingHeader(label, flag ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None))
{
_expandedPreset = key;
using (ImRaii.PushIndent())
{
if (_renamingPresetKey == key)
{
ImGui.Text("Name:");
ImGui.SameLine();
ImGui.SetNextItemWidth(300f);
ImGui.InputTextWithHint("##RenamePreset", "Preset name...", ref _renamePresetName, 64);
ImGui.Text("Description:");
ImGui.SameLine();
ImGui.SetNextItemWidth(300f);
ImGui.InputTextWithHint("##EditDescription", "Description (optional)...", ref _renamePresetDescription, 256);
using (ImRaii.Disabled(string.IsNullOrWhiteSpace(_renamePresetName)))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Check, "Save Changes"))
{
RenamePreset(key, _renamePresetName, _renamePresetDescription);
}
}
ImGui.SameLine();
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Times, "Cancel"))
{
_renamingPresetKey = null;
_renamePresetName = string.Empty;
_renamePresetDescription = string.Empty;
}
}
else
{
if (!string.IsNullOrEmpty(preset.Description))
{
ImGui.TextWrapped(preset.Description);
}
Vector4 col = ImGuiColors.DalamudGrey;
ImU8String text2 = new ImU8String(26, 2);
text2.AppendLiteral("Created: ");
text2.AppendFormatted(preset.CreatedAt.ToLocalTime(), "g");
text2.AppendLiteral(" | Total quests: ");
text2.AppendFormatted(preset.QuestIds.Count);
ImGui.TextColored(in col, text2);
}
ImGui.Spacing();
using (ImRaii.Disabled(availableQuests.Count == 0))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Plus, $"Add Available ({availableQuests.Count})"))
{
AddPresetToPriority(availableQuests);
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
if (availableQuests.Count == 0)
{
ImGui.SetTooltip("No available quests to add (all completed or already in priority).");
}
else
{
ImU8String tooltip = new ImU8String(44, 1);
tooltip.AppendLiteral("Add ");
tooltip.AppendFormatted(availableQuests.Count);
tooltip.AppendLiteral(" available quests to the priority queue.");
ImGui.SetTooltip(tooltip);
}
}
ImGui.SameLine();
using (ImRaii.Disabled(alreadyPriorityQuests.Count == 0))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Minus, $"Remove from Priority ({alreadyPriorityQuests.Count})"))
{
RemovePresetFromPriority(alreadyPriorityQuests);
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
if (alreadyPriorityQuests.Count == 0)
{
ImGui.SetTooltip("No quests from this preset are in the priority queue.");
}
else
{
ImU8String tooltip2 = new ImU8String(39, 1);
tooltip2.AppendLiteral("Remove ");
tooltip2.AppendFormatted(alreadyPriorityQuests.Count);
tooltip2.AppendLiteral(" quests from the priority queue.");
ImGui.SetTooltip(tooltip2);
}
}
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Upload, "Export"))
{
ExportPresetToClipboard(preset);
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Export this preset to clipboard for sharing.");
}
ImGui.SameLine();
using (ImRaii.Disabled(_renamingPresetKey != null))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Pen, "Edit"))
{
_renamingPresetKey = key;
_renamePresetName = preset.Name;
_renamePresetDescription = preset.Description;
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Edit preset name and description.");
}
ImGui.SameLine();
int count = _questController.ManualPriorityQuests.Count;
using (ImRaii.Disabled(count == 0))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Sync, "Update"))
{
UpdatePresetQuests(key, preset);
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
if (count == 0)
{
ImGui.SetTooltip("Add quests to the priority queue first.");
}
else
{
ImU8String tooltip3 = new ImU8String(71, 1);
tooltip3.AppendLiteral("Replace this preset's quests with the current priority queue (");
tooltip3.AppendFormatted(count);
tooltip3.AppendLiteral(" quests).");
ImGui.SetTooltip(tooltip3);
}
}
ImGui.SameLine();
using (ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Trash, "Delete"))
{
_presetToDelete = key;
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Hold CTRL to delete this preset.");
}
ImGui.Spacing();
ImGui.Separator();
foreach (ElementId item in availableQuests)
{
if (_questData.TryGetQuestInfo(item, out IQuestInfo questInfo) && _uiUtils.ChecklistItem(questInfo.Name ?? "", ImGuiColors.DalamudYellow, FontAwesomeIcon.Running))
{
_questTooltipComponent.Draw(questInfo);
}
}
foreach (ElementId item2 in alreadyPriorityQuests)
{
if (_questData.TryGetQuestInfo(item2, out IQuestInfo questInfo2) && _uiUtils.ChecklistItem(questInfo2.Name ?? "", ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight))
{
_questTooltipComponent.Draw(questInfo2);
}
}
foreach (ElementId item3 in completedQuests)
{
if (_questData.TryGetQuestInfo(item3, out IQuestInfo questInfo3) && _uiUtils.ChecklistItem(questInfo3.Name ?? "", ImGuiColors.ParsedGreen, FontAwesomeIcon.Check))
{
_questTooltipComponent.Draw(questInfo3);
}
}
foreach (ElementId unknownQuest in GetUnknownQuests(preset.QuestIds))
{
_uiUtils.ChecklistItem($"Unknown Quest ({unknownQuest})", ImGuiColors.DalamudGrey, FontAwesomeIcon.Question);
}
ImGui.Spacing();
}
}
else if (_expandedPreset == key)
{
_expandedPreset = null;
}
if (ImGui.IsItemHovered() && !string.IsNullOrEmpty(preset.Description))
{
ImGui.BeginTooltip();
ImGui.TextUnformatted(preset.Description);
ImGui.EndTooltip();
}
}
}
private void DrawBottomButtons()
{
PresetExportData presetExportData = ParseClipboardPreset();
List<PresetExportData> list = ParseClipboardAllPresets();
using (ImRaii.Disabled(presetExportData == null))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Download, "Import Preset") && presetExportData != null)
{
ImportPresetFromClipboard(presetExportData);
}
}
if (presetExportData == null && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Copy a valid preset string to clipboard first.");
}
ImGui.SameLine();
using (ImRaii.Disabled(list == null || list.Count == 0))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.FileImport, "Import All" + ((list != null && list.Count > 0) ? $" ({list.Count})" : "")) && list != null)
{
ImportAllPresetsFromClipboard(list);
}
}
if ((list == null || list.Count == 0) && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Copy a valid 'all presets' export string to clipboard first.");
}
ImGui.SameLine();
using (ImRaii.Disabled(_configuration.General.SavedPresets.Count == 0))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.FileExport, $"Export All ({_configuration.General.SavedPresets.Count})"))
{
ExportAllPresetsToClipboard();
}
}
if (_configuration.General.SavedPresets.Count == 0 && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("No saved presets to export.");
}
ImGui.SameLine();
using (ImRaii.Disabled(_configuration.General.SavedPresets.Count == 0 || !ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Trash, "Clear All"))
{
_configuration.General.SavedPresets.Clear();
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Cleared all saved presets");
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
if (_configuration.General.SavedPresets.Count == 0)
{
ImGui.SetTooltip("No saved presets to clear.");
}
else
{
ImGui.SetTooltip("Hold CTRL to clear all saved presets.");
}
}
}
private void SaveCurrentPreset()
{
List<ElementId> list = _questController.ManualPriorityQuests.Select((Quest q) => q.Id).ToList();
string key = GeneratePresetKey(_newPresetName);
Configuration.SavedQuestPreset savedQuestPreset = new Configuration.SavedQuestPreset
{
Name = _newPresetName.Trim(),
Description = _newPresetDescription.Trim(),
QuestIds = list,
CreatedAt = DateTime.UtcNow
};
_configuration.General.SavedPresets[key] = savedQuestPreset;
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Saved preset '{PresetName}' with {Count} quests", savedQuestPreset.Name, list.Count);
_chatGui.Print($"Saved preset '{savedQuestPreset.Name}' with {list.Count} quests.", "Questionable", 576);
_newPresetName = string.Empty;
_newPresetDescription = string.Empty;
_showSaveDialog = false;
}
private string GeneratePresetKey(string name)
{
string text = "user_" + name.Trim().ToUpperInvariant().Replace(' ', '_');
string text2 = text;
int num = 1;
while (_configuration.General.SavedPresets.ContainsKey(text2))
{
text2 = $"{text}_{num}";
num++;
}
return text2;
}
private void ExportPresetToClipboard(Configuration.SavedQuestPreset preset)
{
string s = JsonConvert.SerializeObject(new PresetExportData
{
Name = preset.Name,
Description = preset.Description,
QuestIds = preset.QuestIds.Select((ElementId x) => x.ToString()).ToList()
});
ImGui.SetClipboardText("qst:preset:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(s)));
_chatGui.Print("Exported preset '" + preset.Name + "' to clipboard.", "Questionable", 576);
}
private void ExportAllPresetsToClipboard()
{
List<PresetExportData> list = _configuration.General.SavedPresets.Values.Select((Configuration.SavedQuestPreset preset) => new PresetExportData
{
Name = preset.Name,
Description = preset.Description,
QuestIds = preset.QuestIds.Select((ElementId x) => x.ToString()).ToList()
}).ToList();
string s = JsonConvert.SerializeObject(list);
ImGui.SetClipboardText("qst:presets:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(s)));
_chatGui.Print($"Exported {list.Count} presets to clipboard.", "Questionable", 576);
}
private static PresetExportData? ParseClipboardPreset()
{
try
{
string text = ImGui.GetClipboardText().Trim();
if (text.StartsWith("qst:preset:", StringComparison.InvariantCulture))
{
string text2 = text;
int length = "qst:preset:".Length;
string s = text2.Substring(length, text2.Length - length);
return JsonConvert.DeserializeObject<PresetExportData>(Encoding.UTF8.GetString(Convert.FromBase64String(s)));
}
}
catch (Exception)
{
}
return null;
}
private static List<PresetExportData>? ParseClipboardAllPresets()
{
try
{
string text = ImGui.GetClipboardText().Trim();
if (text.StartsWith("qst:presets:", StringComparison.InvariantCulture))
{
string text2 = text;
int length = "qst:presets:".Length;
string s = text2.Substring(length, text2.Length - length);
return JsonConvert.DeserializeObject<List<PresetExportData>>(Encoding.UTF8.GetString(Convert.FromBase64String(s)));
}
}
catch (Exception)
{
}
return null;
}
private void ImportPresetFromClipboard(PresetExportData exportData)
{
List<ElementId> list = exportData.QuestIds.Select((string id) => ElementId.FromString(id)).ToList();
string key = GeneratePresetKey(exportData.Name);
Configuration.SavedQuestPreset savedQuestPreset = new Configuration.SavedQuestPreset
{
Name = exportData.Name,
Description = exportData.Description,
QuestIds = list,
CreatedAt = DateTime.UtcNow
};
_configuration.General.SavedPresets[key] = savedQuestPreset;
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Imported preset '{PresetName}' with {Count} quests from clipboard", savedQuestPreset.Name, list.Count);
_chatGui.Print($"Imported preset '{savedQuestPreset.Name}' with {list.Count} quests.", "Questionable", 576);
}
private void ImportAllPresetsFromClipboard(List<PresetExportData> allExportData)
{
int num = 0;
foreach (PresetExportData allExportDatum in allExportData)
{
List<ElementId> questIds = allExportDatum.QuestIds.Select((string id) => ElementId.FromString(id)).ToList();
string key = GeneratePresetKey(allExportDatum.Name);
Configuration.SavedQuestPreset value = new Configuration.SavedQuestPreset
{
Name = allExportDatum.Name,
Description = allExportDatum.Description,
QuestIds = questIds,
CreatedAt = DateTime.UtcNow
};
_configuration.General.SavedPresets[key] = value;
num++;
}
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Imported {Count} presets from clipboard", num);
_chatGui.Print($"Imported {num} presets from clipboard.", "Questionable", 576);
}
private void AddPresetToPriority(List<ElementId> questIds)
{
int num = 0;
foreach (ElementId questId in questIds)
{
if (_questController.AddQuestPriority(questId))
{
num++;
}
}
if (num > 0)
{
_chatGui.Print($"Added {num} quests to priority queue.", "Questionable", 576);
}
_logger.LogInformation("Added {Count} quests from saved preset to priority list", num);
}
private void RemovePresetFromPriority(List<ElementId> questIds)
{
int num = 0;
foreach (ElementId questId in questIds)
{
Quest quest = _questController.ManualPriorityQuests.FirstOrDefault((Quest q) => q.Id.Equals(questId));
if (quest != null)
{
_questController.ManualPriorityQuests.Remove(quest);
num++;
}
}
if (num > 0)
{
_questController.SavePriorityQuests();
_chatGui.Print($"Removed {num} quests from priority queue.", "Questionable", 576);
_logger.LogInformation("Removed {Count} quests from priority list", num);
}
}
private void UpdatePresetQuests(string key, Configuration.SavedQuestPreset preset)
{
List<ElementId> list = _questController.ManualPriorityQuests.Select((Quest q) => q.Id).ToList();
int count = preset.QuestIds.Count;
preset.QuestIds = list;
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Updated preset '{PresetName}' from {OldCount} to {NewCount} quests", preset.Name, count, list.Count);
_chatGui.Print($"Updated preset '{preset.Name}' with {list.Count} quests (was {count}).", "Questionable", 576);
}
private void RenamePreset(string key, string newName, string newDescription)
{
if (_configuration.General.SavedPresets.TryGetValue(key, out Configuration.SavedQuestPreset value))
{
string name = value.Name;
value.Name = newName.Trim();
value.Description = newDescription.Trim();
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Updated preset '{OldName}' to '{NewName}'", name, value.Name);
}
_renamingPresetKey = null;
_renamePresetName = string.Empty;
_renamePresetDescription = string.Empty;
}
private List<ElementId> GetAvailableQuests(List<ElementId> questIds)
{
return (from questId in questIds
where _questFunctions.IsReadyToAcceptQuest(questId) || _questFunctions.IsQuestAccepted(questId)
where !_questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(questId))
where _questRegistry.IsKnownQuest(questId)
select questId).ToList();
}
private List<ElementId> GetCompletedQuests(List<ElementId> questIds)
{
return questIds.Where((ElementId questId) => _questFunctions.IsQuestComplete(questId)).ToList();
}
private List<ElementId> GetAlreadyPriorityQuests(List<ElementId> questIds)
{
return (from questId in questIds
where _questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(questId))
where !_questFunctions.IsQuestComplete(questId)
select questId).ToList();
}
private List<ElementId> GetUnknownQuests(List<ElementId> questIds)
{
IQuestInfo questInfo;
return questIds.Where((ElementId questId) => !_questData.TryGetQuestInfo(questId, out questInfo)).ToList();
}
}