diff --git a/BondageClub/CSS/Styles.css b/BondageClub/CSS/Styles.css
index 891e1bca3c..aff852be8d 100644
--- a/BondageClub/CSS/Styles.css
+++ b/BondageClub/CSS/Styles.css
@@ -342,3 +342,7 @@ select:invalid:not(:disabled):not(:read-only) {
 		scrollbar-color: rgb(149, 149, 149) rgb(0, 0, 0, 0.7);
 	}
 }
+
+.hidden {
+	display: none;
+}
\ No newline at end of file
diff --git a/BondageClub/CSS/chat.css b/BondageClub/CSS/chat.css
index c1c02d5815..e37c2557fe 100644
--- a/BondageClub/CSS/chat.css
+++ b/BondageClub/CSS/chat.css
@@ -98,7 +98,25 @@
   border-left: min(0.2vh, 0.1vw) inset black;
   justify-self: center;
 }
-
+#chat-room-reply-indicator-text {
+  color: var(--base-font-color);
+  text-overflow: ellipsis;
+  text-wrap: nowrap;
+  overflow: hidden;
+  flex: 1;
+  line-height: 1.6em;
+  border: min(0.2vh, 0.1vw) solid black;
+  border-right: none;
+  user-select: none
+}
+#chat-room-reply-indicator:not(.hidden) {
+  background-color: var(--base-color, #eee);
+  position: absolute;
+  transform: translateY(-105%);
+  width: 100%;
+  display: flex;
+  height: 1.6em;
+}
 #InputChat {
   grid-area: chat-input;
   min-height: var(--button-size);
@@ -110,7 +128,15 @@
   border: unset;
   outline: unset;
 }
-
+#chat-room-reply-indicator-close::before {
+  content: "❌";
+}
+#chat-room-reply-indicator-close {
+  background-color: var(--base-color, #eee);
+  border: min(0.2vh, 0.1vw) solid black;
+  cursor: pointer;
+  aspect-ratio: 1;
+}
 #InputChat:focus {
   scrollbar-width: auto;
 }
@@ -237,7 +263,18 @@
 
   background-color: var(--base-color);
 }
-
+.chat-room-message-reply::before {
+  content: "↱";
+  position: relative;
+  top: 4px
+}
+.chat-room-message-reply {
+  display: block;
+  color: gray;
+  width: 100%;
+  font-size: 0.75em;
+  text-align: left;
+}
 .ChatMessageName {
   text-shadow: 0.05em 0.05em black;
   color: var(--label-color);
@@ -272,11 +309,15 @@
 #TextAreaChatLog[data-colortheme="dark2"] {
   background-color: #111;
   color: #eee;
+  --base-color: #111;
+  --base-font-color: #eee;
 }
 
 #TextAreaChatLog[data-colortheme="dark"]~#chat-room-bot,
 #TextAreaChatLog[data-colortheme="dark2"]~#chat-room-bot {
   --button-color: #eee;
+  --base-color: #111;
+  --base-font-color: #eee;
   background-color: #111;
   border-color: rgba(0, 0, 0, 0.25);
   color: #eee;
@@ -284,6 +325,7 @@
 
 #TextAreaChatLog[data-colortheme="dark"]~#chat-room-buttons-div,
 #TextAreaChatLog[data-colortheme="dark2"]~#chat-room-buttons-div {
+  
   border-left: min(0.2vh, 0.1vw) inset rgba(0, 0, 0, 0.25);
 }
 
