diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
index 5ac6cff21bb..c7ef4a576d3 100644
--- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm
+++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
@@ -21086,7 +21086,7 @@
/area/station/command/meeting_room)
"gnR" = (
/obj/structure/toilet/greyscale{
- cistern = 1;
+ cistern_open = 1;
dir = 1
},
/obj/machinery/light/small/directional/south,
@@ -54761,7 +54761,7 @@
/area/station/engineering/storage_shared)
"qrq" = (
/obj/structure/toilet/greyscale{
- cistern = 1;
+ cistern_open = 1;
dir = 1
},
/obj/effect/spawner/random/entertainment/cigar,
diff --git a/_maps/map_files/Mining/Lavaland.dmm b/_maps/map_files/Mining/Lavaland.dmm
index f2d6eb671e3..1ae71b1799f 100644
--- a/_maps/map_files/Mining/Lavaland.dmm
+++ b/_maps/map_files/Mining/Lavaland.dmm
@@ -5489,7 +5489,7 @@
/area/mine/lounge)
"Hz" = (
/obj/effect/decal/cleanable/dirt,
-/obj/structure/toilet/secret{
+/obj/structure/toilet{
dir = 4
},
/obj/effect/mob_spawn/corpse/human/skeleton,
diff --git a/code/game/objects/items/clown_items.dm b/code/game/objects/items/clown_items.dm
index 27fa0031485..52ba71a2538 100644
--- a/code/game/objects/items/clown_items.dm
+++ b/code/game/objects/items/clown_items.dm
@@ -233,6 +233,22 @@
M.emote("flip")
COOLDOWN_START(src, golden_horn_cooldown, 1 SECONDS)
+/obj/item/bikehorn/rubberducky/plasticducky
+ name = "plastic ducky"
+ desc = "It's a cheap plastic knockoff of a loveable bathtime toy."
+ custom_materials = list(/datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT)
+
+/obj/item/bikehorn/rubberducky
+ name = "rubber ducky"
+ desc = "Rubber ducky you're so fine, you make bathtime lots of fuuun. Rubber ducky I'm awfully fooooond of yooooouuuu~" //thanks doohl
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "rubberducky"
+ inhand_icon_state = "rubberducky"
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ worn_icon_state = "duck"
+ sound_file = 'sound/effects/quack.ogg'
+
//canned laughter
/obj/item/reagent_containers/cup/soda_cans/canned_laughter
name = "Canned Laughter"
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 3d976b72cca..f43cc8dfa1c 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -26,7 +26,7 @@
icon_state = ""
GLOB.cameranet.updateVisibility(src)
-/obj/structure/Destroy()
+/obj/structure/Destroy(force)
GLOB.cameranet.updateVisibility(src)
if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
QUEUE_SMOOTH_NEIGHBORS(src)
diff --git a/code/game/objects/structures/curtains.dm b/code/game/objects/structures/curtains.dm
new file mode 100644
index 00000000000..aead6fafb01
--- /dev/null
+++ b/code/game/objects/structures/curtains.dm
@@ -0,0 +1,156 @@
+/**
+ * Shower Curtains
+ */
+/obj/structure/curtain
+ name = "curtain"
+ desc = "Contains less than 1% mercury."
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "bathroom-open"
+ color = "#ACD1E9" //Default color, didn't bother hardcoding other colors, mappers can and should easily change it.
+ alpha = 200 //Mappers can also just set this to 255 if they want curtains that can't be seen through
+ layer = SIGN_LAYER
+ anchored = TRUE
+ opacity = FALSE
+ density = FALSE
+ /// used in making the icon state
+ var/icon_type = "bathroom"
+ var/open = TRUE
+ /// if it can be seen through when closed
+ var/opaque_closed = FALSE
+
+/obj/structure/curtain/Initialize(mapload)
+ // see-through curtains should let emissives shine through
+ if(!opaque_closed)
+ blocks_emissive = EMISSIVE_BLOCK_NONE
+ return ..()
+
+/obj/structure/curtain/proc/toggle()
+ open = !open
+ if(open)
+ layer = SIGN_LAYER
+ set_opacity(FALSE)
+ else
+ layer = WALL_OBJ_LAYER
+ if(opaque_closed)
+ set_opacity(TRUE)
+
+ update_appearance()
+
+/obj/structure/curtain/update_icon_state()
+ icon_state = "[icon_type]-[open ? "open" : "closed"]"
+ return ..()
+
+/obj/structure/curtain/attackby(obj/item/W, mob/user)
+ if (istype(W, /obj/item/toy/crayon))
+ color = input(user,"","Choose Color",color) as color
+ else
+ return ..()
+
+/obj/structure/curtain/wrench_act(mob/living/user, obj/item/tool)
+ . = ..()
+ default_unfasten_wrench(user, tool, time = 5 SECONDS)
+ return TRUE
+
+/obj/structure/curtain/wirecutter_act(mob/living/user, obj/item/I)
+ ..()
+ if(anchored)
+ return TRUE
+
+ user.visible_message(span_warning("[user] cuts apart [src]."),
+ span_notice("You start to cut apart [src]."), span_hear("You hear cutting."))
+ if(I.use_tool(src, user, 50, volume=100) && !anchored)
+ to_chat(user, span_notice("You cut apart [src]."))
+ deconstruct()
+
+ return TRUE
+
+
+/obj/structure/curtain/attack_hand(mob/user, list/modifiers)
+ . = ..()
+ if(.)
+ return
+ playsound(loc, 'sound/effects/curtain.ogg', 50, TRUE)
+ toggle()
+
+/obj/structure/curtain/atom_deconstruct(disassembled = TRUE)
+ new /obj/item/stack/sheet/cloth (loc, 2)
+ new /obj/item/stack/sheet/plastic (loc, 2)
+ new /obj/item/stack/rods (loc, 1)
+
+/obj/structure/curtain/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
+ switch(damage_type)
+ if(BRUTE)
+ if(damage_amount)
+ playsound(src.loc, 'sound/weapons/slash.ogg', 80, TRUE)
+ else
+ playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE)
+ if(BURN)
+ playsound(loc, 'sound/items/welder.ogg', 80, TRUE)
+
+/obj/structure/curtain/bounty
+ icon_type = "bounty"
+ icon_state = "bounty-open"
+ color = null
+ alpha = 255
+ opaque_closed = TRUE
+
+/obj/structure/curtain/bounty/start_closed
+ icon_state = "bounty-closed"
+
+/obj/structure/curtain/bounty/start_closed/Initialize(mapload)
+ . = ..()
+ if(open)
+ toggle()
+
+/obj/structure/curtain/cloth
+ color = null
+ alpha = 255
+ opaque_closed = TRUE
+
+/obj/structure/curtain/cloth/atom_deconstruct(disassembled = TRUE)
+ new /obj/item/stack/sheet/cloth (loc, 4)
+ new /obj/item/stack/rods (loc, 1)
+
+/obj/structure/curtain/cloth/fancy
+ icon_type = "cur_fancy"
+ icon_state = "cur_fancy-open"
+
+/obj/structure/curtain/cloth/fancy/mechanical
+ var/id = null
+
+/obj/structure/curtain/cloth/fancy/mechanical/Destroy()
+ GLOB.curtains -= src
+ return ..()
+
+/obj/structure/curtain/cloth/fancy/mechanical/Initialize(mapload)
+ . = ..()
+ GLOB.curtains += src
+
+/obj/structure/curtain/cloth/fancy/mechanical/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ id = "[port.shuttle_id]_[id]"
+
+/obj/structure/curtain/cloth/fancy/mechanical/proc/open()
+ icon_state = "[icon_type]-open"
+ layer = SIGN_LAYER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
+ set_density(FALSE)
+ open = TRUE
+ set_opacity(FALSE)
+
+/obj/structure/curtain/cloth/fancy/mechanical/proc/close()
+ icon_state = "[icon_type]-closed"
+ layer = WALL_OBJ_LAYER
+ set_density(TRUE)
+ open = FALSE
+ if(opaque_closed)
+ set_opacity(TRUE)
+
+/obj/structure/curtain/cloth/fancy/mechanical/attack_hand(mob/user, list/modifiers)
+ return
+
+/obj/structure/curtain/cloth/fancy/mechanical/start_closed
+ icon_state = "cur_fancy-closed"
+
+/obj/structure/curtain/cloth/fancy/mechanical/start_closed/Initialize(mapload)
+ . = ..()
+ close()
diff --git a/code/game/objects/structures/water_structures/sink.dm b/code/game/objects/structures/water_structures/sink.dm
new file mode 100644
index 00000000000..f4c9d5a28f4
--- /dev/null
+++ b/code/game/objects/structures/water_structures/sink.dm
@@ -0,0 +1,301 @@
+/obj/structure/sink
+ name = "sink"
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "sink"
+ desc = "A sink used for washing one's hands and face. Passively reclaims water over time."
+ anchored = TRUE
+ layer = ABOVE_OBJ_LAYER
+ pixel_z = 1
+ ///Something's being washed at the moment
+ var/busy = FALSE
+ ///What kind of reagent is produced by this sink by default? (We now have actual plumbing, Arcane, August 2020)
+ var/dispensedreagent = /datum/reagent/water
+ ///Material to drop when broken or deconstructed.
+ var/buildstacktype = /obj/item/stack/sheet/iron
+ ///Number of sheets of material to drop when broken or deconstructed.
+ var/buildstackamount = 1
+ ///Does the sink have a water recycler to recollect it's water supply?
+ var/has_water_reclaimer = TRUE
+ ///Units of water to reclaim per second
+ var/reclaim_rate = 0.5
+ ///Amount of shift the pixel for placement
+ var/pixel_shift = 14
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink, (-14))
+
+/obj/structure/sink/Initialize(mapload, ndir = 0, has_water_reclaimer = null)
+ . = ..()
+
+ if(ndir)
+ dir = ndir
+
+ if(has_water_reclaimer != null)
+ src.has_water_reclaimer = has_water_reclaimer
+
+ switch(dir)
+ if(NORTH)
+ pixel_x = 0
+ pixel_y = -pixel_shift
+ if(SOUTH)
+ pixel_x = 0
+ pixel_y = pixel_shift
+ if(EAST)
+ pixel_x = -pixel_shift
+ pixel_y = 0
+ if(WEST)
+ pixel_x = pixel_shift
+ pixel_y = 0
+
+ create_reagents(100, NO_REACT)
+ if(src.has_water_reclaimer)
+ reagents.add_reagent(dispensedreagent, 100)
+ AddComponent(/datum/component/plumbing/simple_demand, extend_pipe_to_edge = TRUE)
+
+/obj/structure/sink/examine(mob/user)
+ . = ..()
+ if(has_water_reclaimer)
+ . += span_notice("A water recycler is installed. It looks like you could pry it out.")
+ . += span_notice("[reagents.total_volume]/[reagents.maximum_volume] liquids remaining.")
+
+/obj/structure/sink/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(.)
+ return
+ if(!user || !istype(user))
+ return
+ if(!iscarbon(user))
+ return
+ if(!Adjacent(user))
+ return
+ if(reagents.total_volume < 5)
+ to_chat(user, span_warning("The sink has no more contents left!"))
+ return
+ if(busy)
+ to_chat(user, span_warning("Someone's already washing here!"))
+ return
+ var/selected_area = user.parse_zone_with_bodypart(user.zone_selected)
+ var/washing_face = 0
+ if(selected_area in list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES))
+ washing_face = 1
+ user.visible_message(span_notice("[user] starts washing [user.p_their()] [washing_face ? "face" : "hands"]..."), \
+ span_notice("You start washing your [washing_face ? "face" : "hands"]..."))
+ busy = TRUE
+
+ if(!do_after(user, 4 SECONDS, target = src))
+ busy = FALSE
+ return
+
+ busy = FALSE
+ reagents.remove_all(5)
+ reagents.expose(user, TOUCH, 5 / max(reagents.total_volume, 5))
+ begin_reclamation()
+ if(washing_face)
+ SEND_SIGNAL(user, COMSIG_COMPONENT_CLEAN_FACE_ACT, CLEAN_WASH)
+ else if(ishuman(user))
+ var/mob/living/carbon/human/human_user = user
+ if(!human_user.wash_hands(CLEAN_WASH))
+ to_chat(user, span_warning("Your hands are covered by something!"))
+ return
+ else
+ user.wash(CLEAN_WASH)
+
+ user.visible_message(span_notice("[user] washes [user.p_their()] [washing_face ? "face" : "hands"] using [src]."), \
+ span_notice("You wash your [washing_face ? "face" : "hands"] using [src]."))
+
+/obj/structure/sink/attackby(obj/item/O, mob/living/user, params)
+ if(busy)
+ to_chat(user, span_warning("Someone's already washing here!"))
+ return
+
+ if(is_reagent_container(O))
+ var/obj/item/reagent_containers/RG = O
+ if(reagents.total_volume <= 0)
+ to_chat(user, span_notice("\The [src] is dry."))
+ return FALSE
+ if(RG.is_refillable())
+ if(!RG.reagents.holder_full())
+ reagents.trans_to(RG, RG.amount_per_transfer_from_this, transferred_by = user)
+ begin_reclamation()
+ to_chat(user, span_notice("You fill [RG] from [src]."))
+ return TRUE
+ to_chat(user, span_notice("\The [RG] is full."))
+ return FALSE
+
+ if(istype(O, /obj/item/melee/baton/security))
+ var/obj/item/melee/baton/security/baton = O
+ if(baton.cell?.charge && baton.active)
+ flick("baton_active", src)
+ user.Paralyze(baton.knockdown_time)
+ user.set_stutter(baton.knockdown_time)
+ baton.cell.use(baton.cell_hit_cost)
+ user.visible_message(span_warning("[user] shocks [user.p_them()]self while attempting to wash the active [baton.name]!"), \
+ span_userdanger("You unwisely attempt to wash [baton] while it's still on."))
+ playsound(src, baton.on_stun_sound, 50, TRUE)
+ return
+
+ if(istype(O, /obj/item/mop))
+ if(reagents.total_volume <= 0)
+ to_chat(user, span_notice("\The [src] is dry."))
+ return FALSE
+ reagents.trans_to(O, 5, transferred_by = user)
+ begin_reclamation()
+ to_chat(user, span_notice("You wet [O] in [src]."))
+ playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE)
+ return
+
+ if(O.tool_behaviour == TOOL_WRENCH)
+ O.play_tool_sound(src)
+ deconstruct()
+ return
+
+ if(O.tool_behaviour == TOOL_CROWBAR)
+ if(!has_water_reclaimer)
+ to_chat(user, span_warning("There isn't a water recycler to remove."))
+ return
+
+ O.play_tool_sound(src)
+ has_water_reclaimer = FALSE
+ new/obj/item/stock_parts/water_recycler(get_turf(loc))
+ to_chat(user, span_notice("You remove the water reclaimer from [src]"))
+ return
+
+ if(istype(O, /obj/item/stack/medical/gauze))
+ var/obj/item/stack/medical/gauze/G = O
+ new /obj/item/reagent_containers/cup/rag(src.loc)
+ to_chat(user, span_notice("You tear off a strip of gauze and make a rag."))
+ G.use(1)
+ return
+
+ if(istype(O, /obj/item/stack/sheet/cloth))
+ var/obj/item/stack/sheet/cloth/cloth = O
+ new /obj/item/reagent_containers/cup/rag(loc)
+ to_chat(user, span_notice("You tear off a strip of cloth and make a rag."))
+ cloth.use(1)
+ return
+
+ if(istype(O, /obj/item/stack/ore/glass))
+ new /obj/item/stack/sheet/sandblock(loc)
+ to_chat(user, span_notice("You wet the sand in the sink and form it into a block."))
+ O.use(1)
+ return
+
+ if(istype(O, /obj/item/stock_parts/water_recycler))
+ if(has_water_reclaimer)
+ to_chat(user, span_warning("There is already has a water recycler installed."))
+ return
+
+ playsound(src, 'sound/machines/click.ogg', 20, TRUE)
+ qdel(O)
+ has_water_reclaimer = TRUE
+ begin_reclamation()
+ return
+
+ if(istype(O, /obj/item/storage/fancy/pickles_jar))
+ if(O.contents.len)
+ to_chat(user, span_notice("Looks like there's something left in the jar"))
+ return
+ new /obj/item/reagent_containers/cup/beaker/large(loc)
+ to_chat(user, span_notice("You washed the jar, ridding it of the brine."))
+ qdel(O)
+ return
+
+ if(!istype(O))
+ return
+ if(O.item_flags & ABSTRACT) //Abstract items like grabs won't wash. No-drop items will though because it's still technically an item in your hand.
+ return
+
+ if(!user.combat_mode)
+ to_chat(user, span_notice("You start washing [O]..."))
+ busy = TRUE
+ if(!do_after(user, 4 SECONDS, target = src))
+ busy = FALSE
+ return 1
+ busy = FALSE
+ O.wash(CLEAN_WASH)
+ reagents.expose(O, TOUCH, 5 / max(reagents.total_volume, 5))
+ user.visible_message(span_notice("[user] washes [O] using [src]."), \
+ span_notice("You wash [O] using [src]."))
+ return 1
+ else
+ return ..()
+
+/obj/structure/sink/atom_deconstruct(dissambled = TRUE)
+ drop_materials()
+ if(has_water_reclaimer)
+ new /obj/item/stock_parts/water_recycler(drop_location())
+
+/obj/structure/sink/process(seconds_per_tick)
+ // Water reclamation complete?
+ if(!has_water_reclaimer || reagents.total_volume >= reagents.maximum_volume)
+ return PROCESS_KILL
+
+ reagents.add_reagent(dispensedreagent, reclaim_rate * seconds_per_tick)
+
+/obj/structure/sink/proc/drop_materials()
+ if(buildstacktype)
+ new buildstacktype(loc,buildstackamount)
+ else
+ for(var/i in custom_materials)
+ var/datum/material/M = i
+ new M.sheet_type(loc, FLOOR(custom_materials[M] / SHEET_MATERIAL_AMOUNT, 1))
+
+/obj/structure/sink/proc/begin_reclamation()
+ START_PROCESSING(SSplumbing, src)
+
+/obj/structure/sink/kitchen
+ name = "kitchen sink"
+ icon_state = "sink_alt"
+ pixel_z = 4
+ pixel_shift = 16
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink/kitchen, (-16))
+
+/obj/structure/sink/greyscale
+ icon_state = "sink_greyscale"
+ material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+ buildstacktype = null
+
+/obj/structure/sinkframe
+ name = "sink frame"
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "sink_frame"
+ desc = "A sink frame, that needs a water recycler to finish construction."
+ anchored = FALSE
+ material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+
+/obj/structure/sinkframe/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/simple_rotation)
+
+/obj/structure/sinkframe/attackby(obj/item/tool, mob/living/user, params)
+ if(istype(tool, /obj/item/stock_parts/water_recycler))
+ qdel(tool)
+ var/obj/structure/sink/greyscale/new_sink = new(loc, REVERSE_DIR(dir), TRUE)
+ new_sink.set_custom_materials(custom_materials)
+ qdel(src)
+ playsound(new_sink, 'sound/machines/click.ogg', 20, TRUE)
+ return
+ return ..()
+
+/obj/structure/sinkframe/wrench_act(mob/living/user, obj/item/tool)
+ . = ..()
+
+ tool.play_tool_sound(src)
+ var/obj/structure/sink/greyscale/new_sink = new(loc, REVERSE_DIR(dir), FALSE)
+ new_sink.set_custom_materials(custom_materials)
+ qdel(src)
+
+ return TRUE
+
+/obj/structure/sinkframe/wrench_act_secondary(mob/living/user, obj/item/tool)
+ . = ..()
+ tool.play_tool_sound(src)
+ deconstruct()
+ return TRUE
+
+/obj/structure/sinkframe/atom_deconstruct(dissambled = TRUE)
+ drop_materials()
+
+/obj/structure/sinkframe/proc/drop_materials()
+ for(var/datum/material/material as anything in custom_materials)
+ new material.sheet_type(loc, FLOOR(custom_materials[material] / SHEET_MATERIAL_AMOUNT, 1))
diff --git a/code/game/objects/structures/water_structures/toilet.dm b/code/game/objects/structures/water_structures/toilet.dm
new file mode 100644
index 00000000000..ab6ebc38fad
--- /dev/null
+++ b/code/game/objects/structures/water_structures/toilet.dm
@@ -0,0 +1,271 @@
+/obj/structure/toilet
+ name = "toilet"
+ desc = "The HT-451, a torque rotation-based, waste disposal unit for small matter. This one seems remarkably clean."
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "toilet00" //The first number represents if the toilet lid is up, the second is if the cistern is open.
+ base_icon_state = "toilet"
+ density = FALSE
+ anchored = TRUE
+
+ ///Boolean if whether the toilet is currently flushing.
+ var/flushing = FALSE
+ ///Boolean if the toilet seat is up.
+ var/cover_open = FALSE
+ ///Boolean if the cistern is up, allowing items to be put in/out.
+ var/cistern_open = FALSE
+ ///The combined weight of all items in the cistern put together.
+ var/w_items = 0
+ ///Reference to the mob being given a swirlie.
+ var/mob/living/swirlie
+ ///The type of material used to build the toilet.
+ var/buildstacktype = /obj/item/stack/sheet/iron
+ ///How much of the buildstacktype is needed to construct the toilet.
+ var/buildstackamount = 1
+ ///Lazylist of items in the cistern.
+ var/list/cistern_items
+ ///Lazylist of fish in the toilet, not to be mixed with the items in the cistern. Max of 3
+ var/list/fishes
+ ///Static toilet water overlay given to toilets that are facing a direction we can see the water in.
+ var/static/mutable_appearance/toilet_water_overlay
+
+/obj/structure/toilet/Initialize(mapload)
+ . = ..()
+ if(isnull(toilet_water_overlay))
+ toilet_water_overlay = mutable_appearance(icon, "[base_icon_state]-water")
+ cover_open = round(rand(0, 1))
+ update_appearance(UPDATE_ICON)
+ if(mapload && SSmapping.level_trait(z, ZTRAIT_STATION))
+ AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/toilet)
+ register_context()
+
+/obj/structure/toilet/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+ if(user.pulling && isliving(user.pulling))
+ context[SCREENTIP_CONTEXT_LMB] = "Give Swirlie"
+ else if(cover_open && istype(held_item, /obj/item/fish))
+ context[SCREENTIP_CONTEXT_LMB] = "Insert Fish"
+ else if(cover_open && LAZYLEN(fishes))
+ context[SCREENTIP_CONTEXT_LMB] = "Grab Fish"
+ else if(cistern_open)
+ if(isnull(held_item))
+ context[SCREENTIP_CONTEXT_LMB] = "Check Cistern"
+ else
+ context[SCREENTIP_CONTEXT_LMB] = "Insert Item"
+ context[SCREENTIP_CONTEXT_RMB] = "Flush"
+ context[SCREENTIP_CONTEXT_ALT_LMB] = "[cover_open ? "Close" : "Open"] Lid"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/structure/toilet/examine(mob/user)
+ . = ..()
+ if(cover_open && LAZYLEN(fishes))
+ . += span_notice("You can see fish in the toilet, you can probably take one out.")
+
+/obj/structure/toilet/examine_more(mob/user)
+ . = ..()
+ if(cistern_open && LAZYLEN(cistern_items))
+ . += span_notice("You can see [cistern_items.len] items inside of the cistern.")
+
+/obj/structure/toilet/Destroy(force)
+ . = ..()
+ QDEL_LAZYLIST(fishes)
+ QDEL_LAZYLIST(cistern_items)
+
+/obj/structure/toilet/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone in cistern_items)
+ LAZYREMOVE(cistern_items, gone)
+ return
+ if(gone in fishes)
+ LAZYREMOVE(fishes, gone)
+ return
+
+/obj/structure/toilet/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(.)
+ return
+
+ if(swirlie)
+ user.changeNext_move(CLICK_CD_MELEE)
+ playsound(src.loc, SFX_SWING_HIT, 25, TRUE)
+ swirlie.visible_message(span_danger("[user] slams the toilet seat onto [swirlie]'s head!"), span_userdanger("[user] slams the toilet seat onto your head!"), span_hear("You hear reverberating porcelain."))
+ log_combat(user, swirlie, "swirlied (brute)")
+ swirlie.adjustBruteLoss(5)
+ return
+
+ if(user.pulling && isliving(user.pulling))
+ user.changeNext_move(CLICK_CD_MELEE)
+ var/mob/living/grabbed_mob = user.pulling
+ if(user.grab_state < GRAB_AGGRESSIVE)
+ to_chat(user, span_warning("You need a tighter grip!"))
+ return
+ if(grabbed_mob.loc != get_turf(src))
+ to_chat(user, span_warning("[grabbed_mob] needs to be on [src]!"))
+ return
+ if(swirlie)
+ return
+ if(cover_open)
+ grabbed_mob.visible_message(span_danger("[user] starts to give [grabbed_mob] a swirlie!"), span_userdanger("[user] starts to give you a swirlie..."))
+ swirlie = grabbed_mob
+ var/was_alive = (swirlie.stat != DEAD)
+ if(!do_after(user, 3 SECONDS, target = src, timed_action_flags = IGNORE_HELD_ITEM))
+ swirlie = null
+ return
+ grabbed_mob.visible_message(span_danger("[user] gives [grabbed_mob] a swirlie!"), span_userdanger("[user] gives you a swirlie!"), span_hear("You hear a toilet flushing."))
+ if(iscarbon(grabbed_mob))
+ var/mob/living/carbon/carbon_grabbed = grabbed_mob
+ if(!carbon_grabbed.internal)
+ log_combat(user, carbon_grabbed, "swirlied (oxy)")
+ carbon_grabbed.adjustOxyLoss(5)
+ else
+ log_combat(user, grabbed_mob, "swirlied (oxy)")
+ grabbed_mob.adjustOxyLoss(5)
+ if(was_alive && swirlie.stat == DEAD && swirlie.client)
+ swirlie.client.give_award(/datum/award/achievement/misc/swirlie, swirlie) // just like space high school all over again!
+ swirlie = null
+ else
+ playsound(src.loc, 'sound/effects/bang.ogg', 25, TRUE)
+ grabbed_mob.visible_message(span_danger("[user] slams [grabbed_mob.name] into [src]!"), span_userdanger("[user] slams you into [src]!"))
+ log_combat(user, grabbed_mob, "toilet slammed")
+ grabbed_mob.adjustBruteLoss(5)
+ return
+
+ if(cistern_open && !cover_open && user.CanReach(src))
+ if(!LAZYLEN(cistern_items))
+ to_chat(user, span_notice("The cistern is empty."))
+ return
+ var/obj/item/random_cistern_item = pick(cistern_items)
+ if(ishuman(user))
+ user.put_in_hands(random_cistern_item)
+ else
+ random_cistern_item.forceMove(drop_location())
+ to_chat(user, span_notice("You find [random_cistern_item] in the cistern."))
+ w_items -= random_cistern_item.w_class
+ return
+
+ if(!flushing && LAZYLEN(fishes) && cover_open)
+ var/obj/item/random_fish = pick(fishes)
+ if(ishuman(user))
+ user.put_in_hands(random_fish)
+ else
+ random_fish.forceMove(drop_location())
+ to_chat(user, span_notice("You take [random_fish] out of the toilet, poor thing."))
+
+/obj/structure/toilet/click_alt(mob/living/user)
+ if(flushing)
+ return CLICK_ACTION_BLOCKING
+ cover_open = !cover_open
+ update_appearance(UPDATE_ICON)
+ return CLICK_ACTION_SUCCESS
+
+/obj/structure/toilet/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(flushing)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ flushing = TRUE
+ playsound(src, "sound/machines/toilet_flush.ogg", cover_open ? 40 : 20, TRUE)
+ if(cover_open && (dir & SOUTH))
+ update_appearance(UPDATE_OVERLAYS)
+ flick_overlay_view(mutable_appearance(icon, "[base_icon_state]-water-flick"), 3 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(end_flushing)), 4 SECONDS)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/obj/structure/toilet/update_icon_state()
+ icon_state = "[base_icon_state][cover_open][cistern_open]"
+ return ..()
+
+/obj/structure/toilet/update_overlays()
+ . = ..()
+ if(!flushing && cover_open && (dir & SOUTH))
+ . += toilet_water_overlay
+
+/obj/structure/toilet/atom_deconstruct(dissambled = TRUE)
+ for(var/obj/toilet_item in cistern_items)
+ toilet_item.forceMove(drop_location())
+ if(buildstacktype)
+ new buildstacktype(loc,buildstackamount)
+ else
+ for(var/datum/material/M as anything in custom_materials)
+ new M.sheet_type(loc, FLOOR(custom_materials[M] / SHEET_MATERIAL_AMOUNT, 1))
+
+/obj/structure/toilet/attackby(obj/item/attacking_item, mob/living/user, params)
+ add_fingerprint(user)
+ if(cover_open && istype(attacking_item, /obj/item/fish))
+ if(fishes >= 3)
+ to_chat(user, span_warning("There's too many fishes, flush them down first."))
+ return
+ if(!user.transferItemToLoc(attacking_item, src))
+ to_chat(user, span_warning("\The [attacking_item] is stuck to your hand!"))
+ return
+ var/obj/item/fish/the_fish = attacking_item
+ if(the_fish.status == FISH_DEAD)
+ to_chat(user, span_warning("You place [attacking_item] into [src], may it rest in peace."))
+ else
+ to_chat(user, span_notice("You place [attacking_item] into [src], hopefully no one will miss it!"))
+ LAZYADD(fishes, attacking_item)
+ return
+
+ if(cistern_open && !user.combat_mode)
+ if(attacking_item.w_class > WEIGHT_CLASS_NORMAL)
+ to_chat(user, span_warning("[attacking_item] does not fit!"))
+ return
+ if(w_items + attacking_item.w_class > WEIGHT_CLASS_HUGE)
+ to_chat(user, span_warning("The cistern is full!"))
+ return
+ if(!user.transferItemToLoc(attacking_item, src))
+ to_chat(user, span_warning("\The [attacking_item] is stuck to your hand, you cannot put it in the cistern!"))
+ return
+ LAZYADD(cistern_items, attacking_item)
+ w_items += attacking_item.w_class
+ to_chat(user, span_notice("You carefully place [attacking_item] into the cistern."))
+ return
+
+ if(is_reagent_container(attacking_item) && !user.combat_mode)
+ if (!cover_open)
+ return
+ if(istype(attacking_item, /obj/item/food/monkeycube))
+ var/obj/item/food/monkeycube/cube = attacking_item
+ cube.Expand()
+ return
+ var/obj/item/reagent_containers/RG = attacking_item
+ RG.reagents.add_reagent(/datum/reagent/water, min(RG.volume - RG.reagents.total_volume, RG.amount_per_transfer_from_this))
+ to_chat(user, span_notice("You fill [RG] from [src]. Gross."))
+ return ..()
+
+/obj/structure/toilet/crowbar_act(mob/living/user, obj/item/tool)
+ to_chat(user, span_notice("You start to [cistern_open ? "replace the lid on" : "lift the lid off"] the cistern..."))
+ playsound(loc, 'sound/effects/stonedoor_openclose.ogg', 50, TRUE)
+ if(tool.use_tool(src, user, 30))
+ user.visible_message(
+ span_notice("[user] [cistern_open ? "replaces the lid on" : "lifts the lid off"] the cistern!"),
+ span_notice("You [cistern_open ? "replace the lid on" : "lift the lid off"] the cistern!"),
+ span_hear("You hear grinding porcelain."))
+ cistern_open = !cistern_open
+ update_appearance(UPDATE_ICON_STATE)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/structure/toilet/wrench_act(mob/living/user, obj/item/tool)
+ tool.play_tool_sound(src)
+ deconstruct()
+ return ITEM_INTERACT_SUCCESS
+
+///Ends the flushing animation and updates overlays if necessary
+/obj/structure/toilet/proc/end_flushing()
+ flushing = FALSE
+ if(cover_open && (dir & SOUTH))
+ update_appearance(UPDATE_OVERLAYS)
+ QDEL_LAZYLIST(fishes)
+
+/obj/structure/toilet/greyscale
+ material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+ buildstacktype = null
+
+/obj/structure/toilet/secret
+ var/secret_type = null
+
+/obj/structure/toilet/secret/Initialize(mapload)
+ . = ..()
+ if(secret_type)
+ var/obj/item/secret = new secret_type(src)
+ secret.desc += " It's a secret!"
+ w_items += secret.w_class
+ LAZYADD(cistern_items, secret)
diff --git a/code/game/objects/structures/water_structures/urinal.dm b/code/game/objects/structures/water_structures/urinal.dm
new file mode 100644
index 00000000000..3b34e2cc0e5
--- /dev/null
+++ b/code/game/objects/structures/water_structures/urinal.dm
@@ -0,0 +1,116 @@
+/obj/structure/urinal
+ name = "urinal"
+ desc = "The HU-452, an experimental urinal. Comes complete with experimental urinal cake."
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "urinal"
+ density = FALSE
+ anchored = TRUE
+ /// Can you currently put an item inside
+ var/exposed = FALSE
+ /// What's in the urinal
+ var/obj/item/hidden_item
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/structure/urinal, 32)
+
+/obj/structure/urinal/Initialize(mapload)
+ . = ..()
+ if(mapload)
+ hidden_item = new /obj/item/food/urinalcake(src)
+ find_and_hang_on_wall()
+
+/obj/structure/urinal/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == hidden_item)
+ hidden_item = null
+
+/obj/structure/urinal/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(.)
+ return
+
+ if(user.pulling && isliving(user.pulling))
+ var/mob/living/grabbed_mob = user.pulling
+ if(user.grab_state >= GRAB_AGGRESSIVE)
+ if(grabbed_mob.loc != get_turf(src))
+ to_chat(user, span_notice("[grabbed_mob.name] needs to be on [src]."))
+ return
+ user.changeNext_move(CLICK_CD_MELEE)
+ user.visible_message(span_danger("[user] slams [grabbed_mob] into [src]!"), span_danger("You slam [grabbed_mob] into [src]!"))
+ grabbed_mob.emote("scream")
+ grabbed_mob.adjustBruteLoss(8)
+ else
+ to_chat(user, span_warning("You need a tighter grip!"))
+ return
+
+ if(exposed)
+ if(hidden_item)
+ to_chat(user, span_notice("You fish [hidden_item] out of the drain enclosure."))
+ user.put_in_hands(hidden_item)
+ else
+ to_chat(user, span_warning("There is nothing in the drain holder!"))
+ return
+ return ..()
+
+/obj/structure/urinal/attackby(obj/item/attacking_item, mob/user, params)
+ if(exposed)
+ if(hidden_item)
+ to_chat(user, span_warning("There is already something in the drain enclosure!"))
+ return
+ if(attacking_item.w_class > WEIGHT_CLASS_TINY)
+ to_chat(user, span_warning("[attacking_item] is too large for the drain enclosure."))
+ return
+ if(!user.transferItemToLoc(attacking_item, src))
+ to_chat(user, span_warning("[attacking_item] is stuck to your hand, you cannot put it in the drain enclosure!"))
+ return
+ hidden_item = attacking_item
+ to_chat(user, span_notice("You place [attacking_item] into the drain enclosure."))
+ return
+ return ..()
+
+/obj/structure/urinal/screwdriver_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+ to_chat(user, span_notice("You start to [exposed ? "screw the cap back into place" : "unscrew the cap to the drain protector"]..."))
+ playsound(loc, 'sound/effects/stonedoor_openclose.ogg', 50, TRUE)
+ if(I.use_tool(src, user, 20))
+ user.visible_message(span_notice("[user] [exposed ? "screws the cap back into place" : "unscrew the cap to the drain protector"]!"),
+ span_notice("You [exposed ? "screw the cap back into place" : "unscrew the cap on the drain"]!"),
+ span_hear("You hear metal and squishing noises."))
+ exposed = !exposed
+ return TRUE
+
+/obj/structure/urinal/wrench_act_secondary(mob/living/user, obj/item/tool)
+ tool.play_tool_sound(user)
+ deconstruct(TRUE)
+ balloon_alert(user, "removed urinal")
+ return ITEM_INTERACT_SUCCESS
+
+/obj/structure/urinal/atom_deconstruct(disassembled = TRUE)
+ new /obj/item/wallframe/urinal(loc)
+ hidden_item?.forceMove(drop_location())
+
+/obj/item/wallframe/urinal
+ name = "urinal frame"
+ desc = "An unmounted urinal. Attach it to a wall to use."
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "urinal"
+ result_path = /obj/structure/urinal
+ pixel_shift = 32
+
+/obj/item/food/urinalcake
+ name = "urinal cake"
+ desc = "The noble urinal cake, protecting the station's pipes from the station's pee. Do not eat."
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "urinalcake"
+ w_class = WEIGHT_CLASS_TINY
+ food_reagents = list(
+ /datum/reagent/chlorine = 3,
+ /datum/reagent/ammonia = 1,
+ )
+ foodtypes = TOXIC | GROSS
+ preserved_food = TRUE
+
+/obj/item/food/urinalcake/attack_self(mob/living/user)
+ user.visible_message(span_notice("[user] squishes [src]!"), span_notice("You squish [src]."), "You hear a squish.")
+ icon_state = "urinalcake_squish"
+ addtimer(VARSET_CALLBACK(src, icon_state, "urinalcake"), 0.8 SECONDS)
diff --git a/code/game/objects/structures/water_structures/water_source.dm b/code/game/objects/structures/water_structures/water_source.dm
new file mode 100644
index 00000000000..b7ad26a65ea
--- /dev/null
+++ b/code/game/objects/structures/water_structures/water_source.dm
@@ -0,0 +1,149 @@
+//Water source, use the type water_source for unlimited water sources like classic sinks.
+/obj/structure/water_source
+ name = "Water Source"
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "sink"
+ desc = "A sink used for washing one's hands and face. This one seems to be infinite!"
+ anchored = TRUE
+ ///Boolean on whether something is currently being washed, preventing multiple people from cleaning at once.
+ var/busy = FALSE
+ ///The reagent that is dispensed from this source, by default it's water.
+ var/datum/reagent/dispensedreagent = /datum/reagent/water
+
+/obj/structure/water_source/Initialize(mapload)
+ . = ..()
+ create_reagents(INFINITY, NO_REACT)
+ reagents.add_reagent(dispensedreagent, INFINITY)
+
+/obj/structure/water_source/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(.)
+ return
+ if(!iscarbon(user))
+ return
+ if(!Adjacent(user))
+ return
+
+ if(busy)
+ to_chat(user, span_warning("Someone's already washing here!"))
+ return
+ var/selected_area = user.parse_zone_with_bodypart(user.zone_selected)
+ var/washing_face = FALSE
+ if(selected_area in list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES))
+ washing_face = TRUE
+ user.visible_message(
+ span_notice("[user] starts washing [user.p_their()] [washing_face ? "face" : "hands"]..."),
+ span_notice("You start washing your [washing_face ? "face" : "hands"]..."))
+ busy = TRUE
+
+ if(!do_after(user, 4 SECONDS, target = src))
+ busy = FALSE
+ return
+
+ busy = FALSE
+
+ if(washing_face)
+ SEND_SIGNAL(user, COMSIG_COMPONENT_CLEAN_FACE_ACT, CLEAN_WASH)
+ else if(ishuman(user))
+ var/mob/living/carbon/human/human_user = user
+ if(!human_user.wash_hands(CLEAN_WASH))
+ to_chat(user, span_warning("Your hands are covered by something!"))
+ return
+ else
+ user.wash(CLEAN_WASH)
+
+ user.visible_message(
+ span_notice("[user] washes [user.p_their()] [washing_face ? "face" : "hands"] using [src]."),
+ span_notice("You wash your [washing_face ? "face" : "hands"] using [src]."),
+ )
+
+/obj/structure/water_source/attackby(obj/item/attacking_item, mob/living/user, params)
+ if(busy)
+ to_chat(user, span_warning("Someone's already washing here!"))
+ return
+
+ if(attacking_item.item_flags & ABSTRACT) //Abstract items like grabs won't wash. No-drop items will though because it's still technically an item in your hand.
+ return
+
+ if(is_reagent_container(attacking_item))
+ var/obj/item/reagent_containers/container = attacking_item
+ if(container.is_refillable())
+ if(!container.reagents.holder_full())
+ container.reagents.add_reagent(dispensedreagent, min(container.volume - container.reagents.total_volume, container.amount_per_transfer_from_this))
+ to_chat(user, span_notice("You fill [container] from [src]."))
+ return TRUE
+ to_chat(user, span_notice("\The [container] is full."))
+ return FALSE
+
+ if(istype(attacking_item, /obj/item/melee/baton/security))
+ var/obj/item/melee/baton/security/baton = attacking_item
+ if(baton.cell?.charge && baton.active)
+ flick("baton_active", src)
+ user.Paralyze(baton.knockdown_time)
+ user.set_stutter(baton.knockdown_time)
+ baton.cell.use(baton.cell_hit_cost)
+ user.visible_message(
+ span_warning("[user] shocks [user.p_them()]self while attempting to wash the active [baton.name]!"),
+ span_userdanger("You unwisely attempt to wash [baton] while it's still on."))
+ playsound(src, baton.on_stun_sound, 50, TRUE)
+ return
+
+ if(istype(attacking_item, /obj/item/mop))
+ attacking_item.reagents.add_reagent(dispensedreagent, 5)
+ to_chat(user, span_notice("You wet [attacking_item] in [src]."))
+ playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE)
+ return
+
+ if(istype(attacking_item, /obj/item/stack/medical/gauze))
+ var/obj/item/stack/medical/gauze/G = attacking_item
+ new /obj/item/reagent_containers/cup/rag(loc)
+ to_chat(user, span_notice("You tear off a strip of gauze and make a rag."))
+ G.use(1)
+ return
+
+ if(istype(attacking_item, /obj/item/stack/sheet/cloth))
+ var/obj/item/stack/sheet/cloth/cloth = attacking_item
+ new /obj/item/reagent_containers/cup/rag(loc)
+ to_chat(user, span_notice("You tear off a strip of cloth and make a rag."))
+ cloth.use(1)
+ return
+
+ if(istype(attacking_item, /obj/item/stack/ore/glass))
+ new /obj/item/stack/sheet/sandblock(loc)
+ to_chat(user, span_notice("You wet the sand and form it into a block."))
+ attacking_item.use(1)
+ return
+
+ if(!user.combat_mode)
+ to_chat(user, span_notice("You start washing [attacking_item]..."))
+ busy = TRUE
+ if(!do_after(user, 4 SECONDS, target = src))
+ busy = FALSE
+ return TRUE
+ busy = FALSE
+ attacking_item.wash(CLEAN_WASH)
+ reagents.expose(attacking_item, TOUCH, 5 / max(reagents.total_volume, 5))
+ user.visible_message(
+ span_notice("[user] washes [attacking_item] using [src]."),
+ span_notice("You wash [attacking_item] using [src]."))
+ return TRUE
+
+ return ..()
+
+/obj/structure/water_source/puddle //splishy splashy ^_^
+ name = "puddle"
+ desc = "A puddle used for washing one's hands and face."
+ icon_state = "puddle"
+ base_icon_state = "puddle"
+ resistance_flags = UNACIDABLE
+
+//ATTACK HAND IGNORING PARENT RETURN VALUE
+/obj/structure/water_source/puddle/attack_hand(mob/user, list/modifiers)
+ icon_state = "[base_icon_state]-splash"
+ . = ..()
+ icon_state = base_icon_state
+
+/obj/structure/water_source/puddle/attackby(obj/item/attacking_item, mob/user, params)
+ icon_state = "[base_icon_state]-splash"
+ . = ..()
+ icon_state = base_icon_state
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
deleted file mode 100644
index 51d85d4583d..00000000000
--- a/code/game/objects/structures/watercloset.dm
+++ /dev/null
@@ -1,877 +0,0 @@
-/obj/structure/toilet
- name = "toilet"
- desc = "The HT-451, a torque rotation-based, waste disposal unit for small matter. This one seems remarkably clean."
- icon = 'icons/obj/watercloset.dmi'
- icon_state = "toilet00"
- density = FALSE
- anchored = TRUE
- var/open = FALSE //if the lid is up
- var/cistern = 0 //if the cistern bit is open
- var/w_items = 0 //the combined w_class of all the items in the cistern
- var/mob/living/swirlie = null //the mob being given a swirlie
- var/buildstacktype = /obj/item/stack/sheet/iron //they're iron now, shut up
- var/buildstackamount = 1
-
-/obj/structure/toilet/Initialize(mapload)
- . = ..()
- open = round(rand(0, 1))
- update_appearance()
- if(mapload && SSmapping.level_trait(z, ZTRAIT_STATION))
- AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/toilet)
-
-/obj/structure/toilet/attack_hand(mob/living/user, list/modifiers)
- . = ..()
- if(.)
- return
- if(swirlie)
- user.changeNext_move(CLICK_CD_MELEE)
- playsound(src.loc, SFX_SWING_HIT, 25, TRUE)
- swirlie.visible_message(span_danger("[user] slams the toilet seat onto [swirlie]'s head!"), span_userdanger("[user] slams the toilet seat onto your head!"), span_hear("You hear reverberating porcelain."))
- log_combat(user, swirlie, "swirlied (brute)")
- swirlie.adjustBruteLoss(5)
-
- else if(user.pulling && isliving(user.pulling))
- user.changeNext_move(CLICK_CD_MELEE)
- var/mob/living/GM = user.pulling
- if(user.grab_state >= GRAB_AGGRESSIVE)
- if(GM.loc != get_turf(src))
- to_chat(user, span_warning("[GM] needs to be on [src]!"))
- return
- if(!swirlie)
- if(open)
- GM.visible_message(span_danger("[user] starts to give [GM] a swirlie!"), span_userdanger("[user] starts to give you a swirlie..."))
- swirlie = GM
- var/was_alive = (swirlie.stat != DEAD)
- if(do_after(user, 3 SECONDS, target = src, timed_action_flags = IGNORE_HELD_ITEM))
- GM.visible_message(span_danger("[user] gives [GM] a swirlie!"), span_userdanger("[user] gives you a swirlie!"), span_hear("You hear a toilet flushing."))
- if(iscarbon(GM))
- var/mob/living/carbon/C = GM
- if(!C.internal)
- log_combat(user, C, "swirlied (oxy)")
- C.adjustOxyLoss(5)
- else
- log_combat(user, GM, "swirlied (oxy)")
- GM.adjustOxyLoss(5)
- if(was_alive && swirlie.stat == DEAD && swirlie.client)
- swirlie.client.give_award(/datum/award/achievement/misc/swirlie, swirlie) // just like space high school all over again!
- swirlie = null
- else
- playsound(src.loc, 'sound/effects/bang.ogg', 25, TRUE)
- GM.visible_message(span_danger("[user] slams [GM.name] into [src]!"), span_userdanger("[user] slams you into [src]!"))
- log_combat(user, GM, "toilet slammed")
- GM.adjustBruteLoss(5)
- else
- to_chat(user, span_warning("You need a tighter grip!"))
-
- else if(cistern && !open && user.CanReach(src))
- if(!contents.len)
- to_chat(user, span_notice("The cistern is empty."))
- else
- var/obj/item/I = pick(contents)
- if(ishuman(user))
- user.put_in_hands(I)
- else
- I.forceMove(drop_location())
- to_chat(user, span_notice("You find [I] in the cistern."))
- w_items -= I.w_class
- else
- open = !open
- update_appearance()
-
-
-/obj/structure/toilet/update_icon_state()
- icon_state = "toilet[open][cistern]"
- return ..()
-
-/obj/structure/toilet/atom_deconstruct(dissambled = TRUE)
- for(var/obj/toilet_item in contents)
- toilet_item.forceMove(drop_location())
- if(buildstacktype)
- new buildstacktype(loc,buildstackamount)
- else
- for(var/i in custom_materials)
- var/datum/material/M = i
- new M.sheet_type(loc, FLOOR(custom_materials[M] / SHEET_MATERIAL_AMOUNT, 1))
-
-/obj/structure/toilet/attackby(obj/item/I, mob/living/user, params)
- add_fingerprint(user)
- if(I.tool_behaviour == TOOL_CROWBAR)
- to_chat(user, span_notice("You start to [cistern ? "replace the lid on the cistern" : "lift the lid off the cistern"]..."))
- playsound(loc, 'sound/effects/stonedoor_openclose.ogg', 50, TRUE)
- if(I.use_tool(src, user, 30))
- user.visible_message(span_notice("[user] [cistern ? "replaces the lid on the cistern" : "lifts the lid off the cistern"]!"), span_notice("You [cistern ? "replace the lid on the cistern" : "lift the lid off the cistern"]!"), span_hear("You hear grinding porcelain."))
- cistern = !cistern
- update_appearance()
- return COMPONENT_CANCEL_ATTACK_CHAIN
- else if(I.tool_behaviour == TOOL_WRENCH)
- I.play_tool_sound(src)
- deconstruct()
- return TRUE
- else if(cistern && !user.combat_mode)
- if(I.w_class > WEIGHT_CLASS_NORMAL)
- to_chat(user, span_warning("[I] does not fit!"))
- return
- if(w_items + I.w_class > WEIGHT_CLASS_HUGE)
- to_chat(user, span_warning("The cistern is full!"))
- return
- if(!user.transferItemToLoc(I, src))
- to_chat(user, span_warning("\The [I] is stuck to your hand, you cannot put it in the cistern!"))
- return
- w_items += I.w_class
- to_chat(user, span_notice("You carefully place [I] into the cistern."))
- return
-
- if(is_reagent_container(I) && !user.combat_mode)
- if (!open)
- return
- if(istype(I, /obj/item/food/monkeycube))
- var/obj/item/food/monkeycube/cube = I
- cube.Expand()
- return
- var/obj/item/reagent_containers/RG = I
- RG.reagents.add_reagent(/datum/reagent/water, min(RG.volume - RG.reagents.total_volume, RG.amount_per_transfer_from_this))
- to_chat(user, span_notice("You fill [RG] from [src]. Gross."))
- . = ..()
-
-/obj/structure/toilet/secret
- var/obj/item/secret
- var/secret_type = null
-
-/obj/structure/toilet/secret/Initialize(mapload)
- . = ..()
- if (secret_type)
- secret = new secret_type(src)
- secret.desc += " It's a secret!"
- w_items += secret.w_class
- contents += secret
-
-/obj/structure/toilet/greyscale
- material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
- buildstacktype = null
-
-/obj/structure/urinal
- name = "urinal"
- desc = "The HU-452, an experimental urinal. Comes complete with experimental urinal cake."
- icon = 'icons/obj/watercloset.dmi'
- icon_state = "urinal"
- density = FALSE
- anchored = TRUE
- /// Can you currently put an item inside
- var/exposed = FALSE
- /// What's in the urinal
- var/obj/item/hidden_item
-
-MAPPING_DIRECTIONAL_HELPERS(/obj/structure/urinal, 32)
-
-/obj/structure/urinal/Initialize(mapload)
- . = ..()
- hidden_item = new /obj/item/food/urinalcake
- find_and_hang_on_wall()
-
-/obj/structure/urinal/attack_hand(mob/living/user, list/modifiers)
- . = ..()
- if(.)
- return
- if(user.pulling && isliving(user.pulling))
- var/mob/living/GM = user.pulling
- if(user.grab_state >= GRAB_AGGRESSIVE)
- if(GM.loc != get_turf(src))
- to_chat(user, span_notice("[GM.name] needs to be on [src]."))
- return
- user.changeNext_move(CLICK_CD_MELEE)
- user.visible_message(span_danger("[user] slams [GM] into [src]!"), span_danger("You slam [GM] into [src]!"))
- GM.adjustBruteLoss(8)
- else
- to_chat(user, span_warning("You need a tighter grip!"))
-
- else if(exposed)
- if(!hidden_item)
- to_chat(user, span_warning("There is nothing in the drain holder!"))
- else
- if(ishuman(user))
- user.put_in_hands(hidden_item)
- else
- hidden_item.forceMove(get_turf(src))
- to_chat(user, span_notice("You fish [hidden_item] out of the drain enclosure."))
- hidden_item = null
- else
- ..()
-
-/obj/structure/urinal/attackby(obj/item/I, mob/living/user, params)
- if(exposed)
- if (hidden_item)
- to_chat(user, span_warning("There is already something in the drain enclosure!"))
- return
- if(I.w_class > 1)
- to_chat(user, span_warning("[I] is too large for the drain enclosure."))
- return
- if(!user.transferItemToLoc(I, src))
- to_chat(user, span_warning("[I] is stuck to your hand, you cannot put it in the drain enclosure!"))
- return
- hidden_item = I
- to_chat(user, span_notice("You place [I] into the drain enclosure."))
- else
- return ..()
-
-/obj/structure/urinal/screwdriver_act(mob/living/user, obj/item/I)
- if(..())
- return TRUE
- to_chat(user, span_notice("You start to [exposed ? "screw the cap back into place" : "unscrew the cap to the drain protector"]..."))
- playsound(loc, 'sound/effects/stonedoor_openclose.ogg', 50, TRUE)
- if(I.use_tool(src, user, 20))
- user.visible_message(span_notice("[user] [exposed ? "screws the cap back into place" : "unscrew the cap to the drain protector"]!"),
- span_notice("You [exposed ? "screw the cap back into place" : "unscrew the cap on the drain"]!"),
- span_hear("You hear metal and squishing noises."))
- exposed = !exposed
- return TRUE
-
-/obj/structure/urinal/atom_deconstruct(disassembled = TRUE)
- new /obj/item/wallframe/urinal(loc)
-
-/obj/item/wallframe/urinal
- name = "urinal frame"
- desc = "An unmounted urinal. Attach it to a wall to use."
- icon = 'icons/obj/watercloset.dmi'
- icon_state = "urinal"
- result_path = /obj/structure/urinal
- pixel_shift = 32
-
-/obj/item/food/urinalcake
- name = "urinal cake"
- desc = "The noble urinal cake, protecting the station's pipes from the station's pee. Do not eat."
- icon = 'icons/obj/watercloset.dmi'
- icon_state = "urinalcake"
- w_class = WEIGHT_CLASS_TINY
- food_reagents = list(
- /datum/reagent/chlorine = 3,
- /datum/reagent/ammonia = 1,
- )
- foodtypes = TOXIC | GROSS
- preserved_food = TRUE
-
-/obj/item/food/urinalcake/attack_self(mob/living/user)
- user.visible_message(span_notice("[user] squishes [src]!"), span_notice("You squish [src]."), "You hear a squish.")
- icon_state = "urinalcake_squish"
- addtimer(VARSET_CALLBACK(src, icon_state, "urinalcake"), 0.8 SECONDS)
-
-/obj/item/bikehorn/rubberducky/plasticducky
- name = "plastic ducky"
- desc = "It's a cheap plastic knockoff of a loveable bathtime toy."
- custom_materials = list(/datum/material/plastic =HALF_SHEET_MATERIAL_AMOUNT)
-
-/obj/item/bikehorn/rubberducky
- name = "rubber ducky"
- desc = "Rubber ducky you're so fine, you make bathtime lots of fuuun. Rubber ducky I'm awfully fooooond of yooooouuuu~" //thanks doohl
- icon = 'icons/obj/watercloset.dmi'
- icon_state = "rubberducky"
- inhand_icon_state = "rubberducky"
- lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items_righthand.dmi'
- worn_icon_state = "duck"
- sound_file = 'sound/effects/quack.ogg'
-
-/obj/structure/sink
- name = "sink"
- icon = 'icons/obj/watercloset.dmi'
- icon_state = "sink"
- desc = "A sink used for washing one's hands and face. Passively reclaims water over time."
- anchored = TRUE
- layer = ABOVE_OBJ_LAYER
- pixel_z = 1
- ///Something's being washed at the moment
- var/busy = FALSE
- ///What kind of reagent is produced by this sink by default? (We now have actual plumbing, Arcane, August 2020)
- var/dispensedreagent = /datum/reagent/water
- ///Material to drop when broken or deconstructed.
- var/buildstacktype = /obj/item/stack/sheet/iron
- ///Number of sheets of material to drop when broken or deconstructed.
- var/buildstackamount = 1
- ///Does the sink have a water recycler to recollect it's water supply?
- var/has_water_reclaimer = TRUE
- ///Units of water to reclaim per second
- var/reclaim_rate = 0.5
- ///Amount of shift the pixel for placement
- var/pixel_shift = 14
-
-MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink, (-14))
-
-/obj/structure/sink/Initialize(mapload, ndir = 0, has_water_reclaimer = null)
- . = ..()
-
- if(ndir)
- dir = ndir
-
- if(has_water_reclaimer != null)
- src.has_water_reclaimer = has_water_reclaimer
-
- switch(dir)
- if(NORTH)
- pixel_x = 0
- pixel_y = -pixel_shift
- if(SOUTH)
- pixel_x = 0
- pixel_y = pixel_shift
- if(EAST)
- pixel_x = -pixel_shift
- pixel_y = 0
- if(WEST)
- pixel_x = pixel_shift
- pixel_y = 0
-
- create_reagents(100, NO_REACT)
- if(src.has_water_reclaimer)
- reagents.add_reagent(dispensedreagent, 100)
- AddComponent(/datum/component/plumbing/simple_demand, extend_pipe_to_edge = TRUE)
-
-/obj/structure/sink/examine(mob/user)
- . = ..()
- if(has_water_reclaimer)
- . += span_notice("A water recycler is installed. It looks like you could pry it out.")
- . += span_notice("[reagents.total_volume]/[reagents.maximum_volume] liquids remaining.")
-
-/obj/structure/sink/attack_hand(mob/living/user, list/modifiers)
- . = ..()
- if(.)
- return
- if(!user || !istype(user))
- return
- if(!iscarbon(user))
- return
- if(!Adjacent(user))
- return
- if(reagents.total_volume < 5)
- to_chat(user, span_warning("The sink has no more contents left!"))
- return
- if(busy)
- to_chat(user, span_warning("Someone's already washing here!"))
- return
- var/selected_area = user.parse_zone_with_bodypart(user.zone_selected)
- var/washing_face = 0
- if(selected_area in list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES))
- washing_face = 1
- user.visible_message(span_notice("[user] starts washing [user.p_their()] [washing_face ? "face" : "hands"]..."), \
- span_notice("You start washing your [washing_face ? "face" : "hands"]..."))
- busy = TRUE
-
- if(!do_after(user, 4 SECONDS, target = src))
- busy = FALSE
- return
-
- busy = FALSE
- reagents.remove_all(5)
- reagents.expose(user, TOUCH, 5 / max(reagents.total_volume, 5))
- begin_reclamation()
- if(washing_face)
- SEND_SIGNAL(user, COMSIG_COMPONENT_CLEAN_FACE_ACT, CLEAN_WASH)
- else if(ishuman(user))
- var/mob/living/carbon/human/human_user = user
- if(!human_user.wash_hands(CLEAN_WASH))
- to_chat(user, span_warning("Your hands are covered by something!"))
- return
- else
- user.wash(CLEAN_WASH)
-
- user.visible_message(span_notice("[user] washes [user.p_their()] [washing_face ? "face" : "hands"] using [src]."), \
- span_notice("You wash your [washing_face ? "face" : "hands"] using [src]."))
-
-/obj/structure/sink/attackby(obj/item/O, mob/living/user, params)
- if(busy)
- to_chat(user, span_warning("Someone's already washing here!"))
- return
-
- if(is_reagent_container(O))
- var/obj/item/reagent_containers/RG = O
- if(reagents.total_volume <= 0)
- to_chat(user, span_notice("\The [src] is dry."))
- return FALSE
- if(RG.is_refillable())
- if(!RG.reagents.holder_full())
- reagents.trans_to(RG, RG.amount_per_transfer_from_this, transferred_by = user)
- begin_reclamation()
- to_chat(user, span_notice("You fill [RG] from [src]."))
- return TRUE
- to_chat(user, span_notice("\The [RG] is full."))
- return FALSE
-
- if(istype(O, /obj/item/melee/baton/security))
- var/obj/item/melee/baton/security/baton = O
- if(baton.cell?.charge && baton.active)
- flick("baton_active", src)
- user.Paralyze(baton.knockdown_time)
- user.set_stutter(baton.knockdown_time)
- baton.cell.use(baton.cell_hit_cost)
- user.visible_message(span_warning("[user] shocks [user.p_them()]self while attempting to wash the active [baton.name]!"), \
- span_userdanger("You unwisely attempt to wash [baton] while it's still on."))
- playsound(src, baton.on_stun_sound, 50, TRUE)
- return
-
- if(istype(O, /obj/item/mop))
- if(reagents.total_volume <= 0)
- to_chat(user, span_notice("\The [src] is dry."))
- return FALSE
- reagents.trans_to(O, 5, transferred_by = user)
- begin_reclamation()
- to_chat(user, span_notice("You wet [O] in [src]."))
- playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE)
- return
-
- if(O.tool_behaviour == TOOL_WRENCH)
- O.play_tool_sound(src)
- deconstruct()
- return
-
- if(O.tool_behaviour == TOOL_CROWBAR)
- if(!has_water_reclaimer)
- to_chat(user, span_warning("There isn't a water recycler to remove."))
- return
-
- O.play_tool_sound(src)
- has_water_reclaimer = FALSE
- new/obj/item/stock_parts/water_recycler(get_turf(loc))
- to_chat(user, span_notice("You remove the water reclaimer from [src]"))
- return
-
- if(istype(O, /obj/item/stack/medical/gauze))
- var/obj/item/stack/medical/gauze/G = O
- new /obj/item/reagent_containers/cup/rag(src.loc)
- to_chat(user, span_notice("You tear off a strip of gauze and make a rag."))
- G.use(1)
- return
-
- if(istype(O, /obj/item/stack/sheet/cloth))
- var/obj/item/stack/sheet/cloth/cloth = O
- new /obj/item/reagent_containers/cup/rag(loc)
- to_chat(user, span_notice("You tear off a strip of cloth and make a rag."))
- cloth.use(1)
- return
-
- if(istype(O, /obj/item/stack/ore/glass))
- new /obj/item/stack/sheet/sandblock(loc)
- to_chat(user, span_notice("You wet the sand in the sink and form it into a block."))
- O.use(1)
- return
-
- if(istype(O, /obj/item/stock_parts/water_recycler))
- if(has_water_reclaimer)
- to_chat(user, span_warning("There is already has a water recycler installed."))
- return
-
- playsound(src, 'sound/machines/click.ogg', 20, TRUE)
- qdel(O)
- has_water_reclaimer = TRUE
- begin_reclamation()
- return
-
- if(istype(O, /obj/item/storage/fancy/pickles_jar))
- if(O.contents.len)
- to_chat(user, span_notice("Looks like there's something left in the jar"))
- return
- new /obj/item/reagent_containers/cup/beaker/large(loc)
- to_chat(user, span_notice("You washed the jar, ridding it of the brine."))
- qdel(O)
- return
-
- if(!istype(O))
- return
- if(O.item_flags & ABSTRACT) //Abstract items like grabs won't wash. No-drop items will though because it's still technically an item in your hand.
- return
-
- if(!user.combat_mode)
- to_chat(user, span_notice("You start washing [O]..."))
- busy = TRUE
- if(!do_after(user, 4 SECONDS, target = src))
- busy = FALSE
- return 1
- busy = FALSE
- O.wash(CLEAN_WASH)
- reagents.expose(O, TOUCH, 5 / max(reagents.total_volume, 5))
- user.visible_message(span_notice("[user] washes [O] using [src]."), \
- span_notice("You wash [O] using [src]."))
- return 1
- else
- return ..()
-
-/obj/structure/sink/atom_deconstruct(dissambled = TRUE)
- drop_materials()
- if(has_water_reclaimer)
- new /obj/item/stock_parts/water_recycler(drop_location())
-
-/obj/structure/sink/process(seconds_per_tick)
- // Water reclamation complete?
- if(!has_water_reclaimer || reagents.total_volume >= reagents.maximum_volume)
- return PROCESS_KILL
-
- reagents.add_reagent(dispensedreagent, reclaim_rate * seconds_per_tick)
-
-/obj/structure/sink/proc/drop_materials()
- if(buildstacktype)
- new buildstacktype(loc,buildstackamount)
- else
- for(var/i in custom_materials)
- var/datum/material/M = i
- new M.sheet_type(loc, FLOOR(custom_materials[M] / SHEET_MATERIAL_AMOUNT, 1))
-
-/obj/structure/sink/proc/begin_reclamation()
- START_PROCESSING(SSplumbing, src)
-
-/obj/structure/sink/kitchen
- name = "kitchen sink"
- icon_state = "sink_alt"
- pixel_z = 4
- pixel_shift = 16
-
-MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink/kitchen, (-16))
-
-/obj/structure/sink/greyscale
- icon_state = "sink_greyscale"
- material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
- buildstacktype = null
-
-/obj/structure/sinkframe
- name = "sink frame"
- icon = 'icons/obj/watercloset.dmi'
- icon_state = "sink_frame"
- desc = "A sink frame, that needs a water recycler to finish construction."
- anchored = FALSE
- material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
-
-/obj/structure/sinkframe/Initialize(mapload)
- . = ..()
- AddComponent(/datum/component/simple_rotation)
-
-/obj/structure/sinkframe/attackby(obj/item/tool, mob/living/user, params)
- if(istype(tool, /obj/item/stock_parts/water_recycler))
- qdel(tool)
- var/obj/structure/sink/greyscale/new_sink = new(loc, REVERSE_DIR(dir), TRUE)
- new_sink.set_custom_materials(custom_materials)
- qdel(src)
- playsound(new_sink, 'sound/machines/click.ogg', 20, TRUE)
- return
- return ..()
-
-/obj/structure/sinkframe/wrench_act(mob/living/user, obj/item/tool)
- . = ..()
-
- tool.play_tool_sound(src)
- var/obj/structure/sink/greyscale/new_sink = new(loc, REVERSE_DIR(dir), FALSE)
- new_sink.set_custom_materials(custom_materials)
- qdel(src)
-
- return TRUE
-
-/obj/structure/sinkframe/wrench_act_secondary(mob/living/user, obj/item/tool)
- . = ..()
- tool.play_tool_sound(src)
- deconstruct()
- return TRUE
-
-/obj/structure/sinkframe/atom_deconstruct(dissambled = TRUE)
- drop_materials()
-
-/obj/structure/sinkframe/proc/drop_materials()
- for(var/datum/material/material as anything in custom_materials)
- new material.sheet_type(loc, FLOOR(custom_materials[material] / SHEET_MATERIAL_AMOUNT, 1))
- return
-
-//Water source, use the type water_source for unlimited water sources like classic sinks.
-/obj/structure/water_source
- name = "Water Source"
- icon = 'icons/obj/watercloset.dmi'
- icon_state = "sink"
- desc = "A sink used for washing one's hands and face. This one seems to be infinite!"
- anchored = TRUE
- var/busy = FALSE //Something's being washed at the moment
- var/dispensedreagent = /datum/reagent/water // for whenever plumbing happens
-
-/obj/structure/water_source/Initialize(mapload)
- . = ..()
-
- create_reagents(INFINITY, NO_REACT)
- reagents.add_reagent(dispensedreagent, INFINITY)
-
-/obj/structure/water_source/attack_hand(mob/living/user, list/modifiers)
- . = ..()
- if(.)
- return
- if(!iscarbon(user))
- return
- if(!Adjacent(user))
- return
-
- if(busy)
- to_chat(user, span_warning("Someone's already washing here!"))
- return
- var/selected_area = user.parse_zone_with_bodypart(user.zone_selected)
- var/washing_face = FALSE
- if(selected_area in list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES))
- washing_face = TRUE
- user.visible_message(span_notice("[user] starts washing [user.p_their()] [washing_face ? "face" : "hands"]..."), \
- span_notice("You start washing your [washing_face ? "face" : "hands"]..."))
- busy = TRUE
-
- if(!do_after(user, 4 SECONDS, target = src))
- busy = FALSE
- return
-
- busy = FALSE
-
- if(washing_face)
- SEND_SIGNAL(user, COMSIG_COMPONENT_CLEAN_FACE_ACT, CLEAN_WASH)
- else if(ishuman(user))
- var/mob/living/carbon/human/human_user = user
- if(!human_user.wash_hands(CLEAN_WASH))
- to_chat(user, span_warning("Your hands are covered by something!"))
- return
- else
- user.wash(CLEAN_WASH)
-
- user.visible_message(span_notice("[user] washes [user.p_their()] [washing_face ? "face" : "hands"] using [src]."), \
- span_notice("You wash your [washing_face ? "face" : "hands"] using [src]."))
-
-/obj/structure/water_source/attackby(obj/item/O, mob/living/user, params)
- if(busy)
- to_chat(user, span_warning("Someone's already washing here!"))
- return
-
- if(is_reagent_container(O))
- var/obj/item/reagent_containers/container = O
- if(container.is_refillable())
- if(!container.reagents.holder_full())
- container.reagents.add_reagent(dispensedreagent, min(container.volume - container.reagents.total_volume, container.amount_per_transfer_from_this))
- to_chat(user, span_notice("You fill [container] from [src]."))
- return TRUE
- to_chat(user, span_notice("\The [container] is full."))
- return FALSE
-
- if(istype(O, /obj/item/melee/baton/security))
- var/obj/item/melee/baton/security/baton = O
- if(baton.cell?.charge && baton.active)
- flick("baton_active", src)
- user.Paralyze(baton.knockdown_time)
- user.set_stutter(baton.knockdown_time)
- baton.cell.use(baton.cell_hit_cost)
- user.visible_message(span_warning("[user] shocks [user.p_them()]self while attempting to wash the active [baton.name]!"), \
- span_userdanger("You unwisely attempt to wash [baton] while it's still on."))
- playsound(src, baton.on_stun_sound, 50, TRUE)
- return
-
- if(istype(O, /obj/item/mop))
- O.reagents.add_reagent(dispensedreagent, 5)
- to_chat(user, span_notice("You wet [O] in [src]."))
- playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE)
- return
-
- if(istype(O, /obj/item/stack/medical/gauze))
- var/obj/item/stack/medical/gauze/G = O
- new /obj/item/reagent_containers/cup/rag(loc)
- to_chat(user, span_notice("You tear off a strip of gauze and make a rag."))
- G.use(1)
- return
-
- if(istype(O, /obj/item/stack/sheet/cloth))
- var/obj/item/stack/sheet/cloth/cloth = O
- new /obj/item/reagent_containers/cup/rag(loc)
- to_chat(user, span_notice("You tear off a strip of cloth and make a rag."))
- cloth.use(1)
- return
-
- if(istype(O, /obj/item/stack/ore/glass))
- new /obj/item/stack/sheet/sandblock(loc)
- to_chat(user, span_notice("You wet the sand and form it into a block."))
- O.use(1)
- return
-
- if(O.item_flags & ABSTRACT) //Abstract items like grabs won't wash. No-drop items will though because it's still technically an item in your hand.
- return
-
- if(!user.combat_mode)
- to_chat(user, span_notice("You start washing [O]..."))
- busy = TRUE
- if(!do_after(user, 4 SECONDS, target = src))
- busy = FALSE
- return TRUE
- busy = FALSE
- O.wash(CLEAN_WASH)
- reagents.expose(O, TOUCH, 5 / max(reagents.total_volume, 5))
- user.visible_message(span_notice("[user] washes [O] using [src]."), \
- span_notice("You wash [O] using [src]."))
- return TRUE
-
- return ..()
-
-
-/obj/structure/water_source/puddle //splishy splashy ^_^
- name = "puddle"
- desc = "A puddle used for washing one's hands and face."
- icon_state = "puddle"
- resistance_flags = UNACIDABLE
-
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/structure/water_source/puddle/attack_hand(mob/user, list/modifiers)
- icon_state = "puddle-splash"
- . = ..()
- icon_state = "puddle"
-
-/obj/structure/water_source/puddle/attackby(obj/item/O, mob/user, params)
- icon_state = "puddle-splash"
- . = ..()
- icon_state = "puddle"
-
-//End legacy sink
-
-
-//Shower Curtains//
-//Defines used are pre-existing in layers.dm//
-
-/obj/structure/curtain
- name = "curtain"
- desc = "Contains less than 1% mercury."
- icon = 'icons/obj/watercloset.dmi'
- icon_state = "bathroom-open"
- var/icon_type = "bathroom"//used in making the icon state
- color = "#ACD1E9" //Default color, didn't bother hardcoding other colors, mappers can and should easily change it.
- alpha = 200 //Mappers can also just set this to 255 if they want curtains that can't be seen through
- layer = SIGN_LAYER
- anchored = TRUE
- opacity = FALSE
- density = FALSE
- var/open = TRUE
- /// if it can be seen through when closed
- var/opaque_closed = FALSE
-
-/obj/structure/curtain/Initialize(mapload)
- // see-through curtains should let emissives shine through
- if(!opaque_closed)
- blocks_emissive = EMISSIVE_BLOCK_NONE
- return ..()
-
-/obj/structure/curtain/proc/toggle()
- open = !open
- if(open)
- layer = SIGN_LAYER
- set_opacity(FALSE)
- else
- layer = WALL_OBJ_LAYER
- if(opaque_closed)
- set_opacity(TRUE)
-
- update_appearance()
-
-/obj/structure/curtain/update_icon_state()
- icon_state = "[icon_type]-[open ? "open" : "closed"]"
- return ..()
-
-/obj/structure/curtain/attackby(obj/item/W, mob/user)
- if (istype(W, /obj/item/toy/crayon))
- color = input(user,"","Choose Color",color) as color
- else
- return ..()
-
-/obj/structure/curtain/wrench_act(mob/living/user, obj/item/tool)
- . = ..()
- default_unfasten_wrench(user, tool, time = 5 SECONDS)
- return TRUE
-
-/obj/structure/curtain/wirecutter_act(mob/living/user, obj/item/I)
- ..()
- if(anchored)
- return TRUE
-
- user.visible_message(span_warning("[user] cuts apart [src]."),
- span_notice("You start to cut apart [src]."), span_hear("You hear cutting."))
- if(I.use_tool(src, user, 50, volume=100) && !anchored)
- to_chat(user, span_notice("You cut apart [src]."))
- deconstruct()
-
- return TRUE
-
-
-/obj/structure/curtain/attack_hand(mob/user, list/modifiers)
- . = ..()
- if(.)
- return
- playsound(loc, 'sound/effects/curtain.ogg', 50, TRUE)
- toggle()
-
-/obj/structure/curtain/atom_deconstruct(disassembled = TRUE)
- new /obj/item/stack/sheet/cloth (loc, 2)
- new /obj/item/stack/sheet/plastic (loc, 2)
- new /obj/item/stack/rods (loc, 1)
-
-/obj/structure/curtain/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
- switch(damage_type)
- if(BRUTE)
- if(damage_amount)
- playsound(src.loc, 'sound/weapons/slash.ogg', 80, TRUE)
- else
- playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE)
- if(BURN)
- playsound(loc, 'sound/items/welder.ogg', 80, TRUE)
-
-/obj/structure/curtain/bounty
- icon_type = "bounty"
- icon_state = "bounty-open"
- color = null
- alpha = 255
- opaque_closed = TRUE
-
-/obj/structure/curtain/bounty/start_closed
- icon_state = "bounty-closed"
-
-/obj/structure/curtain/bounty/start_closed/Initialize(mapload)
- . = ..()
- if(open)
- toggle()
-
-/obj/structure/curtain/cloth
- color = null
- alpha = 255
- opaque_closed = TRUE
-
-/obj/structure/curtain/cloth/atom_deconstruct(disassembled = TRUE)
- new /obj/item/stack/sheet/cloth (loc, 4)
- new /obj/item/stack/rods (loc, 1)
-
-/obj/structure/curtain/cloth/fancy
- icon_type = "cur_fancy"
- icon_state = "cur_fancy-open"
-
-/obj/structure/curtain/cloth/fancy/mechanical
- var/id = null
-
-/obj/structure/curtain/cloth/fancy/mechanical/Destroy()
- GLOB.curtains -= src
- return ..()
-
-/obj/structure/curtain/cloth/fancy/mechanical/Initialize(mapload)
- . = ..()
- GLOB.curtains += src
-
-/obj/structure/curtain/cloth/fancy/mechanical/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
- id = "[port.shuttle_id]_[id]"
-
-/obj/structure/curtain/cloth/fancy/mechanical/proc/open()
- icon_state = "[icon_type]-open"
- layer = SIGN_LAYER
- SET_PLANE_IMPLICIT(src, GAME_PLANE)
- set_density(FALSE)
- open = TRUE
- set_opacity(FALSE)
-
-/obj/structure/curtain/cloth/fancy/mechanical/proc/close()
- icon_state = "[icon_type]-closed"
- layer = WALL_OBJ_LAYER
- set_density(TRUE)
- open = FALSE
- if(opaque_closed)
- set_opacity(TRUE)
-
-/obj/structure/curtain/cloth/fancy/mechanical/attack_hand(mob/user, list/modifiers)
- return
-
-/obj/structure/curtain/cloth/fancy/mechanical/start_closed
- icon_state = "cur_fancy-closed"
-
-/obj/structure/curtain/cloth/fancy/mechanical/start_closed/Initialize(mapload)
- . = ..()
- close()
diff --git a/icons/obj/watercloset.dmi b/icons/obj/watercloset.dmi
index 93ab67c804c..0168e12accc 100644
Binary files a/icons/obj/watercloset.dmi and b/icons/obj/watercloset.dmi differ
diff --git a/sound/attributions.txt b/sound/attributions.txt
index bade328eff9..502c412153a 100644
--- a/sound/attributions.txt
+++ b/sound/attributions.txt
@@ -173,3 +173,5 @@ whistle1.ogg: https://freesound.org/people/taure/sounds/411638/ , license: CC0 1
portal_close, portal_open_1 , portal_open_2 , portal_open_3 , portal_travel made by @virgilcore (discord IGN)
+toilet-flush.ogg is made by shw489 (CC0):
+https://freesound.org/people/shw489/sounds/234389/
diff --git a/sound/machines/toilet_flush.ogg b/sound/machines/toilet_flush.ogg
new file mode 100644
index 00000000000..bddefe76f6a
Binary files /dev/null and b/sound/machines/toilet_flush.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index ea9917c8e44..4284535ad3c 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2715,6 +2715,7 @@
#include "code\game\objects\structures\cat_house.dm"
#include "code\game\objects\structures\chess.dm"
#include "code\game\objects\structures\containers.dm"
+#include "code\game\objects\structures\curtains.dm"
#include "code\game\objects\structures\deployable_turret.dm"
#include "code\game\objects\structures\destructible_structures.dm"
#include "code\game\objects\structures\displaycase.dm"
@@ -2775,7 +2776,6 @@
#include "code\game\objects\structures\training_machine.dm"
#include "code\game\objects\structures\traps.dm"
#include "code\game\objects\structures\votingbox.dm"
-#include "code\game\objects\structures\watercloset.dm"
#include "code\game\objects\structures\windoor_assembly.dm"
#include "code\game\objects\structures\window.dm"
#include "code\game\objects\structures\beds_chairs\alien_nest.dm"
@@ -2841,6 +2841,10 @@
#include "code\game\objects\structures\transit_tubes\transit_tube.dm"
#include "code\game\objects\structures\transit_tubes\transit_tube_construction.dm"
#include "code\game\objects\structures\transit_tubes\transit_tube_pod.dm"
+#include "code\game\objects\structures\water_structures\sink.dm"
+#include "code\game\objects\structures\water_structures\toilet.dm"
+#include "code\game\objects\structures\water_structures\urinal.dm"
+#include "code\game\objects\structures\water_structures\water_source.dm"
#include "code\game\turfs\baseturf_skipover.dm"
#include "code\game\turfs\baseturfs.dm"
#include "code\game\turfs\change_turf.dm"