From 1a30a33b7c5310618a2ca23bbe7888f6121a1cd1 Mon Sep 17 00:00:00 2001 From: mineymonkey Date: Tue, 4 Aug 2026 05:54:29 -0500 Subject: [PATCH] Another circuit fix (#22977) Reduces max phoron and steel storage potential of the IC Circuit Printer. Particularly steel is reduced when the cloning disk is installed and phoron is set to one bar instead of two. Also put in some safety measures for importing being lost due to server latency. Oh and well the radio command receiver is built in now. --- .../machinery/telecomms/broadcasting.dm | 5 ++ .../assemblies/electronic_radio_headset.dm | 23 ++++- .../integrated_electronics/core/printer.dm | 86 ++++++++++++++++--- .../integrated_electronics/subtypes/input.dm | 4 +- ...ther circuit fix Mineymonkey changelog.yml | 10 +++ .../tgui/interfaces/CircuitPrinter.tsx | 20 +++-- 6 files changed, 128 insertions(+), 20 deletions(-) create mode 100644 html/changelogs/another circuit fix Mineymonkey changelog.yml diff --git a/code/game/objects/structures/machinery/telecomms/broadcasting.dm b/code/game/objects/structures/machinery/telecomms/broadcasting.dm index 1529dbb950d..d0247d89e81 100644 --- a/code/game/objects/structures/machinery/telecomms/broadcasting.dm +++ b/code/game/objects/structures/machinery/telecomms/broadcasting.dm @@ -178,6 +178,11 @@ var/list/receive = get_hearers_in_radio_ranges(radios) + // Electronic radio headsets expose received traffic to their built-in + // command receiver independently of whether a mob can hear the message. + for(var/obj/item/radio/headset/circuitry/circuit_headset in radios) + circuit_headset.receive_circuit_radio_command(data["name"], message, get_frequency_name(frequency)) + // Cut out admins which have radio chatter disabled for (var/mob/R in receive) if(R.client && R.client.holder && !(R.client.prefs.toggles & CHAT_RADIO)) diff --git a/code/modules/integrated_electronics/core/assemblies/electronic_radio_headset.dm b/code/modules/integrated_electronics/core/assemblies/electronic_radio_headset.dm index 8df66d2d518..7b9d9266edd 100644 --- a/code/modules/integrated_electronics/core/assemblies/electronic_radio_headset.dm +++ b/code/modules/integrated_electronics/core/assemblies/electronic_radio_headset.dm @@ -14,6 +14,8 @@ 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 + // Built-in receiver that exposes radio messages to the installed circuit network. + var/obj/item/integrated_circuit/input/radio_command_receiver/circuit_radio_receiver = null /obj/item/radio/headset/circuitry/Initialize(mapload, printed = FALSE) @@ -26,6 +28,7 @@ /obj/item/radio/headset/circuitry/Destroy() QDEL_NULL(circuit_assembly) circuit_action = null + circuit_radio_receiver = null return ..() @@ -39,17 +42,20 @@ 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 + circuit_radio_receiver = locate(/obj/item/integrated_circuit/input/radio_command_receiver) in circuit_assembly.contents if(!circuit_action) circuit_action = new(circuit_assembly) circuit_assembly.force_add_circuit(circuit_action) + if(!circuit_radio_receiver) + circuit_radio_receiver = new(circuit_assembly) + circuit_assembly.force_add_circuit(circuit_radio_receiver) + default_action_type = /datum/action/item_action/integrated_circuit action_button_name = "Activate [capitalize_first_letters(name)]" @@ -93,7 +99,6 @@ 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 @@ -101,11 +106,16 @@ 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 + new_headset.circuit_radio_receiver = locate(/obj/item/integrated_circuit/input/radio_command_receiver) 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) + if(!new_headset.circuit_radio_receiver) + new_headset.circuit_radio_receiver = new(internal_assembly) + internal_assembly.force_add_circuit(new_headset.circuit_radio_receiver) + new_headset.default_action_type = /datum/action/item_action/integrated_circuit new_headset.action_button_name = "Activate [capitalize_first_letters(new_headset.name)]" @@ -258,3 +268,10 @@ return to_chat(H, SPAN_NOTICE("\The [src] states, \"[message]\"")) + + +/obj/item/radio/headset/circuitry/proc/receive_circuit_radio_command(speaker_name, message, channel) + if(!circuit_radio_receiver || circuit_radio_receiver.assembly != circuit_assembly) + return + + circuit_radio_receiver.receive_radio_command(speaker_name, message, channel, src) diff --git a/code/modules/integrated_electronics/core/printer.dm b/code/modules/integrated_electronics/core/printer.dm index 7ce0163ccf7..25bc6bf2f47 100644 --- a/code/modules/integrated_electronics/core/printer.dm +++ b/code/modules/integrated_electronics/core/printer.dm @@ -31,6 +31,11 @@ var/clone_blueprint_name = null var/clone_blueprint_export = null var/clone_blueprint_import_buffer = "" + var/clone_blueprint_import_id = null + var/clone_blueprint_import_expected_chunks = 0 + var/clone_blueprint_import_received_chunks = 0 + var/clone_blueprint_import_received_length = 0 + var/list/clone_blueprint_import_chunks = null var/clone_blueprint_export_visible = FALSE var/clone_blueprint_metal_cost = 0 var/clone_blueprint_phoron_cost = 0 @@ -45,13 +50,14 @@ /obj/item/integrated_circuit_printer/upgraded upgraded = TRUE can_clone = TRUE - max_metal = 500 - max_phoron = 20 + max_metal = 250 + max_phoron = 10 /obj/item/integrated_circuit_printer/Destroy() assembly_to_clone = null clone_item_to_clone = null + clone_blueprint_import_chunks = null return ..() @@ -206,8 +212,6 @@ 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 @@ -332,6 +336,60 @@ return TRUE + +/obj/item/integrated_circuit_printer/proc/receive_clone_blueprint_chunk(import_id, chunk_index, chunk_count, import_chunk, mob/user) + if(!istext(import_id) || !length(import_id) || !istext(import_chunk)) + to_chat(user, SPAN_WARNING("Invalid cloning blueprint transfer data was received.")) + return FALSE + + chunk_index = text2num(chunk_index) + chunk_count = text2num(chunk_count) + if(!chunk_index || !chunk_count || chunk_index < 1 || chunk_index > chunk_count) + to_chat(user, SPAN_WARNING("Invalid cloning blueprint chunk numbering was received.")) + return FALSE + + if(length(import_chunk) > IC_BLUEPRINT_CHUNK_LIMIT) + to_chat(user, SPAN_WARNING("That blueprint chunk is [length(import_chunk)] characters, but chunks must be [IC_BLUEPRINT_CHUNK_LIMIT] characters or less.")) + return FALSE + + if(clone_blueprint_import_id != import_id) + clone_blueprint_import_id = import_id + clone_blueprint_import_expected_chunks = chunk_count + clone_blueprint_import_received_chunks = 0 + clone_blueprint_import_received_length = 0 + clone_blueprint_import_chunks = new/list(chunk_count) + clone_blueprint_import_buffer = "" + else if(clone_blueprint_import_expected_chunks != chunk_count) + to_chat(user, SPAN_WARNING("The cloning blueprint transfer changed its expected chunk count.")) + return FALSE + + if(isnull(clone_blueprint_import_chunks[chunk_index])) + if(clone_blueprint_import_received_length + length(import_chunk) > IC_BLUEPRINT_BUFFER_LIMIT) + to_chat(user, SPAN_WARNING("The cloning blueprint import buffer cannot exceed [IC_BLUEPRINT_BUFFER_LIMIT] characters.")) + return FALSE + clone_blueprint_import_chunks[chunk_index] = import_chunk + clone_blueprint_import_received_chunks++ + clone_blueprint_import_received_length += length(import_chunk) + + if(clone_blueprint_import_received_chunks < clone_blueprint_import_expected_chunks) + return TRUE + + for(var/i = 1 to clone_blueprint_import_expected_chunks) + if(isnull(clone_blueprint_import_chunks[i])) + return TRUE + clone_blueprint_import_buffer = clone_blueprint_import_chunks.Join() + + clone_blueprint_import_id = null + clone_blueprint_import_expected_chunks = 0 + clone_blueprint_import_received_chunks = 0 + clone_blueprint_import_received_length = 0 + clone_blueprint_import_chunks = null + + var/import_success = import_clone_blueprint_text(clone_blueprint_import_buffer, user) + if(import_success) + clone_blueprint_import_buffer = "" + return import_success + /obj/item/integrated_circuit_printer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) @@ -384,6 +442,13 @@ return append_clone_blueprint_chunk(import_chunk, usr) + if(action == "receive_import_chunk_tgui") + if(!can_clone) + to_chat(usr, SPAN_WARNING("\The [src] needs a circuit cloner upgrade for that.")) + return FALSE + + return receive_clone_blueprint_chunk(params["import_id"], params["index"], params["count"], params["chunk"], usr) + if(action == "reject_oversized_import_tgui") var/blueprint_length = text2num(params["length"]) if(!blueprint_length) @@ -412,6 +477,11 @@ clone_blueprint_name = null clone_blueprint_export = null clone_blueprint_import_buffer = "" + clone_blueprint_import_id = null + clone_blueprint_import_expected_chunks = 0 + clone_blueprint_import_received_chunks = 0 + clone_blueprint_import_received_length = 0 + clone_blueprint_import_chunks = null clone_blueprint_export_visible = FALSE clone_blueprint_metal_cost = 0 clone_blueprint_phoron_cost = 0 @@ -670,9 +740,6 @@ 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 @@ -683,9 +750,6 @@ 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 @@ -739,6 +803,7 @@ if(linked_new_pin) target_pin.linked |= linked_new_pin + linked_new_pin.linked |= target_pin /proc/ic_copy_clone_value(value) @@ -958,6 +1023,7 @@ if(linked_pin) source_pin.linked |= linked_pin + linked_pin.linked |= source_pin /obj/item/integrated_circuit_printer/proc/find_matching_blueprint_builtin(obj/item/electronic_assembly/clone, circuit_path, list/used_builtins) diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm index 67bf0205154..4d6a6e7c834 100644 --- a/code/modules/integrated_electronics/subtypes/input.dm +++ b/code/modules/integrated_electronics/subtypes/input.dm @@ -1889,7 +1889,9 @@ 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 + complexity = 0 + size = -1 + removable = FALSE inputs = list( "radio reference" = IC_PINTYPE_REF ) diff --git a/html/changelogs/another circuit fix Mineymonkey changelog.yml b/html/changelogs/another circuit fix Mineymonkey changelog.yml new file mode 100644 index 00000000000..c649153bd50 --- /dev/null +++ b/html/changelogs/another circuit fix Mineymonkey changelog.yml @@ -0,0 +1,10 @@ +author: mineymonkey + + +delete-after: True + +changes: + - bugfix: Added some safeties to prevent import loss due to server latency. + - bugfix: Fixed a bug where cloned built-in circuits were not being properly registered. + - balance: Reduced the maximum amount of metal and phoron that can be stored in a printer from 500 -> 250 and 20 -> 10 respectively. No cloned circuits can hit those numbers, so this is just a safety measure to prevent accidental loss of materials. + - balance: Made the in-built radio command receiver cost no complexity to keep in-line with other in-built components. diff --git a/tgui/packages/tgui/interfaces/CircuitPrinter.tsx b/tgui/packages/tgui/interfaces/CircuitPrinter.tsx index 5f906c489ad..bbb9962fccf 100644 --- a/tgui/packages/tgui/interfaces/CircuitPrinter.tsx +++ b/tgui/packages/tgui/interfaces/CircuitPrinter.tsx @@ -11,6 +11,7 @@ import { useBackend, useLocalState } from '../backend'; import { Window } from '../layouts'; const BLUEPRINT_IMPORT_CHUNK_DELAY = 250; +const BLUEPRINT_IMPORT_TRANSPORT_CHUNK_LIMIT = 256; const sleep = (duration: number) => new Promise((resolve) => setTimeout(resolve, duration)); @@ -167,7 +168,10 @@ export const CircuitPrinter = (props) => { setIsImportingBlueprint(true); let offset = 0; - const chunkLimit = Math.max(1, data.blueprint_chunk_limit || 1000); + const chunkLimit = Math.min( + BLUEPRINT_IMPORT_TRANSPORT_CHUNK_LIMIT, + Math.max(1, data.blueprint_chunk_limit || 1000), + ); const bufferLimit = data.blueprint_buffer_limit || 500000; if (importText.length > bufferLimit) { @@ -179,20 +183,24 @@ export const CircuitPrinter = (props) => { } try { - await act('begin_import_buffer'); + const importId = `${Date.now()}-${Math.random()}`; + const chunkCount = Math.ceil(importText.length / chunkLimit); + let chunkIndex = 1; while (offset < importText.length) { const chunk = importText.slice(offset, offset + chunkLimit); - await act('append_import_chunk_tgui', { - chunk: encodeURIComponent(chunk), + act('receive_import_chunk_tgui', { + import_id: importId, + index: chunkIndex, + count: chunkCount, + chunk, }); offset += chunk.length; + chunkIndex++; await sleep(BLUEPRINT_IMPORT_CHUNK_DELAY); } - - await act('finish_import_buffer_tgui'); } finally { setIsImportingBlueprint(false); }