#define ADDICTION_SPEEDUP_TIME 1500 // 2.5 minutes ///////////// // SLEEPER // //////////// /obj/machinery/sleeper name = "sleeper" desc = "Injects chemicals into the bloodstream of the occupant, can remove chemicals from the bloodstream of the occupant through dialysis." icon = 'icons/obj/cryogenic2.dmi' icon_state = "sleeper-open" var/base_icon = "sleeper" density = TRUE anchored = TRUE dir = WEST var/mob/living/carbon/human/occupant = null /// A separate effect for the occupant, as you can't animate overlays reliably and constantly removing and adding overlays is spamming the subsystem. var/obj/effect/occupant_overlay = null var/possible_chems = list("ephedrine", "salglu_solution", "salbutamol", "charcoal") var/amounts = list(5, 10) /// Beaker loaded into the sleeper. Used for dialysis. var/obj/item/reagent_containers/glass/beaker = null /// Whether the machine is currently performing dialysis. var/filtering = FALSE var/max_chem var/initial_bin_rating = 1 var/controls_inside = FALSE var/auto_eject_dead = FALSE idle_power_consumption = 1250 active_power_consumption = 2500 light_color = LIGHT_COLOR_CYAN light_power = 0.5 /obj/machinery/sleeper/examine(mob/user) . = ..() if(occupant) if(occupant.stat == DEAD) . += SPAN_WARNING("You see [occupant.name] inside. [occupant.p_they(TRUE)] [occupant.p_are()] dead!") else . += SPAN_NOTICE("You see [occupant.name] inside.") . += SPAN_NOTICE("You can Alt-Click to eject the current occupant. Click-drag someone to the sleeper to place them in it after a short delay.") /obj/machinery/sleeper/power_change() ..() //we don't check parent return here because we also care about BROKEN if(!(stat & (BROKEN|NOPOWER))) set_light(2) else set_light(0) /obj/machinery/sleeper/update_icon_state() if(occupant) icon_state = base_icon else icon_state = "[base_icon]-open" /obj/machinery/sleeper/Initialize(mapload) . = ..() component_parts = list() component_parts += new /obj/item/circuitboard/sleeper(null) // Customizable bin rating, used by the labor camp to stop people filling themselves with chemicals and escaping. var/obj/item/stock_parts/matter_bin/B = new(null) B.rating = initial_bin_rating component_parts += B component_parts += new /obj/item/stock_parts/manipulator(null) component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new /obj/item/stack/cable_coil(null, 1) RefreshParts() /obj/machinery/sleeper/upgraded/Initialize(mapload) . = ..() component_parts = list() component_parts += new /obj/item/circuitboard/sleeper(null) component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null) component_parts += new /obj/item/stock_parts/manipulator/femto(null) component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new /obj/item/stack/cable_coil(null, 1) RefreshParts() /obj/machinery/sleeper/RefreshParts() var/E for(var/obj/item/stock_parts/matter_bin/B in component_parts) E += B.rating max_chem = E * 20 /obj/machinery/sleeper/Destroy() for(var/mob/M in contents) M.forceMove(get_turf(src)) return ..() /obj/machinery/sleeper/relaymove(mob/user as mob) if(user.incapacitated()) return 0 //maybe they should be able to get out with cuffs, but whatever go_out() /obj/machinery/sleeper/process() for(var/mob/M as mob in src) // makes sure that simple mobs don't get stuck inside a sleeper when they resist out of occupant's grasp if(M == occupant) continue else M.forceMove(loc) if(occupant) if(auto_eject_dead && occupant.stat == DEAD) playsound(loc, 'sound/machines/buzz-sigh.ogg', 40) go_out() return if(filtering > 0 && beaker) // To prevent runtimes from drawing blood from runtime, and to prevent getting IPC blood. if(!istype(occupant) || !occupant.dna || (NO_BLOOD in occupant.dna.species.species_traits)) filtering = 0 return if(beaker.reagents.total_volume < beaker.reagents.maximum_volume) occupant.transfer_blood_to(beaker, 1) for(var/datum/reagent/reagent in occupant.reagents.reagent_list) occupant.transfer_blood_to(beaker, 1) if(reagent.id in GLOB.blocked_chems) occupant.reagents.remove_reagent(reagent.id, 3) beaker.reagents.add_reagent("saturated_charcoal", 3) continue occupant.reagents.trans_id_to(beaker, reagent.id, 3) for(var/A in occupant.reagents.addiction_list) var/datum/reagent/R = A var/addiction_removal_chance = 5 if(world.timeofday > (R.last_addiction_dose + ADDICTION_SPEEDUP_TIME)) // 2.5 minutes addiction_removal_chance = 10 if(prob(addiction_removal_chance)) atom_say("Patient's addiction was cured.") playsound(get_turf(src), 'sound/machines/ping.ogg', 50, 0) to_chat(occupant, SPAN_BOLDNOTICE("You no longer feel reliant on [R.name]!")) occupant.reagents.addiction_list.Remove(R) qdel(R) updateDialog() return /obj/machinery/sleeper/attack_ai(mob/user) return attack_hand(user) /obj/machinery/sleeper/attack_ghost(mob/user) ui_interact(user) /obj/machinery/sleeper/attack_hand(mob/user) if(stat & (NOPOWER|BROKEN)) return if(panel_open) to_chat(user, SPAN_NOTICE("Close the maintenance panel first.")) return ui_interact(user) /obj/machinery/sleeper/ui_state(mob/user) return GLOB.default_state /obj/machinery/sleeper/ui_interact(mob/user, datum/tgui/ui = null) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "Sleeper", "Sleeper") ui.open() /obj/machinery/sleeper/ui_data(mob/user) var/list/data = list() data["amounts"] = amounts data["hasOccupant"] = occupant ? 1 : 0 var/occupantData[0] if(occupant) occupantData["name"] = occupant.name occupantData["stat"] = occupant.stat occupantData["health"] = occupant.health occupantData["maxHealth"] = occupant.maxHealth occupantData["minHealth"] = HEALTH_THRESHOLD_DEAD occupantData["bruteLoss"] = occupant.getBruteLoss() occupantData["oxyLoss"] = occupant.getOxyLoss() occupantData["toxLoss"] = occupant.getToxLoss() occupantData["fireLoss"] = occupant.getFireLoss() occupantData["paralysis"] = occupant.AmountParalyzed() occupantData["hasBlood"] = 0 occupantData["bodyTemperature"] = occupant.bodytemperature occupantData["maxTemp"] = 1000 // Because we can put simple_animals in here, we need to do something tricky to get things working nice occupantData["temperatureSuitability"] = 0 // 0 is the baseline if(ishuman(occupant) && occupant.dna.species) // I wanna do something where the bar gets bluer as the temperature gets lower // For now, I'll just use the standard format for the temperature status var/datum/species/sp = occupant.dna.species if(occupant.bodytemperature < sp.cold_level_3) occupantData["temperatureSuitability"] = -3 else if(occupant.bodytemperature < sp.cold_level_2) occupantData["temperatureSuitability"] = -2 else if(occupant.bodytemperature < sp.cold_level_1) occupantData["temperatureSuitability"] = -1 else if(occupant.bodytemperature > sp.heat_level_3) occupantData["temperatureSuitability"] = 3 else if(occupant.bodytemperature > sp.heat_level_2) occupantData["temperatureSuitability"] = 2 else if(occupant.bodytemperature > sp.heat_level_1) occupantData["temperatureSuitability"] = 1 else if(isanimal(occupant)) var/mob/living/simple_animal/silly = occupant if(silly.bodytemperature < silly.minbodytemp) occupantData["temperatureSuitability"] = -3 else if(silly.bodytemperature > silly.maxbodytemp) occupantData["temperatureSuitability"] = 3 else if(isbasicmob(occupant)) var/mob/living/basic/basicmob = occupant if(basicmob.bodytemperature < basicmob.minimum_survivable_temperature) occupantData["temperatureSuitability"] = -3 else if(basicmob.bodytemperature > basicmob.maximum_survivable_temperature) occupantData["temperatureSuitability"] = 3 // Blast you, imperial measurement system occupantData["btCelsius"] = occupant.bodytemperature - T0C occupantData["btFaren"] = ((occupant.bodytemperature - T0C) * (9.0/5.0))+ 32 // I'm not sure WHY you'd want to put a simple_animal in a sleeper, but precedent is precedent // Runtime is aptly named, isn't she? if(ishuman(occupant) && !(NO_BLOOD in occupant.dna.species.species_traits)) occupantData["pulse"] = occupant.get_pulse() occupantData["hasBlood"] = 1 occupantData["bloodLevel"] = round(occupant.blood_volume) occupantData["bloodMax"] = occupant.max_blood occupantData["bloodPercent"] = round(100*(occupant.blood_volume/occupant.max_blood), 0.01) data["occupant"] = occupantData data["maxchem"] = max_chem data["dialysis"] = filtering data["auto_eject_dead"] = auto_eject_dead if(beaker) data["isBeakerLoaded"] = 1 if(beaker.reagents) data["beakerMaxSpace"] = beaker.reagents.maximum_volume data["beakerFreeSpace"] = round(beaker.reagents.maximum_volume - beaker.reagents.total_volume) else data["beakerMaxSpace"] = 0 data["beakerFreeSpace"] = 0 else data["isBeakerLoaded"] = FALSE var/chemicals[0] for(var/re in possible_chems) var/datum/reagent/temp = GLOB.chemical_reagents_list[re] if(temp) var/reagent_amount = 0 var/pretty_amount var/injectable = occupant ? 1 : 0 var/overdosing = 0 var/caution = 0 // To make things clear that you're coming close to an overdose if(occupant && occupant.reagents) reagent_amount = occupant.reagents.get_reagent_amount(temp.id) // If they're mashing the highest concentration, they get one warning if(temp.overdose_threshold && reagent_amount + 10 > temp.overdose_threshold) caution = 1 if(temp.id in occupant.reagents.overdose_list()) overdosing = 1 pretty_amount = round(reagent_amount, 0.05) chemicals.Add(list(list("title" = temp.name, "id" = temp.id, "commands" = list("chemical" = temp.id), "occ_amount" = reagent_amount, "pretty_amount" = pretty_amount, "injectable" = injectable, "overdosing" = overdosing, "od_warning" = caution))) data["chemicals"] = chemicals return data /obj/machinery/sleeper/ui_act(action, params, datum/tgui/ui) if(..()) return if(!controls_inside && usr == occupant) return if(panel_open) to_chat(usr, SPAN_NOTICE("Close the maintenance panel first.")) return if(stat & (NOPOWER|BROKEN)) return . = TRUE switch(action) if("chemical") if(!occupant) return if(occupant.stat == DEAD) to_chat(usr, SPAN_DANGER("This person has no life to preserve anymore. Take [occupant.p_them()] to a department capable of reanimating them.")) return var/chemical = params["chemid"] var/amount = text2num(params["amount"]) if(!length(chemical) || amount <= 0) return inject_chemical(usr, chemical, amount) if("removebeaker") remove_beaker(ui.user) if("togglefilter") toggle_filter() if("ejectify") eject() if("auto_eject_dead_on") auto_eject_dead = TRUE if("auto_eject_dead_off") auto_eject_dead = FALSE else return FALSE add_fingerprint(usr) /obj/machinery/sleeper/item_interaction(mob/living/user, obj/item/used, list/modifiers) if(istype(used, /obj/item/reagent_containers/glass) && user.a_intent != INTENT_HARM) if(!beaker) if(!user.drop_item()) to_chat(user, SPAN_WARNING("[used] is stuck to you!")) return ITEM_INTERACT_COMPLETE beaker = used used.forceMove(src) user.visible_message("[user] adds \a [used] to [src]!", "You add \a [used] to [src]!") SStgui.update_uis(src) return ITEM_INTERACT_COMPLETE else to_chat(user, SPAN_WARNING("The sleeper has a beaker already.")) return ITEM_INTERACT_COMPLETE if(istype(used, /obj/item/grab)) var/obj/item/grab/G = used if(panel_open) to_chat(user, SPAN_BOLDNOTICE("Close the maintenance panel first.")) return ITEM_INTERACT_COMPLETE if(!ismob(G.affecting)) return ITEM_INTERACT_COMPLETE if(occupant) to_chat(user, SPAN_BOLDNOTICE("The sleeper is already occupied!")) return ITEM_INTERACT_COMPLETE if(G.affecting.has_buckled_mobs()) //mob attached to us to_chat(user, SPAN_WARNING("[G.affecting] will not fit into [src] because [G.affecting.p_they()] [G.affecting.p_have()] a slime latched onto [G.affecting.p_their()] head.")) return ITEM_INTERACT_COMPLETE visible_message("[user] starts putting [G.affecting.name] into the sleeper.") if(do_after(user, 20, target = G.affecting)) if(occupant) to_chat(user, SPAN_BOLDNOTICE("The sleeper is already occupied!")) return ITEM_INTERACT_COMPLETE if(!G || !G.affecting) return ITEM_INTERACT_COMPLETE var/mob/M = G.affecting M.forceMove(src) occupant = M update_icon(UPDATE_ICON_STATE) update_icon(UPDATE_OVERLAYS) to_chat(M, SPAN_BOLDNOTICE("You feel cool air surround you. You go numb as your senses turn inward.")) add_fingerprint(user) qdel(G) SStgui.update_uis(src) return ITEM_INTERACT_COMPLETE return ..() /obj/machinery/sleeper/crowbar_act(mob/user, obj/item/I) if(default_deconstruction_crowbar(user, I)) return TRUE /obj/machinery/sleeper/screwdriver_act(mob/user, obj/item/I) if(occupant) to_chat(user, SPAN_NOTICE("The maintenance panel is locked.")) return TRUE if(default_deconstruction_screwdriver(user, "[base_icon]-o", "[base_icon]-open", I)) return TRUE /obj/machinery/sleeper/wrench_act(mob/user, obj/item/I) . = TRUE if(!I.use_tool(src, user, 0, volume = I.tool_volume)) return if(occupant) to_chat(user, SPAN_NOTICE("The scanner is occupied.")) return if(panel_open) to_chat(user, SPAN_NOTICE("Close the maintenance panel first.")) return setDir(turn(dir, -90)) /obj/machinery/sleeper/ex_act(severity) if(filtering) toggle_filter() if(occupant) occupant.ex_act(severity) ..() /obj/machinery/sleeper/handle_atom_del(atom/A) ..() if(A == occupant) occupant = null update_icon(UPDATE_ICON_STATE) SStgui.update_uis(src) if(A == beaker) beaker = null SStgui.update_uis(src) /obj/machinery/sleeper/emp_act(severity) if(filtering) toggle_filter() if(stat & (BROKEN|NOPOWER)) ..(severity) return if(occupant) go_out() ..(severity) /obj/machinery/sleeper/narsie_act() go_out() new /obj/effect/gibspawner/generic(get_turf(loc)) //I REPLACE YOUR TECHNOLOGY WITH FLESH! qdel(src) /obj/machinery/sleeper/proc/toggle_filter() if(filtering || !beaker) filtering = FALSE else filtering = TRUE /obj/machinery/sleeper/proc/go_out() if(filtering) toggle_filter() if(!occupant) return occupant.forceMove(loc) occupant = null playsound(src, 'sound/machines/podopen.ogg', 5) update_icon(UPDATE_ICON_STATE|UPDATE_OVERLAYS) // eject trash the occupant dropped for(var/atom/movable/A in contents - component_parts - list(beaker)) A.forceMove(loc) SStgui.update_uis(src) /obj/machinery/sleeper/force_eject_occupant(mob/target) go_out() /obj/machinery/sleeper/proc/inject_chemical(mob/living/user, chemical, amount) if(!(chemical in possible_chems)) to_chat(user, SPAN_NOTICE("The sleeper does not offer that chemical!")) return if(!(amount in amounts)) return if(occupant) if(occupant.reagents) if(occupant.reagents.get_reagent_amount(chemical) + amount <= max_chem) occupant.reagents.add_reagent(chemical, amount) else to_chat(user, "You can not inject any more of this chemical.") else to_chat(user, "The patient rejects the chemicals!") else to_chat(user, "There's no occupant in the sleeper!") /obj/machinery/sleeper/AltClick(mob/user) if(issilicon(user)) eject() return if(!Adjacent(user) || !ishuman(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) return eject() /obj/machinery/sleeper/proc/eject(mob/user) go_out() add_fingerprint(usr) /obj/machinery/sleeper/proc/remove_beaker(mob/user) if(user.stat || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED) || !Adjacent(user)) return if(beaker) filtering = FALSE user.put_in_hands(beaker) beaker = null SStgui.update_uis(src) add_fingerprint(user) /obj/machinery/sleeper/MouseDrop_T(atom/movable/O, mob/user) if(!permitted_check(O, user)) return var/mob/living/L = O if(L == user) visible_message("[user] starts climbing into the sleeper.") else visible_message("[user] starts putting [L.name] into the sleeper.") INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/machinery/sleeper, put_in), O, user, L) return TRUE /obj/machinery/sleeper/proc/put_in(atom/movable/O, mob/user, mob/living/L) // need this proc to use INVOKE_ASYNC in other proc. You're not recommended to use that one if(do_after(user, 20, target = L)) if(!permitted_check(O, user)) return if(!L) return L.forceMove(src) occupant = L playsound(src, 'sound/machines/podclose.ogg', 5) update_icon(UPDATE_ICON_STATE|UPDATE_OVERLAYS) to_chat(L, SPAN_BOLDNOTICE("You feel cool air surround you. You go numb as your senses turn inward.")) add_fingerprint(user) if(user.pulling == L) user.stop_pulling() QDEL_LIST_CONTENTS(L.grabbed_by) SStgui.update_uis(src) /obj/machinery/sleeper/proc/permitted_check(atom/movable/O, mob/user) if(O.loc == user) //no you can't pull things out of your ass return if(user.incapacitated()) //are you cuffed, dying, lying, stunned or other return if(get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src)) // is the mob anchored, too far away from you, or are you too far away from the source return if(!ismob(O)) //humans only return if(isanimal_or_basicmob(O) || issilicon(O)) //animals and robots dont fit return if(!ishuman(user) && !isrobot(user)) //No ghosts or mice putting people into the sleeper return if(!user.loc) // just in case someone manages to get a closet into the blue light dimension, as unlikely as that seems return if(!isturf(user.loc) || !isturf(O.loc)) // are you in a container/closet/pod/etc? return if(panel_open) to_chat(user, SPAN_BOLDNOTICE("Close the maintenance panel first.")) return if(occupant) to_chat(user, SPAN_BOLDNOTICE("The sleeper is already occupied!")) return var/mob/living/L = O if(!istype(L) || L.buckled) return if(L.abiotic()) to_chat(user, SPAN_BOLDNOTICE("Subject may not hold anything in their hands.")) return if(L.has_buckled_mobs()) //mob attached to us to_chat(user, SPAN_WARNING("[L] will not fit into [src] because [L.p_they()] [L.p_have()] a slime latched onto [L.p_their()] head.")) return return TRUE /obj/machinery/sleeper/AllowDrop() return FALSE /obj/machinery/sleeper/update_overlays() . = ..() if(occupant_overlay) QDEL_NULL(occupant_overlay) if(!occupant) return occupant_overlay = new(get_turf(src)) occupant_overlay.icon = occupant.icon occupant_overlay.icon_state = occupant.icon_state occupant_overlay.overlays = occupant.overlays occupant_overlay.dir = dir occupant_overlay.layer = layer + 0.01 var/matrix/MA = matrix(transform) if(dir == NORTH) MA.TurnTo(0, 180) occupant_overlay.dir = SOUTH // trust me if(dir == EAST) MA.TurnTo(0, 270) occupant_overlay.pixel_y = -8 if(dir == WEST) MA.TurnTo(0 , 90) occupant_overlay.pixel_y = -8 MA.Scale(0.66, 0.66) occupant_overlay.transform = MA var/mutable_appearance/rim = mutable_appearance(icon = icon, icon_state = "[base_icon]_first_overlay", layer = occupant_overlay.layer + 0.01) var/mutable_appearance/lid = mutable_appearance(icon = icon, icon_state = "[base_icon]-lid-nodetail", layer = rim.layer + 0.01, alpha = 140) . += rim . += lid /obj/machinery/sleeper/syndie icon_state = "sleeper_s-open" base_icon = "sleeper_s" possible_chems = list("epinephrine", "ether", "salbutamol", "styptic_powder", "silver_sulfadiazine") controls_inside = TRUE light_color = LIGHT_COLOR_DARKRED /obj/machinery/sleeper/syndie/Initialize(mapload) . = ..() component_parts = list() component_parts += new /obj/item/circuitboard/sleeper/syndicate(null) var/obj/item/stock_parts/matter_bin/B = new(null) B.rating = initial_bin_rating component_parts += B component_parts += new /obj/item/stock_parts/manipulator(null) component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new /obj/item/stack/cable_coil(null, 1) RefreshParts() /obj/machinery/sleeper/clockwork name = "soothing sleeper" desc = "A large cryogenics unit built from brass. Its surface is pleasantly cool the touch." icon_state = "sleeper_c-open" base_icon = "sleeper_c" possible_chems = list("epinephrine", "salbutamol", "styptic_powder", "silver_sulfadiazine", "oculine", "mannitol") light_color = LIGHT_COLOR_DARKRED /obj/machinery/sleeper/clockwork/crowbar_act(mob/user, obj/item/I) to_chat(user, SPAN_WARNING("You pry on the internal mechanisms of [src] with all your might, but they refuse to budge!")) return FALSE #undef ADDICTION_SPEEDUP_TIME