Ports AI Portrait Picker + Curator Portrait Picker [PORT] [BOUNTY] (#11508)

* Gives curators a console for printing portraits not in the round onto canvases (#59146)

* commit

* AIs can now select portraits as their display (#53994)

A new option has been added to the AI display radial menu alongside random, "Portrait"

It opens a small menu that lets you peruse all of the portraits and select one as your display.

This can let avid artists draw exactly what they want their AI to be like, and not-so-avid artists to bring tears to my eyes.

* cummit

* e

* boom

* map merge + icon

* Update YogStation.dmm

* oops

Co-authored-by: tralezab <40974010+tralezab@users.noreply.github.com>
This commit is contained in:
TheGamerdk
2021-06-13 12:22:06 +02:00
committed by GitHub
parent 420699d43f
commit a0362cb3d1
22 changed files with 627 additions and 113 deletions

View File

@@ -13170,6 +13170,9 @@
/obj/machinery/newscaster{
pixel_x = -32
},
/obj/machinery/modular_computer/console/preset/curator{
dir = 4
},
/turf/open/floor/carpet/exoticpurple,
/area/library)
"aCU" = (

View File

@@ -57216,7 +57216,9 @@
/turf/open/floor/circuit/telecomms/mainframe,
/area/tcommsat/server)
"exz" = (
/obj/item/twohanded/required/kirbyplants/random,
/obj/machinery/modular_computer/console/preset/curator{
dir = 4
},
/turf/open/floor/wood,
/area/library)
"eyM" = (

View File

@@ -19952,7 +19952,6 @@
/obj/structure/extinguisher_cabinet{
pixel_x = -26
},
/obj/machinery/libraryscanner,
/obj/effect/turf_decal/tile/neutral{
dir = 1
},
@@ -19963,6 +19962,9 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
/obj/machinery/modular_computer/console/preset/curator{
dir = 4
},
/turf/open/floor/plasteel/dark,
/area/library)
"aUX" = (
@@ -44707,6 +44709,7 @@
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
dir = 6
},
/obj/machinery/libraryscanner,
/turf/open/floor/wood,
/area/library)
"vQD" = (

View File

@@ -13,6 +13,30 @@
},
/turf/open/floor/plating,
/area/crew_quarters/heads/hos)
"aac" = (
/obj/machinery/requests_console{
department = "Security";
departmentType = 5;
pixel_x = -30
},
/obj/machinery/camera{
c_tag = "Brig Control Room";
dir = 4
},
/obj/machinery/light{
dir = 8
},
/obj/structure/bed/dogbed{
desc = "Jacob's bed! Looks comfy";
name = "Jacob's bed"
},
/mob/living/simple_animal/pet/dog/pug{
desc = "Much better at protecting the armory than your average warden.";
name = "Warden Jacob";
real_name = "Warden Jacob"
},
/turf/open/floor/plasteel/showroomfloor,
/area/security/warden)
"aad" = (
/obj/machinery/gulag_processor,
/turf/open/floor/plasteel,
@@ -79,6 +103,22 @@
/obj/machinery/holopad,
/turf/open/floor/plasteel,
/area/security/prison)
"aan" = (
/obj/structure/bed/dogbed/ian,
/obj/structure/sign/painting{
persistence_id = "public";
pixel_y = 32
},
/obj/item/toy/figure/hop{
layer = 2.89;
pixel_x = 8;
pixel_y = -5
},
/mob/living/simple_animal/pet/dog/corgi/Ian{
dir = 8
},
/turf/open/floor/plasteel,
/area/crew_quarters/heads/hop)
"aao" = (
/obj/machinery/light{
dir = 4
@@ -107,6 +147,32 @@
},
/turf/open/floor/plasteel/dark,
/area/ai_monitored/security/armory)
"aar" = (
/obj/structure/flora/junglebush,
/obj/structure/flora/ausbushes/sparsegrass,
/obj/item/toy/figure/geneticist,
/mob/living/carbon/monkey,
/turf/open/floor/grass,
/area/medical/genetics)
"aas" = (
/obj/effect/turf_decal/siding/thinplating{
dir = 8
},
/obj/machinery/requests_console{
department = "Janitorial";
departmentType = 1;
pixel_y = -32
},
/obj/effect/decal/cleanable/dirt,
/obj/structure/disposalpipe/segment{
dir = 8
},
/mob/living/simple_animal/hostile/lizard{
name = "Wags-His-Tail";
real_name = "Wags-His-Tail"
},
/turf/open/floor/plasteel,
/area/janitor)
"aat" = (
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plasteel,
@@ -234,6 +300,22 @@
},
/turf/open/floor/plasteel/dark,
/area/crew_quarters/fitness)
"aaF" = (
/obj/structure/filingcabinet/chestdrawer,
/obj/effect/turf_decal/tile/neutral{
dir = 1
},
/obj/effect/turf_decal/tile/neutral,
/obj/effect/turf_decal/tile/neutral{
dir = 4
},
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
/obj/item/toy/figure/ce,
/mob/living/simple_animal/parrot/Poly,
/turf/open/floor/plasteel/dark,
/area/crew_quarters/heads/chief)
"aaG" = (
/obj/structure/window/reinforced,
/obj/structure/chair{
@@ -32678,30 +32760,6 @@
"cFl" = (
/turf/closed/wall,
/area/maintenance/solars/port/aft)
"cFx" = (
/obj/machinery/requests_console{
department = "Security";
departmentType = 5;
pixel_x = -30
},
/obj/machinery/camera{
c_tag = "Brig Control Room";
dir = 4
},
/obj/machinery/light{
dir = 8
},
/mob/living/simple_animal/pet/dog/pug{
desc = "Much better at protecting the armory than your average warden.";
name = "Warden Jacob";
real_name = "Warden Jacob"
},
/obj/structure/bed/dogbed{
desc = "Jacob's bed! Looks comfy";
name = "Jacob's bed"
},
/turf/open/floor/plasteel/showroomfloor,
/area/security/warden)
"cFH" = (
/obj/structure/cable{
icon_state = "2-4"
@@ -33080,22 +33138,6 @@
},
/turf/open/floor/plasteel/dark,
/area/engine/engineering)
"cMX" = (
/obj/structure/filingcabinet/chestdrawer,
/obj/effect/turf_decal/tile/neutral{
dir = 1
},
/obj/effect/turf_decal/tile/neutral,
/obj/effect/turf_decal/tile/neutral{
dir = 4
},
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
/mob/living/simple_animal/parrot/Poly,
/obj/item/toy/figure/ce,
/turf/open/floor/plasteel/dark,
/area/crew_quarters/heads/chief)
"cNa" = (
/obj/machinery/door/window/northleft{
dir = 4;
@@ -35197,6 +35239,12 @@
},
/turf/open/floor/circuit,
/area/ai_monitored/turret_protected/ai)
"eeH" = (
/obj/machinery/modular_computer/console/preset/curator{
dir = 4
},
/turf/open/floor/wood,
/area/library)
"eeI" = (
/obj/structure/table/wood,
/obj/item/canvas/twentythreeXnineteen{
@@ -42748,25 +42796,6 @@
},
/turf/open/floor/plating,
/area/quartermaster/storage)
"jrd" = (
/obj/effect/turf_decal/siding/thinplating{
dir = 8
},
/mob/living/simple_animal/hostile/lizard{
name = "Wags-His-Tail";
real_name = "Wags-His-Tail"
},
/obj/machinery/requests_console{
department = "Janitorial";
departmentType = 1;
pixel_y = -32
},
/obj/effect/decal/cleanable/dirt,
/obj/structure/disposalpipe/segment{
dir = 8
},
/turf/open/floor/plasteel,
/area/janitor)
"jrf" = (
/obj/machinery/advanced_airlock_controller{
pixel_y = 24
@@ -49386,13 +49415,6 @@
},
/turf/open/floor/plating,
/area/quartermaster/storage)
"nPI" = (
/obj/structure/flora/junglebush,
/obj/structure/flora/ausbushes/sparsegrass,
/mob/living/carbon/monkey,
/obj/item/toy/figure/geneticist,
/turf/open/floor/grass,
/area/medical/genetics)
"nQh" = (
/obj/structure/grille/broken,
/obj/effect/decal/cleanable/glass,
@@ -53646,22 +53668,6 @@
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
/turf/open/floor/plating,
/area/maintenance/port/fore)
"qGm" = (
/obj/structure/bed/dogbed/ian,
/obj/structure/sign/painting{
persistence_id = "public";
pixel_y = 32
},
/mob/living/simple_animal/pet/dog/corgi/Ian{
dir = 8
},
/obj/item/toy/figure/hop{
layer = 2.89;
pixel_x = 8;
pixel_y = -5
},
/turf/open/floor/plasteel,
/area/crew_quarters/heads/hop)
"qGp" = (
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 4
@@ -93388,7 +93394,7 @@ bjE
knP
bbX
bmo
qGm
aan
bpb
bqz
blt
@@ -94875,7 +94881,7 @@ aEq
aEw
oJs
aHt
cFx
aac
agQ
ahv
ahQ
@@ -96486,7 +96492,7 @@ bBt
bCv
dNs
mKr
jrd
aas
bCv
bBR
eEO
@@ -96767,7 +96773,7 @@ alp
amX
aiv
bZD
cMX
aaF
ccj
cdm
bCR
@@ -107269,7 +107275,7 @@ hah
bfL
cDK
bon
nPI
aar
sMx
auz
hZk
@@ -111363,7 +111369,7 @@ aPg
aQr
aFu
aTb
aIt
eeH
pfS
aYW
aYW

View File

@@ -82276,7 +82276,6 @@
/turf/open/floor/plasteel,
/area/maintenance/port)
"cwy" = (
/obj/item/twohanded/required/kirbyplants/random,
/obj/effect/turf_decal/tile/neutral{
dir = 1
},
@@ -82287,6 +82286,7 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
/obj/machinery/modular_computer/console/preset/curator,
/turf/open/floor/plasteel/dark,
/area/library)
"cwz" = (

View File

@@ -69982,6 +69982,9 @@
/obj/machinery/light_switch{
pixel_x = 28
},
/obj/machinery/modular_computer/console/preset/curator{
dir = 8
},
/turf/open/floor/wood,
/area/library)
"cVh" = (

3
code/__DEFINES/art.dm Normal file
View File

@@ -0,0 +1,3 @@
///tgui tab portrait categories- they're the same across all portrait tguis.
#define TAB_PUBLIC 1

View File

@@ -126,6 +126,7 @@
#define INIT_ORDER_TIMER 1
#define INIT_ORDER_DEFAULT 0
#define INIT_ORDER_AIR -1
#define INIT_ORDER_PERSISTENCE -2 //before assets because some assets take data from SSPersistence
#define INIT_ORDER_ASSETS -4
#define INIT_ORDER_ICON_SMOOTHING -5
#define INIT_ORDER_OVERLAY -6
@@ -137,7 +138,6 @@
#define INIT_ORDER_PATH -50
#define INIT_ORDER_DISCORD -60
#define INIT_ORDER_EXPLOSIONS -69
#define INIT_ORDER_PERSISTENCE -95
#define INIT_ORDER_STATPANELS -98
#define INIT_ORDER_DEMO -99 // To avoid a bunch of changes related to initialization being written, do this last
#define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init.

View File

@@ -77,6 +77,7 @@ GLOBAL_LIST_INIT(ai_core_display_screens, list(
"Nanotrasen",
"Not Malf",
"Plain",
"Portrait",
"President",
"Random",
"Rainbow",
@@ -96,6 +97,10 @@ GLOBAL_LIST_INIT(ai_core_display_screens, list(
else
if(input == "Random")
input = pick(GLOB.ai_core_display_screens - "Random")
if(input == "Portrait")
var/datum/portrait_picker/tgui = new(usr)//create the datum
tgui.ui_interact(usr)//datum has a tgui component, here we open the window
return "ai-portrait" //just take this until they decide
return "ai-[lowertext(input)]"
GLOBAL_LIST_INIT(security_depts_prefs, list(SEC_DEPT_RANDOM, SEC_DEPT_NONE, SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY, SEC_DEPT_SERVICE))

View File

@@ -46,11 +46,13 @@
var/list/grid
var/canvas_color = "#ffffff" //empty canvas color
var/used = FALSE
var/painting_name //Painting name, this is set after framing.
var/painting_name = "Untitled Artwork" //Painting name, this is set after framing.
var/finalized = FALSE //Blocks edits
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
@@ -184,7 +186,7 @@
/obj/item/canvas/proc/try_rename(mob/user)
var/new_name = stripped_input(user,"What do you want to name the painting?")
if(!painting_name && new_name && user.canUseTopic(src,BE_CLOSE))
if(new_name != painting_name && new_name && user.canUseTopic(src,BE_CLOSE))
painting_name = new_name
SStgui.update_uis(src)
@@ -215,6 +217,17 @@
framed_offset_x = 5
framed_offset_y = 6
/obj/item/canvas/twentyfour_twentyfour
name = "ai universal standard canvas"
desc = "Besides being very large, the AI can accept these as a display from their internal database after you've hung it up."
icon_state = "24x24"
width = 24
height = 24
pixel_x = 2
pixel_y = 1
framed_offset_x = 4
framed_offset_y = 5
/obj/item/wallframe/painting
name = "painting frame"
desc = "The perfect showcase for your favorite deathtrap memories."
@@ -249,15 +262,18 @@
/obj/structure/sign/painting/attackby(obj/item/I, mob/user, params)
if(!C && istype(I, /obj/item/canvas))
frame_canvas(user,I)
else if(C && !C.painting_name && istype(I,/obj/item/pen))
else if(C && C.painting_name == initial(C.painting_name) && istype(I,/obj/item/pen))
try_rename(user)
else
return ..()
/obj/structure/sign/painting/examine(mob/user)
. = ..()
if(persistence_id)
. += "<span class='notice'>Any painting placed here will be archived at the end of the shift.</span>"
if(C)
C.ui_interact(user)
. += "<span class='notice'>Use wirecutters to remove the painting.</span>"
/obj/structure/sign/painting/wirecutter_act(mob/living/user, obj/item/I)
. = ..()
@@ -277,7 +293,7 @@
update_icon()
/obj/structure/sign/painting/proc/try_rename(mob/user)
if(!C.painting_name)
if(C.painting_name == initial(C.painting_name))
C.try_rename(user)
/obj/structure/sign/painting/update_icon()
@@ -299,18 +315,30 @@
frame.pixel_y = C.framed_offset_y - 1
add_overlay(frame)
/**
* Loads a painting from SSpersistence. Called globally by said subsystem when it inits
*
* Deleting paintings leaves their json, so this proc will remove the json and try again if it finds one of those.
*/
/obj/structure/sign/painting/proc/load_persistent()
if(!persistence_id)
return
if(!SSpersistence.paintings || !SSpersistence.paintings[persistence_id] || !length(SSpersistence.paintings[persistence_id]))
return
var/list/chosen = pick(SSpersistence.paintings[persistence_id])
var/title = chosen["title"]
var/author = chosen["ckey"]
var/png = "data/paintings/[persistence_id]/[chosen["md5"]].png"
if(!fexists(png))
stack_trace("Persistent painting [chosen["md5"]].png was not found in [persistence_id] directory.")
if(!persistence_id || !SSpersistence.paintings || !SSpersistence.paintings[persistence_id])
return
var/list/painting_category = SSpersistence.paintings[persistence_id]
var/list/painting
while(!painting)
if(!length(SSpersistence.paintings[persistence_id]))
return //aborts loading anything this category has no usable paintings
var/list/chosen = pick(painting_category)
if(!fexists("data/paintings/[persistence_id]/[chosen["md5"]].png")) //shitmin deleted this art, lets remove json entry to avoid errors
painting_category -= list(chosen)
continue //and try again
painting = chosen
var/title = painting["title"]
var/author = painting["ckey"]
var/png = "data/paintings/[persistence_id]/[painting["md5"]].png"
if(!title)
title = "Untitled Artwork" //legacy artwork allowed null names which was bad for the json, lets fix that
painting["title"] = title
var/icon/I = new(png)
var/obj/item/canvas/new_canvas
var/w = I.Width()
@@ -326,17 +354,20 @@
new_canvas.finalized = TRUE
new_canvas.painting_name = title
new_canvas.author_ckey = author
new_canvas.name = "painting - [title]"
C = new_canvas
update_icon()
/obj/structure/sign/painting/proc/save_persistent()
if(!persistence_id || !C)
if(!persistence_id || !C || C.no_save)
return
if(sanitize_filename(persistence_id) != persistence_id)
stack_trace("Invalid persistence_id - [persistence_id]")
return
if(!C.painting_name)
C.painting_name = "Untitled Artwork"
var/data = C.get_data_string()
var/md5 = md5(data)
var/md5 = md5(lowertext(data))
var/list/current = SSpersistence.paintings[persistence_id]
if(!current)
current = list()

View File

@@ -1,4 +1,4 @@
//DEFINITIONS FOR ASSET DATUMS START HERE.
a//DEFINITIONS FOR ASSET DATUMS START HERE.
/datum/asset/simple/tgui_common
keep_local_name = TRUE
@@ -381,3 +381,22 @@
// Special case to handle Bluespace Crystals
Insert("polycrystal", 'icons/obj/telescience.dmi', "polycrystal")
..()
/datum/asset/simple/portraits
var/tab = "use subtypes of this please"
assets = list()
/datum/asset/simple/portraits/New()
if(!SSpersistence.paintings || !SSpersistence.paintings[tab] || !length(SSpersistence.paintings[tab]))
return
for(var/p in SSpersistence.paintings[tab])
var/list/portrait = p
var/png = "data/paintings/[tab]/[portrait["md5"]].png"
if(fexists(png))
var/asset_name = "[tab]_[portrait["md5"]]"
assets[asset_name] = png
..() //this is where it registers all these assets we added to the list
/datum/asset/simple/portraits/public
tab = "public"

View File

@@ -1555,7 +1555,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
uplink_spawn_loc = new_loc
if("ai_core_icon")
var/ai_core_icon = input(user, "Choose your preferred AI core display screen:", "AI Core Display Screen Selection") as null|anything in GLOB.ai_core_display_screens
var/ai_core_icon = input(user, "Choose your preferred AI core display screen:", "AI Core Display Screen Selection") as null|anything in GLOB.ai_core_display_screens - "Portrait"
if(ai_core_icon)
preferred_ai_core_display = ai_core_icon

View File

@@ -136,7 +136,8 @@
if(client)
apply_pref_name("ai",client)
set_core_display_icon()
INVOKE_ASYNC(src, .proc/set_core_display_icon)
holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"default"))
@@ -219,11 +220,18 @@
set name = "Set AI Core Display"
if(incapacitated())
return
icon = initial(icon)
icon_state = "ai"
cut_overlays()
var/list/iconstates = GLOB.ai_core_display_screens
for(var/option in iconstates)
if(option == "Random")
iconstates[option] = image(icon = initial(src.icon), icon_state = "ai-random") //yogs start - AI donor icons
continue
if(option == "Portrait")
iconstates[option] = image(icon = src.icon, icon_state = "ai-portrait")
continue
iconstates[option] = image(icon = initial(src.icon), icon_state = resolve_ai_icon(option))
if(is_donator(client))
@@ -231,6 +239,9 @@
if(S.owner == client.ckey || !S.owner) //We own this skin.
iconstates[S] = image(icon = S.icon, icon_state = S.icon_state)
view_core()
var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42)

View File

@@ -0,0 +1,69 @@
//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.
/datum/portrait_picker
var/client/holder //client of whoever is using this datum
/datum/portrait_picker/New(user)//user can either be a client or a mob due to byondcode(tm)
if (istype(user, /client))
var/client/user_client = user
holder = user_client //if its a client, assign it to holder
else
var/mob/user_mob = user
holder = user_mob.client //if its a mob, assign the mob's client to holder
/datum/portrait_picker/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "PortraitPicker")
ui.open()
/datum/portrait_picker/ui_close()
qdel(src)
/datum/portrait_picker/ui_state(mob/user)
return GLOB.conscious_state
/datum/portrait_picker/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/simple/portraits/public)
)
/datum/portrait_picker/ui_data(mob/user)
var/list/data = list()
data["public_paintings"] = SSpersistence.paintings["public"] ? SSpersistence.paintings["public"] : 0
return data
/datum/portrait_picker/ui_act(action, params)
. = ..()
if(.)
return
switch(action)
if("select")
var/list/tab2key = list(TAB_PUBLIC = "public")
var/folder = tab2key[params["tab"]]
var/list/current_list = SSpersistence.paintings[folder]
var/list/chosen_portrait = current_list[params["selected"]]
var/png = "data/paintings/[folder]/[chosen_portrait["md5"]].png"
var/icon/portrait_icon = new(png)
var/mob/living/ai = holder.mob
var/w = portrait_icon.Width()
var/h = portrait_icon.Height()
var/mutable_appearance/MA = mutable_appearance(portrait_icon)
if(w == 23 || h == 23)
to_chat(ai, "<span class='notice'>Small note: 23x23 Portraits are accepted, but they do not fit perfectly inside the display frame.</span>")
MA.pixel_x = 5
MA.pixel_y = 5
else if(w == 24 || h == 24)
to_chat(ai, "<span class='notice'>Portrait Accepted. Enjoy!</span>")
MA.pixel_x = 4
MA.pixel_y = 4
else
to_chat(ai, "<span class='warning'>Sorry, only 23x23 and 24x24 Portraits are accepted.</span>")
return
ai.cut_overlays() //so people can't keep repeatedly select portraits to add stacking overlays
ai.icon_state = "ai-portrait-active"//background
ai.add_overlay(MA)

View File

@@ -4,6 +4,7 @@
. = ..()
cut_overlays() //remove portraits
var/old_icon = icon_state
if("[icon_state]_dead" in icon_states(icon))
icon_state = "[icon_state]_dead"

View File

@@ -77,3 +77,15 @@
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
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())

