diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index 9a55bb7127b..b8b361b8747 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -14985,9 +14985,7 @@
/obj/structure/window/reinforced{
dir = 1
},
-/obj/machinery/computer/exodrone_control_console{
- dir = 2
- },
+/obj/machinery/computer/exodrone_control_console,
/obj/effect/turf_decal/stripes/line{
dir = 1
},
@@ -46663,8 +46661,14 @@
/area/maintenance/port/aft)
"gKR" = (
/obj/structure/table/wood,
-/obj/item/paper_bin,
-/obj/item/pen,
+/obj/item/paper_bin{
+ pixel_x = 5;
+ pixel_y = 3
+ },
+/obj/item/pen{
+ pixel_x = 5;
+ pixel_y = 3
+ },
/obj/machinery/status_display/evac/directional/south,
/obj/effect/turf_decal/tile/neutral{
dir = 1
@@ -46676,6 +46680,13 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
+/obj/item/folder{
+ pixel_x = -6;
+ pixel_y = 2
+ },
+/obj/item/laser_pointer{
+ pixel_x = 3
+ },
/turf/open/floor/iron/dark,
/area/service/library)
"gKX" = (
@@ -58013,9 +58024,7 @@
/obj/structure/window/reinforced{
dir = 1
},
-/obj/machinery/computer/exoscanner_control{
- dir = 2
- },
+/obj/machinery/computer/exoscanner_control,
/obj/effect/turf_decal/stripes/line{
dir = 1
},
@@ -84306,8 +84315,7 @@
},
/obj/structure/table/reinforced,
/obj/item/paper_bin{
- pixel_x = -7;
- pixel_y = 0
+ pixel_x = -7
},
/obj/item/pen{
pixel_x = -7
@@ -110737,12 +110745,10 @@
/area/medical/surgery/room_b)
"xNJ" = (
/obj/machinery/light/directional/south,
-/obj/structure/table/wood,
-/obj/item/folder,
-/obj/item/laser_pointer{
- pixel_x = 3
- },
/obj/item/radio/intercom/directional/south,
+/obj/machinery/modular_computer/console/preset/curator{
+ dir = 1
+ },
/turf/open/floor/iron/dark,
/area/service/library)
"xNR" = (
diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
index 8eb51b423b7..30289a58aa9 100644
--- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm
+++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
@@ -27843,9 +27843,15 @@
/area/maintenance/space_hut/cabin)
"gZl" = (
/obj/structure/table/wood,
-/obj/item/pen/red,
+/obj/item/paper_bin{
+ pixel_x = -4;
+ pixel_y = 3
+ },
+/obj/item/pen/red{
+ pixel_x = -6
+ },
/obj/item/pen/blue{
- pixel_x = 5;
+ pixel_x = -1;
pixel_y = 5
},
/turf/open/floor/wood,
@@ -40696,7 +40702,9 @@
/turf/open/floor/iron/dark,
/area/ai_monitored/command/nuke_storage)
"osm" = (
-/obj/machinery/libraryscanner,
+/obj/machinery/modular_computer/console/preset/curator{
+ dir = 8
+ },
/turf/open/floor/wood,
/area/service/library)
"oss" = (
@@ -52282,10 +52290,7 @@
/area/medical/virology)
"uSm" = (
/obj/structure/table/wood,
-/obj/item/paper_bin{
- pixel_x = 1;
- pixel_y = 9
- },
+/obj/machinery/libraryscanner,
/turf/open/floor/wood,
/area/service/library)
"uSw" = (
diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm
index 317b9c4acde..b60b387d8b0 100644
--- a/_maps/map_files/KiloStation/KiloStation.dmm
+++ b/_maps/map_files/KiloStation/KiloStation.dmm
@@ -53935,9 +53935,17 @@
pixel_x = -4;
pixel_y = 4
},
-/obj/item/camera,
/obj/machinery/computer/security/telescreen/entertainment/directional/south,
/obj/effect/decal/cleanable/dirt,
+/obj/item/taperecorder{
+ pixel_x = 5;
+ pixel_y = 4
+ },
+/obj/item/camera,
+/obj/item/pen{
+ pixel_x = -7;
+ pixel_y = -5
+ },
/turf/open/floor/iron/dark,
/area/service/library)
"iKn" = (
@@ -58302,6 +58310,10 @@
/obj/machinery/light_switch/directional/north,
/obj/machinery/newscaster/security_unit/directional/west,
/obj/effect/decal/cleanable/dirt,
+/obj/item/storage/fancy/candle_box{
+ pixel_x = 4;
+ pixel_y = 4
+ },
/turf/open/floor/iron/dark,
/area/service/library)
"kBH" = (
@@ -70583,19 +70595,12 @@
/obj/effect/turf_decal/tile/neutral{
dir = 1
},
-/obj/structure/table/wood,
-/obj/item/storage/fancy/candle_box{
- pixel_x = 4;
- pixel_y = 4
- },
-/obj/item/taperecorder{
- pixel_x = -4;
- pixel_y = 4
- },
-/obj/item/pen,
/obj/machinery/light/small/directional/south,
/obj/item/radio/intercom/directional/south,
/obj/effect/decal/cleanable/dirt,
+/obj/machinery/modular_computer/console/preset/curator{
+ dir = 1
+ },
/turf/open/floor/iron/dark,
/area/service/library)
"pSI" = (
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index 76f11155bc1..253c8cdb987 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -69964,6 +69964,9 @@
/area/commons/dorms)
"uRq" = (
/obj/machinery/light_switch/directional/east,
+/obj/machinery/modular_computer/console/preset/curator{
+ dir = 8
+ },
/turf/open/floor/wood,
/area/service/library)
"uRy" = (
diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm
index d89b4fbb130..e4f87b4ae88 100644
--- a/_maps/map_files/tramstation/tramstation.dmm
+++ b/_maps/map_files/tramstation/tramstation.dmm
@@ -1980,8 +1980,20 @@
"ahj" = (
/obj/structure/table/wood,
/obj/effect/turf_decal/siding/wood/corner,
+/obj/item/clipboard,
/obj/item/folder,
-/obj/item/pen/red,
+/obj/item/pen/red{
+ pixel_x = -5
+ },
+/obj/item/pen{
+ pixel_x = 5
+ },
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/structure/window/reinforced{
+ dir = 1
+ },
/turf/open/floor/wood,
/area/service/library)
"ahl" = (
@@ -5284,12 +5296,13 @@
/area/cargo/qm)
"arj" = (
/obj/structure/table/wood,
+/obj/structure/window/reinforced,
/obj/item/camera_film{
pixel_x = 3;
pixel_y = 3
},
/obj/item/camera_film,
-/obj/structure/window/reinforced,
+/obj/item/storage/photo_album/library,
/turf/open/floor/wood,
/area/service/library)
"ark" = (
@@ -72049,13 +72062,15 @@
/turf/open/floor/iron/white,
/area/medical/virology)
"xNF" = (
-/obj/structure/table/wood,
/obj/effect/turf_decal/siding/wood{
dir = 4
},
-/obj/item/clipboard,
-/obj/item/pen,
-/obj/item/storage/photo_album/library,
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/machinery/modular_computer/console/preset/curator{
+ dir = 8
+ },
/turf/open/floor/wood,
/area/service/library)
"xNN" = (
diff --git a/code/__DEFINES/art.dm b/code/__DEFINES/art.dm
new file mode 100644
index 00000000000..473f7f3565e
--- /dev/null
+++ b/code/__DEFINES/art.dm
@@ -0,0 +1,5 @@
+
+///tgui tab portrait categories- they're the same across all portrait tguis.
+#define TAB_LIBRARY 1
+#define TAB_SECURE 2
+#define TAB_PRIVATE 3
diff --git a/code/game/objects/structures/artstuff.dm b/code/game/objects/structures/artstuff.dm
index f3c0f75e5eb..11c566b5a6f 100644
--- a/code/game/objects/structures/artstuff.dm
+++ b/code/game/objects/structures/artstuff.dm
@@ -52,6 +52,8 @@
var/author_ckey
var/icon_generated = FALSE
var/icon/generated_icon
+ ///boolean that blocks persistence from saving it. enabled from printing copies, because we do not want to save copies.
+ var/no_save = FALSE
// Painting overlay offset when framed
var/framed_offset_x = 11
@@ -372,7 +374,7 @@
update_appearance()
/obj/structure/sign/painting/proc/save_persistent()
- if(!persistence_id || !current_canvas)
+ if(!persistence_id || !current_canvas || current_canvas.no_save)
return
if(sanitize_filename(persistence_id) != persistence_id)
stack_trace("Invalid persistence_id - [persistence_id]")
diff --git a/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm b/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm
index ba785251b77..45ddb425039 100644
--- a/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm
+++ b/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm
@@ -1,14 +1,10 @@
-//Portrait picker! It's a tgui window that lets you look through all the portraits, and choose one as your AI.
-
-//very similar to centcom_podlauncher in terms of how this is coded, so i kept a lot of comments from it
-//^ wow! it's the second time i've said this! i'm a real coder now, copying my statement of copying other people's stuff.
-
-
-#define TAB_LIBRARY 1
-#define TAB_SECURE 2
-#define TAB_PRIVATE 3
-
+/**
+ * ## Portrait picker!!
+ *
+ * It's a tgui window that lets you look through all the portraits, and choose one as your AI.
+ * very similar to centcom_podlauncher in terms of how this is coded, so i kept a lot of comments from it
+ */
/datum/portrait_picker
var/client/holder //client of whoever is using this datum
diff --git a/code/modules/modular_computers/computers/machinery/console_presets.dm b/code/modules/modular_computers/computers/machinery/console_presets.dm
index 5909faf0767..e5cc515d119 100644
--- a/code/modules/modular_computers/computers/machinery/console_presets.dm
+++ b/code/modules/modular_computers/computers/machinery/console_presets.dm
@@ -107,6 +107,17 @@
hard_drive.store_file(new/datum/computer_file/program/chatclient())
hard_drive.store_file(new/datum/computer_file/program/arcade())
+// curator
+/obj/machinery/modular_computer/console/preset/curator
+ console_department = "Civilian"
+ name = "curator console"
+ desc = "A stationary computer. This one comes preloaded with art programs."
+ _has_printer = TRUE
+
+/obj/machinery/modular_computer/console/preset/curator/install_programs()
+ var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
+ hard_drive.store_file(new/datum/computer_file/program/portrait_printer())
+
// ===== CARGO CHAT CONSOLES =====
/obj/machinery/modular_computer/console/preset/cargochat
name = "cargo chatroom console"
diff --git a/code/modules/modular_computers/file_system/programs/portrait_printer.dm b/code/modules/modular_computers/file_system/programs/portrait_printer.dm
new file mode 100644
index 00000000000..b622cde0798
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/portrait_printer.dm
@@ -0,0 +1,83 @@
+
+///how much paper it takes from the printer to create a canvas.
+#define CANVAS_PAPER_COST 10
+
+/**
+ * ## portrait printer!
+ *
+ * Program that lets the curator browse all of the portraits in the database
+ * They are free to print them out as they please.
+ */
+/datum/computer_file/program/portrait_printer
+ filename = "PortraitPrinter"
+ filedesc = "Marlowe Treeby's Art Galaxy"
+ category = PROGRAM_CATEGORY_CREW
+ program_icon_state = "dummy"
+ extended_desc = "This program connects to a Spinward Sector community art site for viewing and printing art."
+ transfer_access = ACCESS_LIBRARY
+ usage_flags = PROGRAM_CONSOLE
+ requires_ntnet = TRUE
+ size = 9
+ tgui_id = "NtosPortraitPrinter"
+ program_icon = "paint-brush"
+
+/datum/computer_file/program/portrait_printer/ui_data(mob/user)
+ var/list/data = list()
+ data["library"] = SSpersistence.paintings["library"] ? SSpersistence.paintings["library"] : 0
+ data["library_secure"] = SSpersistence.paintings["library_secure"] ? SSpersistence.paintings["library_secure"] : 0
+ data["library_private"] = SSpersistence.paintings["library_private"] ? SSpersistence.paintings["library_private"] : 0 //i'm gonna regret this, won't i?
+ return data
+
+/datum/computer_file/program/portrait_printer/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/portraits/library),
+ get_asset_datum(/datum/asset/simple/portraits/library_secure),
+ get_asset_datum(/datum/asset/simple/portraits/library_private)
+ )
+
+/datum/computer_file/program/portrait_printer/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ //printer check!
+ var/obj/item/computer_hardware/printer/printer
+ if(computer)
+ printer = computer.all_components[MC_PRINT]
+ if(!printer)
+ to_chat(usr, "Hardware error: A printer is required to print a canvas.")
+ return
+ if(printer.stored_paper < CANVAS_PAPER_COST)
+ to_chat(usr, "Printing error: Your printer needs at least [CANVAS_PAPER_COST] paper to print a canvas.")
+ return
+ printer.stored_paper -= CANVAS_PAPER_COST
+
+ //canvas printing!
+ var/list/tab2key = list(TAB_LIBRARY = "library", TAB_SECURE = "library_secure", TAB_PRIVATE = "library_private")
+ var/folder = tab2key[params["tab"]]
+ var/list/current_list = SSpersistence.paintings[folder]
+ var/list/chosen_portrait = current_list[params["selected"]]
+ var/author = chosen_portrait["author"]
+ var/title = chosen_portrait["title"]
+ var/png = "data/paintings/[folder]/[chosen_portrait["md5"]].png"
+ var/icon/art_icon = new(png)
+ var/obj/item/canvas/printed_canvas
+ var/art_width = art_icon.Width()
+ var/art_height = art_icon.Height()
+ for(var/canvas_type in typesof(/obj/item/canvas))
+ printed_canvas = canvas_type
+ if(initial(printed_canvas.width) == art_width && initial(printed_canvas.height) == art_height)
+ printed_canvas = new canvas_type(get_turf(computer.physical))
+ break
+ printed_canvas.fill_grid_from_icon(art_icon)
+ printed_canvas.generated_icon = art_icon
+ printed_canvas.icon_generated = TRUE
+ printed_canvas.finalized = TRUE
+ printed_canvas.painting_name = title
+ printed_canvas.author_ckey = author
+ printed_canvas.name = "painting - [title]"
+ ///this is a copy of something that is already in the database- it should not be able to be saved.
+ printed_canvas.no_save = TRUE
+ printed_canvas.update_icon()
+ to_chat(usr, "You have printed [title] onto a new canvas.")
+ playsound(computer.physical, 'sound/items/poster_being_created.ogg', 100, TRUE)
diff --git a/tgstation.dme b/tgstation.dme
index 4432a414ff6..efb4a9fd5e5 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -31,6 +31,7 @@
#include "code\__DEFINES\ai.dm"
#include "code\__DEFINES\antagonists.dm"
#include "code\__DEFINES\aquarium.dm"
+#include "code\__DEFINES\art.dm"
#include "code\__DEFINES\atmospherics.dm"
#include "code\__DEFINES\atom_hud.dm"
#include "code\__DEFINES\bitfields.dm"
@@ -2816,6 +2817,7 @@
#include "code\modules\modular_computers\file_system\programs\ntdownloader.dm"
#include "code\modules\modular_computers\file_system\programs\ntmonitor.dm"
#include "code\modules\modular_computers\file_system\programs\ntnrc_client.dm"
+#include "code\modules\modular_computers\file_system\programs\portrait_printer.dm"
#include "code\modules\modular_computers\file_system\programs\powermonitor.dm"
#include "code\modules\modular_computers\file_system\programs\radar.dm"
#include "code\modules\modular_computers\file_system\programs\robocontrol.dm"
diff --git a/tgui/packages/tgui/interfaces/NtosPortraitPrinter.js b/tgui/packages/tgui/interfaces/NtosPortraitPrinter.js
new file mode 100644
index 00000000000..23312d6a09e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NtosPortraitPrinter.js
@@ -0,0 +1,140 @@
+import { resolveAsset } from '../assets';
+import { useBackend, useLocalState } from '../backend';
+import { Button, NoticeBox, Section, Stack, Tabs } from '../components';
+import { NtosWindow } from '../layouts';
+
+export const NtosPortraitPrinter = (props, context) => {
+ const { act, data } = useBackend(context);
+ const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
+ const [listIndex, setListIndex] = useLocalState(context, 'listIndex', 0);
+ const {
+ library,
+ library_secure,
+ library_private,
+ } = data;
+ const TABS = [
+ {
+ name: 'Common Portraits',
+ asset_prefix: "library",
+ list: library,
+ },
+ {
+ name: 'Secure Portraits',
+ asset_prefix: "library_secure",
+ list: library_secure,
+ },
+ {
+ name: 'Private Portraits',
+ asset_prefix: "library_private",
+ list: library_private,
+ },
+ ];
+ const tab2list = TABS[tabIndex].list;
+ const current_portrait_title = tab2list[listIndex]["title"];
+ const current_portrait_asset_name = TABS[tabIndex].asset_prefix + "_" + tab2list[listIndex]["md5"];
+ return (
+
+
+
+
+
+
+ {TABS.map((tabObj, i) => !!tabObj.list && (
+ {
+ setListIndex(0);
+ setTabIndex(i);
+ }}>
+ {tabObj.name}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ {current_portrait_title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Printing a canvas costs 10 paper from
+ the printer installed in your machine.
+
+
+
+
+
+
+ );
+};