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); }