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"