mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-30 16:47:13 +01:00
Toilet and urinal updates (flushing dead fish included) (#83490)
## About The Pull Request I split the water closets file into a folder for all the different structures and also did a few other things 1. Remakes https://github.com/tgstation/tgstation/pull/81914 2. Reworks toilets, it’s now Alt Click to open/close the cover, left click takes things out of the toilet and cistern, right click flushes 3. You can now put dead fish in the toilet to flush it 4. Adds context tips to toilets. ## Why It's Good For The Game When I was a 7 year old boy, I had a friend goldfish named Jeremy. He was my greatest friend until one day when my brother told me it’s for babies and flushed him alive down the toilet, and I’ve never seen it again. Closes https://github.com/tgstation/tgstation/issues/81903 Lets you flush down people’s fish in the toilet, and re-experience their loss. This was inspired by my lawyer fish PR, to add things more on the non-fishing side of fish. ## Changelog 🆑 add: You can now flush toilets. You can also put fish in the toilet. And flush them. fix: Urinals can no longer be used to delete items. fix: Urinals no longer grant infinite urinal cakes. /🆑 --------- Co-authored-by: Jacquerel <hnevard@gmail.com> Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com>
This commit is contained in:
co-authored by
Jacquerel
GoldenAlpharex
parent
432b88a05b
commit
7d6adad613
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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]."), "<i>You hear a squish.</i>")
|
||||
icon_state = "urinalcake_squish"
|
||||
addtimer(VARSET_CALLBACK(src, icon_state, "urinalcake"), 0.8 SECONDS)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user