Automatically redeem untradeable mounts/minions/orchestrion rolls/TT cards/fashion...
authorLiza Carvelli <liza@carvel.li>
Thu, 2 Jan 2025 21:50:50 +0000 (22:50 +0100)
committerLiza Carvelli <liza@carvel.li>
Thu, 2 Jan 2025 21:50:59 +0000 (22:50 +0100)
Questionable.sln.DotSettings
Questionable/Controller/Steps/Shared/RedeemRewardItems.cs [new file with mode: 0644]
Questionable/Data/QuestData.cs
Questionable/Model/ItemReward.cs [new file with mode: 0644]
Questionable/Model/QuestInfo.cs
Questionable/QuestionablePlugin.cs
Questionable/Windows/JournalComponents/QuestRewardComponent.cs [new file with mode: 0644]
Questionable/Windows/JournalProgressWindow.cs
Questionable/Windows/QuestComponents/QuestTooltipComponent.cs

index 830879e07afddbe1b28a18ef4c6459e15555f7d9..ef6a1abd74b35d326dc7dd84f9b6ed2b342369e7 100644 (file)
@@ -25,6 +25,7 @@
        <s:Boolean x:Key="/Default/UserDictionary/Words/=mnemo/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=nightsoil/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=ondo/@EntryIndexedValue">True</s:Boolean>
+       <s:Boolean x:Key="/Default/UserDictionary/Words/=orchestrion/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=ostall/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=palaka_0027s/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=rostra/@EntryIndexedValue">True</s:Boolean>
diff --git a/Questionable/Controller/Steps/Shared/RedeemRewardItems.cs b/Questionable/Controller/Steps/Shared/RedeemRewardItems.cs
new file mode 100644 (file)
index 0000000..4d11e72
--- /dev/null
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using Dalamud.Game.ClientState.Conditions;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using Questionable.Data;
+using Questionable.Functions;
+using Questionable.Model;
+using Questionable.Model.Questing;
+
+namespace Questionable.Controller.Steps.Shared;
+
+internal static class RedeemRewardItems
+{
+    internal sealed class Factory(QuestData questData) : ITaskFactory
+    {
+        public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
+        {
+            if (step.InteractionType != EInteractionType.AcceptQuest)
+                return [];
+
+            List<ITask> tasks = [];
+            unsafe
+            {
+                InventoryManager* inventoryManager = InventoryManager.Instance();
+                if (inventoryManager == null)
+                    return tasks;
+
+                foreach (var itemReward in questData.RedeemableItems)
+                {
+                    if (inventoryManager->GetInventoryItemCount(itemReward.ItemId) > 0 &&
+                        !itemReward.IsUnlocked())
+                    {
+                        tasks.Add(new Task(itemReward));
+                    }
+                }
+            }
+
+            return tasks;
+        }
+    }
+
+    internal sealed record Task(ItemReward ItemReward) : ITask
+    {
+        public override string ToString() => $"TryRedeem({ItemReward.Name})";
+    }
+
+    internal sealed class Executor(
+        GameFunctions gameFunctions,
+        ICondition condition) : TaskExecutor<Task>
+    {
+        private DateTime _continueAt;
+
+        protected override bool Start()
+        {
+            if (condition[ConditionFlag.Mounted])
+                return false;
+
+            _continueAt = DateTime.Now.Add(Task.ItemReward.CastTime).AddSeconds(1);
+            return gameFunctions.UseItem(Task.ItemReward.ItemId);
+        }
+
+        public override ETaskResult Update()
+        {
+            if (condition[ConditionFlag.Casting])
+                return ETaskResult.StillRunning;
+
+            return DateTime.Now <= _continueAt ? ETaskResult.StillRunning : ETaskResult.TaskComplete;
+        }
+    }
+}
index 8f51c4991f62259edfb38bfea4f5c708276e7984..7a3229a76a4208c3e5c40bff6846a5d41c2227ad 100644 (file)
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.Immutable;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Dalamud.Plugin.Services;
@@ -218,8 +219,15 @@ internal sealed class QuestData
             quest.JournalGenre = 82;
             quest.SortKey = 0;
         }
