diff --git a/code/_globalvars/lists/wiremod.dm b/code/_globalvars/lists/wiremod.dm index 59c575dfa31..5facb57c929 100644 --- a/code/_globalvars/lists/wiremod.dm +++ b/code/_globalvars/lists/wiremod.dm @@ -1,3 +1,4 @@ +/// The basic types that don't have any super special behaviour. GLOBAL_LIST_INIT(wiremod_basic_types, list( PORT_TYPE_ANY, PORT_TYPE_STRING, @@ -7,3 +8,12 @@ GLOBAL_LIST_INIT(wiremod_basic_types, list( PORT_TYPE_TABLE, PORT_TYPE_ATOM, )) + +/// The fundamental datatypes of the byond game engine. +GLOBAL_LIST_INIT(wiremod_fundamental_types, list( + PORT_TYPE_ANY, + PORT_TYPE_NUMBER, + PORT_TYPE_ATOM, + PORT_TYPE_STRING, + PORT_TYPE_LIST, +)) diff --git a/code/modules/wiremod/components/admin/proccall.dm b/code/modules/wiremod/components/admin/proccall.dm index 198e19d0915..529cfcd18d4 100644 --- a/code/modules/wiremod/components/admin/proccall.dm +++ b/code/modules/wiremod/components/admin/proccall.dm @@ -42,7 +42,6 @@ output_value = add_output_port("Output Value", PORT_TYPE_ANY) /obj/item/circuit_component/proccall/input_received(datum/port/input/port) - var/called_on if(proccall_options.value == COMP_PROC_OBJECT) called_on = entity.value @@ -58,6 +57,9 @@ if(!to_invoke) return + INVOKE_ASYNC(src, .proc/do_proccall, called_on, to_invoke, params) + +/obj/item/circuit_component/proccall/proc/do_proccall(called_on, to_invoke, params) GLOB.AdminProcCaller = "CHAT_[parent.display_name]" //_ won't show up in ckeys so it'll never match with a real admin var/result = WrapAdminProcCall(called_on, to_invoke, params) GLOB.AdminProcCaller = null diff --git a/code/modules/wiremod/components/admin/signal_handler/signal_handler.dm b/code/modules/wiremod/components/admin/signal_handler/signal_handler.dm new file mode 100644 index 00000000000..0309f567986 --- /dev/null +++ b/code/modules/wiremod/components/admin/signal_handler/signal_handler.dm @@ -0,0 +1,203 @@ +#define COMP_SIGNAL_HANDLER_GLOBAL "Global" +#define COMP_SIGNAL_HANDLER_OBJECT "Object" + +/** + * # Signal Handler Component + * + * A component that registers signals on events and listens for them. + */ +/obj/item/circuit_component/signal_handler + display_name = "Signal Handler" + desc = "A component that listens for signals on an object. Registering a new object will automatically unregister the old." + circuit_flags = CIRCUIT_FLAG_ADMIN|CIRCUIT_FLAG_INSTANT + + /// Whether it is a global or object signal + var/datum/port/input/option/signal_handler_options + + /// The list of signal IDs that can be selected as an option. + var/datum/port/input/option/signal_id + + var/list/signal_map + + /// Entity to register the signal on + var/datum/port/input/target + /// Registers the signal + var/datum/port/input/register + /// Unregisters the signal from the current registered entity. + var/datum/port/input/unregister + + /// The custom signal ports from the current signal type. Used for saving and loading. + var/list/signal_ports + /// The custom input from the current signal type. + var/list/datum/port/input/input_signal_ports = list() + /// The custom output from the current signal type. + var/list/datum/port/output/output_signal_ports = list() + + /// The entity received from the event. + var/datum/port/output/entity + /// The event has been triggered + var/datum/port/output/event_triggered + + /// The current entity that has the signal registered on it + var/datum/weakref/current_registered_entity + /// The current registered signal + var/registered_signal + + /// Whether it is a custom signal id or not. + var/custom_signal = FALSE + +/obj/item/circuit_component/signal_handler/populate_options() + var/static/list/component_options = list( + COMP_SIGNAL_HANDLER_OBJECT, + COMP_SIGNAL_HANDLER_GLOBAL, + ) + signal_handler_options = add_option_port("Signal Handler Options", component_options, trigger = null) + + signal_id = add_option_port("Signal ID", GLOB.integrated_circuit_signal_ids, trigger = null) + signal_map = GLOB.integrated_circuit_signal_ids + +/obj/item/circuit_component/signal_handler/populate_ports() + register = add_input_port("Register", PORT_TYPE_SIGNAL, order = 2, trigger = .proc/register_signals) + unregister = add_input_port("Unregister Current", PORT_TYPE_SIGNAL, order = 2, trigger = .proc/unregister_signals) + + add_source_entity() + event_triggered = add_output_port("Triggered", PORT_TYPE_SIGNAL, order = 2) + +/obj/item/circuit_component/signal_handler/proc/add_source_entity() + if(target) + remove_input_port(target) + if(entity) + remove_output_port(entity) + + target = add_input_port("Target", PORT_TYPE_ATOM, order = 1, trigger = null) + entity = add_output_port("Source Entity", PORT_TYPE_ATOM, order = 0) + +/obj/item/circuit_component/signal_handler/save_data_to_list(list/component_data) + . = ..() + component_data["signal_id"] = signal_id.value + component_data["signal_port_data"] = signal_ports + +/obj/item/circuit_component/signal_handler/load_data_from_list(list/component_data) + signal_id.set_value(component_data["signal_id"], force = TRUE) + registered_signal = signal_id.value + load_new_ports(component_data["signal_port_data"]) + custom_signal = TRUE + return ..() + + +/obj/item/circuit_component/signal_handler/pre_input_received(datum/port/input/port) + if(signal_id == port) + custom_signal = FALSE + if(current_registered_entity) + unregister_signals(port) + + var/last_registered_signal = registered_signal + registered_signal = signal_id.value + + if(registered_signal != last_registered_signal) + var/list/data = signal_map[registered_signal] + if(data) + load_new_ports(data) + unregister_signals(port) + + if(signal_handler_options == port) + set_signal_options(port) + +/obj/item/circuit_component/signal_handler/proc/set_signal_options(datum/port/input/port) + CIRCUIT_TRIGGER + + switch(signal_handler_options.value) + if(COMP_SIGNAL_HANDLER_GLOBAL) + signal_id.possible_options = GLOB.integrated_circuit_global_signal_ids + signal_map = GLOB.integrated_circuit_global_signal_ids + remove_output_port(entity) + remove_input_port(target) + if(COMP_SIGNAL_HANDLER_OBJECT) + signal_id.possible_options = GLOB.integrated_circuit_signal_ids + signal_map = GLOB.integrated_circuit_signal_ids + add_source_entity() + + if(!custom_signal) + signal_id.set_value(null, TRUE) + unregister_signals() + +/obj/item/circuit_component/signal_handler/proc/register_signals(datum/port/input/port) + CIRCUIT_TRIGGER + if(current_registered_entity) + unregister_signals(port) + + var/datum/target_datum = target.value + if(signal_handler_options.value == COMP_SIGNAL_HANDLER_GLOBAL) + target_datum = SSdcs + + if(target_datum) + RegisterSignal(target_datum, registered_signal, .proc/handle_signal_received) + current_registered_entity = WEAKREF(target_datum) + +/obj/item/circuit_component/signal_handler/proc/load_new_ports(list/ports_to_load) + for(var/datum/port/input/input_port as anything in input_signal_ports) + remove_input_port(input_port) + for(var/datum/port/output/output_port as anything in output_signal_ports) + remove_output_port(output_port) + input_signal_ports = list() + output_signal_ports = list() + + signal_ports = ports_to_load + for(var/list/data in signal_ports) + if(data["is_response"]) + var/datum/port/input/bitflag_input = add_input_port(data["name"], PORT_TYPE_SIGNAL, order = 1, trigger = .proc/handle_bitflag_received) + input_signal_ports[bitflag_input] = data["bitflag"] + else + output_signal_ports += add_output_port(data["name"], data["type"], order = 1) + + +/obj/item/circuit_component/signal_handler/proc/unregister_signals(datum/port/input/port) + CIRCUIT_TRIGGER + + var/datum/registered_datum = current_registered_entity?.resolve() + if(!registered_datum) + return + + UnregisterSignal(registered_datum, registered_signal) + current_registered_entity = null + +/obj/item/circuit_component/signal_handler/proc/handle_signal_received(...) + SIGNAL_HANDLER + var/list/arguments = args.Copy() + + // usr is not supposed to be defined whilst these execute, which it can be for some signal IDs. + // Especially if you try to proccall something - it'll fail because of this reason. + // No other way to solve this problem without refactoring proccall code, but it's admin tooling so it's whatever. + var/temp_usr = usr + usr = null + + SScircuit_component.queue_instant_run() + var/first_arg = popleft(arguments) + if(entity) + entity.set_output(first_arg) + + for(var/datum/port/output/port as anything in output_signal_ports) + port.set_output(popleft(arguments)) + event_triggered.set_output(COMPONENT_SIGNAL) + var/list/output = SScircuit_component.execute_instant_run() + + usr = temp_usr + + if(!output) + message_admins("[parent.get_creator_admin()] took too much CPU time trying to handle a signal. Reduce the amount of circuit components attached to your [name] circuit component.") + return + + return output["bitflag"] || NONE + +/obj/item/circuit_component/signal_handler/proc/handle_bitflag_received(datum/port/input/port, list/return_values) + CIRCUIT_TRIGGER + if(!return_values) + return + + if(!return_values["bitflag"]) + return_values["bitflag"] = NONE + + return_values["bitflag"] |= input_signal_ports[port] + +#undef COMP_SIGNAL_HANDLER_GLOBAL +#undef COMP_SIGNAL_HANDLER_OBJECT diff --git a/code/modules/wiremod/components/admin/signal_handler/signal_list.dm b/code/modules/wiremod/components/admin/signal_handler/signal_list.dm new file mode 100644 index 00000000000..5a302e2adc4 --- /dev/null +++ b/code/modules/wiremod/components/admin/signal_handler/signal_list.dm @@ -0,0 +1,103 @@ +/proc/circuit_signal_response(name, bitflag) + SHOULD_BE_PURE(TRUE) + return list( + "name" = name, + "bitflag" = bitflag, + "is_response" = TRUE + ) + +/proc/circuit_signal_param(name, type) + SHOULD_BE_PURE(TRUE) + return list( + "name" = name, + "type" = type, + "is_response" = FALSE + ) + +GLOBAL_LIST_INIT(integrated_circuit_signal_ids, generate_circuit_signal_list()) + +/proc/generate_circuit_signal_list() + var/cancel_attack = circuit_signal_response("Cancel Attack", COMPONENT_CANCEL_ATTACK_CHAIN) + var/target = circuit_signal_param("Target", PORT_TYPE_ATOM) + var/user = circuit_signal_param("User", PORT_TYPE_ATOM) + var/item = circuit_signal_param("Item", PORT_TYPE_ATOM) + var/entity = circuit_signal_param("Entity", PORT_TYPE_ATOM) + + return list( + COMSIG_PARENT_QDELETING = list(), + COMSIG_PARENT_ATTACKBY = list(circuit_signal_response("Cancel Attack", COMPONENT_NO_AFTERATTACK), item, user), + COMSIG_PARENT_ATTACKBY_SECONDARY = list(circuit_signal_response("Cancel Attack", COMPONENT_NO_AFTERATTACK), item, user), + COMSIG_PARENT_EXAMINE = list(user), + + COMSIG_ATOM_ATTACK_HAND = list(cancel_attack, user), + COMSIG_ATOM_ATTACK_GHOST = list(cancel_attack, user), + COMSIG_ATOM_BUMPED = list(entity), + COMSIG_ATOM_HITBY = list(entity), + + COMSIG_ITEM_ATTACK = list(cancel_attack, target, user), + COMSIG_ITEM_PRE_ATTACK = list(cancel_attack, target, user), + COMSIG_ITEM_AFTERATTACK = list(cancel_attack, target, user), + COMSIG_ITEM_ATTACK_SECONDARY = list(circuit_signal_response("Cancel Attack", COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN), target, user), + COMSIG_ITEM_ATTACK_SELF = list(cancel_attack, user), + COMSIG_ITEM_ATTACK_SELF_SECONDARY = list(cancel_attack, user), + ) + +GLOBAL_LIST_INIT(integrated_circuit_global_signal_ids, generate_global_circuit_signal_list()) + +/proc/generate_global_circuit_signal_list() + var/client_ent = circuit_signal_param("Client", PORT_TYPE_ATOM) + var/entity = circuit_signal_param("Entity", PORT_TYPE_ATOM) + + return list( + COMSIG_GLOB_MOB_DEATH = list(entity, circuit_signal_param("Gibbed", PORT_TYPE_NUMBER)), + COMSIG_GLOB_MOB_CREATED = list(entity), + COMSIG_GLOB_CLIENT_CONNECT = list(client_ent), + + COMSIG_GLOB_JOB_AFTER_LATEJOIN_SPAWN = list(circuit_signal_param("Job Subsystem", PORT_TYPE_ATOM), entity, client_ent), + COMSIG_GLOB_CREWMEMBER_JOINED = list(circuit_signal_param("Crewmember", PORT_TYPE_ATOM), circuit_signal_param("Rank", PORT_TYPE_STRING)) + ) + +/obj/item/circuit_component/signal_handler/ui_state(mob/user) + return GLOB.admin_state + +/obj/item/circuit_component/signal_handler/ui_static_data(mob/user) + . = list() + .["global_port_types"] = GLOB.wiremod_fundamental_types + + +/obj/item/circuit_component/signal_handler/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CircuitSignalHandler", name) + ui.open() + ui.set_autoupdate(FALSE) + +/obj/item/circuit_component/signal_handler/ui_act(action, list/params, datum/tgui/ui) + . = ..() + if(.) + return + + var/signal_id = params["signal_id"] + + if(!istext(signal_id)) + return + + var/list/responses = params["responses"] + var/list/parameters = params["parameters"] + if(!islist(responses) || !islist(parameters)) + return + + var/list/sanitized_data = list() + + for(var/list/data as anything in responses) + sanitized_data += list(circuit_signal_response(data["name"], text2num(data["bitflag"]))) + for(var/list/data as anything in parameters) + sanitized_data += list(circuit_signal_param(data["name"], data["datatype"])) + + var/extra_info = "" + if(params["global"]) + GLOB.integrated_circuit_global_signal_ids[signal_id] = sanitized_data + extra_info = " as a global signal" + else + GLOB.integrated_circuit_signal_ids[signal_id] = sanitized_data + balloon_alert(usr, "successfully added [signal_id][extra_info]") diff --git a/code/modules/wiremod/components/atom/self.dm b/code/modules/wiremod/components/atom/self.dm index 1f24c875548..f6f88a80ae3 100644 --- a/code/modules/wiremod/components/atom/self.dm +++ b/code/modules/wiremod/components/atom/self.dm @@ -10,11 +10,17 @@ /// The shell this component is attached to. var/datum/port/output/output + /// The signal sent when the status is updated. + var/datum/port/output/shell_received + /obj/item/circuit_component/self/populate_ports() - output = add_output_port("Self", PORT_TYPE_ATOM) + output = add_output_port("Shell", PORT_TYPE_ATOM) + shell_received = add_output_port("Shell Updated", PORT_TYPE_SIGNAL) /obj/item/circuit_component/self/register_shell(atom/movable/shell) output.set_output(shell) + shell_received.set_output(COMPONENT_SIGNAL) /obj/item/circuit_component/self/unregister_shell(atom/movable/shell) output.set_output(null) + shell_received.set_output(COMPONENT_SIGNAL) diff --git a/code/modules/wiremod/core/datatypes.dm b/code/modules/wiremod/core/datatypes.dm index b81ad0876b2..048e7c23802 100644 --- a/code/modules/wiremod/core/datatypes.dm +++ b/code/modules/wiremod/core/datatypes.dm @@ -28,7 +28,7 @@ GLOBAL_LIST_INIT(circuit_datatypes, generate_circuit_datatypes()) * Used for implicit conversions between outputs and inputs (e.g. number -> string) * and applying/removing signals on inputs */ -/datum/circuit_datatype/proc/convert_value(datum/port/port, value_to_convert) +/datum/circuit_datatype/proc/convert_value(datum/port/port, value_to_convert, force = FALSE) return value_to_convert /** diff --git a/code/modules/wiremod/core/integrated_circuit.dm b/code/modules/wiremod/core/integrated_circuit.dm index 4313d27ffe0..7d8f6f4c0fa 100644 --- a/code/modules/wiremod/core/integrated_circuit.dm +++ b/code/modules/wiremod/core/integrated_circuit.dm @@ -84,7 +84,7 @@ GLOBAL_LIST_EMPTY_TYPED(integrated_circuits, /obj/item/integrated_circuit) for(var/obj/item/circuit_component/to_delete in attached_components) remove_component(to_delete) qdel(to_delete) - QDEL_LIST(circuit_variables) + QDEL_LIST_ASSOC_VAL(circuit_variables) attached_components.Cut() shell = null examined_component = null diff --git a/code/modules/wiremod/core/port.dm b/code/modules/wiremod/core/port.dm index aeabf886766..78b5ad846fb 100644 --- a/code/modules/wiremod/core/port.dm +++ b/code/modules/wiremod/core/port.dm @@ -49,7 +49,7 @@ if(src.value != value || force) if(isatom(value)) UnregisterSignal(value, COMSIG_PARENT_QDELETING) - src.value = datatype_handler.convert_value(src, value) + src.value = datatype_handler.convert_value(src, value, force) if(isatom(value)) RegisterSignal(value, COMSIG_PARENT_QDELETING, .proc/null_value) SEND_SIGNAL(src, COMSIG_PORT_SET_VALUE, value) diff --git a/code/modules/wiremod/datatypes/any.dm b/code/modules/wiremod/datatypes/any.dm index a8a2f8457f6..6ab5432cf24 100644 --- a/code/modules/wiremod/datatypes/any.dm +++ b/code/modules/wiremod/datatypes/any.dm @@ -7,4 +7,7 @@ return TRUE /datum/circuit_datatype/any/handle_manual_input(datum/port/input/port, mob/user, user_input) - return text2num(user_input) || user_input + var/result = text2num(user_input) + if(isnull(result)) + return user_input + return result diff --git a/code/modules/wiremod/datatypes/option.dm b/code/modules/wiremod/datatypes/option.dm index c6de346c9df..f7a36db1c7c 100644 --- a/code/modules/wiremod/datatypes/option.dm +++ b/code/modules/wiremod/datatypes/option.dm @@ -1,10 +1,11 @@ /datum/port/input/option var/list/possible_options -/datum/port/input/option/New(obj/item/circuit_component/to_connect, name, datatype, trigger, default, possible_options) +/datum/port/input/option/New(obj/item/circuit_component/to_connect, name, datatype, order = 1, trigger = null, default = null, possible_options) . = ..() src.possible_options = possible_options - set_value(default, force = TRUE) + if(length(possible_options)) + set_value(possible_options[1]) /datum/circuit_datatype/option datatype = PORT_TYPE_OPTION @@ -21,7 +22,10 @@ return datatype_to_check == PORT_TYPE_STRING -/datum/circuit_datatype/option/convert_value(datum/port/input/option/port, value_to_convert) +/datum/circuit_datatype/option/convert_value(datum/port/input/option/port, value_to_convert, force = FALSE) + if(force) + return value_to_convert + if(!port.possible_options) return null diff --git a/tgstation.dme b/tgstation.dme index 881d18a85d5..c20ba8523aa 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3800,6 +3800,8 @@ #include "code\modules\wiremod\components\admin\setvar.dm" #include "code\modules\wiremod\components\admin\spawn.dm" #include "code\modules\wiremod\components\admin\to_type.dm" +#include "code\modules\wiremod\components\admin\signal_handler\signal_handler.dm" +#include "code\modules\wiremod\components\admin\signal_handler\signal_list.dm" #include "code\modules\wiremod\components\atom\direction.dm" #include "code\modules\wiremod\components\atom\gps.dm" #include "code\modules\wiremod\components\atom\health.dm" diff --git a/tgui/packages/tgui/components/Dropdown.js b/tgui/packages/tgui/components/Dropdown.js index 7486807e3f3..fb407882b00 100644 --- a/tgui/packages/tgui/components/Dropdown.js +++ b/tgui/packages/tgui/components/Dropdown.js @@ -72,6 +72,7 @@ export class Dropdown extends Component { noscroll, nochevron, width, + openWidth = width, onClick, selected, disabled, @@ -90,7 +91,7 @@ export class Dropdown extends Component { ref={menu => { this.menuRef = menu; }} tabIndex="-1" style={{ - 'width': width, + 'width': openWidth, }} className={classes([ noscroll && 'Dropdown__menu-noscroll' || 'Dropdown__menu', @@ -103,7 +104,7 @@ export class Dropdown extends Component { return (
{ + bitflags: BitflagToString; + + constructor(props) { + super(props); + this.state = { + signal_id: "signal_id", + responseList: [], + parameterList: [], + global: false, + }; + + this.bitflags = {}; + + for (let i = 0; i < 24; i++) { + this.bitflags[1 << i] = `Flag ${i+1}`; + } + } + + render() { + const { act, data } = useBackend(this.context); + const { responseList, parameterList, signal_id, global } + = this.state as CircuitSignalHandlerState; + const { + global_port_types, + } = data; + return ( + + + + + + + this.setState({ signal_id: value })} + /> + + + this.setState({ global: !global })} + /> + + + + + + +
+ + {responseList.map((val, index) => ( + { + responseList.splice(index, 1); + this.setState({ parameterList }); + }} + onEnter={(e, value) => { + const param = responseList[index]; + param.name = value; + this.setState({ parameterList }); + }} + /> + ))} + +
+
+ +
+ + {parameterList.map((val, index) => ( + { + parameterList.splice(index, 1); + this.setState({ parameterList }); + }} + onSetOption={type => { + const param = parameterList[index]; + param.datatype = type; + this.setState({ parameterList }); + }} + onEnter={(e, value) => { + const param = parameterList[index]; + param.name = value; + this.setState({ parameterList }); + }} + /> + ))} + +
+
+
+
+ +