diff --git a/Dalamud/Game/Chat/SeStringHandling/Payload.cs b/Dalamud/Game/Chat/SeStringHandling/Payload.cs
index eb02e4fe3..b7a900b2c 100644
--- a/Dalamud/Game/Chat/SeStringHandling/Payload.cs
+++ b/Dalamud/Game/Chat/SeStringHandling/Payload.cs
@@ -151,6 +151,10 @@ namespace Dalamud.Game.Chat.SeStringHandling
payload = new StatusPayload();
break;
+ case EmbeddedInfoType.QuestLink:
+ payload = new QuestPayload();
+ break;
+
case EmbeddedInfoType.LinkTerminator:
// this has no custom handling and so needs to fallthrough to ensure it is captured
default:
@@ -225,6 +229,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
PlayerName = 0x01,
ItemLink = 0x03,
MapPositionLink = 0x04,
+ QuestLink = 0x05,
Status = 0x09,
LinkTerminator = 0xCF // not clear but seems to always follow a link
diff --git a/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs b/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs
index b3fca980e..060d14093 100644
--- a/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs
+++ b/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs
@@ -47,6 +47,10 @@ namespace Dalamud.Game.Chat.SeStringHandling
///
Icon,
///
+ /// A SeString payload representing a quest link.
+ ///
+ Quest,
+ ///
/// An SeString payload representing any data we don't handle.
///
Unknown
diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/QuestPayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/QuestPayload.cs
new file mode 100644
index 000000000..ac802755e
--- /dev/null
+++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/QuestPayload.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Dalamud.Data;
+using Lumina.Excel.GeneratedSheets;
+using Newtonsoft.Json;
+
+namespace Dalamud.Game.Chat.SeStringHandling.Payloads {
+ ///
+ /// An SeString Payload representing an interactable quest link.
+ ///
+ public class QuestPayload : Payload {
+ public override PayloadType Type => PayloadType.Quest;
+
+ private Quest quest;
+ ///
+ /// The underlying Lumina Quest represented by this payload.
+ ///
+ ///
+ /// Value is evaluated lazily and cached.
+ ///
+
+ [JsonIgnore]
+ public Quest Quest {
+ get {
+ this.quest ??= this.DataResolver.GetExcelSheet().GetRow(this.questId);
+ return this.quest;
+ }
+ }
+
+ [JsonProperty]
+ private uint questId;
+
+ internal QuestPayload() { }
+
+ ///
+ /// Creates a payload representing an interactable quest link for the specified quest.
+ ///
+ /// DataManager instance needed to resolve game data.
+ /// The id of the quest.
+ public QuestPayload(DataManager data, uint questId) {
+ this.DataResolver = data;
+ this.questId = questId;
+ }
+
+ ///
+ public override string ToString() {
+ return $"{Type} - QuestId: {this.questId}, Name: {Quest?.Name ?? "QUEST NOT FOUND"}";
+ }
+
+ protected override byte[] EncodeImpl() {
+ var idBytes = MakeInteger((ushort) this.questId);
+ var chunkLen = idBytes.Length + 4;
+
+ var bytes = new List() {
+ START_BYTE, (byte) SeStringChunkType.Interactable, (byte) chunkLen, (byte) EmbeddedInfoType.QuestLink,
+ };
+
+ bytes.AddRange(idBytes);
+ bytes.AddRange(new byte[] {0x01, 0x01, END_BYTE});
+ return bytes.ToArray();
+
+ }
+
+ protected override void DecodeImpl(BinaryReader reader, long endOfStream) {
+ // Game uses int16, Luimina uses int32
+ this.questId = GetInteger(reader) + 65536;
+ }
+ }
+}