Files
Bubberstation/code/game/objects/structures/watercloset.dm
T
ArcaneMusicandGitHub a77ac3e4f1 Adds a new component so that items that are "attached" to walls will now drop/deconstruct on turf destruction. (#77417)
## About The Pull Request

Adds a new component, called wall_mounted, which applies on the
wallframe objects on construction, as well as a number of wall frame
objects and structures to cover mapped in, roundstart objects of the
like.
I might have forgotten a few, but this covers the vast majority that
players will run into in a given round.

This will cover wall destruction, turf explosion, the whole nine yards,
and call that object/structure/machine's deconstruct proc. We have some
special handling for intercoms as well since they're apparently items.

So most basic case is this: You have a wall. that wall holds a sign. If
you examine the wall, it tells you that the wall is currently supporting
the **Example Sign**. It tells you that if the wall is damaged or
destroyed, the sign will **fall off the wall.** So, if you were to
welder, bomb, or hulk your way through that wall, it would call the
deconstruct() proc on that sign, and fall off the wall, leaving an item
sign at the foot of the wall.

## To-Do

- [x] Stop breaking all wallmounts when operating shuttles (Signal
conflict with COMSIG_TURF_CHANGED 😔)
- [x] Confirm that the ~~deconstruct~~ designated proc of each wallmount
falling is sane for the intended object
- [x] Clean up the contents of the wall_mounted component to reduce
copy-paste on object init.
- [x] Add it to more stuff that may just not have a directional helper?
- [x] ~~Change how APC construction is handled to make it easier!~~
- [x] ~~Don't accidently nerf malf AI into the ground I guess~~

## Why It's Good For The Game

Closes #22283.
Helps close more of #47526.
Closes #54983.
Closes https://github.com/wall-nerds/wallening/issues/90.

All of these objects are "wall mounts". It stands to reason that they're
mounted to the walls they appear to be attached to. This attempts to
rectify them by giving them a turf link to the turf they're mounted to,
and then upon changes to that turf, dropping or breaking that object.
It'll need a little more polish to get to 100%, since I can see a few
more issues to iron out first, but I'm dropping this here for now to get
some feedback and put some fire under me to get this completed.

## Changelog

🆑
add: Wall mounted objects (Things like APCs, Air Alarms, Light switches,
Signs, Posters, Newscasters, you name it) will now fall to the ground
and break or deconstruct when their attaching wall is changed or broken.
/🆑
2023-09-16 00:17:38 +00:00

890 lines
29 KiB
Plaintext

/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/deconstruct()
if(!(flags_1 & NODECONSTRUCT_1))
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 && !(flags_1&NODECONSTRUCT_1))
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/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
new /obj/item/wallframe/urinal(loc)
qdel(src)
/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"), 8)
/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 = parse_zone(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, 40, target = src))
busy = FALSE
return
busy = FALSE
reagents.remove_any(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 && !(flags_1&NODECONSTRUCT_1))
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, 40, 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/deconstruct()
if(!(flags_1 & NODECONSTRUCT_1))
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/deconstruct()
if(!(flags_1 & NODECONSTRUCT_1))
drop_materials()
return ..()
/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/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 = parse_zone(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"
/obj/structure/water_source/puddle/deconstruct(disassembled = TRUE)
qdel(src)
//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_PLANE_IMPLICIT(src, GAME_PLANE)
set_density(FALSE)
set_opacity(FALSE)
else
layer = WALL_OBJ_LAYER
SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
set_density(TRUE)
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/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)
qdel(src)
/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/deconstruct(disassembled = TRUE)
new /obj/item/stack/sheet/cloth (loc, 4)
new /obj/item/stack/rods (loc, 1)
qdel(src)
/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_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
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()