+
+        RedeemableItems = quests.Where(x => x is QuestInfo)
+            .Cast<QuestInfo>()
+            .SelectMany(x => x.ItemRewards)
+            .ToImmutableHashSet();
     }
 
+    public ImmutableHashSet<ItemReward> RedeemableItems { get; }
+
     private void AddPreviousQuest(QuestId questToUpdate, QuestId requiredQuestId)
     {
         QuestInfo quest = (QuestInfo)_quests[questToUpdate];
diff --git a/Questionable/Model/ItemReward.cs b/Questionable/Model/ItemReward.cs
new file mode 100644 (file)
index 0000000..0304474
--- /dev/null
@@ -0,0 +1,99 @@
+using System;
+using Dalamud.Utility;
+using FFXIVClientStructs.FFXIV.Client.Game.UI;
+using Lumina.Excel.Sheets;
+using Questionable.Model.Questing;
+
+namespace Questionable.Model;
+
+public enum EItemRewardType
+{
+    Mount,
+    Minion,
+    OrchestrionRoll,
+    TripleTriadCard,
+    FashionAccessory,
+}
+
+public sealed class ItemRewardDetails(Item item, ElementId elementId)
+{
+    public uint ItemId { get; } = item.RowId;
+    public string Name { get; } = item.Name.ToDalamudString().ToString();
+    public TimeSpan CastTime { get; } = TimeSpan.FromSeconds(item.CastTimeSeconds);
+    public ElementId ElementId { get; } = elementId;
+}
+
+public abstract record ItemReward(ItemRewardDetails Item)
+{
+    internal static ItemReward? CreateFromItem(Item item, ElementId elementId)
+    {
+        if (item.ItemAction.ValueNullable?.Type is 1322)
+            return new MountReward(new ItemRewardDetails(item, elementId), item.ItemAction.Value.Data[0]);
+
+        if (item.ItemAction.ValueNullable?.Type is 853)
+            return new MinionReward(new ItemRewardDetails(item, elementId), item.ItemAction.Value.Data[0]);
+
+        if (item.AdditionalData.GetValueOrDefault<Orchestrion>() is { } orchestrionRoll)
+            return new OrchestrionRollReward(new ItemRewardDetails(item, elementId), orchestrionRoll.RowId);
+
+        if (item.AdditionalData.GetValueOrDefault<TripleTriadCard>() is { } tripleTriadCard)
+            return new TripleTriadCardReward(new ItemRewardDetails(item, elementId), (ushort)tripleTriadCard.RowId);
+
+        if (item.ItemAction.ValueNullable?.Type is 20086)
+            return new FashionAccessoryReward(new ItemRewardDetails(item, elementId), item.ItemAction.Value.Data[0]);
+
+        return null;
+    }
+
+    public uint ItemId => Item.ItemId;
+    public string Name => Item.Name;
+    public ElementId ElementId => Item.ElementId;
+    public TimeSpan CastTime => Item.CastTime;
+    public abstract EItemRewardType Type { get; }
+    public abstract bool IsUnlocked();
+}
+
+public sealed record MountReward(ItemRewardDetails Item, uint MountId)
+    : ItemReward(Item)
+{
+    public override EItemRewardType Type => EItemRewardType.Mount;
+
+    public override unsafe bool IsUnlocked()
+        => PlayerState.Instance()->IsMountUnlocked(MountId);
+}
+
+public sealed record MinionReward(ItemRewardDetails Item, uint MinionId)
+    : ItemReward(Item)
+{
+    public override EItemRewardType Type => EItemRewardType.Minion;
+
+    public override unsafe bool IsUnlocked()
+        => UIState.Instance()->IsCompanionUnlocked(MinionId);
+}
+
+public sealed record OrchestrionRollReward(ItemRewardDetails Item, uint OrchestrionRollId)
+    : ItemReward(Item)
+{
+    public override EItemRewardType Type => EItemRewardType.OrchestrionRoll;
+
+    public override unsafe bool IsUnlocked() =>
+        PlayerState.Instance()->IsOrchestrionRollUnlocked(OrchestrionRollId);
+}
+
+public sealed record TripleTriadCardReward(ItemRewardDetails Item, ushort TripleTriadCardId)
+    : ItemReward(Item)
+{
+    public override EItemRewardType Type => EItemRewardType.TripleTriadCard;
+
+    public override unsafe bool IsUnlocked() =>
+        UIState.Instance()->IsTripleTriadCardUnlocked(TripleTriadCardId);
+}
+
+public sealed record FashionAccessoryReward(ItemRewardDetails Item, uint AccessoryId)
+    : ItemReward(Item)
+{
+    public override EItemRewardType Type => EItemRewardType.FashionAccessory;
+
+    public override unsafe bool IsUnlocked() =>
+        PlayerState.Instance()->IsOrnamentUnlocked(AccessoryId);
+}
index e34c16d7a362d80c63420f716b327ca2e286a5ac..06c6dc34b99f7be976f1d35ce010b5adff4468f6 100644 (file)
@@ -2,10 +2,11 @@
 using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Linq;
-using FFXIVClientStructs.FFXIV.Client.UI.Agent;
 using LLib.GameData;
+using Lumina.Excel.Sheets;
 using Questionable.Model.Questing;
 using ExcelQuest = Lumina.Excel.Sheets.Quest;
+using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
 
 namespace Questionable.Model;
 
@@ -54,7 +55,8 @@ internal sealed class QuestInfo : IQuestInfo
         QuestLockJoin = (EQuestJoin)quest.QuestLockJoin;
         JournalGenre = quest.JournalGenre.ValueNullable?.RowId;
         SortKey = quest.SortKey;
-        IsMainScenarioQuest = quest.JournalGenre.ValueNullable?.JournalCategory.ValueNullable?.JournalSection.ValueNullable?.RowId is 0 or 1;
+        IsMainScenarioQuest = quest.JournalGenre.ValueNullable?.JournalCategory.ValueNullable?.JournalSection
+            .ValueNullable?.RowId is 0 or 1;
         CompletesInstantly = quest.TodoParams[0].ToDoCompleteSeq == 0;
         PreviousInstanceContent = quest.InstanceContent.Select(x => (ushort)x.RowId).Where(x => x != 0).ToList();
         PreviousInstanceContentJoin = (EQuestJoin)quest.InstanceContentJoin;
@@ -67,6 +69,15 @@ internal sealed class QuestInfo : IQuestInfo
         NewGamePlusChapter = newGamePlusChapter;
         StartingCity = startingCity;
         MoogleDeliveryLevel = (byte)quest.DeliveryQuest.RowId;
+        ItemRewards = quest.Reward.Where(x => x.RowId > 0 && x.Is<Item>())
+            .Select(x => x.GetValueOrDefault<Item>())
+            .Where(x => x != null)
+            .Cast<Item>()
+            .Where(x => x.IsUntradable)
+            .Select(x => ItemReward.CreateFromItem(x, QuestId))
+            .Where(x => x != null)
+            .Cast<ItemReward>()
+            .ToList();
         Expansion = (EExpansionVersion)quest.Expansion.RowId;
     }
 
