code:
GLOBAL_LIST_INIT(wiremod_filter_info, list(
	"alpha" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
		"icon" = PORT_TYPE_STRING,
		"render_source" = PORT_TYPE_STRING,
		"flags" = PORT_TYPE_NUMBER,
	),
	"angular_blur" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
		"size" = PORT_TYPE_NUMBER,
	),
	"displace" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
		"size" = PORT_TYPE_NUMBER,
		"icon" = PORT_TYPE_STRING,
		"render_source" = PORT_TYPE_NUMBER,
	),
	"drop_shadow" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
		"size" = PORT_TYPE_NUMBER,
		"offset" = PORT_TYPE_NUMBER,
		"color" = PORT_TYPE_STRING,
	),
	"blur" = list(
		"size" = PORT_TYPE_NUMBER,
	),
	"layer" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
		"icon" = PORT_TYPE_STRING,
		"render_source" = PORT_TYPE_STRING,
		"flags" = PORT_TYPE_NUMBER,
		"color" = PORT_TYPE_STRING,
		"transform" = PORT_TYPE_LIST(PORT_TYPE_ANY),
		"blend_mode" = PORT_TYPE_NUMBER,
	),
	"motion_blur" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
	),
	"outline" = list(
		"size" = PORT_TYPE_NUMBER,
		"color" = PORT_TYPE_STRING,
		"flags" = PORT_TYPE_NUMBER,
	),
	"radial_blur" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
		"size" = PORT_TYPE_NUMBER,
	),
	"rays" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
		"size" = PORT_TYPE_NUMBER,
		"color" = PORT_TYPE_STRING,
		"offset" = PORT_TYPE_NUMBER,
		"density" = PORT_TYPE_NUMBER,
		"threshold" = PORT_TYPE_NUMBER,
		"factor" = PORT_TYPE_NUMBER,
		"flags" = PORT_TYPE_NUMBER,
	),
	"ripple" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
		"size" = PORT_TYPE_NUMBER,
		"repeat" = PORT_TYPE_NUMBER,
		"radius" = PORT_TYPE_NUMBER,
		"falloff" = PORT_TYPE_NUMBER,
		"flags" = PORT_TYPE_NUMBER,
	),
	"wave" = list(
		"x" = PORT_TYPE_NUMBER,
		"y" = PORT_TYPE_NUMBER,
		"size" = PORT_TYPE_NUMBER,
		"offset" = PORT_TYPE_NUMBER,
		"flags" = PORT_TYPE_NUMBER,
	),
))

GLOBAL_LIST_INIT(wiremod_flag_info, list(
	"Animation Easing" = list(
		"Easing Type" = list(
			"LINEAR_EASING" = LINEAR_EASING,
			"SINE_EASING" = SINE_EASING,
			"CIRCULAR_EASING" = CIRCULAR_EASING,
			"QUAD_EASING" = QUAD_EASING,
			"CUBIC_EASING" = CUBIC_EASING,
			"BOUNCE_EASING" = BOUNCE_EASING,
			"ELASTIC_EASING" = ELASTIC_EASING,
			"BACK_EASING" = BACK_EASING,
			"JUMP_EASING" = JUMP_EASING,
		),
		"EASE_IN" = EASE_IN,
		"EASE_OUT" = EASE_OUT,
	),
	"Animation Flags" = list(
		"ANIMATION_END_NOW" = ANIMATION_END_NOW,
		"ANIMATION_LINEAR_TRANSFORM" = ANIMATION_LINEAR_TRANSFORM,
		"ANIMATION_PARALLEL" = ANIMATION_PARALLEL,
		"ANIMATION_RELATIVE" = ANIMATION_RELATIVE,
	),
	"Alpha Filter Flags" = list(
		"MASK_INVERSE" = MASK_INVERSE,
		"MASK_SWAP" = MASK_SWAP,
	),
	"Color Space" = list("_" = list(
		"COLORSPACE_RGB" = COLORSPACE_RGB,
		"COLORSPACE_HSV" = COLORSPACE_HSV,
		"COLORSPACE_HSL" = COLORSPACE_HSL,
		"COLORSPACE_HCY" = COLORSPACE_HCY,
	)),
	"Layering Flags" = list("_" = list(
		"FILTER_OVERLAY" = FILTER_OVERLAY,
		"FILTER_UNDERLAY" = FILTER_UNDERLAY,
	)),
	"Layering Blend Mode" = list("_" = list(
		"BLEND_OVERLAY" = BLEND_OVERLAY,
		"BLEND_ADD" = BLEND_ADD,
		"BLEND_SUBTRACT" = BLEND_SUBTRACT,
		"BLEND_MULTIPLY" = BLEND_MULTIPLY,
		"BLEND_INSET_OVERLAY" = BLEND_INSET_OVERLAY,
	)),
	"Outline Flags" = list(
		"OUTLINE_SHARP" = OUTLINE_SHARP,
		"OUTLINE_SQUARE" = OUTLINE_SQUARE,
	),
	"Ray Flags" = list(
		"FILTER_OVERLAY" = FILTER_OVERLAY,
		"FILTER_UNDERLAY" = FILTER_UNDERLAY,
	),
	"Ripple Flags" = list(
		"WAVE_BOUNDED" = WAVE_BOUNDED,
	),
	"Wave Flags" = list(
		"WAVE_BOUNDED" = WAVE_BOUNDED,
		"WAVE_SIDEWAYS" = WAVE_SIDEWAYS,
	),
))

/obj/item/circuit_component/filter_helper
	display_name = "Filter Parameter Helper"
	desc = "Constructs a list of filter parameters from the inputs."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	var/datum/port/input/option/filter_type_port
	var/current_filter_type

	var/list/filter_params = list()

	var/datum/port/output/output_params

/obj/item/circuit_component/filter_helper/populate_options()
	filter_type_port = add_option_port("Filter Type", assoc_to_keys(GLOB.wiremod_filter_info))

/obj/item/circuit_component/filter_helper/populate_ports()
	current_filter_type = filter_type_port.value
	output_params = add_output_port("Parameters", PORT_TYPE_ASSOC_LIST(PORT_TYPE_STRING, PORT_TYPE_ANY))
	handle_filter_type_changed()

/obj/item/circuit_component/filter_helper/pre_input_received(datum/port/input/port)
	if((port == filter_type_port) && filter_type_port.value != current_filter_type)
		current_filter_type = filter_type_port.value
		handle_filter_type_changed()

/obj/item/circuit_component/filter_helper/proc/handle_filter_type_changed()
	for(var/param_name in filter_params)
		remove_input_port(filter_params[param_name])
	filter_params.Cut()
	for(var/param_name in GLOB.wiremod_filter_info[current_filter_type])
		filter_params[param_name] = add_input_port(param_name,
			GLOB.wiremod_filter_info[current_filter_type][param_name],
			default = GLOB.master_filter_info?[current_filter_type]["defaults"][param_name])

/obj/item/circuit_component/filter_helper/input_received(datum/port/input/port, list/return_values)
	var/list/new_params = list()
	for(var/param_name in filter_params)
		var/datum/port/input/param_port = filter_params[param_name]
		if(!isnull(param_port.value))
			new_params[param_name] = param_port.value

	output_params.set_value(new_params)

/obj/item/circuit_component/filter_adder
	display_name = "Add Filter"
	desc = "Adds a filter to the target atom."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	var/datum/port/input/target_port
	var/datum/port/input/filter_name
	var/datum/port/input/filter_priority
	var/datum/port/input/option/filter_type_port
	var/current_filter_type

	var/list/filter_params = list()

/obj/item/circuit_component/filter_adder/populate_options()
	filter_type_port = add_option_port("Filter Type", assoc_to_keys(GLOB.wiremod_filter_info))

/obj/item/circuit_component/filter_adder/populate_ports()
	current_filter_type = filter_type_port.value
	target_port = add_input_port("Target", PORT_TYPE_ATOM)
	filter_name = add_input_port("Filter Name", PORT_TYPE_STRING)
	filter_priority = add_input_port("Priority", PORT_TYPE_NUMBER)
	handle_filter_type_changed()

/obj/item/circuit_component/filter_adder/pre_input_received(datum/port/input/port)
	if((port == filter_type_port) && filter_type_port.value != current_filter_type)
		current_filter_type = filter_type_port.value
		handle_filter_type_changed()

/obj/item/circuit_component/filter_adder/proc/handle_filter_type_changed()
	for(var/param_name in filter_params)
		remove_input_port(filter_params[param_name])
	filter_params.Cut()
	for(var/param_name in GLOB.wiremod_filter_info[current_filter_type])
		filter_params[param_name] = add_input_port(param_name,
			GLOB.wiremod_filter_info[current_filter_type][param_name],
			order = 1.5,
			default = GLOB.master_filter_info?[current_filter_type]["defaults"][param_name])

/obj/item/circuit_component/filter_adder/input_received(datum/port/input/port, list/return_values)
	var/atom/target_atom = target_port.value
	if(!target_atom)
		return

	var/list/new_params = list()
	new_params["type"] = current_filter_type
	for(var/param_name in filter_params)
		var/datum/port/input/param_port = filter_params[param_name]
		new_params[param_name] = param_port.value

	target_atom.add_filter(filter_name.value, filter_priority.value, new_params)

/obj/item/circuit_component/filter_remover
	display_name = "Filter Remover"
	desc = "Removes the specified filter from the target."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	var/datum/port/input/target_port
	var/datum/port/input/filter_name

/obj/item/circuit_component/filter_remover/populate_ports()
	target_port = add_input_port("Target", PORT_TYPE_ATOM)
	filter_name = add_input_port("Filter Name", PORT_TYPE_STRING)

/obj/item/circuit_component/filter_remover/input_received(datum/port/input/port)
	var/atom/target_atom = target_port.value
	if(!target_atom)
		return

	target_atom.remove_filter(filter_name.value)

/obj/item/circuit_component/bitflag_helper
	display_name = "Animation & Filter Bitflag Helper"
	desc = "Allows you to construct bitflags for BYOND animation and filter parameters without having to manually search for the corresponding values."

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	var/datum/port/input/option/bitflag_port
	var/current_bitflag

	var/list/flag_ports = list()

	var/datum/port/output/output_bitflag

/obj/item/circuit_component/bitflag_helper/populate_options()
	bitflag_port = add_option_port("Bitflag Type", assoc_to_keys(GLOB.wiremod_flag_info))

/obj/item/circuit_component/bitflag_helper/populate_ports()
	current_bitflag = bitflag_port.value
	handle_bitflag_type_changed()
	output_bitflag = add_output_port("Output", PORT_TYPE_NUMBER)

/obj/item/circuit_component/bitflag_helper/pre_input_received(datum/port/input/port)
	if(port == bitflag_port && bitflag_port.value != current_bitflag)
		current_bitflag = bitflag_port.value
		handle_bitflag_type_changed()

/obj/item/circuit_component/bitflag_helper/proc/handle_bitflag_type_changed()
	for(var/flag_name in flag_ports)
		remove_input_port(flag_ports[flag_name])
	flag_ports.Cut()

	var/list/flags = GLOB.wiremod_flag_info[current_bitflag]
	for(var/flag in flags)
		if(islist(flags[flag]))
			flag_ports[flag] = add_option_port(flag == "_" ? current_bitflag : flag, flags[flag])
		else
			flag_ports[flag] = add_input_port(flag, PORT_TYPE_NUMBER)

/obj/item/circuit_component/bitflag_helper/input_received(datum/port/input/port)
	var/new_output = 0
	var/list/flags = GLOB.wiremod_flag_info[current_bitflag]
	for(var/flag in flags)
		if(!flag_ports[flag])
			continue
		else
			var/datum/port/input/flag_port = flag_ports[flag]
			if(islist(flags[flag]))
				var/list/flag_options = flags[flag]
				new_output += flag_options[flag_port.value]
			else if(flag_port.value)
				new_output += flags[flag]

	output_bitflag.set_output(new_output)


code:
/**
 * # Get Variable Component
 *
 * A component that gets a variable on an object
 */
