From b011e723c64e936178ffbf2df9197faf44af2d48 Mon Sep 17 00:00:00 2001
From: vincentiusvin <54709710+vincentiusvin@users.noreply.github.com>
Date: Fri, 12 Aug 2022 21:09:07 +0700
Subject: [PATCH] Refactors operating tables to be event driven + QoL + Unit
Test (#69015)
* Event driven table
* Operating computer fix + loosening of check
* Unit testing
* IT NEEDS TO BE FORCE MOVE YOU GOTTA CLIMB TABLES AAAAH
* Migrated patient to carbon instead of human
Has no real bearing on the experiments tbh
* DNAs can be null apparently
* Simplify replacement code
* Move comments
---
code/game/machinery/computer/Operating.dm | 8 +--
code/game/objects/structures/tables_racks.dm | 67 +++++++++++--------
code/modules/unit_tests/_unit_tests.dm | 1 +
code/modules/unit_tests/operating_table.dm | 32 +++++++++
.../tgui/interfaces/OperatingComputer.js | 9 +--
5 files changed, 82 insertions(+), 35 deletions(-)
create mode 100644 code/modules/unit_tests/operating_table.dm
diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm
index 04c9442dcb1..ae7d19d8580 100644
--- a/code/game/machinery/computer/Operating.dm
+++ b/code/game/machinery/computer/Operating.dm
@@ -91,10 +91,10 @@
return data
data["table"] = table
- if(!table.check_eligible_patient())
- return data
data["patient"] = list()
- var/mob/living/carbon/human/patient = table.patient
+ if(!table.patient)
+ return data
+ var/mob/living/carbon/patient = table.patient
switch(patient.stat)
if(CONSCIOUS)
@@ -110,7 +110,7 @@
data["patient"]["stat"] = "Dead"
data["patient"]["statstate"] = "bad"
data["patient"]["health"] = patient.health
- data["patient"]["blood_type"] = patient.dna.blood_type
+ data["patient"]["blood_type"] = patient.dna?.blood_type
data["patient"]["maxHealth"] = patient.maxHealth
data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD
data["patient"]["bruteLoss"] = patient.getBruteLoss()
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 8ffa6a27da9..ca68b31da51 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -681,7 +681,7 @@
buckle_lying = NO_BUCKLE_LYING
buckle_requires_restraints = TRUE
custom_materials = list(/datum/material/silver = 2000)
- var/mob/living/carbon/human/patient = null
+ var/mob/living/carbon/patient = null
var/obj/machinery/computer/operating/computer = null
/obj/structure/table/optable/Initialize(mapload)
@@ -691,44 +691,57 @@
if(computer)
computer.table = src
break
+ RegisterSignal(loc, COMSIG_ATOM_ENTERED, .proc/mark_patient)
+ RegisterSignal(loc, COMSIG_ATOM_EXITED, .proc/unmark_patient)
/obj/structure/table/optable/Destroy()
- . = ..()
if(computer && computer.table == src)
computer.table = null
+ patient = null
+ UnregisterSignal(loc, COMSIG_ATOM_ENTERED)
+ UnregisterSignal(loc, COMSIG_ATOM_EXITED)
+ return ..()
/obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob)
pushed_mob.forceMove(loc)
pushed_mob.set_resting(TRUE, TRUE)
visible_message(span_notice("[user] lays [pushed_mob] on [src]."))
- get_patient()
-/obj/structure/table/optable/proc/get_patient()
- var/mob/living/carbon/M = locate(/mob/living/carbon) in loc
- if(M)
- if(M.resting)
- set_patient(M)
- else
- set_patient(null)
-
-/obj/structure/table/optable/proc/set_patient(new_patient)
- if(patient)
- UnregisterSignal(patient, COMSIG_PARENT_QDELETING)
- patient = new_patient
- if(patient)
- RegisterSignal(patient, COMSIG_PARENT_QDELETING, .proc/patient_deleted)
-
-/obj/structure/table/optable/proc/patient_deleted(datum/source)
+/// Any mob that enters our tile will be marked as a potential patient. They will be turned into a patient if they lie down.
+/obj/structure/table/optable/proc/mark_patient(datum/source, mob/living/carbon/potential_patient)
SIGNAL_HANDLER
- set_patient(null)
+ if(!istype(potential_patient))
+ return
+ RegisterSignal(potential_patient, COMSIG_LIVING_SET_BODY_POSITION, .proc/recheck_patient)
+ recheck_patient(potential_patient) // In case the mob is already lying down before they entered.
-/obj/structure/table/optable/proc/check_eligible_patient()
- get_patient()
- if(!patient)
- return FALSE
- if(ishuman(patient))
- return TRUE
- return FALSE
+/// Unmark the potential patient.
+/obj/structure/table/optable/proc/unmark_patient(datum/source, mob/living/carbon/potential_patient)
+ SIGNAL_HANDLER
+ if(!istype(potential_patient))
+ return
+ if(potential_patient == patient)
+ recheck_patient(patient) // Can just set patient to null, but doing the recheck lets us find a replacement patient.
+ UnregisterSignal(potential_patient, COMSIG_LIVING_SET_BODY_POSITION)
+
+/// Someone on our tile just lied down, got up, moved in, or moved out.
+/// potential_patient is the mob that had one of those four things change.
+/// The check is a bit broad so we can find a replacement patient.
+/obj/structure/table/optable/proc/recheck_patient(mob/living/carbon/potential_patient)
+ SIGNAL_HANDLER
+ if(patient && patient != potential_patient)
+ return
+
+ if(potential_patient.body_position == LYING_DOWN && potential_patient.loc == loc)
+ patient = potential_patient
+ return
+
+ // Find another lying mob as a replacement.
+ for (var/mob/living/carbon/replacement_patient in loc.contents)
+ if(replacement_patient.body_position == LYING_DOWN)
+ patient = replacement_patient
+ return
+ patient = null
/*
* Racks
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 18c94422482..b02d5d5ba29 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -120,6 +120,7 @@
#include "ntnetwork_tests.dm"
#include "nuke_cinematic.dm"
#include "objectives.dm"
+#include "operating_table.dm"
#include "outfit_sanity.dm"
#include "paintings.dm"
#include "pills.dm"
diff --git a/code/modules/unit_tests/operating_table.dm b/code/modules/unit_tests/operating_table.dm
new file mode 100644
index 00000000000..fad2cbe9831
--- /dev/null
+++ b/code/modules/unit_tests/operating_table.dm
@@ -0,0 +1,32 @@
+/// Make a mob hop on an optable, rest, get up, rest again, and then move to another tile.
+/// While the mob is still an active patient, move another mob in too.
+/// This is so the replacement code can kick in when the original mob is no longer valid.
+/datum/unit_test/operating_table
+
+/datum/unit_test/operating_table/Run()
+ var/obj/structure/table/optable/table = allocate(/obj/structure/table/optable)
+ var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human, get_step(table, NORTH))
+ var/mob/living/carbon/human/replacement_human = allocate(/mob/living/carbon/human, get_step(table, NORTH))
+
+ // Resting is a bit more high level than bodypos, gets us nicer coverage.
+ human.set_resting(new_resting = FALSE, instant = TRUE)
+ replacement_human.set_resting(new_resting = FALSE, instant = TRUE)
+
+ human.forceMove(get_turf(table))
+ TEST_ASSERT_NULL(table.patient, "Operating table is occupied by a non-resting patient.")
+
+ human.set_resting(new_resting = TRUE, instant = TRUE)
+ TEST_ASSERT_EQUAL(table.patient, human, "Operating table failed to update for a resting patient.")
+
+ human.set_resting(new_resting = FALSE, instant = TRUE)
+ TEST_ASSERT_NULL(table.patient, "Operating table is occupied by a non-resting patient.")
+
+ human.set_resting(new_resting = TRUE, instant = TRUE)
+ TEST_ASSERT_EQUAL(table.patient, human, "Operating table failed to update for a resting patient.")
+
+ replacement_human.forceMove(get_turf(table))
+ replacement_human.set_resting(new_resting = TRUE, instant = TRUE)
+ TEST_ASSERT_EQUAL(table.patient, human, "Operating table patient unset by another patient jumping in.")
+
+ human.forceMove(get_step(get_turf(table), NORTH))
+ TEST_ASSERT_EQUAL(table.patient, replacement_human, "Operating table failed to find a replacement patient.")
diff --git a/tgui/packages/tgui/interfaces/OperatingComputer.js b/tgui/packages/tgui/interfaces/OperatingComputer.js
index a718cfc1669..078df2a0642 100644
--- a/tgui/packages/tgui/interfaces/OperatingComputer.js
+++ b/tgui/packages/tgui/interfaces/OperatingComputer.js
@@ -55,13 +55,13 @@ const PatientStateView = (props, context) => {
return (
<>
- {(patient && (
+ {Object.keys(patient).length ? (
{patient.stat}
- {patient.blood_type}
+ {patient.blood_type || 'Unable to determine blood type'}
{
))}
- )) ||
- 'No Patient Detected'}
+ ) : (
+ 'No Patient Detected'
+ )}
{procedures.length === 0 && }
{procedures.map((procedure) => (