mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-11 08:06:33 +01:00
940 lines
34 KiB
Plaintext
940 lines
34 KiB
Plaintext
GLOBAL_LIST_INIT(canvas_dimensions, init_canvas_dimensions())
|
|
|
|
/proc/init_canvas_dimensions()
|
|
. = list()
|
|
for(var/obj/item/canvas/canvas_type as anything in typesof(/obj/item/canvas))
|
|
var/width = canvas_type::width
|
|
var/height = canvas_type::height
|
|
.["[width]x[height]"] = list(width, height)
|
|
|
|
///////////
|
|
// EASEL //
|
|
///////////
|
|
|
|
/obj/structure/easel
|
|
name = "easel"
|
|
desc = "Only for the finest of art!"
|
|
icon = 'icons/obj/art/artstuff.dmi'
|
|
icon_state = "easel"
|
|
density = TRUE
|
|
resistance_flags = FLAMMABLE
|
|
max_integrity = 60
|
|
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 5)
|
|
var/obj/item/canvas/painting = null
|
|
|
|
//Adding canvases
|
|
/obj/structure/easel/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
|
if(istype(tool, /obj/item/canvas))
|
|
var/obj/item/canvas/canvas = tool
|
|
user.transfer_item_to_turf(canvas, get_turf(src), silent = FALSE)
|
|
painting = canvas
|
|
canvas.layer = layer+0.1
|
|
user.visible_message(span_notice("[user] puts \the [canvas] on \the [src]."),span_notice("You place \the [canvas] on \the [src]."))
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
|
|
//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/art/artstuff.dmi'
|
|
icon_state = "11x11"
|
|
flags_1 = UNPAINTABLE_1
|
|
resistance_flags = FLAMMABLE
|
|
interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_ALLOW_USER_LOCATION
|
|
var/width = 11
|
|
var/height = 11
|
|
/// empty canvas color
|
|
var/canvas_color = "#ffffff"
|
|
/// The sprite editor workspace that carries the data for this canvas
|
|
var/datum/sprite_editor_workspace/workspace
|
|
/// Is it clean canvas or was there something painted on it at some point, used to decide when to show wip splotch overlay
|
|
var/used = FALSE
|
|
var/finalized = FALSE //Blocks edits
|
|
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
|
|
|
|
///reference to the last patron's mind datum, used to allow them (and no others) to change the frame before the round ends.
|
|
var/datum/weakref/last_patron
|
|
|
|
var/datum/painting/painting_metadata
|
|
|
|
// Painting overlay offset when framed
|
|
var/framed_offset_x = 11
|
|
var/framed_offset_y = 10
|
|
|
|
/**
|
|
* How big the grid cells that compose the painting are in the UI (multiplied by zoom).
|
|
* This impacts the size of the UI, so smaller values are generally better for bigger canvases and vice-versa
|
|
*/
|
|
var/pixels_per_unit = 9
|
|
|
|
SET_BASE_PIXEL(11, 10)
|
|
|
|
custom_price = PAYCHECK_CREW
|
|
|
|
/obj/item/canvas/Initialize(mapload)
|
|
. = ..()
|
|
workspace = new(width,
|
|
height,
|
|
color_mode = SPRITE_EDITOR_COLOR_MODE_RGB,
|
|
config_flags = NONE,
|
|
tool_flags = SPRITE_EDITOR_TOOL_PENCIL | SPRITE_EDITOR_TOOL_BUCKET,
|
|
initial_layer_color = "[canvas_color]ff" // To avoid needing to handle strings of mixed lengths, sprite editor workspaces always use the alpha channel
|
|
)
|
|
RegisterSignal(workspace, COMSIG_SPRITE_EDITOR_VALIDATE_COLOR, PROC_REF(validate_color))
|
|
painting_metadata = new
|
|
painting_metadata.title = "Untitled Artwork"
|
|
painting_metadata.creation_round_id = GLOB.round_id
|
|
painting_metadata.width = width
|
|
painting_metadata.height = height
|
|
ADD_KEEP_TOGETHER(src, INNATE_TRAIT)
|
|
|
|
/obj/item/canvas/Destroy()
|
|
last_patron = null
|
|
if(istype(loc,/obj/structure/sign/painting))
|
|
var/obj/structure/sign/painting/frame = loc
|
|
frame.remove_art_element(painting_metadata.credit_value)
|
|
painting_metadata = null
|
|
return ..()
|
|
|
|
/obj/item/canvas/attack_self(mob/user)
|
|
. = ..()
|
|
ui_interact(user)
|
|
|
|
/obj/item/canvas/ui_host(mob/user)
|
|
if(istype(loc,/obj/structure/sign/painting))
|
|
return loc
|
|
return ..()
|
|
|
|
/obj/item/canvas/ui_state(mob/user)
|
|
if(finalized)
|
|
return GLOB.hold_or_view_state
|
|
return GLOB.default_state
|
|
|
|
/obj/item/canvas/ui_interact(mob/user, datum/tgui/ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "Canvas", name)
|
|
ui.open()
|
|
|
|
/obj/item/canvas/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
|
ui_interact(user)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/item/canvas/ui_static_data(mob/user)
|
|
. = ..()
|
|
.["year_offset"] = STATION_YEAR_OFFSET
|
|
|
|
/obj/item/canvas/ui_data(mob/user)
|
|
. = ..()
|
|
var/list/metadata = list(
|
|
"title" = painting_metadata.title,
|
|
"author" = painting_metadata.creator_name,
|
|
"patron" = painting_metadata.patron_name,
|
|
"medium" = painting_metadata.medium,
|
|
"date" = painting_metadata.creation_date,
|
|
)
|
|
var/list/editor_data = workspace.sprite_editor_ui_data()
|
|
var/can_edit = TRUE
|
|
|
|
var/obj/item/implement = user.get_active_held_item()
|
|
var/implement_color = get_paint_tool_color(implement)
|
|
var/can_change_implement_color = can_change_paint_tool_color(implement)
|
|
if(implement_color)
|
|
editor_data["serverSelectedColor"] = implement_color
|
|
editor_data["serverPalette"] = get_paint_tool_palette(implement)
|
|
editor_data["maxServerColors"] = get_paint_tool_palette_capacity(implement)
|
|
editor_data["onSelectServerColor"] = "onSelectColor"
|
|
editor_data["onAddServerColor"] = "onAddPaletteColor"
|
|
editor_data["onRemoveServerColor"] = "onRemovePaletteColor"
|
|
if(can_change_implement_color)
|
|
editor_data["toolFlags"] |= SPRITE_EDITOR_TOOL_DROPPER
|
|
else
|
|
can_edit = FALSE
|
|
return list(
|
|
"metadata" = metadata,
|
|
"editorData" = editor_data,
|
|
"pixelsPerUnit" = pixels_per_unit,
|
|
"finalized" = finalized,
|
|
"allowColorPicker" = can_change_implement_color,
|
|
"editable" = can_edit && !finalized, //Ideally you should be able to draw moustaches on existing paintings in the gallery but that's not implemented yet
|
|
"showPlaque" = istype(loc, /obj/structure/sign/painting)
|
|
)
|
|
|
|
/obj/item/canvas/examine(mob/user)
|
|
. = ..()
|
|
ui_interact(user)
|
|
|
|
/obj/item/canvas/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
var/mob/user = usr
|
|
var/obj/item/implement = user.get_active_held_item()
|
|
var/datum/component/palette/palette_comp = implement?.GetComponent(/datum/component/palette)
|
|
switch(action)
|
|
if("spriteEditorCommand")
|
|
. = TRUE
|
|
if(finalized)
|
|
return
|
|
var/command = params["command"]
|
|
if(command != "transaction") // Painting only allows transactions, no undo/redo or layer visibility toggling
|
|
return
|
|
if(!workspace.new_transaction(params["transaction"]))
|
|
return
|
|
var/medium = get_paint_tool_medium(implement)
|
|
if(medium && painting_metadata.medium && painting_metadata.medium != medium)
|
|
painting_metadata.medium = "Mixed medium"
|
|
else
|
|
painting_metadata.medium = medium
|
|
used = TRUE
|
|
update_appearance()
|
|
if("onSelectColor")
|
|
. = TRUE
|
|
var/paint_color = copytext(params["color"], 1, 8)
|
|
implement?.set_painting_tool_color(paint_color)
|
|
if("onAddPaletteColor")
|
|
. = TRUE
|
|
if(!palette_comp)
|
|
return
|
|
if(length(palette_comp.colors) >= palette_comp.max_colors)
|
|
return
|
|
var/paint_color = copytext(params["color"], 1, 8)
|
|
palette_comp.colors += paint_color
|
|
if("onRemovePaletteColor")
|
|
. = TRUE
|
|
if(!palette_comp)
|
|
return
|
|
var/color_index = params["index"]
|
|
palette_comp.colors.Cut(color_index, color_index+1)
|
|
if("finalize")
|
|
. = TRUE
|
|
finalize(user)
|
|
if("patronage")
|
|
. = TRUE
|
|
patron(user)
|
|
|
|
/obj/item/canvas/proc/validate_color(_source, paint_color)
|
|
SIGNAL_HANDLER
|
|
paint_color = copytext(paint_color, 1, 8)
|
|
var/obj/item/implement = usr.get_active_held_item()
|
|
if(!implement || !((get_paint_tool_color(implement) == paint_color) || (paint_color in get_paint_tool_palette(implement))))
|
|
return COLOR_IS_INVALID
|
|
|
|
/obj/item/canvas/proc/finalize(mob/user)
|
|
if(finalized || painting_metadata.loaded_from_json)
|
|
return
|
|
if(!in_range(src, user))
|
|
user.balloon_alert(user, "too far away!")
|
|
return
|
|
if(!try_rename(user))
|
|
return
|
|
|
|
painting_metadata.creator_ckey = user.ckey
|
|
painting_metadata.creator_name = user.real_name
|
|
painting_metadata.creation_date = time2text(world.realtime, "DDD MMM DD hh:mm:ss YYYY", TIMEZONE_UTC)
|
|
painting_metadata.creation_round_id = GLOB.round_id
|
|
generate_proper_overlay()
|
|
finalized = TRUE
|
|
|
|
SStgui.update_uis(src)
|
|
|
|
#define CURATOR_PERCENTILE_CUT 0.225
|
|
#define SERVICE_PERCENTILE_CUT 0.125
|
|
|
|
/obj/item/canvas/proc/patron(mob/living/user)
|
|
if(!finalized || !isliving(user))
|
|
return
|
|
if(!painting_metadata.loaded_from_json)
|
|
if(tgui_alert(user, "The painting hasn't been archived yet and will be lost at the end of the shift if not placed in an elegible frame. Continue?","Unarchived Painting",list("Yes","No")) != "Yes")
|
|
return
|
|
var/mob/living/living_user = user
|
|
var/obj/item/card/id/id_card = living_user.get_idcard(TRUE)
|
|
if(!id_card)
|
|
to_chat(user, span_warning("You don't even have a id and you want to be an art patron?"))
|
|
return
|
|
if(!id_card.can_be_used_in_payment(user))
|
|
to_chat(user, span_warning("No valid non-departmental account found."))
|
|
return
|
|
var/datum/bank_account/account = id_card.registered_account
|
|
if(!account.has_money(painting_metadata.credit_value))
|
|
to_chat(user, span_warning("You can't afford this."))
|
|
return
|
|
var/sniped_amount = painting_metadata.credit_value
|
|
var/offer_amount = tgui_input_number(user, "How much do you want to offer?", "Patronage Amount", (painting_metadata.credit_value + 1), account.account_balance, painting_metadata.credit_value)
|
|
if(!offer_amount || QDELETED(user) || QDELETED(src) || !istype(loc, /obj/structure/sign/painting) || !user.can_perform_action(loc, FORBID_TELEKINESIS_REACH))
|
|
return
|
|
if(sniped_amount != painting_metadata.credit_value)
|
|
return
|
|
if(!account.adjust_money(-offer_amount, "Painting: Patron of [painting_metadata.title]"))
|
|
to_chat(user, span_warning("Transaction failure. Please try again."))
|
|
return
|
|
|
|
var/datum/bank_account/service_account = SSeconomy.get_dep_account(ACCOUNT_SRV)
|
|
service_account.adjust_money(offer_amount * SERVICE_PERCENTILE_CUT)
|
|
///We give the curator(s) a cut (unless they're themselves the patron), as it's their job to curate and promote art among other things.
|
|
if(SSeconomy.bank_accounts_by_job[/datum/job/curator])
|
|
var/list/curator_accounts = SSeconomy.bank_accounts_by_job[/datum/job/curator] - account
|
|
var/curators_length = length(curator_accounts)
|
|
if(curators_length)
|
|
var/curator_cut = round(offer_amount * CURATOR_PERCENTILE_CUT / curators_length)
|
|
if(curator_cut)
|
|
for(var/datum/bank_account/curator as anything in curator_accounts)
|
|
curator.adjust_money(curator_cut, "Painting: Patronage cut")
|
|
curator.bank_card_talk("Cut on patronage received, account now holds [curator.account_balance] [MONEY_SYMBOL].")
|
|
|
|
if(istype(loc, /obj/structure/sign/painting))
|
|
var/obj/structure/sign/painting/frame = loc
|
|
frame.remove_art_element(painting_metadata.credit_value)
|
|
frame.add_art_element(offer_amount)
|
|
|
|
painting_metadata.patron_ckey = user.ckey
|
|
painting_metadata.patron_name = user.real_name
|
|
painting_metadata.credit_value = offer_amount
|
|
last_patron = WEAKREF(user.mind)
|
|
|
|
to_chat(user, span_notice("Nanotrasen Trust Foundation thanks you for your contribution. You're now an official patron of this painting."))
|
|
if(HAS_PERSONALITY(user, /datum/personality/creative))
|
|
user.add_mood_event("creative_patronage", /datum/mood_event/creative_patronage)
|
|
if(HAS_PERSONALITY(user, /datum/personality/unimaginative))
|
|
user.add_mood_event("unimaginative_patronage", /datum/mood_event/unimaginative_patronage)
|
|
var/list/possible_frames = SSpersistent_paintings.get_available_frames(offer_amount)
|
|
if(possible_frames.len <= 1) // Not much room for choices here.
|
|
return
|
|
if(tgui_alert(user, "Do you want to change the frame appearance now? You can do so later this shift with Alt-Click as long as you're a patron.","Patronage Frame",list("Yes","No")) != "Yes")
|
|
return
|
|
if(!can_select_frame(user))
|
|
return
|
|
SStgui.close_uis(src) // Close the examine ui so that the radial menu doesn't end up covered by it and people don't get confused.
|
|
select_new_frame(user, possible_frames)
|
|
|
|
#undef CURATOR_PERCENTILE_CUT
|
|
#undef SERVICE_PERCENTILE_CUT
|
|
|
|
/obj/item/canvas/proc/select_new_frame(mob/user, list/candidates)
|
|
var/possible_frames = candidates || SSpersistent_paintings.get_available_frames(painting_metadata.credit_value)
|
|
var/list/radial_options = list()
|
|
for(var/frame_name in possible_frames)
|
|
radial_options[frame_name] = image(icon, "[icon_state]frame_[frame_name]")
|
|
var/result = show_radial_menu(user, loc, radial_options, radius = 60, custom_check = CALLBACK(src, PROC_REF(can_select_frame), user), tooltips = TRUE)
|
|
if(!result)
|
|
return
|
|
painting_metadata.frame_type = result
|
|
var/obj/structure/sign/painting/our_frame = loc
|
|
our_frame.balloon_alert(user, "frame set to [result]")
|
|
our_frame.update_appearance()
|
|
|
|
/obj/item/canvas/proc/can_select_frame(mob/user)
|
|
if(!istype(loc, /obj/structure/sign/painting))
|
|
return FALSE
|
|
if(!loc.IsReachableBy(user) || IS_DEAD_OR_INCAP(user))
|
|
return FALSE
|
|
if(!last_patron || !IS_WEAKREF_OF(user?.mind, last_patron))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/item/canvas/update_overlays()
|
|
. = ..()
|
|
if(icon_generated)
|
|
var/mutable_appearance/detail = mutable_appearance(generated_icon)
|
|
detail.pixel_w = 1
|
|
detail.pixel_z = 1
|
|
. += detail
|
|
return
|
|
if(!used)
|
|
return
|
|
|
|
var/mutable_appearance/detail = mutable_appearance(icon, "[icon_state]wip")
|
|
detail.pixel_w = 1
|
|
detail.pixel_z = 1
|
|
. += detail
|
|
|
|
/obj/item/canvas/proc/generate_proper_overlay()
|
|
if(icon_generated)
|
|
return
|
|
var/png_filename = "data/paintings/temp_painting.png"
|
|
var/image_data = get_data_string()
|
|
var/result = rustg_dmi_create_png(png_filename, "[width]", "[height]", image_data)
|
|
if(result)
|
|
CRASH("Error generating painting png : [result]")
|
|
painting_metadata.md5 = md5(LOWER_TEXT(image_data))
|
|
generated_icon = new(png_filename)
|
|
icon_generated = TRUE
|
|
update_appearance()
|
|
|
|
/obj/item/canvas/proc/get_data_string()
|
|
var/list/data = list()
|
|
var/list/grid = workspace.layers[1]["data"]["[SOUTH]"]
|
|
for(var/y in 1 to height)
|
|
for(var/x in 1 to width)
|
|
data += grid[y][x]
|
|
return data.Join("")
|
|
|
|
//Todo make this element ?
|
|
/obj/item/canvas/proc/get_paint_tool_color(obj/item/painting_implement)
|
|
if(!painting_implement)
|
|
return
|
|
if(istype(painting_implement, /obj/item/paint_palette))
|
|
var/obj/item/paint_palette/palette = painting_implement
|
|
return LOWER_TEXT(palette.current_color)
|
|
if(istype(painting_implement, /obj/item/toy/crayon))
|
|
var/obj/item/toy/crayon/crayon = painting_implement
|
|
return LOWER_TEXT(crayon.paint_color)
|
|
else if(istype(painting_implement, /obj/item/pen))
|
|
var/obj/item/pen/pen = painting_implement
|
|
return LOWER_TEXT(pen.colour)
|
|
else if (istype(painting_implement, /obj/item/airlock_painter/decal))
|
|
var/obj/item/airlock_painter/decal/painter = painting_implement
|
|
return LOWER_TEXT(painter.selected_custom_color)
|
|
else if(istype(painting_implement, /obj/item/soap) || istype(painting_implement, /obj/item/rag))
|
|
return LOWER_TEXT(canvas_color)
|
|
|
|
/obj/item/canvas/proc/get_paint_tool_palette(obj/item/painting_implement)
|
|
if(!painting_implement)
|
|
return list()
|
|
var/datum/component/palette/palette_comp = painting_implement.GetComponent(/datum/component/palette)
|
|
if(!palette_comp)
|
|
var/implement_color = get_paint_tool_color(painting_implement)
|
|
return implement_color ? list(implement_color) : list()
|
|
return palette_comp.colors
|
|
|
|
/obj/item/canvas/proc/get_paint_tool_palette_capacity(obj/item/painting_implement)
|
|
if(!painting_implement)
|
|
return
|
|
var/datum/component/palette/palette_comp = painting_implement.GetComponent(/datum/component/palette)
|
|
if(!palette_comp)
|
|
return get_paint_tool_color(painting_implement) ? 1 : 0
|
|
return palette_comp.max_colors
|
|
|
|
/obj/item/canvas/proc/can_change_paint_tool_color(obj/item/painting_implement)
|
|
if(!painting_implement)
|
|
return
|
|
if(istype(painting_metadata, /obj/item/paint_palette) || istype(painting_implement, /obj/item/airlock_painter/decal))
|
|
return TRUE
|
|
if(istype(painting_implement, /obj/item/toy/crayon))
|
|
var/obj/item/toy/crayon/crayon = painting_implement
|
|
return crayon.can_change_colour
|
|
|
|
/// Generates medium description
|
|
/obj/item/canvas/proc/get_paint_tool_medium(obj/item/painting_implement)
|
|
if(!painting_implement)
|
|
return
|
|
if(istype(painting_implement, /obj/item/paint_palette))
|
|
return "Oil on canvas"
|
|
else if(istype(painting_implement, /obj/item/toy/crayon/spraycan))
|
|
return "Spraycan on canvas"
|
|
else if(istype(painting_implement, /obj/item/toy/crayon))
|
|
return "Crayon on canvas"
|
|
else if(istype(painting_implement, /obj/item/pen) || istype(painting_implement, /obj/item/airlock_painter/decal))
|
|
return "Ink on canvas"
|
|
else if(istype(painting_implement, /obj/item/soap) || istype(painting_implement, /obj/item/rag))
|
|
return //These are just for cleaning, ignore them
|
|
else
|
|
return "Unknown medium"
|
|
|
|
/obj/item/canvas/proc/try_rename(mob/user)
|
|
if(painting_metadata.loaded_from_json) // No renaming old paintings
|
|
return TRUE
|
|
var/new_name = tgui_input_text(user, "What do you want to name the painting?", "Title Your Masterpiece", max_length = MAX_NAME_LEN)
|
|
new_name = reject_bad_name(new_name, allow_numbers = TRUE, ascii_only = FALSE, strict = TRUE, cap_after_symbols = FALSE)
|
|
if(isnull(new_name))
|
|
return FALSE
|
|
if(new_name != painting_metadata.title && user.can_perform_action(src))
|
|
painting_metadata.title = new_name
|
|
switch(tgui_alert(user, "Do you want to sign it or remain anonymous?", "Sign painting?", list("Yes", "No", "Cancel")))
|
|
if("Yes")
|
|
return TRUE
|
|
if("No")
|
|
painting_metadata.creator_name = "Anonymous"
|
|
return TRUE
|
|
|
|
return FALSE
|
|
|
|
/obj/item/canvas/nineteen_nineteen
|
|
name = "canvas (19x19)"
|
|
icon_state = "19x19"
|
|
width = 19
|
|
height = 19
|
|
SET_BASE_PIXEL(7, 7)
|
|
framed_offset_x = 7
|
|
framed_offset_y = 7
|
|
|
|
/obj/item/canvas/twentythree_nineteen
|
|
name = "canvas (23x19)"
|
|
icon_state = "23x19"
|
|
width = 23
|
|
height = 19
|
|
SET_BASE_PIXEL(5, 7)
|
|
framed_offset_x = 5
|
|
framed_offset_y = 7
|
|
pixels_per_unit = 8
|
|
|
|
/obj/item/canvas/twentythree_twentythree
|
|
name = "canvas (23x23)"
|
|
icon_state = "23x23"
|
|
width = 23
|
|
height = 23
|
|
SET_BASE_PIXEL(5, 5)
|
|
framed_offset_x = 5
|
|
framed_offset_y = 5
|
|
pixels_per_unit = 8
|
|
|
|
/obj/item/canvas/twentyfour_twentyfour
|
|
name = "canvas (24x24) (AI Universal Standard)"
|
|
desc = "Besides being almost too large for a standard frame, 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
|
|
SET_BASE_PIXEL(4, 4)
|
|
framed_offset_x = 4
|
|
framed_offset_y = 4
|
|
pixels_per_unit = 8
|
|
|
|
/obj/item/canvas/thirtysix_twentyfour
|
|
name = "canvas (36x24)"
|
|
desc = "A very large canvas to draw out your soul on. You'll need a larger frame to put it on a wall."
|
|
icon_state = "24x24" //The vending spritesheet needs the icons to be 32x32. We'll set the actual icon on Initialize.
|
|
width = 36
|
|
height = 24
|
|
SET_BASE_PIXEL(-4, 4)
|
|
framed_offset_x = 14
|
|
framed_offset_y = 4
|
|
pixels_per_unit = 7
|
|
w_class = WEIGHT_CLASS_BULKY
|
|
|
|
custom_price = PAYCHECK_CREW * 1.25
|
|
|
|
/obj/item/canvas/thirtysix_twentyfour/Initialize(mapload)
|
|
. = ..()
|
|
AddElement(/datum/element/item_scaling, 1, 0.8)
|
|
icon = 'icons/obj/art/artstuff_64x64.dmi'
|
|
icon_state = "36x24"
|
|
|
|
/obj/item/canvas/fortyfive_twentyseven
|
|
name = "canvas (45x27)"
|
|
desc = "The largest canvas available on the space market. You'll need a larger frame to put it on a wall."
|
|
icon_state = "24x24" //Ditto
|
|
width = 45
|
|
height = 27
|
|
SET_BASE_PIXEL(-8, 2)
|
|
framed_offset_x = 9
|
|
framed_offset_y = 4
|
|
pixels_per_unit = 6
|
|
w_class = WEIGHT_CLASS_BULKY
|
|
|
|
custom_price = PAYCHECK_CREW * 1.75
|
|
|
|
/obj/item/canvas/fortyfive_twentyseven/Initialize(mapload)
|
|
. = ..()
|
|
AddElement(/datum/element/item_scaling, 1, 0.7)
|
|
icon = 'icons/obj/art/artstuff_64x64.dmi'
|
|
icon_state = "45x27"
|
|
|
|
/obj/item/wallframe/painting
|
|
name = "painting frame"
|
|
desc = "The perfect showcase for your favorite deathtrap memories."
|
|
icon = 'icons/obj/signs.dmi'
|
|
custom_materials = list(/datum/material/wood =SHEET_MATERIAL_AMOUNT)
|
|
resistance_flags = FLAMMABLE
|
|
flags_1 = NONE
|
|
icon_state = "frame-empty"
|
|
result_path = /obj/structure/sign/painting
|
|
pixel_shift = 30
|
|
|
|
/obj/structure/sign/painting
|
|
name = "Painting"
|
|
desc = "Art or \"Art\"? You decide."
|
|
icon = 'icons/obj/signs.dmi'
|
|
icon_state = "frame-empty"
|
|
base_icon_state = "frame"
|
|
custom_materials = list(/datum/material/wood =SHEET_MATERIAL_AMOUNT)
|
|
resistance_flags = FLAMMABLE
|
|
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
|
|
/// The list of canvas types accepted by this frame
|
|
var/list/accepted_canvas_types = list(
|
|
/obj/item/canvas,
|
|
/obj/item/canvas/nineteen_nineteen,
|
|
/obj/item/canvas/twentythree_nineteen,
|
|
/obj/item/canvas/twentythree_twentythree,
|
|
/obj/item/canvas/twentyfour_twentyfour,
|
|
)
|
|
/// the type of wallframe it 'disassembles' into
|
|
var/wallframe_type = /obj/item/wallframe/painting
|
|
|
|
/obj/structure/sign/painting/get_save_vars()
|
|
return ..() - NAMEOF(src, icon)
|
|
|
|
/obj/structure/sign/painting/Initialize(mapload)
|
|
. = ..()
|
|
SSpersistent_paintings.painting_frames += src
|
|
|
|
/obj/structure/sign/painting/Destroy()
|
|
. = ..()
|
|
SSpersistent_paintings.painting_frames -= src
|
|
|
|
/obj/structure/sign/painting/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
|
if(!current_canvas && istype(tool, /obj/item/canvas))
|
|
frame_canvas(user, tool)
|
|
return ITEM_INTERACT_SUCCESS
|
|
if(current_canvas && current_canvas.painting_metadata.title == initial(current_canvas.painting_metadata.title) && istype(tool, /obj/item/pen))
|
|
if(try_rename(user))
|
|
SStgui.update_uis(src)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/structure/sign/painting/atom_deconstruct(disassembled)
|
|
var/turf/drop_turf = drop_location()
|
|
current_canvas?.forceMove(drop_turf)
|
|
var/obj/item/wallframe/frame = new wallframe_type(drop_turf)
|
|
frame.update_integrity(get_integrity()) //Transfer how damaged it is.
|
|
|
|
/obj/structure/sign/painting/examine(mob/user)
|
|
. = ..()
|
|
if(persistence_id)
|
|
. += span_notice("Any painting placed here will be archived at the end of the shift.")
|
|
if(current_canvas)
|
|
current_canvas.ui_interact(user)
|
|
. += span_notice("Use wirecutters to remove the painting.")
|
|
if(IS_WEAKREF_OF(user?.mind, current_canvas.last_patron))
|
|
. += span_notice("<b>Alt-Click</b> to change select a new appearance for the frame of this painting.")
|
|
|
|
/obj/structure/sign/painting/wirecutter_act(mob/living/user, obj/item/I)
|
|
. = ..()
|
|
if(current_canvas)
|
|
current_canvas.forceMove(drop_location())
|
|
to_chat(user, span_notice("You remove the painting from the frame."))
|
|
return TRUE
|
|
|
|
/obj/structure/sign/painting/Exited(atom/movable/movable, atom/newloc)
|
|
. = ..()
|
|
if(movable == current_canvas)
|
|
if(!QDELETED(current_canvas))
|
|
remove_art_element(current_canvas.painting_metadata.credit_value)
|
|
current_canvas = null
|
|
update_appearance()
|
|
|
|
/obj/structure/sign/painting/click_alt(mob/user)
|
|
if(!current_canvas?.can_select_frame(user))
|
|
return CLICK_ACTION_BLOCKING
|
|
|
|
INVOKE_ASYNC(current_canvas, TYPE_PROC_REF(/obj/item/canvas, select_new_frame), user)
|
|
return CLICK_ACTION_SUCCESS
|
|
|
|
/obj/structure/sign/painting/proc/frame_canvas(mob/living/user, obj/item/canvas/new_canvas)
|
|
if(!(new_canvas.type in accepted_canvas_types))
|
|
to_chat(user, span_warning("[new_canvas] won't fit in this frame."))
|
|
return FALSE
|
|
if(user.transferItemToLoc(new_canvas,src))
|
|
current_canvas = new_canvas
|
|
if(!current_canvas.finalized)
|
|
current_canvas.finalize(user)
|
|
to_chat(user,span_notice("You frame [current_canvas]."))
|
|
add_art_element()
|
|
update_appearance()
|
|
if(HAS_PERSONALITY(user, /datum/personality/creative))
|
|
user.add_mood_event("creative_framing", /datum/mood_event/creative_framing)
|
|
if(HAS_PERSONALITY(user, /datum/personality/unimaginative))
|
|
user.add_mood_event("unimaginative_framing", /datum/mood_event/unimaginative_framing)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/obj/structure/sign/painting/proc/try_rename(mob/user)
|
|
if(current_canvas.painting_metadata.title != initial(current_canvas.painting_metadata.title))
|
|
return
|
|
if(!current_canvas.try_rename(user))
|
|
return
|
|
SStgui.update_uis(current_canvas)
|
|
|
|
/obj/structure/sign/painting/update_icon_state(updates=ALL)
|
|
. = ..()
|
|
// Stops the frame icon_state from poking out behind the paintings. we have proper frame overlays in artstuff.dmi.
|
|
icon = current_canvas?.generated_icon ? null : initial(icon)
|
|
|
|
/obj/structure/sign/painting/update_name(updates)
|
|
name = current_canvas ? "painting - [current_canvas.painting_metadata.title]" : initial(name)
|
|
return ..()
|
|
|
|
/obj/structure/sign/painting/update_desc(updates)
|
|
desc = current_canvas ? desc_with_canvas : initial(desc)
|
|
return ..()
|
|
|
|
/obj/structure/sign/painting/update_overlays()
|
|
. = ..()
|
|
if(!current_canvas?.generated_icon)
|
|
return
|
|
|
|
var/mutable_appearance/painting = mutable_appearance(current_canvas.generated_icon)
|
|
painting.pixel_w = current_canvas.framed_offset_x
|
|
painting.pixel_z = current_canvas.framed_offset_y
|
|
. += painting
|
|
var/frame_type = current_canvas.painting_metadata.frame_type
|
|
. += mutable_appearance(current_canvas.icon,"[current_canvas.icon_state]frame_[frame_type]") //add the 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 FALSE
|
|
var/list/valid_paintings = SSpersistent_paintings.get_paintings_with_tag(persistence_id)
|
|
if(!length(valid_paintings))
|
|
return FALSE //aborts loading anything this category has no usable paintings
|
|
var/datum/painting/painting = pick(valid_paintings)
|
|
var/png = "data/paintings/images/[painting.md5].png"
|
|
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)
|
|
if(!(new_canvas in accepted_canvas_types))
|
|
CRASH("Found painting with canvas size not compatible with this frame. Canvas type: [new_canvas]")
|
|
new_canvas = new T(src)
|
|
break
|
|
if(!istype(new_canvas))
|
|
CRASH("Found painting size with no matching canvas type")
|
|
new_canvas.painting_metadata = painting
|
|
fill_grid_from_icon(new_canvas.workspace.get_first_layer_pixel_data(), I)
|
|
new_canvas.generated_icon = I
|
|
new_canvas.icon_generated = TRUE
|
|
new_canvas.finalized = TRUE
|
|
new_canvas.name = "painting - [painting.title]"
|
|
current_canvas = new_canvas
|
|
add_art_element()
|
|
current_canvas.update_appearance()
|
|
update_appearance()
|
|
return TRUE
|
|
|
|
/obj/structure/sign/painting/proc/add_art_element()
|
|
var/artistic_value = get_art_value(current_canvas.painting_metadata.credit_value)
|
|
if(artistic_value)
|
|
AddElement(/datum/element/art, artistic_value)
|
|
|
|
/obj/structure/sign/painting/proc/remove_art_element(patronage)
|
|
var/artistic_value = get_art_value(patronage)
|
|
if(artistic_value)
|
|
RemoveElement(/datum/element/art, artistic_value)
|
|
|
|
/obj/structure/sign/painting/proc/get_art_value(patronage)
|
|
switch(patronage)
|
|
if(PATRONAGE_SUPERB_FRAME to INFINITY)
|
|
return GREAT_ART
|
|
if(PATRONAGE_EXCELLENT_FRAME to PATRONAGE_SUPERB_FRAME)
|
|
return GOOD_ART
|
|
if(PATRONAGE_NICE_FRAME to PATRONAGE_EXCELLENT_FRAME)
|
|
return OK_ART
|
|
return 0
|
|
|
|
/obj/structure/sign/painting/proc/save_persistent()
|
|
if(!persistence_id || !current_canvas || current_canvas.no_save || current_canvas.painting_metadata.loaded_from_json)
|
|
return
|
|
if(SANITIZE_FILENAME(persistence_id) != persistence_id)
|
|
stack_trace("Invalid persistence_id - [persistence_id]")
|
|
return
|
|
var/data = current_canvas.get_data_string()
|
|
var/md5 = md5(LOWER_TEXT(data))
|
|
var/list/current = SSpersistent_paintings.paintings[persistence_id]
|
|
if(!current)
|
|
current = list()
|
|
for(var/datum/painting/entry in SSpersistent_paintings.paintings)
|
|
if(entry.md5 == md5) // No duplicates
|
|
return
|
|
current_canvas.painting_metadata.md5 = md5
|
|
if(!current_canvas.painting_metadata.tags)
|
|
current_canvas.painting_metadata.tags = list(persistence_id)
|
|
else
|
|
current_canvas.painting_metadata.tags |= persistence_id
|
|
var/png_directory = "data/paintings/images/"
|
|
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]")
|
|
SSpersistent_paintings.paintings += current_canvas.painting_metadata
|
|
|
|
/obj/item/canvas/proc/fill_grid_from_icon(icon/I)
|
|
var/list/grid = workspace.layers[1]["data"]["[SOUTH]"]
|
|
var/h = I.Height() + 1
|
|
for(var/x in 1 to width)
|
|
for(var/y in 1 to height)
|
|
grid[y][x] = I.GetPixel(x,h-y)
|
|
|
|
/obj/item/wallframe/painting/large
|
|
name = "large painting frame"
|
|
desc = "The perfect showcase for your favorite deathtrap memories. Make sure you have enough space to mount this one to the wall."
|
|
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT*2)
|
|
icon_state = "frame-large-empty"
|
|
result_path = /obj/structure/sign/painting/large
|
|
pixel_shift = 0 //See [/obj/structure/sign/painting/large/proc/finalize_size]
|
|
custom_price = PAYCHECK_CREW * 1.25
|
|
|
|
/obj/item/wallframe/painting/large/try_build(turf/on_wall, mob/user)
|
|
. = ..()
|
|
if(!.)
|
|
return
|
|
var/our_dir = get_dir(user, on_wall)
|
|
var/check_dir = our_dir & (EAST|WEST) ? NORTH : EAST
|
|
var/turf/closed/wall/second_wall = get_step(on_wall, check_dir)
|
|
if(!istype(second_wall) || !second_wall.IsReachableBy(user))
|
|
to_chat(user, span_warning("You need a reachable wall to the [check_dir == EAST ? "right" : "left"] of this one to mount this frame!"))
|
|
return FALSE
|
|
if(check_wall_item(second_wall, our_dir, wall_external))
|
|
to_chat(user, span_warning("There's already an item on the wall to the [check_dir == EAST ? "right" : "left"] of this one!"))
|
|
return FALSE
|
|
|
|
/obj/item/wallframe/painting/large/after_attach(obj/object)
|
|
. = ..()
|
|
var/obj/structure/sign/painting/large/our_frame = object
|
|
our_frame.finalize_size()
|
|
|
|
/obj/structure/sign/painting/large
|
|
icon = 'icons/obj/art/artstuff_64x64.dmi'
|
|
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT*2)
|
|
accepted_canvas_types = list(
|
|
/obj/item/canvas/thirtysix_twentyfour,
|
|
/obj/item/canvas/fortyfive_twentyseven,
|
|
)
|
|
wallframe_type = /obj/item/wallframe/painting/large
|
|
|
|
/obj/structure/sign/painting/large/Initialize(mapload)
|
|
// Necessary so that the painting is framed correctly by the frame overlay when flipped.
|
|
ADD_KEEP_TOGETHER(src, INNATE_TRAIT)
|
|
if(mapload)
|
|
finalize_size()
|
|
return ..()
|
|
|
|
/**
|
|
* This frame is visually put between two wall turfs and it has an icon that's bigger than 32px, and because
|
|
* of the way it's designed, the pixel_shift variable from the wallframe item won't do.
|
|
* Also we want higher bounds so it actually covers an extra wall turf, so that it can count toward check_wall_item calls for
|
|
* that wall turf.
|
|
*/
|
|
/obj/structure/sign/painting/large/proc/finalize_size()
|
|
switch(dir)
|
|
if(SOUTH)
|
|
pixel_y = -32
|
|
bound_width = 64
|
|
if(NORTH)
|
|
bound_width = 64
|
|
if(WEST)
|
|
// Totally intended so that the frame sprite doesn't spill behind the wall and get partly covered by the darkness plane.
|
|
// Ditto for the ones below.
|
|
pixel_x = -29
|
|
bound_height = 64
|
|
if(EAST)
|
|
bound_height = 64
|
|
|
|
/obj/structure/sign/painting/large/get_turfs_to_mount_on()
|
|
return (!pixel_x && !pixel_y) ? list(get_step(src, dir)) : ..()
|
|
|
|
/obj/structure/sign/painting/large/frame_canvas(mob/living/user, obj/item/canvas/new_canvas)
|
|
. = ..()
|
|
if(.)
|
|
set_painting_offsets()
|
|
|
|
/obj/structure/sign/painting/large/load_persistent()
|
|
. = ..()
|
|
if(.)
|
|
set_painting_offsets()
|
|
|
|
/obj/structure/sign/painting/large/proc/set_painting_offsets()
|
|
switch(dir)
|
|
if(EAST)
|
|
transform = transform.Turn(90)
|
|
pixel_x += 29
|
|
pixel_y += 29
|
|
if(WEST)
|
|
transform = transform.Turn(-90)
|
|
if(NORTH)
|
|
pixel_y += 29
|
|
|
|
/obj/structure/sign/painting/large/Exited(atom/movable/movable, atom/newloc)
|
|
if(movable == current_canvas)
|
|
switch(dir)
|
|
if(EAST)
|
|
transform = transform.Turn(-90)
|
|
pixel_x -= 29
|
|
pixel_y -= 29
|
|
if(WEST)
|
|
transform = transform.Turn(90)
|
|
if(NORTH)
|
|
pixel_y -= 29
|
|
return ..()
|
|
|
|
//Presets for art gallery mapping, for paintings to be shared across stations
|
|
/obj/structure/sign/painting/library
|
|
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 = "library"
|
|
|
|
/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_secure"
|
|
|
|
/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"
|
|
|
|
/obj/structure/sign/painting/large/library
|
|
name = "\improper Large Painting Exhibit mounting"
|
|
desc = "For the bulkier art pieces, hand-picked by the curator."
|
|
desc_with_canvas = "A curated, large piece of art (or \"art\"). Hopefully the price of the canvas was worth it."
|
|
persistence_id = "library_large"
|
|
|
|
/obj/structure/sign/painting/large/library_private
|
|
name = "\improper Private Painting Exhibit mounting"
|
|
desc = "For the privier and less tasteful compositions that oughtn't to be shown in a parlor nor to the masses."
|
|
desc_with_canvas = "A painting that oughn't to be shown to the less open-minded commoners."
|
|
persistence_id = "library_large_private"
|
|
|
|
|
|
#define AVAILABLE_PALETTE_SPACE 14 // Enough to fill two radial menu pages
|
|
|
|
/// Simple painting utility.
|
|
/obj/item/paint_palette
|
|
name = "paint palette"
|
|
desc = "paintbrush included"
|
|
icon = 'icons/obj/art/artstuff.dmi'
|
|
icon_state = "palette"
|
|
lefthand_file = 'icons/mob/inhands/equipment/palette_lefthand.dmi'
|
|
righthand_file = 'icons/mob/inhands/equipment/palette_righthand.dmi'
|
|
w_class = WEIGHT_CLASS_TINY
|
|
///Chosen paint color
|
|
var/current_color = COLOR_BLACK
|
|
|
|
/obj/item/paint_palette/Initialize(mapload)
|
|
. = ..()
|
|
AddComponent(/datum/component/palette, AVAILABLE_PALETTE_SPACE, current_color)
|
|
|
|
/obj/item/paint_palette/attack_self(mob/user, modifiers)
|
|
. = ..()
|
|
pick_painting_tool_color(user, current_color)
|
|
|
|
/obj/item/paint_palette/set_painting_tool_color(chosen_color)
|
|
. = ..()
|
|
current_color = chosen_color
|
|
|
|
#undef AVAILABLE_PALETTE_SPACE
|