/obj/item/circuit_component/get_variable
	display_name = "Get Variable"
	desc = "A component that gets a variable on an object."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	/// Whether to grab a global variable or a variable from this entity.
	var/datum/port/input/option/getvar_options

	/// Entity to get variable of
	var/datum/port/input/entity

	/// Expected type of output
	var/datum/port/input/option/expected_output_type

	/// Variable name
	var/datum/port/input/variable_name

	/// Variable value
	var/datum/port/output/output_value

/obj/item/circuit_component/get_variable/populate_options()
	getvar_options = add_option_port("Variable Options", list("Object", "Global"))
	expected_output_type = add_option_port("Expected Output Type", GLOB.wiremod_fundamental_types)

/obj/item/circuit_component/get_variable/populate_ports()
	entity = add_input_port("Target", PORT_TYPE_DATUM)
	variable_name = add_input_port("Variable Name", PORT_TYPE_STRING, order = 2)
	output_value = add_output_port("Output Value", PORT_TYPE_ANY, order = 2)

/obj/item/circuit_component/get_variable/pre_input_received(datum/port/input/port)
	if(port == getvar_options)
		remove_input_port(entity)
		entity = null
		if(getvar_options.value == "Object")
			entity = add_input_port("Target", PORT_TYPE_DATUM)

	if(port == expected_output_type)
		if(output_value.datatype != expected_output_type.value)
			output_value.set_datatype(expected_output_type.value)

/obj/item/circuit_component/get_variable/input_received(datum/port/input/port)
	var/atom/object = entity?.value
	if(getvar_options.value == "Global")
		object = GLOB

	var/var_name = variable_name.value
	if(!var_name || !object)
		output_value.set_output(null)
		return

	if(!object.can_vv_get(var_name) || !(var_name in object.vars))
		output_value.set_output(null)
		return

	output_value.set_output(object.vars[var_name])

repo_name: AnalWerewolf/sundown

license: mit


code:
#define COMP_INPUT_STRING "string"
#define COMP_INPUT_NUMBER "number"
#define COMP_INPUT_LIST "list"

/**
 * # Input Request Component
 *
 * Requests an input from someone.
 */
/obj/item/circuit_component/input_request
	display_name = "Input Request"
	desc = "Converts a string into a typepath. Useful for adding components."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	/// Options for input requests
	var/datum/port/input/option/input_options

	/// The input path to convert into a typepath
	var/datum/port/input/entity

	/// The custom parameter of the option
	var/datum/port/input/parameter

	/// The response from the player
	var/datum/port/output/input_response

	/// Triggered when the input is received
	var/datum/port/output/input_triggered

	/// Triggered when the player fails to give an input.
	var/datum/port/output/input_failed

/obj/item/circuit_component/input_request/populate_options()
	var/static/list/component_options = list(
		COMP_INPUT_STRING,
		COMP_INPUT_NUMBER,
		COMP_INPUT_LIST
	)

	input_options = add_option_port("Option", component_options)

/obj/item/circuit_component/input_request/populate_ports()
	entity = add_input_port("Entity", PORT_TYPE_ATOM)
	input_response = add_output_port("Response", PORT_TYPE_ANY)
	input_triggered = add_output_port("Input Sent", PORT_TYPE_SIGNAL)
	input_failed = add_output_port("Input Failed", PORT_TYPE_SIGNAL)

	update_options()

/obj/item/circuit_component/input_request/input_received(datum/port/input/port)
	var/mob/player = entity.value
	if(!istype(player))
		return

	INVOKE_ASYNC(src, .proc/request_input_from_player, player)

/obj/item/circuit_component/input_request/proc/request_input_from_player(mob/player)
	var/new_option = input_options.value
	switch(new_option)
		if(COMP_INPUT_STRING)
			var/player_input = input(player, "Input a value", "Input value") as null|text
			if(!player_input)
				return
			input_response.set_output(player_input)
		if(COMP_INPUT_NUMBER)
			var/player_input = input(player, "Input a value", "Input value") as null|num
			if(!player_input)
				return
			input_response.set_output(player_input)
		if(COMP_INPUT_LIST)
			var/list/data = parameter.value
			if(!islist(data))
				return
			var/player_input = input(player, "Input a value", "Input value") as null|anything in data
			if(!player_input)
				return
			input_response.set_output(player_input)
	input_triggered.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/input_request/pre_input_received(datum/port/input/port)
	if(port == input_options)
		update_options(port)

/obj/item/circuit_component/input_request/proc/update_options(datum/port/input/port)
	var/new_option = input_options.value
	if(parameter)
		remove_input_port(parameter)
		parameter = null

	switch(new_option)
		if(COMP_INPUT_STRING)
			input_response.set_datatype(PORT_TYPE_STRING)
		if(COMP_INPUT_NUMBER)
			input_response.set_datatype(PORT_TYPE_NUMBER)
		if(COMP_INPUT_LIST)
			parameter = add_input_port("Options List", PORT_TYPE_LIST(PORT_TYPE_ANY))
			input_response.set_datatype(PORT_TYPE_STRING)

#undef COMP_INPUT_STRING
#undef COMP_INPUT_NUMBER
#undef COMP_INPUT_LIST


code:
#define COMP_PROC_GLOBAL "Global"
#define COMP_PROC_OBJECT "Object"

/**
 * # Proc Call Component
 *
 * A component that calls a proc on an object and outputs the return value
 */
/obj/item/circuit_component/proccall
	display_name = "Proc Call"
	desc = "A component that calls a proc on an object."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	var/datum/port/input/option/proccall_options

	/// Expected type of output
	var/datum/port/input/option/expected_output_type

	/// Entity to proccall on
	var/datum/port/input/entity

	/// Proc to call
	var/datum/port/input/proc_name

	/// Arguments
	var/datum/port/input/arguments

	/// Returns the output from the proccall
	var/datum/port/output/output_value

/obj/item/circuit_component/proccall/populate_options()
	var/static/list/component_options = list(
		COMP_PROC_OBJECT,
		COMP_PROC_GLOBAL,
	)

	proccall_options = add_option_port("Proccall Options", component_options)

	expected_output_type = add_option_port("Expected Output Type", GLOB.wiremod_fundamental_types)

/obj/item/circuit_component/proccall/populate_ports()
	entity = add_input_port("Target", PORT_TYPE_DATUM)
	proc_name = add_input_port("Proc Name", PORT_TYPE_STRING)
	arguments = add_input_port("Arguments", PORT_TYPE_LIST(PORT_TYPE_ANY))

	output_value = add_output_port("Output Value", PORT_TYPE_ANY)

/obj/item/circuit_component/proccall/pre_input_received(datum/port/input/port)
	if(port == expected_output_type)
		if(output_value.datatype != expected_output_type.value)
			output_value.set_datatype(expected_output_type.value)

/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
	else
		called_on = GLOBAL_PROC

	if(!called_on)
		return

	var/to_invoke = proc_name.value
	var/list/params = arguments.value || list()

	if(!to_invoke)
		return

	if(called_on != GLOBAL_PROC && !hascall(called_on, to_invoke))
		return

	INVOKE_ASYNC(src, .proc/do_proccall, called_on, to_invoke, recursive_list_resolve(params))

/obj/item/circuit_component/proccall/proc/do_proccall(called_on, to_invoke, params)
	var/result = HandleUserlessProcCall(parent.get_creator(), called_on, to_invoke, params)
	output_value.set_output(result)

#undef COMP_PROC_GLOBAL
#undef COMP_PROC_OBJECT


code:
/**
 * # Save Shell Component
 *
 * A component that saves and loads a shell for the integrated circuit.
 */
/obj/item/circuit_component/save_shell
	display_name = "Save Shell"
	desc = "A component that saves a shell."
	circuit_flags = CIRCUIT_FLAG_ADMIN

	/// Returns the output from the proccall
	var/datum/port/output/on_loaded

	var/atom/movable/loaded_shell

/obj/item/circuit_component/save_shell/populate_ports()
	on_loaded = add_output_port("On Loaded", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/save_shell/add_to(obj/item/integrated_circuit/added_to)
	. = ..()
	RegisterSignal(added_to, COMSIG_CIRCUIT_POST_LOAD, .proc/on_post_load)
	RegisterSignal(added_to, COMSIG_CIRCUIT_PRE_SAVE_TO_JSON, .proc/on_pre_save_to_json)

/obj/item/circuit_component/save_shell/removed_from(obj/item/integrated_circuit/removed_from)
	UnregisterSignal(removed_from, list(COMSIG_CIRCUIT_POST_LOAD, COMSIG_CIRCUIT_PRE_SAVE_TO_JSON))
	return ..()

/obj/item/circuit_component/save_shell/proc/on_post_load(datum/source)
	SIGNAL_HANDLER
	loaded_shell.AddComponent(/datum/component/shell, starting_circuit = parent)
	on_loaded.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/save_shell/proc/on_pre_save_to_json(datum/source, list/general_data)
	SIGNAL_HANDLER
	// We're custom saving the shell, disable any USB cable connections and default shell components.
	general_data["external_objects"] = list()

/obj/item/circuit_component/save_shell/save_data_to_list(list/component_data)
	. = ..()
	var/atom/movable/shell = parent.shell
	component_data["shell_type"] = shell.type
	var/list/shell_variables = list()
	for(var/variable in shell.vars - GLOB.duplicate_forbidden_vars)
		var/variable_data = shell.vars[variable]
		if(!istext(variable_data) && !isnum(variable_data))
			continue
		shell_variables[variable] = variable_data

	component_data["shell_variables"] = shell_variables

/obj/item/circuit_component/save_shell/load_data_from_list(list/component_data)
	if(parent.shell)
		return

	var/shell_type = text2path(component_data["shell_type"])
	if(!shell_type || !ispath(shell_type, /atom))
		return ..()

	loaded_shell = new shell_type(drop_location())
	if(!loaded_shell)
		return
	loaded_shell.datum_flags |= DF_VAR_EDITED

	var/list/shell_variables = component_data["shell_variables"]
	for(var/variable in shell_variables - GLOB.duplicate_forbidden_vars)
		var/variable_data = shell_variables[variable]
		loaded_shell.vv_edit_var(variable, variable_data)
	return ..()


code:
/**
 * # SDQL Component
 *
 * A component that performs an sdql operation
 */
/obj/item/circuit_component/sdql_operation
	display_name = "SDQL Operation"
	desc = "A component that performs an SDQL operation when invoked."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	/// SDQL Operation to invoke
	var/datum/port/input/sdql_operation

	var/datum/port/output/results

/obj/item/circuit_component/sdql_operation/populate_ports()
	sdql_operation = add_input_port("SDQL String", PORT_TYPE_STRING)
	results = add_output_port("Result", PORT_TYPE_LIST(PORT_TYPE_STRING))

/obj/item/circuit_component/sdql_operation/input_received(datum/port/input/port)
	if(GLOB.AdminProcCaller)
		return TRUE

	INVOKE_ASYNC(src, .proc/execute_sdql, port)

/obj/item/circuit_component/sdql_operation/proc/execute_sdql(datum/port/input/port)
	var/operation = sdql_operation.value

	if(!operation)
		return

	var/result = HandleUserlessSDQL(parent.get_creator(), operation)
	results.set_output(result)


code:
/**
 * # Set Variable Component
 *
 * A component that sets a variable on an object
 */
/obj/item/circuit_component/set_variable
	display_name = "Set Variable"
	desc = "A component that sets a variable on an object."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	/// Entity to set variable of
	var/datum/port/input/entity

	/// Variable name
	var/datum/port/input/variable_name

	/// New value to set the variable name to.
	var/datum/port/input/new_value

/obj/item/circuit_component/set_variable/populate_ports()
	entity = add_input_port("Target", PORT_TYPE_DATUM)
	variable_name = add_input_port("Variable Name", PORT_TYPE_STRING)
	new_value = add_input_port("New Value", PORT_TYPE_ANY)

/obj/item/circuit_component/set_variable/input_received(datum/port/input/port)
	var/atom/object = entity.value
	var/var_name = variable_name.value
	if(!var_name || !object)
		return

	var/resolved_new_value = new_value.value
	if(islist(resolved_new_value))
		var/list/to_resolve = resolved_new_value
		resolved_new_value = recursive_list_resolve(to_resolve)

	object.vv_edit_var(var_name, resolved_new_value)


code:
#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."
	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

	/// Whether this executes instantly or not. If set to 0, this will not execute instantly.
	var/datum/port/input/instant

	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 on the target.
	var/datum/port/input/unregister
	/// Unregisters the signal from everyone.
	var/datum/port/input/unregister_all

	/// 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 entities that have the signal registered on it
	var/list/datum/weakref/registered_entities = list()
	/// 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()
	instant = add_input_port("Instant", PORT_TYPE_NUMBER, order = 0.5, trigger = null, default = 1)
	register = add_input_port("Register", PORT_TYPE_SIGNAL, order = 2, trigger = .proc/register_signals)
	unregister = add_input_port("Unregister", PORT_TYPE_SIGNAL, order = 2, trigger = .proc/unregister_signals)
	unregister_all = add_input_port("Unregister All", PORT_TYPE_SIGNAL, order = 2, trigger = .proc/unregister_signals_all)

	add_source_entity()
	event_triggered = add_output_port("Triggered", PORT_TYPE_INSTANT_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_DATUM, order = 1, trigger = null)
	entity = add_output_port("Source Entity", PORT_TYPE_DATUM, 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.value != registered_signal)
		custom_signal = FALSE
		unregister_signals_all(port)
		registered_signal = signal_id.value
		var/list/data = signal_map[registered_signal]
		if(data)
			load_new_ports(data)

	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)
			target = null
			entity = null
		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_all(port)