View File

@@ -0,0 +1,77 @@
///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"
program_icon_state = "paint-brush"
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"
/datum/computer_file/program/portrait_printer/ui_data(mob/user)
var/list/data = list()
data["public_paintings"] = SSpersistence.paintings["public"] ? SSpersistence.paintings["public"] : 0
return data
/datum/computer_file/program/portrait_printer/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/simple/portraits/public)
)
/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, "<span class='notice'>Hardware error: A printer is required to print a canvas.</span>")
return
if(printer.stored_paper < CANVAS_PAPER_COST)
to_chat(usr, "<span class='notice'>Printing error: Your printer needs at least [CANVAS_PAPER_COST] paper to print a canvas.</span>")
return
printer.stored_paper -= CANVAS_PAPER_COST
//canvas printing!
var/list/tab2key = list(TAB_PUBLIC = "public")
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, "<span class='notice'>You have printed [title] onto a new canvas.</span>")
playsound(computer.physical, 'sound/items/poster_being_created.ogg', 100, TRUE)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 KiB

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 908 B

After

Width:  |  Height:  |  Size: 980 B

View File

@@ -0,0 +1,127 @@
import { resolveAsset } from '../assets';
import { useBackend, useLocalState } from '../backend';
import { Button, NoticeBox, Section, Flex, 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 {
public_paintings,
} = data;
const TABS = [
{
name: 'Public Portraits',
asset_prefix: "public",
list: public_paintings,
},
];
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 (
<NtosWindow
width={400}
height={406}>
<NtosWindow.Content>
<Flex direction="column" height="100%">
<Flex.Item>
<Section fitted>
<Tabs fluid textAlign="center">
{TABS.map((tabObj, i) => !!tabObj.list && (
<Tabs.Tab
key={i}
selected={i === tabIndex}
onClick={() => {
setListIndex(0);
setTabIndex(i);
}}>
{tabObj.name}
</Tabs.Tab>
))}
</Tabs>
</Section>
</Flex.Item>
<Flex.Item grow={2}>
<Section fill>
<Flex
height="100%"
align="center"
justify="center"
direction="column">
<Flex.Item>
<img
src={resolveAsset(current_portrait_asset_name)}
height="128px"
width="128px"
style={{
'vertical-align': 'middle',
'-ms-interpolation-mode': 'nearest-neighbor',
}} />
</Flex.Item>
<Flex.Item className="Section__titleText">
{current_portrait_title}
</Flex.Item>
</Flex>
</Section>
</Flex.Item>
<Flex.Item>
<Flex>
<Flex.Item grow={3}>
<Section height="100%">
<Flex justify="space-between">
<Flex.Item grow={1}>
<Button
icon="angle-double-left"
disabled={listIndex === 0}
onClick={() => setListIndex(0)}
/>
</Flex.Item>
<Flex.Item grow={3}>
<Button
disabled={listIndex === 0}
icon="chevron-left"
onClick={() => setListIndex(listIndex-1)}
/>
</Flex.Item>
<Flex.Item grow={3}>
<Button
icon="check"
content="Print Portrait"
onClick={() => act("select", {
tab: tabIndex+1,
selected: listIndex+1,
})}
/>
</Flex.Item>
<Flex.Item grow={1}>
<Button
icon="chevron-right"
disabled={listIndex === tab2list.length-1}
onClick={() => setListIndex(listIndex+1)}
/>
</Flex.Item>
<Flex.Item>
<Button
icon="angle-double-right"
disabled={listIndex === tab2list.length-1}
onClick={() => setListIndex(tab2list.length-1)}
/>
</Flex.Item>
</Flex>
</Section>
</Flex.Item>
</Flex>
<Flex.Item mt={1} mb={-1}>
<NoticeBox info>
Printing a canvas costs 10 paper from
the printer installed in your machine.
</NoticeBox>
</Flex.Item>
</Flex.Item>
</Flex>
</NtosWindow.Content>
</NtosWindow>
);
};

