diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index e71561defcdb..72a7cdc5ed02 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -104,3 +104,4 @@
 
 //Used in visible_message_flags, audible_message_flags and runechat_flags
 #define EMOTE_MESSAGE (1<<0)
+#define LOOC_MESSAGE (1<<1) //monke: looc
diff --git a/code/__DEFINES/speech_channels.dm b/code/__DEFINES/speech_channels.dm
index d17b6b8f9a84..65ab0d51fe68 100644
--- a/code/__DEFINES/speech_channels.dm
+++ b/code/__DEFINES/speech_channels.dm
@@ -5,3 +5,4 @@
 #define OOC_CHANNEL "OOC"
 #define ADMIN_CHANNEL "Admin"
 #define MENTOR_CHANNEL "Mentor"
+#define LOOC_CHANNEL "LOOC" // monkestation edit: add LOOC
diff --git a/code/__DEFINES/~monkestation/chat.dm b/code/__DEFINES/~monkestation/chat.dm
new file mode 100644
index 000000000000..1b1a4abda2d8
--- /dev/null
+++ b/code/__DEFINES/~monkestation/chat.dm
@@ -0,0 +1 @@
+#define MESSAGE_TYPE_LOOC "looc"
diff --git a/code/__DEFINES/~monkestation/keybinding.dm b/code/__DEFINES/~monkestation/keybinding.dm
new file mode 100644
index 000000000000..b074c433ef49
--- /dev/null
+++ b/code/__DEFINES/~monkestation/keybinding.dm
@@ -0,0 +1 @@
+#define COMSIG_KB_CLIENT_LOOC_DOWN "keybinding_client_looc_down"
diff --git a/code/__DEFINES/~monkestation/logging.dm b/code/__DEFINES/~monkestation/logging.dm
new file mode 100644
index 000000000000..af3bff1232d8
--- /dev/null
+++ b/code/__DEFINES/~monkestation/logging.dm
@@ -0,0 +1 @@
+#define LOG_CATEGORY_GAME_LOOC "game-looc"
diff --git a/code/__DEFINES/~monkestation/span.dm b/code/__DEFINES/~monkestation/span.dm
index 11666ba5c1e9..4da802b4f72a 100644
--- a/code/__DEFINES/~monkestation/span.dm
+++ b/code/__DEFINES/~monkestation/span.dm
@@ -5,5 +5,4 @@
 #define span_clockred(str) ("<span class='clockred'>" + str + "</span>")
 #define span_ratvar(str) ("<span class='ratvar'>" + str + "</span>")
 
-
 #define REQUEST_MENTORHELP "request_mentorhelp"
diff --git a/code/controllers/configuration/entries/monkestation.dm b/code/controllers/configuration/entries/monkestation.dm
index 9df2b8d34dca..9410ab93edbd 100644
--- a/code/controllers/configuration/entries/monkestation.dm
+++ b/code/controllers/configuration/entries/monkestation.dm
@@ -26,3 +26,5 @@
 //Endpoint for Github Issues, the `owner/repo` part.
 /datum/config_entry/string/issue_slug
 	protection = CONFIG_ENTRY_LOCKED
+
+/datum/config_entry/flag/looc_enabled
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index 3d9123a0c317..7459e196a6b5 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -299,8 +299,10 @@
 		return
 
 	// Display visual above source
-	if(runechat_flags & EMOTE_MESSAGE)
+	if(CHECK_BITFIELD(runechat_flags, EMOTE_MESSAGE))
 		new /datum/chatmessage(raw_message, speaker, src, message_language, list("emote", "italics"))
+	else if(CHECK_BITFIELD(runechat_flags, LOOC_MESSAGE))
+		new /datum/chatmessage(raw_message, speaker, src, message_language, list("looc", "italics"))
 	else
 		new /datum/chatmessage(raw_message, speaker, src, message_language, spans)
 
diff --git a/code/datums/keybinding/living.dm b/code/datums/keybinding/living.dm
index 38e324e9769c..9fa539c0aa1d 100644
--- a/code/datums/keybinding/living.dm
+++ b/code/datums/keybinding/living.dm
@@ -61,7 +61,7 @@
 	return TRUE
 
 /datum/keybinding/living/rest
-	hotkey_keys = list("U")
+	hotkey_keys = list("R") // monke: move this, so LOOC can be U, adjacent to other communication keys.
 	name = "rest"
 	full_name = "Rest"
 	description = "Lay down, or get up."
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index beed817d2ffe..745402dc9a3d 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -42,6 +42,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
 	/datum/admins/proc/toggleguests, /*toggles whether guests can join the current game*/
 	/datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/
 	/datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/
