Files
Paradise/code/game/machinery/washing_machine.dm
AlanandGitHub db9492f9b8 Add beach shells. (#31673)
* Add beach shells.

* Improve shell icons and palettes.
2026-02-27 19:06:24 +00:00

401 lines
15 KiB
Plaintext

#define DEFAULT_TUB_CAPACITY 20
#define WM_STATE_EMPTY 1
#define WM_STATE_FULL 2
#define WM_STATE_RUNNING 3
#define WM_STATE_BLOODY 4
#define WM_STATE_RUNNING_BLOODY 5
/obj/machinery/washing_machine
name = "washing machine"
desc = "Gets rid of those pesky bloodstains, or your money back!"
icon = 'icons/obj/machines/washing_machine.dmi'
icon_state = "wm_10"
density = TRUE
anchored = TRUE
var/washing_state = WM_STATE_EMPTY
/// LAZYLIST of insert obj/item's inside the washer tub
var/list/inserted_items
/// LAZYLIST of inserted mob/living's inside the washer tube
var/list/inserted_mobs
/// Is the washer in the middle of a wash cycle? Used to regulate certain interactions with the washer
var/washing = FALSE
/// Is the washer currently open?
var/door_open = FALSE
/// How much shit is currently in this laundry machine? Used to cap how much stuff you can put in here
var/current_tub_capacity = 0
/// The cap for how much stuff you can shove in here
var/max_tub_capacity = DEFAULT_TUB_CAPACITY
/// Is the washing machine tub filled with gibs/blood? Effects the sprite it uses
var/bloody_mess = FALSE
/// Where is our DYE color coming from?
var/obj/item/color_source
/// What DYE color are we dyeing stuff?
var/wash_color
/obj/machinery/washing_machine/Destroy()
eject_tub_contents() // cleans up refs & signals
return ..()
/obj/machinery/washing_machine/update_icon_state()
icon_state = "wm_[washing_state][door_open ? 1 : 0][panel_open ? "_panel" : ""]"
/// Determines which "WM state" to apply based on if the machine is a bloody mess, full/empty, washing, etc and then updates the icon state accordingly
/obj/machinery/washing_machine/proc/update_washing_state()
if(bloody_mess)
washing_state = washing ? WM_STATE_RUNNING_BLOODY : WM_STATE_BLOODY
update_appearance(UPDATE_ICON_STATE)
return
if(washing)
washing_state = WM_STATE_RUNNING
update_appearance(UPDATE_ICON_STATE)
return
washing_state = current_tub_capacity ? WM_STATE_FULL : WM_STATE_EMPTY
update_appearance(UPDATE_ICON_STATE)
/obj/machinery/washing_machine/examine(mob/user)
. = ..()
. += SPAN_NOTICE("Its door is currently [door_open ? "open" : "closed"].")
if(washing)
. += SPAN_NOTICE("It is currently in its wash cycle.")
else
. += SPAN_NOTICE("You can <b>Alt-Click</b> [src] to start its washing cycle.")
if(bloody_mess)
. += SPAN_WARNING("The inside is covered in blood and gibs, you will need to clean it out with soap first.")
else
var/total_contents = LAZYLEN(inserted_items) + LAZYLEN(inserted_mobs)
. += SPAN_NOTICE("It has [total_contents] item\s inside.")
/obj/machinery/washing_machine/AltClick(mob/user)
if(user.stat != CONSCIOUS || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED) || !Adjacent(user))
return
if(stat & (NOPOWER|BROKEN))
return
if(!washing && !door_open)
start_wash_cycle()
/obj/machinery/washing_machine/attack_hand(mob/user)
if(washing && !door_open)
to_chat(user, SPAN_WARNING("[src]'s door is shut, you will need to wait until the end of the cycle."))
return
toggle_door()
/obj/machinery/washing_machine/item_interaction(mob/living/user, obj/item/used, list/modifiers)
if(user.a_intent == INTENT_HARM)
return ..()
if(default_unfasten_wrench(user, used))
return ITEM_INTERACT_COMPLETE
if(default_deconstruction_screwdriver(user, icon_state, icon_state, used))
update_appearance(UPDATE_ICON_STATE)
return ITEM_INTERACT_COMPLETE
if(default_deconstruction_crowbar(user, used, FALSE))
return ITEM_INTERACT_COMPLETE
if(istype(used, /obj/item/soap))
return ..() // need to be able to clean washing machine without putting stuff into the washing machine :D
if(istype(used, /obj/item/grab))
var/obj/item/grab/G = used
if(isliving(G.assailant))
if(attempt_insert(user, G.affecting))
qdel(G)
return ITEM_INTERACT_COMPLETE
if(istype(used))
if(attempt_insert(user, used))
return ITEM_INTERACT_COMPLETE
return ..()
/*
* # attempt_insert
*
* First part in the insert chain for putting items into the laundry machine, does some sanity checks on both the user and atom
* moveable that's about to be shoved inside. Makes sure the user can actually perform this action and then calls the next proper
* proc in the insert chain depending if they're a mob or obj/item
*/
/obj/machinery/washing_machine/proc/attempt_insert(mob/user, atom/movable/atom_to_insert)
if(!anchored || (stat & BROKEN) || (stat & NOPOWER))
return FALSE
if(!door_open)
to_chat(user, SPAN_WARNING("[src]'s door is closed!"))
return FALSE // simply not possible (or is it?)
if(!istype(atom_to_insert) || !istype(user))
return FALSE
if(user.stat != CONSCIOUS || !Adjacent(user) || !atom_to_insert.Adjacent(user))
return FALSE
if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
to_chat(user, SPAN_NOTICE("You try to insert [atom_to_insert] into [src] but your hands are blocked!"))
return FALSE
if(isliving(atom_to_insert)) // not ismob() b/c we only want living creatures, ghosts could just float out of the washing machine whenever they wanted
var/mob/living/sacrifice = atom_to_insert
if(sacrifice.mob_size >= MOB_SIZE_HUMAN) // prevents some most mobs from being washed even if it would be really really funny
to_chat(user, SPAN_NOTICE("You try to insert [sacrifice] into [src] but [sacrifice.p_theyre()] too large to fit inside!"))
return FALSE
if(sacrifice.buckled)
to_chat(user, SPAN_NOTICE("You try to insert [sacrifice] into [src] but [sacrifice.p_theyre()] still buckled to something!"))
return FALSE
return insert_mob_into_tub(user, sacrifice)
if(isitem(atom_to_insert)) // Restricting this to only item types prevents a lot of issues, you really only wanna shove items in here anyway...
var/obj/item/item_to_insert = atom_to_insert
if(item_to_insert.anchored)
return FALSE
if(item_to_insert.w_class >= WEIGHT_CLASS_HUGE)
to_chat(user, SPAN_NOTICE("You try to insert [item_to_insert] into [src] but [item_to_insert.p_them()] is too large to fit inside!"))
return FALSE
return insert_item_into_tub(user, item_to_insert)
/// Handles inserting mobs into the washing machines, checks machines capacity, does a do_after, and then applys appropriate signals and updates machines state
/obj/machinery/washing_machine/proc/insert_mob_into_tub(mob/user, mob/living/mob_to_insert)
var/mob_content_size = mob_to_insert.mob_size
if(max_tub_capacity < (mob_content_size * 2) + current_tub_capacity)
to_chat(user, SPAN_WARNING("You try to insert [mob_to_insert] into [src] but it is too full for [mob_to_insert.p_them()]!"))
return FALSE
mob_to_insert.visible_message(SPAN_WARNING("[user] starts shoving [mob_to_insert] into [src]!"), SPAN_USERDANGER("[user] starts shoving you into [src]!"))
if(!do_after_once(user, (3 SECONDS * mob_content_size), target = src, attempt_cancel_message = "You stop inserting [mob_to_insert] into [src]."))
return FALSE
mob_to_insert.forceMove(src)
RegisterSignal(mob_to_insert, COMSIG_PARENT_QDELETING, PROC_REF(check_tub_contents))
RegisterSignal(mob_to_insert, COMSIG_MOVABLE_MOVED, PROC_REF(check_tub_contents))
LAZYADD(inserted_mobs, mob_to_insert)
calculate_tub_capacity()
update_washing_state()
add_attack_logs(user, mob_to_insert, "Shoved into washing machine.")
to_chat(mob_to_insert, SPAN_USERDANGER("[user] shoves you into [src]. Oh shit!"))
mob_to_insert.visible_message(SPAN_DANGER("[user] shoves [mob_to_insert] into [src]."), SPAN_DANGER("You shove [mob_to_insert] into [src]."))
return TRUE
/// Handles inserting obj/items into the washing machines, checks machines capacity, does a do_after, and then applys appropriate signals and updates machines state
/obj/machinery/washing_machine/proc/insert_item_into_tub(mob/user, obj/item/item_to_insert)
var/item_content_size = item_to_insert.w_class
if(max_tub_capacity < (item_content_size + current_tub_capacity))
to_chat(user, SPAN_WARNING("You try to insert [item_to_insert] into [src] but [src] is too full for [item_to_insert.p_them()]!"))
return FALSE
if(!do_after_once(user, (1 SECONDS * (item_to_insert.w_class / 3)), target = src, attempt_cancel_message = "You stop inserting [item_to_insert] into [src]."))
return FALSE
if(!user.drop_item())
to_chat(user, SPAN_WARNING("[item_to_insert] is stuck to your hand!"))
return FALSE
item_to_insert.forceMove(src)
RegisterSignal(item_to_insert, COMSIG_PARENT_QDELETING, PROC_REF(check_tub_contents))
RegisterSignal(item_to_insert, COMSIG_MOVABLE_MOVED, PROC_REF(check_tub_contents))
LAZYADD(inserted_items, item_to_insert)
calculate_tub_capacity()
update_washing_state()
to_chat(user, SPAN_NOTICE("You insert [item_to_insert] into [src]."))
return TRUE
/*
* # check_tub_contents()
*
* Managing tons of contents inside the object is messy business, you never know when something is deleted, teleported, or moved into your obj
* without your machine knowing, this clears bad references for GC/tracking purposes and adds references when something is added randomly to
* our contents. This ensures we don't nullspace shit (such as a mob dropping a wrench or something) and spit it all back out afterwards even
* if we didn't add it our selves!
*
* This proc is called via COMSIG_PARENT_QDELETING for objects we put inside through the insert proc
*/
/obj/machinery/washing_machine/proc/check_tub_contents()
SIGNAL_HANDLER
if(LAZYLEN(inserted_items))
for(var/obj/item/I in inserted_items)
if(QDELETED(I) || I.loc != src)
if(!QDELETED(I))
UnregisterSignal(I, COMSIG_PARENT_QDELETING)
UnregisterSignal(I, COMSIG_MOVABLE_MOVED)
LAZYREMOVE(inserted_items, I)
if(LAZYLEN(inserted_mobs))
for(var/mob/living/L in inserted_mobs)
if(QDELETED(L) || L.loc != src)
if(!QDELETED(L))
UnregisterSignal(L, COMSIG_PARENT_QDELETING)
UnregisterSignal(L, COMSIG_MOVABLE_MOVED)
LAZYREMOVE(inserted_mobs, L)
for(var/obj/item/I in contents)
if(!LAZYIN(inserted_items, I))
LAZYADD(inserted_items, I)
RegisterSignal(I, COMSIG_PARENT_QDELETING, PROC_REF(check_tub_contents))
for(var/mob/living/L in contents)
if(!LAZYIN(inserted_mobs, L))
LAZYADD(inserted_mobs, L)
RegisterSignal(L, COMSIG_PARENT_QDELETING, PROC_REF(check_tub_contents))
calculate_tub_capacity()
update_washing_state()
/// Moves all Items/Mobs out of the laundry machine onto the Washing Machine's Loc, unregisters signals, then nulls the tracking lists
/obj/machinery/washing_machine/proc/eject_tub_contents()
if(bloody_mess)
playsound(loc, 'sound/effects/splat.ogg', 50, TRUE)
check_tub_contents() // one last time, we wanna make sure nothing got teleported out
if(LAZYLEN(inserted_items))
for(var/obj/item/I in inserted_items)
if(bloody_mess)
I.add_blood() // what a mess!
I.forceMove(loc)
UnregisterSignal(I, COMSIG_PARENT_QDELETING)
UnregisterSignal(I, COMSIG_MOVABLE_MOVED)
LAZYNULL(inserted_items)
if(LAZYLEN(inserted_mobs))
for(var/mob/living/L in inserted_mobs)
L.forceMove(loc)
UnregisterSignal(L, COMSIG_PARENT_QDELETING)
UnregisterSignal(L, COMSIG_MOVABLE_MOVED)
LAZYNULL(inserted_mobs)
calculate_tub_capacity()
update_washing_state()
/// Calculates the total capacity of the contents of the tub,
/obj/machinery/washing_machine/proc/calculate_tub_capacity()
var/new_capacity = 0
if(LAZYLEN(inserted_items))
for(var/obj/item/I in inserted_items)
new_capacity += I.w_class
if(LAZYLEN(inserted_mobs))
for(var/mob/living/L in inserted_mobs)
new_capacity += L.mob_size * 2
current_tub_capacity = new_capacity
/obj/machinery/washing_machine/proc/toggle_door()
door_open = !door_open
if(door_open)
eject_tub_contents()
update_washing_state()
/// Attempts to locate stamps or crayons for dyeing purposes
/obj/machinery/washing_machine/proc/locate_color_source()
var/list/color_sources = list()
for(var/obj/item/toy/crayon/C in contents)
color_sources += C
for(var/obj/item/stamp/S in contents)
color_sources += S
for(var/obj/item/toy/shell/L in contents)
color_sources += L
if(!length(color_sources))
return
color_source = pick(color_sources)
if(istype(color_source, /obj/item/toy/crayon))
var/obj/item/toy/crayon/C = color_source
wash_color = C.dye_color
if(istype(color_source, /obj/item/stamp))
var/obj/item/stamp/S = color_source
wash_color = S.dye_color
if(istype(color_source, /obj/item/toy/shell))
var/obj/item/toy/shell/L = color_source
wash_color = L.dye_color
/obj/machinery/washing_machine/proc/start_wash_cycle()
if(stat & (NOPOWER|BROKEN))
return
locate_color_source()
washing = TRUE
playsound(loc, 'sound/machines/click.ogg', 50, TRUE)
atom_say("Beginning Wash Cycle!")
sleep(1 SECONDS)
update_washing_state()
use_power(100)
playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE)
addtimer(CALLBACK(src, PROC_REF(wash_cycle)), 15 SECONDS)
/obj/machinery/washing_machine/proc/wash_cycle()
for(var/obj/item/I in inserted_items)
I.machine_wash(src)
for(var/mob/living/L in inserted_mobs)
L.machine_wash(src)
update_washing_state()
use_power(50 * current_tub_capacity)
playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE)
addtimer(CALLBACK(src, PROC_REF(end_wash_cycle)), 15 SECONDS)
/obj/machinery/washing_machine/proc/end_wash_cycle()
if(bloody_mess)
for(var/obj/item/I in inserted_items)
I.add_blood(I.blood_DNA)
QDEL_NULL(color_source)
playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE)
washing = FALSE
atom_say("Wash Cycle Complete!")
sleep(1 SECONDS)
playsound(loc, 'sound/machines/ding.ogg', 50, TRUE)
update_washing_state()
/obj/machinery/washing_machine/container_resist(mob/living/L)
. = ..()
if(washing)
return
if(!door_open)
toggle_door()
return
else
L.forceMove(loc)
/obj/machinery/washing_machine/deconstruct(disassembled = TRUE)
eject_tub_contents()
new /obj/item/stack/sheet/metal(drop_location(), 5)
new /obj/item/circuitboard/washing_machine(drop_location(), 2)
qdel(src)
/obj/machinery/washing_machine/AllowDrop()
return TRUE // so gibs and dropped items end up inside of the washing machine instead when dropped
/obj/machinery/washing_machine/cleaning_act(mob/user, atom/cleaner, cleanspeed, text_verb, text_description, text_targetname)
if(washing)
to_chat(user, SPAN_WARNING("[src]'s door is shut, you will need to wait until the end of the cycle to clean it out."))
return
. = ..()
if(. && !washing)
bloody_mess = FALSE
update_washing_state()
/obj/item/machine_wash(obj/machinery/washing_machine/washer)
. = ..()
if(washer.color_source)
dye_item(washer.wash_color)
/obj/item/holder/machine_wash(obj/machinery/washing_machine/washer)
. = ..()
for(var/mob/living/held_mob in contents)
held_mob.machine_wash(washer)
/obj/item/stack/sheet/hairlesshide/machine_wash(obj/machinery/washing_machine/washer)
. = ..()
new /obj/item/stack/sheet/wetleather(loc, amount)
qdel(src)
/mob/living/simple_animal/machine_wash(obj/machinery/washing_machine/washer)
if(mob_size >= MOB_SIZE_HUMAN)
return
washer.bloody_mess = TRUE
add_attack_logs(washer, src, "gibbed by washing machine")
gib() //warcrime time!
#undef DEFAULT_TUB_CAPACITY
#undef WM_STATE_EMPTY
#undef WM_STATE_FULL
#undef WM_STATE_RUNNING
#undef WM_STATE_BLOODY
#undef WM_STATE_RUNNING_BLOODY
//
// , ,, ,
// | || | ,/ _____ \.
// \_||_/ ||_/ \_||
// || \_| . . |_/
// || | L |
// ,|| |`==='|
// |>| ___`> -<'___
// |>|\ / |
// \>| \ / , . . |
// || \/ /| . | . | |
// ||\ ` / | ___|___ | | (
// (( || `--' | _______ | | )) (
// ( )\|| ( )\ | - --- - | -| ( ( \ ))
// (\/ || ))/ ( | -- - -- | | )) ) \((
// ( ()||((( ())| | |( (( () )hjm
//
// "Allow this Devil to stand testament to the
// the shitcode that once haunted this file"
//