tgui backend

This commit is contained in:
LetterN
2021-10-28 12:51:31 +08:00
parent 853ff1d8ad
commit 49940c373e
110 changed files with 7353 additions and 259 deletions
@@ -0,0 +1,58 @@
/**
* # Compare Component
*
* Abstract component to build conditional components
*/
/obj/item/circuit_component/compare
display_name = "Compare"
/// The amount of input ports to have
var/input_port_amount = 4
/// The trigger for the true/false signals
var/datum/port/input/compare
/// Signals sent on compare
var/datum/port/output/true
var/datum/port/output/false
/// The result from the output
var/datum/port/output/result
var/list/datum/port/input/compare_ports = list()
/obj/item/circuit_component/compare/Initialize()
. = ..()
for(var/port_id in 1 to input_port_amount)
var/letter = ascii2text(text2ascii("A") + (port_id-1))
compare_ports += add_input_port(letter, PORT_TYPE_ANY)
load_custom_ports()
compare = add_input_port("Compare", PORT_TYPE_SIGNAL)
true = add_output_port("True", PORT_TYPE_SIGNAL)
false = add_output_port("False", PORT_TYPE_SIGNAL)
result = add_output_port("Result", PORT_TYPE_NUMBER)
/**
* Used by derivatives to load their own ports in for custom use.
*/
/obj/item/circuit_component/compare/proc/load_custom_ports()
return
/obj/item/circuit_component/compare/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/logic_result = do_comparisons(compare_ports)
if(COMPONENT_TRIGGERED_BY(compare, port))
if(logic_result)
true.set_output(COMPONENT_SIGNAL)
else
false.set_output(COMPONENT_SIGNAL)
result.set_output(logic_result)
/// Do the comparisons and return a result
/obj/item/circuit_component/compare/proc/do_comparisons(list/ports)
return FALSE
@@ -0,0 +1,306 @@
/**
* # Module Component
*
* A component that has an input, output
*/
/obj/item/circuit_component/module
display_name = "Module"
desc = "A component that has other components within it, acting like a function. Use it in your hand to control the amount of input and output ports it has, as well as being able to access the integrated circuit contained inside."
var/obj/item/integrated_circuit/module/internal_circuit
var/obj/item/circuit_component/module_input/input_component
var/obj/item/circuit_component/module_output/output_component
/// Linked ports that follow a `first_port = second_port` keyed structure.
var/list/linked_ports = list()
var/port_limit = 10
/obj/item/integrated_circuit/module
var/obj/item/circuit_component/module/attached_module
/obj/item/integrated_circuit/module/ui_host(mob/user)
. = ..()
if(. == src)
return attached_module
/obj/item/integrated_circuit/module/set_display_name(new_name)
. = ..()
attached_module.display_name = new_name
attached_module.name = "module ([new_name])"
/obj/item/integrated_circuit/module/load_component(type)
if(!attached_module)
return ..()
if(ispath(type, /obj/item/circuit_component/module_input))
return attached_module.input_component
if(ispath(type, /obj/item/circuit_component/module_output))
return attached_module.output_component
return ..()
/obj/item/integrated_circuit/module/Destroy()
attached_module = null
return ..()
/obj/item/circuit_component/module_input
display_name = "Input"
desc = "A component that receives data from the module it is attached to"
removable = FALSE
/// The currently attached module
var/obj/item/circuit_component/module/attached_module
/obj/item/circuit_component/module_input/Destroy()
attached_module = null
return ..()
/obj/item/circuit_component/module_output
display_name = "Output"
desc = "A component that outputs data to the module it is attached to."
removable = FALSE
/// The currently attached module
var/obj/item/circuit_component/module/attached_module
/obj/item/circuit_component/module_output/input_received(datum/port/input/port)
. = ..()
if(!port)
return
// We don't check the parent here because frankly, we don't care. We only sync our input with the module's output
var/datum/port/output/port_to_update = attached_module.linked_ports[port]
if(!port_to_update)
CRASH("[port.type] doesn't have a linked port in [type]!")
port_to_update.set_output(port.value)
/obj/item/circuit_component/module/input_received(datum/port/input/port)
. = ..()
if(!port)
return
var/datum/port/output/port_to_update = linked_ports[port]
if(!port_to_update)
CRASH("[port.type] doesn't have a linked port in [type]!")
port_to_update.set_output(port.value)
/obj/item/circuit_component/module_output/Destroy()
attached_module = null
return ..()
/obj/item/circuit_component/module/Initialize()
. = ..()
internal_circuit = new(src)
internal_circuit.attached_module = src
input_component = new(internal_circuit)
input_component.attached_module = src
internal_circuit.add_component(input_component)
input_component.rel_x = 0
input_component.rel_y = 200
output_component = new(internal_circuit)
output_component.attached_module = src
internal_circuit.add_component(output_component)
output_component.rel_x = 400
output_component.rel_y = 200
/obj/item/circuit_component/module/save_data_to_list(list/component_data)
. = ..()
component_data["integrated_circuit"] = internal_circuit.convert_to_json()
var/list/input_data = list()
for(var/datum/port/input/input_port as anything in input_ports)
input_data += list(list(
"name" = input_port.name,
"type" = input_port.datatype,
))
var/list/output_data = list()
for(var/datum/port/output/output_port as anything in output_ports)
output_data += list(list(
"name" = output_port.name,
"type" = output_port.datatype,
))
component_data["input_ports"] = input_data
component_data["output_ports"] = output_data
/obj/item/circuit_component/module/load_data_from_list(list/component_data)
. = ..()
var/list/input_ports = component_data["input_ports"]
for(var/list/port_data as anything in input_ports)
add_and_link_input_port(port_data["name"], port_data["type"])
var/list/output_ports = component_data["output_ports"]
for(var/list/port_data as anything in output_ports)
add_and_link_output_port(port_data["name"], port_data["type"])
if(component_data["integrated_circuit"])
internal_circuit.load_circuit_data(component_data["integrated_circuit"])
/obj/item/circuit_component/module/proc/add_and_link_input_port(name, type)
var/datum/port/new_port = add_input_port(name, type)
linked_ports[new_port] = input_component.add_output_port(name, type)
/obj/item/circuit_component/module/proc/add_and_link_output_port(name, type)
var/datum/port/new_port = output_component.add_input_port(name, type)
linked_ports[new_port] = add_output_port(name, type)
/obj/item/circuit_component/module/add_to(obj/item/integrated_circuit/added_to)
. = ..()
RegisterSignal(added_to, COMSIG_CIRCUIT_SET_CELL, .proc/handle_set_cell)
RegisterSignal(added_to, COMSIG_CIRCUIT_SET_ON, .proc/handle_set_on)
RegisterSignal(added_to, COMSIG_CIRCUIT_SET_SHELL, .proc/handle_set_shell)
internal_circuit.set_cell(added_to.cell)
internal_circuit.set_shell(added_to.shell)
internal_circuit.set_on(added_to.on)
/obj/item/circuit_component/module/removed_from(obj/item/integrated_circuit/removed_from)
internal_circuit.set_cell(null)
internal_circuit.set_on(FALSE)
internal_circuit.remove_current_shell()
UnregisterSignal(removed_from, list(
COMSIG_CIRCUIT_SET_CELL,
COMSIG_CIRCUIT_SET_ON,
COMSIG_CIRCUIT_SET_SHELL,
))
return ..()
/obj/item/circuit_component/module/proc/handle_set_cell(datum/source, obj/item/stock_parts/cell/cell)
SIGNAL_HANDLER
internal_circuit.set_cell(cell)
/obj/item/circuit_component/module/proc/handle_set_on(datum/source, new_value)
SIGNAL_HANDLER
internal_circuit.set_on(new_value)
/obj/item/circuit_component/module/proc/handle_set_shell(datum/source, atom/movable/new_shell)
SIGNAL_HANDLER
internal_circuit.set_shell(new_shell)
/obj/item/circuit_component/module/Destroy()
QDEL_NULL(input_component)
QDEL_NULL(output_component)
QDEL_NULL(internal_circuit)
linked_ports = null
return ..()
/obj/item/circuit_component/module/ui_data(mob/user)
. = list()
.["input_ports"] = list()
for(var/datum/port/input/input_port as anything in input_ports)
.["input_ports"] += list(list(
"name" = input_port.name,
"type" = input_port.datatype,
))
.["output_ports"] = list()
for(var/datum/port/output/output_port as anything in output_ports)
.["output_ports"] += list(list(
"name" = output_port.name,
"type" = output_port.datatype,
))
/obj/item/circuit_component/module/ui_static_data(mob/user)
. = list()
.["global_port_types"] = GLOB.wiremod_basic_types
/obj/item/circuit_component/module/attackby(obj/item/I, mob/living/user, params)
if(istype(I, /obj/item/circuit_component))
internal_circuit.attackby(I, user, params)
return
return ..()
#define WITHIN_RANGE(id, table) (id >= 1 && id <= length(table))
/obj/item/circuit_component/module/ui_act(action, list/params)
. = ..()
if(.)
return
switch(action)
if("open_internal_circuit")
internal_circuit.interact(usr)
. = TRUE
if("add_input_port")
if(length(input_ports) > port_limit)
return
add_and_link_input_port("Input Port", PORT_TYPE_ANY)
. = TRUE
if("remove_input_port")
var/port_id = text2num(params["port_id"])
if(!WITHIN_RANGE(port_id, input_ports))
return
var/datum/port/removed_port = input_ports[port_id]
linked_ports -= removed_port
remove_input_port(removed_port)
input_component.remove_output_port(input_component.output_ports[port_id])
. = TRUE
if("add_output_port")
if(length(output_ports) > port_limit)
return
add_and_link_output_port("Output Port", PORT_TYPE_ANY)
. = TRUE
if("remove_output_port")
var/port_id = text2num(params["port_id"])
if(!WITHIN_RANGE(port_id, output_ports))
return
var/datum/port/removed_port = output_component.input_ports[port_id]
linked_ports -= removed_port
remove_output_port(output_ports[port_id])
output_component.remove_input_port(removed_port)
. = TRUE
if("set_port_name", "set_port_type")
var/port_id = text2num(params["port_id"])
var/is_input = params["is_input"]
var/list/ports_to_use
var/list/internal_ports_to_use
if(is_input)
ports_to_use = input_ports
internal_ports_to_use = input_component.output_ports
else
ports_to_use = output_ports
internal_ports_to_use = output_component.input_ports
if(!WITHIN_RANGE(port_id, ports_to_use))
return
var/datum/port/component_port = ports_to_use[port_id]
var/datum/port/internal_component_port = internal_ports_to_use[port_id]
if(action == "set_port_type")
var/type = params["port_type"]
if(!(type in GLOB.wiremod_basic_types))
return
component_port.set_datatype(type)
internal_component_port.set_datatype(type)
else
var/port_name = params["port_name"]
if(!port_name)
return
port_name = strip_html(port_name, PORT_MAX_NAME_LENGTH)
component_port.name = port_name
internal_component_port.name = port_name
. = TRUE
if(.)
SStgui.update_uis(internal_circuit)
#undef WITHIN_RANGE
/obj/item/circuit_component/module/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "CircuitModule", name)
ui.open()
ui.set_autoupdate(FALSE)
@@ -0,0 +1,68 @@
/**
* # Light Component
*
* Emits a light of a specific brightness and colour. Requires a shell.
*/
/obj/item/circuit_component/light
display_name = "Light"
desc = "A component that emits a light of a specific brightness and colour. Requires a shell."
/// The colours of the light
var/datum/port/input/red
var/datum/port/input/green
var/datum/port/input/blue
/// The brightness
var/datum/port/input/brightness
/// Whether the light is on or not
var/datum/port/input/on
var/max_power = 5
var/min_lightness = 0.4
var/shell_light_color
/obj/item/circuit_component/light/get_ui_notices()
. = ..()
. += create_ui_notice("Maximum Brightness: [max_power]", "orange", "lightbulb")
/obj/item/circuit_component/light/Initialize()
. = ..()
red = add_input_port("Red", PORT_TYPE_NUMBER)
green = add_input_port("Green", PORT_TYPE_NUMBER)
blue = add_input_port("Blue", PORT_TYPE_NUMBER)
brightness = add_input_port("Brightness", PORT_TYPE_NUMBER)
on = add_input_port("On", PORT_TYPE_NUMBER)
/obj/item/circuit_component/light/register_shell(atom/movable/shell)
. = ..()
TRIGGER_CIRCUIT_COMPONENT(src, null)
/obj/item/circuit_component/light/unregister_shell(atom/movable/shell)
shell.set_light_on(FALSE)
return ..()
/obj/item/circuit_component/light/input_received(datum/port/input/port)
. = ..()
brightness.set_value(clamp(brightness.value || 0, 0, max_power))
red.set_value(clamp(red.value, 0, 255))
blue.set_value(clamp(blue.value, 0, 255))
green.set_value(clamp(green.value, 0, 255))
var/list/hsl = rgb2hsl(red.value || 0, green.value || 0, blue.value || 0)
var/list/light_col = hsl2rgb(hsl[1], hsl[2], max(min_lightness, hsl[3]))
shell_light_color = rgb(light_col[1], light_col[2], light_col[3])
if(.)
return
if(parent.shell)
set_atom_light(parent.shell)
/obj/item/circuit_component/light/proc/set_atom_light(atom/movable/target_atom)
// Clamp anyways just for safety
var/bright_val = min(max(brightness.value || 0, 0), max_power)
target_atom.set_light_power(bright_val)
target_atom.set_light_range(bright_val)
target_atom.set_light_color(shell_light_color)
target_atom.set_light_on(!!on.value)
@@ -0,0 +1,175 @@
/**
* # Man-Machine Interface Component
*
* Allows an MMI to be inserted into a shell, allowing it to be linked up. Requires a shell.
*/
/obj/item/circuit_component/mmi
display_name = "Man-Machine Interface"
desc = "A component that allows MMI to enter shells to send output signals."
/// The message to send to the MMI in the shell.
var/datum/port/input/message
/// Sends the current MMI a message
var/datum/port/input/send
/// Ejects the current MMI
var/datum/port/input/eject
/// Called when the MMI tries moving north
var/datum/port/output/north
/// Called when the MMI tries moving east
var/datum/port/output/east
/// Called when the MMI tries moving south
var/datum/port/output/south
/// Called when the MMI tries moving west
var/datum/port/output/west
/// Returns what the MMI last clicked on.
var/datum/port/output/clicked_atom
/// Called when the MMI clicks.
var/datum/port/output/attack
/// Called when the MMI right clicks.
var/datum/port/output/secondary_attack
/// The current MMI card
var/obj/item/mmi/brain
/// Maximum length of the message that can be sent to the MMI
var/max_length = 300
/obj/item/circuit_component/mmi/Initialize()
. = ..()
message = add_input_port("Message", PORT_TYPE_STRING)
send = add_input_port("Send Message", PORT_TYPE_SIGNAL)
eject = add_input_port("Eject", PORT_TYPE_SIGNAL)
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)
attack = add_output_port("Attack", PORT_TYPE_SIGNAL)
secondary_attack = add_output_port("Secondary Attack", PORT_TYPE_SIGNAL)
clicked_atom = add_output_port("Target Entity", PORT_TYPE_ATOM)
/obj/item/circuit_component/mmi/Destroy()
remove_current_brain()
return ..()
/obj/item/circuit_component/mmi/input_received(datum/port/input/port)
. = ..()
if(.)
return
if(!brain)
return
if(COMPONENT_TRIGGERED_BY(eject, port))
remove_current_brain()
if(COMPONENT_TRIGGERED_BY(send, port))
if(!message.value)
return
var/msg_str = copytext(html_encode(message.value), 1, max_length)
var/mob/living/target = brain.brainmob
if(!target)
return
to_chat(target, "[span_bold("You hear a message in your ear: ")][msg_str]")
/obj/item/circuit_component/mmi/register_shell(atom/movable/shell)
. = ..()
RegisterSignal(shell, COMSIG_PARENT_ATTACKBY, .proc/handle_attack_by)
/obj/item/circuit_component/mmi/unregister_shell(atom/movable/shell)
UnregisterSignal(shell, COMSIG_PARENT_ATTACKBY)
remove_current_brain()
return ..()
/obj/item/circuit_component/mmi/proc/handle_attack_by(atom/movable/shell, obj/item/item, mob/living/attacker)
SIGNAL_HANDLER
if(istype(item, /obj/item/mmi))
var/obj/item/mmi/target_mmi = item
if(!target_mmi.brainmob)
return
add_mmi(item)
return COMPONENT_NO_AFTERATTACK
/obj/item/circuit_component/mmi/proc/add_mmi(obj/item/mmi/to_add)
remove_current_brain()
to_add.forceMove(src)
if(to_add.brainmob)
update_mmi_mob(to_add, null, to_add.brainmob)
brain = to_add
RegisterSignal(to_add, COMSIG_PARENT_QDELETING, .proc/remove_current_brain)
RegisterSignal(to_add, COMSIG_MOVABLE_MOVED, .proc/mmi_moved)
/obj/item/circuit_component/mmi/proc/mmi_moved(atom/movable/mmi)
SIGNAL_HANDLER
if(mmi.loc != src)
remove_current_brain()
/obj/item/circuit_component/mmi/proc/remove_current_brain()
SIGNAL_HANDLER
if(!brain)
return
if(brain.brainmob)
update_mmi_mob(brain, brain.brainmob)
UnregisterSignal(brain, list(
COMSIG_PARENT_QDELETING,
COMSIG_MOVABLE_MOVED
))
if(brain.loc == src)
brain.forceMove(drop_location())
brain = null
/obj/item/circuit_component/mmi/proc/update_mmi_mob(datum/source, mob/living/old_mmi, mob/living/new_mmi)
SIGNAL_HANDLER
if(old_mmi)
old_mmi.remote_control = null
UnregisterSignal(old_mmi, COMSIG_MOB_CLICKON)
if(new_mmi)
new_mmi.remote_control = src
RegisterSignal(new_mmi, COMSIG_MOB_CLICKON, .proc/handle_mmi_attack)
/obj/item/circuit_component/mmi/relaymove(mob/living/user, direct)
if(user != brain.brainmob)
return ..()
if(direct & NORTH)
north.set_output(COMPONENT_SIGNAL)
if(direct & WEST)
west.set_output(COMPONENT_SIGNAL)
if(direct & EAST)
east.set_output(COMPONENT_SIGNAL)
if(direct & SOUTH)
south.set_output(COMPONENT_SIGNAL)
return TRUE
/obj/item/circuit_component/mmi/proc/handle_mmi_attack(mob/living/source, atom/target, list/mods)
SIGNAL_HANDLER
var/list/modifiers = params2list(mods)
if(modifiers[RIGHT_CLICK])
clicked_atom.set_output(target)
secondary_attack.set_output(COMPONENT_SIGNAL)
. = COMSIG_MOB_CANCEL_CLICKON
else if(modifiers[LEFT_CLICK] && !modifiers[SHIFT_CLICK] && !modifiers[ALT_CLICK] && !modifiers[CTRL_CLICK])
clicked_atom.set_output(target)
attack.set_output(COMPONENT_SIGNAL)
. = COMSIG_MOB_CANCEL_CLICKON
/obj/item/circuit_component/mmi/add_to(obj/item/integrated_circuit/add_to)
. = ..()
if(HAS_TRAIT(add_to, TRAIT_COMPONENT_MMI))
return FALSE
ADD_TRAIT(add_to, TRAIT_COMPONENT_MMI, src)
/obj/item/circuit_component/mmi/removed_from(obj/item/integrated_circuit/removed_from)
REMOVE_TRAIT(removed_from, TRAIT_COMPONENT_MMI, src)
remove_current_brain()
return ..()
@@ -0,0 +1,113 @@
/**
* # Pathfinding component
*
* Calcualtes a path, returns a list of entities. Each entity is the next step in the path. Can be used with the direction component to move.
*/
/obj/item/circuit_component/pathfind
display_name = "Pathfinder"
desc = "When triggered, the next step to the target's location as an entity. This can be used with the direction component and the drone shell to make it move on its own. The Id Card input port is for considering ID access when pathing, it does not give the shell actual access."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
var/datum/port/input/input_X
var/datum/port/input/input_Y
var/datum/port/input/id_card
var/datum/port/output/output
var/datum/port/output/finished
var/datum/port/output/failed
var/datum/port/output/reason_failed
var/list/path
var/turf/old_dest
var/turf/next_turf
// Cooldown to limit how frequently we can path to the same location.
var/same_path_cooldown = 5 SECONDS
var/different_path_cooldown = 30 SECONDS
var/max_range = 60
/obj/item/circuit_component/pathfind/get_ui_notices()
. = ..()
// Not necessary to show the same path cooldown, since it doesn't change much for the player
. += create_ui_notice("Pathfinding Cooldown: [DisplayTimeText(different_path_cooldown)]", "orange", "stopwatch")
. += create_ui_notice("Maximum Range: [max_range] tiles", "orange", "info")
/obj/item/circuit_component/pathfind/Initialize()
. = ..()
input_X = add_input_port("Target X", PORT_TYPE_NUMBER, FALSE)
input_Y = add_input_port("Target Y", PORT_TYPE_NUMBER, FALSE)
id_card = add_input_port("ID Card", PORT_TYPE_ATOM, FALSE)
output = add_output_port("Next step", PORT_TYPE_ATOM)
finished = add_output_port("Arrived to destination", PORT_TYPE_SIGNAL)
failed = add_output_port("Failed", PORT_TYPE_SIGNAL)
reason_failed = add_output_port("Fail reason", PORT_TYPE_STRING)
/obj/item/circuit_component/pathfind/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/target_X = input_X.value
if(isnull(target_X))
return
var/target_Y = input_Y.value
if(isnull(target_Y))
return
var/atom/path_id = id_card.value
if(path_id && !istype(path_id, /obj/item/card/id))
path_id = null
failed.set_output(COMPONENT_SIGNAL)
reason_failed.set_output("Object marked is not an ID! Using no ID instead.")
// Get both the current turf and the destination's turf
var/turf/current_turf = get_turf(src)
var/turf/destination = locate(target_X, target_Y, current_turf?.z)
// We're already here! No need to do anything.
if(current_turf == destination)
finished.set_output(COMPONENT_SIGNAL)
old_dest = null
TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME)
next_turf = null
return
// If we're going to the same place and the cooldown hasn't subsided, we're probably on the same path as before
if (destination == old_dest && TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME))
// Check if the current turf is the same as the current turf we're supposed to be in. If so, then we set the next step as the next turf on the list
if(current_turf == next_turf)
popleft(path)
next_turf = get_turf(path[1])
output.set_output(next_turf)
// Restart the cooldown since we don't need a new path ( TIMER_COOLDOWN_START might restart the timer by itself and i dont need to call TIMER_COOLDOWN_END, but better safe than sorry )
TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME)
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME, same_path_cooldown)
else // Either we're not going to the same place or the cooldown is over. Either way, we need a new path
if(destination != old_dest && TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_PATHFIND_DIF))
failed.set_output(COMPONENT_SIGNAL)
reason_failed.set_output("Cooldown still active!")
return
TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME)
old_dest = destination
path = get_path_to(src, destination, max_range, id=path_id)
if(length(path) == 0 || !path)// Check if we can even path there
next_turf = null
failed.set_output(COMPONENT_SIGNAL)
reason_failed.set_output("Can't go there!")
return
else
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_PATHFIND_DIF, different_path_cooldown)
next_turf = get_turf(path[1])
output.set_output(next_turf)
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME, same_path_cooldown)
@@ -0,0 +1,31 @@
/**
* # Pull Component
*
* Tells the shell to start pulling on a designated atom. Only works on movable shells.
*/
/obj/item/circuit_component/pull
display_name = "Start Pulling"
desc = "A component that can force the shell to pull entities. Only works for drone shells."
/// Frequency input
var/datum/port/input/target
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
/obj/item/circuit_component/pull/Initialize()
. = ..()
target = add_input_port("Target", PORT_TYPE_ATOM)
/obj/item/circuit_component/pull/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/atom/target_atom = target.value
if(!target_atom)
return
var/mob/shell = parent.shell
if(!istype(shell) || get_dist(shell, target_atom) > 1 || shell.z != target_atom.z)
return
shell.start_pulling(target_atom)
@@ -0,0 +1,74 @@
#define COMP_RADIO_PUBLIC "public"
#define COMP_RADIO_PRIVATE "private"
/**
* # Radio Component
*
* Listens out for signals on the designated frequencies and sends signals on designated frequencies
*/
/obj/item/circuit_component/radio
display_name = "Radio"
desc = "A component that can listen and send frequencies. If set to private, the component will only receive signals from other components attached to circuitboards with the same owner id."
/// The publicity options. Controls whether it's public or private.
var/datum/port/input/option/public_options
/// Frequency input
var/datum/port/input/freq
/// Signal input
var/datum/port/input/code
/// Current frequency value
var/current_freq = DEFAULT_SIGNALER_CODE
var/datum/radio_frequency/radio_connection
/obj/item/circuit_component/radio/populate_options()
var/static/component_options = list(
COMP_RADIO_PUBLIC,
COMP_RADIO_PRIVATE,
)
public_options = add_option_port("Encryption Options", component_options)
/obj/item/circuit_component/radio/Initialize()
. = ..()
freq = add_input_port("Frequency", PORT_TYPE_NUMBER, default = FREQ_SIGNALER)
code = add_input_port("Code", PORT_TYPE_NUMBER, default = DEFAULT_SIGNALER_CODE)
TRIGGER_CIRCUIT_COMPONENT(src, null)
// These are cleaned up on the parent
trigger_input = add_input_port("Send", PORT_TYPE_SIGNAL)
trigger_output = add_output_port("Received", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/radio/Destroy()
SSradio.remove_object(src, current_freq)
return ..()
/obj/item/circuit_component/radio/input_received(datum/port/input/port)
. = ..()
freq.set_value(sanitize_frequency(freq.value, TRUE))
if(.)
return
var/frequency = freq.value
SSradio.remove_object(src, current_freq)
radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER)
current_freq = frequency
if(COMPONENT_TRIGGERED_BY(trigger_input, port))
var/datum/signal/signal = new(list("code" = round(code.value) || 0, "key" = parent?.owner_id))
radio_connection.post_signal(src, signal)
/obj/item/circuit_component/radio/receive_signal(datum/signal/signal)
. = FALSE
if(!signal)
return
if(signal.data["code"] != round(code.value || 0))
return
if(public_options.value == COMP_RADIO_PRIVATE && parent?.owner_id != signal.data["key"])
return
trigger_output.set_output(COMPONENT_SIGNAL)
#undef COMP_RADIO_PUBLIC
#undef COMP_RADIO_PRIVATE
@@ -0,0 +1,66 @@
/**
* # Sound Emitter Component
*
* A component that emits a sound when it receives an input.
*/
/obj/item/circuit_component/soundemitter
display_name = "Sound Emitter"
desc = "A component that emits a sound when it receives an input. The frequency is a multiplier which determines the speed at which the sound is played"
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
/// Sound to play
var/datum/port/input/option/sound_file
/// Volume of the sound when played
var/datum/port/input/volume
/// Frequency of the sound when played
var/datum/port/input/frequency
/// The cooldown for this component of how often it can play sounds.
var/sound_cooldown = 2 SECONDS
var/list/options_map
/obj/item/circuit_component/soundemitter/get_ui_notices()
. = ..()
. += create_ui_notice("Sound Cooldown: [DisplayTimeText(sound_cooldown)]", "orange", "stopwatch")
/obj/item/circuit_component/soundemitter/Initialize()
. = ..()
volume = add_input_port("Volume", PORT_TYPE_NUMBER, default = 35)
frequency = add_input_port("Frequency", PORT_TYPE_NUMBER, default = 0)
/obj/item/circuit_component/soundemitter/populate_options()
var/static/component_options = list(
"Buzz" = 'sound/machines/buzz-sigh.ogg',
"Buzz Twice" = 'sound/machines/buzz-two.ogg',
"Chime" = 'sound/machines/chime.ogg',
"Honk" = 'sound/items/bikehorn.ogg',
"Ping" = 'sound/machines/ping.ogg',
"Sad Trombone" = 'sound/misc/sadtrombone.ogg',
"Warn" = 'sound/machines/warning-buzzer.ogg',
"Slow Clap" = 'sound/machines/slowclap.ogg',
)
sound_file = add_option_port("Sound Option", component_options)
options_map = component_options
/obj/item/circuit_component/soundemitter/input_received(datum/port/input/port)
. = ..()
volume.set_value(clamp(volume.value, 0, 100))
frequency.set_value(clamp(frequency.value, -100, 100))
if(.)
return
if(TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_SOUNDEMITTER))
return
var/sound_to_play = options_map[sound_file.value]
if(!sound_to_play)
return
playsound(src, sound_to_play, volume.value, frequency != 0, frequency = frequency.value)
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_SOUNDEMITTER, sound_cooldown)
@@ -0,0 +1,40 @@
/**
* # Speech Component
*
* Sends a message. Requires a shell.
*/
/obj/item/circuit_component/speech
display_name = "Speech"
desc = "A component that sends a message. Requires a shell."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
/// The message to send
var/datum/port/input/message
/// The cooldown for this component of how often it can send speech messages.
var/speech_cooldown = 1 SECONDS
/obj/item/circuit_component/speech/get_ui_notices()
. = ..()
. += create_ui_notice("Speech Cooldown: [DisplayTimeText(speech_cooldown)]", "orange", "stopwatch")
/obj/item/circuit_component/speech/Initialize()
. = ..()
message = add_input_port("Message", PORT_TYPE_STRING, FALSE)
/obj/item/circuit_component/speech/input_received(datum/port/input/port)
. = ..()
if(.)
return
if(TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_SPEECH))
return
if(message.value)
var/atom/movable/shell = parent.shell
// Prevents appear as the individual component if there is a shell.
if(shell)
shell.say(message.value)
else
say(message.value)
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_SPEECH, speech_cooldown)
@@ -0,0 +1,42 @@
/**
* # 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
/// Entity to get variable of
var/datum/port/input/entity
/// Variable name
var/datum/port/input/variable_name
/// Variable value
var/datum/port/output/output_value
/obj/item/circuit_component/get_variable/Initialize()
. = ..()
entity = add_input_port("Target", PORT_TYPE_ATOM)
variable_name = add_input_port("Variable Name", PORT_TYPE_STRING)
output_value = add_output_port("Output Value", PORT_TYPE_ANY)
/obj/item/circuit_component/get_variable/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/atom/object = entity.value
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])
@@ -0,0 +1,72 @@
#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
/// 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)
/obj/item/circuit_component/proccall/Initialize()
. = ..()
entity = add_input_port("Target", PORT_TYPE_ATOM)
proc_name = add_input_port("Proc Name", PORT_TYPE_STRING)
arguments = add_input_port("Arguments", PORT_TYPE_LIST)
output_value = add_output_port("Output Value", PORT_TYPE_ANY)
/obj/item/circuit_component/proccall/input_received(datum/port/input/port)
. = ..()
if(.)
return
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/params = arguments.value || list()
if(!to_invoke)
return
GLOB.AdminProcCaller = "CHAT_[parent.display_name]" //_ won't show up in ckeys so it'll never match with a real admin
var/result = WrapAdminProcCall(called_on, to_invoke, params)
GLOB.AdminProcCaller = null
output_value.set_output(result)
#undef COMP_PROC_GLOBAL
#undef COMP_PROC_OBJECT
@@ -0,0 +1,36 @@
/**
* # 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/Initialize()
. = ..()
sdql_operation = add_input_port("SDQL String", PORT_TYPE_STRING)
results = add_output_port("Result", PORT_TYPE_LIST)
/obj/item/circuit_component/sdql_operation/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/operation = sdql_operation.value
if(GLOB.AdminProcCaller || !operation)
return TRUE
GLOB.AdminProcCaller = "CHAT_[parent.display_name]" //_ won't show up in ckeys so it'll never match with a real admin
var/list/result = world.SDQL2_query(operation, parent.get_creator_admin(), parent.get_creator())
GLOB.AdminProcCaller = null
results.set_output(result)
@@ -0,0 +1,36 @@
/**
* # 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/Initialize()
. = ..()
entity = add_input_port("Target", PORT_TYPE_ATOM)
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)
. = ..()
if(.)
return
var/atom/object = entity.value
var/var_name = variable_name.value
if(!var_name || !object)
return
object.vv_edit_var(var_name, new_value.value)
@@ -0,0 +1,47 @@
/**
* # 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/Initialize()
. = ..()
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)
spawned_atom = add_output_port("Spawned Atom", PORT_TYPE_ATOM)
/obj/item/circuit_component/spawn_atom/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/typepath = input_path.value
if(!ispath(typepath, /atom))
return
var/list/params = parameters.value
if(!params)
params = list()
params.Insert(1, spawn_at.value)
spawned_atom.set_output(new typepath(arglist(params)))
@@ -0,0 +1,28 @@
/**
* # 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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
type_output.set_output(text2path(input_path.value))
@@ -0,0 +1,66 @@
/**
* # 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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
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)
@@ -0,0 +1,34 @@
/**
* # 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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
var/turf/location = get_turf(src)
x_pos.set_output(location?.x)
y_pos.set_output(location?.y)
z_pos.set_output(location?.z)
@@ -0,0 +1,62 @@
/**
* # 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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
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)
@@ -0,0 +1,35 @@
/**
* # 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
var/datum/port/output/speaker_port
/// The trigger sent when this event occurs
var/datum/port/output/trigger_port
/obj/item/circuit_component/hear/Initialize()
. = ..()
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)
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)
trigger_port.set_output(COMPONENT_SIGNAL)
@@ -0,0 +1,21 @@
/**
* # 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
/obj/item/circuit_component/self/Initialize()
. = ..()
output = add_output_port("Self", PORT_TYPE_ATOM)
/obj/item/circuit_component/self/register_shell(atom/movable/shell)
output.set_output(shell)
/obj/item/circuit_component/self/unregister_shell(atom/movable/shell)
output.set_output(null)
@@ -0,0 +1,35 @@
/**
* # 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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
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)
@@ -0,0 +1,62 @@
#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/Initialize()
. = ..()
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
@@ -0,0 +1,103 @@
/**
* # 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/Initialize()
. = ..()
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 ..()
@@ -0,0 +1,122 @@
/**
* # 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/Initialize()
. = ..()
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
@@ -0,0 +1,64 @@
/**
* # 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/Initialize()
. = ..()
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")
@@ -0,0 +1,48 @@
/**
* # 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/Initialize()
. = ..()
list_port = add_input_port("List", PORT_TYPE_LIST)
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)
. = ..()
if(.)
return
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))
@@ -0,0 +1,42 @@
/**
* # 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/Initialize()
. = ..()
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)
/obj/item/circuit_component/get_column/input_received(datum/port/input/port)
. = ..()
if(.)
return
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)
@@ -0,0 +1,42 @@
/**
* # 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 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
/obj/item/circuit_component/index/Initialize()
. = ..()
index_port = add_input_port("Index", PORT_TYPE_ANY)
list_port = add_input_port("List", PORT_TYPE_LIST)
output = add_output_port("Value", PORT_TYPE_ANY)
/obj/item/circuit_component/index/input_received(datum/port/input/port)
. = ..()
if(.)
return
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])
@@ -0,0 +1,42 @@
/**
* # 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/Initialize()
. = ..()
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_LIST)
/obj/item/circuit_component/index_table/input_received(datum/port/input/port)
. = ..()
if(.)
return
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])
@@ -0,0 +1,86 @@
/**
* # List Literal Component
*
* Return a list literal.
*/
/obj/item/circuit_component/list_literal
display_name = "List Literal"
desc = "A component that returns the value of a list at a given index. Attack in hand to increase list size, right click to decrease list size."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
/// 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
/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/set_list_size(new_size)
if(new_size <= 0)
for(var/datum/port/input/port in entry_ports)
remove_input_port(port)
entry_ports = list()
length = 0
return
while(length > new_size)
var/index = length(entry_ports)
var/entry_port = entry_ports[index]
entry_ports -= entry_port
remove_input_port(entry_port)
length--
while(length < new_size)
length++
var/index = length(input_ports)
if(trigger_input)
index -= 1
entry_ports += add_input_port("Index [index+1]", PORT_TYPE_ANY, index = index+1)
/obj/item/circuit_component/list_literal/Initialize()
. = ..()
set_list_size(default_list_size)
list_output = add_output_port("Value", PORT_TYPE_LIST)
/obj/item/circuit_component/list_literal/Destroy()
list_output = null
return ..()
// Increases list length
/obj/item/circuit_component/list_literal/attack_self(mob/user, list/modifiers)
. = ..()
set_list_size(min(length + 1, max_size))
balloon_alert(user, "new size is now [length]")
// Decreases list length
/obj/item/circuit_component/list_literal/attack_self_secondary(mob/user, list/modifiers)
. = ..()
set_list_size(max(length - 1, min_size))
balloon_alert(user, "new size is now [length]")
/obj/item/circuit_component/list_literal/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/list/new_literal = list()
for(var/datum/port/input/entry_port as anything in entry_ports)
// Prevents lists from merging together
new_literal += list(entry_port.value)
list_output.set_output(new_literal)
@@ -0,0 +1,93 @@
/**
* # 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/Initialize()
. = ..()
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/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)
if(.)
return
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)
@@ -0,0 +1,42 @@
/**
* # 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/Initialize()
. = ..()
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)
/obj/item/circuit_component/split/input_received(datum/port/input/port)
. = ..()
if(.)
return
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)
@@ -0,0 +1,88 @@
#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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
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
@@ -0,0 +1,62 @@
/**
* # 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/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)
return ..()
/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
@@ -0,0 +1,29 @@
/**
* # 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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
output.set_output(length(input_port.value))
@@ -0,0 +1,57 @@
#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
@@ -0,0 +1,29 @@
/**
* # 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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
result.set_output(!input_port.value)
@@ -0,0 +1,40 @@
/**
* # 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/Initialize()
. = ..()
minimum = add_input_port("Minimum", PORT_TYPE_NUMBER, FALSE)
maximum = add_input_port("Maximum", PORT_TYPE_NUMBER, FALSE)
output = add_output_port("Output", PORT_TYPE_NUMBER)
/obj/item/circuit_component/random/input_received(datum/port/input/port)
. = ..()
if(.)
return
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))
@@ -0,0 +1,62 @@
/**
* # 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
var/datum/port/input/push_hid
var/datum/port/output/hid
var/datum/port/output/data_package
var/datum/port/output/secondary_package
var/datum/port/input/enc_key
var/datum/port/input/option/data_type_options
var/datum/port/input/option/secondary_data_type_options
/obj/item/circuit_component/ntnet_receive/Initialize()
. = ..()
data_package = add_output_port("Data Package", PORT_TYPE_ANY)
secondary_package = add_output_port("Secondary Package", 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/populate_options()
var/static/component_options = list(
PORT_TYPE_ANY,
PORT_TYPE_STRING,
PORT_TYPE_NUMBER,
PORT_TYPE_LIST,
PORT_TYPE_ATOM,
)
data_type_options = add_option_port("Data Type", component_options)
secondary_data_type_options = add_option_port("Secondary Data Type", component_options)
/obj/item/circuit_component/ntnet_receive/input_received(datum/port/input/port)
. = ..()
if(.)
return
if(COMPONENT_TRIGGERED_BY(data_type_options, port))
data_package.set_datatype(data_type_options.value)
if(COMPONENT_TRIGGERED_BY(secondary_data_type_options, port))
secondary_package.set_datatype(secondary_data_type_options.value)
return TRUE
/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
data_package.set_output(data.data["data"])
secondary_package.set_output(data.data["data_secondary"])
trigger_output.set_output(COMPONENT_SIGNAL)
@@ -0,0 +1,29 @@
/**
* # 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
var/datum/port/input/data_package
var/datum/port/input/secondary_package
var/datum/port/input/enc_key
/obj/item/circuit_component/ntnet_send/Initialize()
. = ..()
data_package = add_input_port("Data Package", PORT_TYPE_ANY)
secondary_package = add_input_port("Secondary Package", PORT_TYPE_ANY)
enc_key = add_input_port("Encryption Key", PORT_TYPE_STRING)
/obj/item/circuit_component/ntnet_send/input_received(datum/port/input/port)
. = ..()
if(.)
return
ntnet_send(list("data" = data_package.value, "data_secondary" = secondary_package.value, "enc_key" = enc_key.value))
@@ -0,0 +1,36 @@
/**
* # 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/Initialize()
. = ..()
result = add_output_port("Result", PORT_TYPE_NUMBER)
/obj/item/circuit_component/pressuresensor/input_received(datum/port/input/port)
. = ..()
if(.)
return
//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)
@@ -0,0 +1,36 @@
/**
* # 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/Initialize()
. = ..()
result = add_output_port("Result", PORT_TYPE_NUMBER)
/obj/item/circuit_component/tempsensor/input_received(datum/port/input/port)
. = ..()
if(.)
return
//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)
@@ -0,0 +1,42 @@
/**
* # 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
/// 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/Initialize()
. = ..()
for(var/port_id in 1 to input_port_amount)
var/letter = ascii2text(text2ascii("A") + (port_id-1))
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)
. = ..()
if(.)
return
var/result = ""
var/list/ports = input_ports.Copy()
ports -= trigger_input
for(var/datum/port/input/input_port as anything in ports)
var/value = input_port.value
if(isnull(value))
continue
result += "[value]"
output.set_output(result)
@@ -0,0 +1,35 @@
/**
* # 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/load_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)
@@ -0,0 +1,54 @@
#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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
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
@@ -0,0 +1,29 @@
/**
* #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/Initialize()
. = ..()
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)
. = ..()
if(.)
return
output.set_output(text2num(input_port.value))
@@ -0,0 +1,40 @@
/**
* # 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 = 5
/obj/item/circuit_component/tostring/Initialize()
. = ..()
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)
. = ..()
if(.)
return
var/value = input_port.value
if(isatom(value))
var/turf/location = get_turf(src)
var/atom/object = value
if(object.z != location.z || get_dist(location, object) > max_range)
output.set_output(PORT_TYPE_ATOM)
return
output.set_output("[value]")
@@ -0,0 +1,57 @@
/**
* # Clock Component
*
* Fires every tick of the circuit timer SS
*/
/obj/item/circuit_component/clock
display_name = "Clock"
desc = "A component that repeatedly fires."
/// Whether the clock is on or not
var/datum/port/input/on
/// The signal from this clock component
var/datum/port/output/signal
/obj/item/circuit_component/clock/get_ui_notices()
. = ..()
. += create_ui_notice("Clock Interval: [DisplayTimeText(COMP_CLOCK_DELAY)]", "orange", "clock")
/obj/item/circuit_component/clock/Initialize()
. = ..()
on = add_input_port("On", PORT_TYPE_NUMBER)
signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/clock/input_received(datum/port/input/port)
. = ..()
if(.)
return
if(on.value)
start_process()
else
stop_process()
/obj/item/circuit_component/clock/Destroy()
stop_process()
return ..()
/obj/item/circuit_component/clock/process(delta_time)
signal.set_output(COMPONENT_SIGNAL)
/**
* Adds the component to the SSclock_component process list
*
* Starts ticking to send signals between periods of time
*/
/obj/item/circuit_component/clock/proc/start_process()
START_PROCESSING(SSclock_component, src)
/**
* Removes the component to the SSclock_component process list
*
* Signals stop getting sent.
*/
/obj/item/circuit_component/clock/proc/stop_process()
STOP_PROCESSING(SSclock_component, src)
@@ -0,0 +1,43 @@
/// The minimum delay value that the delay component can have.
#define COMP_DELAY_MIN_VALUE 0.1
/**
* # Delay Component
*
* Delays a signal by a specified duration.
*/
/obj/item/circuit_component/delay
display_name = "Delay"
desc = "A component that delays a signal by a specified duration."
/// Amount to delay by
var/datum/port/input/delay_amount
/// Input signal to fire the delay
var/datum/port/input/trigger
/// The output of the signal
var/datum/port/output/output
/obj/item/circuit_component/delay/Initialize()
. = ..()
delay_amount = add_input_port("Delay", PORT_TYPE_NUMBER, FALSE)
trigger = add_input_port("Trigger", PORT_TYPE_SIGNAL)
output = add_output_port("Result", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/delay/input_received(datum/port/input/port)
. = ..()
if(.)
return
if(!COMPONENT_TRIGGERED_BY(trigger, port))
return
var/delay = delay_amount.value
if(delay > COMP_DELAY_MIN_VALUE)
// Convert delay into deciseconds
addtimer(CALLBACK(output, /datum/port/output.proc/set_output, trigger.value), delay*10)
else
output.set_output(trigger.value)
#undef COMP_DELAY_MIN_VALUE
@@ -0,0 +1,70 @@
/**
* # Getter Component
*
* Gets the current value from a variable.
*/
/obj/item/circuit_component/getter
display_name = "Variable Getter"
desc = "A component that gets a variable globally on the circuit."
/// Variable name
var/datum/port/input/option/variable_name
/// The value of the variable
var/datum/port/output/value
var/datum/circuit_variable/current_variable
/obj/item/circuit_component/getter/populate_options()
variable_name = add_option_port("Variable", null)
/obj/item/circuit_component/getter/add_to(obj/item/integrated_circuit/added_to)
. = ..()
variable_name.possible_options = added_to.circuit_variables
/obj/item/circuit_component/getter/removed_from(obj/item/integrated_circuit/removed_from)
variable_name.possible_options = null
return ..()
/obj/item/circuit_component/getter/Initialize()
. = ..()
value = add_output_port("Value", PORT_TYPE_ANY)
/obj/item/circuit_component/getter/input_received(datum/port/input/port)
. = ..()
// We don't care much about the parent's return value. We only care if the parent exists
// since this should never really fail.
if(!parent)
return
var/variable_string = variable_name.value
if(!variable_string)
remove_current_variable()
value.set_output(null)
return
var/datum/circuit_variable/variable = parent.circuit_variables[variable_string]
if(!variable)
remove_current_variable()
value.set_output(null)
return
set_current_variable(variable)
value.set_output(variable.value)
/obj/item/circuit_component/getter/proc/remove_current_variable()
SIGNAL_HANDLER
if(current_variable)
current_variable.remove_listener(src)
UnregisterSignal(current_variable, COMSIG_PARENT_QDELETING)
current_variable = null
/obj/item/circuit_component/getter/proc/set_current_variable(datum/circuit_variable/variable)
if(variable == current_variable)
return
remove_current_variable()
current_variable = variable
current_variable.add_listener(src)
RegisterSignal(current_variable, COMSIG_PARENT_QDELETING, .proc/remove_current_variable)
value.set_datatype(variable.datatype)
@@ -0,0 +1,82 @@
/**
* # Router Component
*
* Writes one of multiple inputs to one of multiple outputs.
*/
/obj/item/circuit_component/router
display_name = "Router"
desc = "Copies the input chosen by \"Input Selector\" to the output chosen by \"Output Selector\"."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
var/datum/port/input/option/router_options
/// Which ports to connect.
var/datum/port/input/input_selector
var/datum/port/input/output_selector
/// How many ports to have.
var/input_port_amount = 4
var/output_port_amount = 4
/// Current type of the ports
var/current_type
/// The ports to route.
var/list/datum/port/input/ins
var/list/datum/port/output/outs
/obj/item/circuit_component/router/populate_options()
var/static/component_options = list(
PORT_TYPE_ANY,
PORT_TYPE_STRING,
PORT_TYPE_NUMBER,
PORT_TYPE_LIST,
PORT_TYPE_ATOM,
)
router_options = add_option_port("Router Options", component_options)
/obj/item/circuit_component/router/Initialize()
. = ..()
current_type = router_options.value
if(input_port_amount > 1)
input_selector = add_input_port("Input Selector", PORT_TYPE_NUMBER, default = 1)
if(output_port_amount > 1)
output_selector = add_input_port("Output Selector", PORT_TYPE_NUMBER, default = 1)
ins = list()
for(var/port_id in 1 to input_port_amount)
ins += add_input_port(input_port_amount > 1 ? "Input [port_id]" : "Input", current_type)
outs = list()
for(var/port_id in 1 to output_port_amount)
outs += add_output_port(output_port_amount > 1 ? "Output [port_id]" : "Output", current_type)
/obj/item/circuit_component/router/Destroy()
input_selector = null
output_selector = null
ins.Cut()
ins = null
outs.Cut()
outs = null
return ..()
// If I is in range, L[I]. If I is out of range, wrap around.
#define WRAPACCESS(L, I) L[(((I||1)-1)%length(L)+length(L))%length(L)+1]
/obj/item/circuit_component/router/input_received(datum/port/input/port)
. = ..()
var/current_option = router_options.value
if(current_type != current_option)
current_type = current_option
for(var/datum/port/input/input as anything in ins)
input.set_datatype(current_type)
for(var/datum/port/output/output as anything in outs)
output.set_datatype(current_type)
if(.)
return
var/datum/port/input/input = WRAPACCESS(ins, input_selector ? input_selector.value : 1)
var/datum/port/output/output = WRAPACCESS(outs, output_selector ? output_selector.value : 1)
output.set_output(input.value)
/obj/item/circuit_component/router/multiplexer
display_name = "Multiplexer"
desc = "Copies the input chosen by \"Input Selector\" to the output."
output_port_amount = 1
@@ -0,0 +1,58 @@
/**
* # Setter Component
*
* Stores the current input when triggered into a variable.
*/
/obj/item/circuit_component/setter
display_name = "Variable Setter"
desc = "A component that sets a variable globally on the circuit."
circuit_flags = CIRCUIT_FLAG_OUTPUT_SIGNAL
/// Variable name
var/datum/port/input/option/variable_name
/// The input to store
var/datum/port/input/input_port
/// The trigger to store the current value of the input
var/datum/port/input/trigger
var/current_type
/obj/item/circuit_component/setter/populate_options()
variable_name = add_option_port("Variable", null)
/obj/item/circuit_component/setter/add_to(obj/item/integrated_circuit/added_to)
. = ..()
variable_name.possible_options = added_to.circuit_variables
/obj/item/circuit_component/setter/removed_from(obj/item/integrated_circuit/removed_from)
variable_name.possible_options = null
return ..()
/obj/item/circuit_component/setter/Initialize()
. = ..()
input_port = add_input_port("Input", PORT_TYPE_ANY)
trigger = add_input_port("Store", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/setter/input_received(datum/port/input/port)
. = ..()
var/variable_string = variable_name.value
if(!variable_string)
return
var/datum/circuit_variable/variable = parent.circuit_variables[variable_string]
if(!variable)
return
if(variable.datatype != current_type)
current_type = variable.datatype
input_port.set_datatype(current_type)
if(.)
return
if(!COMPONENT_TRIGGERED_BY(trigger, port))
return TRUE
variable.set_value(input_port.value)
@@ -0,0 +1,60 @@
/**
* # Typecast Component
*
* A component that casts a value to a type if it matches or outputs null.
*/
/obj/item/circuit_component/typecast
display_name = "Typecast"
desc = "A component that casts a value to a type if it matches or outputs null."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
var/datum/port/input/option/typecast_options
var/datum/port/input/input_value
var/datum/port/output/output_value
var/current_type
/obj/item/circuit_component/typecast/Initialize()
. = ..()
current_type = typecast_options.value
input_value = add_input_port("Input", PORT_TYPE_ANY)
output_value = add_output_port("Output", current_type)
/obj/item/circuit_component/typecast/populate_options()
var/static/list/component_options = list(
PORT_TYPE_STRING,
PORT_TYPE_NUMBER,
PORT_TYPE_LIST,
PORT_TYPE_ATOM,
)
typecast_options = add_option_port("Typecast Options", component_options)
/obj/item/circuit_component/typecast/input_received(datum/port/input/port)
. = ..()
var/current_option = typecast_options.value
if(current_type != current_option)
current_type = current_option
output_value.set_datatype(current_type)
if(.)
return
var/value = input_value.value
var/value_to_set = null
switch(current_option)
if(PORT_TYPE_STRING)
if(istext(value))
value_to_set = value
if(PORT_TYPE_NUMBER)
if(isnum(value))
value_to_set = value
if(PORT_TYPE_LIST)
if(islist(value))
value_to_set = value
if(PORT_TYPE_ATOM)
if(isatom(value))
value_to_set = value
output_value.set_output(value_to_set)
@@ -0,0 +1,51 @@
#define COMP_TYPECHECK_MOB "organism"
#define COMP_TYPECHECK_HUMAN "humanoid"
/**
* # Typecheck Component
*
* Checks the type of a value
*/
/obj/item/circuit_component/compare/typecheck
display_name = "Typecheck"
desc = "A component that checks the type of its input."
input_port_amount = 1
var/datum/port/input/option/typecheck_options
/obj/item/circuit_component/compare/typecheck/populate_options()
var/static/component_options = list(
PORT_TYPE_STRING,
PORT_TYPE_NUMBER,
PORT_TYPE_LIST,
PORT_TYPE_ATOM,
COMP_TYPECHECK_MOB,
COMP_TYPECHECK_HUMAN,
)
typecheck_options = add_option_port("Typecheck Options", component_options)
/obj/item/circuit_component/compare/typecheck/do_comparisons(list/ports)
if(!length(ports))
return
. = FALSE
// We're only comparing the first port/value. There shouldn't be any more.
var/datum/port/input/input_port = ports[1]
var/input_val = input_port.value
switch(typecheck_options.value)
if(PORT_TYPE_STRING)
return istext(input_val)
if(PORT_TYPE_NUMBER)
return isnum(input_val)
if(PORT_TYPE_LIST)
return islist(input_val)
if(PORT_TYPE_ATOM)
return isatom(input_val)
if(COMP_TYPECHECK_MOB)
return ismob(input_val)
if(COMP_TYPECHECK_HUMAN)
return ishuman(input_val)
#undef COMP_TYPECHECK_MOB
#undef COMP_TYPECHECK_HUMAN
+75
View File
@@ -0,0 +1,75 @@
/// An admin verb to view all circuits, plus useful information
/datum/admins/proc/view_all_circuits()
set category = "Admin.Game"
set name = "View All Circuits"
var/static/datum/circuit_admin_panel/circuit_admin_panel = new
circuit_admin_panel.ui_interact(usr)
/datum/circuit_admin_panel
/datum/circuit_admin_panel/ui_static_data(mob/user)
var/list/data = list()
data["circuits"] = list()
for (var/obj/item/integrated_circuit/circuit as anything in GLOB.integrated_circuits)
var/datum/mind/inserter = circuit.inserter_mind?.resolve()
data["circuits"] += list(list(
"ref" = REF(circuit),
"name" = "[circuit.name] in [loc_name(circuit)]",
"creator" = circuit.get_creator(),
"has_inserter" = !isnull(inserter),
))
return data
/datum/circuit_admin_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if (.)
return .
if (!istext(params["circuit"]))
return FALSE
var/obj/item/integrated_circuit/circuit = locate(params["circuit"])
if (!istype(circuit))
to_chat(usr, span_warning("That circuit no longer exists."))
return FALSE
switch (action)
if ("duplicate_circuit")
if (alert(usr, "This will spawn the new circuit at where you are, are you sure?", "Confirm", "Yes", "No") != "Yes")
return FALSE
var/list/errors = list()
var/obj/item/integrated_circuit/loaded/new_circuit = new(usr.drop_location())
new_circuit.load_circuit_data(circuit.convert_to_json(), errors)
if (length(errors))
to_chat(usr, span_warning("Somehow, duplicating the circuit failed:"))
for (var/error in errors)
to_chat(usr, span_warning(error))
if ("follow_circuit")
usr.client?.admin_follow(circuit)
if ("save_circuit")
circuit.attempt_save_to(usr.client)
if ("vv_circuit")
usr.client?.debug_variables(circuit)
if ("open_circuit")
circuit.ui_interact(usr)
if ("open_player_panel")
var/datum/mind/inserter = circuit.inserter_mind?.resolve()
usr.client?.holder?.show_player_panel(inserter?.current)
return TRUE
/datum/circuit_admin_panel/ui_state(mob/user)
return GLOB.admin_state
/datum/circuit_admin_panel/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "CircuitAdminPanel")
ui.open()
+270
View File
@@ -0,0 +1,270 @@
/**
* # Integrated Circuit Component
*
* A component that performs a function when given an input
*
* Can be attached to an integrated circuitboard, where it can then
* be connected between other components to provide an output or to receive
* an input. This is the base type of all components
*/
/obj/item/circuit_component
name = COMPONENT_DEFAULT_NAME
icon = 'icons/obj/module.dmi'
icon_state = "component"
inhand_icon_state = "electronic"
/// The name of the component shown on the UI
var/display_name = "Generic"
/// The integrated_circuit that this component is attached to.
var/obj/item/integrated_circuit/parent
/// A list that contains the outpurt ports on this component
/// Used to connect between the ports
var/list/datum/port/output/output_ports = list()
/// A list that contains the components the input ports on this component
/// Used to connect between the ports
var/list/datum/port/input/input_ports = list()
/// Generic trigger input for triggering this component
var/datum/port/input/trigger_input
var/datum/port/output/trigger_output
/// The flags of the circuit to control basic generalised behaviour.
var/circuit_flags = NONE
/// Used to determine the x position of the component within the UI
var/rel_x = 0
/// Used to determine the y position of the component within the UI
var/rel_y = 0
/// The power usage whenever this component receives an input
var/power_usage_per_input = 1
// Whether the component is removable or not. Only affects user UI
var/removable = TRUE
// Defines which shells support this component. Only used as an informational guide, does not restrict placing these components in circuits.
var/required_shells = null
/// Called when the option ports should be set up
/obj/item/circuit_component/proc/populate_options()
return
/// Extension of add_input_port. Simplifies the code to make an option port to reduce boilerplate
/obj/item/circuit_component/proc/add_option_port(name, list/list_to_use)
return add_input_port(name, PORT_TYPE_OPTION, port_type = /datum/port/input/option, extra_args = list("possible_options" = list_to_use))
/obj/item/circuit_component/Initialize()
. = ..()
if(name == COMPONENT_DEFAULT_NAME)
name = "[lowertext(display_name)] [COMPONENT_DEFAULT_NAME]"
populate_options()
return INITIALIZE_HINT_LATELOAD
/obj/item/circuit_component/LateInitialize()
. = ..()
if(circuit_flags & CIRCUIT_FLAG_INPUT_SIGNAL)
trigger_input = add_input_port("Trigger", PORT_TYPE_SIGNAL)
if(circuit_flags & CIRCUIT_FLAG_OUTPUT_SIGNAL)
trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/Destroy()
if(parent)
// Prevents a Destroy() recursion
var/obj/item/integrated_circuit/old_parent = parent
parent = null
old_parent.remove_component(src)
trigger_input = null
trigger_output = null
QDEL_LIST(output_ports)
QDEL_LIST(input_ports)
return ..()
/**
* Called when a shell is registered from the component/the component is added to a circuit.
*
* Register all signals here on the shell.
* Arguments:
* * shell - Shell being registered
*/
/obj/item/circuit_component/proc/register_shell(atom/movable/shell)
return
/**
* Called when a shell is unregistered from the component/the component is removed from a circuit.
*
* Unregister all signals here on the shell.
* Arguments:
* * shell - Shell being unregistered
*/
/obj/item/circuit_component/proc/unregister_shell(atom/movable/shell)
return
/**
* Disconnects a component from other components
*
* Disconnects both the input and output ports of the component
*/
/obj/item/circuit_component/proc/disconnect()
for(var/datum/port/output/port_to_disconnect as anything in output_ports)
port_to_disconnect.disconnect_all()
for(var/datum/port/input/port_to_disconnect as anything in input_ports)
port_to_disconnect.disconnect_all()
/**
* Adds an input port and returns it
*
* Arguments:
* * name - The name of the input port
* * type - The datatype it handles
* * trigger - Whether this input port triggers an update on the component when updated.
*/
/obj/item/circuit_component/proc/add_input_port(name, type, trigger = TRUE, default = null, index = null, port_type = /datum/port/input, extra_args = null)
var/list/arguments = list(src)
arguments += args
if(extra_args)
arguments += extra_args
var/datum/port/input/input_port = new port_type(arglist(arguments))
if(index)
input_ports.Insert(index, input_port)
else
input_ports += input_port
if(parent)
SStgui.update_uis(parent)
return input_port
/**
* Removes an input port and deletes it. This will not cleanup any references made by derivatives of the circuit component
*
* Arguments:
* * input_port - The input port to remove.
*/
/obj/item/circuit_component/proc/remove_input_port(datum/port/input/input_port)
input_ports -= input_port
qdel(input_port)
if(parent)
SStgui.update_uis(parent)
/**
* Adds an output port and returns it
*
* Arguments:
* * name - The name of the output port
* * type - The datatype it handles.
*/
/obj/item/circuit_component/proc/add_output_port(name, type)
var/list/arguments = list(src)
arguments += args
var/datum/port/output/output_port = new(arglist(arguments))
output_ports += output_port
return output_port
/**
* Removes an output port and deletes it. This will not cleanup any references made by derivatives of the circuit component
*
* Arguments:
* * output_port - The output port to remove.
*/
/obj/item/circuit_component/proc/remove_output_port(datum/port/output/output_port)
output_ports -= output_port
qdel(output_port)
if(parent)
SStgui.update_uis(parent)
/**
* Called whenever an input is received from one of the ports.
*
* Return value indicates that the circuit should not do anything. Also prevents an output signal.
* Arguments:
* * port - Can be null. The port that sent the input
*/
/obj/item/circuit_component/proc/input_received(datum/port/input/port)
SHOULD_CALL_PARENT(TRUE)
if(!parent?.on)
return TRUE
if(!parent.admin_only)
if(circuit_flags & CIRCUIT_FLAG_ADMIN)
message_admins("[display_name] tried to execute on [parent.get_creator_admin()] that has admin_only set to 0")
return TRUE
var/obj/item/stock_parts/cell/cell = parent.get_cell()
if(!cell?.use(power_usage_per_input))
return TRUE
if((circuit_flags & CIRCUIT_FLAG_INPUT_SIGNAL) && !COMPONENT_TRIGGERED_BY(trigger_input, port))
return TRUE
/// Called when this component is about to be added to an integrated_circuit.
/obj/item/circuit_component/proc/add_to(obj/item/integrated_circuit/added_to)
return TRUE
/// Called when this component is removed from an integrated_circuit.
/obj/item/circuit_component/proc/removed_from(obj/item/integrated_circuit/removed_from)
return
/**
* Gets the UI notices to be displayed on the CircuitInfo panel.
*
* Returns a list of buttons in the following format
* list(
* "icon" = ICON(string)
* "content" = CONTENT(string)
* "color" = COLOR(string, not a hex)
* )
*/
/obj/item/circuit_component/proc/get_ui_notices()
. = list()
if(!removable)
. += create_ui_notice("Unremovable", "red", "lock")
if(length(required_shells))
. += create_ui_notice("Supported Shells:", "green", "notes-medical")
for(var/atom/movable/shell as anything in required_shells)
. += create_ui_notice(initial(shell.name), "green", "plus-square")
if(length(input_ports))
. += create_ui_notice("Power Usage Per Input: [power_usage_per_input]", "orange", "bolt")
/**
* Creates a UI notice entry to be used in get_ui_notices()
*
* Returns a list that can then be added to the return list in get_ui_notices()
*/
/obj/item/circuit_component/proc/create_ui_notice(content, color, icon)
SHOULD_BE_PURE(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
return list(list(
"icon" = icon,
"content" = content,
"color" = color,
))
/**
* Creates a table UI notice entry to be used in get_ui_notices()
*
* Returns a list that can then be added to the return list in get_ui_notices()
* Used by components to list their available columns. Recommended to use at the end of get_ui_notices()
*/
/obj/item/circuit_component/proc/create_table_notices(list/entries)
SHOULD_BE_PURE(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
. = list()
. += create_ui_notice("Available Columns:", "grey", "question-circle")
for(var/entry in entries)
. += create_ui_notice("Column Name: '[entry]'", "grey", "columns")
/obj/item/circuit_component/proc/register_usb_parent(atom/movable/parent)
return
/obj/item/circuit_component/proc/unregister_usb_parent(atom/movable/parent)
return
@@ -0,0 +1,394 @@
/// Component printer, creates components for integrated circuits.
/obj/machinery/component_printer
name = "component printer"
desc = "Produces components for the creation of integrated circuits."
icon = 'icons/obj/wiremod_fab.dmi'
icon_state = "fab-idle"
circuit = /obj/item/circuitboard/machine/component_printer
/// The internal material bus
var/datum/component/remote_materials/materials
density = TRUE
/// The techweb the printer will get researched designs from
var/datum/techweb/techweb
/obj/machinery/component_printer/Initialize(mapload)
. = ..()
techweb = SSresearch.science_tech
materials = AddComponent( \
/datum/component/remote_materials, \
"component_printer", \
mapload, \
mat_container_flags = BREAKDOWN_FLAGS_LATHE, \
)
/obj/machinery/component_printer/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ComponentPrinter", name)
ui.open()
/obj/machinery/component_printer/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
)
/obj/machinery/component_printer/ui_act(action, list/params)
. = ..()
if (.)
return
switch (action)
if ("print")
var/design_id = params["designId"]
if (!techweb.researched_designs[design_id])
return TRUE
var/datum/design/design = SSresearch.techweb_design_by_id(design_id)
if (!(design.build_type & COMPONENT_PRINTER))
return TRUE
if (materials.on_hold())
say("Mineral access is on hold, please contact the quartermaster.")
return TRUE
if (!materials.mat_container?.has_materials(design.materials))
say("Not enough materials.")
return TRUE
balloon_alert_to_viewers("printed [design.name]")
materials.mat_container?.use_materials(design.materials)
materials.silo_log(src, "printed", -1, design.name, design.materials)
var/atom/printed_design = new design.build_path(drop_location())
printed_design.pixel_x = printed_design.base_pixel_x + rand(-5, 5)
printed_design.pixel_y = printed_design.base_pixel_y + rand(-5, 5)
if ("remove_mat")
var/datum/material/material = locate(params["ref"])
var/amount = text2num(params["amount"])
if (!amount)
return TRUE
// SAFETY: eject_sheets checks for valid mats
materials.eject_sheets(material, amount)
return TRUE
/obj/machinery/component_printer/ui_data(mob/user)
var/list/data = list()
data["materials"] = materials.mat_container.ui_data()
return data
/obj/machinery/component_printer/ui_static_data(mob/user)
var/list/data = list()
var/list/designs = list()
// for (var/datum/design/component/component_design_type as anything in subtypesof(/datum/design/component))
for (var/researched_design_id in techweb.researched_designs)
var/datum/design/design = SSresearch.techweb_design_by_id(researched_design_id)
if (!(design.build_type & COMPONENT_PRINTER))
continue
designs[researched_design_id] = list(
"name" = design.name,
"description" = design.desc,
"materials" = get_material_cost_data(design.materials),
"categories" = design.category,
)
data["designs"] = designs
return data
/obj/machinery/component_printer/crowbar_act(mob/living/user, obj/item/tool)
if(..())
return TRUE
return default_deconstruction_crowbar(tool)
/obj/machinery/component_printer/screwdriver_act(mob/living/user, obj/item/tool)
if(..())
return TRUE
return default_deconstruction_screwdriver(user, "fab-o", "fab-idle", tool)
/obj/machinery/component_printer/proc/get_material_cost_data(list/materials)
var/list/data = list()
for (var/datum/material/material_type as anything in materials)
data[initial(material_type.name)] = materials[material_type]
return data
/obj/item/circuitboard/machine/component_printer
name = "\improper Component Printer (Machine Board)"
greyscale_colors = CIRCUIT_COLOR_SCIENCE
build_path = /obj/machinery/component_printer
req_components = list(
/obj/item/stock_parts/matter_bin = 2,
/obj/item/stock_parts/manipulator = 2,
/obj/item/reagent_containers/glass/beaker = 2,
)
/obj/machinery/debug_component_printer
name = "debug component printer"
desc = "Produces components for the creation of integrated circuits."
icon = 'icons/obj/wiremod_fab.dmi'
icon_state = "fab-idle"
/// All of the possible circuit designs stored by this debug printer
var/list/all_circuit_designs
density = TRUE
/obj/machinery/debug_component_printer/Initialize()
. = ..()
all_circuit_designs = list()
for(var/id in SSresearch.techweb_designs)
var/datum/design/design = SSresearch.techweb_design_by_id(id)
if((design.build_type & COMPONENT_PRINTER) && design.build_path)
all_circuit_designs[design.build_path] = list(
"name" = design.name,
"description" = design.desc,
"materials" = design.materials,
"categories" = design.category
)
for(var/obj/item/circuit_component/component as anything in subtypesof(/obj/item/circuit_component))
var/categories = list("Inaccessible")
if(initial(component.circuit_flags) & CIRCUIT_FLAG_ADMIN)
categories = list("Admin")
if(!(component in all_circuit_designs))
all_circuit_designs[component] = list(
"name" = initial(component.display_name),
"description" = initial(component.desc),
"materials" = list(),
"categories" = categories,
)
/obj/machinery/debug_component_printer/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ComponentPrinter", name)
ui.open()
/obj/machinery/debug_component_printer/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
)
/obj/machinery/debug_component_printer/ui_act(action, list/params)
. = ..()
if (.)
return
switch (action)
if ("print")
var/build_path = text2path(params["designId"])
if (!build_path)
return TRUE
var/list/design = all_circuit_designs[build_path]
if(!design)
return TRUE
balloon_alert_to_viewers("printed [design["name"]]")
var/atom/printed_design = new build_path(drop_location())
printed_design.pixel_x = printed_design.base_pixel_x + rand(-5, 5)
printed_design.pixel_y = printed_design.base_pixel_y + rand(-5, 5)
return TRUE
/obj/machinery/debug_component_printer/ui_static_data(mob/user)
var/list/data = list()
data["materials"] = list()
data["designs"] = all_circuit_designs
return data
/// Module duplicator, allows you to save and recreate module components.
/obj/machinery/module_duplicator
name = "module duplicator"
desc = "Allows you to duplicate module components so that you don't have to recreate them. Scan a module component over this machine to add it as an entry."
icon = 'icons/obj/wiremod_fab.dmi'
icon_state = "module-fab-idle"
circuit = /obj/item/circuitboard/machine/module_duplicator
/// The internal material bus
var/datum/component/remote_materials/materials
density = TRUE
var/list/scanned_designs = list()
var/cost_per_component = 1000
/obj/machinery/module_duplicator/Initialize(mapload)
. = ..()
materials = AddComponent( \
/datum/component/remote_materials, \
"module_duplicator", \
mapload, \
mat_container_flags = BREAKDOWN_FLAGS_LATHE, \
)
/obj/machinery/module_duplicator/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ComponentPrinter", name)
ui.open()
/obj/machinery/module_duplicator/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
)
/obj/machinery/module_duplicator/ui_act(action, list/params)
. = ..()
if (.)
return
switch (action)
if ("print")
var/design_id = text2num(params["designId"])
if (design_id < 1 || design_id > length(scanned_designs))
return TRUE
var/list/design = scanned_designs[design_id]
if (materials.on_hold())
say("Mineral access is on hold, please contact the quartermaster.")
return TRUE
if (!materials.mat_container?.has_materials(design["materials"]))
say("Not enough materials.")
return TRUE
balloon_alert_to_viewers("printed [design["name"]]")
materials.mat_container?.use_materials(design["materials"])
materials.silo_log(src, "printed", -1, design["name"], design["materials"])
print_module(design)
if ("remove_mat")
var/datum/material/material = locate(params["ref"])
var/amount = text2num(params["amount"])
if (!amount)
return TRUE
// SAFETY: eject_sheets checks for valid mats
materials.eject_sheets(material, amount)
return TRUE
/obj/machinery/module_duplicator/proc/print_module(list/design)
flick("module-fab-print", src)
addtimer(CALLBACK(src, .proc/finish_module_print, design), 1.6 SECONDS)
/obj/machinery/module_duplicator/proc/finish_module_print(list/design)
var/obj/item/circuit_component/module/module = new(drop_location())
module.load_data_from_list(design["dupe_data"])
module.pixel_x = module.base_pixel_x + rand(-5, 5)
module.pixel_y = module.base_pixel_y + rand(-5, 5)
/obj/machinery/module_duplicator/attackby(obj/item/weapon, mob/user, params)
if(!istype(weapon, /obj/item/circuit_component/module))
return ..()
var/obj/item/circuit_component/module/module = weapon
if(module.circuit_flags & CIRCUIT_FLAG_UNDUPEABLE)
balloon_alert(user, "module cannot be saved!")
return ..()
if(module.display_name == initial(module.display_name))
balloon_alert(user, "module needs a name!")
return ..()
for(var/list/component_data as anything in scanned_designs)
if(component_data["name"] == module.display_name)
balloon_alert(user, "module name already exists!")
return ..()
var/total_cost = 0
for(var/obj/item/circuit_component/component as anything in module.internal_circuit.attached_components)
if(component.circuit_flags & CIRCUIT_FLAG_UNDUPEABLE)
balloon_alert(user, "module contains prohibited components!")
return ..()
total_cost += cost_per_component
var/list/data = list()
data["dupe_data"] = list()
module.save_data_to_list(data["dupe_data"])
data["name"] = module.display_name
data["desc"] = "A module that has been loaded in by [user]."
data["materials"] = list(/datum/material/glass = total_cost)
flick("module-fab-scan", src)
addtimer(CALLBACK(src, .proc/finish_module_scan, user, data), 1.4 SECONDS)
/obj/machinery/module_duplicator/proc/finish_module_scan(mob/user, data)
scanned_designs += list(data)
balloon_alert(user, "module has been saved.")
playsound(src, 'sound/machines/ping.ogg', 50)
/obj/machinery/module_duplicator/ui_data(mob/user)
var/list/data = list()
data["materials"] = materials.mat_container.ui_data()
return data
/obj/machinery/module_duplicator/ui_static_data(mob/user)
var/list/data = list()
var/list/designs = list()
var/index = 1
for (var/list/design as anything in scanned_designs)
designs["[index]"] = list(
"name" = design["name"],
"description" = design["desc"],
"materials" = get_material_cost_data(design["materials"]),
"categories" = list("Circuitry"),
)
index++
data["designs"] = designs
return data
/obj/machinery/module_duplicator/crowbar_act(mob/living/user, obj/item/tool)
if(..())
return TRUE
return default_deconstruction_crowbar(tool)
/obj/machinery/module_duplicator/screwdriver_act(mob/living/user, obj/item/tool)
if(..())
return TRUE
return default_deconstruction_screwdriver(user, "module-fab-o", "module-fab-idle", tool)
/obj/machinery/module_duplicator/proc/get_material_cost_data(list/materials)
var/list/data = list()
for (var/datum/material/material_type as anything in materials)
data[initial(material_type.name)] = materials[material_type]
return data
/obj/item/circuitboard/machine/module_duplicator
name = "\improper Module Duplicator (Machine Board)"
greyscale_colors = CIRCUIT_COLOR_SCIENCE
build_path = /obj/machinery/module_duplicator
req_components = list(
/obj/item/stock_parts/matter_bin = 2,
/obj/item/stock_parts/manipulator = 2,
/obj/item/reagent_containers/glass/beaker = 2,
)
+91
View File
@@ -0,0 +1,91 @@
// An assoc list of all the possible datatypes.
GLOBAL_LIST_INIT(circuit_datatypes, generate_circuit_datatypes())
/proc/generate_circuit_datatypes()
var/list/datatypes_by_key = list()
for(var/datum/circuit_datatype/type as anything in subtypesof(/datum/circuit_datatype))
if(!initial(type.datatype))
continue
datatypes_by_key[initial(type.datatype)] = new type()
return datatypes_by_key
/**
* A circuit datatype. Used to determine the datatype of a port and also handle any additional behaviour.
*/
/datum/circuit_datatype
/// The key. Used to identify the datatype. Should be a define.
var/datatype
/// The color of the port in the UI. Doesn't work with hex colours.
var/color = "blue"
/// The flags of the circuit datatype
var/datatype_flags = 0
/**
* Returns the value to be set for the port
*
* Used for implicit conversions between outputs and inputs (e.g. number -> string)
* and applying/removing signals on inputs
*/
/datum/circuit_datatype/proc/convert_value(datum/port/port, value_to_convert)
return value_to_convert
/**
* Determines if a datatype is compatible with another port of a different type.
* Note: This is ALWAYS called on the input port, never on the output port.
* Inputs need to care about what types they're receiving, output ports don't have to care.
*
* Arguments:
* * datatype_to_check - The datatype to check
*/
/datum/circuit_datatype/proc/can_receive_from_datatype(datatype_to_check)
return datatype == datatype_to_check // This is already done by default on the input port.
/**
* Called when the datatype is given to a port.
*
* Arguments:
* * gained_port - The gained port.
*/
/datum/circuit_datatype/proc/on_gain(datum/port/gained_port)
return
/**
* Called when the datatype is removed from a port.
*
* Arguments:
* * lost_port - The removed port.
*/
/datum/circuit_datatype/proc/on_loss(datum/port/lost_port)
return
/**
* Determines if a port is compatible with this datatype.
* This WILL throw a runtime if it returns false. This is for sanity checking and it should not return false
* unless under extraordinary circumstances or people fail to write proper code.
*
* Arguments:
* * port - The port to check if it is compatible.
*/
/datum/circuit_datatype/proc/is_compatible(datum/port/port)
return TRUE
/**
* The data to send to the UI attached to the port. Received by the type in FUNDAMENTAL_PORT_TYPES
*
* Arguments:
* * port - The port sending the data.
*/
/datum/circuit_datatype/proc/datatype_ui_data(datum/port/port)
return
/**
* When an input is manually set by a player. This is where extra sanitizing can happen. Will still call convert_value()
*
* Arguments:
* * port - The port sending the data.
* *
*/
/datum/circuit_datatype/proc/handle_manual_input(datum/port/input/port, mob/user, user_input)
return user_input
+227
View File
@@ -0,0 +1,227 @@
#define LOG_ERROR(list, error) if(list) { list.Add(error) }
// Determines if a port can have a predefined input value if it is of this type.
GLOBAL_LIST_INIT(circuit_dupe_whitelisted_types, list(
PORT_TYPE_NUMBER,
PORT_TYPE_STRING,
PORT_TYPE_LIST,
PORT_TYPE_ANY,
PORT_TYPE_OPTION,
))
/// Loads a circuit based on json data at a location. Can also load usb connections, such as arrest consoles.
/obj/item/integrated_circuit/proc/load_circuit_data(json_data, list/errors)
var/list/general_data = json_decode(json_data)
if(!general_data)
LOG_ERROR(errors, "Invalid json format!")
return
if(general_data["display_name"])
set_display_name(general_data["display_name"])
var/list/variable_data = general_data["variables"]
for(var/list/variable as anything in variable_data)
var/variable_name = variable["name"]
circuit_variables[variable_name] = new /datum/circuit_variable(variable_name, variable["datatype"])
admin_only = general_data["admin_only"]
var/list/circuit_data = general_data["components"]
var/list/identifiers_to_circuit = list()
for(var/identifier in circuit_data)
var/list/component_data = circuit_data[identifier]
var/type = text2path(component_data["type"])
if(!ispath(type, /obj/item/circuit_component))
LOG_ERROR(errors, "Invalid path for circuit component, expected [/obj/item/circuit_component], got [type]")
continue
var/obj/item/circuit_component/component = load_component(type)
identifiers_to_circuit[identifier] = component
component.load_data_from_list(component_data)
var/list/input_ports_data = component_data["input_ports_stored_data"]
for(var/port_name in input_ports_data)
var/datum/port/input/port
var/list/port_data = input_ports_data[port_name]
for(var/datum/port/input/port_to_check as anything in component.input_ports)
if(port_to_check.name == port_name)
port = port_to_check
break
port.set_input(port_data["stored_data"])
var/list/external_objects = general_data["external_objects"]
for(var/identifier in external_objects)
var/list/object_data = external_objects[identifier]
var/type = text2path(object_data["type"])
if(!ispath(type))
LOG_ERROR(errors, "Invalid path for external object, expected a path, got [type]")
continue
var/atom/movable/object = new type(drop_location())
var/list/connected_components = list()
for(var/component_id in object_data["connected_components"])
var/obj/item/circuit_component/component = identifiers_to_circuit[component_id]
if(!component)
continue
connected_components += component
SEND_SIGNAL(object, COMSIG_MOVABLE_CIRCUIT_LOADED, src, connected_components)
for(var/identifier in identifiers_to_circuit)
var/obj/item/circuit_component/component = identifiers_to_circuit[identifier]
var/list/component_data = circuit_data[identifier]
var/list/connections = component_data["connections"]
for(var/port_name in connections)
var/datum/port/input/port
var/list/connection_data = connections[port_name]
for(var/datum/port/input/port_to_check as anything in component.input_ports)
if(port_to_check.name == port_name)
port = port_to_check
break
if(!port)
LOG_ERROR(errors, "Port [port_name] not found for [component.type].")
continue
if(connection_data["stored_data"])
if(!(port.datatype in GLOB.circuit_dupe_whitelisted_types))
continue
port.set_input(connection_data["stored_data"])
continue
// The || list(connected_data) is for backwards compatibility with when inputs could only be connected to up to one output.
for(var/list/output_data in (connection_data["connected_ports"] || list(connection_data)))
var/obj/item/circuit_component/connected_component = identifiers_to_circuit[output_data["component_id"]]
if(!connected_component)
LOG_ERROR(errors, "No connected component found for [component.type] for port [connection_data["port_name"]]. (connected component identifier: [connection_data["component_id"]])")
continue
var/datum/port/output/output_port
var/output_port_name = output_data["port_name"]
for(var/datum/port/output/port_to_check as anything in connected_component.output_ports)
if(port_to_check.name == output_port_name)
output_port = port_to_check
break
if(!output_port)
LOG_ERROR(errors, "No output port found for [component.type] for port [output_port_name] on component [connected_component.type]")
continue
port.connect(output_port)
#undef LOG_ERROR
/// Converts a circuit into json.
/obj/item/integrated_circuit/proc/convert_to_json()
var/list/circuit_to_identifiers = list()
var/list/identifiers = list()
var/list/external_objects = list() // Objects that are connected to a component. These objects will be linked to the components.
for(var/obj/item/circuit_component/component as anything in attached_components)
var/identifier = "[component.type][length(identifiers)]"
identifiers += identifier
circuit_to_identifiers[component] = identifier
var/list/objects = list()
SEND_SIGNAL(component, COMSIG_CIRCUIT_COMPONENT_SAVE, objects)
for(var/atom/movable/object as anything in objects)
if(object in external_objects)
external_objects[object] += identifier
continue
external_objects[object] = list(identifier)
var/list/circuit_data = list()
for(var/obj/item/circuit_component/component as anything in circuit_to_identifiers)
var/identifier = circuit_to_identifiers[component]
var/list/component_data = list()
component_data["type"] = component.type
var/list/connections = list()
var/list/input_ports_stored_data = list()
for(var/datum/port/input/input as anything in component.input_ports)
var/list/connection_data = list()
if(!length(input.connected_ports))
if(isnull(input.value) || !(input.datatype in GLOB.circuit_dupe_whitelisted_types))
continue
connection_data["stored_data"] = input.value
input_ports_stored_data[input.name] = connection_data
continue
connection_data["connected_ports"] = list()
for(var/datum/port/output/output as anything in input.connected_ports)
connection_data["connected_ports"] += list(list(
"component_id" = circuit_to_identifiers[output.connected_component],
"port_name" = output.name,
))
connections[input.name] = connection_data
component_data["connections"] = connections
component_data["input_ports_stored_data"] = input_ports_stored_data
component.save_data_to_list(component_data)
circuit_data[identifier] = component_data
var/external_objects_key = list()
for(var/atom/movable/object as anything in external_objects)
var/list/new_data = list()
new_data["type"] = object.type
new_data["connected_components"] = external_objects[object]
external_objects_key["[object.type][length(external_objects_key)]"] = new_data
var/list/general_data = list()
general_data["components"] = circuit_data
general_data["external_objects"] = external_objects_key
general_data["display_name"] = display_name
general_data["admin_only"] = admin_only
var/list/variables = list()
for(var/variable_identifier in circuit_variables)
var/list/new_data = list()
var/datum/circuit_variable/variable = circuit_variables[variable_identifier]
new_data["name"] = variable.name
new_data["datatype"] = variable.datatype
variables += list(new_data)
general_data["variables"] = variables
return json_encode(general_data)
/obj/item/integrated_circuit/proc/load_component(type)
var/obj/item/circuit_component/component = new type(src)
add_component(component)
return component
/// Saves data to a list. Shouldn't be used unless you are quite literally saving the data of a component to a list. Input value is the list to save the data to
/obj/item/circuit_component/proc/save_data_to_list(list/component_data)
component_data["rel_x"] = rel_x
component_data["rel_y"] = rel_y
/// Loads data from a list
/obj/item/circuit_component/proc/load_data_from_list(list/component_data)
rel_x = component_data["rel_x"]
rel_y = component_data["rel_y"]
/client/proc/load_circuit()
set name = "Load Circuit"
set category = "Admin.Fun"
if(!check_rights(R_VAREDIT))
return
var/list/errors = list()
var/option = alert(usr, "Load by file or direct input?", "Load by file or string", "File", "Direct Input")
var/txt
switch(option)
if("File")
txt = file2text(tgui_input_num(usr, "Input File") as file|null)
if("Direct Input")
txt = tgui_input_num(usr, "Input JSON", "Input JSON") as text|null
if(!txt)
return
var/obj/item/integrated_circuit/loaded/circuit = new(mob.drop_location())
circuit.load_circuit_data(txt, errors)
if(length(errors))
to_chat(src, span_warning("The following errors were found whilst compiling the circuit data:"))
for(var/error in errors)
to_chat(src, span_warning(error))
@@ -0,0 +1,600 @@
/// A list of all integrated circuits
GLOBAL_LIST_EMPTY_TYPED(integrated_circuits, /obj/item/integrated_circuit)
/**
* # Integrated Circuitboard
*
* A circuitboard that holds components that work together
*
* Has a limited amount of power.
*/
/obj/item/integrated_circuit
name = "integrated circuit"
desc = "By inserting components and a cell into this, wiring them up, and putting them into a shell, anyone can pretend to be a programmer."
icon = 'icons/obj/module.dmi'
icon_state = "integrated_circuit"
inhand_icon_state = "electronic"
/// The name that appears on the shell.
var/display_name = ""
/// The max length of the name.
var/label_max_length = 24
/// The power of the integrated circuit
var/obj/item/stock_parts/cell/cell
/// The shell that this circuitboard is attached to. Used by components.
var/atom/movable/shell
/// The attached components
var/list/obj/item/circuit_component/attached_components = list()
/// Whether the integrated circuit is on or not. Handled by the shell.
var/on = FALSE
/// Whether the integrated circuit is locked or not. Handled by the shell.
var/locked = FALSE
/// Whether the integrated circuit is admin only. Disables power usage and allows admin circuits to be attached, at the cost of making it inaccessible to regular users.
var/admin_only = FALSE
/// The ID that is authorized to unlock/lock the shell so that the circuit can/cannot be removed.
var/datum/weakref/owner_id
/// The current examined component. Used in IntegratedCircuit UI
var/datum/weakref/examined_component
/// Set by the shell. Holds the reference to the owner who inserted the component into the shell.
var/datum/weakref/inserter_mind
/// Variables stored on this integrated circuit. with a `variable_name = value` structure
var/list/datum/circuit_variable/circuit_variables = list()
/// The maximum amount of setters and getters a circuit can have
var/max_setters_and_getters = 30
/// The current setter and getter count the circuit has.
var/setter_and_getter_count = 0
/// X position of the examined_component
var/examined_rel_x = 0
/// Y position of the examined component
var/examined_rel_y = 0
/// The X position of the screen. Used for adding components
var/screen_x = 0
/// The Y position of the screen. Used for adding components.
var/screen_y = 0
/obj/item/integrated_circuit/Initialize()
. = ..()
GLOB.integrated_circuits += src
RegisterSignal(src, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, .proc/on_atom_usb_cable_try_attach)
/obj/item/integrated_circuit/loaded/Initialize()
. = ..()
set_cell(new /obj/item/stock_parts/cell/high(src))
/obj/item/integrated_circuit/Destroy()
for(var/obj/item/circuit_component/to_delete in attached_components)
remove_component(to_delete)
qdel(to_delete)
QDEL_LIST(circuit_variables)
attached_components.Cut()
shell = null
examined_component = null
owner_id = null
QDEL_NULL(cell)
GLOB.integrated_circuits -= src
return ..()
/obj/item/integrated_circuit/examine(mob/user)
. = ..()
if(cell)
. += span_notice("The charge meter reads [cell ? round(cell.percent(), 1) : 0]%.")
else
. += span_notice("There is no power cell installed.")
/obj/item/integrated_circuit/proc/set_cell(obj/item/stock_parts/cell_to_set)
SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_CELL, cell_to_set)
cell = cell_to_set
/obj/item/integrated_circuit/attackby(obj/item/I, mob/living/user, params)
. = ..()
if(istype(I, /obj/item/circuit_component))
add_component_manually(I, user)
return
if(istype(I, /obj/item/stock_parts/cell))
if(cell)
balloon_alert(user, "there already is a cell inside!")
return
if(!user.transferItemToLoc(I, src))
return
set_cell(I)
I.add_fingerprint(user)
user.visible_message(span_notice("[user] inserts a power cell into [src]."), span_notice("You insert the power cell into [src]."))
return
if(istype(I, /obj/item/card/id))
balloon_alert(user, "owner id set for [I]")
owner_id = WEAKREF(I)
return
if(I.tool_behaviour == TOOL_SCREWDRIVER)
if(!cell)
return
I.play_tool_sound(src)
user.visible_message(span_notice("[user] unscrews the power cell from [src]."), span_notice("You unscrew the power cell from [src]."))
cell.forceMove(drop_location())
set_cell(null)
return
/**
* Registers an movable atom as a shell
*
* No functionality is done here. This is so that input components
* can properly register any signals on the shell.
* Arguments:
* * new_shell - The new shell to register.
*/
/obj/item/integrated_circuit/proc/set_shell(atom/movable/new_shell)
remove_current_shell()
set_on(TRUE)
SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_SHELL, new_shell)
shell = new_shell
RegisterSignal(shell, COMSIG_PARENT_QDELETING, .proc/remove_current_shell)
for(var/obj/item/circuit_component/attached_component as anything in attached_components)
attached_component.register_shell(shell)
// Their input ports may be updated with user values, but the outputs haven't updated
// because on is FALSE
TRIGGER_CIRCUIT_COMPONENT(attached_component, null)
/**
* Unregisters the current shell attached to this circuit.
*/
/obj/item/integrated_circuit/proc/remove_current_shell()
SIGNAL_HANDLER
if(!shell)
return
shell.name = initial(shell.name)
for(var/obj/item/circuit_component/attached_component as anything in attached_components)
attached_component.unregister_shell(shell)
UnregisterSignal(shell, COMSIG_PARENT_QDELETING)
shell = null
set_on(FALSE)
SEND_SIGNAL(src, COMSIG_CIRCUIT_SHELL_REMOVED)
/obj/item/integrated_circuit/proc/set_on(new_value)
SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_ON, new_value)
on = new_value
/**
* Adds a component to the circuitboard
*
* Once the component is added, the ports can be attached to other components
*/
/obj/item/integrated_circuit/proc/add_component(obj/item/circuit_component/to_add, mob/living/user)
if(to_add.parent)
return
if(SEND_SIGNAL(src, COMSIG_CIRCUIT_ADD_COMPONENT, to_add, user) & COMPONENT_CANCEL_ADD_COMPONENT)
return
if(!to_add.add_to(src))
return
var/success = FALSE
if(user)
success = user.transferItemToLoc(to_add, src)
else
success = to_add.forceMove(src)
if(!success)
return
to_add.rel_x = rand(COMPONENT_MIN_RANDOM_POS, COMPONENT_MAX_RANDOM_POS) - screen_x
to_add.rel_y = rand(COMPONENT_MIN_RANDOM_POS, COMPONENT_MAX_RANDOM_POS) - screen_y
to_add.parent = src
attached_components += to_add
RegisterSignal(to_add, COMSIG_MOVABLE_MOVED, .proc/component_move_handler)
SStgui.update_uis(src)
if(shell)
to_add.register_shell(shell)
return TRUE
/**
* Adds a component to the circuitboard through a manual action.
*/
/obj/item/integrated_circuit/proc/add_component_manually(obj/item/circuit_component/to_add, mob/living/user)
if (SEND_SIGNAL(src, COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY, to_add, user) & COMPONENT_CANCEL_ADD_COMPONENT)
return
return add_component(to_add, user)
/obj/item/integrated_circuit/proc/component_move_handler(obj/item/circuit_component/source)
SIGNAL_HANDLER
if(source.loc != src)
remove_component(source)
/**
* Removes a component to the circuitboard
*
* This removes all connects between the ports
*/
/obj/item/integrated_circuit/proc/remove_component(obj/item/circuit_component/to_remove)
if(shell)
to_remove.unregister_shell(shell)
UnregisterSignal(to_remove, COMSIG_MOVABLE_MOVED)
attached_components -= to_remove
to_remove.disconnect()
to_remove.parent = null
SEND_SIGNAL(to_remove, COMSIG_CIRCUIT_COMPONENT_REMOVED, src)
SStgui.update_uis(src)
to_remove.removed_from(src)
/obj/item/integrated_circuit/get_cell()
return cell
/obj/item/integrated_circuit/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/simple/circuit_assets)
)
/obj/item/integrated_circuit/ui_static_data(mob/user)
. = list()
.["global_basic_types"] = GLOB.wiremod_basic_types
.["screen_x"] = screen_x
.["screen_y"] = screen_y
/obj/item/integrated_circuit/ui_data(mob/user)
. = list()
.["components"] = list()
for(var/obj/item/circuit_component/component as anything in attached_components)
if (component.circuit_flags & CIRCUIT_FLAG_HIDDEN)
.["components"] += null
continue
var/list/component_data = list()
component_data["input_ports"] = list()
for(var/datum/port/input/port as anything in component.input_ports)
var/current_data = port.value
if(isatom(current_data)) // Prevent passing the name of the atom.
current_data = null
var/list/connected_to = list()
for(var/datum/port/output/output as anything in port.connected_ports)
connected_to += REF(output)
component_data["input_ports"] += list(list(
"name" = port.name,
"type" = port.datatype,
"ref" = REF(port), // The ref is the identifier to work out what it is connected to
"connected_to" = connected_to,
"color" = port.color,
"current_data" = current_data,
"datatype_data" = port.datatype_ui_data(user),
))
component_data["output_ports"] = list()
for(var/datum/port/output/port as anything in component.output_ports)
component_data["output_ports"] += list(list(
"name" = port.name,
"type" = port.datatype,
"ref" = REF(port),
"color" = port.color,
))
component_data["name"] = component.display_name
component_data["x"] = component.rel_x
component_data["y"] = component.rel_y
component_data["removable"] = component.removable
.["components"] += list(component_data)
.["variables"] = list()
for(var/variable_name in circuit_variables)
var/datum/circuit_variable/variable = circuit_variables[variable_name]
var/list/variable_data = list()
variable_data["name"] = variable.name
variable_data["datatype"] = variable.datatype
variable_data["color"] = variable.color
.["variables"] += list(variable_data)
.["display_name"] = display_name
var/obj/item/circuit_component/examined
if(examined_component)
examined = examined_component.resolve()
.["examined_name"] = examined?.display_name
.["examined_desc"] = examined?.desc
.["examined_notices"] = examined?.get_ui_notices()
.["examined_rel_x"] = examined_rel_x
.["examined_rel_y"] = examined_rel_y
.["is_admin"] = check_rights_for(user.client, R_VAREDIT)
/obj/item/integrated_circuit/ui_host(mob/user)
if(shell)
return shell
return ..()
/obj/item/integrated_circuit/can_interact(mob/user)
if(locked)
return FALSE
return ..()
/obj/item/integrated_circuit/ui_status(mob/user)
. = ..()
if (isobserver(user))
. = max(., UI_UPDATE)
// Extra protection because ui_state will not close the UI if they already have the ui open,
// as ui_state is only set during
if(admin_only)
if(!check_rights_for(user.client, R_VAREDIT))
return UI_CLOSE
else
return UI_INTERACTIVE
/obj/item/integrated_circuit/ui_state(mob/user)
if(!shell)
return GLOB.hands_state
return GLOB.physical_obscured_state
/obj/item/integrated_circuit/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "IntegratedCircuit", name)
ui.open()
ui.set_autoupdate(FALSE)
#define WITHIN_RANGE(id, table) (id >= 1 && id <= length(table))
/obj/item/integrated_circuit/ui_act(action, list/params)
. = ..()
if(.)
return
switch(action)
if("add_connection")
var/input_component_id = text2num(params["input_component_id"])
var/output_component_id = text2num(params["output_component_id"])
var/input_port_id = text2num(params["input_port_id"])
var/output_port_id = text2num(params["output_port_id"])
if(!WITHIN_RANGE(input_component_id, attached_components) || !WITHIN_RANGE(output_component_id, attached_components))
return
var/obj/item/circuit_component/input_component = attached_components[input_component_id]
var/obj/item/circuit_component/output_component = attached_components[output_component_id]
if(!WITHIN_RANGE(input_port_id, input_component.input_ports) || !WITHIN_RANGE(output_port_id, output_component.output_ports))
return
var/datum/port/input/input_port = input_component.input_ports[input_port_id]
var/datum/port/output/output_port = output_component.output_ports[output_port_id]
if(!input_port.can_receive_from_datatype(output_port.datatype))
return
input_port.connect(output_port)
. = TRUE
if("remove_connection")
var/component_id = text2num(params["component_id"])
var/is_input = params["is_input"]
var/port_id = text2num(params["port_id"])
if(!WITHIN_RANGE(component_id, attached_components))
return
var/obj/item/circuit_component/component = attached_components[component_id]
var/list/port_table
if(is_input)
port_table = component.input_ports
else
port_table = component.output_ports
if(!WITHIN_RANGE(port_id, port_table))
return
var/datum/port/port = port_table[port_id]
port.disconnect_all()
. = TRUE
if("detach_component")
var/component_id = text2num(params["component_id"])
if(!WITHIN_RANGE(component_id, attached_components))
return
var/obj/item/circuit_component/component = attached_components[component_id]
if(!component.removable)
return
component.disconnect()
remove_component(component)
if(component.loc == src)
usr.put_in_hands(component)
. = TRUE
if("set_component_coordinates")
var/component_id = text2num(params["component_id"])
if(!WITHIN_RANGE(component_id, attached_components))
return
var/obj/item/circuit_component/component = attached_components[component_id]
component.rel_x = min(max(-COMPONENT_MAX_POS, text2num(params["rel_x"])), COMPONENT_MAX_POS)
component.rel_y = min(max(-COMPONENT_MAX_POS, text2num(params["rel_y"])), COMPONENT_MAX_POS)
. = TRUE
if("set_component_input")
var/component_id = text2num(params["component_id"])
var/port_id = text2num(params["port_id"])
if(!WITHIN_RANGE(component_id, attached_components))
return
var/obj/item/circuit_component/component = attached_components[component_id]
if(!WITHIN_RANGE(port_id, component.input_ports))
return
var/datum/port/input/port = component.input_ports[port_id]
if(params["set_null"])
port.set_input(null)
return TRUE
if(params["marked_atom"])
if(port.datatype != PORT_TYPE_ATOM && port.datatype != PORT_TYPE_ANY)
return
var/obj/item/multitool/circuit/marker = usr.get_active_held_item()
if(!istype(marker))
var/client/user = usr.client
if(!check_rights_for(user, R_VAREDIT))
return TRUE
var/atom/marked_atom = user.holder.marked_datum
if(!marked_atom)
return TRUE
port.set_input(marked_atom)
balloon_alert(usr, "updated [port.name]'s value to marked object.")
return TRUE
if(!marker.marked_atom)
port.set_input(null)
marker.say("Cleared port ('[port.name]')'s value.")
return TRUE
marker.say("Updated port ('[port.name]')'s value to the marked entity.")
port.set_input(marker.marked_atom)
return TRUE
var/user_input = port.handle_manual_input(usr, params["input"])
if(isnull(user_input))
return TRUE
port.set_input(user_input)
. = TRUE
if("get_component_value")
var/component_id = text2num(params["component_id"])
var/port_id = text2num(params["port_id"])
if(!WITHIN_RANGE(component_id, attached_components))
return
var/obj/item/circuit_component/component = attached_components[component_id]
if(!WITHIN_RANGE(port_id, component.output_ports))
return
var/datum/port/output/port = component.output_ports[port_id]
var/value = port.value
if(isatom(value))
value = PORT_TYPE_ATOM
else if(isnull(value))
value = "null"
var/string_form = copytext("[value]", 1, PORT_MAX_STRING_DISPLAY)
if(length(string_form) >= PORT_MAX_STRING_DISPLAY-1)
string_form += "..."
balloon_alert(usr, "[port.name] value: [string_form]")
. = TRUE
if("set_display_name")
var/new_name = params["display_name"]
if(new_name)
set_display_name(strip_html(params["display_name"], label_max_length))
else
set_display_name("")
if(shell)
if(display_name != "")
shell.name = "[initial(shell.name)] ([display_name])"
else
shell.name = initial(shell.name)
. = TRUE
if("set_examined_component")
var/component_id = text2num(params["component_id"])
if(!WITHIN_RANGE(component_id, attached_components))
return
examined_component = WEAKREF(attached_components[component_id])
examined_rel_x = text2num(params["x"])
examined_rel_y = text2num(params["y"])
. = TRUE
if("remove_examined_component")
examined_component = null
. = TRUE
if("save_circuit")
return attempt_save_to(usr.client)
if("add_variable")
var/variable_identifier = trim(copytext(params["variable_name"], 1, PORT_MAX_NAME_LENGTH))
if(variable_identifier in circuit_variables)
return TRUE
if(variable_identifier == "")
return TRUE
var/variable_datatype = params["variable_datatype"]
if(!(variable_datatype in GLOB.wiremod_basic_types))
return
circuit_variables[variable_identifier] = new /datum/circuit_variable(variable_identifier, variable_datatype)
. = TRUE
if("remove_variable")
var/variable_identifier = params["variable_name"]
if(!(variable_identifier in circuit_variables))
return
var/datum/circuit_variable/variable = circuit_variables[variable_identifier]
if(!variable)
return
circuit_variables -= variable_identifier
qdel(variable)
. = TRUE
if("add_setter_or_getter")
if(setter_and_getter_count >= max_setters_and_getters)
balloon_alert(usr, "setter and getter count at maximum capacity")
return
var/designated_type = /obj/item/circuit_component/getter
if(params["is_setter"])
designated_type = /obj/item/circuit_component/setter
var/obj/item/circuit_component/component = new designated_type(src)
if(!add_component(component, usr))
qdel(component)
return
RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_REMOVED, .proc/clear_setter_or_getter)
setter_and_getter_count++
if("move_screen")
screen_x = text2num(params["screen_x"])
screen_y = text2num(params["screen_y"])
/obj/item/integrated_circuit/proc/clear_setter_or_getter(datum/source)
SIGNAL_HANDLER
// This'll also be called in the Destroy() override of /obj/item/circuit_component
if(!QDELING(source))
qdel(source)
setter_and_getter_count--
/obj/item/integrated_circuit/proc/on_atom_usb_cable_try_attach(datum/source, obj/item/usb_cable/usb_cable, mob/user)
SIGNAL_HANDLER
usb_cable.balloon_alert(user, "circuit needs to be in a compatible shell")
return COMSIG_CANCEL_USB_CABLE_ATTACK
#undef WITHIN_RANGE
/// Sets the display name that appears on the shell.
/obj/item/integrated_circuit/proc/set_display_name(new_name)
display_name = new_name
/**
* Returns the creator of the integrated circuit. Used in admin messages and other related things.
*/
/obj/item/integrated_circuit/proc/get_creator_admin()
return get_creator(include_link = TRUE)
/**
* Returns the creator of the integrated circuit. Used in admin logs and other related things.
*/
/obj/item/integrated_circuit/proc/get_creator(include_link = FALSE)
var/datum/mind/inserter
if(inserter_mind)
inserter = inserter_mind.resolve()
var/obj/item/card/id/id_card
if(owner_id)
id_card = owner_id.resolve()
return "[src] (Shell: [shell || "*null*"], Inserter: [key_name(inserter, include_link)], Owner ID: [id_card?.name || "*null*"])"
/// Attempts to save a circuit to a given client
/obj/item/integrated_circuit/proc/attempt_save_to(client/saver)
if(!check_rights_for(saver, R_VAREDIT))
return FALSE
var/temp_file = file("data/CircuitDownloadTempFile")
fdel(temp_file)
WRITE_FILE(temp_file, convert_to_json())
DIRECT_OUTPUT(saver, ftp(temp_file, "[display_name || "circuit"].json"))
return TRUE
+60
View File
@@ -0,0 +1,60 @@
/obj/item/multitool/circuit
name = "circuit multitool"
desc = "A circuit multitool. Used to mark entities which can then be uploaded to components by pressing the upload button on a port. \
Acts as a normal multitool otherwise. Use in hand to clear marked entity so that you can mark another entity."
icon_state = "multitool_circuit"
/// The marked atom of this multitool
var/atom/marked_atom
/obj/item/multitool/circuit/Destroy()
marked_atom = null
return ..()
/obj/item/multitool/circuit/examine(mob/user)
. = ..()
. += span_notice("It has [marked_atom? "a" : "no"] marked entity registered.")
/obj/item/multitool/circuit/attack_self(mob/user, modifiers)
. = ..()
if(.)
return
if(!marked_atom)
return
say("Cleared marked targets.")
clear_marked_atom()
return TRUE
/obj/item/multitool/circuit/melee_attack_chain(mob/user, atom/target, params)
var/is_right_clicking = LAZYACCESS(params2list(params), RIGHT_CLICK)
if(marked_atom || !user.Adjacent(target) || is_right_clicking)
return ..()
say("Marked [target].")
marked_atom = target
RegisterSignal(marked_atom, COMSIG_PARENT_QDELETING, .proc/cleanup_marked_atom)
update_icon()
flick("multitool_circuit_flick", src)
playsound(src.loc, 'sound/misc/compiler-stage2.ogg', 30, TRUE)
return TRUE
/obj/item/multitool/circuit/update_overlays()
. = ..()
cut_overlays()
if(marked_atom)
. += "marked_overlay"
/// Clears the current marked atom
/obj/item/multitool/circuit/proc/clear_marked_atom()
if(!marked_atom)
return
UnregisterSignal(marked_atom, COMSIG_PARENT_QDELETING)
marked_atom = null
update_icon()
/obj/item/multitool/circuit/proc/cleanup_marked_atom(datum/source)
SIGNAL_HANDLER
if(source == marked_atom)
clear_marked_atom()
+216
View File
@@ -0,0 +1,216 @@
/**
* # Component Port
*
* A port used by a component. Connects to other ports.
*/
/datum/port
/// The component this port is attached to
var/obj/item/circuit_component/connected_component
/// Name of the port. Used when displaying the port.
var/name
/// The port type. Ports can only connect to each other if the type matches
var/datatype
/// The value that's currently in the port. It's of the above type.
var/value
/// The default port type. Stores the original datatype of the port set on Initialize.
var/datum/circuit_datatype/datatype_handler
/// The port color. If unset, appears as blue.
var/color
/datum/port/New(obj/item/circuit_component/to_connect, name, datatype)
if(!to_connect)
qdel(src)
return
. = ..()
connected_component = to_connect
src.name = name
set_datatype(datatype)
/datum/port/Destroy(force)
disconnect_all()
connected_component = null
datatype_handler = null
return ..()
/**
* Sets the port's value to value.
* Casts to the port's datatype (e.g. number -> string), and assumes this can be done.
*/
/datum/port/proc/set_value(value, force = FALSE)
if(src.value != value || force)
if(isatom(value))
UnregisterSignal(value, COMSIG_PARENT_QDELETING)
src.value = datatype_handler.convert_value(src, value)
if(isatom(value))
RegisterSignal(value, COMSIG_PARENT_QDELETING, .proc/null_value)
SEND_SIGNAL(src, COMSIG_PORT_SET_VALUE, value)
/**
* Updates the value of the input and calls input_received on the connected component
*/
/datum/port/input/proc/set_input(value)
if(QDELETED(src)) //Pain
return
set_value(value)
if(trigger)
TRIGGER_CIRCUIT_COMPONENT(connected_component, src)
/datum/port/output/proc/set_output(value)
set_value(value)
/**
* Sets the datatype of the port.
*
* Arguments:
* * new_type - The type this port is to be set to.
*/
/datum/port/proc/set_datatype(type_to_set)
if(type_to_set == datatype)
return
if(datatype_handler)
datatype_handler.on_loss(src)
datatype_handler = null
var/datum/circuit_datatype/handler = GLOB.circuit_datatypes[type_to_set]
if(!handler || !handler.is_compatible(src))
type_to_set = PORT_TYPE_ANY
handler = GLOB.circuit_datatypes[type_to_set]
// We can't leave this port without a type or else it'll just keep spewing out unnecessary and unneeded runtimes as well as leaving the circuit in a broken state.
stack_trace("[src] port attempted to be set to an incompatible datatype! (target datatype to set: [type_to_set])")
datatype = type_to_set
datatype_handler = handler
color = datatype_handler.color
datatype_handler.on_gain(src)
src.value = datatype_handler.convert_value(src, value)
SEND_SIGNAL(src, COMSIG_PORT_SET_TYPE, type_to_set)
if(connected_component?.parent)
SStgui.update_uis(connected_component.parent)
/datum/port/input/set_datatype(new_type)
for(var/datum/port/output/output as anything in connected_ports)
check_type(output)
..()
/**
* Returns the data from the datatype
*/
/datum/port/proc/datatype_ui_data()
return datatype_handler.datatype_ui_data(src)
/**
* # Output Port
*
* An output port that many input ports can connect to
*
* Sends a signal whenever the output value is changed
*/
/datum/port/output
/**
* Disconnects a port from all other ports.
*
* Called by [/obj/item/circuit_component] whenever it is disconnected from
* an integrated circuit
*/
/datum/port/proc/disconnect_all()
SEND_SIGNAL(src, COMSIG_PORT_DISCONNECT)
/datum/port/input/disconnect_all()
..()
for(var/datum/port/output/output as anything in connected_ports)
disconnect(output)
/datum/port/input/proc/disconnect(datum/port/output/output)
connected_ports -= output
UnregisterSignal(output, COMSIG_PORT_SET_VALUE)
UnregisterSignal(output, COMSIG_PORT_SET_TYPE)
UnregisterSignal(output, COMSIG_PORT_DISCONNECT)
/// Do our part in setting all source references anywhere to null.
/datum/port/proc/on_value_qdeleting(datum/source)
SIGNAL_HANDLER
if(value == source)
value = null
else
stack_trace("Impossible? [src] should only receive COMSIG_PARENT_QDELETING from an atom currently in the port, not [source].")
/**
* # Input Port
*
* An input port remembers connected output ports.
*
* Registers the PORT_SET_VALUE signal on each connected port,
* and keeps its value equal to the last such signal received.
*/
/datum/port/input
/// Whether this port triggers an update whenever an output is received.
var/trigger = FALSE
/// The ports this port is wired to.
var/list/datum/port/output/connected_ports
/datum/port/input/New(obj/item/circuit_component/to_connect, name, datatype, trigger, default)
. = ..()
set_value(default)
src.trigger = trigger
src.connected_ports = list()
/**
* Introduces two ports to one another.
*/
/datum/port/input/proc/connect(datum/port/output/output)
connected_ports |= output
RegisterSignal(output, COMSIG_PORT_SET_VALUE, .proc/receive_value)
RegisterSignal(output, COMSIG_PORT_SET_TYPE, .proc/check_type)
RegisterSignal(output, COMSIG_PORT_DISCONNECT, .proc/disconnect)
// For signals, we don't update the input to prevent sending a signal when connecting ports.
if(!(datatype_handler.datatype_flags & DATATYPE_FLAG_AVOID_VALUE_UPDATE))
set_input(output.value)
/**
* Determines if a datatype is compatible with another port of a different type.
*
* Arguments:
* * other_datatype - The datatype to check
*/
/datum/port/input/proc/can_receive_from_datatype(datatype_to_check)
return datatype_handler.can_receive_from_datatype(datatype_to_check)
/**
* Determines if a datatype is compatible with another port of a different type.
*
* Arguments:
* * other_datatype - The datatype to check
*/
/datum/port/input/proc/handle_manual_input(mob/user, manual_input)
if(datatype_handler.datatype_flags & DATATYPE_FLAG_ALLOW_MANUAL_INPUT)
return datatype_handler.handle_manual_input(src, user, manual_input)
return null
/**
* Mirror value updates from connected output ports after an input_receive_delay.
*/
/datum/port/input/proc/receive_value(datum/port/output/output, value)
SIGNAL_HANDLER
SScircuit_component.add_callback(CALLBACK(src, .proc/set_input, value))
/// Signal handler proc to null the input if an atom is deleted. An update is not sent because this was not set by anything.
/datum/port/proc/null_value(datum/source)
SIGNAL_HANDLER
if(value == source)
value = null
/**
* Handle type updates from connected output ports, breaking uncastable connections.
*/
/datum/port/input/proc/check_type(datum/port/output/output)
SIGNAL_HANDLER
if(!can_receive_from_datatype(output.datatype))
disconnect(output)
+123
View File
@@ -0,0 +1,123 @@
/// A cable that can connect integrated circuits to anything with a USB port, such as computers and machines.
/obj/item/usb_cable
name = "usb cable"
desc = "A cable that can connect integrated circuits to anything with a USB port, such as computers and machines."
icon = 'icons/obj/wiremod.dmi'
icon_state = "usb_cable"
inhand_icon_state = "coil"
base_icon_state = "coil"
w_class = WEIGHT_CLASS_TINY
custom_materials = list(/datum/material/iron = 75)
/// The currently connected circuit
var/obj/item/integrated_circuit/attached_circuit
/obj/item/usb_cable/Destroy()
attached_circuit = null
STOP_PROCESSING(SSobj, src)
return ..()
/obj/item/usb_cable/Initialize()
. = ..()
RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/on_moved)
/obj/item/usb_cable/examine(mob/user)
. = ..()
if (!isnull(attached_circuit))
. += span_notice("It is attached to [attached_circuit.shell || attached_circuit].")
// Look, I'm not happy about this either, but moving an object doesn't call Moved if it's inside something else.
// There's good reason for this, but there's no element or similar yet to track it as far as I know.
// SSobj runs infrequently, this is only ran while there's an attached circuit, its performance cost is negligible.
/obj/item/usb_cable/process(delta_time)
if (!check_in_range())
return PROCESS_KILL
/obj/item/usb_cable/pre_attack(atom/target, mob/living/user, params)
. = ..()
if (.)
return
if (prob(1))
balloon_alert(user, "wrong way, god damnit")
return TRUE
var/signal_result = SEND_SIGNAL(target, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, src, user)
var/last_attached_circuit = attached_circuit
if (signal_result & COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT)
if (isnull(attached_circuit))
CRASH("Producers of COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT must set attached_circuit")
balloon_alert(user, "connected to circuit\nconnect to a port")
playsound(src, 'sound/machines/pda_button1.ogg', 20, TRUE)
if (last_attached_circuit != attached_circuit)
if (!isnull(last_attached_circuit))
unregister_circuit_signals(last_attached_circuit)
register_circuit_signals()
START_PROCESSING(SSobj, src)
return TRUE
if (signal_result & COMSIG_USB_CABLE_ATTACHED)
// Short messages are better to read
var/connection_description = "port"
if (istype(target, /obj/machinery/computer))
connection_description = "computer"
else if (ismachinery(target))
connection_description = "machine"
balloon_alert(user, "connected to [connection_description]")
playsound(src, 'sound/items/screwdriver2.ogg', 20, TRUE)
return TRUE
if (signal_result & COMSIG_CANCEL_USB_CABLE_ATTACK)
return TRUE
return FALSE
/obj/item/usb_cable/suicide_act(mob/user)
user.visible_message(span_suicide("[user] is wrapping [src] around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!"))
return OXYLOSS
/obj/item/usb_cable/proc/register_circuit_signals()
RegisterSignal(attached_circuit, COMSIG_MOVABLE_MOVED, .proc/on_moved)
RegisterSignal(attached_circuit, COMSIG_PARENT_QDELETING, .proc/on_circuit_qdeling)
RegisterSignal(attached_circuit.shell, COMSIG_MOVABLE_MOVED, .proc/on_moved)
/obj/item/usb_cable/proc/unregister_circuit_signals(obj/item/integrated_circuit/old_circuit)
UnregisterSignal(attached_circuit, list(
COMSIG_MOVABLE_MOVED,
COMSIG_PARENT_QDELETING,
))
UnregisterSignal(attached_circuit.shell, COMSIG_MOVABLE_MOVED)
/obj/item/usb_cable/proc/on_moved()
SIGNAL_HANDLER
check_in_range()
/obj/item/usb_cable/proc/check_in_range()
if (isnull(attached_circuit))
STOP_PROCESSING(SSobj, src)
return FALSE
if (!IN_GIVEN_RANGE(attached_circuit, src, USB_CABLE_MAX_RANGE))
balloon_alert_to_viewers("detached, too far away")
unregister_circuit_signals(attached_circuit)
attached_circuit = null
STOP_PROCESSING(SSobj, src)
return FALSE
return TRUE
/obj/item/usb_cable/proc/on_circuit_qdeling()
SIGNAL_HANDLER
attached_circuit = null
STOP_PROCESSING(SSobj, src)
+49
View File
@@ -0,0 +1,49 @@
/**
* A circuit variable that holds the name, the datatype and the colour of the variable (taken from the datatype).
*
* Used in integrated circuits for setter and getter circuit components.
*/
/datum/circuit_variable
/// The display name of the circuit variable
var/name
/// The datatype of the circuit variable. Used by the setter and getter circuit components
var/datatype
/// The colour that appears in the UI. The value is set to the datatype's matching colour
var/color
/// The current value held by the variable.
var/value
/// The components that are currently listening. Triggers them when the value is updated.
var/list/obj/item/circuit_component/listeners
/datum/circuit_variable/New(name, datatype)
. = ..()
src.name = name
src.datatype = datatype
var/datum/circuit_datatype/circuit_datatype = GLOB.circuit_datatypes[datatype]
src.listeners = list()
src.color = circuit_datatype.color
/datum/circuit_variable/Destroy(force, ...)
listeners = null
return ..()
/// Sets the value of the circuit component and triggers the appropriate listeners
/datum/circuit_variable/proc/set_value(new_value)
value = new_value
for(var/obj/item/circuit_component/component as anything in listeners)
TRIGGER_CIRCUIT_COMPONENT(component, null)
/// Adds a listener to receive inputs when the variable has a value that is set.
/datum/circuit_variable/proc/add_listener(obj/item/circuit_component/to_add)
listeners += to_add
/// Removes a listener to receive inputs when the variable has a value that is set. Listener will usually clean themselves up
/datum/circuit_variable/proc/remove_listener(obj/item/circuit_component/to_remove)
listeners -= to_remove
+10
View File
@@ -0,0 +1,10 @@
/datum/circuit_datatype/any
datatype = PORT_TYPE_ANY
color = "blue"
datatype_flags = DATATYPE_FLAG_ALLOW_MANUAL_INPUT
/datum/circuit_datatype/any/can_receive_from_datatype(datatype_to_check)
return TRUE
/datum/circuit_datatype/any/handle_manual_input(datum/port/input/port, mob/user, user_input)
return text2num(user_input) || user_input
+9
View File
@@ -0,0 +1,9 @@
// This file is for types that do not have any special conversion behaviour
/datum/circuit_datatype/list_type
datatype = PORT_TYPE_LIST
color = "white"
/datum/circuit_datatype/table
datatype = PORT_TYPE_TABLE
color = "grey"
+10
View File
@@ -0,0 +1,10 @@
/datum/circuit_datatype/entity
datatype = PORT_TYPE_ATOM
color = "purple"
datatype_flags = DATATYPE_FLAG_ALLOW_MANUAL_INPUT
/datum/circuit_datatype/entity/convert_value(datum/port/port, value_to_convert)
var/atom/object = value_to_convert
if(QDELETED(object))
return null
return object
+14
View File
@@ -0,0 +1,14 @@
/datum/circuit_datatype/number
datatype = PORT_TYPE_NUMBER
color = "green"
datatype_flags = DATATYPE_FLAG_ALLOW_MANUAL_INPUT
/datum/circuit_datatype/number/can_receive_from_datatype(datatype_to_check)
. = ..()
if(.)
return
return datatype_to_check == PORT_TYPE_NUMBER
/datum/circuit_datatype/number/handle_manual_input(datum/port/input/port, mob/user, user_input)
return text2num(user_input)
+33
View File
@@ -0,0 +1,33 @@
/datum/port/input/option
var/list/possible_options
/datum/port/input/option/New(obj/item/circuit_component/to_connect, name, datatype, trigger, default, possible_options)
. = ..()
src.possible_options = possible_options
set_value(default, force = TRUE)
/datum/circuit_datatype/option
datatype = PORT_TYPE_OPTION
color = "violet"
datatype_flags = DATATYPE_FLAG_ALLOW_MANUAL_INPUT
/datum/circuit_datatype/option/is_compatible(datum/port/gained_port)
return istype(gained_port, /datum/port/input/option)
/datum/circuit_datatype/option/can_receive_from_datatype(datatype_to_check)
. = ..()
if(.)
return
return datatype_to_check == PORT_TYPE_STRING
/datum/circuit_datatype/option/convert_value(datum/port/input/option/port, value_to_convert)
if(!port.possible_options)
return null
if(value_to_convert in port.possible_options)
return value_to_convert
return port.possible_options[1]
/datum/circuit_datatype/option/datatype_ui_data(datum/port/input/option/port)
return port.possible_options
+17
View File
@@ -0,0 +1,17 @@
/datum/circuit_datatype/signal
datatype = PORT_TYPE_SIGNAL
color = "teal"
datatype_flags = DATATYPE_FLAG_ALLOW_MANUAL_INPUT|DATATYPE_FLAG_AVOID_VALUE_UPDATE
/datum/circuit_datatype/signal/can_receive_from_datatype(datatype_to_check)
. = ..()
if(.)
return
return datatype_to_check == PORT_TYPE_NUMBER
/datum/circuit_datatype/signal/handle_manual_input(datum/port/input/port, mob/user, user_input)
var/atom/parent = port.connected_component
if(parent)
parent.balloon_alert(user, "triggered [port.name]")
return COMPONENT_SIGNAL
+17
View File
@@ -0,0 +1,17 @@
/datum/circuit_datatype/string
datatype = PORT_TYPE_STRING
color = "orange"
datatype_flags = DATATYPE_FLAG_ALLOW_MANUAL_INPUT
/datum/circuit_datatype/string/can_receive_from_datatype(datatype_to_check)
return TRUE
/datum/circuit_datatype/string/convert_value(datum/port/port, value_to_convert)
if(isnull(value_to_convert))
return
// So that they can't easily get the name like this.
if(isatom(value_to_convert))
return PORT_TYPE_ATOM
else
return copytext("[value_to_convert]", 1, PORT_MAX_STRING_LENGTH)
@@ -0,0 +1,14 @@
/**
* # Hello World preset
*
* Says "Hello World" when triggered. Needs to be wired up and connected first.
*/
/obj/item/integrated_circuit/loaded/hello_world
/obj/item/integrated_circuit/loaded/hello_world/Initialize()
. = ..()
var/obj/item/circuit_component/speech/speech = new()
add_component(speech)
speech.message.set_input("Hello World")
@@ -0,0 +1,20 @@
/**
* # Speech Relay preset
*
* Acts like poly. Says whatever it hears.
*/
/obj/item/integrated_circuit/loaded/speech_relay
/obj/item/integrated_circuit/loaded/speech_relay/Initialize()
. = ..()
var/obj/item/circuit_component/hear/hear = new()
add_component(hear)
hear.rel_x = 100
hear.rel_y = 200
var/obj/item/circuit_component/speech/speech = new()
add_component(speech)
speech.rel_x = 400
speech.rel_y = 200
speech.message.connect(hear.message_port)
+133
View File
@@ -0,0 +1,133 @@
/datum/wires/airlock/shell
holder_type = /obj/machinery/door/airlock/shell
proper_name = "Circuit Airlock"
/datum/wires/airlock/shell/on_cut(wire, mend)
// Don't allow them to re-enable autoclose.
if(wire == WIRE_TIMING)
return
return ..()
/obj/machinery/door/airlock/shell
name = "circuit airlock"
autoclose = FALSE
/obj/machinery/door/airlock/shell/Initialize()
. = ..()
AddComponent( \
/datum/component/shell, \
unremovable_circuit_components = list(new /obj/item/circuit_component/airlock), \
capacity = SHELL_CAPACITY_LARGE, \
shell_flags = SHELL_FLAG_ALLOW_FAILURE_ACTION \
)
/obj/machinery/door/airlock/shell/check_access(obj/item/I)
return FALSE
/obj/machinery/door/airlock/shell/canAIControl(mob/user)
return FALSE
/obj/machinery/door/airlock/shell/canAIHack(mob/user)
return FALSE
/obj/machinery/door/airlock/shell/set_wires()
return new /datum/wires/airlock/shell(src)
/obj/item/circuit_component/airlock
display_name = "Airlock"
desc = "The general interface with an airlock. Includes general statuses of the airlock"
/// Called when attack_hand is called on the shell.
var/obj/machinery/door/airlock/attached_airlock
/// Bolts the airlock (if possible)
var/datum/port/input/bolt
/// Unbolts the airlock (if possible)
var/datum/port/input/unbolt
/// Opens the airlock (if possible)
var/datum/port/input/open
/// Closes the airlock (if possible)
var/datum/port/input/close
/// Contains whether the airlock is open or not
var/datum/port/output/is_open
/// Contains whether the airlock is bolted or not
var/datum/port/output/is_bolted
/// Called when the airlock is opened.
var/datum/port/output/opened
/// Called when the airlock is closed
var/datum/port/output/closed
/// Called when the airlock is bolted
var/datum/port/output/bolted
/// Called when the airlock is unbolted
var/datum/port/output/unbolted
/obj/item/circuit_component/airlock/Initialize()
. = ..()
// Input Signals
bolt = add_input_port("Bolt", PORT_TYPE_SIGNAL)
unbolt = add_input_port("Unbolt", PORT_TYPE_SIGNAL)
open = add_input_port("Open", PORT_TYPE_SIGNAL)
close = add_input_port("Close", PORT_TYPE_SIGNAL)
// States
is_open = add_output_port("Is Open", PORT_TYPE_NUMBER)
is_bolted = add_output_port("Is Bolted", PORT_TYPE_NUMBER)
// Output Signals
opened = add_output_port("Opened", PORT_TYPE_SIGNAL)
closed = add_output_port("Closed", PORT_TYPE_SIGNAL)
bolted = add_output_port("Bolted", PORT_TYPE_SIGNAL)
unbolted = add_output_port("Unbolted", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/airlock/register_shell(atom/movable/shell)
. = ..()
if(istype(shell, /obj/machinery/door/airlock))
attached_airlock = shell
RegisterSignal(shell, COMSIG_AIRLOCK_SET_BOLT, .proc/on_airlock_set_bolted)
RegisterSignal(shell, COMSIG_AIRLOCK_OPEN, .proc/on_airlock_open)
RegisterSignal(shell, COMSIG_AIRLOCK_CLOSE, .proc/on_airlock_closed)
/obj/item/circuit_component/airlock/unregister_shell(atom/movable/shell)
attached_airlock = null
UnregisterSignal(shell, list(
COMSIG_AIRLOCK_SET_BOLT,
COMSIG_AIRLOCK_OPEN,
COMSIG_AIRLOCK_CLOSE,
))
return ..()
/obj/item/circuit_component/airlock/proc/on_airlock_set_bolted(datum/source, should_bolt)
SIGNAL_HANDLER
is_bolted.set_output(should_bolt)
if(should_bolt)
bolted.set_output(COMPONENT_SIGNAL)
else
unbolted.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/airlock/proc/on_airlock_open(datum/source, force)
SIGNAL_HANDLER
is_open.set_output(TRUE)
opened.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/airlock/proc/on_airlock_closed(datum/source, forced)
SIGNAL_HANDLER
is_open.set_output(FALSE)
closed.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/airlock/input_received(datum/port/input/port)
. = ..()
if(.)
return
if(!attached_airlock)
return
if(COMPONENT_TRIGGERED_BY(bolt, port))
attached_airlock.bolt()
if(COMPONENT_TRIGGERED_BY(unbolt, port))
attached_airlock.unbolt()
if(COMPONENT_TRIGGERED_BY(open, port) && attached_airlock.density)
INVOKE_ASYNC(attached_airlock, /obj/machinery/door/airlock.proc/open)
if(COMPONENT_TRIGGERED_BY(close, port) && !attached_airlock.density)
INVOKE_ASYNC(attached_airlock, /obj/machinery/door/airlock.proc/close)
+55
View File
@@ -0,0 +1,55 @@
/**
* # Bot
*
* Immobile (but not dense) shells that can interact with world.
*/
/obj/structure/bot
name = "bot"
icon = 'icons/obj/wiremod.dmi'
icon_state = "setup_medium_box"
density = FALSE
light_system = MOVABLE_LIGHT
light_on = FALSE
/obj/structure/bot/Initialize()
. = ..()
AddComponent( \
/datum/component/shell, \
unremovable_circuit_components = list(new /obj/item/circuit_component/bot), \
capacity = SHELL_CAPACITY_LARGE, \
shell_flags = SHELL_FLAG_USB_PORT, \
)
/obj/item/circuit_component/bot
display_name = "Bot"
desc = "Triggers when someone interacts with the bot."
/// Called when attack_hand is called on the shell.
var/datum/port/output/signal
/// The user who used the bot
var/datum/port/output/entity
/obj/item/circuit_component/bot/Initialize()
. = ..()
entity = add_output_port("User", PORT_TYPE_ATOM)
signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/bot/Destroy()
signal = null
entity = null
return ..()
/obj/item/circuit_component/bot/register_shell(atom/movable/shell)
RegisterSignal(shell, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)
/obj/item/circuit_component/bot/unregister_shell(atom/movable/shell)
UnregisterSignal(shell, COMSIG_ATOM_ATTACK_HAND)
/obj/item/circuit_component/bot/proc/on_attack_hand(atom/source, mob/user)
SIGNAL_HANDLER
source.balloon_alert(user, "pushed button")
playsound(source, get_sfx("terminal_type"), 25, FALSE)
entity.set_output(user)
signal.set_output(COMPONENT_SIGNAL)
@@ -0,0 +1,553 @@
/obj/item/organ/cyberimp/bci
name = "brain-computer interface"
desc = "An implant that can be placed in a user's head to control circuits using their brain."
icon = 'icons/obj/wiremod.dmi'
icon_state = "bci"
zone = BODY_ZONE_HEAD
w_class = WEIGHT_CLASS_TINY
/obj/item/organ/cyberimp/bci/Initialize()
. = ..()
AddComponent(/datum/component/shell, list(
new /obj/item/circuit_component/bci_core,
new /obj/item/circuit_component/bci_action(null, "One"),
new /obj/item/circuit_component/bci_action(null, "Two"),
new /obj/item/circuit_component/bci_action(null, "Three"),
), SHELL_CAPACITY_SMALL)
/obj/item/organ/cyberimp/bci/Insert(mob/living/carbon/reciever, special, drop_if_replaced)
. = ..()
// Organs are put in nullspace, but this breaks circuit interactions
forceMove(reciever)
/obj/item/organ/cyberimp/bci/say(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced)
if (owner)
// Otherwise say_dead will be called.
// It's intentional that a circuit for a dead person does not speak from the shell.
if (owner.stat == DEAD)
return
owner.say(message, forced = "circuit speech")
else
return ..()
/obj/item/circuit_component/bci_action
display_name = "BCI Action"
desc = "Represents an action the user can take when implanted with the brain-computer interface."
required_shells = list(/obj/item/organ/cyberimp/bci)
/// The icon of the button
var/datum/port/input/option/icon_options
/// The name to use for the button
var/datum/port/input/button_name
/// Called when the user presses the button
var/datum/port/output/signal
/// A reference to the action button itself
var/datum/action/innate/bci_action/bci_action
/obj/item/circuit_component/bci_action/Initialize(mapload, default_icon)
. = ..()
if (!isnull(default_icon))
icon_options.set_input(default_icon)
button_name = add_input_port("Name", PORT_TYPE_STRING)
signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/bci_action/Destroy()
QDEL_NULL(bci_action)
return ..()
/obj/item/circuit_component/bci_action/populate_options()
var/static/action_options = list(
"Blank",
"One",
"Two",
"Three",
"Four",
"Five",
"Blood",
"Bomb",
"Brain",
"Brain Damage",
"Cross",
"Electricity",
"Exclamation",
"Heart",
"Id",
"Info",
"Injection",
"Magnetism",
"Minus",
"Network",
"Plus",
"Power",
"Question",
"Radioactive",
"Reaction",
"Repair",
"Say",
"Scan",
"Shield",
"Skull",
"Sleep",
"Wireless",
)
icon_options = add_option_port("Icon", action_options)
/obj/item/circuit_component/bci_action/register_shell(atom/movable/shell)
var/obj/item/organ/cyberimp/bci/bci = shell
bci_action = new(src)
update_action()
bci.actions += list(bci_action)
/obj/item/circuit_component/bci_action/unregister_shell(atom/movable/shell)
var/obj/item/organ/cyberimp/bci/bci = shell
bci.actions -= bci_action
QDEL_NULL(bci_action)
/obj/item/circuit_component/bci_action/input_received(datum/port/input/port)
. = ..()
if (.)
return
if (!isnull(bci_action))
update_action()
/obj/item/circuit_component/bci_action/proc/update_action()
bci_action.name = button_name.value
bci_action.button_icon_state = "bci_[replacetextEx(lowertext(icon_options.value), " ", "_")]"
/datum/action/innate/bci_action
name = "Action"
icon_icon = 'icons/mob/actions/actions_items.dmi'
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "bci_power"
var/obj/item/circuit_component/bci_action/circuit_component
/datum/action/innate/bci_action/New(obj/item/circuit_component/bci_action/circuit_component)
..()
src.circuit_component = circuit_component
/datum/action/innate/bci_action/Destroy()
circuit_component.bci_action = null
circuit_component = null
return ..()
/datum/action/innate/bci_action/Activate()
circuit_component.signal.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/bci_core
display_name = "BCI Core"
desc = "Controls the core operations of the BCI."
/// A reference to the action button to look at charge/get info
var/datum/action/innate/bci_charge_action/charge_action
var/datum/port/input/message
var/datum/port/input/send_message_signal
var/datum/port/output/user_port
var/datum/weakref/user
/obj/item/circuit_component/bci_core/Initialize()
. = ..()
message = add_input_port("Message", PORT_TYPE_STRING)
send_message_signal = add_input_port("Send Message", PORT_TYPE_SIGNAL)
user_port = add_output_port("User", PORT_TYPE_ATOM)
/obj/item/circuit_component/bci_core/Destroy()
QDEL_NULL(charge_action)
return ..()
/obj/item/circuit_component/bci_core/register_shell(atom/movable/shell)
var/obj/item/organ/cyberimp/bci/bci = shell
charge_action = new(src)
bci.actions += list(charge_action)
RegisterSignal(shell, COMSIG_ORGAN_IMPLANTED, .proc/on_organ_implanted)
RegisterSignal(shell, COMSIG_ORGAN_REMOVED, .proc/on_organ_removed)
/obj/item/circuit_component/bci_core/unregister_shell(atom/movable/shell)
var/obj/item/organ/cyberimp/bci/bci = shell
bci.actions -= charge_action
QDEL_NULL(charge_action)
UnregisterSignal(shell, list(
COMSIG_ORGAN_IMPLANTED,
COMSIG_ORGAN_REMOVED,
))
/obj/item/circuit_component/bci_core/input_received(datum/port/input/port)
. = ..()
if (.)
return .
if (COMPONENT_TRIGGERED_BY(send_message_signal, port))
var/sent_message = trim(message.value)
if (!sent_message)
return
var/mob/living/carbon/resolved_owner = user?.resolve()
if (isnull(resolved_owner))
return
if (resolved_owner.stat == DEAD)
return
to_chat(resolved_owner, "<i>You hear a strange, robotic voice in your head...</i> \"[span_robot("[html_encode(sent_message)]")]\"")
/obj/item/circuit_component/bci_core/proc/on_organ_implanted(datum/source, mob/living/carbon/owner)
SIGNAL_HANDLER
user_port.set_output(owner)
user = WEAKREF(owner)
RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/on_examine)
RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/on_borg_charge)
RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_electrocute)
/obj/item/circuit_component/bci_core/proc/on_organ_removed(datum/source, mob/living/carbon/owner)
SIGNAL_HANDLER
user_port.set_output(null)
user = null
UnregisterSignal(owner, list(
COMSIG_PARENT_EXAMINE,
COMSIG_PROCESS_BORGCHARGER_OCCUPANT,
COMSIG_LIVING_ELECTROCUTE_ACT,
))
/obj/item/circuit_component/bci_core/proc/on_borg_charge(datum/source, amount)
SIGNAL_HANDLER
if (isnull(parent.cell))
return
parent.cell.give(amount)
/obj/item/circuit_component/bci_core/proc/on_electrocute(datum/source, shock_damage, siemens_coefficient, flags)
SIGNAL_HANDLER
if (isnull(parent.cell))
return
if (flags & SHOCK_ILLUSION)
return
parent.cell.give(shock_damage * 2)
to_chat(source, span_notice("You absorb some of the shock into your [parent.name]!"))
/obj/item/circuit_component/bci_core/proc/on_examine(datum/source, mob/mob, list/examine_text)
SIGNAL_HANDLER
if (isobserver(mob))
examine_text += span_notice("[source.p_they(capitalized = TRUE)] [source.p_have()] <a href='?src=[REF(src)];open_bci=1'>\a [parent] implanted in [source.p_them()]</a>.")
/obj/item/circuit_component/bci_core/Topic(href, list/href_list)
..()
if (!isobserver(usr))
return
if (href_list["open_bci"])
parent.attack_ghost(usr)
/datum/action/innate/bci_charge_action
name = "Check BCI Charge"
check_flags = NONE
icon_icon = 'icons/obj/power.dmi'
button_icon_state = "cell"
var/obj/item/circuit_component/bci_core/circuit_component
/datum/action/innate/bci_charge_action/New(obj/item/circuit_component/bci_core/circuit_component)
..()
src.circuit_component = circuit_component
button.maptext_x = 2
button.maptext_y = 0
update_maptext()
START_PROCESSING(SSobj, src)
/datum/action/innate/bci_charge_action/Destroy()
circuit_component.charge_action = null
circuit_component = null
STOP_PROCESSING(SSobj, src)
return ..()
/datum/action/innate/bci_charge_action/Trigger()
var/obj/item/stock_parts/cell/cell = circuit_component.parent.cell
if (isnull(cell))
to_chat(owner, span_boldwarning("[circuit_component.parent] has no power cell."))
else
to_chat(owner, span_info("[circuit_component.parent]'s [cell.name] has <b>[cell.percent()]%</b> charge left."))
to_chat(owner, span_info("You can recharge it by using a cyborg recharging station."))
/datum/action/innate/bci_charge_action/process(delta_time)
update_maptext()
/datum/action/innate/bci_charge_action/proc/update_maptext()
var/obj/item/stock_parts/cell/cell = circuit_component.parent.cell
button.maptext = cell ? MAPTEXT("[cell.percent()]%") : ""
/obj/machinery/bci_implanter
name = "brain-computer interface manipulation chamber"
desc = "A machine that, when given a brain-computer interface, will implant it into an occupant. Otherwise, will remove any brain-computer interfaces they already have."
circuit = /obj/item/circuitboard/machine/bci_implanter
icon = 'icons/obj/machines/bci_implanter.dmi'
icon_state = "bci_implanter"
base_icon_state = "bci_implanter"
layer = ABOVE_WINDOW_LAYER
use_power = IDLE_POWER_USE
anchored = TRUE
density = TRUE
obj_flags = NO_BUILD // Becomes undense when the door is open
idle_power_usage = 50
active_power_usage = 300
var/busy = FALSE
var/busy_icon_state
var/locked = FALSE
var/datum/weakref/bci_to_implant
COOLDOWN_DECLARE(message_cooldown)
/obj/machinery/bci_implanter/Initialize()
. = ..()
occupant_typecache = typecacheof(/mob/living/carbon)
/obj/machinery/bci_implanter/Destroy()
QDEL_NULL(bci_to_implant)
return ..()
/obj/machinery/bci_implanter/examine(mob/user)
. = ..()
if (isnull(bci_to_implant?.resolve()))
. += span_notice("There is no BCI inserted.")
else
. += span_notice("Right-click to remove current BCI.")
/obj/machinery/bci_implanter/proc/set_busy(status, working_icon)
busy = status
busy_icon_state = working_icon
update_appearance()
/obj/machinery/bci_implanter/update_icon_state()
if (occupant)
icon_state = busy ? busy_icon_state : "[base_icon_state]_occupied"
return ..()
icon_state = "[base_icon_state][state_open ? "_open" : null]"
return ..()
/obj/machinery/bci_implanter/update_overlays()
var/list/overlays = ..()
if ((machine_stat & MAINT) || panel_open)
overlays += "maint"
return overlays
if (machine_stat & (NOPOWER|BROKEN))
return overlays
if (busy || locked)
overlays += "red"
if (locked)
overlays += "bolted"
return overlays
overlays += "green"
return overlays
/obj/machinery/bci_implanter/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
if (. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
return .
if(!user.Adjacent(src))
return
if (locked)
balloon_alert(user, "it's locked!")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
var/obj/item/organ/cyberimp/bci/bci_to_implant_resolved = bci_to_implant?.resolve()
if (isnull(bci_to_implant_resolved))
balloon_alert(user, "no bci inserted!")
else
user.put_in_hands(bci_to_implant_resolved)
balloon_alert(user, "ejected bci")
bci_to_implant = null
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/machinery/bci_implanter/attackby(obj/item/weapon, mob/user, params)
var/obj/item/organ/cyberimp/bci/new_bci = weapon
if (istype(new_bci))
if (!(locate(/obj/item/integrated_circuit) in new_bci))
balloon_alert(user, "bci has no circuit!")
return
var/obj/item/organ/cyberimp/bci/previous_bci_to_implant = bci_to_implant?.resolve()
bci_to_implant = WEAKREF(weapon)
weapon.forceMove(src)
if (isnull(previous_bci_to_implant))
balloon_alert(user, "inserted bci")
else
balloon_alert(user, "swapped bci")
user.put_in_hands(previous_bci_to_implant)
return
return ..()
/obj/machinery/bci_implanter/attackby_secondary(obj/item/weapon, mob/user, params)
if (!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, weapon))
update_appearance()
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
if (default_pry_open(weapon))
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
if (default_deconstruction_crowbar(weapon))
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/machinery/bci_implanter/proc/start_process()
if (machine_stat & (NOPOWER|BROKEN))
return
if ((machine_stat & MAINT) || panel_open)
return
if (!occupant || busy)
return
var/locked_state = locked
locked = TRUE
set_busy(TRUE, "[initial(icon_state)]_raising")
addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_active"), 1 SECONDS)
addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_falling"), 2 SECONDS)
addtimer(CALLBACK(src, .proc/complete_process, locked_state), 3 SECONDS)
/obj/machinery/bci_implanter/proc/complete_process(locked_state)
locked = locked_state
set_busy(FALSE)
var/mob/living/carbon/carbon_occupant = occupant
if (!istype(carbon_occupant))
return
playsound(loc, 'sound/machines/ping.ogg', 30, FALSE)
var/obj/item/organ/cyberimp/bci/bci_organ = carbon_occupant.getorgan(/obj/item/organ/cyberimp/bci)
var/obj/item/organ/cyberimp/bci/bci_to_implant_resolved = bci_to_implant?.resolve()
if (bci_organ)
bci_organ.Remove(carbon_occupant)
if (isnull(bci_to_implant_resolved))
say("Occupant's previous brain-computer interface has been transferred to internal storage unit.")
bci_organ.forceMove(src)
bci_to_implant = WEAKREF(bci_organ)
else
say("Occupant's previous brain-computer interface has been ejected.")
bci_organ.forceMove(drop_location())
else if (!isnull(bci_to_implant_resolved))
say("Occupant has been injected with [bci_to_implant_resolved].")
bci_to_implant_resolved.Insert(carbon_occupant)
bci_to_implant = null
/obj/machinery/bci_implanter/open_machine()
if(state_open)
return FALSE
..()
return TRUE
/obj/machinery/bci_implanter/close_machine(mob/living/carbon/user)
if(!state_open)
return FALSE
..()
var/mob/living/carbon/carbon_occupant = occupant
if (istype(occupant))
var/obj/item/organ/cyberimp/bci/existing_bci_organ = carbon_occupant.getorgan(/obj/item/organ/cyberimp/bci)
if (isnull(existing_bci_organ) && isnull(bci_to_implant?.resolve()))
say("No brain-computer interface inserted, and occupant does not have one. Insert a BCI to implant one.")
playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
return FALSE
addtimer(CALLBACK(src, .proc/start_process), 1 SECONDS)
return TRUE
/obj/machinery/bci_implanter/relaymove(mob/living/user, direction)
var/message
if (locked)
message = "it won't budge!"
else if (user.stat != CONSCIOUS)
message = "you don't have the energy!"
if (!isnull(message))
if (COOLDOWN_FINISHED(src, message_cooldown))
COOLDOWN_START(src, message_cooldown, 5 SECONDS)
balloon_alert(user, message)
return
open_machine()
/obj/machinery/bci_implanter/interact(mob/user)
if (state_open)
close_machine(null, user)
return
else if (locked)
balloon_alert(user, "it's locked!")
return
open_machine()
/obj/item/circuitboard/machine/bci_implanter
name = "Brain-Computer Interface Manipulation Chamber (Machine Board)"
greyscale_colors = CIRCUIT_COLOR_SCIENCE
build_path = /obj/machinery/bci_implanter
req_components = list(
/obj/item/stock_parts/micro_laser = 2,
/obj/item/stock_parts/manipulator = 1,
)
@@ -0,0 +1,51 @@
/**
* # Compact Remote
*
* A handheld device with one big button.
*/
/obj/item/compact_remote
name = "compact remote"
icon = 'icons/obj/wiremod.dmi'
icon_state = "setup_small_simple"
inhand_icon_state = "electronic"
worn_icon_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
light_system = MOVABLE_LIGHT_DIRECTIONAL
light_on = FALSE
/obj/item/compact_remote/Initialize()
. = ..()
AddComponent(/datum/component/shell, list(
new /obj/item/circuit_component/compact_remote()
), SHELL_CAPACITY_SMALL)
/obj/item/circuit_component/compact_remote
display_name = "Compact Remote"
desc = "Used to receive inputs from the compact remote shell. Use the shell in hand to trigger the output signal."
/// Called when attack_self is called on the shell.
var/datum/port/output/signal
/// The user who used the bot
var/datum/port/output/entity
/obj/item/circuit_component/compact_remote/Initialize()
. = ..()
entity = add_output_port("User", PORT_TYPE_ATOM)
signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/compact_remote/register_shell(atom/movable/shell)
RegisterSignal(shell, COMSIG_ITEM_ATTACK_SELF, .proc/send_trigger)
/obj/item/circuit_component/compact_remote/unregister_shell(atom/movable/shell)
UnregisterSignal(shell, COMSIG_ITEM_ATTACK_SELF)
/**
* Called when the shell item is used in hand.
*/
/obj/item/circuit_component/compact_remote/proc/send_trigger(atom/source, mob/user)
SIGNAL_HANDLER
source.balloon_alert(user, "clicked primary button")
playsound(source, get_sfx("terminal_type"), 25, FALSE)
entity.set_output(user)
signal.set_output(COMPONENT_SIGNAL)
+89
View File
@@ -0,0 +1,89 @@
/**
* # Compact Remote
*
* A handheld device with several buttons.
* In game, this translates to having different signals for normal usage, alt-clicking, and ctrl-clicking when in your hand.
*/
/obj/item/controller
name = "controller"
icon = 'icons/obj/wiremod.dmi'
icon_state = "setup_small_calc"
inhand_icon_state = "electronic"
worn_icon_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
light_system = MOVABLE_LIGHT_DIRECTIONAL
light_on = FALSE
/obj/item/controller/Initialize()
. = ..()
AddComponent(/datum/component/shell, list(
new /obj/item/circuit_component/controller()
), SHELL_CAPACITY_MEDIUM)
/obj/item/circuit_component/controller
display_name = "Controller"
desc = "Used to receive inputs from the controller shell. Use the shell in hand to trigger the output signal. Alt-click for the alternate signal. Right click for the extra signal."
/// The three separate buttons that are called in attack_hand on the shell.
var/datum/port/output/signal
var/datum/port/output/alt
var/datum/port/output/right
/// The entity output
var/datum/port/output/entity
/obj/item/circuit_component/controller/Initialize()
. = ..()
entity = add_output_port("User", PORT_TYPE_ATOM)
signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
alt = add_output_port("Alternate Signal", PORT_TYPE_SIGNAL)
right = add_output_port("Extra Signal", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/controller/register_shell(atom/movable/shell)
RegisterSignal(shell, COMSIG_ITEM_ATTACK_SELF, .proc/send_trigger)
RegisterSignal(shell, COMSIG_CLICK_ALT, .proc/send_alternate_signal)
RegisterSignal(shell, COMSIG_ITEM_ATTACK_SELF_SECONDARY, .proc/send_right_signal)
/obj/item/circuit_component/controller/unregister_shell(atom/movable/shell)
UnregisterSignal(shell, list(
COMSIG_ITEM_ATTACK_SELF,
COMSIG_ITEM_ATTACK_SELF_SECONDARY,
COMSIG_CLICK_ALT,
))
/**
* Called when the shell item is used in hand
*/
/obj/item/circuit_component/controller/proc/send_trigger(atom/source, mob/user)
SIGNAL_HANDLER
if(!user.Adjacent(source))
return
source.balloon_alert(user, "clicked primary button")
playsound(source, get_sfx("terminal_type"), 25, FALSE)
entity.set_output(user)
signal.set_output(COMPONENT_SIGNAL)
/**
* Called when the shell item is alt-clicked
*/
/obj/item/circuit_component/controller/proc/send_alternate_signal(atom/source, mob/user)
SIGNAL_HANDLER
if(!user.Adjacent(source))
return
source.balloon_alert(user, "clicked alternate button")
playsound(source, get_sfx("terminal_type"), 25, FALSE)
entity.set_output(user)
alt.set_output(COMPONENT_SIGNAL)
/**
* Called when the shell item is right-clicked in active hand
*/
/obj/item/circuit_component/controller/proc/send_right_signal(atom/source, mob/user)
SIGNAL_HANDLER
if(!user.Adjacent(source))
return
source.balloon_alert(user, "clicked extra button")
playsound(source, get_sfx("terminal_type"), 25, FALSE)
entity.set_output(user)
right.set_output(COMPONENT_SIGNAL)
+82
View File
@@ -0,0 +1,82 @@
/**
* # Drone
*
* A movable mob that can be fed inputs on which direction to travel.
*/
/mob/living/circuit_drone
name = "drone"
icon = 'icons/obj/wiremod.dmi'
icon_state = "setup_medium_med"
living_flags = 0
light_system = MOVABLE_LIGHT_DIRECTIONAL
light_on = FALSE
/mob/living/circuit_drone/Initialize()
. = ..()
AddComponent(/datum/component/shell, list(
new /obj/item/circuit_component/bot_circuit()
), SHELL_CAPACITY_LARGE)
/mob/living/circuit_drone/updatehealth()
. = ..()
if(health < 0)
gib(no_brain = TRUE, no_organs = TRUE, no_bodyparts = TRUE)
/mob/living/circuit_drone/spawn_gibs()
new /obj/effect/gibspawner/robot(drop_location(), src, get_static_viruses())
/obj/item/circuit_component/bot_circuit
display_name = "Drone"
desc = "Used to send movement output signals to the drone shell."
/// The inputs to allow for the drone to move
var/datum/port/input/north
var/datum/port/input/east
var/datum/port/input/south
var/datum/port/input/west
// Done like this so that travelling diagonally is more simple
COOLDOWN_DECLARE(north_delay)
COOLDOWN_DECLARE(east_delay)
COOLDOWN_DECLARE(south_delay)
COOLDOWN_DECLARE(west_delay)
/// Delay between each movement
var/move_delay = 0.2 SECONDS
/obj/item/circuit_component/bot_circuit/Initialize()
. = ..()
north = add_input_port("Move North", PORT_TYPE_SIGNAL)
east = add_input_port("Move East", PORT_TYPE_SIGNAL)
south = add_input_port("Move South", PORT_TYPE_SIGNAL)
west = add_input_port("Move West", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/bot_circuit/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/mob/living/shell = parent.shell
if(!istype(shell) || shell.stat)
return
var/direction
if(COMPONENT_TRIGGERED_BY(north, port) && COOLDOWN_FINISHED(src, north_delay))
direction = NORTH
COOLDOWN_START(src, north_delay, move_delay)
else if(COMPONENT_TRIGGERED_BY(east, port) && COOLDOWN_FINISHED(src, east_delay))
direction = EAST
COOLDOWN_START(src, east_delay, move_delay)
else if(COMPONENT_TRIGGERED_BY(south, port) && COOLDOWN_FINISHED(src, south_delay))
direction = SOUTH
COOLDOWN_START(src, south_delay, move_delay)
else if(COMPONENT_TRIGGERED_BY(west, port) && COOLDOWN_FINISHED(src, west_delay))
direction = WEST
COOLDOWN_START(src, west_delay, move_delay)
if(!direction)
return
if(shell.Process_Spacemove(direction))
shell.Move(get_step(shell, direction), direction)
+140
View File
@@ -0,0 +1,140 @@
/**
* # Money Bot
*
* Immobile (but not dense) shell that can receive and dispense money.
*/
/obj/structure/money_bot
name = "money bot"
icon = 'icons/obj/wiremod.dmi'
icon_state = "setup_large"
density = FALSE
light_system = MOVABLE_LIGHT
light_on = FALSE
var/stored_money = 0
/obj/structure/money_bot/deconstruct(disassembled)
new /obj/item/holochip(drop_location(), stored_money)
return ..()
/obj/structure/money_bot/proc/add_money(to_add)
stored_money += to_add
SEND_SIGNAL(src, COMSIG_MONEYBOT_ADD_MONEY, to_add)
/obj/structure/money_bot/Initialize()
. = ..()
AddComponent(/datum/component/shell, list(
new /obj/item/circuit_component/money_bot(),
new /obj/item/circuit_component/money_dispenser()
), SHELL_CAPACITY_LARGE)
/obj/structure/money_bot/wrench_act(mob/living/user, obj/item/tool)
set_anchored(!anchored)
tool.play_tool_sound(src)
balloon_alert(user, "You [anchored?"secure":"unsecure"] [src].")
return TRUE
/obj/item/circuit_component/money_dispenser
display_name = "Money Dispenser"
desc = "Used to dispense money from the money bot. Money is taken from the internal storage of money."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
/// The amount of money to dispense
var/datum/port/input/dispense_amount
/// Outputs a signal when it fails to output any money.
var/datum/port/output/on_fail
var/obj/structure/money_bot/attached_bot
/obj/item/circuit_component/money_dispenser/Initialize()
. = ..()
dispense_amount = add_input_port("Amount", PORT_TYPE_NUMBER)
on_fail = add_output_port("On Failed", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/money_dispenser/register_shell(atom/movable/shell)
. = ..()
if(istype(shell, /obj/structure/money_bot))
attached_bot = shell
/obj/item/circuit_component/money_dispenser/unregister_shell(atom/movable/shell)
attached_bot = null
return ..()
/obj/item/circuit_component/money_dispenser/input_received(datum/port/input/port)
. = ..()
if(.)
return
if(!attached_bot)
return
var/to_dispense = clamp(dispense_amount.value, 0, attached_bot.stored_money)
if(!to_dispense)
on_fail.set_output(COMPONENT_SIGNAL)
return
attached_bot.add_money(-to_dispense)
new /obj/item/holochip(drop_location(), to_dispense)
/obj/item/circuit_component/money_bot
display_name = "Money Bot"
var/obj/structure/money_bot/attached_bot
desc = "Used to receive input signals when money is inserted into the money bot shell and also keep track of the total money in the shell."
/// Total money in the shell
var/datum/port/output/total_money
/// Amount of the last money inputted into the shell
var/datum/port/output/money_input
/// Trigger for when money is inputted into the shell
var/datum/port/output/money_trigger
/// The person who input the money
var/datum/port/output/entity
/obj/item/circuit_component/money_bot/Initialize()
. = ..()
total_money = add_output_port("Total Money", PORT_TYPE_NUMBER)
money_input = add_output_port("Last Input Money", PORT_TYPE_NUMBER)
entity = add_output_port("User", PORT_TYPE_ATOM)
money_trigger = add_output_port("Money Input", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/money_bot/register_shell(atom/movable/shell)
. = ..()
if(istype(shell, /obj/structure/money_bot))
attached_bot = shell
total_money.set_output(attached_bot.stored_money)
RegisterSignal(shell, COMSIG_PARENT_ATTACKBY, .proc/handle_money_insert)
RegisterSignal(shell, COMSIG_MONEYBOT_ADD_MONEY, .proc/handle_money_update)
/obj/item/circuit_component/money_bot/unregister_shell(atom/movable/shell)
UnregisterSignal(shell, list(
COMSIG_PARENT_ATTACKBY,
COMSIG_MONEYBOT_ADD_MONEY,
))
total_money.set_output(null)
attached_bot = null
return ..()
/obj/item/circuit_component/money_bot/proc/handle_money_insert(atom/source, obj/item/item, mob/living/attacker)
SIGNAL_HANDLER
if(!attached_bot || !iscash(item))
return
var/amount_to_insert = item.get_item_credit_value()
if(!amount_to_insert)
balloon_alert(attacker, "this has no value!")
return
attached_bot.add_money(amount_to_insert)
balloon_alert(attacker, "inserted [amount_to_insert] credits.")
money_input.set_output(amount_to_insert)
entity.set_output(attacker)
money_trigger.set_output(COMPONENT_SIGNAL)
qdel(item)
/obj/item/circuit_component/money_bot/proc/handle_money_update(atom/source)
SIGNAL_HANDLER
if(attached_bot)
total_money.set_output(attached_bot.stored_money)
+62
View File
@@ -0,0 +1,62 @@
/**
* # Scanner
*
* A handheld device that lets you flash it over people.
*/
/obj/item/wiremod_scanner
name = "scanner"
icon = 'icons/obj/wiremod.dmi'
icon_state = "setup_small"
inhand_icon_state = "electronic"
worn_icon_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
light_system = MOVABLE_LIGHT_DIRECTIONAL
light_on = FALSE
/obj/item/wiremod_scanner/Initialize()
. = ..()
AddComponent(/datum/component/shell, list(
new /obj/item/circuit_component/wiremod_scanner()
), SHELL_CAPACITY_SMALL)
/obj/item/circuit_component/wiremod_scanner
display_name = "Scanner"
desc = "Used to receive scanned entities from the scanner."
/// Called when afterattack is called on the shell.
var/datum/port/output/signal
/// The attacker
var/datum/port/output/attacker
/// The entity being attacked
var/datum/port/output/attacking
/obj/item/circuit_component/wiremod_scanner/Initialize()
. = ..()
attacker = add_output_port("Scanner", PORT_TYPE_ATOM)
attacking = add_output_port("Scanned Entity", PORT_TYPE_ATOM)
signal = add_output_port("Scanned", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/wiremod_scanner/register_shell(atom/movable/shell)
RegisterSignal(shell, COMSIG_ITEM_AFTERATTACK, .proc/handle_afterattack)
/obj/item/circuit_component/wiremod_scanner/unregister_shell(atom/movable/shell)
UnregisterSignal(shell, COMSIG_ITEM_AFTERATTACK)
/**
* Called when the shell item attacks something
*/
/obj/item/circuit_component/wiremod_scanner/proc/handle_afterattack(atom/source, atom/target, mob/user, proximity_flag)
SIGNAL_HANDLER
if(!proximity_flag)
return
source.balloon_alert(user, "scanned object")
playsound(source, get_sfx("terminal_type"), 25, FALSE)
attacker.set_output(user)
attacking.set_output(target)
signal.set_output(COMPONENT_SIGNAL)
@@ -0,0 +1,66 @@
/obj/structure/scanner_gate_shell
name = "circuit scanner gate"
desc = "A gate able to perform mid-depth scans on any organisms who pass under it."
icon = 'icons/obj/machines/scangate.dmi'
icon_state = "scangate_black"
var/scanline_timer
/obj/structure/scanner_gate_shell/Initialize()
. = ..()
set_scanline("passive")
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = .proc/on_entered,
)
AddElement(/datum/element/connect_loc, loc_connections)
AddComponent(/datum/component/shell, list(
new /obj/item/circuit_component/scanner_gate()
), SHELL_CAPACITY_LARGE, SHELL_FLAG_REQUIRE_ANCHOR)
/obj/structure/scanner_gate_shell/wrench_act(mob/living/user, obj/item/tool)
set_anchored(!anchored)
tool.play_tool_sound(src)
balloon_alert(user, "You [anchored?"secure":"unsecure"] [src].")
return TRUE
/obj/structure/scanner_gate_shell/proc/on_entered(datum/source, atom/movable/AM)
SIGNAL_HANDLER
set_scanline("scanning", 10)
SEND_SIGNAL(src, COMSIG_SCANGATE_SHELL_PASS, AM)
/obj/structure/scanner_gate_shell/proc/set_scanline(type, duration)
cut_overlays()
deltimer(scanline_timer)
add_overlay(type)
if(duration)
scanline_timer = addtimer(CALLBACK(src, .proc/set_scanline, "passive"), duration, TIMER_STOPPABLE)
/obj/item/circuit_component/scanner_gate
display_name = "Scanner Gate"
desc = "A gate able to perform mid-depth scans on any object that pass through it."
circuit_flags = CIRCUIT_FLAG_OUTPUT_SIGNAL
var/datum/port/output/scanned
var/obj/structure/scanner_gate_shell/attached_gate
/obj/item/circuit_component/scanner_gate/Initialize()
. = ..()
scanned = add_output_port("Scanned Object", PORT_TYPE_ATOM)
/obj/item/circuit_component/scanner_gate/register_shell(atom/movable/shell)
. = ..()
if(istype(shell, /obj/structure/scanner_gate_shell))
attached_gate = shell
RegisterSignal(attached_gate, COMSIG_SCANGATE_SHELL_PASS, .proc/on_trigger)
/obj/item/circuit_component/scanner_gate/unregister_shell(atom/movable/shell)
UnregisterSignal(attached_gate, COMSIG_SCANGATE_SHELL_PASS)
attached_gate = null
return ..()
/obj/item/circuit_component/scanner_gate/proc/on_trigger(datum/source, atom/movable/passed)
SIGNAL_HANDLER
scanned.set_output(passed)
trigger_output.set_output(COMPONENT_SIGNAL)
+24
View File
@@ -0,0 +1,24 @@
/**
* # Server
*
* Immobile (but not dense) shells that can interact with
* world.
*/
/obj/structure/server
name = "server"
icon = 'icons/obj/wiremod.dmi'
icon_state = "setup_stationary"
density = TRUE
light_system = MOVABLE_LIGHT
light_on = FALSE
/obj/structure/server/Initialize()
. = ..()
AddComponent(/datum/component/shell, null, SHELL_CAPACITY_VERY_LARGE, SHELL_FLAG_REQUIRE_ANCHOR|SHELL_FLAG_USB_PORT)
/obj/structure/server/wrench_act(mob/living/user, obj/item/tool)
set_anchored(!anchored)
tool.play_tool_sound(src)
balloon_alert(user, "You [anchored?"secure":"unsecure"] [src].")
return TRUE
+65
View File
@@ -0,0 +1,65 @@
/**
* # Shell Item
*
* Printed out by protolathes. Screwdriver to complete the shell.
*/
/obj/item/shell
name = "assembly"
desc = "A shell assembly that can be completed by screwdrivering it."
icon = 'icons/obj/wiremod.dmi'
var/shell_to_spawn
var/screw_delay = 3 SECONDS
/obj/item/shell/screwdriver_act(mob/living/user, obj/item/tool)
user.visible_message(span_notice("[user] begins finishing [src]."), span_notice("You begin finishing [src]."))
tool.play_tool_sound(src)
if(!do_after(user, screw_delay, src))
return
user.visible_message(span_notice("[user] finishes [src]."), span_notice("You finish [src]."))
var/turf/drop_loc = drop_location()
qdel(src)
if(drop_loc)
new shell_to_spawn(drop_loc)
return TRUE
/obj/item/shell/bot
name = "bot assembly"
icon_state = "setup_medium_box-open"
shell_to_spawn = /obj/structure/bot
/obj/item/shell/money_bot
name = "money bot assembly"
icon_state = "setup_large-open"
shell_to_spawn = /obj/structure/money_bot
/obj/item/shell/drone
name = "drone assembly"
icon_state = "setup_medium_med-open"
shell_to_spawn = /mob/living/circuit_drone
/obj/item/shell/server
name = "server assembly"
icon_state = "setup_stationary-open"
shell_to_spawn = /obj/structure/server
screw_delay = 10 SECONDS
/obj/item/shell/airlock
name = "circuit airlock assembly"
icon = 'icons/obj/doors/airlocks/station/public.dmi'
icon_state = "construction"
shell_to_spawn = /obj/machinery/door/airlock/shell
screw_delay = 10 SECONDS
/obj/item/shell/bci
name = "brain-computer interface assembly"
icon_state = "bci-open"
shell_to_spawn = /obj/item/organ/cyberimp/bci
/obj/item/shell/scanner_gate
name = "scanner gate assembly"
icon = 'icons/obj/machines/scangate.dmi'
icon_state = "scangate_black_open"
shell_to_spawn = /obj/structure/scanner_gate_shell