diff --git a/aurorastation.dme b/aurorastation.dme
index ce705c103e8..1c7c783bfab 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -873,9 +873,11 @@
#include "code\game\objects\items\bees_items.dm"
#include "code\game\objects\items\blueprints.dm"
#include "code\game\objects\items\bodybag.dm"
+#include "code\game\objects\items\canvas.dm"
#include "code\game\objects\items\contraband.dm"
#include "code\game\objects\items\crayons.dm"
#include "code\game\objects\items\draftingchalk.dm"
+#include "code\game\objects\items\easel.dm"
#include "code\game\objects\items\eightball.dm"
#include "code\game\objects\items\glassjar.dm"
#include "code\game\objects\items\holomenu.dm"
diff --git a/code/game/objects/items/canvas.dm b/code/game/objects/items/canvas.dm
new file mode 100644
index 00000000000..45f52a1fc1d
--- /dev/null
+++ b/code/game/objects/items/canvas.dm
@@ -0,0 +1,139 @@
+/**
+ * Interface for using DrawBox() to draw 1 pixel on a coordinate.
+ * Returns the same icon specifed in the argument, but with the pixel drawn.
+ */
+/proc/DrawPixel(icon/I, color, drawX, drawY)
+ if(!I)
+ return FALSE
+
+ var/Iwidth = I.Width()
+ var/Iheight = I.Height()
+
+ if(drawX > Iwidth || drawX <= 0)
+ return FALSE
+ if(drawY > Iheight || drawY <= 0)
+ return FALSE
+
+ I.DrawBox(color, drawX, drawY)
+ return I
+
+/**
+ * Interface for easy drawing of one pixel on an atom.
+ */
+/atom/proc/DrawPixelOn(color, drawX, drawY)
+ var/icon/I = new(icon)
+ var/icon/J = DrawPixel(I, color, drawX, drawY)
+ if(J) // Only set the icon if it succeeded, the icon without the pixel is 1000x better than a black square.
+ icon = J
+ return J
+ return FALSE
+
+/obj/item/canvas
+ name = "11px by 11px canvas"
+ desc = "Draw out your soul on this canvas! Only crayons can draw on it."
+ icon = 'icons/obj/canvas.dmi'
+ icon_state = "11x11"
+
+/obj/item/canvas/nineteen_nineteen
+ name = "19px by 19px canvas"
+ icon_state = "19x19"
+
+/obj/item/canvas/twentythree_nineteen
+ name = "23px by 19px canvas"
+ icon_state = "23x19"
+
+/obj/item/canvas/twentythree_twentythree
+ name = "23px by 23px canvas"
+ icon_state = "23x23"
+
+/**
+ * One pixel increments.
+ */
+/obj/item/canvas/attackby(obj/item/I, mob/user, params)
+ if(I.iswrench())
+ playsound(src.loc, I.usesound, 100, 1)
+ to_chat(user, SPAN_NOTICE("You begin to [anchored ? "loosen" : "tighten"] \the [src]..."))
+ if(do_after(user, 40 / I.toolspeed))
+ user.visible_message("[user] [anchored ? "loosens" : "tightens"] \the [src].", SPAN_NOTICE("You [anchored ? "loosen" : "tighten"] \the [src]."), SPAN_NOTICE("You hear a ratchet."))
+ anchored = !anchored
+ return
+
+ // Click info.
+ var/list/click_params = params2list(params)
+ var/pixX = text2num(click_params["icon-x"])
+ var/pixY = text2num(click_params["icon-y"])
+
+ // Should always be true, otherwise you didn't click the object, but let's check because SS13~
+ if(!click_params || !click_params["icon-x"] || !click_params["icon-y"])
+ return
+
+ // Cleaning one pixel with a soap or rag.
+ if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/glass/rag))
+ // Pixel info created only when needed.
+ var/icon/masterpiece = icon(icon,icon_state)
+ var/thePix = masterpiece.GetPixel(pixX,pixY)
+ var/icon/Ico = clean_canvas()
+ if(!Ico)
+ qdel(masterpiece)
+ return
+
+ var/theOriginalPix = Ico.GetPixel(pixX,pixY)
+ if(thePix != theOriginalPix) // Clour changed.
+ DrawPixelOn(theOriginalPix,pixX,pixY)
+ qdel(masterpiece)
+
+ // Drawing one pixel with a crayon.
+ else if(istype(I, /obj/item/pen/crayon))
+ var/obj/item/pen/crayon/C = I
+ DrawPixelOn(C.shadeColour, pixX, pixY)
+ else
+ return ..()
+
+/**
+ * Clean the whole canvas.
+ */
+/obj/item/canvas/attack_self(mob/user)
+ if(!user)
+ return
+ var/icon/blank = clean_canvas()
+ if(blank)
+ // It's basically a giant etch-a-sketch.
+ icon = blank
+ user.visible_message("[user] cleans the canvas.", SPAN_NOTICE("You clean the canvas."))
+
+/obj/item/canvas/afterattack(turf/A, mob/user, flag, params)
+ if(!istype(A))
+ return
+
+ if(!iswall(A))
+ return
+
+ var/turf/target_turf = A
+ var/turf/source_turf = get_turf(user)
+
+ var/dir_offset = 0
+ if(target_turf != source_turf)
+ dir_offset = get_dir(source_turf, target_turf)
+ if(!(dir_offset in global.cardinal))
+ to_chat(user, SPAN_WARNING("You cannot reach that from here."))
+ return
+
+ if(user.unEquip(src, source_turf))
+ if(params)
+ var/list/mouse_control = params2list(params)
+ if(mouse_control["icon-x"])
+ pixel_x = text2num(mouse_control["icon-x"]) - 16
+ if(dir_offset & EAST)
+ pixel_x += 32
+ else if(dir_offset & WEST)
+ pixel_x -= 32
+ if(mouse_control["icon-y"])
+ pixel_y = text2num(mouse_control["icon-y"]) - 16
+ if(dir_offset & NORTH)
+ pixel_y += 32
+ else if(dir_offset & SOUTH)
+ pixel_y -= 32
+
+/obj/item/canvas/proc/clean_canvas()
+ var/icon/I = icon(initial(icon), initial(icon_state))
+ . = I
\ No newline at end of file
diff --git a/code/game/objects/items/easel.dm b/code/game/objects/items/easel.dm
new file mode 100644
index 00000000000..925de487ec7
--- /dev/null
+++ b/code/game/objects/items/easel.dm
@@ -0,0 +1,47 @@
+/obj/structure/easel
+ name = "easel"
+ desc = "Only for the finest of art!"
+ icon = 'icons/obj/canvas.dmi'
+ icon_state = "easel"
+ density = TRUE
+ build_amt = 5
+ var/obj/item/canvas/painting = null
+
+/obj/structure/easel/Initialize(ml, _mat, _reinf_mat)
+ . = ..()
+ moved_event.register(src, src, /obj/structure/easel/proc/move_painting)
+
+/obj/structure/easel/Destroy()
+ painting = null
+ moved_event.unregister(src, src, /obj/structure/easel/proc/move_painting)
+ return ..()
+
+/*
+ * Adding canvases.
+ */
+/obj/structure/easel/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/canvas))
+ var/obj/item/canvas/C = I
+ if(user.unEquip(C))
+ painting = C
+ C.forceMove(get_turf(src))
+ C.layer = layer + 0.1
+ C.pixel_x = 0
+ C.pixel_y = 0
+ C.pixel_z = 0
+ user.visible_message("[user] puts \the [C] on \the [src].", SPAN_NOTICE("You place \the [C] on \the [src]."))
+ return
+ if(I.iswrench())
+ to_chat(user, SPAN_NOTICE("You dismantle \the [src]."))
+ dismantle()
+ return
+ return ..()
+
+/*
+ * Stick to the easel like glue.
+ */
+/obj/structure/easel/proc/move_painting()
+ if(painting && Adjacent(painting)) // Only move if it's near us.
+ painting.forceMove(get_turf(src))
+ else
+ painting = null
\ No newline at end of file
diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm
index 0e62b0def28..d6d02ebda2b 100644
--- a/code/modules/library/lib_items.dm
+++ b/code/modules/library/lib_items.dm
@@ -46,11 +46,9 @@
to_chat(user, "You begin dismantling \the [src].")
if(do_after(user,25))
to_chat(user, "You dismantle \the [src].")
- new /obj/item/stack/material/wood(get_turf(src), 3)
for(var/obj/item/book/b in contents)
b.forceMove((get_turf(src)))
- qdel(src)
-
+ dismantle()
else
..()
diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm
index 8542e65af86..39663e46848 100644
--- a/code/modules/materials/material_recipes.dm
+++ b/code/modules/materials/material_recipes.dm
@@ -165,7 +165,8 @@
new /datum/stack_recipe("wooden bucket", /obj/item/reagent_containers/glass/bucket/wood, 2, time = 4, one_per_turf = 0, on_floor = 0),
new /datum/stack_recipe("shaft", /obj/item/material/shaft, 10, time = 25, one_per_turf = 0, on_floor = 0),
new /datum/stack_recipe("buckler donut", /obj/item/material/woodenshield, 20, time = 25, one_per_turf = 0, on_floor = 0),
- new /datum/stack_recipe("torch handle", /obj/item/torch, 3, time = 5, one_per_turf = 0, on_floor = 0)
+ new /datum/stack_recipe("torch handle", /obj/item/torch, 3, time = 5, one_per_turf = 0, on_floor = 0),
+ new /datum/stack_recipe("easel", /obj/structure/easel, BUILD_AMT, time = 15, one_per_turf = 1, on_floor = 1)
))
/material/stone/generate_recipes()
@@ -209,6 +210,13 @@
new /datum/stack_recipe("red shower curtain", /obj/structure/curtain/open/shower/security, BUILD_AMT, time = 10),
new /datum/stack_recipe("privacy curtain", /obj/structure/curtain/open/privacy, BUILD_AMT, time = 10)
))
+ recipes += new /datum/stack_recipe_list("[display_name] canvases",
+ list(
+ new /datum/stack_recipe("canvas 11x11", /obj/item/canvas, 3),
+ new /datum/stack_recipe("canvas 19x19", /obj/item/canvas/nineteen_nineteen, 5),
+ new /datum/stack_recipe("canvas 23x19", /obj/item/canvas/twentythree_nineteen, 6),
+ new /datum/stack_recipe("canvas 23x23", /obj/item/canvas/twentythree_twentythree, 8)
+ ))
/material/hide/xeno/generate_recipes()
..()
diff --git a/html/changelogs/geeves-painting_time.yml b/html/changelogs/geeves-painting_time.yml
new file mode 100644
index 00000000000..ef10a8ac37c
--- /dev/null
+++ b/html/changelogs/geeves-painting_time.yml
@@ -0,0 +1,7 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "You can now make an easel out of 5 wood, and a canvas of various sizes out of cloth. To draw on the canvas, use a crayon on it, to clean, use soap or a rag. Using the canvas in-hand will completely clean it."
+ - tweak: "Bookcases now drop all the materials used to make them when dismantled."
\ No newline at end of file
diff --git a/icons/obj/canvas.dmi b/icons/obj/canvas.dmi
new file mode 100644
index 00000000000..ea24341f5e0
Binary files /dev/null and b/icons/obj/canvas.dmi differ