diff --git a/aurorastation.dme b/aurorastation.dme index e45665d8632..fd14b898cef 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -2592,6 +2592,7 @@ #include "code\modules\integrated_electronics\core\printer.dm" #include "code\modules\integrated_electronics\core\tools.dm" #include "code\modules\integrated_electronics\core\assemblies\clothing.dm" +#include "code\modules\integrated_electronics\core\assemblies\electronic_radio_headset.dm" #include "code\modules\integrated_electronics\core\assemblies\generic.dm" #include "code\modules\integrated_electronics\core\special_pins\boolean_pin.dm" #include "code\modules\integrated_electronics\core\special_pins\char_pin.dm" @@ -2607,15 +2608,18 @@ #include "code\modules\integrated_electronics\subtypes\built_in.dm" #include "code\modules\integrated_electronics\subtypes\converters.dm" #include "code\modules\integrated_electronics\subtypes\data_transfer.dm" +#include "code\modules\integrated_electronics\subtypes\debug.dm" #include "code\modules\integrated_electronics\subtypes\input.dm" #include "code\modules\integrated_electronics\subtypes\insert_slot.dm" #include "code\modules\integrated_electronics\subtypes\lists.dm" #include "code\modules\integrated_electronics\subtypes\logic.dm" #include "code\modules\integrated_electronics\subtypes\manipulation.dm" +#include "code\modules\integrated_electronics\subtypes\math_extended.dm" #include "code\modules\integrated_electronics\subtypes\memory.dm" #include "code\modules\integrated_electronics\subtypes\output.dm" #include "code\modules\integrated_electronics\subtypes\power.dm" #include "code\modules\integrated_electronics\subtypes\reagents.dm" +#include "code\modules\integrated_electronics\subtypes\server.dm" #include "code\modules\integrated_electronics\subtypes\smart.dm" #include "code\modules\integrated_electronics\subtypes\time.dm" #include "code\modules\integrated_electronics\subtypes\trig.dm" diff --git a/code/__DEFINES/assemblies.dm b/code/__DEFINES/assemblies.dm index 02c4e68fd5c..b8bc2e9f9b8 100644 --- a/code/__DEFINES/assemblies.dm +++ b/code/__DEFINES/assemblies.dm @@ -1,2 +1,6 @@ #define IC_COMPONENTS_BASE 25 #define IC_COMPLEXITY_BASE 75 + +#define IC_OVERFLOW_COMPONENT_TO_COMPLEXITY 2 +#define IC_OVERFLOW_COMPLEXITY_TO_COMPONENT 6 +#define IC_OVERFLOW_LIMIT 0.15 diff --git a/code/controllers/subsystems/processing/electronics.dm b/code/controllers/subsystems/processing/electronics.dm index a792dd9fa0c..c68b45d3545 100644 --- a/code/controllers/subsystems/processing/electronics.dm +++ b/code/controllers/subsystems/processing/electronics.dm @@ -1,13 +1,12 @@ -#define IC_SPAWN_DEFAULT 1 // If the circuit comes in the default circuit box and able to be printed in the IC printer. +#define IC_SPAWN_DEFAULT 1 // If the circuit comes in the default circuit box and able to be printed in the IC printer. #define IC_SPAWN_RESEARCH 2 // If the circuit design will be available in the IC printer after upgrading it. PROCESSING_SUBSYSTEM_DEF(electronics) name = "Electronics" - wait = 2 SECONDS + wait = 1 SECOND priority = SS_PRIORITY_ELECTRONICS flags = SS_KEEP_TIMING init_order = INIT_ORDER_MISC_FIRST - var/list/all_integrated_circuits = list() var/list/printer_recipe_list = list() var/list/printer_recipe_list_basic = list() @@ -20,13 +19,14 @@ PROCESSING_SUBSYSTEM_DEF(electronics) // First loop is to seperate the actual circuits from base circuits. var/list/circuits_to_use = list() for(var/obj/item/integrated_circuit/IC in all_integrated_circuits) - if((IC.spawn_flags & (IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH))) + if((IC.get_printer_spawn_flags() & (IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH))) circuits_to_use += IC // Second loop is to find all categories. for(var/obj/item/integrated_circuit/IC in circuits_to_use) if(!(IC.category_text in found_categories)) found_categories += IC.category_text + found_categories += "Assemblies" found_categories += "Tools" @@ -57,6 +57,7 @@ PROCESSING_SUBSYSTEM_DEF(electronics) new /obj/item/electronic_assembly/medium/medical, new /obj/item/electronic_assembly/medium/gun, new /obj/item/electronic_assembly/medium/radio, + new /obj/item/electronic_assembly/medium/anomaly_drill, new /obj/item/electronic_assembly/large/default, new /obj/item/electronic_assembly/large/scope, new /obj/item/electronic_assembly/large/terminal, @@ -78,7 +79,7 @@ PROCESSING_SUBSYSTEM_DEF(electronics) new /obj/item/clothing/glasses/circuitry, new /obj/item/clothing/shoes/circuitry, new /obj/item/clothing/head/circuitry, - new /obj/item/clothing/ears/circuitry, + new /obj/item/radio/headset/circuitry, new /obj/item/clothing/suit/circuitry ) @@ -94,12 +95,13 @@ PROCESSING_SUBSYSTEM_DEF(electronics) var/is_basic = TRUE if(istype(O, /obj/item/integrated_circuit)) var/obj/item/integrated_circuit/IC = O - if((IC.spawn_flags & IC_SPAWN_RESEARCH) && (!(IC.spawn_flags & IC_SPAWN_DEFAULT))) + var/printer_spawn_flags = IC.get_printer_spawn_flags() + if((printer_spawn_flags & IC_SPAWN_RESEARCH) && (!(printer_spawn_flags & IC_SPAWN_DEFAULT))) is_basic = FALSE - - printer_recipe_list_basic += list(list(path = "[O.type]", name = "[O.name]", desc = "[O.desc]", "basic" = is_basic, "category" = category)) - printer_recipe_list_upgraded += list(list(path = "[O.type]", name = "[O.name]", desc = "[O.desc]", "basic" = TRUE, "category" = category)) - + var/list/recipe = list(path = "[O.type]", name = "[O.name]", desc = "[O.desc]", "basic" = is_basic, "category" = category) + if(is_basic) + printer_recipe_list_basic += list(recipe) + printer_recipe_list_upgraded += list(recipe) return SS_INIT_SUCCESS diff --git a/code/game/objects/items/weapons/implants/implants/circuit.dm b/code/game/objects/items/weapons/implants/implants/circuit.dm index beec752227f..005903b040d 100644 --- a/code/game/objects/items/weapons/implants/implants/circuit.dm +++ b/code/game/objects/items/weapons/implants/implants/circuit.dm @@ -9,9 +9,9 @@ /obj/item/implant/integrated_circuit/isLegal() return TRUE -/obj/item/implant/integrated_circuit/Initialize() +/obj/item/implant/integrated_circuit/Initialize(mapload, printed = FALSE) . = ..() - IC = new(src) + IC = new(src, printed) IC.implant = src /obj/item/implant/integrated_circuit/Destroy() diff --git a/code/modules/clothing/clothing_accessories.dm b/code/modules/clothing/clothing_accessories.dm index af6f29d09e7..04e2bde0c52 100644 --- a/code/modules/clothing/clothing_accessories.dm +++ b/code/modules/clothing/clothing_accessories.dm @@ -9,34 +9,39 @@ return 0 /obj/item/clothing/attackby(obj/item/attacking_item, mob/user) - if(IC && (istype(attacking_item, /obj/item/integrated_circuit) || attacking_item.tool_behaviour == TOOL_WRENCH || attacking_item.tool_behaviour == TOOL_CROWBAR || \ + if(IC) + if(attacking_item.tool_behaviour == TOOL_CROWBAR) + IC.attackby(attacking_item, user) + return TRUE + + if(IC.opened && (istype(attacking_item, /obj/item/integrated_circuit) || attacking_item.tool_behaviour == TOOL_WRENCH || \ istype(attacking_item, /obj/item/integrated_electronics/wirer) || istype(attacking_item, /obj/item/integrated_electronics/debugger) || \ attacking_item.tool_behaviour == TOOL_MULTITOOL || attacking_item.tool_behaviour == TOOL_SCREWDRIVER || istype(attacking_item, /obj/item/cell/device))) - - IC.attackby(attacking_item, user) + IC.attackby(attacking_item, user) + return TRUE else if(istype(attacking_item, /obj/item/clothing/accessory)) if(!valid_accessory_slots || !valid_accessory_slots.len) - to_chat(usr, SPAN_WARNING("You cannot attach accessories of any kind to \the [src].")) - return + to_chat(user, SPAN_WARNING("You cannot attach accessories of any kind to \the [src].")) + return TRUE var/obj/item/clothing/accessory/A = attacking_item A.before_attached(src, user) if(can_attach_accessory(A)) user.drop_item() attach_accessory(user, A) - return + return TRUE else to_chat(user, SPAN_WARNING("You cannot attach more accessories of this type to [src].")) - return + return TRUE if(LAZYLEN(accessories)) for(var/obj/item/clothing/accessory/A in accessories) A.attackby(attacking_item, user) - return + return TRUE - ..() + return ..() /obj/item/clothing/attack_hand(var/mob/user) //only forward to the attached accessory if the clothing is equipped (not in a storage) diff --git a/code/modules/integrated_electronics/_defines.dm b/code/modules/integrated_electronics/_defines.dm index 60f9eb72b15..fe41b626b64 100644 --- a/code/modules/integrated_electronics/_defines.dm +++ b/code/modules/integrated_electronics/_defines.dm @@ -39,6 +39,9 @@ // Data limits. #define IC_MAX_LIST_LENGTH 200 +#define IC_DEFAULT_PHORON_COST 0.005 +#define IC_BLUEPRINT_CHUNK_LIMIT 1000 +#define IC_BLUEPRINT_BUFFER_LIMIT 500000 /obj/item/integrated_circuit name = "integrated circuit" @@ -61,6 +64,7 @@ var/power_draw_idle = 0 // How much power is drawn when doing nothing. var/spawn_flags // Used for world initializing, see the #defines above. var/category_text = "NO CATEGORY THIS IS A BUG" // To show up on circuit printer, and perhaps other places. + var/phoron_cost = IC_DEFAULT_PHORON_COST var/removable = TRUE // Determines if a circuit is removable from the assembly. var/displayed_name = "" var/allow_multitool = TRUE // Allows additional multitool functionality diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm index a181dfad3af..5cf571d5c4e 100644 --- a/code/modules/integrated_electronics/core/assemblies.dm +++ b/code/modules/integrated_electronics/core/assemblies.dm @@ -1,6 +1,11 @@ +/* + * core/assemblies.dm + * Base electronic assembly behavior: circuit storage, open/closed handling, interaction, icon updates, cloning hooks, and wiring rules. + */ + /obj/item/electronic_assembly - name = "electronic assembly" - desc = "It's a case, for building small electronics with." + name = "small circuit case" + desc = "A case for building small electronic assemblies." w_class = WEIGHT_CLASS_SMALL icon = 'icons/obj/assemblies/electronic_setups.dmi' icon_state = "setup_small" @@ -9,25 +14,32 @@ var/max_components = IC_COMPONENTS_BASE var/max_complexity = IC_COMPLEXITY_BASE + var/max_components_device = 16 + var/max_complexity_device = 42 + var/max_components_implant = 16 + var/max_complexity_implant = 42 + // Whether the assembly panel is open for direct circuit access. var/opened = 0 var/can_anchor = FALSE // If true, wrenching it will anchor it. - var/obj/item/cell/device/battery // Internal cell which most circuits need to work. + // Power cell or battery object feeding the assembly. + var/obj/item/cell/battery // Internal cell which most circuits need to work. + // User-selected detail color used for assembly and wearable overlays. var/detail_color = COLOR_ASSEMBLY_BLACK var/obj/item/card/id/access_card /obj/item/electronic_assembly/implant name = "electronic implant" icon_state = "setup_implant" - desc = "It's a case, for building very tiny electronics with." + desc = "A case for building very small electronic assemblies." w_class = WEIGHT_CLASS_TINY - max_components = IC_COMPONENTS_BASE * 3/4 - max_complexity = IC_COMPLEXITY_BASE * 3/4 + max_components = /obj/item/electronic_assembly::max_components_implant + max_complexity = /obj/item/electronic_assembly::max_complexity_implant var/obj/item/implant/integrated_circuit/implant = null /obj/item/electronic_assembly/Initialize(mapload, printed = FALSE) . = ..() if (!printed) - battery = new(src) + battery = new /obj/item/cell/device(src) START_PROCESSING(SSelectronics, src) access_card = new /obj/item/card/id(src) @@ -86,11 +98,13 @@ total_parts += part.size total_complexity = total_complexity + part.complexity var/list/HTML = list() + var/effective_component_limit = get_effective_component_limit(total_complexity) + var/effective_complexity_limit = get_effective_complexity_limit(total_parts) HTML += "
Refresh | " HTML += "Rename
" - HTML += "[total_parts]/[max_components] ([round((total_parts / max_components) * 100, 0.1)]%) space taken up in the assembly.
" - HTML += "[total_complexity]/[max_complexity] ([round((total_complexity / max_complexity) * 100, 0.1)]%) maximum complexity.
" + HTML += "[total_parts]/[max_components] space taken up in the assembly[effective_component_limit > max_components ? " ([effective_component_limit] with headroom)" : ""].
" + HTML += "[total_complexity]/[max_complexity] maximum complexity[effective_complexity_limit > max_complexity ? " ([effective_complexity_limit] with headroom)" : ""].
" if(battery) HTML += "[round(battery.charge, 0.1)]/[battery.maxcharge] ([round(battery.percent(), 0.1)]%) cell charge. Remove" else @@ -150,7 +164,7 @@ /obj/item/electronic_assembly/verb/rename() set name = "Rename Circuit" set category = "Object" - set desc = "Rename your circuit, useful to stay organized." + set desc = "Renames the circuit to make assemblies easier to organize." set src in usr var/mob/M = usr @@ -186,7 +200,7 @@ /obj/item/electronic_assembly/feedback_hints(mob/user, distance, is_adjacent) . = ..() - if(opened && is_adjacent) + if(is_adjacent && opened) for(var/obj/item/integrated_circuit/IC in contents) . += SPAN_NOTICE("It contains \a [IC].") @@ -200,6 +214,16 @@ for(var/obj/item/integrated_circuit/part in contents) . += part.size +/obj/item/electronic_assembly/proc/get_effective_complexity_limit(total_part_size) + var/remaining_size = max(0, max_components - total_part_size) + var/overflow_limit = round(max_complexity * IC_OVERFLOW_LIMIT) + return max_complexity + min(remaining_size * IC_OVERFLOW_COMPONENT_TO_COMPLEXITY, overflow_limit) + +/obj/item/electronic_assembly/proc/get_effective_component_limit(total_complexity) + var/remaining_complexity = max(0, max_complexity - total_complexity) + var/overflow_limit = round(max_components * IC_OVERFLOW_LIMIT) + return max_components + min(round(remaining_complexity / IC_OVERFLOW_COMPLEXITY_TO_COMPONENT), overflow_limit) + // Returns true if the circuit made it inside. /obj/item/electronic_assembly/proc/add_circuit(obj/item/integrated_circuit/IC, mob/user) if(!opened) @@ -212,11 +236,13 @@ var/total_part_size = get_part_size() var/total_complexity = get_part_complexity() + var/new_part_size = total_part_size + IC.size + var/new_complexity = total_complexity + IC.complexity - if((total_part_size + IC.size) > max_components) + if(new_part_size > get_effective_component_limit(new_complexity)) to_chat(user, SPAN_WARNING("You can't seem to add the '[IC.name]', as there's insufficient space.")) return FALSE - if((total_complexity + IC.complexity) > max_complexity) + if(new_complexity > get_effective_complexity_limit(new_part_size)) to_chat(user, SPAN_WARNING("You can't seem to add the '[IC.name]', since this setup's too complicated for the case.")) return FALSE @@ -273,7 +299,7 @@ return TRUE - else if(istype(attacking_item, /obj/item/cell/device)) + else if(istype(attacking_item, /obj/item/cell)) if(!opened) to_chat(user, SPAN_WARNING("\The [src] isn't open, so you can't put anything inside. Try using a crowbar.")) for(var/obj/item/integrated_circuit/input/S in contents) @@ -286,7 +312,7 @@ S.attackby_react(attacking_item,user,user.a_intent) return FALSE - var/obj/item/cell/device/cell = attacking_item + var/obj/item/cell/cell = attacking_item user.drop_from_inventory(cell,src) battery = cell playsound(get_turf(src), 'sound/items/Deconstruct.ogg', 50, 1) @@ -313,6 +339,7 @@ return if(opened) interact(user) + return var/list/input_selection = list() var/list/available_inputs = list() diff --git a/code/modules/integrated_electronics/core/assemblies/clothing.dm b/code/modules/integrated_electronics/core/assemblies/clothing.dm index 04b916da665..c9046c3c1a7 100644 --- a/code/modules/integrated_electronics/core/assemblies/clothing.dm +++ b/code/modules/integrated_electronics/core/assemblies/clothing.dm @@ -1,15 +1,28 @@ +/* + * core/assemblies/clothing.dm + * Wearable electronic assemblies that can be embedded into clothing-style items and driven through item actions. + */ + // The base subtype for assemblies that can be worn. Certain pieces will have more or less capabilities // E.g. Glasses have less room than something worn over the chest. // Note that the electronic assembly is INSIDE the object that actually gets worn, in a similar way to implants. +/obj/item/electronic_assembly + var/max_components_clothing = 26 + var/max_complexity_clothing = 68 + /obj/item/electronic_assembly/clothing name = "electronic clothing" var/clothing_icon_state = "circuitry" // Needs to match the clothing's base icon_state. - desc = "It's a case, for building machines attached to clothing." + desc = "A clothing-mounted case for integrated electronics." w_class = WEIGHT_CLASS_SMALL - max_components = IC_COMPONENTS_BASE - max_complexity = IC_COMPLEXITY_BASE + var/max_components_clothing_small = 14 + var/max_complexity_clothing_small = 36 + var/max_components_clothing_large = 42 + var/max_complexity_clothing_large = 105 + max_components = /obj/item/electronic_assembly::max_components_clothing + max_complexity = /obj/item/electronic_assembly::max_complexity_clothing var/obj/item/clothing/clothing = null /obj/item/electronic_assembly/clothing/ui_host() @@ -22,23 +35,29 @@ return clothing /obj/item/electronic_assembly/clothing/update_icon() - clothing.icon_state = "[initial(clothing.icon_state)][opened ? "-open" : ""]" + if(!clothing) + return + + clothing.icon_state = opened ? "[initial(clothing.icon_state)]-open" : initial(clothing.icon_state) clothing.ClearOverlays() - var/image/detail_overlay = image('icons/obj/assemblies/wearable_electronic_setups.dmi', "[initial(clothing.icon_state)][opened ? "-open" : ""]-color") + + var/image/detail_overlay = image('icons/obj/assemblies/wearable_electronic_setups.dmi', "[clothing.icon_state]-color") detail_overlay.color = detail_color clothing.AddOverlays(detail_overlay) - clothing.update_clothing_icon() + + if(hascall(clothing, "update_clothing_icon")) + call(clothing, "update_clothing_icon")() // This is 'small' relative to the size of regular clothing assemblies. /obj/item/electronic_assembly/clothing/small - max_components = IC_COMPONENTS_BASE / 2 - max_complexity = IC_COMPLEXITY_BASE / 2 + max_components = /obj/item/electronic_assembly/clothing::max_components_clothing_small + max_complexity = /obj/item/electronic_assembly/clothing::max_complexity_clothing_small w_class = WEIGHT_CLASS_TINY // Ditto. /obj/item/electronic_assembly/clothing/large - max_components = IC_COMPONENTS_BASE * 2 - max_complexity = IC_COMPLEXITY_BASE * 2 + max_components = /obj/item/electronic_assembly/clothing::max_components_clothing_large + max_complexity = /obj/item/electronic_assembly/clothing::max_complexity_clothing_large w_class = WEIGHT_CLASS_NORMAL /obj/item/electronic_assembly/clothing/rename() @@ -63,9 +82,9 @@ ..() // Does most of the repeatative setup. -/obj/item/clothing/proc/setup_integrated_circuit(new_type) +/obj/item/clothing/proc/setup_integrated_circuit(new_type, printed = FALSE) // Set up the internal circuit holder. - IC = new new_type(src) + IC = new new_type(src, printed) IC.clothing = src IC.name = name @@ -83,22 +102,22 @@ // Jumpsuit. /obj/item/clothing/under/circuitry name = "electronic jumpsuit" - desc = "It's a wearable case for electronics. This one is a black jumpsuit with wiring weaved into the fabric." + desc = "A wearable case for integrated electronics. This one is a black jumpsuit with wiring weaved into the fabric." icon = 'icons/obj/assemblies/wearable_electronic_setups.dmi' contained_sprite = TRUE icon_state = "jumpsuit" item_state = "jumpsuit" worn_state = "jumpsuit" -/obj/item/clothing/under/circuitry/Initialize() - setup_integrated_circuit(/obj/item/electronic_assembly/clothing) +/obj/item/clothing/under/circuitry/Initialize(mapload, printed = FALSE) + setup_integrated_circuit(/obj/item/electronic_assembly/clothing, printed) return ..() /obj/item/clothing/under/circuitry/build_additional_parts(mob/living/carbon/human/H, mob_icon, slot) var/static/list/valid_slots if(!valid_slots) valid_slots = list(slot_w_uniform_str) - if(IC.detail_color && (slot in valid_slots)) + if(IC?.detail_color && (slot in valid_slots)) var/image/electronic_overlay = overlay_image(icon, "[item_state][slot_str_to_contained_flag(slot)]-color", IC.detail_color, RESET_COLOR) return electronic_overlay return ..() @@ -106,22 +125,22 @@ // Gloves. /obj/item/clothing/gloves/circuitry name = "electronic gloves" - desc = "It's a wearable case for electronics. This one is a pair of black gloves, with wires woven into them. A small \ + desc = "A wearable case for integrated electronics. This one is a pair of black gloves, with wires woven into them. A small \ device with a screen is attached to the left glove." icon = 'icons/obj/assemblies/wearable_electronic_setups.dmi' contained_sprite = TRUE icon_state = "gloves" item_state = "gloves" -/obj/item/clothing/gloves/circuitry/Initialize() - setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small) +/obj/item/clothing/gloves/circuitry/Initialize(mapload, printed = FALSE) + setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small, printed) return ..() /obj/item/clothing/gloves/circuitry/build_additional_parts(mob/living/carbon/human/H, mob_icon, slot) var/static/list/valid_slots if(!valid_slots) valid_slots = list(slot_gloves_str) - if(IC.detail_color && (slot in valid_slots)) + if(IC?.detail_color && (slot in valid_slots)) var/image/electronic_overlay = overlay_image(icon, "[item_state][slot_str_to_contained_flag(slot)]-color", IC.detail_color, RESET_COLOR) return electronic_overlay return ..() @@ -139,22 +158,22 @@ // Glasses. /obj/item/clothing/glasses/circuitry name = "electronic goggles" - desc = "It's a wearable case for electronics. This one is a pair of goggles, with wiring sticking out. \ + desc = "A wearable case for integrated electronics. This one is a pair of goggles, with wiring sticking out. \ Could this augment your vision?" // Sadly it won't, or at least not yet. icon = 'icons/obj/assemblies/wearable_electronic_setups.dmi' contained_sprite = TRUE icon_state = "goggles" item_state = "goggles" -/obj/item/clothing/glasses/circuitry/Initialize() - setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small) +/obj/item/clothing/glasses/circuitry/Initialize(mapload, printed = FALSE) + setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small, printed) return ..() /obj/item/clothing/glasses/circuitry/build_additional_parts(mob/living/carbon/human/H, mob_icon, slot) var/static/list/valid_slots if(!valid_slots) valid_slots = list(slot_glasses_str, slot_l_hand_str, slot_r_hand_str) - if(IC.detail_color && (slot in valid_slots)) + if(IC?.detail_color && (slot in valid_slots)) var/image/electronic_overlay = overlay_image(icon, "[item_state][slot_str_to_contained_flag(slot)]-color", IC.detail_color, RESET_COLOR) return electronic_overlay return ..() @@ -177,22 +196,22 @@ // Shoes /obj/item/clothing/shoes/circuitry name = "electronic boots" - desc = "It's a wearable case for electronics. This one is a pair of boots, with wires attached to a small \ + desc = "A wearable case for integrated electronics. This one is a pair of boots, with wires attached to a small \ cover." icon = 'icons/obj/assemblies/wearable_electronic_setups.dmi' contained_sprite = TRUE icon_state = "shoes" item_state = "shoes" -/obj/item/clothing/shoes/circuitry/Initialize() - setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small) +/obj/item/clothing/shoes/circuitry/Initialize(mapload, printed = FALSE) + setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small, printed) return ..() /obj/item/clothing/shoes/circuitry/build_additional_parts(mob/living/carbon/human/H, mob_icon, slot) var/static/list/valid_slots if(!valid_slots) valid_slots = list(slot_shoes_str) - if(IC.detail_color && (slot in valid_slots)) + if(IC?.detail_color && (slot in valid_slots)) var/image/electronic_overlay = overlay_image(icon, "[item_state][slot_str_to_contained_flag(slot)]-color", IC.detail_color, RESET_COLOR) return electronic_overlay return ..() @@ -200,44 +219,44 @@ // Head /obj/item/clothing/head/circuitry name = "electronic headwear" - desc = "It's a wearable case for electronics. This one appears to be a very technical-looking piece that \ + desc = "A wearable case for integrated electronics. This one appears to be a very technical-looking piece that \ goes around the collar, with a heads-up-display attached on the right." icon = 'icons/obj/assemblies/wearable_electronic_setups.dmi' contained_sprite = TRUE icon_state = "head" item_state = "head" -/obj/item/clothing/head/circuitry/Initialize() - setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small) +/obj/item/clothing/head/circuitry/Initialize(mapload, printed = FALSE) + setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small, printed) return ..() /obj/item/clothing/head/circuitry/build_additional_parts(mob/living/carbon/human/H, mob_icon, slot) var/static/list/valid_slots if(!valid_slots) valid_slots = list(slot_head_str) - if(IC.detail_color && (slot in valid_slots)) + if(IC?.detail_color && (slot in valid_slots)) var/image/electronic_overlay = overlay_image(icon, "[item_state][slot_str_to_contained_flag(slot)]-color", IC.detail_color, RESET_COLOR) return electronic_overlay return ..() -// Ear +// Ears /obj/item/clothing/ears/circuitry name = "electronic earwear" - desc = "It's a wearable case for electronics. This one appears to be a technical-looking headset." + desc = "A wearable case for integrated electronics. This one appears to be a technical-looking headset." icon = 'icons/obj/assemblies/wearable_electronic_setups.dmi' contained_sprite = TRUE icon_state = "headset" item_state = "headset" -/obj/item/clothing/ears/circuitry/Initialize() - setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small) +/obj/item/clothing/ears/circuitry/Initialize(mapload, printed = FALSE) + setup_integrated_circuit(/obj/item/electronic_assembly/clothing/small, printed) return ..() /obj/item/clothing/ears/circuitry/build_additional_parts(mob/living/carbon/human/H, mob_icon, slot) var/static/list/valid_slots if(!valid_slots) valid_slots = list(slot_l_ear_str, slot_r_ear_str) - if(IC.detail_color && (slot in valid_slots)) + if(IC?.detail_color && (slot in valid_slots)) var/image/electronic_overlay = overlay_image(icon, "[item_state][slot_str_to_contained_flag(slot)]-color", IC.detail_color, RESET_COLOR) return electronic_overlay return ..() @@ -245,22 +264,22 @@ // Exo-slot /obj/item/clothing/suit/circuitry name = "electronic chestpiece" - desc = "It's a wearable case for electronics. This one appears to be a very technical-looking vest, that \ + desc = "A wearable case for integrated electronics. This one appears to be a very technical-looking vest, that \ almost looks professionally made, however the wiring popping out betrays that idea." icon = 'icons/obj/assemblies/wearable_electronic_setups.dmi' contained_sprite = TRUE icon_state = "chest" item_state = "chest" -/obj/item/clothing/suit/circuitry/Initialize() - setup_integrated_circuit(/obj/item/electronic_assembly/clothing/large) +/obj/item/clothing/suit/circuitry/Initialize(mapload, printed = FALSE) + setup_integrated_circuit(/obj/item/electronic_assembly/clothing/large, printed) return ..() /obj/item/clothing/suit/circuitry/build_additional_parts(mob/living/carbon/human/H, mob_icon, slot) var/static/list/valid_slots if(!valid_slots) valid_slots = list(slot_wear_suit_str) - if(IC.detail_color && (slot in valid_slots)) + if(IC?.detail_color && (slot in valid_slots)) var/image/electronic_overlay = overlay_image(icon, "[item_state][slot_str_to_contained_flag(slot)]-color", IC.detail_color, RESET_COLOR) return electronic_overlay return ..() diff --git a/code/modules/integrated_electronics/core/assemblies/electronic_radio_headset.dm b/code/modules/integrated_electronics/core/assemblies/electronic_radio_headset.dm new file mode 100644 index 00000000000..fa9416efe6b --- /dev/null +++ b/code/modules/integrated_electronics/core/assemblies/electronic_radio_headset.dm @@ -0,0 +1,261 @@ +/obj/item/radio/headset/circuitry + name = "electronic radio headset" + desc = "A radio headset modified to accept integrated circuitry. Takes encryption keys." + icon = 'icons/obj/assemblies/wearable_electronic_setups.dmi' + contained_sprite = TRUE + icon_state = "headset" + item_state = "headset" + slot_flags = SLOT_EARS + + ks1type = null + ks2type = null + + // Internal assembly that stores the actual circuits installed inside the host item. + var/obj/item/electronic_assembly/clothing/small/circuit_assembly = null + // Built-in action circuit used to expose an activation button to the wearer/user. + var/obj/item/integrated_circuit/built_in/action_button/circuit_action = null + + +/obj/item/radio/headset/circuitry/Initialize(mapload, printed = FALSE) + . = ..() + setup_headset_integrated_circuit() + refresh_headset_sprite() + refresh_radio_state() + + +/obj/item/radio/headset/circuitry/Destroy() + QDEL_NULL(circuit_assembly) + circuit_action = null + return ..() + + +/obj/item/radio/headset/circuitry/proc/setup_headset_integrated_circuit() + if(circuit_assembly) + circuit_assembly.forceMove(src) + circuit_assembly.clothing = src + circuit_assembly.name = name + else + circuit_assembly = new /obj/item/electronic_assembly/clothing/small(src, TRUE) + circuit_assembly.clothing = src + circuit_assembly.name = name + + QDEL_NULL(circuit_assembly.battery) + + for(var/obj/item/integrated_circuit/C in circuit_assembly.contents) + C.assembly = circuit_assembly + + circuit_action = locate(/obj/item/integrated_circuit/built_in/action_button) in circuit_assembly.contents + + if(!circuit_action) + circuit_action = new(circuit_assembly) + circuit_assembly.force_add_circuit(circuit_action) + + default_action_type = /datum/action/item_action/integrated_circuit + action_button_name = "Activate [capitalize_first_letters(name)]" + + +/obj/item/radio/headset/circuitry/proc/get_cloneable_assembly() + return circuit_assembly + + +/obj/item/radio/headset/circuitry/proc/get_clone_host_type() + return type + +/obj/item/radio/headset/circuitry/feedback_hints(mob/user, distance, is_adjacent) + . = ..() + + if(distance <= 1 && !circuit_assembly?.opened) + return + + var/obj/item/electronic_assembly/E = get_cloneable_assembly() + if(E) + for(var/obj/item/integrated_circuit/IC in E.contents) + . += SPAN_NOTICE("It contains \a [IC].") + +/obj/item/radio/headset/circuitry/proc/clone_with_integrated_assembly(atom/location, obj/item/integrated_circuit_printer/printer, obj/item/electronic_assembly/source_assembly) + if(!location || !printer || !source_assembly) + return null + + var/obj/item/radio/headset/circuitry/new_headset = new type(location) + + if(!new_headset || !new_headset.circuit_assembly) + if(new_headset) + qdel(new_headset) + return null + + var/obj/item/electronic_assembly/clothing/small/internal_assembly = new_headset.circuit_assembly + + if(!printer.copy_assembly_into_existing(source_assembly, internal_assembly)) + qdel(new_headset) + return null + + internal_assembly.forceMove(new_headset) + internal_assembly.clothing = new_headset + internal_assembly.name = source_assembly.name + internal_assembly.detail_color = source_assembly.detail_color + internal_assembly.opened = FALSE + internal_assembly.battery = null + + for(var/obj/item/integrated_circuit/C in internal_assembly.contents) + C.assembly = internal_assembly + + new_headset.name = source_assembly.name + new_headset.circuit_assembly = internal_assembly + new_headset.circuit_action = locate(/obj/item/integrated_circuit/built_in/action_button) in internal_assembly.contents + + if(!new_headset.circuit_action) + new_headset.circuit_action = new(internal_assembly) + internal_assembly.force_add_circuit(new_headset.circuit_action) + + new_headset.default_action_type = /datum/action/item_action/integrated_circuit + new_headset.action_button_name = "Activate [capitalize_first_letters(new_headset.name)]" + + new_headset.refresh_headset_sprite() + new_headset.refresh_radio_state() + + return new_headset + + +/obj/item/radio/headset/circuitry/proc/refresh_radio_state() + on = TRUE + set_frequency(PUB_FREQ) + set_listening(TRUE) + recalculateChannels(TRUE) + + if(ismob(loc)) + possibly_deactivate_in_loc() + + +/obj/item/radio/headset/circuitry/proc/refresh_headset_sprite() + icon = 'icons/obj/assemblies/wearable_electronic_setups.dmi' + contained_sprite = TRUE + icon_state = circuit_assembly?.opened ? "headset-open" : "headset" + item_state = "headset" + + ClearOverlays() + + if(circuit_assembly?.detail_color) + var/image/detail_overlay = image('icons/obj/assemblies/wearable_electronic_setups.dmi', "[icon_state]-color") + detail_overlay.color = circuit_assembly.detail_color + AddOverlays(detail_overlay) + + if(hascall(src, "update_clothing_icon")) + call(src, "update_clothing_icon")() + +/obj/item/radio/headset/circuitry/attack_self(mob/user) + return ..() + + +/obj/item/radio/headset/circuitry/attackby(obj/item/attacking_item, mob/user) + if(istype(attacking_item, /obj/item/integrated_circuit_printer)) + var/obj/item/integrated_circuit_printer/P = attacking_item + if(P.scan_cloneable_item(src, user)) + return TRUE + + if(attacking_item.tool_behaviour == TOOL_CROWBAR && circuit_assembly) + circuit_assembly.attackby(attacking_item, user) + refresh_headset_sprite() + refresh_radio_state() + return TRUE + + if(istype(attacking_item, /obj/item/encryptionkey)) + . = ..() + refresh_radio_state() + return + + if(circuit_assembly?.opened) + circuit_assembly.attackby(attacking_item, user) + refresh_headset_sprite() + return TRUE + + return ..() + + +/obj/item/radio/headset/circuitry/AltClick(mob/user) + if(!circuit_assembly) + return ..() + + if(!circuit_assembly.opened) + to_chat(user, SPAN_WARNING("The circuit panel is closed.")) + return + + return circuit_assembly.attack_self(user) + + +/obj/item/radio/headset/circuitry/verb/access_integrated_circuit() + set category = "Object.Equipped" + set name = "Access Integrated Circuit" + set src in usr + + if(!circuit_assembly) + return + + if(!circuit_assembly.opened) + to_chat(usr, SPAN_WARNING("The circuit panel is closed.")) + return + + circuit_assembly.attack_self(usr) + + +/obj/item/radio/headset/circuitry/equipped(mob/user, slot) + . = ..() + + if(slot == slot_l_ear || slot == slot_r_ear || slot == slot_l_ear_str || slot == slot_r_ear_str) + refresh_radio_state() + refresh_headset_sprite() + + +/obj/item/radio/headset/circuitry/dropped(mob/user) + . = ..() + set_listening(FALSE, actual_setting = FALSE) + + +/obj/item/radio/headset/circuitry/Moved(atom/old_loc, forced) + . = ..() + possibly_deactivate_in_loc() + + +/obj/item/radio/headset/circuitry/build_additional_parts(mob/living/carbon/human/H, mob_icon, slot) + var/static/list/valid_slots + + if(!valid_slots) + valid_slots = list(slot_l_ear_str, slot_r_ear_str) + + if(circuit_assembly?.detail_color && (slot in valid_slots)) + var/image/electronic_overlay = overlay_image(icon, "[item_state][slot_str_to_contained_flag(slot)]-color", circuit_assembly.detail_color, RESET_COLOR) + return electronic_overlay + + return ..() + + +/obj/item/radio/headset/circuitry/proc/get_wearer() + if(!ishuman(loc)) + return null + + var/mob/living/carbon/human/H = loc + + if(H.l_ear == src) + return H + + if(H.r_ear == src) + return H + + return null + + +/obj/item/radio/headset/circuitry/proc/private_output(message) + var/mob/living/carbon/human/H = get_wearer() + + if(!H) + return + + to_chat(H, SPAN_NOTICE("[message]")) + + +/obj/item/radio/headset/circuitry/proc/private_tts_output(message) + var/mob/living/carbon/human/H = get_wearer() + + if(!H) + return + + to_chat(H, SPAN_NOTICE("\The [src] states, \"[message]\"")) diff --git a/code/modules/integrated_electronics/core/assemblies/generic.dm b/code/modules/integrated_electronics/core/assemblies/generic.dm index 45b52f25a7e..3ac9921c4e8 100644 --- a/code/modules/integrated_electronics/core/assemblies/generic.dm +++ b/code/modules/integrated_electronics/core/assemblies/generic.dm @@ -1,213 +1,311 @@ +/* + * core/assemblies/generic.dm + * Generic handheld, large, wall-mounted, and specialty electronic assembly variants. + */ + // Generic subtypes without a lot of special code. +// Anchored pickup protection. +// This prevents anchored electronic assemblies from being picked up like normal items. +// Anchored assemblies still pass ordinary hand interaction through attack_self(), so closed +// assemblies expose their external inputs and opened assemblies expose the circuit UI. + +/obj/item/electronic_assembly + var/supports_builtin_powernet = FALSE + var/supports_builtin_locomotion = FALSE + var/max_components_tiny_assembly = 12 + var/max_complexity_tiny_assembly = 30 + var/max_components_medium_assembly = 55 + var/max_complexity_medium_assembly = 140 + var/max_components_large_assembly = 110 + var/max_complexity_large_assembly = 240 + var/max_components_drone = 38 + var/max_complexity_drone = 95 + var/max_components_wallmount = 55 + var/max_complexity_wallmount = 140 + +/obj/item/electronic_assembly/attack_hand(mob/user) + if(anchored) + if(!check_interactivity(user)) + return TRUE + + attack_self(user) + return TRUE + + return ..() + +/obj/item/electronic_assembly/mob_can_equip(mob/user, slot, disable_warning = FALSE, bypass_blocked_check = FALSE, is_overlay_check = FALSE) + if(anchored) + if(!disable_warning) + to_chat(user, SPAN_WARNING("\The [src] is anchored in place.")) + return FALSE + + return ..(user, slot, disable_warning, bypass_blocked_check, is_overlay_check) + // Small assemblies. /obj/item/electronic_assembly/default - name = "type-a electronic assembly" + name = "basic small circuit case" /obj/item/electronic_assembly/calc - name = "type-b electronic assembly" + name = "calculator small circuit case" icon_state = "setup_small_calc" - desc = "It's a case, for building small electronics with. This one resembles a pocket calculator." + desc = "A case for building small electronic assemblies. This one resembles a pocket calculator." /obj/item/electronic_assembly/clam - name = "type-c electronic assembly" + name = "clamshell small circuit case" icon_state = "setup_small_clam" - desc = "It's a case, for building small electronics with. This one has a clamshell design." + desc = "A case for building small electronic assemblies. This one has a clamshell design." /obj/item/electronic_assembly/simple - name = "type-d electronic assembly" + name = "compact small circuit case" icon_state = "setup_small_simple" - desc = "It's a case, for building small electronics with. This one has a simple design." + desc = "A case for building small electronic assemblies. This one has a simple design." /obj/item/electronic_assembly/hook - name = "type-e electronic assembly" + name = "belt-clip small circuit case" icon_state = "setup_small_hook" - desc = "It's a case, for building small electronics with. This one looks like it has a belt clip, but it's purely decorative." + desc = "A case for building small electronic assemblies. This one looks like it has a belt clip, but it's purely decorative." /obj/item/electronic_assembly/pda - name = "type-f electronic assembly" + name = "PDA-style small circuit case" icon_state = "setup_small_pda" - desc = "It's a case, for building small electronics with. This one resembles a PDA." + desc = "A case for building small electronic assemblies. This one resembles a PDA." + // Tiny assemblies. /obj/item/electronic_assembly/tiny - name = "electronic device" + name = "tiny circuit case" icon_state = "setup_device" - desc = "It's a case, for building tiny-sized electronics with." + desc = "A case for building tiny electronic assemblies." w_class = WEIGHT_CLASS_TINY - max_components = IC_COMPONENTS_BASE / 2 - max_complexity = IC_COMPLEXITY_BASE / 2 + max_components = /obj/item/electronic_assembly::max_components_tiny_assembly + max_complexity = /obj/item/electronic_assembly::max_complexity_tiny_assembly /obj/item/electronic_assembly/tiny/default - name = "type-a electronic device" + name = "basic tiny circuit case" /obj/item/electronic_assembly/tiny/cylinder - name = "type-b electronic device" + name = "cylindrical tiny circuit case" icon_state = "setup_device_cylinder" - desc = "It's a case, for building tiny-sized electronics with. This one has a cylindrical design." + desc = "A case for building tiny electronic assemblies. This one has a cylindrical design." /obj/item/electronic_assembly/tiny/scanner - name = "type-c electronic device" + name = "scanner tiny circuit case" icon_state = "setup_device_scanner" - desc = "It's a case, for building tiny-sized electronics with. This one has a scanner-like design." + desc = "A case for building tiny electronic assemblies. This one has a scanner-like design." /obj/item/electronic_assembly/tiny/hook - name = "type-d electronic device" + name = "belt-clip tiny circuit case" icon_state = "setup_device_hook" - desc = "It's a case, for building tiny-sized electronics with. This one looks like it has a belt clip, but it's purely decorative." + desc = "A case for building tiny electronic assemblies. This one looks like it has a belt clip, but it's purely decorative." /obj/item/electronic_assembly/tiny/box - name = "type-e electronic device" + name = "boxy tiny circuit case" icon_state = "setup_device_box" - desc = "It's a case, for building tiny-sized electronics with. This one has a boxy design." + desc = "A case for building tiny electronic assemblies. This one has a boxy design." + // Medium assemblies. /obj/item/electronic_assembly/medium - name = "electronic mechanism" + name = "medium circuit case" icon_state = "setup_medium" - desc = "It's a case, for building medium-sized electronics with." + desc = "A case for building medium electronic assemblies." w_class = WEIGHT_CLASS_NORMAL - max_components = IC_COMPONENTS_BASE * 2 - max_complexity = IC_COMPLEXITY_BASE * 2 + var/max_components_anomaly_drill = 30 + var/max_complexity_anomaly_drill = 80 + max_components = /obj/item/electronic_assembly::max_components_medium_assembly + max_complexity = /obj/item/electronic_assembly::max_complexity_medium_assembly /obj/item/electronic_assembly/medium/default - name = "type-a electronic mechanism" + name = "basic medium circuit case" /obj/item/electronic_assembly/medium/box - name = "type-b electronic mechanism" + name = "boxy medium circuit case" icon_state = "setup_medium_box" - desc = "It's a case, for building medium-sized electronics with. This one has a boxy design." + desc = "A case for building medium electronic assemblies. This one has a boxy design." /obj/item/electronic_assembly/medium/clam - name = "type-c electronic mechanism" + name = "clamshell medium circuit case" icon_state = "setup_medium_clam" - desc = "It's a case, for building medium-sized electronics with. This one has a clamshell design." + desc = "A case for building medium electronic assemblies. This one has a clamshell design." /obj/item/electronic_assembly/medium/medical - name = "type-d electronic mechanism" + name = "medical medium circuit case" icon_state = "setup_medium_med" - desc = "It's a case, for building medium-sized electronics with. This one resembles some type of medical apparatus." + desc = "A case for building medium electronic assemblies. This one resembles some type of medical apparatus." /obj/item/electronic_assembly/medium/gun - name = "type-e electronic mechanism" + name = "tool-shaped medium circuit case" icon_state = "setup_medium_gun" item_state = "circuitgun" - desc = "It's a case, for building medium-sized electronics with. This one resembles a gun, or some type of tool, \ + desc = "A case for building medium electronic assemblies. This one resembles a gun, or some type of tool, \ if you're feeling optimistic." /obj/item/electronic_assembly/medium/radio - name = "type-f electronic mechanism" + name = "radio medium circuit case" icon_state = "setup_medium_radio" - desc = "It's a case, for building medium-sized electronics with. This one resembles an old radio." + desc = "A case for building medium electronic assemblies. This one resembles an old radio." + +/obj/item/electronic_assembly/medium/anomaly_drill + name = "anomaly drill assembly" + desc = "A prebuilt electronic assembly designed to drill toward anomaly sensor coordinates." + icon_state = "setup_medium_drill" + w_class = WEIGHT_CLASS_NORMAL + max_components = /obj/item/electronic_assembly/medium::max_components_anomaly_drill + max_complexity = /obj/item/electronic_assembly/medium::max_complexity_anomaly_drill + can_anchor = FALSE + +/obj/item/electronic_assembly/medium/anomaly_drill/Initialize(mapload, printed = FALSE) + . = ..() + + var/obj/item/integrated_circuit/manipulation/xenoarch_precision_excavator/drill_controller = new(src) + drill_controller.removable = FALSE + drill_controller.assembly = src + force_add_circuit(drill_controller) + // Large assemblies. +// These get a built-in, non-removable power network interface. /obj/item/electronic_assembly/large - name = "electronic machine" + name = "large circuit machine" icon_state = "setup_large" - desc = "It's a case, for building large electronics with." + desc = "A case for building large electronic assemblies." w_class = WEIGHT_CLASS_BULKY - max_components = IC_COMPONENTS_BASE * 4 - max_complexity = IC_COMPLEXITY_BASE * 4 + max_components = /obj/item/electronic_assembly::max_components_large_assembly + max_complexity = /obj/item/electronic_assembly::max_complexity_large_assembly can_anchor = TRUE + supports_builtin_powernet = TRUE + +/obj/item/electronic_assembly/large/Initialize(mapload, printed = FALSE) + . = ..() + add_builtin_powernet() /obj/item/electronic_assembly/large/default - name = "type-a electronic machine" + name = "basic large circuit machine" /obj/item/electronic_assembly/large/scope - name = "type-b electronic machine" + name = "oscilloscope large circuit machine" icon_state = "setup_large_scope" - desc = "It's a case, for building large electronics with. This one resembles an oscilloscope." + desc = "A case for building large electronic assemblies. This one resembles an oscilloscope." /obj/item/electronic_assembly/large/terminal - name = "type-c electronic machine" + name = "terminal large circuit machine" icon_state = "setup_large_terminal" - desc = "It's a case, for building large electronics with. This one resembles a computer terminal." + desc = "A case for building large electronic assemblies. This one resembles a computer terminal." /obj/item/electronic_assembly/large/arm - name = "type-d electronic machine" + name = "robotic arm large circuit machine" icon_state = "setup_large_arm" - desc = "It's a case, for building large electronics with. This one resembles a robotic arm." + desc = "A case for building large electronic assemblies. This one resembles a robotic arm." /obj/item/electronic_assembly/large/tall - name = "type-e electronic machine" + name = "tall large circuit machine" icon_state = "setup_large_tall" - desc = "It's a case, for building large electronics with. This one has a tall design." + desc = "A case for building large electronic assemblies. This one has a tall design." /obj/item/electronic_assembly/large/industrial - name = "type-f electronic machine" + name = "industrial large circuit machine" icon_state = "setup_large_industrial" - desc = "It's a case, for building large electronics with. This one resembles some kind of industrial machinery." + desc = "A case for building large electronic assemblies. This one resembles some kind of industrial machinery." + // Drone assemblies, which can move with the locomotion circuit. /obj/item/electronic_assembly/drone - name = "electronic drone" + name = "circuit drone" icon_state = "setup_drone" - desc = "It's a case, for building mobile electronics with." + desc = "A mobile case for building electronic assemblies." w_class = WEIGHT_CLASS_NORMAL - max_components = IC_COMPONENTS_BASE * 1.5 - max_complexity = IC_COMPLEXITY_BASE * 1.5 + max_components = /obj/item/electronic_assembly::max_components_drone + max_complexity = /obj/item/electronic_assembly::max_complexity_drone can_anchor = FALSE + supports_builtin_locomotion = TRUE + +/obj/item/electronic_assembly/drone/Initialize(mapload, printed = FALSE) + . = ..() + add_builtin_locomotion() /obj/item/electronic_assembly/drone/can_move() return TRUE /obj/item/electronic_assembly/drone/default - name = "type-a electronic drone" + name = "basic circuit drone" /obj/item/electronic_assembly/drone/arms - name = "type-b electronic drone" + name = "armed circuit drone" icon_state = "setup_drone_arms" - desc = "It's a case, for building mobile electronics with. This one is armed and dangerous." + desc = "A mobile case for building weapon-capable electronic assemblies." /obj/item/electronic_assembly/drone/medbot - name = "type-d electronic drone" + name = "medical circuit drone" icon_state = "setup_drone_medbot" - desc = "It's a case, for building mobile electronics with. This one resembles a Medibot." + desc = "A mobile case for building electronic assemblies. This one resembles a Medibot." /obj/item/electronic_assembly/drone/genbot - name = "type-e electronic drone" + name = "utility circuit drone" icon_state = "setup_drone_genbot" - desc = "It's a case, for building mobile electronics with. This one has a generic bot design." + desc = "A mobile case for building electronic assemblies. This one has a generic bot design." /obj/item/electronic_assembly/drone/android - name = "type-f electronic drone" + name = "android circuit drone" icon_state = "setup_drone_android" - desc = "It's a case, for building mobile electronics with. This one has a hominoid design." + desc = "A mobile case for building electronic assemblies. This one has a hominoid design." + // Wall mounted assemblies. +// These get a built-in, non-removable power network interface. /obj/item/electronic_assembly/wallmount - name = "wall-mounted electronic assembly" + name = "medium wall-mounted circuit case" icon_state = "setup_wallmount_medium" - desc = "It's a case, for building medium-sized electronics with. It has a magnetized \ + desc = "A case for building medium electronic assemblies. It has a magnetized \ backing to allow it to stick to walls." w_class = WEIGHT_CLASS_NORMAL - max_components = IC_COMPONENTS_BASE * 2 - max_complexity = IC_COMPLEXITY_BASE * 2 + var/max_components_wallmount_heavy = 110 + var/max_complexity_wallmount_heavy = 240 + var/max_components_wallmount_light = IC_COMPONENTS_BASE + var/max_complexity_wallmount_light = IC_COMPLEXITY_BASE + var/max_components_wallmount_tiny = 12 + var/max_complexity_wallmount_tiny = 30 + max_components = /obj/item/electronic_assembly::max_components_wallmount + max_complexity = /obj/item/electronic_assembly::max_complexity_wallmount can_anchor = TRUE + supports_builtin_powernet = TRUE + +/obj/item/electronic_assembly/wallmount/Initialize(mapload, printed = FALSE) + . = ..() + add_builtin_powernet() /obj/item/electronic_assembly/wallmount/proc/mount_assembly(turf/on_wall, mob/user) - if(get_dist(on_wall,user) > 1) + if(get_dist(on_wall, user) > 1) return + var/ndir = get_dir(on_wall, user) if(!(ndir in GLOB.cardinals)) return + var/turf/T = get_turf(user) if(!istype(T, /turf/simulated/floor)) to_chat(user, SPAN_WARNING("You cannot place \the [src] on this spot!")) return + playsound(src.loc, 'sound/machines/click.ogg', 75, 1) - user.visible_message("\The [user] attaches \the [src] to the wall.", + + user.visible_message( + "\The [user] attaches \the [src] to the wall.", SPAN_NOTICE("You attach \the [src] to the wall."), - "You hear clicking.") - if(isrobot(user)) //Robots cannot unequip/drop items, for Safety Reasons. + "You hear clicking." + ) + + if(isrobot(user)) // Robots cannot unequip/drop items, for safety reasons. forceMove(T) - user.drop_item(T) + else + user.drop_item(T) + anchored = TRUE on_anchored() set_pixel_offsets() @@ -222,31 +320,58 @@ pixel_y = DIR2PIXEL_Y(dir) /obj/item/electronic_assembly/wallmount/heavy - name = "heavy wall-mounted electronic assembly" + name = "large wall-mounted circuit machine" icon_state = "setup_wallmount_large" - desc = "It's a case, for building large electronics with. It has a magnetized backing \ + desc = "A case for building large electronic assemblies. It has a magnetized backing \ to allow it to stick to walls." w_class = WEIGHT_CLASS_BULKY - max_components = IC_COMPONENTS_BASE * 4 - max_complexity = IC_COMPLEXITY_BASE * 4 + max_components = /obj/item/electronic_assembly/wallmount::max_components_wallmount_heavy + max_complexity = /obj/item/electronic_assembly/wallmount::max_complexity_wallmount_heavy /obj/item/electronic_assembly/wallmount/light - name = "light wall-mounted electronic assembly" + name = "small wall-mounted circuit case" icon_state = "setup_wallmount_small" - desc = "It's a case, for building small electronics with. It has a magnetized backing \ + desc = "A case for building small electronic assemblies. It has a magnetized backing \ to allow it to stick to walls." w_class = WEIGHT_CLASS_SMALL - max_components = IC_COMPONENTS_BASE - max_complexity = IC_COMPLEXITY_BASE + max_components = /obj/item/electronic_assembly/wallmount::max_components_wallmount_light + max_complexity = /obj/item/electronic_assembly/wallmount::max_complexity_wallmount_light /obj/item/electronic_assembly/wallmount/tiny - name = "tiny wall-mounted electronic assembly" + name = "tiny wall-mounted circuit case" icon_state = "setup_wallmount_tiny" - desc = "It's a case, for building tiny electronics with. It has a magnetized backing \ + desc = "A case for building tiny electronic assemblies. It has a magnetized backing \ to allow it to stick to walls." w_class = WEIGHT_CLASS_TINY - max_components = IC_COMPONENTS_BASE / 2 - max_complexity = IC_COMPLEXITY_BASE / 2 + max_components = /obj/item/electronic_assembly/wallmount::max_components_wallmount_tiny + max_complexity = /obj/item/electronic_assembly/wallmount::max_complexity_wallmount_tiny + + +// Built-in circuits. + +/obj/item/electronic_assembly/proc/add_builtin_powernet() + if(!supports_builtin_powernet) + return + + for(var/obj/item/integrated_circuit/passive/power/powernet/P in contents) + return + + var/obj/item/integrated_circuit/passive/power/powernet/builtin/P = new(src) + P.removable = FALSE + P.assembly = src + force_add_circuit(P) + +/obj/item/electronic_assembly/proc/add_builtin_locomotion() + if(!supports_builtin_locomotion) + return + + for(var/obj/item/integrated_circuit/manipulation/locomotion/builtin/L in contents) + return + + var/obj/item/integrated_circuit/manipulation/locomotion/builtin/L = new(src) + L.removable = FALSE + L.assembly = src + force_add_circuit(L) /obj/item/electronic_assembly/proc/get_assembly_holder() return src diff --git a/code/modules/integrated_electronics/core/device.dm b/code/modules/integrated_electronics/core/device.dm index e6f53e9e4b2..57d124c5ed2 100644 --- a/code/modules/integrated_electronics/core/device.dm +++ b/code/modules/integrated_electronics/core/device.dm @@ -1,7 +1,13 @@ +/* + * core/device.dm + * Base device support for electronics-related items that are not themselves circuit assemblies. + */ + /obj/item/assembly/electronic_assembly name = "electronic device" - desc = "It's a case for building electronics with. It can be attached to other small devices." + desc = "A case for building electronics that can attach to other small devices." icon_state = "setup_device" + // Whether the assembly panel is open for direct circuit access. var/opened = 0 var/obj/item/electronic_assembly/device/EA @@ -61,17 +67,17 @@ set src in usr set category = "Object" set name = "Open/Close Device Assembly" - set desc = "Open or close device assembly!" + set desc = "Opens or closes the device assembly." toggle_open(usr) /obj/item/electronic_assembly/device name = "electronic device" icon_state = "setup_device" - desc = "It's a tiny electronic device with specific use for attaching to other devices." + desc = "A tiny electronic device designed to attach to other devices." w_class = WEIGHT_CLASS_TINY - max_components = IC_COMPONENTS_BASE * 3/4 - max_complexity = IC_COMPLEXITY_BASE * 3/4 + max_components = /obj/item/electronic_assembly::max_components_device + max_complexity = /obj/item/electronic_assembly::max_complexity_device var/obj/item/assembly/electronic_assembly/holder var/obj/item/integrated_circuit/built_in/device_input/input diff --git a/code/modules/integrated_electronics/core/helpers.dm b/code/modules/integrated_electronics/core/helpers.dm index 66fb065ff4e..2eea2946893 100644 --- a/code/modules/integrated_electronics/core/helpers.dm +++ b/code/modules/integrated_electronics/core/helpers.dm @@ -1,13 +1,21 @@ +/* + * core/helpers.dm + * Utility procs for integrated electronics, including formatting, data conversion, cloning, list handling, and shared validation. + */ + /obj/item/integrated_circuit/proc/setup_io(list/io_list, io_type, list/io_default_list) var/list/io_list_copy = io_list.Copy() io_list.Cut() var/i = 0 for(var/io_entry in io_list_copy) + i++ + var/default_data = null var/io_type_override = null + // Override the default data. - if(LAZYLEN(io_default_list)) // List containing special pin types that need to be added. - default_data = io_default_list["[i]"] // This is deliberately text because the index is a number in text form. + if(LAZYLEN(io_default_list)) + default_data = io_default_list["[i]"] // Override the pin type. if(io_list_copy[io_entry]) @@ -19,25 +27,32 @@ io_list += new io_type(src, io_entry, default_data) /obj/item/integrated_circuit/proc/set_pin_data(pin_type, pin_number, datum/new_data) - if (istype(new_data) && !isweakref(new_data)) + if(istype(new_data) && !isweakref(new_data)) new_data = WEAKREF(new_data) var/datum/integrated_io/pin = get_pin_ref(pin_type, pin_number) - if (!pin) + if(!pin) CRASH("Invalid pin ref.") return pin.write_data_to_pin(new_data) /obj/item/integrated_circuit/proc/get_pin_data(pin_type, pin_number, var/resolve_weakrefs = TRUE) var/datum/integrated_io/pin = get_pin_ref(pin_type, pin_number) + if(!pin) + return null return pin.get_data(resolve_weakrefs) /obj/item/integrated_circuit/proc/get_pin_data_as_type(pin_type, pin_number, as_type) var/datum/integrated_io/pin = get_pin_ref(pin_type, pin_number) + if(!pin) + return null return pin.data_as_type(as_type) /obj/item/integrated_circuit/proc/activate_pin(pin_number) var/datum/integrated_io/activate/A = activators[pin_number] + if(!A) + return FALSE A.push_data(pin_number) + return TRUE /datum/integrated_io/proc/get_data(var/resolve_weakrefs = TRUE) if(resolve_weakrefs && isweakref(data)) @@ -45,6 +60,9 @@ return data /obj/item/integrated_circuit/proc/get_pin_ref(pin_type, pin_number) + if(!pin_number || pin_number < 1) + return null + switch(pin_type) if(IC_INPUT) if(pin_number > inputs.len) @@ -58,26 +76,389 @@ if(pin_number > activators.len) return null return activators[pin_number] + return null /obj/item/integrated_circuit/proc/handle_wire(datum/integrated_io/pin, obj/item/integrated_electronics/tool) + if(!pin || !tool) + return FALSE + if(istype(tool, /obj/item/integrated_electronics/wirer)) var/obj/item/integrated_electronics/wirer/wirer = tool - if(pin) - wirer.wire(pin, usr) - return 1 + wirer.wire(pin, usr) + return TRUE - else if(istype(tool, /obj/item/integrated_electronics/debugger)) + if(istype(tool, /obj/item/integrated_electronics/debugger)) var/obj/item/integrated_electronics/debugger/debugger = tool - if(pin) - debugger.write_data(pin, usr) - return 1 - return 0 + debugger.write_data(pin, usr) + return TRUE + + return FALSE /proc/XorEncrypt(string, key) - if(!string || !key ||!istext(string)||!istext(key)) - return + if(!string || !key || !istext(string) || !istext(key)) + return null + var/r for(var/i = 1 to length(string)) - r += ascii2text(text2ascii(string,i) ^ text2ascii(key,(i-1)%length(string)+1)) + r += ascii2text(text2ascii(string, i) ^ text2ascii(key, (i - 1) % length(key) + 1)) return r + +// ------------------------------------------------------------ +// Integrated circuit helper procs. +// These are intentionally generic so new circuits can reuse them +// instead of duplicating null, list, text, number, and boolean checks. +// ------------------------------------------------------------ + +/proc/ic_is_null(value) + return isnull(value) + +/proc/ic_is_text(value) + return istext(value) + +/proc/ic_is_number(value) + return isnum(value) + +/proc/ic_is_list(value) + return islist(value) + +/proc/ic_is_ref(value) + if(isweakref(value)) + return TRUE + if(istype(value, /datum)) + return TRUE + return FALSE + +/proc/ic_is_safe_ref_atom(atom/A) + if(!istype(A)) + return FALSE + if(istype(A, /mob/abstract/ghost)) + return FALSE + return TRUE + +/proc/ic_truthy(value) + if(isnull(value)) + return FALSE + + if(isnum(value)) + return value != 0 + + if(istext(value)) + return length(value) > 0 + + if(islist(value)) + var/list/L = value + return L.len > 0 + + return TRUE + +/proc/ic_falsey(value) + return !ic_truthy(value) + +/proc/ic_safe_number(value, default_value = 0) + if(isnum(value)) + return value + + if(istext(value)) + var/converted = text2num(value) + if(!isnull(converted)) + return converted + + return default_value + +/proc/ic_safe_text(value, default_value = "") + if(isnull(value)) + return default_value + + if(istext(value)) + return value + + if(isnum(value)) + return num2text(value) + + if(isweakref(value)) + var/datum/weakref/W = value + var/datum/resolved = W.resolve() + if(resolved) + return "[resolved]" + return default_value + + return "[value]" + +/proc/ic_safe_bool(value) + return ic_truthy(value) + +/proc/ic_display_value(value) + if(isnull(value)) + return "null" + + if(isweakref(value)) + var/datum/weakref/W = value + var/datum/resolved = W.resolve() + if(resolved) + return "[resolved]" + return "null ref" + + if(islist(value)) + var/list/L = value + return ic_list_to_text(L, ", ") + + if(istext(value)) + return value + + if(isnum(value)) + return num2text(value) + + return "[value]" + +/proc/ic_type_text(value) + if(isnull(value)) + return "null" + + if(isweakref(value)) + return "weakref" + + if(isnum(value)) + return "number" + + if(istext(value)) + return "string" + + if(islist(value)) + return "list" + + if(istype(value, /datum)) + return "ref" + + return "unknown" + +/proc/ic_list_to_text(list/L, separator = ", ") + if(!islist(L)) + return "" + + var/list/output = list() + for(var/entry in L) + output += ic_display_value(entry) + + return jointext(output, separator) + +/proc/ic_text_to_list(text, separator = ",") + if(!istext(text)) + return list() + + var/list/output = list() + var/list/split_values = splittext(text, separator) + + for(var/entry in split_values) + output += trim(entry) + + return output + +/proc/ic_copy_list(list/L) + if(!islist(L)) + return list() + + return L.Copy() + +/proc/ic_filter_nulls(list/L) + if(!islist(L)) + return list() + + var/list/output = list() + for(var/entry in L) + if(!isnull(entry)) + output += entry + + return output + +/proc/ic_list_contains(list/L, value) + if(!islist(L)) + return FALSE + + return L.Find(value) ? TRUE : FALSE + +/proc/ic_list_length(list/L) + if(!islist(L)) + return 0 + + return L.len + +/proc/ic_clamp_number(value, minimum, maximum) + var/number = ic_safe_number(value, minimum) + + if(number < minimum) + return minimum + + if(number > maximum) + return maximum + + return number + +/proc/ic_percent(value, maximum) + var/number = ic_safe_number(value, 0) + var/max_number = ic_safe_number(maximum, 0) + + if(max_number <= 0) + return 0 + + return (number / max_number) * 100 + +/proc/ic_range_check(value, minimum, maximum) + var/number = ic_safe_number(value, 0) + + if(number < minimum) + return -1 + + if(number > maximum) + return 1 + + return 0 + +/proc/ic_range_text(value, minimum, maximum) + var/range_result = ic_range_check(value, minimum, maximum) + + if(range_result < 0) + return "low" + + if(range_result > 0) + return "high" + + return "normal" + +/proc/ic_format_status(label, value) + return "[ic_safe_text(label, "Status")]: [ic_display_value(value)]" + +/proc/ic_format_warning(label, value) + return "WARNING: [ic_safe_text(label, "Value")] [ic_display_value(value)]" + +/proc/ic_format_error(label, value) + return "ERROR: [ic_safe_text(label, "Value")] [ic_display_value(value)]" + +/proc/ic_join_lines(list/L) + if(!islist(L)) + return "" + + var/list/output = list() + for(var/entry in L) + if(isnull(entry)) + continue + output += ic_display_value(entry) + + return jointext(output, "\n") + +/proc/ic_make_saveable_value(value) + if(isnull(value) || isnum(value) || istext(value)) + return value + + if(isweakref(value)) + var/datum/weakref/W = value + var/datum/resolved = W.resolve() + if(resolved) + return "[resolved]" + return null + + if(islist(value)) + var/list/source_list = value + var/list/new_list = list() + + for(var/entry in source_list) + new_list += ic_make_saveable_value(entry) + + return new_list + + return "[value]" + +/proc/ic_sanitize_template_value(value) + var/text_value = ic_display_value(value) + text_value = replacetext(text_value, "\n", " ") + text_value = replacetext(text_value, ascii2text(13), " ") + return text_value + +/proc/ic_apply_template(template, value_1 = null, value_2 = null, value_3 = null, value_4 = null, value_5 = null, value_6 = null, value_7 = null, value_8 = null) + if(!istext(template)) + return "" + + var/output = template + output = replacetext(output, "{1}", ic_sanitize_template_value(value_1)) + output = replacetext(output, "{2}", ic_sanitize_template_value(value_2)) + output = replacetext(output, "{3}", ic_sanitize_template_value(value_3)) + output = replacetext(output, "{4}", ic_sanitize_template_value(value_4)) + output = replacetext(output, "{5}", ic_sanitize_template_value(value_5)) + output = replacetext(output, "{6}", ic_sanitize_template_value(value_6)) + output = replacetext(output, "{7}", ic_sanitize_template_value(value_7)) + output = replacetext(output, "{8}", ic_sanitize_template_value(value_8)) + + return output + +/proc/ic_get_nested_list_value(list/L, index, default_value = null) + if(!islist(L)) + return default_value + + var/number_index = round(ic_safe_number(index, 0)) + + if(number_index < 1 || number_index > L.len) + return default_value + + return L[number_index] + +/proc/ic_set_nested_list_value(list/L, index, value) + if(!islist(L)) + return list() + + var/number_index = round(ic_safe_number(index, 0)) + + if(number_index < 1 || number_index > IC_MAX_LIST_LENGTH) + return L + + while(L.len < number_index) + L += null + + L[number_index] = value + return L + +/proc/ic_number_or_null(value) + if(isnum(value)) + return value + + if(istext(value)) + var/converted = text2num(value) + if(!isnull(converted)) + return converted + + return null + +/proc/ic_add_numbers(value_1, value_2) + var/A = ic_number_or_null(value_1) + var/B = ic_number_or_null(value_2) + + if(isnull(A) || isnull(B)) + return null + + return A + B + +/proc/ic_subtract_numbers(value_1, value_2) + var/A = ic_number_or_null(value_1) + var/B = ic_number_or_null(value_2) + + if(isnull(A) || isnull(B)) + return null + + return A - B + +/proc/ic_multiply_numbers(value_1, value_2) + var/A = ic_number_or_null(value_1) + var/B = ic_number_or_null(value_2) + + if(isnull(A) || isnull(B)) + return null + + return A * B + +/proc/ic_divide_numbers(value_1, value_2) + var/A = ic_number_or_null(value_1) + var/B = ic_number_or_null(value_2) + + if(isnull(A) || isnull(B) || B == 0) + return null + + return A / B diff --git a/code/modules/integrated_electronics/core/integrated_circuit.dm b/code/modules/integrated_electronics/core/integrated_circuit.dm index 8a589d1e25e..5081dbb3d53 100644 --- a/code/modules/integrated_electronics/core/integrated_circuit.dm +++ b/code/modules/integrated_electronics/core/integrated_circuit.dm @@ -29,6 +29,9 @@ a creative player the means to solve many problems. Circuits are held inside an /obj/item/integrated_circuit/proc/on_data_written() //Override this for special behaviour when new data gets pushed to the circuit. return +/obj/item/integrated_circuit/proc/get_printer_spawn_flags() + return spawn_flags + /obj/item/integrated_circuit/Destroy() for(var/datum/integrated_io/I in inputs) qdel(I) @@ -70,7 +73,7 @@ a creative player the means to solve many problems. Circuits are held inside an /obj/item/integrated_circuit/verb/rename_component() set name = "Rename Circuit" set category = "Object" - set desc = "Rename your circuit, useful to stay organized." + set desc = "Renames the circuit to make assemblies easier to organize." var/mob/M = usr if(!check_interactivity(M)) @@ -175,7 +178,8 @@ a creative player the means to solve many problems. Circuits are held inside an HTML += "
Power Draw: [power_draw_idle] W (Idle)" if(power_draw_per_use) HTML += "
Power Draw: [power_draw_per_use] W (Active)" // Borgcode says that powercells' checked_use() takes joules as input. - HTML += "
[extended_desc]" + if(extended_desc) + HTML += "
[extended_desc]" var/datum/browser/B = new(user, assembly ? "assembly-[REF(assembly)]" : "circuit-[REF(src)]", (displayed_name && displayed_name != name) ? "[displayed_name] ([name])" : name, window_width, window_height) B.set_content(HTML.Join()) @@ -319,7 +323,7 @@ a creative player the means to solve many problems. Circuits are held inside an /obj/item/integrated_circuit/proc/pull_data() for(var/datum/integrated_io/I in inputs) - I.push_data() + I.pull_data() /obj/item/integrated_circuit/proc/draw_idle_power() if(assembly) diff --git a/code/modules/integrated_electronics/core/pins.dm b/code/modules/integrated_electronics/core/pins.dm index 6f44648ba2a..605753c4f27 100644 --- a/code/modules/integrated_electronics/core/pins.dm +++ b/code/modules/integrated_electronics/core/pins.dm @@ -1,6 +1,6 @@ /* Pins both hold data for circuits, as well move data between them. Some also cause circuits to do their function. DATA_CHANNEL pins are the data holding/moving kind, -where as PULSE_CHANNEL causes circuits to work() when their pulse hits them. +whereas PULSE_CHANNEL causes circuits to work() when their pulse hits them. A visualization of how pins work is below. Imagine the below image involves an addition circuit. @@ -68,14 +68,15 @@ list[]( if(islist(input)) var/list/my_list = input - var/result = "list\[[my_list.len]\](" - if(my_list.len) + var/list_length = length(my_list) + var/result = "list\[[list_length]\](" + if(list_length) result += "
" var/pos = 0 for(var/line in my_list) result += "[display_data(line)]" pos++ - if(pos != my_list.len) + if(pos != list_length) result += ",
" result += "
" result += ")" diff --git a/code/modules/integrated_electronics/core/printer.dm b/code/modules/integrated_electronics/core/printer.dm index c53bdca156c..e89579c7830 100644 --- a/code/modules/integrated_electronics/core/printer.dm +++ b/code/modules/integrated_electronics/core/printer.dm @@ -4,73 +4,152 @@ icon = 'icons/obj/assemblies/electronic_tools.dmi' icon_state = "circuit_printer" w_class = WEIGHT_CLASS_NORMAL + + // Material storage. + // One sheet equals 10 stored material units. + // Total upgraded storage cap is 520 material units: + // 500 steel + 20 phoron. var/metal = 0 var/max_metal = 100 - var/metal_per_sheet = 10 // One sheet equals this much metal. + var/metal_per_sheet = 10 - var/upgraded = FALSE // When hit with an upgrade disk, will turn true, allowing it to print the higher tier circuits. - var/can_clone = FALSE // Same for above, but will allow the printer to duplicate a specific assembly. + // Stored phoron amount available for printing. + var/phoron = 0 + var/max_phoron = 20 + var/phoron_per_sheet = 10 + + var/clone_metal_storage_bonus = 400 + + var/upgraded = FALSE + var/can_clone = FALSE var/obj/item/electronic_assembly/assembly_to_clone + var/obj/item/clone_item_to_clone + var/currently_printing = FALSE + + // Imported/exported copy-paste blueprint data. + var/list/clone_blueprint_data = null + var/clone_blueprint_name = null + var/clone_blueprint_export = null + var/clone_blueprint_import_buffer = "" + var/clone_blueprint_export_visible = FALSE + var/clone_blueprint_metal_cost = 0 + var/clone_blueprint_phoron_cost = 0 + var/clone_blueprint_print_time = 0 + + // Clone timing is based on total clone steel cost. + // 1 SECOND is 10 ticks on this codebase. + var/clone_min_print_time = 5 SECONDS + var/clone_max_print_time = 300 SECONDS + /obj/item/integrated_circuit_printer/upgraded upgraded = TRUE can_clone = TRUE + max_metal = 500 + max_phoron = 20 + + +/obj/item/integrated_circuit_printer/Destroy() + assembly_to_clone = null + clone_item_to_clone = null + + return ..() + /obj/item/integrated_circuit_printer/attackby(obj/item/attacking_item, mob/user) - if(istype(attacking_item,/obj/item/stack/material)) + if(istype(attacking_item, /obj/item/stack/material)) var/obj/item/stack/material/stack = attacking_item if(stack.material.type == MATERIAL_STEEL) - var/num = min((max_metal - metal) / metal_per_sheet, stack.amount) + var/num = min(FLOOR((max_metal - metal) / metal_per_sheet, 1), stack.amount) if(num < 1) - to_chat(user, SPAN_WARNING("\The [src] is too full to add more metal.")) - return + to_chat(user, SPAN_WARNING("\The [src] is too full to add more steel.")) + return TRUE + if(stack.use(num)) - to_chat(user, SPAN_NOTICE("You add [num] sheet\s to \the [src].")) + to_chat(user, SPAN_NOTICE("You add [num] steel sheet\s to \the [src].")) metal += num * metal_per_sheet return TRUE - if(istype(attacking_item,/obj/item/integrated_circuit)) + if(stack.material.type == MATERIAL_PHORON) + var/num = min(FLOOR((max_phoron - phoron) / phoron_per_sheet, 1), stack.amount) + if(num < 1) + to_chat(user, SPAN_WARNING("\The [src] is too full to add more phoron.")) + return TRUE + + if(stack.use(num)) + to_chat(user, SPAN_NOTICE("You add [num] phoron sheet\s to \the [src].")) + phoron += num * phoron_per_sheet + return TRUE + + if(scan_cloneable_item(attacking_item, user)) + return TRUE + + if(istype(attacking_item, /obj/item/integrated_circuit)) to_chat(user, SPAN_NOTICE("You insert the circuit into \the [src]. ")) user.unEquip(attacking_item) metal = min(metal + attacking_item.w_class, max_metal) + phoron = min(phoron + get_circuit_phoron_cost(attacking_item.type), max_phoron) qdel(attacking_item) return TRUE - if(istype(attacking_item,/obj/item/disk/integrated_circuit/upgrade/advanced)) - if(upgraded) - to_chat(user, SPAN_WARNING("\The [src] already has this upgrade. ")) - return TRUE - to_chat(user, SPAN_NOTICE("You install \the [attacking_item] into \the [src]. ")) - upgraded = TRUE - return TRUE - - if(istype(attacking_item,/obj/item/disk/integrated_circuit/upgrade/clone)) + if(istype(attacking_item, /obj/item/disk/integrated_circuit/upgrade/clone)) if(can_clone) to_chat(user, SPAN_WARNING("\The [src] already has this upgrade. ")) return TRUE - to_chat(user, SPAN_NOTICE("You install \the [attacking_item] into \the [src]. ")) + + to_chat(user, SPAN_NOTICE("You install \the [attacking_item] into \the [src]. ")) can_clone = TRUE + max_metal = min(max_metal + clone_metal_storage_bonus, 500) + qdel(attacking_item) + return TRUE + + if(istype(attacking_item, /obj/item/disk/integrated_circuit/upgrade/advanced)) + if(upgraded) + to_chat(user, SPAN_WARNING("\The [src] already has this upgrade. ")) + return TRUE + + to_chat(user, SPAN_NOTICE("You install \the [attacking_item] into \the [src]. ")) + upgraded = TRUE + qdel(attacking_item) return TRUE return ..() + /obj/item/integrated_circuit_printer/attack_self(var/mob/user) ui_interact(user) + /obj/item/integrated_circuit_printer/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) + if(!ui) ui = new(user, src, "CircuitPrinter", "Integrated Circuit Printer", 600, 500) ui.open() + /obj/item/integrated_circuit_printer/ui_data(mob/user) var/list/data = list() data["metal"] = metal / metal_per_sheet data["metal_max"] = max_metal / metal_per_sheet + data["phoron"] = phoron / phoron_per_sheet + data["phoron_max"] = max_phoron / phoron_per_sheet data["upgraded"] = upgraded data["can_clone"] = can_clone - data["assembly_to_clone"] = assembly_to_clone ? assembly_to_clone.name : "None" + data["assembly_to_clone"] = assembly_to_clone ? (clone_item_to_clone ? clone_item_to_clone.name : assembly_to_clone.name) : clone_blueprint_name ? clone_blueprint_name : "None" + data["has_clone_loaded"] = assembly_to_clone || clone_blueprint_data ? TRUE : FALSE + data["clone_cost"] = assembly_to_clone ? round(get_clone_metal_cost(assembly_to_clone) / metal_per_sheet, 0.1) : clone_blueprint_data ? round(clone_blueprint_metal_cost / metal_per_sheet, 0.1) : 0 + data["clone_phoron_cost"] = assembly_to_clone ? round(get_clone_phoron_cost(assembly_to_clone) / phoron_per_sheet, 0.01) : clone_blueprint_data ? round(clone_blueprint_phoron_cost / phoron_per_sheet, 0.01) : 0 + data["clone_print_time"] = assembly_to_clone ? round(get_clone_print_time(assembly_to_clone) / 10, 0.1) : clone_blueprint_data ? round(clone_blueprint_print_time / 10, 0.1) : 0 + data["currently_printing"] = currently_printing + data["has_live_clone_scan"] = assembly_to_clone ? TRUE : FALSE + data["has_clone_blueprint"] = clone_blueprint_data ? TRUE : FALSE + data["has_clone_blueprint_export"] = clone_blueprint_export ? TRUE : FALSE + data["clone_blueprint_export_visible"] = clone_blueprint_export_visible + data["clone_blueprint_export"] = clone_blueprint_export_visible && clone_blueprint_export ? clone_blueprint_export : "" + data["blueprint_chunk_limit"] = IC_BLUEPRINT_CHUNK_LIMIT + data["blueprint_buffer_limit"] = IC_BLUEPRINT_BUFFER_LIMIT if(upgraded) data["circuits"] = SSelectronics.printer_recipe_list_upgraded @@ -81,6 +160,178 @@ return data + +/obj/item/integrated_circuit_printer/proc/resolve_cloneable_assembly(obj/item/target) + if(!target) + return null + + if(istype(target, /obj/item/electronic_assembly)) + return target + + if(istype(target, /obj/item/radio/headset/circuitry)) + var/obj/item/radio/headset/circuitry/headset = target + return headset.get_cloneable_assembly() + + return null + + +/obj/item/integrated_circuit_printer/proc/scan_cloneable_item(obj/item/target, mob/user) + var/obj/item/electronic_assembly/E = resolve_cloneable_assembly(target) + + if(!E) + return FALSE + + if(!can_clone) + to_chat(user, SPAN_WARNING("\The [src] needs a circuit cloner upgrade before it can scan assemblies.")) + return TRUE + + assembly_to_clone = E + clone_item_to_clone = istype(target, /obj/item/electronic_assembly) ? null : target + clone_blueprint_data = null + clone_blueprint_name = null + clone_blueprint_metal_cost = 0 + clone_blueprint_phoron_cost = 0 + clone_blueprint_print_time = 0 + clone_blueprint_export_visible = FALSE + var/list/scanned_blueprint = serialize_clone_blueprint(assembly_to_clone, clone_item_to_clone) + clone_blueprint_export = scanned_blueprint ? json_encode(scanned_blueprint) : null + + to_chat(user, SPAN_NOTICE("You scan \the [target] into \the [src]'s cloning buffer.")) + SStgui.update_uis(src) + return TRUE + +/obj/item/integrated_circuit_printer/proc/import_clone_blueprint_text(var/import_text, var/mob/user) + if(!import_text) + if(user) + to_chat(user, SPAN_WARNING("No cloning blueprint text was provided.")) + return FALSE + + import_text = html_decode(import_text) + + if(length(import_text) > IC_BLUEPRINT_BUFFER_LIMIT) + to_chat(user, SPAN_WARNING("That cloning blueprint is [length(import_text)] characters, but the maximum import size is [IC_BLUEPRINT_BUFFER_LIMIT] characters.")) + return FALSE + + var/start_index = findtext(import_text, "{") + var/end_index = findlasttext(import_text, "}") + + if(start_index && end_index && end_index >= start_index) + import_text = copytext(import_text, start_index, end_index + 1) + + + var/imported_blueprint + try + imported_blueprint = json_decode(import_text) + catch(var/exception/a) + if(user) + to_chat(user, SPAN_WARNING("That cloning blueprint text failed JSON decoding: [a].")) + if(user) + to_chat(user, SPAN_WARNING("Import text preview start: [copytext(import_text, 1, min(length(import_text), 200))]")) + if(length(import_text) > 200) + to_chat(user, SPAN_WARNING("Import text preview end: [copytext(import_text, max(1, length(import_text) - 199), length(import_text))]")) + return FALSE + + + if(!islist(imported_blueprint)) + if(user) + to_chat(user, SPAN_WARNING("That cloning blueprint text is invalid JSON or did not decode into a list.")) + return FALSE + + var/assembly_path = text2path(imported_blueprint["assembly_type"]) + if(!assembly_path || !ispath(assembly_path, /obj/item/electronic_assembly)) + var/assembly_type_text = imported_blueprint["assembly_type"] + if(user) + to_chat(user, SPAN_WARNING("That cloning blueprint does not contain a valid assembly type: [assembly_type_text].")) + return FALSE + + + var/list/circuit_blueprints = imported_blueprint["circuits"] + if(!islist(circuit_blueprints)) + if(user) + to_chat(user, SPAN_WARNING("That cloning blueprint does not contain a valid circuits list.")) + return FALSE + + + var/new_metal_cost = 0 + try + new_metal_cost = get_clone_blueprint_metal_cost(imported_blueprint) + catch(var/exception/b) + if(user) + to_chat(user, SPAN_WARNING("Blueprint steel cost calculation failed: [b].")) + return FALSE + + var/new_phoron_cost = 0 + try + new_phoron_cost = get_clone_blueprint_phoron_cost(imported_blueprint) + catch(var/exception/c) + if(user) + to_chat(user, SPAN_WARNING("Blueprint phoron cost calculation failed: [c].")) + return FALSE + + var/new_print_time = 0 + try + new_print_time = get_clone_blueprint_print_time(imported_blueprint) + catch(var/exception/d) + if(user) + to_chat(user, SPAN_WARNING("Blueprint print time calculation failed: [d].")) + return FALSE + + var/new_blueprint_name = imported_blueprint["name"] + if(!new_blueprint_name) + var/obj/item/electronic_assembly/E = assembly_path + new_blueprint_name = initial(E.name) + + clone_blueprint_data = imported_blueprint + clone_blueprint_name = new_blueprint_name + clone_blueprint_export = import_text + clone_blueprint_export_visible = FALSE + clone_blueprint_metal_cost = new_metal_cost + clone_blueprint_phoron_cost = new_phoron_cost + clone_blueprint_print_time = new_print_time + assembly_to_clone = null + clone_item_to_clone = null + + if(user) + to_chat(user, SPAN_NOTICE("You import the cloning blueprint: [clone_blueprint_name].")) + SStgui.update_uis(src) + return TRUE + +/obj/item/integrated_circuit_printer/proc/append_clone_blueprint_chunk(var/import_chunk, var/mob/user, var/max_chunk_length = IC_BLUEPRINT_CHUNK_LIMIT) + if(!istext(import_chunk)) + if(user) + to_chat(user, SPAN_WARNING("No blueprint chunk text was received from the UI.")) + return FALSE + + // The TGUI sends each chunk through encodeURIComponent() after slicing the + // original JSON. Decode each complete encoded chunk before appending it. + // This avoids BYOND/TGUI transport edge cases where raw JSON punctuation + // can be lost or altered inside action parameters. + if(length(import_chunk) > max_chunk_length * 6) + if(user) + to_chat(user, SPAN_WARNING("That encoded blueprint chunk is [length(import_chunk)] characters, but chunks must be [max_chunk_length] decoded characters or less.")) + return FALSE + + import_chunk = url_decode(import_chunk) + + if(!length(import_chunk)) + if(user) + to_chat(user, SPAN_WARNING("The received blueprint chunk was empty.")) + return FALSE + + if(length(import_chunk) > max_chunk_length) + if(user) + to_chat(user, SPAN_WARNING("That blueprint chunk is [length(import_chunk)] characters, but chunks must be [max_chunk_length] characters or less.")) + return FALSE + + if(length(clone_blueprint_import_buffer) + length(import_chunk) > IC_BLUEPRINT_BUFFER_LIMIT) + if(user) + to_chat(user, SPAN_WARNING("The cloning blueprint import buffer cannot hold [length(clone_blueprint_import_buffer) + length(import_chunk)] characters. Maximum: [IC_BLUEPRINT_BUFFER_LIMIT].")) + return FALSE + + clone_blueprint_import_buffer += import_chunk + + return TRUE + /obj/item/integrated_circuit_printer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) @@ -88,46 +339,874 @@ add_fingerprint(usr) + if(currently_printing) + to_chat(usr, SPAN_WARNING("\The [src] is already printing.")) + return FALSE + + if(action == "clear_clone") + assembly_to_clone = null + clone_item_to_clone = null + if(!clone_blueprint_data) + clone_blueprint_export = null + clone_blueprint_export_visible = FALSE + return TRUE + + if(action == "show_clone_blueprint_export") + if(!clone_blueprint_export) + to_chat(usr, SPAN_WARNING("There is no cloning blueprint export loaded.")) + return FALSE + + clone_blueprint_export_visible = TRUE + return TRUE + + if(action == "hide_clone_blueprint_export") + clone_blueprint_export_visible = FALSE + return TRUE + + if(action == "begin_import_buffer") + if(!can_clone) + to_chat(usr, SPAN_WARNING("\The [src] needs a circuit cloner upgrade for that.")) + return FALSE + + clone_blueprint_import_buffer = "" + clone_blueprint_export_visible = FALSE + return TRUE + + if(action == "append_import_chunk_tgui") + if(!can_clone) + to_chat(usr, SPAN_WARNING("\The [src] needs a circuit cloner upgrade for that.")) + return FALSE + + var/import_chunk = params["chunk"] + if(!istext(import_chunk)) + to_chat(usr, SPAN_WARNING("No blueprint chunk text was received from the UI.")) + return FALSE + + return append_clone_blueprint_chunk(import_chunk, usr) + + if(action == "reject_oversized_import_tgui") + var/blueprint_length = text2num(params["length"]) + if(!blueprint_length) + to_chat(usr, SPAN_WARNING("The pasted cloning blueprint is too large to import. Maximum: [IC_BLUEPRINT_BUFFER_LIMIT] characters.")) + else + to_chat(usr, SPAN_WARNING("The pasted cloning blueprint is [blueprint_length] characters, but the maximum import size is [IC_BLUEPRINT_BUFFER_LIMIT] characters.")) + return FALSE + + if(action == "finish_import_buffer_tgui") + if(!can_clone) + to_chat(usr, SPAN_WARNING("\The [src] needs a circuit cloner upgrade for that.")) + return FALSE + + if(!clone_blueprint_import_buffer) + to_chat(usr, SPAN_WARNING("The cloning blueprint import buffer is empty.")) + return FALSE + + + var/import_success = import_clone_blueprint_text(clone_blueprint_import_buffer, usr) + if(import_success) + clone_blueprint_import_buffer = "" + return import_success + + if(action == "clear_imported_clone") + clone_blueprint_data = null + clone_blueprint_name = null + clone_blueprint_export = null + clone_blueprint_import_buffer = "" + clone_blueprint_export_visible = FALSE + clone_blueprint_metal_cost = 0 + clone_blueprint_phoron_cost = 0 + clone_blueprint_print_time = 0 + to_chat(usr, SPAN_NOTICE("You clear the imported circuit cloning blueprint.")) + return TRUE + + if(action == "clone") + if(!can_clone) + to_chat(usr, SPAN_WARNING("\The [src] needs a circuit cloner upgrade for that.")) + return FALSE + + var/cost = 0 + var/phoron_cost = 0 + var/print_time = 0 + var/obj/item/electronic_assembly/live_scan_to_print = null + var/obj/item/live_host_to_print = null + var/list/blueprint_to_print = null + + if(assembly_to_clone && !QDELETED(assembly_to_clone)) + live_scan_to_print = assembly_to_clone + if(clone_item_to_clone && !QDELETED(clone_item_to_clone)) + live_host_to_print = clone_item_to_clone + cost = get_clone_metal_cost(live_scan_to_print) + phoron_cost = get_clone_phoron_cost(live_scan_to_print) + print_time = get_clone_print_time(live_scan_to_print) + else + assembly_to_clone = null + clone_item_to_clone = null + + if(!clone_blueprint_data) + to_chat(usr, SPAN_WARNING("There is no valid assembly or imported blueprint loaded into the cloning buffer.")) + return FALSE + + blueprint_to_print = clone_blueprint_data + cost = clone_blueprint_metal_cost + phoron_cost = clone_blueprint_phoron_cost + print_time = clone_blueprint_print_time + + if(metal - cost < 0) + to_chat(usr, SPAN_WARNING("You need [round(cost / metal_per_sheet, 0.1)] sheet\s of steel to clone that!")) + return FALSE + + if(phoron - phoron_cost < 0) + to_chat(usr, SPAN_WARNING("You need [round(phoron_cost / phoron_per_sheet, 0.01)] sheet\s of phoron to clone that!")) + return FALSE + + currently_printing = TRUE + to_chat(usr, SPAN_NOTICE("\The [src] begins printing a cloned assembly.")) + + var/mob/printing_user = usr + spawn(print_time) + finish_clone_print(printing_user, cost, phoron_cost, live_scan_to_print, live_host_to_print, blueprint_to_print) + + return TRUE + if(action == "build") var/build_type = text2path(params["build"]) if(!build_type || !ispath(build_type)) return FALSE - if (!can_print(build_type)) + if(!can_print(build_type)) to_chat(usr, SPAN_WARNING("[src] buzzes angrily at you!")) return FALSE var/cost = 1 - var/is_asm = FALSE + var/phoron_cost = 0 + var/is_printed_assembly_container = is_printed_assembly_container_type(build_type) + if(ispath(build_type, /obj/item/electronic_assembly)) var/obj/item/electronic_assembly/E = build_type - cost = round( (initial(E.max_complexity) + initial(E.max_components) ) / 4) - is_asm = TRUE + cost = round((initial(E.max_complexity) + initial(E.max_components)) / 4) else if(ispath(build_type, /obj/item/integrated_circuit)) var/obj/item/integrated_circuit/IC = build_type cost = initial(IC.w_class) + phoron_cost = get_circuit_phoron_cost(build_type) if(metal - cost < 0) - to_chat(usr, SPAN_WARNING("You need [cost] metal to build that!")) + to_chat(usr, SPAN_WARNING("You need [cost] steel to build that!")) return FALSE + + if(phoron - phoron_cost < 0) + to_chat(usr, SPAN_WARNING("You need [round(phoron_cost / phoron_per_sheet, 0.01)] sheet\s of phoron to build that!")) + return FALSE + metal -= cost - if (is_asm) + phoron -= phoron_cost + + if(is_printed_assembly_container) new build_type(get_turf(loc), TRUE) else new build_type(get_turf(loc)) - . = TRUE -/obj/item/integrated_circuit_printer/proc/can_print(build_type) - for(var/category in SSelectronics.printer_recipe_list) - var/list/current_list = SSelectronics.printer_recipe_list[category] - - for (var/obj/O in current_list) - if (O.type == build_type) - return TRUE + return TRUE return FALSE -// FUKKEN UPGRADE DISKS + +/obj/item/integrated_circuit_printer/proc/finish_clone_print(mob/user, cost, phoron_cost, obj/item/electronic_assembly/live_scan_to_print, obj/item/live_host_to_print, list/blueprint_to_print) + if(QDELETED(src)) + return + + currently_printing = FALSE + + if(metal - cost < 0) + if(user) + to_chat(user, SPAN_WARNING("\The [src] does not have enough steel to finish the cloned assembly.")) + return + + if(phoron - phoron_cost < 0) + if(user) + to_chat(user, SPAN_WARNING("\The [src] does not have enough phoron to finish the cloned assembly.")) + return + + metal -= cost + phoron -= phoron_cost + + var/obj/item/new_clone = null + + if(live_scan_to_print && !QDELETED(live_scan_to_print)) + if(istype(live_host_to_print, /obj/item/radio/headset/circuitry) && !QDELETED(live_host_to_print)) + var/obj/item/radio/headset/circuitry/headset = live_host_to_print + new_clone = headset.clone_with_integrated_assembly(get_turf(src), src, live_scan_to_print) + else + new_clone = clone_assembly(live_scan_to_print, get_turf(src)) + else if(blueprint_to_print) + new_clone = clone_assembly_from_blueprint(blueprint_to_print, get_turf(src)) + + if(!new_clone) + if(user) + to_chat(user, SPAN_WARNING("\The [src] fails to finish the cloned assembly.")) + return + + if(user) + to_chat(user, SPAN_NOTICE("\The [src] finishes printing \the [new_clone].")) + + +/obj/item/integrated_circuit_printer/proc/get_circuit_phoron_cost(circuit_type) + if(!circuit_type || !ispath(circuit_type, /obj/item/integrated_circuit)) + return 0 + + var/obj/item/integrated_circuit/IC = circuit_type + return max(0, initial(IC.phoron_cost) * phoron_per_sheet) + + +/obj/item/integrated_circuit_printer/proc/get_clone_metal_cost(obj/item/electronic_assembly/source) + if(!source) + return 0 + + var/case_cost = round((source.max_complexity + source.max_components) / 4) + var/component_cost = 0 + + for(var/obj/item/integrated_circuit/IC in source.contents) + if(!IC.removable) + continue + component_cost += max(1, IC.w_class) + + return max(1, case_cost + component_cost) + + +/obj/item/integrated_circuit_printer/proc/get_clone_phoron_cost(obj/item/electronic_assembly/source) + if(!source) + return 0 + + var/component_cost = 0 + + for(var/obj/item/integrated_circuit/IC in source.contents) + if(!IC.removable) + continue + component_cost += get_circuit_phoron_cost(IC.type) + + return component_cost + + +/obj/item/integrated_circuit_printer/proc/get_clone_print_time(obj/item/electronic_assembly/source) + if(!source) + return clone_min_print_time + + var/cost = get_clone_metal_cost(source) + var/effective_max_cost = max(1, max_metal) + var/cost_ratio = cost / effective_max_cost + + if(cost_ratio < 0) + cost_ratio = 0 + if(cost_ratio > 1) + cost_ratio = 1 + + var/scaled_time = clone_min_print_time + ((clone_max_print_time - clone_min_print_time) * cost_ratio) + + if(scaled_time < clone_min_print_time) + scaled_time = clone_min_print_time + if(scaled_time > clone_max_print_time) + scaled_time = clone_max_print_time + + return round(scaled_time) + + +/obj/item/integrated_circuit_printer/proc/clone_pin_data(datum/integrated_io/source_pin, datum/integrated_io/target_pin) + if(!source_pin || !target_pin) + return + + target_pin.write_data_to_pin(ic_copy_clone_value(source_pin.data)) + + +/obj/item/integrated_circuit_printer/proc/clone_io_list_data(list/source_pins, list/target_pins) + var/pin_count = min(source_pins.len, target_pins.len) + + for(var/i = 1 to pin_count) + clone_pin_data(source_pins[i], target_pins[i]) + + +/obj/item/integrated_circuit_printer/proc/map_builtin_circuits(obj/item/electronic_assembly/source, obj/item/electronic_assembly/clone, list/circuit_map) + var/list/used_clone_builtins = list() + + for(var/obj/item/integrated_circuit/source_circuit in source.contents) + if(source_circuit.removable) + continue + + for(var/obj/item/integrated_circuit/clone_circuit in clone.contents) + if(clone_circuit.removable) + continue + if(clone_circuit.type != source_circuit.type) + continue + if(used_clone_builtins.Find(clone_circuit)) + continue + + circuit_map[source_circuit] = clone_circuit + used_clone_builtins += clone_circuit + break + + +/obj/item/integrated_circuit_printer/proc/copy_assembly_into_existing(obj/item/electronic_assembly/source, obj/item/electronic_assembly/clone) + if(!source || !clone) + return FALSE + + clone.name = source.name + clone.detail_color = source.detail_color + clone.opened = FALSE + clone.update_icon() + + var/list/circuit_map = list() + map_builtin_circuits(source, clone, circuit_map) + + for(var/obj/item/integrated_circuit/source_circuit in source.contents) + if(!source_circuit.removable) + continue + + var/obj/item/integrated_circuit/new_circuit = new source_circuit.type(clone) + new_circuit.displayed_name = source_circuit.displayed_name + new_circuit.removable = source_circuit.removable + new_circuit.assembly = clone + clone.force_add_circuit(new_circuit) + circuit_map[source_circuit] = new_circuit + + for(var/obj/item/integrated_circuit/source_circuit in source.contents) + if(!source_circuit.removable) + continue + + var/obj/item/integrated_circuit/new_circuit = circuit_map[source_circuit] + if(!new_circuit) + continue + + clone_io_list_data(source_circuit.inputs, new_circuit.inputs) + clone_io_list_data(source_circuit.outputs, new_circuit.outputs) + clone_io_list_data(source_circuit.activators, new_circuit.activators) + source_circuit.copy_clone_state_to(new_circuit) + + for(var/obj/item/integrated_circuit/source_circuit in source.contents) + if(!source_circuit.removable) + continue + + var/obj/item/integrated_circuit/new_circuit = circuit_map[source_circuit] + if(!new_circuit) + continue + + copy_cloned_links(source_circuit.inputs, new_circuit.inputs, circuit_map) + copy_cloned_links(source_circuit.outputs, new_circuit.outputs, circuit_map) + copy_cloned_links(source_circuit.activators, new_circuit.activators, circuit_map) + + return TRUE + + +/obj/item/integrated_circuit_printer/proc/clone_assembly(obj/item/electronic_assembly/source, atom/location) + if(!source || !location) + return null + + var/obj/item/electronic_assembly/clone = new source.type(location, TRUE) + + if(!copy_assembly_into_existing(source, clone)) + qdel(clone) + return null + + return clone + + +/obj/item/integrated_circuit_printer/proc/copy_cloned_links(list/source_pins, list/target_pins, list/circuit_map) + var/pin_count = min(source_pins.len, target_pins.len) + + for(var/i = 1 to pin_count) + var/datum/integrated_io/source_pin = source_pins[i] + var/datum/integrated_io/target_pin = target_pins[i] + + for(var/datum/integrated_io/source_link in source_pin.linked) + var/obj/item/integrated_circuit/linked_source_circuit = source_link.holder + var/obj/item/integrated_circuit/linked_new_circuit = circuit_map[linked_source_circuit] + if(!linked_new_circuit) + continue + + var/datum/integrated_io/linked_new_pin = null + var/source_link_index = linked_source_circuit.inputs.Find(source_link) + + if(source_link_index) + linked_new_pin = linked_new_circuit.inputs[source_link_index] + else + source_link_index = linked_source_circuit.outputs.Find(source_link) + if(source_link_index) + linked_new_pin = linked_new_circuit.outputs[source_link_index] + else + source_link_index = linked_source_circuit.activators.Find(source_link) + if(source_link_index) + linked_new_pin = linked_new_circuit.activators[source_link_index] + + if(linked_new_pin) + target_pin.linked |= linked_new_pin + + +/proc/ic_copy_clone_value(value) + if(islist(value)) + var/list/source_list = value + var/list/new_list = list() + + for(var/entry in source_list) + var/associated_value = source_list[entry] + if(!isnull(associated_value)) + new_list[ic_copy_clone_value(entry)] = ic_copy_clone_value(associated_value) + else + new_list += list(ic_copy_clone_value(entry)) + + return new_list + + return value + + +/obj/item/integrated_circuit_printer/proc/make_saveable_clone_value(value) + if(isnull(value) || isnum(value) || istext(value)) + return value + + if(islist(value)) + var/list/source_list = value + var/list/new_list = list() + + for(var/entry in source_list) + var/associated_value = source_list[entry] + if(!isnull(associated_value)) + new_list[make_saveable_clone_value(entry)] = make_saveable_clone_value(associated_value) + else + new_list += list(make_saveable_clone_value(entry)) + + return new_list + + return null + + +/obj/item/integrated_circuit_printer/proc/serialize_pin_data_list(list/pins) + var/list/pin_data = list() + + for(var/datum/integrated_io/pin in pins) + pin_data += list(make_saveable_clone_value(pin.data)) + + return pin_data + + +/obj/item/integrated_circuit_printer/proc/get_pin_group_name(obj/item/integrated_circuit/circuit, datum/integrated_io/pin) + if(circuit.inputs.Find(pin)) + return "inputs" + + if(circuit.outputs.Find(pin)) + return "outputs" + + if(circuit.activators.Find(pin)) + return "activators" + + return null + + +/obj/item/integrated_circuit_printer/proc/get_pin_group_index(obj/item/integrated_circuit/circuit, datum/integrated_io/pin) + var/index = circuit.inputs.Find(pin) + if(index) + return index + + index = circuit.outputs.Find(pin) + if(index) + return index + + index = circuit.activators.Find(pin) + if(index) + return index + + return 0 + + +/obj/item/integrated_circuit_printer/proc/serialize_pin_links(obj/item/integrated_circuit/source_circuit, datum/integrated_io/source_pin, list/circuit_order) + var/list/links = list() + + for(var/datum/integrated_io/source_link in source_pin.linked) + var/obj/item/integrated_circuit/linked_circuit = source_link.holder + var/linked_circuit_index = circuit_order.Find(linked_circuit) + if(!linked_circuit_index) + continue + + var/linked_group = get_pin_group_name(linked_circuit, source_link) + var/linked_pin_index = get_pin_group_index(linked_circuit, source_link) + + if(!linked_group || !linked_pin_index) + continue + + links += list(list( + "circuit" = linked_circuit_index, + "group" = linked_group, + "pin" = linked_pin_index + )) + + return links + + +/obj/item/integrated_circuit_printer/proc/serialize_pin_link_list(obj/item/integrated_circuit/source_circuit, list/pins, list/circuit_order) + var/list/link_data = list() + + for(var/datum/integrated_io/source_pin in pins) + link_data += list(serialize_pin_links(source_circuit, source_pin, circuit_order)) + + return link_data + + +/obj/item/integrated_circuit_printer/proc/serialize_clone_blueprint(obj/item/electronic_assembly/source, obj/item/host = null) + if(!source) + return null + + var/list/circuit_order = list() + + // Keep removable circuits first so existing blueprint imports retain their old circuit index behavior. + // Built-in circuits are appended after them so exported JSON still records links to and from built-ins. + for(var/obj/item/integrated_circuit/source_circuit in source.contents) + if(!source_circuit.removable) + continue + circuit_order += source_circuit + + for(var/obj/item/integrated_circuit/source_circuit in source.contents) + if(source_circuit.removable) + continue + circuit_order += source_circuit + + var/list/circuits = list() + + for(var/obj/item/integrated_circuit/source_circuit in circuit_order) + var/list/circuit_data = list() + circuit_data["type"] = "[source_circuit.type]" + circuit_data["displayed_name"] = source_circuit.displayed_name + circuit_data["removable"] = source_circuit.removable + circuit_data["inputs"] = serialize_pin_data_list(source_circuit.inputs) + circuit_data["outputs"] = serialize_pin_data_list(source_circuit.outputs) + circuit_data["activators"] = serialize_pin_data_list(source_circuit.activators) + circuit_data["input_links"] = serialize_pin_link_list(source_circuit, source_circuit.inputs, circuit_order) + circuit_data["output_links"] = serialize_pin_link_list(source_circuit, source_circuit.outputs, circuit_order) + circuit_data["activator_links"] = serialize_pin_link_list(source_circuit, source_circuit.activators, circuit_order) + + if(istype(source_circuit, /obj/item/integrated_circuit/memory/constant)) + var/obj/item/integrated_circuit/memory/constant/C = source_circuit + circuit_data["constant_data"] = make_saveable_clone_value(C.data) + + circuits += list(circuit_data) + + // Serialized assembly data used for import/export cloning. + var/list/blueprint = list() + blueprint["assembly_type"] = "[source.type]" + blueprint["name"] = source.name + blueprint["detail_color"] = source.detail_color + blueprint["circuits"] = circuits + if(istype(host, /obj/item/radio/headset/circuitry)) + var/obj/item/radio/headset/circuitry/headset = host + var/host_type = headset.get_clone_host_type() + if(host_type && ispath(host_type, /obj/item)) + blueprint["host_type"] = "[host_type]" + + return blueprint + + +/obj/item/integrated_circuit_printer/proc/apply_blueprint_pin_data(list/source_data, list/target_pins) + if(!source_data || !target_pins) + return + + var/pin_count = min(source_data.len, target_pins.len) + + for(var/i = 1 to pin_count) + var/datum/integrated_io/target_pin = target_pins[i] + target_pin.write_data_to_pin(source_data[i]) + + +/obj/item/integrated_circuit_printer/proc/get_blueprint_pin(obj/item/integrated_circuit/circuit, group, pin_index) + if(!circuit || !group || !pin_index) + return null + + if(group == "inputs") + if(pin_index <= circuit.inputs.len) + return circuit.inputs[pin_index] + + if(group == "outputs") + if(pin_index <= circuit.outputs.len) + return circuit.outputs[pin_index] + + if(group == "activators") + if(pin_index <= circuit.activators.len) + return circuit.activators[pin_index] + + return null + + +/obj/item/integrated_circuit_printer/proc/apply_blueprint_links(list/source_link_data, list/source_pins, list/new_circuits) + if(!source_link_data || !source_pins || !new_circuits) + return + + var/pin_count = min(source_link_data.len, source_pins.len) + + for(var/i = 1 to pin_count) + var/datum/integrated_io/source_pin = source_pins[i] + var/list/links = source_link_data[i] + + if(!links) + continue + + for(var/list/link_data in links) + var/linked_circuit_index = link_data["circuit"] + var/linked_group = link_data["group"] + var/linked_pin_index = link_data["pin"] + + if(!linked_circuit_index || linked_circuit_index > new_circuits.len) + continue + + var/obj/item/integrated_circuit/linked_circuit = new_circuits[linked_circuit_index] + var/datum/integrated_io/linked_pin = get_blueprint_pin(linked_circuit, linked_group, linked_pin_index) + + if(linked_pin) + source_pin.linked |= linked_pin + + +/obj/item/integrated_circuit_printer/proc/find_matching_blueprint_builtin(obj/item/electronic_assembly/clone, circuit_path, list/used_builtins) + if(!clone || !circuit_path) + return null + + for(var/obj/item/integrated_circuit/clone_circuit in clone.contents) + if(clone_circuit.removable) + continue + + if(clone_circuit.type != circuit_path) + continue + + if(used_builtins.Find(clone_circuit)) + continue + + used_builtins += clone_circuit + return clone_circuit + + return null + + +/obj/item/integrated_circuit_printer/proc/copy_assembly_into_existing_from_blueprint(list/blueprint, obj/item/electronic_assembly/clone) + if(!blueprint || !clone) + return FALSE + + clone.name = blueprint["name"] + clone.detail_color = blueprint["detail_color"] + clone.opened = FALSE + clone.update_icon() + + var/list/new_circuits = list() + var/list/used_builtins = list() + var/list/circuit_blueprints = blueprint["circuits"] + + // Build an index-aligned list that mirrors the blueprint circuit order. + // Removable circuits are newly printed. Built-in circuits are mapped to the + // clone assembly's existing non-removable circuits so links to built-ins survive. + for(var/list/circuit_data in circuit_blueprints) + var/circuit_path = text2path(circuit_data["type"]) + + if(!circuit_path || !ispath(circuit_path, /obj/item/integrated_circuit)) + new_circuits += null + continue + + var/obj/item/integrated_circuit/new_circuit = null + + if(circuit_data["removable"]) + new_circuit = new circuit_path(clone) + new_circuit.displayed_name = circuit_data["displayed_name"] + new_circuit.removable = circuit_data["removable"] + new_circuit.assembly = clone + clone.force_add_circuit(new_circuit) + else + new_circuit = find_matching_blueprint_builtin(clone, circuit_path, used_builtins) + + if(new_circuit) + apply_blueprint_pin_data(circuit_data["inputs"], new_circuit.inputs) + apply_blueprint_pin_data(circuit_data["outputs"], new_circuit.outputs) + apply_blueprint_pin_data(circuit_data["activators"], new_circuit.activators) + + if(istype(new_circuit, /obj/item/integrated_circuit/memory/constant)) + var/obj/item/integrated_circuit/memory/constant/C = new_circuit + C.data = circuit_data["constant_data"] + C.accepting_refs = FALSE + + new_circuits += new_circuit + + // Apply links by blueprint index. Because new_circuits is index-aligned, + // links to removable circuits and built-in circuits both resolve correctly. + for(var/i = 1 to circuit_blueprints.len) + var/list/circuit_data = circuit_blueprints[i] + var/obj/item/integrated_circuit/new_circuit = new_circuits[i] + + if(!new_circuit) + continue + + apply_blueprint_links(circuit_data["input_links"], new_circuit.inputs, new_circuits) + apply_blueprint_links(circuit_data["output_links"], new_circuit.outputs, new_circuits) + apply_blueprint_links(circuit_data["activator_links"], new_circuit.activators, new_circuits) + + return TRUE + +/obj/item/integrated_circuit_printer/proc/clone_assembly_from_blueprint(list/blueprint, atom/location) + if(!blueprint || !location) + return null + + var/assembly_path = text2path(blueprint["assembly_type"]) + if(!assembly_path || !ispath(assembly_path, /obj/item/electronic_assembly)) + return null + + var/host_path = text2path(blueprint["host_type"]) + if(host_path && ispath(host_path, /obj/item)) + // Some host items, such as circuitry headsets, need their own clone helper to + // create and attach the internal assembly correctly. A raw new host may not + // have a cloneable assembly available yet, which caused imported headset + // blueprints to decode successfully but fail at print completion. + if(ispath(host_path, /obj/item/radio/headset/circuitry)) + var/obj/item/radio/headset/circuitry/source_headset = new host_path(src) + var/obj/item/electronic_assembly/source_assembly = new assembly_path(src, TRUE) + + if(copy_assembly_into_existing_from_blueprint(blueprint, source_assembly)) + var/obj/item/cloned_host = source_headset.clone_with_integrated_assembly(location, src, source_assembly) + qdel(source_assembly) + qdel(source_headset) + return cloned_host + + qdel(source_assembly) + qdel(source_headset) + return null + + var/obj/item/host = new host_path(location) + + if(istype(host, /obj/item/radio/headset/circuitry)) + var/obj/item/radio/headset/circuitry/headset = host + var/obj/item/electronic_assembly/host_assembly = headset.get_cloneable_assembly() + if(istype(host_assembly)) + if(copy_assembly_into_existing_from_blueprint(blueprint, host_assembly)) + return host + + qdel(host) + return null + + var/obj/item/electronic_assembly/clone = new assembly_path(location, TRUE) + + if(!copy_assembly_into_existing_from_blueprint(blueprint, clone)) + qdel(clone) + return null + + return clone + + +/obj/item/integrated_circuit_printer/proc/get_clone_blueprint_metal_cost(list/blueprint) + if(!blueprint) + return 0 + + var/assembly_path = text2path(blueprint["assembly_type"]) + if(!assembly_path || !ispath(assembly_path, /obj/item/electronic_assembly)) + return 0 + + var/obj/item/electronic_assembly/E = assembly_path + var/case_cost = round((initial(E.max_complexity) + initial(E.max_components)) / 4) + + var/component_cost = 0 + var/list/circuit_blueprints = blueprint["circuits"] + if(!islist(circuit_blueprints)) + return 0 + + for(var/list/circuit_data in circuit_blueprints) + if(!islist(circuit_data)) + continue + if(!circuit_data["removable"]) + continue + + var/circuit_path = text2path(circuit_data["type"]) + if(!circuit_path || !ispath(circuit_path, /obj/item/integrated_circuit)) + continue + + var/obj/item/integrated_circuit/IC = circuit_path + component_cost += max(1, initial(IC.w_class)) + + return max(1, case_cost + component_cost) + + +/obj/item/integrated_circuit_printer/proc/get_clone_blueprint_phoron_cost(list/blueprint) + if(!blueprint) + return 0 + + var/component_cost = 0 + var/list/circuit_blueprints = blueprint["circuits"] + if(!islist(circuit_blueprints)) + return 0 + + for(var/list/circuit_data in circuit_blueprints) + if(!islist(circuit_data)) + continue + if(!circuit_data["removable"]) + continue + + var/circuit_path = text2path(circuit_data["type"]) + if(!circuit_path || !ispath(circuit_path, /obj/item/integrated_circuit)) + continue + + component_cost += get_circuit_phoron_cost(circuit_path) + + return component_cost + + +/obj/item/integrated_circuit_printer/proc/get_clone_blueprint_print_time(list/blueprint) + if(!blueprint) + return clone_min_print_time + + var/cost = get_clone_blueprint_metal_cost(blueprint) + var/effective_max_cost = max(1, max_metal) + var/cost_ratio = cost / effective_max_cost + + if(cost_ratio < 0) + cost_ratio = 0 + if(cost_ratio > 1) + cost_ratio = 1 + + var/scaled_time = clone_min_print_time + ((clone_max_print_time - clone_min_print_time) * cost_ratio) + + if(scaled_time < clone_min_print_time) + scaled_time = clone_min_print_time + if(scaled_time > clone_max_print_time) + scaled_time = clone_max_print_time + + return round(scaled_time) + + +/obj/item/integrated_circuit/proc/copy_clone_state_to(obj/item/integrated_circuit/target) + return + + +/obj/item/integrated_circuit/memory/constant/copy_clone_state_to(obj/item/integrated_circuit/target) + var/obj/item/integrated_circuit/memory/constant/new_constant = target + if(!istype(new_constant)) + return + + if(islist(data)) + new_constant.data = ic_copy_clone_value(data) + else + new_constant.data = data + + new_constant.accepting_refs = FALSE + + +/obj/item/integrated_circuit_printer/proc/can_print(build_type) + var/list/recipes = upgraded ? SSelectronics.printer_recipe_list_upgraded : SSelectronics.printer_recipe_list_basic + var/build_path = "[build_type]" + + for(var/list/recipe in recipes) + if(recipe["path"] == build_path) + return TRUE + + return FALSE + + +/obj/item/integrated_circuit_printer/proc/is_printed_assembly_container_type(build_type) + return ispath(build_type, /obj/item/electronic_assembly) \ + || ispath(build_type, /obj/item/implant/integrated_circuit) \ + || ispath(build_type, /obj/item/clothing/under/circuitry) \ + || ispath(build_type, /obj/item/clothing/gloves/circuitry) \ + || ispath(build_type, /obj/item/clothing/glasses/circuitry) \ + || ispath(build_type, /obj/item/clothing/shoes/circuitry) \ + || ispath(build_type, /obj/item/clothing/head/circuitry) \ + || ispath(build_type, /obj/item/clothing/ears/circuitry) \ + || ispath(build_type, /obj/item/radio/headset/circuitry) \ + || ispath(build_type, /obj/item/clothing/suit/circuitry) + + /obj/item/disk/integrated_circuit/upgrade name = "integrated circuit printer upgrade disk" desc = "Install this into your integrated circuit printer to enhance it." @@ -137,11 +1216,12 @@ w_class = WEIGHT_CLASS_SMALL origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 4) + /obj/item/disk/integrated_circuit/upgrade/advanced name = "integrated circuit printer upgrade disk - advanced designs" desc = "Install this into your integrated circuit printer to enhance it. This one adds new, advanced designs to the printer." -// To be implemented later. + /obj/item/disk/integrated_circuit/upgrade/clone name = "integrated circuit printer upgrade disk - circuit cloner" desc = "Install this into your integrated circuit printer to enhance it. This one allows the printer to duplicate assemblies." diff --git a/code/modules/integrated_electronics/core/special_pins/boolean_pin.dm b/code/modules/integrated_electronics/core/special_pins/boolean_pin.dm index 7e769d862fd..14e23561616 100644 --- a/code/modules/integrated_electronics/core/special_pins/boolean_pin.dm +++ b/code/modules/integrated_electronics/core/special_pins/boolean_pin.dm @@ -1,3 +1,8 @@ +/* + * core/special_pins/boolean_pin.dm + * Boolean pin type behavior and conversion for TRUE/FALSE style circuit values. + */ + // These pins only contain 0 or 1. Null is not allowed. /datum/integrated_io/boolean name = "boolean pin" diff --git a/code/modules/integrated_electronics/core/special_pins/char_pin.dm b/code/modules/integrated_electronics/core/special_pins/char_pin.dm index d20eb38f56e..2171d4c231f 100644 --- a/code/modules/integrated_electronics/core/special_pins/char_pin.dm +++ b/code/modules/integrated_electronics/core/special_pins/char_pin.dm @@ -1,3 +1,8 @@ +/* + * core/special_pins/char_pin.dm + * Character/string single-character pin behavior and validation. + */ + // These pins can only contain a 1 character string or null. /datum/integrated_io/char name = "char pin" diff --git a/code/modules/integrated_electronics/core/special_pins/color_pin.dm b/code/modules/integrated_electronics/core/special_pins/color_pin.dm index c5fe37ffe51..192f62295b7 100644 --- a/code/modules/integrated_electronics/core/special_pins/color_pin.dm +++ b/code/modules/integrated_electronics/core/special_pins/color_pin.dm @@ -1,3 +1,8 @@ +/* + * core/special_pins/color_pin.dm + * Color pin parsing, validation, and display handling for circuit color values. + */ + // These pins can only contain a color (in the form of #FFFFFF) or null. /datum/integrated_io/color name = "color pin" @@ -13,18 +18,19 @@ if(isnull(new_data) || istext(new_data)) if(istext(new_data)) new_data = uppertext(new_data) - if(length(new_data) != 7) // We can hex if we want to, we can leave your strings behind - return // Cause your strings don't hex and if they don't hex - var/friends = copytext(new_data, 2, 8) // Well they're are no strings of mine - // I say, we can go where we want to, a place where they will never find + if(length(new_data) != 7) + return + if(copytext(new_data, 1, 2) != "#") + return + var/hex_digits = copytext(new_data, 2, 8) var/safety_dance = 1 - while(safety_dance >= 7) // And we can act like we come from out of this world.log - var/hex = copytext(friends, safety_dance, safety_dance+1) - if(!(hex in list("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"))) - return // Leave the fake one far behind, + while(safety_dance <= 6) + var/hex = copytext(hex_digits, safety_dance, safety_dance + 1) + if(!findtext("0123456789ABCDEF", hex)) + return safety_dance++ - data = new_data // And we can hex + data = new_data holder.on_data_written() // This randomizes the color. diff --git a/code/modules/integrated_electronics/core/special_pins/dir_pin.dm b/code/modules/integrated_electronics/core/special_pins/dir_pin.dm index 25a6c5c7c31..8aadba10176 100644 --- a/code/modules/integrated_electronics/core/special_pins/dir_pin.dm +++ b/code/modules/integrated_electronics/core/special_pins/dir_pin.dm @@ -1,3 +1,8 @@ +/* + * core/special_pins/dir_pin.dm + * Direction pin conversion and validation for BYOND direction constants. + */ + // These pins can only contain directions (1,2,4,8...) or null. /datum/integrated_io/dir name = "dir pin" diff --git a/code/modules/integrated_electronics/core/special_pins/list_pin.dm b/code/modules/integrated_electronics/core/special_pins/list_pin.dm index 9a60283d356..f9fd9b08653 100644 --- a/code/modules/integrated_electronics/core/special_pins/list_pin.dm +++ b/code/modules/integrated_electronics/core/special_pins/list_pin.dm @@ -1,4 +1,12 @@ -// These pins contain a list. Null is not allowed. +/* + * core/special_pins/list_pin.dm + * List pin serialization, display, cloning, and safety handling for list-valued circuit data. + */ + +// These pins contain a list. +// Lists may contain strings, numbers, weakrefs, nested lists, or null. +// Individual circuits are responsible for deciding what data types they accept. + /datum/integrated_io/list name = "list pin" data = list() @@ -6,14 +14,10 @@ /datum/integrated_io/list/ask_for_pin_data(mob/user) interact(user) -// Positionless remove/Edit are a bit weird, -// not sure if adding these buttons is quite a good idea. -// They introduce uncertainty, since, in case of 2 elements, -// they will work just with the 1st one /datum/integrated_io/list/proc/interact(mob/user) var/list/my_list = data var/t = "

[src]


" - t += "List length: [my_list.len]
" + t += "List length: [length(my_list)]
" t += "Refresh | " t += "Add | " t += "Remove | " @@ -21,113 +25,161 @@ t += "Swap | " t += "Clear
" t += "
" - // Iterating by index simplifies editing/deletion in game, - // since the href_list["pos"] var is consistent + for(var/i = 1; i <= length(my_list); i++) t += "#[i] | [display_data(my_list[i])] | " t += "Edit | " t += "Remove
" + var/datum/browser/B = new(user, "list_pin_[REF(src)]", null, 500, 400) B.set_content(t) B.open(FALSE) /datum/integrated_io/list/proc/add_to_list(mob/user, new_entry) - if(!new_entry && user) - new_entry = ask_for_data_type(user) - // is_valid can't be used here, since is_valid checks "data" and has no arguments - if(!isnull(new_entry)) - Add(new_entry) + if(isnull(new_entry) && user) + new_entry = ask_for_data_type(user, null, list("string", "number", "null")) + + Add(new_entry) /datum/integrated_io/list/proc/Add(new_entry) + if(!islist(data)) + data = list() + var/list/my_list = data - if(my_list.len > IC_MAX_LIST_LENGTH) + + if(length(my_list) >= IC_MAX_LIST_LENGTH) my_list.Cut(1, 2) - my_list.Add(new_entry) + + my_list += list(new_entry) + holder.on_data_written() /datum/integrated_io/list/proc/remove_from_list_by_position(mob/user, position) var/list/my_list = data - if(!my_list.len) + + if(!length(my_list)) to_chat(user, SPAN_WARNING("The list is empty, there's nothing to remove.")) return - if(!position) + + if(isnull(position)) return - if(position > my_list.len) + + position = round(position) + + if(position < 1 || position > length(my_list)) return - var/target_entry = my_list[position] - // Should be able to remove nulls, since we can add a null to the list from the outside. - my_list -= target_entry + + my_list.Cut(position, position + 1) + holder.on_data_written() /datum/integrated_io/list/proc/remove_from_list(mob/user, target_entry) var/list/my_list = data - if(!my_list.len) + + if(!length(my_list)) to_chat(user, SPAN_WARNING("The list is empty, there's nothing to remove.")) return - if(!target_entry) + + if(isnull(target_entry)) target_entry = input("Which piece of data do you want to remove?", "Remove") as null|anything in my_list - if(target_entry) - my_list -= target_entry + + if(!isnull(target_entry)) + var/position = my_list.Find(target_entry) + if(position) + my_list.Cut(position, position + 1) + holder.on_data_written() /datum/integrated_io/list/proc/edit_in_list(mob/user, target_entry) var/list/my_list = data - if(!my_list.len) + + if(!length(my_list)) to_chat(user, SPAN_WARNING("The list is empty, there's nothing to modify.")) return - if(!target_entry) + + if(isnull(target_entry)) target_entry = input("Which piece of data do you want to edit?", "Edit") as null|anything in my_list - if(target_entry) - var/edited_entry = ask_for_data_type(user, target_entry) - var/i = my_list.Find(target_entry) - if(edited_entry) - my_list[i] = edited_entry -/datum/integrated_io/list/proc/edit_in_list_by_position(mob/user, var/position) + if(!isnull(target_entry)) + var/i = my_list.Find(target_entry) + if(i) + var/edited_entry = ask_for_data_type(user, target_entry, list("string", "number", "null")) + my_list[i] = edited_entry + holder.on_data_written() + +/datum/integrated_io/list/proc/edit_in_list_by_position(mob/user, position) var/list/my_list = data - if(!my_list.len) + + if(!length(my_list)) to_chat(user, SPAN_WARNING("The list is empty, there's nothing to modify.")) return - if(!position) - return - if(position > my_list.len) - return - var/target_entry = my_list[position] - if(target_entry) - var/edited_entry = ask_for_data_type(user, target_entry) - if(edited_entry) - my_list[position] = edited_entry -/datum/integrated_io/list/proc/swap_inside_list(mob/user, var/first_target, var/second_target) + if(isnull(position)) + return + + position = round(position) + + if(position < 1 || position > length(my_list)) + return + + var/target_entry = my_list[position] + var/edited_entry = ask_for_data_type(user, target_entry, list("string", "number", "null")) + my_list[position] = edited_entry + holder.on_data_written() + +/datum/integrated_io/list/proc/swap_inside_list(mob/user, first_target, second_target) var/list/my_list = data - if(my_list.len <= 1) + + if(length(my_list) <= 1) to_chat(user, SPAN_WARNING("The list is empty, or too small to do any meaningful swapping.")) return - if(!first_target) + + if(isnull(first_target)) first_target = input("Which piece of data do you want to swap? (1)", "Swap") as null|anything in my_list - if(first_target) - if(!second_target) - second_target = input("Which piece of data do you want to swap? (2)", "Swap") as null|anything in my_list - first_target + if(isnull(first_target)) + return - if(second_target) - var/first_pos = my_list.Find(first_target) - var/second_pos = my_list.Find(second_target) - my_list.Swap(first_pos, second_pos) + if(isnull(second_target)) + second_target = input("Which piece of data do you want to swap? (2)", "Swap") as null|anything in my_list - first_target + + if(isnull(second_target)) + return + + var/first_pos = my_list.Find(first_target) + var/second_pos = my_list.Find(second_target) + + if(first_pos && second_pos) + my_list.Swap(first_pos, second_pos) + holder.on_data_written() /datum/integrated_io/list/proc/clear_list(mob/user) var/list/my_list = data my_list.Cut() + holder.on_data_written() /datum/integrated_io/list/scramble() var/list/my_list = data - my_list = shuffle(my_list) + data = shuffle(my_list) push_data() /datum/integrated_io/list/write_data_to_pin(var/new_data) - if(islist(new_data)) - var/list/new_list = new_data - // Sanitize the input - no nulls allowed. - while(new_list.Remove(null) == 1) - data = new_list.Copy() + if(isnull(new_data)) + data = list() holder.on_data_written() + return + + if(!islist(new_data)) + return + + var/list/new_list = new_data + var/list/sanitized = list() + + for(var/value in new_list) + if(sanitized.len >= IC_MAX_LIST_LENGTH) + break + if(isnull(value) || isnum(value) || istext(value) || isweakref(value) || islist(value)) + sanitized += list(value) + + data = sanitized + holder.on_data_written() /datum/integrated_io/list/display_pin_type() return IC_FORMAT_LIST @@ -135,8 +187,9 @@ /datum/integrated_io/list/Topic(href, href_list, state = GLOB.always_state) if(!holder.check_interactivity(usr)) return + if(..()) - return 1 + return TRUE if(href_list["add"]) add_to_list(usr) @@ -159,5 +212,7 @@ else edit_in_list(usr) - holder.interact(usr) // Refresh the main UI, - interact(usr) // and the list UI. + holder.interact(usr) + interact(usr) + + return TRUE diff --git a/code/modules/integrated_electronics/core/special_pins/number_pin.dm b/code/modules/integrated_electronics/core/special_pins/number_pin.dm index 49feea1d36b..2a23b273cfe 100644 --- a/code/modules/integrated_electronics/core/special_pins/number_pin.dm +++ b/code/modules/integrated_electronics/core/special_pins/number_pin.dm @@ -1,3 +1,8 @@ +/* + * core/special_pins/number_pin.dm + * Number pin conversion, validation, and formatting. + */ + // These pins can only contain numbers (int and floating point) or null. /datum/integrated_io/number name = "number pin" diff --git a/code/modules/integrated_electronics/core/special_pins/ref_pin.dm b/code/modules/integrated_electronics/core/special_pins/ref_pin.dm index 90901357a23..029678f11df 100644 --- a/code/modules/integrated_electronics/core/special_pins/ref_pin.dm +++ b/code/modules/integrated_electronics/core/special_pins/ref_pin.dm @@ -1,3 +1,8 @@ +/* + * core/special_pins/ref_pin.dm + * Reference pin behavior for passing object references between circuits. + */ + // These pins only contain weakrefs or null. /datum/integrated_io/ref name = "ref pin" diff --git a/code/modules/integrated_electronics/core/special_pins/string_pin.dm b/code/modules/integrated_electronics/core/special_pins/string_pin.dm index 8b1320469b5..0ac9b021bcf 100644 --- a/code/modules/integrated_electronics/core/special_pins/string_pin.dm +++ b/code/modules/integrated_electronics/core/special_pins/string_pin.dm @@ -1,3 +1,8 @@ +/* + * core/special_pins/string_pin.dm + * String pin conversion, validation, and formatting. + */ + // These pins can only contain text and null. /datum/integrated_io/string name = "string pin" diff --git a/code/modules/integrated_electronics/core/tools.dm b/code/modules/integrated_electronics/core/tools.dm index 0760693b83d..211c7f97fdb 100644 --- a/code/modules/integrated_electronics/core/tools.dm +++ b/code/modules/integrated_electronics/core/tools.dm @@ -1,3 +1,8 @@ +/* + * core/tools.dm + * Tools used to manipulate integrated electronics, such as wiring, debugging, and assembly maintenance helpers. + */ + #define WIRE "wire" #define WIRING "wiring" #define UNWIRE "unwire" @@ -176,6 +181,7 @@ icon_state = "detailer" item_flags = ITEM_FLAG_NO_BLUDGEON w_class = WEIGHT_CLASS_SMALL + // User-selected detail color used for assembly and wearable overlays. var/detail_color = COLOR_ASSEMBLY_WHITE var/static/list/color_list = list( "black" = COLOR_ASSEMBLY_BLACK, @@ -217,7 +223,7 @@ /obj/item/storage/bag/circuits name = "circuit kit" - desc = "This kit is essential for any circuitry projects." + desc = "A kit containing basic tools and parts for circuit projects." icon = 'icons/obj/assemblies/electronic_tools.dmi' icon_state = "circuit_kit" w_class = WEIGHT_CLASS_NORMAL @@ -279,7 +285,7 @@ /obj/item/storage/bag/circuits/mini name = "circuit box" - desc = "Used to partition categories of circuits, for a neater workspace." + desc = "Stores circuits by category for easier organization." w_class = WEIGHT_CLASS_SMALL display_contents_with_number = TRUE pickup_blacklist = list( @@ -299,7 +305,7 @@ /obj/item/storage/bag/circuits/mini/arithmetic name = "arithmetic circuit box" - desc = "Warning: Contains math." + desc = "Contains arithmetic and numeric utility circuits." icon_state = "box_arithmetic" spawn_types = list( /obj/item/integrated_circuit/arithmetic @@ -310,7 +316,7 @@ /obj/item/storage/bag/circuits/mini/trig name = "trig circuit box" - desc = "Danger: Contains more math." + desc = "Contains trigonometric circuits." icon_state = "box_trig" spawn_types = list( /obj/item/integrated_circuit/trig @@ -321,7 +327,7 @@ /obj/item/storage/bag/circuits/mini/input name = "input circuit box" - desc = "Tell these circuits everything you know." + desc = "Contains circuits that read data from the surrounding environment." icon_state = "box_input" spawn_types = list( /obj/item/integrated_circuit/input @@ -332,7 +338,7 @@ /obj/item/storage/bag/circuits/mini/output name = "output circuit box" - desc = "Circuits to interface with the world beyond itself." + desc = "Contains circuits that display, announce, or transmit data." icon_state = "box_output" spawn_types = list( /obj/item/integrated_circuit/output @@ -343,7 +349,7 @@ /obj/item/storage/bag/circuits/mini/memory name = "memory circuit box" - desc = "Machines can be quite forgetful without these." + desc = "Contains memory circuits for storing circuit data." icon_state = "box_memory" spawn_types = list( /obj/item/integrated_circuit/memory @@ -354,7 +360,7 @@ /obj/item/storage/bag/circuits/mini/logic name = "logic circuit box" - desc = "May or may not be Turing complete." + desc = "Contains logic circuits for boolean-style control and comparisons." icon_state = "box_logic" spawn_types = list( /obj/item/integrated_circuit/logic @@ -365,18 +371,55 @@ /obj/item/storage/bag/circuits/mini/time name = "time circuit box" - desc = "No time machine parts, sadly." + desc = "Contains timer and clock circuits." icon_state = "box_time" spawn_types = list( /obj/item/integrated_circuit/time ) +/obj/item/storage/bag/circuits/mini/time/fill() + var/list/basic_time_circuits = list( + /obj/item/integrated_circuit/time/delay, + /obj/item/integrated_circuit/time/delay/five_sec, + /obj/item/integrated_circuit/time/delay/one_sec, + /obj/item/integrated_circuit/time/delay/half_sec, + /obj/item/integrated_circuit/time/delay/tenth_sec, + /obj/item/integrated_circuit/time/ticker/fast, + /obj/item/integrated_circuit/time/ticker, + /obj/item/integrated_circuit/time/ticker/slow, + /obj/item/integrated_circuit/time/ticker/very_slow, + /obj/item/integrated_circuit/time/clock + ) + for(var/circuit_type in basic_time_circuits) + for(var/i in 1 to 4) + new circuit_type(src) + /obj/item/storage/bag/circuits/mini/time/all spawn_flags_to_use = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH +/obj/item/storage/bag/circuits/mini/time/all/fill() + var/list/all_time_circuits = list( + /obj/item/integrated_circuit/time/delay, + /obj/item/integrated_circuit/time/delay/five_sec, + /obj/item/integrated_circuit/time/delay/one_sec, + /obj/item/integrated_circuit/time/delay/half_sec, + /obj/item/integrated_circuit/time/delay/tenth_sec, + /obj/item/integrated_circuit/time/delay/custom, + /obj/item/integrated_circuit/time/ticker/rapid, + /obj/item/integrated_circuit/time/ticker/fast, + /obj/item/integrated_circuit/time/ticker, + /obj/item/integrated_circuit/time/ticker/slow, + /obj/item/integrated_circuit/time/ticker/very_slow, + /obj/item/integrated_circuit/time/ticker/custom, + /obj/item/integrated_circuit/time/clock + ) + for(var/circuit_type in all_time_circuits) + for(var/i in 1 to 4) + new circuit_type(src) + /obj/item/storage/bag/circuits/mini/reagents name = "reagent circuit box" - desc = "Unlike most electronics, these circuits are supposed to come in contact with liquids." + desc = "Contains circuits for handling reagents and containers." icon_state = "box_reagents" spawn_types = list( /obj/item/integrated_circuit/reagent @@ -387,7 +430,7 @@ /obj/item/storage/bag/circuits/mini/transfer name = "transfer circuit box" - desc = "Useful for moving data representing something arbitrary to another arbitrary virtual place." + desc = "Contains circuits for routing data between pins and devices." icon_state = "box_transfer" spawn_types = list( /obj/item/integrated_circuit/transfer @@ -398,7 +441,7 @@ /obj/item/storage/bag/circuits/mini/converter name = "converter circuit box" - desc = "Transform one piece of data to another type of data with these." + desc = "Contains circuits for converting data between supported formats." icon_state = "box_converter" spawn_types = list( /obj/item/integrated_circuit/converter @@ -409,7 +452,7 @@ /obj/item/storage/bag/circuits/mini/smart name = "smart box" - desc = "Sentience not included." + desc = "Contains smart control circuits for navigation and automation." icon_state = "box_ai" spawn_types = list( /obj/item/integrated_circuit/smart @@ -420,7 +463,7 @@ /obj/item/storage/bag/circuits/mini/manipulation name = "manipulation box" - desc = "Make your machines actually useful with these." + desc = "Contains circuits that perform physical actions through an assembly." icon_state = "box_manipulation" spawn_types = list( /obj/item/integrated_circuit/manipulation @@ -431,7 +474,7 @@ /obj/item/storage/bag/circuits/mini/power name = "power circuit box" - desc = "Electronics generally require electricity." + desc = "Contains circuits that generate, receive, or transmit power." icon_state = "box_power" spawn_types = list( /obj/item/integrated_circuit/passive/power, diff --git a/code/modules/integrated_electronics/passive/passive.dm b/code/modules/integrated_electronics/passive/passive.dm index 7678d02ad5f..5528a83623e 100644 --- a/code/modules/integrated_electronics/passive/passive.dm +++ b/code/modules/integrated_electronics/passive/passive.dm @@ -1,3 +1,8 @@ +/* + * passive/passive.dm + * Base passive circuit category for circuits that provide background behavior instead of triggered work. + */ + // 'Passive' components do not have any pins, and instead contribute in some form to the assembly holding them. /obj/item/integrated_circuit/passive inputs = list() diff --git a/code/modules/integrated_electronics/passive/power.dm b/code/modules/integrated_electronics/passive/power.dm index 68ec6336bc9..16f83b06992 100644 --- a/code/modules/integrated_electronics/passive/power.dm +++ b/code/modules/integrated_electronics/passive/power.dm @@ -1,7 +1,12 @@ +/* + * passive/power.dm + * Passive power circuits and power-network integration for assemblies. + */ + /obj/item/integrated_circuit/passive/power name = "power thingy" - desc = "Does power stuff." + desc = "Base type for integrated power circuits." complexity = 5 origin_tech = list(TECH_POWER = 2, TECH_ENGINEERING = 2, TECH_DATA = 2) category_text = "Power - Passive" @@ -12,14 +17,13 @@ // For calculators. /obj/item/integrated_circuit/passive/power/solar_cell name = "tiny photovoltaic cell" - desc = "It's a very tiny solar cell, generally used in calculators." + desc = "A very small solar cell for low-power assemblies." extended_desc = "The cell generates up to 100 W of energy in optimal lighting conditions. Less light will result in less power being generated." icon_state = "solar_cell" complexity = 8 origin_tech = list(TECH_POWER = 3, TECH_ENGINEERING = 3, TECH_DATA = 2) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - /// Multiplied by amount of lumens to get amount of power to generate. var/power_factor = 15 /obj/item/integrated_circuit/passive/power/solar_cell/make_energy() @@ -33,7 +37,7 @@ // For fat machines that need fat power, like drones. /obj/item/integrated_circuit/passive/power/relay name = "tesla power relay" - desc = "A seemingly enigmatic device which connects to nearby APCs wirelessly and draws power from them." + desc = "Connects to nearby APCs wirelessly and draws power from them." w_class = WEIGHT_CLASS_NORMAL extended_desc = "The siphon generates 1 kW of energy, so long as an APC is in the same room, with a cell that has energy. It will always drain \ from the 'equipment' power channel." @@ -46,7 +50,7 @@ // For really fat machines. /obj/item/integrated_circuit/passive/power/relay/large name = "large tesla power relay" - desc = "A seemingly enigmatic device which connects to nearby APCs wirelessly and draws power from them, now in industiral size!" + desc = "Connects to nearby APCs wirelessly and draws a larger amount of power from them." w_class = WEIGHT_CLASS_BULKY extended_desc = "The siphon generates 4 kW of energy, so long as an APC is in the same room, with a cell that has energy. It will always drain \ from the 'equipment' power channel." @@ -68,7 +72,7 @@ // For implants. /obj/item/integrated_circuit/passive/power/metabolic_siphon name = "metabolic siphon" - desc = "A complicated piece of technology which converts bodily nutriments of a host into electricity, or vice versa." + desc = "Converts a host's nutrients into electricity, or uses electricity to supply nutrients." extended_desc = "The siphon generates 10W of energy, so long as the siphon exists inside a biological entity. The entity will feel an increased \ appetite and will need to eat more often due to this. This device will fail if used inside synthetic entities.\ If the polarity is reversed, it will instead generate chemical energy with electricity, continuously consuming power from the assembly.\ @@ -122,7 +126,7 @@ name = "fuel cell" desc = "Produces electricity from chemicals." icon_state = "chemical_cell" - extended_desc = "This is effectively an internal beaker. It will consume and produce power from phoron, slime jelly, welding fuel, carbon,\ + extended_desc = "Functions as an internal beaker for the assembly. It will consume and produce power from phoron, slime jelly, welding fuel, carbon,\ ethanol, nutriments and blood, in order of decreasing efficiency. It will consume fuel only if the battery can take more energy." atom_flags = ATOM_FLAG_OPEN_CONTAINER @@ -162,25 +166,33 @@ name = "power network interface" desc = "Gives or takes power from a wire underneath the machine." icon_state = "powernet" - extended_desc = "The assembly must be anchored, with a wrench, and a wire node must be avaiable directly underneath.
\ + extended_desc = "The assembly must be anchored, with a wrench, and a wire node must be available directly underneath.
\ The first pin determines if power is moved at all. The second pin, if true, will draw from the powernet to charge the assembly's \ cell, otherwise it will give power from the cell to the powernet." complexity = 20 + size = 1 inputs = list( "active" = IC_PINTYPE_BOOLEAN, "draw power" = IC_PINTYPE_BOOLEAN - ) + ) outputs = list( "power in grid" = IC_PINTYPE_NUMBER, "surplus power" = IC_PINTYPE_NUMBER, "load" = IC_PINTYPE_NUMBER - ) + ) activators = list() spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 2, TECH_POWER = 2) var/obj/structure/machinery/power/circuit_io/IO = null // Dummy power machine to move energy in/out without a bunch of code duplication. var/throughput = 10000 // Give/take up to 10kW. +/obj/item/integrated_circuit/passive/power/powernet/builtin + name = "built-in power network interface" + desc = "A built-in power network interface for machine-integrated electronic assemblies." + complexity = 0 + size = 0 + spawn_flags = 0 + /obj/item/integrated_circuit/passive/power/powernet/Initialize() IO = new(src) return ..() diff --git a/code/modules/integrated_electronics/subtypes/arithmetic.dm b/code/modules/integrated_electronics/subtypes/arithmetic.dm index 160715ebdd5..f22697c7774 100644 --- a/code/modules/integrated_electronics/subtypes/arithmetic.dm +++ b/code/modules/integrated_electronics/subtypes/arithmetic.dm @@ -1,3 +1,8 @@ +/* + * subtypes/arithmetic.dm + * Arithmetic circuits for numeric operations and math transformations. + */ + //These circuits do simple math. /obj/item/integrated_circuit/arithmetic complexity = 1 @@ -13,7 +18,7 @@ ) outputs = list("result" = IC_PINTYPE_NUMBER) activators = list("compute" = IC_PINTYPE_PULSE_IN, "on computed" = IC_PINTYPE_PULSE_OUT) - category_text = "Arithmetic" + category_text = "MATH - Arithmetic" power_draw_per_use = 50 // Math is pretty cheap. // +Adding+ // @@ -21,7 +26,7 @@ /obj/item/integrated_circuit/arithmetic/addition name = "addition circuit" desc = "This circuit can add numbers together." - extended_desc = "The order that the calculation goes is;
\ + extended_desc = "Calculation order:
\ result = ((((A + B) + C) + D) ... ) and so on, until all pins have been added. \ Null pins are ignored." icon_state = "addition" @@ -43,7 +48,7 @@ /obj/item/integrated_circuit/arithmetic/subtraction name = "subtraction circuit" desc = "This circuit can subtract numbers." - extended_desc = "The order that the calculation goes is;
\ + extended_desc = "Calculation order:
\ result = ((((A - B) - C) - D) ... ) and so on, until all pins have been subtracted. \ Null pins are ignored. Pin A must be a number or the circuit will not function." icon_state = "subtraction" @@ -71,7 +76,7 @@ /obj/item/integrated_circuit/arithmetic/multiplication name = "multiplication circuit" desc = "This circuit can multiply numbers." - extended_desc = "The order that the calculation goes is;
\ + extended_desc = "Calculation order:
\ result = ((((A * B) * C) * D) ... ) and so on, until all pins have been multiplied. \ Null pins are ignored. Pin A must be a number or the circuit will not function." icon_state = "multiplication" @@ -98,8 +103,8 @@ /obj/item/integrated_circuit/arithmetic/division name = "division circuit" - desc = "This circuit can divide numbers, just don't think about trying to divide by zero!" - extended_desc = "The order that the calculation goes is;
\ + desc = "Divides one number by another. Division by zero is not valid." + extended_desc = "Calculation order:
\ result = ((((A / B) / C) / D) ... ) and so on, until all pins have been divided. \ Null pins, and pins containing 0, are ignored. Pin A must be a number or the circuit will not function." icon_state = "division" @@ -146,8 +151,8 @@ /obj/item/integrated_circuit/arithmetic/sign name = "sign circuit" - desc = "This will say if a number is positive, negative, or zero." - extended_desc = "Will output 1, -1, or 0, depending on if A is a postive number, a negative number, or zero, respectively." + desc = "Outputs whether a number is positive, negative, or zero." + extended_desc = "Outputs 1 for positive numbers, -1 for negative numbers, and 0 for zero." icon_state = "sign" inputs = list("A" = IC_PINTYPE_NUMBER) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -196,7 +201,7 @@ /obj/item/integrated_circuit/arithmetic/absolute name = "absolute circuit" - desc = "This outputs a non-negative version of the number you put in. This may also be thought of as its distance from zero." + desc = "Outputs the absolute value of A." icon_state = "absolute" inputs = list("A" = IC_PINTYPE_NUMBER) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -212,12 +217,70 @@ push_data() activate_pin(2) +// Clamp and deadband // + +/obj/item/integrated_circuit/arithmetic/clamp_deadband + name = "clamp and deadband circuit" + desc = "Clamps a number to a range and reports whether it is inside an optional deadband." + extended_desc = "If minimum and maximum are entered backward, they are swapped. Deadband radius values at or below zero disable the deadband check." + icon_state = "comparator" + complexity = 2 + inputs = list( + "value" = IC_PINTYPE_NUMBER, + "minimum" = IC_PINTYPE_NUMBER, + "maximum" = IC_PINTYPE_NUMBER, + "deadband center" = IC_PINTYPE_NUMBER, + "deadband radius" = IC_PINTYPE_NUMBER + ) + outputs = list( + "clamped value" = IC_PINTYPE_NUMBER, + "in range" = IC_PINTYPE_BOOLEAN, + "inside deadband" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "process" = IC_PINTYPE_PULSE_IN, + "on processed" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/arithmetic/clamp_deadband/do_work() + var/value = get_pin_data(IC_INPUT, 1) + var/minimum = get_pin_data(IC_INPUT, 2) + var/maximum = get_pin_data(IC_INPUT, 3) + var/deadband_center = get_pin_data(IC_INPUT, 4) + var/deadband_radius = get_pin_data(IC_INPUT, 5) + + if(!isnum(value)) + value = 0 + if(!isnum(minimum)) + minimum = value + if(!isnum(maximum)) + maximum = value + + if(minimum > maximum) + var/swap_value = minimum + minimum = maximum + maximum = swap_value + + var/clamped_value = clamp(value, minimum, maximum) + var/in_range = (value >= minimum && value <= maximum) + var/inside_deadband = FALSE + + if(isnum(deadband_center) && isnum(deadband_radius) && deadband_radius > 0) + inside_deadband = abs(value - deadband_center) <= abs(deadband_radius) + + set_pin_data(IC_OUTPUT, 1, clamped_value) + set_pin_data(IC_OUTPUT, 2, in_range) + set_pin_data(IC_OUTPUT, 3, inside_deadband) + push_data() + activate_pin(2) + // Averaging // /obj/item/integrated_circuit/arithmetic/average name = "average circuit" - desc = "This circuit is of average quality, however it will compute the average for numbers you give it." - extended_desc = "Note that null pins are ignored, where as a pin containing 0 is included in the averaging calculation." + desc = "Calculates the average of the provided numeric inputs." + extended_desc = "Note that null pins are ignored, whereas a pin containing 0 is included in the averaging calculation." icon_state = "average" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -240,7 +303,7 @@ // Pi, because why the hell not? // /obj/item/integrated_circuit/arithmetic/pi name = "pi constant circuit" - desc = "Not recommended for cooking. Outputs '3.14159' when it receives a pulse." + desc = "Outputs the numeric constant pi when pulsed." icon_state = "pi" inputs = list() spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -253,7 +316,7 @@ // Random // /obj/item/integrated_circuit/arithmetic/random name = "random number generator circuit" - desc = "This gives a random (integer) number between values A and B inclusive." + desc = "Outputs a random integer between A and B, inclusive." extended_desc = "'Inclusive' means that the upper bound is included in the range of numbers, e.g. L = 1 and H = 3 will allow \ for outputs of 1, 2, or 3. H being the higher number is not strictly required." icon_state = "random" @@ -276,7 +339,7 @@ /obj/item/integrated_circuit/arithmetic/square_root name = "square root circuit" - desc = "This outputs the square root of a number you put in." + desc = "Outputs the square root of A." icon_state = "square_root" inputs = list("A" = IC_PINTYPE_NUMBER) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -285,7 +348,7 @@ var/result = 0 for(var/datum/integrated_io/I in inputs) I.pull_data() - if(isnum(I.data)) + if(isnum(I.data) && I.data >= 0) result = sqrt(I.data) set_pin_data(IC_OUTPUT, 1, result) @@ -296,7 +359,7 @@ /obj/item/integrated_circuit/arithmetic/modulo name = "modulo circuit" - desc = "Gets the remainder of A / B." + desc = "Outputs the remainder of A divided by B." icon_state = "modulo" inputs = list("A" = IC_PINTYPE_NUMBER, "B" = IC_PINTYPE_NUMBER) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -316,7 +379,7 @@ /obj/item/integrated_circuit/arithmetic/min name = "min circuit" - desc = "This outputs the smallest of the numbers you put in." + desc = "Outputs the smallest provided number." // The states are rarely used symbols for the operations // Letters didn't fit as well icon_state = "min" @@ -329,6 +392,9 @@ if(isnum(I.data)) values.Add(I.data) + if(!length(values)) + return + set_pin_data(IC_OUTPUT, 1, min(values)) push_data() activate_pin(2) @@ -337,7 +403,7 @@ /obj/item/integrated_circuit/arithmetic/max name = "max circuit" - desc = "This outputs the biggest of the numbers you put in." + desc = "Outputs the largest provided number." icon_state = "max" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -348,6 +414,9 @@ if(isnum(I.data)) values.Add(I.data) + if(!length(values)) + return + set_pin_data(IC_OUTPUT, 1, max(values)) push_data() activate_pin(2) diff --git a/code/modules/integrated_electronics/subtypes/built_in.dm b/code/modules/integrated_electronics/subtypes/built_in.dm index 702e1d4e112..a5177f95f00 100644 --- a/code/modules/integrated_electronics/subtypes/built_in.dm +++ b/code/modules/integrated_electronics/subtypes/built_in.dm @@ -1,6 +1,11 @@ +/* + * subtypes/built_in.dm + * Built-in non-removable circuits used by assemblies to expose internal behavior to normal circuit networks. + */ + /obj/item/integrated_circuit/built_in name = "integrated circuit" - desc = "It's a tiny chip! This one doesn't seem to do much, however." + desc = "A base integrated circuit component." icon = 'icons/obj/assemblies/electronic_components.dmi' icon_state = "template" size = -1 @@ -9,7 +14,7 @@ /obj/item/integrated_circuit/built_in/device_input name = "assembly input" - desc = "A built in chip for handling pulses from attached assembly items." + desc = "A built-in chip for handling pulses from attached assembly items." complexity = 0 //This acts as a limitation on building machines, more resource-intensive components cost more 'space'. activators = list("on pulsed" = IC_PINTYPE_PULSE_OUT) @@ -18,7 +23,7 @@ /obj/item/integrated_circuit/built_in/device_output name = "assembly out" - desc = "A built in chip for pulsing attached assembly items." + desc = "A built-in chip for pulsing attached assembly items." complexity = 0 //This acts as a limitation on building machines, more resource-intensive components cost more 'space'. activators = list("pulse attached" = IC_PINTYPE_PULSE_IN) @@ -30,7 +35,7 @@ // Triggered when clothing assembly's hud button is clicked, used in-hand, or when another clothing-specific interaction occurs (like touching something with gloves on). /obj/item/integrated_circuit/built_in/action_button name = "external trigger circuit" - desc = "A built in chip that outputs a pulse when an external control event occurs. It also provides additional data depending on the nature of the event and device." + desc = "A built-in chip that outputs a pulse when an external control event occurs. It also provides additional data depending on the nature of the event and device." extended_desc = "This outputs a pulse if the assembly's HUD button is clicked while the assembly is closed." complexity = 0 activators = list("on activation" = IC_PINTYPE_PULSE_OUT) diff --git a/code/modules/integrated_electronics/subtypes/converters.dm b/code/modules/integrated_electronics/subtypes/converters.dm index 4ba9fb35e74..79cb4921119 100644 --- a/code/modules/integrated_electronics/subtypes/converters.dm +++ b/code/modules/integrated_electronics/subtypes/converters.dm @@ -1,3 +1,8 @@ +/* + * subtypes/converters.dm + * Converter circuits that translate between numbers, strings, booleans, directions, colors, and other pin formats. + */ + //These circuits convert one variable to another. /obj/item/integrated_circuit/converter complexity = 2 @@ -10,7 +15,7 @@ /obj/item/integrated_circuit/converter/num2text name = "number to string" desc = "This circuit can convert a number variable into a string." - extended_desc = "Because of game limitations null/false variables will output a '0' string." + extended_desc = "Null or false-equivalent inputs output as the text value '0'." complexity = 1 icon_state = "num-string" inputs = list("input" = IC_PINTYPE_NUMBER) @@ -33,7 +38,8 @@ /obj/item/integrated_circuit/converter/num2text4 name = "4-way number to string" desc = "This circuit can convert up to four number variables into strings." - extended_desc = "Because of game limitations null/false variables will output a '0' string." + extended_desc = "Null or false-equivalent inputs output as the text value '0'." + complexity = 2 icon_state = "num-string" inputs = list( "input 1" = IC_PINTYPE_NUMBER, @@ -69,7 +75,7 @@ /obj/item/integrated_circuit/converter/num2text8 name = "8-way number to string" desc = "This circuit can convert up to eight number variables into strings." - extended_desc = "Because of game limitations null/false variables will output a '0' string." + extended_desc = "Null or false-equivalent inputs output as the text value '0'." complexity = 4 icon_state = "num-string" inputs = list( @@ -116,7 +122,11 @@ desc = "This circuit can convert a string variable into a number." icon_state = "string-num" inputs = list("input" = IC_PINTYPE_STRING) - outputs = list("output" = IC_PINTYPE_NUMBER) + outputs = list( + "output" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/converter/text2num/do_work() @@ -126,7 +136,10 @@ if(!isnull(incoming)) result = text2num(incoming) + var/valid = isnum(result) set_pin_data(IC_OUTPUT, 1, result) + set_pin_data(IC_OUTPUT, 2, valid) + set_pin_data(IC_OUTPUT, 3, valid ? "Number parsed." : "Input is not a number.") push_data() activate_pin(2) @@ -149,9 +162,80 @@ push_data() activate_pin(2) +/obj/item/integrated_circuit/converter/ref_exists + name = "reference exists" + desc = "Checks whether a reference currently points to a valid atom." + icon_state = "ref-string" + inputs = list("input" = IC_PINTYPE_REF) + outputs = list( + "exists" = IC_PINTYPE_BOOLEAN, + "resolved ref" = IC_PINTYPE_REF + ) + activators = list( + "convert" = IC_PINTYPE_PULSE_IN, + "on convert" = IC_PINTYPE_PULSE_OUT, + "on valid" = IC_PINTYPE_PULSE_OUT, + "on invalid" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/converter/ref_exists/do_work() + var/atom/A = get_pin_data_as_type(IC_INPUT, 1, /atom) + var/valid = !!A + + set_pin_data(IC_OUTPUT, 1, valid) + set_pin_data(IC_OUTPUT, 2, valid ? A : null) + push_data() + activate_pin(2) + activate_pin(valid ? 3 : 4) + +/obj/item/integrated_circuit/converter/ref_to_coords + name = "reference to coordinates" + desc = "Converts a reference into absolute X, Y, Z coordinates and its turf reference." + icon_state = "ref-string" + inputs = list("input" = IC_PINTYPE_REF) + outputs = list( + "X" = IC_PINTYPE_NUMBER, + "Y" = IC_PINTYPE_NUMBER, + "Z" = IC_PINTYPE_NUMBER, + "turf" = IC_PINTYPE_REF, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + activators = list( + "convert" = IC_PINTYPE_PULSE_IN, + "on convert" = IC_PINTYPE_PULSE_OUT, + "on valid" = IC_PINTYPE_PULSE_OUT, + "on invalid" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/converter/ref_to_coords/do_work() + var/atom/A = get_pin_data_as_type(IC_INPUT, 1, /atom) + var/turf/T = A ? get_turf(A) : null + + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 3, null) + set_pin_data(IC_OUTPUT, 4, null) + set_pin_data(IC_OUTPUT, 5, FALSE) + set_pin_data(IC_OUTPUT, 6, "Invalid reference.") + + if(T) + set_pin_data(IC_OUTPUT, 1, T.x) + set_pin_data(IC_OUTPUT, 2, T.y) + set_pin_data(IC_OUTPUT, 3, T.z) + set_pin_data(IC_OUTPUT, 4, T) + set_pin_data(IC_OUTPUT, 5, TRUE) + set_pin_data(IC_OUTPUT, 6, "Coordinates converted.") + + push_data() + activate_pin(2) + activate_pin(T ? 3 : 4) + /obj/item/integrated_circuit/converter/lowercase name = "lowercase string converter" - desc = "this will cause a string to come out in all lowercase." + desc = "Converts text to lowercase." icon_state = "lowercase" inputs = list("input" = IC_PINTYPE_STRING) outputs = list("output" = IC_PINTYPE_STRING) @@ -170,7 +254,7 @@ /obj/item/integrated_circuit/converter/uppercase name = "uppercase string converter" - desc = "THIS WILL CAUSE A STRING TO COME OUT IN ALL UPPERCASE." + desc = "Converts text to uppercase." icon_state = "uppercase" inputs = list("input" = IC_PINTYPE_STRING) outputs = list("output" = IC_PINTYPE_STRING) @@ -189,7 +273,7 @@ /obj/item/integrated_circuit/converter/concatenator name = "concatenator" - desc = "This joins many strings together to get one big string." + desc = "Joins multiple text inputs into one text output." complexity = 4 inputs = list( "A" = IC_PINTYPE_STRING, @@ -218,8 +302,8 @@ /obj/item/integrated_circuit/converter/separator name = "separator" - desc = "This splits as single string into two at the relative split point." - extended_desc = "This circuits splits a given string into two, based on the string, and the index value. \ + desc = "Splits a text input into two outputs at the selected position." + extended_desc = "This circuit splits a given string into two, based on the string, and the index value. \ The index splits the string after the given index, including spaces. So 'a person' with an index of '3' \ will split into 'a p' and 'erson'." complexity = 4 @@ -252,7 +336,7 @@ /obj/item/integrated_circuit/converter/findstring name = "find text" - desc = "This gives position of sample in the string. Or returns 0." + desc = "Outputs the position of a sample text within the input text, or 0 if it is not found." extended_desc = "The first pin is the string to be examined. The second pin is the sample to be found. \ For example, 'eat this burger',' ' will give you position 4. This circuit isn't case sensitive." complexity = 4 @@ -297,43 +381,6 @@ activate_pin(2) -/obj/item/integrated_circuit/converter/radians2degrees - name = "radians to degrees converter" - desc = "Converts radians to degrees." - inputs = list("radian" = IC_PINTYPE_NUMBER) - outputs = list("degrees" = IC_PINTYPE_NUMBER) - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - -/obj/item/integrated_circuit/converter/radians2degrees/do_work() - var/result = null - pull_data() - var/incoming = get_pin_data(IC_INPUT, 1) - if(!isnull(incoming)) - result = TO_DEGREES(incoming) - - set_pin_data(IC_OUTPUT, 1, result) - push_data() - activate_pin(2) - -/obj/item/integrated_circuit/converter/degrees2radians - name = "degrees to radians converter" - desc = "Converts degrees to radians." - inputs = list("degrees" = IC_PINTYPE_NUMBER) - outputs = list("radians" = IC_PINTYPE_NUMBER) - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - -/obj/item/integrated_circuit/converter/degrees2radians/do_work() - var/result = null - pull_data() - var/incoming = get_pin_data(IC_INPUT, 1) - if(!isnull(incoming)) - result = TO_RADIANS(incoming) - - set_pin_data(IC_OUTPUT, 1, result) - push_data() - activate_pin(2) - - /obj/item/integrated_circuit/converter/abs_to_rel_coords name = "abs to rel coordinate converter" desc = "Easily convert absolute coordinates to relative coordinates with this." @@ -365,6 +412,113 @@ push_data() activate_pin(2) +/obj/item/integrated_circuit/converter/dir_to_vector + name = "direction to vector" + desc = "Converts a BYOND direction into a two-number X/Y movement vector." + icon_state = "num-string" + inputs = list("direction" = IC_PINTYPE_DIR) + outputs = list( + "X" = IC_PINTYPE_NUMBER, + "Y" = IC_PINTYPE_NUMBER, + "vector" = IC_PINTYPE_LIST, + "valid" = IC_PINTYPE_BOOLEAN + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/converter/dir_to_vector/do_work() + var/direction = get_pin_data(IC_INPUT, 1) + var/x = 0 + var/y = 0 + + if(isnum(direction)) + if(direction & EAST) + x++ + if(direction & WEST) + x-- + if(direction & NORTH) + y++ + if(direction & SOUTH) + y-- + + set_pin_data(IC_OUTPUT, 1, x) + set_pin_data(IC_OUTPUT, 2, y) + set_pin_data(IC_OUTPUT, 3, list(x, y)) + set_pin_data(IC_OUTPUT, 4, isnum(direction) && (x || y)) + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/converter/vector_to_dir + name = "vector to direction" + desc = "Converts X/Y vector components into a BYOND direction." + icon_state = "num-string" + inputs = list( + "X" = IC_PINTYPE_NUMBER, + "Y" = IC_PINTYPE_NUMBER + ) + outputs = list( + "direction" = IC_PINTYPE_DIR, + "direction text" = IC_PINTYPE_STRING, + "valid" = IC_PINTYPE_BOOLEAN + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/converter/vector_to_dir/do_work() + var/x = get_pin_data(IC_INPUT, 1) + var/y = get_pin_data(IC_INPUT, 2) + var/direction = 0 + + if(isnum(x) && isnum(y)) + if(x > 0) + direction |= EAST + else if(x < 0) + direction |= WEST + if(y > 0) + direction |= NORTH + else if(y < 0) + direction |= SOUTH + + set_pin_data(IC_OUTPUT, 1, direction || null) + set_pin_data(IC_OUTPUT, 2, direction ? dir2text(direction) : null) + set_pin_data(IC_OUTPUT, 3, !!direction) + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/converter/delta_to_dir + name = "delta to direction" + desc = "Converts relative delta X/Y values into a BYOND direction." + icon_state = "num-string" + inputs = list( + "delta X" = IC_PINTYPE_NUMBER, + "delta Y" = IC_PINTYPE_NUMBER + ) + outputs = list( + "direction" = IC_PINTYPE_DIR, + "direction text" = IC_PINTYPE_STRING, + "valid" = IC_PINTYPE_BOOLEAN + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/converter/delta_to_dir/do_work() + var/x = get_pin_data(IC_INPUT, 1) + var/y = get_pin_data(IC_INPUT, 2) + var/direction = 0 + + if(isnum(x) && isnum(y)) + if(x > 0) + direction |= EAST + else if(x < 0) + direction |= WEST + if(y > 0) + direction |= NORTH + else if(y < 0) + direction |= SOUTH + + set_pin_data(IC_OUTPUT, 1, direction || null) + set_pin_data(IC_OUTPUT, 2, direction ? dir2text(direction) : null) + set_pin_data(IC_OUTPUT, 3, !!direction) + push_data() + activate_pin(2) + /obj/item/integrated_circuit/converter/stringlength name = "len circuit" desc = "This circuit will return the number of characters in a string." diff --git a/code/modules/integrated_electronics/subtypes/data_transfer.dm b/code/modules/integrated_electronics/subtypes/data_transfer.dm index a5256ddc471..ac776c37c8c 100644 --- a/code/modules/integrated_electronics/subtypes/data_transfer.dm +++ b/code/modules/integrated_electronics/subtypes/data_transfer.dm @@ -1,10 +1,15 @@ +/* + * subtypes/data_transfer.dm + * Data transfer circuits for copying, routing, packing, or unpacking values between pins. + */ + /obj/item/integrated_circuit/transfer category_text = "Data Transfer" power_draw_per_use = 20 /obj/item/integrated_circuit/transfer/multiplexer name = "two multiplexer" - desc = "This is what those in the business tend to refer to as a 'mux' or data selector. It moves data from one of the selected inputs to the output." + desc = "Selects one input and sends that value to the output." extended_desc = "The first input pin is used to select which of the other input pins which has its data moved to the output. \ If the input selection is outside the valid range then no output is given." complexity = 2 @@ -58,7 +63,7 @@ /obj/item/integrated_circuit/transfer/demultiplexer name = "two demultiplexer" - desc = "This is what those in the business tend to refer to as a 'demux'. It moves data from the input to one of the selected outputs." + desc = "Sends one input value to the selected output." extended_desc = "The first input pin is used to select which of the output pins is given the data from the second input pin. \ If the output selection is outside the valid range then no output is given." complexity = 2 @@ -114,8 +119,8 @@ /obj/item/integrated_circuit/transfer/wireless name = "subspace transceiver" - desc = "A wireless transmitter-receiver pair that can transmit data between devices." - extended_desc = "The first input is the data to send to the other device, and the second input is the channel to send it on." + desc = "Transmits data wirelessly between linked devices." + extended_desc = "The first input is the data to send. The second input selects the transmission channel." complexity = 12 spawn_flags = IC_SPAWN_RESEARCH icon_state = "bluespace" diff --git a/code/modules/integrated_electronics/subtypes/debug.dm b/code/modules/integrated_electronics/subtypes/debug.dm new file mode 100644 index 00000000000..1603f735a68 --- /dev/null +++ b/code/modules/integrated_electronics/subtypes/debug.dm @@ -0,0 +1,151 @@ +/* + * subtypes/debug.dm + * Debug/testing circuits used to inspect values, format helper output, and verify integrated electronics behavior. + */ + +/obj/item/integrated_circuit/debug + category_text = "Debugging" + complexity = 1 + spawn_flags = 0 + +/obj/item/integrated_circuit/debug/helper_test + name = "helper test circuit" + desc = "Tests integrated circuit helper procs." + extended_desc = "Outputs formatted helper proc results from one input." + icon_state = "template" + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + inputs = list( + "input" = IC_PINTYPE_STRING, + "number input" = IC_PINTYPE_STRING, + "template" = IC_PINTYPE_STRING + ) + outputs = list( + "display value" = IC_PINTYPE_STRING, + "type text" = IC_PINTYPE_STRING, + "safe number" = IC_PINTYPE_NUMBER, + "template output" = IC_PINTYPE_STRING + ) + activators = list( + "test" = IC_PINTYPE_PULSE_IN, + "on tested" = IC_PINTYPE_PULSE_OUT + ) + +/obj/item/integrated_circuit/debug/helper_test/do_work() + pull_data() + + var/input_value = get_pin_data(IC_INPUT, 1) + var/number_value = get_pin_data(IC_INPUT, 2) + var/template_value = get_pin_data(IC_INPUT, 3) + + set_pin_data(IC_OUTPUT, 1, ic_display_value(input_value)) + set_pin_data(IC_OUTPUT, 2, ic_type_text(input_value)) + set_pin_data(IC_OUTPUT, 3, ic_safe_number(number_value, 0)) + set_pin_data(IC_OUTPUT, 4, ic_apply_template(template_value, input_value, number_value, ic_type_text(input_value))) + + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/debug/one_shot_pulse + name = "one-shot pulse" + desc = "Allows one pulse through, then blocks further pulses until reset." + extended_desc = "This circuit is useful for preventing repeated signals. When triggered, it sends one pulse if it has not already fired. After firing, it will ignore further trigger pulses until the reset activator is pulsed." + icon_state = "template" + complexity = 2 + w_class = WEIGHT_CLASS_TINY + size = 1 + power_draw_per_use = 50 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + var/has_fired = FALSE + + inputs = list( + "enabled" = IC_PINTYPE_BOOLEAN + ) + + outputs = list( + "has fired" = IC_PINTYPE_BOOLEAN + ) + + activators = list( + "trigger" = IC_PINTYPE_PULSE_IN, + "reset" = IC_PINTYPE_PULSE_IN, + "pulse out" = IC_PINTYPE_PULSE_OUT, + "blocked" = IC_PINTYPE_PULSE_OUT + ) + +/obj/item/integrated_circuit/debug/one_shot_pulse/do_work(activator_id) + var/active_pin = activator_id + + if(istype(active_pin, /datum/integrated_io)) + active_pin = activators.Find(active_pin) + + if(istext(active_pin)) + for(var/i = 1 to activators.len) + var/datum/integrated_io/A = activators[i] + if(A.name == active_pin) + active_pin = i + break + + if(!isnum(active_pin)) + active_pin = 1 + + switch(active_pin) + if(1) + trigger_once() + return + if(2) + reset_latch() + return + +/obj/item/integrated_circuit/debug/one_shot_pulse/proc/trigger_once() + pull_data() + + var/enabled = get_pin_data(IC_INPUT, 1) + + if(!enabled) + enabled = TRUE + + if(!enabled) + set_pin_data(IC_OUTPUT, 1, has_fired) + push_data() + activate_pin(4) + return + + if(has_fired) + set_pin_data(IC_OUTPUT, 1, has_fired) + push_data() + activate_pin(4) + return + + has_fired = TRUE + set_pin_data(IC_OUTPUT, 1, has_fired) + push_data() + activate_pin(3) + +/obj/item/integrated_circuit/debug/one_shot_pulse/proc/reset_latch() + has_fired = FALSE + set_pin_data(IC_OUTPUT, 1, has_fired) + push_data() + +/obj/item/integrated_circuit/debug/one_shot_pulse/copy_clone_state_to(obj/item/integrated_circuit/target) + var/obj/item/integrated_circuit/debug/one_shot_pulse/new_latch = target + if(!istype(new_latch)) + return + + new_latch.has_fired = has_fired + +/obj/item/integrated_circuit/debug/blank + name = "debug marker" + desc = "A blank debug circuit. It does nothing and exists only as a movable, removable, renameable marker." + extended_desc = "This circuit has no inputs, outputs, or activators. It is useful as a spacer, label, or visual divider inside an assembly." + icon_state = "template" + complexity = 0 + size = 0 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list() + outputs = list() + activators = list() + +/obj/item/integrated_circuit/debug/blank/do_work() + return diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm index 404b37ff88a..67bf0205154 100644 --- a/code/modules/integrated_electronics/subtypes/input.dm +++ b/code/modules/integrated_electronics/subtypes/input.dm @@ -129,7 +129,7 @@ set_pin_data(IC_OUTPUT, 2, H.get_pulse_as_number(GETPULSE_TOOL)) set_pin_data(IC_OUTPUT, 3, H.blood_pressure()) set_pin_data(IC_OUTPUT, 4, H.get_blood_oxygenation()) - set_pin_data(IC_OUTPUT, 5, H.bodytemperature) + set_pin_data(IC_OUTPUT, 5, H.bodytemperature-273.15) set_pin_data(IC_OUTPUT, 6, H.is_asystole()) push_data() @@ -176,7 +176,7 @@ set_pin_data(IC_OUTPUT, 2, H.get_pulse_as_number(GETPULSE_TOOL)) set_pin_data(IC_OUTPUT, 3, H.blood_pressure()) set_pin_data(IC_OUTPUT, 4, H.get_blood_oxygenation()) - set_pin_data(IC_OUTPUT, 5, H.bodytemperature) + set_pin_data(IC_OUTPUT, 5, H.bodytemperature-273.15) set_pin_data(IC_OUTPUT, 6, H.is_asystole()) // Advanced set_pin_data(IC_OUTPUT, 7, get_severity(H.getBruteLoss(), TRUE)) @@ -205,7 +205,13 @@ "amount of reagents" = IC_PINTYPE_NUMBER, "density" = IC_PINTYPE_BOOLEAN, "opacity" = IC_PINTYPE_BOOLEAN, - "occupied turf" = IC_PINTYPE_REF + "occupied turf" = IC_PINTYPE_REF, + "target" = IC_PINTYPE_REF, + "absolute X" = IC_PINTYPE_NUMBER, + "absolute Y" = IC_PINTYPE_NUMBER, + "absolute Z" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT, "not scanned" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_RESEARCH @@ -215,10 +221,14 @@ /obj/item/integrated_circuit/input/examiner/do_work() var/atom/H = get_pin_data_as_type(IC_INPUT, 1, /atom) var/turf/T = get_turf(src) - if(!istype(H)) //Invalid input + if(!ic_is_safe_ref_atom(H)) //Invalid input + set_pin_data(IC_OUTPUT, 15, FALSE) + set_pin_data(IC_OUTPUT, 16, "Invalid target.") + push_data() + activate_pin(3) return - if(H in view(T)) // This is a camera. It can't examine thngs,that it can't see. + if((H in view(T)) || (get_turf(H) == T)) set_pin_data(IC_OUTPUT, 1, H.name) @@ -236,9 +246,18 @@ set_pin_data(IC_OUTPUT, 8, H.density) set_pin_data(IC_OUTPUT, 9, H.opacity) set_pin_data(IC_OUTPUT, 10, get_turf(H)) + set_pin_data(IC_OUTPUT, 11, H) + set_pin_data(IC_OUTPUT, 12, H.x) + set_pin_data(IC_OUTPUT, 13, H.y) + set_pin_data(IC_OUTPUT, 14, H.z) + set_pin_data(IC_OUTPUT, 15, TRUE) + set_pin_data(IC_OUTPUT, 16, "Scanned.") push_data() activate_pin(2) else + set_pin_data(IC_OUTPUT, 15, FALSE) + set_pin_data(IC_OUTPUT, 16, "Target out of scanner view.") + push_data() activate_pin(3) /obj/item/integrated_circuit/input/local_locator @@ -289,6 +308,8 @@ var/list/nearby_things = range(1, get_turf(src)) var/list/valid_things = list() for(var/atom/thing in nearby_things) + if(!ic_is_safe_ref_atom(thing)) + continue if(thing.type != desired_type) continue valid_things += thing @@ -327,11 +348,15 @@ var/desired_type = A?.type if(desired_type) for(var/atom/thing in nearby_things) + if(!ic_is_safe_ref_atom(thing)) + continue if(thing.type == desired_type) valid_things.Add(thing) else if(istext(I)) var/DT = I for(var/atom/thing in nearby_things) + if(!ic_is_safe_ref_atom(thing)) + continue if(findtext(addtext(thing.name, " ", thing.desc), DT, 1, 0)) valid_things.Add(thing) if(valid_things.len) @@ -422,14 +447,16 @@ activate_pin(3) - for(var/mob/O in hearers(1, get_turf(src))) - O.show_message("[icon2html(src, viewers(get_turf(src)))] *beep* *beep*", 3, "*beep* *beep*", 2) + var/turf/T = get_turf(src) + var/list/viewing = viewers(T) + for(var/mob/O in hearers(1, T)) + O.show_message("[icon2html(src, viewing)] *beep* *beep*", 3, "*beep* *beep*", 2) //This circuit gives information on where the machine is. /obj/item/integrated_circuit/input/gps name = "global positioning system" desc = "This allows you to easily know the position of a machine containing this device." - extended_desc = "The GPS's coordinates it gives is absolute, not relative." + extended_desc = "The GPS outputs absolute map coordinates, not coordinates relative to the assembly." icon = 'icons/obj/item/gps.dmi' icon_state = "gps" item_state = "radio" @@ -439,7 +466,9 @@ outputs = list( "X"= IC_PINTYPE_NUMBER, "Y" = IC_PINTYPE_NUMBER, - "Z" = IC_PINTYPE_NUMBER + "Z" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) activators = list( "get coordinates" = IC_PINTYPE_PULSE_IN, @@ -454,20 +483,25 @@ set_pin_data(IC_OUTPUT, 1, null) set_pin_data(IC_OUTPUT, 2, null) set_pin_data(IC_OUTPUT, 3, null) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, "No turf.") if(!T) + push_data() return set_pin_data(IC_OUTPUT, 1, T.x) set_pin_data(IC_OUTPUT, 2, T.y) set_pin_data(IC_OUTPUT, 3, T.z) + set_pin_data(IC_OUTPUT, 4, TRUE) + set_pin_data(IC_OUTPUT, 5, "Coordinates acquired.") push_data() activate_pin(2) /obj/item/integrated_circuit/input/microphone name = "microphone" - desc = "Useful for spying on people or for voice activated machines." - extended_desc = "This will automatically translate most languages it hears to Tau Ceti Basic. \ + desc = "Detects nearby speech and outputs the speaker and message text." + extended_desc = "When triggered by speech it can hear, this circuit outputs the speaker and message text, translated to Tau Ceti Basic when possible. \ The first activation pin is always pulsed when the circuit hears someone talk, while the second one \ is only triggered if it hears someone speaking a language other than Tau Ceti Basic." icon_state = "recorder" @@ -491,7 +525,16 @@ /obj/item/integrated_circuit/input/microphone/Destroy() return ..() -/obj/item/integrated_circuit/input/microphone/hear_talk(mob/living/M, msg, var/verb="says", datum/language/speaking=null) +/obj/item/integrated_circuit/input/microphone/hear_talk(mob/living/M, msg, var/verb = "says", datum/language/speaking = null) + receive_radio_message(M, msg, null, speaking) + +/obj/item/integrated_circuit/input/microphone/proc/receive_radio_message(mob/living/M, msg, channel = null, datum/language/speaking = null) + /* + * Shared microphone intake. + * + * Local speech enters through hear_talk(). + * Radio speech enters when /obj/item/radio/headset/circuitry relays it. + */ if(isanimal(M)) if(!M.universal_speak) return @@ -511,6 +554,23 @@ if(translated) activate_pin(2) +/obj/item/integrated_circuit/input/microphone/proc/receive_radio_text(speaker_name, msg, channel = null, language_name = null) + /* + * Text-only fallback for radio relay hooks that do not expose mob/language datums. + * This cannot perform real datum-based translation because no datum/language is provided. + */ + if(!msg) + return + + set_pin_data(IC_OUTPUT, 1, speaker_name ? speaker_name : "Unknown") + set_pin_data(IC_OUTPUT, 2, msg) + + push_data() + activate_pin(1) + + if(language_name && language_name != "Tau Ceti Basic" && language_name != "Common") + activate_pin(2) + /obj/item/integrated_circuit/input/sensor name = "sensor" desc = "Scans and obtains a reference for any objects or persons near you. All you need to do is shove the machine in their face." @@ -524,6 +584,8 @@ power_draw_per_use = 1200 /obj/item/integrated_circuit/input/sensor/proc/sense(var/atom/A, mob/user) + if(!ic_is_safe_ref_atom(A)) + return FALSE if(!user.Adjacent(A)) return FALSE var/ignore_bags = get_pin_data(IC_INPUT, 1) @@ -561,7 +623,10 @@ GAS_NO2 = IC_PINTYPE_NUMBER, GAS_CHLORINE = IC_PINTYPE_NUMBER, GAS_WATERVAPOR = IC_PINTYPE_NUMBER, - "other" = IC_PINTYPE_NUMBER + "other" = IC_PINTYPE_NUMBER, + "total moles" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_RESEARCH @@ -572,7 +637,7 @@ name = "internal battery monitor" desc = "This monitors the charge level of an internal battery." icon_state = "internalbm" - extended_desc = "This circuit will give you values of charge, max charge and percentage of the internal battery on demand." + extended_desc = "When pulsed, this circuit reports the assembly battery charge, maximum charge, and charge percentage." w_class = WEIGHT_CLASS_TINY complexity = 1 inputs = list() @@ -580,7 +645,7 @@ "cell charge" = IC_PINTYPE_NUMBER, "max charge" = IC_PINTYPE_NUMBER, "percentage" = IC_PINTYPE_NUMBER, - "refference to assembly" = IC_PINTYPE_REF + "reference to assembly" = IC_PINTYPE_REF ) activators = list("read" = IC_PINTYPE_PULSE_IN, "on read" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -601,11 +666,60 @@ push_data() activate_pin(2) +/obj/item/integrated_circuit/input/assembly_state + name = "assembly state monitor" + desc = "Reports basic state for the assembly containing this circuit." + icon_state = "internalbm" + extended_desc = "Reports assembly charge, maximum charge, charge percentage, open state, and holder reference. It only reads the current assembly." + w_class = WEIGHT_CLASS_TINY + complexity = 2 + inputs = list() + outputs = list( + "cell charge" = IC_PINTYPE_NUMBER, + "max charge" = IC_PINTYPE_NUMBER, + "percentage" = IC_PINTYPE_NUMBER, + "opened" = IC_PINTYPE_BOOLEAN, + "holder ref" = IC_PINTYPE_REF, + "valid" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "read" = IC_PINTYPE_PULSE_IN, + "on read" = IC_PINTYPE_PULSE_OUT, + "on invalid" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 20 + +/obj/item/integrated_circuit/input/assembly_state/do_work() + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 3, null) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, null) + set_pin_data(IC_OUTPUT, 6, FALSE) + + if(!assembly) + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 4, assembly.opened) + set_pin_data(IC_OUTPUT, 5, assembly.get_assembly_holder()) + set_pin_data(IC_OUTPUT, 6, TRUE) + + if(assembly.battery) + set_pin_data(IC_OUTPUT, 1, assembly.battery.charge) + set_pin_data(IC_OUTPUT, 2, assembly.battery.maxcharge) + set_pin_data(IC_OUTPUT, 3, assembly.battery.maxcharge ? 100 * assembly.battery.charge / assembly.battery.maxcharge : 0) + + push_data() + activate_pin(2) + /obj/item/integrated_circuit/input/externalbm name = "external battery monitor" desc = "This can help watch the battery level of any device in range." icon_state = "externalbm" - extended_desc = "This circuit will give you values of charge, max charge and percentage of any device or battery in view" + extended_desc = "When pulsed, this circuit reports the charge, maximum charge, and charge percentage of a visible power cell or powered device." w_class = WEIGHT_CLASS_TINY complexity = 2 inputs = list("target" = IC_PINTYPE_REF) @@ -640,7 +754,6 @@ var/turf/A = get_turf(src) if(AM in view(A)) - push_data() set_pin_data(IC_OUTPUT, 1, cell.charge) set_pin_data(IC_OUTPUT, 2, cell.maxcharge) set_pin_data(IC_OUTPUT, 3, cell.percent()) @@ -650,6 +763,9 @@ /obj/item/integrated_circuit/input/atmo_scanner/do_work() var/turf/T = get_turf(src) if(!istype(T)) //Invalid input + set_pin_data(IC_OUTPUT, 18, FALSE) + set_pin_data(IC_OUTPUT, 19, "No turf.") + push_data() return var/datum/gas_mixture/environment = T.return_air() @@ -689,7 +805,9 @@ set_pin_data(IC_OUTPUT, 14, round(chlorine_level*100,0.01)) set_pin_data(IC_OUTPUT, 15, round(watervapor_level*100,0.01)) set_pin_data(IC_OUTPUT, 16, round(unknown_level, 0.01)) - set_pin_data(IC_OUTPUT, 17, round(unknown_level, 0.01)) + set_pin_data(IC_OUTPUT, 17, round(total_moles, 0.01)) + set_pin_data(IC_OUTPUT, 18, TRUE) + set_pin_data(IC_OUTPUT, 19, "Atmosphere scanned.") else set_pin_data(IC_OUTPUT, 1, 0) set_pin_data(IC_OUTPUT, 2, -273.15) @@ -708,6 +826,8 @@ set_pin_data(IC_OUTPUT, 15, 0) set_pin_data(IC_OUTPUT, 16, 0) set_pin_data(IC_OUTPUT, 17, 0) + set_pin_data(IC_OUTPUT, 18, TRUE) + set_pin_data(IC_OUTPUT, 19, "Vacuum scanned.") push_data() activate_pin(2) @@ -718,7 +838,9 @@ complexity = 3 inputs = list() outputs = list( - "pressure" = IC_PINTYPE_NUMBER + "pressure" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_RESEARCH @@ -728,6 +850,9 @@ /obj/item/integrated_circuit/input/pressure_sensor/do_work() var/turf/T = get_turf(src) if(!istype(T)) //Invalid input + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, "No turf.") + push_data() return var/datum/gas_mixture/environment = T.return_air() @@ -738,6 +863,8 @@ set_pin_data(IC_OUTPUT, 1, pressure) else set_pin_data(IC_OUTPUT, 1, 0) + set_pin_data(IC_OUTPUT, 2, TRUE) + set_pin_data(IC_OUTPUT, 3, "Pressure scanned.") push_data() activate_pin(2) @@ -748,7 +875,9 @@ complexity = 3 inputs = list() outputs = list( - "temperature" = IC_PINTYPE_NUMBER + "temperature" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_RESEARCH @@ -758,6 +887,9 @@ /obj/item/integrated_circuit/input/temperature_sensor/do_work() var/turf/T = get_turf(src) if(!istype(T)) //Invalid input + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, "No turf.") + push_data() return var/datum/gas_mixture/environment = T.return_air() @@ -767,6 +899,8 @@ set_pin_data(IC_OUTPUT, 1, round(environment.temperature-T0C,0.1)) else set_pin_data(IC_OUTPUT, 1, -273.15) + set_pin_data(IC_OUTPUT, 2, TRUE) + set_pin_data(IC_OUTPUT, 3, "Temperature scanned.") push_data() activate_pin(2) @@ -785,13 +919,18 @@ /obj/item/integrated_circuit/input/gas_sensor/Initialize() outputs = list( - gas_name = IC_PINTYPE_NUMBER + gas_name = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) . = ..() /obj/item/integrated_circuit/input/gas_sensor/do_work() var/turf/T = get_turf(src) if(!istype(T)) //Invalid input + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, "No turf.") + push_data() return var/datum/gas_mixture/environment = T.return_air() @@ -802,6 +941,8 @@ set_pin_data(IC_OUTPUT, 1, round(gas_level*100,0.1)) else set_pin_data(IC_OUTPUT, 1, 0) + set_pin_data(IC_OUTPUT, 2, TRUE) + set_pin_data(IC_OUTPUT, 3, "[gas_display_name] scanned.") push_data() activate_pin(2) @@ -930,7 +1071,13 @@ outputs = list( "located ref" = IC_PINTYPE_LIST, "Written letters" = IC_PINTYPE_STRING, - "area" = IC_PINTYPE_STRING + "area" = IC_PINTYPE_STRING, + "target X" = IC_PINTYPE_NUMBER, + "target Y" = IC_PINTYPE_NUMBER, + "target Z" = IC_PINTYPE_NUMBER, + "content count" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) activators = list( "scan" = IC_PINTYPE_PULSE_IN, @@ -946,17 +1093,28 @@ var/turf/circuit_turf = get_turf(src) var/area_name = get_area_name(scanned_turf) if(!istype(scanned_turf)) //Invalid input + set_pin_data(IC_OUTPUT, 8, FALSE) + set_pin_data(IC_OUTPUT, 9, "Invalid turf.") + push_data() activate_pin(3) return if(scanned_turf in view(circuit_turf)) // This is a camera. It can't examine things that it can't see. var/list/turf_contents = new() for(var/obj/U in scanned_turf) - turf_contents += WEAKREF(U) + if(ic_is_safe_ref_atom(U)) + turf_contents += WEAKREF(U) for(var/mob/U in scanned_turf) - turf_contents += WEAKREF(U) + if(ic_is_safe_ref_atom(U)) + turf_contents += WEAKREF(U) set_pin_data(IC_OUTPUT, 1, turf_contents) set_pin_data(IC_OUTPUT, 3, area_name) + set_pin_data(IC_OUTPUT, 4, scanned_turf.x) + set_pin_data(IC_OUTPUT, 5, scanned_turf.y) + set_pin_data(IC_OUTPUT, 6, scanned_turf.z) + set_pin_data(IC_OUTPUT, 7, length(turf_contents)) + set_pin_data(IC_OUTPUT, 8, TRUE) + set_pin_data(IC_OUTPUT, 9, "Tile scanned.") var/list/St = new() for(var/obj/effect/decal/cleanable/crayon/I in scanned_turf) St.Add(I.icon_state) @@ -965,6 +1123,9 @@ push_data() activate_pin(2) else + set_pin_data(IC_OUTPUT, 8, FALSE) + set_pin_data(IC_OUTPUT, 9, "Target out of scanner view.") + push_data() activate_pin(3) /obj/item/integrated_circuit/input/advanced_locator_list @@ -976,7 +1137,7 @@ and will then provide a list of all found objects which are similar. \ The second pin is a radius." inputs = list("desired type ref list" = IC_PINTYPE_LIST, "radius" = IC_PINTYPE_NUMBER) - outputs = list("located ref" = IC_PINTYPE_LIST) + outputs = list("located ref" = IC_PINTYPE_LIST, "count" = IC_PINTYPE_NUMBER) activators = list("locate" = IC_PINTYPE_PULSE_IN,"found" = IC_PINTYPE_PULSE_OUT,"not found" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH power_draw_per_use = 300 @@ -992,6 +1153,7 @@ /obj/item/integrated_circuit/input/advanced_locator_list/do_work() set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, 0) var/list/input_list = list() input_list = get_pin_data(IC_INPUT, 1) if(length(input_list)) //if there is no input don't do anything. @@ -1003,7 +1165,7 @@ if(istext(item)) for(var/i in nearby_things) var/atom/thing = i - if(ismob(thing) && !isliving(thing)) + if(!ic_is_safe_ref_atom(thing) || (ismob(thing) && !isliving(thing))) continue if(findtext(addtext(thing.name," ",thing.desc), item, 1, 0) ) valid_things.Add(WEAKREF(thing)) @@ -1012,13 +1174,14 @@ var/desired_type = A.type for(var/i in nearby_things) var/atom/thing = i - if(thing.type != desired_type) + if(!ic_is_safe_ref_atom(thing) || (ismob(thing) && !isliving(thing))) continue - if(ismob(thing) && !isliving(thing)) + if(thing.type != desired_type) continue valid_things.Add(WEAKREF(thing)) if(valid_things.len) set_pin_data(IC_OUTPUT, 1, valid_things) + set_pin_data(IC_OUTPUT, 2, length(valid_things)) activate_pin(2) else activate_pin(3) @@ -1039,7 +1202,7 @@ power_draw_per_use = 1200 /obj/item/integrated_circuit/input/sensor/ranged/sense(atom/A, mob/user) - if(!user || (!istype(A) && !isliving(A))) + if(!user || !ic_is_safe_ref_atom(A)) return FALSE if(user.client) if(!(A in view(user.client))) @@ -1075,6 +1238,8 @@ /obj/item/integrated_circuit/input/obj_scanner/attackby_react(var/atom/A,var/mob/user,intent) if(intent!=I_HELP) return FALSE + if(!ic_is_safe_ref_atom(A)) + return FALSE if(!check_then_do_work()) return FALSE var/pu = get_pin_data(IC_INPUT, 1) @@ -1086,28 +1251,57 @@ activate_pin(1) return TRUE +/proc/ic_split_assignment(raw_assignment) + var/list/result = list( + "job title" = null, + "corporation" = null + ) + + if(!istext(raw_assignment) || !length(raw_assignment)) + return result + + var/assignment = trim(raw_assignment) + var/open_paren = findtext(assignment, "(") + var/close_paren = findtext(assignment, ")", open_paren + 1) + + if(open_paren && close_paren && close_paren > open_paren) + result["job title"] = trim(copytext(assignment, 1, open_paren)) + result["corporation"] = trim(copytext(assignment, open_paren + 1, close_paren)) + else + result["job title"] = assignment + + return result + + /obj/item/integrated_circuit/input/card_reader - name = "ID card reader" //To differentiate it from the data card reader - desc = "A circuit that can read the registred name, assignment, and PassKey string from an ID card." + name = "ID card reader" // To differentiate it from the data card reader. + desc = "A circuit that can read the registered name, assignment, job title, corporation, and PassKey string from an ID card." icon_state = "card_reader" complexity = 4 spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + inputs = list( "enable credential cache" = IC_PINTYPE_BOOLEAN ) + outputs = list( "registered name" = IC_PINTYPE_STRING, "assignment" = IC_PINTYPE_STRING, + "job title" = IC_PINTYPE_STRING, + "corporation" = IC_PINTYPE_STRING, "passkey" = IC_PINTYPE_LIST ) + activators = list( "on read" = IC_PINTYPE_PULSE_OUT ) + /obj/item/integrated_circuit/input/card_reader/attackby_react(obj/item/I, mob/living/user, intent) var/obj/item/card/id/card = I.GetID() var/list/access = I.GetAccess() + if(!access) return @@ -1115,17 +1309,23 @@ assembly?.access_card.access |= access if(card) // An ID card. + var/list/split_assignment = ic_split_assignment(card.assignment) + set_pin_data(IC_OUTPUT, 1, card.registered_name) set_pin_data(IC_OUTPUT, 2, card.assignment) + set_pin_data(IC_OUTPUT, 3, split_assignment["job title"]) + set_pin_data(IC_OUTPUT, 4, split_assignment["corporation"]) - else if(length(access)) // A non-card object that has access levels. + else if(length(access)) // A non-card object that has access levels. set_pin_data(IC_OUTPUT, 1, null) set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 3, null) + set_pin_data(IC_OUTPUT, 4, null) else return FALSE - set_pin_data(IC_OUTPUT, 3, access) + set_pin_data(IC_OUTPUT, 5, access) push_data() activate_pin(1) @@ -1138,7 +1338,9 @@ complexity = 3 inputs = list() outputs = list( - "radiation" = IC_PINTYPE_NUMBER + "radiation" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_DEFAULT @@ -1148,6 +1350,9 @@ /obj/item/integrated_circuit/input/radiation_sensor/do_work() var/turf/T = get_turf(src) if(!istype(T)) //Invalid input + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, "No turf.") + push_data() return var/radiation_count = SSradiation.get_rads_at_turf(get_turf(src)) @@ -1156,7 +1361,10 @@ set_pin_data(IC_OUTPUT, 1, round(radiation_count,0.1)) else set_pin_data(IC_OUTPUT, 1, 0) + set_pin_data(IC_OUTPUT, 2, TRUE) + set_pin_data(IC_OUTPUT, 3, "Radiation scanned.") push_data() + activate_pin(2) /obj/item/integrated_circuit/input/light_sensor name = "light sensor" @@ -1165,7 +1373,9 @@ complexity = 3 inputs = list() outputs = list( - "lumens" = IC_PINTYPE_NUMBER + "lumens" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_DEFAULT @@ -1175,6 +1385,9 @@ /obj/item/integrated_circuit/input/light_sensor/do_work() var/turf/T = get_turf(src) if(!istype(T)) //Invalid input + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, "No turf.") + push_data() return var/lumens = T.get_lumcount(0, 3) * 5 @@ -1182,7 +1395,10 @@ set_pin_data(IC_OUTPUT, 1, round(lumens,0.1)) else set_pin_data(IC_OUTPUT, 1, 0) + set_pin_data(IC_OUTPUT, 2, TRUE) + set_pin_data(IC_OUTPUT, 3, "Light scanned.") push_data() + activate_pin(2) /obj/item/integrated_circuit/input/face_scanner name = "face scanner" @@ -1226,15 +1442,22 @@ /obj/item/integrated_circuit/input/anomaly_scanner name = "alden-saraspova counter" - desc = "A miniaturised Alden-Saraspova counter, used to detected anomalous readings. Often used by xenoarchaeologists." - extended_desc = "It can only scan a 14x14 area and has an built-in 15 second cooldown." + desc = "A miniaturized Alden-Saraspova counter, used to detected anomalous readings. Often used by xenoarchaeologists." + extended_desc = "It can only scan a 14x14 area and has a built-in 15 second cooldown." icon_state = "medscan_adv" complexity = 12 outputs = list( "exotic particle type" = IC_PINTYPE_STRING, - "range detected at" = IC_PINTYPE_NUMBER + "range detected at" = IC_PINTYPE_NUMBER, + "target turf" = IC_PINTYPE_REF, + "target X" = IC_PINTYPE_NUMBER, + "target Y" = IC_PINTYPE_NUMBER, + "target Z" = IC_PINTYPE_NUMBER, + "direction" = IC_PINTYPE_DIR, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) - activators = list("scan" = IC_PINTYPE_PULSE_IN, "anomaly found" = IC_PINTYPE_PULSE_OUT) + activators = list("scan" = IC_PINTYPE_PULSE_IN, "anomaly found" = IC_PINTYPE_PULSE_OUT, "nothing found" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 4, TECH_DATA = 4, TECH_MAGNET = 6, TECH_BLUESPACE = 4) power_draw_per_use = 2000 @@ -1242,17 +1465,494 @@ /obj/item/integrated_circuit/input/anomaly_scanner/do_work() if(!SSxenoarch) + set_pin_data(IC_OUTPUT, 8, FALSE) + set_pin_data(IC_OUTPUT, 9, "Xenoarchaeology subsystem unavailable.") + push_data() + activate_pin(3) return var/turf/our_turf = get_turf(src) + var/found_anomaly = FALSE for(var/turf/simulated/mineral/scanned_turf in view(our_turf)) // Restrict range to only visible tiles, instead of the entire Z-level like standalone AS counters if(scanned_turf.artifact_find) + found_anomaly = TRUE if(scanned_turf.artifact_find.artifact_id) set_pin_data(IC_OUTPUT, 1, scanned_turf.artifact_find.artifact_id) else set_pin_data(IC_OUTPUT, 1, "Exotic Particles (Various)") set_pin_data(IC_OUTPUT, 2, get_dist(our_turf, scanned_turf)) + set_pin_data(IC_OUTPUT, 3, scanned_turf) + set_pin_data(IC_OUTPUT, 4, scanned_turf.x) + set_pin_data(IC_OUTPUT, 5, scanned_turf.y) + set_pin_data(IC_OUTPUT, 6, scanned_turf.z) + set_pin_data(IC_OUTPUT, 7, get_dir(our_turf, scanned_turf)) + set_pin_data(IC_OUTPUT, 8, TRUE) + set_pin_data(IC_OUTPUT, 9, "Anomaly found.") push_data() activate_pin(2) break + if(!found_anomaly) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 3, null) + set_pin_data(IC_OUTPUT, 4, null) + set_pin_data(IC_OUTPUT, 5, null) + set_pin_data(IC_OUTPUT, 6, null) + set_pin_data(IC_OUTPUT, 7, null) + set_pin_data(IC_OUTPUT, 8, FALSE) + set_pin_data(IC_OUTPUT, 9, "No anomaly found.") + push_data() + activate_pin(3) ..() + +/obj/item/integrated_circuit/input/depth_scanner + name = "depth scanner" + desc = "A miniaturized depth analysis scanner, used to read xenoarchaeological depth and clearance data from scanned mineral turfs." + extended_desc = "Feed it a scanned turf reference. If the turf contains a find, it outputs required depth, clearance, current depth, safe depth, material, and coordinates." + icon_state = "medscan_adv" + complexity = 12 + inputs = list( + "target turf" = IC_PINTYPE_REF + ) + outputs = list( + "target turf" = IC_PINTYPE_REF, + "required depth" = IC_PINTYPE_NUMBER, + "clearance" = IC_PINTYPE_NUMBER, + "current depth" = IC_PINTYPE_NUMBER, + "safe depth" = IC_PINTYPE_NUMBER, + "material" = IC_PINTYPE_STRING, + "coordinates" = IC_PINTYPE_STRING, + "status" = IC_PINTYPE_STRING, + "target X" = IC_PINTYPE_NUMBER, + "target Y" = IC_PINTYPE_NUMBER, + "target Z" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "scan" = IC_PINTYPE_PULSE_IN, + "find detected" = IC_PINTYPE_PULSE_OUT, + "nothing detected" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 4, TECH_MAGNET = 4) + power_draw_per_use = 1000 + cooldown_per_use = 5 SECONDS + +/obj/item/integrated_circuit/input/depth_scanner/do_work() + var/turf/simulated/mineral/scanned_turf = get_pin_data_as_type(IC_INPUT, 1, /turf/simulated/mineral) + + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 3, null) + set_pin_data(IC_OUTPUT, 4, null) + set_pin_data(IC_OUTPUT, 5, null) + set_pin_data(IC_OUTPUT, 6, null) + set_pin_data(IC_OUTPUT, 7, null) + set_pin_data(IC_OUTPUT, 8, "No valid mineral turf scanned.") + set_pin_data(IC_OUTPUT, 9, null) + set_pin_data(IC_OUTPUT, 10, null) + set_pin_data(IC_OUTPUT, 11, null) + set_pin_data(IC_OUTPUT, 12, FALSE) + + if(!istype(scanned_turf)) + push_data() + activate_pin(3) + return + + var/turf/circuit_turf = get_turf(src) + if(!(scanned_turf in view(circuit_turf))) + set_pin_data(IC_OUTPUT, 8, "Target turf is out of scanner range.") + push_data() + activate_pin(3) + return + + if(!(scanned_turf.finds && scanned_turf.finds.len) && !scanned_turf.artifact_find) + set_pin_data(IC_OUTPUT, 1, scanned_turf) + set_pin_data(IC_OUTPUT, 7, "[scanned_turf.x], [scanned_turf.y], [scanned_turf.z]") + set_pin_data(IC_OUTPUT, 8, "No subsurface find detected.") + set_pin_data(IC_OUTPUT, 9, scanned_turf.x) + set_pin_data(IC_OUTPUT, 10, scanned_turf.y) + set_pin_data(IC_OUTPUT, 11, scanned_turf.z) + set_pin_data(IC_OUTPUT, 12, TRUE) + push_data() + activate_pin(3) + return + + var/required_depth = 0 + var/clearance = 0 + var/current_depth = scanned_turf.excavation_level * 2 + var/material = scanned_turf.mineral ? scanned_turf.mineral.display_name : "Rock" + + if(scanned_turf.finds && scanned_turf.finds.len) + var/datum/find/F = scanned_turf.finds[1] + required_depth = F.excavation_required * 2 + clearance = F.clearance_range * 2 + material = get_responsive_reagent(F.find_type) + else if(scanned_turf.artifact_find) + required_depth = rand(75, 100) + clearance = rand(5, 25) + material = "Unknown anomaly" + + var/safe_depth = max(required_depth - clearance, 0) + + set_pin_data(IC_OUTPUT, 1, scanned_turf) + set_pin_data(IC_OUTPUT, 2, required_depth) + set_pin_data(IC_OUTPUT, 3, clearance) + set_pin_data(IC_OUTPUT, 4, current_depth) + set_pin_data(IC_OUTPUT, 5, safe_depth) + set_pin_data(IC_OUTPUT, 6, material) + set_pin_data(IC_OUTPUT, 7, "[scanned_turf.x], [scanned_turf.y], [scanned_turf.z]") + set_pin_data(IC_OUTPUT, 8, "Find detected.") + set_pin_data(IC_OUTPUT, 9, scanned_turf.x) + set_pin_data(IC_OUTPUT, 10, scanned_turf.y) + set_pin_data(IC_OUTPUT, 11, scanned_turf.z) + set_pin_data(IC_OUTPUT, 12, TRUE) + + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/input/ref_validator + name = "reference validator" + desc = "Checks whether a reference is valid and identifies its general type." + icon_state = "video_camera" + complexity = 4 + inputs = list( + "reference" = IC_PINTYPE_REF + ) + outputs = list( + "valid" = IC_PINTYPE_BOOLEAN, + "name" = IC_PINTYPE_STRING, + "type path" = IC_PINTYPE_STRING, + "is mob" = IC_PINTYPE_BOOLEAN, + "is item" = IC_PINTYPE_BOOLEAN, + "is machinery" = IC_PINTYPE_BOOLEAN, + "is turf" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "validate" = IC_PINTYPE_PULSE_IN, + "valid" = IC_PINTYPE_PULSE_OUT, + "invalid" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/input/ref_validator/do_work() + var/atom/A = get_pin_data_as_type(IC_INPUT, 1, /atom) + + set_pin_data(IC_OUTPUT, 1, FALSE) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 3, null) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, FALSE) + set_pin_data(IC_OUTPUT, 6, FALSE) + set_pin_data(IC_OUTPUT, 7, FALSE) + + if(!A) + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, TRUE) + set_pin_data(IC_OUTPUT, 2, A.name) + set_pin_data(IC_OUTPUT, 3, "[A.type]") + set_pin_data(IC_OUTPUT, 4, ismob(A)) + set_pin_data(IC_OUTPUT, 5, isitem(A)) + set_pin_data(IC_OUTPUT, 6, istype(A, /obj/structure/machinery)) + set_pin_data(IC_OUTPUT, 7, isturf(A)) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/input/distance_checker + name = "distance checker" + desc = "Checks distance and adjacency between two references. If reference B is empty, it checks the tile in front of reference A." + extended_desc = "Compares reference A against reference B and outputs the distance, whether they are adjacent, whether they are on the same tile, and whether they are on the same z-level. If reference B is empty, the circuit uses the turf directly in front of reference A instead. This is useful for checking whether an assembly is facing a nearby object, wall, turf, or target location without needing a second reference input." + icon_state = "video_camera" + complexity = 4 + inputs = list( + "reference A" = IC_PINTYPE_REF, + "reference B" = IC_PINTYPE_REF + ) + outputs = list( + "distance" = IC_PINTYPE_NUMBER, + "adjacent" = IC_PINTYPE_BOOLEAN, + "same tile" = IC_PINTYPE_BOOLEAN, + "same z-level" = IC_PINTYPE_BOOLEAN, + "delta X" = IC_PINTYPE_NUMBER, + "delta Y" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + activators = list( + "check" = IC_PINTYPE_PULSE_IN, + "checked" = IC_PINTYPE_PULSE_OUT, + "not checked" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/input/distance_checker/do_work() + var/atom/A = get_pin_data_as_type(IC_INPUT, 1, /atom) + var/atom/B = get_pin_data_as_type(IC_INPUT, 2, /atom) + + if(!A) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, FALSE) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 7, FALSE) + set_pin_data(IC_OUTPUT, 8, "Invalid reference A.") + push_data() + activate_pin(3) + return + + var/turf/TA = get_turf(A) + var/turf/TB = null + if(TA) + TB = B ? get_turf(B) : get_step(TA, A.dir) + + if(!TA || !TB) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, FALSE) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 7, FALSE) + set_pin_data(IC_OUTPUT, 8, "Reference has no turf.") + push_data() + activate_pin(3) + return + + var/same_z = TA.z == TB.z + var/distance = same_z ? get_dist(TA, TB) : null + + set_pin_data(IC_OUTPUT, 1, distance) + set_pin_data(IC_OUTPUT, 2, same_z && distance <= 1) + set_pin_data(IC_OUTPUT, 3, TA == TB) + set_pin_data(IC_OUTPUT, 4, same_z) + set_pin_data(IC_OUTPUT, 5, TB.x - TA.x) + set_pin_data(IC_OUTPUT, 6, TB.y - TA.y) + set_pin_data(IC_OUTPUT, 7, TRUE) + set_pin_data(IC_OUTPUT, 8, "Distance checked.") + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/input/direction_to_ref + name = "direction to reference" + desc = "Calculates the direction from one reference to another." + icon_state = "video_camera" + complexity = 4 + inputs = list( + "source" = IC_PINTYPE_REF, + "target" = IC_PINTYPE_REF + ) + outputs = list( + "direction" = IC_PINTYPE_NUMBER, + "direction text" = IC_PINTYPE_STRING, + "delta X" = IC_PINTYPE_NUMBER, + "delta Y" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + activators = list( + "calculate" = IC_PINTYPE_PULSE_IN, + "calculated" = IC_PINTYPE_PULSE_OUT, + "not calculated" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/input/direction_to_ref/do_work() + var/atom/source = get_pin_data_as_type(IC_INPUT, 1, /atom) + var/atom/target = get_pin_data_as_type(IC_INPUT, 2, /atom) + + if(!source || !target) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 5, FALSE) + set_pin_data(IC_OUTPUT, 6, "Invalid source or target.") + push_data() + activate_pin(3) + return + + var/direction = get_dir(source, target) + var/turf/source_turf = get_turf(source) + var/turf/target_turf = get_turf(target) + if(!source_turf || !target_turf) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 5, FALSE) + set_pin_data(IC_OUTPUT, 6, "Source or target has no turf.") + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, direction) + set_pin_data(IC_OUTPUT, 2, dir2text(direction)) + set_pin_data(IC_OUTPUT, 3, target_turf.x - source_turf.x) + set_pin_data(IC_OUTPUT, 4, target_turf.y - source_turf.y) + set_pin_data(IC_OUTPUT, 5, TRUE) + set_pin_data(IC_OUTPUT, 6, "Direction calculated.") + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/input/area_scanner + name = "area scanner" + desc = "Returns the area of a referenced object." + icon_state = "video_camera" + complexity = 3 + inputs = list( + "target" = IC_PINTYPE_REF + ) + outputs = list( + "area name" = IC_PINTYPE_STRING, + "area ref" = IC_PINTYPE_REF + ) + activators = list( + "scan" = IC_PINTYPE_PULSE_IN, + "scanned" = IC_PINTYPE_PULSE_OUT, + "not scanned" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/input/area_scanner/do_work() + var/atom/A = get_pin_data_as_type(IC_INPUT, 1, /atom) + + if(!A) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + push_data() + activate_pin(3) + return + + var/area/AR = get_area(A) + + if(!AR) + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, AR.name) + set_pin_data(IC_OUTPUT, 2, AR) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/input/access_checker + name = "access checker" + desc = "Checks whether an access list contains required access." + icon_state = "card_reader" + complexity = 4 + inputs = list( + "access list" = IC_PINTYPE_LIST, + "required access" = IC_PINTYPE_LIST + ) + outputs = list( + "allowed" = IC_PINTYPE_BOOLEAN, + "missing access" = IC_PINTYPE_LIST + ) + activators = list( + "check" = IC_PINTYPE_PULSE_IN, + "allowed" = IC_PINTYPE_PULSE_OUT, + "denied" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/input/access_checker/do_work() + var/list/access_list = get_pin_data(IC_INPUT, 1) + var/list/required_access = get_pin_data(IC_INPUT, 2) + var/list/missing_access = list() + + if(!islist(access_list)) + access_list = list() + + if(!islist(required_access)) + required_access = list() + + for(var/access in required_access) + if(!(access in access_list)) + missing_access += access + + var/allowed = !length(missing_access) + + set_pin_data(IC_OUTPUT, 1, allowed) + set_pin_data(IC_OUTPUT, 2, missing_access) + + push_data() + activate_pin(allowed ? 2 : 3) + + +/obj/item/integrated_circuit/input/radio_command_receiver + name = "radio command receiver" + desc = "Receives radio-style command text relayed through an inserted radio." + extended_desc = "This circuit receives relayed radio text and exposes the speaker, message, channel, command, arguments, and payload. If a radio reference is provided, only messages relayed from that radio will be accepted." + icon_state = "recorder" + complexity = 10 + inputs = list( + "radio reference" = IC_PINTYPE_REF + ) + outputs = list( + "speaker" = IC_PINTYPE_STRING, + "message" = IC_PINTYPE_STRING, + "channel" = IC_PINTYPE_STRING, + "command" = IC_PINTYPE_STRING, + "argument 1" = IC_PINTYPE_STRING, + "argument 2" = IC_PINTYPE_STRING, + "argument list" = IC_PINTYPE_LIST, + "payload" = IC_PINTYPE_STRING + ) + activators = list( + "on command received" = IC_PINTYPE_PULSE_OUT + ) + power_draw_per_use = 150 + spawn_flags = null + + +/obj/item/integrated_circuit/input/radio_command_receiver/proc/receive_radio_command(speaker_name, message, channel, obj/item/radio/source_radio = null) + if(!check_then_do_work()) + return + + var/obj/item/radio/required_radio = get_pin_data_as_type(IC_INPUT, 1, /obj/item/radio) + + if(required_radio && !source_radio) + return + + if(required_radio && source_radio != required_radio) + return + + if(!istext(message) || !length(message)) + return + + var/list/parts = splittext(message, " ") + var/list/clean_parts = list() + + for(var/part in parts) + if(istext(part) && length(part)) + clean_parts += part + + if(!length(clean_parts)) + return + + var/list/arguments = list() + for(var/i = 2 to length(clean_parts)) + arguments += clean_parts[i] + + var/list/payload_parts = list() + for(var/i = 2 to length(clean_parts)) + payload_parts += clean_parts[i] + + var/payload = jointext(payload_parts, " ") + + set_pin_data(IC_OUTPUT, 1, speaker_name ? "[speaker_name]" : "Unknown") + set_pin_data(IC_OUTPUT, 2, message) + set_pin_data(IC_OUTPUT, 3, channel ? "[channel]" : null) + set_pin_data(IC_OUTPUT, 4, lowertext(clean_parts[1])) + set_pin_data(IC_OUTPUT, 5, length(clean_parts) >= 2 ? clean_parts[2] : null) + set_pin_data(IC_OUTPUT, 6, length(clean_parts) >= 3 ? clean_parts[3] : null) + set_pin_data(IC_OUTPUT, 7, arguments) + set_pin_data(IC_OUTPUT, 8, payload) + + push_data() + activate_pin(1) diff --git a/code/modules/integrated_electronics/subtypes/insert_slot.dm b/code/modules/integrated_electronics/subtypes/insert_slot.dm index edec8de243c..1e33386cb0b 100644 --- a/code/modules/integrated_electronics/subtypes/insert_slot.dm +++ b/code/modules/integrated_electronics/subtypes/insert_slot.dm @@ -1,56 +1,114 @@ -//Insert slots allow items to be inserted into assemblies from outside -//These items can then be used by other components +/* + * subtypes/insert_slot.dm + * Insert-slot circuits that hold items and expose slot state, item references, and capacity information to the circuit network. + */ + +//Insert slots allow items to be inserted into assemblies from outside. +//These items can then be used by other components through ref pins. + /obj/item/integrated_circuit/insert_slot category_text = "Insert slot" var/capacity = 0 var/list/allowed_types = list() var/list/items_contained = list() - activators = list("eject contents" = IC_PINTYPE_PULSE_IN) - outputs = list("has item" = IC_PINTYPE_BOOLEAN) + + activators = list( + "eject contents" = IC_PINTYPE_PULSE_IN, + "on inserted" = IC_PINTYPE_PULSE_OUT, + "on ejected" = IC_PINTYPE_PULSE_OUT + ) + + outputs = list( + "has item" = IC_PINTYPE_BOOLEAN, + "slot reference" = IC_PINTYPE_REF, + "item reference" = IC_PINTYPE_REF, + "item name" = IC_PINTYPE_STRING, + "stored count" = IC_PINTYPE_NUMBER, + "remaining capacity" = IC_PINTYPE_NUMBER + ) + power_draw_per_use = 10 w_class = WEIGHT_CLASS_NORMAL size = 5 complexity = 1 -//call this function from components that want to get items from this component -//set remove to FALSE if you dont want the item removed from the component and just want a reference to it -//(e.g. for beakers) -/obj/item/integrated_circuit/insert_slot/proc/get_item(var/remove = FALSE) - if(items_contained.len > 0) - var/itemToReturn = items_contained[1] - if(remove) - items_contained -= itemToReturn - if(items_contained.len <= 0) - set_pin_data(IC_OUTPUT, 1, FALSE) - push_data() - return itemToReturn +/obj/item/integrated_circuit/insert_slot/Initialize() + . = ..() + items_contained = list() + update_outputs() + +/obj/item/integrated_circuit/insert_slot/interact(mob/user) + update_outputs() + . = ..() + +/obj/item/integrated_circuit/insert_slot/proc/get_first_item() + if(items_contained.len) + return items_contained[1] return null +//Call this from components that want to get items from this component. +//Set remove to FALSE if you do not want the item removed from the slot and just want a reference. +/obj/item/integrated_circuit/insert_slot/proc/get_item(var/remove = FALSE) + if(items_contained.len > 0) + var/obj/item/item_to_return = items_contained[1] + + if(remove) + items_contained -= item_to_return + update_outputs() + push_data() + + return item_to_return + + return null + +/obj/item/integrated_circuit/insert_slot/proc/update_outputs() + var/obj/item/held_item = get_first_item() + + set_pin_data(IC_OUTPUT, 1, !!held_item) + set_pin_data(IC_OUTPUT, 2, src) + set_pin_data(IC_OUTPUT, 3, held_item) + set_pin_data(IC_OUTPUT, 4, held_item ? held_item.name : null) + set_pin_data(IC_OUTPUT, 5, items_contained.len) + set_pin_data(IC_OUTPUT, 6, max(capacity - items_contained.len, 0)) + /obj/item/integrated_circuit/insert_slot/do_work() - for(var/obj/item/O in items_contained) + while(items_contained.len) + var/obj/item/O = items_contained[1] + items_contained -= O O.forceMove(get_turf(src)) visible_message(SPAN_NOTICE("\The [src] drops [O] on the ground.")) - items_contained -= O - set_pin_data(IC_OUTPUT, 1, FALSE) + + update_outputs() push_data() + activate_pin(3) return TRUE /obj/item/integrated_circuit/insert_slot/proc/insert(var/obj/item/O, var/mob/user) - if(is_type_in_list(O, allowed_types)) - if(items_contained.len >= capacity) - to_chat(user, SPAN_WARNING("\The [src] is too full to add [O].")) - return FALSE - items_contained += O - user.drop_from_inventory(O,src) - to_chat(user, SPAN_NOTICE("You add [O] to \the [src].")) - set_pin_data(IC_OUTPUT, 1, TRUE) - return TRUE + if(!O || !user) + return FALSE + + if(!is_type_in_list(O, allowed_types)) + return FALSE + + if(items_contained.len >= capacity) + to_chat(user, SPAN_WARNING("\The [src] is too full to add [O].")) + return FALSE + + items_contained += O + user.drop_from_inventory(O, src) + + to_chat(user, SPAN_NOTICE("You add [O] to \the [src].")) + + update_outputs() + push_data() + activate_pin(2) + return TRUE /obj/item/integrated_circuit/insert_slot/paper_tray name = "paper tray" desc = "A simple paper tray similar to one from a printer." - extended_desc = "A simple paper tray, paper can be inserted and used by other components. \ - Paper can be inserted through a slot in the casing. Holds 10 sheets" + extended_desc = "A simple paper tray. Paper can be inserted and used by other components. Holds 10 sheets. \ + The slot reference output can be wired directly into printer paper-source inputs." capacity = 10 size = 8 power_draw_per_use = 30 @@ -58,13 +116,336 @@ spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 2, TECH_MATERIAL = 2) + outputs = list( + "has item" = IC_PINTYPE_BOOLEAN, + "slot reference" = IC_PINTYPE_REF, + "item reference" = IC_PINTYPE_REF, + "item name" = IC_PINTYPE_STRING, + "stored count" = IC_PINTYPE_NUMBER, + "remaining capacity" = IC_PINTYPE_NUMBER, + "top paper text" = IC_PINTYPE_STRING, + "top paper used length" = IC_PINTYPE_NUMBER + ) + +/obj/item/integrated_circuit/insert_slot/paper_tray/update_outputs() + ..() + + var/obj/item/paper/P = get_first_item() + + if(P) + set_pin_data(IC_OUTPUT, 7, P.info) + set_pin_data(IC_OUTPUT, 8, length(P.info)) + else + set_pin_data(IC_OUTPUT, 7, null) + set_pin_data(IC_OUTPUT, 8, 0) + /obj/item/integrated_circuit/insert_slot/beaker_holder name = "beaker holder" - desc = "A slot for holding a beaker" - extended_desc = "A slot for holding a beaker with reagents. \ - It has a bracket to hold the beaker in place and a lid to prevent spillage. \ - There is an extraction tube built into the lid" + desc = "A slot for holding a beaker." + extended_desc = "A slot for holding a beaker with reagents. It has a bracket to hold the beaker in place and a lid to prevent spillage. \ + The slot reference output can be wired directly into reagent pump source inputs." capacity = 1 allowed_types = list(/obj/item/reagent_containers/glass/beaker) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 2, TECH_BIO = 2, TECH_MATERIAL = 2) + + outputs = list( + "has item" = IC_PINTYPE_BOOLEAN, + "slot reference" = IC_PINTYPE_REF, + "item reference" = IC_PINTYPE_REF, + "item name" = IC_PINTYPE_STRING, + "stored count" = IC_PINTYPE_NUMBER, + "remaining capacity" = IC_PINTYPE_NUMBER, + "reagent volume" = IC_PINTYPE_NUMBER, + "maximum volume" = IC_PINTYPE_NUMBER, + "free space" = IC_PINTYPE_NUMBER, + "reagent names" = IC_PINTYPE_LIST + ) + +/obj/item/integrated_circuit/insert_slot/beaker_holder/update_outputs() + ..() + + var/obj/item/reagent_containers/glass/beaker/B = get_first_item() + var/list/reagent_names = list() + + if(B && B.reagents) + for(var/_RE in B.reagents.reagent_volumes) + var/singleton/reagent/RE = GET_SINGLETON(_RE) + reagent_names += RE.name + + set_pin_data(IC_OUTPUT, 7, B.reagents.total_volume) + set_pin_data(IC_OUTPUT, 8, B.reagents.maximum_volume) + set_pin_data(IC_OUTPUT, 9, REAGENTS_FREE_SPACE(B.reagents)) + set_pin_data(IC_OUTPUT, 10, reagent_names) + else + set_pin_data(IC_OUTPUT, 7, 0) + set_pin_data(IC_OUTPUT, 8, 0) + set_pin_data(IC_OUTPUT, 9, 0) + set_pin_data(IC_OUTPUT, 10, list()) + +/obj/item/integrated_circuit/insert_slot/id_slot + name = "ID card slot" + desc = "A slot for holding and reading an ID card." + extended_desc = "A narrow slot for holding a single ID card. Outputs the card reference, registered name, assignment, job title, corporation, and access list. \ + If credential caching is enabled, inserted access is copied to the assembly access card." + capacity = 1 + size = 4 + power_draw_per_use = 15 + allowed_types = list(/obj/item/card/id) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 2, TECH_DATA = 2) + + inputs = list( + "enable credential cache" = IC_PINTYPE_BOOLEAN + ) + + outputs = list( + "has item" = IC_PINTYPE_BOOLEAN, + "slot reference" = IC_PINTYPE_REF, + "item reference" = IC_PINTYPE_REF, + "item name" = IC_PINTYPE_STRING, + "stored count" = IC_PINTYPE_NUMBER, + "remaining capacity" = IC_PINTYPE_NUMBER, + "registered name" = IC_PINTYPE_STRING, + "assignment" = IC_PINTYPE_STRING, + "job title" = IC_PINTYPE_STRING, + "corporation" = IC_PINTYPE_STRING, + "passkey" = IC_PINTYPE_LIST + ) + +/obj/item/integrated_circuit/insert_slot/id_slot/insert(var/obj/item/O, var/mob/user) + . = ..() + if(.) + cache_access_if_enabled(O) + +/obj/item/integrated_circuit/insert_slot/id_slot/proc/cache_access_if_enabled(var/obj/item/I) + if(!get_pin_data(IC_INPUT, 1)) + return + + if(!assembly || !assembly.access_card) + return + + var/list/access = I.GetAccess() + if(access) + assembly.access_card.access |= access + +/obj/item/integrated_circuit/insert_slot/id_slot/update_outputs() + ..() + + var/obj/item/I = get_first_item() + set_pin_data(IC_OUTPUT, 1, !!I) + set_pin_data(IC_OUTPUT, 2, src) + set_pin_data(IC_OUTPUT, 3, I) + set_pin_data(IC_OUTPUT, 4, I ? I.name : null) + set_pin_data(IC_OUTPUT, 5, I ? 1 : 0) + set_pin_data(IC_OUTPUT, 6, I ? 0 : capacity) + var/obj/item/card/id/card = null + var/list/access = null + + if(I) + card = I.GetID() + access = I.GetAccess() + + if(card) + var/list/split_assignment = ic_split_assignment(card.assignment) + + set_pin_data(IC_OUTPUT, 7, card.registered_name) + set_pin_data(IC_OUTPUT, 8, card.assignment) + set_pin_data(IC_OUTPUT, 9, split_assignment["job title"]) + set_pin_data(IC_OUTPUT, 10, split_assignment["corporation"]) + set_pin_data(IC_OUTPUT, 11, access ? access : list()) + else + set_pin_data(IC_OUTPUT, 7, null) + set_pin_data(IC_OUTPUT, 8, null) + set_pin_data(IC_OUTPUT, 9, null) + set_pin_data(IC_OUTPUT, 10, null) + set_pin_data(IC_OUTPUT, 11, list()) + +/obj/item/integrated_circuit/insert_slot/card_slot + name = "card slot" + desc = "A generic slot for holding and reading card-like objects." + extended_desc = "A generic card slot. It accepts any card object, outputs the held item reference, and attempts to read access data from it." + capacity = 1 + size = 4 + power_draw_per_use = 15 + allowed_types = list(/obj/item/card) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 2, TECH_DATA = 2) + + inputs = list( + "enable credential cache" = IC_PINTYPE_BOOLEAN + ) + + outputs = list( + "has item" = IC_PINTYPE_BOOLEAN, + "slot reference" = IC_PINTYPE_REF, + "item reference" = IC_PINTYPE_REF, + "item name" = IC_PINTYPE_STRING, + "stored count" = IC_PINTYPE_NUMBER, + "remaining capacity" = IC_PINTYPE_NUMBER, + "registered name" = IC_PINTYPE_STRING, + "assignment" = IC_PINTYPE_STRING, + "job title" = IC_PINTYPE_STRING, + "corporation" = IC_PINTYPE_STRING, + "passkey" = IC_PINTYPE_LIST, + "has access data" = IC_PINTYPE_BOOLEAN + ) + +/obj/item/integrated_circuit/insert_slot/card_slot/insert(var/obj/item/O, var/mob/user) + . = ..() + if(.) + cache_access_if_enabled(O) + +/obj/item/integrated_circuit/insert_slot/card_slot/proc/cache_access_if_enabled(var/obj/item/I) + if(!get_pin_data(IC_INPUT, 1)) + return + + if(!assembly || !assembly.access_card) + return + + var/list/access = I.GetAccess() + if(access) + assembly.access_card.access |= access + +/obj/item/integrated_circuit/insert_slot/card_slot/update_outputs() + ..() + + var/obj/item/I = get_first_item() + var/obj/item/card/id/card = null + var/list/access = null + + if(I) + card = I.GetID() + access = I.GetAccess() + + if(card) + var/list/split_assignment = ic_split_assignment(card.assignment) + + set_pin_data(IC_OUTPUT, 7, card.registered_name) + set_pin_data(IC_OUTPUT, 8, card.assignment) + set_pin_data(IC_OUTPUT, 9, split_assignment["job title"]) + set_pin_data(IC_OUTPUT, 10, split_assignment["corporation"]) + set_pin_data(IC_OUTPUT, 11, access ? access : list()) + set_pin_data(IC_OUTPUT, 12, TRUE) + else + set_pin_data(IC_OUTPUT, 7, null) + set_pin_data(IC_OUTPUT, 8, null) + set_pin_data(IC_OUTPUT, 9, null) + set_pin_data(IC_OUTPUT, 10, null) + set_pin_data(IC_OUTPUT, 11, access ? access : list()) + set_pin_data(IC_OUTPUT, 12, !!length(access)) + +/obj/item/integrated_circuit/insert_slot/power_cell_slot + name = "power cell slot" + desc = "A socket for holding and reading a power cell." + extended_desc = "A reinforced socket that holds a single power cell. Outputs charge, maximum charge, and charge percentage." + capacity = 1 + size = 7 + power_draw_per_use = 20 + allowed_types = list( + /obj/item/cell, + /obj/item/cell/device, + /obj/item/cell/device/high + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 2, TECH_POWER = 3) + + outputs = list( + "has item" = IC_PINTYPE_BOOLEAN, + "slot reference" = IC_PINTYPE_REF, + "item reference" = IC_PINTYPE_REF, + "item name" = IC_PINTYPE_STRING, + "stored count" = IC_PINTYPE_NUMBER, + "remaining capacity" = IC_PINTYPE_NUMBER, + "cell charge" = IC_PINTYPE_NUMBER, + "max charge" = IC_PINTYPE_NUMBER, + "percentage" = IC_PINTYPE_NUMBER + ) + +/obj/item/integrated_circuit/insert_slot/power_cell_slot/update_outputs() + ..() + + var/obj/item/cell/C = get_first_item() + + if(C) + set_pin_data(IC_OUTPUT, 7, C.charge) + set_pin_data(IC_OUTPUT, 8, C.maxcharge) + set_pin_data(IC_OUTPUT, 9, C.percent()) + else + set_pin_data(IC_OUTPUT, 7, 0) + set_pin_data(IC_OUTPUT, 8, 0) + set_pin_data(IC_OUTPUT, 9, 0) + + +/obj/item/integrated_circuit/insert_slot/storage_slot + name = "storage slot" + desc = "A bracket for holding a storage container." + extended_desc = "A bracket that can hold one storage item. Outputs the storage reference for scanner, grabber, sorter, or custom storage logic." + capacity = 1 + size = 8 + power_draw_per_use = 25 + allowed_types = list(/obj/item/storage) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 2, TECH_MATERIAL = 2) + + outputs = list( + "has item" = IC_PINTYPE_BOOLEAN, + "slot reference" = IC_PINTYPE_REF, + "item reference" = IC_PINTYPE_REF, + "item name" = IC_PINTYPE_STRING, + "stored count" = IC_PINTYPE_NUMBER, + "remaining capacity" = IC_PINTYPE_NUMBER, + "storage reference" = IC_PINTYPE_REF, + "contained item count" = IC_PINTYPE_NUMBER + ) + +/obj/item/integrated_circuit/insert_slot/storage_slot/update_outputs() + ..() + + var/obj/item/storage/S = get_first_item() + + if(S) + set_pin_data(IC_OUTPUT, 7, S) + set_pin_data(IC_OUTPUT, 8, S.contents.len) + else + set_pin_data(IC_OUTPUT, 7, null) + set_pin_data(IC_OUTPUT, 8, 0) + +/obj/item/integrated_circuit/insert_slot/tool_slot + name = "tool slot" + desc = "A reinforced slot for holding a tool." + extended_desc = "A utility slot that can hold one tool or general item. Outputs the held item reference for machines that require a physical tool." + capacity = 1 + size = 6 + power_draw_per_use = 20 + allowed_types = list(/obj/item) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 2, TECH_MATERIAL = 2) + + outputs = list( + "has item" = IC_PINTYPE_BOOLEAN, + "slot reference" = IC_PINTYPE_REF, + "item reference" = IC_PINTYPE_REF, + "item name" = IC_PINTYPE_STRING, + "stored count" = IC_PINTYPE_NUMBER, + "remaining capacity" = IC_PINTYPE_NUMBER, + "tool reference" = IC_PINTYPE_REF + ) + +/obj/item/integrated_circuit/insert_slot/tool_slot/update_outputs() + ..() + + var/obj/item/I = get_first_item() + set_pin_data(IC_OUTPUT, 7, I) + +/obj/item/integrated_circuit/insert_slot/item_tray + name = "general item tray" + desc = "A generic tray for holding small items." + extended_desc = "A broad item tray. It accepts ordinary items and exposes the held item reference, name, count, and remaining capacity. \ + Use this when a more specific insert slot does not exist." + capacity = 5 + size = 8 + power_draw_per_use = 20 + allowed_types = list(/obj/item) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 2, TECH_MATERIAL = 2) diff --git a/code/modules/integrated_electronics/subtypes/lists.dm b/code/modules/integrated_electronics/subtypes/lists.dm index 7dbe521a087..ee50ba96523 100644 --- a/code/modules/integrated_electronics/subtypes/lists.dm +++ b/code/modules/integrated_electronics/subtypes/lists.dm @@ -1,3 +1,8 @@ +/* + * subtypes/lists.dm + * List circuits for creating, reading, writing, merging, filtering, and transforming list pin data. + */ + //These circuits do things with lists, and use special list pins for stability. /obj/item/integrated_circuit/list complexity = 1 @@ -9,27 +14,47 @@ category_text = "Lists" power_draw_per_use = 200 + +/obj/item/integrated_circuit/list/empty + name = "empty list circuit" + desc = "This circuit outputs an empty list." + extended_desc = "Outputs an empty list that can be passed into append, write, and other list-editing circuits." + inputs = list() + outputs = list( + "empty list" = IC_PINTYPE_LIST + ) + icon_state = "addition" + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/empty/do_work() + set_pin_data(IC_OUTPUT, 1, list()) + push_data() + activate_pin(2) + + /obj/item/integrated_circuit/list/pick name = "pick circuit" desc = "This circuit will randomly 'pick' an element from a list that is inputted." - extended_desc = "Will output null if the list is empty. Input list is unmodified." + extended_desc = "Outputs null if the list is empty. The input list is not changed." icon_state = "addition" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/list/pick/do_work() var/result = null - var/list/input_list = get_pin_data(IC_INPUT, 1) // List pins guarantee that there is a list inside, even if just an empty one. - if(input_list.len) + var/list/input_list = get_pin_data(IC_INPUT, 1) + + if(islist(input_list) && input_list.len) result = pick(input_list) set_pin_data(IC_OUTPUT, 1, result) push_data() activate_pin(2) + /obj/item/integrated_circuit/list/append name = "append circuit" desc = "This circuit will add an element to a list." - extended_desc = "The new element will always be at the bottom of the list." + extended_desc = "Adds the new element to the end of the list." inputs = list( "list to append" = IC_PINTYPE_LIST, "input" = IC_PINTYPE_ANY @@ -42,14 +67,105 @@ /obj/item/integrated_circuit/list/append/do_work() var/list/input_list = get_pin_data(IC_INPUT, 1) - var/list/output_list = list() - var/new_entry = get_pin_data(IC_INPUT, 2) - output_list = input_list + new_entry + var/list/output_list = islist(input_list) ? input_list.Copy() : list() + var/new_entry = get_pin_data(IC_INPUT, 2, FALSE) + + output_list += list(new_entry) set_pin_data(IC_OUTPUT, 1, output_list) push_data() activate_pin(2) + +/obj/item/integrated_circuit/list/unique_append + name = "unique append circuit" + desc = "Appends a value only if the list does not already contain it." + extended_desc = "The output list is copied from the input list and capped at the requested max length, up to a hard maximum of 64 entries." + inputs = list( + "list to append" = IC_PINTYPE_LIST, + "input" = IC_PINTYPE_ANY, + "max length" = IC_PINTYPE_NUMBER + ) + inputs_default = list( + "3" = 16 + ) + outputs = list( + "appended list" = IC_PINTYPE_LIST, + "appended" = IC_PINTYPE_BOOLEAN, + "duplicate" = IC_PINTYPE_BOOLEAN, + "full" = IC_PINTYPE_BOOLEAN, + "length" = IC_PINTYPE_NUMBER + ) + activators = list( + "append" = IC_PINTYPE_PULSE_IN, + "on appended" = IC_PINTYPE_PULSE_OUT, + "on duplicate" = IC_PINTYPE_PULSE_OUT, + "on full" = IC_PINTYPE_PULSE_OUT, + "on invalid" = IC_PINTYPE_PULSE_OUT + ) + icon_state = "addition" + complexity = 3 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/unique_append/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/new_entry = get_pin_data(IC_INPUT, 2, FALSE) + var/max_length = get_pin_data(IC_INPUT, 3) + + if(!islist(input_list)) + set_pin_data(IC_OUTPUT, 1, list()) + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, FALSE) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, 0) + push_data() + activate_pin(5) + return + + if(!isnum(max_length)) + max_length = 16 + max_length = clamp(round(max_length), 1, 64) + + var/list/output_list = list() + for(var/i = 1 to min(input_list.len, max_length)) + output_list += list(input_list[i]) + + var/appended = FALSE + + // Match list_contains behavior so refs and display-equivalent primitive values do not create accidental duplicates. + for(var/item in output_list) + if(item == new_entry || "[item]" == "[new_entry]") + set_pin_data(IC_OUTPUT, 1, output_list) + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, TRUE) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, output_list.len) + push_data() + activate_pin(3) + return + + if(output_list.len >= max_length) + set_pin_data(IC_OUTPUT, 1, output_list) + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, FALSE) + set_pin_data(IC_OUTPUT, 4, TRUE) + set_pin_data(IC_OUTPUT, 5, output_list.len) + push_data() + activate_pin(4) + return + + output_list += list(new_entry) + appended = TRUE + + set_pin_data(IC_OUTPUT, 1, output_list) + set_pin_data(IC_OUTPUT, 2, appended) + set_pin_data(IC_OUTPUT, 3, FALSE) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, output_list.len) + push_data() + activate_pin(2) + + /obj/item/integrated_circuit/list/search name = "search circuit" desc = "This circuit will give index of desired element in the list." @@ -67,10 +183,12 @@ /obj/item/integrated_circuit/list/search/do_work() var/list/input_list = get_pin_data(IC_INPUT, 1) var/item = get_pin_data(IC_INPUT, 2) - set_pin_data(IC_OUTPUT, 1, input_list.Find(item)) + + set_pin_data(IC_OUTPUT, 1, islist(input_list) ? input_list.Find(item) : 0) push_data() activate_pin(2) + /obj/item/integrated_circuit/list/at name = "at circuit" desc = "This circuit will pick an element from a list by index." @@ -79,18 +197,102 @@ "list" = IC_PINTYPE_LIST, "index" = IC_PINTYPE_NUMBER ) - outputs = list("item" = IC_PINTYPE_ANY) + outputs = list( + "item" = IC_PINTYPE_ANY, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) icon_state = "addition" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/list/at/do_work() var/list/input_list = get_pin_data(IC_INPUT, 1) var/index = get_pin_data(IC_INPUT, 2) - var/item = input_list[index] + var/item = null + var/valid = FALSE + var/status = "Invalid index." + + if(!islist(input_list)) + input_list = list() + + if(!isnull(index)) + index = round(index) + + if(index >= 1 && index <= input_list.len) + item = input_list[index] + valid = TRUE + status = "Item read." + else + status = "Index out of range." + set_pin_data(IC_OUTPUT, 1, item) + set_pin_data(IC_OUTPUT, 2, valid) + set_pin_data(IC_OUTPUT, 3, status) push_data() activate_pin(2) + +/obj/item/integrated_circuit/list/ring_index + name = "ring index circuit" + desc = "Advances an index through a list and wraps safely at either end." + extended_desc = "Outputs the wrapped next index and selected value. Empty lists return invalid without changing anything." + inputs = list( + "list" = IC_PINTYPE_LIST, + "current index" = IC_PINTYPE_NUMBER, + "step" = IC_PINTYPE_NUMBER + ) + inputs_default = list( + "2" = 0, + "3" = 1 + ) + outputs = list( + "next index" = IC_PINTYPE_NUMBER, + "selected value" = IC_PINTYPE_ANY, + "length" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "select" = IC_PINTYPE_PULSE_IN, + "on selected" = IC_PINTYPE_PULSE_OUT, + "on invalid" = IC_PINTYPE_PULSE_OUT + ) + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/ring_index/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/list_length = islist(input_list) ? input_list.len : 0 + + if(!list_length) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 3, 0) + set_pin_data(IC_OUTPUT, 4, FALSE) + push_data() + activate_pin(3) + return + + var/current_index = get_pin_data(IC_INPUT, 2) + var/step = get_pin_data(IC_INPUT, 3) + + if(!isnum(current_index)) + current_index = 0 + if(!isnum(step)) + step = 1 + + var/next_index = ((round(current_index) + round(step) - 1) % list_length) + 1 + if(next_index < 1) + next_index += list_length + + set_pin_data(IC_OUTPUT, 1, next_index) + set_pin_data(IC_OUTPUT, 2, input_list[next_index]) + set_pin_data(IC_OUTPUT, 3, list_length) + set_pin_data(IC_OUTPUT, 4, TRUE) + push_data() + activate_pin(2) + + /obj/item/integrated_circuit/list/delete name = "delete circuit" desc = "This circuit will delete the element from a list by index." @@ -100,73 +302,183 @@ "index" = IC_PINTYPE_NUMBER ) outputs = list( - "item" = IC_PINTYPE_LIST + "redacted list" = IC_PINTYPE_LIST, + "deleted" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) icon_state = "addition" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/list/delete/do_work() var/list/input_list = get_pin_data(IC_INPUT, 1) - var/list/red_list = list() + var/list/output_list = islist(input_list) ? input_list.Copy() : list() var/index = get_pin_data(IC_INPUT, 2) - var/j = 0 - for(var/I in input_list) - j = j + 1 - if(j != index) - red_list.Add(I) - set_pin_data(IC_OUTPUT, 1, red_list) + var/deleted = FALSE + var/status = "Invalid index." + + if(!isnull(index)) + index = round(index) + + if(index >= 1 && index <= output_list.len) + output_list.Cut(index, index + 1) + deleted = TRUE + status = "Item deleted." + else + status = "Index out of range." + + set_pin_data(IC_OUTPUT, 1, output_list) + set_pin_data(IC_OUTPUT, 2, deleted) + set_pin_data(IC_OUTPUT, 3, status) push_data() activate_pin(2) + /obj/item/integrated_circuit/list/write name = "write circuit" - desc = "This circuit will write element in list with given index." - extended_desc = "If there is no element with such index, it will give the same list, as before." + desc = "This circuit will write an element into a list with a given index." + extended_desc = "If the index is beyond the current list length, the list will expand with null entries until the index exists." inputs = list( "list" = IC_PINTYPE_LIST, "index" = IC_PINTYPE_NUMBER, "item" = IC_PINTYPE_ANY ) outputs = list( - "redacted list" = IC_PINTYPE_LIST + "redacted list" = IC_PINTYPE_LIST, + "written" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING ) icon_state = "addition" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/list/write/do_work() var/list/input_list = get_pin_data(IC_INPUT, 1) + var/list/output_list = islist(input_list) ? input_list.Copy() : list() var/index = get_pin_data(IC_INPUT, 2) var/item = get_pin_data(IC_INPUT, 3) - input_list[index] = item - set_pin_data(IC_OUTPUT, 1, input_list) + var/written = FALSE + var/status = "Invalid index." + + if(!isnull(index)) + index = round(index) + + if(index >= 1 && index <= IC_MAX_LIST_LENGTH) + while(output_list.len < index) + output_list.Add(null) + + output_list[index] = item + written = TRUE + status = "Item written." + else + status = "Index out of range." + + set_pin_data(IC_OUTPUT, 1, output_list) + set_pin_data(IC_OUTPUT, 2, written) + set_pin_data(IC_OUTPUT, 3, status) push_data() activate_pin(2) + +/obj/item/integrated_circuit/list/write4 + name = "4-way write circuit" + desc = "This circuit writes up to four items into a list." + extended_desc = "Each item is written directly to its matching list position. Item 1 writes to list position 1, item 2 writes to position 2, and so on." + inputs = list( + "list" = IC_PINTYPE_LIST, + "item 1" = IC_PINTYPE_ANY, + "item 2" = IC_PINTYPE_ANY, + "item 3" = IC_PINTYPE_ANY, + "item 4" = IC_PINTYPE_ANY + ) + outputs = list( + "redacted list" = IC_PINTYPE_LIST + ) + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/write4/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/list/output_list = islist(input_list) ? input_list.Copy() : list() + + for(var/i = 1; i <= 4; i++) + var/item = get_pin_data(IC_INPUT, i + 1) + + while(output_list.len < i) + output_list.Add(null) + + output_list[i] = item + + set_pin_data(IC_OUTPUT, 1, output_list) + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/list/write8 + name = "8-way write circuit" + desc = "This circuit writes up to eight items into a list." + extended_desc = "Each item is written directly to its matching list position. Item 1 writes to list position 1, item 2 writes to position 2, and so on." + inputs = list( + "list" = IC_PINTYPE_LIST, + "item 1" = IC_PINTYPE_ANY, + "item 2" = IC_PINTYPE_ANY, + "item 3" = IC_PINTYPE_ANY, + "item 4" = IC_PINTYPE_ANY, + "item 5" = IC_PINTYPE_ANY, + "item 6" = IC_PINTYPE_ANY, + "item 7" = IC_PINTYPE_ANY, + "item 8" = IC_PINTYPE_ANY + ) + outputs = list( + "redacted list" = IC_PINTYPE_LIST + ) + icon_state = "addition" + complexity = 3 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/write8/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/list/output_list = islist(input_list) ? input_list.Copy() : list() + + for(var/i = 1; i <= 8; i++) + var/item = get_pin_data(IC_INPUT, i + 1) + + while(output_list.len < i) + output_list.Add(null) + + output_list[i] = item + + set_pin_data(IC_OUTPUT, 1, output_list) + push_data() + activate_pin(2) + + /obj/item/integrated_circuit/list/len name = "len circuit" desc = "This circuit will give length of the list." inputs = list( - "list" = IC_PINTYPE_LIST, - ) + "list" = IC_PINTYPE_LIST + ) outputs = list( - "item" = IC_PINTYPE_NUMBER + "length" = IC_PINTYPE_NUMBER ) icon_state = "addition" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/list/len/do_work() var/list/input_list = get_pin_data(IC_INPUT, 1) - set_pin_data(IC_OUTPUT, 1, input_list.len) + + set_pin_data(IC_OUTPUT, 1, islist(input_list) ? input_list.len : 0) push_data() activate_pin(2) + /obj/item/integrated_circuit/list/jointext name = "join text circuit" - desc = "This circuit will add all elements of a list into one string, seperated by a character." - extended_desc = "Default settings will encode the entire list into a string." + desc = "This circuit will add all elements of a list into one string, separated by a character." + extended_desc = "Default settings will encode the entire list into a string. Mixed list values are converted to display text before joining." inputs = list( "list to join" = IC_PINTYPE_LIST, - "delimiter" = IC_PINTYPE_CHAR, + "delimiter" = IC_PINTYPE_STRING, "start" = IC_PINTYPE_NUMBER, "end" = IC_PINTYPE_NUMBER ) @@ -189,9 +501,501 @@ var/result = null - if(input_list.len && delimiter && !isnull(start) && !isnull(end)) - result = jointext(input_list, delimiter, start, end) + if(islist(input_list) && input_list.len && !isnull(delimiter) && !isnull(start) && !isnull(end)) + var/list/filtered_list = list() + for(var/item in input_list) + if(!isnull(item)) + filtered_list += item + result = jointext(filtered_list, delimiter, start, end) + + set_pin_data(IC_OUTPUT, 1, result) + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/list/any_greater_than + name = "any greater than circuit" + desc = "This circuit checks whether any number in a list is greater than a target value." + extended_desc = "Non-number values are ignored. Outputs true if at least one number in the list is greater than the comparison value." + category_text = "MATH - List Comparisons" + inputs = list( + "list" = IC_PINTYPE_LIST, + "compare value" = IC_PINTYPE_NUMBER + ) + outputs = list( + "result" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "compare" = IC_PINTYPE_PULSE_IN, + "on true result" = IC_PINTYPE_PULSE_OUT, + "on false result" = IC_PINTYPE_PULSE_OUT + ) + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/any_greater_than/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/compare_value = get_pin_data(IC_INPUT, 2) + + var/result = FALSE + + if(islist(input_list) && !isnull(compare_value)) + for(var/value in input_list) + if(!isnum(value)) + continue + + if(value > compare_value) + result = TRUE + break + + set_pin_data(IC_OUTPUT, 1, result) + push_data() + + if(result) + activate_pin(2) + else + activate_pin(3) + + +/obj/item/integrated_circuit/list/any_less_than + name = "any less than circuit" + desc = "This circuit checks whether any number in a list is less than a target value." + extended_desc = "Non-number values are ignored. Outputs true if at least one number in the list is less than the comparison value." + category_text = "MATH - List Comparisons" + inputs = list( + "list" = IC_PINTYPE_LIST, + "compare value" = IC_PINTYPE_NUMBER + ) + outputs = list( + "result" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "compare" = IC_PINTYPE_PULSE_IN, + "on true result" = IC_PINTYPE_PULSE_OUT, + "on false result" = IC_PINTYPE_PULSE_OUT + ) + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/any_less_than/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/compare_value = get_pin_data(IC_INPUT, 2) + + var/result = FALSE + + if(islist(input_list) && !isnull(compare_value)) + for(var/value in input_list) + if(!isnum(value)) + continue + + if(value < compare_value) + result = TRUE + break + + set_pin_data(IC_OUTPUT, 1, result) + push_data() + + if(result) + activate_pin(2) + else + activate_pin(3) + + +/obj/item/integrated_circuit/list/any_equal_to + name = "any equal to circuit" + desc = "This circuit checks whether any value in a list is equal to a target value." + extended_desc = "Outputs true if at least one value in the list is equal to the comparison value. This can compare numbers, strings, booleans, refs, or null." + category_text = "MATH - List Comparisons" + inputs = list( + "list" = IC_PINTYPE_LIST, + "compare value" = IC_PINTYPE_ANY + ) + outputs = list( + "result" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "compare" = IC_PINTYPE_PULSE_IN, + "on true result" = IC_PINTYPE_PULSE_OUT, + "on false result" = IC_PINTYPE_PULSE_OUT + ) + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/any_equal_to/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/compare_value = get_pin_data(IC_INPUT, 2) + + var/result = FALSE + + if(islist(input_list)) + for(var/value in input_list) + if(value == compare_value) + result = TRUE + break + + set_pin_data(IC_OUTPUT, 1, result) + push_data() + + if(result) + activate_pin(2) + else + activate_pin(3) + +/obj/item/integrated_circuit/list/arithmetic + name = "list arithmetic" + desc = "This circuit performs basic arithmetic between two lists of numbers." + extended_desc = "Takes two lists and applies the selected operation index-by-index. If either value is not a number, the output at that position is null. Supported operations are add, subtract, multiply, and divide." + category_text = "MATH - List Math" + icon_state = "addition" + complexity = 8 + inputs = list( + "list A" = IC_PINTYPE_LIST, + "list B" = IC_PINTYPE_LIST, + "operation" = IC_PINTYPE_STRING + ) + outputs = list( + "result" = IC_PINTYPE_LIST + ) + activators = list( + "calculate" = IC_PINTYPE_PULSE_IN, + "on calculated" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/arithmetic/do_work() + pull_data() + + var/list/list_a = get_pin_data(IC_INPUT, 1) + var/list/list_b = get_pin_data(IC_INPUT, 2) + var/operation = lowertext("[get_pin_data(IC_INPUT, 3)]") + + var/list/result = list() + + if(!islist(list_a) || !islist(list_b)) + set_pin_data(IC_OUTPUT, 1, null) + push_data() + activate_pin(2) + return + + var/max_length = max(length(list_a), length(list_b)) + + for(var/i = 1 to max_length) + var/a = list_a[i] + var/b = list_b[i] + + if(!isnum(a) || !isnum(b)) + result.len++ + continue + + switch(operation) + if("add", "+", "addition") + result += a + b + + if("subtract", "-", "subtraction") + result += a - b + + if("multiply", "*", "multiplication") + result += a * b + + if("divide", "/", "division") + if(b == 0) + result.len++ + else + result += a / b + + else + result.len++ set_pin_data(IC_OUTPUT, 1, result) push_data() activate_pin(2) + +/obj/item/integrated_circuit/list/list_contains + name = "list contains" + desc = "Checks whether a list contains a value." + category_text = "MATH - List Comparisons" + icon_state = "template" + complexity = 3 + inputs = list( + "list" = IC_PINTYPE_LIST, + "value" = IC_PINTYPE_ANY + ) + outputs = list( + "contains" = IC_PINTYPE_BOOLEAN, + "index" = IC_PINTYPE_NUMBER, + "matched value" = IC_PINTYPE_ANY + ) + activators = list( + "check" = IC_PINTYPE_PULSE_IN, + "found" = IC_PINTYPE_PULSE_OUT, + "not found" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/list_contains/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/value = get_pin_data(IC_INPUT, 2, FALSE) + + set_pin_data(IC_OUTPUT, 1, FALSE) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 3, null) + + if(!islist(input_list) || !length(input_list)) + push_data() + activate_pin(3) + return + + var/index = 1 + + for(var/item in input_list) + if(item == value || "[item]" == "[value]") + set_pin_data(IC_OUTPUT, 1, TRUE) + set_pin_data(IC_OUTPUT, 2, index) + set_pin_data(IC_OUTPUT, 3, item) + push_data() + activate_pin(2) + return + + index++ + + push_data() + activate_pin(3) + + +/obj/item/integrated_circuit/list/list_filter_text + name = "list text filter" + desc = "Filters a list by matching text against each value." + icon_state = "template" + complexity = 5 + inputs = list( + "list" = IC_PINTYPE_LIST, + "match text" = IC_PINTYPE_STRING + ) + outputs = list( + "filtered list" = IC_PINTYPE_LIST, + "matches found" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "filter" = IC_PINTYPE_PULSE_IN, + "found" = IC_PINTYPE_PULSE_OUT, + "not found" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/list_filter_text/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/match_text = get_pin_data(IC_INPUT, 2) + var/list/filtered_list = list() + + if(!islist(input_list) || !istext(match_text) || !length(match_text)) + set_pin_data(IC_OUTPUT, 1, filtered_list) + set_pin_data(IC_OUTPUT, 2, FALSE) + push_data() + activate_pin(3) + return + + var/lower_match = lowertext(match_text) + + for(var/item in input_list) + var/check_text = lowertext("[item]") + + if(isweakref(item)) + var/datum/weakref/WR = item + var/atom/A = WR.resolve() + if(A) + check_text = lowertext("[A.name] [A.desc]") + + else if(istype(item, /atom)) + var/atom/A = item + check_text = lowertext("[A.name] [A.desc]") + + if(findtext(check_text, lower_match)) + filtered_list += item + + set_pin_data(IC_OUTPUT, 1, filtered_list) + set_pin_data(IC_OUTPUT, 2, length(filtered_list) ? TRUE : FALSE) + + push_data() + activate_pin(length(filtered_list) ? 2 : 3) + + +/obj/item/integrated_circuit/list/list_join + name = "list joiner" + desc = "Joins a list into a single string using a separator." + icon_state = "template" + complexity = 3 + inputs = list( + "list" = IC_PINTYPE_LIST, + "separator" = IC_PINTYPE_STRING + ) + outputs = list( + "joined string" = IC_PINTYPE_STRING + ) + activators = list( + "join" = IC_PINTYPE_PULSE_IN, + "on joined" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/list_join/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/separator = get_pin_data(IC_INPUT, 2) + + if(!islist(input_list)) + input_list = list() + + if(!istext(separator)) + separator = ", " + + var/list/text_values = list() + + for(var/item in input_list) + text_values += "[item]" + + set_pin_data(IC_OUTPUT, 1, jointext(text_values, separator)) + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/list/list_sort + name = "list sorter" + desc = "Sorts a list." + extended_desc = "Sorts values alphabetically or numerically depending on what the list contains." + icon_state = "template" + complexity = 4 + inputs = list( + "list" = IC_PINTYPE_LIST, + "ascending" = IC_PINTYPE_BOOLEAN + ) + outputs = list( + "sorted list" = IC_PINTYPE_LIST + ) + activators = list( + "sort" = IC_PINTYPE_PULSE_IN, + "on sorted" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/list_sort/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + var/ascending = get_pin_data(IC_INPUT, 2) + + if(!islist(input_list)) + input_list = list() + + var/list/sorted_list = input_list.Copy() + sortTim(sorted_list, /proc/cmp_text_asc) + + if(!ascending) + sorted_list = reverseList(sorted_list) + + set_pin_data(IC_OUTPUT, 1, sorted_list) + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/list/random_selector + name = "random list selector" + desc = "Selects a random value from a list." + icon_state = "template" + complexity = 3 + inputs = list( + "list" = IC_PINTYPE_LIST + ) + outputs = list( + "selected value" = IC_PINTYPE_ANY, + "selected index" = IC_PINTYPE_NUMBER + ) + activators = list( + "select" = IC_PINTYPE_PULSE_IN, + "selected" = IC_PINTYPE_PULSE_OUT, + "not selected" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/random_selector/do_work() + var/list/input_list = get_pin_data(IC_INPUT, 1) + + var/list_length = islist(input_list) ? input_list.len : 0 + if(!list_length) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + push_data() + activate_pin(3) + return + + var/index = rand(1, list_length) + + set_pin_data(IC_OUTPUT, 1, input_list[index]) + set_pin_data(IC_OUTPUT, 2, index) + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/list/weighted_random_selector + name = "weighted random selector" + desc = "Selects a random value from a list using a matching list of weights." + icon_state = "template" + complexity = 6 + inputs = list( + "values" = IC_PINTYPE_LIST, + "weights" = IC_PINTYPE_LIST + ) + outputs = list( + "selected value" = IC_PINTYPE_ANY, + "selected index" = IC_PINTYPE_NUMBER + ) + activators = list( + "select" = IC_PINTYPE_PULSE_IN, + "selected" = IC_PINTYPE_PULSE_OUT, + "not selected" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/list/weighted_random_selector/do_work() + var/list/values = get_pin_data(IC_INPUT, 1) + var/list/weights = get_pin_data(IC_INPUT, 2) + + if(!islist(values) || !islist(weights) || !length(values) || !length(weights)) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, null) + push_data() + activate_pin(3) + return + + var/selection_count = min(values.len, weights.len) + var/total_weight = 0 + + for(var/i = 1 to selection_count) + var/weight = weights[i] + if(isnum(weight) && weight > 0) + total_weight += weight + + if(total_weight <= 0) + push_data() + activate_pin(3) + return + + var/roll = rand(1, total_weight) + var/current_weight = 0 + + for(var/i = 1 to selection_count) + var/weight = weights[i] + + if(!isnum(weight) || weight <= 0) + continue + + current_weight += weight + + if(roll <= current_weight) + set_pin_data(IC_OUTPUT, 1, values[i]) + set_pin_data(IC_OUTPUT, 2, i) + push_data() + activate_pin(2) + return + + push_data() + activate_pin(3) diff --git a/code/modules/integrated_electronics/subtypes/logic.dm b/code/modules/integrated_electronics/subtypes/logic.dm index d6a718d34cc..0f0f0c8f112 100644 --- a/code/modules/integrated_electronics/subtypes/logic.dm +++ b/code/modules/integrated_electronics/subtypes/logic.dm @@ -1,11 +1,16 @@ +/* + * subtypes/logic.dm + * Logic and comparison circuits for boolean gates, equality checks, numeric comparisons, and conditional routing. + */ + /obj/item/integrated_circuit/logic name = "logic gate" desc = "This tiny chip will decide for you!" - extended_desc = "Logic circuits will treat a null, 0, and a \"\" string value as FALSE and anything else as TRUE. If inputs are of mismatching type, expect undocumented behaviour." + extended_desc = "Logic circuits treat null, 0, and empty text as FALSE. Non-zero numbers, non-empty text, valid refs, and populated lists are TRUE. Compare matching value types for reliable results." complexity = 3 outputs = list("result" = IC_PINTYPE_BOOLEAN) activators = list("compare" = IC_PINTYPE_PULSE_IN) - category_text = "Logic" + category_text = "LOGIC - Boolean Logic" power_draw_per_use = 10 /obj/item/integrated_circuit/logic/binary @@ -72,6 +77,7 @@ name = "equal gate" desc = "This gate compares two values, and outputs the number one if both are the same." icon_state = "equal" + category_text = "LOGIC - Comparisons" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/logic/binary/equals/do_compare(A, B) @@ -159,6 +165,7 @@ name = "not equal gate" desc = "This gate compares two values, and outputs the number one if both are different." icon_state = "not_equal" + category_text = "LOGIC - Comparisons" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/logic/binary/not_equals/do_compare(A, B) @@ -186,6 +193,7 @@ name = "less than gate" desc = "This will output 'one' if the first input is less than the second input." icon_state = "less_than" + category_text = "LOGIC - Comparisons" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/logic/binary/less_than/do_compare(A, B) @@ -195,6 +203,7 @@ name = "less than or equal gate" desc = "This will output 'one' if the first input is less than, or equal to the second input." icon_state = "less_than_or_equal" + category_text = "LOGIC - Comparisons" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/logic/binary/less_than_or_equal/do_compare(A, B) @@ -204,6 +213,7 @@ name = "greater than gate" desc = "This will output 'one' if the first input is greater than the second input." icon_state = "greater_than" + category_text = "LOGIC - Comparisons" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/logic/binary/greater_than/do_compare(A, B) @@ -213,6 +223,7 @@ name = "greater_than or equal gate" desc = "This will output 'one' if the first input is greater than, or equal to the second input." icon_state = "greater_than_or_equal" + category_text = "LOGIC - Comparisons" spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH /obj/item/integrated_circuit/logic/binary/greater_than_or_equal/do_compare(A, B) @@ -227,3 +238,336 @@ /obj/item/integrated_circuit/logic/unary/not/do_check(A) return !A + +/obj/item/integrated_circuit/logic/edge_detector + name = "edge detector" + desc = "Turns a changing boolean signal into one-shot output pulses." + extended_desc = "Stores the previous signal state internally. It only pulses when the input changes, making it useful for preventing ticker-fed logic from repeatedly firing the same action." + icon_state = "template" + complexity = 2 + inputs = list( + "signal" = IC_PINTYPE_BOOLEAN + ) + outputs = list( + "current state" = IC_PINTYPE_BOOLEAN, + "previous state" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "check" = IC_PINTYPE_PULSE_IN, + "on rising edge" = IC_PINTYPE_PULSE_OUT, + "on falling edge" = IC_PINTYPE_PULSE_OUT, + "on changed" = IC_PINTYPE_PULSE_OUT, + "on unchanged" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + var/previous_state = FALSE + var/has_previous_state = FALSE + +/obj/item/integrated_circuit/logic/edge_detector/do_work() + var/current_state = get_pin_data(IC_INPUT, 1) ? TRUE : FALSE + var/old_state = previous_state + + set_pin_data(IC_OUTPUT, 1, current_state) + set_pin_data(IC_OUTPUT, 2, old_state) + push_data() + + if(!has_previous_state) + previous_state = current_state + has_previous_state = TRUE + activate_pin(5) + return + + if(current_state == previous_state) + activate_pin(5) + return + + previous_state = current_state + activate_pin(current_state ? 2 : 3) + activate_pin(4) + +/obj/item/integrated_circuit/logic/threshold_comparator + name = "threshold comparator" + desc = "Checks whether a number is below, inside, or above a range." + icon_state = "comparator" + category_text = "LOGIC - Comparisons" + complexity = 3 + inputs = list( + "value" = IC_PINTYPE_NUMBER, + "minimum" = IC_PINTYPE_NUMBER, + "maximum" = IC_PINTYPE_NUMBER + ) + outputs = list( + "below range" = IC_PINTYPE_BOOLEAN, + "inside range" = IC_PINTYPE_BOOLEAN, + "above range" = IC_PINTYPE_BOOLEAN, + "status text" = IC_PINTYPE_STRING + ) + activators = list( + "compare" = IC_PINTYPE_PULSE_IN, + "below" = IC_PINTYPE_PULSE_OUT, + "inside" = IC_PINTYPE_PULSE_OUT, + "above" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/logic/threshold_comparator/do_work() + var/value = get_pin_data(IC_INPUT, 1) + var/minimum = get_pin_data(IC_INPUT, 2) + var/maximum = get_pin_data(IC_INPUT, 3) + + if(!isnum(value) || !isnum(minimum) || !isnum(maximum)) + return + + var/below = value < minimum + var/above = value > maximum + var/inside = !below && !above + + set_pin_data(IC_OUTPUT, 1, below) + set_pin_data(IC_OUTPUT, 2, inside) + set_pin_data(IC_OUTPUT, 3, above) + set_pin_data(IC_OUTPUT, 4, below ? "BELOW RANGE" : above ? "ABOVE RANGE" : "INSIDE RANGE") + + push_data() + + if(below) + activate_pin(2) + else if(inside) + activate_pin(3) + else + activate_pin(4) + + +/obj/item/integrated_circuit/logic/multi_threshold_status + name = "multi-threshold status" + desc = "Classifies a number as normal, warning, or danger using low and high thresholds." + icon_state = "comparator" + category_text = "LOGIC - Comparisons" + complexity = 5 + inputs = list( + "value" = IC_PINTYPE_NUMBER, + "danger low" = IC_PINTYPE_NUMBER, + "warning low" = IC_PINTYPE_NUMBER, + "warning high" = IC_PINTYPE_NUMBER, + "danger high" = IC_PINTYPE_NUMBER + ) + outputs = list( + "normal" = IC_PINTYPE_BOOLEAN, + "warning" = IC_PINTYPE_BOOLEAN, + "danger" = IC_PINTYPE_BOOLEAN, + "status text" = IC_PINTYPE_STRING + ) + activators = list( + "check" = IC_PINTYPE_PULSE_IN, + "normal" = IC_PINTYPE_PULSE_OUT, + "warning" = IC_PINTYPE_PULSE_OUT, + "danger" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/logic/multi_threshold_status/do_work() + var/value = get_pin_data(IC_INPUT, 1) + var/danger_low = get_pin_data(IC_INPUT, 2) + var/warning_low = get_pin_data(IC_INPUT, 3) + var/warning_high = get_pin_data(IC_INPUT, 4) + var/danger_high = get_pin_data(IC_INPUT, 5) + + if(!isnum(value) || !isnum(danger_low) || !isnum(warning_low) || !isnum(warning_high) || !isnum(danger_high)) + return + + var/normal = FALSE + var/warning = FALSE + var/danger = FALSE + var/status = "NORMAL" + + if(value <= danger_low) + danger = TRUE + status = "LOW DANGER" + else if(value < warning_low) + warning = TRUE + status = "LOW WARNING" + else if(value >= danger_high) + danger = TRUE + status = "HIGH DANGER" + else if(value > warning_high) + warning = TRUE + status = "HIGH WARNING" + else + normal = TRUE + + set_pin_data(IC_OUTPUT, 1, normal) + set_pin_data(IC_OUTPUT, 2, warning) + set_pin_data(IC_OUTPUT, 3, danger) + set_pin_data(IC_OUTPUT, 4, status) + + push_data() + + if(normal) + activate_pin(2) + else if(warning) + activate_pin(3) + else + activate_pin(4) + + +/obj/item/integrated_circuit/logic/cooldown_limiter + name = "cooldown limiter" + desc = "Allows a pulse through only when its cooldown has expired." + extended_desc = "Cooldown is clamped between 1 and 600 seconds. Disabled gates block incoming pulses without updating their cooldown." + icon_state = "template" + complexity = 4 + inputs = list( + "cooldown seconds" = IC_PINTYPE_NUMBER, + "enabled" = IC_PINTYPE_BOOLEAN + ) + inputs_default = list( + "1" = 2, + "2" = TRUE + ) + outputs = list( + "ready" = IC_PINTYPE_BOOLEAN, + "remaining cooldown" = IC_PINTYPE_NUMBER + ) + activators = list( + "pulse in" = IC_PINTYPE_PULSE_IN, + "pulse out" = IC_PINTYPE_PULSE_OUT, + "on blocked" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 20 + + var/next_allowed_time = 0 + +/obj/item/integrated_circuit/logic/cooldown_limiter/do_work() + var/cooldown_seconds = get_pin_data(IC_INPUT, 1) + var/enabled = get_pin_data(IC_INPUT, 2) + + if(!isnum(cooldown_seconds)) + cooldown_seconds = 2 + + cooldown_seconds = between(1, cooldown_seconds, 600) + + if(!enabled) + set_pin_data(IC_OUTPUT, 1, FALSE) + set_pin_data(IC_OUTPUT, 2, 0) + push_data() + activate_pin(3) + return + + if(world.time >= next_allowed_time) + next_allowed_time = world.time + cooldown_seconds SECONDS + set_pin_data(IC_OUTPUT, 1, TRUE) + set_pin_data(IC_OUTPUT, 2, 0) + push_data() + activate_pin(2) + return + + var/remaining = max(round((next_allowed_time - world.time) / 10, 0.1), 0) + set_pin_data(IC_OUTPUT, 1, FALSE) + set_pin_data(IC_OUTPUT, 2, remaining) + push_data() + activate_pin(3) + + +/obj/item/integrated_circuit/logic/pulse_counter + name = "threshold pulse counter" + desc = "Counts incoming pulses and reports when a threshold has been reached." + extended_desc = "Count is clamped between -100000 and 100000. Use the reset pulse to set the count to the configured reset value." + icon_state = "counter" + complexity = 3 + inputs = list( + "threshold" = IC_PINTYPE_NUMBER, + "reset value" = IC_PINTYPE_NUMBER + ) + inputs_default = list( + "1" = 1, + "2" = 0 + ) + outputs = list( + "count" = IC_PINTYPE_NUMBER, + "threshold met" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "increment" = IC_PINTYPE_PULSE_IN, + "decrement" = IC_PINTYPE_PULSE_IN, + "reset" = IC_PINTYPE_PULSE_IN, + "on count changed" = IC_PINTYPE_PULSE_OUT, + "on threshold met" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 20 + + var/count = 0 + +/obj/item/integrated_circuit/logic/pulse_counter/do_work(activator_id) + var/active_pin = activator_id + + if(istype(active_pin, /datum/integrated_io)) + active_pin = activators.Find(active_pin) + + if(!isnum(active_pin)) + active_pin = 1 + + switch(active_pin) + if(2) + count-- + if(3) + var/reset_value = get_pin_data(IC_INPUT, 2) + count = isnum(reset_value) ? round(reset_value) : 0 + else + count++ + + count = clamp(count, -100000, 100000) + var/threshold = get_pin_data(IC_INPUT, 1) + if(!isnum(threshold)) + threshold = 1 + threshold = clamp(round(threshold), -100000, 100000) + var/threshold_met = count >= threshold + + set_pin_data(IC_OUTPUT, 1, count) + set_pin_data(IC_OUTPUT, 2, threshold_met) + push_data() + activate_pin(4) + if(threshold_met) + activate_pin(5) + + +/obj/item/integrated_circuit/logic/pulse_sequencer + name = "pulse sequencer" + desc = "Cycles through four output pulses. If reset is true when pulsed, the sequence resets instead." + icon_state = "template" + complexity = 4 + inputs = list( + "reset" = IC_PINTYPE_BOOLEAN + ) + outputs = list( + "current step" = IC_PINTYPE_NUMBER + ) + activators = list( + "pulse" = IC_PINTYPE_PULSE_IN, + "step 1 pulse" = IC_PINTYPE_PULSE_OUT, + "step 2 pulse" = IC_PINTYPE_PULSE_OUT, + "step 3 pulse" = IC_PINTYPE_PULSE_OUT, + "step 4 pulse" = IC_PINTYPE_PULSE_OUT, + "on reset" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + var/current_step = 0 + +/obj/item/integrated_circuit/logic/pulse_sequencer/do_work() + if(get_pin_data(IC_INPUT, 1)) + current_step = 0 + set_pin_data(IC_OUTPUT, 1, current_step) + push_data() + activate_pin(6) + return + + current_step++ + + if(current_step > 4) + current_step = 1 + + set_pin_data(IC_OUTPUT, 1, current_step) + push_data() + activate_pin(current_step + 1) diff --git a/code/modules/integrated_electronics/subtypes/manipulation.dm b/code/modules/integrated_electronics/subtypes/manipulation.dm index 8cad682e894..8af931122ca 100644 --- a/code/modules/integrated_electronics/subtypes/manipulation.dm +++ b/code/modules/integrated_electronics/subtypes/manipulation.dm @@ -1,6 +1,25 @@ /obj/item/integrated_circuit/manipulation category_text = "Manipulation" +/obj/item/integrated_circuit/manipulation/proc/get_object() + return assembly ? assembly : src + +/obj/item/integrated_circuit/manipulation/proc/check_target(atom/A, exclude_contents = FALSE) + if(!A || !assembly) + return FALSE + + if(exclude_contents && (A in assembly.contents)) + return FALSE + + var/turf/target_turf = get_turf(A) + var/turf/assembly_turf = get_turf(assembly) + + if(!target_turf || !assembly_turf) + return FALSE + + return target_turf.Adjacent(assembly_turf) || target_turf == assembly_turf + + /obj/item/integrated_circuit/manipulation/weapon_firing name = "weapon firing mechanism" desc = "This somewhat complicated system allows one to slot in a gun, direct it towards a position, and remotely fire it." @@ -20,9 +39,9 @@ "fire" = IC_PINTYPE_PULSE_IN ) var/obj/item/gun/installed_gun - spawn_flags = IC_SPAWN_RESEARCH + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 3, TECH_COMBAT = 4) - power_draw_per_use = 500 // The targeting mechanism uses this. The actual gun uses its own cell for firing if it's an energy weapon. + power_draw_per_use = 500 /obj/item/integrated_circuit/manipulation/weapon_firing/Destroy() QDEL_NULL(installed_gun) @@ -34,7 +53,8 @@ if(installed_gun) to_chat(user, SPAN_WARNING("There's already a weapon installed.")) return - user.drop_from_inventory(gun,src) + + user.drop_from_inventory(gun, src) installed_gun = gun size += gun.w_class to_chat(user, SPAN_NOTICE("You slide \the [gun] into the firing mechanism.")) @@ -59,20 +79,24 @@ var/target_x = get_pin_data(IC_INPUT, 1) var/target_y = get_pin_data(IC_INPUT, 2) - if (!isnum(target_x) || !isnum(target_y) || !assembly) + if(!isnum(target_x) || !isnum(target_y) || !assembly) return target_x = round(target_x) target_y = round(target_y) - if(target_x == 0 && target_y == 0) // Don't shoot ourselves. + if(target_x == 0 && target_y == 0) return var/turf/T = get_turf(assembly) + if(!T) + return + var/Tx = T.x + target_x var/Ty = T.y + target_y - if (Tx > world.maxx || Tx < 1 || Ty > world.maxy || Ty < 1) - return // Off the edge of the map. + + if(Tx > world.maxx || Tx < 1 || Ty > world.maxy || Ty < 1) + return T = locate(Tx, Ty, T.z) @@ -81,6 +105,7 @@ installed_gun.Fire_userless(T) + /obj/item/integrated_circuit/manipulation/locomotion name = "locomotion circuit" desc = "This allows a machine to move in a given direction." @@ -90,24 +115,42 @@ being held, or anchored in some way. It should be noted that the ability to move is dependant on the type of assembly that this circuit inhabits." w_class = WEIGHT_CLASS_NORMAL complexity = 20 -// size = 5 inputs = list("direction" = IC_PINTYPE_DIR) outputs = list() activators = list("step towards dir" = IC_PINTYPE_PULSE_IN) - spawn_flags = IC_SPAWN_RESEARCH + spawn_flags = null power_draw_per_use = 1000 + var/next_move_time = 0 + +/obj/item/integrated_circuit/manipulation/locomotion/builtin + name = "integrated locomotion circuit" + desc = "A built-in chip that allows a mobile assembly to move in a given direction. Drone-optimized for responsive movement." + size = -1 + complexity = 0 + removable = FALSE + spawn_flags = null + next_move_time = 0 /obj/item/integrated_circuit/manipulation/locomotion/do_work() ..() + var/turf/T = get_turf(src) if(T && assembly) if(assembly.anchored || !assembly.can_move()) return - if(assembly.loc == T) // Check if we're held by someone. If the loc is the floor, we're not held. + + if(assembly.loc == T) + if(world.time < next_move_time) + return + var/datum/integrated_io/wanted_dir = inputs[1] if(isnum(wanted_dir.data)) step(assembly, wanted_dir.data) + // Drones move faster for better responsiveness; other assemblies move at standard pace + var/move_delay = istype(assembly, /obj/item/electronic_assembly/drone) ? 1 SECOND : 2 SECONDS + next_move_time = world.time + move_delay + /obj/item/integrated_circuit/manipulation/grenade name = "grenade primer" @@ -121,7 +164,7 @@ inputs = list("detonation time" = IC_PINTYPE_NUMBER) outputs = list() activators = list("prime grenade" = IC_PINTYPE_PULSE_IN) - spawn_flags = IC_SPAWN_RESEARCH + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 3, TECH_COMBAT = 4) var/obj/item/grenade/attached_grenade var/pre_attached_grenade_type @@ -129,12 +172,13 @@ /obj/item/integrated_circuit/manipulation/grenade/Initialize() . = ..() if(pre_attached_grenade_type) - var/grenade = new pre_attached_grenade_type(src) - attach_grenade(grenade) + var/obj/item/grenade/G = new pre_attached_grenade_type(src) + attach_grenade(G) /obj/item/integrated_circuit/manipulation/grenade/Destroy() if(attached_grenade && !attached_grenade.active) attached_grenade.dropInto(loc) + detach_grenade() . = ..() @@ -143,7 +187,7 @@ if(istype(G)) if(attached_grenade) to_chat(user, SPAN_WARNING("There is already a grenade attached!")) - else if(user.unEquip(G, force=1)) + else if(user.unEquip(G, force = 1)) user.visible_message(SPAN_WARNING("\The [user] attaches \a [G] to \the [src]!"), SPAN_NOTICE("You attach \the [G] to \the [src].")) attach_grenade(G) G.forceMove(src) @@ -163,11 +207,12 @@ var/datum/integrated_io/detonation_time = inputs[1] if(isnum(detonation_time.data) && detonation_time.data > 0) attached_grenade.det_time = between(1, detonation_time.data, 12) SECONDS + attached_grenade.activate() + var/atom/holder = loc log_and_message_admins("activated a grenade assembly. Last touches: Assembly: [holder.fingerprintslast] Circuit: [fingerprintslast] Grenade: [attached_grenade.fingerprintslast]") -// These procs do not relocate the grenade, that's the callers responsibility /obj/item/integrated_circuit/manipulation/grenade/proc/attach_grenade(var/obj/item/grenade/G) attached_grenade = G RegisterSignal(attached_grenade, COMSIG_QDELETING, PROC_REF(detach_grenade)) @@ -177,6 +222,7 @@ /obj/item/integrated_circuit/manipulation/grenade/proc/detach_grenade() if(!attached_grenade) return + UnregisterSignal(attached_grenade, COMSIG_QDELETING) attached_grenade = null size = initial(size) @@ -185,54 +231,67 @@ /obj/item/integrated_circuit/manipulation/grenade/frag pre_attached_grenade_type = /obj/item/grenade/frag origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 3, TECH_COMBAT = 10) - spawn_flags = null // Used for world initializing, see the #defines above. + spawn_flags = null + /obj/item/integrated_circuit/manipulation/grabber name = "grabber" desc = "A circuit with its own inventory for small/medium items, used to grab and store things." icon_state = "grabber" - extended_desc = "The circuit accepts a reference to thing to be grabbed. It can store up to 10 things. Modes: 1 for grab. 0 for eject the first thing. -1 for eject all." + extended_desc = "Accepts a reference to the target item. Stores up to 10 items. Modes: 1 grabs, 0 ejects the first stored item, -1 ejects all stored items." w_class = WEIGHT_CLASS_TINY size = 3 - complexity = 10 - inputs = list("target" = IC_PINTYPE_REF,"mode" = IC_PINTYPE_NUMBER) - outputs = list("first" = IC_PINTYPE_REF, "last" = IC_PINTYPE_REF, "amount" = IC_PINTYPE_NUMBER) - activators = list("pulse in" = IC_PINTYPE_PULSE_IN,"pulse out" = IC_PINTYPE_PULSE_OUT) - spawn_flags = IC_SPAWN_RESEARCH + inputs = list( + "target" = IC_PINTYPE_REF, + "mode" = IC_PINTYPE_NUMBER + ) + outputs = list( + "first" = IC_PINTYPE_REF, + "last" = IC_PINTYPE_REF, + "amount" = IC_PINTYPE_NUMBER + ) + activators = list( + "pulse in" = IC_PINTYPE_PULSE_IN, + "pulse out" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH power_draw_per_use = 500 /obj/item/integrated_circuit/manipulation/grabber/do_work() var/turf/T = get_turf(src) var/atom/movable/AM = get_pin_data_as_type(IC_INPUT, 1, /obj/item) var/mode = get_pin_data(IC_INPUT, 2) + if(mode == 1) - if(AM.Adjacent(T)) + if(AM && AM.Adjacent(T)) if(contents.len < 10) - if(istype(AM,/obj/item)) + if(istype(AM, /obj/item)) var/obj/item/A = AM if(A.w_class < WEIGHT_CLASS_NORMAL) AM.forceMove(src) + if(mode == 0) if(contents.len) var/obj/item/U = contents[1] U.forceMove(T) + if(mode == -1) if(contents.len) var/obj/item/U for(U in contents) U.forceMove(T) - set_pin_data(IC_OUTPUT, 1, contents[1]) - set_pin_data(IC_OUTPUT, 2, contents[contents.len]) - set_pin_data(IC_OUTPUT, 3, src.contents.len) + + set_pin_data(IC_OUTPUT, 1, contents.len ? contents[1] : null) + set_pin_data(IC_OUTPUT, 2, contents.len ? contents[contents.len] : null) + set_pin_data(IC_OUTPUT, 3, contents.len) push_data() activate_pin(2) - /obj/item/integrated_circuit/manipulation/thrower name = "thrower" - desc = "A compact launcher to throw things from inside or nearby tiles" + desc = "Launches stored or nearby items from the assembly." extended_desc = "The first and second inputs need to be numbers. They are coordinates to throw thing at, relative to the machine itself. \ The 'fire' activator will cause the mechanism to attempt to throw thing at the coordinates, if possible. Note that the \ projectile need to be inside the machine, or to be on an adjacent tile, and to be up to medium size." @@ -243,173 +302,213 @@ "target X rel" = IC_PINTYPE_NUMBER, "target Y rel" = IC_PINTYPE_NUMBER, "projectile" = IC_PINTYPE_REF - ) + ) outputs = list() activators = list( "fire" = IC_PINTYPE_PULSE_IN ) - spawn_flags = IC_SPAWN_RESEARCH + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 3, TECH_COMBAT = 4) power_draw_per_use = 500 /obj/item/integrated_circuit/manipulation/thrower/do_work() - var/datum/integrated_io/target_x = inputs[1] var/datum/integrated_io/target_y = inputs[2] var/datum/integrated_io/projectile = inputs[3] + if(!isweakref(projectile.data)) return + var/obj/item/A = projectile.data.resolve() + if(!A) + return + if(A.anchored) return - if(A.w_class>WEIGHT_CLASS_NORMAL) + + if(A.w_class > WEIGHT_CLASS_NORMAL) return + var/turf/T = get_turf(src.assembly) var/turf/TP = get_turf(A) + + if(!T || !TP) + return + if(!(TP.Adjacent(T))) return - if(src.assembly) - if(isnum(target_x.data)) - target_x.data = round(target_x.data) - if(isnum(target_y.data)) - target_y.data = round(target_y.data) - if(target_x.data == 0 && target_y.data == 0) - return + if(!src.assembly) + return - // We need to do this in order to enable relative coordinates, as locate() only works for absolute coordinates. - var/i - if(target_x.data > 0) - i = abs(target_x.data) - while(i > 0) - T = get_step(T, EAST) - i-- - else - i = abs(target_x.data) - while(i > 0) - T = get_step(T, WEST) - i-- + if(isnum(target_x.data)) + target_x.data = round(target_x.data) - i = 0 - if(target_y.data > 0) - i = abs(target_y.data) - while(i > 0) - T = get_step(T, NORTH) - i-- - else if(target_y.data < 0) - i = abs(target_y.data) - while(i > 0) - T = get_step(T, SOUTH) - i-- + if(isnum(target_y.data)) + target_y.data = round(target_y.data) - if(!T) - return + if(target_x.data == 0 && target_y.data == 0) + return + + var/i + if(target_x.data > 0) + i = abs(target_x.data) + while(i > 0) + T = get_step(T, EAST) + i-- + else + i = abs(target_x.data) + while(i > 0) + T = get_step(T, WEST) + i-- + + i = 0 + if(target_y.data > 0) + i = abs(target_y.data) + while(i > 0) + T = get_step(T, NORTH) + i-- + else if(target_y.data < 0) + i = abs(target_y.data) + while(i > 0) + T = get_step(T, SOUTH) + i-- + + if(!T) + return + + A.forceMove(get_turf(src)) + A.throw_at(T, round(clamp(sqrt(target_x.data * target_x.data + target_y.data * target_y.data), 0, 8), 1), 3, assembly) - A.forceMove(get_turf(src)) - A.throw_at(T, round(clamp(sqrt(target_x.data*target_x.data+target_y.data*target_y.data),0,8),1), 3, assembly) /obj/item/integrated_circuit/manipulation/shocker name = "shocker circuit" - desc = "Used to shock adjacent creatures with electricity." + desc = "Delivers an electrical shock to an adjacent target." icon_state = "shocker" extended_desc = "The circuit accepts a reference to creature to shock. It can shock a target on adjacent tiles. \ - Severity determines the power draw and usage of each shock. It accepts values between 0 and 20. It has a 5 second cooldown." + Severity determines the power draw and usage of each shock. It accepts values between 0 and 30. It has a 2 second cooldown." w_class = WEIGHT_CLASS_TINY complexity = 24 - inputs = list("target" = IC_PINTYPE_REF,"severity" = IC_PINTYPE_NUMBER) + inputs = list( + "target" = IC_PINTYPE_REF, + "severity" = IC_PINTYPE_NUMBER + ) outputs = list() - activators = list("shock" = IC_PINTYPE_PULSE_IN) - spawn_flags = IC_SPAWN_RESEARCH + activators = list( + "shock" = IC_PINTYPE_PULSE_IN + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH power_draw_per_use = 0 var/shocktime /obj/item/integrated_circuit/manipulation/shocker/on_data_written() var/s = get_pin_data(IC_INPUT, 2) - power_draw_per_use = clamp(s,0,20)*200 // big power draw to shock large creatures + power_draw_per_use = clamp(s, 0, 30) * 200 /obj/item/integrated_circuit/manipulation/shocker/do_work() ..() + var/turf/T = get_turf(src) var/mob/living/M = get_pin_data_as_type(IC_INPUT, 1, /mob/living) - if(!istype(M)) //Invalid input + + if(!istype(M)) return - if(!T.Adjacent(M)) - return //Can't reach - if(shocktime + (5 SECONDS) > world.time) + + if(!T || !T.Adjacent(M)) + return + + if(shocktime + (2 SECONDS) > world.time) to_chat(M, SPAN_DANGER("You feel a light tingle from [src]. Luckily it was charging!")) return - else - msg_admin_attack("An integrated circuit with a shocker circuit was used to shock [M.name] ([M.ckey]) (JMP)") - to_chat(M, SPAN_DANGER("You feel a sharp shock from the [src]!")) - spark(get_turf(M), 3, 1) - M.stun_effect_act(0, clamp(get_pin_data(IC_INPUT, 2),0,20), null) - shocktime = world.time - return + + msg_admin_attack("An integrated circuit with a shocker circuit was used to shock [M.name] ([M.ckey]) (JMP)") + to_chat(M, SPAN_DANGER("You feel a sharp shock from the [src]!")) + spark(get_turf(M), 3, 1) + M.stun_effect_act(0, clamp(get_pin_data(IC_INPUT, 2), 0, 30), null) + shocktime = world.time + /obj/item/integrated_circuit/manipulation/flasher name = "flasher circuit" - desc = "Used to flash adjacent creatures." + desc = "Triggers a flash effect against adjacent targets." icon_state = "video_camera" - extended_desc = "The circuit accepts a reference to creature to flash. It can shock a target on adjacent tiles. \ + extended_desc = "The circuit accepts a reference to creature to flash. It can flash a target on adjacent tiles. \ Severity determines the power draw and duration of each flash. It accepts values between 1 and 4. It has a 5 second cooldown." w_class = WEIGHT_CLASS_TINY complexity = 24 - inputs = list("target" = IC_PINTYPE_REF,"severity" = IC_PINTYPE_NUMBER) + inputs = list( + "target" = IC_PINTYPE_REF, + "severity" = IC_PINTYPE_NUMBER + ) outputs = list() - activators = list("flash" = IC_PINTYPE_PULSE_IN) - spawn_flags = IC_SPAWN_RESEARCH + activators = list( + "flash" = IC_PINTYPE_PULSE_IN + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH power_draw_per_use = 0 var/flashtime /obj/item/integrated_circuit/manipulation/flasher/on_data_written() var/s = get_pin_data(IC_INPUT, 2) - power_draw_per_use = clamp(s,1,4)*2000 // big power draw to achieve a blinding flash + power_draw_per_use = clamp(s, 1, 4) * 2000 /obj/item/integrated_circuit/manipulation/flasher/do_work() ..() + var/turf/T = get_turf(src) var/mob/living/scanned_mob = get_pin_data_as_type(IC_INPUT, 1, /mob/living) - if(!istype(scanned_mob)) //Invalid input + + if(!istype(scanned_mob)) return - if(!T.Adjacent(scanned_mob)) - return //Can't reach - if((flashtime + (5 SECONDS)) > world.time) // Cooldown + + if(!T || !T.Adjacent(scanned_mob)) + return + + if((flashtime + (5 SECONDS)) > world.time) visible_message(SPAN_WARNING("You see a weak flash from the [src].")) - else - var/flash_duration = clamp(get_pin_data(IC_INPUT, 2),1,4) - - scanned_mob.flash_act(length = flash_duration SECONDS) - - flashtime = world.time - msg_admin_attack("An integrated circuit with a flasher was used to flash [scanned_mob.name] ([scanned_mob.ckey]) (JMP)") - playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1) return -/obj/item/integrated_circuit/manipulation/portal_opener // basically a mini telescience setup, but consumes bluespace crystals, is one-way, only along cardinals and ordinals, cannot cross z-levels, and more imprecise (higher chance of getting beamed into a wall/table). + var/flash_duration = clamp(get_pin_data(IC_INPUT, 2), 1, 4) + + scanned_mob.flash_act(length = flash_duration SECONDS) + + flashtime = world.time + msg_admin_attack("An integrated circuit with a flasher was used to flash [scanned_mob.name] ([scanned_mob.ckey]) (JMP)") + playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1) + + +/obj/item/integrated_circuit/manipulation/portal_opener name = "bluespace portal circuit" - desc = "A miniaturised circuit that uses bluespace crystals to open a one-way bluespace portal. Costlier to use than a standard telescience set up, but portable." - extended_desc = "The circuit can store 3 bluespace crystals; each crystal is 1 use. Power determines number of tiles jumped (max 50m). Lifespan determines how long the portal is there (3-15 sec). Imprecise with a high variance (DANGER IN CLOSED SPACES!). It has a cooldown of 30 seconds." + desc = "A miniaturized circuit that uses bluespace crystals to open a one-way bluespace portal. Costlier to use than a standard telescience setup, but portable." + extended_desc = "The circuit can store 3 bluespace crystals; each crystal is 1 use. Power determines number of tiles jumped, up to 50m. Lifespan determines how long the portal is there, from 3 to 15 seconds. Coordinates may be imprecise. with a high variance (DANGER IN CLOSED SPACES!). It has a cooldown of 30 seconds." icon_state = "shocker" w_class = WEIGHT_CLASS_TINY size = 20 - complexity = 30 // needs a big assembly if you want to use this in any non-simplistic way - inputs = list("direction" = IC_PINTYPE_DIR, "power" = IC_PINTYPE_NUMBER, "lifespan" = IC_PINTYPE_NUMBER) - outputs = list("bluespace crystals stored" = IC_PINTYPE_NUMBER) - activators = list("open portal" = IC_PINTYPE_PULSE_IN, "portal opened" = IC_PINTYPE_PULSE_OUT, "cannot open portal" = IC_PINTYPE_PULSE_OUT) - spawn_flags = IC_SPAWN_RESEARCH - power_draw_per_use = 1000 // multiplied by up to 50 during on_data_written(). should slurp up power in smaller assemblies or those without power generation - cooldown_per_use = 30 SECONDS // big cooldown + complexity = 30 + inputs = list( + "direction" = IC_PINTYPE_DIR, + "power" = IC_PINTYPE_NUMBER, + "lifespan" = IC_PINTYPE_NUMBER + ) + outputs = list( + "bluespace crystals stored" = IC_PINTYPE_NUMBER + ) + activators = list( + "open portal" = IC_PINTYPE_PULSE_IN, + "portal opened" = IC_PINTYPE_PULSE_OUT, + "cannot open portal" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 1000 + cooldown_per_use = 30 SECONDS origin_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 4, TECH_MATERIAL = 4, TECH_BLUESPACE = 5) - - /// Lazylist of stored bluespace crystals stored in the device for amount checks/deletions. var/list/obj/item/bluespace_crystal/crystals - - /// Maximum number of crystals the circuit can store. var/max_crystals = 3 /obj/item/integrated_circuit/manipulation/portal_opener/Destroy() for(var/obj/item/bluespace_crystal/crystal in crystals) crystal.dropInto(get_turf(assembly)) + LAZYNULL(crystals) return ..() @@ -427,69 +526,80 @@ ..() /obj/item/integrated_circuit/manipulation/portal_opener/on_data_written() - power_draw_per_use = round(between(100, 100*(get_pin_data(IC_INPUT, 2)), 5000), 50) // potentially big power draw for a maximum jump range + power_draw_per_use = round(between(100, 100 * get_pin_data(IC_INPUT, 2), 5000), 50) ..() /obj/item/integrated_circuit/manipulation/portal_opener/do_work() - if(!LAZYLEN(crystals) >= 1) // no crystals, no portal + if(LAZYLEN(crystals) < 1) activate_pin(3) return var/turf/assembly_turf = get_turf(assembly) - if(assembly_turf) - // maffs - var/proj_power = round(between(1, get_pin_data(IC_INPUT, 2), 50), 1) // max range of 50 metres - var/proj_rotation = dir2angle(get_pin_data(IC_INPUT, 1)) // in degrees please - var/proj_angle = 60 - - var/datum/projectile_data/proj_data = projectile_trajectory(assembly_turf.x, assembly_turf.y, proj_rotation, proj_angle, proj_power) - - var/destination_x = clamp(round(proj_data.dest_x, 1), 1, world.maxx) // dont want to exit map - var/destination_y = clamp(round(proj_data.dest_y, 1), 1, world.maxy) - - var/turf/portal_destination = locate(destination_x, destination_y, assembly_turf.z) - - // effects - playsound(assembly.loc, 'sound/weapons/flash.ogg', 25, 1) - spark(assembly, 5, GLOB.alldirs) - - // portal creation - var/lifespan = between(30, SecondsToTicks(get_pin_data(IC_INPUT, 2)), 150) - var/obj/effect/portal/our_portal = new /obj/effect/portal(assembly_turf, null, null, lifespan, 0) - our_portal.set_target(portal_destination) - our_portal.precision = 2 // very imprecise, 5x5 turf variance. best used in very open spaces - our_portal.has_failed = FALSE - - // consume a bluespace crystal - var/obj/item/bluespace_crystal/consumed_bsc = LAZYACCESS(crystals, 1) - LAZYREMOVE(crystals, consumed_bsc) - qdel(consumed_bsc) - - activate_pin(2) - else + if(!assembly_turf) activate_pin(3) + return - set_pin_data(IC_OUTPUT, 1, LAZYLEN(crystals)) // set the bluespace crystals counter + var/proj_power = round(between(1, get_pin_data(IC_INPUT, 2), 50), 1) + var/proj_rotation = dir2angle(get_pin_data(IC_INPUT, 1)) + var/proj_angle = 60 + + var/datum/projectile_data/proj_data = projectile_trajectory(assembly_turf.x, assembly_turf.y, proj_rotation, proj_angle, proj_power) + + var/destination_x = clamp(round(proj_data.dest_x, 1), 1, world.maxx) + var/destination_y = clamp(round(proj_data.dest_y, 1), 1, world.maxy) + + var/turf/portal_destination = locate(destination_x, destination_y, assembly_turf.z) + if(!portal_destination) + activate_pin(3) + return + + playsound(assembly.loc, 'sound/weapons/flash.ogg', 25, 1) + spark(assembly, 5, GLOB.alldirs) + + var/lifespan = between(30, SecondsToTicks(get_pin_data(IC_INPUT, 3)), 150) + var/obj/effect/portal/our_portal = new /obj/effect/portal(assembly_turf, null, null, lifespan, 0) + our_portal.set_target(portal_destination) + our_portal.precision = 2 + our_portal.has_failed = FALSE + + var/obj/item/bluespace_crystal/consumed_bsc = LAZYACCESS(crystals, 1) + LAZYREMOVE(crystals, consumed_bsc) + qdel(consumed_bsc) + + activate_pin(2) + + set_pin_data(IC_OUTPUT, 1, LAZYLEN(crystals)) push_data() ..() + /obj/item/integrated_circuit/manipulation/bubble_shield name = "bubble shield circuit" - desc = "A large bubble shield generator circuit, like those found in atmospheric emergency shields... sans all the automated features." - extended_desc = "Receives relative coordinates to project a shield upon (must be within 3 tiles). Strength determines shield hitpoints (range: 10-50). Lifespan given in seconds (range: 2-60 secs). Can only sustain 3 shields." + desc = "Projects temporary bubble shields at relative coordinates." + extended_desc = "Receives relative coordinates to project a shield upon, within 3 tiles. Strength determines shield hitpoints, range 10-50. Lifespan given in seconds, range 2-60. Can only sustain 3 shields." icon_state = "power_transmitter" w_class = WEIGHT_CLASS_TINY - size = 30 // needs a medium-sized assembly minimum + size = 30 complexity = 20 - inputs = list("relative X" = IC_PINTYPE_NUMBER, "relative Y" = IC_PINTYPE_NUMBER, "strength" = IC_PINTYPE_NUMBER, "lifespan" = IC_PINTYPE_NUMBER) - outputs = list("shields projected" = IC_PINTYPE_NUMBER) - activators = list("create shield" = IC_PINTYPE_PULSE_IN, "shield created" = IC_PINTYPE_PULSE_OUT, "shield capacity reached" = IC_PINTYPE_PULSE_OUT) - spawn_flags = IC_SPAWN_RESEARCH + inputs = list( + "relative X" = IC_PINTYPE_NUMBER, + "relative Y" = IC_PINTYPE_NUMBER, + "strength" = IC_PINTYPE_NUMBER, + "lifespan" = IC_PINTYPE_NUMBER + ) + outputs = list( + "shields projected" = IC_PINTYPE_NUMBER + ) + activators = list( + "create shield" = IC_PINTYPE_PULSE_IN, + "shield created" = IC_PINTYPE_PULSE_OUT, + "shield capacity reached" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH power_draw_per_use = 1000 - power_draw_idle = 0 // Adjusted by amount of active shields. + power_draw_idle = 0 origin_tech = list(TECH_ENGINEERING = 4, TECH_MAGNET = 5) - /// A list of shields deployed by this circuit for amount checks/shield deletions. var/list/obj/structure/machinery/shield/deployed_shields /obj/item/integrated_circuit/manipulation/bubble_shield/Destroy() @@ -508,38 +618,42 @@ var/strength = between(10, get_pin_data(IC_INPUT, 3), 50) var/lifespan = between(2, get_pin_data(IC_INPUT, 4), 60) - // find target turf based on relative coordinates var/target_relative_x = round(get_pin_data(IC_INPUT, 1)) var/target_relative_y = round(get_pin_data(IC_INPUT, 2)) var/turf/our_turf = get_turf(assembly) + if(!our_turf) + return + var/target_x = our_turf.x + target_relative_x var/target_y = our_turf.y + target_relative_y - if (target_x > world.maxx || target_x < 1 || target_y > world.maxy || target_y < 1) - return // Off the edge of the map. - var/target_turf = locate(target_x, target_y, our_turf.z) + if(target_x > world.maxx || target_x < 1 || target_y > world.maxy || target_y < 1) + return + + var/turf/target_turf = locate(target_x, target_y, our_turf.z) // create the shield if(target_turf && (target_turf in view(3, our_turf))) // must be in view and within 3 tiles if(locate(/obj/structure/machinery/shield) in target_turf) // already got a shield return - if(LAZYLEN(deployed_shields) >= 3) // do not generate more than 3 shields - activate_pin(2) + if(LAZYLEN(deployed_shields) >= 3) + activate_pin(3) return //actually creating the shield var/obj/structure/machinery/shield/shield = new /obj/structure/machinery/shield(target_turf) shield.name = "bubble shield" shield.health = strength LAZYADD(deployed_shields, shield) - addtimer(CALLBACK(src, PROC_REF(kill_shield), shield), lifespan SECONDS, TIMER_DELETE_ME) // clean up after ourselves later - set_pin_data(IC_OUTPUT, 1, LAZYLEN(deployed_shields)) - activate_pin(1) - // power stuff + addtimer(CALLBACK(src, PROC_REF(kill_shield), shield), lifespan SECONDS, TIMER_DELETE_ME) + + set_pin_data(IC_OUTPUT, 1, LAZYLEN(deployed_shields)) + activate_pin(2) + if(deployed_shields) - power_draw_idle = power_draw_per_use*(strength/10)*LAZYLEN(deployed_shields) + power_draw_idle = power_draw_per_use * (strength / 10) * LAZYLEN(deployed_shields) else power_draw_idle = 0 @@ -548,3 +662,564 @@ /obj/item/integrated_circuit/manipulation/bubble_shield/power_fail() for(var/obj/structure/machinery/shield/shield in deployed_shields) kill_shield(shield) + + +/obj/item/integrated_circuit/manipulation/xenoarch_precision_excavator + name = "xenoarch precision excavator" + desc = "A circuit-controlled precision excavator for xenoarchaeological finds." + extended_desc = "Excavates a supplied scanned mineral turf reference using depth data supplied by a depth scanner. By itself, it performs one excavation pulse. With other circuits, it can be wired into a scan-excavate-scan loop." + inputs = list( + "target turf" = IC_PINTYPE_REF, + "required depth" = IC_PINTYPE_NUMBER, + "clearance" = IC_PINTYPE_NUMBER, + "current depth" = IC_PINTYPE_NUMBER + ) + outputs = list( + "status" = IC_PINTYPE_STRING, + "required depth" = IC_PINTYPE_NUMBER, + "clearance" = IC_PINTYPE_NUMBER, + "current depth" = IC_PINTYPE_NUMBER, + "remaining safe depth" = IC_PINTYPE_NUMBER, + "excavation amount" = IC_PINTYPE_NUMBER, + "direction" = IC_PINTYPE_STRING + ) + activators = list( + "excavate" = IC_PINTYPE_PULSE_IN, + "excavation advanced" = IC_PINTYPE_PULSE_OUT, + "perfect extraction" = IC_PINTYPE_PULSE_OUT, + "unsafe" = IC_PINTYPE_PULSE_OUT, + "failed" = IC_PINTYPE_PULSE_OUT + ) + icon_state = "circuit_drill" + complexity = 0 + size = 1 + spawn_flags = null + power_draw_per_use = 1000 + cooldown_per_use = 2 SECONDS + +/obj/item/integrated_circuit/manipulation/xenoarch_precision_excavator/do_work() + if(!assembly) + set_pin_data(IC_OUTPUT, 1, "No assembly.") + set_pin_data(IC_OUTPUT, 7, "none") + push_data() + activate_pin(5) + return + + if(!assembly.draw_power(power_draw_per_use)) + set_pin_data(IC_OUTPUT, 1, "Insufficient power.") + set_pin_data(IC_OUTPUT, 7, "none") + push_data() + activate_pin(5) + return + + var/turf/simulated/mineral/M = get_pin_data_as_type(IC_INPUT, 1, /turf/simulated/mineral) + var/required_depth_input = get_pin_data(IC_INPUT, 2) + var/clearance_input = get_pin_data(IC_INPUT, 3) + var/current_depth_input = get_pin_data(IC_INPUT, 4) + + if(!M) + set_pin_data(IC_OUTPUT, 1, "No scanned mineral turf supplied.") + set_pin_data(IC_OUTPUT, 7, "none") + push_data() + activate_pin(5) + return + + if(isnull(required_depth_input) || isnull(clearance_input) || isnull(current_depth_input)) + set_pin_data(IC_OUTPUT, 1, "Missing depth scanner data.") + set_pin_data(IC_OUTPUT, 7, "none") + push_data() + activate_pin(5) + return + + if(!(M.finds && M.finds.len)) + set_pin_data(IC_OUTPUT, 1, "Scanned mineral turf has no xenoarch find.") + set_pin_data(IC_OUTPUT, 7, "none") + push_data() + activate_pin(5) + return + + var/turf/current_turf = get_turf(assembly) + if(!current_turf) + set_pin_data(IC_OUTPUT, 1, "Could not locate excavator assembly.") + set_pin_data(IC_OUTPUT, 7, "none") + push_data() + activate_pin(5) + return + + var/turf/current_target = get_turf(M) + if(get_dist(current_turf, current_target) > 1) + set_pin_data(IC_OUTPUT, 1, "Target turf is not adjacent to the excavator.") + set_pin_data(IC_OUTPUT, 7, "none") + push_data() + activate_pin(5) + return + + set_pin_data(IC_OUTPUT, 7, dir2text(get_dir(current_turf, current_target))) + + var/required_depth = required_depth_input / 2 + var/clearance = clearance_input / 2 + var/current_depth = current_depth_input / 2 + + var/safe_depth = required_depth - clearance + var/remaining_safe = safe_depth - current_depth + var/final_amount = required_depth - current_depth + + set_pin_data(IC_OUTPUT, 2, required_depth_input) + set_pin_data(IC_OUTPUT, 3, clearance_input) + set_pin_data(IC_OUTPUT, 4, current_depth_input) + set_pin_data(IC_OUTPUT, 5, remaining_safe * 2) + + if(remaining_safe < 0) + set_pin_data(IC_OUTPUT, 1, "Unsafe: already inside clearance zone.") + set_pin_data(IC_OUTPUT, 6, 0) + push_data() + activate_pin(4) + return + + var/excavation_amount = 0 + + if(remaining_safe > 0) + if(remaining_safe >= 15) + excavation_amount = 15 + else if(remaining_safe >= 6) + excavation_amount = 6 + else if(remaining_safe >= 5) + excavation_amount = 5 + else if(remaining_safe >= 4) + excavation_amount = 4 + else if(remaining_safe >= 3) + excavation_amount = 3 + else if(remaining_safe >= 2) + excavation_amount = 2 + else if(remaining_safe >= 1) + excavation_amount = 1 + else if(remaining_safe >= 0.5) + excavation_amount = 0.5 + else + excavation_amount = final_amount + + set_pin_data(IC_OUTPUT, 6, excavation_amount * 2) + + if(excavation_amount <= 0) + set_pin_data(IC_OUTPUT, 1, "No valid excavation amount.") + push_data() + activate_pin(5) + return + + var/result = M.ic_precision_excavate(excavation_amount) + + set_pin_data(IC_OUTPUT, 1, result) + push_data() + + if(result == "Perfect extraction complete.") + activate_pin(3) + else if(findtext(result, "Unsafe")) + activate_pin(4) + else if(result == "Excavation advanced.") + activate_pin(2) + else + activate_pin(5) + + +// ----------------------------------------------------------------------------- +// Baystation manipulation circuit additions. +// ----------------------------------------------------------------------------- + +/obj/item/integrated_circuit/manipulation/plant_module + name = "plant manipulation module" + desc = "Used to uproot weeds and harvest or plant trays." + icon_state = "plant_m" + extended_desc = "The circuit accepts a reference to a hydroponic tray or an item on an adjacent tile. Mode input: 0 harvest, 1 uproot weeds, 2 uproot plant, 3 plant seed. Harvesting outputs a list of harvested plants." + w_class = WEIGHT_CLASS_TINY + complexity = 10 + inputs = list( + "tray" = IC_PINTYPE_REF, + "mode" = IC_PINTYPE_NUMBER, + "item" = IC_PINTYPE_REF + ) + outputs = list( + "result" = IC_PINTYPE_LIST + ) + activators = list( + "pulse in" = IC_PINTYPE_PULSE_IN, + "pulse out" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 50 + +/obj/item/integrated_circuit/manipulation/plant_module/do_work() + ..() + + var/obj/acting_object = get_object() + var/obj/OM = get_pin_data_as_type(IC_INPUT, 1, /obj) + var/obj/O = get_pin_data_as_type(IC_INPUT, 3, /obj/item) + var/mode = get_pin_data(IC_INPUT, 2) + + if(!check_target(OM)) + push_data() + activate_pin(2) + return + + var/obj/structure/machinery/portable_atmospherics/hydroponics/TR = OM + if(!istype(TR)) + push_data() + activate_pin(2) + return + + switch(mode) + if(0) + var/list/harvest_output = TR.harvest() + if(length(harvest_output)) + set_pin_data(IC_OUTPUT, 1, harvest_output) + push_data() + + if(1) + TR.weedlevel = 0 + TR.update_icon() + + if(2) + if(TR.seed) + TR.age = 0 + TR.health = 0 + if(TR.harvest) + TR.harvest = FALSE + qdel(TR.seed) + TR.seed = null + + TR.weedlevel = 0 + TR.dead = 0 + TR.update_icon() + + if(3) + if(!check_target(O)) + activate_pin(2) + return FALSE + + if(istype(O, /obj/item/seeds) && !istype(O, /obj/item/seeds/cutting)) + if(!TR.seed) + var/obj/item/seeds/S = O + acting_object.visible_message(SPAN_NOTICE("[acting_object] plants [O].")) + TR.seed = S.seed + TR.lastproduce = 0 + TR.dead = 0 + TR.age = 1 + TR.health = 100 + TR.lastcycle = world.time + qdel(O) + TR.update_icon() + TR.check_health() + + activate_pin(2) + + +/obj/item/integrated_circuit/manipulation/seed_extractor + name = "seed extractor module" + desc = "Used to extract seeds from grown produce." + icon_state = "plant_m" + extended_desc = "The circuit accepts a reference to a plant item and extracts seeds from it, outputting the results to a list." + complexity = 8 + inputs = list( + "target" = IC_PINTYPE_REF + ) + outputs = list( + "result" = IC_PINTYPE_LIST + ) + activators = list( + "pulse in" = IC_PINTYPE_PULSE_IN, + "pulse out" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 50 + +/obj/item/integrated_circuit/manipulation/seed_extractor/do_work() + ..() + + var/obj/item/reagent_containers/food/snacks/grown/O = get_pin_data_as_type(IC_INPUT, 1, /obj/item/reagent_containers/food/snacks/grown) + if(!check_target(O)) + push_data() + activate_pin(2) + return + + var/list/seed_output = list() + + for(var/i in 1 to rand(1, 4)) + var/obj/item/seeds/seeds = new(get_turf(O)) + seeds.seed = SSplants.seeds[O.plantname] + seeds.seed_type = SSplants.seeds[O.seed.name] + seeds.update_seed() + seed_output += seeds + + qdel(O) + + if(length(seed_output)) + set_pin_data(IC_OUTPUT, 1, seed_output) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/manipulation/claw + name = "pulling claw" + desc = "Pulls a referenced target toward the assembly." + icon_state = "pull_claw" + extended_desc = "Accepts a reference to the target that should be pulled." + w_class = WEIGHT_CLASS_NORMAL + size = 3 + complexity = 10 + inputs = list( + "target" = IC_PINTYPE_REF, + "dir" = IC_PINTYPE_DIR + ) + outputs = list( + "is pulling" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "pulse in" = IC_PINTYPE_PULSE_IN, + "pulse out" = IC_PINTYPE_PULSE_OUT, + "release" = IC_PINTYPE_PULSE_IN, + "pull to dir" = IC_PINTYPE_PULSE_IN + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 50 + var/obj/item/pulling + +/obj/item/integrated_circuit/manipulation/claw/Destroy() + stop_pulling() + . = ..() + +/obj/item/integrated_circuit/manipulation/claw/do_work(ord) + var/obj/acting_object = get_object() + var/obj/item/to_pull = get_pin_data_as_type(IC_INPUT, 1, /obj/item) + + switch(ord) + if(1) + if(can_pull(to_pull) && check_target(to_pull, exclude_contents = TRUE)) + set_pin_data(IC_OUTPUT, 1, TRUE) + pulling = to_pull + acting_object.visible_message("\The [acting_object] starts pulling \the [to_pull] around.") + push_data() + activate_pin(2) + + if(3) + if(pulling) + stop_pulling() + + if(4) + if(pulling) + var/dir = get_pin_data(IC_INPUT, 2) + var/turf/G = get_step(get_turf(acting_object), dir) + var/turf/Pl = get_turf(pulling) + var/turf/F = get_step_towards(Pl, G) + + if(F && acting_object.Adjacent(F)) + if(!step_towards(pulling, F)) + F = get_step_towards2(Pl, G) + if(F && acting_object.Adjacent(F)) + step_towards(pulling, F) + + activate_pin(2) + +/obj/item/integrated_circuit/manipulation/claw/proc/can_pull(obj/item/I) + return assembly && I && I.w_class <= assembly.w_class && !I.anchored + +/obj/item/integrated_circuit/manipulation/claw/proc/pull() + var/obj/acting_object = get_object() + if(isturf(acting_object.loc)) + step_towards(pulling, src) + else + stop_pulling() + +/obj/item/integrated_circuit/manipulation/claw/proc/check_pull() + if(get_dist(pulling, src) > 1) + stop_pulling() + +/obj/item/integrated_circuit/manipulation/claw/proc/stop_pulling() + if(!pulling) + return + + var/atom/movable/AM = get_object() + if(AM) + AM.visible_message("\The [AM] stops pulling \the [pulling].") + + pulling = null + set_pin_data(IC_OUTPUT, 1, FALSE) + activate_pin(3) + push_data() + + +/obj/item/integrated_circuit/manipulation/bluespace_rift + name = "bluespace rift generator" + desc = "Opens a short-range bluespace rift to another nearby location." + extended_desc = "Opens a short-range unstable bluespace rift. Rift direction determines where the rift opens relative to the assembly." + icon_state = "bluespace" + complexity = 100 + size = 3 + cooldown_per_use = 10 SECONDS + power_draw_per_use = 300 + inputs = list( + "rift direction" = IC_PINTYPE_DIR + ) + outputs = list() + activators = list( + "open rift" = IC_PINTYPE_PULSE_IN + ) + origin_tech = list(TECH_MAGNET = 1, TECH_BLUESPACE = 3) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/manipulation/bluespace_rift/do_work() + var/turf/depart = get_turf(assembly) + if(!depart) + playsound(src, 'sound/effects/sparks2.ogg', 50, 1) + return + + var/turf/arrive = get_random_turf_in_range(src, 10) + if(!arrive) + playsound(src, 'sound/effects/sparks2.ogg', 50, 1) + return + + var/step_dir = get_pin_data(IC_INPUT, 1) + if(!isnum(step_dir) || !(step_dir in GLOB.cardinals)) + step_dir = assembly ? assembly.dir : SOUTH + + depart = get_step(depart, step_dir) || depart + + var/obj/effect/portal/P = new /obj/effect/portal(depart, null, null, 30 SECONDS, 0) + P.set_target(arrive) + P.precision = 0 + P.has_failed = FALSE + + playsound(src, 'sound/effects/sparks2.ogg', 50, 1) + + +/obj/item/integrated_circuit/manipulation/ai + name = "integrated intelligence control circuit" + desc = "Similar in structure to an intellicard, this circuit allows a stored intelligence to pulse four different activators for control of a circuit." + extended_desc = "Load a compatible card by inserting the container into the device slot. Unload by using the circuit in-hand." + icon_state = "ai" + complexity = 15 + cooldown_per_use = 1 SECOND + power_draw_per_use = 20 + activators = list( + "Upwards" = IC_PINTYPE_PULSE_OUT, + "Downwards" = IC_PINTYPE_PULSE_OUT, + "Left" = IC_PINTYPE_PULSE_OUT, + "Right" = IC_PINTYPE_PULSE_OUT + ) + origin_tech = list(TECH_DATA = 4) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + var/mob/controlling + var/obj/item/aicard + +/obj/item/integrated_circuit/manipulation/ai/verb/open_menu() + set name = "Control Inputs" + set desc = "Allows circuit pulses to activate buttons on the attached assembly." + set category = "Object" + set src = usr.loc + + if(assembly) + assembly.attack_self(usr) + +/obj/item/integrated_circuit/manipulation/ai/relaymove(mob/user, direction) + . = ..() + + switch(direction) + if(1) + activate_pin(1) + if(2) + activate_pin(2) + if(4) + activate_pin(3) + if(8) + activate_pin(4) + +/obj/item/integrated_circuit/manipulation/ai/proc/load_ai(mob/user, obj/item/card) + if(controlling) + to_chat(user, SPAN_WARNING("There is already a card in there!")) + return + + var/mob/living/L = locate(/mob/living) in card.contents + if(L && L.key && user.unEquip(card, force = 1)) + L.forceMove(src) + controlling = L + card.forceMove(src) + aicard = card + user.visible_message("\The [user] loads \the [card] into \the [src]'s device slot.") + to_chat(L, SPAN_NOTICE("### IICC FIRMWARE LOADED ###")) + +/obj/item/integrated_circuit/manipulation/ai/proc/unload_ai() + if(!controlling || !aicard) + return + + controlling.forceMove(aicard) + to_chat(controlling, SPAN_NOTICE("### IICC FIRMWARE DELETED. HAVE A NICE DAY ###")) + src.visible_message("\The [aicard] pops out of \the [src]!") + aicard.dropInto(loc) + aicard = null + controlling = null + +/obj/item/integrated_circuit/manipulation/ai/attackby(obj/item/attacking_item, mob/user) + if(istype(attacking_item, /obj/item/aicard)) + load_ai(user, attacking_item) + return + + ..() + +/obj/item/integrated_circuit/manipulation/ai/attack_self(mob/user) + unload_ai() + +/obj/item/integrated_circuit/manipulation/ai/Destroy() + unload_ai() + . = ..() + + +/obj/item/integrated_circuit/manipulation/anchoring + name = "anchoring bolts" + desc = "Pop-out anchoring bolts which can secure an assembly to the floor." + outputs = list( + "enabled" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "toggle" = IC_PINTYPE_PULSE_IN, + "on toggle" = IC_PINTYPE_PULSE_OUT + ) + complexity = 8 + cooldown_per_use = 2 SECONDS + power_draw_per_use = 50 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 2) + +/obj/item/integrated_circuit/manipulation/anchoring/do_work(ord) + if(!isturf(assembly?.loc)) + return + + if(ord == 1) + assembly.anchored = !assembly.anchored + visible_message(assembly.anchored ? SPAN_NOTICE("\The [get_object()] deploys a set of anchoring bolts!") : SPAN_NOTICE("\The [get_object()] retracts its anchoring bolts.")) + set_pin_data(IC_OUTPUT, 1, assembly.anchored) + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/manipulation/hatchlock + name = "maintenance hatch lock" + desc = "An electronically controlled lock for the assembly's maintenance hatch." + extended_desc = "Warning: locking the hatch without a working unlock circuit can permanently prevent access to the assembly." + icon_state = "hatch_lock" + outputs = list( + "enabled" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "toggle" = IC_PINTYPE_PULSE_IN, + "on toggle" = IC_PINTYPE_PULSE_OUT + ) + complexity = 4 + cooldown_per_use = 2 SECONDS + power_draw_per_use = 50 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 2) + var/lock_enabled = FALSE + +/obj/item/integrated_circuit/manipulation/hatchlock/do_work(ord) + if(ord == 1) + lock_enabled = !lock_enabled + visible_message(SPAN_NOTICE("\The [get_object()] whirrs. The screws are now [lock_enabled ? "covered" : "exposed"].")) diff --git a/code/modules/integrated_electronics/subtypes/math_extended.dm b/code/modules/integrated_electronics/subtypes/math_extended.dm new file mode 100644 index 00000000000..bc2dc5723d1 --- /dev/null +++ b/code/modules/integrated_electronics/subtypes/math_extended.dm @@ -0,0 +1,1969 @@ +/* + * subtypes/math_extended.dm + * Extended numerical, list, vector, matrix, calculus, and vector-calculus circuits. + */ + +#define IC_MATH_LIST_LIMIT 128 +#define IC_MATH_VECTOR_LIMIT 16 +#define IC_MATH_MATRIX_LIMIT 4 +#define IC_MATH_GRID_LIMIT 7 + +/proc/ic_math_is_number(value) + return isnum(value) + +/proc/ic_math_numeric_list(list/input, limit = IC_MATH_LIST_LIMIT) + if(!islist(input) || !length(input) || length(input) > limit) + return null + + var/list/result = list() + for(var/value in input) + if(!isnum(value)) + return null + result += value + + return result + +/proc/ic_math_same_length_lists(list/a, list/b, limit = IC_MATH_LIST_LIMIT) + if(!islist(a) || !islist(b) || !length(a) || length(a) != length(b) || length(a) > limit) + return FALSE + return TRUE + +/proc/ic_math_sum(list/input) + var/result = 0 + for(var/value in input) + result += value + return result + +/proc/ic_math_mean(list/input) + if(!length(input)) + return null + return ic_math_sum(input) / length(input) + +/proc/ic_math_median(list/input) + if(!length(input)) + return null + var/list/sorted = sortTim(input.Copy(), GLOBAL_PROC_REF(cmp_numeric_asc)) + var/middle = length(sorted) / 2 + if(length(sorted) % 2) + return sorted[round(middle + 0.5)] + return (sorted[middle] + sorted[middle + 1]) / 2 + +/proc/ic_math_mode(list/input) + if(!length(input)) + return null + var/list/counts = list() + var/best_value = null + var/best_count = 0 + for(var/value in input) + counts["[value]"] = counts["[value]"] + 1 + if(counts["[value]"] > best_count) + best_count = counts["[value]"] + best_value = value + return best_value + +/proc/ic_math_variance(list/input) + if(!length(input)) + return null + var/mean = ic_math_mean(input) + var/total = 0 + for(var/value in input) + total += Square(value - mean) + return total / length(input) + +/proc/ic_math_percentile(list/input, percentile) + if(!length(input) || !isnum(percentile) || percentile < 0 || percentile > 100) + return null + var/list/sorted = sortTim(input.Copy(), GLOBAL_PROC_REF(cmp_numeric_asc)) + if(length(sorted) == 1) + return sorted[1] + var/position = 1 + ((percentile / 100) * (length(sorted) - 1)) + var/lower_index = max(1, min(round(position), length(sorted))) + var/upper_index = max(1, min(Ceiling(position), length(sorted))) + if(lower_index == upper_index) + return sorted[lower_index] + var/fraction = position - lower_index + return sorted[lower_index] + ((sorted[upper_index] - sorted[lower_index]) * fraction) + +/proc/ic_math_vector_magnitude(list/vector) + var/total = 0 + for(var/value in vector) + total += value * value + return sqrt(total) + +/proc/ic_math_matrix(list/input) + if(!islist(input) || !length(input) || length(input) > IC_MATH_MATRIX_LIMIT) + return null + + var/width = 0 + var/list/result = list() + for(var/row in input) + if(!islist(row)) + return null + var/list/row_list = row + if(!length(row_list) || length(row_list) > IC_MATH_MATRIX_LIMIT) + return null + if(!width) + width = length(row_list) + else if(width != length(row_list)) + return null + var/list/numeric_row = ic_math_numeric_list(row_list, IC_MATH_MATRIX_LIMIT) + if(!numeric_row) + return null + result += list(numeric_row) + + return result + +/proc/ic_math_grid(list/input) + if(!islist(input) || !length(input) || length(input) > IC_MATH_GRID_LIMIT) + return null + + var/width = 0 + var/list/result = list() + for(var/row in input) + if(!islist(row)) + return null + var/list/row_list = row + if(!length(row_list) || length(row_list) > IC_MATH_GRID_LIMIT) + return null + if(!width) + width = length(row_list) + else if(width != length(row_list)) + return null + var/list/grid_row = list() + for(var/value in row_list) + if(isnum(value)) + grid_row += value + else if(islist(value)) + var/list/vector = ic_math_numeric_list(value, 2) + if(!vector || length(vector) < 2) + return null + grid_row += list(list(vector[1], vector[2])) + else + return null + result += list(grid_row) + + return result + +/proc/ic_math_matrix_dimensions(list/input) + var/list/matrix = ic_math_matrix(input) + if(!matrix) + return null + + return list(length(matrix), length(matrix[1])) + +/proc/ic_math_grid_dimensions(list/input) + var/list/grid = ic_math_grid(input) + if(!grid) + return null + + return list(length(grid), length(grid[1])) + +/proc/ic_math_matrix_to_text(list/input, limit = MAX_MESSAGE_LEN) + var/list/matrix = ic_math_matrix(input) + if(!matrix) + return null + + var/list/rows = list() + for(var/list/row in matrix) + rows += "\[[jointext(row, ", ")]\]" + + var/text = jointext(rows, "; ") + if(length(text) > limit) + return "[copytext(text, 1, limit - 3)]..." + return text + +/proc/ic_math_grid_to_text(list/input, limit = MAX_MESSAGE_LEN) + var/list/grid = ic_math_grid(input) + if(!grid) + return null + + var/list/rows = list() + for(var/list/row in grid) + var/list/values = list() + for(var/value in row) + if(islist(value)) + var/list/vector = value + values += "([vector[1]], [vector[2]])" + else + values += "[value]" + rows += "\[[jointext(values, ", ")]\]" + + var/text = jointext(rows, "; ") + if(length(text) > limit) + return "[copytext(text, 1, limit - 3)]..." + return text + +/obj/item/integrated_circuit/math + name = "math circuit" + desc = "Performs a mathematical operation." + complexity = 2 + inputs = list("A" = IC_PINTYPE_NUMBER, "B" = IC_PINTYPE_NUMBER) + outputs = list( + "result" = IC_PINTYPE_NUMBER, + "status" = IC_PINTYPE_STRING + ) + activators = list( + "compute" = IC_PINTYPE_PULSE_IN, + "on success" = IC_PINTYPE_PULSE_OUT, + "on failure" = IC_PINTYPE_PULSE_OUT + ) + category_text = "MATH - Utility" + power_draw_per_use = 50 + spawn_flags = null + var/operation = null + +/obj/item/integrated_circuit/math/get_printer_spawn_flags() + if(isnull(operation)) + return null + return IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/math/proc/succeed(result, status = "OK") + set_pin_data(IC_OUTPUT, 1, result) + set_pin_data(IC_OUTPUT, 2, status) + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/proc/fail(status = "Invalid input") + set_pin_data(IC_OUTPUT, 2, status) + push_data() + activate_pin(3) + +// Utility and algebra. +/obj/item/integrated_circuit/math/numeric + category_text = "MATH - Utility" + +/obj/item/integrated_circuit/math/numeric/do_work() + var/A = get_pin_data(IC_INPUT, 1) + var/B = get_pin_data(IC_INPUT, 2) + var/C = get_pin_data(IC_INPUT, 3) + + if(operation != "atan2" && operation != "linear" && operation != "quadratic" && operation != "clamp" && !isnum(A)) + fail() + return + + switch(operation) + if("clamp") + if(!isnum(A) || !isnum(B) || !isnum(C)) + fail() + return + succeed(clamp(A, B, C)) + if("floor") + succeed(round(A)) + if("ceiling") + succeed(Ceiling(A)) + if("log") + if(!isnum(B) || A <= 0 || B <= 0 || B == 1) + fail("Invalid logarithm") + return + succeed(log(B, A)) + if("ln") + if(A <= 0) + fail("Invalid logarithm") + return + succeed(log(A)) + if("exp") + if(A > 100 || A < -100) + fail("Out of range") + return + succeed(NUM_E ** A) + if("arcsin") + if(A < -1 || A > 1) + fail("Out of range") + return + succeed(arcsin(A)) + if("arccos") + if(A < -1 || A > 1) + fail("Out of range") + return + succeed(arccos(A)) + if("arctan") + succeed(arctan(A)) + if("atan2") + if(!isnum(A) || !isnum(B)) + fail() + return + succeed(Atan2(A, B)) + if("deg2rad") + succeed(A * M_PI / 180) + if("rad2deg") + succeed(A * 180 / M_PI) + if("sinh") + succeed(fsinh(A)) + if("cosh") + succeed(fcosh(A)) + if("tanh") + succeed(ftanh(A)) + if("sech") + succeed(fsech(A)) + if("csch") + succeed(fcsch(A)) + if("coth") + succeed(fcoth(A)) + if("deadband") + if(!isnum(A) || !isnum(B)) + fail() + return + B = abs(B) + succeed(abs(A) <= B ? 0 : A) + if("linear") + if(!isnum(A) || !isnum(B) || !isnum(C)) + fail() + return + succeed((A * B) + C) + if("quadratic") + var/D = get_pin_data(IC_INPUT, 4) + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D)) + fail() + return + succeed((A * B * B) + (C * B) + D) + else + fail("Unknown operation") + +/obj/item/integrated_circuit/math/numeric/clamp + name = "clamp circuit" + desc = "Restricts a sensor, score, or control value to an inclusive minimum and maximum." + inputs = list("value" = IC_PINTYPE_NUMBER, "minimum" = IC_PINTYPE_NUMBER, "maximum" = IC_PINTYPE_NUMBER) + operation = "clamp" + +/obj/item/integrated_circuit/math/numeric/floor + name = "floor circuit" + desc = "Rounds a number downward." + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "floor" + +/obj/item/integrated_circuit/math/numeric/ceiling + name = "ceiling circuit" + desc = "Rounds a number upward." + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "ceiling" + +/obj/item/integrated_circuit/math/numeric/linear + name = "linear function circuit" + desc = "Applies a linear scale and offset to a sensor value." + category_text = "MATH - Algebra" + inputs = list("scale" = IC_PINTYPE_NUMBER, "value" = IC_PINTYPE_NUMBER, "offset" = IC_PINTYPE_NUMBER) + operation = "linear" + +/obj/item/integrated_circuit/math/numeric/quadratic + name = "quadratic function circuit" + desc = "Calculates a*x^2+b*x+c." + category_text = "MATH - Algebra" + inputs = list("a" = IC_PINTYPE_NUMBER, "x" = IC_PINTYPE_NUMBER, "b" = IC_PINTYPE_NUMBER, "c" = IC_PINTYPE_NUMBER) + operation = "quadratic" + +/obj/item/integrated_circuit/math/numeric/logarithm + name = "logarithm circuit" + desc = "Calculates log base B of A." + category_text = "MATH - Algebra" + operation = "log" + +/obj/item/integrated_circuit/math/numeric/natural_log + name = "natural log circuit" + desc = "Calculates the natural logarithm." + category_text = "MATH - Algebra" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "ln" + +/obj/item/integrated_circuit/math/numeric/exponential + name = "exponential circuit" + desc = "Calculates e raised to the input value." + category_text = "MATH - Algebra" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "exp" + +/obj/item/integrated_circuit/math/numeric/arcsin + name = "arc sine circuit" + desc = "Calculates arcsine in degrees." + category_text = "MATH - Trigonometry" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "arcsin" + +/obj/item/integrated_circuit/math/numeric/arccos + name = "arc cosine circuit" + desc = "Calculates arccosine in degrees." + category_text = "MATH - Trigonometry" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "arccos" + +/obj/item/integrated_circuit/math/numeric/arctan + name = "arc tangent circuit" + desc = "Calculates arctangent in degrees." + category_text = "MATH - Trigonometry" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "arctan" + +/obj/item/integrated_circuit/math/numeric/atan2 + name = "atan2 circuit" + desc = "Calculates a direction angle from relative X and Y components for aiming, display, or movement logic." + category_text = "MATH - Trigonometry" + inputs = list("relative x" = IC_PINTYPE_NUMBER, "relative y" = IC_PINTYPE_NUMBER) + operation = "atan2" + +/obj/item/integrated_circuit/math/numeric/deadband + name = "deadband circuit" + desc = "Suppresses small noisy values around zero so sensor-driven controls do not twitch." + category_text = "MATH - Signal Processing" + inputs = list("value" = IC_PINTYPE_NUMBER, "deadband" = IC_PINTYPE_NUMBER) + operation = "deadband" + +/obj/item/integrated_circuit/math/numeric/degrees_to_radians + name = "degrees to radians circuit" + desc = "Converts degrees to radians." + category_text = "MATH - Trigonometry" + inputs = list("degrees" = IC_PINTYPE_NUMBER) + operation = "deg2rad" + +/obj/item/integrated_circuit/math/numeric/radians_to_degrees + name = "radians to degrees circuit" + desc = "Converts radians to degrees." + category_text = "MATH - Trigonometry" + inputs = list("radians" = IC_PINTYPE_NUMBER) + operation = "rad2deg" + +/obj/item/integrated_circuit/math/numeric/hyperbolic_sine + name = "hyperbolic sine circuit" + desc = "Calculates hyperbolic sine." + category_text = "MATH - Trigonometry" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "sinh" + +/obj/item/integrated_circuit/math/numeric/hyperbolic_cosine + name = "hyperbolic cosine circuit" + desc = "Calculates hyperbolic cosine." + category_text = "MATH - Trigonometry" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "cosh" + +/obj/item/integrated_circuit/math/numeric/hyperbolic_tangent + name = "hyperbolic tangent circuit" + desc = "Calculates hyperbolic tangent." + category_text = "MATH - Trigonometry" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "tanh" + +/obj/item/integrated_circuit/math/numeric/hyperbolic_secant + name = "hyperbolic secant circuit" + desc = "Calculates hyperbolic secant." + category_text = "MATH - Trigonometry" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "sech" + +/obj/item/integrated_circuit/math/numeric/hyperbolic_cosecant + name = "hyperbolic cosecant circuit" + desc = "Calculates hyperbolic cosecant." + category_text = "MATH - Trigonometry" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "csch" + +/obj/item/integrated_circuit/math/numeric/hyperbolic_cotangent + name = "hyperbolic cotangent circuit" + desc = "Calculates hyperbolic cotangent." + category_text = "MATH - Trigonometry" + inputs = list("value" = IC_PINTYPE_NUMBER) + operation = "coth" + +// Geometry. +/obj/item/integrated_circuit/math/geometry + category_text = "MATH - Geometry" + inputs = list( + "x1" = IC_PINTYPE_NUMBER, + "y1" = IC_PINTYPE_NUMBER, + "x2" = IC_PINTYPE_NUMBER, + "y2" = IC_PINTYPE_NUMBER + ) + +/obj/item/integrated_circuit/math/geometry/do_work() + var/A = get_pin_data(IC_INPUT, 1) + var/B = get_pin_data(IC_INPUT, 2) + var/C = get_pin_data(IC_INPUT, 3) + var/D = get_pin_data(IC_INPUT, 4) + var/E = get_pin_data(IC_INPUT, 5) + var/F = get_pin_data(IC_INPUT, 6) + + switch(operation) + if("distance2") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D)) + fail() + return + succeed(sqrt(Square(C - A) + Square(D - B))) + if("distance2sq") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D)) + fail() + return + succeed(Square(C - A) + Square(D - B)) + if("distance3") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D) || !isnum(E) || !isnum(F)) + fail() + return + succeed(sqrt(Square(D - A) + Square(E - B) + Square(F - C))) + if("manhattan") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D)) + fail() + return + succeed(abs(C - A) + abs(D - B)) + if("chebyshev") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D)) + fail() + return + succeed(max(abs(C - A), abs(D - B))) + if("midpoint") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D)) + fail() + return + set_pin_data(IC_OUTPUT, 1, (A + C) / 2) + set_pin_data(IC_OUTPUT, 2, (B + D) / 2) + set_pin_data(IC_OUTPUT, 3, "OK") + push_data() + activate_pin(2) + if("slope") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D) || C == A) + fail("Vertical line") + return + succeed((D - B) / (C - A)) + if("angle") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D)) + fail() + return + succeed(Atan2(C - A, D - B)) + if("circle_area") + if(!isnum(A) || A < 0) + fail() + return + succeed(M_PI * A * A) + if("circle_circumference") + if(!isnum(A) || A < 0) + fail() + return + succeed(2 * M_PI * A) + if("rectangle_area") + if(!isnum(A) || !isnum(B)) + fail() + return + succeed(A * B) + if("box_volume") + if(!isnum(A) || !isnum(B) || !isnum(C)) + fail() + return + succeed(A * B * C) + if("sphere_volume") + if(!isnum(A) || A < 0) + fail() + return + succeed((4 / 3) * M_PI * A * A * A) + +/obj/item/integrated_circuit/math/geometry/midpoint + name = "midpoint 2D circuit" + desc = "Finds the center point between two coordinates for patrol, follow, or targeting logic." + outputs = list("x" = IC_PINTYPE_NUMBER, "y" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "midpoint" + +/obj/item/integrated_circuit/math/geometry/distance2 + name = "distance 2D circuit" + desc = "Calculates direct range between two coordinates for follow distance, target scoring, or proximity alarms." + operation = "distance2" + +/obj/item/integrated_circuit/math/geometry/distance2sq + name = "distance squared 2D circuit" + desc = "Calculates squared range for cheaper target comparisons when exact distance is not needed." + operation = "distance2sq" + +/obj/item/integrated_circuit/math/geometry/distance3 + name = "distance 3D circuit" + desc = "Calculates Euclidean distance between two 3D points." + inputs = list("x1" = IC_PINTYPE_NUMBER, "y1" = IC_PINTYPE_NUMBER, "z1" = IC_PINTYPE_NUMBER, "x2" = IC_PINTYPE_NUMBER, "y2" = IC_PINTYPE_NUMBER, "z2" = IC_PINTYPE_NUMBER) + operation = "distance3" + +/obj/item/integrated_circuit/math/geometry/manhattan + name = "manhattan distance circuit" + desc = "Calculates grid-step distance between two coordinates for tile movement estimates." + operation = "manhattan" + +/obj/item/integrated_circuit/math/geometry/chebyshev + name = "chebyshev distance circuit" + desc = "Calculates maximum-axis tile distance for simple range gates and square sensor zones." + operation = "chebyshev" + +/obj/item/integrated_circuit/math/geometry/slope + name = "slope circuit" + desc = "Calculates rise over run between two coordinates for directional trend displays." + operation = "slope" + +/obj/item/integrated_circuit/math/geometry/angle_between_points + name = "angle between points circuit" + desc = "Calculates a heading from one coordinate to another for directional displays or aiming logic." + operation = "angle" + +/obj/item/integrated_circuit/math/geometry/circle_area + name = "circle area circuit" + desc = "Calculates circle area from radius." + inputs = list("radius" = IC_PINTYPE_NUMBER) + operation = "circle_area" + +/obj/item/integrated_circuit/math/geometry/circle_circumference + name = "circle circumference circuit" + desc = "Calculates circle circumference from radius." + inputs = list("radius" = IC_PINTYPE_NUMBER) + operation = "circle_circumference" + +/obj/item/integrated_circuit/math/geometry/rectangle_area + name = "rectangle area circuit" + desc = "Calculates rectangle area." + inputs = list("width" = IC_PINTYPE_NUMBER, "height" = IC_PINTYPE_NUMBER) + operation = "rectangle_area" + +/obj/item/integrated_circuit/math/geometry/box_volume + name = "box volume circuit" + desc = "Calculates box volume." + inputs = list("width" = IC_PINTYPE_NUMBER, "height" = IC_PINTYPE_NUMBER, "depth" = IC_PINTYPE_NUMBER) + operation = "box_volume" + +/obj/item/integrated_circuit/math/geometry/sphere_volume + name = "sphere volume circuit" + desc = "Calculates sphere volume from radius." + inputs = list("radius" = IC_PINTYPE_NUMBER) + operation = "sphere_volume" + +// List math and statistics. +/obj/item/integrated_circuit/math/list + category_text = "MATH - List Math" + inputs = list("list A" = IC_PINTYPE_LIST, "list B" = IC_PINTYPE_LIST) + outputs = list( + "result" = IC_PINTYPE_LIST, + "status" = IC_PINTYPE_STRING + ) + complexity = 5 + +/obj/item/integrated_circuit/math/list/do_work() + var/list/A = get_pin_data(IC_INPUT, 1) + var/list/B = get_pin_data(IC_INPUT, 2) + var/N = get_pin_data(IC_INPUT, 3) + + if(operation == "add" || operation == "subtract" || operation == "multiply" || operation == "divide") + if(!ic_math_same_length_lists(A, B)) + fail("Mismatched or invalid lists") + return + var/list_length = length(A) + var/list/result = list() + for(var/i = 1 to list_length) + var/a = A[i] + var/b = B[i] + if(!isnum(a) || !isnum(b) || (operation == "divide" && b == 0)) + fail("Invalid numeric entry") + return + switch(operation) + if("add") + result += a + b + if("subtract") + result += a - b + if("multiply") + result += a * b + if("divide") + result += a / b + succeed(result) + return + + var/list/numbers = ic_math_numeric_list(A) + if(!numbers) + fail("Invalid numeric list") + return + + switch(operation) + if("clamp") + var/minimum = get_pin_data(IC_INPUT, 2) + var/maximum = get_pin_data(IC_INPUT, 3) + if(!isnum(minimum) || !isnum(maximum)) + fail() + return + var/list/result = list() + for(var/value in numbers) + result += clamp(value, minimum, maximum) + succeed(result) + if("abs") + var/list/result = list() + for(var/value in numbers) + result += abs(value) + succeed(result) + if("normalize") + var/minimum = min(numbers) + var/maximum = max(numbers) + if(maximum == minimum) + fail("Flat list") + return + var/list/result = list() + for(var/value in numbers) + result += (value - minimum) / (maximum - minimum) + succeed(result) + if("moving_average") + if(!isnum(N) || N < 1) + fail("Invalid window") + return + N = round(N) + var/list_length = length(numbers) + var/list/result = list() + for(var/i = 1 to list_length) + var/start = max(1, i - N + 1) + var/total = 0 + var/count = 0 + for(var/j = start to i) + total += numbers[j] + count++ + result += total / count + succeed(result) + else + fail("Unknown operation") + +/obj/item/integrated_circuit/math/list/list_add + name = "list add circuit" + desc = "Adds two matching numeric lists element by element, useful for combining sensor vectors or target scores." + operation = "add" + +/obj/item/integrated_circuit/math/list/list_subtract + name = "list subtract circuit" + desc = "Subtracts matching numeric lists element by element, useful for deltas between sensor samples." + operation = "subtract" + +/obj/item/integrated_circuit/math/list/list_multiply + name = "list multiply circuit" + desc = "Multiplies matching numeric lists element by element, useful for applying per-target weights." + operation = "multiply" + +/obj/item/integrated_circuit/math/list/list_divide + name = "list divide circuit" + desc = "Divides matching numeric lists element by element, useful for ratios or normalized sensor comparisons." + operation = "divide" + +/obj/item/integrated_circuit/math/list/list_clamp + name = "list clamp circuit" + desc = "Clamps every numeric value in a list for bounded sensor displays or threat scores." + inputs = list("list" = IC_PINTYPE_LIST, "minimum" = IC_PINTYPE_NUMBER, "maximum" = IC_PINTYPE_NUMBER) + operation = "clamp" + +/obj/item/integrated_circuit/math/list/list_absolute + name = "list absolute value circuit" + desc = "Returns absolute values for every number in a list, useful for error magnitudes and sensor deltas." + inputs = list("list" = IC_PINTYPE_LIST) + operation = "abs" + +/obj/item/integrated_circuit/math/list/list_normalize + name = "list normalize circuit" + desc = "Scales a numeric list to 0-1 for target scoring, display bars, and weighted selection." + inputs = list("list" = IC_PINTYPE_LIST) + operation = "normalize" + +/obj/item/integrated_circuit/math/list/moving_average + name = "moving average circuit" + desc = "Smooths a numeric sample list for stable sensor displays and non-jittery alarms." + category_text = "MATH - Statistics" + inputs = list("samples" = IC_PINTYPE_LIST, "window" = IC_PINTYPE_NUMBER) + operation = "moving_average" + +/obj/item/integrated_circuit/math/statistics + category_text = "MATH - Statistics" + inputs = list("list" = IC_PINTYPE_LIST) + outputs = list( + "result" = IC_PINTYPE_NUMBER, + "status" = IC_PINTYPE_STRING + ) + complexity = 5 + +/obj/item/integrated_circuit/math/statistics/do_work() + var/list/input = get_pin_data(IC_INPUT, 1) + var/list/numbers = ic_math_numeric_list(input) + if(operation != "count" && !numbers) + fail("Invalid numeric list") + return + + switch(operation) + if("count") + if(!islist(input) || length(input) > IC_MATH_LIST_LIMIT) + fail("Invalid list") + return + succeed(length(input)) + if("sum") + succeed(ic_math_sum(numbers)) + if("mean") + succeed(ic_math_mean(numbers)) + if("median") + succeed(ic_math_median(numbers)) + if("mode") + succeed(ic_math_mode(numbers)) + if("min") + succeed(min(numbers)) + if("max") + succeed(max(numbers)) + if("range") + succeed(max(numbers) - min(numbers)) + if("variance") + succeed(ic_math_variance(numbers)) + if("stdev") + succeed(sqrt(ic_math_variance(numbers))) + if("percentile") + var/percentile = get_pin_data(IC_INPUT, 2) + var/result = ic_math_percentile(numbers, percentile) + if(isnull(result)) + fail("Invalid percentile") + return + succeed(result) + if("index_min") + succeed(numbers.Find(min(numbers))) + if("index_max") + succeed(numbers.Find(max(numbers))) + if("weighted_average") + var/list/weights = ic_math_numeric_list(get_pin_data(IC_INPUT, 2)) + var/list_length = length(numbers) + if(!weights || length(weights) != list_length) + fail("Invalid weights") + return + var/weighted_total = 0 + var/weight_total = 0 + for(var/i = 1 to list_length) + weighted_total += numbers[i] * weights[i] + weight_total += weights[i] + if(weight_total == 0) + fail("Zero weight") + return + succeed(weighted_total / weight_total) + +/obj/item/integrated_circuit/math/statistics/count + name = "count circuit" + desc = "Counts list entries for target lists, sample buffers, and queue monitors." + operation = "count" + +/obj/item/integrated_circuit/math/statistics/sum + name = "sum circuit" + desc = "Sums numeric samples for total exposure, resource use, or combined score." + operation = "sum" + +/obj/item/integrated_circuit/math/statistics/mean + name = "mean circuit" + desc = "Averages sensor samples for stable displays and threshold checks." + operation = "mean" + +/obj/item/integrated_circuit/math/statistics/median + name = "median circuit" + desc = "Finds the median sample, useful for rejecting one-off sensor spikes." + operation = "median" + +/obj/item/integrated_circuit/math/statistics/mode + name = "mode circuit" + desc = "Calculates the mode of a numeric list." + operation = "mode" + +/obj/item/integrated_circuit/math/statistics/minimum + name = "minimum of list circuit" + desc = "Finds the smallest sample or best low-score target in a numeric list." + operation = "min" + +/obj/item/integrated_circuit/math/statistics/maximum + name = "maximum of list circuit" + desc = "Finds the largest sample or strongest target score in a numeric list." + operation = "max" + +/obj/item/integrated_circuit/math/statistics/range + name = "range circuit" + desc = "Measures spread between samples for anomaly, noise, or instability detection." + operation = "range" + +/obj/item/integrated_circuit/math/statistics/variance + name = "variance circuit" + desc = "Measures sample volatility for unstable sensors, radiation bursts, or target score spread." + operation = "variance" + +/obj/item/integrated_circuit/math/statistics/standard_deviation + name = "standard deviation circuit" + desc = "Measures typical sensor noise or target-score spread." + operation = "stdev" + +/obj/item/integrated_circuit/math/statistics/percentile + name = "percentile circuit" + desc = "Calculates a percentile for robust environmental thresholds or ranked target scores." + inputs = list("list" = IC_PINTYPE_LIST, "percentile" = IC_PINTYPE_NUMBER) + operation = "percentile" + +/obj/item/integrated_circuit/math/statistics/index_minimum + name = "index of minimum circuit" + desc = "Returns the list position of the smallest value for target cycling or lowest reading selection." + operation = "index_min" + +/obj/item/integrated_circuit/math/statistics/index_maximum + name = "index of maximum circuit" + desc = "Returns the list position of the largest value for target cycling or hotspot selection." + operation = "index_max" + +/obj/item/integrated_circuit/math/statistics/weighted_average + name = "weighted average circuit" + desc = "Combines values with matching weights for target scoring and sensor fusion." + inputs = list("values" = IC_PINTYPE_LIST, "weights" = IC_PINTYPE_LIST) + operation = "weighted_average" + +// Vector and matrix operations. +/obj/item/integrated_circuit/math/vector + category_text = "MATH - Vector" + inputs = list("vector A" = IC_PINTYPE_LIST, "vector B" = IC_PINTYPE_LIST) + outputs = list( + "result" = IC_PINTYPE_LIST, + "status" = IC_PINTYPE_STRING + ) + complexity = 5 + +/obj/item/integrated_circuit/math/vector/do_work() + var/list/A = ic_math_numeric_list(get_pin_data(IC_INPUT, 1), IC_MATH_VECTOR_LIMIT) + var/list/B = ic_math_numeric_list(get_pin_data(IC_INPUT, 2), IC_MATH_VECTOR_LIMIT) + var/N = get_pin_data(IC_INPUT, 2) + + switch(operation) + if("create2") + var/x = get_pin_data(IC_INPUT, 1) + var/y = get_pin_data(IC_INPUT, 2) + if(!isnum(x) || !isnum(y)) + fail() + return + succeed(list(x, y)) + if("create3") + var/x = get_pin_data(IC_INPUT, 1) + var/y = get_pin_data(IC_INPUT, 2) + var/z = get_pin_data(IC_INPUT, 3) + if(!isnum(x) || !isnum(y) || !isnum(z)) + fail() + return + succeed(list(x, y, z)) + if("add", "subtract", "dot") + if(!A || !B || length(A) != length(B)) + fail("Mismatched vectors") + return + var/vector_length = length(A) + if(operation == "dot") + var/result = 0 + for(var/i = 1 to vector_length) + result += A[i] * B[i] + succeed(result) + return + var/list/result = list() + for(var/i = 1 to vector_length) + result += operation == "add" ? A[i] + B[i] : A[i] - B[i] + succeed(result) + if("scale") + if(!A || !isnum(N)) + fail() + return + var/list/result = list() + for(var/value in A) + result += value * N + succeed(result) + if("magnitude") + if(!A) + fail() + return + succeed(ic_math_vector_magnitude(A)) + if("magnitude_sq") + if(!A) + fail() + return + var/result = 0 + for(var/value in A) + result += value * value + succeed(result) + if("normalize") + if(!A) + fail() + return + var/magnitude = ic_math_vector_magnitude(A) + if(magnitude == 0) + fail("Zero vector") + return + var/list/result = list() + for(var/value in A) + result += value / magnitude + succeed(result) + if("cross") + if(!A || !B || length(A) != 3 || length(B) != 3) + fail("Requires 3D vectors") + return + succeed(list((A[2] * B[3]) - (A[3] * B[2]), (A[3] * B[1]) - (A[1] * B[3]), (A[1] * B[2]) - (A[2] * B[1]))) + if("cross2") + if(!A || !B || length(A) != 2 || length(B) != 2) + fail("Requires 2D vectors") + return + succeed((A[1] * B[2]) - (A[2] * B[1])) + if("angle") + if(!A || !B || length(A) != length(B)) + fail("Mismatched vectors") + return + var/vector_length = length(A) + var/dot = 0 + for(var/i = 1 to vector_length) + dot += A[i] * B[i] + var/mag = ic_math_vector_magnitude(A) * ic_math_vector_magnitude(B) + if(mag == 0) + fail("Zero vector") + return + succeed(arccos(clamp(dot / mag, -1, 1))) + if("projection") + if(!A || !B || length(A) != length(B)) + fail("Mismatched vectors") + return + var/vector_length = length(A) + var/dot_ab = 0 + var/dot_bb = 0 + for(var/i = 1 to vector_length) + dot_ab += A[i] * B[i] + dot_bb += B[i] * B[i] + if(dot_bb == 0) + fail("Zero vector") + return + var/scale = dot_ab / dot_bb + var/list/result = list() + for(var/value in B) + result += value * scale + succeed(result) + if("clamp_magnitude") + var/max_magnitude = get_pin_data(IC_INPUT, 2) + if(!A || !isnum(max_magnitude) || max_magnitude < 0) + fail() + return + var/magnitude = ic_math_vector_magnitude(A) + if(magnitude <= max_magnitude) + succeed(A.Copy()) + return + var/list/result = list() + for(var/value in A) + result += value / magnitude * max_magnitude + succeed(result) + +/obj/item/integrated_circuit/math/vector/create2 + name = "vector create 2D circuit" + desc = "Creates a 2D vector from relative X/Y values for movement, aiming, or field sampling." + inputs = list("relative x" = IC_PINTYPE_NUMBER, "relative y" = IC_PINTYPE_NUMBER) + operation = "create2" + +/obj/item/integrated_circuit/math/vector/create3 + name = "vector create 3D circuit" + desc = "Creates a 3D vector for coordinate deltas or multi-sensor values." + inputs = list("x" = IC_PINTYPE_NUMBER, "y" = IC_PINTYPE_NUMBER, "z" = IC_PINTYPE_NUMBER) + operation = "create3" + +/obj/item/integrated_circuit/math/vector/add + name = "vector add circuit" + desc = "Adds two vectors for offset movement, steering, or combined sensor direction." + operation = "add" + +/obj/item/integrated_circuit/math/vector/subtract + name = "vector subtract circuit" + desc = "Subtracts vectors to get relative movement, target offsets, or sample deltas." + operation = "subtract" + +/obj/item/integrated_circuit/math/vector/scale + name = "vector scale circuit" + desc = "Scales a movement or sensor vector by a numeric strength." + inputs = list("vector" = IC_PINTYPE_LIST, "scale" = IC_PINTYPE_NUMBER) + operation = "scale" + +/obj/item/integrated_circuit/math/vector/magnitude + name = "vector magnitude circuit" + desc = "Calculates vector length for distance, signal strength, or movement error." + inputs = list("vector" = IC_PINTYPE_LIST) + operation = "magnitude" + +/obj/item/integrated_circuit/math/vector/magnitude_squared + name = "vector magnitude squared circuit" + desc = "Calculates squared vector length for cheap target-distance comparisons." + inputs = list("vector" = IC_PINTYPE_LIST) + operation = "magnitude_sq" + +/obj/item/integrated_circuit/math/vector/normalize + name = "vector normalize circuit" + desc = "Turns a movement or gradient vector into a unit direction." + inputs = list("vector" = IC_PINTYPE_LIST) + operation = "normalize" + +/obj/item/integrated_circuit/math/vector/dot + name = "dot product circuit" + desc = "Scores how closely two directions align for targeting, following, and field checks." + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "dot" + +/obj/item/integrated_circuit/math/vector/cross + name = "cross product circuit" + desc = "Calculates a 3D perpendicular vector for advanced orientation logic." + operation = "cross" + +/obj/item/integrated_circuit/math/vector/cross2 + name = "2D scalar cross product circuit" + desc = "Calculates 2D turn direction, useful for left/right steering decisions." + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "cross2" + +/obj/item/integrated_circuit/math/vector/angle + name = "angle between vectors circuit" + desc = "Calculates angle between two vectors for aim error or heading difference." + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "angle" + +/obj/item/integrated_circuit/math/vector/projection + name = "projection circuit" + desc = "Projects one vector onto another to measure progress along a route or sensor axis." + operation = "projection" + +/obj/item/integrated_circuit/math/vector/clamp_magnitude + name = "vector clamp magnitude circuit" + desc = "Limits movement or control vector strength without changing direction." + inputs = list("vector" = IC_PINTYPE_LIST, "maximum magnitude" = IC_PINTYPE_NUMBER) + operation = "clamp_magnitude" + +/obj/item/integrated_circuit/math/matrix + category_text = "MATH - Matrix" + inputs = list("matrix A" = IC_PINTYPE_LIST, "matrix B" = IC_PINTYPE_LIST) + outputs = list("result" = IC_PINTYPE_LIST, "status" = IC_PINTYPE_STRING) + complexity = 8 + +/obj/item/integrated_circuit/math/matrix/do_work() + var/list/A = ic_math_matrix(get_pin_data(IC_INPUT, 1)) + var/list/B = ic_math_matrix(get_pin_data(IC_INPUT, 2)) + + switch(operation) + if("add", "subtract") + if(!A || !B || length(A) != length(B) || length(A[1]) != length(B[1])) + fail("Mismatched matrices") + return + var/list/result = list() + for(var/y = 1 to length(A)) + var/list/row = list() + for(var/x = 1 to length(A[y])) + row += operation == "add" ? A[y][x] + B[y][x] : A[y][x] - B[y][x] + result += list(row) + succeed(result) + if("multiply") + if(!A || !B || length(A[1]) != length(B)) + fail("Dimension mismatch") + return + var/list/result = list() + for(var/y = 1 to length(A)) + var/list/row = list() + for(var/x = 1 to length(B[1])) + var/value = 0 + for(var/k = 1 to length(B)) + value += A[y][k] * B[k][x] + row += value + result += list(row) + succeed(result) + if("matrix_vector") + var/list/vector = ic_math_numeric_list(get_pin_data(IC_INPUT, 2), IC_MATH_VECTOR_LIMIT) + if(!A || !vector || length(A[1]) != length(vector)) + fail("Dimension mismatch") + return + var/list/result = list() + for(var/y = 1 to length(A)) + var/value = 0 + for(var/x = 1 to length(vector)) + value += A[y][x] * vector[x] + result += value + succeed(result) + if("transpose") + if(!A) + fail() + return + var/list/result = list() + for(var/x = 1 to length(A[1])) + var/list/row = list() + for(var/y = 1 to length(A)) + row += A[y][x] + result += list(row) + succeed(result) + if("identity") + var/size = get_pin_data(IC_INPUT, 1) + if(!isnum(size) || size < 1 || size > IC_MATH_MATRIX_LIMIT) + fail("Invalid size") + return + size = round(size) + var/list/result = list() + for(var/y = 1 to size) + var/list/row = list() + for(var/x = 1 to size) + row += x == y ? 1 : 0 + result += list(row) + succeed(result) + if("det2") + if(!A || length(A) != 2 || length(A[1]) != 2) + fail("Requires 2x2") + return + succeed((A[1][1] * A[2][2]) - (A[1][2] * A[2][1])) + if("det3") + if(!A || length(A) != 3 || length(A[1]) != 3) + fail("Requires 3x3") + return + var/result = A[1][1] * ((A[2][2] * A[3][3]) - (A[2][3] * A[3][2])) + result -= A[1][2] * ((A[2][1] * A[3][3]) - (A[2][3] * A[3][1])) + result += A[1][3] * ((A[2][1] * A[3][2]) - (A[2][2] * A[3][1])) + succeed(result) + if("inverse2") + if(!A || length(A) != 2 || length(A[1]) != 2) + fail("Requires 2x2") + return + var/determinant = (A[1][1] * A[2][2]) - (A[1][2] * A[2][1]) + if(determinant == 0) + fail("Singular matrix") + return + succeed(list(list(A[2][2] / determinant, -A[1][2] / determinant), list(-A[2][1] / determinant, A[1][1] / determinant))) + if("rotation2") + var/angle = get_pin_data(IC_INPUT, 1) + if(!isnum(angle)) + fail() + return + succeed(list(list(cos(angle), -sin(angle)), list(sin(angle), cos(angle)))) + +/obj/item/integrated_circuit/math/matrix/add + name = "matrix add circuit" + desc = "Adds two matrices." + operation = "add" + +/obj/item/integrated_circuit/math/matrix/subtract + name = "matrix subtract circuit" + desc = "Subtracts two matrices." + operation = "subtract" + +/obj/item/integrated_circuit/math/matrix/multiply + name = "matrix multiply circuit" + desc = "Combines numeric transforms for compact coordinate, grid, or vector processing." + operation = "multiply" + +/obj/item/integrated_circuit/math/matrix/vector_multiply + name = "matrix vector multiply circuit" + desc = "Applies a matrix transform to a vector for rotation, scaling, or coordinate conversion." + inputs = list("matrix" = IC_PINTYPE_LIST, "vector" = IC_PINTYPE_LIST) + operation = "matrix_vector" + +/obj/item/integrated_circuit/math/matrix/transpose + name = "transpose circuit" + desc = "Swaps matrix rows and columns for grid reorientation and row/column inspection." + inputs = list("matrix" = IC_PINTYPE_LIST) + operation = "transpose" + +/obj/item/integrated_circuit/math/matrix/identity + name = "identity matrix circuit" + desc = "Creates a neutral matrix transform for building or testing matrix chains." + inputs = list("size" = IC_PINTYPE_NUMBER) + operation = "identity" + +/obj/item/integrated_circuit/math/matrix/determinant2 + name = "determinant 2x2 circuit" + desc = "Calculates a 2x2 determinant." + inputs = list("matrix" = IC_PINTYPE_LIST) + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "det2" + +/obj/item/integrated_circuit/math/matrix/determinant3 + name = "determinant 3x3 circuit" + desc = "Calculates a 3x3 determinant." + inputs = list("matrix" = IC_PINTYPE_LIST) + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "det3" + +/obj/item/integrated_circuit/math/matrix/inverse2 + name = "inverse 2x2 circuit" + desc = "Calculates a 2x2 inverse matrix." + inputs = list("matrix" = IC_PINTYPE_LIST) + operation = "inverse2" + +/obj/item/integrated_circuit/math/matrix/rotation2 + name = "2D rotation transform circuit" + desc = "Creates a 2D rotation matrix from degrees for rotating movement or target vectors." + inputs = list("angle" = IC_PINTYPE_NUMBER) + operation = "rotation2" + +/obj/item/integrated_circuit/math/matrix/from_rows + name = "matrix from rows circuit" + desc = "Builds a numeric matrix from row lists for transform, grid, and display circuits." + inputs = list( + "row 1" = IC_PINTYPE_LIST, + "row 2" = IC_PINTYPE_LIST, + "row 3" = IC_PINTYPE_LIST, + "row 4" = IC_PINTYPE_LIST + ) + outputs = list( + "matrix" = IC_PINTYPE_LIST, + "rows" = IC_PINTYPE_NUMBER, + "columns" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + complexity = 6 + operation = "from_rows" + +/obj/item/integrated_circuit/math/matrix/from_rows/do_work() + var/list/matrix = list() + + for(var/i = 1 to IC_MATH_MATRIX_LIMIT) + var/list/row = get_pin_data(IC_INPUT, i) + if(!islist(row)) + continue + matrix += list(row) + + matrix = ic_math_matrix(matrix) + if(!matrix) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, "Invalid matrix") + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, matrix) + set_pin_data(IC_OUTPUT, 2, length(matrix)) + set_pin_data(IC_OUTPUT, 3, length(matrix[1])) + set_pin_data(IC_OUTPUT, 4, TRUE) + set_pin_data(IC_OUTPUT, 5, "OK") + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/matrix/from_flat_list + name = "matrix from flat list circuit" + desc = "Builds a numeric matrix from flat samples plus row and column counts." + inputs = list( + "values" = IC_PINTYPE_LIST, + "rows" = IC_PINTYPE_NUMBER, + "columns" = IC_PINTYPE_NUMBER + ) + outputs = list( + "matrix" = IC_PINTYPE_LIST, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + complexity = 6 + operation = "from_flat_list" + +/obj/item/integrated_circuit/math/matrix/from_flat_list/do_work() + var/list/values = ic_math_numeric_list(get_pin_data(IC_INPUT, 1), IC_MATH_MATRIX_LIMIT * IC_MATH_MATRIX_LIMIT) + var/rows = round(get_pin_data(IC_INPUT, 2)) + var/columns = round(get_pin_data(IC_INPUT, 3)) + + if(!values || !isnum(rows) || !isnum(columns) || rows < 1 || rows > IC_MATH_MATRIX_LIMIT || columns < 1 || columns > IC_MATH_MATRIX_LIMIT || length(values) != rows * columns) + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, "Invalid matrix dimensions") + push_data() + activate_pin(3) + return + + var/list/matrix = list() + var/index = 1 + for(var/y = 1 to rows) + var/list/row = list() + for(var/x = 1 to columns) + row += values[index++] + matrix += list(row) + + set_pin_data(IC_OUTPUT, 1, matrix) + set_pin_data(IC_OUTPUT, 2, TRUE) + set_pin_data(IC_OUTPUT, 3, "OK") + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/matrix/validator + name = "matrix validator circuit" + desc = "Checks whether a matrix is valid before feeding it into transforms or displays." + inputs = list("matrix" = IC_PINTYPE_LIST) + outputs = list( + "valid" = IC_PINTYPE_BOOLEAN, + "rows" = IC_PINTYPE_NUMBER, + "columns" = IC_PINTYPE_NUMBER, + "status" = IC_PINTYPE_STRING + ) + complexity = 3 + operation = "validator" + +/obj/item/integrated_circuit/math/matrix/validator/do_work() + var/list/dimensions = ic_math_matrix_dimensions(get_pin_data(IC_INPUT, 1)) + + if(!dimensions) + set_pin_data(IC_OUTPUT, 1, FALSE) + set_pin_data(IC_OUTPUT, 2, 0) + set_pin_data(IC_OUTPUT, 3, 0) + set_pin_data(IC_OUTPUT, 4, "Invalid matrix") + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, TRUE) + set_pin_data(IC_OUTPUT, 2, dimensions[1]) + set_pin_data(IC_OUTPUT, 3, dimensions[2]) + set_pin_data(IC_OUTPUT, 4, "OK") + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/matrix/set_element + name = "matrix element writer circuit" + desc = "Writes one reading or transform value into a copied matrix at a row and column." + inputs = list( + "matrix" = IC_PINTYPE_LIST, + "row" = IC_PINTYPE_NUMBER, + "column" = IC_PINTYPE_NUMBER, + "value" = IC_PINTYPE_NUMBER + ) + outputs = list( + "matrix" = IC_PINTYPE_LIST, + "changed" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + complexity = 5 + operation = "set_element" + +/obj/item/integrated_circuit/math/matrix/set_element/do_work() + var/list/matrix = ic_math_matrix(get_pin_data(IC_INPUT, 1)) + var/row_index = round(get_pin_data(IC_INPUT, 2)) + var/column_index = round(get_pin_data(IC_INPUT, 3)) + var/value = get_pin_data(IC_INPUT, 4) + + if(!matrix || !isnum(row_index) || !isnum(column_index) || !isnum(value) || row_index < 1 || row_index > length(matrix) || column_index < 1 || column_index > length(matrix[1])) + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, "Invalid element") + push_data() + activate_pin(3) + return + + var/list/row = matrix[row_index] + row[column_index] = value + + set_pin_data(IC_OUTPUT, 1, matrix) + set_pin_data(IC_OUTPUT, 2, TRUE) + set_pin_data(IC_OUTPUT, 3, "OK") + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/matrix/read_element + name = "matrix element reader circuit" + desc = "Reads one matrix cell for displays, thresholds, or selected grid values." + inputs = list( + "matrix" = IC_PINTYPE_LIST, + "row" = IC_PINTYPE_NUMBER, + "column" = IC_PINTYPE_NUMBER + ) + outputs = list( + "value" = IC_PINTYPE_NUMBER, + "found" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + complexity = 3 + operation = "read_element" + +/obj/item/integrated_circuit/math/matrix/read_element/do_work() + var/list/matrix = ic_math_matrix(get_pin_data(IC_INPUT, 1)) + var/row_index = round(get_pin_data(IC_INPUT, 2)) + var/column_index = round(get_pin_data(IC_INPUT, 3)) + + if(!matrix || !isnum(row_index) || !isnum(column_index) || row_index < 1 || row_index > length(matrix) || column_index < 1 || column_index > length(matrix[1])) + set_pin_data(IC_OUTPUT, 2, FALSE) + set_pin_data(IC_OUTPUT, 3, "Invalid element") + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, matrix[row_index][column_index]) + set_pin_data(IC_OUTPUT, 2, TRUE) + set_pin_data(IC_OUTPUT, 3, "OK") + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/matrix/to_text + name = "matrix to text circuit" + desc = "Converts a numeric matrix into compact display text for debugging transforms and grids." + inputs = list("matrix" = IC_PINTYPE_LIST) + outputs = list( + "text" = IC_PINTYPE_STRING, + "status" = IC_PINTYPE_STRING + ) + complexity = 4 + operation = "to_text" + +/obj/item/integrated_circuit/math/matrix/to_text/do_work() + var/text = ic_math_matrix_to_text(get_pin_data(IC_INPUT, 1), MAX_MESSAGE_LEN) + + if(isnull(text)) + set_pin_data(IC_OUTPUT, 2, "Invalid matrix") + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, text) + set_pin_data(IC_OUTPUT, 2, length(text) >= MAX_MESSAGE_LEN - 3 ? "Truncated" : "OK") + push_data() + activate_pin(2) + +// Calculus and interpolation. +/obj/item/integrated_circuit/math/calculus + category_text = "MATH - Calculus" + inputs = list("A" = IC_PINTYPE_NUMBER, "B" = IC_PINTYPE_NUMBER, "C" = IC_PINTYPE_NUMBER) + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + complexity = 5 + +/obj/item/integrated_circuit/math/calculus/do_work() + var/A = get_pin_data(IC_INPUT, 1) + var/B = get_pin_data(IC_INPUT, 2) + var/C = get_pin_data(IC_INPUT, 3) + var/D = get_pin_data(IC_INPUT, 4) + var/E = get_pin_data(IC_INPUT, 5) + + switch(operation) + if("difference") + if(!isnum(A) || !isnum(B)) + fail() + return + succeed(A - B) + if("derivative") + if(!isnum(A) || !isnum(B) || !isnum(C) || C == 0) + fail("Invalid delta time") + return + succeed((A - B) / C) + if("second_derivative") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D) || D == 0) + fail("Invalid delta time") + return + succeed((A - (2 * B) + C) / (D * D)) + if("average_rate") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D) || D == C) + fail("Invalid interval") + return + succeed((B - A) / (D - C)) + if("integral", "trapezoid") + var/list/samples = ic_math_numeric_list(get_pin_data(IC_INPUT, 1)) + var/delta = get_pin_data(IC_INPUT, 2) + if(!samples || !isnum(delta)) + fail() + return + var/result = 0 + if(operation == "integral") + result = ic_math_sum(samples) * delta + else + if(length(samples) < 2) + fail("Too few samples") + return + for(var/i = 1 to length(samples) - 1) + result += ((samples[i] + samples[i + 1]) / 2) * delta + succeed(result) + if("running_integral") + if(!isnum(A) || !isnum(B) || !isnum(C)) + fail() + return + succeed(B + (A * C)) + if("threshold") + if(!isnum(A) || !isnum(B) || !isnum(C)) + fail() + return + succeed((B < C && A >= C) || (B > C && A <= C)) + if("hysteresis") + if(!isnum(A) || !isnum(B) || !isnum(C) || C < B) + fail() + return + var/previous_state = get_pin_data(IC_INPUT, 4) + if(A >= C) + succeed(TRUE) + return + if(A <= B) + succeed(FALSE) + return + succeed(previous_state ? TRUE : FALSE) + if("linear_interpolation", "lerp") + if(!isnum(A) || !isnum(B) || !isnum(C)) + fail() + return + succeed(A + ((B - A) * C)) + if("inverse_lerp") + if(!isnum(A) || !isnum(B) || !isnum(C) || B == A) + fail() + return + succeed((C - A) / (B - A)) + if("remap") + if(!isnum(A) || !isnum(B) || !isnum(C) || !isnum(D) || !isnum(E) || C == B) + fail() + return + succeed(D + ((A - B) / (C - B)) * (E - D)) + if("smoothstep") + if(!isnum(A) || !isnum(B) || !isnum(C) || B == A) + fail() + return + var/t = clamp((C - A) / (B - A), 0, 1) + succeed(t * t * (3 - (2 * t))) + if("exp_smoothing") + if(!isnum(A) || !isnum(B) || !isnum(C)) + fail() + return + C = clamp(C, 0, 1) + succeed((C * A) + ((1 - C) * B)) + +/obj/item/integrated_circuit/math/calculus/derivative + name = "derivative approximation circuit" + desc = "Calculates rate of change so circuits can detect rapidly rising or falling sensor values." + inputs = list("current" = IC_PINTYPE_NUMBER, "previous" = IC_PINTYPE_NUMBER, "delta time" = IC_PINTYPE_NUMBER) + operation = "derivative" + +/obj/item/integrated_circuit/math/calculus/difference + name = "difference circuit" + desc = "Outputs current minus previous for simple rising/falling sensor checks." + inputs = list("current" = IC_PINTYPE_NUMBER, "previous" = IC_PINTYPE_NUMBER) + operation = "difference" + +/obj/item/integrated_circuit/math/calculus/second_derivative + name = "second derivative approximation circuit" + desc = "Calculates changing rate of change for acceleration-like trends in sensors or movement." + inputs = list("current" = IC_PINTYPE_NUMBER, "previous" = IC_PINTYPE_NUMBER, "older" = IC_PINTYPE_NUMBER, "delta time" = IC_PINTYPE_NUMBER) + operation = "second_derivative" + +/obj/item/integrated_circuit/math/calculus/average_rate + name = "average rate of change circuit" + desc = "Calculates average change over an interval for logged sensor or distance readings." + inputs = list("start value" = IC_PINTYPE_NUMBER, "end value" = IC_PINTYPE_NUMBER, "start time" = IC_PINTYPE_NUMBER, "end time" = IC_PINTYPE_NUMBER) + operation = "average_rate" + +/obj/item/integrated_circuit/math/calculus/integral + name = "integral approximation from samples circuit" + desc = "Totals a sample list over time for accumulated exposure, dose, or resource use." + inputs = list("samples" = IC_PINTYPE_LIST, "delta" = IC_PINTYPE_NUMBER) + operation = "integral" + +/obj/item/integrated_circuit/math/calculus/running_integral + name = "running integral circuit" + desc = "Adds current value times delta to a previous total for exposure, dose, and accumulated resource tracking." + inputs = list("current value" = IC_PINTYPE_NUMBER, "previous total" = IC_PINTYPE_NUMBER, "delta" = IC_PINTYPE_NUMBER) + operation = "running_integral" + +/obj/item/integrated_circuit/math/calculus/trapezoid + name = "trapezoidal integral circuit" + desc = "Totals a changing sample list more smoothly for accumulated exposure or environmental dose." + inputs = list("samples" = IC_PINTYPE_LIST, "delta" = IC_PINTYPE_NUMBER) + operation = "trapezoid" + +/obj/item/integrated_circuit/math/calculus/threshold + name = "threshold crossing detector circuit" + desc = "Pulsed detector for sensor values crossing a threshold in either direction." + inputs = list("current" = IC_PINTYPE_NUMBER, "previous" = IC_PINTYPE_NUMBER, "threshold" = IC_PINTYPE_NUMBER) + outputs = list("crossed" = IC_PINTYPE_BOOLEAN, "status" = IC_PINTYPE_STRING) + operation = "threshold" + +/obj/item/integrated_circuit/math/calculus/hysteresis + name = "hysteresis circuit" + desc = "Keeps alarms and controllers from flickering by switching on at a high threshold and off at a low threshold." + inputs = list("value" = IC_PINTYPE_NUMBER, "off threshold" = IC_PINTYPE_NUMBER, "on threshold" = IC_PINTYPE_NUMBER, "previous state" = IC_PINTYPE_BOOLEAN) + outputs = list("state" = IC_PINTYPE_BOOLEAN, "status" = IC_PINTYPE_STRING) + operation = "hysteresis" + +/obj/item/integrated_circuit/math/calculus/linear_interpolation + name = "linear interpolation circuit" + desc = "Blends between two values for smooth displays, servo-like controls, or target prediction." + inputs = list("start" = IC_PINTYPE_NUMBER, "end" = IC_PINTYPE_NUMBER, "t" = IC_PINTYPE_NUMBER) + operation = "linear_interpolation" + +/obj/item/integrated_circuit/math/calculus/lerp + name = "lerp circuit" + desc = "Blends between two values for smooth displays, servo-like controls, or target prediction." + inputs = list("start" = IC_PINTYPE_NUMBER, "end" = IC_PINTYPE_NUMBER, "t" = IC_PINTYPE_NUMBER) + operation = "lerp" + +/obj/item/integrated_circuit/math/calculus/inverse_lerp + name = "inverse lerp circuit" + desc = "Converts a value inside a range into a 0-1 progress fraction for displays and scoring." + inputs = list("start" = IC_PINTYPE_NUMBER, "end" = IC_PINTYPE_NUMBER, "value" = IC_PINTYPE_NUMBER) + operation = "inverse_lerp" + +/obj/item/integrated_circuit/math/calculus/remap + name = "remap circuit" + desc = "Converts sensor ranges into display percentages, threat scores, or actuator strengths." + inputs = list("value" = IC_PINTYPE_NUMBER, "from min" = IC_PINTYPE_NUMBER, "from max" = IC_PINTYPE_NUMBER, "to min" = IC_PINTYPE_NUMBER, "to max" = IC_PINTYPE_NUMBER) + operation = "remap" + +/obj/item/integrated_circuit/math/calculus/smoothstep + name = "smoothstep circuit" + desc = "Produces a smoothed 0-1 transition between two thresholds for gradual control outputs." + inputs = list("edge 0" = IC_PINTYPE_NUMBER, "edge 1" = IC_PINTYPE_NUMBER, "value" = IC_PINTYPE_NUMBER) + operation = "smoothstep" + +/obj/item/integrated_circuit/math/calculus/exponential_smoothing + name = "exponential smoothing circuit" + desc = "Filters noisy sensor values using the previous output and a tunable alpha." + inputs = list("current" = IC_PINTYPE_NUMBER, "previous output" = IC_PINTYPE_NUMBER, "alpha" = IC_PINTYPE_NUMBER) + operation = "exp_smoothing" + +/obj/item/integrated_circuit/math/vector_calculus + category_text = "MATH - Vector Calculus" + inputs = list("grid" = IC_PINTYPE_LIST, "x" = IC_PINTYPE_NUMBER, "y" = IC_PINTYPE_NUMBER, "step" = IC_PINTYPE_NUMBER) + outputs = list("result" = IC_PINTYPE_LIST, "status" = IC_PINTYPE_STRING) + complexity = 8 + +/obj/item/integrated_circuit/math/vector_calculus/proc/read_grid_value(list/grid, x, y) + if(y < 1 || y > length(grid)) + return null + var/list/row = grid[y] + if(x < 1 || x > length(row)) + return null + return row[x] + +/obj/item/integrated_circuit/math/vector_calculus/do_work() + var/list/grid = ic_math_grid(get_pin_data(IC_INPUT, 1)) + var/x = round(get_pin_data(IC_INPUT, 2)) + var/y = round(get_pin_data(IC_INPUT, 3)) + var/step = get_pin_data(IC_INPUT, 4) + if(!grid || !isnum(x) || !isnum(y) || !isnum(step) || step == 0) + fail("Invalid grid") + return + + var/center = read_grid_value(grid, x, y) + var/left = read_grid_value(grid, x - 1, y) + var/right = read_grid_value(grid, x + 1, y) + var/down = read_grid_value(grid, x, y - 1) + var/up = read_grid_value(grid, x, y + 1) + + if(isnull(center) || isnull(left) || isnull(right) || isnull(down) || isnull(up)) + fail("Point requires neighbors") + return + + switch(operation) + if("gradient") + if(!isnum(left) || !isnum(right) || !isnum(down) || !isnum(up)) + fail("Scalar grid required") + return + succeed(list((right - left) / (2 * step), (up - down) / (2 * step))) + if("laplacian") + if(!isnum(center) || !isnum(left) || !isnum(right) || !isnum(down) || !isnum(up)) + fail("Scalar grid required") + return + succeed((left + right + up + down - (4 * center)) / (step * step)) + if("directional") + var/list/direction = ic_math_numeric_list(get_pin_data(IC_INPUT, 5), IC_MATH_VECTOR_LIMIT) + if(!direction || length(direction) < 2) + fail("Direction required") + return + var/magnitude = ic_math_vector_magnitude(direction) + if(magnitude == 0) + fail("Zero direction") + return + var/gx = (right - left) / (2 * step) + var/gy = (up - down) / (2 * step) + succeed((gx * direction[1] / magnitude) + (gy * direction[2] / magnitude)) + if("divergence", "curl2") + if(!islist(left) || !islist(right) || !islist(down) || !islist(up)) + fail("Vector grid required") + return + var/list/L = ic_math_numeric_list(left, 2) + var/list/R = ic_math_numeric_list(right, 2) + var/list/D = ic_math_numeric_list(down, 2) + var/list/U = ic_math_numeric_list(up, 2) + if(!L || !R || !D || !U || length(L) < 2 || length(R) < 2 || length(D) < 2 || length(U) < 2) + fail("2D vector grid required") + return + if(operation == "divergence") + succeed(((R[1] - L[1]) + (U[2] - D[2])) / (2 * step)) + else + succeed(((R[2] - L[2]) - (U[1] - D[1])) / (2 * step)) + +/obj/item/integrated_circuit/math/vector_calculus/gradient + name = "gradient circuit" + desc = "Finds which direction nearby scalar readings increase, useful for heat, radiation, or signal tracking." + operation = "gradient" + +/obj/item/integrated_circuit/math/vector_calculus/divergence + name = "divergence circuit" + desc = "Measures whether a vector field spreads outward or inward around a point." + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "divergence" + +/obj/item/integrated_circuit/math/vector_calculus/curl2 + name = "curl 2D circuit" + desc = "Measures local rotation in a 2D vector field for flow or steering analysis." + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "curl2" + +/obj/item/integrated_circuit/math/vector_calculus/laplacian + name = "laplacian circuit" + desc = "Detects hotspots and anomalies where the center reading differs sharply from its neighbors." + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "laplacian" + +/obj/item/integrated_circuit/math/vector_calculus/directional_derivative + name = "directional derivative circuit" + desc = "Measures whether scalar readings rise or fall along a supplied movement direction." + inputs = list("grid" = IC_PINTYPE_LIST, "x" = IC_PINTYPE_NUMBER, "y" = IC_PINTYPE_NUMBER, "step" = IC_PINTYPE_NUMBER, "direction" = IC_PINTYPE_LIST) + outputs = list("result" = IC_PINTYPE_NUMBER, "status" = IC_PINTYPE_STRING) + operation = "directional" + +/obj/item/integrated_circuit/math/vector_calculus/grid_from_rows + name = "scalar grid from rows circuit" + desc = "Builds a bounded scalar grid from nearby numeric readings for gradient, Laplacian, and hotspot circuits." + inputs = list( + "row 1" = IC_PINTYPE_LIST, + "row 2" = IC_PINTYPE_LIST, + "row 3" = IC_PINTYPE_LIST, + "row 4" = IC_PINTYPE_LIST, + "row 5" = IC_PINTYPE_LIST, + "row 6" = IC_PINTYPE_LIST, + "row 7" = IC_PINTYPE_LIST + ) + outputs = list( + "grid" = IC_PINTYPE_LIST, + "rows" = IC_PINTYPE_NUMBER, + "columns" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + complexity = 8 + power_draw_per_use = 150 + operation = "grid_from_rows" + +/obj/item/integrated_circuit/math/vector_calculus/grid_from_rows/do_work() + var/list/grid = list() + + for(var/i = 1 to IC_MATH_GRID_LIMIT) + var/list/row = get_pin_data(IC_INPUT, i) + if(!islist(row)) + continue + grid += list(row) + + grid = ic_math_grid(grid) + if(!grid) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, "Invalid grid") + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, grid) + set_pin_data(IC_OUTPUT, 2, length(grid)) + set_pin_data(IC_OUTPUT, 3, length(grid[1])) + set_pin_data(IC_OUTPUT, 4, TRUE) + set_pin_data(IC_OUTPUT, 5, "OK") + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/vector_calculus/vector_grid_from_components + name = "vector grid from components circuit" + desc = "Builds a 2D vector-field grid from matching X and Y component readings." + inputs = list( + "x component grid" = IC_PINTYPE_LIST, + "y component grid" = IC_PINTYPE_LIST + ) + outputs = list( + "grid" = IC_PINTYPE_LIST, + "rows" = IC_PINTYPE_NUMBER, + "columns" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + complexity = 10 + power_draw_per_use = 200 + operation = "vector_grid_from_components" + +/obj/item/integrated_circuit/math/vector_calculus/vector_grid_from_components/do_work() + var/list/x_grid = ic_math_grid(get_pin_data(IC_INPUT, 1)) + var/list/y_grid = ic_math_grid(get_pin_data(IC_INPUT, 2)) + + if(!x_grid || !y_grid || length(x_grid) != length(y_grid) || length(x_grid[1]) != length(y_grid[1])) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, "Mismatched grids") + push_data() + activate_pin(3) + return + + var/list/result = list() + for(var/y = 1 to length(x_grid)) + var/list/x_row = x_grid[y] + var/list/y_row = y_grid[y] + var/list/row = list() + for(var/x = 1 to length(x_row)) + if(!isnum(x_row[x]) || !isnum(y_row[x])) + set_pin_data(IC_OUTPUT, 4, FALSE) + set_pin_data(IC_OUTPUT, 5, "Scalar grids required") + push_data() + activate_pin(3) + return + row += list(list(x_row[x], y_row[x])) + result += list(row) + + set_pin_data(IC_OUTPUT, 1, result) + set_pin_data(IC_OUTPUT, 2, length(result)) + set_pin_data(IC_OUTPUT, 3, length(result[1])) + set_pin_data(IC_OUTPUT, 4, TRUE) + set_pin_data(IC_OUTPUT, 5, "OK") + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/vector_calculus/grid_validator + name = "grid validator circuit" + desc = "Checks whether a sensor grid is valid before feeding it into field-analysis circuits." + inputs = list("grid" = IC_PINTYPE_LIST) + outputs = list( + "valid" = IC_PINTYPE_BOOLEAN, + "rows" = IC_PINTYPE_NUMBER, + "columns" = IC_PINTYPE_NUMBER, + "status" = IC_PINTYPE_STRING + ) + complexity = 4 + operation = "grid_validator" + +/obj/item/integrated_circuit/math/vector_calculus/grid_validator/do_work() + var/list/dimensions = ic_math_grid_dimensions(get_pin_data(IC_INPUT, 1)) + + if(!dimensions) + set_pin_data(IC_OUTPUT, 1, FALSE) + set_pin_data(IC_OUTPUT, 2, 0) + set_pin_data(IC_OUTPUT, 3, 0) + set_pin_data(IC_OUTPUT, 4, "Invalid grid") + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, TRUE) + set_pin_data(IC_OUTPUT, 2, dimensions[1]) + set_pin_data(IC_OUTPUT, 3, dimensions[2]) + set_pin_data(IC_OUTPUT, 4, "OK") + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/vector_calculus/grid_to_text + name = "grid to text circuit" + desc = "Converts a scalar or 2D vector grid into compact display text for debugging sensor layouts." + inputs = list("grid" = IC_PINTYPE_LIST) + outputs = list( + "text" = IC_PINTYPE_STRING, + "status" = IC_PINTYPE_STRING + ) + complexity = 5 + operation = "grid_to_text" + +/obj/item/integrated_circuit/math/vector_calculus/grid_to_text/do_work() + var/text = ic_math_grid_to_text(get_pin_data(IC_INPUT, 1), MAX_MESSAGE_LEN) + + if(isnull(text)) + set_pin_data(IC_OUTPUT, 2, "Invalid grid") + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, text) + set_pin_data(IC_OUTPUT, 2, length(text) >= MAX_MESSAGE_LEN - 3 ? "Truncated" : "OK") + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/math/vector/to_text + name = "vector to text circuit" + desc = "Converts a numeric vector into compact display text for movement and field-analysis debugging." + inputs = list("vector" = IC_PINTYPE_LIST) + outputs = list( + "text" = IC_PINTYPE_STRING, + "status" = IC_PINTYPE_STRING + ) + complexity = 3 + operation = "to_text" + +/obj/item/integrated_circuit/math/vector/to_text/do_work() + var/list/vector = ic_math_numeric_list(get_pin_data(IC_INPUT, 1), IC_MATH_VECTOR_LIMIT) + + if(!vector) + set_pin_data(IC_OUTPUT, 2, "Invalid vector") + push_data() + activate_pin(3) + return + + set_pin_data(IC_OUTPUT, 1, "([jointext(vector, ", ")])") + set_pin_data(IC_OUTPUT, 2, "OK") + push_data() + activate_pin(2) + +#undef IC_MATH_LIST_LIMIT +#undef IC_MATH_VECTOR_LIMIT +#undef IC_MATH_MATRIX_LIMIT +#undef IC_MATH_GRID_LIMIT diff --git a/code/modules/integrated_electronics/subtypes/memory.dm b/code/modules/integrated_electronics/subtypes/memory.dm index d28ceff3d23..57b7601b28c 100644 --- a/code/modules/integrated_electronics/subtypes/memory.dm +++ b/code/modules/integrated_electronics/subtypes/memory.dm @@ -1,3 +1,8 @@ +/* + * subtypes/memory.dm + * Memory circuits that store circuit values across pulses and expose saved values through output pins. + */ + /obj/item/integrated_circuit/memory complexity = 1 category_text = "Memory" @@ -40,6 +45,9 @@ /obj/item/integrated_circuit/memory/storage/do_work() for(var/i = 1 to inputs.len) var/data = get_pin_data(IC_INPUT, i) + if(islist(data)) + var/list/list_data = data + data = list_data.Copy() set_pin_data(IC_OUTPUT, i, data) push_data() activate_pin(2) @@ -53,7 +61,7 @@ /obj/item/integrated_circuit/memory/storage/large name = "large memory circuit" - desc = "This big circuit can hold eight pieces of data." + desc = "This circuit can store eight pieces of data." icon_state = "memory8" origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 3) power_draw_per_use = 40 @@ -61,7 +69,7 @@ /obj/item/integrated_circuit/memory/storage/huge name = "large memory stick" - desc = "This stick of memory can hold up up to sixteen pieces of data." + desc = "This memory stick can store up to sixteen pieces of data." icon_state = "memory16" w_class = WEIGHT_CLASS_NORMAL spawn_flags = IC_SPAWN_RESEARCH @@ -71,7 +79,7 @@ /obj/item/integrated_circuit/memory/constant name = "constant chip" - desc = "This tiny chip can store one piece of data, which cannot be overwritten without disassembly." + desc = "This chip stores one constant value. It cannot be changed without disassembly." icon_state = "memory1" inputs = list() outputs = list("output pin" = IC_PINTYPE_ANY) @@ -84,7 +92,11 @@ var/data /obj/item/integrated_circuit/memory/constant/do_work() - set_pin_data(IC_OUTPUT, 1, data) + var/output_data = data + if(islist(output_data)) + var/list/list_data = output_data + output_data = list_data.Copy() + set_pin_data(IC_OUTPUT, 1, output_data) push_data() activate_pin(2) diff --git a/code/modules/integrated_electronics/subtypes/output.dm b/code/modules/integrated_electronics/subtypes/output.dm index 4ddd75740b1..551d0827e05 100644 --- a/code/modules/integrated_electronics/subtypes/output.dm +++ b/code/modules/integrated_electronics/subtypes/output.dm @@ -1,9 +1,14 @@ +/* + * subtypes/output.dm + * Output circuits that speak, display, signal, pulse, transmit, or otherwise emit circuit results into the game world. + */ + /obj/item/integrated_circuit/output category_text = "Output" /obj/item/integrated_circuit/output/screen name = "small screen" - desc = "This small screen can display a single piece of data, when the machine is examined closely." + desc = "Displays one value when the assembly is examined closely." icon_state = "screen" inputs = list("displayed data" = IC_PINTYPE_ANY) outputs = list() @@ -34,31 +39,130 @@ /obj/item/integrated_circuit/output/screen/medium name = "screen" - desc = "This screen allows for people holding the device to see a piece of data." + desc = "Displays data to the person holding the device." icon_state = "screen_medium" power_draw_per_use = 200 /obj/item/integrated_circuit/output/screen/medium/do_work() ..() - var/list/nearby_things = range(0, get_turf(src)) + var/turf/T = get_turf(src) + if(!T) + return + var/list/nearby_things = range(0, T) + var/list/viewing = viewers(T) for(var/mob/M in nearby_things) var/obj/O = assembly ? assembly : src - to_chat(M, SPAN_NOTICE("[icon2html(O, viewers(get_turf(src)))] [stuff_to_display]")) + to_chat(M, SPAN_NOTICE("[icon2html(O, viewing)] [stuff_to_display]")) /obj/item/integrated_circuit/output/screen/large name = "large screen" - desc = "This screen allows for people able to see the device to see a piece of data." + desc = "Displays data to nearby viewers who can see the device." icon_state = "screen_large" power_draw_per_use = 400 /obj/item/integrated_circuit/output/screen/large/do_work() ..() - var/obj/O = assembly ? loc : assembly - O.visible_message(SPAN_NOTICE("[icon2html(O, viewers(get_turf(src)))] [stuff_to_display]")) + var/obj/O = assembly ? assembly : src + var/turf/T = get_turf(src) + if(!O || !T) + return + O.visible_message(SPAN_NOTICE("[icon2html(O, viewers(T))] [stuff_to_display]")) + +/obj/item/integrated_circuit/output/state_display + name = "state display" + desc = "Displays a compact label and value when the assembly is examined closely." + icon_state = "screen" + complexity = 2 + inputs = list( + "label" = IC_PINTYPE_STRING, + "value" = IC_PINTYPE_ANY, + "enabled" = IC_PINTYPE_BOOLEAN + ) + inputs_default = list( + "1" = "Status", + "3" = TRUE + ) + outputs = list( + "displayed" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "update" = IC_PINTYPE_PULSE_IN, + "on updated" = IC_PINTYPE_PULSE_OUT, + "on invalid" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 100 + + var/stuff_to_display = null + +/obj/item/integrated_circuit/output/state_display/disconnect_all() + ..() + stuff_to_display = null + +/obj/item/integrated_circuit/output/state_display/any_examine(mob/user) + . = ..() + if(!isnull(stuff_to_display)) + . += "There is a little status display reading '[stuff_to_display]'." + +/obj/item/integrated_circuit/output/state_display/do_work() + if(!get_pin_data(IC_INPUT, 3)) + stuff_to_display = null + set_pin_data(IC_OUTPUT, 1, FALSE) + push_data() + activate_pin(3) + return + + var/label = get_pin_data(IC_INPUT, 1) + var/value = get_pin_data(IC_INPUT, 2, FALSE) + + if(isnull(value)) + stuff_to_display = null + set_pin_data(IC_OUTPUT, 1, FALSE) + push_data() + activate_pin(3) + return + + stuff_to_display = sanitizeSafe("[ic_safe_text(label, "Status")]: [ic_display_value(value)]", MAX_MESSAGE_LEN) + set_pin_data(IC_OUTPUT, 1, TRUE) + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/output/pulse_result + name = "pulse result router" + desc = "Routes a value into true, false, or invalid pulse outputs." + icon_state = "template" + complexity = 1 + inputs = list( + "value" = IC_PINTYPE_ANY + ) + outputs = list( + "value" = IC_PINTYPE_ANY + ) + activators = list( + "evaluate" = IC_PINTYPE_PULSE_IN, + "on true" = IC_PINTYPE_PULSE_OUT, + "on false" = IC_PINTYPE_PULSE_OUT, + "on invalid" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 10 + +/obj/item/integrated_circuit/output/pulse_result/do_work() + var/value = get_pin_data(IC_INPUT, 1, FALSE) + + set_pin_data(IC_OUTPUT, 1, value) + push_data() + + if(isnull(value)) + activate_pin(4) + else if(ic_truthy(value)) + activate_pin(2) + else + activate_pin(3) /obj/item/integrated_circuit/output/light name = "light" - desc = "This light can turn on and off on command." + desc = "Turns a light on or off when pulsed." icon_state = "light" complexity = 4 inputs = list() @@ -112,7 +216,7 @@ /obj/item/integrated_circuit/output/light/advanced name = "advanced light" - desc = "This light can turn on and off on command, in any color, and in various brightness levels." + desc = "Turns a configurable colored light on or off when pulsed." icon_state = "light_adv" complexity = 8 inputs = list( @@ -128,7 +232,7 @@ /obj/item/integrated_circuit/output/sound name = "speaker circuit" - desc = "A miniature speaker is attached to this component." + desc = "Plays sounds from the assembly." icon_state = "speaker" complexity = 8 cooldown_per_use = 1 SECOND @@ -144,8 +248,8 @@ /obj/item/integrated_circuit/output/text_to_speech name = "text-to-speech circuit" - desc = "A miniature speaker is attached to this component." - extended_desc = "This unit is more advanced than the plain speaker circuit, able to transpose any valid text to speech." + desc = "Plays sounds from the assembly." + extended_desc = "Converts valid text input into spoken audio." icon_state = "speaker" complexity = 12 cooldown_per_use = 1 SECOND @@ -156,10 +260,30 @@ power_draw_per_use = 600 /obj/item/integrated_circuit/output/text_to_speech/do_work() - text = get_pin_data(IC_INPUT, 1) - if(!isnull(text)) - var/obj/O = assembly ? loc : assembly - audible_message("[icon2html(O, viewers(get_turf(O)))] \The [O.name] states, \"[text]\"") + var/text = get_pin_data(IC_INPUT, 1) + + if(isnull(text)) + return + + /* + * If this TTS circuit is inside an electronic radio headset, + * output privately to the headset wearer only. + */ + var/obj/item/electronic_assembly/clothing/A = loc + + if(istype(A)) + var/obj/item/radio/headset/circuitry/H = A.clothing + + if(istype(H)) + H.private_tts_output(text) + return + + /* + * Fallback behavior for every TTS circuit outside the electronic radio headset. + */ + var/obj/O = loc ? loc : src + var/turf/T = get_turf(O) + audible_message("[icon2html(O, viewers(T))] \The [O.name] states, \"[text]\"") /obj/item/integrated_circuit/output/sound/Initialize() . = ..() @@ -183,7 +307,7 @@ /obj/item/integrated_circuit/output/sound/beeper name = "beeper circuit" - desc = "A miniature speaker is attached to this component. This is often used in the construction of motherboards, which use \ + desc = "Plays sounds from the assembly. This is often used in the construction of motherboards, which use \ the speaker to tell the user if something goes very wrong when booting up. It can also do other similar synthetic sounds such \ as buzzing, pinging, chiming, and more." sounds = list( @@ -200,7 +324,7 @@ /obj/item/integrated_circuit/output/sound/beepsky name = "securitron sound circuit" - desc = "A miniature speaker is attached to this component. Considered by some to be the essential component for a securitron." + desc = "A miniature speaker that plays alert tones or warning sounds." sounds = list( "creep" = 'sound/voice/bcreep.ogg', "criminal" = 'sound/voice/bcriminal.ogg', @@ -215,7 +339,7 @@ /obj/item/integrated_circuit/output/sound/medbot name = "medibot sound circuit" - desc = "A miniature speaker is attached to this component, used to annoy patients while they get pricked by a medbot." + desc = "A miniature speaker that plays medical bot audio cues." sounds = list( "surgeon" = 'sound/voice/medbot/msurgeon.ogg', "radar" = 'sound/voice/medbot/mradar.ogg', @@ -237,8 +361,8 @@ /obj/item/integrated_circuit/output/video_camera name = "video camera circuit" - desc = "This small camera allows a remote viewer to see what it sees." - extended_desc = "The camera is linked to the Research camera network by default, but can be changed." + desc = "Creates a camera feed from the assembly's position." + extended_desc = "Creates a camera feed. It uses the Research camera network by default, but the network can be changed." icon_state = "video_camera" w_class = WEIGHT_CLASS_SMALL complexity = 10 @@ -295,8 +419,8 @@ /obj/item/integrated_circuit/output/led name = "light-emitting diode" - desc = "This a LED that is lit whenever there is TRUE-equivalent data on its input." - extended_desc = "TRUE-equivalent values are: Non-empty strings, non-zero numbers, and valid refs." + desc = "This LED lights while its input contains TRUE-equivalent data." + extended_desc = "TRUE values include non-empty text, non-zero numbers, valid refs, and populated lists. FALSE values include null, 0, and empty text." complexity = 0.1 icon_state = "led" inputs = list("lit" = IC_PINTYPE_BOOLEAN) @@ -375,7 +499,7 @@ /obj/item/integrated_circuit/output/printer name = "printer" - desc = "This printer can print text to a sheet of paper. If the eject pin is set or the paper is full of text it will eject the paper after printing" + desc = "Prints text to paper. If the eject input is enabled, or if the paper is full, the paper is ejected after printing." icon_state = "screen" inputs = list("printed data" = IC_PINTYPE_ANY, "paper source" = IC_PINTYPE_REF, "eject sheet" = IC_PINTYPE_BOOLEAN) outputs = list() @@ -410,6 +534,8 @@ paper_sheet.set_content(null, copytext(stuff_to_print, 1, MAX_PAPER_MESSAGE_LEN)) stuff_to_print = copytext(stuff_to_print, MAX_PAPER_MESSAGE_LEN) if(stuff_to_print || eject) + if(!using_tray) + return paper_sheet = paper_source.get_item(TRUE) audible_message(SPAN_NOTICE("\The [src] buzzes and spits out a sheet of paper.")) paper_sheet.forceMove(get_turf(src)) diff --git a/code/modules/integrated_electronics/subtypes/power.dm b/code/modules/integrated_electronics/subtypes/power.dm index 7d8782e1f79..2ed335a24fe 100644 --- a/code/modules/integrated_electronics/subtypes/power.dm +++ b/code/modules/integrated_electronics/subtypes/power.dm @@ -1,9 +1,14 @@ +/* + * subtypes/power.dm + * Active power-management circuits that expose assembly charge, draw, and cell state to the circuit network. + */ + /obj/item/integrated_circuit/power/ category_text = "Power - Active" /obj/item/integrated_circuit/power/transmitter name = "power transmission circuit" - desc = "This can wirelessly transmit electricity from an assembly's battery towards a nearby machine." + desc = "Wirelessly transmits electricity from an assembly battery to a nearby machine." icon_state = "power_transmitter" extended_desc = "This circuit transmits 5 kJ of electricity every time the activator pin is pulsed. The input pin must be \ a reference to a machine to send electricity to. This can be a battery, or anything containing a battery. The machine can exist \ @@ -25,7 +30,7 @@ /obj/item/integrated_circuit/power/transmitter/large name = "large power transmission circuit" - desc = "This can wirelessly transmit a lot of electricity from an assembly's battery towards a nearby machine. Warning: Do not operate in flammable enviroments." + desc = "Wirelessly transmits a large amount of electricity from an assembly battery to a nearby machine. Avoid use near flammable environments." extended_desc = "This circuit transmits 20 kJ of electricity every time the activator pin is pulsed. The input pin must be \ a reference to a machine to send electricity to. This can be a battery, or anything containing a battery. The machine can exist \ inside the assembly, or adjacent to it. The power is sourced from the assembly's power cell. If the target is outside of the assembly, \ diff --git a/code/modules/integrated_electronics/subtypes/reagents.dm b/code/modules/integrated_electronics/subtypes/reagents.dm index 8bbe6fe1873..2505b1758af 100644 --- a/code/modules/integrated_electronics/subtypes/reagents.dm +++ b/code/modules/integrated_electronics/subtypes/reagents.dm @@ -1,3 +1,8 @@ +/* + * subtypes/reagents.dm + * Reagent circuits for reading containers, transferring chemicals, and exposing reagent metadata. + */ + /obj/item/integrated_circuit/reagent category_text = "Reagent" var/volume = 0 @@ -10,7 +15,7 @@ /obj/item/integrated_circuit/reagent/injector name = "integrated hypo-injector" - desc = "This scary looking thing is able to pump liquids into whatever it's pointed at." + desc = "Pumps reagents into the targeted adjacent container or target." icon_state = "injector" extended_desc = "This autoinjector can push reagents into another container or someone else outside of the machine. The target \ must be adjacent to the machine, and if it is a person, they cannot be wearing thick clothing." @@ -19,7 +24,7 @@ cooldown_per_use = 6 SECONDS inputs = list("target" = IC_PINTYPE_REF, "injection amount" = IC_PINTYPE_NUMBER) inputs_default = list("2" = 5) - outputs = list("volume used" = IC_PINTYPE_NUMBER,"self reference" = IC_PINTYPE_REF) + outputs = list("volume used" = IC_PINTYPE_NUMBER, "self reference" = IC_PINTYPE_REF) activators = list("inject" = IC_PINTYPE_PULSE_IN, "on injected" = IC_PINTYPE_PULSE_OUT, "on fail" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH volume = 30 @@ -27,11 +32,13 @@ var/direc = 1 var/transfer_amount = 10 + /obj/item/integrated_circuit/reagent/injector/interact(mob/user) set_pin_data(IC_OUTPUT, 2, src) push_data() ..() + /obj/item/integrated_circuit/reagent/injector/on_reagent_change() set_pin_data(IC_OUTPUT, 1, reagents.total_volume) push_data() @@ -39,94 +46,144 @@ /obj/item/integrated_circuit/reagent/injector/on_data_written() var/new_amount = get_pin_data(IC_INPUT, 2) + + if(!isnum(new_amount)) + return + if(new_amount < 0) new_amount = -new_amount direc = 0 else direc = 1 - if(isnum(new_amount)) - new_amount = clamp(new_amount, 0, volume) - transfer_amount = new_amount + + new_amount = clamp(new_amount, 0, volume) + transfer_amount = new_amount + /obj/item/integrated_circuit/reagent/injector/do_work() - set waitfor = 0 // Don't sleep in a proc that is called by a processor without this set, otherwise it'll delay the entire thing - var/atom/movable/AM = get_pin_data_as_type(IC_INPUT, 1, /atom/movable) - if(!istype(AM)) //Invalid input + + if(!istype(AM)) activate_pin(3) return if(direc == 1) + if(!reagents || !reagents.total_volume) + activate_pin(3) + return - if(!istype(AM)) //Invalid input + if(!AM.can_be_injected_by(src)) activate_pin(3) return - if(!reagents.total_volume) // Empty - activate_pin(3) - return - if(AM.can_be_injected_by(src)) - if(isliving(AM)) - var/mob/living/L = AM - var/turf/T = get_turf(AM) + + if(isliving(AM)) + var/mob/living/L = AM + var/turf/T = get_turf(AM) + + if(T) T.visible_message(SPAN_WARNING("[assembly] is trying to inject [L]!")) - sleep(3 SECONDS) - if(!L.can_be_injected_by(src)) - activate_pin(3) - return - var/contained = reagents.get_reagents() - var/trans = reagents.trans_to_mob(L, transfer_amount, CHEM_BLOOD) - message_admins("[assembly] injected \the [L] with [trans]u of [contained].") - to_chat(AM, SPAN_NOTICE("You feel a tiny prick!")) - visible_message(SPAN_WARNING("[assembly] injects [L]!")) - else - reagents.trans_to(AM, transfer_amount) - else - if(reagents.total_volume >= volume) // Full + addtimer(CALLBACK(src, PROC_REF(finish_living_injection), L), 3 SECONDS) + return + + if(reagents.trans_to(AM, transfer_amount)) + activate_pin(2) + else + activate_pin(3) + + return + + if(reagents.total_volume >= volume) + activate_pin(3) + return + + var/obj/target = AM + + if(!target.reagents) + activate_pin(3) + return + + var/turf/TS = get_turf(src) + var/turf/TT = get_turf(AM) + + if(!TS || !TT || !TS.Adjacent(TT)) + activate_pin(3) + return + + var/tramount = clamp(min(transfer_amount, REAGENTS_FREE_SPACE(reagents)), 0, reagents.maximum_volume) + + if(ismob(target)) + if(!istype(target, /mob/living/carbon)) activate_pin(3) return - var/obj/target = AM - if(!target.reagents) - activate_pin(3) - return - var/turf/TS = get_turf(src) - var/turf/TT = get_turf(AM) - if(!TS.Adjacent(TT)) - activate_pin(3) - return - var/tramount = clamp(min(transfer_amount, REAGENTS_FREE_SPACE(reagents)), 0, reagents.maximum_volume) - if(ismob(target))//Blood! - if(istype(target, /mob/living/carbon)) - var/mob/living/carbon/T = target - if(!T.dna) - if(T.reagents.trans_to_obj(src, tramount)) - activate_pin(2) - else - activate_pin(3) - return - if((T.mutations & NOCLONE)) //target done been et, no more blood in him - if(T.reagents.trans_to_obj(src, tramount)) - activate_pin(2) - else - activate_pin(3) - return - T.take_blood(src,tramount) - visible_message( SPAN_NOTICE("[assembly] takes a blood sample from [target].")) + + var/mob/living/carbon/T = target + + if(!T.dna) + if(T.reagents.trans_to_obj(src, tramount)) + activate_pin(2) else activate_pin(3) - return + return - else //if not mob - if(!target.reagents.total_volume) - visible_message( SPAN_NOTICE("[target] is empty.")) + if(T.mutations & NOCLONE) + if(T.reagents.trans_to_obj(src, tramount)) + activate_pin(2) + else activate_pin(3) - return - target.reagents.trans_to_obj(src, tramount) + return + + T.take_blood(src, tramount) + visible_message(SPAN_NOTICE("[assembly] takes a blood sample from [target].")) + activate_pin(2) + return + + if(!target.reagents.total_volume) + visible_message(SPAN_NOTICE("[target] is empty.")) + activate_pin(3) + return + + target.reagents.trans_to_obj(src, tramount) activate_pin(2) + +/obj/item/integrated_circuit/reagent/injector/proc/finish_living_injection(mob/living/L) + if(QDELETED(src) || QDELETED(L)) + return + + if(!assembly) + activate_pin(3) + return + + if(!reagents || !reagents.total_volume) + activate_pin(3) + return + + if(!L.can_be_injected_by(src)) + activate_pin(3) + return + + var/turf/TS = get_turf(src) + var/turf/TL = get_turf(L) + + if(!TS || !TL || !TS.Adjacent(TL)) + activate_pin(3) + return + + var/contained = reagents.get_reagents() + var/trans = reagents.trans_to_mob(L, transfer_amount, CHEM_BLOOD) + + if(trans) + message_admins("[assembly] injected \the [L] with [trans]u of [contained].") + to_chat(L, SPAN_NOTICE("You feel a tiny prick!")) + visible_message(SPAN_WARNING("[assembly] injects [L]!")) + activate_pin(2) + else + activate_pin(3) + /obj/item/integrated_circuit/reagent/pump name = "reagent pump" - desc = "Moves liquids safely inside a machine, or even nearby it." + desc = "Moves reagents inside the assembly or to nearby targets." icon_state = "reagent_pump" extended_desc = "This is a pump, which will move liquids from the source ref to the target ref. The third pin determines \ how much liquid is moved per pulse, between 0 and 50. The pump can move reagents to any open container inside the machine, or \ @@ -195,9 +252,9 @@ /obj/item/integrated_circuit/reagent/storage name = "reagent storage" - desc = "Stores liquid inside, and away from electrical components. Can store up to 60u." + desc = "Stores reagents safely inside the assembly. Can store up to 60u." icon_state = "reagent_storage" - extended_desc = "This is effectively an internal beaker." + extended_desc = "Functions as an internal beaker for the assembly." atom_flags = ATOM_FLAG_OPEN_CONTAINER complexity = 4 inputs = list() @@ -218,9 +275,9 @@ /obj/item/integrated_circuit/reagent/storage/cryo name = "cryo reagent storage" - desc = "Stores liquid inside, and away from electrical components. Can store up to 60u. This will also suppress reactions." + desc = "Stores reagents safely inside the assembly. Can store up to 60u. This will also suppress reactions." icon_state = "reagent_storage_cryo" - extended_desc = "This is effectively an internal cryo beaker." + extended_desc = "Functions as an internal cryo beaker for the assembly." atom_flags = ATOM_FLAG_OPEN_CONTAINER | ATOM_FLAG_NO_REACT complexity = 8 spawn_flags = IC_SPAWN_RESEARCH @@ -228,9 +285,9 @@ /obj/item/integrated_circuit/reagent/storage/big name = "big reagent storage" - desc = "Stores liquid inside, and away from electrical components. Can store up to 180u." + desc = "Stores reagents safely inside the assembly. Can store up to 180u." icon_state = "reagent_storage_big" - extended_desc = "This is effectively an internal beaker." + extended_desc = "Functions as an internal beaker for the assembly." atom_flags = ATOM_FLAG_OPEN_CONTAINER complexity = 16 volume = 180 @@ -239,9 +296,9 @@ /obj/item/integrated_circuit/reagent/storage/scan name = "reagent scanner" - desc = "Stores liquid inside, and away from electrical components. Can store up to 60u. On pulse this beaker will send list of contained reagents, as well as analyse their taste." + desc = "Stores reagents safely inside the assembly. Can store up to 60u. On pulse, outputs a list of contained reagents and analyzes their taste." icon_state = "reagent_scan" - extended_desc = "Mostly useful for reagent filters." + extended_desc = "Useful for reagent filters and other chemistry circuits that need a target reagent name." atom_flags = ATOM_FLAG_OPEN_CONTAINER complexity = 8 outputs = list("volume used" = IC_PINTYPE_NUMBER,"self reference" = IC_PINTYPE_REF,"list of reagents" = IC_PINTYPE_LIST,"taste" = IC_PINTYPE_STRING) @@ -261,7 +318,7 @@ /obj/item/integrated_circuit/reagent/filter name = "reagent filter" - desc = "Filtering liquids by list of desired or unwanted reagents." + desc = "Filters reagents using a configured list of desired or blocked reagent names." icon_state = "reagent_filter" extended_desc = "This is a filter which will move liquids from the source ref to the target ref. \ It will move all reagents, except list, given in fourth pin if amount value is positive.\ diff --git a/code/modules/integrated_electronics/subtypes/server.dm b/code/modules/integrated_electronics/subtypes/server.dm new file mode 100644 index 00000000000..2ab488533ef --- /dev/null +++ b/code/modules/integrated_electronics/subtypes/server.dm @@ -0,0 +1,878 @@ +GLOBAL_LIST_EMPTY(ic_database_servers) + +/proc/ic_normalize_database_id(database_id) + if(!istext(database_id) || !length(database_id)) + return "main" + + return lowertext(database_id) + + +/proc/ic_database_response(success = FALSE, response = null, status = "No response.") + return list( + "success" = success, + "response" = response, + "status" = status + ) + + +/obj/item/integrated_circuit/server + category_text = "Data Server" + complexity = 2 + power_draw_per_use = 300 + spawn_flags = 0 + activators = list( + "compute" = IC_PINTYPE_PULSE_IN, + "on computed" = IC_PINTYPE_PULSE_OUT + ) + + +/obj/item/integrated_circuit/server/proc/build_record_from_pins(field_count) + var/list/record = list() + + for(var/i = 1 to field_count) + var/key = get_pin_data(IC_INPUT, (i * 2) - 1) + if(istext(key) && length(key)) + record[key] = get_pin_data(IC_INPUT, i * 2) + + return record + + +/obj/item/integrated_circuit/server/database + name = "database server" + desc = "A keyed database server for integrated circuits." + extended_desc = "Stores keyed values for server client circuits. Commands: GET, SET, DELETE, CLEAR, HAS, KEYS, LEN, APPEND." + icon_state = "addition" + complexity = 8 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "server id" = IC_PINTYPE_STRING, + "command" = IC_PINTYPE_STRING, + "key" = IC_PINTYPE_STRING, + "value" = IC_PINTYPE_ANY + ) + + inputs_default = list( + "1" = "main", + "2" = "GET" + ) + + outputs = list( + "success" = IC_PINTYPE_BOOLEAN, + "response" = IC_PINTYPE_ANY, + "status" = IC_PINTYPE_STRING + ) + + var/database_id = "main" + var/list/storage = list() + + +/obj/item/integrated_circuit/server/database/Initialize() + . = ..() + register_database() + + +/obj/item/integrated_circuit/server/database/Destroy() + unregister_database() + storage = null + return ..() + + +/obj/item/integrated_circuit/server/database/proc/register_database() + database_id = ic_normalize_database_id(database_id) + GLOB.ic_database_servers[database_id] = src + + +/obj/item/integrated_circuit/server/database/proc/unregister_database() + if(database_id && GLOB.ic_database_servers[database_id] == src) + GLOB.ic_database_servers -= database_id + + +/obj/item/integrated_circuit/server/database/proc/set_database_id(new_id) + new_id = ic_normalize_database_id(new_id) + + if(new_id == database_id) + return + + unregister_database() + database_id = new_id + register_database() + + +/obj/item/integrated_circuit/server/database/proc/handle_request(command, key, value) + if(!istext(command)) + return ic_database_response(FALSE, null, "Command must be text.") + + command = uppertext(command) + + switch(command) + if("GET") + if(!istext(key) || !length(key)) + return ic_database_response(FALSE, null, "GET requires a text key.") + + return ic_database_response(TRUE, storage[key], "Read complete.") + + if("SET") + if(!istext(key) || !length(key)) + return ic_database_response(FALSE, null, "SET requires a text key.") + + storage[key] = value + return ic_database_response(TRUE, value, "Write complete.") + + if("DELETE") + if(!istext(key) || !length(key)) + return ic_database_response(FALSE, null, "DELETE requires a text key.") + + var/existed = (key in storage) + storage -= key + + if(existed) + return ic_database_response(TRUE, TRUE, "Deleted.") + + return ic_database_response(FALSE, FALSE, "Key not found.") + + if("CLEAR") + storage.Cut() + return ic_database_response(TRUE, TRUE, "Database cleared.") + + if("HAS") + if(!istext(key) || !length(key)) + return ic_database_response(FALSE, null, "HAS requires a text key.") + + return ic_database_response(TRUE, (key in storage), "Check complete.") + + if("KEYS") + var/list/keys = list() + + for(var/K in storage) + keys.Add(K) + + return ic_database_response(TRUE, keys, "Keys returned.") + + if("LEN") + return ic_database_response(TRUE, storage.len, "Length returned.") + + if("APPEND") + if(!istext(key) || !length(key)) + return ic_database_response(FALSE, null, "APPEND requires a text key.") + + var/list/current = storage[key] + + if(!islist(current)) + current = list() + + current.Add(value) + storage[key] = current + + return ic_database_response(TRUE, current, "Append complete.") + + return ic_database_response(FALSE, null, "Unknown command.") + + +/obj/item/integrated_circuit/server/database/do_work() + pull_data() + + var/new_id = get_pin_data(IC_INPUT, 1) + var/command = get_pin_data(IC_INPUT, 2) + var/key = get_pin_data(IC_INPUT, 3) + var/value = get_pin_data(IC_INPUT, 4) + + set_database_id(new_id) + + var/list/result = handle_request(command, key, value) + + set_pin_data(IC_OUTPUT, 1, result["success"]) + set_pin_data(IC_OUTPUT, 2, result["response"]) + set_pin_data(IC_OUTPUT, 3, result["status"]) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/client + name = "database client" + desc = "Sends requests to a database server." + extended_desc = "Targets a database server by server id. Commands: GET, SET, DELETE, CLEAR, HAS, KEYS, LEN, APPEND." + icon_state = "addition" + complexity = 4 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "server id" = IC_PINTYPE_STRING, + "command" = IC_PINTYPE_STRING, + "key" = IC_PINTYPE_STRING, + "value" = IC_PINTYPE_ANY + ) + + inputs_default = list( + "1" = "main", + "2" = "GET" + ) + + outputs = list( + "success" = IC_PINTYPE_BOOLEAN, + "response" = IC_PINTYPE_ANY, + "status" = IC_PINTYPE_STRING + ) + + +/obj/item/integrated_circuit/server/client/do_work() + pull_data() + + var/database_id = ic_normalize_database_id(get_pin_data(IC_INPUT, 1)) + var/command = get_pin_data(IC_INPUT, 2) + var/key = get_pin_data(IC_INPUT, 3) + var/value = get_pin_data(IC_INPUT, 4) + + var/list/result = ic_database_response(FALSE, null, "Server not found.") + + var/obj/item/integrated_circuit/server/database/S = GLOB.ic_database_servers[database_id] + + if(istype(S)) + result = S.handle_request(command, key, value) + + set_pin_data(IC_OUTPUT, 1, result["success"]) + set_pin_data(IC_OUTPUT, 2, result["response"]) + set_pin_data(IC_OUTPUT, 3, result["status"]) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/scanner + name = "database scanner" + desc = "Outputs a list of active database server ids." + extended_desc = "Useful for debugging database wiring or discovering which database servers are available." + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list() + + outputs = list( + "server ids" = IC_PINTYPE_LIST, + "server count" = IC_PINTYPE_NUMBER + ) + + +/obj/item/integrated_circuit/server/scanner/do_work() + var/list/server_ids = list() + + for(var/database_id in GLOB.ic_database_servers) + var/obj/item/integrated_circuit/server/database/S = GLOB.ic_database_servers[database_id] + + if(istype(S)) + server_ids.Add(database_id) + + set_pin_data(IC_OUTPUT, 1, server_ids) + set_pin_data(IC_OUTPUT, 2, server_ids.len) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/record_builder4 + name = "4-field record builder" + desc = "Builds an associative list record from up to four key/value pairs." + extended_desc = "This is useful for making database records like pressure, oxygen, temperature, and status." + icon_state = "addition" + complexity = 3 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "key 1" = IC_PINTYPE_STRING, + "value 1" = IC_PINTYPE_ANY, + "key 2" = IC_PINTYPE_STRING, + "value 2" = IC_PINTYPE_ANY, + "key 3" = IC_PINTYPE_STRING, + "value 3" = IC_PINTYPE_ANY, + "key 4" = IC_PINTYPE_STRING, + "value 4" = IC_PINTYPE_ANY + ) + + outputs = list( + "record" = IC_PINTYPE_LIST + ) + + +/obj/item/integrated_circuit/server/record_builder4/do_work() + pull_data() + + set_pin_data(IC_OUTPUT, 1, build_record_from_pins(4)) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/record_builder8 + name = "8-field record builder" + desc = "Builds an associative list record from up to eight key/value pairs." + extended_desc = "This is useful for larger database records." + icon_state = "addition" + complexity = 5 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "key 1" = IC_PINTYPE_STRING, + "value 1" = IC_PINTYPE_ANY, + "key 2" = IC_PINTYPE_STRING, + "value 2" = IC_PINTYPE_ANY, + "key 3" = IC_PINTYPE_STRING, + "value 3" = IC_PINTYPE_ANY, + "key 4" = IC_PINTYPE_STRING, + "value 4" = IC_PINTYPE_ANY, + "key 5" = IC_PINTYPE_STRING, + "value 5" = IC_PINTYPE_ANY, + "key 6" = IC_PINTYPE_STRING, + "value 6" = IC_PINTYPE_ANY, + "key 7" = IC_PINTYPE_STRING, + "value 7" = IC_PINTYPE_ANY, + "key 8" = IC_PINTYPE_STRING, + "value 8" = IC_PINTYPE_ANY + ) + + outputs = list( + "record" = IC_PINTYPE_LIST + ) + + +/obj/item/integrated_circuit/server/record_builder8/do_work() + pull_data() + + set_pin_data(IC_OUTPUT, 1, build_record_from_pins(8)) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/record_reader + name = "record reader" + desc = "Reads one field from an associative list record." + extended_desc = "Use this after a database GET returns a record list." + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "record" = IC_PINTYPE_LIST, + "field" = IC_PINTYPE_STRING + ) + + outputs = list( + "value" = IC_PINTYPE_ANY, + "found" = IC_PINTYPE_BOOLEAN + ) + + +/obj/item/integrated_circuit/server/record_reader/do_work() + pull_data() + + var/list/record = get_pin_data(IC_INPUT, 1) + var/field = get_pin_data(IC_INPUT, 2) + + var/value = null + var/found = FALSE + + if(islist(record) && istext(field) && length(field)) + value = record[field] + found = !isnull(value) || (field in record) + + set_pin_data(IC_OUTPUT, 1, value) + set_pin_data(IC_OUTPUT, 2, found) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/record_writer + name = "record writer" + desc = "Writes one field into an associative list record." + extended_desc = "Takes an existing record, writes a field, and outputs the edited record." + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "record" = IC_PINTYPE_LIST, + "field" = IC_PINTYPE_STRING, + "value" = IC_PINTYPE_ANY + ) + + outputs = list( + "record" = IC_PINTYPE_LIST, + "success" = IC_PINTYPE_BOOLEAN + ) + + +/obj/item/integrated_circuit/server/record_writer/do_work() + pull_data() + + var/list/input_record = get_pin_data(IC_INPUT, 1) + var/field = get_pin_data(IC_INPUT, 2) + var/value = get_pin_data(IC_INPUT, 3) + + var/list/output_record = list() + var/success = FALSE + + if(islist(input_record)) + output_record = input_record.Copy() + + if(istext(field) && length(field)) + output_record[field] = value + success = TRUE + + set_pin_data(IC_OUTPUT, 1, output_record) + set_pin_data(IC_OUTPUT, 2, success) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/record_keys + name = "record keys" + desc = "Outputs a list of keys from an associative list record." + extended_desc = "Useful for reading database records and checking their field names." + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "record" = IC_PINTYPE_LIST + ) + + outputs = list( + "keys" = IC_PINTYPE_LIST, + "length" = IC_PINTYPE_NUMBER + ) + + +/obj/item/integrated_circuit/server/record_keys/do_work() + pull_data() + + var/list/record = get_pin_data(IC_INPUT, 1) + var/list/keys = list() + + if(islist(record)) + for(var/K in record) + keys.Add(K) + + set_pin_data(IC_OUTPUT, 1, keys) + set_pin_data(IC_OUTPUT, 2, keys.len) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/text_contains + name = "text contains" + desc = "Checks if one text value contains another." + extended_desc = "Useful for filtering logs, messages, status strings, or command text." + icon_state = "textpad" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "text" = IC_PINTYPE_STRING, + "search" = IC_PINTYPE_STRING, + "case sensitive" = IC_PINTYPE_BOOLEAN + ) + + inputs_default = list( + "3" = FALSE + ) + + outputs = list( + "contains" = IC_PINTYPE_BOOLEAN, + "position" = IC_PINTYPE_NUMBER + ) + + +/obj/item/integrated_circuit/server/text_contains/do_work() + pull_data() + + var/text = get_pin_data(IC_INPUT, 1) + var/search = get_pin_data(IC_INPUT, 2) + var/case_sensitive = get_pin_data(IC_INPUT, 3) + + var/position = 0 + + if(istext(text) && istext(search) && length(search)) + if(case_sensitive) + position = findtext(text, search) + else + position = findtext(lowertext(text), lowertext(search)) + + set_pin_data(IC_OUTPUT, 1, position > 0) + set_pin_data(IC_OUTPUT, 2, position) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/queue_push + name = "queue push" + desc = "Adds a value to the end of a queue list." + extended_desc = "Use this with database APPEND or with a retrieved list." + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "queue" = IC_PINTYPE_LIST, + "value" = IC_PINTYPE_ANY + ) + + outputs = list( + "queue" = IC_PINTYPE_LIST, + "length" = IC_PINTYPE_NUMBER + ) + + +/obj/item/integrated_circuit/server/queue_push/do_work() + pull_data() + + var/list/input_queue = get_pin_data(IC_INPUT, 1) + var/value = get_pin_data(IC_INPUT, 2) + + var/list/output_queue = list() + + if(islist(input_queue)) + output_queue = input_queue.Copy() + + output_queue.Add(value) + + set_pin_data(IC_OUTPUT, 1, output_queue) + set_pin_data(IC_OUTPUT, 2, output_queue.len) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/queue_pop + name = "queue pop" + desc = "Removes and outputs the first value from a queue list." + extended_desc = "Outputs the first list item separately, then outputs the remaining list as a new queue." + icon_state = "subtraction" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "queue" = IC_PINTYPE_LIST + ) + + outputs = list( + "value" = IC_PINTYPE_ANY, + "remaining queue" = IC_PINTYPE_LIST, + "had value" = IC_PINTYPE_BOOLEAN + ) + + +/obj/item/integrated_circuit/server/queue_pop/do_work() + pull_data() + + var/list/input_queue = get_pin_data(IC_INPUT, 1) + var/list/output_queue = list() + var/value = null + var/had_value = FALSE + + if(islist(input_queue)) + output_queue = input_queue.Copy() + + if(output_queue.len) + value = output_queue[1] + output_queue.Cut(1, 2) + had_value = TRUE + + set_pin_data(IC_OUTPUT, 1, value) + set_pin_data(IC_OUTPUT, 2, output_queue) + set_pin_data(IC_OUTPUT, 3, had_value) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/list_first_last + name = "list first/last" + desc = "Outputs the first and last values from a list." + extended_desc = "Useful for logs, queues, and message buffers." + icon_state = "addition" + complexity = 2 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "list" = IC_PINTYPE_LIST + ) + + outputs = list( + "first" = IC_PINTYPE_ANY, + "last" = IC_PINTYPE_ANY, + "length" = IC_PINTYPE_NUMBER, + "has values" = IC_PINTYPE_BOOLEAN + ) + + +/obj/item/integrated_circuit/server/list_first_last/do_work() + pull_data() + + var/list/input_list = get_pin_data(IC_INPUT, 1) + + var/first = null + var/last = null + var/length = 0 + var/has_values = FALSE + + if(islist(input_list)) + length = input_list.len + + if(length) + first = input_list[1] + last = input_list[length] + has_values = TRUE + + set_pin_data(IC_OUTPUT, 1, first) + set_pin_data(IC_OUTPUT, 2, last) + set_pin_data(IC_OUTPUT, 3, length) + set_pin_data(IC_OUTPUT, 4, has_values) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/database_counter + name = "database counter client" + desc = "Reads a numeric database value, changes it, and writes it back." + extended_desc = "Useful for scores, cycle counts, alarm counts, and quotas." + icon_state = "addition" + complexity = 5 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "server id" = IC_PINTYPE_STRING, + "key" = IC_PINTYPE_STRING, + "amount" = IC_PINTYPE_NUMBER + ) + + inputs_default = list( + "1" = "main", + "3" = 1 + ) + + outputs = list( + "success" = IC_PINTYPE_BOOLEAN, + "new value" = IC_PINTYPE_NUMBER, + "status" = IC_PINTYPE_STRING + ) + + +/obj/item/integrated_circuit/server/database_counter/do_work() + pull_data() + + var/database_id = ic_normalize_database_id(get_pin_data(IC_INPUT, 1)) + var/key = get_pin_data(IC_INPUT, 2) + var/amount = get_pin_data(IC_INPUT, 3) + + var/success = FALSE + var/new_value = 0 + var/status = "Server not found." + + if(isnull(amount)) + amount = 1 + + if(!istext(key) || !length(key)) + status = "Counter requires a text key." + else + var/obj/item/integrated_circuit/server/database/S = GLOB.ic_database_servers[database_id] + + if(istype(S)) + var/current = S.storage[key] + + if(isnum(current)) + new_value = current + else + new_value = 0 + + new_value += amount + S.storage[key] = new_value + + success = TRUE + status = "Counter updated." + + set_pin_data(IC_OUTPUT, 1, success) + set_pin_data(IC_OUTPUT, 2, new_value) + set_pin_data(IC_OUTPUT, 3, status) + + push_data() + activate_pin(2) + + +/obj/item/integrated_circuit/server/database_compare_write + name = "database compare-write client" + desc = "Writes a database value only if the current value matches the expected value." + extended_desc = "Useful for locks, queues, safe counters, and avoiding accidental overwrites." + icon_state = "addition" + complexity = 6 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "server id" = IC_PINTYPE_STRING, + "key" = IC_PINTYPE_STRING, + "expected value" = IC_PINTYPE_ANY, + "new value" = IC_PINTYPE_ANY + ) + + inputs_default = list( + "1" = "main" + ) + + outputs = list( + "success" = IC_PINTYPE_BOOLEAN, + "current value" = IC_PINTYPE_ANY, + "status" = IC_PINTYPE_STRING + ) + + +/obj/item/integrated_circuit/server/database_compare_write/do_work() + pull_data() + + var/database_id = ic_normalize_database_id(get_pin_data(IC_INPUT, 1)) + var/key = get_pin_data(IC_INPUT, 2) + var/expected_value = get_pin_data(IC_INPUT, 3) + var/new_value = get_pin_data(IC_INPUT, 4) + + var/success = FALSE + var/current_value = null + var/status = "Server not found." + + if(!istext(key) || !length(key)) + status = "Compare-write requires a text key." + else + var/obj/item/integrated_circuit/server/database/S = GLOB.ic_database_servers[database_id] + + if(istype(S)) + current_value = S.storage[key] + + if(current_value == expected_value) + S.storage[key] = new_value + current_value = new_value + success = TRUE + status = "Compare-write complete." + else + status = "Current value did not match expected value." + + set_pin_data(IC_OUTPUT, 1, success) + set_pin_data(IC_OUTPUT, 2, current_value) + set_pin_data(IC_OUTPUT, 3, status) + + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/server/command_pad + name = "database command pad" + desc = "Parses typed text commands and sends them to a database server." + extended_desc = "Commands: READ key, WRITE key value, DELETE key, HAS key, KEYS, LEN, CLEAR, APPEND key value." + icon_state = "addition" + complexity = 6 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "server id" = IC_PINTYPE_STRING, + "command text" = IC_PINTYPE_STRING + ) + + inputs_default = list( + "1" = "research" + ) + + outputs = list( + "success" = IC_PINTYPE_BOOLEAN, + "response" = IC_PINTYPE_ANY, + "status" = IC_PINTYPE_STRING, + "parsed command" = IC_PINTYPE_STRING, + "parsed key" = IC_PINTYPE_STRING, + "parsed value" = IC_PINTYPE_STRING + ) + + activators = list( + "submit" = IC_PINTYPE_PULSE_IN, + "on accepted" = IC_PINTYPE_PULSE_OUT, + "on rejected" = IC_PINTYPE_PULSE_OUT + ) + + +/obj/item/integrated_circuit/server/command_pad/do_work() + pull_data() + + var/database_id = ic_normalize_database_id(get_pin_data(IC_INPUT, 1)) + var/raw_text = get_pin_data(IC_INPUT, 2) + + var/success = FALSE + var/response = null + var/status = "No command." + var/parsed_command = null + var/parsed_key = null + var/parsed_value = null + + if(!istext(raw_text) || !length(raw_text)) + set_pin_data(IC_OUTPUT, 1, FALSE) + set_pin_data(IC_OUTPUT, 2, null) + set_pin_data(IC_OUTPUT, 3, "Command pad requires text.") + set_pin_data(IC_OUTPUT, 4, null) + set_pin_data(IC_OUTPUT, 5, null) + set_pin_data(IC_OUTPUT, 6, null) + push_data() + activate_pin(3) + return + + var/text = trim(raw_text) + var/first_space = findtext(text, " ") + + if(first_space) + parsed_command = uppertext(trim(copytext(text, 1, first_space))) + var/remainder = trim(copytext(text, first_space + 1)) + + var/second_space = findtext(remainder, " ") + if(second_space) + parsed_key = trim(copytext(remainder, 1, second_space)) + parsed_value = trim(copytext(remainder, second_space + 1)) + else + parsed_key = remainder + else + parsed_command = uppertext(text) + + switch(parsed_command) + if("READ") + parsed_command = "GET" + + if("WRITE") + parsed_command = "SET" + + if("REMOVE") + parsed_command = "DELETE" + + if("ADD") + parsed_command = "APPEND" + + var/obj/item/integrated_circuit/server/database/S = GLOB.ic_database_servers[database_id] + + if(!istype(S)) + status = "Server not found." + else + var/list/result = S.handle_request(parsed_command, parsed_key, parsed_value) + + success = result["success"] + response = result["response"] + status = result["status"] + + set_pin_data(IC_OUTPUT, 1, success) + set_pin_data(IC_OUTPUT, 2, response) + set_pin_data(IC_OUTPUT, 3, status) + set_pin_data(IC_OUTPUT, 4, parsed_command) + set_pin_data(IC_OUTPUT, 5, parsed_key) + set_pin_data(IC_OUTPUT, 6, parsed_value) + + push_data() + + if(success) + activate_pin(2) + else + activate_pin(3) diff --git a/code/modules/integrated_electronics/subtypes/smart.dm b/code/modules/integrated_electronics/subtypes/smart.dm index 7b79cca0f16..ce422da401b 100644 --- a/code/modules/integrated_electronics/subtypes/smart.dm +++ b/code/modules/integrated_electronics/subtypes/smart.dm @@ -1,54 +1,86 @@ +/* + * subtypes/smart.dm + * Smart utility circuits such as pathfinding and higher-level automation helpers. + */ + /obj/item/integrated_circuit/smart category_text = "Smart" + spawn_flags = 0 /obj/item/integrated_circuit/smart/basic_pathfinder name = "basic pathfinder" - desc = "This complex circuit is able to determine what direction a given target is." - extended_desc = "This circuit uses a miniturized integrated camera to determine where the target is. If the machine \ + desc = "Outputs the direction of the selected target." + extended_desc = "This circuit uses a miniaturized integrated camera to determine where the target is. If the machine \ cannot see the target, it will not be able to calculate the correct direction." icon_state = "numberpad" complexity = 5 - inputs = list("target" = IC_PINTYPE_REF,"ignore obstacles" = IC_PINTYPE_BOOLEAN) - outputs = list("dir" = IC_PINTYPE_DIR) - activators = list("calculate dir" = IC_PINTYPE_PULSE_IN, "on calculated" = IC_PINTYPE_PULSE_OUT,"not calculated" = IC_PINTYPE_PULSE_OUT) + inputs = list( + "target" = IC_PINTYPE_REF, + "ignore obstacles" = IC_PINTYPE_BOOLEAN + ) + outputs = list( + "dir" = IC_PINTYPE_DIR + ) + activators = list( + "calculate dir" = IC_PINTYPE_PULSE_IN, + "on calculated" = IC_PINTYPE_PULSE_OUT, + "not calculated" = IC_PINTYPE_PULSE_OUT + ) spawn_flags = IC_SPAWN_RESEARCH power_draw_per_use = 400 /obj/item/integrated_circuit/smart/basic_pathfinder/do_work() var/datum/integrated_io/I = inputs[1] set_pin_data(IC_OUTPUT, 1, null) + if(!isweakref(I.data)) activate_pin(3) return - var/atom/A = I.data.resolve() + + var/datum/weakref/W = I.data + var/atom/A = W.resolve() + if(!A) activate_pin(3) return - if(!(A in view(get_turf(src)))) + + var/turf/start = get_turf(src) + var/turf/target_turf = get_turf(A) + + if(!start || !target_turf || !(A in view(start))) push_data() activate_pin(3) - return // Can't see the target. + return if(get_pin_data(IC_INPUT, 2)) - set_pin_data(IC_OUTPUT, 1, get_dir(get_turf(src), get_turf(A))) + set_pin_data(IC_OUTPUT, 1, get_dir(start, target_turf)) else - set_pin_data(IC_OUTPUT, 1, get_dir(get_turf(src), get_step_towards2(get_turf(src),A))) + set_pin_data(IC_OUTPUT, 1, get_dir(start, get_step_towards2(start, A))) + push_data() activate_pin(2) /obj/item/integrated_circuit/smart/coord_basic_pathfinder name = "coordinate pathfinder" - desc = "This complex circuit is able to determine what direction a given target is." - extended_desc = "This circuit uses absolute coordinates to determine where the target is. If the machine \ - cannot see the target, it will not be able to calculate the correct direction. \ + desc = "Outputs the direction of the selected target." + extended_desc = "This circuit uses absolute coordinates to determine where the target is. \ This circuit will only work while inside an assembly." icon_state = "numberpad" complexity = 5 - inputs = list("X" = IC_PINTYPE_NUMBER,"Y" = IC_PINTYPE_NUMBER,"ignore obstacles" = IC_PINTYPE_BOOLEAN) - outputs = list( "dir" = IC_PINTYPE_DIR, - "distance" = IC_PINTYPE_NUMBER + inputs = list( + "X" = IC_PINTYPE_NUMBER, + "Y" = IC_PINTYPE_NUMBER, + "ignore obstacles" = IC_PINTYPE_BOOLEAN + ) + outputs = list( + "dir" = IC_PINTYPE_DIR, + "distance" = IC_PINTYPE_NUMBER + ) + activators = list( + "calculate dir" = IC_PINTYPE_PULSE_IN, + "on calculated" = IC_PINTYPE_PULSE_OUT, + "not calculated" = IC_PINTYPE_PULSE_OUT ) - activators = list("calculate dir" = IC_PINTYPE_PULSE_IN, "on calculated" = IC_PINTYPE_PULSE_OUT,"not calculated" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_RESEARCH power_draw_per_use = 400 @@ -56,18 +88,547 @@ if(!assembly) activate_pin(3) return + var/turf/T = get_turf(assembly) - var/target_x = clamp(get_pin_data(IC_INPUT, 1), 0, world.maxx) - var/target_y = clamp(get_pin_data(IC_INPUT, 2), 0, world.maxy) - var/turf/A = locate(target_x, target_y, T.z) - set_pin_data(IC_OUTPUT, 1, null) - if(!A||A==T) + if(!T) activate_pin(3) return - if(get_pin_data(IC_INPUT, 2)) - set_pin_data(IC_OUTPUT, 1, get_dir(get_turf(src), get_turf(A))) + + var/target_x = round(clamp(get_pin_data(IC_INPUT, 1), 1, world.maxx)) + var/target_y = round(clamp(get_pin_data(IC_INPUT, 2), 1, world.maxy)) + var/turf/A = locate(target_x, target_y, T.z) + + set_pin_data(IC_OUTPUT, 1, null) + + if(!A || A == T) + push_data() + activate_pin(3) + return + + if(get_pin_data(IC_INPUT, 3)) + set_pin_data(IC_OUTPUT, 1, get_dir(T, A)) else - set_pin_data(IC_OUTPUT, 1, get_dir(get_turf(src), get_step_towards2(get_turf(src),A))) - set_pin_data(IC_OUTPUT, 2, sqrt((A.x-T.x)*(A.x-T.x)+ (A.y-T.y)*(A.y-T.y))) + set_pin_data(IC_OUTPUT, 1, get_dir(T, get_step_towards2(T, A))) + + set_pin_data(IC_OUTPUT, 2, sqrt((A.x - T.x) * (A.x - T.x) + (A.y - T.y) * (A.y - T.y))) push_data() activate_pin(2) + +/obj/item/integrated_circuit/smart/relative_position + name = "relative position calculator" + desc = "Calculates relative coordinates between two supplied references." + extended_desc = "This does not scan, pathfind, or move anything. It only compares refs already supplied to the circuit and fails safely if either ref is invalid." + icon_state = "numberpad" + complexity = 5 + inputs = list( + "origin ref" = IC_PINTYPE_REF, + "target ref" = IC_PINTYPE_REF + ) + outputs = list( + "relative X" = IC_PINTYPE_NUMBER, + "relative Y" = IC_PINTYPE_NUMBER, + "relative Z" = IC_PINTYPE_NUMBER, + "distance" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN + ) + activators = list( + "calculate" = IC_PINTYPE_PULSE_IN, + "on valid" = IC_PINTYPE_PULSE_OUT, + "on invalid" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_RESEARCH + power_draw_per_use = 200 + +/obj/item/integrated_circuit/smart/relative_position/do_work() + var/origin = get_pin_data(IC_INPUT, 1) + var/target = get_pin_data(IC_INPUT, 2) + + if(!istype(origin, /atom) || !istype(target, /atom)) + set_relative_position_result(null, null, null, null, FALSE) + return + + var/turf/origin_turf = get_turf(origin) + var/turf/target_turf = get_turf(target) + + if(!origin_turf || !target_turf) + set_relative_position_result(null, null, null, null, FALSE) + return + + var/dx = target_turf.x - origin_turf.x + var/dy = target_turf.y - origin_turf.y + var/dz = target_turf.z - origin_turf.z + var/distance = sqrt(dx * dx + dy * dy + dz * dz) + + set_relative_position_result(dx, dy, dz, distance, TRUE) + +/obj/item/integrated_circuit/smart/relative_position/proc/set_relative_position_result(dx, dy, dz, distance, valid) + set_pin_data(IC_OUTPUT, 1, dx) + set_pin_data(IC_OUTPUT, 2, dy) + set_pin_data(IC_OUTPUT, 3, dz) + set_pin_data(IC_OUTPUT, 4, distance) + set_pin_data(IC_OUTPUT, 5, valid) + push_data() + activate_pin(valid ? 2 : 3) + +/obj/item/integrated_circuit/smart/coordinate_navigator + name = "coordinate navigator" + desc = "This complex circuit calculates a walkable path toward absolute coordinates." + extended_desc = "This circuit finds a walkable cardinal path toward absolute coordinates. It can route around walls, long barriers, and U-shaped obstructions within its search limit. It is intended for drones using a locomotion circuit." + icon_state = "numberpad" + complexity = 20 + w_class = WEIGHT_CLASS_TINY + size = 3 + power_draw_per_use = 800 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + var/list/current_path = list() + var/path_index = 1 + var/last_target_x = null + var/last_target_y = null + var/max_search_nodes = 600 + + inputs = list( + "target X" = IC_PINTYPE_NUMBER, + "target Y" = IC_PINTYPE_NUMBER, + "recalculate if blocked" = IC_PINTYPE_BOOLEAN, + "max search nodes" = IC_PINTYPE_NUMBER + ) + + outputs = list( + "direction" = IC_PINTYPE_DIR, + "distance remaining" = IC_PINTYPE_NUMBER, + "path length" = IC_PINTYPE_NUMBER, + "status" = IC_PINTYPE_STRING + ) + + activators = list( + "calculate path" = IC_PINTYPE_PULSE_IN, + "step path" = IC_PINTYPE_PULSE_IN, + "clear path" = IC_PINTYPE_PULSE_IN, + "on step ready" = IC_PINTYPE_PULSE_OUT, + "on arrived" = IC_PINTYPE_PULSE_OUT, + "on failed" = IC_PINTYPE_PULSE_OUT + ) + +/obj/item/integrated_circuit/smart/coordinate_navigator/do_work(activator_id) + if(!assembly) + set_navigation_failure("Circuit is not installed in an assembly.") + return + + var/active_pin = activator_id + + if(istype(active_pin, /datum/integrated_io)) + active_pin = activators.Find(active_pin) + + if(istext(active_pin)) + for(var/i = 1 to activators.len) + var/datum/integrated_io/A = activators[i] + if(A.name == active_pin) + active_pin = i + break + + if(!isnum(active_pin)) + active_pin = 1 + + switch(active_pin) + if(1) + calculate_path() + return + if(2) + step_path() + return + if(3) + clear_path() + return + + set_navigation_failure("Unknown navigation activator: [activator_id].") + +/obj/item/integrated_circuit/smart/coordinate_navigator/proc/clear_path() + current_path = list() + path_index = 1 + last_target_x = null + last_target_y = null + + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, 0) + set_pin_data(IC_OUTPUT, 3, 0) + set_pin_data(IC_OUTPUT, 4, "Path cleared.") + push_data() + +/obj/item/integrated_circuit/smart/coordinate_navigator/proc/calculate_path() + var/turf/start = get_turf(assembly) + if(!start) + set_navigation_failure("No assembly turf.") + return + + var/target_x = round(get_pin_data(IC_INPUT, 1)) + var/target_y = round(get_pin_data(IC_INPUT, 2)) + + if(!isnum(target_x) || !isnum(target_y)) + set_navigation_failure("Invalid target coordinates.") + return + + target_x = clamp(target_x, 1, world.maxx) + target_y = clamp(target_y, 1, world.maxy) + + var/turf/goal = locate(target_x, target_y, start.z) + if(!goal) + set_navigation_failure("Target turf does not exist.") + return + + if(start == goal) + current_path = list(start) + path_index = 1 + last_target_x = target_x + last_target_y = target_y + + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, 0) + set_pin_data(IC_OUTPUT, 3, 1) + set_pin_data(IC_OUTPUT, 4, "Already at target.") + push_data() + activate_pin(5) + return + + var/node_limit = round(get_pin_data(IC_INPUT, 4)) + if(!isnum(node_limit) || node_limit <= 0) + node_limit = max_search_nodes + + var/list/new_path = find_path_to_turf(start, goal, node_limit) + + if(!new_path || !new_path.len) + set_navigation_failure("No path found within [node_limit] nodes.") + return + + current_path = new_path + path_index = 2 + last_target_x = target_x + last_target_y = target_y + + output_next_step("Path calculated.") + +/obj/item/integrated_circuit/smart/coordinate_navigator/proc/step_path() + var/turf/start = get_turf(assembly) + if(!start) + set_navigation_failure("No assembly turf.") + return + + var/target_x = round(get_pin_data(IC_INPUT, 1)) + var/target_y = round(get_pin_data(IC_INPUT, 2)) + + if(!isnum(target_x) || !isnum(target_y)) + set_navigation_failure("Invalid target coordinates.") + return + + target_x = clamp(target_x, 1, world.maxx) + target_y = clamp(target_y, 1, world.maxy) + + if(!current_path || !current_path.len) + calculate_path() + return + + if(target_x != last_target_x || target_y != last_target_y) + calculate_path() + return + + advance_path_index(start) + + if(path_index > current_path.len) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, 0) + set_pin_data(IC_OUTPUT, 3, current_path.len) + set_pin_data(IC_OUTPUT, 4, "Arrived.") + push_data() + activate_pin(5) + return + + var/turf/next_turf = current_path[path_index] + if(!next_turf || turf_is_blocked_for_navigation(next_turf)) + if(get_pin_data(IC_INPUT, 3)) + calculate_path() + return + + set_navigation_failure("Next path turf is blocked.") + return + + output_next_step("Step ready.") + +/obj/item/integrated_circuit/smart/coordinate_navigator/proc/output_next_step(status) + var/turf/start = get_turf(assembly) + if(!start) + set_navigation_failure("No assembly turf.") + return + + advance_path_index(start) + + if(path_index > current_path.len) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, 0) + set_pin_data(IC_OUTPUT, 3, current_path.len) + set_pin_data(IC_OUTPUT, 4, "Arrived.") + push_data() + activate_pin(5) + return + + var/turf/next_turf = current_path[path_index] + if(!next_turf) + set_navigation_failure("Path index points to no turf.") + return + + if(get_dist(start, next_turf) > 1) + if(get_pin_data(IC_INPUT, 3)) + calculate_path() + return + + set_navigation_failure("Path is stale.") + return + + if(turf_is_blocked_for_navigation(next_turf)) + if(get_pin_data(IC_INPUT, 3)) + calculate_path() + return + + set_navigation_failure("Next path turf is blocked.") + return + + var/dir_to_next = get_dir(start, next_turf) + var/distance_remaining = max(0, current_path.len - path_index + 1) + + set_pin_data(IC_OUTPUT, 1, dir_to_next) + set_pin_data(IC_OUTPUT, 2, distance_remaining) + set_pin_data(IC_OUTPUT, 3, current_path.len) + set_pin_data(IC_OUTPUT, 4, status) + push_data() + activate_pin(4) + +/obj/item/integrated_circuit/smart/coordinate_navigator/proc/advance_path_index(turf/current_turf) + while(path_index <= current_path.len && current_path[path_index] == current_turf) + path_index++ + +/obj/item/integrated_circuit/smart/coordinate_navigator/proc/set_navigation_failure(status) + set_pin_data(IC_OUTPUT, 1, null) + set_pin_data(IC_OUTPUT, 2, 0) + set_pin_data(IC_OUTPUT, 3, current_path ? current_path.len : 0) + set_pin_data(IC_OUTPUT, 4, status) + push_data() + activate_pin(6) + +/obj/item/integrated_circuit/smart/coordinate_navigator/proc/find_path_to_turf(turf/start, turf/goal, node_limit) + if(!start || !goal) + return null + + if(turf_is_blocked_for_navigation(goal)) + return null + + var/list/queue = list(start) + var/list/visited = list() + var/list/parents = list() + + visited[start] = TRUE + + var/head = 1 + var/nodes_checked = 0 + var/found_goal = FALSE + + while(head <= queue.len) + var/turf/current = queue[head] + head++ + nodes_checked++ + + if(nodes_checked > node_limit) + break + + if(current == goal) + found_goal = TRUE + break + + for(var/check_dir in get_prioritized_dirs(current, goal)) + var/turf/next_turf = get_step(current, check_dir) + if(!next_turf) + continue + + if(visited[next_turf]) + continue + + if(turf_is_blocked_for_navigation(next_turf)) + continue + + visited[next_turf] = TRUE + parents[next_turf] = current + queue += next_turf + + if(!found_goal) + return null + + var/list/reversed_path = list() + var/turf/walk = goal + + while(walk) + reversed_path += walk + + if(walk == start) + break + + walk = parents[walk] + + if(!walk) + return null + + var/list/final_path = list() + for(var/i = reversed_path.len, i >= 1, i--) + final_path += reversed_path[i] + + return final_path + +/obj/item/integrated_circuit/smart/coordinate_navigator/proc/get_prioritized_dirs(turf/current, turf/goal) + var/list/dirs = list() + + if(!current || !goal) + return GLOB.cardinals + + var/dx = goal.x - current.x + var/dy = goal.y - current.y + + if(abs(dx) >= abs(dy)) + if(dx > 0) + dirs += EAST + else if(dx < 0) + dirs += WEST + + if(dy > 0) + dirs += NORTH + else if(dy < 0) + dirs += SOUTH + else + if(dy > 0) + dirs += NORTH + else if(dy < 0) + dirs += SOUTH + + if(dx > 0) + dirs += EAST + else if(dx < 0) + dirs += WEST + + for(var/D in GLOB.cardinals) + if(!(D in dirs)) + dirs += D + + return dirs + +/obj/item/integrated_circuit/smart/coordinate_navigator/proc/turf_is_blocked_for_navigation(turf/T) + if(!T) + return TRUE + + if(T.density) + return TRUE + + for(var/atom/movable/AM in T) + if(AM == assembly) + continue + + if(AM.density) + return TRUE + + return FALSE + +/obj/item/integrated_circuit/smart/coordinate_command_parser + name = "coordinate command parser" + desc = "Parses simple coordinate commands into X and Y number outputs." + extended_desc = "This circuit parses text commands such as 'GOTO 25 56', '25 56', 'X 25 Y 56', or 'X=25 Y=56'. It outputs target X and Y coordinates for use with a coordinate navigator." + icon_state = "numberpad" + complexity = 6 + w_class = WEIGHT_CLASS_TINY + size = 1 + power_draw_per_use = 200 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + inputs = list( + "command" = IC_PINTYPE_STRING + ) + + outputs = list( + "target X" = IC_PINTYPE_NUMBER, + "target Y" = IC_PINTYPE_NUMBER, + "valid" = IC_PINTYPE_BOOLEAN, + "status" = IC_PINTYPE_STRING + ) + + activators = list( + "parse command" = IC_PINTYPE_PULSE_IN, + "on parsed" = IC_PINTYPE_PULSE_OUT, + "on failed" = IC_PINTYPE_PULSE_OUT + ) + +/obj/item/integrated_circuit/smart/coordinate_command_parser/do_work() + pull_data() + + var/command = get_pin_data(IC_INPUT, 1) + + if(!istext(command) || !length(command)) + set_parse_result(null, null, FALSE, "No command.") + return + + var/list/numbers = extract_numbers_from_command(command) + + if(!numbers || numbers.len < 2) + set_parse_result(null, null, FALSE, "Command needs two numbers.") + return + + var/target_x = round(numbers[1]) + var/target_y = round(numbers[2]) + + target_x = clamp(target_x, 1, world.maxx) + target_y = clamp(target_y, 1, world.maxy) + + set_parse_result(target_x, target_y, TRUE, "Parsed coordinates: [target_x], [target_y].") + +/obj/item/integrated_circuit/smart/coordinate_command_parser/proc/set_parse_result(target_x, target_y, valid, status) + set_pin_data(IC_OUTPUT, 1, target_x) + set_pin_data(IC_OUTPUT, 2, target_y) + set_pin_data(IC_OUTPUT, 3, valid) + set_pin_data(IC_OUTPUT, 4, status) + push_data() + + if(valid) + activate_pin(2) + else + activate_pin(3) + +/obj/item/integrated_circuit/smart/coordinate_command_parser/proc/extract_numbers_from_command(command) + var/list/numbers = list() + var/current_number = "" + var/reading_number = FALSE + var/allow_negative = TRUE + + for(var/i = 1 to length(command)) + var/char = copytext(command, i, i + 1) + + if(char >= "0" && char <= "9") + current_number += char + reading_number = TRUE + allow_negative = FALSE + continue + + if(char == "-" && allow_negative) + current_number += char + reading_number = TRUE + allow_negative = FALSE + continue + + if(reading_number) + var/parsed = text2num(current_number) + if(!isnull(parsed)) + numbers += parsed + + current_number = "" + reading_number = FALSE + + allow_negative = TRUE + + if(reading_number) + var/parsed = text2num(current_number) + if(!isnull(parsed)) + numbers += parsed + + return numbers diff --git a/code/modules/integrated_electronics/subtypes/time.dm b/code/modules/integrated_electronics/subtypes/time.dm index bf72ff5d688..3c598e91c74 100644 --- a/code/modules/integrated_electronics/subtypes/time.dm +++ b/code/modules/integrated_electronics/subtypes/time.dm @@ -1,6 +1,11 @@ +/* + * subtypes/time.dm + * Timing circuits: delays, clocks, pulses, cooldown-style behavior, and scheduled activation. + */ + /obj/item/integrated_circuit/time name = "time circuit" - desc = "Now you can build your own clock!" + desc = "Outputs the current local time when pulsed." complexity = 2 inputs = list() outputs = list() @@ -14,7 +19,7 @@ var/delay = 2 SECONDS activators = list("incoming"= IC_PINTYPE_PULSE_IN,"outgoing" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - power_draw_per_use = 20 + power_draw_per_use = 30 /obj/item/integrated_circuit/time/delay/do_work() addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/item/integrated_circuit, activate_pin), 2), delay) @@ -26,6 +31,7 @@ icon_state = "delay-50" delay = 5 SECONDS spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 20 /obj/item/integrated_circuit/time/delay/one_sec name = "one-sec delay circuit" @@ -34,30 +40,37 @@ icon_state = "delay-10" delay = 1 SECOND spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 40 /obj/item/integrated_circuit/time/delay/half_sec name = "half-sec delay circuit" desc = "This sends a pulse signal out after a delay, critical for ensuring proper control flow in a complex machine. \ This circuit is set to send a pulse after a delay of half a second." icon_state = "delay-5" + complexity = 4 delay = 5 spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 60 /obj/item/integrated_circuit/time/delay/tenth_sec name = "tenth-sec delay circuit" desc = "This sends a pulse signal out after a delay, critical for ensuring proper control flow in a complex machine. \ This circuit is set to send a pulse after a delay of 1/10th of a second." icon_state = "delay-1" + complexity = 6 delay = 1 spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 100 /obj/item/integrated_circuit/time/delay/custom name = "custom delay circuit" desc = "This sends a pulse signal out after a delay, critical for ensuring proper control flow in a complex machine. \ This circuit's delay can be customized, between 1/10th of a second to one hour. The delay is updated upon receiving a pulse." icon_state = "delay" + complexity = 6 inputs = list("delay time" = IC_PINTYPE_NUMBER) spawn_flags = IC_SPAWN_RESEARCH + power_draw_per_use = 100 /obj/item/integrated_circuit/time/delay/custom/on_data_written() delay = between(1, get_pin_data(IC_INPUT, 1), 1 HOUR) @@ -65,10 +78,10 @@ /obj/item/integrated_circuit/time/ticker name = "ten second ticker" - desc = "This circuit sends an automatic pulse every ten seconds." + desc = "Sends an automatic pulse every ten seconds." icon_state = "tick-m" complexity = 8 - var/seconds_to_pulse = 10 SECONDS + var/seconds_to_pulse = 10 var/ticks_completed = 0 var/is_running = FALSE inputs = list("enable ticking" = IC_PINTYPE_BOOLEAN) @@ -85,33 +98,51 @@ if(do_tick && !is_running) is_running = TRUE START_PROCESSING(SSelectronics, src) - else if(is_running) + else if(!do_tick && is_running) is_running = FALSE STOP_PROCESSING(SSelectronics, src) ticks_completed = 0 /obj/item/integrated_circuit/time/ticker/process() var/process_ticks = SSelectronics.wait + var/pulse_ticks = SecondsToTicks(seconds_to_pulse) ticks_completed += process_ticks - if(ticks_completed >= SecondsToTicks(seconds_to_pulse)) - if(SecondsToTicks(seconds_to_pulse) >= process_ticks) - ticks_completed -= SecondsToTicks(seconds_to_pulse) + if(ticks_completed >= pulse_ticks) + if(pulse_ticks >= process_ticks) + ticks_completed -= pulse_ticks else ticks_completed = 0 + if(!check_power()) + power_fail() + return activate_pin(1) +/obj/item/integrated_circuit/time/ticker/power_fail() + is_running = FALSE + STOP_PROCESSING(SSelectronics, src) + ticks_completed = 0 + +/obj/item/integrated_circuit/time/ticker/rapid + name = "one second ticker" + desc = "Sends a high-speed automatic pulse every second." + icon_state = "tick-f" + complexity = 18 + seconds_to_pulse = 1 + spawn_flags = IC_SPAWN_RESEARCH + power_draw_per_use = 240 + /obj/item/integrated_circuit/time/ticker/fast name = "two second ticker" - desc = "This advanced circuit sends an automatic pulse every two seconds." + desc = "Sends an automatic pulse every two seconds." icon_state = "tick-f" complexity = 12 seconds_to_pulse = 2 - spawn_flags = IC_SPAWN_RESEARCH - power_draw_per_use = 80 + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 120 /obj/item/integrated_circuit/time/ticker/slow name = "thirty second ticker" - desc = "This simple circuit sends an automatic pulse every thirty seconds." + desc = "Sends an automatic pulse every thirty seconds." icon_state = "tick-s" complexity = 4 seconds_to_pulse = 30 @@ -120,7 +151,7 @@ /obj/item/integrated_circuit/time/ticker/very_slow name = "five minute ticker" - desc = "This simple circuit sends an automatic pulse every five minutes (three hundred seconds)." + desc = "Sends an automatic pulse every five minutes." icon_state = "tick-s" complexity = 4 seconds_to_pulse = 300 @@ -129,12 +160,12 @@ /obj/item/integrated_circuit/time/ticker/custom name = "custom ticker" - desc = "This advanced circuit sends an automatic pulse with a configurable interval between 2 and 600 seconds. Default: 60 seconds." + desc = "Sends automatic pulses at a configurable interval between 2 and 600 seconds. Default: 60 seconds." icon_state = "tick-f" complexity = 15 seconds_to_pulse = 60 spawn_flags = IC_SPAWN_RESEARCH - power_draw_per_use = 80 + power_draw_per_use = 120 inputs = list("enable ticking" = IC_PINTYPE_BOOLEAN, "ticker time" = IC_PINTYPE_NUMBER) /obj/item/integrated_circuit/time/ticker/custom/on_data_written() @@ -143,7 +174,7 @@ /obj/item/integrated_circuit/time/clock name = "integrated clock" - desc = "Tells you what the local time is, specific to your station, planet, or facility." + desc = "Outputs the local station, planet, or facility time." icon_state = "clock" inputs = list() outputs = list( @@ -164,3 +195,137 @@ push_data() activate_pin(2) + +/obj/item/integrated_circuit/timed/signal_burst_ticker + name = "signal burst ticker" + desc = "A ticker that starts when pulsed, ticks a fixed number of times, then stops." + extended_desc = "Useful when you want a signaler, scanner, button, or other pulse source to temporarily enable a repeating circuit chain. Pulse count is clamped from 1 to 20, and interval is clamped from 1 to 60 seconds." + icon_state = "clock" + complexity = 10 + spawn_flags = IC_SPAWN_RESEARCH + category_text = "Time" + power_draw_per_use = 160 + + inputs = list( + "pulse count" = IC_PINTYPE_NUMBER, + "interval seconds" = IC_PINTYPE_NUMBER, + "enabled" = IC_PINTYPE_BOOLEAN + ) + + inputs_default = list( + "1" = 4, + "2" = 2, + "3" = TRUE + ) + + outputs = list( + "current pulse" = IC_PINTYPE_NUMBER, + "running" = IC_PINTYPE_BOOLEAN + ) + + activators = list( + "start" = IC_PINTYPE_PULSE_IN, + "stop" = IC_PINTYPE_PULSE_IN, + "on pulse" = IC_PINTYPE_PULSE_OUT, + "on finished" = IC_PINTYPE_PULSE_OUT + ) + + var/running = FALSE + var/current_tick = 0 + var/max_ticks = 4 + var/delay_time = 2 SECONDS + var/burst_id = 0 + + +/obj/item/integrated_circuit/timed/signal_burst_ticker/do_work(activator_id) + var/active_pin = activator_id + + if(istype(active_pin, /datum/integrated_io)) + active_pin = activators.Find(active_pin) + + if(!isnum(active_pin)) + active_pin = 1 + + if(active_pin == 2) + stop_burst(FALSE) + return + + if(running) + return + + pull_data() + + if(!get_pin_data(IC_INPUT, 3)) + stop_burst(FALSE) + return + + var/input_ticks = get_pin_data(IC_INPUT, 1) + var/input_delay = get_pin_data(IC_INPUT, 2) + + if(isnum(input_delay)) + input_delay = between(1, input_delay, 60) + else + input_delay = 2 + + if(isnum(input_ticks)) + input_ticks = between(1, round(input_ticks), 20) + else + input_ticks = 4 + + delay_time = input_delay SECONDS + max_ticks = input_ticks + current_tick = 0 + running = TRUE + burst_id++ + + set_pin_data(IC_OUTPUT, 1, current_tick) + set_pin_data(IC_OUTPUT, 2, running) + push_data() + + // Timer IDs make old callbacks harmless after stop/restart or deletion. + addtimer(CALLBACK(src, PROC_REF(process_tick), burst_id), delay_time) + + +/obj/item/integrated_circuit/timed/signal_burst_ticker/proc/process_tick(expected_burst_id) + if(!running || expected_burst_id != burst_id) + return + + if(!check_power()) + power_fail() + return + + current_tick++ + + set_pin_data(IC_OUTPUT, 1, current_tick) + set_pin_data(IC_OUTPUT, 2, running) + push_data() + activate_pin(3) + + if(current_tick >= max_ticks) + stop_burst(TRUE) + return + + addtimer(CALLBACK(src, PROC_REF(process_tick), expected_burst_id), delay_time) + +/obj/item/integrated_circuit/timed/signal_burst_ticker/proc/stop_burst(finished) + if(!running && !finished) + set_pin_data(IC_OUTPUT, 2, FALSE) + push_data() + return + + running = FALSE + burst_id++ + set_pin_data(IC_OUTPUT, 2, running) + push_data() + + if(finished) + activate_pin(4) + + +/obj/item/integrated_circuit/timed/signal_burst_ticker/Destroy() + running = FALSE + burst_id++ + return ..() + +/obj/item/integrated_circuit/timed/signal_burst_ticker/power_fail() + stop_burst(FALSE) diff --git a/code/modules/integrated_electronics/subtypes/trig.dm b/code/modules/integrated_electronics/subtypes/trig.dm index 6029dfaf1d0..d6011ef0fe6 100644 --- a/code/modules/integrated_electronics/subtypes/trig.dm +++ b/code/modules/integrated_electronics/subtypes/trig.dm @@ -1,3 +1,8 @@ +/* + * subtypes/trig.dm + * Trigonometry and coordinate math circuits. + */ + //These circuits do not-so-simple math. /obj/item/integrated_circuit/trig complexity = 1 @@ -16,7 +21,7 @@ "compute" = IC_PINTYPE_PULSE_IN, "on computed" = IC_PINTYPE_PULSE_OUT ) - category_text = "Trig" + category_text = "MATH - Trigonometry" extended_desc = "Input and output are in degrees." power_draw_per_use = 10 // Still cheap math. @@ -24,7 +29,7 @@ /obj/item/integrated_circuit/trig/sine name = "sin circuit" - desc = "Has nothing to do with evil, unless you consider trigonometry to be evil. Outputs the sine of A." + desc = "Outputs the sine of A." icon_state = "sine" inputs = list("A" = IC_PINTYPE_NUMBER) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -64,7 +69,7 @@ /obj/item/integrated_circuit/trig/tangent name = "tan circuit" - desc = "Outputs the tangent of A. Guaranteed to not go on a tangent about its existance." + desc = "Outputs the tangent of A." icon_state = "tangent" inputs = list("A" = IC_PINTYPE_NUMBER) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -105,7 +110,7 @@ /obj/item/integrated_circuit/trig/secant name = "sec circuit" - desc = "Outputs the secant of A. Has nothing to do with the security department." + desc = "Outputs the secant of A." icon_state = "secant" inputs = list("A" = IC_PINTYPE_NUMBER) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH diff --git a/code/modules/integrated_electronics/~defines/~defines.dm b/code/modules/integrated_electronics/~defines/~defines.dm index 621967d81e7..bb664910fcc 100644 --- a/code/modules/integrated_electronics/~defines/~defines.dm +++ b/code/modules/integrated_electronics/~defines/~defines.dm @@ -1,3 +1,8 @@ +/* + * ~defines/~defines.dm + * Undefines temporary macros after integrated electronics files finish compiling. + */ + #undef IC_INPUT #undef IC_OUTPUT #undef IC_ACTIVATOR diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm index ee99b4051a1..29588ca6db2 100644 --- a/code/modules/mining/mine_turfs.dm +++ b/code/modules/mining/mine_turfs.dm @@ -17,7 +17,6 @@ name = "impassable substrate" color = "#222222" -/// This is a global list so we can share the same list with all mineral turfs; it's the same for all of them anyways. GLOBAL_LIST_INIT(mineral_can_smooth_with, list( /turf/simulated/mineral, /turf/simulated/wall, @@ -395,6 +394,31 @@ GLOBAL_LIST_INIT(mineral_can_smooth_with, list( new /obj/structure/sculpting_block(src) GetDrilled(1) +/turf/simulated/mineral/proc/ic_precision_excavate(var/amount) + if(!finds?.len) + return "No xenoarch find in target rock." + + var/datum/find/F = finds[1] + if(!F) + return "Invalid xenoarch find." + + if(amount <= 0) + return "Invalid excavation amount." + + if(excavation_level + amount > F.excavation_required) + return "Unsafe: excavation would strike past the find." + + if(excavation_level + amount > F.excavation_required - F.clearance_range) + if(round(excavation_level + amount) == F.excavation_required) + excavation_level += amount + excavate_find(100, F) + return "Perfect extraction complete." + + return "Unsafe: excavation would breach clearance zone." + + excavation_level += amount + return "Excavation advanced." + /turf/simulated/mineral/proc/get_geodata() if(!geologic_data) geologic_data = new /datum/geosample(src) @@ -742,8 +766,6 @@ GLOBAL_LIST_INIT(mineral_can_smooth_with, list( roof_type = null turf_flags = TURF_FLAG_BACKGROUND -/// Same as the other, this is a global so we don't have a lot of pointless lists floating around. -/// Basalt is explicitly omitted so ash will spill onto basalt turfs. GLOBAL_LIST_INIT(asteroid_floor_smooth, list( /turf/simulated/floor/exoplanet/asteroid/ash, /turf/simulated/mineral, diff --git a/code/modules/research/designs/protolathe/integrated_electronics_designs.dm b/code/modules/research/designs/protolathe/integrated_electronics_designs.dm index 7b896d566ed..5e6e818e003 100644 --- a/code/modules/research/designs/protolathe/integrated_electronics_designs.dm +++ b/code/modules/research/designs/protolathe/integrated_electronics_designs.dm @@ -56,13 +56,20 @@ /datum/design/item/integrated_electronics/custom_circuit_printer name = "Portable Integrated Circuit Printer" desc = "A portable(ish) printer for modular machines." - req_tech = list(TECH_MATERIAL = 3, TECH_ENGINEERING = 4, TECH_DATA = 5) + req_tech = list(TECH_MATERIAL = 3, TECH_ENGINEERING = 3, TECH_DATA = 3) materials = list(MATERIAL_STEEL = 10000) build_path = /obj/item/integrated_circuit_printer /datum/design/item/integrated_electronics/custom_circuit_printer_upgrade name = "Integrated Circuit Printer Upgrade - Advanced Designs" desc = "Allows the integrated circuit printer to create advanced circuits." - req_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 4) + req_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 5) materials = list(MATERIAL_STEEL = 2000) build_path = /obj/item/disk/integrated_circuit/upgrade/advanced + +/datum/design/item/integrated_electronics/custom_circuit_printer_upgrade/clone + name = "Integrated Circuit Printer Upgrade Disk - Circuit Cloner" + desc = "An upgrade disk that allows the integrated circuit printer to duplicate assemblies." + req_tech = list(TECH_ENGINEERING = 5, TECH_DATA = 6) + materials = list(DEFAULT_WALL_MATERIAL = 3000) + build_path = /obj/item/disk/integrated_circuit/upgrade/clone diff --git a/html/changelogs/mineymonkey integrated circuit overhaul 2.0.yml b/html/changelogs/mineymonkey integrated circuit overhaul 2.0.yml new file mode 100644 index 00000000000..1d05fc36ad2 --- /dev/null +++ b/html/changelogs/mineymonkey integrated circuit overhaul 2.0.yml @@ -0,0 +1,19 @@ +author: mineymonkey + +delete-after: True + +changes: + - rscadd: "Added electronic radio headsets, wearable radios with internal integrated circuit assemblies, encryption key support, and private text-to-speech output." + - rscadd: "Added integrated circuit printer cloning and blueprint import/export support for assemblies and supported hosted assemblies." + - rscadd: "Added new integrated circuit tools for data storage, database commands, radio commands, references, coordinates, access checks, card/item slots, power cells, and item trays." + - rscadd: "Added expanded math circuits, including algebra, geometry, list math, statistics, vectors, matrices, calculus, and vector calculus." + - rscadd: "Added practical automation circuits, including edge detection, cooldown limiting, pulse counting, pulse routing, state display, list utilities, coordinate navigation, and command parsing." + - rscadd: "Added xenoarch precision excavator circuit support with anomaly depth, clearance, excavation, and target direction outputs." + - rscadd: "Added additional manipulation circuits and a low-complexity debug circuit for organizing circuit layouts." + - rscadd: "Made the circuit cloner upgrade disk printable in the protolathe." + - qol: "Reorganized integrated circuit printer categories, math subgroups, logic/comparison groups, circuit descriptions, and circuit box descriptions." + - qol: "Improved cloning preservation for circuit names, pin data, constant memory values, built-in circuit links, hosted assembly state, and electronic radio headset internals." + - qol: "Improved scanner, server, command pad, reference, coordinate, list, ticker, and printer workflows with clearer outputs, safer invalid-input handling, and bounded pulse behavior." + - bugfix: "Fixed imported electronic radio headset blueprints failing to recreate the headset wrapper item or correctly bind the internal assembly." + - bugfix: "Fixed large integrated circuit blueprint imports losing or corrupting JSON characters during chunked transfer." + - bugfix: "Fixed several integrated circuit edge cases involving invalid refs, empty or null lists, injector targets, radio command receiver declarations, and abstract math circuits appearing in printers." diff --git a/icons/obj/assemblies/electronic_components.dmi b/icons/obj/assemblies/electronic_components.dmi index 0f8917f27e0..1f902aa76b1 100644 Binary files a/icons/obj/assemblies/electronic_components.dmi and b/icons/obj/assemblies/electronic_components.dmi differ diff --git a/icons/obj/assemblies/electronic_setups.dmi b/icons/obj/assemblies/electronic_setups.dmi index d3b96886437..275fbf64cf0 100644 Binary files a/icons/obj/assemblies/electronic_setups.dmi and b/icons/obj/assemblies/electronic_setups.dmi differ diff --git a/tgui/packages/tgui/interfaces/CircuitPrinter.tsx b/tgui/packages/tgui/interfaces/CircuitPrinter.tsx index 9537eaaa491..5f906c489ad 100644 --- a/tgui/packages/tgui/interfaces/CircuitPrinter.tsx +++ b/tgui/packages/tgui/interfaces/CircuitPrinter.tsx @@ -1,19 +1,40 @@ import { Button, + Input, LabeledList, ProgressBar, Section, + TextArea, } from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; -import { useBackend } from '../backend'; +import { useBackend, useLocalState } from '../backend'; import { Window } from '../layouts'; +const BLUEPRINT_IMPORT_CHUNK_DELAY = 250; + +const sleep = (duration: number) => + new Promise((resolve) => setTimeout(resolve, duration)); + export type PrinterData = { metal: number; metal_max: number; + phoron: number; + phoron_max: number; upgraded: BooleanLike; can_clone: BooleanLike; assembly_to_clone: string; + clone_cost: number; + clone_phoron_cost: number; + clone_print_time: number; + currently_printing: BooleanLike; + has_clone_loaded: BooleanLike; + has_live_clone_scan: BooleanLike; + has_clone_blueprint: BooleanLike; + has_clone_blueprint_export: BooleanLike; + clone_blueprint_export_visible: BooleanLike; + clone_blueprint_export: string; + blueprint_chunk_limit: number; + blueprint_buffer_limit: number; circuits: Circuit[]; categories: string[]; }; @@ -26,11 +47,159 @@ type Circuit = { category: string; }; +type CircuitSubgroup = { + key: string; + group: string; + subgroup: string; + circuits: Circuit[]; +}; + +type CircuitGroup = { + group: string; + subgroups: CircuitSubgroup[]; +}; + +const splitCircuitCategory = (category: string) => { + const separator = ' - '; + const subgroupedGroups = ['LOGIC', 'MATH']; + const separatorIndex = category.indexOf(separator); + const group = category.slice(0, separatorIndex); + + if (separatorIndex < 0 || !subgroupedGroups.includes(group)) { + return { + group: category, + subgroup: category, + }; + } + + return { + group, + subgroup: category.slice(separatorIndex + separator.length), + }; +}; + export const CircuitPrinter = (props) => { const { act, data } = useBackend(); + const [searchTerm, setSearchTerm] = useLocalState('searchTerm', ''); + const [blueprintText, setBlueprintText] = useLocalState( + 'blueprintText', + '', + ); + const [showBlueprintImport, setShowBlueprintImport] = useLocalState( + 'showBlueprintImport', + false, + ); + const [isImportingBlueprint, setIsImportingBlueprint] = + useLocalState('isImportingBlueprint', false); + const [collapsedCategories, setCollapsedCategories] = useLocalState< + Record + >('collapsedCategories', {}); + + const normalizedSearchTerm = searchTerm.toLowerCase(); + const categories = [...data.categories].sort(); + const categoryGroups = categories.reduce( + (groups, category) => { + const { group, subgroup } = splitCircuitCategory(category); + const groupMatches = group.toLowerCase().includes(normalizedSearchTerm); + const subgroupMatches = subgroup + .toLowerCase() + .includes(normalizedSearchTerm); + const categoryMatches = category + .toLowerCase() + .includes(normalizedSearchTerm); + + const circuits = data.circuits.filter((circuit) => { + if (circuit.category !== category) { + return false; + } + + if ( + !normalizedSearchTerm || + groupMatches || + subgroupMatches || + categoryMatches + ) { + return true; + } + + return !!circuit.name?.toLowerCase().includes(normalizedSearchTerm); + }); + + if (!circuits.length) { + return groups; + } + + let circuitGroup = groups.find((entry) => entry.group === group); + + if (!circuitGroup) { + circuitGroup = { + group, + subgroups: [], + }; + groups.push(circuitGroup); + } + + circuitGroup.subgroups.push({ + key: category, + group, + subgroup, + circuits, + }); + + return groups; + }, + [], + ); + + categoryGroups.sort((a, b) => a.group.localeCompare(b.group)); + categoryGroups.forEach((group) => { + group.subgroups.sort((a, b) => a.subgroup.localeCompare(b.subgroup)); + }); + + const importPastedBlueprint = async () => { + const importText = blueprintText; + + if (!importText || isImportingBlueprint) { + return; + } + + setIsImportingBlueprint(true); + + let offset = 0; + const chunkLimit = Math.max(1, data.blueprint_chunk_limit || 1000); + const bufferLimit = data.blueprint_buffer_limit || 500000; + + if (importText.length > bufferLimit) { + act('reject_oversized_import_tgui', { + length: importText.length, + }); + setIsImportingBlueprint(false); + return; + } + + try { + await act('begin_import_buffer'); + + while (offset < importText.length) { + const chunk = importText.slice(offset, offset + chunkLimit); + + await act('append_import_chunk_tgui', { + chunk: encodeURIComponent(chunk), + }); + + offset += chunk.length; + await sleep(BLUEPRINT_IMPORT_CHUNK_DELAY); + } + + await act('finish_import_buffer_tgui'); + } finally { + setIsImportingBlueprint(false); + } + }; + return ( - +
@@ -48,27 +217,256 @@ export const CircuitPrinter = (props) => { {data.metal} sheets + + + {data.phoron} sheets + + {data.upgraded ? 'Yes' : 'No'} + + {data.can_clone ? 'Installed' : 'Not Installed'} + + + {data.currently_printing ? 'Yes' : 'No'} +
- {data.categories.sort().map((category) => ( -
- {data.circuits.map((circuit) => - circuit.category === category ? ( + + {!!data.can_clone && ( +
+ + + {data.assembly_to_clone || 'None'} + + + {data.assembly_to_clone && data.assembly_to_clone !== 'None' + ? `${data.clone_cost} sheets` + : 'N/A'} + + + {data.assembly_to_clone && data.assembly_to_clone !== 'None' + ? `${data.clone_phoron_cost} sheets` + : 'N/A'} + + + {data.assembly_to_clone && data.assembly_to_clone !== 'None' + ? `${data.clone_print_time} seconds` + : 'N/A'} + + + +