/obj/item/circuit_component/signal_handler/proc/register_signals(datum/port/input/port)
	CIRCUIT_TRIGGER
	var/datum/target_datum = target?.value
	if(signal_handler_options.value == COMP_SIGNAL_HANDLER_GLOBAL)
		target_datum = SSdcs

	if(target_datum)
		// We override because an admin may try registering a signal on the same object/datum again, so this prevents any runtimes from occuring
		RegisterSignal(target_datum, registered_signal, .proc/handle_signal_received, override = TRUE)
		registered_entities |= 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 = 3, 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_all(datum/port/input/port)
	CIRCUIT_TRIGGER
	for(var/datum/weakref/weakref_of_object as anything in registered_entities)
		var/datum/datum_to_unregister = weakref_of_object.resolve()
		if(!datum_to_unregister)
			continue
		UnregisterSignal(datum_to_unregister, registered_signal)
	registered_entities.Cut()

/obj/item/circuit_component/signal_handler/proc/unregister_signals(datum/port/input/port)
	CIRCUIT_TRIGGER

	var/datum/registered_datum = target?.value
	if(signal_handler_options.value == COMP_SIGNAL_HANDLER_GLOBAL)
		registered_datum = SSdcs

	if(!registered_datum)
		return

	UnregisterSignal(registered_datum, registered_signal)
	registered_entities -= WEAKREF(registered_datum)

/obj/item/circuit_component/signal_handler/proc/run_ports_on_args(list/arguments)
	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)

/obj/item/circuit_component/signal_handler/proc/handle_signal_received(...)
	SIGNAL_HANDLER
	var/list/arguments = args.Copy()

	if(!instant.value)
		run_ports_on_args(arguments)
		return

	// 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()
	run_ports_on_args(arguments)
	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


