diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 786b6d7b184..6573f2eec63 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -126,3 +126,7 @@ // Various computer signals for interrupting via skill or other effects. #define COMSIG_USE_REACTOR_COMPUTER "use_reactor_computer" #define COMSIG_USE_MECH_FAB "use_mech_fab" + +// Surgery Signals +/// Signal raised against the surgeon attempting to perform a surgery to query their components for any rate mods. +#define COMSIG_GET_SURGERY_SUCCESS_MODIFIERS "get_surgery_success_modifiers" diff --git a/code/datums/components/morale/morale_component.dm b/code/datums/components/morale/morale_component.dm index 5e3cbe6a142..610837e1d97 100644 --- a/code/datums/components/morale/morale_component.dm +++ b/code/datums/components/morale/morale_component.dm @@ -51,6 +51,9 @@ */ var/panic_chance_ceiling = 10 + /// The maximum possible positive or negative contribution to surgery success chances from morale modifiers. + var/surgery_success_contribution = 10 + /datum/component/morale/proc/get_morale_ratio() return morale_ratio @@ -96,6 +99,7 @@ RegisterSignal(parent, COMSIG_MECH_MOVE_WASD, PROC_REF(handle_user_move), override = TRUE) RegisterSignal(parent, COMSIG_MECH_MOVE_STRAFE, PROC_REF(handle_user_strafe), override = TRUE) RegisterSignal(parent, COMSIG_MECH_TOGGLE_POWER, PROC_REF(handle_mech_toggle_power), override = TRUE) + RegisterSignal(parent, COMSIG_GET_SURGERY_SUCCESS_MODIFIERS, PROC_REF(handle_surgery_modifiers), override = TRUE) /datum/component/morale/Destroy() QDEL_LIST_FORCE(moodlets) @@ -113,6 +117,7 @@ UnregisterSignal(parent, COMSIG_MECH_MOVE_WASD) UnregisterSignal(parent, COMSIG_MECH_MOVE_STRAFE) UnregisterSignal(parent, COMSIG_MECH_TOGGLE_POWER) + UnregisterSignal(parent, COMSIG_GET_SURGERY_SUCCESS_MODIFIERS) return ..() /datum/component/morale/process(seconds_per_tick) @@ -228,3 +233,7 @@ *delay = *delay - (5 * morale_ratio) SECONDS to_chat(user, SPAN_WARNING("The pressure on your mind causes you to stumble in searching for the power switch...")) + +/datum/component/morale/proc/handle_surgery_modifiers(mob/living/user, success_rate) + SIGNAL_HANDLER + *success_rate = *success_rate + surgery_success_contribution * morale_ratio diff --git a/code/datums/skills/occupational/medical.dm b/code/datums/skills/occupational/medical.dm index edf9f4a4717..c749937dd0c 100644 --- a/code/datums/skills/occupational/medical.dm +++ b/code/datums/skills/occupational/medical.dm @@ -11,6 +11,9 @@ /singleton/skill/surgery name = "Surgery" description = "Governs the user's ability to perform surgical procedures on organic humanoids, as well as what complexity of procedures can be performed. " \ + + "A low rank in this skill causes surgery procedures to have a significantly higher chance to fail, while high ranks improve surgical chances. " \ + + "The more advanced a surgery is, the greater the penalties will be from attempting it unskilled. " \ + + "Having high ranks in this skill can also help offset the penalties from using non-ideal tools in surgery. " \ + "This does not affect \"surgeries\" performed on mechanical prosthetics, robots, or synthetics in general." maximum_level = SKILL_LEVEL_PROFESSIONAL uneducated_skill_cap = SKILL_LEVEL_FAMILIAR // Only the most basic of all surgeries could be bought into, you'll need a real doctor education to do anything more. @@ -20,15 +23,15 @@ required = TRUE skill_level_descriptions = alist( SKILL_LEVEL_UNFAMILIAR = "You have zero training or experience with surgery.
" \ - + " - You cannot perform any surgical procedures on organic humanoids.", + + " - You suffer a large penalty to the chances of successfully performing any surgery on organic humanoids.", SKILL_LEVEL_FAMILIAR = "You have minimal training on the basics of surgery. This is equivalent to a fresh med school graduate, or a military corpsman.
" \ - + "You can perform the following procedures:
" \ + + "You can perform the following procedures without any penalties:
" \ + " - Opening or Closing incisions.
" \ + " - Clamp bleeders.
" \ + " - Fixing Arterial Bleeding
" \ + " - Amputating a limb.", SKILL_LEVEL_TRAINED = "You have years of formal training and experience with surgery. This is equivalent to a fully licensed surgeon.
" \ - + "You can perform the following procedures:
" \ + + "You can perform the following procedures without any penalties:
" \ + " - Opening or Closing incisions.
" \ + " - Clamp bleeders.
" \ + " - Fixing Arterial Bleeding
" \ @@ -40,7 +43,7 @@ + " - Re-attach (organic) limbs. Robotic limbs require the Robotics skill instead.
" \ + " - Repair non-necrotic organs other than the brain, or mechanical prosthetics.
", SKILL_LEVEL_PROFESSIONAL = "You are a world class surgeon with decades worth of training and experience.
" \ - + "You can perform the following procedures:
" \ + + "You can perform the following procedures without any penalties:
" \ + " - Opening or Closing incisions.
" \ + " - Clamp bleeders.
" \ + " - Fixing Arterial Bleeding
" \ diff --git a/code/datums/skills/occupational/operations.dm b/code/datums/skills/occupational/operations.dm index 71728bbfc7c..120328024c7 100644 --- a/code/datums/skills/occupational/operations.dm +++ b/code/datums/skills/occupational/operations.dm @@ -1,6 +1,10 @@ /singleton/skill/robotics name = "Robotics" - description = "Governs the user's ability to perform surgical procedures on synthetics, as well as the complexity of what procedures can be performed." + description = "Governs the user's ability to perform surgical procedures on synthetics, as well as the complexity of what procedures can be performed. " \ + + "A low rank in this skill causes surgery procedures to have a significantly higher chance to fail, while high ranks improve surgical chances. " \ + + "The more advanced a surgery is, the greater the penalties will be from attempting it unskilled. " \ + + "Having high ranks in this skill can also help offset the penalties from using non-ideal tools in surgery. " \ + + "This does not affect \"surgeries\" performed on organics." maximum_level = SKILL_LEVEL_PROFESSIONAL uneducated_skill_cap = SKILL_LEVEL_FAMILIAR category = /singleton/skill_category/occupational @@ -9,15 +13,15 @@ required = TRUE skill_level_descriptions = alist( SKILL_LEVEL_UNFAMILIAR = "You have zero training or experience with synthetics.
" \ - + " - You cannot perform any surgical procedures on synthetics.", + + " - You suffer a large penalty to the chances of successfully performing any surgery on synthetics.", SKILL_LEVEL_FAMILIAR = "You have minimal training on the basics of synthetic repair and maintenance. This could be the level of a hobbyist, or someone currently pursuing a degree in robotics.
" \ - + "You can perform the following procedures:
" \ + + "You can perform the following procedures without penalties:
" \ + " - Opening or closing external maintenance panels to make superficial repairs.
" \ + " - Repairing basic damage with a welder or cables.
" \ + " - Repairing external damage to mechanical limbs.
" \ + " - Cutting someone out of a hardsuit.", SKILL_LEVEL_TRAINED = "You have years of formal training or experience on repairing and maintaining synthetics equivalent to a Bachelor's degree in Robotics.
" \ - + "You can perform the following procedures:
" \ + + "You can perform the following procedures without penalties:
" \ + " - Opening or closing external maintenance panels to make superficial repairs.
" \ + " - Repairing basic damage with a welder or cables.
" \ + " - Repairing external damage to mechanical limbs.
" \ @@ -30,7 +34,7 @@ + " - Perform all forms of internal repairs to IPCs.
" \ + " - Prepare an MMI for cyborgification.", SKILL_LEVEL_PROFESSIONAL = "Not currently implemented, functions exactly as per Trained.
" \ - + "You can perform the following procedures:
" \ + + "You can perform the following procedures without penalties:
" \ + " - Opening or closing external maintenance panels to make superficial repairs.
" \ + " - Repairing basic damage with a welder or cables.
" \ + " - Repairing external damage to mechanical limbs.
" \ diff --git a/code/modules/surgery/_defines.dm b/code/modules/surgery/_defines.dm index 40f5f0b7caf..991640e29e1 100644 --- a/code/modules/surgery/_defines.dm +++ b/code/modules/surgery/_defines.dm @@ -24,3 +24,10 @@ //macros #define IS_ORGAN_FULLY_OPEN affected.open == ((affected.encased || affected.robotic) ? ORGAN_ENCASED_RETRACTED : ORGAN_OPEN_RETRACTED) + +//skill difficulty ratings +#define SURGERY_DIFFICULTY_TRIVIAL 10 +#define SURGERY_DIFFICULTY_EASY 15 +#define SURGERY_DIFFICULTY_MEDIUM 20 +#define SURGERY_DIFFICULTY_HARD 25 +#define SURGERY_DIFFICULTY_EXTREME 33 diff --git a/code/modules/surgery/facial_surgery.dm b/code/modules/surgery/facial_surgery.dm index 430ea28999f..0a66b81d69d 100644 --- a/code/modules/surgery/facial_surgery.dm +++ b/code/modules/surgery/facial_surgery.dm @@ -27,6 +27,7 @@ min_duration = 70 max_duration = 90 skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_MEDIUM /singleton/surgery_step/generic/prepare_face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) return ..() && target_zone == BP_MOUTH && target.op_stage.face == FACE_CUT_OPEN @@ -60,6 +61,7 @@ min_duration = 30 max_duration = 70 skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_EXTREME /singleton/surgery_step/generic/alter_face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) return ..() && target_zone == BP_MOUTH && target.op_stage.face == FACE_RETRACTED @@ -108,6 +110,7 @@ min_duration = 50 max_duration = 80 skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_MEDIUM /singleton/surgery_step/face/cauterize/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) return ..() && target.op_stage.face > FACE_NORMAL @@ -152,6 +155,7 @@ min_duration = 70 max_duration = 90 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_MEDIUM /singleton/surgery_step/robotics/face/synthskinopen/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) return ..() && target.op_stage.face == FACE_NORMAL && target.get_species() == SPECIES_IPC_SHELL @@ -181,6 +185,7 @@ min_duration = 70 max_duration = 90 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_MEDIUM /singleton/surgery_step/robotics/face/prepare_face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) return ..() && target_zone == BP_MOUTH && target.op_stage.face == FACE_CUT_OPEN @@ -211,6 +216,7 @@ min_duration = 30 max_duration = 70 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_EXTREME /singleton/surgery_step/robotics/face/alter_synthface/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) return ..() && target_zone == BP_MOUTH && target.op_stage.face == FACE_RETRACTED @@ -257,6 +263,7 @@ min_duration = 50 max_duration = 80 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_MEDIUM /singleton/surgery_step/robotics/face/seal_face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) return ..() && target.op_stage.face > FACE_NORMAL diff --git a/code/modules/surgery/organs_internal.dm b/code/modules/surgery/organs_internal.dm index bafe2f987a0..86a0a47a883 100644 --- a/code/modules/surgery/organs_internal.dm +++ b/code/modules/surgery/organs_internal.dm @@ -4,6 +4,7 @@ can_infect = TRUE blood_level = 1 skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_EXTREME /singleton/surgery_step/internal/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) diff --git a/code/modules/surgery/other.dm b/code/modules/surgery/other.dm index 768c582b8a0..e7141051a38 100644 --- a/code/modules/surgery/other.dm +++ b/code/modules/surgery/other.dm @@ -17,6 +17,7 @@ min_duration = 40 max_duration = 60 skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_HARD /singleton/surgery_step/fix_vein/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) @@ -67,6 +68,7 @@ min_duration = 80 max_duration = 130 skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_PROFESSIONAL) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_EXTREME /singleton/surgery_step/internal/fix_dead_tissue/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) @@ -133,6 +135,7 @@ min_duration = 80 max_duration = 90 skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_PROFESSIONAL) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_EXTREME /singleton/surgery_step/treat_necrosis/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) @@ -200,6 +203,7 @@ min_duration = 50 max_duration = 70 skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_MEDIUM /singleton/surgery_step/fix_tendon/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) @@ -241,7 +245,8 @@ min_duration = 100 max_duration = 160 - skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR) + skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_TRIVIAL /singleton/surgery_step/hardsuit/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) diff --git a/code/modules/surgery/robotics.dm b/code/modules/surgery/robotics.dm index 23a772295b5..258a519542d 100644 --- a/code/modules/surgery/robotics.dm +++ b/code/modules/surgery/robotics.dm @@ -473,6 +473,7 @@ min_duration = 50 max_duration = 70 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_MEDIUM /singleton/surgery_step/internal/fix_internal_wiring/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) @@ -523,6 +524,7 @@ min_duration = 100 max_duration = 200 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_HARD /singleton/surgery_step/internal/fix_internal_electronics/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) @@ -564,6 +566,7 @@ min_duration = 75 max_duration = 120 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_HARD /singleton/surgery_step/internal/fix_internal_plating/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) @@ -616,6 +619,7 @@ min_duration = 100 max_duration = 150 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_HARD /singleton/surgery_step/internal/replace_internal_plating/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) @@ -662,6 +666,7 @@ var/fast_repair = FALSE skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_MEDIUM /singleton/surgery_step/internal/replace_external_plating/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) @@ -703,6 +708,7 @@ min_duration = 100 max_duration = 150 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_TRIVIAL /singleton/surgery_step/internal/replace_external_plating/g2 name = "Replace G2 External Armour Plating" @@ -755,6 +761,7 @@ min_duration = 15 max_duration = 25 skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED) + skill_diff_fail_modifier = SURGERY_DIFFICULTY_MEDIUM /singleton/surgery_step/robotics/repair_endoskeleton/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) if(!..()) diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index 83e3699e0ee..5d4d4550e6c 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -21,9 +21,17 @@ var/requires_surgery_compatibility = TRUE - /// The associative list of skills and their paired requirement levels to be able to perform a given surgery. + /** + * The associative list of skills and their paired requirement levels to be able to perform a given surgery. + * These are considered soft requirements, so if a surgery requires skill level 3 in something, and you're at level 1 + * Then the surgery will take a penalty of twice the skill_diff_fail_modifier (30% by default). + * Exceeding the skill requirement can also offset having lower success rates from things like tools. + */ var/alist/skill_requirements + /// The bonus (or penalty) fail rate to a surgery per point of skill diff. As a percent chance. + var/skill_diff_fail_modifier = SURGERY_DIFFICULTY_EASY + /// Returns how well tool is suited for this step. /singleton/surgery_step/proc/tool_quality(obj/item/tool) for(var/T in allowed_tools) @@ -131,14 +139,6 @@ if(user.client) // In case of future autodocs. S = tgui_input_list(user, "Which surgery would you like to perform?", "Surgery", possible_surgeries) - // Check via skill components if the user knows how to perform the surgery. - for (var/skill_comp, required_level in S.skill_requirements) - var/skill_level = GET_SKILL_LEVEL(user, skill_comp) - // Null condition handles NPCs and Antags that won't have the skill setup. - if (!isnull(skill_level) && skill_level < required_level) - to_chat(user, SPAN_WARNING("You lack the skills needed to perform this surgical procedure.")) - return TRUE - // We didn't find a surgery, or decided not to perform one. if(!istype(S)) if(tool.item_flags & ITEM_FLAG_SURGERY) //Is this supposed to be used for surgery? @@ -155,7 +155,23 @@ M.op_stage.in_progress += list(zone = user) S.begin_step(user, M, zone, tool) var/duration = rand(S.min_duration, S.max_duration) - if(prob(S.tool_quality(tool)) && do_mob(user, M, duration) && !autofail) + + // Get the base surgery success rate based on tools. + // This should eventually be reworked to use ToolQualityComponents when we add that. + var/success_rate = S.tool_quality(tool) + + // Query the surgeon if they have any components that would like to modify the success chance. + SEND_SIGNAL(user, COMSIG_GET_SURGERY_SUCCESS_MODIFIERS, &success_rate) + + // Skill modifier checks + for (var/skill_comp, required_level in S.skill_requirements) + var/skill_level = GET_SKILL_LEVEL(user, skill_comp) + // Null condition handles NPCs and Antags that won't have the skill setup. + if (!isnull(skill_level)) + success_rate += (skill_level - required_level) * S.skill_diff_fail_modifier + // End of skill modifier checks + + if(prob(success_rate) && do_mob(user, M, duration) && !autofail) S.end_step(user, M, zone, tool) else if ((tool in user.contents) && user.Adjacent(M)) S.fail_step(user, M, zone, tool) diff --git a/html/changelogs/hellfirejag - surgery skill reworks.yml b/html/changelogs/hellfirejag - surgery skill reworks.yml new file mode 100644 index 00000000000..6baa697f4a5 --- /dev/null +++ b/html/changelogs/hellfirejag - surgery skill reworks.yml @@ -0,0 +1,5 @@ +author: Hellfirejag +delete-after: True +changes: + - rscadd: "Reworked surgery to use soft requirements and signals to directly modify the success chances of surgery. These penalties or bonuses stack with those from not having ideal tools." + - rscadd: "Morale modifiers now affect surgery success rates."