diff --git a/code/__DEFINES/wiremod.dm b/code/__DEFINES/wiremod.dm index 421650e3bf1e..26d967fde93f 100644 --- a/code/__DEFINES/wiremod.dm +++ b/code/__DEFINES/wiremod.dm @@ -99,10 +99,12 @@ #define SHELL_FLAG_CIRCUIT_UNMODIFIABLE (1<<5) // Shell capacities. These can be converted to configs very easily later -#define SHELL_CAPACITY_TINY 12 -#define SHELL_CAPACITY_SMALL 25 -#define SHELL_CAPACITY_MEDIUM 50 -#define SHELL_CAPACITY_LARGE 100 +// NON-MODULE CHANGE START +#define SHELL_CAPACITY_TINY 25 +#define SHELL_CAPACITY_SMALL 50 +#define SHELL_CAPACITY_MEDIUM 100 +#define SHELL_CAPACITY_LARGE 250 +// NON-MODULE CHANGE END #define SHELL_CAPACITY_VERY_LARGE 500 /// The maximum range a USB cable can be apart from a source diff --git a/code/modules/wiremod/core/component_printer.dm b/code/modules/wiremod/core/component_printer.dm index 3fb736540ec1..f79c40edc14b 100644 --- a/code/modules/wiremod/core/component_printer.dm +++ b/code/modules/wiremod/core/component_printer.dm @@ -356,7 +356,7 @@ update_static_data_for_all_viewers() -/obj/machinery/module_duplicator/ui_act(action, list/params) +/obj/machinery/module_duplicator/ui_act(action, list/params, datum/tgui/ui) // NON-MODULAR CHANGE . = ..() if (.) return @@ -365,10 +365,43 @@ if ("print") var/design_id = text2num(params["designId"]) - if (design_id < 1 || design_id > length(scanned_designs)) + /// NON-MODULAR CHANGE START + + var/list/all_designs = scanned_designs + if (!isnull(SSpersistence.circuit_designs[ui.user.client?.ckey])) + all_designs = scanned_designs | SSpersistence.circuit_designs[ui.user.client?.ckey] + + if (design_id < 1 || design_id > length(all_designs)) + return TRUE + + var/list/design = all_designs[design_id] + + if (design["author_ckey"] != ui.user.client?.ckey && !(design in scanned_designs)) // Get away from here, cheater return TRUE - var/list/design = scanned_designs[design_id] + var/list/design_data = null + if (islist(design["dupe_data"])) + design_data = json_decode(design["dupe_data"]["integrated_circuit"]) + else + design_data = json_decode(design["dupe_data"]) + + if(!design_data) + say("Invalid design data.") + return FALSE + + var/list/circuit_data = design_data["components"] + for(var/identifier in circuit_data) + var/list/component_data = circuit_data[identifier] + var/comp_type = text2path(component_data["type"]) + if (!ispath(comp_type, /obj/item/circuit_component)) + say("[component_data["name"]] component in this circuit has been recalled, unable to proceed.") + return TRUE + + if (isnull(current_unlocked_designs[comp_type]) && !isnull(all_circuit_designs[comp_type])) + say("[component_data["name"]] component has not been researched yet.") + return TRUE + + /// NON-MODULAR CHANGE END if (materials.on_hold()) say("Mineral access is on hold, please contact the quartermaster.") @@ -387,6 +420,33 @@ // SAFETY: eject_sheets checks for valid mats materials.eject_sheets(material, amount) + // NON-MODULAR CHANGE START + + if ("delete") + var/design_id = text2num(params["designId"]) + + var/list/all_designs = scanned_designs + if (!isnull(SSpersistence.circuit_designs[ui.user.client?.ckey])) + all_designs = scanned_designs | SSpersistence.circuit_designs[ui.user.client?.ckey] + + if (design_id < 1 || design_id > length(all_designs)) + return TRUE + + var/list/design = all_designs[design_id] + + if (design["author_ckey"] != ui.user.client?.ckey) + return TRUE + + if(tgui_alert(ui.user, "Are you sure you want to delete [design["name"]]?", "Module Duplicator", list("Yes","No")) != "Yes") + return TRUE + + if (!isnull(SSpersistence.circuit_designs[design["author_ckey"]])) + SSpersistence.circuit_designs[design["author_ckey"]] -= list(design) + scanned_designs -= list(design) + update_static_data_for_all_viewers() + + // NON-MODULAR CHANGE END + return TRUE /obj/machinery/module_duplicator/proc/print_module(list/design) @@ -421,7 +481,7 @@ data["dupe_data"] = list() module.save_data_to_list(data["dupe_data"]) - data["name"] = module.display_name + data["name"] = "[module.display_name]" // NON-MODULAR CHANGE data["desc"] = "A module that has been loaded in by [user]." data["materials"] = list(GET_MATERIAL_REF(/datum/material/glass) = module.circuit_size * cost_per_component) else if(istype(weapon, /obj/item/integrated_circuit)) @@ -431,7 +491,7 @@ return ..() data["dupe_data"] = integrated_circuit.convert_to_json() - data["name"] = integrated_circuit.display_name + data["name"] = "[integrated_circuit.display_name]" // NON-MODULAR CHANGE data["desc"] = "An integrated circuit that has been loaded in by [user]." var/datum/design/integrated_circuit/circuit_design = SSresearch.techweb_design_by_id("integrated_circuit") @@ -449,17 +509,38 @@ balloon_alert(user, "it needs a name!") return ..() - for(var/list/component_data as anything in scanned_designs) + // NON-MODULAR CHANGE START + + data["author_ckey"] = user.client?.ckey + + var/list/all_designs = scanned_designs + if (!isnull(user.client?.ckey)) + if (isnull(SSpersistence.circuit_designs[user.client?.ckey])) + SSpersistence.load_circuits_by_ckey(user.client?.ckey) + all_designs = scanned_designs | SSpersistence.circuit_designs[user.client?.ckey] + + for(var/list/component_data as anything in all_designs) + if (component_data["author_ckey"] != user.client?.ckey && !(component_data in scanned_designs)) + continue if(component_data["name"] == data["name"]) balloon_alert(user, "name already exists!") return ..() + // NON-MODULAR CHANGE END + flick("module-fab-scan", src) addtimer(CALLBACK(src, PROC_REF(finish_module_scan), user, data), 1.4 SECONDS) /obj/machinery/module_duplicator/proc/finish_module_scan(mob/user, data) scanned_designs += list(data) + // NON-MODULAR CHANGE START + if (!isnull(user.client?.ckey)) + if (isnull(SSpersistence.circuit_designs[user.client?.ckey])) + SSpersistence.load_circuits_by_ckey(user.client?.ckey) + SSpersistence.circuit_designs[user.client?.ckey] += list(data) + // NON-MODULAR CHANGE END + balloon_alert(user, "module has been saved.") playsound(src, 'sound/machines/ping.ogg', 50) @@ -475,8 +556,16 @@ var/list/designs = list() + // NON-MODULAR CHANGE START + + var/list/all_designs = scanned_designs + if (!isnull(user.client?.ckey)) + if (isnull(SSpersistence.circuit_designs[user.client?.ckey])) + SSpersistence.load_circuits_by_ckey(user.client?.ckey) + all_designs = scanned_designs | SSpersistence.circuit_designs[user.client?.ckey] + var/index = 1 - for (var/list/design as anything in scanned_designs) + for (var/list/design as anything in all_designs) var/list/cost = list() var/list/materials = design["materials"] @@ -490,11 +579,42 @@ "id" = "[index]", "icon" = "integrated_circuit", "categories" = list("/Saved Circuits"), + "can_delete" = (design["author_ckey"] == user.client?.ckey), + "print_error" = null, ) + + var/list/invalid_list = list() + var/list/unresearched_list = list() + var/list/design_data = null + if (islist(design["dupe_data"])) + design_data = json_decode(design["dupe_data"]["integrated_circuit"]) + else + design_data = json_decode(design["dupe_data"]) + + if(!design_data) + index++ + continue + + var/list/circuit_data = design_data["components"] + for(var/identifier in circuit_data) + var/list/component_data = circuit_data[identifier] + var/comp_type = text2path(component_data["type"]) + if (!ispath(comp_type, /obj/item/circuit_component)) + invalid_list |= component_data["name"] + else if (isnull(current_unlocked_designs[comp_type]) && !isnull(all_circuit_designs[comp_type])) + unresearched_list |= component_data["name"] + + if (invalid_list.len) + designs["[index]"]["print_error"] = "Following components have been recalled: [invalid_list.Join(", ")]" + if (unresearched_list.len) + designs["[index]"]["print_error"] = (designs["[index]"]["print_error"] || "") + "[designs["[index]"]["print_error"] ? "; " : ""]Following components are yet to be researched: [unresearched_list.Join(", ")]" + index++ data["designs"] = designs + // NON-MODULAR CHANGE END + return data /obj/machinery/module_duplicator/crowbar_act(mob/living/user, obj/item/tool) diff --git a/code/modules/wiremod/core/duplicator.dm b/code/modules/wiremod/core/duplicator.dm index 7b373db6e359..9676aa808a00 100644 --- a/code/modules/wiremod/core/duplicator.dm +++ b/code/modules/wiremod/core/duplicator.dm @@ -150,6 +150,7 @@ GLOBAL_LIST_INIT(circuit_dupe_whitelisted_types, list( var/list/component_data = list() component_data["type"] = component.type + component_data["name"] = component.name // NON-MODULAR CHANGE var/list/connections = list() var/list/input_ports_stored_data = list() diff --git a/maplestation.dme b/maplestation.dme index 1c9322709cb1..1d2395b01d4f 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -6498,7 +6498,17 @@ #include "maplestation_modules\code\modules\vending\_vending.dm" #include "maplestation_modules\code\modules\vending\clothesmate.dm" #include "maplestation_modules\code\modules\vending\wardrobes.dm" -#include "maplestation_modules\code\modules\wiremod\shells.dm" +#include "maplestation_modules\code\modules\wiremod\circuit_exporting.dm" +#include "maplestation_modules\code\modules\wiremod\components\bci_click_intercept.dm" +#include "maplestation_modules\code\modules\wiremod\components\camera.dm" +#include "maplestation_modules\code\modules\wiremod\components\cell_charge.dm" +#include "maplestation_modules\code\modules\wiremod\components\item_interaction.dm" +#include "maplestation_modules\code\modules\wiremod\components\mining.dm" +#include "maplestation_modules\code\modules\wiremod\components\mmi.dm" +#include "maplestation_modules\code\modules\wiremod\components\modsuit.dm" +#include "maplestation_modules\code\modules\wiremod\components\screen.dm" +#include "maplestation_modules\code\modules\wiremod\components\tile_scanner.dm" +#include "maplestation_modules\code\modules\wiremod\shells.dm" #include "maplestation_modules\story_content\albert_equipment\code\albertclothing.dm" #include "maplestation_modules\story_content\albert_equipment\code\albertitem.dm" #include "maplestation_modules\story_content\armored_corps\code\clothing\aylie_cloak.dm" diff --git a/maplestation_modules/code/modules/research/designs/wiremod_designs.dm b/maplestation_modules/code/modules/research/designs/wiremod_designs.dm index 46a0da24b400..10ee74209601 100644 --- a/maplestation_modules/code/modules/research/designs/wiremod_designs.dm +++ b/maplestation_modules/code/modules/research/designs/wiremod_designs.dm @@ -16,7 +16,37 @@ // The module duplicate is also 1/4th the cost. /obj/machinery/module_duplicator cost_per_component = SHEET_MATERIAL_AMOUNT * 0.025 + +/datum/design/component/bci_click + name = "Click Interceptor Component" + id = "comp_bci_click" + build_path = /obj/item/circuit_component/click_interceptor +/datum/design/component/circuit_camera + name = "Camera Component" + id = "comp_circuit_camera" + build_path = /obj/item/circuit_component/circuit_camera + +/datum/design/component/cell_charge + name = "Cell Charge Component" + id = "comp_cell_charge" + build_path = /obj/item/circuit_component/cell_charge + +/datum/design/component/mining + name = "Mining Component" + id = "comp_mining" + build_path = /obj/item/circuit_component/mining + +/datum/design/component/screen + name = "Screen Component" + id = "comp_screen" + build_path = /obj/item/circuit_component/screen + +/datum/design/component/tile_scanner + name = "Tile Scanner Component" + id = "comp_tile_scanner" + build_path = /obj/item/circuit_component/tile_scanner + /datum/design/headset_shell name = "Headset Shell" desc = "A portable shell integrated with a radio headset." diff --git a/maplestation_modules/code/modules/research/techweb/all_nodes.dm b/maplestation_modules/code/modules/research/techweb/all_nodes.dm index 78a30555fdb2..dbda9468dff7 100644 --- a/maplestation_modules/code/modules/research/techweb/all_nodes.dm +++ b/maplestation_modules/code/modules/research/techweb/all_nodes.dm @@ -86,6 +86,20 @@ ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) +/datum/techweb_node/basic_circuitry + id_additions = list( + "comp_circuit_camera", + "comp_cell_charge", + "comp_mining", + "comp_screen", + "comp_tile_scanner", + ) + +/datum/techweb_node/bci_shells + id_additions = list( + "comp_bci_click", + ) + /datum/techweb_node/adv_shells id_additions = list( "headset_shell", diff --git a/maplestation_modules/code/modules/wiremod/circuit_exporting.dm b/maplestation_modules/code/modules/wiremod/circuit_exporting.dm new file mode 100644 index 000000000000..f9a2775e492a --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/circuit_exporting.dm @@ -0,0 +1,173 @@ +#define CIRCUITS_DATA_FILEPATH "data/circuit_designs/" + +/obj/machinery/module_duplicator/attackby_secondary(obj/item/weapon, mob/user, params) + if (!istype(weapon, /obj/item/integrated_circuit)) + return ..() + + if (HAS_TRAIT(weapon, TRAIT_CIRCUIT_UNDUPABLE)) + balloon_alert(user, "unable to scan!") + return ..() + + flick("module-fab-scan", src) + addtimer(CALLBACK(src, PROC_REF(finish_json_export), weapon, user), 1.4 SECONDS) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/module_duplicator/proc/finish_json_export(obj/item/integrated_circuit/circuit, mob/user) + var/datum/tgui_input_text/default_value/text_input = new(user, "", "Circuit Export", circuit.convert_to_json(), INFINITY, TRUE, FALSE, 0, GLOB.always_state) + text_input.ui_interact(user) + +/datum/tgui_input_text/default_value/ui_static_data(mob/user) + . = ..() + .["default_value"] = default + +/obj/machinery/module_duplicator/attack_hand_secondary(mob/user, list/modifiers) + var/circuit_data = tgui_input_text(user, "Import JSON for your circuit", "Circuit Import", "", INFINITY, TRUE, FALSE) + if (!circuit_data) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + var/list/errors = list() + var/obj/item/integrated_circuit/temp_circuit = new(src) + temp_circuit.load_circuit_data(circuit_data, errors) // For data validation + if (errors.len) + qdel(temp_circuit) + balloon_alert(user, "invalid import!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + var/circuit_name = tgui_input_text(user, "Name your circuit", "Circuit Import") + if (!circuit_name) + qdel(temp_circuit) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + for(var/list/component_data as anything in scanned_designs) + if(component_data["name"] == circuit_name) + balloon_alert(user, "name already exists!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + var/list/data = list() + data["name"] = circuit_name + data["desc"] = "An integrated circuit that has been loaded in by [user]." + data["dupe_data"] = circuit_data + + var/datum/design/integrated_circuit/circuit_design = SSresearch.techweb_design_by_id("integrated_circuit") + var/materials = list(GET_MATERIAL_REF(/datum/material/glass) = temp_circuit.current_size * cost_per_component) + for(var/material_type in circuit_design.materials) + materials[material_type] += circuit_design.materials[material_type] + qdel(temp_circuit) + data["materials"] = materials + data["integrated_circuit"] = TRUE + scanned_designs += list(data) + balloon_alert(user, "circuit loaded!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/component_printer/attackby(obj/item/weapon, mob/living/user, params) + if(istype(weapon, /obj/item/circuit_component/module) && !user.combat_mode) + var/obj/item/circuit_component/module/module = weapon + module.internal_circuit.linked_component_printer = WEAKREF(src) + module.internal_circuit.update_static_data_for_all_viewers() + balloon_alert(user, "successfully linked to the integrated circuit") + return + return ..() + +/obj/machinery/module_duplicator + /// The current unlocked circuit component designs. Used by integrated circuits to print off circuit components remotely. + var/list/current_unlocked_designs = list() + /// The techweb the duplicastor will get researched designs from + var/datum/techweb/techweb + /// List of all circuit designs + var/static/list/all_circuit_designs = null + +/obj/machinery/module_duplicator/Initialize(mapload) + . = ..() + + if (all_circuit_designs) + return + + all_circuit_designs = list() + for (var/datum/design/component/design as anything in subtypesof(/datum/design/component)) + all_circuit_designs[design::build_path] = design::id + +/obj/machinery/module_duplicator/LateInitialize() + . = ..() + if(!CONFIG_GET(flag/no_default_techweb_link) && !techweb) + CONNECT_TO_RND_SERVER_ROUNDSTART(techweb, src) + if(techweb) + on_connected_techweb() + +/obj/machinery/module_duplicator/proc/connect_techweb(datum/techweb/new_techweb) + if(techweb) + UnregisterSignal(techweb, list(COMSIG_TECHWEB_ADD_DESIGN, COMSIG_TECHWEB_REMOVE_DESIGN)) + techweb = new_techweb + if(!isnull(techweb)) + on_connected_techweb() + +/obj/machinery/module_duplicator/proc/on_connected_techweb() + for (var/researched_design_id in techweb.researched_designs) + var/datum/design/design = SSresearch.techweb_design_by_id(researched_design_id) + if (!(design.build_type & COMPONENT_PRINTER) || !ispath(design.build_path, /obj/item/circuit_component)) + continue + + current_unlocked_designs[design.build_path] = design.id + + RegisterSignal(techweb, COMSIG_TECHWEB_ADD_DESIGN, PROC_REF(on_research)) + RegisterSignal(techweb, COMSIG_TECHWEB_REMOVE_DESIGN, PROC_REF(on_removed)) + +/obj/machinery/module_duplicator/multitool_act(mob/living/user, obj/item/multitool/tool) + if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) + connect_techweb(tool.buffer) + return TRUE + +/obj/machinery/module_duplicator/proc/on_research(datum/source, datum/design/added_design, custom) + SIGNAL_HANDLER + if (!(added_design.build_type & COMPONENT_PRINTER) || !ispath(added_design.build_path, /obj/item/circuit_component)) + return + current_unlocked_designs[added_design.build_path] = added_design.id + +/obj/machinery/module_duplicator/proc/on_removed(datum/source, datum/design/added_design, custom) + SIGNAL_HANDLER + if (!(added_design.build_type & COMPONENT_PRINTER) || !ispath(added_design.build_path, /obj/item/circuit_component)) + return + current_unlocked_designs -= added_design.build_path + +/datum/controller/subsystem/persistence + ///Associated list of all saved circuits, ckey -> list of designs + var/list/circuit_designs = list() + +/datum/controller/subsystem/persistence/collect_data() + . = ..() + save_circuits() + +/datum/controller/subsystem/persistence/proc/load_circuits_by_ckey(user) + var/json_file = file("[CIRCUITS_DATA_FILEPATH][user].json") + if(!fexists(json_file)) + circuit_designs[user] = list() + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + circuit_designs[user] = list() + return + var/list/new_circuit_designs = json["data"] + for (var/list/design in new_circuit_designs) + var/list/new_materials = list() + for (var/material in design["materials"]) + new_materials[GET_MATERIAL_REF(text2path(material))] = design["materials"][material] + design["materials"] = new_materials + circuit_designs[user] = new_circuit_designs + +/datum/controller/subsystem/persistence/proc/save_circuits() + for (var/user in circuit_designs) + var/json_file = file("[CIRCUITS_DATA_FILEPATH][user].json") + var/file_data = list() + var/list/user_designs = circuit_designs[user] + var/list/designs_to_store = user_designs.Copy() + + for (var/list/design in designs_to_store) + var/list/new_materials = list() + for (var/datum/material/material in design["materials"]) + new_materials["[material.type]"] = design["materials"][material] + design["materials"] = new_materials + + file_data["data"] = designs_to_store + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +#undef CIRCUITS_DATA_FILEPATH diff --git a/maplestation_modules/code/modules/wiremod/components/bci_click_intercept.dm b/maplestation_modules/code/modules/wiremod/components/bci_click_intercept.dm new file mode 100644 index 000000000000..fcd296247095 --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/components/bci_click_intercept.dm @@ -0,0 +1,128 @@ +/** + * # Click Interceptor Component + * + * Outputs a signal when the user clicks on something. If its been primed via a signal, their click will be negated completely. + * Requires a BCI shell. + */ + +/obj/item/circuit_component/click_interceptor + display_name = "Click Interceptor" + desc = "A component that allows the user to pinpoint an object using their mind. Sending an input will negate the next input the user does. Requires a BCI shell." + category = "BCI" + + required_shells = list(/obj/item/organ/internal/cyberimp/bci) + + var/datum/port/input/click_toggle + var/datum/port/input/move_toggle + + var/datum/port/output/left_click + var/datum/port/output/right_click + var/datum/port/output/middle_click + + var/datum/port/output/north + var/datum/port/output/east + var/datum/port/output/south + var/datum/port/output/west + + var/datum/port/output/clicked_atom + + // Click modifiers + var/datum/port/output/alt_click + var/datum/port/output/ctrl_click + var/datum/port/output/shift_click + + var/obj/item/organ/internal/cyberimp/bci/bci + var/intercept_click = FALSE + var/intercept_move = FALSE + +/obj/item/circuit_component/click_interceptor/populate_ports() + north = add_output_port("North", PORT_TYPE_SIGNAL) + east = add_output_port("East", PORT_TYPE_SIGNAL) + south = add_output_port("South", PORT_TYPE_SIGNAL) + west = add_output_port("West", PORT_TYPE_SIGNAL) + + left_click = add_output_port("Left Click", PORT_TYPE_SIGNAL) + right_click = add_output_port("Right Click", PORT_TYPE_SIGNAL) + middle_click = add_output_port("Middle Click", PORT_TYPE_SIGNAL) + + clicked_atom = add_output_port("Target", PORT_TYPE_ATOM) + + alt_click = add_output_port("Alt Click", PORT_TYPE_NUMBER) + shift_click = add_output_port("Shift Click", PORT_TYPE_NUMBER) + ctrl_click = add_output_port("Ctrl Click", PORT_TYPE_NUMBER) + + click_toggle = add_input_port("Intercept Next Click", PORT_TYPE_SIGNAL) + move_toggle = add_input_port("Intercept Next Move", PORT_TYPE_SIGNAL) + +/obj/item/circuit_component/click_interceptor/register_shell(atom/movable/shell) + if(!istype(shell, /obj/item/organ/internal/cyberimp/bci)) + return + + bci = shell + RegisterSignal(bci, COMSIG_ORGAN_IMPLANTED, PROC_REF(on_implanted)) + RegisterSignal(bci, COMSIG_ORGAN_REMOVED, PROC_REF(on_removed)) + + var/mob/living/owner = bci.owner + if(istype(owner)) + RegisterSignal(owner, COMSIG_MOB_CLICKON, PROC_REF(handle_click)) + RegisterSignal(owner, COMSIG_MOB_CLIENT_PRE_MOVE, PROC_REF(handle_move)) + +/obj/item/circuit_component/click_interceptor/unregister_shell(atom/movable/shell) + UnregisterSignal(bci, list(COMSIG_ORGAN_IMPLANTED, COMSIG_ORGAN_REMOVED)) + if (istype(bci.owner)) + UnregisterSignal(bci.owner, list(COMSIG_MOB_CLICKON, COMSIG_MOB_CLIENT_PRE_MOVE)) + bci = null + +/obj/item/circuit_component/click_interceptor/input_received(datum/port/input/port) + if (COMPONENT_TRIGGERED_BY(click_toggle, port)) + intercept_click = TRUE + if (COMPONENT_TRIGGERED_BY(move_toggle, port)) + intercept_move = TRUE + +/obj/item/circuit_component/click_interceptor/proc/handle_move(datum/source, list/move_args) + SIGNAL_HANDLER + var/move_dir = get_dir(get_turf(source), move_args[MOVE_ARG_NEW_LOC]) + + if (move_dir & NORTH) + north.set_output(COMPONENT_SIGNAL) + if (move_dir & EAST) + east.set_output(COMPONENT_SIGNAL) + if (move_dir & SOUTH) + south.set_output(COMPONENT_SIGNAL) + if (move_dir & WEST) + west.set_output(COMPONENT_SIGNAL) + + if (intercept_move) + return COMSIG_MOB_CLIENT_BLOCK_PRE_MOVE + +/obj/item/circuit_component/click_interceptor/proc/on_implanted(datum/source, mob/living/carbon/owner) + SIGNAL_HANDLER + RegisterSignal(owner, COMSIG_MOB_CLICKON, PROC_REF(handle_click)) + RegisterSignal(owner, COMSIG_MOB_CLIENT_PRE_MOVE, PROC_REF(handle_move)) + +/obj/item/circuit_component/click_interceptor/proc/on_removed(datum/source, mob/living/carbon/owner) + SIGNAL_HANDLER + UnregisterSignal(owner, list(COMSIG_MOB_CLICKON, COMSIG_MOB_CLIENT_PRE_MOVE)) + +/obj/item/circuit_component/click_interceptor/proc/handle_click(mob/living/source, atom/target, list/modifiers) + SIGNAL_HANDLER + + if (source.stat >= UNCONSCIOUS) + return + + alt_click.set_output(LAZYACCESS(modifiers, ALT_CLICK)) + shift_click.set_output(LAZYACCESS(modifiers, SHIFT_CLICK)) + ctrl_click.set_output(LAZYACCESS(modifiers, CTRL_CLICK)) + + clicked_atom.set_output(target) + + if (LAZYACCESS(modifiers, RIGHT_CLICK)) + right_click.set_output(COMPONENT_SIGNAL) + else if (LAZYACCESS(modifiers, MIDDLE_CLICK)) + middle_click.set_output(COMPONENT_SIGNAL) + else + left_click.set_output(COMPONENT_SIGNAL) + + if (intercept_click) + intercept_click = FALSE + return COMSIG_MOB_CANCEL_CLICKON diff --git a/maplestation_modules/code/modules/wiremod/components/camera.dm b/maplestation_modules/code/modules/wiremod/components/camera.dm new file mode 100644 index 000000000000..98883d7503c1 --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/components/camera.dm @@ -0,0 +1,52 @@ +/** + * # Camera component + * + * Adds a camera to the shell + */ + +/obj/item/circuit_component/circuit_camera + display_name = "Camera" + desc = "A component that links to the station's camera network." + category = "Entity" + + var/datum/port/input/enable_trigger + var/datum/port/input/disable_trigger + var/datum/port/input/tag_input + + var/datum/port/output/enabled + var/datum/port/output/disabled + + var/obj/machinery/camera/circuit/camera + +/obj/item/circuit_component/circuit_camera/populate_ports() + tag_input = add_input_port("Camera name", PORT_TYPE_STRING) + enable_trigger = add_input_port("Enable", PORT_TYPE_SIGNAL) + disable_trigger = add_input_port("Disable", PORT_TYPE_SIGNAL) + + enabled = add_output_port("Enabled", PORT_TYPE_SIGNAL) + disabled = add_output_port("Disabled", PORT_TYPE_SIGNAL) + +/obj/item/circuit_component/circuit_camera/register_shell(atom/movable/shell) + camera = new(shell) + +/obj/item/circuit_component/circuit_camera/unregister_shell(atom/movable/shell) + QDEL_NULL(camera) + +/obj/item/circuit_component/circuit_camera/input_received(datum/port/input/port) + if (isnull(camera)) + return + + camera.c_tag = tag_input.value + + if(COMPONENT_TRIGGERED_BY(enable_trigger, port) && !camera.status) + camera.toggle_cam() + enabled.set_output(COMPONENT_SIGNAL) + + if(COMPONENT_TRIGGERED_BY(disable_trigger, port) && camera.status) + camera.toggle_cam() + disabled.set_output(COMPONENT_SIGNAL) + +/obj/machinery/camera/circuit + c_tag = "Circuit Shell: Unspecified" + desc = "This camera belongs in a circuit shell. If you see this, tell a coder!" + network = list("ss13", "rd") diff --git a/maplestation_modules/code/modules/wiremod/components/cell_charge.dm b/maplestation_modules/code/modules/wiremod/components/cell_charge.dm new file mode 100644 index 000000000000..d8ef8f2a93b7 --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/components/cell_charge.dm @@ -0,0 +1,28 @@ +/** + * # Cell Charge Component + * + * Returns circuit's current cell charge and its capacity + */ + +/obj/item/circuit_component/cell_charge + display_name = "Cell Charge" + desc = "A component that reads current cell charge and its maximum capacity." + category = "Sensor" + + var/datum/port/output/current_charge + var/datum/port/output/max_charge + + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + +/obj/item/circuit_component/cell_charge/populate_ports() + current_charge = add_output_port("Current Charge", PORT_TYPE_NUMBER) + max_charge = add_output_port("Max Charge", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/cell_charge/input_received(datum/port/input/port) + var/obj/item/stock_parts/cell/battery = parent.cell + + if(!istype(battery)) + return + + max_charge.set_output(battery.maxcharge) + current_charge.set_output(battery.charge) diff --git a/maplestation_modules/code/modules/wiremod/components/item_interaction.dm b/maplestation_modules/code/modules/wiremod/components/item_interaction.dm new file mode 100644 index 000000000000..1638c36eaa82 --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/components/item_interaction.dm @@ -0,0 +1,88 @@ +s/* + * # Item Interaction component + * + * Allows a shell to left, right or middle click an atom next to it. Supports ctrl, shift and alt clicking. Drone shell only. + * + * Currently admin-only because I'm afraid this could generate far too many runtimes + */ + +/obj/item/circuit_component/item_interact + display_name = "Item Interaction" + desc = "A component that allows a shell to interact with objects right next to it. Drone shell only." + category = "Action" + + var/datum/port/input/target + + var/datum/port/input/left_click + var/datum/port/input/middle_click + var/datum/port/input/right_click + + var/datum/port/input/alt_click + var/datum/port/input/shift_click + var/datum/port/input/ctrl_click + var/datum/port/input/in_hand_click + + var/click_delay = 0.8 SECONDS // Same as click cooldown + COOLDOWN_DECLARE(click_cooldown) + +/obj/item/circuit_component/item_interact/populate_ports() + target = add_input_port("Target", PORT_TYPE_ATOM) + + left_click = add_input_port("Left Click", PORT_TYPE_SIGNAL) + middle_click = add_input_port("Middle Click", PORT_TYPE_SIGNAL) + right_click = add_input_port("Right Click", PORT_TYPE_SIGNAL) + + alt_click = add_input_port("Alt Click", PORT_TYPE_NUMBER) + shift_click = add_input_port("Shift Click", PORT_TYPE_NUMBER) + ctrl_click = add_input_port("Ctrl Click", PORT_TYPE_NUMBER) + in_hand_click = add_input_port("In-Hand Click", PORT_TYPE_NUMBER) + + trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL, order = 2) + +/obj/item/circuit_component/item_interact/input_received(datum/port/input/port, list/return_values) + if (!COOLDOWN_FINISHED(src, click_delay)) + return + + if (isnull(target.value)) + return + + if (!COMPONENT_TRIGGERED_BY(left_click, port) && !COMPONENT_TRIGGERED_BY(middle_click, port) && !COMPONENT_TRIGGERED_BY(right_click, port)) + return + + var/mob/shell = parent.shell + if(!istype(shell) || !shell.CanReach(target.value)) + return + + var/list/modifiers = list() + + if (alt_click.value) + modifiers[ALT_CLICK] = TRUE + if (shift_click.value) + modifiers[SHIFT_CLICK] = TRUE + if (ctrl_click.value) + modifiers[CTRL_CLICK] = TRUE + + if (in_hand_click.value) + if (!isitem(target.value)) + return + + var/obj/item/target_item = target.value + if (COMPONENT_TRIGGERED_BY(left_click, port)) + target_item.attack_self(shell, modifiers) + COOLDOWN_START(src, click_cooldown, click_delay) + trigger_output.set_output(COMPONENT_SIGNAL) + + if (COMPONENT_TRIGGERED_BY(right_click, port)) + target_item.attack_self_secondary(shell, modifiers) + COOLDOWN_START(src, click_cooldown, click_delay) + trigger_output.set_output(COMPONENT_SIGNAL) + return + + if (COMPONENT_TRIGGERED_BY(left_click, port)) + modifiers[LEFT_CLICK] = TRUE + if (COMPONENT_TRIGGERED_BY(middle_click, port)) + modifiers[MIDDLE_CLICK] = TRUE + if (COMPONENT_TRIGGERED_BY(right_click, port)) + modifiers[RIGHT_CLICK] = TRUE + + INVOKE_ASYNC(shell, TYPE_PROC_REF(/mob, ClickOn), target.value, list2params(modifiers)) diff --git a/maplestation_modules/code/modules/wiremod/components/mining.dm b/maplestation_modules/code/modules/wiremod/components/mining.dm new file mode 100644 index 000000000000..4a6e441a5e2c --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/components/mining.dm @@ -0,0 +1,33 @@ +/* + * # Mining component + * + * Allows to mine rocks walls and floors + */ + +/obj/item/circuit_component/mining + display_name = "Mining" + desc = "A component that can mine rock turfs. Only works with drone shells." + category = "Action" + + var/datum/port/input/target + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + +/obj/item/circuit_component/mining/populate_ports() + target = add_input_port("Target", PORT_TYPE_ATOM) + +/obj/item/circuit_component/mining/input_received(datum/port/input/port) + var/atom/target_atom = target.value + + var/mob/shell = parent.shell + if(!istype(shell) || !shell.CanReach(target_atom)) + return + + if (ismineralturf(target_atom)) + var/turf/closed/mineral/wall = target_atom + wall.gets_drilled(shell, FALSE) + return + + if (isasteroidturf(target_atom)) + var/turf/open/misc/asteroid/floor = target_atom + floor.getDug() + return diff --git a/maplestation_modules/code/modules/wiremod/components/mmi.dm b/maplestation_modules/code/modules/wiremod/components/mmi.dm new file mode 100644 index 000000000000..68756e1d6554 --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/components/mmi.dm @@ -0,0 +1,33 @@ +/obj/item/circuit_component/mmi + // Called when the MMI middle clicks. + var/datum/port/output/middle_click + + // Click modifiers + var/datum/port/output/alt_click + var/datum/port/output/ctrl_click + var/datum/port/output/shift_click + +/obj/item/circuit_component/mmi/populate_ports() + . = ..() + middle_click = add_output_port("Middle Click", PORT_TYPE_SIGNAL) + alt_click = add_output_port("Alt Click", PORT_TYPE_NUMBER) + shift_click = add_output_port("Shift Click", PORT_TYPE_NUMBER) + ctrl_click = add_output_port("Ctrl Click", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/mmi/handle_mmi_attack(mob/living/source, atom/target, list/modifiers) + alt_click.set_output(LAZYACCESS(modifiers, ALT_CLICK)) + shift_click.set_output(LAZYACCESS(modifiers, SHIFT_CLICK)) + ctrl_click.set_output(LAZYACCESS(modifiers, CTRL_CLICK)) + clicked_atom.set_output(target) + + if (LAZYACCESS(modifiers, RIGHT_CLICK)) + secondary_attack.set_output(COMPONENT_SIGNAL) + return COMSIG_MOB_CANCEL_CLICKON + + if (LAZYACCESS(modifiers, MIDDLE_CLICK)) + middle_click.set_output(COMPONENT_SIGNAL) + return COMSIG_MOB_CANCEL_CLICKON + + attack.set_output(COMPONENT_SIGNAL) + if (!LAZYACCESS(modifiers, ALT_CLICK) && !LAZYACCESS(modifiers, SHIFT_CLICK) && !LAZYACCESS(modifiers, CTRL_CLICK)) + return COMSIG_MOB_CANCEL_CLICKON diff --git a/maplestation_modules/code/modules/wiremod/components/modsuit.dm b/maplestation_modules/code/modules/wiremod/components/modsuit.dm new file mode 100644 index 000000000000..eda41ce40983 --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/components/modsuit.dm @@ -0,0 +1,136 @@ +/// WHAT COULD GO WRONG I WONDER + +/obj/item/circuit_component/mod_adapter_core + /// do you think god stays in heaven + var/datum/port/input/north + var/datum/port/input/east + var/datum/port/input/south + var/datum/port/input/west + + var/datum/port/output/moved + var/datum/port/output/move_fail + + /// because he too lives in fear of what he's created + var/datum/port/input/target + var/datum/port/input/swap_hands + + var/datum/port/output/swapped_hands + + var/datum/port/input/left_click + var/datum/port/input/middle_click + var/datum/port/input/right_click + + var/datum/port/input/alt_click + var/datum/port/input/shift_click + var/datum/port/input/ctrl_click + + var/datum/port/output/clicked + + var/datum/port/input/get_hands + + var/datum/port/output/active_hand + var/datum/port/output/inactive_hand + var/datum/port/output/fetched_hands + + COOLDOWN_DECLARE(move_cooldown) + COOLDOWN_DECLARE(click_cooldown) + + var/click_delay = 0.8 SECONDS // Same as clicking + +/obj/item/circuit_component/mod_adapter_core/populate_ports() + . = ..() + north = add_input_port("North", PORT_TYPE_SIGNAL) + east = add_input_port("East", PORT_TYPE_SIGNAL) + south = add_input_port("South", PORT_TYPE_SIGNAL) + west = add_input_port("West", PORT_TYPE_SIGNAL) + moved = add_output_port("Successful Move", PORT_TYPE_SIGNAL) + move_fail = add_output_port("Failed Move", PORT_TYPE_SIGNAL) + + left_click = add_input_port("Left Click", PORT_TYPE_SIGNAL) + right_click = add_input_port("Right Click", PORT_TYPE_SIGNAL) + middle_click = add_input_port("Middle Click", PORT_TYPE_SIGNAL) + clicked = add_output_port("After Click", PORT_TYPE_SIGNAL) + + target = add_input_port("Target", PORT_TYPE_ATOM) + swap_hands = add_input_port("Swap Hands", PORT_TYPE_SIGNAL) + swapped_hands = add_output_port("Swapped Hands", PORT_TYPE_SIGNAL) + + alt_click = add_input_port("Alt Click", PORT_TYPE_NUMBER) + shift_click = add_input_port("Shift Click", PORT_TYPE_NUMBER) + ctrl_click = add_input_port("Ctrl Click", PORT_TYPE_NUMBER) + + get_hands = add_input_port("Get Currently Held Items", PORT_TYPE_SIGNAL) + active_hand = add_output_port("Active Hand", PORT_TYPE_ATOM) + inactive_hand = add_output_port("Inactive Hand", PORT_TYPE_ATOM) + fetched_hands = add_output_port("Fetched Held Items", PORT_TYPE_SIGNAL) + +/obj/item/circuit_component/mod_adapter_core/input_received(datum/port/input/port) + . = ..() + + var/mob/living/carbon/wearer = attached_module?.mod?.wearer + if (!istype(wearer)) + return + + if (COMPONENT_TRIGGERED_BY(get_hands, port) && attached_module.mod.gauntlets?.loc == wearer) + active_hand.set_output(wearer.get_active_held_item()) + active_hand.set_output(wearer.get_inactive_held_item()) + fetched_hands.set_output(COMPONENT_SIGNAL) + return + + if (COMPONENT_TRIGGERED_BY(swap_hands, port) && attached_module.mod.gauntlets?.loc == wearer) + wearer.swap_hand() + swapped_hands.set_output(COMPONENT_SIGNAL) + return + + if (COOLDOWN_FINISHED(src, move_cooldown) && attached_module.mod.boots?.loc == wearer) + var/move_dir = null + + if (COMPONENT_TRIGGERED_BY(north, port)) + move_dir = NORTH + if (COMPONENT_TRIGGERED_BY(east, port)) + move_dir = EAST + if (COMPONENT_TRIGGERED_BY(south, port)) + move_dir = SOUTH + if (COMPONENT_TRIGGERED_BY(west, port)) + move_dir = WEST + + if (!isnull(move_dir)) + COOLDOWN_START(src, move_cooldown, wearer.cached_multiplicative_slowdown) + if (!isnull(wearer.client)) + COOLDOWN_START(wearer.client, move_delay, wearer.cached_multiplicative_slowdown) + if (wearer.Move(get_step(get_turf(wearer), NORTH))) + moved.set_output(COMPONENT_SIGNAL) + else + move_fail.set_output(COMPONENT_SIGNAL) + return + + if (!COOLDOWN_FINISHED(src, click_cooldown) || attached_module.mod.gauntlets?.loc != wearer) + return + + if (!COMPONENT_TRIGGERED_BY(left_click, port) && !COMPONENT_TRIGGERED_BY(middle_click, port) && !COMPONENT_TRIGGERED_BY(right_click, port)) + return + + var/list/modifiers = list() + + if (alt_click.value) + modifiers[ALT_CLICK] = TRUE + if (shift_click.value) + modifiers[SHIFT_CLICK] = TRUE + if (ctrl_click.value) + modifiers[CTRL_CLICK] = TRUE + if (COMPONENT_TRIGGERED_BY(left_click, port)) + modifiers[LEFT_CLICK] = TRUE + if (COMPONENT_TRIGGERED_BY(middle_click, port)) + modifiers[MIDDLE_CLICK] = TRUE + if (COMPONENT_TRIGGERED_BY(right_click, port)) + modifiers[RIGHT_CLICK] = TRUE + + COOLDOWN_START(src, click_cooldown, click_delay) + INVOKE_ASYNC(src, PROC_REF(do_click), target.value, list2params(modifiers)) + +/obj/item/circuit_component/mod_adapter_core/proc/do_click(atom/target, params) + var/mob/living/carbon/wearer = attached_module?.mod?.wearer + if (!istype(wearer)) + return + wearer.ClickOn(target, params) + clicked.set_output(COMPONENT_SIGNAL) diff --git a/maplestation_modules/code/modules/wiremod/components/screen.dm b/maplestation_modules/code/modules/wiremod/components/screen.dm new file mode 100644 index 000000000000..ca6864906ad6 --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/components/screen.dm @@ -0,0 +1,42 @@ +/** + * # Screen component + * + * Displays text when examined + * Can flash the message to all viewers + * Returns entity when examined + */ +/obj/item/circuit_component/screen + display_name = "Screen" + desc = "A component that displays information. Activating the component will make it flash the message to all nearby viewers." + category = "Entity" + + /// The input port + var/datum/port/input/input_port + + /// Entity that examined the circuit + var/datum/port/output/viewer + var/datum/port/output/observed + + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + +/obj/item/circuit_component/screen/populate_ports() + input_port = add_input_port("Display text", PORT_TYPE_STRING) + viewer = add_output_port("Viewer", PORT_TYPE_ATOM) + observed = add_output_port("Observed", PORT_TYPE_SIGNAL) + +/obj/item/circuit_component/screen/input_received(datum/port/input/port) + var/atom/owner = parent.shell || parent + owner.visible_message("[icon2html(owner, viewers(owner))] [owner] flashes \"[span_notice(input_port.value)]\" on its screen.") + +/obj/item/circuit_component/screen/register_shell(atom/movable/shell) + RegisterSignal(shell, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + +/obj/item/circuit_component/screen/unregister_shell(atom/movable/shell) + UnregisterSignal(shell, COMSIG_ATOM_EXAMINE) + +/obj/item/circuit_component/screen/proc/on_examine(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + examine_list += span_notice("It's screen is displaying \"[input_port.value]\"") + viewer.set_output(user) + observed.set_output(COMPONENT_SIGNAL) diff --git a/maplestation_modules/code/modules/wiremod/components/tile_scanner.dm b/maplestation_modules/code/modules/wiremod/components/tile_scanner.dm new file mode 100644 index 000000000000..aa2ed186b464 --- /dev/null +++ b/maplestation_modules/code/modules/wiremod/components/tile_scanner.dm @@ -0,0 +1,43 @@ +/* + * # Tile Scanner component + * + * Outputs a list of all atoms on a given tile, offset from circuit's position + */ + +/obj/item/circuit_component/tile_scanner + display_name = "Tile Scanner" + desc = "A component that scans a tile based on an offset from the shell." + category = "Sensor" + + var/datum/port/input/x_pos + var/datum/port/input/y_pos + + var/scan_delay = 0.5 SECONDS + COOLDOWN_DECLARE(scan_cooldown) + + var/datum/port/output/scanned_atoms + + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL + + +/obj/item/circuit_component/tile_scanner/populate_ports() + x_pos = add_input_port("X offset", PORT_TYPE_NUMBER) + y_pos = add_input_port("Y offset", PORT_TYPE_NUMBER) + scanned_atoms = add_output_port("Scanned Objects", PORT_TYPE_LIST(PORT_TYPE_ATOM)) + trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL, order = 2) + +/obj/item/circuit_component/tile_scanner/input_received(datum/port/input/port, list/return_values) + if (!COOLDOWN_FINISHED(src, scan_cooldown)) + return + + if (isnull(x_pos.value) || isnull(y_pos.value)) + return + + var/turf/target_turf = locate(parent.shell.x + x_pos.value, parent.shell.y + y_pos.value, parent.shell.z) + if (!target_turf) + return + + var/list/result = list(target_turf) + target_turf.contents + COOLDOWN_START(src, scan_cooldown, scan_delay) + scanned_atoms.set_output(result) + trigger_output.set_output(COMPONENT_SIGNAL) diff --git a/tgui/packages/tgui/interfaces/ComponentPrinter.tsx b/tgui/packages/tgui/interfaces/ComponentPrinter.tsx index 7c8f16a8c535..0e6fddc4a1e1 100644 --- a/tgui/packages/tgui/interfaces/ComponentPrinter.tsx +++ b/tgui/packages/tgui/interfaces/ComponentPrinter.tsx @@ -77,7 +77,9 @@ const Recipe = (props: RecipeProps) => { const canPrint = !Object.entries(design.cost).some( ([material, amount]) => - !available[material] || amount > (available[material] ?? 0), + !available[material] || + amount > (available[material] ?? 0) || + !!design.print_error, ); return ( @@ -93,6 +95,31 @@ const Recipe = (props: RecipeProps) => { + {!!design.print_error && ( + +
+ +
+
+ )} + {design.can_delete && ( +
+ design.can_delete && act('delete', { designId: design.id }) + } + className={classes([ + 'FabricatorRecipe__Button', + 'FabricatorRecipe__Button--icon', + ])} + > + +
+ )} { @@ -99,7 +100,7 @@ const InputArea = (props: { onType: (value: string) => void; }) => { const { act, data } = useBackend(); - const { max_length, multiline } = data; + const { max_length, multiline, default_value } = data; const { input, onType } = props; const visualMultiline = multiline || input.length >= 30; @@ -122,6 +123,8 @@ const InputArea = (props: { onInput={(_, value) => onType(value)} placeholder="Type something..." value={input} - /> + > + {default_value} + ); };