From 901a1d44505f66f9bd8dda02baba6ce854fef1f1 Mon Sep 17 00:00:00 2001
From: tmyqlfpir <80724828+tmyqlfpir@users.noreply.github.com>
Date: Thu, 30 Apr 2026 00:52:55 +1000
Subject: [PATCH] Add USB ports to emitters/temperature pumps/temperature
machines (#95497)
## About The Pull Request
This PR adds USB functionality to temperature pumps, emitters and
temperature control unit machines.
### New USB Components
* Emitter
New exposed controls allow you to manually fire a single beam, and get
signals whenever it is toggled on/off/fired.
It doesn't allow a player to toggle it on/off (must be done physically).
ID locking the emitter will also lock out the ability to fire the
emitter via USB.
* Temperature Pump
This works exactly the same as the other USB controlled pumps, with the
addition of setting the heat rate.
* Temperature Control Unit
The USB interface now allows this to be remotely turned off and on like
the other USB pump interfaces.
## Why It's Good For The Game
These three parts are crucial to engineering/atmos projects, and gives
experienced players more depth to toy with circuits.
## Changelog
:cl:
add: Added USB interface to emitters, temperature pumps and temperature
control unit machines
/:cl:
---
code/__DEFINES/dcs/signals/signals_object.dm | 6 +
.../binary_devices/temperature_pump.dm | 1 +
.../components/unary_devices/thermomachine.dm | 2 +
code/modules/power/singularity/emitter.dm | 3 +
.../modules/wiremod/components/usb/emitter.dm | 58 ++++++++++
.../components/usb/temperature_pump.dm | 105 ++++++++++++++++++
.../wiremod/components/usb/thermomachine.dm | 100 +++++++++++++++++
tgstation.dme | 3 +
8 files changed, 278 insertions(+)
create mode 100644 code/modules/wiremod/components/usb/emitter.dm
create mode 100644 code/modules/wiremod/components/usb/temperature_pump.dm
create mode 100644 code/modules/wiremod/components/usb/thermomachine.dm
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index 3081b60449f..08e024bec8a 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -90,6 +90,12 @@
/// from /obj/machinery/atmospherics/set_on(active): (on)
#define COMSIG_ATMOS_MACHINE_SET_ON "atmos_machine_set_on"
+/// from /obj/machinery/power/emitter/interact(mob/user): (on)
+#define COMSIG_EMITTER_MACHINE_SET_ON "emitter_machine_set_on"
+
+/// from /obj/machinery/power/emitter/fire_beam(mob/user): (fired)
+#define COMSIG_EMITTER_MACHINE_ON_FIRE "emitter_machine_fire"
+
/// from /obj/machinery/light_switch/set_lights(), sent to every switch in the area: (status)
#define COMSIG_LIGHT_SWITCH_SET "light_switch_set"
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm
index 496a992e50b..60e355110b7 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm
@@ -14,6 +14,7 @@
/obj/machinery/atmospherics/components/binary/temperature_pump/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/usb_port, typecacheof(list(/obj/item/circuit_component/atmos_temperature_pump), only_root_path = TRUE))
register_context()
/obj/machinery/atmospherics/components/binary/temperature_pump/add_context(atom/source, list/context, obj/item/held_item, mob/user)
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
index 7fb5364170b..87d25f25a32 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
@@ -43,6 +43,8 @@
/obj/machinery/atmospherics/components/unary/thermomachine/Initialize(mapload)
. = ..()
update_appearance(UPDATE_ICON)
+ if(interactive)
+ AddComponent(/datum/component/usb_port, typecacheof(list(/obj/item/circuit_component/thermomachine), only_root_path = TRUE))
register_context()
/obj/machinery/atmospherics/components/unary/thermomachine/add_context(atom/source, list/context, obj/item/held_item, mob/user)
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm
index c7d79f1a551..af4e7fa98ac 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/singularity/emitter.dm
@@ -78,6 +78,7 @@
sparks.attach(src)
AddElement(/datum/element/simple_rotation)
AddElement(/datum/element/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES)
+ AddComponent(/datum/component/usb_port, typecacheof(list(/obj/item/circuit_component/emitter), only_root_path = TRUE))
/obj/machinery/power/emitter/welded/Initialize(mapload)
welded = TRUE
@@ -190,6 +191,7 @@
log_game("[src] turned [active ? "ON" : "OFF"] by [key_name(user)] in [AREACOORD(src)]")
investigate_log("turned [active ? "ON" : "OFF"] by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_ENGINE)
update_appearance()
+ SEND_SIGNAL(src, COMSIG_EMITTER_MACHINE_SET_ON, active ? TRUE : FALSE)
/obj/machinery/power/emitter/attack_animal(mob/living/simple_animal/user, list/modifiers)
if(ismegafauna(user) && anchored)
@@ -268,6 +270,7 @@
else
fire_delay = rand(minimum_fire_delay,maximum_fire_delay) * fire_rate_mod
shot_number = 0
+ SEND_SIGNAL(src, COMSIG_EMITTER_MACHINE_ON_FIRE)
return projectile
/obj/machinery/power/emitter/can_be_unfasten_wrench(mob/user, silent)
diff --git a/code/modules/wiremod/components/usb/emitter.dm b/code/modules/wiremod/components/usb/emitter.dm
new file mode 100644
index 00000000000..a817a3d8cf5
--- /dev/null
+++ b/code/modules/wiremod/components/usb/emitter.dm
@@ -0,0 +1,58 @@
+/**
+ * USB wiremod interface for emitter machines
+ */
+/obj/item/circuit_component/emitter
+ display_name = "Emitter"
+ desc = "Allows you to manually fire and get the ready status of an emitter. Must be unlocked to use fired signal."
+
+ ///Manually trigger the emitter
+ var/datum/port/input/fire
+
+ ///Send a signal when the emitter is turned on
+ var/datum/port/output/turned_on
+ ///Send a signal when the emitter is turned off
+ var/datum/port/output/turned_off
+ ///Send a signal when the emitter has fired
+ var/datum/port/output/has_fired
+
+ ///The component parent object
+ var/obj/machinery/power/emitter/attached_emitter
+
+/obj/item/circuit_component/emitter/populate_ports()
+ fire = add_input_port("Fire", PORT_TYPE_SIGNAL, trigger = PROC_REF(fire_emitter))
+
+ turned_on = add_output_port("Turned On", PORT_TYPE_SIGNAL)
+ turned_off = add_output_port("Turned Off", PORT_TYPE_SIGNAL)
+ has_fired = add_output_port("Has Fired", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/emitter/register_usb_parent(atom/movable/parent)
+ . = ..()
+ if(istype(parent, /obj/machinery/power/emitter))
+ attached_emitter = parent
+ RegisterSignal(attached_emitter, COMSIG_EMITTER_MACHINE_SET_ON, PROC_REF(handle_emitter_activation))
+ RegisterSignal(attached_emitter, COMSIG_EMITTER_MACHINE_ON_FIRE, PROC_REF(handle_emitter_on_fire))
+
+/obj/item/circuit_component/emitter/unregister_usb_parent(atom/movable/parent)
+ UnregisterSignal(attached_emitter, COMSIG_EMITTER_MACHINE_SET_ON)
+ UnregisterSignal(attached_emitter, COMSIG_EMITTER_MACHINE_ON_FIRE)
+ attached_emitter = null
+ return ..()
+
+/obj/item/circuit_component/emitter/proc/handle_emitter_activation(datum/source, active)
+ SIGNAL_HANDLER
+ if(active)
+ turned_on.set_output(COMPONENT_SIGNAL)
+ else
+ turned_off.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/emitter/proc/handle_emitter_on_fire(datum/source, active)
+ SIGNAL_HANDLER
+ has_fired.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/emitter/proc/fire_emitter()
+ CIRCUIT_TRIGGER
+ if(!attached_emitter)
+ return
+ if(attached_emitter.locked) // do not fire if emitter is locked
+ return
+ attached_emitter.fire_beam_pulse()
diff --git a/code/modules/wiremod/components/usb/temperature_pump.dm b/code/modules/wiremod/components/usb/temperature_pump.dm
new file mode 100644
index 00000000000..abd199701d1
--- /dev/null
+++ b/code/modules/wiremod/components/usb/temperature_pump.dm
@@ -0,0 +1,105 @@
+/**
+ * USB wiremod interface for temperature pumps
+ */
+/obj/item/circuit_component/atmos_temperature_pump
+ display_name = "Atmospheric Temperature Pump"
+ desc = "The interface for communicating with a temperature pump."
+
+ ///Set the percent of the heat delta to transfer
+ var/datum/port/input/heat_transfer_rate
+ ///Activate the pump
+ var/datum/port/input/on
+ ///Deactivate the pump
+ var/datum/port/input/off
+ ///Signals the circuit to retrieve the pump's current percent and temperature
+ var/datum/port/input/request_data
+
+ ///Current status of the percent
+ var/datum/port/output/heat_transfer_rate_state
+ ///Pressure of the input port
+ var/datum/port/output/input_pressure
+ ///Pressure of the output port
+ var/datum/port/output/output_pressure
+ ///Temperature of the input port
+ var/datum/port/output/input_temperature
+ ///Temperature of the output port
+ var/datum/port/output/output_temperature
+
+ ///Whether the pump is currently active
+ var/datum/port/output/is_active
+ ///Send a signal when the pump is turned on
+ var/datum/port/output/turned_on
+ ///Send a signal when the pump is turned off
+ var/datum/port/output/turned_off
+
+ ///The component parent object
+ var/obj/machinery/atmospherics/components/binary/temperature_pump/connected_pump
+
+/obj/item/circuit_component/atmos_temperature_pump/populate_ports()
+ heat_transfer_rate = add_input_port("Set Heat Transfer Rate", PORT_TYPE_NUMBER, trigger = PROC_REF(set_pump_heat_rate))
+ on = add_input_port("Turn On", PORT_TYPE_SIGNAL, trigger = PROC_REF(set_pump_on))
+ off = add_input_port("Turn Off", PORT_TYPE_SIGNAL, trigger = PROC_REF(set_pump_off))
+ request_data = add_input_port("Request Port Data", PORT_TYPE_SIGNAL, trigger = PROC_REF(request_pump_data))
+
+ heat_transfer_rate_state = add_output_port("Heat Transfer Rate", PORT_TYPE_NUMBER)
+ input_pressure = add_output_port("Input Pressure", PORT_TYPE_NUMBER)
+ output_pressure = add_output_port("Output Pressure", PORT_TYPE_NUMBER)
+ input_temperature = add_output_port("Input Temperature", PORT_TYPE_NUMBER)
+ output_temperature = add_output_port("Output Temperature", PORT_TYPE_NUMBER)
+
+ is_active = add_output_port("Active", PORT_TYPE_NUMBER)
+ turned_on = add_output_port("Turned On", PORT_TYPE_SIGNAL)
+ turned_off = add_output_port("Turned Off", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/atmos_temperature_pump/register_usb_parent(atom/movable/shell)
+ . = ..()
+ if(istype(shell, /obj/machinery/atmospherics/components/binary/temperature_pump))
+ connected_pump = shell
+ RegisterSignal(connected_pump, COMSIG_ATMOS_MACHINE_SET_ON, PROC_REF(handle_pump_activation))
+
+/obj/item/circuit_component/atmos_temperature_pump/unregister_usb_parent(atom/movable/shell)
+ UnregisterSignal(connected_pump, COMSIG_ATMOS_MACHINE_SET_ON)
+ connected_pump = null
+ return ..()
+
+/obj/item/circuit_component/atmos_temperature_pump/pre_input_received(datum/port/input/port)
+ heat_transfer_rate.set_value(clamp(heat_transfer_rate.value, 0, connected_pump ? connected_pump.max_heat_transfer_rate : 100))
+
+/obj/item/circuit_component/atmos_temperature_pump/proc/handle_pump_activation(datum/source, active)
+ SIGNAL_HANDLER
+ is_active.set_output(active)
+ if(active)
+ turned_on.set_output(COMPONENT_SIGNAL)
+ else
+ turned_off.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/atmos_temperature_pump/proc/set_pump_heat_rate()
+ CIRCUIT_TRIGGER
+ if(!connected_pump)
+ return
+ connected_pump.heat_transfer_rate = heat_transfer_rate.value
+
+/obj/item/circuit_component/atmos_temperature_pump/proc/set_pump_on()
+ CIRCUIT_TRIGGER
+ if(!connected_pump)
+ return
+ connected_pump.set_on(TRUE)
+
+/obj/item/circuit_component/atmos_temperature_pump/proc/set_pump_off()
+ CIRCUIT_TRIGGER
+ if(!connected_pump)
+ return
+ connected_pump.set_on(FALSE)
+ connected_pump.update_appearance(UPDATE_ICON)
+
+/obj/item/circuit_component/atmos_temperature_pump/proc/request_pump_data()
+ CIRCUIT_TRIGGER
+ if(!connected_pump)
+ return
+ var/datum/gas_mixture/air_input = connected_pump.airs[1]
+ var/datum/gas_mixture/air_output = connected_pump.airs[2]
+ heat_transfer_rate_state.set_output(connected_pump.heat_transfer_rate)
+ input_pressure.set_output(air_input.return_pressure())
+ output_pressure.set_output(air_output.return_pressure())
+ input_temperature.set_output(air_input.return_temperature())
+ output_temperature.set_output(air_output.return_temperature())
diff --git a/code/modules/wiremod/components/usb/thermomachine.dm b/code/modules/wiremod/components/usb/thermomachine.dm
new file mode 100644
index 00000000000..49929e2da83
--- /dev/null
+++ b/code/modules/wiremod/components/usb/thermomachine.dm
@@ -0,0 +1,100 @@
+/**
+ * USB wiremod interface for temperature control unit machines
+ */
+/obj/item/circuit_component/thermomachine
+ display_name = "Temperature Control Unit"
+ desc = "The interface for communicating with a temperature control unit."
+
+ ///Set the target temperature
+ var/datum/port/input/temperature
+ ///Activate the machine
+ var/datum/port/input/on
+ ///Deactivate the machine
+ var/datum/port/input/off
+ ///Signals the circuit to retrieve the machine's current pressure and temperature
+ var/datum/port/input/request_data
+
+ ///Pressure of the port
+ var/datum/port/output/port_pressure
+ ///Temperature of the port port
+ var/datum/port/output/port_temperature
+
+ ///Whether the machine is currently active
+ var/datum/port/output/is_active
+ ///Send a signal when the machine is turned on
+ var/datum/port/output/turned_on
+ ///Send a signal when the machine is turned off
+ var/datum/port/output/turned_off
+
+ ///The component parent object
+ var/obj/machinery/atmospherics/components/unary/thermomachine/connected_machine
+
+/obj/item/circuit_component/thermomachine/populate_ports()
+ temperature = add_input_port("Set Temperature", PORT_TYPE_NUMBER, trigger = PROC_REF(set_temperature))
+ on = add_input_port("Turn On", PORT_TYPE_SIGNAL, trigger = PROC_REF(set_machine_on))
+ off = add_input_port("Turn Off", PORT_TYPE_SIGNAL, trigger = PROC_REF(set_machine_off))
+ request_data = add_input_port("Request Port Data", PORT_TYPE_SIGNAL, trigger = PROC_REF(request_pump_data))
+
+ port_pressure = add_output_port("Pressure", PORT_TYPE_NUMBER)
+ port_temperature = add_output_port("Temperature", PORT_TYPE_NUMBER)
+
+ is_active = add_output_port("Active", PORT_TYPE_NUMBER)
+ turned_on = add_output_port("Turned On", PORT_TYPE_SIGNAL)
+ turned_off = add_output_port("Turned Off", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/thermomachine/register_usb_parent(atom/movable/shell)
+ . = ..()
+ if(istype(shell, /obj/machinery/atmospherics/components/unary/thermomachine))
+ connected_machine = shell
+ RegisterSignal(connected_machine, COMSIG_ATMOS_MACHINE_SET_ON, PROC_REF(handle_pump_activation))
+
+/obj/item/circuit_component/thermomachine/unregister_usb_parent(atom/movable/shell)
+ UnregisterSignal(connected_machine, COMSIG_ATMOS_MACHINE_SET_ON)
+ connected_machine = null
+ return ..()
+
+/obj/item/circuit_component/thermomachine/pre_input_received(datum/port/input/port)
+ if(connected_machine)
+ var/min = connected_machine.min_temperature
+ var/max = connected_machine.max_temperature
+ temperature.set_value(clamp(temperature.value, min, max))
+
+/obj/item/circuit_component/thermomachine/proc/handle_pump_activation(datum/source, active)
+ SIGNAL_HANDLER
+ is_active.set_output(active)
+ if(active)
+ turned_on.set_output(COMPONENT_SIGNAL)
+ else
+ turned_off.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/thermomachine/proc/set_temperature()
+ CIRCUIT_TRIGGER
+ if(!connected_machine)
+ return
+ var/min = connected_machine.min_temperature
+ var/max = connected_machine.max_temperature
+ connected_machine.target_temperature = clamp(temperature.value, min, max)
+ connected_machine.update_icon_state()
+
+/obj/item/circuit_component/thermomachine/proc/set_machine_on()
+ CIRCUIT_TRIGGER
+ if(!connected_machine)
+ return
+ connected_machine.set_on(TRUE)
+ connected_machine.update_use_power(ACTIVE_POWER_USE)
+
+/obj/item/circuit_component/thermomachine/proc/set_machine_off()
+ CIRCUIT_TRIGGER
+ if(!connected_machine)
+ return
+ connected_machine.set_on(FALSE)
+ connected_machine.update_use_power(IDLE_POWER_USE)
+ connected_machine.update_appearance(UPDATE_ICON)
+
+/obj/item/circuit_component/thermomachine/proc/request_pump_data()
+ CIRCUIT_TRIGGER
+ if(!connected_machine)
+ return
+ var/datum/gas_mixture/port = connected_machine.airs[1]
+ port_pressure.set_output(port.return_pressure())
+ port_temperature.set_output(port.return_temperature())
diff --git a/tgstation.dme b/tgstation.dme
index 9fe2ac109f5..496de2a3b74 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -6856,6 +6856,9 @@
#include "code\modules\wiremod\components\table\get_column.dm"
#include "code\modules\wiremod\components\table\index_table.dm"
#include "code\modules\wiremod\components\table\select.dm"
+#include "code\modules\wiremod\components\usb\emitter.dm"
+#include "code\modules\wiremod\components\usb\temperature_pump.dm"
+#include "code\modules\wiremod\components\usb\thermomachine.dm"
#include "code\modules\wiremod\components\utility\clock.dm"
#include "code\modules\wiremod\components\utility\delay.dm"
#include "code\modules\wiremod\components\utility\nfc_receive.dm"