mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-29 11:02:05 +00:00
## About The Pull Request Fixes a logical inconsistency with several types of containers that are **functionally open** for game mechanics (allowing refilling, transfer, etc.) but are **perceived as closed** from a user perspective.
315 lines
12 KiB
Plaintext
315 lines
12 KiB
Plaintext
/obj/item/inhaler
|
|
name = "inhaler"
|
|
desc = "A small device capable of administering short bursts of aerosolized chemicals. Requires a canister to function."
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
|
|
icon = 'icons/obj/medical/chemical.dmi'
|
|
icon_state = "inhaler_generic"
|
|
|
|
custom_materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 0.1)
|
|
|
|
/// The currently installed canister, from which we get our reagents. Nullable.
|
|
var/obj/item/reagent_containers/inhaler_canister/canister
|
|
/// The path for our initial canister to be generated by. If not null, we start with that canister type.
|
|
var/obj/item/reagent_containers/inhaler_canister/initial_casister_path
|
|
|
|
/// The underlay of our canister, if one is installed.
|
|
var/mutable_appearance/canister_underlay
|
|
/// The y offset to be applied to [canister_underlay].
|
|
var/canister_underlay_y_offset = -2
|
|
/// If true, we will show a rotary display with how many puffs we can be used for until the canister runs out.
|
|
var/show_puffs_left = TRUE // this is how real inhalers work
|
|
|
|
/obj/item/inhaler/Initialize(mapload)
|
|
. = ..()
|
|
if (ispath(initial_casister_path, /obj/item/reagent_containers/inhaler_canister))
|
|
set_canister(new initial_casister_path)
|
|
|
|
/obj/item/inhaler/Destroy(force)
|
|
QDEL_NULL(canister)
|
|
|
|
return ..()
|
|
|
|
/obj/item/inhaler/handle_deconstruct(disassembled)
|
|
. = ..()
|
|
|
|
canister?.forceMove(drop_location())
|
|
|
|
/obj/item/inhaler/proc/update_canister_underlay()
|
|
if (isnull(canister))
|
|
underlays -= canister_underlay
|
|
canister_underlay = null
|
|
else if (isnull(canister_underlay))
|
|
canister_underlay = mutable_appearance(canister.icon, canister.icon_state)
|
|
canister_underlay.pixel_z = canister_underlay_y_offset
|
|
underlays += canister_underlay
|
|
|
|
/obj/item/inhaler/examine(mob/user)
|
|
. = ..()
|
|
|
|
if (isnull(canister))
|
|
return
|
|
|
|
. += span_blue("It seems to have <b>[canister]</b> inserted.")
|
|
if (!show_puffs_left)
|
|
return
|
|
|
|
var/puffs_left = canister.get_puffs_left()
|
|
if (puffs_left > 0)
|
|
puffs_left = span_blue("[puffs_left]")
|
|
else
|
|
puffs_left = span_danger("[puffs_left]")
|
|
. += "Its rotary display shows its canister can be used [puffs_left] more times."
|
|
|
|
/obj/item/inhaler/Exited(atom/movable/gone, direction)
|
|
. = ..()
|
|
|
|
if (gone == canister)
|
|
set_canister(null, move_canister = FALSE)
|
|
|
|
|
|
/obj/item/inhaler/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
|
if (!isliving(interacting_with))
|
|
return ..() // default behavior
|
|
var/mob/living/target_mob = interacting_with
|
|
|
|
if (!can_puff(target_mob, user))
|
|
return NONE
|
|
|
|
var/puff_timer = 0
|
|
|
|
var/pre_use_visible_message
|
|
var/pre_use_self_message
|
|
var/pre_use_target_message
|
|
|
|
var/post_use_visible_message
|
|
var/post_use_self_message
|
|
var/post_use_target_message
|
|
|
|
if (target_mob == user) // no need for a target message
|
|
puff_timer = canister.self_administer_delay
|
|
|
|
pre_use_visible_message = span_notice("[user] puts [src] to [user.p_their()] lips, fingers on the canister...")
|
|
pre_use_self_message = span_notice("You put [src] to your lips and put pressure on the canister...")
|
|
|
|
post_use_visible_message = span_notice("[user] takes a puff of [src]!")
|
|
post_use_self_message = span_notice("You take a puff of [src]!")
|
|
else
|
|
puff_timer = canister.other_administer_delay
|
|
|
|
pre_use_visible_message = span_warning("[user] tries to force [src] between [target_mob]'s lips...")
|
|
pre_use_self_message = span_notice("You try to put [src] to [target_mob]'s lips...")
|
|
pre_use_target_message = span_userdanger("[user] tries to force [src] between your lips!")
|
|
|
|
post_use_visible_message = span_warning("[user] forces [src] between [target_mob]'s lips and pushes the canister down!")
|
|
post_use_self_message = span_notice("You force [src] between [target_mob]'s lips and press on the canister!")
|
|
post_use_target_message = span_userdanger("[user] forces [src] between your lips and presses on the canister, filling your lungs with aerosol!")
|
|
|
|
if (puff_timer > 0)
|
|
user.visible_message(pre_use_visible_message, ignored_mobs = list(user, target_mob))
|
|
to_chat(user, pre_use_self_message)
|
|
if (pre_use_target_message)
|
|
to_chat(target_mob, pre_use_target_message)
|
|
if (!do_after(user, puff_timer, src))
|
|
return NONE
|
|
if (!can_puff(target_mob, user)) // sanity
|
|
return NONE
|
|
|
|
user.visible_message(post_use_visible_message, ignored_mobs = list(user, target_mob))
|
|
to_chat(user, post_use_self_message)
|
|
if (post_use_target_message)
|
|
to_chat(target_mob, post_use_target_message)
|
|
|
|
canister.puff(user, target_mob)
|
|
|
|
/obj/item/inhaler/attack_self(mob/user, modifiers)
|
|
try_remove_canister(user, modifiers)
|
|
|
|
return ..()
|
|
|
|
/obj/item/inhaler/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
|
if (istype(tool, /obj/item/reagent_containers/inhaler_canister))
|
|
return try_insert_canister(tool, user, modifiers)
|
|
|
|
return ..()
|
|
|
|
/// Tries to remove the canister, if any is inserted.
|
|
/obj/item/inhaler/proc/try_remove_canister(mob/living/user, modifiers)
|
|
if (isnull(canister))
|
|
balloon_alert(user, "no canister inserted!")
|
|
return FALSE
|
|
|
|
if (canister.removal_time > 0)
|
|
balloon_alert(user, "removing canister...")
|
|
if (!do_after(user, canister.removal_time, src))
|
|
return FALSE
|
|
|
|
balloon_alert(user, "canister removed")
|
|
playsound(src, canister.post_insert_sound, canister.post_insert_volume)
|
|
set_canister(null, user)
|
|
|
|
// Tries to insert a canister, if none is already inserted.
|
|
/obj/item/inhaler/proc/try_insert_canister(obj/item/reagent_containers/inhaler_canister/new_canister, mob/living/user, params)
|
|
if (!isnull(canister))
|
|
balloon_alert(user, "remove the existing canister!")
|
|
return FALSE
|
|
|
|
balloon_alert(user, "inserting canister...")
|
|
playsound(src, new_canister.pre_insert_sound, new_canister.pre_insert_volume)
|
|
if (!do_after(user, new_canister.insertion_time, src))
|
|
return FALSE
|
|
playsound(src, new_canister.post_insert_sound, new_canister.post_insert_volume)
|
|
balloon_alert(user, "canister inserted")
|
|
set_canister(new_canister, user)
|
|
|
|
return TRUE
|
|
|
|
/// Setter proc for [canister]. Moves the existing canister out of the inhaler, while moving a new canister inside and registering it.
|
|
/obj/item/inhaler/proc/set_canister(obj/item/reagent_containers/inhaler_canister/new_canister, mob/living/user, move_canister = TRUE)
|
|
if (move_canister && !isnull(canister))
|
|
if (iscarbon(loc))
|
|
var/mob/living/carbon/carbon_loc = loc
|
|
INVOKE_ASYNC(carbon_loc, TYPE_PROC_REF(/mob/living/carbon, put_in_hands), canister)
|
|
else if (!isnull(loc))
|
|
canister.forceMove(loc)
|
|
|
|
canister = new_canister
|
|
canister?.forceMove(src)
|
|
update_canister_underlay()
|
|
|
|
/// Determines if we can be used. Fails on no canister, empty canister, invalid targets, or non-breathing targets.
|
|
/obj/item/inhaler/proc/can_puff(mob/living/target_mob, mob/living/user, silent = FALSE)
|
|
if (isnull(canister))
|
|
if (!silent)
|
|
balloon_alert(user, "no canister!")
|
|
return FALSE
|
|
if (isnull(canister.reagents) || canister.reagents.total_volume <= 0)
|
|
if (!silent)
|
|
balloon_alert(user, "canister is empty!")
|
|
return FALSE
|
|
if (!iscarbon(target_mob)) // maybe mix this into a general has mouth check
|
|
if (!silent)
|
|
balloon_alert(user, "not breathing!")
|
|
return FALSE
|
|
var/mob/living/carbon/carbon_target = target_mob
|
|
if (carbon_target.is_mouth_covered())
|
|
if (!silent)
|
|
balloon_alert(user, "expose the mouth!")
|
|
return FALSE
|
|
if (HAS_TRAIT(carbon_target, TRAIT_NOBREATH))
|
|
if (!silent)
|
|
balloon_alert(user, "not breathing!")
|
|
return FALSE
|
|
var/obj/item/organ/lungs/lungs = carbon_target.get_organ_slot(ORGAN_SLOT_LUNGS)
|
|
if (isnull(lungs) || lungs.received_pressure_mult <= 0)
|
|
if (!silent)
|
|
balloon_alert(user, "not breathing!")
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/obj/item/reagent_containers/inhaler_canister
|
|
name = "inhaler canister"
|
|
desc = "A small canister filled with aerosolized reagents for use in a inhaler."
|
|
w_class = WEIGHT_CLASS_TINY
|
|
|
|
icon = 'icons/obj/medical/chemical.dmi'
|
|
icon_state = "canister_generic"
|
|
|
|
initial_reagent_flags = SEALED_CONTAINER | DRAINABLE | REFILLABLE | NO_SPLASH
|
|
has_variable_transfer_amount = FALSE
|
|
|
|
max_integrity = 60
|
|
|
|
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 0.2)
|
|
|
|
/// The sound that plays when we are used.
|
|
var/puff_sound = 'sound/effects/spray.ogg'
|
|
/// The volume of [puff_sound]
|
|
var/puff_volume = 20
|
|
|
|
/// The sound that plays when someone TRIES to insert us.
|
|
var/pre_insert_sound = 'sound/items/taperecorder/tape_flip.ogg'
|
|
/// The sound that plays when we are removed or inserted.
|
|
var/post_insert_sound = 'sound/items/taperecorder/taperecorder_close.ogg'
|
|
|
|
/// The volume of [pre_insert_sound]
|
|
var/pre_insert_volume = 50
|
|
/// The volume of [post_insert_sound]
|
|
var/post_insert_volume = 50
|
|
|
|
/// The time it takes to insert us into a inhaler.
|
|
var/insertion_time = 2 SECONDS
|
|
/// The time it takes to remove us from a inhaler.
|
|
var/removal_time = 0.5 SECONDS
|
|
|
|
/// The time it takes for us to be used on someone else.
|
|
var/other_administer_delay = 3 SECONDS
|
|
/// The time it takes for us to be used on our owner.
|
|
var/self_administer_delay = 1 SECONDS
|
|
|
|
/// Called when a inhaler we are in is used on someone. Transfers reagents and plays the puff sound.
|
|
/obj/item/reagent_containers/inhaler_canister/proc/puff(mob/living/user, mob/living/carbon/target)
|
|
playsound(src, puff_sound, puff_volume, TRUE, -6)
|
|
reagents.trans_to(target, amount_per_transfer_from_this, transferred_by = user, methods = INHALE)
|
|
|
|
/// Returns a integer approximating how many puffs we can be used for.
|
|
/obj/item/reagent_containers/inhaler_canister/proc/get_puffs_left()
|
|
return ROUND_UP(reagents.total_volume / amount_per_transfer_from_this)
|
|
|
|
/obj/item/reagent_containers/inhaler_canister/handle_deconstruct(disassembled)
|
|
if (!reagents?.total_volume)
|
|
return ..()
|
|
|
|
var/datum/reagents/smoke_reagents = new/datum/reagents() // Lets be safe first, our own reagents may be qdelled if we get deleted
|
|
var/datum/effect_system/fluid_spread/smoke/chem/smoke_machine/smoke = new()
|
|
smoke_reagents.my_atom = src
|
|
for (var/datum/reagent/reagent as anything in reagents.reagent_list)
|
|
smoke_reagents.add_reagent(reagent.type, reagent.volume, added_purity = reagent.purity)
|
|
reagents.remove_reagent(reagent.type, reagent.volume)
|
|
if (smoke_reagents.reagent_list)
|
|
smoke.set_up(1, holder = src, location = get_turf(src), carry = smoke_reagents)
|
|
smoke.start(log = TRUE)
|
|
visible_message(span_warning("[src] breaks open and sprays its aerosilized contents everywhere!"))
|
|
else
|
|
visible_message(span_warning("[src] breaks open - but is empty!"))
|
|
|
|
return ..()
|
|
|
|
/obj/item/inhaler/medical
|
|
icon_state = "inhaler_medical"
|
|
|
|
/obj/item/inhaler/salbutamol
|
|
name = "salbutamol inhaler"
|
|
icon_state = "inhaler_medical"
|
|
initial_casister_path = /obj/item/reagent_containers/inhaler_canister/salbutamol
|
|
|
|
/obj/item/reagent_containers/inhaler_canister/salbutamol
|
|
name = "salbutamol canister"
|
|
icon_state = "canister_medical"
|
|
list_reagents = list(/datum/reagent/medicine/salbutamol = 30)
|
|
|
|
/obj/item/inhaler/albuterol
|
|
name = "albuterol inhaler"
|
|
icon_state = "inhaler_medical"
|
|
initial_casister_path = /obj/item/reagent_containers/inhaler_canister/albuterol
|
|
|
|
/obj/item/reagent_containers/inhaler_canister/albuterol
|
|
name = "albuterol canister"
|
|
desc = "A small canister filled with aerosolized reagents for use in a inhaler. This one contains albuterol, a potent bronchodilator that can stop \
|
|
asthma attacks in their tracks."
|
|
icon_state = "canister_medical"
|
|
list_reagents = list(/datum/reagent/medicine/albuterol = 30)
|
|
|
|
/obj/item/reagent_containers/inhaler_canister/albuterol/asthma
|
|
name = "low-pressure albuterol canister"
|
|
desc = "A small canister filled with aerosolized reagents for use in a inhaler. This one contains albuterol, a potent bronchodilator that can stop \
|
|
asthma attacks in their tracks. It seems to be a lower-pressure variant, and can only hold 20u."
|
|
list_reagents = list(/datum/reagent/medicine/albuterol = 20)
|
|
volume = 20
|
|
|
|
/obj/item/inhaler/albuterol/asthma
|
|
name = "rescue inhaler"
|
|
icon_state = "inhaler_generic"
|
|
initial_casister_path = /obj/item/reagent_containers/inhaler_canister/albuterol/asthma
|