+	/datum/admins/proc/togglelooc, /*MONKESTATION EDIT; toggles looc on/off for everyone*/
 	/datum/admins/proc/trophy_manager,
 	/datum/admins/proc/view_all_circuits,
 	/datum/admins/proc/open_artifactpanel,
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index bc55615573b2..4c0c0e11a297 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -1133,6 +1133,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
 				if(OOC_CHANNEL)
 					var/ooc = tgui_say_create_open_command(OOC_CHANNEL)
 					winset(src, "default-[REF(key)]", "parent=default;name=[key];command=[ooc]")
+				if(LOOC_CHANNEL) // monke edit: looc
+					var/looc = tgui_say_create_open_command(LOOC_CHANNEL)
+					winset(src, "default-[REF(key)]", "parent=default;name=[key];command=[looc]")
 				if(ADMIN_CHANNEL)
 					if(holder)
 						var/asay = tgui_say_create_open_command(ADMIN_CHANNEL)
diff --git a/code/modules/tgui_input/say_modal/modal.dm b/code/modules/tgui_input/say_modal/modal.dm
index 9ff47aae3fe1..444d67812ae1 100644
--- a/code/modules/tgui_input/say_modal/modal.dm
+++ b/code/modules/tgui_input/say_modal/modal.dm
@@ -82,7 +82,7 @@
 	if(!payload?["channel"])
 		CRASH("No channel provided to an open TGUI-Say")
 	window_open = TRUE
-	if(payload["channel"] != OOC_CHANNEL && (payload["channel"] != ADMIN_CHANNEL) && (payload["channel"] != MENTOR_CHANNEL))
+	if(payload["channel"] != OOC_CHANNEL && payload["channel"] != LOOC_CHANNEL && (payload["channel"] != ADMIN_CHANNEL) && (payload["channel"] != MENTOR_CHANNEL)) // monke: add LOOC
 		start_thinking()
 	if(client.typing_indicators)
 		log_speech_indicators("[key_name(client)] started typing at [loc_name(client.mob)], indicators enabled.")
diff --git a/code/modules/tgui_input/say_modal/speech.dm b/code/modules/tgui_input/say_modal/speech.dm
index 4601ebdaabd9..247f29bac59d 100644
--- a/code/modules/tgui_input/say_modal/speech.dm
+++ b/code/modules/tgui_input/say_modal/speech.dm
@@ -44,6 +44,9 @@
 		if(OOC_CHANNEL)
 			client.ooc(entry)
 			return TRUE
+		if(LOOC_CHANNEL)
+			client.looc(entry)
+			return TRUE
 		if(ADMIN_CHANNEL)
 			client.cmd_admin_say(entry)
 			return TRUE
@@ -91,7 +94,7 @@
 		return TRUE
 	if(type == "force")
 		var/target_channel = payload["channel"]
-		if(target_channel == ME_CHANNEL || target_channel == OOC_CHANNEL)
+		if(target_channel == ME_CHANNEL || target_channel == OOC_CHANNEL || target_channel == LOOC_CHANNEL) // monkestation: add looc
 			target_channel = SAY_CHANNEL // No ooc leaks
 		delegate_speech(alter_entry(payload), target_channel)
 		return TRUE
diff --git a/config/game_options.txt b/config/game_options.txt
index 68a8f1f1d132..b336b336d268 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -15,6 +15,10 @@ REVIVAL_BRAIN_LIFE -1
 ## Comment this out if you want OOC to be automatically disabled during the round, it will be enabled during the lobby and after the round end results.
 OOC_DURING_ROUND
 
+## LOOC
+## Comment this out to disable LOOC
+LOOC_ENABLED
+
 ## EMOJI ###
 ## Comment this out if you want to disable emojis
 EMOJIS
diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm
index faa70d81cee9..b85562b90d87 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -27,6 +27,7 @@ em						{font-style: normal;	font-weight: bold;}
 .oocplain				{}
 .warningplain			{}
 .ooc					{					font-weight: bold;}
