diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index cb2ff740a55..136f42a77eb 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -1393,3 +1393,11 @@
#define COMSIG_VACUUM_BAG_ATTACH "comsig_vacuum_bag_attach"
/// Called on a bag being detached from a vacuum parent
#define COMSIG_VACUUM_BAG_DETACH "comsig_vacuum_bag_detach"
+
+// Organ signals
+/// Called on the organ when it is implanted into someone (mob/living/carbon/receiver)
+#define COMSIG_ORGAN_IMPLANTED "comsig_organ_implanted"
+
+/// Called on the organ when it is removed from someone (mob/living/carbon/old_owner)
+#define COMSIG_ORGAN_REMOVED "comsig_organ_removed"
+
diff --git a/code/__DEFINES/wiremod.dm b/code/__DEFINES/wiremod.dm
index 807270544cc..07278ad0f7d 100644
--- a/code/__DEFINES/wiremod.dm
+++ b/code/__DEFINES/wiremod.dm
@@ -138,3 +138,5 @@
#define CIRCUIT_FLAG_INPUT_SIGNAL (1<<0)
/// Creates an output trigger that sends a pulse whenever the component is successfully triggered
#define CIRCUIT_FLAG_OUTPUT_SIGNAL (1<<1)
+/// This circuit component does not show in the menu.
+#define CIRCUIT_FLAG_HIDDEN (1<<2)
diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm
index b7734cfd4c5..a8200913a3d 100644
--- a/code/modules/research/designs/wiremod_designs.dm
+++ b/code/modules/research/designs/wiremod_designs.dm
@@ -321,3 +321,23 @@
build_path = /obj/item/shell/airlock
build_type = PROTOLATHE | COMPONENT_PRINTER
category = list("Circuitry", "Shells")
+
+/datum/design/bci_shell
+ name = "Brain-Computer Interface Shell"
+ desc = "An implant that can be placed in a user's head to control circuits using their brain."
+ id = "bci_shell"
+ materials = list(
+ /datum/material/glass = 2000,
+ /datum/material/iron = 8000,
+ )
+ build_path = /obj/item/shell/bci
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ category = list("Circuitry", "Shells")
+
+/datum/design/board/bci_implanter
+ name = "Brain-Computer Interface Manipulation Chamber"
+ desc = "A machine that, when given a brain-computer interface, will implant it into an occupant. Otherwise, will remove any brain-computer interfaces they already have."
+ id = "bci_implanter"
+ build_path = /obj/item/circuitboard/machine/bci_implanter
+ build_type = IMPRINTER | COMPONENT_PRINTER
+ category = list("Circuitry", "Core")
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 6905d92ebe7..da16d8e1d80 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -656,6 +656,8 @@
description = "Grants access to more complicated shell designs."
prereq_ids = list("basic_circuitry", "engineering")
design_ids = list(
+ "bci_implanter",
+ "bci_shell",
"bot_shell",
"door_shell",
"controller_shell",
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index 0441a7ba465..cf251c74d67 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -61,6 +61,7 @@
else
qdel(replaced)
+ SEND_SIGNAL(src, COMSIG_ORGAN_IMPLANTED, reciever)
SEND_SIGNAL(reciever, COMSIG_CARBON_GAIN_ORGAN, src, special)
owner = reciever
@@ -92,6 +93,7 @@
for(var/datum/action/action as anything in actions)
action.Remove(organ_owner)
+ SEND_SIGNAL(src, COMSIG_ORGAN_REMOVED, organ_owner)
SEND_SIGNAL(organ_owner, COMSIG_CARBON_LOSE_ORGAN, src, special)
START_PROCESSING(SSobj, src)
diff --git a/code/modules/wiremod/integrated_circuit.dm b/code/modules/wiremod/integrated_circuit.dm
index b1415f682b7..b203be6411e 100644
--- a/code/modules/wiremod/integrated_circuit.dm
+++ b/code/modules/wiremod/integrated_circuit.dm
@@ -224,6 +224,10 @@
. = list()
.["components"] = list()
for(var/obj/item/circuit_component/component as anything in attached_components)
+ if (component.circuit_flags & CIRCUIT_FLAG_HIDDEN)
+ .["components"] += null
+ continue
+
var/list/component_data = list()
component_data["input_ports"] = list()
for(var/datum/port/input/port as anything in component.input_ports)
diff --git a/code/modules/wiremod/shell/brain_computer_interface.dm b/code/modules/wiremod/shell/brain_computer_interface.dm
new file mode 100644
index 00000000000..a97b6dad692
--- /dev/null
+++ b/code/modules/wiremod/shell/brain_computer_interface.dm
@@ -0,0 +1,545 @@
+/obj/item/organ/cyberimp/bci
+ name = "brain-computer interface"
+ desc = "An implant that can be placed in a user's head to control circuits using their brain."
+ icon = 'icons/obj/wiremod.dmi'
+ icon_state = "bci"
+ zone = BODY_ZONE_HEAD
+ w_class = WEIGHT_CLASS_TINY
+
+/obj/item/organ/cyberimp/bci/Initialize()
+ . = ..()
+
+ AddComponent(/datum/component/shell, list(
+ new /obj/item/circuit_component/bci_core,
+ new /obj/item/circuit_component/bci_action("One"),
+ new /obj/item/circuit_component/bci_action("Two"),
+ new /obj/item/circuit_component/bci_action("Three"),
+ ), SHELL_CAPACITY_SMALL)
+
+/obj/item/organ/cyberimp/bci/Insert(mob/living/carbon/reciever, special, drop_if_replaced)
+ . = ..()
+
+ // Organs are put in nullspace, but this breaks circuit interactions
+ forceMove(reciever)
+
+/obj/item/organ/cyberimp/bci/say(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced)
+ if (owner)
+ // Otherwise say_dead will be called.
+ // It's intentional that a circuit for a dead person does not speak from the shell.
+ if (owner.stat == DEAD)
+ return
+
+ owner.say(message, forced = "circuit speech")
+ else
+ return ..()
+
+/obj/item/circuit_component/bci
+ display_name = "Brain-Computer Interface"
+ display_desc = "Used to receive inputs for the brain-computer interface. User is presented with three buttons."
+
+/obj/item/circuit_component/bci_action
+ display_name = "BCI Action"
+ display_desc = "Represents an action the user can take when implanted with the brain-computer interface."
+
+ /// The name to use for the button
+ var/datum/port/input/button_name
+
+ /// Called when the user presses the button
+ var/datum/port/output/signal
+
+ /// A reference to the action button itself
+ var/datum/action/innate/bci_action/bci_action
+
+/obj/item/circuit_component/bci_action/Initialize(mapload, default_icon)
+ . = ..()
+
+ if (!isnull(default_icon))
+ set_option(default_icon)
+
+ button_name = add_input_port("Name", PORT_TYPE_STRING)
+
+ signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/bci_action/Destroy()
+ button_name = null
+ signal = null
+
+ QDEL_NULL(bci_action)
+
+ return ..()
+
+/obj/item/circuit_component/bci_action/populate_options()
+ var/static/action_options = list(
+ "Blank",
+
+ "One",
+ "Two",
+ "Three",
+ "Four",
+ "Five",
+
+ "Blood",
+ "Bomb",
+ "Brain",
+ "Brain Damage",
+ "Cross",
+ "Electricity",
+ "Exclamation",
+ "Heart",
+ "Id",
+ "Info",
+ "Injection",
+ "Magnetism",
+ "Minus",
+ "Network",
+ "Plus",
+ "Power",
+ "Question",
+ "Radioactive",
+ "Reaction",
+ "Repair",
+ "Say",
+ "Scan",
+ "Shield",
+ "Skull",
+ "Sleep",
+ "Wireless",
+ )
+
+ options = action_options
+
+/obj/item/circuit_component/bci_action/register_shell(atom/movable/shell)
+ var/obj/item/organ/cyberimp/bci/bci = shell
+
+ bci_action = new(src)
+ update_action()
+
+ bci.actions += list(bci_action)
+
+/obj/item/circuit_component/bci_action/unregister_shell(atom/movable/shell)
+ var/obj/item/organ/cyberimp/bci/bci = shell
+
+ bci.actions -= bci_action
+ QDEL_NULL(bci_action)
+
+/obj/item/circuit_component/bci_action/input_received(datum/port/input/port)
+ . = ..()
+
+ if (.)
+ return
+
+ if (!isnull(bci_action))
+ update_action()
+
+/obj/item/circuit_component/bci_action/proc/update_action()
+ bci_action.name = button_name.input_value
+ bci_action.button_icon_state = "nanite_[replacetextEx(lowertext(current_option), " ", "_")]"
+
+/datum/action/innate/bci_action
+ name = "Action"
+ icon_icon = 'icons/mob/actions/actions_items.dmi'
+ check_flags = AB_CHECK_CONSCIOUS
+ button_icon_state = "nanite_power"
+
+ var/obj/item/circuit_component/bci_action/circuit_component
+
+/datum/action/innate/bci_action/New(obj/item/circuit_component/bci_action/circuit_component)
+ ..()
+
+ src.circuit_component = circuit_component
+
+/datum/action/innate/bci_action/Destroy()
+ circuit_component.bci_action = null
+ circuit_component = null
+
+ return ..()
+
+/datum/action/innate/bci_action/Activate()
+ circuit_component.signal.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/bci_core
+ display_name = "BCI Core"
+ display_desc = "Controls the core operations of the BCI."
+
+ /// A reference to the action button to look at charge/get info
+ var/datum/action/innate/bci_charge_action/charge_action
+
+ var/datum/port/input/message
+ var/datum/port/input/send_message_signal
+
+ var/datum/port/output/user_port
+
+ var/datum/weakref/user
+
+/obj/item/circuit_component/bci_core/Initialize()
+ . = ..()
+
+ message = add_input_port("Message", PORT_TYPE_STRING)
+ send_message_signal = add_input_port("Send Message", PORT_TYPE_SIGNAL)
+
+ user_port = add_output_port("User", PORT_TYPE_ATOM)
+
+/obj/item/circuit_component/bci_core/Destroy()
+ QDEL_NULL(charge_action)
+
+ message = null
+ send_message_signal = null
+
+ user_port = null
+
+ user = null
+
+ return ..()
+
+/obj/item/circuit_component/bci_core/register_shell(atom/movable/shell)
+ var/obj/item/organ/cyberimp/bci/bci = shell
+
+ charge_action = new(src)
+ bci.actions += list(charge_action)
+
+ RegisterSignal(shell, COMSIG_ORGAN_IMPLANTED, .proc/on_organ_implanted)
+ RegisterSignal(shell, COMSIG_ORGAN_REMOVED, .proc/on_organ_removed)
+
+/obj/item/circuit_component/bci_core/unregister_shell(atom/movable/shell)
+ var/obj/item/organ/cyberimp/bci/bci = shell
+
+ bci.actions -= charge_action
+ QDEL_NULL(charge_action)
+
+ UnregisterSignal(shell, list(
+ COMSIG_ORGAN_IMPLANTED,
+ COMSIG_ORGAN_REMOVED,
+ ))
+
+/obj/item/circuit_component/bci_core/input_received(datum/port/input/port)
+ . = ..()
+ if (.)
+ return .
+
+ if (COMPONENT_TRIGGERED_BY(send_message_signal, port))
+ var/sent_message = trim(message.input_value)
+ if (!sent_message)
+ return
+
+ var/mob/living/carbon/resolved_owner = user?.resolve()
+ if (isnull(resolved_owner))
+ return
+
+ if (resolved_owner.stat == DEAD)
+ return
+
+ to_chat(resolved_owner, "You hear a strange, robotic voice in your head... \"[span_robot("[html_encode(sent_message)]")]\"")
+
+/obj/item/circuit_component/bci_core/proc/on_organ_implanted(datum/source, mob/living/carbon/owner)
+ SIGNAL_HANDLER
+
+ user_port.set_output(owner)
+ user = WEAKREF(owner)
+
+ RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/on_borg_charge)
+ RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_electrocute)
+
+/obj/item/circuit_component/bci_core/proc/on_organ_removed(datum/source, mob/living/carbon/owner)
+ SIGNAL_HANDLER
+
+ user_port.set_output(null)
+ user = null
+
+ UnregisterSignal(owner, list(
+ COMSIG_PROCESS_BORGCHARGER_OCCUPANT,
+ COMSIG_LIVING_ELECTROCUTE_ACT,
+ ))
+
+/obj/item/circuit_component/bci_core/proc/on_borg_charge(datum/source, amount)
+ SIGNAL_HANDLER
+
+ if (isnull(parent.cell))
+ return
+
+ parent.cell.give(amount)
+
+/obj/item/circuit_component/bci_core/proc/on_electrocute(datum/source, shock_damage, siemens_coefficient, flags)
+ SIGNAL_HANDLER
+
+ if (isnull(parent.cell))
+ return
+
+ if (flags & SHOCK_ILLUSION)
+ return
+
+ parent.cell.give(shock_damage * 2)
+ to_chat(source, span_notice("You absorb some of the shock into your [parent.name]!"))
+
+/datum/action/innate/bci_charge_action
+ name = "Check BCI Charge"
+ check_flags = NONE
+ icon_icon = 'icons/obj/power.dmi'
+ button_icon_state = "cell"
+
+ var/obj/item/circuit_component/bci_core/circuit_component
+
+/datum/action/innate/bci_charge_action/New(obj/item/circuit_component/bci_core/circuit_component)
+ ..()
+
+ src.circuit_component = circuit_component
+
+ button.maptext_x = 2
+ button.maptext_y = 0
+ update_maptext()
+
+ START_PROCESSING(SSobj, src)
+
+/datum/action/innate/bci_charge_action/Destroy()
+ circuit_component.charge_action = null
+ circuit_component = null
+
+ STOP_PROCESSING(SSobj, src)
+
+ return ..()
+
+/datum/action/innate/bci_charge_action/Trigger()
+ var/obj/item/stock_parts/cell/cell = circuit_component.parent.cell
+
+ if (isnull(cell))
+ to_chat(owner, span_boldwarning("[circuit_component.parent] has no power cell."))
+ else
+ to_chat(owner, span_info("[circuit_component.parent]'s [cell.name] has [cell.percent()]% charge left."))
+ to_chat(owner, span_info("You can recharge it by using a cyborg recharging station."))
+
+/datum/action/innate/bci_charge_action/process(delta_time)
+ update_maptext()
+
+/datum/action/innate/bci_charge_action/proc/update_maptext()
+ var/obj/item/stock_parts/cell/cell = circuit_component.parent.cell
+ button.maptext = cell ? MAPTEXT("[cell.percent()]%") : ""
+
+/obj/machinery/bci_implanter
+ name = "brain-computer interface manipulation chamber"
+ desc = "A machine that, when given a brain-computer interface, will implant it into an occupant. Otherwise, will remove any brain-computer interfaces they already have."
+ circuit = /obj/item/circuitboard/machine/bci_implanter
+ icon = 'icons/obj/machines/nanite_chamber.dmi'
+ icon_state = "nanite_chamber"
+ base_icon_state = "nanite_chamber"
+ layer = ABOVE_WINDOW_LAYER
+ use_power = IDLE_POWER_USE
+ anchored = TRUE
+ density = TRUE
+ obj_flags = NO_BUILD // Becomes undense when the door is open
+ idle_power_usage = 50
+ active_power_usage = 300
+
+ var/busy = FALSE
+ var/busy_icon_state
+ var/locked = FALSE
+
+ var/datum/weakref/bci_to_implant
+
+ COOLDOWN_DECLARE(message_cooldown)
+
+/obj/machinery/bci_implanter/Initialize()
+ . = ..()
+ occupant_typecache = typecacheof(/mob/living/carbon)
+
+/obj/machinery/bci_implanter/Destroy()
+ QDEL_NULL(bci_to_implant)
+ return ..()
+
+/obj/machinery/bci_implanter/examine(mob/user)
+ . = ..()
+
+ if (isnull(bci_to_implant?.resolve()))
+ . += span_notice("There is no BCI inserted.")
+ else
+ . += span_notice("Right-click to remove current BCI.")
+
+/obj/machinery/bci_implanter/proc/set_busy(status, working_icon)
+ busy = status
+ busy_icon_state = working_icon
+ update_appearance()
+
+/obj/machinery/bci_implanter/update_icon_state()
+ if (occupant)
+ icon_state = busy ? busy_icon_state : "[base_icon_state]_occupied"
+ return ..()
+ icon_state = "[base_icon_state][state_open ? "_open" : null]"
+ return ..()
+
+/obj/machinery/bci_implanter/update_overlays()
+ var/list/overlays = ..()
+
+ if ((machine_stat & MAINT) || panel_open)
+ overlays += "maint"
+ return overlays
+
+ if (machine_stat & (NOPOWER|BROKEN))
+ return overlays
+
+ if (busy || locked)
+ overlays += "red"
+ if (locked)
+ overlays += "bolted"
+ return overlays
+
+ overlays += "green"
+
+ return overlays
+
+/obj/machinery/bci_implanter/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if (. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
+ return .
+
+ if (locked)
+ balloon_alert(user, "it's locked!")
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ var/obj/item/organ/cyberimp/bci/bci_to_implant_resolved = bci_to_implant?.resolve()
+ if (isnull(bci_to_implant_resolved) && user.Adjacent(src))
+ balloon_alert(user, "no bci inserted!")
+ else
+ user.put_in_hands(bci_to_implant_resolved)
+ balloon_alert(user, "ejected bci")
+
+ bci_to_implant = null
+
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/obj/machinery/bci_implanter/attackby(obj/item/weapon, mob/user, params)
+ var/obj/item/organ/cyberimp/bci/new_bci = weapon
+ if (istype(new_bci))
+ if (!(locate(/obj/item/integrated_circuit) in new_bci))
+ balloon_alert(user, "bci has no circuit!")
+ return
+
+ var/obj/item/organ/cyberimp/bci/previous_bci_to_implant = bci_to_implant?.resolve()
+
+ bci_to_implant = WEAKREF(weapon)
+ weapon.forceMove(src)
+
+ if (isnull(previous_bci_to_implant))
+ balloon_alert(user, "inserted bci")
+ else
+ balloon_alert(user, "swapped bci")
+ user.put_in_hands(previous_bci_to_implant)
+
+ return
+
+ return ..()
+
+/obj/machinery/bci_implanter/attackby_secondary(obj/item/weapon, mob/user, params)
+ if (!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, weapon))
+ update_appearance()
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ if (default_pry_open(weapon))
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ if (default_deconstruction_crowbar(weapon))
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/obj/machinery/bci_implanter/proc/start_process()
+ if (machine_stat & (NOPOWER|BROKEN))
+ return
+ if ((machine_stat & MAINT) || panel_open)
+ return
+ if (!occupant || busy)
+ return
+
+ var/locked_state = locked
+ locked = TRUE
+
+ set_busy(TRUE, "[initial(icon_state)]_raising")
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_active"), 1 SECONDS)
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_falling"), 2 SECONDS)
+ addtimer(CALLBACK(src, .proc/complete_process, locked_state), 3 SECONDS)
+
+/obj/machinery/bci_implanter/proc/complete_process(locked_state)
+ locked = locked_state
+ set_busy(FALSE)
+
+ var/mob/living/carbon/carbon_occupant = occupant
+ if (!istype(carbon_occupant))
+ return
+
+ playsound(loc, 'sound/machines/ping.ogg', 30, FALSE)
+
+ var/obj/item/organ/cyberimp/bci/bci_organ = carbon_occupant.getorgan(/obj/item/organ/cyberimp/bci)
+ var/obj/item/organ/cyberimp/bci/bci_to_implant_resolved = bci_to_implant?.resolve()
+
+ if (bci_organ)
+ bci_organ.Remove(carbon_occupant)
+
+ if (isnull(bci_to_implant_resolved))
+ say("Occupant's previous brain-computer interface has been transferred to internal storage unit.")
+ bci_organ.forceMove(src)
+ bci_to_implant = WEAKREF(bci_organ)
+ else
+ say("Occupant's previous brain-computer interface has been ejected.")
+ bci_organ.forceMove(drop_location())
+ else if (!isnull(bci_to_implant_resolved))
+ say("Occupant has been injected with [bci_to_implant_resolved].")
+ bci_to_implant_resolved.Insert(carbon_occupant)
+ bci_to_implant = null
+
+/obj/machinery/bci_implanter/open_machine()
+ if(state_open)
+ return FALSE
+
+ ..()
+
+ return TRUE
+
+/obj/machinery/bci_implanter/close_machine(mob/living/carbon/user)
+ if(!state_open)
+ return FALSE
+
+ ..()
+
+ var/mob/living/carbon/carbon_occupant = occupant
+ if (istype(occupant))
+ var/obj/item/organ/cyberimp/bci/existing_bci_organ = carbon_occupant.getorgan(/obj/item/organ/cyberimp/bci)
+ if (isnull(existing_bci_organ) && isnull(bci_to_implant?.resolve()))
+ say("No brain-computer interface inserted, and occupant does not have one. Insert a BCI to implant one.")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
+ return FALSE
+
+ addtimer(CALLBACK(src, .proc/start_process), 1 SECONDS)
+ return TRUE
+
+/obj/machinery/bci_implanter/relaymove(mob/living/user, direction)
+ var/message
+
+ if (locked)
+ message = "it won't budge!"
+ else if (user.stat != CONSCIOUS)
+ message = "you don't have the energy!"
+
+ if (!isnull(message))
+ if (COOLDOWN_FINISHED(src, message_cooldown))
+ COOLDOWN_START(src, message_cooldown, 5 SECONDS)
+ balloon_alert(user, "it won't budge!")
+
+ return
+
+ open_machine()
+
+/obj/machinery/bci_implanter/interact(mob/user)
+ if (state_open)
+ close_machine(null, user)
+ return
+ else if (locked)
+ balloon_alert(user, "it's locked!")
+ return
+
+ open_machine()
+
+/obj/item/circuitboard/machine/bci_implanter
+ name = "Brain-Computer Interface Manipulation Chamber (Machine Board)"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/bci_implanter
+ req_components = list(
+ /obj/item/stock_parts/micro_laser = 2,
+ /obj/item/stock_parts/manipulator = 1,
+ )
diff --git a/code/modules/wiremod/shell/shell_items.dm b/code/modules/wiremod/shell/shell_items.dm
index 6f2abdfd12b..95e70d28ea0 100644
--- a/code/modules/wiremod/shell/shell_items.dm
+++ b/code/modules/wiremod/shell/shell_items.dm
@@ -52,3 +52,8 @@
icon_state = "construction"
shell_to_spawn = /obj/machinery/door/airlock/shell
screw_delay = 10 SECONDS
+
+/obj/item/shell/bci
+ name = "brain-computer interface assembly"
+ icon_state = "bci-open"
+ shell_to_spawn = /obj/item/organ/cyberimp/bci
diff --git a/icons/obj/wiremod.dmi b/icons/obj/wiremod.dmi
index e4f9afc8681..6e6fec0e55e 100644
Binary files a/icons/obj/wiremod.dmi and b/icons/obj/wiremod.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index e8a1f8bf1fd..50b0b51f978 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -3693,6 +3693,7 @@
#include "code\modules\wiremod\preset\speech_relay.dm"
#include "code\modules\wiremod\shell\airlock.dm"
#include "code\modules\wiremod\shell\bot.dm"
+#include "code\modules\wiremod\shell\brain_computer_interface.dm"
#include "code\modules\wiremod\shell\compact_remote.dm"
#include "code\modules\wiremod\shell\controller.dm"
#include "code\modules\wiremod\shell\drone.dm"
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/index.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/index.js
index a28f880d39c..3667c66f9fd 100644
--- a/tgui/packages/tgui/interfaces/IntegratedCircuit/index.js
+++ b/tgui/packages/tgui/interfaces/IntegratedCircuit/index.js
@@ -232,7 +232,7 @@ export class IntegratedCircuit extends Component {
backgroundImage={resolveAsset('grid_background.png')}
imageWidth={900}
>
- {components.map((comp, index) => (
+ {components.map((comp, index) => comp && (
{
const connections = [];
for (const comp of components) {
+ if (comp === null) {
+ continue;
+ }
+
for (const port of comp.input_ports) {
if (port.connected_to === NULL_REF) continue;
const output_port = locations[port.connected_to];