mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-27 23:14:18 +01:00
Surgery Soft Requirement Refactor (#22314)
Skills being based on "Hard Requirements" absolutely suck, and I wasn't happy with the surgery code being essentially hardcoded around them. So this PR refactors surgery to use soft requirement checks, based on modifiers to the success rate probability. Surgeries still state what skills (if any) they require and at what level, but rather than blocking the surgery, it instead imposes a penalty(or bonus) on the success chance based on how much the user's surgical skill differs from the required level. This actually stacks with the penalties for "non ideal tools", so if you're completely unskilled in surgery and you attempt to make an incision with a shard of glass, you're going to have an extremely hard time. Individual surgeries also can define how much the success rate is modified by "skill diff", with the more advanced "surgeon exclusive" surgeries having extremely large potential modifiers. I have also added a signal based check for modifiers, which the Morale component now hooks into. This can also allow for other components, implants, drugs, etc to modify the surgery chances in the future. I have manually tested this PR and verified by examining debug breakpoints in VSCode that the system correctly applies its modifiers.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(!..())
|
||||
|
||||
@@ -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(!..())
|
||||
|
||||
@@ -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(!..())
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user