View File

@@ -0,0 +1,139 @@
import { resolveAsset } from '../assets';
import { useBackend, useLocalState } from '../backend';
import { Button, Flex, NoticeBox, Section, Tabs } from '../components';
import { Window } from '../layouts';
export const PortraitPicker = (props, context) => {
const { act, data } = useBackend(context);
const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
const [listIndex, setListIndex] = useLocalState(context, 'listIndex', 0);
const {
public_paintings,
} = data;
const TABS = [
{
name: 'Public Portraits',
asset_prefix: "public",
list: public_paintings,
},
];
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 (
<Window
theme="ntos"
title="Portrait Picker"
width={400}
height={406}>
<Window.Content>
<Flex height="100%" direction="column">
<Flex.Item mb={1}>
<Section fitted>
<Tabs fluid textAlign="center">
{TABS.map((tabObj, i) => (
<Tabs.Tab
key={i}
selected={i === tabIndex}
onClick={() => {
setListIndex(0);
setTabIndex(i);
}}>
{tabObj.name}
</Tabs.Tab>
))}
</Tabs>
</Section>
</Flex.Item>
<Flex.Item mb={1} grow={2}>
<Section fill>
<Flex
height="100%"
align="center"
justify="center"
direction="column">
<Flex.Item>
<img
src={resolveAsset(current_portrait_asset_name)}
height="96px"
width="96px"
style={{
'vertical-align': 'middle',
'-ms-interpolation-mode': 'nearest-neighbor',
}} />
</Flex.Item>
<Flex.Item className="Section__titleText">
{current_portrait_title}
</Flex.Item>
</Flex>
</Section>
</Flex.Item>
<Flex.Item>
<Flex>
<Flex.Item grow={3}>
<Section height="100%">
<Flex justify="space-between">
<Flex.Item grow={1}>
<Button
icon="angle-double-left"
disabled={listIndex === 0}
onClick={() => setListIndex(0)}
/>
</Flex.Item>
<Flex.Item grow={3}>
<Button
disabled={listIndex === 0}
icon="chevron-left"
onClick={() => setListIndex(listIndex-1)}
/>
</Flex.Item>
<Flex.Item grow={3}>
<Button
icon="check"
content="Select Portrait"
onClick={() => act("select", {
tab: tabIndex+1,
selected: listIndex+1,
})}
/>
</Flex.Item>
<Flex.Item grow={1}>
<Button
icon="chevron-right"
disabled={listIndex === tab2list.length-1}
onClick={() => setListIndex(listIndex+1)}
/>
</Flex.Item>
<Flex.Item>
<Button
icon="angle-double-right"
disabled={listIndex === tab2list.length-1}
onClick={() => setListIndex(tab2list.length-1)}
/>
</Flex.Item>
</Flex>
</Section>
</Flex.Item>
</Flex>
<Flex.Item mt={1}>
<NoticeBox info>
Only the 23x23 or 24x24 canvas size art can be
displayed. Make sure you read the warning below
before embracing the wide wonderful world of
artistic expression!
</NoticeBox>
</Flex.Item>
<Flex.Item>
<NoticeBox danger>
WARNING: While Central Command loves art as much as you do,
choosing erotic art will lead to severe consequences.
Additionally, Central Command reserves the right to request
you change your display portrait, for any reason.
</NoticeBox>
</Flex.Item>
</Flex.Item>
</Flex>
</Window.Content>
</Window>
);
};

View File

@@ -22,6 +22,7 @@
#include "code\__DEFINES\access.dm"
#include "code\__DEFINES\admin.dm"
#include "code\__DEFINES\antagonists.dm"
#include "code\__DEFINES\art.dm"
#include "code\__DEFINES\atmospherics.dm"
#include "code\__DEFINES\atom_hud.dm"
#include "code\__DEFINES\callbacks.dm"
@@ -2279,6 +2280,7 @@
#include "code\modules\mob\living\silicon\silicon_movement.dm"
#include "code\modules\mob\living\silicon\ai\ai.dm"
#include "code\modules\mob\living\silicon\ai\ai_defense.dm"
#include "code\modules\mob\living\silicon\ai\ai_portrait_picker.dm"
#include "code\modules\mob\living\silicon\ai\death.dm"
#include "code\modules\mob\living\silicon\ai\examine.dm"
#include "code\modules\mob\living\silicon\ai\laws.dm"
@@ -2476,6 +2478,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"