diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm
index 20d9cdb2c1e..15d78f37276 100644
--- a/code/datums/supplypacks.dm
+++ b/code/datums/supplypacks.dm
@@ -1249,7 +1249,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine
/datum/supply_packs/misc/watertank
name = "Water Tank Crate"
contains = list(/obj/structure/reagent_dispensers/watertank)
- cost = 6
+ cost = 8
containertype = /obj/structure/largecrate
containername = "water tank crate"
@@ -1663,6 +1663,26 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine
)
containername = "shield control board crate"
+///////////// Bathroom Fixtures
+
+/datum/supply_packs/misc/toilet
+ name = "Lavatory Crate"
+ cost = 10
+ contains = list(
+ /obj/item/weapon/bathroom_parts,
+ /obj/item/weapon/bathroom_parts/urinal
+ )
+ containername = "lavatory crate"
+
+/datum/supply_packs/misc/hygiene
+ name = "Hygiene Station Crate"
+ cost = 10
+ contains = list(
+ /obj/item/weapon/bathroom_parts/sink,
+ /obj/item/mounted/shower
+ )
+ containername = "hygiene station crate"
+
//////////////////////////////////////////////////////////////////////////////
//////////////////////////// Vending /////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 61eec85e9cc..1ce51657e85 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -359,3 +359,14 @@
dense_object_backup = AM
break
. = dense_object_backup
+
+/atom/movable/proc/transfer_prints_to(atom/movable/target = null, overwrite = FALSE)
+ if(!target)
+ return
+ if(overwrite)
+ target.fingerprints = fingerprints
+ target.fingerprintshidden = fingerprintshidden
+ else
+ target.fingerprints += fingerprints
+ target.fingerprintshidden += fingerprintshidden
+ target.fingerprintslast = fingerprintslast
diff --git a/code/game/objects/items/mountable_frames/apc_frame.dm b/code/game/objects/items/mountable_frames/apc_frame.dm
index 11542cdd94c..fbdab5584f1 100644
--- a/code/game/objects/items/mountable_frames/apc_frame.dm
+++ b/code/game/objects/items/mountable_frames/apc_frame.dm
@@ -10,11 +10,11 @@
var/turf/turf_loc = get_turf(user)
var/area/area_loc = turf_loc.loc
if(area_loc.get_apc())
- to_chat(user, "This area already has an APC.")
+ to_chat(user, "This area already has an APC.")
return //only one APC per area
for(var/obj/machinery/power/terminal/T in turf_loc)
if(T.master)
- to_chat(user, "There is another network terminal here.")
+ to_chat(user, "There is another network terminal here.")
return
else
var/obj/item/stack/cable_coil/C = new /obj/item/stack/cable_coil(turf_loc)
diff --git a/code/game/objects/items/mountable_frames/frames.dm b/code/game/objects/items/mountable_frames/frames.dm
index 0bc5a8f9be6..2bcbea8cbee 100644
--- a/code/game/objects/items/mountable_frames/frames.dm
+++ b/code/game/objects/items/mountable_frames/frames.dm
@@ -20,11 +20,11 @@
var/turf/turf_loc = get_turf(user)
if(src.mount_reqs.Find("simfloor") && !istype(turf_loc, /turf/simulated/floor))
- to_chat(user, "[src] cannot be placed on this spot.")
+ to_chat(user, "[src] cannot be placed on this spot.")
return
if(src.mount_reqs.Find("nospace"))
var/area/my_area = turf_loc.loc
if(!istype(my_area) || (my_area.requires_power == 0 || istype(my_area,/area/space)))
- to_chat(user, "[src] cannot be placed in this area.")
+ to_chat(user, "[src] cannot be placed in this area.")
return
return 1
diff --git a/code/game/objects/items/mountable_frames/mountables.dm b/code/game/objects/items/mountable_frames/mountables.dm
index 1b720712fa9..7120f8c880d 100644
--- a/code/game/objects/items/mountable_frames/mountables.dm
+++ b/code/game/objects/items/mountable_frames/mountables.dm
@@ -21,11 +21,11 @@
if(proximity_flag != 1) //if we aren't next to the wall
return
if(!( get_dir(on_wall,user) in cardinal))
- to_chat(user, "You need to be standing next to a wall to place \the [src].")
+ to_chat(user, "You need to be standing next to a wall to place \the [src].")
return
if(gotwallitem(get_turf(user), get_dir(on_wall,user)))
- to_chat(user, "There's already an item on this wall!")
+ to_chat(user, "There's already an item on this wall!")
return
return 1
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index 83220bb6a33..4dcccb2145d 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -9,8 +9,22 @@
var/shattered = 0
var/list/ui_users = list()
-/obj/structure/mirror/attack_hand(mob/user as mob)
- if(shattered) return
+/obj/structure/mirror/New(turf/T, newdir = SOUTH, building = FALSE)
+ ..()
+ if(building)
+ switch(newdir)
+ if(NORTH)
+ pixel_y = -32
+ if(SOUTH)
+ pixel_y = 32
+ if(EAST)
+ pixel_x = -32
+ if(WEST)
+ pixel_x = 32
+
+/obj/structure/mirror/attack_hand(mob/user)
+ if(shattered)
+ return
if(ishuman(user))
var/datum/nano_module/appearance_changer/AC = ui_users[user]
@@ -22,14 +36,15 @@
AC.ui_interact(user)
/obj/structure/mirror/proc/shatter()
- if(shattered) return
+ if(shattered)
+ return
shattered = 1
icon_state = "mirror_broke"
playsound(src, "shatter", 70, 1)
desc = "Oh no, seven years of bad luck!"
-/obj/structure/mirror/bullet_act(var/obj/item/projectile/Proj)
+/obj/structure/mirror/bullet_act(obj/item/projectile/Proj)
if(prob(Proj.damage * 2))
if(!shattered)
shatter()
@@ -38,7 +53,19 @@
..()
-/obj/structure/mirror/attackby(obj/item/I as obj, mob/living/user as mob, params)
+/obj/structure/mirror/attackby(obj/item/I, mob/living/user, params)
+ if(isscrewdriver(I))
+ user.visible_message("[user] begins to unfasten [src].", "You begin to unfasten [src].")
+ if(do_after(user, 30 * I.toolspeed, target = src))
+ if(shattered)
+ user.visible_message("[user] drops the broken shards to the floor.", "You drop the broken shards on the floor.")
+ new /obj/item/weapon/shard(get_turf(user))
+ else
+ user.visible_message("[user] carefully places [src] on the floor.", "You carefully place [src] on the floor.")
+ new /obj/item/mounted/mirror(get_turf(user))
+ qdel(src)
+ return
+
user.do_attack_animation(src)
if(shattered)
playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1)
@@ -52,8 +79,9 @@
playsound(src.loc, 'sound/effects/Glasshit.ogg', 70, 1)
-/obj/structure/mirror/attack_alien(mob/living/user as mob)
- if(islarva(user)) return
+/obj/structure/mirror/attack_alien(mob/living/user)
+ if(islarva(user))
+ return
user.do_attack_animation(src)
if(shattered)
playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1)
@@ -62,10 +90,12 @@
shatter()
-/obj/structure/mirror/attack_animal(mob/living/user as mob)
- if(!isanimal(user)) return
+/obj/structure/mirror/attack_animal(mob/living/user)
+ if(!isanimal(user))
+ return
var/mob/living/simple_animal/M = user
- if(M.melee_damage_upper <= 0) return
+ if(M.melee_damage_upper <= 0)
+ return
M.do_attack_animation(src)
if(shattered)
playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1)
@@ -74,7 +104,7 @@
shatter()
-/obj/structure/mirror/attack_slime(mob/living/user as mob)
+/obj/structure/mirror/attack_slime(mob/living/user)
var/mob/living/carbon/slime/S = user
if(!S.is_adult)
return
@@ -83,4 +113,16 @@
playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1)
return
user.visible_message("[user] smashes [src]!")
- shatter()
\ No newline at end of file
+ shatter()
+
+
+/obj/item/mounted/mirror
+ name = "mirror"
+ desc = "Some reflective glass ready to be hung on a wall. Don't break it!"
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "mirror"
+
+/obj/item/mounted/mirror/do_build(turf/on_wall, mob/user)
+ var/obj/structure/mirror/M = new /obj/structure/mirror(get_turf(user), get_dir(on_wall, user), 1)
+ transfer_prints_to(M, TRUE)
+ qdel(src)
\ No newline at end of file
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index d3cc48d46bf..b8e176ba12a 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -47,12 +47,61 @@
open = !open
update_icon()
-
/obj/structure/toilet/update_icon()
icon_state = "toilet[open][cistern]"
-
+ if(!anchored)
+ pixel_x = 0
+ pixel_y = 0
+ layer = OBJ_LAYER
+ else
+ if(dir == SOUTH)
+ pixel_x = 0
+ pixel_y = 8
+ if(dir == NORTH)
+ pixel_x = 0
+ pixel_y = -8
+ layer = FLY_LAYER
/obj/structure/toilet/attackby(obj/item/I, mob/living/user, params)
+ if(iswrench(I))
+ var/choices = list()
+ if(cistern)
+ choices += "Stash"
+ if(anchored)
+ choices += "Disconnect"
+ else
+ choices += "Connect"
+ choices += "Rotate"
+
+ var/response = input(user, "What do you want to do?", "[src]") as null|anything in choices
+ if(!Adjacent(user) || !response) //moved away or cancelled
+ return
+ switch(response)
+ if("Stash")
+ stash_goods(I, user)
+ if("Disconnect")
+ user.visible_message("[user] starts disconnecting [src].", "You begin disconnecting [src]...")
+ if(do_after(user, 40 * I.toolspeed, target = src))
+ if(!loc || !anchored)
+ return
+ user.visible_message("[user] disconnects [src]!", "You disconnect [src]!")
+ anchored = 0
+ update_icon()
+ if("Connect")
+ user.visible_message("[user] starts connecting [src].", "You begin connecting [src]...")
+ if(do_after(user, 40 * I.toolspeed, target = src))
+ if(!loc || anchored)
+ return
+ user.visible_message("[user] connects [src]!", "You connect [src]!")
+ anchored = 1
+ update_icon()
+ if("Rotate")
+ var/list/dir_choices = list("North" = NORTH, "East" = EAST, "South" = SOUTH, "West" = WEST)
+ var/selected = input(user,"Select a direction for the connector.", "Connector Direction") in dir_choices
+ dir = dir_choices[selected]
+ update_icon() //is this necessary? probably not
+ return
+
if(istype(I, /obj/item/weapon/crowbar))
to_chat(user, "You start to [cistern ? "replace the lid on the cistern" : "lift the lid off the cistern"]...")
playsound(loc, 'sound/effects/stonedoor_openclose.ogg', 50, 1)
@@ -68,7 +117,7 @@
var/obj/item/weapon/reagent_containers/RG = I
if(RG.is_open_container())
if(RG.reagents.total_volume >= RG.volume)
- to_chat(user, "\The [RG] is full.")
+ to_chat(user, "[RG] is full.")
else
RG.reagents.add_reagent("toiletwater", min(RG.volume - RG.reagents.total_volume, RG.amount_per_transfer_from_this))
to_chat(user, "You fill [RG] from [src]. Gross.")
@@ -106,20 +155,25 @@
to_chat(user, "You need a tighter grip!")
if(cistern)
- if(I.w_class > WEIGHT_CLASS_NORMAL)
- to_chat(user, "[I] does not fit!")
- return
- if(w_items + I.w_class > WEIGHT_CLASS_HUGE)
- to_chat(user, "The cistern is full!")
- return
- if(!user.drop_item())
- to_chat(user, "\The [I] is stuck to your hand, you cannot put it in the cistern!")
- return
- I.loc = src
- w_items += I.w_class
- to_chat(user, "You carefully place [I] into the cistern.")
+ stash_goods(I, user)
return
+/obj/structure/toilet/proc/stash_goods(obj/item/I, mob/user)
+ if(!I)
+ return
+ if(I.w_class > WEIGHT_CLASS_NORMAL)
+ to_chat(user, "[I] does not fit!")
+ return
+ if(w_items + I.w_class > WEIGHT_CLASS_HUGE)
+ to_chat(user, "The cistern is full!")
+ return
+ if(!user.drop_item())
+ to_chat(user, "[I] is stuck to your hand, you cannot put it in the cistern!")
+ return
+ I.loc = src
+ w_items += I.w_class
+ to_chat(user, "You carefully place [I] into the cistern.")
+
/obj/structure/toilet/secret
var/secret_type = null
@@ -131,6 +185,7 @@
w_items += secret.w_class
+
/obj/structure/urinal
name = "urinal"
desc = "The HU-452, an experimental urinal."
@@ -141,6 +196,27 @@
/obj/structure/urinal/attackby(obj/item/I, mob/user, params)
+ if(iswrench(I))
+ if(anchored)
+ user.visible_message("[user] begins disconnecting [src]...", "You begin to disconnect [src]...")
+ if(do_after(user, 40 * I.toolspeed, target = src))
+ if(!loc || !anchored)
+ return
+ user.visible_message("[user] disconnects [src]!", "You disconnect [src]!")
+ anchored = 0
+ pixel_x = 0
+ pixel_y = 0
+ else
+ user.visible_message("[user] begins connecting [src]...", "You begin to connect [src]...")
+ if(do_after(user, 40 * I.toolspeed, target = src))
+ if(!loc || anchored)
+ return
+ user.visible_message("[user] connects [src]!", "You connect [src]!")
+ anchored = 1
+ pixel_x = 0
+ pixel_y = 32
+ return
+
if(istype(I, /obj/item/weapon/grab))
var/obj/item/weapon/grab/G = I
if(!G.confirm())
@@ -159,6 +235,7 @@
to_chat(user, "You need a tighter grip!")
+
/obj/machinery/shower
name = "shower"
desc = "The HS-451. Installed in the 2550s by the Nanotrasen Hygiene Division."
@@ -173,6 +250,24 @@
var/watertemp = "normal" //freezing, normal, or boiling
var/mobpresent = 0 //true if there is a mob on the shower's loc, this is to ease process()
+/obj/machinery/shower/New(turf/T, newdir = SOUTH, building = FALSE)
+ ..()
+ dir = newdir
+ if(building)
+ pixel_x = 0
+ pixel_y = 0
+ switch(newdir)
+ if(SOUTH)
+ pixel_y = 16
+ if(NORTH)
+ pixel_y = -5
+ layer = FLY_LAYER
+
+/obj/machinery/shower/Destroy()
+ if(mymist)
+ QDEL_NULL(mymist)
+ return ..()
+
//add heat controls? when emagged, you can freeze to death in it?
/obj/effect/mist
@@ -198,9 +293,9 @@
/obj/machinery/shower/attackby(obj/item/I as obj, mob/user as mob, params)
if(I.type == /obj/item/device/analyzer)
to_chat(user, "The water temperature seems to be [watertemp].")
- if(istype(I, /obj/item/weapon/wrench))
+ if(iswrench(I))
to_chat(user, "You begin to adjust the temperature valve with the [I].")
- if(do_after(user, 50, target = src))
+ if(do_after(user, 50 * I.toolspeed, target = src))
switch(watertemp)
if("normal")
watertemp = "freezing"
@@ -209,33 +304,57 @@
if("boiling")
watertemp = "normal"
user.visible_message("[user] adjusts the shower with the [I].", "You adjust the shower with the [I].")
+ update_icon() //letsa update whenever we change the temperature, since the mist might need to change
+ if(iswelder(I))
+ if(on)
+ to_chat(user, "Turn [src] off before you attempt to cut it loose.")
+ return
+ var/obj/item/weapon/weldingtool/WT = I
+ if(WT.isOn())
+ user.visible_message("[user] begins to cut [src] loose.", "You begin to cut [src] loose.")
+ if(do_after(user, 40 * WT.toolspeed, target = src))
+ if(!src || !WT.remove_fuel(0, user))
+ return
+ if(on) //in case someone turned it back on while you were working, make sure we shut that all down
+ on = 0
+ if(mymist)
+ qdel(mymist)
+ ismist = 0
+ user.visible_message("[user] cuts [src] loose!", "You cut [src] loose!")
+ var/obj/item/mounted/shower/S = new /obj/item/mounted/shower(get_turf(user))
+ transfer_prints_to(S, TRUE)
+ qdel(src)
+ else
+ to_chat(user, "[WT] must be on for this task.")
if(on)
I.water_act(100, convertHeat(), src)
-/obj/machinery/shower/update_icon() //this is terribly unreadable, but basically it makes the shower mist up
+/obj/machinery/shower/update_icon() //this makes the shower mist up or clear mist (depending on water temperature)
overlays.Cut() //once it's been on for a while, in addition to handling the water overlay.
- if(mymist)
- qdel(mymist)
-
if(on)
overlays += image('icons/obj/watercloset.dmi', src, "water", MOB_LAYER + 1, dir)
+ var/mist_time = 50 //5 seconds at normal temperature to build up mist
if(watertemp == "freezing")
+ mist_time = 70 //7 seconds on freezing temperature to disperse existing mist
+ if(watertemp == "boiling")
+ mist_time = 20 //2 seconds on boiling temperature to build up mist
+ addtimer(src, "update_mist", mist_time)
+ else
+ addtimer(src, "update_mist", 250) //25 seconds for mist to disperse after being turned off
+
+/obj/machinery/shower/proc/update_mist()
+ if(on)
+ if(watertemp == "freezing")
+ if(mymist)
+ qdel(mymist)
+ ismist = 0
return
- if(!ismist)
- spawn(50)
- if(src && on)
- ismist = 1
- mymist = new /obj/effect/mist(loc)
- else
- ismist = 1
- mymist = new /obj/effect/mist(loc)
- else if(ismist)
ismist = 1
mymist = new /obj/effect/mist(loc)
- spawn(250)
- if(src && !on)
- qdel(mymist)
- ismist = 0
+ else
+ if(mymist)
+ qdel(mymist)
+ ismist = 0
/obj/machinery/shower/Crossed(atom/movable/O)
..()
@@ -351,13 +470,16 @@
qdel(E)
/obj/machinery/shower/process()
- if(!on || !mobpresent) return
+ if(!on || !mobpresent)
+ return
for(var/mob/living/carbon/C in loc)
+ if(prob(33))
+ wash(C) //re-applies water and re-cleans mob while they remain under the shower, 33% chance per process to avoid message spam/quick death
check_heat(C)
/obj/machinery/shower/proc/check_heat(mob/M as mob)
- M.water_act(100, convertHeat(), src) //convenience
- if(!on || watertemp == "normal") return
+ if(!on || watertemp == "normal")
+ return
if(iscarbon(M))
var/mob/living/carbon/C = M
@@ -390,6 +512,8 @@
desc = "A sink used for washing one's hands and face."
anchored = 1
var/busy = 0 //Something's being washed at the moment
+ var/can_move = 1 //if the sink can be disconnected and moved
+ var/can_rotate = 1 //if the sink can be rotated to face alternate directions
/obj/structure/sink/attack_hand(mob/user as mob)
if(!user || !istype(user))
@@ -398,7 +522,9 @@
return
if(!Adjacent(user))
return
-
+ if(!anchored)
+ to_chat(user, "[src] isn't connected, wrench it into position first!")
+ return
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/external/temp = H.bodyparts_by_name["r_hand"]
@@ -415,7 +541,7 @@
var/washing_face = 0
if(selected_area in list("head", "mouth", "eyes"))
washing_face = 1
- user.visible_message("[user] start washing their [washing_face ? "face" : "hands"]...", \
+ user.visible_message("[user] starts washing their [washing_face ? "face" : "hands"]...", \
"You start washing your [washing_face ? "face" : "hands"]...")
busy = 1
@@ -446,6 +572,57 @@
if(!(istype(O)))
return
+ if(iswrench(O))
+ var/obj/item/weapon/wrench/W = O
+
+ var/choices = list()
+ if(anchored)
+ choices += "Wash"
+ if(can_move)
+ choices += "Disconnect"
+ else
+ choices += "Connect"
+ if(can_rotate)
+ choices += "Rotate"
+
+ var/response = input(user, "What do you want to do?", "[src]") as null|anything in choices
+ if(!Adjacent(user) || !response) //moved away or cancelled
+ return
+ switch(response)
+ if("Wash")
+ busy = 1
+ var/wateract = 0
+ wateract = (W.wash(user, src))
+ busy = 0
+ if(wateract)
+ W.water_act(20,310.15,src)
+ if("Disconnect")
+ user.visible_message("[user] starts disconnecting [src].", "You begin disconnecting [src]...")
+ if(do_after(user, 40 * O.toolspeed, target = src))
+ if(!loc || !anchored)
+ return
+ user.visible_message("[user] disconnects [src]!", "You disconnect [src]!")
+ anchored = 0
+ update_icon()
+ if("Connect")
+ user.visible_message("[user] starts connecting [src].", "You begin connecting [src]...")
+ if(do_after(user, 40 * O.toolspeed, target = src))
+ if(!loc || anchored)
+ return
+ user.visible_message("[user] connects [src]!", "You connect [src]!")
+ anchored = 1
+ update_icon()
+ if("Rotate")
+ var/list/dir_choices = list("North" = NORTH, "East" = EAST, "South" = SOUTH, "West" = WEST)
+ var/selected = input(user, "Select a direction for the connector.", "Connector Direction") in dir_choices
+ dir = dir_choices[selected]
+ update_icon() //is this necessary? probably not
+ return
+
+ if(!anchored)
+ to_chat(user, "[src] isn't connected, wrench it into position first!")
+ return
+
busy = 1
var/wateract = 0
wateract = (O.wash(user, src))
@@ -453,14 +630,42 @@
if(wateract)
O.water_act(20,310.15,src)
+/obj/structure/sink/update_icon()
+ ..()
+ layer = OBJ_LAYER
+ if(!anchored)
+ pixel_x = 0
+ pixel_y = 0
+ else
+ //the following code will probably want to be updated in the future to be less reliant on hardcoded offsets based on the can_move/can_rotate values
+ if(!can_move) //puddles
+ return
+ if(!can_rotate) //kitchen sinks
+ pixel_x = 0
+ pixel_y = 28
+ return
+ else //normal sinks
+ if(dir == NORTH || dir == SOUTH)
+ pixel_x = 0
+ pixel_y = (dir == NORTH) ? -5 : 30
+ if(dir == NORTH)
+ layer = FLY_LAYER
+ else
+ pixel_x = (dir == EAST) ? 12 : -12
+ pixel_y = 0
+
+
/obj/structure/sink/kitchen
name = "kitchen sink"
icon_state = "sink_alt"
+ can_rotate = 0
/obj/structure/sink/puddle //splishy splashy ^_^
name = "puddle"
icon_state = "puddle"
+ can_move = 0
+ can_rotate = 0
/obj/structure/sink/puddle/attack_hand(mob/M as mob)
icon_state = "puddle-splash"
@@ -471,3 +676,77 @@
icon_state = "puddle-splash"
..()
icon_state = "puddle"
+
+
+//////////////////////////////////
+// Bathroom Fixture Items //
+//////////////////////////////////
+
+
+/obj/item/mounted/shower
+ name = "shower fixture"
+ desc = "A self-adhering shower fixture. Simply stick to a wall, no plumber needed!"
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "shower"
+ item_state = "buildpipe"
+
+/obj/item/mounted/shower/try_build(turf/on_wall, mob/user, proximity_flag)
+ //overriding this because we don't care about other items on the wall, but still need to do adjacent checks
+ if(!on_wall || !user)
+ return
+ if(proximity_flag != 1) //if we aren't next to the wall
+ return
+ if(!(get_dir(on_wall, user) in cardinal))
+ to_chat(user, "You need to be standing next to a wall to place \the [src].")
+ return
+ return 1
+
+/obj/item/mounted/shower/do_build(turf/on_wall, mob/user)
+ var/obj/machinery/shower/S = new /obj/machinery/shower(get_turf(user), get_dir(on_wall, user), 1)
+ transfer_prints_to(S, TRUE)
+ qdel(src)
+
+
+/obj/item/weapon/bathroom_parts
+ name = "toilet in a box"
+ desc = "An entire toilet in a box, straight from Space Sweden. It has an unpronounceable name."
+ icon = 'icons/obj/storage.dmi'
+ icon_state = "largebox"
+ w_class = WEIGHT_CLASS_BULKY
+ var/result = /obj/structure/toilet
+ var/result_name = "toilet"
+
+/obj/item/weapon/bathroom_parts/urinal
+ name = "urinal in a box"
+ result = /obj/structure/urinal
+ result_name = "urinal"
+
+/obj/item/weapon/bathroom_parts/sink
+ name = "sink in a box"
+ result = /obj/structure/sink
+ result_name = "sink"
+
+/obj/item/weapon/bathroom_parts/New()
+ ..()
+ desc = "An entire [result_name] in a box, straight from Space Sweden. It has an [pick("unpronounceable", "overly accented", "entirely gibberish", "oddly normal-sounding")] name."
+
+/obj/item/weapon/bathroom_parts/attack_self(mob/user)
+ var/turf/T = get_turf(user)
+ if(!T)
+ to_chat(user, "You can't build that here!")
+ return
+ if(result in T.contents)
+ to_chat(user, "There's already \an [result_name] here.")
+ return
+ user.visible_message("[user] begins assembling a new [result_name].", "You begin assembling a new [result_name].")
+ if(do_after(user, 30, target = user))
+ user.visible_message("[user] finishes building a new [result_name]!", "You finish building a new [result_name]!")
+ var/obj/structure/S = new result(T)
+ S.anchored = 0
+ S.dir = user.dir
+ S.update_icon()
+ user.unEquip(src, 1)
+ qdel(src)
+ if(prob(50))
+ new /obj/item/stack/sheet/cardboard(T)
+
diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm
index 99d4fa8fe22..111e43dce32 100644
--- a/code/modules/research/designs/autolathe_designs.dm
+++ b/code/modules/research/designs/autolathe_designs.dm
@@ -855,4 +855,13 @@
build_type = AUTOLATHE
materials = list(MAT_GLASS = 750, MAT_METAL = 250)
build_path = /obj/item/weapon/circuitboard/vendor
- category = list("initial", "Electronics")
\ No newline at end of file
+ category = list("initial", "Electronics")
+
+/datum/design/mirror
+ name = "mirror"
+ desc = "A mountable mirror."
+ id = "mirror"
+ build_type = AUTOLATHE
+ materials = list(MAT_GLASS = 2500) //1.25 glass sheets, broken mirrors will return a shard (1 sheet)
+ build_path = /obj/item/mounted/mirror
+ category = list("initial", "Miscellaneous")
\ No newline at end of file
diff --git a/icons/obj/watercloset.dmi b/icons/obj/watercloset.dmi
index 61ea7ad3aac..4b00c72866f 100644
Binary files a/icons/obj/watercloset.dmi and b/icons/obj/watercloset.dmi differ