@@ -79,7 +90,6 @@ internal sealed class QuestInfo : IQuestInfo
         });
     }
 
-
     public ElementId QuestId { get; }
     public string Name { get; }
     public ushort Level { get; }
@@ -105,6 +115,7 @@ internal sealed class QuestInfo : IQuestInfo
     public byte StartingCity { get; set; }
     public byte MoogleDeliveryLevel { get; }
     public bool IsMoogleDeliveryQuest => JournalGenre == 87;
+    public IReadOnlyList<ItemReward> ItemRewards { get; }
     public EExpansionVersion Expansion { get; }
 
     public void AddPreviousQuest(PreviousQuestInfo questId)
index 1072a54f29c0c6737ab7a71ea3f81e9cd7f69243..f5fa51ee11a405519fa32e55b66aabad2c782851 100644 (file)
@@ -138,6 +138,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddTaskFactory<QuestCleanUp.CheckAlliedSocietyMount>();
         serviceCollection
             .AddTaskExecutor<MoveToLandingLocation.Task, MoveToLandingLocation.MoveToLandingLocationExecutor>();
+        serviceCollection
+            .AddTaskFactoryAndExecutor<RedeemRewardItems.Task, RedeemRewardItems.Factory, RedeemRewardItems.Executor>();
         serviceCollection.AddTaskExecutor<DoGather.Task, DoGather.GatherExecutor>();
         serviceCollection.AddTaskExecutor<DoGatherCollectable.Task, DoGatherCollectable.GatherCollectableExecutor>();
         serviceCollection.AddTaskFactoryAndExecutor<SwitchClassJob.Task, SwitchClassJob.Factory,
