From da64374423593ccb71a1aace5d9ef7af19825287 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:32:41 -0500 Subject: [PATCH] Reverts "Add prosthetic limb" surgery to involve targeting limbs, rather than targeting chest. (Adds stumps) (#95252) ## About The Pull Request - The `prosthetic replacement` surgical operation has been reverted to be closer to how it used to work: The operation is done targeting the limb that's missing The change was made out of necessity, as surgical state was tied to limbs - you had to operate on the chest to re-attach limbs because there was no limb to operate on. To circumvent that, I have done the unthinkable of adding stumps when you are dismembered. - Missing limbs are now represented as an invisible, un-removable, un-interactable limb. Making this change was not as difficult as originally anticipated, and (at least surface level) seems to have broken very little. Surprisingly little had to change to make this work. Direct accesses to `mob.bodyparts` was changed to `mob.get_bodyparts()` with an optional `include_stumps` argument. Similarly, `get_bodypart()` had an optional `include_stumps` added. This means we ultimately barely needed to change anything, and in fact, some loops/checks were able to be streamlined. ## Why It's Good For The Game - As mentioned, this change was out of necessity and was easily the least intuitive part of the broader changes. Reverting it back to how it used to work should make it far easier for people to pick up on, and means we can cut out a bunch of bespoke instruction sets that I had to include. - The addition of stumps also adds a ton of future potential - code wise it allows for stuff like better damage tracking (we can transfer damage between limb <-> stump rather than limb <-> chest), and feature we can do "fun" stuff like have stumps bleed on dismemberment that you can bandage. ## Changelog :cl: Melbert del: "Add prosthetic limb" surgical operation has been reverted to be a bit closer to how it used to work - you operate on the missing limb / limb stump, rather than on the chest. refactor: Missing limbs are now represented as limb stumps. In practice this should change nothing (for now), as no features were rewritten to make use of these besides surgery. Please report any oddities with missing limbs, however. /:cl: --- code/__DEFINES/combat.dm | 7 +- code/__DEFINES/surgery.dm | 5 ++ code/__HELPERS/cmp.dm | 2 +- code/_onclick/hud/screen_objects.dm | 18 ++-- code/_onclick/item_attack.dm | 2 +- code/controllers/subsystem/radiation.dm | 2 +- .../texture_bodypart_overlay.dm | 3 +- code/datums/components/amputating_limbs.dm | 2 +- code/datums/components/bloodysoles.dm | 2 +- code/datums/components/butchering.dm | 4 +- code/datums/components/free_operation.dm | 8 +- code/datums/components/healing_touch.dm | 2 +- .../diseases/advance/symptoms/flesh_eating.dm | 2 +- code/datums/elements/blood_limb_overlay.dm | 6 +- code/datums/elements/organ_set_bonus.dm | 4 +- code/datums/elements/spooky.dm | 2 +- code/datums/embedding.dm | 4 +- code/datums/mutations/autotomy.dm | 2 +- code/datums/mutations/hulk.dm | 4 +- code/datums/mutations/touch.dm | 4 +- .../quirks/negative_quirks/body_purist.dm | 2 +- .../quirks/neutral_quirks/transhumanist.dm | 2 +- .../status_effects/debuffs/rust_corruption.dm | 2 +- code/datums/voice_of_god_command.dm | 2 +- .../machinery/computer/operating_computer.dm | 35 ++------ .../dna_infuser/organ_sets/fish_organs.dm | 4 +- .../experimental_cloning_record.dm | 2 +- code/game/machinery/harvester.dm | 2 +- code/game/objects/items.dm | 2 +- .../items/devices/scanners/health_analyzer.dm | 2 +- .../stacks/golem_food/golem_status_effects.dm | 6 +- code/game/objects/items/stacks/medical.dm | 4 +- .../objects/items/weaponry/melee/sabre.dm | 2 +- code/game/objects/items/wiki_manuals.dm | 2 +- code/game/turfs/open/lava.dm | 2 +- code/modules/admin/smites/berforate.dm | 2 +- code/modules/admin/smites/bloodless.dm | 2 +- code/modules/admin/smites/boneless.dm | 2 +- code/modules/admin/smites/nugget.dm | 2 +- code/modules/antagonists/abductor/abductor.dm | 2 +- .../antagonists/changeling/changeling.dm | 2 +- .../changeling/powers/defib_grasp.dm | 2 +- code/modules/antagonists/cult/cult_armor.dm | 2 +- .../modules/antagonists/heretic/influences.dm | 2 +- .../heretic/items/heretic_armor.dm | 6 +- .../heretic/items/heretic_grenade.dm | 2 +- .../heretic/knowledge/flesh_lore.dm | 2 +- .../heretic/knowledge/moon_lore.dm | 2 +- .../heretic/knowledge/rust_lore.dm | 2 +- .../knowledge/side_knowledge/tier_two.dm | 4 +- .../heretic/knowledge/starting_lore.dm | 2 +- .../heretic/magic/apetravulnera.dm | 4 +- .../antagonists/heretic/magic/blood_cleave.dm | 2 +- .../antagonists/heretic/magic/blood_siphon.dm | 4 +- .../heretic/magic/crimson_cleave.dm | 2 +- .../heretic/status_effects/buffs.dm | 4 +- .../heretic/status_effects/heretic_passive.dm | 4 +- .../heretic/status_effects/mark_effects.dm | 2 +- code/modules/antagonists/spy/spy_bounty.dm | 2 +- .../voidwalker/voidwalker_traumas.dm | 4 +- .../grand_ritual/finales/immortality.dm | 2 +- code/modules/cargo/supplypod.dm | 4 +- .../preferences/species_features/lizard.dm | 2 +- .../experisci/experiment/experiments.dm | 4 +- .../modules/fishing/fish/types/holographic.dm | 2 +- code/modules/fishing/fish/types/ruins.dm | 2 +- .../food_and_drinks/machinery/gibber.dm | 2 +- code/modules/hallucination/blood_flow.dm | 4 +- .../hallucination/screwy_health_doll.dm | 2 +- .../jobs/job_types/head_of_security.dm | 2 +- code/modules/jobs/job_types/prisoner.dm | 2 +- .../jobs/job_types/security_officer.dm | 2 +- code/modules/jobs/job_types/warden.dm | 2 +- code/modules/library/bibles.dm | 2 +- code/modules/lost_crew/damages/post_mortem.dm | 6 +- code/modules/lost_crew/lost_crew_manager.dm | 3 + code/modules/mob/inventory.dm | 1 + .../living/basic/cult/constructs/harvester.dm | 2 +- .../living/basic/farm_animals/goat/_goat.dm | 2 +- .../mob/living/basic/ruin_defender/flesh.dm | 2 +- .../living/basic/space_fauna/lightgeist.dm | 2 +- code/modules/mob/living/blood.dm | 8 +- code/modules/mob/living/carbon/carbon.dm | 59 +++++++----- .../mob/living/carbon/carbon_defense.dm | 5 +- .../mob/living/carbon/carbon_update_icons.dm | 90 ++++++++++--------- .../modules/mob/living/carbon/damage_procs.dm | 19 ++-- code/modules/mob/living/carbon/death.dm | 21 +++-- code/modules/mob/living/carbon/examine.dm | 12 +-- .../mob/living/carbon/human/_species.dm | 8 +- code/modules/mob/living/carbon/human/human.dm | 6 +- .../mob/living/carbon/human/human_defense.dm | 17 ++-- .../living/carbon/human/human_update_icons.dm | 2 +- .../mob/living/carbon/human/inventory.dm | 2 + .../carbon/human/species_types/ethereal.dm | 2 +- code/modules/mob/living/carbon/life.dm | 2 +- code/modules/mob/living/death.dm | 7 ++ code/modules/mob/living/living.dm | 4 +- code/modules/mob/mob_helpers.dm | 2 +- .../projectiles/ammunition/ballistic/rifle.dm | 2 +- code/modules/projectiles/guns/magic/staff.dm | 2 +- .../impure_medicine_reagents.dm | 10 +-- .../chemistry/reagents/other_reagents.dm | 2 +- .../reagents/reagent_containers/patch.dm | 2 +- .../religion/festival/instrument_rites.dm | 2 +- code/modules/religion/religion_sects.dm | 6 +- .../religion/sparring/sparring_datum.dm | 2 +- .../xenobiology/crossbreeding/_misc.dm | 9 +- .../spells/spell_types/touch/scream_for_me.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 24 ++--- .../surgery/bodyparts/dismemberment.dm | 11 ++- code/modules/surgery/bodyparts/head.dm | 1 + code/modules/surgery/bodyparts/helpers.dm | 71 +++++++++++---- code/modules/surgery/bodyparts/parts.dm | 6 ++ .../surgery/bodyparts/robot_bodyparts.dm | 2 +- .../bodyparts/species_parts/misc_bodyparts.dm | 1 + code/modules/surgery/bodyparts/stumps.dm | 72 +++++++++++++++ code/modules/surgery/operations/_operation.dm | 5 +- .../surgery/operations/operation_add_limb.dm | 88 +++++++----------- .../surgery/operations/operation_generic.dm | 9 ++ .../operations/operation_generic_mechanic.dm | 10 ++- code/modules/surgery/surgery_tools.dm | 2 +- code/modules/unit_tests/limbsanity.dm | 5 +- .../unit_tests/organ_bodypart_shuffle.dm | 21 ++++- code/modules/unit_tests/spawn_humans.dm | 20 +++++ code/modules/unit_tests/surgeries.dm | 4 +- code/modules/vehicles/cars/clowncar.dm | 18 ++-- code/modules/vending/vendor/tilting.dm | 2 +- tgstation.dme | 1 + 128 files changed, 566 insertions(+), 386 deletions(-) create mode 100644 code/modules/surgery/bodyparts/stumps.dm diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 878ed53f990..c4e4b40b56c 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -293,10 +293,11 @@ DEFINE_BITFIELD(ammo_box_multiload, list( #define BODY_ZONE_PRECISE_L_FOOT "l_foot" #define BODY_ZONE_PRECISE_R_FOOT "r_foot" -GLOBAL_LIST_INIT(all_body_zones, list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) -GLOBAL_LIST_INIT(limb_zones, list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) +// These lists are ordered as bodyparts would be ordered +GLOBAL_LIST_INIT(all_body_zones, list(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) +GLOBAL_LIST_INIT(limb_zones, list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) -GLOBAL_LIST_INIT(leg_zones, list(BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) +GLOBAL_LIST_INIT(leg_zones, list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) GLOBAL_LIST_INIT(all_precise_body_zones, list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_GROIN, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_PRECISE_R_FOOT)) //We will round to this value in damage calculations. diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm index ab8fee7c9a0..fcf8b853cad 100644 --- a/code/__DEFINES/surgery.dm +++ b/code/__DEFINES/surgery.dm @@ -54,6 +54,9 @@ /// Helper to figure out if a limb is a peg limb #define IS_PEG_LIMB(limb) (limb.bodytype & BODYTYPE_PEG) +/// Is the bodypart a stump +#define IS_STUMP(limb) (limb.bodypart_flags & BODYPART_STUMP) + // Flags for the bodypart_flags var on /obj/item/bodypart /// Bodypart cannot be dismembered or amputated #define BODYPART_UNREMOVABLE (1<<0) @@ -65,6 +68,8 @@ #define BODYPART_UNHUSKABLE (1<<3) /// Bodypart has never been added to a mob #define BODYPART_VIRGIN (1<<4) +/// Not a full bodypart, but in fact is part of a missing limb +#define BODYPART_STUMP (1<<5) // Bodypart change blocking flags ///Bodypart does not get replaced during set_species() diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index 4ff1e10a22f..5e6f71b33c9 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -161,7 +161,7 @@ /// Orders bodyparts by their body_part value, ascending. /proc/cmp_bodypart_by_body_part_asc(obj/item/bodypart/limb_one, obj/item/bodypart/limb_two) - return limb_one.body_part - limb_two.body_part + return limb_one::body_part - limb_two::body_part /// Orders by integrated circuit weight /proc/cmp_port_order_asc(datum/port/compare1, datum/port/compare2) diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 533099c3918..250219e8740 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -799,8 +799,9 @@ update_appearance() /atom/movable/screen/healthdoll/human/update_body_zones() - limbs = list() vis_contents.Cut() + QDEL_LIST_ASSOC_VAL(limbs) + limbs ||= list() var/mob/living/carbon/human/owner = hud.mymob for(var/body_zone in owner.get_all_limbs()) var/atom/movable/screen/healthdoll_limb/limb = new(src, null) @@ -827,12 +828,13 @@ var/list/current_animated = LAZYLISTDUPLICATE(animated_zones) - for(var/obj/item/bodypart/body_part as anything in owner.bodyparts) + for(var/part_zone, body_part_untyped in owner.get_bodyparts_by_zones()) var/icon_key = 0 - var/part_zone = body_part.body_zone - + var/obj/item/bodypart/body_part = body_part_untyped var/list/overridable_key = list(icon_key) - if(body_part.bodypart_disabled) + if(isnull(body_part) || IS_STUMP(body_part)) + icon_key = 6 + else if(body_part.bodypart_disabled) icon_key = 7 else if(owner.stat == DEAD) icon_key = "DEAD" @@ -843,15 +845,11 @@ // calculate what icon state (1-5, or 0 if undamaged) to use based on damage icon_key = clamp(ceil(damage * 5), 0, 5) - if(length(body_part.wounds)) + if(length(body_part?.wounds)) LAZYSET(animated_zones, part_zone, TRUE) else LAZYREMOVE(animated_zones, part_zone) limbs[part_zone].icon_state = "[part_zone][icon_key]" - // handle leftovers - for(var/missing_zone in owner.get_missing_limbs()) - limbs[missing_zone].icon_state = "[missing_zone]6" - LAZYREMOVE(animated_zones, missing_zone) // time to re-sync animations, something changed if(animated_zones ~! current_animated) for(var/animated_zone in animated_zones) diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 7f057a85bf8..c37d6d9d417 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -408,7 +408,7 @@ return TRUE /mob/living/carbon/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker) - var/obj/item/bodypart/hit_bodypart = get_bodypart(hit_zone) || bodyparts[1] + var/obj/item/bodypart/hit_bodypart = get_bodypart(hit_zone) || get_bodypart() if(!hit_bodypart.can_bleed()) return FALSE diff --git a/code/controllers/subsystem/radiation.dm b/code/controllers/subsystem/radiation.dm index a129c2a3464..316331db7c9 100644 --- a/code/controllers/subsystem/radiation.dm +++ b/code/controllers/subsystem/radiation.dm @@ -132,7 +132,7 @@ SUBSYSTEM_DEF(radiation) /// Returns whether or not the human is covered head to toe in rad-protected clothing. /datum/controller/subsystem/radiation/proc/wearing_rad_protected_clothing(mob/living/carbon/human/human) - for (var/obj/item/bodypart/limb as anything in human.bodyparts) + for (var/obj/item/bodypart/limb as anything in human.get_bodyparts()) var/protected = FALSE for (var/obj/item/clothing as anything in human.get_clothing_on_part(limb)) diff --git a/code/datums/bodypart_overlays/texture_bodypart_overlay.dm b/code/datums/bodypart_overlays/texture_bodypart_overlay.dm index 70086d45f9c..920c2973ac8 100644 --- a/code/datums/bodypart_overlays/texture_bodypart_overlay.dm +++ b/code/datums/bodypart_overlays/texture_bodypart_overlay.dm @@ -17,7 +17,8 @@ appearance.add_filter("bodypart_texture_[texture_icon_state]", 1, layering_filter(icon = cached_texture_icon, blend_mode = BLEND_INSET_OVERLAY)) /datum/bodypart_overlay/texture/generate_icon_cache() - return "[type]" + . = ..() + . += "[type]" /datum/bodypart_overlay/texture/can_draw_on_bodypart(obj/item/bodypart/bodypart_owner, mob/living/carbon/owner, is_husked = FALSE) if (!..()) diff --git a/code/datums/components/amputating_limbs.dm b/code/datums/components/amputating_limbs.dm index bfaf52ca90c..28a69ee5a19 100644 --- a/code/datums/components/amputating_limbs.dm +++ b/code/datums/components/amputating_limbs.dm @@ -58,7 +58,7 @@ return var/list/valid_targets = list() - for (var/obj/item/bodypart/possible_target as anything in limbed_victim.bodyparts) + for (var/obj/item/bodypart/possible_target as anything in limbed_victim.get_bodyparts()) if (possible_target.bodypart_flags & BODYPART_UNREMOVABLE) continue if (!(possible_target.body_zone in target_zones)) diff --git a/code/datums/components/bloodysoles.dm b/code/datums/components/bloodysoles.dm index 4dc7a7feeb6..09c945c2f8d 100644 --- a/code/datums/components/bloodysoles.dm +++ b/code/datums/components/bloodysoles.dm @@ -326,7 +326,7 @@ return // Find any leg of our human and add that to the footprint, instead of the default which is to just add the human type - for(var/obj/item/bodypart/leg/affecting in wielder.bodyparts) + for(var/obj/item/bodypart/leg/affecting in wielder.get_bodyparts()) if(!affecting.bodypart_disabled) LAZYSET(footprint.species_types, affecting.limb_id, TRUE) diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm index b1f7eebab1f..bdc41329a74 100644 --- a/code/datums/components/butchering.dm +++ b/code/datums/components/butchering.dm @@ -167,7 +167,7 @@ if (target.body_zone == BODY_ZONE_CHEST && target.owner) // Cannot butcher the chest until we hack off all the other limbs - for (var/obj/item/bodypart/limb as anything in target.owner.bodyparts) + for (var/obj/item/bodypart/limb as anything in target.owner.get_bodyparts()) if (limb != target && limb.butcher_drops && limb.butcher_replacement) to_chat(user, span_warning("You need to butcher all other limbs first!")) return @@ -249,7 +249,7 @@ for(var/obj/item/result as anything in results) if (reagents_in_produced) if (target.owner.reagents) - target.owner.reagents.trans_to(result, target.owner.reagents.total_volume / reagents_in_produced / length(target.owner.bodyparts), remove_blacklisted = TRUE) + target.owner.reagents.trans_to(result, target.owner.reagents.total_volume / reagents_in_produced / length(target.owner.get_bodyparts()), remove_blacklisted = TRUE) result.reagents?.add_reagent(/datum/reagent/consumable/nutriment/fat, target.owner.nutrition / /datum/reagent/consumable/nutriment/fat::nutriment_factor / reagents_in_produced) if(LAZYLEN(diseases)) diff --git a/code/datums/components/free_operation.dm b/code/datums/components/free_operation.dm index a8002ef7f23..1c557cda5ea 100644 --- a/code/datums/components/free_operation.dm +++ b/code/datums/components/free_operation.dm @@ -9,17 +9,17 @@ return COMPONENT_REDUNDANT ADD_TRAIT(parent, TRAIT_READY_TO_OPERATE, REF(src)) var/mob/living/carbon/owner = parent - if(!istype(owner)) + if (!istype(owner)) return - for (var/obj/item/bodypart/limb as anything in owner.bodyparts) + for (var/obj/item/bodypart/limb as anything in owner.get_bodyparts(include_stumps = TRUE)) ADD_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src)) /datum/component/free_operation/Destroy(force) REMOVE_TRAIT(parent, TRAIT_READY_TO_OPERATE, REF(src)) var/mob/living/carbon/owner = parent - if(!istype(owner)) + if (!istype(owner)) return ..() - for (var/obj/item/bodypart/limb as anything in owner.bodyparts) + for (var/obj/item/bodypart/limb as anything in owner.get_bodyparts(include_stumps = TRUE)) REMOVE_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src)) return ..() diff --git a/code/datums/components/healing_touch.dm b/code/datums/components/healing_touch.dm index b8580e2e458..f7a9515c9c0 100644 --- a/code/datums/components/healing_touch.dm +++ b/code/datums/components/healing_touch.dm @@ -174,7 +174,7 @@ if (!iscarbon(target)) return (target.get_brute_loss() > 0 && heal_brute) || (target.get_fire_loss() > 0 && heal_burn) var/mob/living/carbon/carbon_target = target - for (var/obj/item/bodypart/part in carbon_target.bodyparts) + for (var/obj/item/bodypart/part in carbon_target.get_bodyparts()) if (!(part.brute_dam && heal_brute) && !(part.burn_dam && heal_burn)) continue if (!isnull(required_bodytype) && !(part.bodytype & required_bodytype)) diff --git a/code/datums/diseases/advance/symptoms/flesh_eating.dm b/code/datums/diseases/advance/symptoms/flesh_eating.dm index d12e0812335..d767f33fae3 100644 --- a/code/datums/diseases/advance/symptoms/flesh_eating.dm +++ b/code/datums/diseases/advance/symptoms/flesh_eating.dm @@ -60,7 +60,7 @@ Bonus if(bleed) if(ishuman(M)) var/mob/living/carbon/human/H = M - var/obj/item/bodypart/random_part = pick(H.bodyparts) + var/obj/item/bodypart/random_part = pick(H.get_bodyparts()) random_part.adjustBleedStacks(5 * power) return 1 diff --git a/code/datums/elements/blood_limb_overlay.dm b/code/datums/elements/blood_limb_overlay.dm index 3028d3f4754..bab7b6cb0ec 100644 --- a/code/datums/elements/blood_limb_overlay.dm +++ b/code/datums/elements/blood_limb_overlay.dm @@ -12,10 +12,10 @@ . = ..() UnregisterSignal(source, list(COMSIG_BODYPART_GET_LIMB_ICON, COMSIG_BODYPART_GENERATE_ICON_KEY)) -/datum/element/blood_limb_overlay/proc/on_limb_icon(obj/item/bodypart/source, list/limb_icons, dropped, mob/living/carbon/update_on) +/datum/element/blood_limb_overlay/proc/on_limb_icon(obj/item/bodypart/source, list/limb_icons, dropped) SIGNAL_HANDLER - var/list/blood_dna_info = source.blood_dna_info || update_on?.get_blood_dna_list() + var/list/blood_dna_info = source.blood_dna_info if (!LAZYLEN(blood_dna_info) || source.is_invisible) return @@ -38,4 +38,4 @@ var/list/blood_dna_info = source.blood_dna_info || source.owner?.get_blood_dna_list() if (LAZYLEN(blood_dna_info) && !source.is_invisible) - icon_keys += "-blood-[get_color_from_blood_list(blood_dna_info)]" + icon_keys += "blood:[get_color_from_blood_list(blood_dna_info)]" diff --git a/code/datums/elements/organ_set_bonus.dm b/code/datums/elements/organ_set_bonus.dm index 07d7e2474d2..49cd58ddf93 100644 --- a/code/datums/elements/organ_set_bonus.dm +++ b/code/datums/elements/organ_set_bonus.dm @@ -105,7 +105,7 @@ RegisterSignal(carbon_owner, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(texture_limb)) RegisterSignal(carbon_owner, COMSIG_CARBON_REMOVE_LIMB, PROC_REF(untexture_limb)) - for(var/obj/item/bodypart/limb as anything in carbon_owner.bodyparts) + for(var/obj/item/bodypart/limb as anything in carbon_owner.get_bodyparts()) if (!(limb.bodytype & BODYTYPE_ORGANIC)) continue limb.add_bodypart_overlay(new limb_overlay(), update = FALSE) @@ -139,7 +139,7 @@ if(QDELETED(carbon_owner)) return - for(var/obj/item/bodypart/limb as anything in carbon_owner.bodyparts) + for(var/obj/item/bodypart/limb as anything in carbon_owner.get_bodyparts()) var/overlay = locate(limb_overlay) in limb.bodypart_overlays if(!overlay) continue diff --git a/code/datums/elements/spooky.dm b/code/datums/elements/spooky.dm index 85daaaeda78..4ae70bb5095 100644 --- a/code/datums/elements/spooky.dm +++ b/code/datums/elements/spooky.dm @@ -52,7 +52,7 @@ if(isskeleton(human)) return FALSE //undeads are unaffected by the spook-pocalypse. var/bone_amount = 0 - for(var/obj/item/bodypart/part as anything in human.bodyparts) + for(var/obj/item/bodypart/part as anything in human.get_bodyparts()) if((part.biological_state & BIO_FLESH_BONE) == BIO_FLESH_BONE) bone_amount++ if(bone_amount) diff --git a/code/datums/embedding.dm b/code/datums/embedding.dm index 77fec4f8746..32d838eedf4 100644 --- a/code/datums/embedding.dm +++ b/code/datums/embedding.dm @@ -133,7 +133,7 @@ failed_embed(victim, hit_zone, random = TRUE) return - var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.bodyparts[1] + var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.get_bodypart() embed_into(victim, limb) return MOVABLE_IMPACT_ZONE_OVERRIDE @@ -153,7 +153,7 @@ failed_embed(victim, hit_zone, random = TRUE) return - var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.bodyparts[1] + var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.get_bodypart() embed_into(victim, limb) SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_EMBEDDED, payload, hit) diff --git a/code/datums/mutations/autotomy.dm b/code/datums/mutations/autotomy.dm index 03575a8f039..da34bfd4660 100644 --- a/code/datums/mutations/autotomy.dm +++ b/code/datums/mutations/autotomy.dm @@ -27,7 +27,7 @@ return var/list/parts = list() - for(var/obj/item/bodypart/to_remove as anything in cast_on.bodyparts) + for(var/obj/item/bodypart/to_remove as anything in cast_on.get_bodyparts()) if(to_remove.body_zone == BODY_ZONE_HEAD || to_remove.body_zone == BODY_ZONE_CHEST) continue if(to_remove.bodypart_flags & BODYPART_UNREMOVABLE) diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm index 9de066eeacb..6fd35026309 100644 --- a/code/datums/mutations/hulk.dm +++ b/code/datums/mutations/hulk.dm @@ -33,7 +33,7 @@ . = ..() if(!.) return - for(var/obj/item/bodypart/part as anything in owner.bodyparts) + for(var/obj/item/bodypart/part as anything in owner.get_bodyparts()) if (part.bodytype & BODYTYPE_ORGANIC) part.add_color_override(bodypart_color, LIMB_COLOR_HULK) owner.update_body_parts() @@ -74,7 +74,7 @@ /datum/mutation/hulk/on_losing(mob/living/carbon/human/owner) if(..()) return - for(var/obj/item/bodypart/part as anything in owner.bodyparts) + for(var/obj/item/bodypart/part as anything in owner.get_bodyparts()) part.remove_color_override(LIMB_COLOR_HULK) owner.update_body_parts() owner.clear_mood_event("hulk") diff --git a/code/datums/mutations/touch.dm b/code/datums/mutations/touch.dm index ba24f4f30bc..7a91cc390e8 100644 --- a/code/datums/mutations/touch.dm +++ b/code/datums/mutations/touch.dm @@ -226,7 +226,7 @@ // Get at least organic limb to transfer the damage to var/list/mendicant_organic_limbs = list() - for(var/obj/item/bodypart/possible_limb in mendicant.bodyparts) + for(var/obj/item/bodypart/possible_limb in mendicant.get_bodyparts()) if(IS_ORGANIC_LIMB(possible_limb)) mendicant_organic_limbs += possible_limb // None? Gtfo @@ -258,7 +258,7 @@ // Get the hurtguy's limbs and the mendicant's limbs to attempt a 1-1 transfer. var/list/hurt_limbs = hurtguy.get_damaged_bodyparts(1, 1, BODYTYPE_ORGANIC) + hurtguy.get_wounded_bodyparts(BODYTYPE_ORGANIC) var/list/mendicant_organic_limbs = list() - for(var/obj/item/bodypart/possible_limb in mendicant.bodyparts) + for(var/obj/item/bodypart/possible_limb in mendicant.get_bodyparts()) if(IS_ORGANIC_LIMB(possible_limb)) mendicant_organic_limbs += possible_limb diff --git a/code/datums/quirks/negative_quirks/body_purist.dm b/code/datums/quirks/negative_quirks/body_purist.dm index 76221df7a5c..063887b8746 100644 --- a/code/datums/quirks/negative_quirks/body_purist.dm +++ b/code/datums/quirks/negative_quirks/body_purist.dm @@ -31,7 +31,7 @@ var/mob/living/carbon/owner = quirk_holder if(!istype(owner)) return - for(var/obj/item/bodypart/limb as anything in owner.bodyparts) + for(var/obj/item/bodypart/limb as anything in owner.get_bodyparts()) if(IS_ROBOTIC_LIMB(limb)) cybernetics_level++ for(var/obj/item/organ/organ as anything in owner.organs) diff --git a/code/datums/quirks/neutral_quirks/transhumanist.dm b/code/datums/quirks/neutral_quirks/transhumanist.dm index 82570e6cf21..6dfa2d16f6c 100644 --- a/code/datums/quirks/neutral_quirks/transhumanist.dm +++ b/code/datums/quirks/neutral_quirks/transhumanist.dm @@ -76,7 +76,7 @@ var/organic_bodytypes = 0 var/silicon_bodytypes = 0 var/other_bodytypes = FALSE - for(var/obj/item/bodypart/part as anything in target.bodyparts) + for(var/obj/item/bodypart/part as anything in target.get_bodyparts()) if(part.bodytype & BODYTYPE_ROBOTIC) silicon_bodytypes += 1 else if(part.bodytype & BODYTYPE_ORGANIC) diff --git a/code/datums/status_effects/debuffs/rust_corruption.dm b/code/datums/status_effects/debuffs/rust_corruption.dm index 6692ef111a0..66374898ab7 100644 --- a/code/datums/status_effects/debuffs/rust_corruption.dm +++ b/code/datums/status_effects/debuffs/rust_corruption.dm @@ -13,6 +13,6 @@ if(!iscarbon(owner)) return var/mob/living/carbon/carbon_owner = owner - for(var/obj/item/bodypart/robotic_limb as anything in carbon_owner.bodyparts) + for(var/obj/item/bodypart/robotic_limb as anything in carbon_owner.get_bodyparts()) if(IS_ROBOTIC_LIMB(robotic_limb)) robotic_limb.receive_damage(10) diff --git a/code/datums/voice_of_god_command.dm b/code/datums/voice_of_god_command.dm index a49c7cb6c34..22a3993fa78 100644 --- a/code/datums/voice_of_god_command.dm +++ b/code/datums/voice_of_god_command.dm @@ -205,7 +205,7 @@ GLOBAL_LIST_INIT(voice_of_god_commands, init_voice_of_god_commands()) /datum/voice_of_god_command/bleed/execute(list/listeners, mob/living/user, power_multiplier = 1, message) for(var/mob/living/carbon/human/target in listeners) - var/obj/item/bodypart/chosen_part = pick(target.bodyparts) + var/obj/item/bodypart/chosen_part = pick(target.get_bodyparts()) chosen_part.adjustBleedStacks(5) /// This command sets the listeners ablaze. diff --git a/code/game/machinery/computer/operating_computer.dm b/code/game/machinery/computer/operating_computer.dm index d9f645c30bc..416f4c16fac 100644 --- a/code/game/machinery/computer/operating_computer.dm +++ b/code/game/machinery/computer/operating_computer.dm @@ -236,33 +236,14 @@ "mechanic" = operation.operation_flags & OPERATION_MECHANIC, )) - if(!any_recommended && table?.patient) - var/obj/item/part = table.patient.get_bodypart(deprecise_zone(target_zone)) - var/just_drapes = FALSE - if(table.patient.has_limbs) - if(isnull(part)) - data["surgeries"] += list(list( - "name" = "Prepare for [/datum/surgery_operation/prosthetic_replacement::name]", - "desc" = "Prepare the patient's chest for prosthetic limb attachment.", - "tool_rec" = "operate on chest", - "show_as_next" = TRUE, - "show_in_list" = FALSE, - )) - - else if(!HAS_TRAIT(part, TRAIT_READY_TO_OPERATE)) - just_drapes = TRUE - - else if(!HAS_TRAIT(table.patient, TRAIT_READY_TO_OPERATE)) - just_drapes = TRUE - - if(just_drapes) - data["surgeries"] += list(list( - "name" = "Prepare for surgery", - "desc" = "Begin surgery by applying surgical drapes to the patient.", - "tool_rec" = /obj/item/surgical_drapes::name, - "show_as_next" = TRUE, - "show_in_list" = FALSE, - )) + 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 diff --git a/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm index 16bf78ee396..2284e2afa18 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm @@ -93,7 +93,7 @@ if (new_value >= FISH_INFUSION_ALL_ORGANS && tail_color) if (!color_active) - for(var/obj/item/bodypart/limb as anything in carbon_owner.bodyparts) + for(var/obj/item/bodypart/limb as anything in carbon_owner.get_bodyparts()) limb.add_color_override(tail_color, LIMB_COLOR_FISH_INFUSION) color_active = TRUE return @@ -101,7 +101,7 @@ if (!color_active) return - for(var/obj/item/bodypart/limb as anything in carbon_owner.bodyparts) + for(var/obj/item/bodypart/limb as anything in carbon_owner.get_bodyparts()) limb.remove_color_override(LIMB_COLOR_FISH_INFUSION) color_active = FALSE diff --git a/code/game/machinery/experimental_cloner/experimental_cloning_record.dm b/code/game/machinery/experimental_cloner/experimental_cloning_record.dm index 96c1b9bc164..caea82e3677 100644 --- a/code/game/machinery/experimental_cloner/experimental_cloning_record.dm +++ b/code/game/machinery/experimental_cloner/experimental_cloning_record.dm @@ -75,7 +75,7 @@ for (var/trauma_type in brain_traumas) subject.gain_trauma(trauma_type) - for (var/obj/item/bodypart/limb as anything in subject.bodyparts) + for (var/obj/item/bodypart/limb as anything in subject.get_bodyparts()) limb.update_limb(is_creating = TRUE) subject.updateappearance(mutcolor_update = TRUE) diff --git a/code/game/machinery/harvester.dm b/code/game/machinery/harvester.dm index 70849a1198f..0af6c9933a4 100644 --- a/code/game/machinery/harvester.dm +++ b/code/game/machinery/harvester.dm @@ -101,7 +101,7 @@ header = "Gruesome!", ) - operation_order = reverseList(carbon_occupant.bodyparts) //Chest and head are first in bodyparts, so we invert it to make them suffer more + operation_order = reverse_range(carbon_occupant.get_bodyparts()) //Chest and head are first in bodyparts, so we invert it to make them suffer more warming_up = TRUE harvesting = TRUE visible_message(span_notice("\The [src] begins warming up!")) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 08ab1724c5e..ec0ff3340cd 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -2217,7 +2217,7 @@ return FALSE if (!istype(target_limb)) - target_limb = victim.get_bodypart(target_limb) || victim.bodyparts[1] + target_limb = victim.get_bodypart(target_limb) || victim.get_bodypart() return get_embed()?.embed_into(victim, target_limb) diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index e4487f62192..01dfb14d891 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -219,7 +219,7 @@ if(iscarbon(target)) var/mob/living/carbon/carbontarget = target var/any_damage = brute_loss > 0 || fire_loss > 0 || oxy_loss > 0 || tox_loss > 0 || fire_loss > 0 - var/any_missing = length(carbontarget.bodyparts) < (carbontarget.dna?.species?.max_bodypart_count || 6) + var/any_missing = length(carbontarget.get_missing_limbs()) var/any_wounded = length(carbontarget.all_wounds) var/any_embeds = carbontarget.has_embedded_objects() if(any_damage || (mode == SCANNER_VERBOSE && (any_missing || any_wounded || any_embeds))) diff --git a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm index ab8e3796f22..fe44c1eef41 100644 --- a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm +++ b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm @@ -78,7 +78,7 @@ if(isgolem(owner)) var/mob/living/carbon/golem_owner = owner - for (var/obj/item/bodypart/part in golem_owner.bodyparts) + for (var/obj/item/bodypart/part in golem_owner.get_bodyparts()) // these overlays won't look good on anything but golem limbs if (part.limb_id != SPECIES_GOLEM) continue @@ -307,7 +307,7 @@ owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/light_speed) var/mob/living/carbon/carbon_owner = owner - for (var/obj/item/bodypart/arm/arm in carbon_owner.bodyparts) + for (var/obj/item/bodypart/arm/arm in carbon_owner.get_bodyparts()) set_arm_fluff(arm) return TRUE @@ -388,7 +388,7 @@ var/mob/living/carbon/human/human_owner = owner RegisterSignal(human_owner, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_punched)) human_owner.physiology.brute_mod *= brute_modifier - for (var/obj/item/bodypart/arm/arm in human_owner.bodyparts) + for (var/obj/item/bodypart/arm/arm in human_owner.get_bodyparts()) buff_arm(arm) /// Give mining mobs an extra slap diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index d244efb3ad4..de4020bc85d 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -230,7 +230,7 @@ PRIVATE_PROC(TRUE) var/list/other_affected_limbs = list() - for(var/obj/item/bodypart/limb as anything in patient.bodyparts) + for(var/obj/item/bodypart/limb as anything in patient.get_bodyparts()) if(!try_heal_checks(patient, user, limb.body_zone, silent = TRUE)) continue other_affected_limbs += limb.body_zone @@ -774,7 +774,7 @@ return BRUTELOSS patient.emote("scream") - for(var/obj/item/bodypart/bone as anything in patient.bodyparts) + for(var/obj/item/bodypart/bone as anything in patient.get_bodyparts()) // fine to just, use these raw, its a meme anyway var/datum/wound/blunt/bone/severe/oof_ouch = new oof_ouch.apply_wound(bone, wound_source = "bone gel") diff --git a/code/game/objects/items/weaponry/melee/sabre.dm b/code/game/objects/items/weaponry/melee/sabre.dm index 770f5b53a9c..0cc90020800 100644 --- a/code/game/objects/items/weaponry/melee/sabre.dm +++ b/code/game/objects/items/weaponry/melee/sabre.dm @@ -81,7 +81,7 @@ var/list/legs = list() var/obj/item/bodypart/bodypart - for(bodypart in Cuser.bodyparts) + for(bodypart in Cuser.get_bodyparts()) if(bodypart == holding_bodypart) continue if(bodypart.body_part & ARMS) diff --git a/code/game/objects/items/wiki_manuals.dm b/code/game/objects/items/wiki_manuals.dm index 5c0c0802464..56efbd0669a 100644 --- a/code/game/objects/items/wiki_manuals.dm +++ b/code/game/objects/items/wiki_manuals.dm @@ -213,7 +213,7 @@ var/obj/item/bodypart/head = H.get_bodypart(BODY_ZONE_HEAD) if(head) ADD_TRAIT(head, TRAIT_DISFIGURED, TRAIT_GENERIC) - for(var/obj/item/bodypart/part as anything in H.bodyparts) + for(var/obj/item/bodypart/part as anything in H.get_bodyparts()) part.adjustBleedStacks(5) H.gib_animation() sleep(0.3 SECONDS) diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm index d530d00a00f..a931748a577 100644 --- a/code/game/turfs/open/lava.dm +++ b/code/game/turfs/open/lava.dm @@ -437,7 +437,7 @@ var/list/immune_parts = list() // Parts we can't transform because they're not organic or can't be dismembered var/list/transform_parts = list() // Parts we want to transform - for(var/obj/item/bodypart/burn_limb as anything in burn_human.bodyparts) + for(var/obj/item/bodypart/burn_limb as anything in burn_human.get_bodyparts()) if(!IS_ORGANIC_LIMB(burn_limb) || !burn_limb.can_dismember()) immune_parts += burn_limb continue diff --git a/code/modules/admin/smites/berforate.dm b/code/modules/admin/smites/berforate.dm index 6b5fffa38e6..8bd027eb8bc 100644 --- a/code/modules/admin/smites/berforate.dm +++ b/code/modules/admin/smites/berforate.dm @@ -41,7 +41,7 @@ dude.Immobilize(5 SECONDS) for (var/wound_bonus_rep in 1 to repetitions) - for (var/_limb in dude.bodyparts) + for (var/_limb in dude.get_bodyparts()) var/obj/item/bodypart/limb = _limb var/shots_this_limb = 0 for (var/_iter_turf in shuffle(open_adj_turfs)) diff --git a/code/modules/admin/smites/bloodless.dm b/code/modules/admin/smites/bloodless.dm index c970e920f22..df43ed2048a 100644 --- a/code/modules/admin/smites/bloodless.dm +++ b/code/modules/admin/smites/bloodless.dm @@ -8,7 +8,7 @@ to_chat(user, span_warning("This must be used on a carbon mob."), confidential = TRUE) return var/mob/living/carbon/carbon_target = target - for(var/_limb in carbon_target.bodyparts) + for(var/_limb in carbon_target.get_bodyparts()) var/obj/item/bodypart/limb = _limb // fine to use this raw, its a meme smite var/type_wound = pick(list(/datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/moderate)) limb.force_wound_upwards(type_wound, smited = TRUE) diff --git a/code/modules/admin/smites/boneless.dm b/code/modules/admin/smites/boneless.dm index 3fd1a1121f4..5dd2afd6ef2 100644 --- a/code/modules/admin/smites/boneless.dm +++ b/code/modules/admin/smites/boneless.dm @@ -10,7 +10,7 @@ return var/mob/living/carbon/carbon_target = target - for(var/obj/item/bodypart/limb as anything in carbon_target.bodyparts) + for(var/obj/item/bodypart/limb as anything in carbon_target.get_bodyparts()) var/severity = pick_weight(alist( WOUND_SEVERITY_MODERATE = 1, WOUND_SEVERITY_SEVERE = 2, diff --git a/code/modules/admin/smites/nugget.dm b/code/modules/admin/smites/nugget.dm index 18e5254e615..9efde93dfee 100644 --- a/code/modules/admin/smites/nugget.dm +++ b/code/modules/admin/smites/nugget.dm @@ -11,7 +11,7 @@ var/mob/living/carbon/carbon_target = target var/timer = 2 SECONDS - for (var/_limb in carbon_target.bodyparts) + for (var/_limb in carbon_target.get_bodyparts()) var/obj/item/bodypart/limb = _limb if (limb.body_part == HEAD || limb.body_part == CHEST) continue diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm index 380eea90b96..fdbfc81b23c 100644 --- a/code/modules/antagonists/abductor/abductor.dm +++ b/code/modules/antagonists/abductor/abductor.dm @@ -99,7 +99,7 @@ // If we have a team skincolor, apply it here. Applied by admins or 2% chance of natural occurance if(!isnull(team.team_skincolor)) - for(var/obj/item/bodypart/part as anything in new_abductor.bodyparts) + for(var/obj/item/bodypart/part as anything in new_abductor.get_bodyparts()) part.should_draw_greyscale = TRUE part.add_color_override(team.team_skincolor, LIMB_COLOR_AYYLMAO) diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index b841ec26626..53abe0952e4 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -801,7 +801,7 @@ chosen_dna.copy_dna(user.dna, COPY_DNA_SE|COPY_DNA_SPECIES) - for(var/obj/item/bodypart/limb as anything in user.bodyparts) + for(var/obj/item/bodypart/limb as anything in user.get_bodyparts()) limb.update_limb(is_creating = TRUE) user.updateappearance(mutcolor_update = TRUE) diff --git a/code/modules/antagonists/changeling/powers/defib_grasp.dm b/code/modules/antagonists/changeling/powers/defib_grasp.dm index 38c14efdfe7..5d63b5b2e05 100644 --- a/code/modules/antagonists/changeling/powers/defib_grasp.dm +++ b/code/modules/antagonists/changeling/powers/defib_grasp.dm @@ -63,7 +63,7 @@ if(iscarbon(defibber)) var/removed_arms = 0 var/mob/living/carbon/carbon_defibber = defibber - for(var/obj/item/bodypart/arm/limb in carbon_defibber.bodyparts) + for(var/obj/item/bodypart/arm/limb in carbon_defibber.get_bodyparts()) if(limb.dismember(silent = FALSE)) removed_arms++ qdel(limb) diff --git a/code/modules/antagonists/cult/cult_armor.dm b/code/modules/antagonists/cult/cult_armor.dm index d4a62d5fd54..36ede426e69 100644 --- a/code/modules/antagonists/cult/cult_armor.dm +++ b/code/modules/antagonists/cult/cult_armor.dm @@ -151,7 +151,7 @@ return if(!SPT_PROB(15, seconds_per_tick)) return - var/obj/item/bodypart/bone_to_wound = pick(wearer.bodyparts) + var/obj/item/bodypart/bone_to_wound = pick(wearer.get_bodyparts()) var/wound_type = pick(list( /datum/wound/blunt/bone/moderate, /datum/wound/pierce/bleed/moderate, diff --git a/code/modules/antagonists/heretic/influences.dm b/code/modules/antagonists/heretic/influences.dm index 71f79a51cbd..ad7478fc6fd 100644 --- a/code/modules/antagonists/heretic/influences.dm +++ b/code/modules/antagonists/heretic/influences.dm @@ -141,7 +141,7 @@ // A very elaborate way to suicide visible_message(span_userdanger("Psychic tendrils lash out from [src], psychically grabbing onto [user]'s psychically sensitive mind and tearing [user.p_their()] head off!")) - var/obj/item/bodypart/head/head = locate() in human_user.bodyparts + var/obj/item/bodypart/head/head = human_user.get_bodypart(BODY_ZONE_HEAD) if(head?.dismember()) head.forceMove(src) // stored for later fishage else diff --git a/code/modules/antagonists/heretic/items/heretic_armor.dm b/code/modules/antagonists/heretic/items/heretic_armor.dm index 4d0dffa781c..8e496353fae 100644 --- a/code/modules/antagonists/heretic/items/heretic_armor.dm +++ b/code/modules/antagonists/heretic/items/heretic_armor.dm @@ -142,7 +142,7 @@ return var/mob/living/carbon/victim = user var/iteration = 0 - for(var/obj/item/bodypart/limb as anything in victim.bodyparts) + for(var/obj/item/bodypart/limb as anything in victim.get_bodyparts()) if(istype(limb, /obj/item/bodypart/head) || istype(limb, /obj/item/bodypart/chest)) continue iteration++ @@ -259,7 +259,7 @@ if(!length(valid_turfs)) var/mob/living/carbon/carbon_target = target if(iscarbon(target)) - var/obj/item/bodypart/limb = pick(carbon_target.bodyparts) + var/obj/item/bodypart/limb = pick(carbon_target.get_bodyparts()) limb.force_wound_upwards(/datum/wound/slash/flesh/severe) return throw_blade(pick(valid_turfs), target) @@ -451,7 +451,7 @@ return var/mob/living/carbon/victim = user var/iteration = 0 - for(var/obj/item/bodypart/limb as anything in victim.bodyparts) + for(var/obj/item/bodypart/limb as anything in victim.get_bodyparts()) iteration++ addtimer(CALLBACK(limb, TYPE_PROC_REF(/obj/item/bodypart, force_wound_upwards), /datum/wound/slash/flesh/critical), 1 SECONDS * iteration) diff --git a/code/modules/antagonists/heretic/items/heretic_grenade.dm b/code/modules/antagonists/heretic/items/heretic_grenade.dm index 14ceaf1183e..3a5e0e2a129 100644 --- a/code/modules/antagonists/heretic/items/heretic_grenade.dm +++ b/code/modules/antagonists/heretic/items/heretic_grenade.dm @@ -95,7 +95,7 @@ addtimer(CALLBACK(victim, TYPE_PROC_REF(/mob, remove_movespeed_modifier), /datum/movespeed_modifier/reagent/pepperspray), 10 SECONDS) victim.update_damage_hud() victim.adjust_disgust(5) - for(var/obj/item/bodypart/robotic_limb in victim.bodyparts) + for(var/obj/item/bodypart/robotic_limb in victim.get_bodyparts()) if(robotic_limb.biological_state & BIO_ROBOTIC) robotic_limb.receive_damage(5, 5) if(methods & INGEST) diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm index cb2ac3d3bde..f5282015b0a 100644 --- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm @@ -267,7 +267,7 @@ return var/mob/living/carbon/carbon_target = target - var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts) + var/obj/item/bodypart/bodypart = pick(carbon_target.get_bodyparts()) var/datum/wound/crit_wound = new wound_type() crit_wound.apply_wound(bodypart, attack_direction = get_dir(source, target)) diff --git a/code/modules/antagonists/heretic/knowledge/moon_lore.dm b/code/modules/antagonists/heretic/knowledge/moon_lore.dm index f3a9a9da712..eb6428aee27 100644 --- a/code/modules/antagonists/heretic/knowledge/moon_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/moon_lore.dm @@ -300,7 +300,7 @@ to_chat(carbon_view, span_boldbig(span_red(\ "YOUR SENSES REEL AS YOUR MIND IS ENVELOPED BY AN OTHERWORLDLY FORCE ATTEMPTING TO REWRITE YOUR VERY BEING. \ YOU CANNOT EVEN BEGIN TO SCREAM BEFORE YOUR IMPLANT ACTIVATES ITS PSIONIC FAIL-SAFE PROTOCOL, TAKING YOUR HEAD WITH IT."))) - var/obj/item/bodypart/head/head = locate() in carbon_view.bodyparts + var/obj/item/bodypart/head/head = carbon_view.get_bodypart(BODY_ZONE_HEAD) if(!head?.dismember()) carbon_view.gib(DROP_ALL_REMAINS) var/datum/effect_system/reagents_explosion/explosion = new(get_turf(carbon_view), 1, 1, 1) diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm index 207669735e0..565879a7c8c 100644 --- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm @@ -81,7 +81,7 @@ if(iscarbon(target)) var/mob/living/carbon/carbon_target = target - for(var/obj/item/bodypart/robotic_limb as anything in carbon_target.bodyparts) + for(var/obj/item/bodypart/robotic_limb as anything in carbon_target.get_bodyparts()) if(IS_ROBOTIC_LIMB(robotic_limb)) robotic_limb.receive_damage(500) diff --git a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm index cac879740de..5d155281e5e 100644 --- a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm +++ b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm @@ -29,10 +29,10 @@ /datum/heretic_knowledge/codex_morbus/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) . = ..() var/mob/living/carbon/human/to_fuck_up = locate() in selected_atoms - for(var/_limb in to_fuck_up.bodyparts) + for(var/_limb in to_fuck_up.get_bodyparts()) var/obj/item/bodypart/limb = _limb limb.force_wound_upwards(/datum/wound/slash/flesh/critical) - for(var/obj/item/bodypart/limb as anything in to_fuck_up.bodyparts) + for(var/obj/item/bodypart/limb as anything in to_fuck_up.get_bodyparts()) to_fuck_up.cause_wound_of_type_and_severity(WOUND_BLUNT, limb, WOUND_SEVERITY_CRITICAL) return TRUE diff --git a/code/modules/antagonists/heretic/knowledge/starting_lore.dm b/code/modules/antagonists/heretic/knowledge/starting_lore.dm index 830e6edae32..6647ee086bc 100644 --- a/code/modules/antagonists/heretic/knowledge/starting_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/starting_lore.dm @@ -257,7 +257,7 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) // If it is, we will damage a random bodypart, and check that bodypart for its body type, to select between 'skin' or 'exterior'. if(iscarbon(body)) var/mob/living/carbon/carbody = body - var/obj/item/bodypart/bodypart = pick(carbody.bodyparts) + var/obj/item/bodypart/bodypart = pick(carbody.get_bodyparts()) ripped_thing = bodypart carbody.apply_damage(25, BRUTE, bodypart, sharpness = SHARP_EDGED) diff --git a/code/modules/antagonists/heretic/magic/apetravulnera.dm b/code/modules/antagonists/heretic/magic/apetravulnera.dm index c0398b2b745..b878fd36bed 100644 --- a/code/modules/antagonists/heretic/magic/apetravulnera.dm +++ b/code/modules/antagonists/heretic/magic/apetravulnera.dm @@ -38,7 +38,7 @@ return FALSE var/a_limb_got_damaged = FALSE - for(var/obj/item/bodypart/bodypart in cast_on.bodyparts) + for(var/obj/item/bodypart/bodypart in cast_on.get_bodyparts()) if(bodypart.brute_dam < 15) continue a_limb_got_damaged = TRUE @@ -47,7 +47,7 @@ if(!a_limb_got_damaged) var/datum/wound/slash/crit_wound = new wound_type() - crit_wound.apply_wound(pick(cast_on.bodyparts)) + crit_wound.apply_wound(pick(cast_on.get_bodyparts())) cast_on.visible_message( span_danger("[cast_on]'s scratches and bruises are torn open by an unholy force!"), diff --git a/code/modules/antagonists/heretic/magic/blood_cleave.dm b/code/modules/antagonists/heretic/magic/blood_cleave.dm index 065286f490e..e0c9454ba33 100644 --- a/code/modules/antagonists/heretic/magic/blood_cleave.dm +++ b/code/modules/antagonists/heretic/magic/blood_cleave.dm @@ -44,7 +44,7 @@ span_danger("Your veins burst from within and unholy flame erupts from your blood!") ) - var/obj/item/bodypart/bodypart = pick(victim.bodyparts) + var/obj/item/bodypart/bodypart = pick(victim.get_bodyparts()) var/datum/wound/slash/flesh/crit_wound = new wound_type() crit_wound.apply_wound(bodypart) victim.apply_damage(20, BURN, wound_bonus = CANT_WOUND) diff --git a/code/modules/antagonists/heretic/magic/blood_siphon.dm b/code/modules/antagonists/heretic/magic/blood_siphon.dm index cb0b4ec355c..3ef5a7920f8 100644 --- a/code/modules/antagonists/heretic/magic/blood_siphon.dm +++ b/code/modules/antagonists/heretic/magic/blood_siphon.dm @@ -50,11 +50,11 @@ var/mob/living/carbon/carbon_target = cast_on var/mob/living/carbon/carbon_user = owner - for(var/obj/item/bodypart/bodypart as anything in carbon_user.bodyparts) + for(var/obj/item/bodypart/bodypart as anything in carbon_user.get_bodyparts()) for(var/datum/wound/iter_wound as anything in bodypart.wounds) if(prob(50)) continue - var/obj/item/bodypart/target_bodypart = locate(bodypart.type) in carbon_target.bodyparts + var/obj/item/bodypart/target_bodypart = carbon_target.get_bodypart(bodypart.body_zone) if(!target_bodypart) continue iter_wound.remove_wound() diff --git a/code/modules/antagonists/heretic/magic/crimson_cleave.dm b/code/modules/antagonists/heretic/magic/crimson_cleave.dm index b64a5221a50..6ee02e2fea9 100644 --- a/code/modules/antagonists/heretic/magic/crimson_cleave.dm +++ b/code/modules/antagonists/heretic/magic/crimson_cleave.dm @@ -32,7 +32,7 @@ . = ..() if(iscarbon(owner)) var/mob/living/carbon/carbon_owner = owner - for(var/obj/item/bodypart/limbs as anything in carbon_owner.bodyparts) + for(var/obj/item/bodypart/limbs as anything in carbon_owner.get_bodyparts()) for(var/datum/wound/iter_wound as anything in limbs.wounds) iter_wound.remove_wound() diff --git a/code/modules/antagonists/heretic/status_effects/buffs.dm b/code/modules/antagonists/heretic/status_effects/buffs.dm index f3660e2f813..e4802049433 100644 --- a/code/modules/antagonists/heretic/status_effects/buffs.dm +++ b/code/modules/antagonists/heretic/status_effects/buffs.dm @@ -87,7 +87,7 @@ if(!iscarbon(owner)) return var/mob/living/carbon/drinker = owner - for(var/obj/item/bodypart/potentially_wounded as anything in drinker.bodyparts) + for(var/obj/item/bodypart/potentially_wounded as anything in drinker.get_bodyparts()) for(var/datum/wound/found_wound as anything in potentially_wounded.wounds) found_wound.remove_wound() if(length(drinker.get_missing_limbs())) @@ -102,7 +102,7 @@ carbie.adjust_brute_loss(-0.5 * seconds_between_ticks, updating_health = FALSE) carbie.adjust_fire_loss(-0.5 * seconds_between_ticks, updating_health = FALSE) - for(var/BP in carbie.bodyparts) + for(var/BP in carbie.get_bodyparts()) var/obj/item/bodypart/part = BP for(var/W in part.wounds) var/datum/wound/wound = W diff --git a/code/modules/antagonists/heretic/status_effects/heretic_passive.dm b/code/modules/antagonists/heretic/status_effects/heretic_passive.dm index 6f5786b809b..08d2292d677 100644 --- a/code/modules/antagonists/heretic/status_effects/heretic_passive.dm +++ b/code/modules/antagonists/heretic/status_effects/heretic_passive.dm @@ -307,7 +307,7 @@ if(!iscarbon(owner)) return var/mob/living/carbon/carbon_eater = owner - for(var/obj/item/bodypart/wounded_limb as anything in carbon_eater.bodyparts) + for(var/obj/item/bodypart/wounded_limb as anything in carbon_eater.get_bodyparts()) for(var/datum/wound/to_cure as anything in wounded_limb.wounds) to_cure.remove_wound() break @@ -543,7 +543,7 @@ var/mob/living/carbon/carbon_owner = source if(passive_level < HERETIC_LEVEL_UPGRADE) return - for(var/obj/item/bodypart/wounded_limb as anything in carbon_owner.bodyparts) + for(var/obj/item/bodypart/wounded_limb as anything in carbon_owner.get_bodyparts()) for(var/datum/wound/to_cure as anything in wounded_limb.wounds) to_cure.remove_wound() for(var/obj/item/organ/internal as anything in carbon_owner.organs) diff --git a/code/modules/antagonists/heretic/status_effects/mark_effects.dm b/code/modules/antagonists/heretic/status_effects/mark_effects.dm index 465ad530d24..18b35b2eedc 100644 --- a/code/modules/antagonists/heretic/status_effects/mark_effects.dm +++ b/code/modules/antagonists/heretic/status_effects/mark_effects.dm @@ -64,7 +64,7 @@ /datum/status_effect/eldritch/flesh/on_effect() if(ishuman(owner)) var/mob/living/carbon/human/human_owner = owner - var/obj/item/bodypart/bodypart = pick(human_owner.bodyparts) + var/obj/item/bodypart/bodypart = pick(human_owner.get_bodyparts()) human_owner.cause_wound_of_type_and_severity(WOUND_SLASH, bodypart, WOUND_SEVERITY_SEVERE) return ..() diff --git a/code/modules/antagonists/spy/spy_bounty.dm b/code/modules/antagonists/spy/spy_bounty.dm index d58fd398d7a..a1020c3c93e 100644 --- a/code/modules/antagonists/spy/spy_bounty.dm +++ b/code/modules/antagonists/spy/spy_bounty.dm @@ -663,7 +663,7 @@ /datum/spy_bounty/targets_person/some_item/limb_or_organ/find_desired_thing(mob/living/carbon/human/crewmember) if(ispath(desired_type, /obj/item/bodypart)) - return locate(desired_type) in crewmember.bodyparts + return locate(desired_type) in crewmember.get_bodyparts() if(ispath(desired_type, /obj/item/organ)) return locate(desired_type) in crewmember.organs return null diff --git a/code/modules/antagonists/voidwalker/voidwalker_traumas.dm b/code/modules/antagonists/voidwalker/voidwalker_traumas.dm index 3deab1fd0d6..c6c2f3f2340 100644 --- a/code/modules/antagonists/voidwalker/voidwalker_traumas.dm +++ b/code/modules/antagonists/voidwalker/voidwalker_traumas.dm @@ -45,7 +45,7 @@ RegisterSignal(owner, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(texture_limb)) //also catch new limbs being attached RegisterSignal(owner, COMSIG_CARBON_REMOVE_LIMB, PROC_REF(untexture_limb)) //and remove it from limbs if they go away - for(var/obj/item/bodypart as anything in owner.bodyparts) + for(var/obj/item/bodypart as anything in owner.get_bodyparts()) texture_limb(owner, bodypart) if(ishuman(owner)) @@ -75,7 +75,7 @@ var/mob/living/carbon/human/human = owner human.physiology.brute_mod /= brute_mod - for(var/obj/item/bodypart/bodypart as anything in owner.bodyparts) + for(var/obj/item/bodypart/bodypart as anything in owner.get_bodyparts()) untexture_limb(owner, bodypart) owner.update_body() diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm index a9047c79878..40f271a30f4 100644 --- a/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm +++ b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm @@ -124,7 +124,7 @@ target_quirk.add_to_holder(target) dna.copy_dna(target.dna, COPY_DNA_SE|COPY_DNA_SPECIES) - for(var/obj/item/bodypart/limb as anything in target.bodyparts) + for(var/obj/item/bodypart/limb as anything in target.get_bodyparts()) limb.update_limb(is_creating = TRUE) target.updateappearance(mutcolor_update = TRUE) target.domutcheck() diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index c1829cb21cf..6d237f67f8d 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -329,7 +329,7 @@ if (iscarbon(target_living)) //If effectLimb is true (which means we pop limbs off when we hit people): if (effectLimb) var/mob/living/carbon/carbon_target_mob = target_living - for (var/bp in carbon_target_mob.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + for (var/bp in carbon_target_mob.get_bodyparts()) //Look at the bodyparts in our poor mob beneath our pod as it lands var/obj/item/bodypart/bodypart = bp if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson! if(bodypart.dismember()) //Using the power of flextape i've sawed this man's limb in half! @@ -342,7 +342,7 @@ organ_to_yeet.forceMove(turf_underneath) //Move the organ outta the body organ_to_yeet.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away sleep(0.1 SECONDS) - for (var/bp in carbon_target_mob.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + for (var/bp in carbon_target_mob.get_bodyparts()) //Look at the bodyparts in our poor mob beneath our pod as it lands var/obj/item/bodypart/bodypart = bp var/destination = get_edge_target_turf(turf_underneath, pick(GLOB.alldirs)) if (bodypart.dismember()) //Using the power of flextape i've sawed this man's bodypart in half! diff --git a/code/modules/client/preferences/species_features/lizard.dm b/code/modules/client/preferences/species_features/lizard.dm index 10236d9e415..849d1a21114 100644 --- a/code/modules/client/preferences/species_features/lizard.dm +++ b/code/modules/client/preferences/species_features/lizard.dm @@ -94,7 +94,7 @@ correct_legs[BODY_ZONE_R_LEG] = /obj/item/bodypart/leg/right/digitigrade correct_legs[BODY_ZONE_L_LEG] = /obj/item/bodypart/leg/left/digitigrade - for(var/obj/item/bodypart/old_part as anything in target.bodyparts) + for(var/obj/item/bodypart/old_part as anything in target.get_bodyparts()) if(old_part.change_exempt_flags & BP_BLOCK_CHANGE_SPECIES) continue diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index 8f658884830..271322b1a49 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -452,7 +452,7 @@ return if (isandroid(check)) return TRUE - if (length(check.organs) < 6 || length(check.bodyparts) < 6) + if (length(check.organs) < 6 || length(check.get_missing_limbs()) > 1) return FALSE var/static/list/augmented_organ_slots = list( @@ -468,7 +468,7 @@ continue if (!IS_ROBOTIC_ORGAN(organ)) return FALSE - for (var/obj/item/bodypart/bodypart as anything in check.bodyparts) + for (var/obj/item/bodypart/bodypart as anything in check.get_bodyparts()) if (!IS_ROBOTIC_LIMB(bodypart)) return FALSE return TRUE diff --git a/code/modules/fishing/fish/types/holographic.dm b/code/modules/fishing/fish/types/holographic.dm index a4a10857d46..ae4f550ab85 100644 --- a/code/modules/fishing/fish/types/holographic.dm +++ b/code/modules/fishing/fish/types/holographic.dm @@ -115,7 +115,7 @@ if(!iscarbon(user)) return ..() - for(var/obj/item/bodypart/limb in user.bodyparts) + for(var/obj/item/bodypart/limb in user.get_bodyparts()) limb.add_color_override(COLOR_WHITE, LIMB_COLOR_CS_SOURCE_SUICIDE) limb.add_bodypart_overlay(new /datum/bodypart_overlay/texture/checkered(), update = FALSE) diff --git a/code/modules/fishing/fish/types/ruins.dm b/code/modules/fishing/fish/types/ruins.dm index 8654d45538f..db02dcd7130 100644 --- a/code/modules/fishing/fish/types/ruins.dm +++ b/code/modules/fishing/fish/types/ruins.dm @@ -167,7 +167,7 @@ return SHAME var/skin_tone - for(var/obj/item/bodypart/to_wound as anything in user.bodyparts) + for(var/obj/item/bodypart/to_wound as anything in user.get_bodyparts()) if(to_wound == user.get_bodypart(BODY_ZONE_CHEST)) skin_tone = to_wound.species_color || skintone2hex(to_wound.skin_tone) user.cause_wound_of_type_and_severity(WOUND_SLASH, to_wound, WOUND_SEVERITY_CRITICAL, WOUND_SEVERITY_CRITICAL) diff --git a/code/modules/food_and_drinks/machinery/gibber.dm b/code/modules/food_and_drinks/machinery/gibber.dm index 3422eee9944..0c661867370 100644 --- a/code/modules/food_and_drinks/machinery/gibber.dm +++ b/code/modules/food_and_drinks/machinery/gibber.dm @@ -234,7 +234,7 @@ var/mob/living/carbon/human/agent_whiskey = victim var/drop_chance = 0 - for (var/obj/item/bodypart/limb as anything in agent_whiskey.bodyparts) + for (var/obj/item/bodypart/limb as anything in agent_whiskey.get_bodyparts()) if (!limb.butcher_drops) continue diff --git a/code/modules/hallucination/blood_flow.dm b/code/modules/hallucination/blood_flow.dm index 7805768c252..71dbb5c8acf 100644 --- a/code/modules/hallucination/blood_flow.dm +++ b/code/modules/hallucination/blood_flow.dm @@ -11,11 +11,11 @@ return FALSE var/mob/living/carbon/carb_hallucinator = hallucinator - if(!length(carb_hallucinator.bodyparts) || !carb_hallucinator.can_bleed()) + var/list/bodyparts = carb_hallucinator.get_bodyparts() + if(!length(bodyparts) || !carb_hallucinator.can_bleed()) return FALSE var/obj/item/bodypart/picked - var/list/bodyparts = carb_hallucinator.bodyparts.Copy() while(isnull(picked) && length(bodyparts)) picked = pick_n_take(bodyparts) if(!picked.can_bleed()) diff --git a/code/modules/hallucination/screwy_health_doll.dm b/code/modules/hallucination/screwy_health_doll.dm index 35f7356b4d4..2ab8a8c928a 100644 --- a/code/modules/hallucination/screwy_health_doll.dm +++ b/code/modules/hallucination/screwy_health_doll.dm @@ -50,7 +50,7 @@ /datum/hallucination/fake_health_doll/proc/add_fake_limb(obj/item/bodypart/specific_limb, severity) var/mob/living/carbon/human/human_mob = hallucinator - var/obj/item/bodypart/picked = specific_limb || pick(human_mob.bodyparts) + var/obj/item/bodypart/picked = specific_limb || pick(human_mob.get_bodyparts()) if(!(picked in bodyparts)) RegisterSignals(picked, list(COMSIG_QDELETING, COMSIG_BODYPART_REMOVED), PROC_REF(remove_bodypart)) RegisterSignal(picked, COMSIG_BODYPART_UPDATING_HEALTH_HUD, PROC_REF(on_bodypart_hud_update)) diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index 936028f86f0..899224b33f3 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -49,7 +49,7 @@ if(!ishuman(spawned) || !prob(PIG_COP_PROBABILITY)) return var/mob/living/carbon/human/piggy = spawned - for (var/obj/item/bodypart/ham as anything in piggy.bodyparts) + for (var/obj/item/bodypart/ham as anything in piggy.get_bodyparts()) // These are string lists ham.butcher_drops = ham.butcher_drops.Copy() for (var/meat_type in ham.butcher_drops) diff --git a/code/modules/jobs/job_types/prisoner.dm b/code/modules/jobs/job_types/prisoner.dm index 8c55dfcdce0..99b3eab2155 100644 --- a/code/modules/jobs/job_types/prisoner.dm +++ b/code/modules/jobs/job_types/prisoner.dm @@ -73,7 +73,7 @@ var/datum/prisoner_crime/crime = GLOB.prisoner_crimes[crime_name] if (isnull(crime)) return - var/list/limbs_to_tat = new_prisoner.bodyparts.Copy() + var/list/limbs_to_tat = new_prisoner.get_bodyparts() for(var/i in 1 to crime.tattoos) if(!length(SSpersistence.prison_tattoos_to_use) || visuals_only) return diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index 06c78c25f1c..d78fb4f94b0 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -62,7 +62,7 @@ GLOBAL_LIST_EMPTY(security_officer_distribution) if(!ishuman(spawned) || !prob(PIG_COP_PROBABILITY)) return var/mob/living/carbon/human/piggy = spawned - for (var/obj/item/bodypart/ham as anything in piggy.bodyparts) + for (var/obj/item/bodypart/ham as anything in piggy.get_bodyparts()) // These are string lists ham.butcher_drops = ham.butcher_drops.Copy() for (var/meat_type in ham.butcher_drops) diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm index bcc07dfd2d6..eea6d428c10 100644 --- a/code/modules/jobs/job_types/warden.dm +++ b/code/modules/jobs/job_types/warden.dm @@ -47,7 +47,7 @@ if(!ishuman(spawned) || !prob(PIG_COP_PROBABILITY)) return var/mob/living/carbon/human/piggy = spawned - for (var/obj/item/bodypart/ham as anything in piggy.bodyparts) + for (var/obj/item/bodypart/ham as anything in piggy.get_bodyparts()) // These are string lists ham.butcher_drops = ham.butcher_drops.Copy() for (var/meat_type in ham.butcher_drops) diff --git a/code/modules/library/bibles.dm b/code/modules/library/bibles.dm index 950f2c70a5c..07a83912f00 100644 --- a/code/modules/library/bibles.dm +++ b/code/modules/library/bibles.dm @@ -215,7 +215,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list( return BLESSING_FAILED var/mob/living/carbon/human/built_in_his_image = blessed - for(var/obj/item/bodypart/bodypart as anything in built_in_his_image.bodyparts) + for(var/obj/item/bodypart/bodypart as anything in built_in_his_image.get_bodyparts()) if(!IS_ORGANIC_LIMB(bodypart)) balloon_alert(user, "can't heal inorganic!") return BLESSING_IGNORED diff --git a/code/modules/lost_crew/damages/post_mortem.dm b/code/modules/lost_crew/damages/post_mortem.dm index 5eeaabc3302..0e70b0cad62 100644 --- a/code/modules/lost_crew/damages/post_mortem.dm +++ b/code/modules/lost_crew/damages/post_mortem.dm @@ -36,7 +36,11 @@ /datum/corpse_damage/post_mortem/limb_loss/apply_to_body(mob/living/carbon/human/body, severity, list/saved_movables, list/datum/callback/on_revive_and_player_occupancy) var/limbs_to_take = round(min_limbs + (max_limbs - min_limbs) * severity) - var/list/limbs_we_can_take = body.bodyparts - body.get_bodypart(BODY_ZONE_HEAD) - body.get_bodypart(BODY_ZONE_CHEST) + var/list/limbs_we_can_take = list() + for(var/zone in GLOB.limb_zones) + var/bodypart = body.get_bodypart(zone) + if(bodypart) + limbs_we_can_take += bodypart if(!limbs_we_can_take.len) return diff --git a/code/modules/lost_crew/lost_crew_manager.dm b/code/modules/lost_crew/lost_crew_manager.dm index 1494204ad71..08d6d724681 100644 --- a/code/modules/lost_crew/lost_crew_manager.dm +++ b/code/modules/lost_crew/lost_crew_manager.dm @@ -36,9 +36,12 @@ GLOBAL_DATUM_INIT(lost_crew_manager, /datum/lost_crew_manager, new) var/datum/corpse_damage_class/scenario = forced_class || pick_weight(scenarios) scenario = new scenario () + new_body.living_flags |= STOP_OVERLAY_UPDATE_BODY_PARTS scenario.apply_character(new_body, protected_items, recovered_items, on_revive_and_player_occupancy, body_data) scenario.apply_injuries(new_body, recovered_items, on_revive_and_player_occupancy, body_data) scenario.death_lore += "I should get a formalized assignment!" + new_body.living_flags &= ~STOP_OVERLAY_UPDATE_BODY_PARTS + new_body.update_body_parts() . = new_body // so bodies can also be used for runes, morgue, etc diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index c3d55e7167b..4c57ced89bb 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -677,6 +677,7 @@ if(hud_used) hud_used.build_hand_slots() + hud_used.healthdoll.update_body_zones() //GetAllContents that is reasonable and not stupid /mob/living/proc/get_all_gear(equipment_flags = INCLUDE_ACCESSORIES|INCLUDE_PROSTHETICS, recursive = TRUE) diff --git a/code/modules/mob/living/basic/cult/constructs/harvester.dm b/code/modules/mob/living/basic/cult/constructs/harvester.dm index 8fc1226ab09..1c36453b184 100644 --- a/code/modules/mob/living/basic/cult/constructs/harvester.dm +++ b/code/modules/mob/living/basic/cult/constructs/harvester.dm @@ -46,7 +46,7 @@ return ..() var/mob/living/carbon/carbon_target = attack_target - for(var/obj/item/bodypart/limb as anything in carbon_target.bodyparts) + for(var/obj/item/bodypart/limb as anything in carbon_target.get_bodyparts()) if(limb.body_part == HEAD || limb.body_part == CHEST) continue return ..() //if any arms or legs exist, attack diff --git a/code/modules/mob/living/basic/farm_animals/goat/_goat.dm b/code/modules/mob/living/basic/farm_animals/goat/_goat.dm index 583542ff5ce..0f94efaf7fb 100644 --- a/code/modules/mob/living/basic/farm_animals/goat/_goat.dm +++ b/code/modules/mob/living/basic/farm_animals/goat/_goat.dm @@ -79,7 +79,7 @@ if(ishuman(living_target)) var/mob/living/carbon/human/plant_man = target - edible_bodypart = pick(plant_man.bodyparts) + edible_bodypart = pick(plant_man.get_bodyparts()) edible_bodypart.dismember() living_target.visible_message( diff --git a/code/modules/mob/living/basic/ruin_defender/flesh.dm b/code/modules/mob/living/basic/ruin_defender/flesh.dm index 5d607c2c1a4..94bb50f026d 100644 --- a/code/modules/mob/living/basic/ruin_defender/flesh.dm +++ b/code/modules/mob/living/basic/ruin_defender/flesh.dm @@ -114,7 +114,7 @@ return var/list/zone_candidates = target.get_missing_limbs() - for(var/obj/item/bodypart/bodypart in target.bodyparts) + for(var/obj/item/bodypart/bodypart in target.get_bodyparts()) if(bodypart.body_zone == BODY_ZONE_HEAD || bodypart.body_zone == BODY_ZONE_CHEST) continue if(HAS_TRAIT(bodypart, TRAIT_IGNORED_BY_LIVING_FLESH)) diff --git a/code/modules/mob/living/basic/space_fauna/lightgeist.dm b/code/modules/mob/living/basic/space_fauna/lightgeist.dm index f59c5070bff..36ae135619c 100644 --- a/code/modules/mob/living/basic/space_fauna/lightgeist.dm +++ b/code/modules/mob/living/basic/space_fauna/lightgeist.dm @@ -103,7 +103,7 @@ if (!iscarbon(target)) return target.get_brute_loss() > 0 || target.get_fire_loss() > 0 var/mob/living/carbon/carbon_target = target - for (var/obj/item/bodypart/part in carbon_target.bodyparts) + for (var/obj/item/bodypart/part in carbon_target.get_bodyparts()) if (!part.brute_dam && !part.burn_dam) continue if (!(part.bodytype & required_bodytype)) diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 5b75da86443..f06bed87776 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -180,7 +180,7 @@ bleed(bleed_rate * seconds_per_tick) bleed_warn(bleed_rate) - for (var/obj/item/bodypart/bodypart as anything in bodyparts) + for (var/obj/item/bodypart/bodypart as anything in get_bodyparts()) if (bodypart.generic_bleedstacks) bodypart.adjustBleedStacks(-1, 0) @@ -263,7 +263,7 @@ /// Has each bodypart update its bleed/wound overlay icon states /mob/living/carbon/proc/update_bodypart_bleed_overlays() - for(var/obj/item/bodypart/iter_part as anything in bodyparts) + for(var/obj/item/bodypart/iter_part as anything in get_bodyparts()) iter_part.update_part_wound_overlay() /// Bleeds amount units of blood from the mob, sometimes creating a blood splatter on the floor. @@ -290,7 +290,7 @@ return 0 . = 0 - for(var/obj/item/bodypart/bodypart as anything in bodyparts) + for(var/obj/item/bodypart/bodypart as anything in get_bodyparts()) . += bodypart.cached_bleed_rate /mob/living/carbon/human/get_bleed_rate() @@ -359,7 +359,7 @@ /mob/living/carbon/restore_blood() . = ..() - for(var/obj/item/bodypart/bodypart_to_restore as anything in bodyparts) + for(var/obj/item/bodypart/bodypart_to_restore as anything in get_bodyparts()) bodypart_to_restore.setBleedStacks(0) /**************************************************** diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index d1ee541429f..bc884201e83 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -425,8 +425,7 @@ return var/total_burn = 0 var/total_brute = 0 - for(var/X in bodyparts) //hardcoded to streamline things a bit - var/obj/item/bodypart/BP = X + for(var/obj/item/bodypart/BP as anything in get_bodyparts()) total_brute += (BP.brute_dam * BP.body_damage_coeff) total_burn += (BP.burn_dam * BP.body_damage_coeff) set_health(round(maxHealth - get_oxy_loss() - get_tox_loss() - total_burn - total_brute, DAMAGE_PRECISION)) @@ -773,7 +772,7 @@ if(heal_flags & HEAL_LIMBS) regenerate_limbs() - for(var/obj/item/bodypart/limb as anything in bodyparts) + for(var/obj/item/bodypart/limb as anything in get_bodyparts(include_stumps = TRUE)) limb.remove_surgical_state(ALL) if(heal_flags & (HEAL_REFRESH_ORGANS|HEAL_ORGANS)) @@ -911,13 +910,15 @@ switch(new_bodypart.body_part) if(LEG_LEFT, LEG_RIGHT) - set_num_legs(num_legs + 1) - if(!new_bodypart.bodypart_disabled) - set_usable_legs(usable_legs + 1) + if(!IS_STUMP(new_bodypart)) + set_num_legs(num_legs + 1) + if(!new_bodypart.bodypart_disabled) + set_usable_legs(usable_legs + 1) if(ARM_LEFT, ARM_RIGHT) - set_num_hands(num_hands + 1) - if(!new_bodypart.bodypart_disabled) - set_usable_hands(usable_hands + 1) + if(!IS_STUMP(new_bodypart)) + set_num_hands(num_hands + 1) + if(!new_bodypart.bodypart_disabled) + set_usable_hands(usable_hands + 1) synchronize_bodytypes() synchronize_bodyshapes() @@ -938,13 +939,27 @@ switch(old_bodypart.body_part) if(LEG_LEFT, LEG_RIGHT) - set_num_legs(num_legs - 1) - if(!old_bodypart.bodypart_disabled) - set_usable_legs(usable_legs - 1) + if(!IS_STUMP(old_bodypart)) + set_num_legs(num_legs - 1) + if(!old_bodypart.bodypart_disabled) + set_usable_legs(usable_legs - 1) if(ARM_LEFT, ARM_RIGHT) - set_num_hands(num_hands - 1) - if(!old_bodypart.bodypart_disabled) - set_usable_hands(usable_hands - 1) + if(!IS_STUMP(old_bodypart)) + set_num_hands(num_hands - 1) + if(!old_bodypart.bodypart_disabled) + set_usable_hands(usable_hands - 1) + + if(!special && old_bodypart.stump_typepath) + if(old_bodypart.type == old_bodypart.stump_typepath) + stack_trace("Attempted to replace a stump with a stump") + else + var/obj/item/bodypart/stump = new old_bodypart.stump_typepath() + stump.bodyshape = old_bodypart.bodyshape + stump.bodytype = old_bodypart.bodytype + if(!stump.try_attach_limb(src, special = TRUE)) + // the only way this can happen is if the stump is rejected via signal + // not much we can do about that besides hope they know what they're doing + qdel(stump) synchronize_bodytypes() synchronize_bodyshapes() @@ -952,7 +967,7 @@ ///Updates the bodypart speed modifier based on our bodyparts. /mob/living/carbon/proc/update_bodypart_speed_modifier() var/final_modification = 0 - for(var/obj/item/bodypart/leg/bodypart in bodyparts) + for(var/obj/item/bodypart/leg/bodypart in get_bodyparts()) final_modification += bodypart.speed_modifier add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/bodypart, update = TRUE, multiplicative_slowdown = final_modification) @@ -985,7 +1000,7 @@ return var/list/limb_list = list() if(edit_action == "remove") - for(var/obj/item/bodypart/iter_part as anything in bodyparts) + for(var/obj/item/bodypart/iter_part as anything in get_bodyparts()) limb_list += iter_part.body_zone limb_list -= BODY_ZONE_CHEST else @@ -1019,7 +1034,7 @@ var/limb2add = input(usr, "Select a bodypart type to add", "Add/Replace Bodypart") as null|anything in sort_list(limbtypes) var/obj/item/bodypart/new_bp = new limb2add() if(new_bp.replace_limb(src)) - admin_ticket_log("key_name_admin(usr)] has replaced [src]'s [part.type] with [new_bp.type]") + admin_ticket_log("key_name_admin(usr)] has replaced [src]'s [part?.type || "missing limb"] with [new_bp.type]") qdel(part) else to_chat(usr, "Failed to replace bodypart! They might be incompatible.") @@ -1097,7 +1112,7 @@ /mob/living/carbon/proc/is_bleeding() if(!CAN_HAVE_BLOOD(src)) return FALSE - for(var/obj/item/bodypart/part as anything in bodyparts) + for(var/obj/item/bodypart/part as anything in get_bodyparts()) if(part.cached_bleed_rate) return TRUE @@ -1107,7 +1122,7 @@ return FALSE var/total_bleed_rate = 0 - for(var/obj/item/bodypart/part as anything in bodyparts) + for(var/obj/item/bodypart/part as anything in get_bodyparts()) total_bleed_rate += part.cached_bleed_rate return total_bleed_rate @@ -1269,9 +1284,9 @@ /// Goes through the organs and bodyparts of the mob and updates their blood_dna_info, in case their blood type has changed (via set_species() or otherwise) /mob/living/carbon/proc/update_cached_blood_dna_info() var/list/blood_dna_info = get_blood_dna_list() - for(var/obj/item/organ/organ in organs) + for(var/obj/item/organ/organ as anything in organs) organ.blood_dna_info = blood_dna_info - for(var/obj/item/bodypart/bodypart in bodyparts) + for(var/obj/item/bodypart/bodypart as anything in get_bodyparts()) bodypart.blood_dna_info = blood_dna_info /// Setter for changing a mob's blood type diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 5dc3cbd7d70..497f133b117 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -453,7 +453,7 @@ return var/embeds = FALSE - for(var/obj/item/bodypart/limb as anything in bodyparts) + for(var/obj/item/bodypart/limb as anything in get_bodyparts()) for(var/obj/item/weapon as anything in limb.embedded_objects) if(!embeds) embeds = TRUE @@ -562,8 +562,7 @@ /mob/living/carbon/get_organic_health() . = health - for (var/_limb in bodyparts) - var/obj/item/bodypart/limb = _limb + for (var/obj/item/bodypart/limb as anything in get_bodyparts()) if (!IS_ORGANIC_LIMB(limb)) . += (limb.brute_dam * limb.body_damage_coeff) + (limb.burn_dam * limb.body_damage_coeff) diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index 271eef3cd40..b759c290efe 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -290,7 +290,7 @@ remove_overlay(DAMAGE_LAYER) var/mutable_appearance/damage_overlay - for(var/obj/item/bodypart/iter_part as anything in bodyparts) + for(var/obj/item/bodypart/iter_part as anything in get_bodyparts()) if(!iter_part.dmg_overlay_type) continue if(isnull(damage_overlay) && (iter_part.brutestate || iter_part.burnstate)) @@ -319,7 +319,7 @@ return var/mutable_appearance/wound_overlay - for(var/obj/item/bodypart/iter_part as anything in bodyparts) + for(var/obj/item/bodypart/iter_part as anything in get_bodyparts()) if(iter_part.bleed_overlay_icon) var/mutable_appearance/blood_overlay = mutable_appearance('icons/mob/effects/bleed_overlays.dmi', "blank", -WOUND_LAYER, appearance_flags = KEEP_TOGETHER) blood_overlay.color = blood_type.get_wound_color(src) @@ -463,38 +463,35 @@ /mob/living/carbon/proc/update_body_parts(update_limb_data) update_damage_overlays() update_wound_overlays() - var/list/needs_update = list() var/limb_count_update = 0 - for(var/obj/item/bodypart/limb as anything in bodyparts) - limb.update_limb(is_creating = update_limb_data) //Update limb actually doesn't do much, get_limb_icon is the cpu eater. + var/list/new_limbs = list() + for(var/body_zone, limb_untyped in get_bodyparts_by_zones()) + var/obj/item/bodypart/limb = limb_untyped + if(isnull(limb) || IS_STUMP(limb)) + if(icon_render_keys[body_zone]) + icon_render_keys -= body_zone + limb_count_update += 1 + continue - var/old_key = icon_render_keys?[limb.body_zone] //Checks the mob's icon render key list for the bodypart - icon_render_keys[limb.body_zone] = (limb.is_husked) ? limb.generate_husk_key().Join() : limb.generate_icon_key().Join() //Generates a key for the current bodypart + // Update limb actually doesn't do much, get_limb_icon is the cpu eater. + limb.update_limb(is_creating = update_limb_data) - if(icon_render_keys[limb.body_zone] != old_key) //If the keys match, that means the limb doesn't need to be redrawn - needs_update += limb + var/old_key = icon_render_keys[limb.body_zone] + var/new_key = limb.get_cache_key() - limb_count_update += length(needs_update) - var/list/missing_bodyparts = get_missing_limbs() - if(((dna ? dna.species.max_bodypart_count : BODYPARTS_DEFAULT_MAXIMUM) - icon_render_keys.len) != missing_bodyparts.len) //Checks to see if the target gained or lost any limbs. - limb_count_update += 1 - for(var/missing_limb in missing_bodyparts) - icon_render_keys -= missing_limb //Removes dismembered limbs from the key list + if(new_key == old_key) + new_limbs += limb_icon_cache[new_key] + + else + limb_icon_cache[new_key] ||= limb.get_limb_icon(dropped = FALSE) + new_limbs += limb_icon_cache[new_key] + icon_render_keys[limb.body_zone] = new_key + limb_count_update += 1 . = limb_count_update if(!.) return - //GENERATE NEW LIMBS - var/list/new_limbs = list() - for(var/obj/item/bodypart/limb as anything in bodyparts) - if(limb in needs_update) - var/bodypart_icon = limb.get_limb_icon(dropped = FALSE, update_on = src) - new_limbs += bodypart_icon - limb_icon_cache[icon_render_keys[limb.body_zone]] = bodypart_icon //Caches the icon with the bodypart key, as it is new - else - new_limbs += limb_icon_cache[icon_render_keys[limb.body_zone]] //Pulls existing sprites from the cache - remove_overlay(BODYPARTS_LAYER) if(new_limbs.len) @@ -508,6 +505,13 @@ ///////////////////////// // Limb Icon Cache 2.0 // ///////////////////////// + +/// Returns a string representing the bodyparts icon cache key +/obj/item/bodypart/proc/get_cache_key() + if(is_husked) + return jointext(generate_husk_key(), "-") + return jointext(generate_icon_key(), "-") + /** * Called from update_body_parts() these procs handle the limb icon cache. * the limb icon cache adds an icon_render_key to a human mob, it represents: @@ -522,20 +526,20 @@ RETURN_TYPE(/list) . = list() if(is_dimorphic) - . += "[limb_gender]-" - . += "[limb_id]" - . += "-[body_zone]" + . += limb_gender + . += limb_id + . += body_zone if(should_draw_greyscale && draw_color) - . += "-[draw_color]" + . += draw_color if(is_invisible) - . += "-invisible" + . += "invisible" for(var/datum/bodypart_overlay/overlay as anything in bodypart_overlays) if(!overlay.can_draw_on_bodypart(src, owner, is_husked)) continue - . += "-[jointext(overlay.generate_icon_cache(), "-")]" + . += overlay.generate_icon_cache() if(ishuman(owner)) var/mob/living/carbon/human/human_owner = owner - . += "-[human_owner.mob_height]" + . += "[human_owner.mob_height]" SEND_SIGNAL(src, COMSIG_BODYPART_GENERATE_ICON_KEY, .) return . @@ -543,24 +547,22 @@ /obj/item/bodypart/proc/generate_husk_key() RETURN_TYPE(/list) . = list() - . += "[limb_id]-" - . += "[husk_type]" - . += "-husk" - . += "-[body_zone]" + if(is_dimorphic) + . += limb_gender + . += limb_id + . += husk_type + . += "husk" + . += body_zone if(is_invisible) - . += "-invisible" - var/list/blood_dna = blood_dna_info || owner?.get_blood_dna_list() - if (LAZYLEN(blood_dna)) - . += "-[get_color_from_blood_list(blood_dna)]" - else - . += "-[BLOOD_COLOR_RED]" + . += "invisible" + . += "[LAZYLEN(blood_dna_info) ? get_color_from_blood_list(blood_dna_info) : BLOOD_COLOR_RED]" for(var/datum/bodypart_overlay/overlay as anything in bodypart_overlays) if(!overlay.can_draw_on_bodypart(src, owner, TRUE)) continue - . += "-[jointext(overlay.generate_icon_cache(), "-")]" + . += overlay.generate_icon_cache() if(ishuman(owner)) var/mob/living/carbon/human/human_owner = owner - . += "-[human_owner.mob_height]" + . += "[human_owner.mob_height]" return . /obj/item/bodypart/head/generate_icon_key() diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm index 191b303f715..fc2a98df0c0 100644 --- a/code/modules/mob/living/carbon/damage_procs.dm +++ b/code/modules/mob/living/carbon/damage_procs.dm @@ -20,7 +20,7 @@ // ALso we'll automatically covnert string def zones into bodyparts to pass into parent call. else if(!isbodypart(def_zone)) var/random_zone = check_zone(def_zone || get_random_valid_zone(def_zone)) - def_zone = get_bodypart(random_zone) || bodyparts[1] + def_zone = get_bodypart(random_zone) || get_bodypart() . = ..() // Taking brute or burn to bodyparts gives a damage flash @@ -84,13 +84,13 @@ //These procs fetch a cumulative total damage from all bodyparts /mob/living/carbon/get_brute_loss() var/amount = 0 - for(var/obj/item/bodypart/bodypart as anything in bodyparts) + for(var/obj/item/bodypart/bodypart as anything in get_bodyparts()) amount += bodypart.brute_dam return round(amount, DAMAGE_PRECISION) /mob/living/carbon/get_fire_loss() var/amount = 0 - for(var/obj/item/bodypart/bodypart as anything in bodyparts) + for(var/obj/item/bodypart/bodypart as anything in get_bodyparts()) amount += bodypart.burn_dam return round(amount, DAMAGE_PRECISION) @@ -104,7 +104,7 @@ */ /mob/living/carbon/proc/get_brute_loss_for_type(required_bodytype = ALL) var/amount = 0 - for(var/obj/item/bodypart/bodypart as anything in bodyparts) + for(var/obj/item/bodypart/bodypart as anything in get_bodyparts()) if(!(bodypart.bodytype & required_bodytype)) continue amount += bodypart.brute_dam @@ -119,7 +119,7 @@ */ /mob/living/carbon/proc/get_fire_loss_for_type(required_bodytype = ALL) var/amount = 0 - for(var/obj/item/bodypart/bodypart as anything in bodyparts) + for(var/obj/item/bodypart/bodypart as anything in get_bodyparts()) if(!(bodypart.bodytype & required_bodytype)) continue amount += bodypart.burn_dam @@ -239,8 +239,7 @@ ///Returns a list of damaged bodyparts /mob/living/carbon/proc/get_damaged_bodyparts(brute = FALSE, burn = FALSE, required_bodytype = NONE, target_zone = null) var/list/obj/item/bodypart/parts = list() - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X + for(var/obj/item/bodypart/BP as anything in get_bodyparts()) if(required_bodytype && !(BP.bodytype & required_bodytype)) continue if(!isnull(target_zone) && BP.body_zone != target_zone) @@ -252,8 +251,7 @@ ///Returns a list of damageable bodyparts /mob/living/carbon/proc/get_damageable_bodyparts(required_bodytype) var/list/obj/item/bodypart/parts = list() - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X + for(var/obj/item/bodypart/BP as anything in get_bodyparts()) if(required_bodytype && !(BP.bodytype & required_bodytype)) continue if(BP.brute_dam + BP.burn_dam < BP.max_damage) @@ -264,8 +262,7 @@ ///Returns a list of bodyparts with wounds (in case someone has a wound on an otherwise fully healed limb) /mob/living/carbon/proc/get_wounded_bodyparts(required_bodytype) var/list/obj/item/bodypart/parts = list() - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X + for(var/obj/item/bodypart/BP as anything in get_bodyparts()) if(required_bodytype && !(BP.bodytype & required_bodytype)) continue if(LAZYLEN(BP.wounds)) diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm index 77611210d8c..292eda9c6ac 100644 --- a/code/modules/mob/living/carbon/death.dm +++ b/code/modules/mob/living/carbon/death.dm @@ -28,7 +28,7 @@ return ..() /mob/living/carbon/get_gibs_type(drop_bitflags = NONE) - var/obj/item/bodypart/chest = get_bodypart(BODY_ZONE_CHEST) || (length(bodyparts) ? bodyparts[1] : null) + var/obj/item/bodypart/chest = get_bodypart(BODY_ZONE_CHEST) || get_bodypart() if (!istype(chest)) // what return ..() @@ -74,13 +74,22 @@ qdel(organ) /mob/living/carbon/spread_bodyparts(drop_bitflags=NONE) - for(var/obj/item/bodypart/part as anything in bodyparts) + for(var/obj/item/bodypart/part as anything in get_bodyparts()) + if(part.body_zone == BODY_ZONE_CHEST) + continue // never drop this if(!(drop_bitflags & DROP_BRAIN) && part.body_zone == BODY_ZONE_HEAD) - continue - else if(part.body_zone == BODY_ZONE_CHEST) - continue - part.drop_limb() + continue // don't drop head if we aren't dropping a brain + + var/list/leftover_organs = list() + for(var/obj/item/organ/leftover in part) + leftover_organs += leftover + + part.drop_limb(TRUE) part.throw_at(get_edge_target_turf(src, pick(GLOB.alldirs)), rand(1,3), 5) + // any organs that weren't throw out already about need to follow the bodypart out + for(var/obj/item/organ/leftover as anything in leftover_organs) + leftover.Remove(src, TRUE) + leftover.bodypart_insert(part) /mob/living/carbon/set_suicide(suicide_state) //you thought that box trick was pretty clever, didn't you? well now hardmode is on, boyo. . = ..() diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index 946e009d4f8..279e65b8355 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -50,7 +50,7 @@ . += span_deadsay("It appears that [t_his] brain is missing...") var/list/disabled = list() - for(var/obj/item/bodypart/body_part as anything in bodyparts) + for(var/obj/item/bodypart/body_part as anything in get_bodyparts()) if(body_part.bodypart_disabled) disabled += body_part @@ -178,7 +178,7 @@ var/list/obj/item/bodypart/bleeding_limbs = list() var/list/obj/item/bodypart/grasped_limbs = list() - for(var/obj/item/bodypart/body_part as anything in bodyparts) + for(var/obj/item/bodypart/body_part as anything in get_bodyparts()) if(body_part.cached_bleed_rate) bleeding_limbs += body_part.plaintext_zone if(body_part.grasped_by) @@ -338,7 +338,7 @@ var/list/seen_damage = list() // This looks like: ({Damage type} = list({Damage description for that damage type} = {number of times it has appeared}, ...), ...) var/list/most_seen_damage = list() // This looks like: ({Damage type} = {Frequency of the most common description}, ...) var/list/final_descriptions = list() // This looks like: ({Damage type} = {Most common damage description for that type}, ...) - for(var/obj/item/bodypart/part as anything in bodyparts) + for(var/obj/item/bodypart/part as anything in get_bodyparts()) for(var/damage_type in part.damage_examines) var/damage_desc = part.damage_examines[damage_type] if(!seen_damage[damage_type]) @@ -373,7 +373,7 @@ if((held_thing.item_flags & (ABSTRACT|HAND_ITEM)) || HAS_TRAIT(held_thing, TRAIT_EXAMINE_SKIP)) continue . += "[t_He] [t_is] holding [held_thing.examine_title(user)] in [t_his] [get_held_index_name(get_held_index_of_item(held_thing))]." - for(var/obj/item/bodypart/arm/part in bodyparts) + for(var/obj/item/bodypart/arm/part in get_bodyparts()) if(!(part.bodypart_flags & BODYPART_PSEUDOPART)) continue var/obj/item/corresponding_item = get_item_for_held_index(part.held_index) || part @@ -475,7 +475,7 @@ if((held_thing.item_flags & (ABSTRACT|HAND_ITEM)) || HAS_TRAIT(held_thing, TRAIT_EXAMINE_SKIP)) continue . += "[t_He] [t_is] holding [held_thing.examine_title(user)] in [t_his] [get_held_index_name(get_held_index_of_item(held_thing))]." - for(var/obj/item/bodypart/arm/part in bodyparts) + for(var/obj/item/bodypart/arm/part in get_bodyparts()) if(!(part.bodypart_flags & BODYPART_PSEUDOPART)) continue var/obj/item/corresponding_item = get_item_for_held_index(part.held_index) || part @@ -594,7 +594,7 @@ /mob/living/carbon/human/proc/get_mismatched_limb_text() var/list/covered = get_covered_body_zones() var/list/texts = list() - for(var/obj/item/bodypart/part as anything in bodyparts) + for(var/obj/item/bodypart/part as anything in get_bodyparts()) var/part_id = part.limb_id var/obj/item/bodypart/expected_part = dna?.species?.bodypart_overrides[part.body_zone] var/expected_id = initial(expected_part?.limb_id) diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 406b3eb8e26..75dbe2f24e9 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -189,6 +189,8 @@ GLOBAL_LIST_EMPTY(features_by_species) plural_form = "[name]\s" if(!examine_limb_id) examine_limb_id = id + // Carbons determine bodypart order by this list, so we need to make sure it's sorted properly + sortTim(bodypart_overrides, GLOBAL_PROC_REF(cmp_bodypart_by_body_part_asc), associative = TRUE) return ..() @@ -1270,7 +1272,7 @@ GLOBAL_LIST_EMPTY(features_by_species) return // Lets pick a random body part and check for an existing burn - var/obj/item/bodypart/bodypart = pick(humi.bodyparts) + var/obj/item/bodypart/bodypart = pick(humi.get_bodyparts()) var/datum/wound/existing_burn for (var/datum/wound/iterated_wound as anything in bodypart.wounds) var/datum/wound_pregen_data/pregen_data = iterated_wound.get_pregen_data() @@ -1976,7 +1978,7 @@ GLOBAL_LIST_EMPTY(features_by_species) final_bodypart_overrides[BODY_ZONE_R_LEG] = /obj/item/bodypart/leg/right/digitigrade final_bodypart_overrides[BODY_ZONE_L_LEG] = /obj/item/bodypart/leg/left/digitigrade - for(var/obj/item/bodypart/old_part as anything in target.bodyparts) + for(var/obj/item/bodypart/old_part as anything in target.get_bodyparts()) if((old_part.change_exempt_flags & BP_BLOCK_CHANGE_SPECIES) || (old_part.bodypart_flags & BODYPART_IMPLANTED)) continue @@ -2048,7 +2050,7 @@ GLOBAL_LIST_EMPTY(features_by_species) /// Remove body markings /datum/species/proc/remove_body_markings(mob/living/carbon/human/hooman) - for(var/obj/item/bodypart/part as anything in hooman.bodyparts) + for(var/obj/item/bodypart/part as anything in hooman.get_bodyparts()) for(var/datum/bodypart_overlay/simple/body_marking/marking in part.bodypart_overlays) part.remove_bodypart_overlay(marking) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index a49e73fc415..187e684d06e 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -192,8 +192,7 @@ var/status = "" if(get_brute_loss()) to_chat(human_user, "Physical trauma analysis:") - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X + for(var/obj/item/bodypart/BP as anything in get_bodyparts()) var/brutedamage = BP.brute_dam if(brutedamage > 0) status = "received minor physical injuries." @@ -208,8 +207,7 @@ to_chat(human_user, "[BP] appears to have [status]") if(get_fire_loss()) to_chat(human_user, "Analysis of skin burns:") - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X + for(var/obj/item/bodypart/BP as anything in get_bodyparts()) var/burndamage = BP.burn_dam if(burndamage > 0) status = "signs of minor burns." diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index fedc4d6e58c..86b5d138354 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -13,8 +13,7 @@ //If a specific bodypart is targeted, check how that bodypart is protected and return the value. //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X + for(var/obj/item/bodypart/BP as anything in get_bodyparts()) armorval += check_armor(BP, type) organnum++ return (armorval/max(organnum, 1)) @@ -331,8 +330,7 @@ if(EXPLODE_DEVASTATE) max_limb_loss = 4 probability = 50 - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X + for(var/obj/item/bodypart/BP as anything in get_bodyparts()) if(prob(probability) && !prob(getarmor(BP, BOMB)) && BP.body_zone != BODY_ZONE_HEAD && BP.body_zone != BODY_ZONE_CHEST) BP.receive_damage(INFINITY, wound_bonus = CANT_WOUND) //Capped by proc BP.dismember() @@ -557,10 +555,12 @@ combined_msg += span_notice("You check yourself for injuries.") - var/list/missing = get_all_limbs() - for(var/obj/item/bodypart/body_part as anything in bodyparts) - missing -= body_part.body_zone + for(var/part_zone, body_part_untyped in get_bodyparts_by_zones()) + var/obj/item/bodypart/body_part = body_part_untyped + if(isnull(body_part) || IS_STUMP(body_part)) + combined_msg += span_boldannounce("↳ Your [parse_zone(body_part?.body_zone || part_zone)] is missing!") + continue if(body_part.bodypart_flags & BODYPART_PSEUDOPART) //don't show injury text for fake bodyparts; ie chainsaw arms or synthetic armblades continue @@ -568,9 +568,6 @@ if(bodypart_report) combined_msg += "[span_notice("↳")] [bodypart_report]" - for(var/t in missing) - combined_msg += span_boldannounce("↳ Your [parse_zone(t)] is missing!") - var/tox = get_tox_loss() + (disgust / 5) + (HAS_TRAIT(src, TRAIT_SELF_AWARE) ? 0 : (rand(-3, 0) * 5)) switch(tox) if(10 to 20) diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index 7b0d8e7dd0e..15d8175ffcf 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -967,7 +967,7 @@ generate/load female uniform sprites matching all previously decided variables // optimization - none of our limbs or organs have the desired shape return . - for(var/obj/item/bodypart/limb as anything in bodyparts) + for(var/obj/item/bodypart/limb as anything in get_bodyparts()) var/checked_bodyshape = limb.bodyshape // accounts for stuff like snouts for(var/obj/item/organ/organ in limb) diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 439ed75d544..10e9edfb715 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -413,6 +413,8 @@ new_bodypart = newBodyPart(BODY_ZONE_L_ARM) new_bodypart.held_index = i + if(i >= 3) // start indexing them as right_arm2 and so on + new_bodypart.body_zone = "[new_bodypart.body_zone]_[ceil(i / 2)]" new_bodypart.try_attach_limb(src, TRUE) hand_bodyparts[i] = new_bodypart ..() //Don't redraw hands until we have organs for them diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index 2d6754d2b95..bfa1ae5649b 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -67,7 +67,7 @@ var/obj/item/organ/heart/ethereal/ethereal_heart = new_ethereal.get_organ_slot(ORGAN_SLOT_HEART) ethereal_heart.ethereal_color = default_color - for(var/obj/item/bodypart/limb as anything in new_ethereal.bodyparts) + for(var/obj/item/bodypart/limb as anything in new_ethereal.get_bodyparts()) if(limb.limb_id == SPECIES_ETHEREAL) limb.update_limb(is_creating = TRUE) diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index c2e5bb95d46..5fa890e834c 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -502,7 +502,7 @@ return COMPONENT_NO_EXPOSE_REAGENTS /mob/living/carbon/proc/handle_bodyparts(seconds_per_tick) - for(var/obj/item/bodypart/limb as anything in bodyparts) + for(var/obj/item/bodypart/limb as anything in get_bodyparts(include_stumps = TRUE)) . |= limb.on_life(seconds_per_tick) /mob/living/carbon/proc/handle_organs(seconds_per_tick) diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 80189ae4902..32421516713 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -25,6 +25,13 @@ if(drop_bitflags & DROP_BODYPARTS) spread_bodyparts(drop_bitflags) + // failsafe for if we fuck up and leave our brain behind. (other organs are replaceable so we can ignore them.) + var/obj/item/organ/brain/brain = get_organ_slot(ORGAN_SLOT_BRAIN) + if((drop_bitflags & DROP_BRAIN) && !isnull(brain)) + stack_trace("gib invoked with drop_brain() had their brain after spilling organs and bodyparts, meaning both failed!") + brain.Remove(src) + brain.forceMove(drop_location()) + SEND_SIGNAL(src, COMSIG_LIVING_GIBBED, drop_bitflags) qdel(src) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 25cc456dda3..d9466e62241 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -2536,7 +2536,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) return . = num_legs num_legs = new_value - + hud_used?.update_locked_slots() ///Proc to modify the value of usable_legs and hook behavior associated to this event. /mob/living/proc/set_usable_legs(new_value) @@ -2586,7 +2586,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) return . = num_hands num_hands = new_value - + hud_used?.update_locked_slots() ///Proc to modify the value of usable_hands and hook behavior associated to this event. /mob/living/proc/set_usable_hands(new_value) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 02e382c7117..40ee61766b8 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -62,7 +62,7 @@ /mob/living/carbon/get_random_valid_zone(base_zone, base_probability = 80, list/blacklisted_parts, even_weights, bypass_warning) var/list/limbs = list() - for(var/obj/item/bodypart/part as anything in bodyparts) + for(var/obj/item/bodypart/part as anything in get_bodyparts()) var/limb_zone = part.body_zone //cache the zone since we're gonna check it a ton. if(limb_zone in blacklisted_parts) continue diff --git a/code/modules/projectiles/ammunition/ballistic/rifle.dm b/code/modules/projectiles/ammunition/ballistic/rifle.dm index a252832dcd0..3c06aa13950 100644 --- a/code/modules/projectiles/ammunition/ballistic/rifle.dm +++ b/code/modules/projectiles/ammunition/ballistic/rifle.dm @@ -161,7 +161,7 @@ /datum/embedding/rebar_healium/on_successful_embed(mob/living/carbon/victim, obj/item/bodypart/target_limb) . = ..() - for(var/obj/item/bodypart/limb as anything in victim.bodyparts) + for(var/obj/item/bodypart/limb as anything in victim.get_bodyparts()) for(var/obj/item/ammo_casing/rebar/healium/other_rebar in limb.embedded_objects) if (other_rebar == parent) continue diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm index bdbe041d2cf..79f5cfb4fd2 100644 --- a/code/modules/projectiles/guns/magic/staff.dm +++ b/code/modules/projectiles/guns/magic/staff.dm @@ -362,7 +362,7 @@ if (!iscarbon(user)) return BRUTELOSS var/mob/living/carbon/suicider = user - for (var/obj/item/bodypart/limb in suicider.bodyparts) + for (var/obj/item/bodypart/limb in suicider.get_bodyparts()) limb.dismember(BRUTE, silent = FALSE, wounding_type = WOUND_SLASH) sleep(0.25 SECONDS) diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm index 1b0dad125df..ba6ff560c9f 100644 --- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm @@ -1264,7 +1264,7 @@ Basically, we fill the time between now and 2s from now with hands based off the for (var/obj/item/organ/organ as anything in exposed_carbon.organs) organ.add_atom_colour(color_filter, WASHABLE_COLOUR_PRIORITY) - for (var/obj/item/bodypart/part as anything in exposed_carbon.bodyparts) + for (var/obj/item/bodypart/part as anything in exposed_carbon.get_bodyparts()) part.add_atom_colour(color_filter, WASHABLE_COLOUR_PRIORITY) /datum/reagent/inverse/colorful_reagent/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, metabolization_ratio) @@ -1313,22 +1313,22 @@ Basically, we fill the time between now and 2s from now with hands based off the switch(current_cycle) if(10) - for(var/obj/item/bodypart/leg/leg in affected_mob.bodyparts) + for(var/obj/item/bodypart/leg/leg in affected_mob.get_bodyparts()) affected_mob.cause_wound_of_type_and_severity(WOUND_BLUNT, leg, WOUND_SEVERITY_MODERATE) to_chat(affected_mob, span_warning("Your legs start to cave in to your overwhelming gravity!")) if(20) - for(var/obj/item/bodypart/leg/leg in affected_mob.bodyparts) + for(var/obj/item/bodypart/leg/leg in affected_mob.get_bodyparts()) affected_mob.cause_wound_of_type_and_severity(WOUND_BLUNT, leg, WOUND_SEVERITY_SEVERE) to_chat(affected_mob, span_warning("Your bones fragment horribly as the gravity pounds on you!")) if(30) - for(var/obj/item/bodypart/leg/leg in affected_mob.bodyparts) + for(var/obj/item/bodypart/leg/leg in affected_mob.get_bodyparts()) affected_mob.cause_wound_of_type_and_severity(WOUND_BLUNT, leg, WOUND_SEVERITY_CRITICAL) to_chat(affected_mob, span_warning("The gravity of this situation makes your bones snap like popsicle sticks!")) /datum/reagent/inverse/gravitum/overdose_start(mob/living/carbon/affected_mob, metabolization_ratio) . = ..() affected_mob.AddElement(/datum/element/squish, 120 SECONDS) - for(var/obj/item/bodypart/leg/leg in affected_mob.bodyparts) + for(var/obj/item/bodypart/leg/leg in affected_mob.get_bodyparts()) affected_mob.cause_wound_of_type_and_severity(WOUND_SLASH, leg, WOUND_SEVERITY_SEVERE) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 7bed221fd53..1105c0a611c 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -2358,7 +2358,7 @@ for (var/obj/item/organ/organ as anything in exposed_carbon.organs) organ.add_atom_colour(color_filter, WASHABLE_COLOUR_PRIORITY) - for (var/obj/item/bodypart/part as anything in exposed_carbon.bodyparts) + for (var/obj/item/bodypart/part as anything in exposed_carbon.get_bodyparts()) part.add_atom_colour(color_filter, WASHABLE_COLOUR_PRIORITY) /datum/reagent/colorful_reagent/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, metabolization_ratio) diff --git a/code/modules/reagents/reagent_containers/patch.dm b/code/modules/reagents/reagent_containers/patch.dm index e30529bcd57..66096a368f4 100644 --- a/code/modules/reagents/reagent_containers/patch.dm +++ b/code/modules/reagents/reagent_containers/patch.dm @@ -90,7 +90,7 @@ . = ..() if (!.) return - var/obj/item/bodypart/affecting = victim.get_bodypart(hit_zone) || victim.bodyparts[1] + var/obj/item/bodypart/affecting = victim.get_bodypart(hit_zone) || victim.get_bodypart() if (!IS_ORGANIC_LIMB(affecting)) return FALSE return TRUE diff --git a/code/modules/religion/festival/instrument_rites.dm b/code/modules/religion/festival/instrument_rites.dm index e3f53c10fd8..3f03b47747b 100644 --- a/code/modules/religion/festival/instrument_rites.dm +++ b/code/modules/religion/festival/instrument_rites.dm @@ -176,7 +176,7 @@ listener.adjust_brute_loss(damage_dealt) /datum/religion_rites/song_tuner/pain/finish_effect(mob/living/carbon/human/listener, atom/song_source) - var/obj/item/bodypart/sliced_limb = pick(listener.bodyparts) + var/obj/item/bodypart/sliced_limb = pick(listener.get_bodyparts()) sliced_limb.force_wound_upwards(/datum/wound/slash/flesh/moderate/many_cuts) /datum/religion_rites/song_tuner/lullaby diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 05cf51ea433..d375a1c3882 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -111,7 +111,7 @@ return BLESSING_FAILED var/mob/living/carbon/human/blessed = target - for(var/obj/item/bodypart/bodypart as anything in blessed.bodyparts) + for(var/obj/item/bodypart/bodypart as anything in blessed.get_bodyparts()) if(IS_ROBOTIC_LIMB(bodypart)) to_chat(chap, span_warning("[GLOB.deity] refuses to heal this metallic taint!")) return BLESSING_IGNORED @@ -280,7 +280,7 @@ return BLESSING_IGNORED var/mob/living/carbon/human/blessed = blessed_living - for(var/obj/item/bodypart/robolimb as anything in blessed.bodyparts) + for(var/obj/item/bodypart/robolimb as anything in blessed.get_bodyparts()) if(IS_ROBOTIC_LIMB(robolimb)) to_chat(chap, span_warning("[GLOB.deity] refuses to heal this metallic taint!")) return BLESSING_IGNORED @@ -346,7 +346,7 @@ var/transferred = FALSE var/list/hurt_limbs = target.get_damaged_bodyparts(1, 1, BODYTYPE_ORGANIC) + target.get_wounded_bodyparts(BODYTYPE_ORGANIC) var/list/chaplains_limbs = list() - for(var/obj/item/bodypart/possible_limb in chaplain.bodyparts) + for(var/obj/item/bodypart/possible_limb in chaplain.get_bodyparts()) if(IS_ORGANIC_LIMB(possible_limb)) chaplains_limbs += possible_limb diff --git a/code/modules/religion/sparring/sparring_datum.dm b/code/modules/religion/sparring/sparring_datum.dm index fbe0fe93070..c9ea83be5f0 100644 --- a/code/modules/religion/sparring/sparring_datum.dm +++ b/code/modules/religion/sparring/sparring_datum.dm @@ -216,7 +216,7 @@ if(PUNISHMENT_BRAND) var/mob/living/carbon/human/branded = interfering to_chat(interfering, span_warning("[GLOB.deity] brands your flesh for interfering with [chaplain]'s sparring match!!")) - var/obj/item/bodypart/branded_limb = pick(branded.bodyparts) + var/obj/item/bodypart/branded_limb = pick(branded.get_bodyparts()) branded_limb.force_wound_upwards(/datum/wound/burn/flesh/severe/brand, wound_source = "divine intervention") branded.emote("scream") diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm index a8f22f5f980..a75df1542a6 100644 --- a/code/modules/research/xenobiology/crossbreeding/_misc.dm +++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm @@ -37,18 +37,11 @@ Slimecrossing Items saved_part.old_part.heal_damage(INFINITY, INFINITY, null, FALSE) saved_part.old_part.receive_damage(saved_part.brute_dam, saved_part.burn_dam, wound_bonus=CANT_WOUND) dont_chop[zone] = TRUE - for(var/_part in bodyparts) - var/obj/item/bodypart/part = _part - if(dont_chop[part.body_zone]) - continue - part.drop_limb(TRUE) /mob/living/carbon/proc/save_bodyparts() var/list/datum/saved_bodypart/ret = list() - for(var/_part in bodyparts) - var/obj/item/bodypart/part = _part + for(var/obj/item/bodypart/part as anything in get_bodyparts(include_stumps = TRUE)) var/datum/saved_bodypart/saved_part = new(part) - ret[part.body_zone] = saved_part return ret diff --git a/code/modules/spells/spell_types/touch/scream_for_me.dm b/code/modules/spells/spell_types/touch/scream_for_me.dm index d3c142d7039..2c951f1160d 100644 --- a/code/modules/spells/spell_types/touch/scream_for_me.dm +++ b/code/modules/spells/spell_types/touch/scream_for_me.dm @@ -28,7 +28,7 @@ return var/mob/living/carbon/human/human_victim = victim human_victim.emote("scream") - for(var/obj/item/bodypart/to_wound as anything in human_victim.bodyparts) + for(var/obj/item/bodypart/to_wound as anything in human_victim.get_bodyparts()) human_victim.cause_wound_of_type_and_severity(WOUND_SLASH, to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL) return TRUE diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index f266827d4e5..462c7067e17 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -231,6 +231,8 @@ var/static/list/butcher_drop_cache = list() /// What state is the bodypart in for determining surgery availability VAR_FINAL/surgery_state = NONE + /// Typepath of this limb as a stump + var/stump_typepath /obj/item/bodypart/apply_fantasy_bonuses(bonus) . = ..() @@ -625,8 +627,7 @@ SHOULD_CALL_PARENT(TRUE) var/atom/drop_loc = drop_location() - if(IS_ORGANIC_LIMB(src)) - playsound(drop_loc, 'sound/misc/splort.ogg', 50, TRUE, -1) + var/play_sfx = FALSE for(var/obj/item/organ/bodypart_organ in contents) if(bodypart_organ.organ_flags & ORGAN_UNREMOVABLE) @@ -643,10 +644,15 @@ if(drop_loc) //can be null if being deleted bodypart_organ.forceMove(get_turf(drop_loc)) + play_sfx = TRUE if(drop_loc) //can be null during deletion for(var/atom/movable/movable as anything in src) movable.forceMove(drop_loc) + play_sfx = TRUE + + if(play_sfx && IS_ORGANIC_LIMB(src)) + playsound(drop_loc, 'sound/misc/splort.ogg', 50, TRUE, -1) update_icon_dropped() @@ -1261,7 +1267,7 @@ update_icon_dropped() ///Generates an /image for the limb to be used as an overlay -/obj/item/bodypart/proc/get_limb_icon(dropped, mob/living/carbon/update_on) +/obj/item/bodypart/proc/get_limb_icon(dropped) SHOULD_CALL_PARENT(TRUE) RETURN_TYPE(/list) @@ -1275,7 +1281,7 @@ // Handles invisibility (not alpha or actual invisibility but invisibility) if(is_invisible) . += image(icon_invisible, "invisible_[body_zone]", -BODYPARTS_LAYER, dir = image_dir) - SEND_SIGNAL(src, COMSIG_BODYPART_GET_LIMB_ICON, ., dropped, update_on) + SEND_SIGNAL(src, COMSIG_BODYPART_GET_LIMB_ICON, ., dropped) return . // Normal non-husk handling @@ -1303,7 +1309,7 @@ if(brutestate) // divided into two overlays: one that gets colored and one that doesn't. var/image/brute_blood_overlay = image('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0", -DAMAGE_LAYER, dir = SOUTH) - brute_blood_overlay.color = get_color_from_blood_list(update_on ? update_on.get_blood_dna_list() : blood_dna_info) // living mobs can just get it fresh, dropped limbs use blood_dna_info + brute_blood_overlay.color = get_color_from_blood_list(blood_dna_info) var/mutable_appearance/brute_damage_overlay = mutable_appearance('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0_overlay", -DAMAGE_LAYER, appearance_flags = RESET_COLOR) if(brute_damage_overlay) brute_blood_overlay.overlays += brute_damage_overlay @@ -1384,7 +1390,7 @@ for(var/datum/layer in .) overlay.modify_bodypart_appearance(layer) - SEND_SIGNAL(src, COMSIG_BODYPART_GET_LIMB_ICON, ., dropped, update_on) + SEND_SIGNAL(src, COMSIG_BODYPART_GET_LIMB_ICON, ., dropped) return . /obj/item/bodypart/proc/huskify_image(image/thing_to_husk) @@ -1396,11 +1402,7 @@ husk_blood.blend_mode = BLEND_INSET_OVERLAY husk_blood.dir = thing_to_husk.dir husk_blood.layer = thing_to_husk.layer - var/list/blood_dna = blood_dna_info || owner?.get_blood_dna_list() - if (LAZYLEN(blood_dna)) - husk_blood.color = get_color_from_blood_list(blood_dna) - else - husk_blood.color = BLOOD_COLOR_RED + husk_blood.color = LAZYLEN(blood_dna_info) ? get_color_from_blood_list(blood_dna_info) : BLOOD_COLOR_RED return husk_blood ///Add a bodypart overlay and call the appropriate update procs diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 2960dee5577..72108859d3e 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -17,7 +17,7 @@ if(!silent) limb_owner.visible_message(span_danger("[limb_owner]'s [name] is violently dismembered!")) INVOKE_ASYNC(limb_owner, TYPE_PROC_REF(/mob, emote), "scream") - playsound(get_turf(limb_owner), 'sound/effects/dismember.ogg', 80, TRUE) + playsound(limb_owner, 'sound/effects/dismember.ogg', 80, TRUE) limb_owner.add_mood_event("dismembered_[body_zone]", /datum/mood_event/dismembered, src) limb_owner.add_mob_memory(/datum/memory/was_dismembered, lost_limb = src) @@ -123,7 +123,7 @@ if(!special) phantom_owner.hud_used?.update_locked_slots() - if(bodypart_flags & BODYPART_PSEUDOPART) + if(bodypart_flags & (BODYPART_PSEUDOPART|BODYPART_STUMP)) drop_organs(phantom_owner) //Psuedoparts shouldn't have organs, but just in case if(!QDELING(src)) // we might be removed as a part of something qdeling us qdel(src) @@ -281,6 +281,13 @@ if(!can_attach_limb(new_limb_owner, special)) return FALSE + var/obj/item/bodypart/existing_limb = new_limb_owner.get_bodypart(body_zone, include_stumps = TRUE) + if(existing_limb) + if(existing_limb.type != stump_typepath) + stack_trace("Attempted to attach a limb to [new_limb_owner] in zone [body_zone] where they already have a non-stump limb") + existing_limb.drop_limb(special = TRUE) + qdel(existing_limb) + SEND_SIGNAL(new_limb_owner, COMSIG_CARBON_ATTACH_LIMB, src, special, lazy) SEND_SIGNAL(src, COMSIG_BODYPART_ATTACHED, new_limb_owner, special, lazy) new_limb_owner.add_bodypart(src) diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 73a526361f0..88f4cc912f2 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -27,6 +27,7 @@ bodypart_trait_source = HEAD_TRAIT butcher_replacement = /obj/item/bodypart/head/skeleton/nonfunctional base_meat_amount = 0 + stump_typepath = /obj/item/bodypart/head/stump /// Do we show the information about missing organs upon being examined? Defaults to TRUE, useful for Dullahan heads. var/show_organs_on_examine = TRUE diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index c5fc38a8b6c..afb7c18d1e3 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -1,15 +1,55 @@ - -/mob/living/proc/get_bodypart(zone) +/** + * Returns a bodypart of the specified zone that this mob has + * + * * zone: the zone to get. + * Defaults to chest, allowing for skilling zone nullchecks if you don't care what bodypart you get. + * * include_stumps: whether or not to consider stumps as valid bodyparts to return. + * Defaults to FALSE, meaning that if a limb is missing (is a stump), nothing will be returned. + * + * Returns a bodypart, or null. + */ +/mob/living/proc/get_bodypart(zone = BODY_ZONE_CHEST, include_stumps = FALSE) return -/mob/living/carbon/get_bodypart(zone) +/mob/living/carbon/get_bodypart(zone = BODY_ZONE_CHEST, include_stumps = FALSE) RETURN_TYPE(/obj/item/bodypart) - if(!zone) - zone = BODY_ZONE_CHEST for(var/obj/item/bodypart/bodypart as anything in bodyparts) - if(bodypart.body_zone == zone) - return bodypart + if(!include_stumps && IS_STUMP(bodypart)) + continue + if(bodypart.body_zone != zone) + continue + return bodypart + +/** + * Returns all bodyparts this mob has, optionally including stumps. + * + * include_stumps: whether or not to consider stumps as valid bodyparts to return. + * Defaults to FALSE, meaning that if a limb is missing (is a stump), it won't be included in the returned list. + * + * Returns a list of bodyparts, which may be empty. + */ +/mob/living/proc/get_bodyparts(include_stumps = FALSE) + var/list/parts = list() + for(var/zone in get_all_limbs()) + var/obj/item/bodypart/bodypart = get_bodypart(zone, include_stumps) + if(bodypart) + parts += bodypart + + return parts + +/** + * Returns all bodyparts this mob has, indexed by their body zone + * Also includes stumps and nulls, so be sure to check for those if you use this proc. + * (Note: nulls will be very rare - as 95% of missing limbs are represented as stumps - but not impossible due to some edge cases) + * + * Returns a list of bodyparts indexed by their body zone + */ +/mob/living/proc/get_bodyparts_by_zones() as /list + var/list/parts = list() + for(var/zone in get_all_limbs()) + parts[zone] = get_bodypart(zone) + return parts ///Returns TRUE/FALSE on whether the mob should have a limb in a given zone, used for species-restrictions. /mob/living/carbon/proc/should_have_limb(zone) @@ -128,13 +168,14 @@ ///Returns a list of all limbs this mob should have. /mob/living/carbon/get_all_limbs() - if(dna) - return dna.species.bodypart_overrides.Copy() - return ..() + // gets the "normal list", ie chest-head-legs-arms. order matters for human rendering! + . = dna?.species?.bodypart_overrides.Copy() || ..() + // includes any additional adminbussed hands + for(var/obj/item/bodypart/hand in hand_bodyparts) + . |= hand.body_zone ///Returns a list of all missing limbs this mob should have on them, but don't. /mob/living/carbon/proc/get_missing_limbs() as /list - RETURN_TYPE(/list) var/list/full = get_all_limbs() for(var/zone in full) if(get_bodypart(zone)) @@ -177,12 +218,12 @@ ///Remove all embedded objects from all limbs on the carbon mob /mob/living/carbon/proc/remove_all_embedded_objects() - for(var/obj/item/bodypart/bodypart as anything in bodyparts) + for(var/obj/item/bodypart/bodypart as anything in get_bodyparts(include_stumps = TRUE)) for(var/obj/item/embedded as anything in bodypart.embedded_objects) remove_embedded_object(embedded) /mob/living/carbon/proc/has_embedded_objects(include_harmless = FALSE) - for(var/obj/item/bodypart/bodypart as anything in bodyparts) + for(var/obj/item/bodypart/bodypart as anything in get_bodyparts(include_stumps = TRUE)) for(var/obj/item/embedded as anything in bodypart.embedded_objects) if(!include_harmless && embedded.get_embed().is_harmless(consider_stamina = TRUE)) continue @@ -228,7 +269,7 @@ /// Makes sure that the owner's bodytype flags match the flags of all of its parts and organs /mob/living/carbon/proc/synchronize_bodytypes() var/all_limb_flags = NONE - for(var/obj/item/bodypart/limb as anything in bodyparts) + for(var/obj/item/bodypart/limb as anything in get_bodyparts(include_stumps = TRUE)) for(var/obj/item/organ/organ in limb) all_limb_flags |= organ.external_bodytypes all_limb_flags |= limb.bodytype @@ -238,7 +279,7 @@ /// Makes sure that the owner's bodyshape flags match the flags of all of its parts and organs /mob/living/carbon/proc/synchronize_bodyshapes() var/all_limb_flags = NONE - for(var/obj/item/bodypart/limb as anything in bodyparts) + for(var/obj/item/bodypart/limb as anything in get_bodyparts(include_stumps = TRUE)) for(var/obj/item/organ/organ in limb) all_limb_flags |= organ.external_bodyshapes all_limb_flags |= limb.bodyshape diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index c888271cfc2..7c112950265 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -149,6 +149,7 @@ /// Parent Type for arms, should not appear in game. /obj/item/bodypart/arm + abstract_type = /obj/item/bodypart/arm name = "arm" desc = "Hey buddy give me a HAND and report this to the github because you shouldn't be seeing this." abstract_type = /obj/item/bodypart/arm @@ -324,6 +325,7 @@ px_y = 0 bodypart_trait_source = LEFT_ARM_TRAIT butcher_replacement = /obj/item/bodypart/arm/left/skeleton/nonfunctional + stump_typepath = /obj/item/bodypart/arm/left/stump /obj/item/bodypart/arm/left/apply_ownership(mob/living/carbon/new_owner) if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_L_ARM)) @@ -406,6 +408,7 @@ px_y = 0 bodypart_trait_source = RIGHT_ARM_TRAIT butcher_replacement = /obj/item/bodypart/arm/right/skeleton/nonfunctional + stump_typepath = /obj/item/bodypart/arm/right/stump /obj/item/bodypart/arm/right/apply_ownership(mob/living/carbon/new_owner) if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_R_ARM)) @@ -475,6 +478,7 @@ /// Parent Type for legs, should not appear in game. /obj/item/bodypart/leg + abstract_type = /obj/item/bodypart/leg name = "leg" desc = "This item shouldn't exist. Talk about breaking a leg. Badum-Tss!" abstract_type = /obj/item/bodypart/leg @@ -565,6 +569,7 @@ can_be_disabled = TRUE bodypart_trait_source = LEFT_LEG_TRAIT butcher_replacement = /obj/item/bodypart/leg/left/skeleton/nonfunctional + stump_typepath = /obj/item/bodypart/leg/left/stump /obj/item/bodypart/leg/left/apply_ownership(mob/living/carbon/new_owner) if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_L_LEG)) @@ -644,6 +649,7 @@ px_y = 12 bodypart_trait_source = RIGHT_LEG_TRAIT butcher_replacement = /obj/item/bodypart/leg/right/skeleton/nonfunctional + stump_typepath = /obj/item/bodypart/leg/right/stump /obj/item/bodypart/leg/right/apply_ownership(mob/living/carbon/new_owner) if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_R_LEG)) diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm index 4d17428fc9b..fe5d9c6e14d 100644 --- a/code/modules/surgery/bodyparts/robot_bodyparts.dm +++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm @@ -277,7 +277,7 @@ SIGNAL_HANDLER var/all_robotic = TRUE - for(var/obj/item/bodypart/part in owner.bodyparts) + for(var/obj/item/bodypart/part as anything in owner.get_bodyparts()) all_robotic = all_robotic && IS_ROBOTIC_LIMB(part) if(all_robotic) diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index acb4b65a821..41037f538c4 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -547,6 +547,7 @@ throwforce = 25 // It's also a potent weapon show_organs_on_examine = FALSE speech_span = null + stump_typepath = null /obj/item/bodypart/head/dullahan/Entered(obj/item/organ/arrived, atom/old_loc, list/atom/old_locs) . = ..() diff --git a/code/modules/surgery/bodyparts/stumps.dm b/code/modules/surgery/bodyparts/stumps.dm new file mode 100644 index 00000000000..49e4cdf2955 --- /dev/null +++ b/code/modules/surgery/bodyparts/stumps.dm @@ -0,0 +1,72 @@ +// Invisible bodyparts that serve as placeholders for a missing limb +// Used so we can add behavior to "missing limbs" without putting it on the mob itself + +/obj/item/bodypart/head/stump + limb_id = null + plaintext_zone = "neck stump" + stump_typepath = null + scarrable = FALSE + biological_state = NONE + bodypart_flags = BODYPART_UNREMOVABLE | BODYPART_STUMP | BODYPART_VIRGIN + + head_flags = NONE + teeth_count = 0 // lol? + +/obj/item/bodypart/head/stump/Initialize(mapload) + . = ..() + name = "stump" + // these traits are largely redundant, as many places that check for disabled limbs filter stumps. + // however, marking them as unusable for the sake of completeness might make it easier to work with. + ADD_TRAIT(src, TRAIT_PARALYSIS, INNATE_TRAIT) + +/obj/item/bodypart/leg/left/stump + limb_id = null + plaintext_zone = "left leg stump" + stump_typepath = null + scarrable = FALSE + biological_state = NONE + bodypart_flags = BODYPART_UNREMOVABLE | BODYPART_STUMP | BODYPART_VIRGIN + +/obj/item/bodypart/leg/left/stump/Initialize(mapload) + . = ..() + name = "stump" + ADD_TRAIT(src, TRAIT_PARALYSIS, INNATE_TRAIT) + +/obj/item/bodypart/leg/right/stump + limb_id = null + plaintext_zone = "right leg stump" + stump_typepath = null + scarrable = FALSE + biological_state = NONE + bodypart_flags = BODYPART_UNREMOVABLE | BODYPART_STUMP | BODYPART_VIRGIN + +/obj/item/bodypart/leg/right/stump/Initialize(mapload) + . = ..() + name = "stump" + ADD_TRAIT(src, TRAIT_PARALYSIS, INNATE_TRAIT) + +/obj/item/bodypart/arm/left/stump + limb_id = null + plaintext_zone = "left arm stump" + stump_typepath = null + scarrable = FALSE + biological_state = NONE + bodypart_flags = BODYPART_UNREMOVABLE | BODYPART_STUMP | BODYPART_VIRGIN + +/obj/item/bodypart/arm/left/stump/Initialize(mapload) + . = ..() + name = "stump" + ADD_TRAIT(src, TRAIT_PARALYSIS, INNATE_TRAIT) + +/obj/item/bodypart/arm/right/stump + limb_id = null + plaintext_zone = "right arm stump" + stump_typepath = null + scarrable = FALSE + biological_state = NONE + bodypart_flags = BODYPART_UNREMOVABLE | BODYPART_STUMP | BODYPART_VIRGIN + +/obj/item/bodypart/arm/right/stump/Initialize(mapload) + . = ..() + name = "stump" + ADD_TRAIT(src, TRAIT_PARALYSIS, INNATE_TRAIT) diff --git a/code/modules/surgery/operations/_operation.dm b/code/modules/surgery/operations/_operation.dm index 95ad653cd3d..8d49b07c9f7 100644 --- a/code/modules/surgery/operations/_operation.dm +++ b/code/modules/surgery/operations/_operation.dm @@ -1297,6 +1297,9 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) abstract_type = /datum/surgery_operation/limb /// Body type required to perform this operation var/required_bodytype = NONE + /// If TRUE, this operation can be performed on stumps. + /// If FALSE, the target limb must be a full limb. + var/allow_stumps = FALSE /datum/surgery_operation/limb/all_blocked_strings() . = ..() @@ -1308,7 +1311,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) /datum/surgery_operation/limb/get_operation_target(atom/movable/operating_on, body_zone) if (isliving(operating_on)) var/mob/living/patient = operating_on - return patient.get_bodypart(deprecise_zone(body_zone)) + return patient.get_bodypart(deprecise_zone(body_zone), allow_stumps) if (!isbodypart(operating_on)) return null return operating_on diff --git a/code/modules/surgery/operations/operation_add_limb.dm b/code/modules/surgery/operations/operation_add_limb.dm index 3f12368ff12..ab837aafbcb 100644 --- a/code/modules/surgery/operations/operation_add_limb.dm +++ b/code/modules/surgery/operations/operation_add_limb.dm @@ -1,7 +1,7 @@ #define OPERATION_REJECTION_DAMAGE "tox_damage" // This surgery is so snowflake that it doesn't use any of the operation subtypes, it forges its own path -/datum/surgery_operation/prosthetic_replacement +/datum/surgery_operation/limb/prosthetic_replacement name = "prosthetic replacement" desc = "Replace a missing limb with a prosthetic (or arbitrary) item." implements = list( @@ -12,6 +12,7 @@ operation_flags = OPERATION_STANDING_ALLOWED | OPERATION_PRIORITY_NEXT_STEP | OPERATION_NOTABLE | OPERATION_IGNORE_CLOTHES all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED + allow_stumps = TRUE /// List of items that are always allowed to be an arm replacement, even if they fail another requirement. var/list/always_accepted_prosthetics = list( /obj/item/chainsaw, // the OG, too large otherwise @@ -21,25 +22,20 @@ /// Radial slice datums for every augment type VAR_PRIVATE/list/cached_prosthetic_options -/datum/surgery_operation/prosthetic_replacement/get_default_radial_image() - return image(/obj/item/bodypart/chest) - -/datum/surgery_operation/prosthetic_replacement/get_recommended_tool() +/datum/surgery_operation/limb/prosthetic_replacement/get_recommended_tool() return "any limb / any item" -/datum/surgery_operation/prosthetic_replacement/get_any_tool() +/datum/surgery_operation/limb/prosthetic_replacement/get_any_tool() return "Any suitable arm replacement" -/datum/surgery_operation/prosthetic_replacement/all_required_strings() - . = list() - . += "operate on chest (target chest)" - . += ..() - . += "when the chest is prepared, target the zone of the limb you are attaching" +/datum/surgery_operation/limb/prosthetic_replacement/all_required_strings() + . = ..() + . += "the limb must be missing / a stump" -/datum/surgery_operation/prosthetic_replacement/any_required_strings() +/datum/surgery_operation/limb/prosthetic_replacement/any_required_strings() return list("arms may receive any suitable item in lieu of a replacement limb") + ..() -/datum/surgery_operation/prosthetic_replacement/get_radial_options(obj/item/bodypart/chest/chest, obj/item/tool, operating_zone) +/datum/surgery_operation/limb/prosthetic_replacement/get_radial_options(obj/item/bodypart/chest/chest, obj/item/tool, operating_zone) var/datum/radial_menu_choice/option = LAZYACCESS(cached_prosthetic_options, tool.type) if(!option) option = new() @@ -50,32 +46,10 @@ return option -/datum/surgery_operation/prosthetic_replacement/get_operation_target(atom/movable/operating_on, body_zone) - if (!isliving(operating_on)) - return null - var/mob/living/patient = operating_on - // We always operate on the chest even if we're targeting left leg or w/e - return patient.get_bodypart(BODY_ZONE_CHEST) +/datum/surgery_operation/limb/prosthetic_replacement/state_check(obj/item/bodypart/limb) + return IS_STUMP(limb) -/datum/surgery_operation/prosthetic_replacement/has_surgery_state(obj/item/bodypart/chest/chest, state) - return LIMB_HAS_SURGERY_STATE(chest, state) - -/datum/surgery_operation/prosthetic_replacement/has_any_surgery_state(obj/item/bodypart/chest/chest, state) - return LIMB_HAS_ANY_SURGERY_STATE(chest, state) - -/datum/surgery_operation/prosthetic_replacement/get_patient(obj/item/bodypart/chest/chest) - return chest.owner - -/datum/surgery_operation/prosthetic_replacement/is_available(obj/item/bodypart/chest/chest, operated_zone) - var/real_operated_zone = deprecise_zone(operated_zone) - // Operate on the chest but target another zone - if(!HAS_TRAIT(chest, TRAIT_READY_TO_OPERATE) || real_operated_zone == BODY_ZONE_CHEST) - return FALSE - if(chest.owner.get_bodypart(real_operated_zone)) - return FALSE - return ..() - -/datum/surgery_operation/prosthetic_replacement/snowflake_check_availability(obj/item/bodypart/chest, mob/living/surgeon, obj/item/tool, operated_zone) +/datum/surgery_operation/limb/prosthetic_replacement/snowflake_check_availability(obj/item/bodypart/chest, mob/living/surgeon, obj/item/tool, operated_zone) if(!surgeon.canUnEquip(tool)) return FALSE var/real_operated_zone = deprecise_zone(operated_zone) @@ -91,7 +65,7 @@ return FALSE return TRUE -/datum/surgery_operation/prosthetic_replacement/tool_check(obj/item/tool) +/datum/surgery_operation/limb/prosthetic_replacement/tool_check(obj/item/tool) if(tool.item_flags & (ABSTRACT|DROPDEL|HAND_ITEM)) return FALSE if(isbodypart(tool)) @@ -104,41 +78,41 @@ return FALSE return TRUE -/datum/surgery_operation/prosthetic_replacement/pre_preop(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) +/datum/surgery_operation/limb/prosthetic_replacement/pre_preop(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) . = ..() // always operate on absolute body zones operation_args[OPERATION_TARGET_ZONE] = deprecise_zone(operation_args[OPERATION_TARGET_ZONE]) -/datum/surgery_operation/prosthetic_replacement/on_preop(obj/item/bodypart/chest/chest, mob/living/surgeon, obj/item/tool, list/operation_args) - var/target_zone_readable = parse_zone(operation_args[OPERATION_TARGET_ZONE]) +/datum/surgery_operation/limb/prosthetic_replacement/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) display_results( surgeon, - chest.owner, - span_notice("You begin to replace [chest.owner]'s missing [target_zone_readable] with [tool]..."), - span_notice("[surgeon] begins to replace [chest.owner]'s missing [target_zone_readable] with [tool]."), - span_notice("[surgeon] begins to replace [chest.owner]'s missing [target_zone_readable]."), + limb.owner, + // "You begin to attach the right arm to john doe's right arm stump" + span_notice("You begin to attach [tool]'s to [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to attach [tool]'s to [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to attach [tool]'s to [FORMAT_LIMB_OWNER(limb)]."), ) - display_pain(chest.owner, "You feel an uncomfortable sensation where your [target_zone_readable] should be!") + display_pain(limb.owner, "You feel an uncomfortable sensation where your [parse_zone(limb.body_zone)] should be!") operation_args[OPERATION_REJECTION_DAMAGE] = 10 if(isbodypart(tool)) var/obj/item/bodypart/new_limb = tool if(IS_ROBOTIC_LIMB(new_limb)) operation_args[OPERATION_REJECTION_DAMAGE] = 0 - else if(new_limb.check_for_frankenstein(chest.owner)) + else if(new_limb.check_for_frankenstein(limb.owner)) operation_args[OPERATION_REJECTION_DAMAGE] = 30 -/datum/surgery_operation/prosthetic_replacement/on_success(obj/item/bodypart/chest/chest, mob/living/surgeon, obj/item/tool, list/operation_args) +/datum/surgery_operation/limb/prosthetic_replacement/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) if(!surgeon.temporarilyRemoveItemFromInventory(tool)) return // should never happen if(operation_args[OPERATION_REJECTION_DAMAGE] > 0) - chest.owner.apply_damage(operation_args[OPERATION_REJECTION_DAMAGE], TOX) + limb.owner.apply_damage(operation_args[OPERATION_REJECTION_DAMAGE], TOX) if(isbodypart(tool)) - handle_bodypart(chest.owner, surgeon, tool) + handle_bodypart(limb.owner, surgeon, tool) return - handle_arbitrary_prosthetic(chest.owner, surgeon, tool, operation_args[OPERATION_TARGET_ZONE]) + handle_arbitrary_prosthetic(limb.owner, surgeon, tool, operation_args[OPERATION_TARGET_ZONE]) -/datum/surgery_operation/prosthetic_replacement/proc/handle_bodypart(mob/living/carbon/patient, mob/living/surgeon, obj/item/bodypart/bodypart_to_attach) +/datum/surgery_operation/limb/prosthetic_replacement/proc/handle_bodypart(mob/living/carbon/patient, mob/living/surgeon, obj/item/bodypart/bodypart_to_attach) bodypart_to_attach.try_attach_limb(patient) if(bodypart_to_attach.check_for_frankenstein(patient)) bodypart_to_attach.bodypart_flags |= BODYPART_IMPLANTED @@ -150,7 +124,7 @@ ) display_pain(patient, "You feel synthetic sensation wash from your [bodypart_to_attach.plaintext_zone], which you can feel again!", TRUE) -/datum/surgery_operation/prosthetic_replacement/proc/handle_arbitrary_prosthetic(mob/living/carbon/patient, mob/living/surgeon, obj/item/thing_to_attach, target_zone) +/datum/surgery_operation/limb/prosthetic_replacement/proc/handle_arbitrary_prosthetic(mob/living/carbon/patient, mob/living/surgeon, obj/item/thing_to_attach, target_zone) SSblackbox.record_feedback("tally", "arbitrary_prosthetic", 1, initial(thing_to_attach.name)) var/obj/item/bodypart/new_limb = patient.make_item_prosthetic(thing_to_attach, target_zone, 80) new_limb.add_surgical_state(SURGERY_PROSTHETIC_UNSECURED) @@ -187,8 +161,7 @@ span_notice("[surgeon] begins to [tool.singular_name] [limb] to [limb.owner]'s body."), span_notice("[surgeon] begins to [tool.singular_name] something to [limb.owner]'s body."), ) - var/obj/item/bodypart/chest = limb.owner.get_bodypart(BODY_ZONE_CHEST) - display_pain(limb.owner, "[surgeon] begins to [tool.singular_name] [limb] to your body!", IS_ROBOTIC_LIMB(chest)) + display_pain(limb.owner, "[surgeon] begins to [tool.singular_name] [limb] to your body!", IS_ROBOTIC_LIMB(limb)) /datum/surgery_operation/limb/secure_arbitrary_prosthetic/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/stack/medical/tool, list/operation_args) display_results( @@ -198,8 +171,7 @@ span_notice("[surgeon] finishes [tool.apply_verb] [limb] to [limb.owner]'s body."), span_notice("[surgeon] finishes the [tool.apply_verb] procedure!"), ) - var/obj/item/bodypart/chest = limb.owner.get_bodypart(BODY_ZONE_CHEST) - display_pain(limb.owner, "You feel more secure as your prosthetic is firmly attached to your body!", IS_ROBOTIC_LIMB(chest)) + display_pain(limb.owner, "You feel more secure as your prosthetic is firmly attached to your body!", IS_ROBOTIC_LIMB(limb)) limb.remove_surgical_state(SURGERY_PROSTHETIC_UNSECURED) limb.AddComponent(/datum/component/item_as_prosthetic_limb, null, 0) // updates drop probability to zero tool.use(1) diff --git a/code/modules/surgery/operations/operation_generic.dm b/code/modules/surgery/operations/operation_generic.dm index 8288971a4a5..fac2fcdc051 100644 --- a/code/modules/surgery/operations/operation_generic.dm +++ b/code/modules/surgery/operations/operation_generic.dm @@ -21,6 +21,7 @@ success_sound = 'sound/items/handling/surgery/scalpel2.ogg' operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_NO_PATIENT_REQUIRED any_surgery_states_blocked = ALL_SURGERY_SKIN_STATES + allow_stumps = TRUE /// We can't cut mobs with this biostate var/biostate_blacklist = BIO_CHITIN @@ -115,6 +116,7 @@ preop_sound = 'sound/items/handling/surgery/retractor1.ogg' success_sound = 'sound/items/handling/surgery/retractor2.ogg' all_surgery_states_required = SURGERY_SKIN_CUT + allow_stumps = TRUE /datum/surgery_operation/limb/retract_skin/get_default_radial_image() return image(/obj/item/retractor) @@ -165,6 +167,7 @@ /obj/item = 'sound/items/handling/surgery/cautery2.ogg', ) any_surgery_states_required = ALL_SURGERY_SKIN_STATES + allow_stumps = TRUE /datum/surgery_operation/limb/close_skin/get_any_tool() return "Any heat source" @@ -225,6 +228,7 @@ time = 2.4 SECONDS preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_UNCLAMPED + allow_stumps = TRUE /datum/surgery_operation/limb/clamp_bleeders/get_default_radial_image() return image(/obj/item/hemostat) @@ -268,6 +272,7 @@ time = 2.4 SECONDS preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + allow_stumps = TRUE /datum/surgery_operation/limb/unclamp_bleeders/get_default_radial_image() return image(/obj/item/hemostat) @@ -325,6 +330,7 @@ operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_NO_PATIENT_REQUIRED all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED + allow_stumps = TRUE /datum/surgery_operation/limb/saw_bones/get_any_tool() return "Any sharp edged item with decent force" @@ -381,6 +387,7 @@ time = 4 SECONDS all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_required = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED + allow_stumps = TRUE /datum/surgery_operation/limb/fix_bones/get_default_radial_image() return image(/obj/item/stack/medical/bone_gel) @@ -425,6 +432,7 @@ success_sound = 'sound/items/handling/surgery/organ2.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED + allow_stumps = TRUE /datum/surgery_operation/limb/drill_bones/get_any_tool() return "Any sharp pointed item with decent force" @@ -477,6 +485,7 @@ success_sound = 'sound/items/handling/surgery/organ1.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_ORGANS_CUT + allow_stumps = TRUE /datum/surgery_operation/limb/incise_organs/get_any_tool() return "Any sharp edged item" diff --git a/code/modules/surgery/operations/operation_generic_mechanic.dm b/code/modules/surgery/operations/operation_generic_mechanic.dm index bbcdfd55fd2..54a97c7348c 100644 --- a/code/modules/surgery/operations/operation_generic_mechanic.dm +++ b/code/modules/surgery/operations/operation_generic_mechanic.dm @@ -16,6 +16,7 @@ preop_sound = 'sound/items/tools/screwdriver.ogg' success_sound = 'sound/items/tools/screwdriver2.ogg' any_surgery_states_blocked = ALL_SURGERY_SKIN_STATES + allow_stumps = TRUE /datum/surgery_operation/limb/mechanical_incision/get_any_tool() return "Any sharp item" @@ -56,6 +57,7 @@ preop_sound = 'sound/items/tools/ratchet.ogg' success_sound = 'sound/machines/airlock/doorclick.ogg' all_surgery_states_required = SURGERY_SKIN_CUT + allow_stumps = TRUE /datum/surgery_operation/limb/mechanical_open/get_default_radial_image() return image('icons/hud/screen_gen.dmi', "arrow_large_still") @@ -93,6 +95,7 @@ preop_sound = 'sound/items/tools/screwdriver.ogg' success_sound = 'sound/items/tools/screwdriver2.ogg' any_surgery_states_required = ALL_SURGERY_SKIN_STATES + allow_stumps = TRUE /datum/surgery_operation/limb/mechanical_close/get_any_tool() return "Any sharp item" @@ -137,6 +140,7 @@ success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_ORGANS_CUT + allow_stumps = TRUE /datum/surgery_operation/limb/prepare_electronics/get_default_radial_image() return image(/obj/item/multitool) @@ -170,6 +174,7 @@ preop_sound = 'sound/items/tools/ratchet.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED + allow_stumps = TRUE /datum/surgery_operation/limb/mechanic_unwrench/get_default_radial_image() return image(/obj/item/wrench) @@ -198,10 +203,11 @@ TOOL_WRENCH = 1, TOOL_RETRACTOR = 1.33, ) - operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC | OPERATION_NO_PATIENT_REQUIRED time = 2.4 SECONDS preop_sound = 'sound/items/tools/ratchet.ogg' - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED | OPERATION_NO_PATIENT_REQUIRED + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + allow_stumps = TRUE /datum/surgery_operation/limb/mechanic_wrench/state_check(obj/item/bodypart/limb) return LIMB_HAS_BONES(limb) diff --git a/code/modules/surgery/surgery_tools.dm b/code/modules/surgery/surgery_tools.dm index ed1218394a7..69f97210b30 100644 --- a/code/modules/surgery/surgery_tools.dm +++ b/code/modules/surgery/surgery_tools.dm @@ -591,7 +591,7 @@ /obj/item/shears/suicide_act(mob/living/carbon/user) user.visible_message(span_suicide("[user] is pinching [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")) var/timer = 1 SECONDS - for(var/obj/item/bodypart/thing in user.bodyparts) + for(var/obj/item/bodypart/thing in user.get_bodyparts()) if(thing.body_part == CHEST) continue addtimer(CALLBACK(thing, TYPE_PROC_REF(/obj/item/bodypart/, dismember)), timer) diff --git a/code/modules/unit_tests/limbsanity.dm b/code/modules/unit_tests/limbsanity.dm index 6efec37ab76..13f6a4d7d35 100644 --- a/code/modules/unit_tests/limbsanity.dm +++ b/code/modules/unit_tests/limbsanity.dm @@ -1,8 +1,11 @@ /datum/unit_test/limbsanity /datum/unit_test/limbsanity/Run() - for(var/path in subtypesof(/obj/item/bodypart) - list(/obj/item/bodypart/arm, /obj/item/bodypart/leg)) /// removes the abstract items. + for(var/path in valid_subtypesof(/obj/item/bodypart)) /// removes the abstract items. var/obj/item/bodypart/part = path + if(part::bodypart_flags & BODYPART_STUMP) + continue // stumps don't need to have icons + if(part::is_dimorphic) if(!icon_exists(UNLINT(part::should_draw_greyscale ? part::icon_greyscale : part::icon_static), "[part::limb_id]_[part::body_zone]_m")) TEST_FAIL("[path] does not have a valid icon for male variants") diff --git a/code/modules/unit_tests/organ_bodypart_shuffle.dm b/code/modules/unit_tests/organ_bodypart_shuffle.dm index e05c6bd0bd9..ffb2210756a 100644 --- a/code/modules/unit_tests/organ_bodypart_shuffle.dm +++ b/code/modules/unit_tests/organ_bodypart_shuffle.dm @@ -20,7 +20,7 @@ TEST_ASSERT_NULL(organ.bodypart_owner, "Organ '[organ.name] kept reference to bodypart after forceMove into nullspace.") // 3. replace all bodyparts with new ones and place the previously removed organs into the new bodyparts - for(var/obj/item/bodypart/bodypart as anything in hollow_boy.bodyparts) + for(var/obj/item/bodypart/bodypart as anything in hollow_boy.get_bodyparts()) var/obj/item/bodypart/replacement = allocate(bodypart.type) for(var/obj/item/organ/organ as anything in removed_organs) if(replacement.body_zone != deprecise_zone(organ.zone)) @@ -35,3 +35,22 @@ TEST_ASSERT(organ in hollow_boy.organs, "Organ '[organ.name] was put in an empty bodypart that replaced a humans, but the organ did not come with.") TEST_ASSERT(organ.owner == hollow_boy, "Organ '[organ.name]'s owner was not properly updated to the new human after being placed in a replacement bodypart.") TEST_ASSERT(organ.bodypart_owner in hollow_boy.bodyparts, "Organ '[organ.name]'s bodypart_owner was not properly updated to the new bodypart after being placed in a replacement bodypart.") + +/// Tests that gibbing results in a head with a brain +/datum/unit_test/gibbing_organ_transfer + +/datum/unit_test/gibbing_organ_transfer/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/list/bodyparts_for_cleanup = dummy.bodyparts.Copy() + var/list/organs_for_cleanup = dummy.organs.Copy() + var/obj/item/organ/brain/original_brain = dummy.get_organ_slot(ORGAN_SLOT_BRAIN) + var/obj/item/bodypart/head/original_head = dummy.get_bodypart(BODY_ZONE_HEAD) + dummy.gib(ALL) + + TEST_ASSERT(QDELETED(dummy), "Dummy was not deleted after gibbing.") + TEST_ASSERT(!QDELETED(original_head), "Original head was deleted after gibbing.") + TEST_ASSERT(!QDELETED(original_brain), "Original brain was deleted after gibbing.") + TEST_ASSERT(original_brain.loc == original_head, "Original brain was not transferred to the head after gibbing.") + + QDEL_LIST(organs_for_cleanup) + QDEL_LIST(bodyparts_for_cleanup) diff --git a/code/modules/unit_tests/spawn_humans.dm b/code/modules/unit_tests/spawn_humans.dm index f89f59a4cbf..a0691b16c87 100644 --- a/code/modules/unit_tests/spawn_humans.dm +++ b/code/modules/unit_tests/spawn_humans.dm @@ -14,3 +14,23 @@ TEST_ASSERT(!HAS_TRAIT_FROM(dummy, TRAIT_AGEUSIA, NO_TONGUE_TRAIT), "Dummy has ageusia on init, when it should've been removed by its default tongue.") TEST_ASSERT(!dummy.is_blind_from(NO_EYES), "Dummy is blind on init, when it should've been removed by its default eyes.") TEST_ASSERT(!HAS_TRAIT_FROM(dummy, TRAIT_DEAF, NO_EARS), "Dummy is deaf on init, when it should've been removed by its default ears.") + +/// Tests that we can change a mob's hand count without everything breaking +/datum/unit_test/many_armed_humans + +/datum/unit_test/many_armed_humans/Run() + var/mob/living/carbon/human/consistent/dummy = allocate(/mob/living/carbon/human/consistent) + dummy.change_number_of_hands(4) + +/// Tests spawned humans have the correct bodypart order +/datum/unit_test/human_bodypart_order + +/datum/unit_test/human_bodypart_order/Run() + var/mob/living/carbon/human/consistent/dummy = allocate(/mob/living/carbon/human/consistent) + var/list/obj/item/bodypart/bodyparts = dummy.get_bodyparts() + TEST_ASSERT(bodyparts[1].body_zone == BODY_ZONE_CHEST, "First bodypart in bodyparts list is not the chest, this is important for human rendering") + TEST_ASSERT(bodyparts[2].body_zone == BODY_ZONE_HEAD, "Second bodypart in bodyparts list is not the head, this is important for human rendering") + + var/list/obj/item/bodypart/bodyparts_by_zone = dummy.get_bodyparts_by_zones() + TEST_ASSERT(bodyparts_by_zone[1] == BODY_ZONE_CHEST, "First bodypart in bodyparts_by_zone list is not the chest, this is important for human rendering") + TEST_ASSERT(bodyparts_by_zone[2] == BODY_ZONE_HEAD, "Second bodypart in bodyparts_by_zone list is not the head, this is important for human rendering") diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm index 69bc29fb148..7a538da8fbb 100644 --- a/code/modules/unit_tests/surgeries.dm +++ b/code/modules/unit_tests/surgeries.dm @@ -57,9 +57,9 @@ TEST_ASSERT_EQUAL(bobs_head.real_name, "Bob", "Bob's head does not remember that it is from Bob") // Put Bob's head onto Alice's body - var/datum/surgery_operation/prosthetic_replacement/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + var/datum/surgery_operation/limb/prosthetic_replacement/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] user.put_in_active_hand(bobs_head) - UNLINT(surgery.success(alice.get_bodypart(BODY_ZONE_CHEST), user, bobs_head, list())) + UNLINT(surgery.success(alice.get_bodypart(BODY_ZONE_HEAD, TRUE), user, bobs_head, list())) TEST_ASSERT(!isnull(alice.get_bodypart(BODY_ZONE_HEAD)), "Alice has no head after prosthetic replacement") TEST_ASSERT_EQUAL(alice.get_visible_name(), "Bob", "Bob's head was transplanted onto Alice's body, but their name is not Bob") diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index 0672586220c..6cff8a743f6 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -143,14 +143,16 @@ for(var/mob/living/carbon/carbon_occupant in occupants) if(prob(35)) //Note: The randomstep on dump_mobs throws occupants into each other and often causes wounds regardless. continue - for(var/obj/item/bodypart/head/head_to_wound as anything in carbon_occupant.bodyparts) - var/pick_mode = text2num(pick(list( - "[WOUND_PICK_LOWEST_SEVERITY]", - "[WOUND_PICK_HIGHEST_SEVERITY]" - ))) - carbon_occupant.cause_wound_of_type_and_severity(WOUND_BLUNT, head_to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, pick_mode) - carbon_occupant.playsound_local(src, 'sound/items/weapons/flash_ring.ogg', 50) - carbon_occupant.set_eye_blur_if_lower(rand(10 SECONDS, 20 SECONDS)) + var/obj/item/bodypart/head/head_to_wound = carbon_occupant.get_bodypart(BODY_ZONE_HEAD) + if(isnull(head_to_wound)) + return + var/pick_mode = text2num(pick(list( + "[WOUND_PICK_LOWEST_SEVERITY]", + "[WOUND_PICK_HIGHEST_SEVERITY]" + ))) + carbon_occupant.cause_wound_of_type_and_severity(WOUND_BLUNT, head_to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, pick_mode) + carbon_occupant.playsound_local(src, 'sound/items/weapons/flash_ring.ogg', 50) + carbon_occupant.set_eye_blur_if_lower(rand(10 SECONDS, 20 SECONDS)) hittarget_living.add_splatter_floor(small_drip = FALSE) hittarget_living.adjust_brute_loss(200) diff --git a/code/modules/vending/vendor/tilting.dm b/code/modules/vending/vendor/tilting.dm index 1d7d899c0ea..971633c683b 100644 --- a/code/modules/vending/vendor/tilting.dm +++ b/code/modules/vending/vendor/tilting.dm @@ -180,7 +180,7 @@ if (!iscarbon(atom_target)) return FALSE var/mob/living/carbon/carbon_target = atom_target - for(var/obj/item/bodypart/squish_part in carbon_target.bodyparts) + for(var/obj/item/bodypart/squish_part in carbon_target.get_bodyparts()) var/severity = pick(WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, WOUND_SEVERITY_CRITICAL) if (!carbon_target.cause_wound_of_type_and_severity(WOUND_BLUNT, squish_part, severity, wound_source = "crushed by [src]")) squish_part.receive_damage(brute = 30) diff --git a/tgstation.dme b/tgstation.dme index da8503ddcfc..67e316b9900 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6434,6 +6434,7 @@ #include "code\modules\surgery\bodyparts\helpers.dm" #include "code\modules\surgery\bodyparts\parts.dm" #include "code\modules\surgery\bodyparts\robot_bodyparts.dm" +#include "code\modules\surgery\bodyparts\stumps.dm" #include "code\modules\surgery\bodyparts\worn_feature_offset.dm" #include "code\modules\surgery\bodyparts\wounds.dm" #include "code\modules\surgery\bodyparts\species_parts\android_parts.dm"