Port in-game painting from /tg/

This commit is contained in:
Chompstation Bot
2021-07-04 02:24:28 +00:00
committed by Darlantan
parent e276b7aba0
commit 275458f14a
16 changed files with 700 additions and 21 deletions

5
code/__defines/art.dm Normal file
View File

@@ -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

View File

@@ -5,6 +5,10 @@ SUBSYSTEM_DEF(persistence)
var/list/tracking_values = list()
var/list/persistence_datums = list()
/// Places our subsystem can spawn paintings (helps with art spawning differently across maps)
var/list/obj/structure/sign/painting/painting_frames = list()
var/list/paintings = list()
/datum/controller/subsystem/persistence/Initialize()
. = ..()
for(var/thing in subtypesof(/datum/persistent))

View File

@@ -0,0 +1,487 @@
///////////
// EASEL //
///////////
/obj/structure/easel
name = "easel"
desc = "Only for the finest of art!"
icon = 'icons/obj/artstuff.dmi'
icon_state = "easel"
density = TRUE
//resistance_flags = FLAMMABLE
//max_integrity = 60
var/obj/item/canvas/painting = null
//Adding canvases
/obj/structure/easel/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/canvas))
var/obj/item/canvas/canvas = I
user.drop_from_inventory(canvas)
painting = canvas
canvas.forceMove(get_turf(src))
canvas.layer = layer+0.1
user.visible_message("<span class='notice'>[user] puts \the [canvas] on \the [src].</span>","<span class='notice'>You place \the [canvas] on \the [src].</span>")
else
return ..()
//Stick to the easel like glue
/obj/structure/easel/Move()
var/turf/T = get_turf(src)
. = ..()
if(painting && painting.loc == T) //Only move if it's near us.
painting.forceMove(get_turf(src))
else
painting = null
/obj/item/canvas
name = "canvas"
desc = "Draw out your soul on this canvas!"
icon = 'icons/obj/artstuff.dmi'
icon_state = "11x11"
//flags_1 = UNPAINTABLE_1
//resistance_flags = FLAMMABLE
var/width = 11
var/height = 11
var/list/grid
var/canvas_color = "#ffffff" //empty canvas color
var/used = FALSE
var/painting_name = "Untitled Artwork" //Painting name, this is set after framing.
var/finalized = FALSE //Blocks edits
var/author_name
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
var/framed_offset_y = 10
pixel_x = 10
pixel_y = 9
/obj/item/canvas/Initialize()
. = ..()
reset_grid()
/obj/item/canvas/proc/reset_grid()
grid = new/list(width,height)
for(var/x in 1 to width)
for(var/y in 1 to height)
grid[x][y] = canvas_color
/obj/item/canvas/attack_self(mob/user)
. = ..()
tgui_interact(user)
/obj/item/canvas/dropped(mob/user)
pixel_x = initial(pixel_x)
pixel_y = initial(pixel_y)
return ..()
/obj/item/canvas/tgui_state(mob/user)
if(finalized)
return GLOB.tgui_physical_obscured_state
else
return GLOB.tgui_default_state
/obj/item/canvas/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Canvas", name)
ui.set_autoupdate(FALSE)
ui.open()
/obj/item/canvas/attackby(obj/item/I, mob/living/user, params)
if(user.a_intent == I_HELP)
tgui_interact(user)
else
return ..()
/obj/item/canvas/tgui_data(mob/user)
. = ..()
.["grid"] = grid
.["name"] = painting_name
.["finalized"] = finalized
/obj/item/canvas/examine(mob/user)
. = ..()
tgui_interact(user)
/obj/item/canvas/tgui_act(action, params)
. = ..()
if(. || finalized)
return
var/mob/user = usr
switch(action)
if("paint")
var/obj/item/I = user.get_active_hand()
var/color = get_paint_tool_color(I)
if(!color)
return FALSE
var/x = text2num(params["x"])
var/y = text2num(params["y"])
grid[x][y] = color
used = TRUE
update_appearance()
. = TRUE
if("finalize")
. = TRUE
if(!finalized)
finalize(user)
/obj/item/canvas/proc/finalize(mob/user)
finalized = TRUE
author_name = user.real_name
author_ckey = user.ckey
generate_proper_overlay()
try_rename(user)
/obj/item/canvas/proc/update_appearance()
cut_overlays()
if(icon_generated)
var/mutable_appearance/detail = mutable_appearance(generated_icon)
detail.pixel_x = 1
detail.pixel_y = 1
add_overlay(detail)
return
if(!used)
return
var/mutable_appearance/detail = mutable_appearance(icon, "[icon_state]wip")
detail.pixel_x = 1
detail.pixel_y = 1
add_overlay(detail)
/obj/item/canvas/proc/generate_proper_overlay()
if(icon_generated)
return
var/png_filename = "data/persistent/paintings/temp_painting.png"
var/result = rustg_dmi_create_png(png_filename,"[width]","[height]",get_data_string())
if(result)
CRASH("Error generating painting png : [result]")
generated_icon = new(png_filename)
icon_generated = TRUE
update_appearance()
/obj/item/canvas/proc/get_data_string()
var/list/data = list()
for(var/y in 1 to height)
for(var/x in 1 to width)
data += grid[x][y]
return data.Join("")
//Todo make this element ?
/obj/item/canvas/proc/get_paint_tool_color(obj/item/I)
if(!I)
return
if(istype(I, /obj/item/weapon/pen/crayon))
var/obj/item/weapon/pen/crayon/crayon = I
return crayon.colour
else if(istype(I, /obj/item/weapon/pen))
var/obj/item/weapon/pen/P = I
switch(P.colour)
if("black")
return "#000000"
if("blue")
return "#0000ff"
if("red")
return "#ff0000"
return P.colour
else if(istype(I, /obj/item/weapon/soap) || istype(I, /obj/item/weapon/reagent_containers/glass/rag))
return canvas_color
/obj/item/canvas/proc/try_rename(mob/user)
var/new_name = stripped_input(user,"What do you want to name the painting?")
if(new_name != painting_name && new_name && CanUseTopic(user, GLOB.tgui_physical_state))
painting_name = new_name
SStgui.update_uis(src)
/obj/item/canvas/nineteen_nineteen
icon_state = "19x19"
width = 19
height = 19
pixel_x = 5
pixel_y = 10
framed_offset_x = 8
framed_offset_y = 9
/obj/item/canvas/twentythree_nineteen
icon_state = "23x19"
width = 23
height = 19
pixel_x = 4
pixel_y = 10
framed_offset_x = 6
framed_offset_y = 8
/obj/item/canvas/twentythree_twentythree
icon_state = "23x23"
width = 23
height = 23
pixel_x = 5
pixel_y = 9
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/frame/painting
name = "painting frame"
desc = "The perfect showcase for your favorite deathtrap memories."
icon = 'icons/obj/decals.dmi'
//custom_materials = list(/datum/material/wood = 2000)
//flags_1 = NONE
icon_state = "frame-empty"
/obj/item/frame/painting/try_build(turf/on_wall, mob/user as mob)
if(get_dist(on_wall, user) > 1)
return
var/ndir = get_dir(on_wall, user)
if (!(ndir in cardinal))
return
if(!istype(on_wall, /turf/simulated/wall))
to_chat(user, "<span class='warning'>Frame cannot be placed on this spot.</span>")
return
new /obj/structure/sign/painting(get_turf(user), ndir, TRUE)
qdel(src)
/obj/structure/sign/painting
name = "Painting"
desc = "Art or \"Art\"? You decide."
icon = 'icons/obj/decals.dmi'
icon_state = "frame-empty"
var/base_icon_state = "frame"
//custom_materials = list(/datum/material/wood = 2000)
//buildable_sign = FALSE
///Canvas we're currently displaying.
var/obj/item/canvas/current_canvas
///Description set when canvas is added.
var/desc_with_canvas
var/persistence_id
//Presets for art gallery mapping, for paintings to be shared across stations
/obj/structure/sign/painting/public
name = "\improper Public Painting Exhibit mounting"
desc = "For art pieces hung by the public."
desc_with_canvas = "A piece of art (or \"art\"). Anyone could've hung it."
persistence_id = "public"
/obj/structure/sign/painting/library_secure
name = "\improper Curated Painting Exhibit mounting"
desc = "For masterpieces hand-picked by the curator."
desc_with_canvas = "A masterpiece hand-picked by the curator, supposedly."
persistence_id = "library"
req_one_access = list(access_library)
/obj/structure/sign/painting/chapel_secure
name = "\improper Religious Painting Exhibit mounting"
desc = "For masterpieces hand-picked by the chaplain."
desc_with_canvas = "A masterpiece hand-picked by the chaplain, supposedly."
persistence_id = "chapel"
req_one_access = list(access_chapel_office)
/obj/structure/sign/painting/library_private // keep your smut away from prying eyes, or non-librarians at least
name = "\improper Private Painting Exhibit mounting"
desc = "For art pieces deemed too subversive or too illegal to be shared outside of curators."
desc_with_canvas = "A painting hung away from lesser minds."
persistence_id = "library_private"
req_one_access = list(access_library)
/obj/structure/sign/painting/Initialize(mapload, dir, building)
. = ..()
if(persistence_id)
SSpersistence.painting_frames += src
if(dir)
set_dir(dir)
if(building)
pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30)
pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0
/obj/structure/sign/painting/Destroy()
. = ..()
SSpersistence.painting_frames -= src
/obj/structure/sign/painting/attackby(obj/item/I, mob/user, params)
if(!current_canvas && istype(I, /obj/item/canvas))
frame_canvas(user, I)
else if(current_canvas && current_canvas.painting_name == initial(current_canvas.painting_name) && istype(I,/obj/item/weapon/pen))
try_rename(user)
else if(current_canvas && I.is_wirecutter())
unframe_canvas(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(current_canvas)
current_canvas.tgui_interact(user)
. += "<span class='notice'>Use wirecutters to remove the painting.</span>"
/obj/structure/sign/painting/proc/frame_canvas(mob/user,obj/item/canvas/new_canvas)
if(!allowed(user))
to_chat(user, "<span class='warning'>You're not comfortable framing this canvas in such a prestigious spot!</span>")
return
if(user.drop_from_inventory(new_canvas, src))
current_canvas = new_canvas
if(!current_canvas.finalized)
current_canvas.finalize(user)
to_chat(user,"<span class='notice'>You frame [current_canvas].</span>")
update_appearance()
/obj/structure/sign/painting/proc/unframe_canvas(mob/living/user)
if(current_canvas)
current_canvas.forceMove(drop_location())
current_canvas = null
to_chat(user, "<span class='notice'>You remove the painting from the frame.</span>")
update_appearance()
/obj/structure/sign/painting/proc/try_rename(mob/user)
if(current_canvas.painting_name == initial(current_canvas.painting_name))
current_canvas.try_rename(user)
/obj/structure/sign/painting/proc/update_appearance()
name = current_canvas ? "painting - [current_canvas.painting_name]" : initial(name)
desc = current_canvas ? desc_with_canvas : initial(desc)
icon_state = "[base_icon_state]-[current_canvas?.generated_icon ? "hidden" : "empty"]"
cut_overlays()
if(!current_canvas?.generated_icon)
return
. = list()
var/mutable_appearance/MA = mutable_appearance(current_canvas.generated_icon)
MA.pixel_x = current_canvas.framed_offset_x
MA.pixel_y = current_canvas.framed_offset_y
. += MA
var/mutable_appearance/frame = mutable_appearance(current_canvas.icon,"[current_canvas.icon_state]frame")
frame.pixel_x = current_canvas.framed_offset_x - 1
frame.pixel_y = current_canvas.framed_offset_y - 1
. += frame
add_overlay(.)
/obj/item/canvas/proc/fill_grid_from_icon(icon/I)
var/h = I.Height() + 1
for(var/x in 1 to width)
for(var/y in 1 to height)
grid[x][y] = I.GetPixel(x,h-y)
/**
* 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 || !SSpersistence.paintings)
return
var/list/painting_category = list()
for (var/list/P in SSpersistence.paintings)
if(P["persistence_id"] == persistence_id)
painting_category[++painting_category.len] = P
var/list/painting
while(!painting)
if(!length(painting_category))
return //aborts loading anything this category has no usable paintings
var/list/chosen = pick(painting_category)
if(!fexists("data/persistent/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_name = painting["author"]
var/author_ckey = painting["ckey"]
var/png = "data/persistent/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()
var/h = I.Height()
for(var/T in typesof(/obj/item/canvas))
new_canvas = T
if(initial(new_canvas.width) == w && initial(new_canvas.height) == h)
new_canvas = new T(src)
break
new_canvas.fill_grid_from_icon(I)
new_canvas.generated_icon = I
new_canvas.icon_generated = TRUE
new_canvas.finalized = TRUE
new_canvas.painting_name = title
new_canvas.author_name = author_name
new_canvas.author_ckey = author_ckey
new_canvas.name = "painting - [title]"
current_canvas = new_canvas
update_appearance()
/obj/structure/sign/painting/proc/save_persistent()
if(!persistence_id || !current_canvas || current_canvas.no_save)
return
if(sanitize_filename(persistence_id) != persistence_id)
stack_trace("Invalid persistence_id - [persistence_id]")
return
if(!current_canvas.painting_name)
current_canvas.painting_name = "Untitled Artwork"
var/data = current_canvas.get_data_string()
var/md5 = md5(lowertext(data))
LAZYINITLIST(SSpersistence.paintings)
for(var/list/entry in SSpersistence.paintings)
if(entry["md5"] == md5)
return
var/png_directory = "data/persistent/paintings/[persistence_id]/"
var/png_path = png_directory + "[md5].png"
var/result = rustg_dmi_create_png(png_path,"[current_canvas.width]","[current_canvas.height]",data)
if(result)
CRASH("Error saving persistent painting: [result]")
SSpersistence.paintings += list(list(
"persistence_id" = persistence_id,
"title" = current_canvas.painting_name,
"md5" = md5,
"author" = current_canvas.author_name,
"ckey" = current_canvas.author_ckey
))
/obj/structure/sign/painting/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION("removepainting", "Remove Persistent Painting")
/obj/structure/sign/painting/vv_do_topic(list/href_list)
. = ..()
if(href_list["removepainting"])
if(!check_rights(NONE))
return
var/mob/user = usr
if(!persistence_id || !current_canvas)
to_chat(user,"<span class='warning'>This is not a persistent painting.</span>")
return
var/md5 = md5(lowertext(current_canvas.get_data_string()))
var/author = current_canvas.author_ckey
var/list/filenames_found = list()
for(var/list/entry in SSpersistence.paintings)
if(entry["md5"] == md5)
filenames_found += "data/persistent/paintings/[entry["persistence_id"]]/[entry["md5"]].png"
SSpersistence.paintings -= entry
for(var/png in filenames_found)
if(fexists(png))
fdel(png)
for(var/obj/structure/sign/painting/P in SSpersistence.painting_frames)
if(P.current_canvas && md5(P.current_canvas.get_data_string()) == md5)
QDEL_NULL(P.current_canvas)
P.update_appearance()
log_and_message_admins("<span class='notice'>[key_name_admin(user)] has deleted persistent painting made by [author].</span>")

View File

@@ -28,7 +28,12 @@
new /datum/stack_recipe("baggy pants", /obj/item/clothing/under/pants/baggy/white, 8, time = 10 SECONDS, pass_stack_color = TRUE, recycle_material = "[name]"),
new /datum/stack_recipe("belt pouch", /obj/item/weapon/storage/belt/fannypack/white, 25, time = 1 MINUTE, pass_stack_color = TRUE, recycle_material = "[name]"),
new /datum/stack_recipe("crude bandage", /obj/item/stack/medical/crude_pack, 1, time = 2 SECONDS, pass_stack_color = TRUE, recycle_material = "[name]"),
new /datum/stack_recipe("empty sandbag", /obj/item/stack/emptysandbag, 2, time = 2 SECONDS, pass_stack_color = TRUE, supplied_material = "[name]")
new /datum/stack_recipe("empty sandbag", /obj/item/stack/emptysandbag, 2, time = 2 SECONDS, pass_stack_color = TRUE, supplied_material = "[name]"),
new /datum/stack_recipe("painting canvas (11x11)", /obj/item/canvas, 2, time = 2 SECONDS, pass_stack_color = FALSE, supplied_material = "[name]"),
new /datum/stack_recipe("painting canvas (19x19)", /obj/item/canvas/nineteen_nineteen, 3, time = 2 SECONDS, pass_stack_color = FALSE, supplied_material = "[name]"),
new /datum/stack_recipe("painting canvas (23x19)", /obj/item/canvas/twentythree_nineteen, 4, time = 3 SECONDS, pass_stack_color = FALSE, supplied_material = "[name]"),
new /datum/stack_recipe("painting canvas (23x23), AI", /obj/item/canvas/twentythree_twentythree, 5, time = 3 SECONDS, pass_stack_color = FALSE, supplied_material = "[name]"),
new /datum/stack_recipe("painting canvas (24x24)", /obj/item/canvas/twentyfour_twentyfour, 6, time = 3 SECONDS, pass_stack_color = FALSE, supplied_material = "[name]")
)
/datum/material/cloth/syncloth

View File

@@ -43,7 +43,9 @@
new /datum/stack_recipe("crude fishing rod", /obj/item/weapon/material/fishing_rod/built, 8, time = 10 SECONDS, pass_stack_color = TRUE, recycle_material = "[name]"),
new /datum/stack_recipe("wooden standup figure", /obj/structure/barricade/cutout, 5, time = 10 SECONDS, pass_stack_color = TRUE, recycle_material = "[name]"), //VOREStation Add
new /datum/stack_recipe("noticeboard", /obj/structure/noticeboard, 1, recycle_material = "[name]"),
new /datum/stack_recipe("tanning rack", /obj/structure/tanning_rack, 3, one_per_turf = TRUE, time = 20, on_floor = TRUE, supplied_material = "[name]")
new /datum/stack_recipe("tanning rack", /obj/structure/tanning_rack, 3, one_per_turf = TRUE, time = 20, on_floor = TRUE, supplied_material = "[name]"),
new /datum/stack_recipe("painting easel", /obj/structure/easel, 5, one_per_turf = TRUE, time = 20, on_floor = TRUE, supplied_material = "[name]"),
new /datum/stack_recipe("painting frame", /obj/item/frame/painting, 5, one_per_turf = TRUE, time = 20, on_floor = TRUE, supplied_material = "[name]")
)
/datum/material/wood/sif

View File

@@ -147,12 +147,9 @@ var/list/slot_equipment_priority = list( \
// If canremove or other conditions need to be checked then use unEquip instead.
/mob/proc/drop_from_inventory(var/obj/item/W, var/atom/target)
if(W)
remove_from_mob(W, target)
if(!(W && W.loc))
return 1 // self destroying objects (tk, grabs)
return 1
if(!W)
return 0
return remove_from_mob(W, target)
//Drops the item in our left hand
/mob/proc/drop_l_hand(var/atom/Target)

View File

@@ -1400,8 +1400,8 @@
/mob/living/carbon/human/drop_from_inventory(var/obj/item/W, var/atom/Target = null)
if(W in organs)
return
..()
return 0
return ..()
/mob/living/carbon/human/reset_view(atom/A, update_hud = 1)
..()

View File

@@ -719,8 +719,8 @@
/mob/living/carbon/drop_from_inventory(var/obj/item/W, var/atom/Target = null)
if(W in internal_organs)
return
..()
return 0
return ..()
/mob/living/touch_map_edge()

View File

@@ -20,6 +20,14 @@
if(!isnull(entries_decay_at) && !isnull(entries_expire_at))
entries_decay_at = round(entries_expire_at * entries_decay_at)
/datum/persistent/proc/Initialize()
if(fexists(filename))
var/list/tokens = json_decode(file2text(filename))
for(var/list/token in tokens)
if(!CheckTokenSanity(token))
tokens -= token
ProcessAndApplyTokens(tokens)
/datum/persistent/proc/GetValidTurf(var/turf/T, var/list/token)
if(T && CheckTurfContents(T, token))
return T
@@ -93,14 +101,6 @@
"age" = GetEntryAge(entry)
)
/datum/persistent/proc/Initialize()
if(fexists(filename))
var/list/tokens = json_decode(file2text(filename))
for(var/list/token in tokens)
if(!CheckTokenSanity(token))
tokens -= token
ProcessAndApplyTokens(tokens)
/datum/persistent/proc/Shutdown()
if(fexists(filename))
fdel(filename)

View File

@@ -0,0 +1,36 @@
/datum/persistent/paintings
name = "paintings"
entries_expire_at = 1000 // Basically forever
/datum/persistent/paintings/SetFilename()
filename = "data/persistent/paintings.json"
/datum/persistent/paintings/Initialize()
. = ..()
if(fexists(filename))
SSpersistence.paintings = json_decode(file2text(filename))
var/list/tokens = SSpersistence.paintings
for(var/list/token in tokens)
token["age"]++ // Increment age!
if(!CheckTokenSanity(token))
tokens -= token
for(var/obj/structure/sign/painting/P in SSpersistence.painting_frames)
P.load_persistent()
return
/datum/persistent/paintings/CheckTokenSanity(var/list/token)
var/png_filename = "data/paintings/[token["persistence_id"]]/[token["md5"]].png"
if(!fexists(png_filename))
return FALSE
if(token["age"] > entries_expire_at)
fdel(png_filename)
return FALSE
/datum/persistent/paintings/Shutdown()
for(var/obj/structure/sign/painting/P in SSpersistence.painting_frames)
P.save_persistent()
if(fexists(filename))
fdel(filename)
to_file(file(filename), json_encode(SSpersistence.paintings))

View File

@@ -119,7 +119,7 @@
return STATUS_DISABLED
return STATUS_CLOSE // Otherwise, we got nothing.
/mob/living/carbon/human/shared_living_tgui_distance(atom/movable/src_object)
/mob/living/carbon/human/shared_living_tgui_distance(atom/movable/src_object, viewcheck = TRUE)
if((TK in mutations) && (get_dist(src, src_object) <= 2))
return STATUS_INTERACTIVE
return ..()

View File

@@ -22,3 +22,28 @@ GLOBAL_DATUM_INIT(tgui_physical_state, /datum/tgui_state/physical, new)
/mob/living/silicon/ai/physical_can_use_tgui_topic(src_object)
return STATUS_UPDATE // AIs are not physical.
/**
* tgui state: physical_obscured_state
*
* Short-circuits the default state to only check physical distance, being in view doesn't matter
*/
GLOBAL_DATUM_INIT(tgui_physical_obscured_state, /datum/tgui_state/physical_obscured_state, new)
/datum/tgui_state/physical_obscured_state/can_use_topic(src_object, mob/user)
. = user.shared_tgui_interaction(src_object)
if(. > STATUS_CLOSE)
return min(., user.physical_obscured_can_use_topic(src_object))
/mob/proc/physical_obscured_can_use_topic(src_object)
return STATUS_CLOSE
/mob/living/physical_obscured_can_use_topic(src_object)
return shared_living_tgui_distance(src_object, viewcheck = FALSE)
/mob/living/silicon/physical_obscured_can_use_topic(src_object)
return max(STATUS_UPDATE, shared_living_tgui_distance(src_object, viewcheck = FALSE)) // Silicons can always see.
/mob/living/silicon/ai/physical_obscured_can_use_topic(src_object)
return STATUS_UPDATE // AIs are not physical.

BIN
icons/obj/artstuff.dmi Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -0,0 +1,112 @@
import { Component, createRef } from 'inferno';
import { useBackend } from '../backend';
import { Box, Button } from '../components';
import { Window } from '../layouts';
const PX_PER_UNIT = 24;
class PaintCanvas extends Component {
constructor(props) {
super(props);
this.canvasRef = createRef();
this.onCVClick = props.onCanvasClick;
}
componentDidMount() {
this.drawCanvas(this.props);
}
componentDidUpdate() {
this.drawCanvas(this.props);
}
drawCanvas(propSource) {
const ctx = this.canvasRef.current.getContext("2d");
const grid = propSource.value;
const x_size = grid.length;
if (!x_size) {
return;
}
const y_size = grid[0].length;
const x_scale = Math.round(this.canvasRef.current.width / x_size);
const y_scale = Math.round(this.canvasRef.current.height / y_size);
ctx.save();
ctx.scale(x_scale, y_scale);
for (let x = 0; x < grid.length; x++) {
const element = grid[x];
for (let y = 0; y < element.length; y++) {
const color = element[y];
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
}
}
ctx.restore();
}
clickwrapper(event) {
const x_size = this.props.value.length;
if (!x_size) {
return;
}
const y_size = this.props.value[0].length;
const x_scale = this.canvasRef.current.width / x_size;
const y_scale = this.canvasRef.current.height / y_size;
const x = Math.floor(event.offsetX / x_scale)+1;
const y = Math.floor(event.offsetY / y_scale)+1;
this.onCVClick(x, y);
}
render() {
const {
res = 1,
value,
dotsize = PX_PER_UNIT,
...rest
} = this.props;
const [width, height] = getImageSize(value);
return (
<canvas
ref={this.canvasRef}
width={(width * dotsize) || 300}
height={(height * dotsize) || 300}
{...rest}
onClick={e => this.clickwrapper(e)}>
Canvas failed to render.
</canvas>
);
}
}
const getImageSize = value => {
const width = value.length;
const height = width !== 0 ? value[0].length : 0;
return [width, height];
};
export const Canvas = (props, context) => {
const { act, data } = useBackend(context);
const dotsize = PX_PER_UNIT;
const [width, height] = getImageSize(data.grid);
return (
<Window
width={Math.min(700, width * dotsize + 72)}
height={Math.min(700, height * dotsize + 72)}>
<Window.Content>
<Box textAlign="center">
<PaintCanvas
value={data.grid}
dotsize={dotsize}
onCanvasClick={(x, y) => act("paint", { x, y })} />
<Box>
{!data.finalized && (
<Button.Confirm
onClick={() => act("finalize")}
content="Finalize" />
)}
&nbsp;{data.name}
</Box>
</Box>
</Window.Content>
</Window>
);
};

View File

@@ -1507,6 +1507,7 @@
#include "code\game\objects\random\mob_vr.dm"
#include "code\game\objects\random\spacesuits.dm"
#include "code\game\objects\random\unidentified\medicine.dm"
#include "code\game\objects\structures\artstuff.dm"
#include "code\game\objects\structures\barricades.dm"
#include "code\game\objects\structures\barsign.dm"
#include "code\game\objects\structures\bedsheet_bin.dm"
@@ -3569,7 +3570,12 @@
#include "code\modules\persistence\filth.dm"
#include "code\modules\persistence\graffiti.dm"
#include "code\modules\persistence\noticeboard.dm"
<<<<<<< HEAD
#include "code\modules\persistence\noticeboard_yw.dm"
||||||| parent of 89be4e7a2f... Merge pull request #10849 from VOREStation/Arokha/tgart
=======
#include "code\modules\persistence\paintings.dm"
>>>>>>> 89be4e7a2f... Merge pull request #10849 from VOREStation/Arokha/tgart
#include "code\modules\persistence\serialize.dm"
#include "code\modules\persistence\datum\persistence_datum.dm"
#include "code\modules\persistence\effects\filth.dm"