Files
Bubberstation/code/game/machinery/computer/operating_computer.dm
Aliceee2chandGitHub c7408ce841 Mapping previews tool for mapper's mapping needs (#96872)
## About The Pull Request

Makes more machinery using MAP_SWITCHes so their fancy preview icons
could be displayed in SDMM. MAP_SWITCH takes original icon+icon_state of
machinery as first argument and icon+icon_state (basically a machinery
part because the naming system, like `MAP_SWITCH("computer",
"/obj/machinery/computer/slot_machine")` ) from
icons/obj/fluff/map_previews.dmi

Adds a tool (subsystem) for that need that generates preview icons for
stuff that has overlays. To include them in preview generating you have
to set specific variable to TRUE.


## Why It's Good For The Game

Lets us do this instead of black boring consoles: 
(before)
<img width="641" height="575" alt="image"
src="https://github.com/user-attachments/assets/35fc5a19-bc24-4c02-957a-43ddef86e684"
/>
(after)
<img width="641" height="575" alt="image"
src="https://github.com/user-attachments/assets/99310823-747f-4e0a-acf8-60abcc8f2ed1"
/>


## Changelog

🆑
qol: Made machinery with overlays actually be properly displayed in SDMM
editor.
/🆑
2026-07-15 12:48:22 +00:00

292 lines
11 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_state = MAP_SWITCH("computer", "/obj/machinery/computer/crew")
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