From 01a41bef0d28aa92f518815aa6d779dab8918108 Mon Sep 17 00:00:00 2001
From: SkyratBot <59378654+SkyratBot@users.noreply.github.com>
Date: Thu, 14 Oct 2021 22:20:22 +0200
Subject: [PATCH] [MIRROR] Printer Circuit Component [MDB IGNORE] (#8825)
* Printer Circuit Component (#62012)
I'm adding a circuit component that can print text string on a paper object in a variety of colors and font typefaces (currently only web-safe ones are available, maybe i'll add some fancy ones in the future but they'd need to be imported either through @ import of @ font-face in a separate CSS not imported by every tgui UI).
It's important to note that because the UI sanitizes new text inputed by users and not what's already written on the paper (so the pen_color and pen_font don't be purged in the process), we can't safely have these strings "printed" into the info variable directly, because of that these values will be stored in two new list variables, one for the text and one for font color, face and the signature. When the paper sheet UI is opened, these will be sanitized and then parsed into the text, so the next time the paper is edited we can clear these two lists.
Obviously better than a hacky byond proc - parsemarkdown() is outdated af -, albeit a bit messy... like the rest of paper code.
Requires #62033.
* Printer Circuit Component
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
---
code/__DEFINES/text.dm | 8 +
code/__DEFINES/traits.dm | 2 +
code/game/objects/items/devices/PDA/PDA.dm | 10 +-
code/game/objects/items/inspector.dm | 4 +-
code/game/objects/structures/votingbox.dm | 2 +-
.../modular_computers/hardware/printer.dm | 2 +-
code/modules/paperwork/carbonpaper.dm | 2 +-
code/modules/paperwork/paper.dm | 74 +++++++--
code/modules/paperwork/photocopier.dm | 7 +-
code/modules/religion/rites.dm | 4 +-
.../research/designs/wiremod_designs.dm | 5 +
code/modules/research/techweb/all_nodes.dm | 1 +
.../wiremod/components/action/printer.dm | 138 ++++++++++++++++
code/modules/wiremod/core/component.dm | 2 +
sound/machines/dotprinter.ogg | Bin 0 -> 52339 bytes
tgstation.dme | 1 +
tgui/packages/tgui/interfaces/PaperSheet.js | 147 +++++++++++-------
17 files changed, 334 insertions(+), 75 deletions(-)
create mode 100644 code/modules/wiremod/components/action/printer.dm
create mode 100644 sound/machines/dotprinter.ogg
diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm
index 58ab98b132b..48487531d0f 100644
--- a/code/__DEFINES/text.dm
+++ b/code/__DEFINES/text.dm
@@ -9,3 +9,11 @@
/// Simply removes the < and > characters, and limits the length of the message.
#define STRIP_HTML_SIMPLE(text, limit) (GLOB.angular_brackets.Replace(copytext(text, 1, limit), ""))
+
+///Index access defines for paper/var/add_info_style
+#define ADD_INFO_COLOR 1
+#define ADD_INFO_FONT 2
+#define ADD_INFO_SIGN 3
+
+///Adds a html style to a text string. Hacky, but that's how inputted text appear on paper sheets after going through the UI.
+#define PAPER_MARK_TEXT(text, color, font) "[text]\n \n"
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 5c5d667bf85..c1bb2815720 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -526,6 +526,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Trait applied when the MMI component is added to an [/obj/item/integrated_circuit]
#define TRAIT_COMPONENT_MMI "component_mmi"
+/// Trait applied when the MMI component is added to an [/obj/item/integrated_circuit]
+#define TRAIT_COMPONENT_PRINTER "component_printer"
/// If present on a [/mob/living/carbon], will make them appear to have a medium level disease on health HUDs.
#define TRAIT_DISEASELIKE_SEVERITY_MEDIUM "diseaselike_severity_medium"
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index ff5563b1d8c..6b028aa5745 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -1131,11 +1131,15 @@ GLOBAL_LIST_EMPTY(PDAs)
A.analyzer_act(user, src)
if (!scanmode && istype(A, /obj/item/paper) && owner)
- var/obj/item/paper/PP = A
- if (!PP.info)
+ var/obj/item/paper/paper = A
+ if (!paper.get_info_length())
to_chat(user, span_warning("Unable to scan! Paper is blank."))
return
- notehtml = PP.info
+ notehtml = paper.info
+ if(paper.add_info)
+ for(var/index in 1 to length(paper.add_info))
+ var/list/style = paper.add_info_style[index]
+ notehtml += PAPER_MARK_TEXT(paper.add_info[index], style[ADD_INFO_COLOR], style[ADD_INFO_FONT])
note = replacetext(notehtml, "
", "\[br\]")
note = replacetext(note, "
", "\[*\]")
note = replacetext(note, "", "\[list\]")
diff --git a/code/game/objects/items/inspector.dm b/code/game/objects/items/inspector.dm
index df7e35b9c85..83aec23cb0b 100644
--- a/code/game/objects/items/inspector.dm
+++ b/code/game/objects/items/inspector.dm
@@ -160,7 +160,7 @@
. += span_notice("\The [src] contains data on [scanned_area.name].")
else if(scanned_area)
. += span_notice("\The [src] contains data on a vague area on station, you should throw it away.")
- else if(info)
+ else if(get_info_length())
icon_state = "slipfull"
. += span_notice("Wait a minute, this isn't an encrypted inspection report! You should throw it away.")
else
@@ -358,7 +358,7 @@
. += span_notice("\The [src] contains no data on [scanned_area.name].")
else if(scanned_area)
. += span_notice("\The [src] contains no data on a vague area on station, you should throw it away.")
- else if(info)
+ else if(get_info_length())
. += span_notice("Wait a minute, this isn't an encrypted inspection report! You should throw it away.")
else
. += span_notice("Wait a minute, this thing's blank! You should throw it away.")
diff --git a/code/game/objects/structures/votingbox.dm b/code/game/objects/structures/votingbox.dm
index 4fa008b9455..c2261e733d1 100644
--- a/code/game/objects/structures/votingbox.dm
+++ b/code/game/objects/structures/votingbox.dm
@@ -117,7 +117,7 @@
to_chat(user,span_notice("You cast your vote."))
/obj/structure/votebox/proc/valid_vote(obj/item/paper/I)
- if(length_char(I.info) > VOTE_TEXT_LIMIT || findtext(I.info,"Voting Results:
"))
+ if(I.get_info_length() > VOTE_TEXT_LIMIT || findtext(I.info,"Voting Results:
"))
return FALSE
return TRUE
diff --git a/code/modules/modular_computers/hardware/printer.dm b/code/modules/modular_computers/hardware/printer.dm
index fc174d6fc45..cf3687b22c9 100644
--- a/code/modules/modular_computers/hardware/printer.dm
+++ b/code/modules/modular_computers/hardware/printer.dm
@@ -59,7 +59,7 @@
/// Number of sheets we're adding
var/num_to_add = 0
for(var/obj/item/paper/the_paper as anything in bin.papers) // Search for the first blank sheet of paper, then toss it in
- if(the_paper.info != "") // Uh oh, paper has words!
+ if(the_paper.get_info_length()) // Uh oh, paper has words!
continue
if(istype(the_paper, /obj/item/paper/carbon)) // Add both the carbon, and the copy
var/obj/item/paper/carbon/carbon_paper = the_paper
diff --git a/code/modules/paperwork/carbonpaper.dm b/code/modules/paperwork/carbonpaper.dm
index 66c5d51aba5..4fc883ba9af 100644
--- a/code/modules/paperwork/carbonpaper.dm
+++ b/code/modules/paperwork/carbonpaper.dm
@@ -10,7 +10,7 @@
icon_state = "paper"
else
icon_state = "paper_stack"
- if(info)
+ if(info || add_info)
icon_state = "[icon_state]_words"
return ..()
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 6ceb111bf7f..24d9cf01f6c 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -11,6 +11,10 @@
#define MODE_WRITING 1
#define MODE_STAMPING 2
+#define DEFAULT_ADD_INFO_COLOR "black"
+#define DEFAULT_ADD_INFO_FONT "Verdana"
+#define DEFAULT_ADD_INFO_SIGN "signature"
+
/**
* Paper is now using markdown (like in github pull notes) for ALL rendering
* so we do loose a bit of functionality but we gain in easy of use of
@@ -40,6 +44,15 @@
color = "white"
/// What's actually written on the paper.
var/info = ""
+ /**
+ * What's been written on the paper by things other than players.
+ * Will be sanitized by the UI, and finally
+ * added to info when the user edits the paper text.
+ */
+ var/list/add_info
+ /// The font color, face and the signature of the above.
+ var/list/add_info_style
+
var/show_written_words = TRUE
/// The (text for the) stamps on the paper.
@@ -70,9 +83,13 @@
if(colored)
new_paper.color = color
new_paper.info = info
+ new_paper.add_info_style = add_info_style.Copy()
else //This basically just breaks the existing color tag, which we need to do because the innermost tag takes priority.
- var/static/greyscale_info = regex("", "i")
+ new_paper.info = replacetext(info, greyscale_info, "nocolor=$1>")
+ for(var/list/style as anything in add_info_style)
+ LAZYADD(new_paper.add_info_style, list(list(DEFAULT_ADD_INFO_COLOR, style[ADD_INFO_FONT], style[ADD_INFO_SIGN])))
+ new_paper.add_info = add_info?.Copy()
new_paper.stamps = stamps?.Copy()
new_paper.stamped = stamped?.Copy()
new_paper.form_fields = form_fields.Copy()
@@ -86,11 +103,14 @@
* icons. You can modify the pen_color after if need
* be.
*/
-/obj/item/paper/proc/setText(text)
+/obj/item/paper/proc/setText(text, update_icon = TRUE)
info = text
+ add_info = null
+ add_info_style = null
form_fields = null
field_counter = 0
- update_icon_state()
+ if(update_icon)
+ update_appearance()
/obj/item/paper/pickup(user)
if(contact_poison && ishuman(user))
@@ -108,7 +128,7 @@
update_appearance()
/obj/item/paper/update_icon_state()
- if(info && show_written_words)
+ if((info || add_info) && show_written_words)
icon_state = "[initial(icon_state)]_words"
return ..()
@@ -136,7 +156,7 @@
return (BRUTELOSS)
/obj/item/paper/proc/clearpaper()
- info = ""
+ setText("", update_icon = FALSE)
stamps = null
LAZYCLEARLIST(stamped)
cut_overlays()
@@ -196,6 +216,21 @@
add_fingerprint(user)
fire_act(I.get_temperature())
+/obj/item/paper/proc/add_info(text, color = DEFAULT_ADD_INFO_COLOR, font = DEFAULT_ADD_INFO_FONT, signature = DEFAULT_ADD_INFO_SIGN)
+ LAZYADD(add_info, text)
+ LAZYADD(add_info_style, list(list(color, font, signature)))
+
+/obj/item/paper/proc/get_info_length()
+ . = length_char(info)
+ for(var/index in 1 to length(add_info))
+ var/style = LAZYACCESS(add_info_style, index)
+ if(style)
+ var/static/regex/sign_regex = regex("%s(?:ign)?(?=\\s|$)?", "igm")
+ var/signed_text = sign_regex.Replace(add_info[index], style[ADD_INFO_SIGN])
+ . += length_char(PAPER_MARK_TEXT(signed_text, style[ADD_INFO_COLOR], style[ADD_INFO_FONT]))
+ else
+ . += length_char(add_info[index])
+
/obj/item/paper/attackby(obj/item/P, mob/living/user, params)
if(burn_paper_product_attackby_check(P, user))
SStgui.close_uis(src)
@@ -206,7 +241,7 @@
P.attackby(src, user)
return
else if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon))
- if(length(info) >= MAX_PAPER_LENGTH) // Sheet must have less than 1000 charaters
+ if(get_info_length() >= MAX_PAPER_LENGTH) // Sheet must have less than 5000 charaters
to_chat(user, span_warning("This sheet of paper is full!"))
return
ui_interact(user)
@@ -226,6 +261,8 @@
. = ..()
if(.)
info = "[stars(info)]"
+ for(var/index in 1 to add_info)
+ add_info[index] = "[stars(add_info[index])]"
/obj/item/paper/ui_assets(mob/user)
return list(
@@ -239,17 +276,30 @@
ui = new(user, src, "PaperSheet", name)
ui.open()
-
/obj/item/paper/ui_static_data(mob/user)
. = list()
.["text"] = info
+ if(length(add_info))
+ .["add_text"] = add_info
+ .["add_color"] = list()
+ .["add_font"] = list()
+ .["add_sign"] = list()
+ for(var/index in 1 to length(add_info))
+ var/list/style = LAZYACCESS(add_info_style, index)
+ if(!islist(index) || length(style) < ADD_INFO_SIGN) // failsafe for malformed add_info_style.
+ var/list/corrected_style = list(DEFAULT_ADD_INFO_COLOR, DEFAULT_ADD_INFO_FONT, DEFAULT_ADD_INFO_SIGN)
+ LAZYADD(add_info_style, corrected_style)
+ style = corrected_style
+ .["add_color"] += style[ADD_INFO_COLOR]
+ .["add_font"] += style[ADD_INFO_FONT]
+ .["add_sign"] += style[ADD_INFO_SIGN]
+
.["max_length"] = MAX_PAPER_LENGTH
.["paper_color"] = !color || color == "white" ? "#FFFFFF" : color // color might not be set
.["paper_state"] = icon_state /// TODO: show the sheet will bloodied or crinkling?
.["stamps"] = stamps
-
/obj/item/paper/ui_data(mob/user)
var/list/data = list()
data["edit_usr"] = "[user.real_name]"
@@ -355,9 +405,10 @@
if(info != in_paper)
to_chat(ui.user, "You have added to your paper masterpiece!");
info = in_paper
+ add_info = null
+ add_info_style = null
update_static_data(usr,ui)
-
update_appearance()
. = TRUE
@@ -400,3 +451,6 @@
#undef MODE_READING
#undef MODE_WRITING
#undef MODE_STAMPING
+#undef DEFAULT_ADD_INFO_COLOR
+#undef DEFAULT_ADD_INFO_FONT
+#undef DEFAULT_ADD_INFO_SIGN
diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm
index 1f551c570c2..a11cac4e048 100644
--- a/code/modules/paperwork/photocopier.dm
+++ b/code/modules/paperwork/photocopier.dm
@@ -93,7 +93,7 @@
to_chat(usr, span_warning("[src] is currently busy copying something. Please wait until it is finished."))
return FALSE
if(paper_copy)
- if(!length(paper_copy.info))
+ if(!paper_copy.get_info_length())
to_chat(usr, span_warning("An error message flashes across [src]'s screen: \"The supplied paper is blank. Aborting.\""))
return FALSE
// Basic paper
@@ -234,7 +234,10 @@
give_pixel_offset(copied_paper)
//the font color dependant on the amount of toner left.
- copied_paper.info = " 10 ? "#101010" : "#808080"]>[copied_paper.info]"
+ var/chosen_color = toner_cartridge.charges > 10 ? "#101010" : "#808080"
+ copied_paper.info = "[copied_paper.info]"
+ for(var/list/style as anything in copied_paper.add_info_style)
+ style[ADD_INFO_COLOR] = chosen_color
copied_paper.name = paper_copy.name
toner_cartridge.charges -= PAPER_TONER_USE
diff --git a/code/modules/religion/rites.dm b/code/modules/religion/rites.dm
index 2742bb9ca96..0e7cfa57738 100644
--- a/code/modules/religion/rites.dm
+++ b/code/modules/religion/rites.dm
@@ -420,7 +420,7 @@
for(var/obj/item/paper/could_writ in get_turf(religious_tool))
if(istype(could_writ, /obj/item/paper/holy_writ))
continue
- if(could_writ.info) //blank paper pls
+ if(could_writ.get_info_length()) //blank paper pls
continue
writ_target = could_writ //PLEASE SIGN MY AUTOGRAPH
return ..()
@@ -614,7 +614,7 @@
/datum/religion_rites/sparring_contract/perform_rite(mob/living/user, atom/religious_tool)
for(var/obj/item/paper/could_contract in get_turf(religious_tool))
- if(could_contract.info) //blank paper pls
+ if(could_contract.get_info_length()) //blank paper pls
continue
contract_target = could_contract
return ..()
diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm
index 3b96e0c2453..99c75416452 100644
--- a/code/modules/research/designs/wiremod_designs.dm
+++ b/code/modules/research/designs/wiremod_designs.dm
@@ -263,6 +263,11 @@
id = "comp_typecast"
build_path = /obj/item/circuit_component/typecast
+/datum/design/component/printer
+ name = "Printer Component"
+ id = "comp_printer"
+ build_path = /obj/item/circuit_component/printer
+
/datum/design/component/pinpointer
name = "Proximity Pinpointer Component"
id = "comp_pinpointer"
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index ec09ac0c85e..24b84f5440e 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -242,6 +242,7 @@
"comp_ntnet_send",
"comp_pinpointer",
"comp_pressuresensor",
+ "comp_printer",
"comp_radio",
"comp_random",
"comp_router",
diff --git a/code/modules/wiremod/components/action/printer.dm b/code/modules/wiremod/components/action/printer.dm
new file mode 100644
index 00000000000..e6052d2a5cf
--- /dev/null
+++ b/code/modules/wiremod/components/action/printer.dm
@@ -0,0 +1,138 @@
+/**
+ * # Printer Component
+ *
+ * Allows for text strings to be printed on a paper. Requires a shell.
+ */
+/obj/item/circuit_component/printer
+ display_name = "Printer"
+ desc = "A component that prints a string input on a paper. Requires a shell and paper. \
+ Attack with paper to load them in the circuit. Use in hand to dump the bottom-most paper."
+ circuit_flags = CIRCUIT_FLAG_REFUSE_MODULE
+
+ /// Prints stuff on the leftmost paper in the loaded_papers list.
+ var/datum/port/input/print
+ /// The selected font-family used when printing text on paper
+ var/datum/port/input/option/typeface
+ /// The RGB values of the color used when printing text on paper
+ var/datum/port/input/text_color_red
+ var/datum/port/input/text_color_green
+ var/datum/port/input/text_color_blue
+ /// Used to eject the leftmost paper on the loaded_papers list.
+ var/datum/port/input/eject
+ /// The signature that'll replace any %s and %sign used when printing text on paper.
+ var/datum/port/input/signature
+
+ /// The list of papers currently loaded on the component
+ var/list/obj/item/paper/loaded_papers
+ /// The maximum paper capacity of the component
+ var/max_paper_capacity = 10
+
+/obj/item/circuit_component/printer/populate_ports()
+ print = add_input_port("Print", PORT_TYPE_STRING, trigger = .proc/print_on_paper)
+ text_color_red = add_input_port("Color (Red)", PORT_TYPE_NUMBER, trigger = null, default = 0)
+ text_color_green = add_input_port("Color (Green)", PORT_TYPE_NUMBER, trigger = null, default = 0)
+ text_color_blue = add_input_port("Color (Blue)", PORT_TYPE_NUMBER, trigger = null, default = 0)
+ signature = add_input_port("Signature", PORT_TYPE_STRING, trigger = null, default = "signature")
+ eject = add_input_port("Eject", PORT_TYPE_SIGNAL, trigger = .proc/eject_paper, order = 2)
+
+/obj/item/circuit_component/printer/populate_options()
+ var/static/typeface_options = list(
+ PRINTER_FONT,
+ PEN_FONT,
+ FOUNTAIN_PEN_FONT,
+ CRAYON_FONT,
+ "Impact",
+ "Webdings",
+ )
+ typeface = add_option_port("Typeface", typeface_options, trigger = null)
+
+/obj/item/circuit_component/printer/Destroy()
+ QDEL_LIST(loaded_papers)
+ return ..()
+
+/obj/item/circuit_component/printer/add_to(obj/item/integrated_circuit/add_to)
+ . = ..()
+ if(HAS_TRAIT(add_to, TRAIT_COMPONENT_PRINTER))
+ return FALSE
+ ADD_TRAIT(add_to, TRAIT_COMPONENT_PRINTER, src)
+
+/obj/item/circuit_component/printer/removed_from(obj/item/integrated_circuit/removed_from)
+ REMOVE_TRAIT(removed_from, TRAIT_COMPONENT_PRINTER, src)
+ return ..()
+
+/obj/item/circuit_component/printer/register_shell(atom/movable/shell)
+ RegisterSignal(shell, COMSIG_PARENT_ATTACKBY_SECONDARY, .proc/handle_secondary_attackby)
+ RegisterSignal(shell, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+
+/obj/item/circuit_component/printer/unregister_shell(atom/movable/shell)
+ UnregisterSignal(shell, list(COMSIG_PARENT_ATTACKBY_SECONDARY, COMSIG_PARENT_EXAMINE))
+
+/obj/item/circuit_component/printer/get_ui_notices()
+ . = ..()
+ . += create_ui_notice("Papers Stored: [length(loaded_papers)]/[max_paper_capacity]", "orange", "info")
+
+///Allows for paper to be loaded while inside the shell.
+/obj/item/circuit_component/printer/proc/handle_secondary_attackby(atom/movable/shell, obj/item/item, mob/living/attacker)
+ SIGNAL_HANDLER
+ if(istype(item, /obj/item/paper))
+ load_paper(item, attacker)
+ return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN
+
+/obj/item/circuit_component/printer/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+ examine_list += span_notice("It's a printer component installed in. Right-click with paper to reload it.")
+
+/obj/item/circuit_component/printer/attackby(obj/item/item, mob/living/user, params)
+ if(istype(item, /obj/item/paper))
+ load_paper(item, user)
+ else
+ return ..()
+
+/obj/item/circuit_component/printer/proc/load_paper(obj/item/paper/paper, mob/living/user)
+ if(length(loaded_papers) >= max_paper_capacity)
+ to_chat(user, span_warning("[src] can't hold any more paper."))
+ else if(user.transferItemToLoc(paper, src))
+ LAZYADD(loaded_papers, paper)
+ to_chat(user, span_notice("You load [paper] in [src]."))
+ else
+ to_chat(user, span_warning("[paper] seems to be stuck to your hand."))
+
+/obj/item/circuit_component/printer/attack_self(mob/living/user)
+ . = ..()
+ var/obj/item/paper/paper = loaded_papers?[1]
+ if(paper)
+ user.put_in_hands(paper)
+ to_chat(user, span_notice("You remove [paper] from [src]."))
+
+/obj/item/circuit_component/printer/Exited(atom/movable/movable)
+ . = ..()
+ if(movable in loaded_papers)
+ LAZYREMOVE(loaded_papers, movable)
+
+/obj/item/circuit_component/printer/pre_input_received(datum/port/input/port)
+ if(port != print)
+ return
+ text_color_red.set_value(clamp(text_color_red.value, 0, 255))
+ text_color_green.set_value(clamp(text_color_green.value, 0, 255))
+ text_color_blue.set_value(clamp(text_color_blue.value, 0, 255))
+ signature.set_value(reject_bad_text(signature.value, MAX_NAME_LEN, FALSE) || "signature")
+
+/obj/item/circuit_component/printer/proc/print_on_paper(datum/port/input/port)
+ if(!print.value)
+ return
+ var/obj/item/paper/paper = loaded_papers?[1]
+ if(!paper)
+ return
+ paper.add_info(print.value, rgb(text_color_red, text_color_green, text_color_blue), typeface.value, signature.value)
+ log_paper("Printer component writing to paper [paper.name]. [parent.get_creator()].")
+
+/obj/item/circuit_component/printer/proc/eject_paper(datum/port/input/port, list/return_values)
+ var/obj/item/paper/paper = loaded_papers?[1]
+ if(!paper)
+ return
+ playsound(src, "sound/machines/dotprinter.ogg", 30, TRUE)
+ if(isliving(parent?.shell?.loc))
+ var/mob/living/living_loc = parent.shell.loc
+ living_loc.put_in_hands(paper)
+ else
+ paper.forceMove(drop_location())
diff --git a/code/modules/wiremod/core/component.dm b/code/modules/wiremod/core/component.dm
index b7a9fb5cfc1..357858be7a7 100644
--- a/code/modules/wiremod/core/component.dm
+++ b/code/modules/wiremod/core/component.dm
@@ -81,6 +81,8 @@
trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL, order = 2)
if(circuit_flags & CIRCUIT_FLAG_INSTANT)
ui_color = "orange"
+ if(circuit_flags & CIRCUIT_FLAG_REFUSE_MODULE)
+ desc += " Incompatible with module components."
/obj/item/circuit_component/Destroy()
if(parent)
diff --git a/sound/machines/dotprinter.ogg b/sound/machines/dotprinter.ogg
new file mode 100644
index 0000000000000000000000000000000000000000..6ab9cd9421af2647d77c030aa1886c19c68cb940
GIT binary patch
literal 52339
zcmce-eOOahx;MP?LD+$i?hS!%Fxq_Z5ioSJ`I?}1CP2cM0SP3Th~rGyphCnBAIjL7
z={X_9xDz4;MH>-&040V19#X|lb6tmxGtc-gu$?o{
zd9UkvuJ@mJUpqT{Wv{*VUiZ4!egE#?y0dN1o+>~G-)zhy-i@oLcP@`0v<Q=D_YV#8YTfRJ
z#$B|q=S^TvI%@WncY|tHHL;+tJ^wGPoxf?SDc;g$G!H+}qDcM`XKDb>Ve-^o-}Y0GqQJFxQyZM)uYJMezn>)Tz2cKq`7?Y}&<
zWAV_=KOL&UZQtzw`TFnvw7Rc#T|ff^;@lG}sY9ZKq2=5mhSfGk2X(6=5wMSnQjeY~
zaql*rZ#Z?Se)Q75n{P{RzRg*y0%)+x>Ne2O=l*}~ySJa%^?%RVr_U-)(qR)-w`1;GzC^RGi`
zRHo0hWdES`mG@iA-iM-CmB{~jYhTkB*b(9B-6u?DB(u9_O)O^EEIx=7aGpO2R-gtS
z3i@vM{oz(Fx9|4@BfL{{Q+DmCly^s5r`ATWaW;Mhj%DhNlfi+LxBFUWOE`VQ&(B_q
zJ{3Mbau@y<|EQlEim!q78t(Rmk9=T&_in%4_p#?)9z3)6o=TZ~SNpuZZ`5MsNBy%?
zxgWth?DtRYeV4rxp8dJ5|KD%1SAYAT>Z5?;Re&rPx(Z82Iw~sTQ-=3j)_N`gz>2PE@wbOHRer9;XOBA4Zb&Y?mvK|NdKjCY-SpYS
zG+MwRpcYHBp%x#R{P?Zb^SQ-)hbQk5X(mv-%V=k@KEzL_x3r_Jx)Kk#=mXLX8*
za=TWFk23#z<_t)>mgQaIlB0>+k0!r%VvoIVczhw
z008{moULniL{pi*P?^0@nXc2m@_(K&@ajTV*<2PB>~R3%q5cHJ2c4DC$L6b_Oc@*m
zGR*otafsJ(!g|e>e?wC~R2w1uz3klGT${7!J{fT`BY*qTSK6Gy!>{0{^FK*sL9@pM
z8UR=&qLRexUv3#H`bzrS75f&<{P4A`BacenD*D604WQ(!gB!|@ZRvS*Ckwg+8W(=z
z@&|<*>1H_%bSA#Gq2$XgN8t@+JfY)++rHtqF{-zc9m=_^!M+pj*F><`m*P-svTOR@
z`s8Ht+9X1
zJ*PW4Hf48vTXe7p-SBPoodfov(P))!SKs-Fg$3mtEPlFH^hWAp
zbyaOA-JfNYy+7*`rdvoYv-E{mtS5uu=AJa>%HF=
ze{dWY0Wj0Tff>ZyP(TsV%n|@Pp#txEZNoJSFVBuRdH$v
zZ|xGD<<&ZKi?B)u@u>eyZ(0yEG}
z16oB0Ig$bJ_-R1kFYLL+F0@2MZD)hCbRg$ngL0mOeN4LMlR`m3q1Z@|Rd3SXTpb*`U*ChDqW$nVMv~>%AClwl4Vsn8aEN85%sWL0hP(M=&1Pov%Si(9^
z{tRwig%ws50xH=OL`lZ{cu7`W66lTq3j7SOcDAHsB%?6(k)Zc(^{(#9**wYcCpNApX^gNyw!LThhzr!P09HZ^q;
zuWc<4RaTma(8v8%=O^Q8Z$SX|!@cRD>PVodZ-Ov!qUM10*pSZ;hl>tg1E9MS3J|YV
zrw*?|Gk5Q*++9_rQ0`Ek=b+q)kNekvTkGr^ddq#Z2H~I+TrEORxLSm0YV|eXu%mZi6=bz%o!&zg
zP10Q=!bh#FX4Ol&E}r_c_VSpuot@Qf7;PGzA6UUYfZphI?-%9e)huEJ4)K0(p@zX1TN}9E($x#khcI6f4=;p2zhJE>ef+lz@ZIm`J`#L0)cL8ytNr<=
zUS;XGXRsBhoNL;!c7T5S+dH5bDofel+=1pxhI^yGsh0#F1YtD1cW|}x;HtZa(px>C
z@IkOW$=Ze024FEdf9=BRgI0^rpSE@e57r9U=(iUnN!IAq2d&myyYT!$Yp($xJ_yGX
z&Py{8r=hs^$E{y!G%3AUN
z|HIZ*5q#VG|Bq+1#jtf;^PQN&=*m6u`h?~cHZJO3Q4X6pjKj4$MMe
zCEtcpfQApy^b;E|Tr{2e<;byLebV7i*&oC^oQ@yX
zZXB%TR*(9Iqc_3W!z=)Ph4noqJ*>hA?JO6KJ`2oO=>^xCFtmH?j6^2k$IOCZ!W#fxh{fz=Rx_3BjWFUJyo_OqWJ
z{Mo_gmY=o#^x3mdpFL}M_N?jIv$ki?4*c+gChf^n|C#YGbywd0$dmEir@MZ3{nGei
zkQ=-1$wf!?IGL)R^Tu@=PH&nVT^PxApVm(+h<#-hqd(buE}i8VyX1sV|H5oOOrcH{
z;6~Q^z~QB*O0%d%5kiGzlae3z0*3OoCXz;;%DBaPW*|9nx-!PNgVDQC3_jNV<`c_*
zrH}T2_eTHWPS~HW?EGB2WA37XsoAk(VO&3#C0i)3(p>TWr>T$Jt8?6>=)s0+U!VKI
z#58xanhB-Z{`4TUYGQ`WLR2O@fh@89^2_t8{u8dhjNTdj>&oT0=4$fGe=hvz%iqyoLEJ}1
z3^yds``+>3gwdcLjrdTtAS+W}F*l4F)@6>S^^Q~|!QL*Rq79)ugQ&WCODq;kMARDj
zSZ7kSh8bM3qj8a9TxyvffM8OToIEg)EFKsf5)2Ls6rlh1kIr8BAn8-=&oeRiJNM^Y
zQNAL2>D`8eHfra5pnN(@?c0Acw!~02CsQ7Y)%ivns{I!k``78#b?*G!bnZyH8$R2d
z&*bNWgJ#h|x`p1`7l)R%m_*JFlo!e1Tl2n<&{qhA_J()}HCKo%MMeslLOGlVZDP~9
zdy8=!AFaRqdEtD{m@i#Fd&SVmX>3%F*}~Kf_B3YODZ_YN_B7j;T0Z8ki4C9Q{i4c$
z&!3Z)2%pOJgF+!Q6fKbY(d(WDWTqswzMaw1#mS|I=iAJIo?4lK!}ZHzlh{!kA|*fk
z=!S6Ud%vz7oc`&djfbhP|Fi25_s1jab4-(SCQnE8=lX((>GF-92J#)!CqKI>Tc_rY
zo_4F$hw9%J-&~GHfO#qgyaguup^-!_2yUKQmah~mX5wXFwt{~Gpq#GqYs`}2V}Xmk
z^LRCx1%B~CTgy`5&)9z*{B`5z+@|HfP_J*!L;>nQB
zVLJQi1;H<#h}sq#vYnONAxZ->lr~jM;K<>EFHo-AeRlAwv4kRI*pgjt_RFP~0t(6I
z*82=C^d3ru$VG3tEM{jfPHVi=h73Ho@JYn>9T(Tr>;ELZoPSThZrvQ$R~1Y%={lwQ
zf^@yqhU@3hQ=uEop5wa)4PPvzx#H%k(?)xtY>_1mCjzTs8ZjVbZYd4a0?QEmwIeBT
z6s-~~>*OL)M0I|?bi*4{ByB^XM&V-~1ZS_8?$7vq>kIR{`o@$gbHDydvGaURPTXeA
z=CD!f$oN=}w~aDz8aa&3x5kr6^8%TmA2qxeB>PPFjkSBT;bXz!d^v|!NEt*YiUWfP
zXNrk*h9Q=AnS|Ae%8(6Va{7jCW)0meM7H`xay&$%N6_x;)+w{{G6wVReddi4^$AYi
z7JT+V`(sb3aF^1Z!P7xvOqhZCVn=K4yKFnrsf$yEy_naQaUt+wjn8GF`rN4`@HG4&
z5RxhLev*Gf9UoNbA_g;#t29>YzmkMbJ(}wF9*M=a&%E7zs5>lH^^;nUz2<-*e;5ny!=$=TxNqerj}4M>XRvIq}G&W
z4KJwCfY>gl)zTMH#JH%H#Iw0ui%Yvw&M545_7?~@B$Ck-2k|$=Mog5Op3d?#(6&aM
z1rkTT!CIU+1PUpf&P|F2tj;ANv&c%xSZD=@UL(Y>0$lnlLc^O#F@DM{Kz$KM?a2=de0EYDBDL5}I_&
zVGOs_Nd5T;UDkVWOKd5Elguei_niUIJ|T-^oB@X!20HxGdy+_$bZ*F7ZT?o3_k=Q;-G==
z<*Zw-U>^JKuReO|R1|FR`nrGn;>Y@QrlPw+E+=H^?y$M6edJ38m<9@eV#mU6Pw-Wr
z{c-(dLe5CuKYw4{xVMN4^`hw9r-4`RYes7vlu_>6T%9-JEK|?X&Xq?o^UB8bzQwY}
zwrPJ>$i_*J!OEc1DNn+
zPADv94P-(8gXT%TILK=6cevg8KiSzC;>*90Vc;D
z@vCIVXdKp|c3d6J=rGOn51e%tkXgqDFOE)mUb{k*DBk=k-yVX*X5lLgI8p8-tL#N8Db!_mj;3h#7z9gP<(k
zrJp=55$if?5+ZP$WKoKU=n|6n{Nwz*vMUcj!TKjApW{A6
zL2=vlRa$9I=K^m!FI^w2A;$HZiqQrsw7uuzBD>mr3Am-2!Bu!2ChyqU?QGXYCsE=B^lIK-Y(pd3WWW2`hW0iGdJCfWdpy)_VH
zmjDT^9m9jz9uEG<%!wC>mjog#G^MmseuMM`Lfb>5HyB*o{f!=0xQca{-Z_lXrPgWb=AAWseWJWBEhLEtPd|zBYrN
z@s^L-9vdOpN{;hi^n`)V!LCc;^Jt_EZ}SRG5p4qCQThJh%U0_G4o^b@Nls1%L!o=;
zMRC~+Ht(WA$N5(Ij2!1OZ9`{2|2J<1Gh8L9kdl=#V=n6hl
zW}rYGR7awYQt|-=P3#CH
zHZ9HM63YQpzJAWbOWR$EW^9B7t$=#Xbs+Z
z4dCrGTE}6HsEaZRkSVOmjO0n^G#(?S3GD$!PFzrY5ZR{0m{&iVK;x!{BoUB_2X7VL
zj27IMeEs{vFSni9#YXFS|Eop(=a)8PXsm)6T!wVHyFo%#pPft=*H-N-=jld+7v