code:
/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/secondary_cancel_attack = circuit_signal_response("Cancel Attack", COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN)
	var/secondary_continue_attack = circuit_signal_response("Continue Attack", COMPONENT_SECONDARY_CONTINUE_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(secondary_cancel_attack, secondary_continue_attack, target, user),
		COMSIG_ITEM_PRE_ATTACK_SECONDARY = list(secondary_cancel_attack, secondary_continue_attack, target, user),
		COMSIG_ITEM_AFTERATTACK_SECONDARY = list(secondary_cancel_attack, secondary_continue_attack, 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]")


code:
/**
 * # Spawn Atom Component
 *
 * Spawns an atom.
 */
/obj/item/circuit_component/spawn_atom
	display_name = "Spawn Atom"
	desc = "Spawns an atom at a desired location"
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	/// The input path to convert into a typepath
	var/datum/port/input/input_path

	/// The turf to spawn them at
	var/datum/port/input/spawn_at

	/// Parameters to pass to the atom being spawned
	var/datum/port/input/parameters

	/// The result from the output
	var/datum/port/output/spawned_atom

/obj/item/circuit_component/spawn_atom/populate_ports()
	input_path = add_input_port("Type", PORT_TYPE_ANY)
	spawn_at = add_input_port("Spawn At", PORT_TYPE_ATOM)
	parameters = add_input_port("Parameters", PORT_TYPE_LIST(PORT_TYPE_ANY))

	spawned_atom = add_output_port("Spawned Atom", PORT_TYPE_ATOM)

/obj/item/circuit_component/spawn_atom/input_received(datum/port/input/port)

	var/typepath = input_path.value

	if(!ispath(typepath, /atom))
		return

	var/list/params = parameters.value
	if(!params)
		params = list()

	var/list/resolved_params = recursive_list_resolve(params)

	resolved_params.Insert(1, spawn_at.value)

	var/atom/spawned = new typepath(arglist(resolved_params))
	spawned.datum_flags |= DF_VAR_EDITED
	spawned_atom.set_output(spawned)


code:
/**
 * # To Type Component
 *
 * Converts a string into a typepath. Useful for adding components.
 */
/obj/item/circuit_component/to_type
	display_name = "String To Type"
	desc = "Converts a string into a typepath. Useful for adding components."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL|CIRCUIT_FLAG_ADMIN

	/// The input path to convert into a typepath
	var/datum/port/input/input_path

	/// The type output
	var/datum/port/output/type_output

/obj/item/circuit_component/to_type/populate_ports()
	input_path = add_input_port("Type", PORT_TYPE_STRING)
	type_output = add_output_port("Typepath", PORT_TYPE_ANY)

/obj/item/circuit_component/to_type/input_received(datum/port/input/port)

	type_output.set_output(text2path(input_path.value))


code:
/**
 * # Direction Component
 *
 * Return the direction of a mob relative to the component
 */
/obj/item/circuit_component/direction
	display_name = "Get Direction"
	desc = "A component that returns the direction of itself and an entity."

	/// The input port
	var/datum/port/input/input_port

	/// The result from the output
	var/datum/port/output/output

	// Directions outputs
	var/datum/port/output/north
	var/datum/port/output/south
	var/datum/port/output/east
	var/datum/port/output/west

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	/// Maximum range for a valid direction to be returned
	var/max_range = 7

/obj/item/circuit_component/direction/get_ui_notices()
	. = ..()
	. += create_ui_notice("Maximum Range: [max_range] tiles", "orange", "info")

/obj/item/circuit_component/direction/populate_ports()
	input_port = add_input_port("Organism", PORT_TYPE_ATOM)

	output = add_output_port("Direction", PORT_TYPE_STRING)

	north = add_output_port("North", PORT_TYPE_SIGNAL)
	east = add_output_port("East", PORT_TYPE_SIGNAL)
	south = add_output_port("South", PORT_TYPE_SIGNAL)
	west = add_output_port("West", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/direction/input_received(datum/port/input/port)

	var/atom/object = input_port.value
	if(!object)
		return
	var/turf/location = get_turf(src)

	if(object.z != location.z || get_dist(location, object) > max_range)
		output.set_output(null)
		return

	var/direction = get_dir(location, get_turf(object))
	output.set_output(dir2text(direction))

	if(direction & NORTH)
		north.set_output(COMPONENT_SIGNAL)
	if(direction & SOUTH)
		south.set_output(COMPONENT_SIGNAL)
	if(direction & EAST)
		east.set_output(COMPONENT_SIGNAL)
	if(direction & WEST)
		west.set_output(COMPONENT_SIGNAL)


code:
/**
 * # GPS Component
 *
 * Return the location of this
 */
/obj/item/circuit_component/gps
	display_name = "Internal GPS"
	desc = "A component that returns the xyz co-ordinates of itself."

	/// The result from the output
	var/datum/port/output/x_pos
	var/datum/port/output/y_pos
	var/datum/port/output/z_pos

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/gps/populate_ports()
	x_pos = add_output_port("X", PORT_TYPE_NUMBER)
	y_pos = add_output_port("Y", PORT_TYPE_NUMBER)
	z_pos = add_output_port("Z", PORT_TYPE_NUMBER)

/obj/item/circuit_component/gps/input_received(datum/port/input/port)

	var/turf/location = get_turf(src)

	x_pos.set_output(location?.x)
	y_pos.set_output(location?.y)
	z_pos.set_output(location?.z)


code:
/**
 * # Get Health Component
 *
 * Return the health of a mob
 */
/obj/item/circuit_component/health
	display_name = "Get Health"
	desc = "A component that returns the health of an organism."

	/// The input port
	var/datum/port/input/input_port

	/// Brute damage
	var/datum/port/output/brute
	/// Burn damage
	var/datum/port/output/burn
	/// Toxin damage
	var/datum/port/output/toxin
	/// Oxyloss damage
	var/datum/port/output/oxy
	/// Health
	var/datum/port/output/health

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	var/max_range = 5

/obj/item/circuit_component/health/get_ui_notices()
	. = ..()
	. += create_ui_notice("Maximum Range: [max_range] tiles", "orange", "info")

/obj/item/circuit_component/health/populate_ports()
	input_port = add_input_port("Organism", PORT_TYPE_ATOM)

	brute = add_output_port("Brute Damage", PORT_TYPE_NUMBER)
	burn = add_output_port("Burn Damage", PORT_TYPE_NUMBER)
	toxin = add_output_port("Toxin Damage", PORT_TYPE_NUMBER)
	oxy = add_output_port("Suffocation Damage", PORT_TYPE_NUMBER)
	health = add_output_port("Health", PORT_TYPE_NUMBER)

/obj/item/circuit_component/health/input_received(datum/port/input/port)

	var/mob/living/organism = input_port.value
	var/turf/current_turf = get_turf(src)
	if(!istype(organism) || get_dist(current_turf, organism) > max_range || current_turf.z != organism.z)
		brute.set_output(null)
		burn.set_output(null)
		toxin.set_output(null)
		oxy.set_output(null)
		health.set_output(null)
		return

	brute.set_output(organism.getBruteLoss())
	burn.set_output(organism.getFireLoss())
	toxin.set_output(organism.getToxLoss())
	oxy.set_output(organism.getOxyLoss())
	health.set_output(organism.health)


code:
/**
 * # Hear Component
 *
 * Listens for messages. Requires a shell.
 */
/obj/item/circuit_component/hear
	display_name = "Voice Activator"
	desc = "A component that listens for messages. Requires a shell."

	/// The message heard
	var/datum/port/output/message_port
	/// The language heard
	var/datum/port/output/language_port
	/// The speaker name port, usually the name of the person who spoke.
	var/datum/port/output/speaker_name
	/// The speaker entity that is currently speaking. Not necessarily the person who is speaking.
	var/datum/port/output/speaker_port
	/// The trigger sent when this event occurs
	var/datum/port/output/trigger_port

/obj/item/circuit_component/hear/populate_ports()
	message_port = add_output_port("Message", PORT_TYPE_STRING)
	language_port = add_output_port("Language", PORT_TYPE_STRING)
	speaker_port = add_output_port("Speaker", PORT_TYPE_ATOM)
	speaker_name = add_output_port("Speaker Name", PORT_TYPE_STRING)
	trigger_port = add_output_port("Triggered", PORT_TYPE_SIGNAL)
	become_hearing_sensitive(ROUNDSTART_TRAIT)

/obj/item/circuit_component/hear/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods)
	if(speaker == parent?.shell)
		return

	message_port.set_output(raw_message)
	if(message_language)
		language_port.set_output(initial(message_language.name))
	speaker_port.set_output(speaker)
	speaker_name.set_output(speaker.GetVoice())
	trigger_port.set_output(COMPONENT_SIGNAL)


code:
/**
 * # Proximity Pinpointer Component
 *
 * Return the location of its input.
 */
/obj/item/circuit_component/pinpointer
	display_name = "Proximity Pinpointer"
	desc = "A component that returns the xyz co-ordinates of its entity input, as long as its in view."

	var/datum/port/input/target

	var/datum/port/output/x_pos
	var/datum/port/output/y_pos
	var/datum/port/output/z_pos
	var/datum/port/output/on_error

	var/max_range = 7

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/pinpointer/populate_ports()
	target = add_input_port("Target entity", PORT_TYPE_ATOM, FALSE)

	x_pos = add_output_port("X", PORT_TYPE_NUMBER)
	y_pos = add_output_port("Y", PORT_TYPE_NUMBER)
	z_pos = add_output_port("Z", PORT_TYPE_NUMBER)
	on_error = add_output_port("Failed", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/pinpointer/input_received(datum/port/input/port)

	if(isnull(target.value))
		x_pos.set_output(null)
		y_pos.set_output(null)
		z_pos.set_output(null)
		on_error.set_output(COMPONENT_SIGNAL)
		return

	var/atom/target_entity = target.value

	if(is_in_sight(target_entity, get_turf(src)) && IN_GIVEN_RANGE(get_turf(src), target_entity, max_range))
		var/turf/location = get_turf(target_entity)

		x_pos.set_output(location?.x)
		y_pos.set_output(location?.y)
		z_pos.set_output(location?.z)
	else
		x_pos.set_output(null)
		y_pos.set_output(null)
		z_pos.set_output(null)
		on_error.set_output(COMPONENT_SIGNAL)
		return TRUE


code:
/**
 * # Self Component
 *
 * Return the current shell.
 */
/obj/item/circuit_component/self
	display_name = "Self"
	desc = "A component that returns the current shell."

	/// 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("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)


code:
/**
 * # Get Species Component
 *
 * Return the species of a mob
 */
/obj/item/circuit_component/species
	display_name = "Get Species"
	desc = "A component that returns the species of its input."

	/// The input port
	var/datum/port/input/input_port

	/// The result from the output
	var/datum/port/output/output

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/species/populate_ports()
	input_port = add_input_port("Organism", PORT_TYPE_ATOM)

	output = add_output_port("Species", PORT_TYPE_STRING)

/obj/item/circuit_component/species/input_received(datum/port/input/port)

	var/mob/living/carbon/human/human = input_port.value
	if(!istype(human) || !human.has_dna())
		output.set_output(null)
		return

	output.set_output(human.dna.species.name)


code:
#define COMP_BAR_OVERLAY_VERTICAL "Vertical"
#define COMP_BAR_OVERLAY_HORIZONTAL "Horizontal"

/**
 * # Bar Overlay Component
 *
 * Basically an advanced verion of object overlay component that shows a horizontal/vertical bar.
 * Requires a BCI shell.
 */

/obj/item/circuit_component/object_overlay/bar
	display_name = "Bar Overlay"
	desc = "Requires a BCI shell. A component that shows a bar overlay ontop of an object from a range of 0 to 100."

	var/datum/port/input/option/bar_overlay_options
	var/datum/port/input/bar_number

	var/overlay_limit = 10

/obj/item/circuit_component/object_overlay/bar/populate_ports()
	bar_number = add_input_port("Number", PORT_TYPE_NUMBER)

/obj/item/circuit_component/object_overlay/bar/populate_options()
	var/static/component_options_bar = list(
		COMP_BAR_OVERLAY_VERTICAL = "barvert",
		COMP_BAR_OVERLAY_HORIZONTAL = "barhoriz"
	)
	bar_overlay_options = add_option_port("Bar Overlay Options", component_options_bar)
	options_map = component_options_bar

/obj/item/circuit_component/object_overlay/bar/show_to_owner(atom/target_atom, mob/living/owner)
	if(LAZYLEN(active_overlays) >= overlay_limit)
		return

	var/current_option = bar_overlay_options.value

	if(active_overlays[target_atom])
		QDEL_NULL(active_overlays[target_atom])

	var/number_clear = clamp(bar_number.value, 0, 100)
	if(current_option == COMP_BAR_OVERLAY_HORIZONTAL)
		number_clear = round(number_clear / 6.25) * 6.25
	else if(current_option == COMP_BAR_OVERLAY_VERTICAL)
		number_clear = round(number_clear / 10) * 10
	var/image/cool_overlay = image(icon = 'icons/hud/screen_bci.dmi', loc = target_atom, icon_state = "[options_map[current_option]][number_clear]", layer = RIPPLE_LAYER)

	if(image_pixel_x.value)
		cool_overlay.pixel_x = image_pixel_x.value

	if(image_pixel_y.value)
		cool_overlay.pixel_y = image_pixel_y.value

	active_overlays[target_atom] = WEAKREF(target_atom.add_alt_appearance(
		/datum/atom_hud/alternate_appearance/basic/one_person,
		"bar_overlay_[REF(src)]",
		cool_overlay,
		owner,
	))

#undef COMP_BAR_OVERLAY_VERTICAL
#undef COMP_BAR_OVERLAY_HORIZONTAL


code:
/**
 * # Counter Overlay Component
 *
 * Shows an counter overlay.
 * Requires a BCI shell.
 */

/obj/item/circuit_component/counter_overlay
	display_name = "Counter Overlay"
	desc = "A component that shows an three digit counter. Requires a BCI shell."

	required_shells = list(/obj/item/organ/cyberimp/bci)

	var/datum/port/input/counter_number

	var/datum/port/input/image_pixel_x
	var/datum/port/input/image_pixel_y

	var/datum/port/input/signal_update

	var/obj/item/organ/cyberimp/bci/bci
	var/list/numbers = list()
	var/counter_appearance

/obj/item/circuit_component/counter_overlay/populate_ports()
	counter_number = add_input_port("Displayed Number", PORT_TYPE_NUMBER)

	signal_update = add_input_port("Update Overlay", PORT_TYPE_SIGNAL)

	image_pixel_x = add_input_port("X-Axis Shift", PORT_TYPE_NUMBER)
	image_pixel_y = add_input_port("Y-Axis Shift", PORT_TYPE_NUMBER)

/obj/item/circuit_component/counter_overlay/register_shell(atom/movable/shell)
	if(istype(shell, /obj/item/organ/cyberimp/bci))
		bci = shell
		RegisterSignal(shell, COMSIG_ORGAN_REMOVED, .proc/on_organ_removed)

/obj/item/circuit_component/counter_overlay/unregister_shell(atom/movable/shell)
	bci = null
	QDEL_NULL(counter_appearance)
	for(var/number in numbers)
		QDEL_NULL(number)
	UnregisterSignal(shell, COMSIG_ORGAN_REMOVED)

/obj/item/circuit_component/counter_overlay/input_received(datum/port/input/port)
	if(!bci)
		return

	var/mob/living/owner = bci.owner

	if(!owner || !istype(owner) || !owner.client)
		return

	for(var/number in numbers)
		QDEL_NULL(number)
	numbers = list()

	QDEL_NULL(counter_appearance)
	var/image/counter = image(icon = 'icons/hud/screen_bci.dmi', icon_state = "hud_numbers", loc = owner)
	if(image_pixel_x.value)
		counter.pixel_x = image_pixel_x.value
	if(image_pixel_y.value)
		counter.pixel_y = image_pixel_y.value

	counter_appearance = WEAKREF(owner.add_alt_appearance(
		/datum/atom_hud/alternate_appearance/basic/one_person,
		"counter_overlay_[REF(src)]",
		counter,
		owner,
	))

	var/cleared_number = clamp(round(counter_number.value), 0, 999)

	for(var/i = 1 to 3)
		var/cur_num = round(cleared_number / (10 ** (3 - i))) % 10
		var/image/number = image(icon = 'icons/hud/screen_bci.dmi', icon_state = "hud_number_[cur_num]", loc = owner)

		if(image_pixel_x.value)
			number.pixel_x = image_pixel_x.value + (i - 1) * 9
		if(image_pixel_y.value)
			number.pixel_y = image_pixel_y.value

		numbers.Add(WEAKREF(owner.add_alt_appearance(
			/datum/atom_hud/alternate_appearance/basic/one_person,
			"counter_overlay_[REF(src)]_[i]",
			number,
			owner,
		)))

/obj/item/circuit_component/counter_overlay/proc/on_organ_removed(datum/source, mob/living/carbon/owner)
	SIGNAL_HANDLER
	QDEL_NULL(counter_appearance)
	for(var/number in numbers)
		QDEL_NULL(number)

/obj/item/circuit_component/counter_overlay/Destroy()
	QDEL_NULL(counter_appearance)
	for(var/number in numbers)
		QDEL_NULL(number)
	return ..()


code:
/**
 * # Object Overlay Component
 *
 * Shows an overlay ontop of an object. Toggleable.
 * Requires a BCI shell.
 */

#define OBJECT_OVERLAY_LIMIT 10

/obj/item/circuit_component/object_overlay
	display_name = "Object Overlay"
	desc = "Requires a BCI shell. A component that shows an overlay on top of an object."

	required_shells = list(/obj/item/organ/cyberimp/bci)

	var/datum/port/input/option/object_overlay_options

	/// Target atom
	var/datum/port/input/target

	var/datum/port/input/image_pixel_x
	var/datum/port/input/image_pixel_y

	/// On/Off signals
	var/datum/port/input/signal_on
	var/datum/port/input/signal_off

	var/obj/item/organ/cyberimp/bci/bci
	var/list/active_overlays = list()
	var/list/options_map

/obj/item/circuit_component/object_overlay/populate_ports()
	target = add_input_port("Target", PORT_TYPE_ATOM)

	signal_on = add_input_port("Create Overlay", PORT_TYPE_SIGNAL)
	signal_off = add_input_port("Remove Overlay", PORT_TYPE_SIGNAL)

	image_pixel_x = add_input_port("X-Axis Shift", PORT_TYPE_NUMBER)
	image_pixel_y = add_input_port("Y-Axis Shift", PORT_TYPE_NUMBER)

/obj/item/circuit_component/object_overlay/Destroy()
	for(var/active_overlay in active_overlays)
		QDEL_NULL(active_overlay)
	return ..()

/obj/item/circuit_component/object_overlay/populate_options()
	var/static/component_options = list(
		"Corners (Blue)" = "hud_corners",
		"Corners (Red)" = "hud_corners_red",
		"Circle (Blue)" = "hud_circle",
		"Circle (Red)" = "hud_circle_red",
		"Small Corners (Blue)" = "hud_corners_small",
		"Small Corners (Red)" = "hud_corners_small_red",
		"Triangle (Blue)" = "hud_triangle",
		"Triangle (Red)" = "hud_triangle_red",
		"HUD mark (Blue)" = "hud_mark",
		"HUD mark (Red)" = "hud_mark_red"
	)
	object_overlay_options = add_option_port("Object", component_options)
	options_map = component_options

/obj/item/circuit_component/object_overlay/register_shell(atom/movable/shell)
	if(istype(shell, /obj/item/organ/cyberimp/bci))
		bci = shell
		RegisterSignal(shell, COMSIG_ORGAN_REMOVED, .proc/on_organ_removed)

/obj/item/circuit_component/object_overlay/unregister_shell(atom/movable/shell)
	bci = null
	UnregisterSignal(shell, COMSIG_ORGAN_REMOVED)

/obj/item/circuit_component/object_overlay/input_received(datum/port/input/port)
	if(!bci)
		return

	var/mob/living/owner = bci.owner
	var/atom/target_atom = target.value

	if(!owner || !istype(owner) || !owner.client || !target_atom)
		return

	if(COMPONENT_TRIGGERED_BY(signal_on, port))
		show_to_owner(target_atom, owner)

	if(COMPONENT_TRIGGERED_BY(signal_off, port) && (target_atom in active_overlays))
		QDEL_NULL(active_overlays[target_atom])
		active_overlays.Remove(target_atom)

/obj/item/circuit_component/object_overlay/proc/show_to_owner(atom/target_atom, mob/living/owner)
	if(LAZYLEN(active_overlays) >= OBJECT_OVERLAY_LIMIT)
		return

	if(active_overlays[target_atom])
		QDEL_NULL(active_overlays[target_atom])

	var/image/cool_overlay = image(icon = 'icons/hud/screen_bci.dmi', loc = target_atom, icon_state = options_map[object_overlay_options.value], layer = RIPPLE_LAYER)

	if(image_pixel_x.value)
		cool_overlay.pixel_x = image_pixel_x.value

	if(image_pixel_y.value)
		cool_overlay.pixel_y = image_pixel_y.value

	var/alt_appearance = WEAKREF(target_atom.add_alt_appearance(
		/datum/atom_hud/alternate_appearance/basic/one_person,
		"object_overlay_[REF(src)]",
		cool_overlay,
		owner,
	))

	active_overlays[target_atom] = alt_appearance

/obj/item/circuit_component/object_overlay/proc/on_organ_removed(datum/source, mob/living/carbon/owner)
	SIGNAL_HANDLER

	for(var/atom/target_atom in active_overlays)
		QDEL_NULL(active_overlays[target_atom])
		active_overlays.Remove(target_atom)

#undef OBJECT_OVERLAY_LIMIT


code:
/**
 * # Target Intercept Component
 *
 * When activated intercepts next click and outputs clicked atom.
 * Requires a BCI shell.
 */

/obj/item/circuit_component/target_intercept
	display_name = "Target Intercept"
	desc = "Requires a BCI shell. When activated, this component will allow user to target an object using their brain and will output the reference to said object."

	required_shells = list(/obj/item/organ/cyberimp/bci)

	var/datum/port/output/clicked_atom

	var/obj/item/organ/cyberimp/bci/bci
	var/intercept_cooldown = 1 SECONDS

/obj/item/circuit_component/target_intercept/populate_ports()
	trigger_input = add_input_port("Activate", PORT_TYPE_SIGNAL)
	trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL)
	clicked_atom = add_output_port("Targeted Object", PORT_TYPE_ATOM)

/obj/item/circuit_component/target_intercept/register_shell(atom/movable/shell)
	if(istype(shell, /obj/item/organ/cyberimp/bci))
		bci = shell
		RegisterSignal(shell, COMSIG_ORGAN_REMOVED, .proc/on_organ_removed)

/obj/item/circuit_component/target_intercept/unregister_shell(atom/movable/shell)
	bci = null
	UnregisterSignal(shell, COMSIG_ORGAN_REMOVED)

/obj/item/circuit_component/target_intercept/input_received(datum/port/input/port)
	if(!bci)
		return

	var/mob/living/owner = bci.owner
	if(!owner || !istype(owner) || !owner.client)
		return

	if(TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_TARGET_INTERCEPT))
		return

	to_chat(owner, "<B>Left-click to trigger target interceptor!</B>")
	owner.client.click_intercept = src

