// Regular airbubble - folded /obj/item/airbubble name = "air bubble" desc = "Special air bubble designed to protect people inside of it from decompressed environments. Has an integrated cooling unit to preserve a stable temperature inside. Requires a power cell to operate." icon = 'icons/obj/airbubble.dmi' icon_state = "airbubble_fact_folded" w_class = ITEMSIZE_NORMAL var/used = FALSE var/ripped = FALSE var/zipped = FALSE var/obj/item/tank/internal_tank var/syndie = FALSE /obj/item/airbubble/Destroy() qdel(internal_tank) return ..() // Deploy the bubble and transfer all properties of it /obj/item/airbubble/attack_self(mob/user) if (!isturf(user.loc)) to_chat(user, "You're fucking stupid, the air bubble can't deploy in an enclosed space.") return user.visible_message( "[user] begins deploying \the [src].", "You begin deplyoing \the [src]." ) if (!do_after(user, 0.45 SECONDS, act_target = src)) return user.visible_message( "[user] deployed \the [src]." , "You deploy \the [src]." ) var/obj/structure/closet/airbubble/R if(syndie) R = new /obj/structure/closet/airbubble/syndie(user.loc) else R = new /obj/structure/closet/airbubble(user.loc) if(!used) internal_tank = new /obj/item/tank/emergency_oxygen/engi(src) internal_tank.air_contents.adjust_gas(GAS_OXYGEN, (42*ONE_ATMOSPHERE)/(R_IDEAL_GAS_EQUATION*T20C)) R.internal_tank = internal_tank if(!isnull(internal_tank)) internal_tank.forceMove(R) internal_tank = null R.add_fingerprint(user) R.ripped = ripped R.zipped = zipped R.update_icon() R.desc = desc user.drop_from_inventory(src) qdel(src) // Deployed bubble /obj/structure/closet/airbubble name = "air bubble" desc = "Special air bubble designed to protect people inside of it from decompressed environments. Has an integrated cooling unit to preserve a stable temperature inside. Requires a power cell to operate." icon = 'icons/obj/airbubble.dmi' icon_state = "airbubble" open_sound = 'sound/items/zip.ogg' close_sound = 'sound/items/zip.ogg' var/item_path = /obj/item/airbubble var/zipped = FALSE density = 0 storage_capacity = 20 var/contains_body = FALSE var/used = TRUE // If we have deployed it once var/ripped = FALSE // If it has a hole it in, vent all the air outside var/breakout_time = 1 // How many minutes it takes to break out of it. var/use_internal_tank = TRUE var/datum/gas_mixture/inside_air var/internal_tank_valve = 45 // arbitrary for now var/obj/item/tank/internal_tank var/syndie = FALSE var/last_shake = 0 var/cooling = FALSE var/obj/item/cell/cell = null var/max_cooling = 12 //in degrees per second - probably don't need to mess with heat capacity here var/charge_consumption = 8.3 //charge per second at max_cooling var/thermostat = T20C slowdown = 0 // Examine to see tank pressure /obj/structure/closet/airbubble/examine(mob/user) ..() if(!isnull(internal_tank)) to_chat(user, "\The [src] has [internal_tank] attached, that displays [round(internal_tank.air_contents.return_pressure() ? internal_tank.air_contents.return_pressure() : 0)] KPa.") else to_chat(user, "\The [src] has no tank attached.") if (cell) to_chat(user, "\The [src] has [cell] attached, the charge meter reads [round(cell.percent())]%.") else to_chat(user, "[src] has no power cell installed.") /obj/structure/closet/airbubble/can_open() if(zipped) return 0 return 1 /obj/structure/closet/airbubble/can_close(mob/user as mob) if(zipped) return 0 var/turf/T = get_turf(src) for (var/obj/O in T) if (O.density && O != src) return 0 var/mob_num = 0 for(var/mob/living/M in T) mob_num += 1 if(mob_num > 1) to_chat(user, "[src] can only fit one person.") return 0 return 1 /obj/structure/closet/airbubble/dump_contents() for(var/obj/I in src) if (I != internal_tank && I != cell) I.forceMove(loc) for(var/mob/M in src) M.forceMove(loc) if(M.client) M.client.eye = M.client.mob M.client.perspective = MOB_PERSPECTIVE /obj/structure/closet/airbubble/Initialize() . = ..() add_inside() START_PROCESSING(SSfast_process, src) /obj/structure/closet/airbubble/Destroy() STOP_PROCESSING(SSfast_process, src) qdel(internal_tank) if(parts) new parts(loc) if (smooth) queue_smooth_neighbors(src) return ..() /obj/structure/closet/airbubble/toggle(mob/user as mob) if(!(opened ? close(user) : open(user))) to_chat(user, "It won't budge!") return update_icon() return 1 /obj/structure/closet/airbubble/open(mob/user as mob) if(opened) return 0 if(!can_open()) return 0 dump_contents() update_icon() opened = 1 playsound(loc, open_sound, 15, 1, -3) density = 0 return 1 /obj/structure/closet/airbubble/close(mob/user as mob) if(!opened) return 0 if(!can_close(user)) return 0 var/stored_units = 0 if(store_misc) stored_units += store_misc(stored_units) if(store_items) stored_units += store_items(stored_units) if(store_mobs) stored_units += store_mobs(stored_units) update_icon() opened = 0 playsound(loc, close_sound, 25, 0, -3) density = 1 add_inside() return 1 // Fold the bubble, transfering properties. /obj/structure/closet/airbubble/MouseDrop(over_object, src_location, over_location) if((!zipped || ripped )&& (over_object == usr && (in_range(src, usr) || usr.contents.Find(src)))) if(!ishuman(usr)) return if(opened) return 0 if(contents.len > 1) return 0 if(cell) to_chat(usr, "[src] can not be folded with [cell] attached to it.") return usr.visible_message( "[usr] begins folding up the [src.name].", "You begin folding up the [src.name]." ) if (!do_after(usr, 0.45 SECONDS, act_target = src)) return usr.visible_message( "[usr] folds up the [src.name]." , "You fold up the [src.name]." ) var/obj/item/airbubble/bag if(syndie) bag = new /obj/item/airbubble/syndie(get_turf(src)) else bag = new /obj/item/airbubble(get_turf(src)) bag.ripped = ripped bag.zipped = zipped bag.used = TRUE bag.internal_tank = internal_tank if(!isnull(internal_tank)) internal_tank.forceMove(bag) internal_tank = null bag.w_class = ITEMSIZE_LARGE bag.desc = "Special air bubble designed to protect people inside of it from decompressed environments. Has an integrated cooling unit to preserve a stable temperature inside. Requires a power cell to operate." if(syndie) bag.desc += " This does not seem like a regular color scheme" bag.desc += " It appears to be poorly hand folded." if(ripped) bag.icon_state = "[icon_state]_man_folded_ripped" bag.desc += " It has hole in it! Maybe you shouldn't use it!" else bag.icon_state = "[icon_state]_man_folded" qdel(src) return /obj/structure/closet/airbubble/req_breakout() if(opened) return 0 //Door's open... wait, why are you in it's contents then? if(zipped) return 1 // If it is zipped if(breakout) return -1 //Already breaking out. return 0 /obj/structure/closet/airbubble/proc/breakout_callback(var/mob/living/escapee) if (QDELETED(escapee)) return FALSE if ((world.time - last_shake) > 5 SECONDS) playsound(loc, "sound/items/[pick("rip1","rip2")].ogg", 100, 1) shake_animation() last_shake = world.time if (!req_breakout()) return FALSE return TRUE // If we are stuck, and need to get out /obj/structure/closet/airbubble/mob_breakout(var/mob/living/escapee) if (req_breakout() < 1) return escapee.next_move = world.time + 100 escapee.last_special = world.time + 100 to_chat(escapee, "You lean on the back of \the [src] and start punching internal wall with your legs. (this will take about [breakout_time] minutes)") visible_message("\The [src] begins to shake violently! Something is terring it from the inside!") var/time = 360 * breakout_time * 2 breakout = TRUE if (!do_after(escapee, time, act_target = src, extra_checks = CALLBACK(src, .proc/breakout_callback, escapee))) breakout = FALSE return breakout = FALSE to_chat(escapee, "You successfully break out! Tearing the bubble's walls!") // holy shit this is hilarious visible_message("\the [escapee] successfully broke out of \the [src]! Tearing the bubble's walls!") playsound(loc, "sound/items/[pick("rip1","rip2")].ogg", 100, 1) break_open() shake_animation() desc += " It has hole in it! Maybe you shouldn't use it!" // We are out finally, the bubble is ripped. So dump everything out from it. Especially air and user. /obj/structure/closet/airbubble/break_open() ripped = TRUE update_icon() dump_contents() var/datum/gas_mixture/t_air = get_turf_air() t_air.merge(inside_air) // When we shoot bubble, make it rip. /obj/structure/closet/airbubble/bullet_act(var/obj/item/projectile/Proj) ..() ripped = TRUE update_icon() // Change valve on internal tank /obj/structure/closet/airbubble/verb/set_internals() set src in oview(1) set category = "Object" set name = "Set Internals" if(!usr.canmove || usr.stat || usr.restrained()) return if(!ishuman(usr)) to_chat(usr, "This mob type can't use this verb.") return if(!isnull(internal_tank)) usr.visible_message( "[usr] is setting [src] internals.", "You are settting [src] internals." ) if (!do_after(usr, 2 SECONDS, act_target = src)) return usr.visible_message( "[usr] has set [src] internals." , "You set [src] internals." ) if(use_internal_tank) STOP_PROCESSING(SSfast_process, src) else START_PROCESSING(SSfast_process, src) use_internal_tank = !use_internal_tank update_icon() else to_chat(usr, "[src] has no internal tank.") // Remove tank from bubble /obj/structure/closet/airbubble/verb/take_tank() set src in oview(1) set category = "Object" set name = "Remove Air Tank" if(!usr.canmove || usr.stat || usr.restrained()) return if(!ishuman(usr)) to_chat(usr, "This mob type can't use this verb.") return if(!isnull(internal_tank)) usr.visible_message( "[usr] is removing [internal_tank] from [src].", "You are removing [internal_tank] from [src]." ) if (!do_after(usr, 2 SECONDS, act_target = src)) return usr.visible_message( "[usr] has removed [internal_tank] from [src].", "You removed [internal_tank] from [src]." ) for(var/obj/I in src) I.forceMove(usr.loc) use_internal_tank = 0 internal_tank = null update_icon() STOP_PROCESSING(SSfast_process, src) else to_chat(usr, "[src] has no tank.") // Remove tank from bubble /obj/structure/closet/airbubble/verb/take_cell() set src in oview(1) set category = "Object" set name = "Detach Power Cell" if(!usr.canmove || usr.stat || usr.restrained()) return if(!ishuman(usr)) to_chat(usr, "This mob type can't use this verb.") return if(!isnull(cell)) usr.visible_message( "[usr] is removing [cell] from [src].", "You are removing [cell] from [src]." ) if (!do_after(usr, 2 SECONDS, act_target = src)) return usr.visible_message( "[usr] has removed [cell] from [src].", "You removed [cell] from [src]." ) cell.forceMove(usr.loc) cell = null cooling = FALSE update_icon() else to_chat(usr, "[src] has no power cell.") // Handle most of things: restraining, cutting restrains, attaching tank. /obj/structure/closet/airbubble/attackby(obj/W, mob/user as mob) if(istype(W, /obj/item/tank)) if(!isnull(use_internal_tank)) user.visible_message( "[user] is attaching [W] to [src].", "You are attaching [W] to [src]." ) if (!do_after(user, 2 SECONDS, act_target = src)) return user.visible_message( "[user] has attached [W] to [src].", "You attached [W] to [src]." ) internal_tank = W user.drop_from_inventory(W, src) use_internal_tank = 1 START_PROCESSING(SSfast_process, src) else to_chat(user, "[src] already has a tank attached.") return TRUE if(opened) if(istype(W, /obj/item/grab)) var/obj/item/grab/G = W MouseDrop_T(G.affecting, user) return FALSE if(!W.dropsafety()) return FALSE user.drop_item() return TRUE else if(istype(W, /obj/item/handcuffs/cable)) if(zipped) to_chat(user, "[src]'s zipper is already restrained.") return TRUE user.visible_message( "[user] begins putting cable restrains on zipper of [src].", "You begin putting cable restrains on zipper of [src]." ) playsound(loc, 'sound/weapons/cablecuff.ogg', 50, 1) if (!do_after(user, 3 SECONDS, act_target = src, extra_checks = CALLBACK(src, .proc/is_closed))) return TRUE zipped = !zipped update_icon() user.visible_message( "[src]'s zipper have been zipped by [user].", "You put restrains on [src]'s zipper." ) qdel(W) update_icon() return TRUE else if(W.iswirecutter()) if(!zipped) to_chat(user, "[src] has no cables to cut.") attack_hand(user) return TRUE user.visible_message( "[user] begins cutting cable restrains on zipper of [src].", "You begin cutting cable restrains on zipper of [src]." ) playsound(loc, 'sound/items/wirecutter.ogg', 50, 1) if (!do_after(user, 3 SECONDS, act_target = src, extra_checks = CALLBACK(src, .proc/is_closed))) return TRUE zipped = !zipped update_icon() user.visible_message( "[src] zipper's cable restrains has been cut by [user].", "You cut cable restrains on [src]'s zipper." ) new/obj/item/handcuffs/cable(src.loc) update_icon() return TRUE else if(istype(W, /obj/item/cell)) if(!isnull(cell)) to_chat(user, "[src] already has [cell] attached to it.") attack_hand(user) return TRUE user.visible_message( "[user] is attaching [W] to [src].", "You are attaching [W] to [src]." ) if (!do_after(user, 2 SECONDS, act_target = src)) return TRUE user.visible_message( "[user] has attached [W] to [src].", "You attached [W] to [src]." ) cell = W cooling = TRUE user.drop_from_inventory(W, src) return TRUE else return attack_hand(user) /obj/structure/closet/airbubble/store_mobs(var/stored_units) contains_body = ..() return contains_body /obj/structure/closet/airbubble/update_icon() cut_overlays() if(ripped) name = "ripped air bubble" icon_state = "[icon_state]_ripped" else icon_state = "[initial(icon_state)][opened ? "_open" : ""]" if(zipped) add_overlay("[icon_state]_restrained") add_overlay("pressure_[(use_internal_tank) ?("on") : ("off") ]") // Process transfer of air from the tank. Handle if it is ripped open. /obj/structure/closet/airbubble/proc/process_tank_give_air() if(internal_tank) var/datum/gas_mixture/tank_air = internal_tank.return_air() var/release_pressure = internal_tank_valve // If ripped, we are leaking if(ripped) // If we has no pressure in the tank, why bother? if(tank_air.return_pressure() <= 1) STOP_PROCESSING(SSfast_process, src) use_internal_tank = !use_internal_tank visible_message("You hear last bits of air coming out from [src]'s hole.Maybe the tank run out of air?") playsound(loc, "sound/effects/wind/wind_2_1.ogg", 100, 1) return inside_air = get_turf_air() visible_message("You hear air howling from [src]'s hole. Maybe it is good to shut off valve on the internals tank?") playsound(loc, "sound/effects/wind/wind_2_2.ogg", 100, 1) var/transfer_moles = inside_air.volume/(inside_air.temperature * R_IDEAL_GAS_EQUATION) var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) if(inside_air) inside_air.merge(removed) else qdel(removed) return var/inside_pressure = inside_air.return_pressure() var/pressure_delta = min(release_pressure - inside_pressure, (tank_air.return_pressure() - inside_pressure)/2) var/transfer_moles = 0 if(pressure_delta > 0) //inside pressure lower than release pressure if(tank_air.temperature > 0) transfer_moles = pressure_delta*inside_air.volume/(inside_air.temperature * R_IDEAL_GAS_EQUATION) var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) inside_air.merge(removed) else if(pressure_delta < 0) //inside pressure higher than release pressure var/datum/gas_mixture/t_air = get_turf_air() pressure_delta = inside_pressure - release_pressure if(t_air) pressure_delta = min(inside_pressure - t_air.return_pressure(), pressure_delta) if(pressure_delta > 0) //if location pressure is lower than inside pressure transfer_moles = pressure_delta*inside_air.volume/(inside_air.temperature * R_IDEAL_GAS_EQUATION) var/datum/gas_mixture/removed = inside_air.remove(transfer_moles) if(t_air) t_air.merge(removed) else //just delete the inside gas, we're in space or some shit qdel(removed) /obj/structure/closet/airbubble/proc/process_preserve_temp() if (!cooling || !cell) return for(var/mob/living/carbon/human/H in src) var/datum/gas_mixture/t_air = get_turf_air() if(!t_air) return var/env_temp = t_air.temperature var/temp_adj = min(H.bodytemperature - max(thermostat, env_temp), max_cooling) if (temp_adj < 0.5) //only cools, doesn't heat, also we don't need extreme precision return var/charge_usage = (temp_adj/max_cooling)*charge_consumption H.bodytemperature -= temp_adj cell.use(charge_usage) if(cell.charge <= 0) cooling = FALSE /obj/structure/closet/airbubble/return_air() if(use_internal_tank) return inside_air return ..() /obj/structure/closet/airbubble/proc/get_turf_air() var/turf/T = get_turf(src) if(T) . = T.return_air() return /obj/structure/closet/airbubble/proc/return_pressure() . = 0 if(use_internal_tank) . = inside_air.return_pressure() else var/datum/gas_mixture/t_air = get_turf_air() if(t_air) . = t_air.return_pressure() return /obj/structure/closet/airbubble/proc/return_temperature() . = 0 if(use_internal_tank) . = inside_air.temperature else var/datum/gas_mixture/t_air = get_turf_air() if(t_air) . = t_air.temperature return /obj/structure/closet/airbubble/process() process_preserve_temp() process_tank_give_air() /obj/structure/closet/airbubble/proc/add_inside() inside_air = new inside_air.temperature = T20C inside_air.volume = 2 inside_air.adjust_multi(GAS_OXYGEN, O2STANDARD*inside_air.volume/(R_IDEAL_GAS_EQUATION*inside_air.temperature), GAS_NITROGEN, N2STANDARD*inside_air.volume/(R_IDEAL_GAS_EQUATION*inside_air.temperature)) return inside_air // Syndicate airbubble /obj/item/airbubble/syndie name = "air bubble" desc = "Special air bubble designed to protect people inside of it from decompressed environments. Has an integrated cooling unit to preserve a stable temperature inside. Requires a power cell to operate. This does not seem like a regular color scheme." icon_state = "airbubble_syndie_fact_folded" syndie = TRUE /obj/structure/closet/airbubble/syndie name = "air bubble" desc = "Special air bubble designed to protect people inside of it from decompressed environments. Has an integrated cooling unit to preserve a stable temperature inside. Requires a power cell to operate. This does not seem like a regular color scheme." icon_state = "airbubble_syndie" item_path = /obj/item/airbubble/syndie syndie = TRUE