@@ -155,7 +157,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
             .AddTaskFactoryAndExecutor<AetheryteShortcut.Task, AetheryteShortcut.Factory,
                 AetheryteShortcut.UseAetheryteShortcut>();
         serviceCollection
-            .AddTaskExecutor<AetheryteShortcut.MoveAwayFromAetheryte, AetheryteShortcut.MoveAwayFromAetheryteExecutor>();
+            .AddTaskExecutor<AetheryteShortcut.MoveAwayFromAetheryte,
+                AetheryteShortcut.MoveAwayFromAetheryteExecutor>();
         serviceCollection
             .AddTaskFactoryAndExecutor<SkipCondition.SkipTask, SkipCondition.Factory, SkipCondition.CheckSkip>();
         serviceCollection.AddTaskFactoryAndExecutor<Gather.GatheringTask, Gather.Factory, Gather.StartGathering>();
@@ -179,7 +182,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
             .AddTaskFactoryAndExecutor<AethernetShard.Attune, AethernetShard.Factory, AethernetShard.DoAttune>();
         serviceCollection.AddTaskFactoryAndExecutor<Aetheryte.Attune, Aetheryte.Factory, Aetheryte.DoAttune>();
         serviceCollection.AddTaskFactoryAndExecutor<Combat.Task, Combat.Factory, Combat.HandleCombat>();
-        serviceCollection.AddTaskFactoryAndExecutor<Duty.OpenDutyFinderTask, Duty.Factory, Duty.OpenDutyFinderExecutor>();
+        serviceCollection
+            .AddTaskFactoryAndExecutor<Duty.OpenDutyFinderTask, Duty.Factory, Duty.OpenDutyFinderExecutor>();
         serviceCollection.AddTaskExecutor<Duty.StartAutoDutyTask, Duty.StartAutoDutyExecutor>();
         serviceCollection.AddTaskExecutor<Duty.WaitAutoDutyTask, Duty.WaitAutoDutyExecutor>();
         serviceCollection.AddTaskFactory<Emote.Factory>();
@@ -269,6 +273,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
 
         serviceCollection.AddSingleton<QuestJournalUtils>();
         serviceCollection.AddSingleton<QuestJournalComponent>();
+        serviceCollection.AddSingleton<QuestRewardComponent>();
         serviceCollection.AddSingleton<GatheringJournalComponent>();
         serviceCollection.AddSingleton<AlliedSocietyJournalComponent>();
 