+.looc					{					font-weight: bold;}
 .adminobserverooc		{color: #0099cc;	font-weight: bold;}
 .adminooc				{color: #700038;	font-weight: bold;}
 
diff --git a/monkestation/code/datums/keybinding/communication.dm b/monkestation/code/datums/keybinding/communication.dm
new file mode 100644
index 000000000000..502ddd406349
--- /dev/null
+++ b/monkestation/code/datums/keybinding/communication.dm
@@ -0,0 +1,5 @@
+/datum/keybinding/client/communication/looc
+	hotkey_keys = list("U")
+	name = LOOC_CHANNEL
+	full_name = "Local Out Of Character Say (LOOC)"
+	keybind_signal = COMSIG_KB_CLIENT_LOOC_DOWN
diff --git a/monkestation/code/modules/client/preferences/admin.dm b/monkestation/code/modules/client/preferences/admin.dm
new file mode 100644
index 000000000000..75d9cff3d9ff
--- /dev/null
+++ b/monkestation/code/modules/client/preferences/admin.dm
@@ -0,0 +1,13 @@
+/datum/preference/choiced/admin_hear_looc
+	category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+	savefile_key = "admin_hear_looc"
+	savefile_identifier = PREFERENCE_PLAYER
+
+/datum/preference/choiced/admin_hear_looc/init_possible_values()
+	return list("Always", "When Observing", "Never")
+
+/datum/preference/choiced/admin_hear_looc/create_default_value()
+	return "Always"
+
+/datum/preference/choiced/admin_hear_looc/is_accessible(datum/preferences/preferences)
+	return ..() && is_admin(preferences.parent) && CONFIG_GET(flag/looc_enabled)
diff --git a/monkestation/code/modules/client/preferences/runechat.dm b/monkestation/code/modules/client/preferences/runechat.dm
new file mode 100644
index 000000000000..e3d618a22589
--- /dev/null
+++ b/monkestation/code/modules/client/preferences/runechat.dm
@@ -0,0 +1,7 @@
+/datum/preference/toggle/enable_runechat_looc
+	category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+	savefile_key = "see_looc_on_map"
+	savefile_identifier = PREFERENCE_PLAYER
+
+/datum/preference/toggle/enable_runechat_looc/is_accessible(datum/preferences/preferences)
+	return ..() && CONFIG_GET(flag/looc_enabled)
diff --git a/monkestation/code/modules/client/verbs/looc.dm b/monkestation/code/modules/client/verbs/looc.dm
new file mode 100644
index 000000000000..255d1af8d365
--- /dev/null
+++ b/monkestation/code/modules/client/verbs/looc.dm
@@ -0,0 +1,131 @@
+// LOOC ported from Bee, which was in turn ported from Citadel
+
+GLOBAL_VAR_INIT(looc_allowed, TRUE)
+
+/client/verb/looc(msg as text)
+	set name = "LOOC"
+	set desc = "Local OOC, seen only by those in view."
+	set category = "OOC"
+
+	if(GLOB.say_disabled)    //This is here to try to identify lag problems
+		to_chat(usr, span_danger("Speech is currently admin-disabled."))
+		return
+
+	if(!mob)
+		return
+
+	VALIDATE_CLIENT(src)
+
+	if(is_banned_from(mob.ckey, "OOC"))
+		to_chat(src, "<span class='danger'>You have been banned from OOC and LOOC.</span>")
+		return
+	if(!CHECK_BITFIELD(prefs.chat_toggles, CHAT_OOC))
+		to_chat(src, span_danger("You have OOC (and therefore LOOC) muted."))
+		return
+
+	msg = trim(sanitize(msg), MAX_MESSAGE_LEN)
+	if(!length(msg))
+		return
+
+	var/raw_msg = msg
+
+	var/list/filter_result = is_ooc_filtered(msg)
+	if (!CAN_BYPASS_FILTER(usr) && filter_result)
+		REPORT_CHAT_FILTER_TO_USER(usr, filter_result)
+		log_filter("LOOC", msg, filter_result)
+		return
+
+	// Protect filter bypassers from themselves.
+	// Demote hard filter results to soft filter results if necessary due to the danger of accidentally speaking in OOC.
+	var/list/soft_filter_result = filter_result || is_soft_ooc_filtered(msg)
+
+	if (soft_filter_result)
+		if(tgui_alert(usr, "Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to say it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+			return
+		message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[msg]\"")
+		log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[msg]\"")
+
+	// letting mentors use this as they might actually use this to help people. this cannot possibly go wrong! :clueless:
+	if(!holder)
+		if(!CONFIG_GET(flag/looc_enabled))
+			to_chat(src, span_danger("LOOC is disabled."))
+			return
+		if(!GLOB.dooc_allowed && (mob.stat == DEAD) && SSticker.current_state < GAME_STATE_FINISHED && !mentor_datum)
+			to_chat(usr, span_danger("LOOC for dead mobs has been turned off."))
+			return
+		if(CHECK_BITFIELD(prefs.muted, MUTE_OOC))
+			to_chat(src, span_danger("You cannot use LOOC (muted)."))
+			return
+		if(handle_spam_prevention(msg, MUTE_OOC))
+			return
+		if(findtext(msg, "byond://"))
+			to_chat(src, span_danger("Advertising other servers is not allowed."))
+			log_admin("[key_name(src)] has attempted to advertise in LOOC: [msg]")
+			return
+		if(mob.stat && SSticker.current_state < GAME_STATE_FINISHED && !mentor_datum)
+			to_chat(src, span_danger("You cannot salt in LOOC while unconscious or dead."))
+			return
+		if(isdead(mob) && SSticker.current_state < GAME_STATE_FINISHED && !mentor_datum)
+			to_chat(src, span_danger("You cannot use LOOC while ghosting."))
+			return
+	if(is_banned_from(ckey, "OOC"))
+		to_chat(src, span_danger("You have been banned from OOC."))
+		return
+	if(QDELETED(src))
+		return
+
+	msg = emoji_parse(msg)
+	mob.log_talk(raw_msg, LOG_OOC, tag = "LOOC")
+
+	var/list/hearers = list()
+	for(var/mob/hearer in get_hearers_in_view(9, mob))
+		var/client/client = hearer.client
+		if(QDELETED(client) || !CHECK_BITFIELD(client.prefs.chat_toggles, CHAT_OOC))
+			continue
+		hearers[client] = TRUE
+		if((client in GLOB.admins) && is_admin_looc_omnipotent(client))
+			continue
+		to_chat(hearer, span_looc("[span_prefix("LOOC:")] <EM>[span_name("[mob.name]")]:</EM> <span class='message linkify'>[msg]</span>"), type = MESSAGE_TYPE_LOOC, avoid_highlighting = (hearer == mob))
+		if(client.prefs.read_preference(/datum/preference/toggle/enable_runechat_looc))
+			hearer.create_chat_message(mob, /datum/language/common, "\[LOOC: [raw_msg]\]", runechat_flags = LOOC_MESSAGE)
+
+	for(var/client/client in GLOB.admins)
+		if(!CHECK_BITFIELD(client.prefs.chat_toggles, CHAT_OOC) || !is_admin_looc_omnipotent(client))
+			continue
+		var/prefix = "[hearers[client] ? "" : "(R)"]LOOC"
+		if(client.prefs.read_preference(/datum/preference/toggle/enable_runechat_looc))
+			client.mob?.create_chat_message(mob, /datum/language/common, "\[LOOC: [raw_msg]\]", runechat_flags = LOOC_MESSAGE)
+		to_chat(client, span_looc("[span_prefix("[prefix]:")] <EM>[ADMIN_LOOKUPFLW(mob)]:</EM> <span class='message linkify'>[msg]</span>"), type = MESSAGE_TYPE_LOOC, avoid_highlighting = (client == src))
+
+/// Logging for messages sent in LOOC
+/proc/log_looc(text, list/data)
+	logger.Log(LOG_CATEGORY_GAME_LOOC, text, data)
+
+//admin tool
+/proc/toggle_looc(toggle = null)
+	if(!isnull(toggle)) //if we're specifically en/disabling ooc
+		GLOB.looc_allowed = toggle
+	else //otherwise just toggle it
+		GLOB.looc_allowed = !GLOB.looc_allowed
+	to_chat(world, "<span class='oocplain bold'>LOOC channel has been globally [GLOB.looc_allowed ? "enabled" : "disabled"].</span>")
+
+/datum/admins/proc/togglelooc()
+	set category = "Server"
+	set name = "Toggle LOOC"
+	if(!check_rights(R_ADMIN))
+		return
+	toggle_looc()
+	log_admin("[key_name(usr)] toggled LOOC.")
+	message_admins("[key_name_admin(usr)] toggled LOOC.")
+	SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle LOOC", "[GLOB.looc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
+/proc/is_admin_looc_omnipotent(client/admin)
+	if(QDELETED(admin))
+		return FALSE
+	switch(admin.prefs.read_preference(/datum/preference/choiced/admin_hear_looc))
+		if("Always")
+			return TRUE
+		if("When Observing")
+			return isdead(admin.mob) || admin.mob.stat == DEAD
+		else
+			return FALSE
diff --git a/monkestation/code/modules/logging/categories/log_category_game.dm b/monkestation/code/modules/logging/categories/log_category_game.dm
new file mode 100644
index 000000000000..2e476a846204
--- /dev/null
+++ b/monkestation/code/modules/logging/categories/log_category_game.dm
@@ -0,0 +1,4 @@
+/datum/log_category/game_looc
+	category = LOG_CATEGORY_GAME_LOOC
+	config_flag = /datum/config_entry/flag/log_ooc
+	master_category = /datum/log_category/game
diff --git a/tgstation.dme b/tgstation.dme
index c0a7690447a5..2c9087587f41 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -385,6 +385,7 @@
 #include "code\__DEFINES\~monkestation\antagonists.dm"
 #include "code\__DEFINES\~monkestation\artifact.dm"
 #include "code\__DEFINES\~monkestation\asteroids.dm"
+#include "code\__DEFINES\~monkestation\chat.dm"
 #include "code\__DEFINES\~monkestation\clock_cult.dm"
 #include "code\__DEFINES\~monkestation\colors.dm"
 #include "code\__DEFINES\~monkestation\combat.dm"
@@ -393,7 +394,9 @@
 #include "code\__DEFINES\~monkestation\DNA.dm"
 #include "code\__DEFINES\~monkestation\factions.dm"
 #include "code\__DEFINES\~monkestation\interaction_particles.dm"
+#include "code\__DEFINES\~monkestation\keybinding.dm"
 #include "code\__DEFINES\~monkestation\level_traits.dm"
+#include "code\__DEFINES\~monkestation\logging.dm"
 #include "code\__DEFINES\~monkestation\maps.dm"
 #include "code\__DEFINES\~monkestation\mecha.dm"
 #include "code\__DEFINES\~monkestation\misc.dm"
@@ -5643,6 +5646,7 @@
 #include "monkestation\code\datums\diseases\advance\symptoms\clockwork.dm"
 #include "monkestation\code\datums\elements\area_locked.dm"
 #include "monkestation\code\datums\keybinding\carbon.dm"
+#include "monkestation\code\datums\keybinding\communication.dm"
 #include "monkestation\code\datums\keybinding\living.dm"
 #include "monkestation\code\datums\quirks\negative_quirks.dm"
 #include "monkestation\code\datums\quirks\neutral_quirks.dm"
@@ -6040,6 +6044,7 @@
 #include "monkestation\code\modules\client\preference_savefile.dm"
 #include "monkestation\code\modules\client\preferences.dm"
 #include "monkestation\code\modules\client\verbs.dm"
+#include "monkestation\code\modules\client\preferences\admin.dm"
 #include "monkestation\code\modules\client\preferences\anime_implant.dm"
 #include "monkestation\code\modules\client\preferences\bloom.dm"
 #include "monkestation\code\modules\client\preferences\context_menu_requires_shift.dm"
@@ -6047,6 +6052,7 @@
 #include "monkestation\code\modules\client\preferences\interaction_mode.dm"
 #include "monkestation\code\modules\client\preferences\inventory.dm"
 #include "monkestation\code\modules\client\preferences\loadout_override_preference.dm"
+#include "monkestation\code\modules\client\preferences\runechat.dm"
 #include "monkestation\code\modules\client\preferences\sounds.dm"
 #include "monkestation\code\modules\client\preferences\alt_jobs\_job.dm"
 #include "monkestation\code\modules\client\preferences\alt_jobs\titles.dm"
@@ -6056,6 +6062,7 @@
 #include "monkestation\code\modules\client\preferences\species_features\ipc.dm"
 #include "monkestation\code\modules\client\preferences\species_features\secondary_mut_color.dm"
 #include "monkestation\code\modules\client\preferences\species_features\simians.dm"
+#include "monkestation\code\modules\client\verbs\looc.dm"
 #include "monkestation\code\modules\clothing\accessories\accessories.dm"
 #include "monkestation\code\modules\clothing\costumes\gnome.dm"
 #include "monkestation\code\modules\clothing\gloves\gloves.dm"
@@ -6211,6 +6218,7 @@
 #include "monkestation\code\modules\loadouts\items\under\under.dm"
 #include "monkestation\code\modules\loafing\code\loaf.dm"
 #include "monkestation\code\modules\loafing\code\loafer.dm"
+#include "monkestation\code\modules\logging\categories\log_category_game.dm"
 #include "monkestation\code\modules\mapping\access_helpers.dm"
 #include "monkestation\code\modules\mapping\mapping_helpers.dm"
 #include "monkestation\code\modules\maptext\maptext_image_helper.dm"
diff --git a/tgui/packages/tgui-panel/chat/constants.js b/tgui/packages/tgui-panel/chat/constants.js
index fa9bb50b41b3..353e81702e80 100644
--- a/tgui/packages/tgui-panel/chat/constants.js
+++ b/tgui/packages/tgui-panel/chat/constants.js
@@ -28,6 +28,7 @@ export const MESSAGE_TYPE_INFO = 'info';
 export const MESSAGE_TYPE_WARNING = 'warning';
 export const MESSAGE_TYPE_DEADCHAT = 'deadchat';
 export const MESSAGE_TYPE_OOC = 'ooc';
+export const MESSAGE_TYPE_LOOC = 'looc'; // monkestation edit: looc
 export const MESSAGE_TYPE_ADMINPM = 'adminpm';
 export const MESSAGE_TYPE_COMBAT = 'combat';
 export const MESSAGE_TYPE_ADMINCHAT = 'adminchat';
@@ -89,6 +90,12 @@ export const MESSAGE_TYPES = [
     description: 'The bluewall of global OOC messages',
     selector: '.ooc, .adminooc, .adminobserverooc, .oocplain',
   },
+  {
+    type: MESSAGE_TYPE_LOOC,
+    name: 'LOOC',
+    description: 'Local Out Of Character',
+    selector: '.looc',
+  },
   {
     type: MESSAGE_TYPE_ADMINPM,
     name: 'Admin PMs',
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
index c9ef4e4c0932..8018e2e1374e 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
@@ -1184,3 +1184,8 @@ $border-width-px: $border-width * 1px;
   color: #ff70c1;
   font-family: 'Comic Sans MS', cursive, sans-serif;
 }
+
+.looc {
+  color: #ffde5c;
+  font-weight: bold;
+}
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
index a48a709ca3e0..63d2cc270b65 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
@@ -1221,3 +1221,8 @@ $border-width-px: $border-width * 1px;
   color: #ff69bf;
   font-family: 'Comic Sans MS', cursive, sans-serif;
 }
+
+.looc {
+  color: #2e57d1;
+  font-weight: bold;
+}
diff --git a/tgui/packages/tgui-say/constants/index.tsx b/tgui/packages/tgui-say/constants/index.tsx
index e987408421d8..778aa2f11692 100644
--- a/tgui/packages/tgui-say/constants/index.tsx
+++ b/tgui/packages/tgui-say/constants/index.tsx
@@ -4,6 +4,7 @@ export const CHANNELS = [
   'Radio',
   'Me',
   'OOC',
+  'LOOC', // monke: looc
   'Admin',
   'Mentor',
 ] as const;
diff --git a/tgui/packages/tgui-say/styles/colors.scss b/tgui/packages/tgui-say/styles/colors.scss
index b25da4dfa830..b1c733d9c9ae 100644
--- a/tgui/packages/tgui-say/styles/colors.scss
+++ b/tgui/packages/tgui-say/styles/colors.scss
@@ -13,6 +13,7 @@ $say: #a4bad6;
 $radio: #1ecc43;
 $me: #5975da;
 $ooc: #cca300;
+$looc: #fafa3b; // monke: looc
 
 ////////////////////////////////////////////////
 // Subchannel chat colors
@@ -36,6 +37,7 @@ $_channel_map: (
   'radio': $radio,
   'me': $me,
   'ooc': $ooc,
+  'looc': $looc,
   'ai': $ai,
   'admin': $admin,
   'mentor': $mentor,
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/monkestation/looc.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/monkestation/looc.tsx
new file mode 100644
index 000000000000..bc6e5a8607a0
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/monkestation/looc.tsx
@@ -0,0 +1,15 @@
+import { CheckboxInput, FeatureToggle, FeatureChoiced, FeatureDropdownInput } from '../../base';
+
+export const see_looc_on_map: FeatureToggle = {
+  name: 'Enable LOOC Runechat',
+  category: 'RUNECHAT',
+  description: 'LOOC messages will show above heads.',
+  component: CheckboxInput,
+};
+
+export const admin_hear_looc: FeatureChoiced = {
+  name: 'LOOC Omnipotence',
+  category: 'ADMIN',
+  description: 'When to show non-local LOOC messages.',
+  component: FeatureDropdownInput,
+};