/obj/item/circuit_component/target_intercept/proc/on_organ_removed(datum/source, mob/living/carbon/owner)
	SIGNAL_HANDLER

	if(owner.client && owner.client.click_intercept == src)
		owner.client.click_intercept = null

/obj/item/circuit_component/target_intercept/proc/InterceptClickOn(mob/user, params, atom/object)
	user.client.click_intercept = null
	clicked_atom.set_output(object)
	trigger_output.set_output(COMPONENT_SIGNAL)
	TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_TARGET_INTERCEPT, intercept_cooldown)

/obj/item/circuit_component/target_intercept/get_ui_notices()
	. = ..()
	. += create_ui_notice("Target Interception Cooldown: [DisplayTimeText(intercept_cooldown)]", "orange", "stopwatch")


code:
/**
 * # Assoc List Literal Component
 *
 * Return an associative list literal.
 */
/obj/item/circuit_component/list_literal/assoc_literal
	display_name = "Associative List Literal"
	desc = "A component that returns an associative list consisting of the inputs."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	/// The inputs used to create the list
	var/list/datum/port/input/key_ports = list()

/obj/item/circuit_component/list_literal/assoc_literal/clear_lists()
	for(var/datum/port/input/port as anything in key_ports)
		remove_input_port(port)
	for(var/datum/port/input/port as anything in entry_ports)
		remove_input_port(port)
	key_ports = list()
	entry_ports = list()
	length = 0

/obj/item/circuit_component/list_literal/assoc_literal/remove_one_entry()
	var/index = length(entry_ports)
	var/key_port = key_ports[index]
	key_ports -= key_port
	remove_input_port(key_port)
	var/value_port = entry_ports[index]
	entry_ports -= value_port
	remove_input_port(value_port)
	length--

/obj/item/circuit_component/list_literal/assoc_literal/pre_input_received(datum/port/input/port)
	if(port == list_options)
		var/new_datatype = list_options.value
		list_output.set_datatype(PORT_TYPE_ASSOC_LIST(PORT_TYPE_STRING, new_datatype))
		for(var/datum/port/input/port_to_set as anything in entry_ports)
			port_to_set.set_datatype(new_datatype)

/obj/item/circuit_component/list_literal/assoc_literal/add_one_entry()
	length++
	key_ports += add_input_port("Key [length]", PORT_TYPE_STRING)
	entry_ports += add_input_port("Index [length]", PORT_TYPE_ANY)

/obj/item/circuit_component/list_literal/assoc_literal/populate_ports()
	set_list_size(default_list_size)
	list_output = add_output_port("Value", PORT_TYPE_ASSOC_LIST(PORT_TYPE_STRING, PORT_TYPE_ANY))

/obj/item/circuit_component/list_literal/assoc_literal/input_received(datum/port/input/port)
	var/list/new_literal = list()
	for(var/index in 1 to length)
		// To prevent people from infinitely making lists to crash the server
		if(islist(entry_ports[index].value) && get_list_count(entry_ports[index].value, max_list_count) >= max_list_count)
			visible_message("[src] begins to overheat!")
			return
		var/value_to_add = entry_ports[index].value
		if(is_proper_datum(value_to_add))
			value_to_add = WEAKREF(value_to_add)
		new_literal[key_ports[index].value] = entry_ports[index].value

	list_output.set_output(new_literal)


code:
/**
 * # Concat List Component
 *
 * Concatenates a list with a separator
 */
/obj/item/circuit_component/concat_list
	display_name = "Concatenate List"
	desc = "A component that joins up a list with a separator into a single string."

	/// The input port
	var/datum/port/input/list_port

	/// The seperator
	var/datum/port/input/separator

	/// The result from the output
	var/datum/port/output/output
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/concat_list/populate_ports()
	list_port = add_input_port("List", PORT_TYPE_LIST(PORT_TYPE_ANY))
	separator = add_input_port("Seperator", PORT_TYPE_STRING)

	output = add_output_port("Output", PORT_TYPE_STRING)

/obj/item/circuit_component/concat_list/input_received(datum/port/input/port)

	var/seperator = separator.value
	if(!seperator)
		return

	var/list/list_input = list_port.value
	if(!list_input)
		return

	var/list/text_list = list()
	for(var/entry in list_input)
		if(isatom(entry))
			text_list += PORT_TYPE_ATOM
		else
			text_list += "[entry]"

	output.set_output(text_list.Join(seperator))


code:
/**
 * # For Each Component
 *
 * Filters
 */
/obj/item/circuit_component/filter_list
	display_name = "Filter"
	desc = "A component that loops through each element in a list and filters them."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_INSTANT

	/// The list type
	var/datum/port/input/option/list_options

	/// Adds the list to the result
	var/datum/port/input/accept_entry
	/// The list to filter over
	var/datum/port/input/list_to_filter

	/// The current element from the list
	var/datum/port/output/element
	/// The current index from the list
	var/datum/port/output/current_index
	/// A signal that is sent when the list has moved onto the next index.
	var/datum/port/output/on_next_index
	/// The finished list
	var/datum/port/output/finished_list
	/// A signal that is sent when the filtering has finished
	var/datum/port/output/on_finished
	/// A signal that is sent when the filtering has failed
	var/datum/port/output/on_failed

	ui_buttons = list(
		"plus" = "increase",
	)

	/// The limit of iterations before it breaks. Used to prevent from someone iterating a massive list constantly
	var/limit = 300

/obj/item/circuit_component/filter_list/populate_options()
	list_options = add_option_port("List Type", GLOB.wiremod_basic_types)

/obj/item/circuit_component/filter_list/pre_input_received(datum/port/input/port)
	if(port == list_options)
		var/new_datatype = list_options.value
		list_to_filter.set_datatype(PORT_TYPE_LIST(new_datatype))
		finished_list.set_datatype(PORT_TYPE_LIST(new_datatype))
		element.set_datatype(new_datatype)

