Files
Bubberstation/code/datums/components/usb_port.dm
Y0SH1M4S73R f8511ea227 You can install B.O.R.I.S. in circuits with MMI components (+other MMI component changes) (#92002)
## About The Pull Request

This PR adds the ability to install a B.O.R.I.S. in a circuit that
contains an MMI component. These circuits can then be remotely connected
to by an AI by clicking on them or anything they are inside of. To
indicate that a circuit allows remote AI connection, an indicator is
given to the circuit and anything containing it.

Additionally:
- Refactors the MMI component to use `item_interaction`, since it was
pertinent.
- You cannot insert an MMI/B.O.R.I.S. into a locked circuit.
- You can no longer hotswap MMIs/B.O.R.I.S.es - you must manually eject
the inserted one.

Let me know what changelog labels I should use for the hotswap removal
and the prevention of insertion into locked circuits.

## Why It's Good For The Game

If you can put an MMI or posibrain in a circuit, why not allow an AI to
use it using a B.O.R.I.S.?

## Changelog

🆑
add: B.O.R.I.S.es can be installed inside of integrated circuits with
MMI components, allowing an AI to remotely interface with them the same
way an MMI or posibrain could.
refactor: The MMI component now uses item interaction behavior for
inserting MMIs/B.O.R.I.S.es.
/🆑

---------

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2025-09-03 23:25:50 -07:00

271 lines
9.3 KiB
Plaintext

/// Opens up a USB port that can be connected to by circuits, creating registerable circuit components
/datum/component/usb_port
dupe_mode = COMPONENT_DUPE_UNIQUE
/// The component types to create when something plugs in
var/list/circuit_component_types
/// The currently connected circuit
var/obj/item/integrated_circuit/attached_circuit
/// The currently connected USB cable
var/datum/weakref/usb_cable_ref
/// The components inside the parent
var/list/obj/item/circuit_component/circuit_components
/// The beam connecting the USB cable to the machine
var/datum/beam/usb_cable_beam
/// The current physical object that the beam is connected to and listens to.
var/atom/movable/physical_object
/// An extra callback to invoke when registering this component with its parent. Can be a proc name or a callback datum.
var/extra_registration_callback
/// An extra callback to invoke when unregistering this component from its parent. Can be a proc name or a callback datum.
var/extra_unregistration_callback
/datum/component/usb_port/Initialize(list/circuit_component_types, extra_registration_callback, extra_unregistration_callback)
if (!isatom(parent))
return COMPONENT_INCOMPATIBLE
circuit_components = list()
src.circuit_component_types = circuit_component_types
src.extra_registration_callback = extra_registration_callback
src.extra_unregistration_callback = extra_unregistration_callback
/datum/component/usb_port/proc/set_circuit_components(list/components)
var/should_register = FALSE
if(length(circuit_components))
UnregisterFromParent()
should_register = TRUE
QDEL_LIST(circuit_components)
for(var/circuit_component in components)
var/obj/item/circuit_component/component = circuit_component
if(ispath(circuit_component))
component = new circuit_component(null)
if(!should_register)
component.register_usb_parent(parent)
RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_SAVE, PROC_REF(save_component))
circuit_components += component
if(should_register)
RegisterWithParent()
/datum/component/usb_port/RegisterWithParent()
RegisterSignal(parent, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, PROC_REF(on_atom_usb_cable_try_attach))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_MOVABLE_CIRCUIT_LOADED, PROC_REF(on_load))
AddComponentFrom(REF(parent), /datum/component/shuttle_move_deferred_checks, PROC_REF(on_moved))
for(var/obj/item/circuit_component/component as anything in circuit_components)
component.register_usb_parent(parent)
if(extra_registration_callback)
if(istype(extra_registration_callback, /datum/callback))
var/datum/callback/callback = extra_registration_callback
callback.Invoke(src)
else
call(parent, extra_registration_callback)(src)
/datum/component/usb_port/UnregisterFromParent()
UnregisterSignal(parent, list(
COMSIG_ATOM_USB_CABLE_TRY_ATTACH,
COMSIG_ATOM_EXAMINE,
COMSIG_MOVABLE_CIRCUIT_LOADED,
))
RemoveComponentSource(REF(parent), /datum/component/shuttle_move_deferred_checks)
for(var/obj/item/circuit_component/component as anything in circuit_components)
component.unregister_usb_parent(parent)
if(extra_unregistration_callback)
if(istype(extra_unregistration_callback, /datum/callback))
var/datum/callback/callback = extra_unregistration_callback
callback.Invoke(src)
else
call(parent, extra_unregistration_callback)(src)
unregister_circuit_signals()
unregister_physical_signals()
attached_circuit = null
/datum/component/usb_port/proc/save_component(datum/source, list/objects)
SIGNAL_HANDLER
objects += parent
/datum/component/usb_port/proc/on_load(datum/source, obj/item/integrated_circuit/circuit, list/components)
SIGNAL_HANDLER
var/list/components_in_list = list()
for(var/obj/item/circuit_component/component as anything in components)
components_in_list += component.type
for(var/obj/item/circuit_component/component as anything in circuit_components)
if(component.type in components_in_list)
continue
components += component.type
set_circuit_components(components)
var/obj/item/usb_cable/cable = new(circuit.drop_location())
cable.attached_circuit = circuit
on_atom_usb_cable_try_attach(src, cable, null)
/datum/component/usb_port/Destroy()
QDEL_LIST(circuit_components)
QDEL_NULL(usb_cable_beam)
attached_circuit = null
usb_cable_ref = null
return ..()
/datum/component/usb_port/proc/unregister_circuit_signals()
if (isnull(attached_circuit))
return
UnregisterSignal(attached_circuit, list(
COMSIG_CIRCUIT_SHELL_REMOVED,
COMSIG_QDELETING,
COMSIG_CIRCUIT_SET_SHELL,
))
/datum/component/usb_port/proc/unregister_physical_signals()
if (isnull(physical_object))
return
SEND_SIGNAL(src, COMSIG_USB_PORT_UNREGISTER_PHYSICAL_OBJECT, physical_object)
UnregisterSignal(physical_object, COMSIG_ATOM_EXAMINE)
RemoveComponentSource(REF(physical_object), /datum/component/shuttle_move_deferred_checks)
/datum/component/usb_port/proc/attach_circuit_components(obj/item/integrated_circuit/circuitboard)
for(var/obj/item/circuit_component/component as anything in circuit_components)
circuitboard.add_component(component)
RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_REMOVED, PROC_REF(on_circuit_component_removed))
/datum/component/usb_port/proc/on_examine(datum/source, mob/user, list/examine_text)
SIGNAL_HANDLER
if (isnull(attached_circuit))
examine_text += span_notice("There is a USB port on the front.")
else
examine_text += span_notice("[attached_circuit.shell || attached_circuit] is connected to [parent.p_them()] by a USB port.")
/datum/component/usb_port/proc/on_examine_shell(datum/source, mob/user, list/examine_text)
SIGNAL_HANDLER
examine_text += span_notice("[source.p_They()] [source.p_are()] attached to [parent] with a USB cable.")
/datum/component/usb_port/proc/on_atom_usb_cable_try_attach(datum/source, obj/item/usb_cable/connecting_cable, mob/user)
SIGNAL_HANDLER
if (!length(circuit_components))
set_circuit_components(circuit_component_types)
var/atom/atom_parent = parent
if (!isnull(attached_circuit))
if(user)
atom_parent.balloon_alert(user, "usb already connected")
return COMSIG_CANCEL_USB_CABLE_ATTACK
if (isnull(connecting_cable.attached_circuit))
if(user)
connecting_cable.balloon_alert(user, "connect to a shell first")
return COMSIG_CANCEL_USB_CABLE_ATTACK
if (!IN_GIVEN_RANGE(connecting_cable.attached_circuit, parent, USB_CABLE_MAX_RANGE))
if(user)
connecting_cable.balloon_alert(user, "too far away")
return COMSIG_CANCEL_USB_CABLE_ATTACK
if (connecting_cable.attached_circuit.locked)
connecting_cable.balloon_alert(user, "shell is locked!")
return COMSIG_CANCEL_USB_CABLE_ATTACK
usb_cable_ref = WEAKREF(connecting_cable)
attached_circuit = connecting_cable.attached_circuit
connecting_cable.forceMove(attached_circuit)
attach_circuit_components(attached_circuit)
if(user)
attached_circuit.interact(user)
var/new_physical_object = attached_circuit.shell
if(!new_physical_object)
new_physical_object = attached_circuit
RegisterSignal(attached_circuit, COMSIG_CIRCUIT_SHELL_REMOVED, PROC_REF(on_circuit_shell_removed))
RegisterSignal(attached_circuit, COMSIG_QDELETING, PROC_REF(on_circuit_deleting))
RegisterSignal(attached_circuit, COMSIG_CIRCUIT_SET_SHELL, PROC_REF(on_set_shell))
set_physical_object(new_physical_object)
return COMSIG_USB_CABLE_ATTACHED
/datum/component/usb_port/proc/set_physical_object(atom/movable/new_physical_object)
if(physical_object)
unregister_physical_signals()
if(usb_cable_beam)
QDEL_NULL(usb_cable_beam)
var/atom/atom_parent = parent
usb_cable_beam = atom_parent.Beam(new_physical_object, "usb_cable_beam", 'icons/obj/science/circuits.dmi')
AddComponentFrom(REF(new_physical_object), /datum/component/shuttle_move_deferred_checks, PROC_REF(on_moved))
RegisterSignal(new_physical_object, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine_shell))
SEND_SIGNAL(src, COMSIG_USB_PORT_REGISTER_PHYSICAL_OBJECT, new_physical_object)
physical_object = new_physical_object
// Adds support for loading circuits without shells but with usb cables, or loading circuits with shells because the shells might not load first.
/datum/component/usb_port/proc/on_set_shell(datum/source, atom/movable/new_shell)
SIGNAL_HANDLER
set_physical_object(new_shell)
/datum/component/usb_port/proc/on_moved()
SIGNAL_HANDLER
if (isnull(attached_circuit))
return
if (IN_GIVEN_RANGE(attached_circuit, parent, USB_CABLE_MAX_RANGE))
return
detach()
/datum/component/usb_port/proc/on_circuit_deleting()
SIGNAL_HANDLER
detach()
qdel(usb_cable_ref)
/datum/component/usb_port/proc/on_circuit_component_removed(datum/source)
SIGNAL_HANDLER
detach()
/datum/component/usb_port/proc/on_circuit_shell_removed()
SIGNAL_HANDLER
detach()
/datum/component/usb_port/proc/detach()
var/obj/item/usb_cable/usb_cable = usb_cable_ref?.resolve()
if (isnull(usb_cable))
return
for(var/obj/item/circuit_component/component as anything in circuit_components)
UnregisterSignal(component, COMSIG_CIRCUIT_COMPONENT_REMOVED)
attached_circuit.remove_component(component)
component.moveToNullspace()
unregister_circuit_signals()
unregister_physical_signals()
var/atom/atom_parent = parent
usb_cable.forceMove(atom_parent.drop_location())
usb_cable.balloon_alert_to_viewers("snap")
physical_object = null
attached_circuit = null
usb_cable_ref = null
QDEL_NULL(usb_cable_beam)