Files
Alexis 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

291 lines
10 KiB
Plaintext

#define MENU_OPERATION 1
#define MENU_SURGERIES 2
/obj/machinery/computer/operating
name = "operating computer"
desc = "Monitors patient vitals and displays surgery steps. Can be loaded with surgery disks to perform experimental procedures. Automatically syncs to operating tables within its line of sight for surgical tech advancement."
icon_screen = "crew"
icon_keyboard = "med_key"
circuit = /obj/item/circuitboard/computer/operating
interaction_flags_machine = parent_type::interaction_flags_machine | INTERACT_MACHINE_REQUIRES_STANDING
light_color = LIGHT_COLOR_GREEN
/// Linked operating table, if any
var/obj/structure/table/optable/table
/// List if surgery typepaths available on this computer
var/list/advanced_surgeries = list()
/// Techweb linked to this operating computer
var/datum/techweb/linked_techweb
/// Currently selected body zone for surgery
var/target_zone = BODY_ZONE_CHEST
/// Experiment handler component reference
var/datum/component/experiment_handler/experiment_handler
/// Lazyassoclist of mob weakrefs to the zone they had selected when opening the UI
/// When closing the UI we try to revert their zone selector to that zone
var/list/datum/weakref/zone_on_open
/obj/machinery/computer/operating/Initialize(mapload)
. = ..()
find_table()
return INITIALIZE_HINT_LATELOAD
/obj/machinery/computer/operating/post_machine_initialize()
. = ..()
if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb)
CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src)
if(linked_techweb)
RegisterSignal(linked_techweb, COMSIG_TECHWEB_ADD_DESIGN, PROC_REF(on_techweb_research))
RegisterSignal(linked_techweb, COMSIG_TECHWEB_REMOVE_DESIGN, PROC_REF(on_techweb_unresearch))
RegisterSignal(linked_techweb, COMSIG_TECHWEB_EXPERIMENT_COMPLETED, PROC_REF(update_static_data_batched))
for(var/datum/design/surgery/design in linked_techweb.get_researched_design_datums())
advanced_surgeries |= design.surgery
var/list/operating_signals = list(
COMSIG_OPERATING_COMPUTER_AUTOPSY_COMPLETE = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_autopsy_experiment),
)
experiment_handler = AddComponent(
/datum/component/experiment_handler, \
allowed_experiments = list(/datum/experiment/autopsy), \
config_flags = EXPERIMENT_CONFIG_ALWAYS_ACTIVE, \
config_mode = EXPERIMENT_CONFIG_ALTCLICK, \
experiment_signals = operating_signals, \
)
/obj/machinery/computer/operating/Destroy()
for(var/direction in GLOB.alldirs)
table = locate(/obj/structure/table/optable) in get_step(src, direction)
if(table && table.computer == src)
table.computer = null
QDEL_NULL(experiment_handler)
return ..()
/obj/machinery/computer/operating/multitool_act(mob/living/user, obj/item/multitool/tool)
if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb))
linked_techweb = tool.buffer
return TRUE
/obj/machinery/computer/operating/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(istype(tool, /obj/item/disk/surgery))
user.visible_message(
span_notice("[user] begins to load [tool] in [src]..."),
span_notice("You begin to load a surgery protocol from [tool]..."),
span_hear("You hear the chatter of a floppy drive."),
)
var/obj/item/disk/surgery/disky = tool
if(!do_after(user, 1 SECONDS, src))
return ITEM_INTERACT_BLOCKING
advanced_surgeries |= disky.surgeries
update_static_data_for_all_viewers()
playsound(src, 'sound/machines/compiler/compiler-stage2.ogg', 50, FALSE, SILENCED_SOUND_EXTRARANGE)
balloon_alert(user, "surgeries loaded")
return ITEM_INTERACT_SUCCESS
if((tool.item_flags & SURGICAL_TOOL) && !user.combat_mode)
ui_interact(user)
return ITEM_INTERACT_SUCCESS
return NONE
/obj/machinery/computer/operating/on_set_is_operational(old_value)
update_static_data_for_all_viewers()
/obj/machinery/computer/operating/proc/find_table()
for(var/direction in GLOB.alldirs)
table = locate(/obj/structure/table/optable) in get_step(src, direction)
if(table)
table.computer = src
break
/obj/machinery/computer/operating/ui_status(mob/user, datum/ui_state/state)
. = ..()
if(isliving(user) && !issilicon(user))
. = min(., ui_check(user))
/// Checks for special ui state conditions
/obj/machinery/computer/operating/proc/ui_check(mob/living/user)
// lower states should be checked first
// normally machines revert to "disabled" when non-operational,
// but here we outright close it so people can't gleam extra info
if(!is_operational)
return UI_CLOSE
// if you're knocked out, ie anesthetic... definitely a no-go
if(user.stat >= UNCONSCIOUS || HAS_TRAIT(user, TRAIT_KNOCKEDOUT))
return UI_CLOSE
// the patient itself should be blocked from viewing the computer
if(user.body_position == LYING_DOWN)
return (user.loc == table?.loc) ? UI_CLOSE : UI_DISABLED
// we have a tight range check so people can't spy on surgeries from across the room
// likewise it'd be pretty lame if you could see what was going on while incapacitated
if(get_dist(user, src) > 2 || user.incapacitated)
return UI_DISABLED
return UI_INTERACTIVE
/obj/machinery/computer/operating/ui_interact(mob/user, datum/tgui/ui)
. = ..()
ui = SStgui.try_update_ui(user, src, ui)
if(ui)
return
if(ishuman(user))
// if you are the first human to open the ui, it changes to your active zone
if(!LAZYLEN(zone_on_open))
target_zone = user.zone_selected
// then we record what you were looking at for when you close it
LAZYSET(zone_on_open, WEAKREF(user), user.zone_selected)
ui = new(user, src, "OperatingComputer", name)
ui.open()
/obj/machinery/computer/operating/ui_close(mob/user)
. = ..()
// reverts zone to whatever you had going into it, typically chest, so you don't have to mess around with it
var/zone_found = LAZYACCESS(zone_on_open, WEAKREF(user))
if(!zone_found)
return
var/atom/movable/screen/zone_sel/selector = user.hud_used?.screen_objects[HUD_MOB_ZONE_SELECTOR]
selector?.set_selected_zone(zone_found, user, FALSE)
LAZYREMOVE(zone_on_open, WEAKREF(user))
if(!LAZYLEN(zone_on_open))
zone_on_open = initial(zone_on_open)
/obj/machinery/computer/operating/ui_assets(mob/user)
. = ..()
. += get_asset_datum(/datum/asset/simple/body_zones)
/obj/machinery/computer/operating/ui_data(mob/user)
var/list/data = list()
data["has_table"] = !!table
data["target_zone"] = target_zone
if(isnull(table?.patient))
return data
data["patient"] = list()
var/mob/living/patient = table.patient
switch(patient.stat)
if(CONSCIOUS)
data["patient"]["stat"] = "Conscious"
data["patient"]["statstate"] = "good"
if(SOFT_CRIT)
data["patient"]["stat"] = "Critical Condition"
data["patient"]["statstate"] = "average"
if(UNCONSCIOUS, HARD_CRIT)
data["patient"]["stat"] = "Unconscious"
data["patient"]["statstate"] = "average"
if(DEAD)
data["patient"]["stat"] = "Dead"
data["patient"]["statstate"] = "bad"
data["patient"]["health"] = patient.health
data["patient"]["blood_type"] = patient.get_bloodtype()?.name || "UNKNOWN"
data["patient"]["maxHealth"] = patient.maxHealth
data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD
data["patient"]["bruteLoss"] = patient.get_brute_loss()
data["patient"]["fireLoss"] = patient.get_fire_loss()
data["patient"]["toxLoss"] = patient.get_tox_loss()
data["patient"]["oxyLoss"] = patient.get_oxy_loss()
data["patient"]["blood_level"] = patient.blood_volume
data["patient"]["standard_blood_level"] = BLOOD_VOLUME_NORMAL
data["patient"]["surgery_state"] = patient.get_surgery_state_as_list(deprecise_zone(target_zone))
return data
/obj/machinery/computer/operating/ui_static_data(mob/user)
var/list/data = list()
data["experiments"] = list()
data["techwebs"] = list()
data["surgeries"] = list()
if(!is_operational)
return data
if(linked_techweb)
data["techwebs"] += list(list(
"web_id" = linked_techweb.id,
"web_org" = linked_techweb.organization,
"selected" = TRUE,
"ref" = REF(linked_techweb),
"all_servers" = linked_techweb.techweb_servers,
))
for(var/datum/experiment/experiment as anything in linked_techweb.available_experiments)
if(istype(experiment, /datum/experiment/autopsy))
data["experiments"] += list(experiment.to_ui_data())
var/list/operations = GLOB.operations.get_instances_from(GLOB.operations.unlocked | advanced_surgeries)
var/any_recommended = FALSE
for(var/datum/surgery_operation/operation as anything in operations)
var/recommend = FALSE
if(table?.patient && operation.show_as_next_step(table.patient, target_zone))
recommend = TRUE
any_recommended = TRUE
data["surgeries"] += list(list(
"name" = operation.rnd_name || operation.name,
"desc" = operation.rnd_desc || operation.desc,
"tool_rec" = operation.get_recommended_tool() || "error",
"requirements" = operation.get_requirements(),
"show_as_next" = recommend,
"show_in_list" = TRUE,
"priority" = operation.operation_flags & OPERATION_PRIORITY_NEXT_STEP,
"mechanic" = operation.operation_flags & OPERATION_MECHANIC,
))
if(!any_recommended && table?.patient && !HAS_TRAIT(table.patient, TRAIT_READY_TO_OPERATE))
data["surgeries"] += list(list(
"name" = "Prepare for surgery",
"desc" = "Begin surgery by applying surgical drapes to the patient or by buckling the patient to the surgical table.",
"tool_rec" = /obj/item/surgical_drapes::name,
"show_as_next" = TRUE,
"show_in_list" = FALSE,
))
return data
/obj/machinery/computer/operating/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("open_experiments")
experiment_handler.ui_interact(usr)
if("change_zone")
if(params["new_zone"] in (GLOB.all_body_zones + GLOB.all_precise_body_zones))
target_zone = params["new_zone"]
var/atom/movable/screen/zone_sel/selector = ui.user.hud_used?.screen_objects[HUD_MOB_ZONE_SELECTOR]
selector?.set_selected_zone(params["new_zone"], ui.user, FALSE)
update_static_data_for_all_viewers()
return TRUE
/obj/machinery/computer/operating/proc/on_techweb_research(datum/source, datum/design/surgery/design)
SIGNAL_HANDLER
if(!istype(design))
return
advanced_surgeries |= design.surgery
update_static_data_batched()
/obj/machinery/computer/operating/proc/on_techweb_unresearch(datum/source, datum/design/surgery/design)
SIGNAL_HANDLER
if(!istype(design))
return
advanced_surgeries -= design.surgery
update_static_data_batched()
/// Updates static data for all viewers after a miniscule delay (to batch multiple updates together)
/obj/machinery/computer/operating/proc/update_static_data_batched(...)
SIGNAL_HANDLER
addtimer(CALLBACK(src, TYPE_PROC_REF(/datum, update_static_data_for_all_viewers)), 0.1 SECONDS, TIMER_UNIQUE)
#undef MENU_OPERATION
#undef MENU_SURGERIES