From 5a21e2e64c2d94ee10c653613990298170baa93f Mon Sep 17 00:00:00 2001 From: Watermelon914 <37270891+Watermelon914@users.noreply.github.com> Date: Sat, 17 Jul 2021 05:05:57 +0100 Subject: [PATCH] Circuit submodules (#60109) Adds the module component that is basically a subroutine. Allows you to compact your logic into a bunch of functions. --- code/__DEFINES/dcs/signals.dm | 9 + code/__DEFINES/wiremod.dm | 4 + code/_globalvars/lists/wiremod.dm | 9 + code/datums/components/shell.dm | 8 +- .../research/designs/wiremod_designs.dm | 10 +- code/modules/research/techweb/all_nodes.dm | 1 + code/modules/wiremod/component.dm | 25 ++ .../wiremod/components/abstract/module.dm | 252 ++++++++++++++++++ code/modules/wiremod/integrated_circuit.dm | 29 +- code/modules/wiremod/port.dm | 4 - tgstation.dme | 2 + .../packages/tgui/interfaces/CircuitModule.js | 145 ++++++++++ 12 files changed, 480 insertions(+), 18 deletions(-) create mode 100644 code/_globalvars/lists/wiremod.dm create mode 100644 code/modules/wiremod/components/abstract/module.dm create mode 100644 tgui/packages/tgui/interfaces/CircuitModule.js diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 77a6f7ae808..33ed59e50d2 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -1346,6 +1346,15 @@ /// Sent to [/obj/item/circuit_component] when it is removed from a circuit. (/obj/item/integrated_circuit) #define COMSIG_CIRCUIT_COMPONENT_REMOVED "circuit_component_removed" +/// Called when the integrated circuit's cell is set. +#define COMSIG_CIRCUIT_SET_CELL "circuit_set_cell" + +/// Called when the integrated circuit is turned on or off. +#define COMSIG_CIRCUIT_SET_ON "circuit_set_on" + +/// Called when the integrated circuit's shell is set. +#define COMSIG_CIRCUIT_SET_SHELL "circuit_set_shell" + /// Sent to an atom when a [/obj/item/usb_cable] attempts to connect to something. (/obj/item/usb_cable/usb_cable, /mob/user) #define COMSIG_ATOM_USB_CABLE_TRY_ATTACH "usb_cable_try_attach" /// Attaches the USB cable to the atom. If the USB cables moves away, it will disconnect. diff --git a/code/__DEFINES/wiremod.dm b/code/__DEFINES/wiremod.dm index 784dcaa3e3c..807270544cc 100644 --- a/code/__DEFINES/wiremod.dm +++ b/code/__DEFINES/wiremod.dm @@ -4,6 +4,10 @@ /// Define to automatically handle calling the output port. Will not call the output port if the input_received proc returns TRUE. #define TRIGGER_CIRCUIT_COMPONENT(component, port) if(!component.input_received(port) && (component.circuit_flags & CIRCUIT_FLAG_OUTPUT_SIGNAL)) component.trigger_output.set_output(COMPONENT_SIGNAL) +// Port defines + +#define PORT_MAX_NAME_LENGTH 50 + // Port types. Determines what the port can connect to /// Can accept any datatype. Only works for inputs, output types will runtime. diff --git a/code/_globalvars/lists/wiremod.dm b/code/_globalvars/lists/wiremod.dm new file mode 100644 index 00000000000..bada761dcd2 --- /dev/null +++ b/code/_globalvars/lists/wiremod.dm @@ -0,0 +1,9 @@ +GLOBAL_LIST_INIT(wiremod_types, list( + PORT_TYPE_ANY, + PORT_TYPE_STRING, + PORT_TYPE_NUMBER, + PORT_TYPE_SIGNAL, + PORT_TYPE_LIST, + PORT_TYPE_TABLE, + PORT_TYPE_ATOM, +)) diff --git a/code/datums/components/shell.dm b/code/datums/components/shell.dm index 487aa60e5c8..9e064ef3852 100644 --- a/code/datums/components/shell.dm +++ b/code/datums/components/shell.dm @@ -196,7 +196,8 @@ * Attaches a circuit to the parent. Doesn't do any checks to see for any existing circuits so that should be done beforehand. */ /datum/component/shell/proc/attach_circuit(obj/item/integrated_circuit/circuitboard, mob/living/user) - if(!user.transferItemToLoc(circuitboard, parent)) + var/atom/movable/parent_atom = parent + if(!user.transferItemToLoc(circuitboard, parent_atom)) return locked = FALSE attached_circuit = circuitboard @@ -206,11 +207,12 @@ to_add.forceMove(attached_circuit) attached_circuit.add_component(to_add) RegisterSignal(circuitboard, COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY, .proc/on_circuit_add_component_manually) - attached_circuit.set_shell(parent) + attached_circuit.set_shell(parent_atom) + if(attached_circuit.display_name != "") + parent_atom.name = "[initial(parent_atom.name)] ([attached_circuit.display_name])" attached_circuit.locked = FALSE if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR) - var/atom/movable/parent_atom = parent on_unfasten(parent_atom, parent_atom.anchored) /** diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm index 5dcbd975e27..86ecfdb14a8 100644 --- a/code/modules/research/designs/wiremod_designs.dm +++ b/code/modules/research/designs/wiremod_designs.dm @@ -214,15 +214,21 @@ build_path = /obj/item/circuit_component/select /datum/design/component/tempsensor - name = "Temperature Sensor" + name = "Temperature Sensor Component" id = "comp_tempsensor" build_path = /obj/item/circuit_component/tempsensor /datum/design/component/pressuresensor - name = "Pressure Sensor" + name = "Pressure Sensor Component" id = "comp_pressuresensor" build_path = /obj/item/circuit_component/pressuresensor +/datum/design/component/module + name = "Module Component" + id = "comp_module" + build_path = /obj/item/circuit_component/module + + /datum/design/compact_remote_shell name = "Compact Remote Shell" desc = "A handheld shell with one big button." diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 0e8f23e5baa..9785c5259a0 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -213,6 +213,7 @@ "comp_light", "comp_logic", "comp_mmi", + "comp_module", "comp_multiplexer", "comp_not", "comp_pressuresensor", diff --git a/code/modules/wiremod/component.dm b/code/modules/wiremod/component.dm index 82edd1c9876..95e2af3dc15 100644 --- a/code/modules/wiremod/component.dm +++ b/code/modules/wiremod/component.dm @@ -142,8 +142,21 @@ /obj/item/circuit_component/proc/add_input_port(name, type, trigger = TRUE, default = null) var/datum/port/input/input_port = new(src, name, type, trigger, default) input_ports += input_port + if(parent) + SStgui.update_uis(parent) return input_port +/** + * Removes an input port and deletes it. This will not cleanup any references made by derivatives of the circuit component + * + * Arguments: + * * input_port - The input port to remove. + */ +/obj/item/circuit_component/proc/remove_input_port(datum/port/input/input_port) + input_ports -= input_port + qdel(input_port) + if(parent) + SStgui.update_uis(parent) /** * Adds an output port and returns it @@ -157,6 +170,18 @@ output_ports += output_port return output_port +/** + * Removes an output port and deletes it. This will not cleanup any references made by derivatives of the circuit component + * + * Arguments: + * * output_port - The output port to remove. + */ +/obj/item/circuit_component/proc/remove_output_port(datum/port/output/output_port) + output_ports -= output_port + qdel(output_port) + if(parent) + SStgui.update_uis(parent) + /** * Called whenever an input is received from one of the ports. * diff --git a/code/modules/wiremod/components/abstract/module.dm b/code/modules/wiremod/components/abstract/module.dm new file mode 100644 index 00000000000..ba55b12c56b --- /dev/null +++ b/code/modules/wiremod/components/abstract/module.dm @@ -0,0 +1,252 @@ +/** + * # Module Component + * + * A component that has an input, output + */ +/obj/item/circuit_component/module + display_name = "Module" + display_desc = "A component that has other components within it, acting like a function. Use it in your hand to control the amount of input and output ports it has, as well as being able to access the integrated circuit contained inside." + + var/obj/item/integrated_circuit/module/internal_circuit + + var/obj/item/circuit_component/module_input/input_component + var/obj/item/circuit_component/module_output/output_component + + /// Linked ports that follow a `first_port = second_port` keyed structure. + var/list/linked_ports = list() + + var/port_limit = 10 + +/obj/item/integrated_circuit/module + var/obj/item/circuit_component/attached_module + +/obj/item/integrated_circuit/module/ui_host(mob/user) + . = ..() + if(. == src) + return attached_module + +/obj/item/integrated_circuit/module/set_display_name(new_name) + . = ..() + attached_module.display_name = new_name + +/obj/item/integrated_circuit/module/Destroy() + attached_module = null + return ..() + +/obj/item/circuit_component/module_input + display_name = "Input" + display_desc = "A component that receives data from the module it is attached to" + + removable = FALSE + + /// The currently attached module + var/obj/item/circuit_component/module/attached_module + +/obj/item/circuit_component/module_input/Destroy() + attached_module = null + return ..() + +/obj/item/circuit_component/module_output + display_name = "Output" + display_desc = "A component that outputs data to the module it is attached to." + + removable = FALSE + + /// The currently attached module + var/obj/item/circuit_component/module/attached_module + +/obj/item/circuit_component/module_output/input_received(datum/port/input/port) + . = ..() + if(!port) + return + // We don't check the parent here because frankly, we don't care. We only sync our input with the module's output + var/datum/port/output/port_to_update = attached_module.linked_ports[port] + if(!port_to_update) + CRASH("[port.type] doesn't have a linked port in [type]!") + + port_to_update.set_output(port.input_value) + +/obj/item/circuit_component/module/input_received(datum/port/input/port) + . = ..() + if(!port) + return + var/datum/port/output/port_to_update = linked_ports[port] + if(!port_to_update) + CRASH("[port.type] doesn't have a linked port in [type]!") + + port_to_update.set_output(port.input_value) + +/obj/item/circuit_component/module_output/Destroy() + attached_module = null + return ..() + +/obj/item/circuit_component/module/Initialize() + . = ..() + internal_circuit = new(src) + internal_circuit.attached_module = src + + input_component = new(internal_circuit) + input_component.attached_module = src + internal_circuit.add_component(input_component) + input_component.rel_x = 400 + input_component.rel_y = 200 + + output_component = new(internal_circuit) + output_component.attached_module = src + internal_circuit.add_component(output_component) + output_component.rel_x = 400 + output_component.rel_y = 200 + +/obj/item/circuit_component/module/add_to(obj/item/integrated_circuit/added_to) + . = ..() + RegisterSignal(added_to, COMSIG_CIRCUIT_SET_CELL, .proc/handle_set_cell) + RegisterSignal(added_to, COMSIG_CIRCUIT_SET_ON, .proc/handle_set_on) + RegisterSignal(added_to, COMSIG_CIRCUIT_SET_SHELL, .proc/handle_set_shell) + internal_circuit.set_cell(added_to.cell) + internal_circuit.set_shell(added_to.shell) + internal_circuit.set_on(added_to.on) + + +/obj/item/circuit_component/module/removed_from(obj/item/integrated_circuit/removed_from) + internal_circuit.set_cell(null) + internal_circuit.set_on(FALSE) + internal_circuit.remove_current_shell() + UnregisterSignal(removed_from, list( + COMSIG_CIRCUIT_SET_CELL, + COMSIG_CIRCUIT_SET_ON, + COMSIG_CIRCUIT_SET_SHELL, + )) + return ..() + +/obj/item/circuit_component/module/proc/handle_set_cell(datum/source, obj/item/stock_parts/cell/cell) + SIGNAL_HANDLER + internal_circuit.set_cell(cell) + +/obj/item/circuit_component/module/proc/handle_set_on(datum/source, new_value) + SIGNAL_HANDLER + internal_circuit.set_on(new_value) + +/obj/item/circuit_component/module/proc/handle_set_shell(datum/source, atom/movable/new_shell) + SIGNAL_HANDLER + internal_circuit.set_shell(new_shell) + +/obj/item/circuit_component/module/Destroy() + QDEL_NULL(input_component) + QDEL_NULL(output_component) + QDEL_NULL(internal_circuit) + linked_ports = null + return ..() + +/obj/item/circuit_component/module/ui_data(mob/user) + . = list() + .["input_ports"] = list() + for(var/datum/port/input/input_port as anything in input_ports) + .["input_ports"] += list(list( + "name" = input_port.name, + "type" = input_port.datatype, + )) + + .["output_ports"] = list() + for(var/datum/port/output/output_port as anything in output_ports) + .["output_ports"] += list(list( + "name" = output_port.name, + "type" = output_port.datatype, + )) + +/obj/item/circuit_component/module/ui_static_data(mob/user) + . = list() + .["global_port_types"] = GLOB.wiremod_types + +/obj/item/circuit_component/module/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/circuit_component)) + internal_circuit.attackby(I, user, params) + return + return ..() + +#define WITHIN_RANGE(id, table) (id >= 1 && id <= length(table)) + +/obj/item/circuit_component/module/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("open_internal_circuit") + internal_circuit.interact(usr) + . = TRUE + if("add_input_port") + if(length(input_ports) > port_limit) + return + var/datum/port/new_port = add_input_port("Input Port", PORT_TYPE_ANY) + linked_ports[new_port] = input_component.add_output_port("Input Port", PORT_TYPE_ANY) + . = TRUE + if("remove_input_port") + var/port_id = text2num(params["port_id"]) + if(!WITHIN_RANGE(port_id, input_ports)) + return + var/datum/port/removed_port = input_ports[port_id] + linked_ports -= removed_port + remove_input_port(removed_port) + input_component.remove_output_port(input_component.output_ports[port_id]) + . = TRUE + if("add_output_port") + if(length(output_ports) > port_limit) + return + var/datum/port/new_port = output_component.add_input_port("Output Port", PORT_TYPE_ANY) + linked_ports[new_port] = add_output_port("Output Port", PORT_TYPE_ANY) + . = TRUE + if("remove_output_port") + var/port_id = text2num(params["port_id"]) + if(!WITHIN_RANGE(port_id, output_ports)) + return + + var/datum/port/removed_port = output_component.input_ports[port_id] + linked_ports -= removed_port + remove_output_port(output_ports[port_id]) + output_component.remove_input_port(removed_port) + . = TRUE + if("set_port_name", "set_port_type") + var/port_id = text2num(params["port_id"]) + var/is_input = params["is_input"] + + var/list/ports_to_use + var/list/internal_ports_to_use + if(is_input) + ports_to_use = input_ports + internal_ports_to_use = input_component.output_ports + else + ports_to_use = output_ports + internal_ports_to_use = output_component.input_ports + + if(!WITHIN_RANGE(port_id, ports_to_use)) + return + + var/datum/port/component_port = ports_to_use[port_id] + var/datum/port/internal_component_port = internal_ports_to_use[port_id] + + if(action == "set_port_type") + var/type = params["port_type"] + if(!(type in GLOB.wiremod_types)) + return + component_port.set_datatype(type) + internal_component_port.set_datatype(type) + else + var/port_name = params["port_name"] + if(!port_name) + return + port_name = strip_html(port_name, PORT_MAX_NAME_LENGTH) + component_port.name = port_name + internal_component_port.name = port_name + . = TRUE + + if(.) + SStgui.update_uis(internal_circuit) + +#undef WITHIN_RANGE + +/obj/item/circuit_component/module/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CircuitModule", name) + ui.open() + ui.set_autoupdate(FALSE) diff --git a/code/modules/wiremod/integrated_circuit.dm b/code/modules/wiremod/integrated_circuit.dm index fdae1cf903c..b1415f682b7 100644 --- a/code/modules/wiremod/integrated_circuit.dm +++ b/code/modules/wiremod/integrated_circuit.dm @@ -53,7 +53,7 @@ /obj/item/integrated_circuit/loaded/Initialize() . = ..() - cell = new /obj/item/stock_parts/cell/high(src) + set_cell(new /obj/item/stock_parts/cell/high(src)) /obj/item/integrated_circuit/Destroy() for(var/obj/item/circuit_component/to_delete in attached_components) @@ -73,6 +73,10 @@ else . += span_notice("There is no power cell installed.") +/obj/item/integrated_circuit/proc/set_cell(obj/item/stock_parts/cell_to_set) + SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_CELL, cell_to_set) + cell = cell_to_set + /obj/item/integrated_circuit/attackby(obj/item/I, mob/living/user, params) . = ..() if(istype(I, /obj/item/circuit_component)) @@ -85,7 +89,7 @@ return if(!user.transferItemToLoc(I, src)) return - cell = I + set_cell(I) I.add_fingerprint(user) user.visible_message(span_notice("[user] inserts a power cell into [src]."), span_notice("You insert the power cell into [src].")) return @@ -101,7 +105,7 @@ I.play_tool_sound(src) user.visible_message(span_notice("[user] unscrews the power cell from [src]."), span_notice("You unscrew the power cell from [src].")) cell.forceMove(drop_location()) - cell = null + set_cell(null) return /** @@ -114,7 +118,8 @@ */ /obj/item/integrated_circuit/proc/set_shell(atom/movable/new_shell) remove_current_shell() - on = TRUE + set_on(TRUE) + SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_SHELL, new_shell) shell = new_shell RegisterSignal(shell, COMSIG_PARENT_QDELETING, .proc/remove_current_shell) for(var/obj/item/circuit_component/attached_component as anything in attached_components) @@ -122,8 +127,6 @@ // Their input ports may be updated with user values, but the outputs haven't updated // because on is FALSE TRIGGER_CIRCUIT_COMPONENT(attached_component, null) - if(display_name != "") - shell.name = "[initial(shell.name)] ([display_name])" /** * Unregisters the current shell attached to this circuit. @@ -137,9 +140,13 @@ attached_component.unregister_shell(shell) UnregisterSignal(shell, COMSIG_PARENT_QDELETING) shell = null - on = FALSE + set_on(FALSE) SEND_SIGNAL(src, COMSIG_CIRCUIT_SHELL_REMOVED) +/obj/item/integrated_circuit/proc/set_on(new_value) + SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_ON, new_value) + on = new_value + /** * Adds a component to the circuitboard * @@ -428,9 +435,9 @@ var/new_name = params["display_name"] if(new_name) - display_name = strip_html(params["display_name"], label_max_length) + set_display_name(strip_html(params["display_name"], label_max_length)) else - display_name = "" + set_display_name("") if(shell) if(display_name != "") @@ -458,6 +465,10 @@ #undef WITHIN_RANGE +/// Sets the display name that appears on the shell. +/obj/item/integrated_circuit/proc/set_display_name(new_name) + display_name = new_name + /** * Returns the creator of the integrated circuit. Used in admin messages and other related things. */ diff --git a/code/modules/wiremod/port.dm b/code/modules/wiremod/port.dm index 212610ce8e9..a9b2b9b962b 100644 --- a/code/modules/wiremod/port.dm +++ b/code/modules/wiremod/port.dm @@ -52,10 +52,6 @@ return "grey" /datum/port/Destroy(force) - if(!force && !QDELETED(connected_component)) - // This should never happen. Ports should be deleted with their components - stack_trace("Attempted to delete a port with a non-destroyed connected_component! (port name: [name], component type: [connected_component.type])") - return QDEL_HINT_LETMELIVE connected_component = null return ..() diff --git a/tgstation.dme b/tgstation.dme index 60432cd813b..cb93d1b161c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -240,6 +240,7 @@ #include "code\_globalvars\lists\objects.dm" #include "code\_globalvars\lists\poll_ignore.dm" #include "code\_globalvars\lists\typecache.dm" +#include "code\_globalvars\lists\wiremod.dm" #include "code\_globalvars\lists\xenobiology.dm" #include "code\_js\byjax.dm" #include "code\_js\menus.dm" @@ -3588,6 +3589,7 @@ #include "code\modules\wiremod\port.dm" #include "code\modules\wiremod\usb_cable.dm" #include "code\modules\wiremod\components\abstract\compare.dm" +#include "code\modules\wiremod\components\abstract\module.dm" #include "code\modules\wiremod\components\action\light.dm" #include "code\modules\wiremod\components\action\mmi.dm" #include "code\modules\wiremod\components\action\pull.dm" diff --git a/tgui/packages/tgui/interfaces/CircuitModule.js b/tgui/packages/tgui/interfaces/CircuitModule.js new file mode 100644 index 00000000000..bb0ebb983e4 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CircuitModule.js @@ -0,0 +1,145 @@ +import { useBackend } from "../backend"; +import { Stack, Section, Input, Button, Dropdown } from "../components"; +import { Window } from "../layouts"; + +export const CircuitModule = (props, context) => { + const { act, data } = useBackend(context); + const { + input_ports, + output_ports, + global_port_types, + } = data; + return ( + + + + +