mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-03 05:21:27 +00:00
* 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>
339 lines
11 KiB
Plaintext
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)
|