diff --git a/BondageClub/Screens/Online/ChatRoom/ChatRoom.js b/BondageClub/Screens/Online/ChatRoom/ChatRoom.js
index a2e3a5f68f..0dcc1fd096 100644
--- a/BondageClub/Screens/Online/ChatRoom/ChatRoom.js
+++ b/BondageClub/Screens/Online/ChatRoom/ChatRoom.js
@@ -1124,6 +1124,19 @@ function ChatRoomCreateElement() {
 					tag: "div",
 					attributes: { id: "chat-room-bot" },
 					children: [
+						{
+							tag: "div",
+							attributes: {id: "chat-room-reply-indicator"},
+							classList: ["hidden"],
+							children: [
+								{ tag: "span", attributes: { id: "chat-room-reply-indicator-text" }, children: [TextGet("ChatRoomReply")] },
+								ElementButton.Create(
+									"chat-room-reply-indicator-close",
+									ChatRoomMessageReplyStop,
+									{ noStyling: true },
+								),
+							],
+						},
 						{
 							tag: "textarea",
 							attributes: {
@@ -1135,7 +1148,7 @@ function ChatRoomCreateElement() {
 							},
 							eventListeners: {
 								input: ChatRoomChatInputChangeHandler,
-								keyup: ChatRoomStatusUpdateTalk,
+								keyup: (key) => {ChatRoomStatusUpdateTalk(key); ChatRoomStopReplyOnEscape(key);},
 							},
 						},
 						{
@@ -1972,6 +1985,15 @@ let ChatRoomStatusDeadKeys = [
 	"ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown",
 ];
 
+/**
+ * Stops the reply when the escape key is pressed
+ * @param {KeyboardEvent} key
+ */
+function ChatRoomStopReplyOnEscape(key) {
+	if (key.key == "Escape") {
+		ChatRoomMessageReplyStop();
+	}
+}
 /**
  * Sends the "Talk" status to other players if the player typed in the text box and there's a value in it
  * @param {KeyboardEvent} Key
@@ -3066,40 +3088,45 @@ function ChatRoomSendChat() {
  * This function automatically formats and sends the message with all the information
  * needed to reconstruct it on the receiver.
  *
- * @param {"Chat"|"Whisper"} type
+ * @param {"Chat"|"Whisper"|"Emote"} type
  * @param {string} msg
+ * @param {string} [replyId]
  * @return {ServerChatRoomMessage}
  */
-function ChatRoomGenerateChatRoomChatMessage(type, msg) {
-	// Ensure the last OOC content range is closed with a )
-	const lastRange = SpeechGetOOCRanges(msg).pop();
-	if (
-		Player.ChatSettings.OOCAutoClose
-		&& lastRange !== undefined
-		&& msg.charAt(lastRange.start + lastRange.length - 1) !== ")"
-		&& lastRange.start + lastRange.length === msg.length
-		&& lastRange.length !== 1) {
-		msg += ")";
+function ChatRoomGenerateChatRoomChatMessage(type, msg, replyId) {
+	/** @type {ChatMessageDictionary} */
+	const Dictionary = [];
+	if (type === "Chat" || type === "Whisper") {
+		// Ensure the last OOC content range is closed with a )
+		const lastRange = SpeechGetOOCRanges(msg).pop();
+		if (
+			Player.ChatSettings.OOCAutoClose
+			&& lastRange !== undefined
+			&& msg.charAt(lastRange.start + lastRange.length - 1) !== ")"
+			&& lastRange.start + lastRange.length === msg.length
+			&& lastRange.length !== 1) {
+			msg += ")";
+		}
+
+		// Chat messages are always garbled, unless the no speech restriction is lifted
+		let process = {
+			effects: [],
+			text: msg,
+		};
+
+		process = SpeechTransformProcess(Player, msg, SpeechTransformSenderEffects);
+
+		// We always garble, but if "no speech garbling" is enabled,
+		// we also send down the ungarbled message if it's different
+		const originalMsg = Player.RestrictionSettings.NoSpeechGarble && msg !== process.text ? msg : undefined;
+		Dictionary.push({ Effects: process.effects, Original: originalMsg });
 	}
 
-	// Chat messages are always garbled, unless the no speech restriction is lifted
-	let process = {
-		effects: [],
-		text: msg,
-	};
-
-	process = SpeechTransformProcess(Player, msg, SpeechTransformSenderEffects);
-
-	// We always garble, but if "no speech garbling" is enabled,
-	// we also send down the ungarbled message if it's different
-	const originalMsg = Player.RestrictionSettings.NoSpeechGarble && msg !== process.text ? msg : undefined;
-
-	/** @type {ChatMessageDictionary} */
-	let Dictionary = [
-		{ Effects: process.effects, Original: originalMsg },
-	];
-
-	return { Content: process.text, Type: type, Dictionary };
+	if (replyId) {
+		Dictionary.push({ ReplyId: replyId, Tag: "ReplyId" });
+		ChatRoomMessageReplyStop();
+	}
+	return { Content: msg, Type: type, Dictionary };
 }
 
 /**
@@ -3111,8 +3138,8 @@ function ChatRoomSendChatMessage(msg) {
 	// Regular chat can be prevented with an owner presence rule, also validates for forbidden words
 	if (ChatRoomOwnerPresenceRule("BlockTalk", null)) return false;
 	if (!ChatRoomOwnerForbiddenWordCheck(msg)) return false;
-
-	const data = ChatRoomGenerateChatRoomChatMessage("Chat", msg);
+	const replyId = ChatRoomMessageGetReplyId();
+	const data = ChatRoomGenerateChatRoomChatMessage("Chat", msg, replyId);
 	ServerSend("ChatRoomChat", data);
 
 	ChatRoomStimulationMessage("Talk");
@@ -3133,7 +3160,8 @@ function ChatRoomSendWhisper(targetNumber, msg) {
 		return "target-out-of-range";
 	}
 
-	const data = ChatRoomGenerateChatRoomChatMessage("Whisper", msg);
+	const replyId = ChatRoomMessageGetReplyId();
+	const data = ChatRoomGenerateChatRoomChatMessage("Whisper", msg, replyId);
 	data.Target = targetNumber;
 	ServerSend("ChatRoomChat", data);
 
@@ -3176,7 +3204,10 @@ function ChatRoomSendEmote(msg) {
 		if (msg.startsWith(CommandsKey + "action ")) msg = msg.replace(CommandsKey + "action ", "*");
 	}
 	msg = msg.trim();
-	if (msg != "" && msg != "*") ServerSend("ChatRoomChat", { Content: msg, Type: "Emote" });
+	if (msg == "" || msg == "*") return;
+	const replyId = ChatRoomMessageGetReplyId();
+	const data = ChatRoomGenerateChatRoomChatMessage("Emote", msg, replyId);
+	ServerSend("ChatRoomChat", data);
 }
 
 /**
@@ -3205,8 +3236,9 @@ function ChatRoomSendAttemptEmote(msg) {
 	msg += attemptSucceeded ? ": ✔" : ": ❌";
 	msg += " (" + chance + "%)";
 
-	ServerSend("ChatRoomChat", { Content: msg, Type: "Emote" });
-
+	const replyId = ChatRoomMessageGetReplyId();
+	const data = ChatRoomGenerateChatRoomChatMessage("Emote", msg, replyId);
+	ServerSend("ChatRoomChat", data);
 	return;
 }
 
@@ -3940,6 +3972,10 @@ function ChatRoomMessageDefaultMetadataExtractor(data, SenderCharacter) {
 				text = text.toLowerCase();
 			}
 			substitutions.push([entry.Tag, text]);
+		} else if (IsMsgIdDictionaryEntry(entry)) {
+			meta.MsgId = entry.MsgId;
+		} else if (IsReplyIdDictionaryEntry(entry)) {
+			meta.ReplyId = entry.ReplyId;
 		}
 	}
 
@@ -4254,6 +4290,127 @@ function ChatRoomMessageNameClick() {
 	chatInput.value = `/whisper ${memberNumber} ${chatInput.value.replace(/\/whisper\s*\d+ ?/u, "")}`;
 	chatInput.focus();
 }
+// ----- Replies
+/**
+ * Returns the HTML element for the message with the given ID
+ * @param {string} id
+ * @returns {HTMLElement | null}
+ */
+function ChatRoomMessageGetById(id) {
+	const chatLog = document.getElementById("TextAreaChatLog");
+	if (!chatLog) return null;
+	return chatLog.querySelector(`[msgid="${id}"]`)?.parentElement;
+}
+
+/**
+ * Returns the ID of the message that this message is a reply to
+ * @returns {string | null}
+ */
+function ChatRoomMessageGetReplyId() {
+	const chatInput = /** @type {null | HTMLTextAreaElement} */(document.getElementById("InputChat"));
+	if (!chatInput) return;
+	return chatInput.getAttribute("reply-id");
+}
+/**
+ * Returns the name of the character that this message is a reply to.
+ * We get the wrong name when replying to reply that's what this is for.
+ * @param {string} msgId
+ * @param {boolean} isWhisper
+ * @returns {string | null}
+ */
+function ChatRoomMessageGetReplyName(msgId, isWhisper=false) {
+	const message = ChatRoomMessageGetById(msgId);
+	if (message) {
+		if (isWhisper) {
+			const sender = Number(message.getAttribute("data-sender"));
+			return CharacterNickname(ChatRoomCharacter.find(C => C.MemberNumber === sender));
+		}
+		const names = message.querySelectorAll(".ChatMessageName");
+		const name = names[names.length - 1];
+		return name?.textContent;
+	}
+	return null;
+}
+/**
+ * @param {string} msgId
+ * @returns {string | null}
+ */
+function ChatRoomMessageGetReplyContent(msgId) {
+	const message = ChatRoomMessageGetById(msgId);
+	if (message) {
+		const contents = message.querySelectorAll(".chat-room-message-content");
+		const content = contents[contents.length - 1];
+		return content.textContent;
+	}
+	return null;
+}
+
+
+/**
+ * Figures out the type of the message with the given ID
+ * @param {string} msgId
+ * @returns {"Chat" | "Whisper"}
+ */
+function ChatRoomMessageGetType(msgId) {
+	if (!msgId) return "Chat";
+	if (ChatRoomMessageGetById(msgId)?.classList?.contains("ChatMessageWhisper")) return "Whisper";
+	return "Chat";
+}
+/**
+ * Closes the reply.
+ */
+function ChatRoomMessageReplyStop() {
+	const chatInput = /** @type {null | HTMLTextAreaElement} */(document.getElementById("InputChat"));
+	const replyIndicator = document.getElementById("chat-room-reply-indicator");
+	chatInput.removeAttribute("reply-id");
+	replyIndicator.classList.add("hidden");
+}
+
+/**
+ * Sets the reply to the message with the given ID
+ * @param {string} msgId
+ */
+function ChatRoomMessageSetReply(msgId) {
+	const chatInput = /** @type {null | HTMLTextAreaElement} */(document.getElementById("InputChat"));
+	chatInput.setAttribute("reply-id", msgId);
+	const replyMessage = ChatRoomMessageGetById(msgId);
+	const type = ChatRoomMessageGetType(msgId);
+	const isWhisper = type === "Whisper";
+	if (isWhisper) {
+		const receiver = Number(replyMessage.getAttribute("data-sender")) === Player.MemberNumber ? Number(replyMessage.getAttribute("data-target")) : Number(replyMessage.getAttribute("data-sender"));
+		chatInput.value =  `/whisper ${receiver} ${chatInput.value.replace(/\/whisper\s*\d+ ?/u, "")}`;
+	}
+	const replyIndicator = document.getElementById("chat-room-reply-indicator");
+	const replyIndicatorText = document.getElementById("chat-room-reply-indicator-text");
+	const replyName = ChatRoomMessageGetReplyName(msgId, isWhisper);
+	replyIndicatorText.textContent = `${TextGet("ChatRoomReply")}: ${replyName && `${replyName}` || "a message"}`;
+	replyIndicator.classList.remove("hidden");
+	chatInput.focus();
+}
+
+/**
+ * Creates the HTML element for a reply message
+ * @param {string} msgId
+ * @param {string} displayMessage
+ * @returns {HTMLSpanElement | string}
+ */
+function ChatRoomMessageCreateReplyMessageElement(msgId, displayMessage) {
+	if (!msgId) {
+		return displayMessage;
+	}
+	return ElementCreate({
+		tag: "span",
+		classList: ["chat-room-message-content"],
+		attributes:  { "tabindex": -1, "msgid": msgId },
+		children: [displayMessage],
+		eventListeners: {
+			click: (e) => {
+				ChatRoomMessageSetReply(msgId);
+				e.stopPropagation();
+			},
+		}
+	});
+}
 
 /**
  * Update the Chat log with the recieved message
@@ -4274,9 +4431,27 @@ function ChatRoomMessageDisplay(data, msg, SenderCharacter, metadata) {
 	const divChildren = [];
 	/** @type {undefined | string} */
 	let innerHTML = undefined;
+	let reply = undefined;
+	if (metadata.ReplyId) {
+		const replyMessage = ChatRoomMessageGetById(metadata.ReplyId);
+		const type = ChatRoomMessageGetType(metadata.ReplyId);
+		const isWhisper = type === "Whisper";
+		reply = ElementButton.Create(
+			null, () => {if (replyMessage) replyMessage.scrollIntoView();}, { noStyling: true },
+			{
+				button: {
+					classList: ["chat-room-message-reply", "truncated-text"],
+					attributes: { "tabindex": -1 },
+					style: { "--label-color": SenderCharacter.LabelColor },
+					children: replyMessage ? [ChatRoomMessageGetReplyName(metadata.ReplyId, isWhisper), ": ", ChatRoomMessageGetReplyContent(metadata.ReplyId)] : [TextGet("ChatRoomNoMessageFound")],
+				},
+			},
+		);
+	}
 	switch (data.Type) {
 		case "Chat":
 			divChildren.push(
+				reply,
 				ElementButton.Create(
 					null, ChatRoomMessageNameClick, { noStyling: true },
 					{
@@ -4289,12 +4464,13 @@ function ChatRoomMessageDisplay(data, msg, SenderCharacter, metadata) {
 					},
 				),
 				": ",
-				displayMessage,
+				ChatRoomMessageCreateReplyMessageElement(metadata.MsgId,displayMessage)
 			);
 			break;
 		case "Whisper": {
 			const whisperTarget = SenderCharacter.IsPlayer() ? ChatRoomCharacter.find(c => c.MemberNumber == data.Target) : SenderCharacter;
 			divChildren.push(
+				reply,
 				ElementButton.Create(
 					null, ChatRoomMessageNameClick, { noStyling: true },
 					{ button: { classList: ["ReplyButton"], children: ["\u21a9\ufe0f"] } },
@@ -4313,7 +4489,7 @@ function ChatRoomMessageDisplay(data, msg, SenderCharacter, metadata) {
 					},
 				),
 				": ",
-				displayMessage,
+				ChatRoomMessageCreateReplyMessageElement(metadata.MsgId,displayMessage)
 			);
 
 			if (!whisperTarget.IsPlayer()) {
@@ -4327,7 +4503,7 @@ function ChatRoomMessageDisplay(data, msg, SenderCharacter, metadata) {
 
 		case "Action":
 		case "Activity":
-			divChildren.push(`(${displayMessage})`);
+			divChildren.push(reply,ChatRoomMessageCreateReplyMessageElement(metadata.MsgId,`(${displayMessage})`));
 			break;
 
 		case "ServerMessage":
@@ -4340,7 +4516,7 @@ function ChatRoomMessageDisplay(data, msg, SenderCharacter, metadata) {
 			break;
 
 		case "Emote":
-			divChildren.push(`*${displayMessage}*`);
+			divChildren.push(reply,ChatRoomMessageCreateReplyMessageElement(metadata.MsgId,`*${displayMessage}*`));
 			break;
 
 		default:
diff --git a/BondageClub/Screens/Online/ChatRoom/Text_ChatRoom.csv b/BondageClub/Screens/Online/ChatRoom/Text_ChatRoom.csv
index 8b89733878..853521fef9 100644
--- a/BondageClub/Screens/Online/ChatRoom/Text_ChatRoom.csv
+++ b/BondageClub/Screens/Online/ChatRoom/Text_ChatRoom.csv
@@ -34,6 +34,8 @@ ChatRoomStruggleLoosening,Loosening...
 ChatRoomStruggleImpossible,Impossible!
 ChatRoomStruggleLoosen,Loosen
 ChatRoomStruggleGiveUp,Give up
+ChatRoomReply,Replying to
+ChatRoomNoMessageFound,Message could not be found.
 CommandHelp,<strong>Help: KeyWord</strong>
 CommandNoSuchCommand,command: no such command
 CommandPrerequisiteFailed,command: prerequisite check failed
diff --git a/BondageClub/Scripts/Common.js b/BondageClub/Scripts/Common.js
index 8ddb6f7e11..0f399ba462 100644
--- a/BondageClub/Scripts/Common.js
+++ b/BondageClub/Scripts/Common.js
@@ -604,6 +604,13 @@ function CommonStringShuffle(string) {
 	return parts.join('');
 }
 
+/**
+ * Generate a unique ID
+ * @returns {string}
+ */
+function CommonGenerateUniqueID() {
+	return Math.random().toString(36).substring(2);
+}
 /**
  * Converts an array to a string separated by commas (equivalent of .join(","))
  * @param {readonly any[]} Arr - Array to convert to a joined string
diff --git a/BondageClub/Scripts/DictionaryBuilder.js b/BondageClub/Scripts/DictionaryBuilder.js
index 9137b75312..5e3f19a197 100644
--- a/BondageClub/Scripts/DictionaryBuilder.js
+++ b/BondageClub/Scripts/DictionaryBuilder.js
@@ -439,3 +439,19 @@ function IsMessageEffectDictionaryEntry(entry) {
 		&& !!entry.Effects && CommonIsArray(entry.Effects)
 		&& (!entry.Original || typeof entry.Original === "string");
 }
+
+/**
+ * @param {ChatMessageDictionaryEntry} entry
+ * @returns {entry is ReplyIdDictionaryEntry}
+ */
+function IsReplyIdDictionaryEntry(entry) {
+	return CommonIsObject(entry) && typeof entry.ReplyId === "string";
+}
+
+/**
+ * @param {ChatMessageDictionaryEntry} entry
+ * @returns {entry is MsgIdDictionaryEntry}
+ */
+function IsMsgIdDictionaryEntry(entry) {
+	return CommonIsObject(entry) && typeof entry.MsgId === "string";
+}
diff --git a/BondageClub/Scripts/Messages.d.ts b/BondageClub/Scripts/Messages.d.ts
index 5e0146b514..2832b1cfcd 100644
--- a/BondageClub/Scripts/Messages.d.ts
+++ b/BondageClub/Scripts/Messages.d.ts
@@ -750,6 +750,20 @@ interface ActivityNameDictionaryEntry {
 	ActivityName: ActivityName;
 }
 
+/**
+ * A dictionary entry indicating the ID of a message being replied to.
+ */
+interface ReplyIdDictionaryEntry {
+	ReplyId: string;
+	Tag: "ReplyId";
+}
+/**
+ * A dictionary entry indicating the ID of a message.
+ */
+interface MsgIdDictionaryEntry {
+	MsgId: string;
+	Tag: "MsgId";
+}
 /**
  * A dictionary entry with metadata about the chat message transmitted.
  *
@@ -777,7 +791,10 @@ type ChatMessageDictionaryEntry =
 	| ActivityCounterDictionaryEntry
 	| AssetGroupNameDictionaryEntry
 	| ActivityNameDictionaryEntry
-	| MessageEffectEntry;
+	| MessageEffectEntry
+	| MsgIdDictionaryEntry
+	| ReplyIdDictionaryEntry ;
+
 
 type ChatMessageDictionary = ChatMessageDictionaryEntry[];
 
diff --git a/BondageClub/Scripts/Server.js b/BondageClub/Scripts/Server.js
index 53d51f9937..7b38c66cf4 100644
--- a/BondageClub/Scripts/Server.js
+++ b/BondageClub/Scripts/Server.js
@@ -285,8 +285,6 @@ var ServerSendRateLimitInterval = 1200;
 /**
  * Queued messages waiting to be sent
  *
- * @typedef {{ Message: ClientEvent, args: ClientEventParams<ClientEvent>}} SendRateLimitQueueItem
- *
  * @type {SendRateLimitQueueItem[]}
  */
 const ServerSendRateLimitQueue = [];
@@ -302,8 +300,8 @@ let ServerSendRateLimitTimes = [];
  */
 function ServerSend(Message, ...args) {
 	if (!ServerIsLoggedIn() && !["AccountCreate", "AccountLogin", "PasswordReset", "PasswordResetProcess"].includes(Message)) return; // We're not logged in
-
-	ServerSendRateLimitQueue.push({ Message, args });
+	const queueItem = /** @type {SendRateLimitQueueItem} */({ Message, args });
+	ServerSendRateLimitQueue.push(queueItem);
 
 	// Pump the queue manually to fight back against background tab throttling
 	ServerSendQueueProcess();
@@ -323,6 +321,11 @@ function ServerSendQueueProcess() {
 	) {
 		const item = ServerSendRateLimitQueue.shift();
 		if (item) {
+			if (item.Message === "ChatRoomChat") {
+				const [data] = item.args;
+				data[0].Dictionary = data[0].Dictionary ?? [];
+				data[0].Dictionary.push({ Tag: "MsgId", MsgId: CommonGenerateUniqueID() });
+			}
 			ServerSocket.emit(item.Message, ...item.args);
 			ServerSendRateLimitTimes.push(Date.now());
 		}
diff --git a/BondageClub/Scripts/Typedef.d.ts b/BondageClub/Scripts/Typedef.d.ts
index 311a12d784..524a759954 100644
--- a/BondageClub/Scripts/Typedef.d.ts
+++ b/BondageClub/Scripts/Typedef.d.ts
@@ -8,7 +8,8 @@ declare function io(serv: string): SocketIO.Socket;
 
 type ClientEvent = import("@socket.io/component-emitter").EventNames<ClientToServerEvents>;
 type ClientEventParams<Ev extends ClientEvent> = import("@socket.io/component-emitter").EventParams<ClientToServerEvents, Ev>;
-
+type _SendRateLimitQueueItem<T extends ClientEvent> = T extends ClientEvent ? { Message: T, args: ClientEventParams<T>} : never;
+type SendRateLimitQueueItem = _SendRateLimitQueueItem<ClientEvent>;
 interface String {
 	replaceAt(index: number, character: string): string;
 }
@@ -810,6 +811,12 @@ interface IChatRoomMessageMetadata {
 	ChatRoomName?: string;
 	/** The original, ungarbled message, if provided by the sender */
 	OriginalMsg?: string;
+	/** The ID of the message being replied to */
+	ReplyId?: string;
+	/** The ID of the message */
+	MsgId?: string;
+
+
 }
 
 /**