diff --git a/Questionable/Configuration.cs b/Questionable/Configuration.cs index 2e833995..9f86cb94 100644 --- a/Questionable/Configuration.cs +++ b/Questionable/Configuration.cs @@ -18,6 +18,7 @@ internal sealed class Configuration : IPluginConfiguration public GrandCompany GrandCompany { get; set; } = GrandCompany.None; public bool HideInAllInstances { get; set; } = true; public bool UseEscToCancelQuesting { get; set; } = true; + public bool ShowIncompleteSeasonalEvents { get; set; } = true; } internal sealed class AdvancedConfiguration diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index 8517d135..4c7f05b7 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -208,6 +208,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Questionable/Windows/ConfigWindow.cs b/Questionable/Windows/ConfigWindow.cs index 0fa60d69..c2148aa1 100644 --- a/Questionable/Windows/ConfigWindow.cs +++ b/Questionable/Windows/ConfigWindow.cs @@ -83,6 +83,13 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig Save(); } + bool showIncompleteSeasonalEvents = _configuration.General.ShowIncompleteSeasonalEvents; + if (ImGui.Checkbox("Show details for incomplete seasonal events", ref showIncompleteSeasonalEvents)) + { + _configuration.General.ShowIncompleteSeasonalEvents = showIncompleteSeasonalEvents; + Save(); + } + ImGui.EndTabItem(); } diff --git a/Questionable/Windows/QuestComponents/EventInfoComponent.cs b/Questionable/Windows/QuestComponents/EventInfoComponent.cs new file mode 100644 index 00000000..12e3f497 --- /dev/null +++ b/Questionable/Windows/QuestComponents/EventInfoComponent.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Dalamud.Interface; +using Dalamud.Interface.Components; +using Dalamud.Plugin; +using Humanizer; +using Humanizer.Localisation; +using ImGuiNET; +using Questionable.Controller; +using Questionable.Data; +using Questionable.Functions; +using Questionable.Model; +using Questionable.Model.Questing; + +namespace Questionable.Windows.QuestComponents; + +internal sealed class EventInfoComponent +{ + private readonly List _eventQuests = + [ + new EventQuest("Moonfire Faire", [new(5182), new(5183)], + new DateTime(new DateOnly(2024, 8, 26), new TimeOnly(14, 59), DateTimeKind.Utc)), + ]; + + private readonly QuestData _questData; + private readonly QuestRegistry _questRegistry; + private readonly QuestFunctions _questFunctions; + private readonly UiUtils _uiUtils; + private readonly QuestController _questController; + private readonly QuestTooltipComponent _questTooltipComponent; + private readonly Configuration _configuration; + private readonly IDalamudPluginInterface _pluginInterface; + + public EventInfoComponent(QuestData questData, QuestRegistry questRegistry, QuestFunctions questFunctions, + UiUtils uiUtils, QuestController questController, QuestTooltipComponent questTooltipComponent, + Configuration configuration, IDalamudPluginInterface pluginInterface) + { + _questData = questData; + _questRegistry = questRegistry; + _questFunctions = questFunctions; + _uiUtils = uiUtils; + _questController = questController; + _questTooltipComponent = questTooltipComponent; + _configuration = configuration; + _pluginInterface = pluginInterface; + } + + public bool ShouldDraw => _configuration.General.ShowIncompleteSeasonalEvents && _eventQuests.Any(IsIncomplete); + + public void Draw() + { + foreach (var eventQuest in _eventQuests) + { + if (IsIncomplete(eventQuest)) + DrawEventQuest(eventQuest); + } + } + + private void DrawEventQuest(EventQuest eventQuest) + { + string time = (eventQuest.EndsAtUtc - DateTime.UtcNow).Humanize( + precision: 1, + culture: CultureInfo.InvariantCulture, + minUnit: TimeUnit.Minute, + maxUnit: TimeUnit.Day); + ImGui.Text($"{eventQuest.Name} ({time})"); + + float width; + using (var _ = _pluginInterface.UiBuilder.IconFontHandle.Push()) + width = ImGui.CalcTextSize(FontAwesomeIcon.Play.ToIconString()).X + ImGui.GetStyle().FramePadding.X; + + using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) + width -= ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X; + + List startableQuests = eventQuest.QuestIds.Where(x => + _questRegistry.IsKnownQuest(x) && + _questFunctions.IsReadyToAcceptQuest(x) && + x != _questController.StartedQuest?.Quest.Id && + x != _questController.NextQuest?.Quest.Id) + .ToList(); + if (startableQuests.Count == 0) + width = 0; + + foreach (var questId in eventQuest.QuestIds) + { + if (_questFunctions.IsQuestComplete(questId)) + continue; + + string questName = _questData.GetQuestInfo(questId).Name; + if (startableQuests.Contains(questId) && + _questRegistry.TryGetQuest(questId, out Quest? quest)) + { + ImGuiComponents.IconButton(FontAwesomeIcon.Play); + if (ImGui.IsItemClicked()) + { + _questController.SetNextQuest(quest); + _questController.Start("SeasonalEventSelection"); + } + + bool hovered = ImGui.IsItemHovered(); + + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.Text(questName); + hovered |= ImGui.IsItemHovered(); + + if (hovered) + _questTooltipComponent.Draw(quest.Info); + } + else + { + if (width > 0) + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + width); + + var style = _uiUtils.GetQuestStyle(questId); + if (_uiUtils.ChecklistItem(questName, style.Color, style.Icon, ImGui.GetStyle().FramePadding.X)) + _questTooltipComponent.Draw(_questData.GetQuestInfo(questId)); + } + } + } + + private bool IsIncomplete(EventQuest eventQuest) + { + if (eventQuest.EndsAtUtc <= DateTime.UtcNow) + return false; + + return !eventQuest.QuestIds.All(x => _questFunctions.IsQuestComplete(x)); + } + + private sealed record EventQuest(string Name, List QuestIds, DateTime EndsAtUtc); +} diff --git a/Questionable/Windows/QuestComponents/QuestTooltipComponent.cs b/Questionable/Windows/QuestComponents/QuestTooltipComponent.cs index 65bcf240..1f8a19a0 100644 --- a/Questionable/Windows/QuestComponents/QuestTooltipComponent.cs +++ b/Questionable/Windows/QuestComponents/QuestTooltipComponent.cs @@ -1,4 +1,5 @@ -using Dalamud.Interface; +using Dalamud.Game.Text; +using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Client.UI.Agent; @@ -44,6 +45,9 @@ internal sealed class QuestTooltipComponent using var tooltip = ImRaii.Tooltip(); if (tooltip) { + ImGui.Text($"{SeIconChar.LevelEn.ToIconString()}{quest.Level}"); + ImGui.SameLine(); + var (color, _, tooltipText) = _uiUtils.GetQuestStyle(quest.QuestId); ImGui.TextColored(color, tooltipText); if (quest.IsRepeatable) diff --git a/Questionable/Windows/QuestWindow.cs b/Questionable/Windows/QuestWindow.cs index 296f0ac5..fa880ec4 100644 --- a/Questionable/Windows/QuestWindow.cs +++ b/Questionable/Windows/QuestWindow.cs @@ -25,6 +25,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig private readonly ActiveQuestComponent _activeQuestComponent; private readonly ARealmRebornComponent _aRealmRebornComponent; private readonly CreationUtilsComponent _creationUtilsComponent; + private readonly EventInfoComponent _eventInfoComponent; private readonly QuickAccessButtonsComponent _quickAccessButtonsComponent; private readonly RemainingTasksComponent _remainingTasksComponent; private readonly IFramework _framework; @@ -38,6 +39,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig TerritoryData territoryData, ActiveQuestComponent activeQuestComponent, ARealmRebornComponent aRealmRebornComponent, + EventInfoComponent eventInfoComponent, CreationUtilsComponent creationUtilsComponent, QuickAccessButtonsComponent quickAccessButtonsComponent, RemainingTasksComponent remainingTasksComponent, @@ -53,6 +55,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig _territoryData = territoryData; _activeQuestComponent = activeQuestComponent; _aRealmRebornComponent = aRealmRebornComponent; + _eventInfoComponent = eventInfoComponent; _creationUtilsComponent = creationUtilsComponent; _quickAccessButtonsComponent = quickAccessButtonsComponent; _remainingTasksComponent = remainingTasksComponent; @@ -135,6 +138,12 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig ImGui.Separator(); } + if (_eventInfoComponent.ShouldDraw) + { + _eventInfoComponent.Draw(); + ImGui.Separator(); + } + _creationUtilsComponent.Draw(); ImGui.Separator(); diff --git a/Questionable/Windows/UiUtils.cs b/Questionable/Windows/UiUtils.cs index d04d5dac..4e1fb9c3 100644 --- a/Questionable/Windows/UiUtils.cs +++ b/Questionable/Windows/UiUtils.cs @@ -20,7 +20,7 @@ internal sealed class UiUtils _pluginInterface = pluginInterface; } - public (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(ElementId elementId) + public (Vector4 Color, FontAwesomeIcon Icon, string Status) GetQuestStyle(ElementId elementId) { if (_questFunctions.IsQuestAccepted(elementId)) return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Active"); @@ -42,7 +42,7 @@ internal sealed class UiUtils return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times); } - public bool ChecklistItem(string text, Vector4 color, FontAwesomeIcon icon) + public bool ChecklistItem(string text, Vector4 color, FontAwesomeIcon icon, float extraPadding = 0) { // ReSharper disable once UnusedVariable using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) @@ -53,6 +53,8 @@ internal sealed class UiUtils bool hover = ImGui.IsItemHovered(); ImGui.SameLine(); + if (extraPadding > 0) + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + extraPadding); ImGui.TextUnformatted(text); return hover; }