Toilet Overhaul (#19370)

* April Fool !

there's 30 minutes till midnight in my timezone so it's still valid

* Powder that make the maintainer say yes

* Apply suggestions from code review

Co-authored-by: Cameron Lennox <killer65311@gmail.com>

* Edits

* Update watercloset.dm

* five nights at space station toilet

pls merge or else poopwater 2 pr will be next

---------

Co-authored-by: Cameron Lennox <killer65311@gmail.com>
This commit is contained in:
Reo Lozzot
2026-04-23 20:22:06 -05:00
committed by GitHub
parent 6530dce7fa
commit f8fc337da3
18 changed files with 459 additions and 194 deletions
+28 -20
View File
@@ -15,27 +15,35 @@
/obj/effect/effect/water/proc/set_up(var/turf/target, var/step_count = 5, var/delay = 5)
if(!target)
return
for(var/i = 1 to step_count)
if(!loc)
step_process(target, step_count, delay)
/obj/effect/effect/water/proc/step_process(var/turf/target, var/step_count, var/delay, var/iteration)
step_count--
if(!loc)
qdel(src)
return
step_towards(src, target)
var/turf/T = get_turf(src)
if(T && reagents)
reagents.touch_turf(T, reagents.total_volume) //VOREStation Add
var/mob/M
for(var/atom/A in T)
if(!ismob(A) && A.simulated) // Mobs are handled differently
reagents.touch(A, reagents.total_volume)
else if(ismob(A) && !M)
M = A
if(M)
reagents.splash(M, reagents.total_volume)
QDEL_IN(src, 1 SECOND)
return
step_towards(src, target)
var/turf/T = get_turf(src)
if(T && reagents)
reagents.touch_turf(T, reagents.total_volume) //VOREStation Add
var/mob/M
for(var/atom/A in T)
if(!ismob(A) && A.simulated) // Mobs are handled differently
reagents.touch(A, reagents.total_volume)
else if(ismob(A) && !M)
M = A
if(M)
reagents.splash(M, reagents.total_volume)
break
if(T == get_turf(target))
break
sleep(delay)
sleep(10)
qdel(src)
if(T == get_turf(target))
QDEL_IN(src, 1 SECOND)
return
if(step_count > 0)
addtimer(CALLBACK(src, PROC_REF(step_process), target, step_count, delay, iteration), delay)
return
QDEL_IN(src, 1 SECOND)
/obj/effect/effect/water/Move(turf/newloc)
if(newloc.density)
@@ -0,0 +1,7 @@
// For some reason, /landmark seems to be near-exclusively used for spawnpoints. Yet it's also being used here as the shit output.
// I smell a refactor/repath in the future, but that time is not now.
/obj/effect/landmark/teleplumb_exit
name = "teleplumbing exit"
/obj/effect/landmark/teleplumb_exit/Entered(atom/movable/thing, atom/old_loc)
thing.forceMove(get_turf(src))
+344 -67
View File
@@ -14,10 +14,22 @@
icon_state = "toilet"
density = FALSE
anchored = TRUE
var/open = 0 //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/open = FALSE //if the lid is up
var/cistern = FALSE //if the cistern bit is open
var/w_items = 0 //the combined w_class of all the items in the cistern
/// Used to both track the crystal needed to upgrade the toilet, and to tell if the toilet is teleplumbed. Set to True in subtypes or mapping if you'd like it to be teleplumbed on init.
var/obj/item/teleplumb_crystal = null
/// Bin used to upgrade this toilet. Turned into a real object on init.
var/obj/item/stock_parts/matter_bin/bin = /obj/item/stock_parts/matter_bin
//Flushing stuff
var/panic_mult = 1
var/refilling = FALSE
var/datum/weakref/swirlie_mob = null //the mob being given a swirlie
var/datum/weakref/teleplumb_dest_ref //the destination of this toilet if it's teleplumbed
var/list/currently_held_objects = list() //List of objects currently in the toilet, used for flushing.
COOLDOWN_DECLARE(panic_flush)
/obj/structure/toilet/Initialize(mapload)
. = ..()
@@ -25,7 +37,44 @@
update_icon()
AddComponent(/datum/component/hose_connector/endless_drain) // Cannot suck from toilet... for obvious reasons.
/obj/structure/toilet/attack_hand(mob/living/user as mob)
if(ispath(bin))
bin = new bin(src)
if(teleplumb_crystal)
teleplumb_crystal = new /obj/item/bluespace_crystal(src)
teleplumb_dest_ref = WEAKREF(locate(/obj/effect/landmark/teleplumb_exit))
desc = "The BS-500, a bluespace rift-rotation-based waste disposal unit for small matter. This one seems remarkably clean."
// Non-bluespace plumbing. For POIs and player construction n' stuff.
var/obj/structure/disposalpipe/trunk/trunk = locate() in get_turf(src)
AddComponent(/datum/component/disposal_system_connection, FALSE, FALSE) //Dont show our disposal connection, and we want to handle failed flushes on our own.
RegisterSignal(src, COMSIG_DISPOSAL_RECEIVE, PROC_REF(toilet_reflux))
if(trunk)
SEND_SIGNAL(src, COMSIG_DISPOSAL_LINK, trunk)
/obj/structure/toilet/Destroy()
if(bin)
if(bin.type == /obj/item/stock_parts/matter_bin) //Specifically, if this is a basic bin, you dont get it back. Other bins are returned.
QDEL_NULL(bin)
else
bin.forceMove(src.loc)
bin = null
if(teleplumb_crystal)
teleplumb_crystal.forceMove(src.loc)
teleplumb_crystal = null
for(var/atom/movable/AM in currently_held_objects)
AM.forceMove(src.loc)
currently_held_objects = null
swirlie_mob = null
teleplumb_dest_ref = null
. = ..()
/obj/structure/toilet/update_icon()
icon_state = "[initial(icon_state)][open][cistern]"
/obj/structure/toilet/attack_hand(mob/living/user)
var/mob/living/swirlie = swirlie_mob?.resolve()
if(swirlie)
user.setClickCooldown(user.get_attack_speed())
user.visible_message(span_danger("[user] slams the toilet seat onto [swirlie.name]'s head!"), span_notice("You slam the toilet seat onto [swirlie.name]'s head!"), "You hear reverberating porcelain.")
@@ -33,26 +82,45 @@
return
if(cistern && !open)
if(!contents.len)
var/list/cistern_loot = list()
for(var/atom/movable/AM in contents)
if(AM == bin || AM == teleplumb_crystal)
continue
cistern_loot += AM
if(!length(cistern_loot))
//You can take the bluespace crystal out if there's nothing else in the cistern.
if(teleplumb_crystal && ishuman(user)) //Only humans can grief the toilets
if(tgui_alert(user, "You see a glimmering crystal attached to parts of the toilet's components... Do you want to take it?", "Toilet Crystal", list("Take it!", "Leave it.")) == "Take it!")
user.put_in_hands(teleplumb_crystal)
to_chat(user, span_notice("You take \the [teleplumb_crystal]."))
teleplumb_crystal = null
teleplumb_dest_ref = null
desc = initial(desc)
else
to_chat(user, span_notice("You decide to leave it."))
to_chat(user, span_notice("The cistern is empty."))
return
var/obj/item/I = pick(cistern_loot)
if(ishuman(user))
user.put_in_hands(I)
else
var/obj/item/I = pick(contents)
if(ishuman(user))
user.put_in_hands(I)
else
I.loc = get_turf(src)
to_chat(user, span_notice("You find \an [I] in the cistern."))
w_items -= I.w_class
return
I.loc = get_turf(src)
to_chat(user, span_notice("You find \an [I] in the cistern."))
w_items -= I.w_class
return
open = !open
update_icon()
/obj/structure/toilet/update_icon()
icon_state = "[initial(icon_state)][open][cistern]"
/obj/structure/toilet/attack_ai(mob/user)
if(isrobot(user))
if(user.client && !user.is_remote_viewing())
return attack_hand(user)
else
return attack_hand(user)
/obj/structure/toilet/attackby(obj/item/I as obj, mob/living/user as mob)
/obj/structure/toilet/attackby(obj/item/I, mob/living/user)
if(I.has_tool_quality(TOOL_CROWBAR))
to_chat(user, span_notice("You start to [cistern ? "replace the lid on the cistern" : "lift the lid off the cistern"]."))
playsound(src, 'sound/effects/stonedoor_openclose.ogg', 50, 1)
@@ -62,6 +130,16 @@
update_icon()
return
if(I.has_tool_quality(TOOL_WRENCH) && cistern) //Kill Toilet.
if(refilling)
to_chat(user, span_notice("Wait for \the [src] to finish refilling..."))
to_chat(user, span_notice("You begin to dismantle \the [src]..."))
if(!do_after(user, 5 SECONDS, src))
return
to_chat(user, span_notice("You dismantle \the [src]."))
deconstruct()
return
if(istype(I, /obj/item/grab))
user.setClickCooldown(user.get_attack_speed(I))
var/obj/item/grab/G = I
@@ -69,69 +147,268 @@
if(isliving(G.affecting))
var/mob/living/GM = G.affecting
if(G.state>1)
if(!GM.loc == get_turf(src))
to_chat(user, span_notice("[GM.name] needs to be on the toilet."))
return
if(open && !swirlie)
user.visible_message(span_danger("[user] starts to give [GM.name] a swirlie!"), span_notice("You start to give [GM.name] a swirlie!"))
swirlie = GM
if(do_after(user, 3 SECONDS, target = GM))
user.visible_message(span_danger("[user] gives [GM.name] a swirlie!"), span_notice("You give [GM.name] a swirlie!"), "You hear a toilet flushing.")
if(G.state <= GRAB_PASSIVE)
to_chat(user, span_notice("You need a tighter grip."))
return
if(!GM.loc == get_turf(src))
to_chat(user, span_notice("[GM.name] needs to be on the toilet."))
return
var/mob/living/swirlie = swirlie_mob?.resolve()
if(open && !swirlie)
user.visible_message(span_danger("[user] starts to give [GM] a swirlie!"), span_notice("You start to give [GM] a swirlie!"))
swirlie_mob = WEAKREF(GM)
if(do_after(user, 3 SECONDS, target = GM))
if(!open) //Someone closed it while we were trying to swirlie. Rude.
open = TRUE //Open it.
update_icon()
if(!refilling)
user.visible_message(span_danger("[user] gives [GM] a swirlie!"), span_notice("You give [GM] a swirlie!"), "You hear a toilet flushing.")
if(!GM.internal)
GM.adjustOxyLoss(5)
swirlie = null
else
user.visible_message(span_danger("[user] slams [GM.name] into the [src]!"), span_notice("You slam [GM.name] into the [src]!"))
GM.adjustBruteLoss(5)
if(GM.size_multiplier <= 0.75)
GM.visible_message(span_danger("[GM] gets sucked into \the [src] due to their small size!"), span_userdanger("You get sucked into \the [src]!"))
GM.forceMove(get_turf(src))
GM.Weaken(5)
flush()
else
user.visible_message(span_warning("[user] tries to give [GM.name] a swirlie, but the toilet was still refilling!"), span_warning("You cant give [GM] swirlie while \the [src] is still refilling!"))
swirlie_mob = null
else
to_chat(user, span_notice("You need a tighter grip."))
user.visible_message(span_danger("[user] slams [GM] into the [src]!"), span_notice("You slam [GM] into the [src]!"))
GM.adjustBruteLoss(5)
if(cistern && !teleplumb_crystal && istype(I, /obj/item/bluespace_crystal))
to_chat(user, span_notice("You begin to insert \the [I] into \the [src]..."))
if(!do_after(user, 2 SECONDS, src))
return
to_chat(user, span_notice("You insert \the [I] into \the [src]. A deep rumble eminates from within it, and a faint blue glow eminates from the bottom of the bowl for a moment."))
user.drop_item()
I.forceMove(src)
teleplumb_crystal = I
//TODO: add a way to link this to custom destinations.
teleplumb_dest_ref = WEAKREF(locate(/obj/effect/landmark/teleplumb_exit))
desc = "The BS-500, a bluespace rift-rotation-based waste disposal unit for small matter. This one seems remarkably clean."
return
if(cistern && istype(I, /obj/item/stock_parts/matter_bin))
to_chat(user, span_notice("You begin to replace \the [bin] in \the [src] with \the [I]."))
if(!do_after(user, 2 SECONDS, src))
return
to_chat(user, span_notice("You replace \the [bin] with \the [I]."))
bin.forceMove(src.loc) //Remove the old bin.
user.drop_item()
I.forceMove(src)
bin = I //Set the internally stored bin to the new bin.
return
if(cistern && !istype(user,/mob/living/silicon/robot)) //STOP PUTTING YOUR MODULES IN THE TOILET.
if(I.w_class > ITEMSIZE_NORMAL) //3
to_chat(user, span_notice("\The [I] does not fit."))
return
if(w_items + I.w_class > ITEMSIZE_HUGE) //5
if(w_items + I.w_class > ITEMSIZE_COST_TINY * 5) // 5 tiny or 2 small and 1 tiny
to_chat(user, span_notice("The cistern is full."))
return
user.drop_item()
I.loc = src
I.forceMove(src)
w_items += I.w_class
to_chat(user, "You carefully place \the [I] into the cistern.")
return
/obj/structure/toilet/prison
name = "prison toilet"
icon_state = "toilet2"
/obj/structure/toilet/click_alt(mob/user)
if(!isliving(user) || get_dist(user, src) > 1 || user.loc == src )
return
if(user.stat) //replace with user.canUseTopic() in the future
return CLICK_ACTION_BLOCKING
if(!open)
to_chat(user, span_notice("You need to open the lid before flushing \the [src]."))
return CLICK_ACTION_BLOCKING
if(refilling)
to_chat(user, span_notice("The toilet is still refilling its tank."))
playsound(src, 'sound/machines/door_locked.ogg', 30, 1)
//Even while it's flushing, you can repeatedly pull down the lever for a bigger flush.
if(user.a_intent == I_HURT)
if(COOLDOWN_FINISHED(src, panic_flush))
panic_mult++
COOLDOWN_START(src, panic_flush, 1 SECOND) //Let's not encourage hitting the click-cap.
user.visible_message(span_notice("[user] pulls the flush lever mid-flush!"), span_notice("You full the flush lever mid-flush!"), "you hear the sound of a toilet handle being jiggled.")
return CLICK_ACTION_BLOCKING
to_chat(user, span_notice("You need to wait [round((COOLDOWN_TIMELEFT(src, panic_flush)) / 10, 0.1)] more seconds longer before you can pull the flush lever again!"))
return CLICK_ACTION_BLOCKING
//Flush succeeds
user.visible_message(span_notice("[user] flushes the toilet."), span_notice("You flush the toilet."), "you hear a toilet flushing.")
flush()
return CLICK_ACTION_SUCCESS
/obj/structure/toilet/prison/attack_hand(mob/living/user)
/obj/structure/toilet/proc/flush()
refilling = TRUE
playsound(src, 'sound/vore/death7.ogg', 50, 1) //Got lazy about getting new sound files. Have a sick remix lmao.
playsound(src, 'sound/effects/bubbles.ogg', 50, 1)
playsound(src, 'sound/mecha/powerup.ogg', 30, 1)
var/list/bowl_contents = list()
for(var/obj/item/I in loc.contents)
if(istype(I) && !I.anchored)
bowl_contents += I
for(var/mob/living/L in loc.contents)
if(L.buckled || !(L.resting || L.lying))
continue
var/bin_bonus = 0.15
if(bin)
bin_bonus *= bin.rating
if(L.size_multiplier <= 0.6 + bin_bonus)
bowl_contents += L
if(!length(bowl_contents)) //Reduced recharge if nothing is being flushed
VARSET_IN(src, refilling, FALSE, 7.5 SECONDS)
VARSET_IN(src, panic_mult, initial(panic_mult), 7.5 SECONDS)
return
begin_flush(bowl_contents[1], bowl_contents)
return
/obj/structure/toilet/prison/attackby(obj/item/I, mob/living/user)
if(istype(I, /obj/item/grab))
user.setClickCooldown(user.get_attack_speed(I))
var/obj/item/grab/G = I
///Timer proc that takes the object given and begins the flush process. Makes the object spin.
/obj/structure/toilet/proc/begin_flush(atom/movable/flushed, list/pick_list)
flushed.SpinAnimation(5,3)
addtimer(CALLBACK(src, PROC_REF(secondary_flush), flushed, pick_list), 0.2 SECONDS)
if(isliving(G.affecting))
var/mob/living/GM = G.affecting
///Timer proc that takes the object given and removes it from the list, beginning to spin the next object if there is one.
/obj/structure/toilet/proc/secondary_flush(atom/movable/flushed, list/pick_list)
pick_list -= flushed
if(G.state>1)
if(!GM.loc == get_turf(src))
to_chat(user, span_notice("[GM.name] needs to be on the toilet."))
return
if(open && !swirlie)
user.visible_message(span_danger("[user] starts to give [GM.name] a swirlie!"), span_notice("You start to give [GM.name] a swirlie!"))
swirlie = GM
if(do_after(user, 3 SECONDS, target = GM))
user.visible_message(span_danger("[user] gives [GM.name] a swirlie!"), span_notice("You give [GM.name] a swirlie!"), "You hear a toilet flushing.")
if(!GM.internal)
GM.adjustOxyLoss(5)
swirlie = null
else
user.visible_message(span_danger("[user] slams [GM.name] into the [src]!"), span_notice("You slam [GM.name] into the [src]!"))
GM.adjustBruteLoss(5)
else
to_chat(user, span_notice("You need a tighter grip."))
if(!length(pick_list)) //All flushed.
addtimer(CALLBACK(src, PROC_REF(tertiary_flush), flushed, TRUE), 1.5 SECONDS, TIMER_DELETE_ME)
return
addtimer(CALLBACK(src, PROC_REF(tertiary_flush), flushed, FALSE), 1.5 SECONDS, TIMER_DELETE_ME) //Put the object in the bin.
var/obj_to_be_flushed = pick_list[1]
addtimer(CALLBACK(src, PROC_REF(begin_flush), obj_to_be_flushed, pick_list), 0.2 SECONDS, TIMER_DELETE_ME)
///Adds the object to the toilet's current_flush list.
/obj/structure/toilet/proc/tertiary_flush(atom/movable/flushed, flush_completed)
if(flushed.loc == loc)
flushed.forceMove(src)
currently_held_objects += flushed
if(flush_completed) //Flushed it all.
addtimer(CALLBACK(src, PROC_REF(flush_send), currently_held_objects), 1 SECOND, TIMER_DELETE_ME)
VARSET_IN(src, refilling, FALSE, 20 SECONDS)
return
/obj/structure/toilet/proc/flush_send(list/to_send)
var/flush_weight = 0
var/max_flush_weight = get_flush_power()
var/list/taken_contents = list()
for(var/atom/movable/flushed in to_send)
if(flushed.loc != src)
continue
//Mobs and items are calculated differently
var/weight_value = 0
if(isitem(flushed))
var/obj/item/I = flushed
weight_value = I.w_class
if(isliving(flushed))
var/mob/living/L = flushed
weight_value = L.size_multiplier * 10
if(flush_weight + weight_value <= max_flush_weight)
taken_contents += flushed
flush_weight += weight_value
var/atom/teleplumb_dest = teleplumb_dest_ref?.resolve()
if(teleplumb_crystal && teleplumb_dest)
if(isliving(flushed))
var/mob/living/m = flushed
to_chat(m, span_danger("You're glunked down by \the [src] through a series of extradimensional bluespace pipeworks!"))
flushed.forceMove(teleplumb_dest)
var/datum/gas_mixture/air_contents = new(1) //1 liter of nothing, ig.
if(SEND_SIGNAL(src, COMSIG_DISPOSAL_FLUSH, to_send, air_contents))
for(var/atom/movable/flushed in to_send)
if(isliving(flushed))
var/mob/living/m = flushed
to_chat(m, span_warning("You're flushed away by \the [src]!"))
var/flush_failed = FALSE
for(var/atom/movable/flushed in to_send)
if(flushed.loc != src) //Not in here, flushed or otherwise.
continue
flush_failed = TRUE
flushed.forceMove(src.loc)
if(flush_failed)
visible_message(span_warning("\The [src] glurks and splutters, unable to guzzle more stuff down in a single flush!"), span_warning("Glornch"))
panic_mult = initial(panic_mult)
currently_held_objects = list() //Clear the list.
/obj/structure/toilet/proc/toilet_reflux(datum/source, list/received_items, datum/gas_mixture/gas)
SIGNAL_HANDLER
var/turf/T = get_turf(src)
T.assume_air(gas)
if(!length(received_items))
visible_message(span_warning("The water in \the [src] gurgles and bubbles ominously..."), span_notice("You hear a wet gurgling and spluttering..."), runemessage = "glurgles")
return
visible_message(span_danger("\The [src] gurgles for a moment, before spewing forth a bunch of stuff in a wave of toilet water!"), "GLORGLONCH!")
for(var/atom/movable/AM in received_items)
var/turf/target_turf = get_offset_target_turf(loc, rand(-2, 2), rand(-2, 2))
AM.forceMove(T)
AM.pipe_eject(0)
AM.throw_at(target_turf, 5, 1)
//Toilet blast !
for(var/direction in GLOB.alldirs + null) // null is for the center tile.
if(prob(75) && direction != null) //Only sometimes wet blast, except the center tile. That always wets.
continue
var/turf/target_turf = get_ranged_target_turf(src, direction, rand(1, 2))
if(!target_turf) // This shouldn't fail but...
continue
var/obj/effect/effect/water/W = new(get_turf(T))
W.create_reagents(15)
W.reagents.add_reagent(REAGENT_ID_WATER, 15)
W.set_color()
W.set_up(target_turf)
/obj/structure/toilet/atom_deconstruct(disassembled)
place_deconstruction_materials()
for(var/atom/movable/AM in contents) //Should handle both cistern and upgrade parts.
AM.forceMove(src.loc)
/obj/structure/toilet/proc/place_deconstruction_materials()
new /obj/item/stack/material/steel(src.loc, 5)
new /obj/item/reagent_containers/glass/bucket(src.loc)
/obj/structure/toilet/proc/get_flush_power()
. = ITEMSIZE_COST_SMALL * 3 // 3 small items, or 6 tiny items.
if(bin)
. *= bin.rating
. *= panic_mult
if(teleplumb_crystal) //Teleplumbed gets applied at the end
. *= 2
/obj/structure/toilet/teleplumbed
teleplumb_crystal = TRUE
/obj/structure/toilet/prison
name = "prison toilet"
desc = "The HT-421, a torque rotation based waste disposal unit for small matter. This older model isnt quite as capable as newer units."
icon_state = "toilet2"
/obj/structure/toilet/prison/place_deconstruction_materials()
new /obj/item/stack/material/iron(src.loc, 5)
new /obj/item/reagent_containers/glass/bucket(src.loc)
/obj/structure/toilet/prison/get_flush_power()
. = ..()
if(teleplumb_crystal) //No teleplumb bonus.
. /= 2
return round(. / 3) //Has a 1/3 the power of a normal toilet.
/obj/structure/toilet/prison/flush_send(list/to_send) //Permabrig escape prevention
for(var/mob/living/escapee in to_send)
to_chat(escapee, span_warning("A grate at the bottom of \the [src] prevents you from being flushed away!")) //Kinda gross, come to think of it.
escapee.forceMove(src.loc)
to_send -= escapee
. = ..()
/obj/structure/urinal
name = "urinal"
@@ -141,7 +418,7 @@
density = FALSE
anchored = TRUE
/obj/structure/urinal/attackby(obj/item/I as obj, mob/user as mob)
/obj/structure/urinal/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/grab))
var/obj/item/grab/G = I
if(isliving(G.affecting))
@@ -185,7 +462,7 @@
//add heat controls? when emagged, you can freeze to death in it?
/obj/machinery/shower/attack_hand(mob/M as mob)
/obj/machinery/shower/attack_hand(mob/M)
on = !on
update_icon()
handle_mist()
@@ -197,7 +474,7 @@
else
soundloop.stop()
/obj/machinery/shower/attackby(obj/item/I as obj, mob/user as mob)
/obj/machinery/shower/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/analyzer)) //Lol? Why...
to_chat(user, span_notice("The water temperature seems to be [current_temperature]."))
@@ -646,7 +923,7 @@
thing.reagents.clear_reagents()
thing.update_icon()
/obj/structure/sink/attack_hand(mob/user as mob)
/obj/structure/sink/attack_hand(mob/user)
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/external/temp = H.organs_by_name[BP_R_HAND]
@@ -697,7 +974,7 @@
for(var/mob/V in viewers(src, null))
V.show_message(span_notice("[user] washes their hands using \the [src]."))
/obj/structure/sink/attackby(obj/item/O as obj, mob/user as mob)
/obj/structure/sink/attackby(obj/item/O, mob/user)
if(busy)
to_chat(user, span_warning("Someone's already washing here."))
return
@@ -772,12 +1049,12 @@
icon_state = "puddle"
desc = "A small pool of some liquid, ostensibly water."
/obj/structure/sink/puddle/attack_hand(mob/M as mob)
/obj/structure/sink/puddle/attack_hand(mob/M)
icon_state = "puddle-splash"
..()
icon_state = "puddle"
/obj/structure/sink/puddle/attackby(obj/item/O as obj, mob/user as mob)
/obj/structure/sink/puddle/attackby(obj/item/O, mob/user)
icon_state = "puddle-splash"
..()
icon_state = "puddle"
@@ -1,41 +0,0 @@
//Flushable toilets on station levels. Flushing sends stuff directly to a trashpit landmark without stinking up the cargo office.
//Only on-station toilets are affected and only if the trashpit landmark also exists. Otherwise toilets will stay normal.
/obj/structure/toilet
var/teleplumbed = FALSE
var/exit_landmark
/obj/structure/toilet/Initialize(mapload)
if(z in using_map.map_levels)
teleplumbed = TRUE
exit_landmark = locate(/obj/effect/landmark/teleplumb_exit)
if(teleplumbed && exit_landmark)
desc = "The BS-500, a bluespace rift-rotation-based waste disposal unit for small matter. This one seems remarkably clean."
return ..()
/obj/structure/toilet/attack_hand(mob/living/user as mob)
if(open && teleplumbed && exit_landmark)
var/list/bowl_contents = list()
for(var/obj/item/I in loc.contents)
if(istype(I) && !I.anchored)
bowl_contents += I
if(bowl_contents.len)
user.visible_message(span_notice("[user] flushes the toilet."), span_notice("You flush the toilet."))
playsound(src, 'sound/vore/death7.ogg', 50, 1) //Got lazy about getting new sound files. Have a sick remix lmao.
playsound(src, 'sound/effects/bubbles.ogg', 50, 1)
playsound(src, 'sound/mecha/powerup.ogg', 30, 1)
for(var/obj/item/F in bowl_contents)
F.forceMove(get_turf(exit_landmark))
bowl_contents -= F
return
return ..()
/obj/structure/toilet/attack_ai(mob/user as mob)
if(isrobot(user))
if(user.client && !user.is_remote_viewing())
return attack_hand(user)
else
return attack_hand(user)
/obj/effect/landmark/teleplumb_exit
name = "teleplumbing exit"