diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 91c637a9492..62573bad430 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -639,6 +639,9 @@ /// Sent from /obj/item/mob_holder/purple_raptor/proc/toggle_wings() : (mob/living/carbon/human/user) #define COMSIG_RAPTOR_WINGS_CLOSED "raptor_wings_closed" +/// Sent from /obj/machinery/vitals_monitor/proc/set_patient() : (mob/living/old_patient, mob/living/new_patient) +#define COMSIG_VITALS_SET_PATIENT "vitals_set_patient" + /// Sent from /obj/item/bodypart/limb/proc/apply_item(): (obj/item/bodypart/limb) #define COMSIG_ITEM_APPLIED_TO_LIMB "item_applied_to_limb" /// Sent from /obj/item/bodypart/limb/proc/Exited(): (obj/item/bodypart/limb) diff --git a/code/game/machinery/wall_vitals.dm b/code/game/machinery/wall_vitals.dm index 51790a1e7bc..beb8ecc1d7d 100644 --- a/code/game/machinery/wall_vitals.dm +++ b/code/game/machinery/wall_vitals.dm @@ -95,6 +95,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/vitals_reader/advanced, 32) /obj/machinery/vitals_reader/Initialize(mapload, obj/item/circuitboard/C) . = ..() + AddComponent(/datum/component/usb_port, typecacheof(list(/obj/item/circuit_component/vitals_monitor), only_root_path = TRUE)) register_context() return INITIALIZE_HINT_LATELOAD @@ -105,16 +106,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/vitals_reader/advanced, 32) set_light_on(TRUE) /obj/machinery/vitals_reader/Destroy(force) - unset_connection() - unset_patient() // unset connection also unsets patient, but just in case + set_connection(null) + set_patient(null) // unset connection also unsets patient, but just in case return ..() -/obj/machinery/vitals_reader/proc/unset_connected(...) +/obj/machinery/vitals_reader/proc/comsig_unset_connected(...) SIGNAL_HANDLER - UnregisterSignal(connected, COMSIG_QDELETING) - UnregisterSignal(connected, list(COMSIG_OPERATING_TABLE_SET_PATIENT, COMSIG_MACHINERY_SET_OCCUPANT)) - UnregisterSignal(connected, COMSIG_MOVABLE_MOVED) - connected = null + set_connection(null) /obj/machinery/vitals_reader/proc/connected_occupant_changed(datum/source, mob/living/new_patient) SIGNAL_HANDLER @@ -124,26 +122,25 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/vitals_reader/advanced, 32) /obj/machinery/vitals_reader/proc/connected_moved(...) SIGNAL_HANDLER if(get_dist(src, connected) > connection_range) - unset_connected() + set_connection(null) -/obj/machinery/vitals_reader/proc/unset_connection(...) - SIGNAL_HANDLER - if(isnull(connected)) +/// Sets the machine as connected to this vitals reader +/// If there is already a connection, it will be unset first. +/obj/machinery/vitals_reader/proc/set_connection(obj/new_connected) + if(connected == new_connected) + return + if(!isnull(connected)) + UnregisterSignal(connected, COMSIG_QDELETING) + UnregisterSignal(connected, list(COMSIG_OPERATING_TABLE_SET_PATIENT, COMSIG_MACHINERY_SET_OCCUPANT)) + UnregisterSignal(connected, COMSIG_MOVABLE_MOVED) + connected = null + set_patient(null) + update_use_power(NO_POWER_USE) + if(isnull(new_connected)) return - UnregisterSignal(connected, COMSIG_QDELETING) - UnregisterSignal(connected, list(COMSIG_OPERATING_TABLE_SET_PATIENT, COMSIG_MACHINERY_SET_OCCUPANT)) - UnregisterSignal(connected, COMSIG_MOVABLE_MOVED) - connected = null - unset_patient() - update_use_power(NO_POWER_USE) - -/obj/machinery/vitals_reader/proc/set_connection(obj/new_connected) - if(!isnull(connected)) - unset_connected() - connected = new_connected - RegisterSignal(connected, COMSIG_QDELETING, PROC_REF(unset_connected)) + RegisterSignal(connected, COMSIG_QDELETING, PROC_REF(comsig_unset_connected)) RegisterSignals(connected, list(COMSIG_OPERATING_TABLE_SET_PATIENT, COMSIG_MACHINERY_SET_OCCUPANT), PROC_REF(connected_occupant_changed)) RegisterSignal(connected, COMSIG_MOVABLE_MOVED, PROC_REF(connected_moved)) update_use_power(IDLE_POWER_USE) @@ -178,7 +175,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/vitals_reader/advanced, 32) return ITEM_INTERACT_BLOCKING balloon_alert(user, "disconnected from [connected.name]") - unset_connection() + set_connection(null) return ITEM_INTERACT_SUCCESS /// Find and connects to a nearby machine @@ -217,14 +214,14 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/vitals_reader/advanced, 32) Use a [EXAMINE_HINT("multitool")] to connect it to a neighboring machine.") return - if(user.is_blind()) + if(isnull(patient) || user.is_blind()) return if(machine_stat & EMPED) . += span_warning("The display is flickering erratically!") return - if(!issilicon(user) && !isobserver(user) && get_dist(patient, user) > 2) + if(!issilicon(user) && !isobserver(user) && get_dist(src, user) > 2) . += span_notice("You are too far away to read the display.") else if(HAS_TRAIT(user, TRAIT_DUMB) || !user.can_read(src, reading_check_flags = READING_CHECK_LITERACY, silent = TRUE)) . += span_warning("You try to comprehend the display, but it's too complex for you to understand.") @@ -468,15 +465,28 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/vitals_reader/advanced, 32) /obj/machinery/vitals_reader/proc/set_patient(mob/living/new_patient) if(patient == new_patient) return - if(isnull(new_patient)) - unset_patient() - return + SEND_SIGNAL(src, COMSIG_VITALS_SET_PATIENT, patient, new_patient) + if(!isnull(patient)) + UnregisterSignal(patient, list( + COMSIG_QDELETING, + COMSIG_CARBON_POST_REMOVE_LIMB, + COMSIG_CARBON_POST_ATTACH_LIMB, + COMSIG_LIVING_HEALTH_UPDATE, + COMSIG_LIVING_UPDATE_BLOOD_STATUS, + )) - if(isnull(patient)) - unset_patient() + patient = null + end_processing() + if(!QDELING(src)) + update_use_power(IDLE_POWER_USE) + update_appearance() + if(isnull(new_patient)) + return + if(QDELING(src)) + CRASH("[src] is qdeleting but tried to set a new patient ([new_patient])!") patient = new_patient - RegisterSignal(patient, COMSIG_QDELETING, PROC_REF(unset_patient)) + RegisterSignal(patient, COMSIG_QDELETING, PROC_REF(comsig_unset_patient)) RegisterSignals(patient, list( COMSIG_CARBON_POST_REMOVE_LIMB, COMSIG_CARBON_POST_ATTACH_LIMB, @@ -488,27 +498,9 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/vitals_reader/advanced, 32) update_use_power(ACTIVE_POWER_USE) last_reported_stat = null -/// Unset the current patient. -/obj/machinery/vitals_reader/proc/unset_patient(...) +/obj/machinery/vitals_reader/proc/comsig_unset_patient(...) SIGNAL_HANDLER - if(isnull(patient)) - return - - UnregisterSignal(patient, list( - COMSIG_QDELETING, - COMSIG_CARBON_POST_REMOVE_LIMB, - COMSIG_CARBON_POST_ATTACH_LIMB, - COMSIG_LIVING_HEALTH_UPDATE, - COMSIG_LIVING_UPDATE_BLOOD_STATUS, - )) - - patient = null - end_processing() - update_use_power(IDLE_POWER_USE) - if(QDELING(src)) - return - - update_appearance() + set_patient(null) /// Signal proc to update the display when a signal is received. /obj/machinery/vitals_reader/proc/update_overlay_on_signal(...) @@ -536,3 +528,141 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/vitals_reader/advanced, 32) playsound(src, 'sound/effects/glass/glasshit.ogg', 75, TRUE) if(BURN) playsound(src, 'sound/items/tools/welder.ogg', 100, TRUE) + +/obj/item/circuit_component/vitals_monitor + display_name = "Vitals Monitor" + desc = "Allows you to interface with a vitals monitor to read a patient's status." + + /// Reference to the linked vitals monitor + var/obj/machinery/vitals_reader/linked_monitor + + /// Cooldown length between patient stat reads + var/read_cooldown_period = 4 SECONDS + /// Cooldown for reading patient stats + COOLDOWN_DECLARE(read_cooldown) + + /// Outputs a signal when the vitals have been updated + var/datum/port/output/status_updated + /// Outputs a signal when the patient being monitored has changed + var/datum/port/output/patient_changed + + /// Outputs the patient being monitored + var/datum/port/output/patient_entity + /// Outputs a string corresponding to how defibbing would go + var/datum/port/output/defib_state + + /// Outputs total brute damage + var/datum/port/output/patient_brute_damage + /// Outputs total burn damage + var/datum/port/output/patient_burn_damage + /// Outputs total oxyloss damage + var/datum/port/output/patient_oxy_loss + /// Outputs total toxin damage + var/datum/port/output/patient_tox_loss + /// Outputs overall health + var/datum/port/output/patient_overall_health + /// Outputs brain damage + var/datum/port/output/patient_brain_damage + /// Outputs blood level + var/datum/port/output/patient_blood_level + + +/obj/item/circuit_component/vitals_monitor/Destroy() + STOP_PROCESSING(SSclock_component, src) + return ..() + +/obj/item/circuit_component/vitals_monitor/populate_ports() + status_updated = add_output_port("Status Updated", PORT_TYPE_SIGNAL) + patient_changed = add_output_port("Patient Changed", PORT_TYPE_SIGNAL) + + patient_entity = add_output_port("Patient", PORT_TYPE_ATOM) + defib_state = add_output_port("Defib Status", PORT_TYPE_STRING) + + patient_overall_health = add_output_port("Overall Health", PORT_TYPE_NUMBER) + patient_brute_damage = add_output_port("Brute Damage", PORT_TYPE_NUMBER) + patient_burn_damage = add_output_port("Burn Damage", PORT_TYPE_NUMBER) + patient_oxy_loss = add_output_port("Suffocation Damage", PORT_TYPE_NUMBER) + patient_tox_loss = add_output_port("Toxin Damage", PORT_TYPE_NUMBER) + patient_brain_damage = add_output_port("Brain Damage", PORT_TYPE_NUMBER) + patient_blood_level = add_output_port("Blood Level", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/vitals_monitor/register_usb_parent(atom/movable/shell) + . = ..() + linked_monitor = shell + RegisterSignal(shell, COMSIG_VITALS_SET_PATIENT, PROC_REF(handle_patient_change)) + + var/obj/machinery/vitals_reader/monitor = shell + if(!isnull(monitor.patient)) + START_PROCESSING(SSclock_component, src) + +/obj/item/circuit_component/vitals_monitor/unregister_usb_parent(atom/movable/shell) + . = ..() + linked_monitor = null + UnregisterSignal(shell, COMSIG_VITALS_SET_PATIENT) + STOP_PROCESSING(SSclock_component, src) + +/obj/item/circuit_component/vitals_monitor/get_ui_notices() + . = ..() + . += create_ui_notice("Initial Delay: [DisplayTimeText(1 SECONDS)]", "orange", "clock") + . += create_ui_notice("Update Interval: [DisplayTimeText(read_cooldown_period)]", "orange", "clock") + +/obj/item/circuit_component/vitals_monitor/proc/handle_patient_change(datum/source, mob/living/old_patient, mob/living/new_patient) + SIGNAL_HANDLER + if(isnull(new_patient)) + STOP_PROCESSING(SSclock_component, src) + else + START_PROCESSING(SSclock_component, src) + patient_entity.set_output(new_patient) + patient_changed.set_output(COMPONENT_SIGNAL) + // wait a small amount (arbitrary) to provide initial data + COOLDOWN_START(src, read_cooldown, 1 SECONDS) + addtimer(CALLBACK(src, PROC_REF(delayed_reading)), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_DELETE_ME) + +/// Reads a patient value and applies a random amount of error if the monitor is emped or emagged +#define GET_PATIENT_VALUE(monitor, value_to_get) \ + round(monitor.patient.##value_to_get * ((monitor.machine_stat & (EMPED|EMAGGED)) ? rand(0, 100) * 0.01 : 1), DAMAGE_PRECISION) + +/obj/item/circuit_component/vitals_monitor/process(seconds_per_tick) + if(isnull(linked_monitor?.patient)) + return PROCESS_KILL + if(!COOLDOWN_FINISHED(src, read_cooldown)) + return + update_output_stats() + +/obj/item/circuit_component/vitals_monitor/proc/delayed_reading() + if(isnull(linked_monitor?.patient)) + return + update_output_stats() + +/obj/item/circuit_component/vitals_monitor/proc/update_output_stats() + COOLDOWN_START(src, read_cooldown, read_cooldown_period) + status_updated.set_output(COMPONENT_SIGNAL) + + patient_overall_health.set_output(GET_PATIENT_VALUE(linked_monitor, health)) + patient_brute_damage.set_output(GET_PATIENT_VALUE(linked_monitor, get_brute_loss())) + patient_burn_damage.set_output(GET_PATIENT_VALUE(linked_monitor, get_fire_loss())) + patient_oxy_loss.set_output(GET_PATIENT_VALUE(linked_monitor, get_oxy_loss())) + patient_tox_loss.set_output(GET_PATIENT_VALUE(linked_monitor, get_tox_loss())) + patient_brain_damage.set_output(GET_PATIENT_VALUE(linked_monitor, get_organ_loss(ORGAN_SLOT_BRAIN))) + patient_blood_level.set_output(GET_PATIENT_VALUE(linked_monitor, get_blood_volume(TRUE))) + + var/is_defib_possible = DEFIB_POSSIBLE + if(iscarbon(linked_monitor.patient)) + var/mob/living/carbon/carbo_patient = linked_monitor.patient + is_defib_possible = carbo_patient.can_defib() + + switch(is_defib_possible) + if(DEFIB_POSSIBLE) + defib_state.set_output("Possible") + if(DEFIB_FAIL_SUICIDE, DEFIB_FAIL_BLACKLISTED, DEFIB_FAIL_NO_INTELLIGENCE, DEFIB_FAIL_GOLEM) + defib_state.set_output("Impossible") + if(DEFIB_FAIL_HUSK) + defib_state.set_output("Husked") + if(DEFIB_FAIL_TISSUE_DAMAGE) + defib_state.set_output("Damaged") + if(DEFIB_FAIL_FAILING_HEART, DEFIB_FAIL_NO_HEART) + defib_state.set_output("Heart Failure") + if(DEFIB_FAIL_FAILING_BRAIN, DEFIB_FAIL_NO_BRAIN) + defib_state.set_output("Brain Failure") + +#undef GET_PATIENT_VALUE diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 334bdce8d92..d1ee541429f 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -803,6 +803,7 @@ return ..() /mob/living/carbon/proc/can_defib() + SHOULD_BE_PURE(TRUE) if (HAS_TRAIT(src, TRAIT_SUICIDED)) return DEFIB_FAIL_SUICIDE diff --git a/code/modules/wiremod/components/atom/health.dm b/code/modules/wiremod/components/atom/health.dm index 5f071d2758f..8a614066af6 100644 --- a/code/modules/wiremod/components/atom/health.dm +++ b/code/modules/wiremod/components/atom/health.dm @@ -37,7 +37,7 @@ burn = add_output_port("Burn Damage", PORT_TYPE_NUMBER) toxin = add_output_port("Toxin Damage", PORT_TYPE_NUMBER) oxy = add_output_port("Suffocation Damage", PORT_TYPE_NUMBER) - health = add_output_port("Health", PORT_TYPE_NUMBER) + health = add_output_port("Overall Health", PORT_TYPE_NUMBER) /obj/item/circuit_component/health/input_received(datum/port/input/port) @@ -57,4 +57,3 @@ toxin.set_output(organism.get_tox_loss()) oxy.set_output(organism.get_oxy_loss()) health.set_output(organism.health) -