diff --git a/Questionable/Windows/JournalComponents/QuestRewardComponent.cs b/Questionable/Windows/JournalComponents/QuestRewardComponent.cs
new file mode 100644 (file)
index 0000000..2fab310
--- /dev/null
@@ -0,0 +1,65 @@
+using System;
+using System.Linq;
+using Dalamud.Interface.Utility.Raii;
+using ImGuiNET;
+using Questionable.Data;
+using Questionable.Model;
+using Questionable.Windows.QuestComponents;
+
+namespace Questionable.Windows.JournalComponents;
+
+internal sealed class QuestRewardComponent
+{
+    private readonly QuestData _questData;
+    private readonly QuestTooltipComponent _questTooltipComponent;
+    private readonly UiUtils _uiUtils;
+
+    public QuestRewardComponent(
+        QuestData questData,
+        QuestTooltipComponent questTooltipComponent,
+        UiUtils uiUtils)
+    {
+        _questData = questData;
+        _questTooltipComponent = questTooltipComponent;
+        _uiUtils = uiUtils;
+    }
+
+    public void DrawItemRewards()
+    {
+        using var tab = ImRaii.TabItem("Item Rewards");
+        if (!tab)
+            return;
+
+        ImGui.BulletText("Only untradeable items are listed (you can e.g. sell your Wind-up Airship from the enovy quest).");
+
+        DrawGroup("Mounts", EItemRewardType.Mount);
+        DrawGroup("Minions", EItemRewardType.Minion);
+        DrawGroup("Orchestrion Rolls", EItemRewardType.OrchestrionRoll);
+        DrawGroup("Triple Triad Cards", EItemRewardType.TripleTriadCard);
+        DrawGroup("Fashion Accessories", EItemRewardType.FashionAccessory);
+    }
+
+    private void DrawGroup(string label, EItemRewardType type)
+    {
+        if (!ImGui.CollapsingHeader($"{label}###Reward{type}"))
+            return;
+
+        foreach (var item in _questData.RedeemableItems.Where(x => x.Type == type)
+                     .OrderBy(x => x.Name, StringComparer.CurrentCultureIgnoreCase))
+        {
+            if (_uiUtils.ChecklistItem(item.Name, item.IsUnlocked()))
+            {
+                if (_questData.TryGetQuestInfo(item.ElementId, out var questInfo))
+                {
+                    using var tooltip = ImRaii.Tooltip();
+                    if (!tooltip)
+                        continue;
+
+                    ImGui.Text($"Obtained from: {questInfo.Name}");
+                    using (ImRaii.PushIndent())
+                        _questTooltipComponent.DrawInner(questInfo, false);
+                }
+            }
+        }
+    }
+}
index 6a5724a7e10b0aeb0f2a6444e0f6b4ceb99e8e51..e6d2a16d56d0f7d15b9ede7534c42f9045999965 100644 (file)
@@ -12,12 +12,14 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
 {
     private readonly QuestJournalComponent _questJournalComponent;
     private readonly AlliedSocietyJournalComponent _alliedSocietyJournalComponent;
+    private readonly QuestRewardComponent _questRewardComponent;
     private readonly GatheringJournalComponent _gatheringJournalComponent;
     private readonly QuestRegistry _questRegistry;
     private readonly IClientState _clientState;
 
     public JournalProgressWindow(
         QuestJournalComponent questJournalComponent,
+        QuestRewardComponent questRewardComponent,
         AlliedSocietyJournalComponent alliedSocietyJournalComponent,
         GatheringJournalComponent gatheringJournalComponent,
         QuestRegistry questRegistry,
@@ -26,6 +28,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
     {
         _questJournalComponent = questJournalComponent;
         _alliedSocietyJournalComponent = alliedSocietyJournalComponent;
+        _questRewardComponent = questRewardComponent;
         _gatheringJournalComponent = gatheringJournalComponent;
         _questRegistry = questRegistry;
         _clientState = clientState;
@@ -64,6 +67,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
 
         _questJournalComponent.DrawQuests();
         _alliedSocietyJournalComponent.DrawAlliedSocietyQuests();
+        _questRewardComponent.DrawItemRewards();
         _gatheringJournalComponent.DrawGatheringItems();
     }
 
index bcf823ae65c9f2045692eb6342e51dd84f126581..e74beb2a1f79053f475bc02e762a01956da8c0a8 100644 (file)
@@ -40,48 +40,58 @@ internal sealed class QuestTooltipComponent
     {
         using var tooltip = ImRaii.Tooltip();
         if (tooltip)
+            DrawInner(questInfo, true);
+    }
+
+    public void DrawInner(IQuestInfo questInfo, bool showItemRewards)
+    {
+        ImGui.Text($"{SeIconChar.LevelEn.ToIconString()}{questInfo.Level}");
+        ImGui.SameLine();
+
+        var (color, _, tooltipText) = _uiUtils.GetQuestStyle(questInfo.QuestId);
+        ImGui.TextColored(color, tooltipText);
+
+        if (questInfo is QuestInfo { IsSeasonalEvent: true })
         {
-            ImGui.Text($"{SeIconChar.LevelEn.ToIconString()}{questInfo.Level}");
             ImGui.SameLine();
+            ImGui.TextUnformatted("Event");
+        }
 
-            var (color, _, tooltipText) = _uiUtils.GetQuestStyle(questInfo.QuestId);
-            ImGui.TextColored(color, tooltipText);
-            if (questInfo.IsRepeatable)
-            {
-                ImGui.SameLine();
-                ImGui.TextUnformatted("Repeatable");
-            }
-
-            if (questInfo is QuestInfo { CompletesInstantly: true })
-            {
-                ImGui.SameLine();
-                ImGui.TextUnformatted("Instant");
-            }
+        if (questInfo.IsRepeatable)
+        {
+            ImGui.SameLine();
+            ImGui.TextUnformatted("Repeatable");
+        }
 
-            if (_questRegistry.TryGetQuest(questInfo.QuestId, out Quest? quest))
-            {
-                if (quest.Root.Disabled)
-                {
-                    ImGui.SameLine();
-                    ImGui.TextColored(ImGuiColors.DalamudRed, "Disabled");
-                }
+        if (questInfo is QuestInfo { CompletesInstantly: true })
+        {
+            ImGui.SameLine();
+            ImGui.TextUnformatted("Instant");
+        }
 
-                if (quest.Root.Author.Count == 1)
-                    ImGui.Text($"Author: {quest.Root.Author[0]}");
-                else
-                    ImGui.Text($"Authors: {string.Join(", ", quest.Root.Author)}");
-            }
-            else
+        if (_questRegistry.TryGetQuest(questInfo.QuestId, out Quest? quest))
+        {
+            if (quest.Root.Disabled)
             {
                 ImGui.SameLine();
-                ImGui.TextColored(ImGuiColors.DalamudRed, "NoQuestPath");
+                ImGui.TextColored(ImGuiColors.DalamudRed, "Disabled");
             }
 
-            DrawQuestUnlocks(questInfo, 0);
+            if (quest.Root.Author.Count == 1)
+                ImGui.Text($"Author: {quest.Root.Author[0]}");
+            else
+                ImGui.Text($"Authors: {string.Join(", ", quest.Root.Author)}");
         }
+        else
+        {
+            ImGui.SameLine();
+            ImGui.TextColored(ImGuiColors.DalamudRed, "NoQuestPath");
+        }
+
+        DrawQuestUnlocks(questInfo, 0, showItemRewards);
     }
 
-    private void DrawQuestUnlocks(IQuestInfo questInfo, int counter)
+    private void DrawQuestUnlocks(IQuestInfo questInfo, int counter, bool showItemRewards)
     {
         if (counter >= 10)
             return;
@@ -118,12 +128,13 @@ internal sealed class QuestTooltipComponent
                             _questFunctions.IsQuestComplete(q.QuestId) ? byte.MinValue : q.Sequence), iconColor, icon);
 
                     if (qInfo is QuestInfo qstInfo && (counter <= 2 || icon != FontAwesomeIcon.Check))
-                        DrawQuestUnlocks(qstInfo, counter + 1);
+                        DrawQuestUnlocks(qstInfo, counter + 1, false);
                 }
                 else
                 {
                     using var _ = ImRaii.Disabled();
-                    _uiUtils.ChecklistItem($"Unknown Quest ({q.QuestId})", ImGuiColors.DalamudGrey, FontAwesomeIcon.Question);
+                    _uiUtils.ChecklistItem($"Unknown Quest ({q.QuestId})", ImGuiColors.DalamudGrey,
+                        FontAwesomeIcon.Question);
                 }
             }
         }
@@ -193,6 +204,16 @@ internal sealed class QuestTooltipComponent
                 GrandCompany currentGrandCompany = _questFunctions.GetGrandCompany();
                 _uiUtils.ChecklistItem($"Grand Company: {gcName}", actualQuestInfo.GrandCompany == currentGrandCompany);
             }
+
+            if (showItemRewards && actualQuestInfo.ItemRewards.Count > 0)
+            {
+                ImGui.Separator();
+                ImGui.Text("Item Rewards:");
+                foreach (var reward in actualQuestInfo.ItemRewards)
+                {
+                    ImGui.BulletText(reward.Name);
+                }
+            }
         }
 
         if (counter > 0)