mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
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:
@@ -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" = (
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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
3
code/__DEFINES/art.dm
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
///tgui tab portrait categories- they're the same across all portrait tguis.
|
||||
#define TAB_PUBLIC 1
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -147,7 +147,7 @@
|
||||
assets = list(
|
||||
"scanlines.png" = 'html/scanlines.png'
|
||||
)
|
||||
|
||||
|
||||
/datum/asset/simple/jquery
|
||||
legacy = TRUE
|
||||
assets = list(
|
||||
@@ -380,4 +380,23 @@
|
||||
|
||||
// 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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
69
code/modules/mob/living/silicon/ai/ai_portrait_picker.dm
Normal file
69
code/modules/mob/living/silicon/ai/ai_portrait_picker.dm
Normal 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)
|
||||
@@ -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"
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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)
|
||||
BIN
icons/mob/ai.dmi
BIN
icons/mob/ai.dmi
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 |
127
tgui/packages/tgui/interfaces/NtosPortraitPrinter.js
Normal file
127
tgui/packages/tgui/interfaces/NtosPortraitPrinter.js
Normal 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>
|
||||
);
|
||||
};
|
||||
139
tgui/packages/tgui/interfaces/PortraitPicker.js
Normal file
139
tgui/packages/tgui/interfaces/PortraitPicker.js
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user