Files
Bubberstation/code/datums/components/surgery_initiator.dm
SkyratBot 07c88ffb0a [MIRROR] Add new surgery tgui that provides a built in body zone selector and doesn't let you start surgeries if their body is covered. Replaces some stuff with balloon alerts [MDB IGNORE] (#11322)
* Add new surgery tgui (#64579)

* Move element to component, start UI, move assets into their own directory

* Complete UI

* Stop when another surgery is started

* Set your real zone since I forgot you actually need to start the surgery too

* Bring this back since I was just removing it as part of a cleanup for asset cache, but I can't prove it's not used anymore

* Remove unnecessary constructor I was using for something else

* Fix signal override

* Add new surgery tgui that provides a built in body zone selector and doesn't let you start surgeries if their body is covered. Replaces some stuff with balloon alerts

Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
2022-02-06 20:37:56 +00:00

339 lines
11 KiB
Plaintext

/// Allows an item to be used to initiate surgeries.
/datum/component/surgery_initiator
/// The currently selected target that the user is proposing a surgery on
var/datum/weakref/surgery_target_ref
/// The last user, as a weakref
var/datum/weakref/last_user_ref
/datum/component/surgery_initiator/Initialize()
. = ..()
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
/datum/component/surgery_initiator/Destroy(force, silent)
last_user_ref = null
surgery_target_ref = null
return ..()
/datum/component/surgery_initiator/RegisterWithParent()
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/initiate_surgery_moment)
/datum/component/surgery_initiator/UnregisterFromParent()
UnregisterSignal(parent, COMSIG_ITEM_ATTACK)
unregister_signals()
/datum/component/surgery_initiator/proc/unregister_signals()
var/mob/living/last_user = last_user_ref?.resolve()
if (!isnull(last_user_ref))
UnregisterSignal(last_user, COMSIG_MOB_SELECTED_ZONE_SET)
var/mob/living/surgery_target = surgery_target_ref?.resolve()
if (!isnull(surgery_target_ref))
UnregisterSignal(surgery_target, COMSIG_MOB_SURGERY_STARTED)
/// Does the surgery initiation.
/datum/component/surgery_initiator/proc/initiate_surgery_moment(datum/source, atom/target, mob/user)
SIGNAL_HANDLER
if(!isliving(target))
return
INVOKE_ASYNC(src, .proc/do_initiate_surgery_moment, target, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/component/surgery_initiator/proc/do_initiate_surgery_moment(mob/living/target, mob/user)
var/datum/surgery/current_surgery
for(var/i_one in target.surgeries)
var/datum/surgery/surgeryloop = i_one
if(surgeryloop.location == user.zone_selected)
current_surgery = surgeryloop
break
if (!isnull(current_surgery) && !current_surgery.step_in_progress)
attempt_cancel_surgery(current_surgery, target, user)
return
var/list/available_surgeries = get_available_surgeries(user, target)
if(!length(available_surgeries))
if (target.body_position == LYING_DOWN)
target.balloon_alert(user, "no surgeries available!")
else
target.balloon_alert(user, "make them lie down!")
return
unregister_signals()
last_user_ref = WEAKREF(user)
surgery_target_ref = WEAKREF(target)
RegisterSignal(user, COMSIG_MOB_SELECTED_ZONE_SET, .proc/on_set_selected_zone)
RegisterSignal(target, COMSIG_MOB_SURGERY_STARTED, .proc/on_mob_surgery_started)
ui_interact(user)
/datum/component/surgery_initiator/proc/get_available_surgeries(mob/user, mob/living/target)
var/list/available_surgeries = list()
var/mob/living/carbon/carbon_target
var/obj/item/bodypart/affecting
if (iscarbon(target))
carbon_target = target
affecting = carbon_target.get_bodypart(check_zone(user.zone_selected))
for(var/datum/surgery/surgery as anything in GLOB.surgeries_list)
if(!surgery.possible_locs.Find(user.zone_selected))
continue
if(affecting)
if(!surgery.requires_bodypart)
continue
if(surgery.requires_bodypart_type && affecting.status != surgery.requires_bodypart_type)
continue
if(surgery.requires_real_bodypart && affecting.is_pseudopart)
continue
else if(carbon_target && surgery.requires_bodypart) //mob with no limb in surgery zone when we need a limb
continue
if(surgery.lying_required && target.body_position != LYING_DOWN)
continue
if(!surgery.can_start(user, target))
continue
for(var/path in surgery.target_mobtypes)
if(istype(target, path))
available_surgeries += surgery
break
return available_surgeries
/// Does the surgery de-initiation.
/datum/component/surgery_initiator/proc/attempt_cancel_surgery(datum/surgery/the_surgery, mob/living/patient, mob/user)
var/selected_zone = user.zone_selected
if(the_surgery.status == 1)
patient.surgeries -= the_surgery
REMOVE_TRAIT(patient, TRAIT_ALLOWED_HONORBOUND_ATTACK, type)
user.visible_message(
span_notice("[user] removes [parent] from [patient]'s [parse_zone(selected_zone)]."),
span_notice("You remove [parent] from [patient]'s [parse_zone(selected_zone)]."),
)
patient.balloon_alert(user, "stopped work on [parse_zone(selected_zone)]")
qdel(the_surgery)
return
if(!the_surgery.can_cancel)
return
var/required_tool_type = TOOL_CAUTERY
var/obj/item/close_tool = user.get_inactive_held_item()
var/is_robotic = the_surgery.requires_bodypart_type == BODYPART_ROBOTIC
if(is_robotic)
required_tool_type = TOOL_SCREWDRIVER
if(iscyborg(user))
close_tool = locate(/obj/item/cautery) in user.held_items
if(!close_tool)
patient.balloon_alert(user, "need a cautery in an inactive slot to stop the surgery!")
return
else if(!close_tool || close_tool.tool_behaviour != required_tool_type)
patient.balloon_alert(user, "need a [is_robotic ? "screwdriver": "cautery"] in your inactive hand to stop the surgery!")
return
if(the_surgery.operated_bodypart)
the_surgery.operated_bodypart.generic_bleedstacks -= 5
patient.surgeries -= the_surgery
REMOVE_TRAIT(patient, TRAIT_ALLOWED_HONORBOUND_ATTACK, ELEMENT_TRAIT(type))
user.visible_message(
span_notice("[user] closes [patient]'s [parse_zone(selected_zone)] with [close_tool] and removes [parent]."),
span_notice("You close [patient]'s [parse_zone(selected_zone)] with [close_tool] and remove [parent]."),
)
patient.balloon_alert(user, "closed up [parse_zone(selected_zone)]")
qdel(the_surgery)
/datum/component/surgery_initiator/proc/on_mob_surgery_started(mob/source, datum/surgery/surgery, surgery_location)
SIGNAL_HANDLER
var/mob/living/last_user = last_user_ref.resolve()
if (surgery_location != last_user.zone_selected)
return
if (!isnull(last_user))
source.balloon_alert(last_user, "someone else started a surgery!")
ui_close()
/datum/component/surgery_initiator/proc/on_set_selected_zone(mob/source, new_zone)
ui_interact(source)
/datum/component/surgery_initiator/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, "SurgeryInitiator")
ui.open()
/datum/component/surgery_initiator/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if (.)
return .
var/mob/user = usr
var/mob/living/surgery_target = surgery_target_ref.resolve()
if (isnull(surgery_target))
return TRUE
switch (action)
if ("change_zone")
var/zone = params["new_zone"]
if (!(zone in list(
BODY_ZONE_HEAD,
BODY_ZONE_CHEST,
BODY_ZONE_L_ARM,
BODY_ZONE_R_ARM,
BODY_ZONE_L_LEG,
BODY_ZONE_R_LEG,
BODY_ZONE_PRECISE_EYES,
BODY_ZONE_PRECISE_MOUTH,
BODY_ZONE_PRECISE_GROIN,
)))
return TRUE
var/atom/movable/screen/zone_sel/zone_selector = user.hud_used?.zone_select
zone_selector?.set_selected_zone(zone, user)
return TRUE
if ("start_surgery")
for (var/datum/surgery/surgery as anything in get_available_surgeries(user, surgery_target))
if (surgery.name == params["surgery_name"])
try_choose_surgery(user, surgery_target, surgery)
return TRUE
/datum/component/surgery_initiator/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/simple/body_zones),
)
/datum/component/surgery_initiator/ui_data(mob/user)
var/mob/living/surgery_target = surgery_target_ref.resolve()
var/list/surgeries = list()
if (!isnull(surgery_target))
for (var/datum/surgery/surgery as anything in get_available_surgeries(user, surgery_target))
var/list/surgery_info = list(
"name" = surgery.name,
)
if (surgery_needs_exposure(surgery, surgery_target))
surgery_info["blocked"] = TRUE
surgeries += list(surgery_info)
return list(
"selected_zone" = user.zone_selected,
"target_name" = surgery_target?.name,
"surgeries" = surgeries,
)
/datum/component/surgery_initiator/ui_close(mob/user)
unregister_signals()
surgery_target_ref = null
return ..()
/datum/component/surgery_initiator/ui_status(mob/user, datum/ui_state/state)
var/obj/item/item_parent = parent
if (user != item_parent.loc)
return UI_CLOSE
var/mob/living/surgery_target = surgery_target_ref?.resolve()
if (isnull(surgery_target))
return UI_CLOSE
if (!can_start_surgery(user, surgery_target))
return UI_CLOSE
return ..()
/datum/component/surgery_initiator/proc/can_start_surgery(mob/user, mob/living/target)
if (!user.Adjacent(target))
return FALSE
// The item was moved somewhere else
if (!(parent in user))
return FALSE
// While we were choosing, another surgery was started at the same location
for (var/datum/surgery/surgery in target.surgeries)
if (surgery.location == user.zone_selected)
return FALSE
return TRUE
/datum/component/surgery_initiator/proc/try_choose_surgery(mob/user, mob/living/target, datum/surgery/surgery)
if (!can_start_surgery(user, target))
// This could have a more detailed message, but the UI closes when this is true anyway, so
// if it ever comes up, it'll be because of lag.
target.balloon_alert(user, "can't start the surgery!")
return
var/obj/item/bodypart/affecting_limb
var/selected_zone = user.zone_selected
if (iscarbon(target))
var/mob/living/carbon/carbon_target = target
affecting_limb = carbon_target.get_bodypart(check_zone(selected_zone))
if (surgery.requires_bodypart == isnull(affecting_limb))
if (surgery.requires_bodypart)
target.balloon_alert(user, "patient has no [parse_zone(selected_zone)]!")
else
target.balloon_alert(user, "patient has \a [parse_zone(selected_zone)]!")
return
if (!isnull(affecting_limb) && surgery.requires_bodypart_type && affecting_limb.status != surgery.requires_bodypart_type)
target.balloon_alert(user, "not the right type of limb!")
return
if (surgery.lying_required && target.body_position != LYING_DOWN)
target.balloon_alert(user, "patient is not lying down!")
return
if (!surgery.can_start(user, target))
target.balloon_alert(user, "can't start the surgery!")
return
if (surgery_needs_exposure(surgery, target))
target.balloon_alert(user, "expose [target.p_their()] [parse_zone(selected_zone)]!")
return
ui_close()
var/datum/surgery/procedure = new surgery.type(target, selected_zone, affecting_limb)
ADD_TRAIT(target, TRAIT_ALLOWED_HONORBOUND_ATTACK, type)
target.balloon_alert(user, "starting \"[lowertext(procedure.name)]\"")
user.visible_message(
span_notice("[user] drapes [parent] over [target]'s [parse_zone(selected_zone)] to prepare for surgery."),
span_notice("You drape [parent] over [target]'s [parse_zone(selected_zone)] to prepare for \an [procedure.name]."),
)
log_combat(user, target, "operated on", null, "(OPERATION TYPE: [procedure.name]) (TARGET AREA: [selected_zone])")
/datum/component/surgery_initiator/proc/surgery_needs_exposure(datum/surgery/surgery, mob/living/target)
var/mob/living/user = last_user_ref?.resolve()
if (isnull(user))
return FALSE
return !surgery.ignore_clothes && !get_location_accessible(target, user.zone_selected)