/obj/item/circuit_component/filter_list/populate_ports()
	list_to_filter = add_input_port("List Input", PORT_TYPE_LIST(PORT_TYPE_ANY))
	accept_entry = add_input_port("Accept Entry", PORT_TYPE_SIGNAL, trigger = .proc/accept_entry_port)

	element = add_output_port("Element", PORT_TYPE_ANY)
	current_index = add_output_port("Index", PORT_TYPE_NUMBER)
	on_next_index = add_output_port("Next Index", PORT_TYPE_SIGNAL)
	finished_list = add_output_port("Filtered List", PORT_TYPE_LIST(PORT_TYPE_ANY))
	on_finished = add_output_port("On Finished", PORT_TYPE_SIGNAL)
	on_failed = add_output_port("On Failed", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/filter_list/proc/accept_entry_port(datum/port/input/port, list/return_values)
	CIRCUIT_TRIGGER
	if(return_values)
		return_values["accept_entry"] = TRUE

/obj/item/circuit_component/filter_list/input_received(datum/port/input/port)
	var/index = 1
	var/start_tick_usage = TICK_USAGE
	var/list/filtered_list = list()
	for(var/element_in_list in list_to_filter.value)
		if(index > limit && !parent.admin_only)
			break
		SScircuit_component.queue_instant_run(start_tick_usage)
		element.set_output(element_in_list)
		current_index.set_output(index)
		on_next_index.set_output(COMPONENT_SIGNAL)
		index += 1
		var/list/result = SScircuit_component.execute_instant_run()
		if(!result)
			visible_message("[src] starts to overheat!")
			on_failed.set_output(COMPONENT_SIGNAL)
			return
		if(result["accept_entry"])
			filtered_list += list(element_in_list)
	finished_list.set_output(filtered_list)
	on_finished.set_output(COMPONENT_SIGNAL)


code:
/**
 * # For Each Component
 *
 * Sends a signal for each item in a list
 */
/obj/item/circuit_component/foreach
	display_name = "For Each"
	desc = "A component that loops through each element in a list."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL

	/// The list type
	var/datum/port/input/option/list_options

	/// The list to iterate over
	var/datum/port/input/list_to_iterate

	/// The current element from the list
	var/datum/port/output/element
	/// The current index from the list
	var/datum/port/output/current_index
	/// A signal that is sent when the list has moved onto the next index.
	var/datum/port/output/on_next_index
	/// A signal that is sent when the list has finished iterating
	var/datum/port/output/on_finished

	/// The limit of iterations before it breaks. Used to prevent from someone iterating a massive list constantly
	var/limit = 300

/obj/item/circuit_component/foreach/populate_options()
	list_options = add_option_port("List Type", GLOB.wiremod_basic_types)

/obj/item/circuit_component/foreach/pre_input_received(datum/port/input/port)
	if(port == list_options)
		var/new_datatype = list_options.value
		list_to_iterate.set_datatype(PORT_TYPE_LIST(new_datatype))
		element.set_datatype(new_datatype)

/obj/item/circuit_component/foreach/populate_ports()
	list_to_iterate = add_input_port("List Input", PORT_TYPE_LIST(PORT_TYPE_ANY))

	element = add_output_port("Element", PORT_TYPE_ANY)
	current_index = add_output_port("Index", PORT_TYPE_NUMBER)
	on_next_index = add_output_port("Next Index", PORT_TYPE_SIGNAL)
	on_finished = add_output_port("On Finished", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/foreach/input_received(datum/port/input/port)
	var/index = 1
	for(var/element_in_list in list_to_iterate.value)
		if(index > limit && !parent.admin_only)
			break
		element.set_output(element_in_list)
		current_index.set_output(index)
		on_next_index.set_output(COMPONENT_SIGNAL)
		index += 1
	on_finished.set_output(COMPONENT_SIGNAL)


code:
/**
 * # Get Column Component
 *
 * Gets the column of a table and returns it as a regular list.
 */
/obj/item/circuit_component/get_column
	display_name = "Get Column"
	desc = "Gets the column of a table and returns it as a regular list."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	/// The list to perform the filter on
	var/datum/port/input/received_table

	/// The name of the column to check
	var/datum/port/input/column_name

	/// The filtered list
	var/datum/port/output/output_list

/obj/item/circuit_component/get_column/populate_ports()
	received_table = add_input_port("Input", PORT_TYPE_TABLE)
	column_name = add_input_port("Column Name", PORT_TYPE_STRING)
	output_list = add_output_port("Output", PORT_TYPE_LIST(PORT_TYPE_ANY))

/obj/item/circuit_component/get_column/input_received(datum/port/input/port)

	var/list/input_list = received_table.value
	if(!islist(input_list) || isnum(column_name.value))
		return

	var/list/new_list = list()
	for(var/list/entry in input_list)
		var/anything = entry[column_name.value]
		if(islist(anything))
			continue
		new_list += anything

	output_list.set_output(new_list)


code:
/**
 * # Index Component
 *
 * Return the index of a list
 */
/obj/item/circuit_component/index
	display_name = "Index List"
	desc = "A component that returns the value of a list at a given index."

	/// The list type
	var/datum/port/input/option/list_options

	/// The input port
	var/datum/port/input/list_port
	var/datum/port/input/index_port

	/// The result from the output
	var/datum/port/output/output
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	var/index_type = PORT_TYPE_NUMBER

/obj/item/circuit_component/index/populate_options()
	list_options = add_option_port("List Type", GLOB.wiremod_basic_types)

/obj/item/circuit_component/index/proc/make_list_port()
	list_port = add_input_port("List", PORT_TYPE_LIST(PORT_TYPE_ANY))

/obj/item/circuit_component/index/populate_ports()
	index_port = add_input_port("Index", index_type)
	make_list_port()

	output = add_output_port("Value", PORT_TYPE_ANY)

/obj/item/circuit_component/index/pre_input_received(datum/port/input/port)
	if(port == list_options)
		var/new_type = list_options.value
		list_port.set_datatype(PORT_TYPE_LIST(new_type))
		output.set_datatype(new_type)

/obj/item/circuit_component/index/input_received(datum/port/input/port)

	var/index = index_port.value
	var/list/list_input = list_port.value

	if(!islist(list_input) || !index)
		output.set_output(null)
		return

	if(isnum(index) && (index < 1 || index > length(list_input)))
		output.set_output(null)
		return

	output.set_output(list_input[index])

/obj/item/circuit_component/index/assoc_string
	display_name = "Index Associative List"
	desc = "A component that is commonly used to access a row from a table. Accesses data from a key, value list."

	index_type = PORT_TYPE_STRING

/obj/item/circuit_component/index/assoc_string/make_list_port()
	list_port = add_input_port("List", PORT_TYPE_ASSOC_LIST(PORT_TYPE_STRING, PORT_TYPE_ANY))

/obj/item/circuit_component/index/assoc_string/pre_input_received(datum/port/input/port)
	if(port == list_options)
		var/new_type = list_options.value
		list_port.set_datatype(PORT_TYPE_ASSOC_LIST(PORT_TYPE_STRING, new_type))
		output.set_datatype(new_type)


code:
/**
 * # Index Table Component
 *
 * Gets the row of a table using the index inputted. Will return no value if the index is invalid or a proper table is not returned.
 */
/obj/item/circuit_component/index_table
	display_name = "Index Table"
	desc = "Gets the row of a table using the index inputted. Will return no value if the index is invalid or a proper table is not returned."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	/// The list to perform the filter on
	var/datum/port/input/received_table

	/// The target index
	var/datum/port/input/target_index

	/// The filtered list
	var/datum/port/output/output_list

/obj/item/circuit_component/index_table/populate_ports()
	received_table = add_input_port("Input", PORT_TYPE_TABLE)
	target_index = add_input_port("Index", PORT_TYPE_NUMBER)

	output_list = add_output_port("Output", PORT_TYPE_ASSOC_LIST(PORT_TYPE_STRING, PORT_TYPE_ANY))

/obj/item/circuit_component/index_table/input_received(datum/port/input/port)

	var/list/target_list = received_table.value
	if(!islist(target_list) || !length(target_list))
		output_list.set_output(null)
		return

	var/index = target_index.value
	if(index < 1 || index > length(target_list))
		output_list.set_output(null)
		return

	output_list.set_output(target_list[index])


code:
/**
 * # List Literal Component
 *
 * Return a list literal.
 */
/obj/item/circuit_component/list_literal
	display_name = "List Literal"
	desc = "A component that creates a list from whatever input you give it."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	/// The list type
	var/datum/port/input/option/list_options

	/// The inputs used to create the list
	var/list/datum/port/input/entry_ports = list()
	/// The result from the output
	var/datum/port/output/list_output

	var/length = 0

	var/default_list_size = 2

	var/min_size = 1
	var/max_size = 20

	ui_buttons = list(
		"plus" = "increase",
		"minus" = "decrease"
	)

	var/max_list_count = 100

/obj/item/circuit_component/list_literal/populate_options()
	list_options = add_option_port("List Type", GLOB.wiremod_basic_types)

/obj/item/circuit_component/list_literal/save_data_to_list(list/component_data)
	. = ..()
	component_data["length"] = length

/obj/item/circuit_component/list_literal/load_data_from_list(list/component_data)
	set_list_size(component_data["length"])

	return ..()

/obj/item/circuit_component/list_literal/proc/clear_lists()
	for(var/datum/port/input/port as anything in entry_ports)
		remove_input_port(port)
	entry_ports.Cut()
	length = 0

/obj/item/circuit_component/list_literal/proc/remove_one_entry()
	var/index = length(entry_ports)
	var/entry_port = entry_ports[index]
	entry_ports -= entry_port
	remove_input_port(entry_port)
	length--

/obj/item/circuit_component/list_literal/proc/add_one_entry()
	length++
	entry_ports += add_input_port("Index [length]", list_options.value || PORT_TYPE_ANY)

/obj/item/circuit_component/list_literal/proc/set_list_size(new_size)
	if(new_size <= 0)
		clear_lists()
		return

	while(length > new_size)
		remove_one_entry()

	while(length < new_size)
		add_one_entry()

/obj/item/circuit_component/list_literal/pre_input_received(datum/port/input/port)
	if(port == list_options)
		var/new_datatype = list_options.value
		list_output.set_datatype(PORT_TYPE_LIST(new_datatype))
		for(var/datum/port/input/port_to_set as anything in entry_ports)
			port_to_set.set_datatype(new_datatype)

/obj/item/circuit_component/list_literal/populate_ports()
	set_list_size(default_list_size)
	list_output = add_output_port("Value", PORT_TYPE_LIST(PORT_TYPE_ANY))

/obj/item/circuit_component/list_literal/Destroy()
	list_output = null
	return ..()

// Increases list length
/obj/item/circuit_component/list_literal/ui_perform_action(mob/user, action)
	switch(action)
		if("increase")
			set_list_size(min(length + 1, max_size))
		if("decrease")
			set_list_size(max(length - 1, min_size))

/obj/item/circuit_component/list_literal/input_received(datum/port/input/port)

	var/list/new_literal = list()
	for(var/datum/port/input/entry_port as anything in entry_ports)
		var/value = entry_port.value
		// To prevent people from infinitely making lists to crash the server
		if(islist(value) && get_list_count(value, max_list_count) >= max_list_count)
			visible_message("[src] begins to overheat!")
			return
		if(is_proper_datum(value))
			new_literal += WEAKREF(value)
		else
			new_literal += list(value)

	list_output.set_output(new_literal)

/proc/get_list_count(list/value, max_list_count)
	var/list/lists_to_check = list()
	lists_to_check += list(value)
	var/lists = 1
	while(length(lists_to_check))
		var/list/list_to_iterate = lists_to_check[length(lists_to_check)]
		lists_to_check.len--
		for(var/list/list_data in list_to_iterate)
			lists_to_check += list(list_data)
			lists += 1
		if(lists > max_list_count)
			return lists
	return lists


code:
/**
 * # Select Component
 *
 * Selects a list from a list of lists by a specific column. Used only by USBs for communications to and from computers with lists of varying sizes.
 */
/obj/item/circuit_component/select
	display_name = "Select Query"
	desc = "A component used with USB cables that can perform select queries on a list based on the column name selected. The values are then compared with the comparison input."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	var/datum/port/input/option/comparison_options

	/// The list to perform the filter on
	var/datum/port/input/received_table

	/// The name of the column to check
	var/datum/port/input/column_name

	/// The input to compare with
	var/datum/port/input/comparison_input

	/// The filtered list
	var/datum/port/output/filtered_table

	var/current_type = PORT_TYPE_ANY

/obj/item/circuit_component/select/populate_options()
	var/static/component_options = list(
		COMP_COMPARISON_EQUAL,
		COMP_COMPARISON_NOT_EQUAL,
		COMP_COMPARISON_GREATER_THAN,
		COMP_COMPARISON_LESS_THAN,
		COMP_COMPARISON_GREATER_THAN_OR_EQUAL,
		COMP_COMPARISON_LESS_THAN_OR_EQUAL,
	)
	comparison_options = add_option_port("Comparison Options", component_options)

/obj/item/circuit_component/select/populate_ports()
	received_table = add_input_port("Input", PORT_TYPE_TABLE)
	column_name = add_input_port("Column Name", PORT_TYPE_STRING)
	comparison_input = add_input_port("Comparison Input", PORT_TYPE_ANY)

	filtered_table = add_output_port("Output", PORT_TYPE_TABLE)

/obj/item/circuit_component/select/pre_input_received(datum/port/input/port)
	var/current_option = comparison_options.value
	switch(current_option)
		if(COMP_COMPARISON_EQUAL, COMP_COMPARISON_NOT_EQUAL)
			if(current_type != PORT_TYPE_ANY)
				current_type = PORT_TYPE_ANY
				comparison_input.set_datatype(PORT_TYPE_ANY)
		else
			if(current_type != PORT_TYPE_NUMBER)
				current_type = PORT_TYPE_NUMBER
				comparison_input.set_datatype(PORT_TYPE_NUMBER)

/obj/item/circuit_component/select/input_received(datum/port/input/port)
	var/current_option = comparison_options.value
	var/list/input_list = received_table.value
	if(!islist(input_list) || isnum(column_name.value))
		return

	var/comparison_value = comparison_input.value
	var/list/new_list = list()
	for(var/list/entry in input_list)
		var/anything = entry[column_name.value]
		if(islist(anything))
			continue
		if(current_option != COMP_COMPARISON_EQUAL && current_option != COMP_COMPARISON_NOT_EQUAL && !isnum(anything))
			continue
		var/add_to_list = FALSE
		switch(current_option)
			if(COMP_COMPARISON_EQUAL)
				add_to_list = anything == comparison_value
			if(COMP_COMPARISON_NOT_EQUAL)
				add_to_list = anything != comparison_value
			if(COMP_COMPARISON_GREATER_THAN)
				add_to_list = anything > comparison_value
			if(COMP_COMPARISON_GREATER_THAN_OR_EQUAL)
				add_to_list = anything >= comparison_value
			if(COMP_COMPARISON_LESS_THAN)
				add_to_list = anything < comparison_value
			if(COMP_COMPARISON_LESS_THAN_OR_EQUAL)
				add_to_list = anything <= comparison_value

		if(add_to_list)
			new_list += list(entry)

	filtered_table.set_output(new_list)


code:
/**
 * # Split component
 *
 * Splits a string
 */
/obj/item/circuit_component/split
	display_name = "Split"
	desc = "Splits a string by the separator, turning it into a list"

	/// The input port
	var/datum/port/input/input_port

	/// The seperator
	var/datum/port/input/separator

	/// The result from the output
	var/datum/port/output/output

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/split/populate_ports()
	input_port = add_input_port("Input", PORT_TYPE_STRING)
	separator = add_input_port("Seperator", PORT_TYPE_STRING)
	output = add_output_port("Output", PORT_TYPE_LIST(PORT_TYPE_STRING))

/obj/item/circuit_component/split/input_received(datum/port/input/port)

	var/separator_value = separator.value
	if(isnull(separator_value))
		return

	var/value = input_port.value
	if(isnull(value))
		return

	var/list/result = splittext(value,separator_value)

	output.set_output(result)


code:
#define COMP_ARITHMETIC_ADD "Add"
#define COMP_ARITHMETIC_SUBTRACT "Subtract"
#define COMP_ARITHMETIC_MULTIPLY "Multiply"
#define COMP_ARITHMETIC_DIVIDE "Divide"
#define COMP_ARITHMETIC_MIN "Minimum"
#define COMP_ARITHMETIC_MAX "Maximum"

/**
 * # Arithmetic Component
 *
 * General arithmetic unit with add/sub/mult/divide capabilities
 * This one only works with numbers.
 */
/obj/item/circuit_component/arithmetic
	display_name = "Arithmetic"
	desc = "General arithmetic component with arithmetic capabilities."

	/// The amount of input ports to have
	var/input_port_amount = 4

	var/datum/port/input/option/arithmetic_option

	/// The result from the output
	var/datum/port/output/output

	var/list/arithmetic_ports
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/arithmetic/populate_options()
	var/static/component_options = list(
		COMP_ARITHMETIC_ADD,
		COMP_ARITHMETIC_SUBTRACT,
		COMP_ARITHMETIC_MULTIPLY,
		COMP_ARITHMETIC_DIVIDE,
		COMP_ARITHMETIC_MIN,
		COMP_ARITHMETIC_MAX,
	)
	arithmetic_option = add_option_port("Arithmetic Option", component_options)

/obj/item/circuit_component/arithmetic/populate_ports()
	arithmetic_ports = list()
	for(var/port_id in 1 to input_port_amount)
		var/letter = ascii2text(text2ascii("A") + (port_id-1))
		arithmetic_ports += add_input_port(letter, PORT_TYPE_NUMBER)

	output = add_output_port("Output", PORT_TYPE_NUMBER)

/obj/item/circuit_component/arithmetic/input_received(datum/port/input/port)

	var/list/ports = arithmetic_ports.Copy()
	var/datum/port/input/first_port = popleft(ports)
	var/result = first_port.value

	for(var/datum/port/input/input_port as anything in ports)
		var/value = input_port.value
		if(isnull(value))
			continue

		switch(arithmetic_option.value)
			if(COMP_ARITHMETIC_ADD)
				result += value
			if(COMP_ARITHMETIC_SUBTRACT)
				result -= value
			if(COMP_ARITHMETIC_MULTIPLY)
				result *= value
			if(COMP_ARITHMETIC_DIVIDE)
				// Protect from div by zero errors.
				if(value == 0)
					result = null
					break
				result /= value
			if(COMP_ARITHMETIC_MAX)
				result = max(result, value)
			if(COMP_ARITHMETIC_MIN)
				result = min(result, value)

	output.set_output(result)

#undef COMP_ARITHMETIC_ADD
#undef COMP_ARITHMETIC_SUBTRACT
#undef COMP_ARITHMETIC_MULTIPLY
#undef COMP_ARITHMETIC_DIVIDE
#undef COMP_ARITHMETIC_MIN
#undef COMP_ARITHMETIC_MAX


code:
/**
 * # Binary Conversion Component
 *
 * Return an array of binary digits from a number input.
 */
/obj/item/circuit_component/binary_decimal/binary_conversion
	display_name = "Binary Conversion"
	desc = "Splits a decimal number into an array of binary digits, or bits, represented as 1 or 0 and often used in boolean or binary operations like AND, OR and XOR."

/obj/item/circuit_component/binary_decimal/binary_conversion/populate_ports()
	. = ..()
	number = add_input_port("Number", PORT_TYPE_NUMBER)

/obj/item/circuit_component/binary_decimal/binary_conversion/add_bit_port(index)
	return add_output_port("Bit [index]", PORT_TYPE_NUMBER)

/obj/item/circuit_component/binary_decimal/binary_conversion/remove_bit_port(datum/port/to_remove)
	return remove_output_port(to_remove)

/obj/item/circuit_component/binary_decimal/binary_conversion/input_received(datum/port/input/port)
	if(!array_size)
		return

	for(var/iteration in 1 to array_size)
		var/datum/port/output/bit = bit_array[iteration]
		bit.set_output(number.value & (2 ** (iteration - 1)))


code:
/**
 * # Comparison Component
 *
 * Compares two objects
 */
/obj/item/circuit_component/compare/comparison
	display_name = "Comparison"
	desc = "A component that compares two objects."

	var/datum/port/input/option/comparison_option

	input_port_amount = 2
	var/current_type = PORT_TYPE_ANY

/obj/item/circuit_component/compare/comparison/populate_options()
	var/static/component_options = list(
		COMP_COMPARISON_EQUAL,
		COMP_COMPARISON_NOT_EQUAL,
		COMP_COMPARISON_GREATER_THAN,
		COMP_COMPARISON_LESS_THAN,
		COMP_COMPARISON_GREATER_THAN_OR_EQUAL,
		COMP_COMPARISON_LESS_THAN_OR_EQUAL,
	)
	comparison_option = add_option_port("Comparison Option", component_options)

/obj/item/circuit_component/compare/comparison/pre_input_received(datum/port/input/port)
	switch(comparison_option.value)
		if(COMP_COMPARISON_EQUAL, COMP_COMPARISON_NOT_EQUAL)
			if(current_type != PORT_TYPE_ANY)
				current_type = PORT_TYPE_ANY
				compare_ports[1].set_datatype(PORT_TYPE_ANY)
				compare_ports[2].set_datatype(PORT_TYPE_ANY)
		else
			if(current_type != PORT_TYPE_NUMBER)
				current_type = PORT_TYPE_NUMBER
				compare_ports[1].set_datatype(PORT_TYPE_NUMBER)
				compare_ports[2].set_datatype(PORT_TYPE_NUMBER)

/obj/item/circuit_component/compare/comparison/do_comparisons(list/ports)
	if(length(ports) < input_port_amount)
		return FALSE

	// Comparison component only compares the first two ports
	var/input1 = compare_ports[1].value
	var/input2 = compare_ports[2].value
	var/current_option = comparison_option.value

	switch(current_option)
		if(COMP_COMPARISON_EQUAL)
			return input1 == input2
		if(COMP_COMPARISON_NOT_EQUAL)
			return input1 != input2
		if(COMP_COMPARISON_GREATER_THAN)
			return input1 > input2
		if(COMP_COMPARISON_GREATER_THAN_OR_EQUAL)
			return input1 >= input2
		if(COMP_COMPARISON_LESS_THAN)
			return input1 < input2
		if(COMP_COMPARISON_LESS_THAN_OR_EQUAL)
			return input1 <= input2


code:
/**
 * # Decimal Conversion Component
 *
 * Return a number from an array of binary inputs.
 */
/obj/item/circuit_component/binary_decimal/decimal_conversion
	display_name = "Decimal Conversion"
	desc = "Merges an array of binary digits, or bits, represented as 1 or 0 and often used in boolean or binary operations, into a decimal number."

/obj/item/circuit_component/binary_decimal/decimal_conversion/populate_ports()
	. = ..()
	number = add_output_port("Number", PORT_TYPE_NUMBER)

/obj/item/circuit_component/binary_decimal/decimal_conversion/add_bit_port(index)
	return add_input_port("Bit [index]", PORT_TYPE_NUMBER)

/obj/item/circuit_component/binary_decimal/decimal_conversion/remove_bit_port(datum/port/to_remove)
	return remove_input_port(to_remove)

/obj/item/circuit_component/binary_decimal/decimal_conversion/input_received(datum/port/input/port)
	if(!array_size)
		return

	var/result = 0
	for(var/iteration in 1 to array_size)
		var/datum/port/input/bit = bit_array[iteration]
		if(bit.value)
			result += (2 ** (iteration-1))
	number.set_output(result)


code:
/**
 * # Length Component
 *
 * Return the length of an input
 */
/obj/item/circuit_component/length
	display_name = "Length"
	desc = "A component that returns the length of its input."

	/// The input port
	var/datum/port/input/input_port

	/// The result from the output
	var/datum/port/output/output
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/length/populate_ports()
	input_port = add_input_port("Input", PORT_TYPE_ANY)

	output = add_output_port("Length", PORT_TYPE_NUMBER)

/obj/item/circuit_component/length/input_received(datum/port/input/port)

	output.set_output(length(input_port.value))


code:
#define COMP_LOGIC_AND "AND"
#define COMP_LOGIC_OR "OR"
#define COMP_LOGIC_XOR "XOR"

/**
 * # Logic Component
 *
 * General logic unit with AND OR capabilities
 */
/obj/item/circuit_component/compare/logic
	display_name = "Logic"
	desc = "A component with 'and' and 'or' capabilities."

	var/datum/port/input/option/logic_options

/obj/item/circuit_component/compare/logic/populate_options()
	var/static/component_options = list(
		COMP_LOGIC_AND,
		COMP_LOGIC_OR,
		COMP_LOGIC_XOR,
	)
	logic_options = add_option_port("Logic Options", component_options)

/obj/item/circuit_component/compare/logic/do_comparisons(list/ports)
	. = FALSE
	var/current_option = logic_options.value

	// Used by XOR
	var/total_ports = 0
	var/total_true_ports = 0
	for(var/datum/port/input/port as anything in ports)
		if(isnull(port.value) && length(port.connected_ports) == 0)
			continue

		total_ports += 1
		switch(current_option)
			if(COMP_LOGIC_AND)
				if(!port.value)
					return FALSE
				. = TRUE
			if(COMP_LOGIC_OR)
				if(port.value)
					return TRUE
			if(COMP_LOGIC_XOR)
				if(port.value)
					. = TRUE
					total_true_ports += 1

	if(current_option == COMP_LOGIC_XOR)
		if(total_ports == total_true_ports)
			return FALSE
		if(.)
			return TRUE

#undef COMP_LOGIC_AND
#undef COMP_LOGIC_OR
#undef COMP_LOGIC_XOR


code:
/**
 * # Logic Component
 *
 * General logic unit with AND OR capabilities
 */
/obj/item/circuit_component/not
	display_name = "Not"
	desc = "A component that inverts its input."

	/// The input port
	var/datum/port/input/input_port

	/// The result from the output
	var/datum/port/output/result
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/not/populate_ports()
	input_port = add_input_port("Input", PORT_TYPE_ANY)

	result = add_output_port("Result", PORT_TYPE_NUMBER)

/obj/item/circuit_component/not/input_received(datum/port/input/port)

	result.set_output(!input_port.value)


code:
/**
 * # Random Component
 *
 * Generates a random number between specific values
 */
/obj/item/circuit_component/random
	display_name = "Random"
	desc = "A component that returns random values."

	/// The minimum value that the random number can be
	var/datum/port/input/minimum
	/// The maximum value that the random number can be
	var/datum/port/input/maximum

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	/// The result from the output
	var/datum/port/output/output

/obj/item/circuit_component/random/populate_ports()
	minimum = add_input_port("Minimum", PORT_TYPE_NUMBER, trigger = null)
	maximum = add_input_port("Maximum", PORT_TYPE_NUMBER, trigger = null)

	output = add_output_port("Output", PORT_TYPE_NUMBER)

/obj/item/circuit_component/random/input_received(datum/port/input/port)

	var/min_val = minimum.value || 0
	var/max_val = maximum.value || 0

	if(max_val < min_val)
		output.set_output(0)
		return

	output.set_output(rand(min_val, max_val))


code:
#define COMP_TRIGONOMETRY_SINE "Sine"
#define COMP_TRIGONOMETRY_COSINE "Cosine"
#define COMP_TRIGONOMETRY_TANGENT "Tangent"
#define COMP_TRIGONOMETRY_ARCSINE "Arcsine"
#define COMP_TRIGONOMETRY_ARCCOSINE "Arccosine"
#define COMP_TRIGONOMETRY_ARCTANGENT "Arctangent"

/**
 * # Trigonometric Component
 *
 * General trigonometric unit with sine, cosine, tangent and their inverse functions.
 * This one only works with numbers.
 */
/obj/item/circuit_component/trigonometry
	display_name = "Trigonometry"
	desc = "General trigonometry component with main and inverse trigonometry functions."

	var/datum/port/input/option/trigonometric_function

	/// The input port
	var/datum/port/input/input_port

	/// The result from the output
	var/datum/port/output/output

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/trigonometry/populate_options()
	var/static/component_functions = list(
		COMP_TRIGONOMETRY_SINE,
		COMP_TRIGONOMETRY_COSINE,
		COMP_TRIGONOMETRY_TANGENT,
		COMP_TRIGONOMETRY_ARCSINE,
		COMP_TRIGONOMETRY_ARCCOSINE,
		COMP_TRIGONOMETRY_ARCTANGENT,
	)
	trigonometric_function = add_option_port("Trigonometric Function", component_functions)

/obj/item/circuit_component/trigonometry/populate_ports()
	input_port = add_input_port("Input", PORT_TYPE_NUMBER)
	output = add_output_port("Output", PORT_TYPE_NUMBER)

/obj/item/circuit_component/trigonometry/input_received(datum/port/input/port)

	var/result = input_port.value

	switch(trigonometric_function.value)
		if(COMP_TRIGONOMETRY_SINE)
			result = sin(result)
		if(COMP_TRIGONOMETRY_COSINE)
			result = cos(result)
		if(COMP_TRIGONOMETRY_TANGENT)
			result = tan(result)
		if(COMP_TRIGONOMETRY_ARCSINE)
			result = arcsin(result)
		if(COMP_TRIGONOMETRY_ARCCOSINE)
			result = arccos(result)
		if(COMP_TRIGONOMETRY_ARCTANGENT)
			result = arctan(result)

	output.set_output(result)

#undef COMP_TRIGONOMETRY_SINE
#undef COMP_TRIGONOMETRY_COSINE
#undef COMP_TRIGONOMETRY_TANGENT
#undef COMP_TRIGONOMETRY_ARCSINE
#undef COMP_TRIGONOMETRY_ARCCOSINE
#undef COMP_TRIGONOMETRY_ARCTANGENT


code:
/**
 * # NTNet Receiver Component
 *
 * Receives data through NTNet.
 */
/obj/item/circuit_component/ntnet_receive
	display_name = "NTNet Receiver"
	desc = "Receives data packages through NTNet. If Encryption Key is set then only signals with the same Encryption Key will be received."

	circuit_flags = CIRCUIT_FLAG_OUTPUT_SIGNAL //trigger_output

	network_id = __NETWORK_CIRCUITS

	/// The list type
	var/datum/port/input/option/list_options

	/// Data being received
	var/datum/port/output/data_package

	/// Encryption key
	var/datum/port/input/enc_key

/obj/item/circuit_component/ntnet_receive/populate_options()
	list_options = add_option_port("List Type", GLOB.wiremod_basic_types)

/obj/item/circuit_component/ntnet_receive/populate_ports()
	data_package = add_output_port("Data Package", PORT_TYPE_LIST(PORT_TYPE_ANY))
	enc_key = add_input_port("Encryption Key", PORT_TYPE_STRING)
	RegisterSignal(src, COMSIG_COMPONENT_NTNET_RECEIVE, .proc/ntnet_receive)

/obj/item/circuit_component/ntnet_receive/pre_input_received(datum/port/input/port)
	if(port == list_options)
		var/new_datatype = list_options.value
		data_package.set_datatype(PORT_TYPE_LIST(new_datatype))

/obj/item/circuit_component/ntnet_receive/proc/ntnet_receive(datum/source, datum/netdata/data)
	SIGNAL_HANDLER

	if(data.data["enc_key"] != enc_key.value)
		return

	var/datum/weakref/ref = data.data["port"]
	var/datum/port/input/port = ref?.resolve()
	if(!port)
		return

	var/datum/circuit_datatype/datatype_handler = data_package.datatype_handler
	if(!datatype_handler?.can_receive_from_datatype(port.datatype))
		return

	data_package.set_output(data.data["data"])
	trigger_output.set_output(COMPONENT_SIGNAL)


code:
/**
 * # NTNet Transmitter Component
 *
 * Sends a data package through NTNet
 */

/obj/item/circuit_component/ntnet_send
	display_name = "NTNet Transmitter"
	desc = "Sends a data package through NTNet. If Encryption Key is set then transmitted data will be only picked up by receivers with the same Encryption Key."

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL

	network_id = __NETWORK_CIRCUITS

	/// The list type
	var/datum/port/input/option/list_options

	/// Data being sent
	var/datum/port/input/data_package

	/// Encryption key
	var/datum/port/input/enc_key

/obj/item/circuit_component/ntnet_send/populate_options()
	list_options = add_option_port("List Type", GLOB.wiremod_basic_types)

/obj/item/circuit_component/ntnet_send/populate_ports()
	data_package = add_input_port("Data Package", PORT_TYPE_LIST(PORT_TYPE_ANY))
	enc_key = add_input_port("Encryption Key", PORT_TYPE_STRING)

/obj/item/circuit_component/ntnet_send/pre_input_received(datum/port/input/port)
	if(port == list_options)
		var/new_datatype = list_options.value
		data_package.set_datatype(PORT_TYPE_LIST(new_datatype))

/obj/item/circuit_component/ntnet_send/input_received(datum/port/input/port)
	ntnet_send(list("data" = data_package.value, "enc_key" = enc_key.value, "port" = WEAKREF(data_package)))


code:
/**
 * # Pressure Sensor
 *
 * Returns the pressure of the tile
 */
/obj/item/circuit_component/pressuresensor
	display_name = "Pressure Sensor"
	desc = "Outputs the current pressure of the tile"

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	/// The result from the output
	var/datum/port/output/result

/obj/item/circuit_component/pressuresensor/populate_ports()
	result = add_output_port("Result", PORT_TYPE_NUMBER)

/obj/item/circuit_component/pressuresensor/input_received(datum/port/input/port)
	//Get current turf
	var/turf/location = get_turf(src)
	if(!location)
		result.set_output(null)
		return
	//Get environment info
	var/datum/gas_mixture/environment = location.return_air()
	var/total_moles = environment.total_moles()
	var/pressure = environment.return_pressure()
	if(total_moles)
		//If there's atmos, return pressure
		result.set_output(round(pressure,1))
	else
		result.set_output(0)


code:
/**
 * # Temperature Sensor
 *
 * Returns the temperature of the tile
 */
/obj/item/circuit_component/tempsensor
	display_name = "Temperature Sensor"
	desc = "Outputs the current temperature of the tile"

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	/// The result from the output
	var/datum/port/output/result

/obj/item/circuit_component/tempsensor/populate_ports()
	result = add_output_port("Result", PORT_TYPE_NUMBER)

/obj/item/circuit_component/tempsensor/input_received(datum/port/input/port)
	//Get current turf
	var/turf/location = get_turf(src)
	if(!location)
		result.set_output(null)
		return
	//Get environment info
	var/datum/gas_mixture/environment = location.return_air()
	var/total_moles = environment.total_moles()
	if(total_moles)
		//If there's atmos, return temperature
		result.set_output(round(environment.temperature,1))
	else
		result.set_output(0)


code:
/**
 * # Concatenate Component
 *
 * General string concatenation component. Puts strings together.
 */
/obj/item/circuit_component/concat
	display_name = "Concatenate"
	desc = "A component that combines strings."

	/// The amount of input ports to have
	var/input_port_amount = 4

	var/list/datum/port/input/concat_ports = list()

	/// The result from the output
	var/datum/port/output/output
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/concat/populate_ports()
	for(var/port_id in 1 to input_port_amount)
		var/letter = ascii2text(text2ascii("A") + (port_id-1))
		concat_ports += add_input_port(letter, PORT_TYPE_STRING)

	output = add_output_port("Output", PORT_TYPE_STRING)

/obj/item/circuit_component/concat/input_received(datum/port/input/port)

	var/result = ""
	for(var/datum/port/input/input_port as anything in concat_ports)
		var/value = input_port.value
		if(isnull(value))
			continue

		result += "[value]"

	output.set_output(result)


code:
/**
 * # String Contains Component
 *
 * Checks if a string contains a word/letter
 */
/obj/item/circuit_component/compare/contains
	display_name = "String Contains"
	desc = "Checks if a string contains a word/letter"

	input_port_amount = 0

	var/datum/port/input/needle
	var/datum/port/input/haystack

/obj/item/circuit_component/compare/contains/populate_custom_ports()
	needle = add_input_port("Needle", PORT_TYPE_STRING)
	haystack = add_input_port("Haystack", PORT_TYPE_STRING)

/obj/item/circuit_component/compare/contains/Destroy()
	needle = null
	haystack = null
	return ..()

/obj/item/circuit_component/compare/contains/do_comparisons(list/ports)
	if(length(ports) < input_port_amount)
		return

	var/to_find = needle.value
	var/to_search = haystack.value

	if(!to_find || !to_search)
		return

	return findtext(to_search, to_find)


code:
#define COMP_TEXT_LOWER "To Lower"
#define COMP_TEXT_UPPER "To Upper"

/**
 * # Text Component
 *
 * Either makes the text upper case or lower case.
 */
/obj/item/circuit_component/textcase
	display_name = "Text Case"
	desc = "A component that makes its input uppercase or lowercase."

	var/datum/port/input/option/textcase_options

	/// The input port
	var/datum/port/input/input_port

	/// The result of the text operation
	var/datum/port/output/output

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/textcase/populate_options()
	var/static/component_options = list(
		COMP_TEXT_LOWER,
		COMP_TEXT_UPPER,
	)
	textcase_options = add_option_port("Textcase Options", component_options)

/obj/item/circuit_component/textcase/populate_ports()
	input_port = add_input_port("Input", PORT_TYPE_STRING)
	output = add_output_port("Output", PORT_TYPE_STRING)

/obj/item/circuit_component/textcase/input_received(datum/port/input/port)

	var/value = input_port.value
	if(isnull(value))
		return

	var/result
	switch(textcase_options.value)
		if(COMP_TEXT_LOWER)
			result = lowertext(value)
		if(COMP_TEXT_UPPER)
			result = uppertext(value)

	output.set_output(result)

#undef COMP_TEXT_LOWER
#undef COMP_TEXT_UPPER


code:
/**
 * #To Number Component
 *
 * Converts a string into a Number
 */
/obj/item/circuit_component/tonumber
	display_name = "To Number"
	desc = "A component that converts its input (a string) to a number. If there's text in the input, it'll only consider it if it starts with a number. It will take that number and ignore the rest."

	/// The input port
	var/datum/port/input/input_port

	/// The result from the output
	var/datum/port/output/output

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

/obj/item/circuit_component/tonumber/populate_ports()
	input_port = add_input_port("Input", PORT_TYPE_STRING)
	output = add_output_port("Output", PORT_TYPE_NUMBER)

/obj/item/circuit_component/tonumber/input_received(datum/port/input/port)

	output.set_output(text2num(input_port.value))


code:
/**
 * # To String Component
 *
 * Converts any value into a string
 */
/obj/item/circuit_component/tostring
	display_name = "To String"
	desc = "A component that converts its input to text."

	/// The input port
	var/datum/port/input/input_port

	/// The result from the output
	var/datum/port/output/output

	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	var/max_range = 7

/obj/item/circuit_component/tostring/populate_ports()
	input_port = add_input_port("Input", PORT_TYPE_ANY)
	output = add_output_port("Output", PORT_TYPE_STRING)

/obj/item/circuit_component/tostring/input_received(datum/port/input/port)

	var/value = input_port.value
	if(isatom(value))
		var/turf/location = get_turf(src)
		var/turf/target_location = get_turf(value)
		if(target_location.z != location.z || get_dist(location, target_location) > max_range)
			output.set_output(PORT_TYPE_ATOM)
			return

	output.set_output("[value]")