Adds flaying, human butchering and limb surgeries (#94612)

## About The Pull Request

Merry Christmas!

This PR expands on the more gruesome areas of chef content, such as
cannibalism.
Human limbs can now be individually butchered for their meat and skin
with a knife or another butchering implement once the skin has been cut
and retracted (forks now can be used as bootleg retractors). If the limb
contains organs, those will need to be removed first, which also can be
quickly performed with a butchering tool at the cost of damaging the
inhards (head and chest will require the bone to be cut first, which can
be done with a butcher's cleaver if you don't have a saw)
You can also butcher humans whole by right clicking them with a
butchering implement on combat mode, which will attempt to gut/cut apart
the selected limb. Obviously this requires the limb to be prepped first,
so meat hooks/kitchenspikes now allow mobs strung on them to be operated
on without needing drapes. Its a pretty bad spot to be performing
surgeries at though, so beware! Torsos can only be butchered after all
other limbs have been. The gutting/butchering delay is based off the
owner's stat, halving if the limb is detached and doubling if the owner
is still alive and kicking.

<img width="392" height="265" alt="dreamseeker_D7QWbWKStd"
src="https://github.com/user-attachments/assets/050d7c25-3572-4b02-ae93-d8efefe54885"
/>

(The video is too large for github)
https://streamable.com/459a0y

After being butchered, most limbs will leave behind a nonfunctional
skeletal limb (with exceptions like jelly limbs with no skeleton inside)
which is always disabled. Skeleton chests husk their owner, so you'll
need to replace it first.
Gibbers now source their meat from the victim's limbs (and not their
species) at an RNG-based percentage based on the matter bin's tier.

As to allow individual limbs to be butchered, you can now perform (some)
surgeries on individual limbs. There are some obvious restrictions like
zombification or bioware surgeries, but most of the ones that make sense
can be performed on someone's chopped off head as long as you've got the
tools.
Wounds also contribute to surgeries now, major slash wounds count as
cut/open skin (and sealing the vessels would reduce their bleed rate),
and critical blunt wounds count as sawed bones.

I've refactored gibbers, butchering and meat spike code as well as fixed
some backend bugs while I was at it.

## Why It's Good For The Game

While rather gruesome, I feel like the freedom which these mechanics
provide fits us pretty well. We already have plenty of nasty stuff and
this doesn't really go that much further, and a chef serving their own
leg in their dish is pretty funny. (Also you can scrape plasma off
plasmamen bones)
This commit is contained in:
SmArtKar
2025-12-31 12:55:39 -06:00
committed by GitHub
parent 8b74157ccd
commit a4f0dcb76f
70 changed files with 1515 additions and 760 deletions
+2 -1
View File
@@ -100,7 +100,8 @@
if (isnull(bayonet) || !user.combat_mode)
return NONE
INVOKE_ASYNC(bayonet, TYPE_PROC_REF(/obj/item, melee_attack_chain), user, target, modifiers)
// RMB is used for butchering, and bayonets are usually are knives
INVOKE_ASYNC(bayonet, TYPE_PROC_REF(/obj/item, melee_attack_chain), user, target, modifiers - RIGHT_CLICK)
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/component/bayonet_attachable/proc/on_attackby(obj/item/source, obj/item/attacking_item, mob/attacker, list/modifiers)
+410 -108
View File
@@ -27,72 +27,357 @@
src.effectiveness = effectiveness
src.bonus_modifier = bonus_modifier
src.butcher_sound = butcher_sound
if(disabled)
if (disabled)
src.butchering_enabled = FALSE
src.can_be_blunt = can_be_blunt
src.butcher_callback = butcher_callback
if(isitem(parent))
RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(onItemAttack))
/datum/component/butchering/Destroy(force)
butcher_callback = null
return ..()
/datum/component/butchering/proc/onItemAttack(obj/item/source, mob/living/M, mob/living/user)
/datum/component/butchering/RegisterWithParent()
if (!isitem(parent))
return
var/obj/item/item_parent = parent
item_parent.item_flags |= ITEM_HAS_CONTEXTUAL_SCREENTIPS
RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(on_item_attack))
RegisterSignal(parent, COMSIG_ITEM_INTERACTING_WITH_ATOM, PROC_REF(on_item_interaction))
RegisterSignal(parent, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, PROC_REF(add_item_context))
/datum/component/butchering/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK, COMSIG_ITEM_INTERACTING_WITH_ATOM))
/datum/component/butchering/proc/on_item_attack(obj/item/source, mob/living/victim, mob/living/user, list/modifiers)
SIGNAL_HANDLER
if(!user.combat_mode)
return
if(M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it?
if(butchering_enabled && (can_be_blunt || source.get_sharpness()))
INVOKE_ASYNC(src, PROC_REF(startButcher), source, M, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
if(ishuman(M) && source.force && source.get_sharpness())
var/mob/living/carbon/human/H = M
if((user.pulling == H && user.grab_state >= GRAB_AGGRESSIVE) && user.zone_selected == BODY_ZONE_HEAD) // Only aggressive grabbed can be sliced.
if(HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, span_warning("You don't want to harm other living beings!"))
return COMPONENT_CANCEL_ATTACK_CHAIN
if(H.has_status_effect(/datum/status_effect/neck_slice))
return
INVOKE_ASYNC(src, PROC_REF(startNeckSlice), source, H, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/component/butchering/proc/startButcher(obj/item/source, mob/living/M, mob/living/user)
to_chat(user, span_notice("You begin to butcher [M]..."))
playsound(M.loc, butcher_sound, 50, TRUE, -1)
if(do_after(user, speed, M) && M.Adjacent(source))
on_butchering(user, M)
/datum/component/butchering/proc/startNeckSlice(obj/item/source, mob/living/carbon/human/H, mob/living/user)
if(DOING_INTERACTION_WITH_TARGET(user, H))
to_chat(user, span_warning("You're already interacting with [H]!"))
if (!source.get_sharpness() && !can_be_blunt)
return
user.visible_message(span_danger("[user] is slitting [H]'s throat!"), \
span_danger("You start slicing [H]'s throat!"), \
span_hear("You hear a cutting noise!"), ignored_mobs = H)
H.show_message(span_userdanger("Your throat is being slit by [user]!"), MSG_VISUAL, \
span_userdanger("Something is cutting into your neck!"), NONE)
log_combat(user, H, "attempted throat slitting", source)
if (!user.combat_mode)
return
playsound(H.loc, butcher_sound, 50, TRUE, -1)
if(do_after(user, clamp(500 / source.force, 30, 100), H) && H.Adjacent(source))
if(H.has_status_effect(/datum/status_effect/neck_slice))
user.show_message(span_warning("[H]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \
span_warning("Their neck has already been already cut, you can't make the bleeding any worse!"))
// Can we butcher it?
if (victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results) && butchering_enabled)
INVOKE_ASYNC(src, PROC_REF(start_butcher), source, victim, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
if (!ishuman(victim) || !source.force)
return
if (LAZYACCESS(modifiers, RIGHT_CLICK) && butchering_enabled)
INVOKE_ASYNC(src, PROC_REF(butcher_human), source, victim, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
// Neckslicing requires aggro grabs
if (user.pulling != victim || user.grab_state < GRAB_AGGRESSIVE || user.zone_selected != BODY_ZONE_HEAD)
return
if (HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, span_warning("You don't want to harm other living beings!"))
return COMPONENT_CANCEL_ATTACK_CHAIN
if (victim.has_status_effect(/datum/status_effect/neck_slice))
return
INVOKE_ASYNC(src, PROC_REF(start_neck_slice), source, victim, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/component/butchering/proc/on_item_interaction(obj/item/source, mob/living/user, atom/target)
SIGNAL_HANDLER
if (!source.get_sharpness() && !can_be_blunt)
return
if (!user.combat_mode || !butchering_enabled)
return
if (isbodypart(target))
INVOKE_ASYNC(src, PROC_REF(butcher_limb), source, target, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/component/butchering/proc/add_item_context(obj/item/source, list/context, atom/target, mob/living/user)
SIGNAL_HANDLER
if (!source.get_sharpness() && !can_be_blunt)
return NONE
if (!butchering_enabled)
return NONE
if (isbodypart(target))
context[SCREENTIP_CONTEXT_LMB] = "(Combat Mode) Butcher limb"
return CONTEXTUAL_SCREENTIP_SET
if (!isliving(target))
return NONE
var/mob/living/victim = target
if (victim.stat != DEAD && (victim.butcher_results || victim.guaranteed_butcher_results))
context[SCREENTIP_CONTEXT_LMB] = "(Combat Mode) Butcher"
return CONTEXTUAL_SCREENTIP_SET
if (ishuman(victim))
context[SCREENTIP_CONTEXT_RMB] = "(Combat Mode) Butcher"
return CONTEXTUAL_SCREENTIP_SET
return NONE
/datum/component/butchering/proc/butcher_limb(obj/item/source, obj/item/bodypart/target, mob/living/user)
target.add_fingerprint(user)
if (LIMB_HAS_SKIN(target) && !HAS_ANY_SURGERY_STATE(target.surgery_state, SURGERY_SKIN_CUT | SURGERY_SKIN_OPEN))
to_chat(user, span_warning("[target]'s skin is still intact!"))
return
if (LIMB_HAS_BONES(target) && !HAS_ANY_SURGERY_STATE(target.surgery_state, SURGERY_BONE_DRILLED | SURGERY_BONE_SAWED))
// We need to gut the limb before turning it into meat, otherwise just cut around the bone I guess
if (length(target.contents))
to_chat(user, span_warning("[target]'s bones are still intact!"))
return
H.visible_message(span_danger("[user] slits [H]'s throat!"), \
span_userdanger("[user] slits your throat..."))
log_combat(user, H, "wounded via throat slitting", source)
H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus=CANT_WOUND) // easy tiger, we'll get to that in a sec
var/obj/item/bodypart/slit_throat = H.get_bodypart(BODY_ZONE_HEAD)
if (H.cause_wound_of_type_and_severity(WOUND_SLASH, slit_throat, WOUND_SEVERITY_CRITICAL))
H.apply_status_effect(/datum/status_effect/neck_slice)
var/speed_modifier = 1
if (!target.owner)
speed_modifier = 0.5
else if (target.owner.stat < UNCONSCIOUS)
speed_modifier = 1.5 // yeowch
var/limb_descriptor = (target.owner ? "[target.owner]'s [target.plaintext_zone]" : target)
// Okay *hopefully* they're already dead at this point
if (target.body_zone == BODY_ZONE_CHEST && target.owner)
// Butchering the chest gibs the victim
limb_descriptor = target.owner
if (target.owner)
log_combat(user, target.owner, "attempted to butcher", source)
if (length(target.contents))
user.visible_message(span_warning("[user] begins to gut [limb_descriptor]!"), span_notice("You begin to gut [limb_descriptor]..."), ignored_mobs = target.owner)
if (target.owner)
to_chat(target.owner, span_warning("[user] begins to gut your [target.plaintext_zone]!"))
playsound(target.loc, butcher_sound, 50, TRUE, -1)
if (!do_after(user, speed * speed_modifier, target.owner || target))
return
target.drop_organs(user, TRUE)
return
if (!length(target.butcher_drops))
to_chat(user, span_warning("There is nothing left inside [limb_descriptor]!"))
return
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)
if (limb != target && limb.butcher_drops && limb.butcher_replacement)
to_chat(user, span_warning("You need to butcher all other limbs first!"))
return
user.visible_message(span_warning("[user] begins to cut [limb_descriptor] apart!"), span_notice("You begin to cut [limb_descriptor] apart..."), ignored_mobs = target.owner)
if (target.owner)
to_chat(target.owner, span_warning("[user] begins to cut your [target.plaintext_zone] apart!"))
playsound(target.loc, butcher_sound, 50, TRUE, -1)
if (!do_after(user, speed * speed_modifier, target.owner || target))
return
var/list/results = list()
var/turf/drop_loc = target.owner?.drop_location() || target.drop_location()
var/bonus_chance = max(0, (effectiveness - 100) + bonus_modifier) //so 125 total effectiveness = 25% extra chance
var/list/failures = list()
var/list/bonuses = list()
for (var/obj/item/drop_type as anything in target.butcher_drops)
var/amount = target.butcher_drops[drop_type] || 1
var/is_stack = ispath(drop_type, /obj/item/stack)
for (var/i in 1 to amount)
if (!prob(effectiveness))
failures |= drop_type::name
amount -= 1
continue
if (prob(bonus_chance))
if (!is_stack)
if (ispath(drop_type, /obj/item/food/meat))
results += new drop_type(drop_loc, target.blood_dna_info?.Copy())
else
var/obj/item/butcher_result = new drop_type(drop_loc)
if (target.blood_dna_info)
butcher_result.add_blood_DNA(target.blood_dna_info.Copy())
results += butcher_result
amount += 1
bonuses |= drop_type::name
if (is_stack)
continue
if (ispath(drop_type, /obj/item/food/meat))
results += new drop_type(drop_loc, target.blood_dna_info?.Copy())
continue
var/obj/item/butcher_result = new drop_type(drop_loc)
butcher_result.add_blood_DNA(target.blood_dna_info.Copy())
results += butcher_result
if (is_stack && amount)
var/obj/item/stack/butcher_result = new drop_type(drop_loc, amount)
if (target.blood_dna_info)
butcher_result.add_blood_DNA(target.blood_dna_info.Copy())
results += butcher_result
if (target.reagents)
var/meat_produced = 0
for (var/obj/item/food/target_meat in results)
meat_produced += 1
for (var/obj/item/food/target_meat in results)
target.reagents.trans_to(target_meat, target.reagents.total_volume / meat_produced, remove_blacklisted = TRUE)
if (target.owner)
var/reagents_in_produced = 0
for(var/obj/item/result as anything in results)
if(result.reagents)
reagents_in_produced += 1
var/list/diseases = target.owner.get_static_viruses()
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)
result.reagents?.add_reagent(/datum/reagent/consumable/nutriment/fat, target.owner.nutrition / 15 / reagents_in_produced)
if(LAZYLEN(diseases))
var/list/datum/disease/diseases_to_add = list()
for(var/datum/disease/disease as anything in diseases)
// Admin or special viruses that should not be reproduced
if(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
continue
diseases_to_add += disease
if(LAZYLEN(diseases_to_add))
result.AddComponent(/datum/component/infective, diseases_to_add)
for (var/obj/item/food/meat/meat in results)
meat.name = "[target.owner.real_name]'s [meat.name]"
meat.set_custom_materials(list(GET_MATERIAL_REF(/datum/material/meat/mob_meat, target.owner) = 4 * SHEET_MATERIAL_AMOUNT))
meat.subjectname = target.owner.real_name
meat.subjectjob = target.owner.job
user.visible_message(span_warning("[user] butchers [limb_descriptor]!"), span_notice("You butcher [limb_descriptor]."), ignored_mobs = target.owner)
if (!target.owner)
target.drop_organs(violent_removal = TRUE) // Should not happen, but just in case
create_replacement_limb(target, drop_loc)
qdel(target)
return
var/wound_type = null
if (source.damtype == BURN)
wound_type = WOUND_BURN
else
switch (source.get_sharpness())
if (SHARP_EDGED)
wound_type = WOUND_SLASH
if (SHARP_POINTY)
wound_type = WOUND_PIERCE
else
wound_type = WOUND_BLUNT
to_chat(target.owner, span_userdanger("[user] hacks the meat off your [target.plaintext_zone]!"))
var/mob/living/carbon/victim = target.owner
if (!target.butcher_replacement)
target.dismember(source.damtype, wound_type)
target.drop_organs(violent_removal = TRUE) // Should not happen, but just in case
qdel(target)
return
var/obj/item/bodypart/replacement = create_replacement_limb(target, drop_loc)
target.dismember(source.damtype, wound_type)
target.drop_organs(violent_removal = TRUE)
replacement.replace_limb(victim)
replacement.update_limb(is_creating = TRUE)
qdel(target)
/// Creates a replacement (usually skeleton) limb for the butchered one
/datum/component/butchering/proc/create_replacement_limb(obj/item/bodypart/target, drop_loc)
var/drop_type = target.butcher_replacement
var/obj/item/bodypart/replacement = new drop_type(drop_loc)
replacement.set_initial_damage(target.brute_dam, target.burn_dam)
if (IS_ORGANIC_LIMB(replacement) && target.owner)
replacement.blood_dna_info = target.owner.get_blood_dna_list()
for (var/datum/wound/wound as anything in target.wounds)
wound.remove_wound()
wound.apply_wound(replacement, silent = TRUE)
return replacement
/datum/component/butchering/proc/start_butcher(obj/item/source, mob/living/target, mob/living/user)
to_chat(user, span_notice("You begin to butcher [target]..."))
playsound(target.loc, butcher_sound, 50, TRUE, -1)
if (do_after(user, speed, target) && target.Adjacent(source))
on_butchering(user, target)
/datum/component/butchering/proc/butcher_human(obj/item/source, mob/living/carbon/human/victim, mob/living/user)
if (DOING_INTERACTION_WITH_TARGET(user, victim))
to_chat(user, span_warning("You're already interacting with [victim]!"))
return
var/static/list/butcher_spots = typecacheof(list(
/obj/structure/table,
/obj/structure/bed,
/obj/machinery/stasis,
/obj/structure/kitchenspike,
))
var/found_spot = FALSE
for (var/obj/thing in victim.loc)
if (is_type_in_typecache(thing, butcher_spots))
found_spot = TRUE
break
if (!found_spot)
to_chat(user, span_warning("You need a better spot to butcher [victim]!"))
return
var/obj/item/bodypart/limb = victim.get_bodypart(deprecise_zone(user.zone_selected))
if (!limb)
to_chat(user, span_warning("[victim] doesn't have a [parse_zone(deprecise_zone(user.zone_selected))]!"))
return
butcher_limb(source, limb, user)
/datum/component/butchering/proc/start_neck_slice(obj/item/source, mob/living/carbon/human/victim, mob/living/user)
if (DOING_INTERACTION_WITH_TARGET(user, victim))
to_chat(user, span_warning("You're already interacting with [victim]!"))
return
user.visible_message(span_danger("[user] is slitting [victim]'s throat!"), \
span_danger("You start slicing [victim]'s throat!"), \
span_hear("You hear a cutting noise!"), ignored_mobs = victim)
victim.show_message(span_userdanger("Your throat is being slit by [user]!"), MSG_VISUAL, \
span_userdanger("Something is cutting into your neck!"), NONE)
log_combat(user, victim, "attempted throat slitting", source)
playsound(victim.loc, butcher_sound, 50, TRUE, -1)
if (!do_after(user, clamp(500 / source.force, 30, 100), victim) && victim.Adjacent(source))
return
if (victim.has_status_effect(/datum/status_effect/neck_slice))
user.show_message(span_warning("[victim]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \
span_warning("Their neck has already been already cut, you can't make the bleeding any worse!"))
return
victim.visible_message(span_danger("[user] slits [victim]'s throat!"), \
span_userdanger("[user] slits your throat..."))
log_combat(user, victim, "wounded via throat slitting", source)
victim.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus=CANT_WOUND) // easy tiger, we'll get to that in a sec
var/obj/item/bodypart/slit_throat = victim.get_bodypart(BODY_ZONE_HEAD)
if (victim.cause_wound_of_type_and_severity(WOUND_SLASH, slit_throat, WOUND_SEVERITY_CRITICAL))
victim.apply_status_effect(/datum/status_effect/neck_slice)
/**
* Handles a user butchering a target
@@ -107,67 +392,85 @@
var/final_effectiveness = effectiveness - target.butcher_difficulty
var/bonus_chance = max(0, (final_effectiveness - 100) + bonus_modifier) //so 125 total effectiveness = 25% extra chance
if(target.flags_1 & HOLOGRAM_1)
if (target.flags_1 & HOLOGRAM_1)
butcher.visible_message(span_notice("[butcher] tries to butcher [target], but it vanishes."), \
span_notice("You try to butcher [target], but it vanishes."))
qdel(target)
return
for(var/result_typepath in target.butcher_results)
var/obj/remains = result_typepath
var/amount = target.butcher_results[remains]
for(var/_i in 1 to amount)
if(!prob(final_effectiveness))
if(butcher)
to_chat(butcher, span_warning("You fail to harvest some of the [initial(remains.name)] from [target]."))
var/list/failures = list()
var/list/bonuses = list()
for (var/obj/remains as anything in target.butcher_results)
var/amount = target.butcher_results[remains] || 1
var/is_stack = ispath(remains, /obj/item/stack)
for (var/i in 1 to amount)
if (!prob(final_effectiveness))
failures |= remains::name
amount -= 1
continue
if(prob(bonus_chance))
if(butcher)
to_chat(butcher, span_info("You harvest some extra [initial(remains.name)] from [target]!"))
results += new remains (location)
results += new remains (location)
if (prob(bonus_chance))
if (!is_stack)
results += new remains(location)
amount += 1
bonuses |= remains::name
target.butcher_results.Remove(remains) //in case you want to, say, have it drop its results on gib
if (!is_stack)
results += new remains(location)
for(var/guaranteed_result_typepath in target.guaranteed_butcher_results)
var/obj/guaranteed_remains = guaranteed_result_typepath
if (is_stack && amount)
results += new remains(location, amount)
target.butcher_results?.Cut()
if (butcher)
if (length(failures))
to_chat(butcher, span_warning("You fail to harvest some of the [english_list(failures)] from [target]."))
if (length(bonuses))
to_chat(butcher, span_info("You harvest some extra [english_list(bonuses)] from [target]!"))
for (var/obj/guaranteed_remains as anything in target.guaranteed_butcher_results)
var/amount = target.guaranteed_butcher_results[guaranteed_remains]
for(var/i in 1 to amount)
results += new guaranteed_remains (location)
target.guaranteed_butcher_results.Remove(guaranteed_remains)
if (ispath(guaranteed_remains, /obj/item/stack))
results += new guaranteed_remains(location, amount)
continue
for(var/obj/item/carrion in results)
for (var/i in 1 to amount)
results += new guaranteed_remains(location)
target.guaranteed_butcher_results?.Cut()
for (var/obj/item/carrion in results)
var/list/meat_mats = carrion.has_material_type(/datum/material/meat)
if(!length(meat_mats))
if (!length(meat_mats))
continue
carrion.set_custom_materials((carrion.custom_materials - meat_mats) + list(GET_MATERIAL_REF(/datum/material/meat/mob_meat, target) = counterlist_sum(meat_mats)))
// transfer delicious reagents to meat
if(target.reagents)
// Transfer delicious reagents to meat
if (target.reagents)
var/meat_produced = 0
for(var/obj/item/food/meat/slab/target_meat in results)
for (var/obj/item/food/target_meat in results)
meat_produced += 1
for(var/obj/item/food/meat/slab/target_meat in results)
for (var/obj/item/food/target_meat in results)
target.reagents.trans_to(target_meat, target.reagents.total_volume / meat_produced, remove_blacklisted = TRUE)
// dont forget yummy diseases either!
if(iscarbon(target))
// Don't forget yummy diseases either!
if (iscarbon(target))
var/mob/living/carbon/host_target = target
var/list/diseases = host_target.get_static_viruses()
if(LAZYLEN(diseases))
if (LAZYLEN(diseases))
var/list/datum/disease/diseases_to_add = list()
for(var/datum/disease/disease as anything in diseases)
for (var/datum/disease/disease as anything in diseases)
// admin or special viruses that should not be reproduced
if(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
continue
if (!(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS)))
diseases_to_add += disease
diseases_to_add += disease
if(LAZYLEN(diseases_to_add))
for(var/obj/diseased_remains in results)
if (LAZYLEN(diseases_to_add))
for (var/obj/diseased_remains in results)
diseased_remains.AddComponent(/datum/component/infective, diseases_to_add)
if(butcher)
if (butcher)
butcher.visible_message(span_notice("[butcher] butchers [target]."), \
span_notice("You butcher [target]."))
butcher_callback?.Invoke(butcher, target)
@@ -175,20 +478,9 @@
target.log_message("has been butchered by [key_name(butcher)]", LOG_ATTACK)
target.gib(DROP_BRAIN|DROP_ORGANS)
///Enables the butchering mechanic for the mob who has equipped us.
/datum/component/butchering/proc/enable_butchering(datum/source)
SIGNAL_HANDLER
butchering_enabled = TRUE
///Disables the butchering mechanic for the mob who has dropped us.
/datum/component/butchering/proc/disable_butchering(datum/source)
SIGNAL_HANDLER
butchering_enabled = FALSE
///Special snowflake component only used for the recycler.
/// Special snowflake component only used for the recycler.
/datum/component/butchering/recycler
/datum/component/butchering/recycler/Initialize(
speed,
effectiveness,
@@ -198,10 +490,10 @@
can_be_blunt,
butcher_callback,
)
if(!istype(parent, /obj/machinery/recycler)) //EWWW
if (!istype(parent, /obj/machinery/recycler)) //EWWW
return COMPONENT_INCOMPATIBLE
. = ..()
if(. == COMPONENT_INCOMPATIBLE)
if (. == COMPONENT_INCOMPATIBLE)
return
var/static/list/loc_connections = list(
@@ -212,13 +504,13 @@
/datum/component/butchering/recycler/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
if(!isliving(arrived))
if (!isliving(arrived))
return
var/mob/living/victim = arrived
var/obj/machinery/recycler/eater = parent
if(eater.safety_mode || (eater.machine_stat & (BROKEN|NOPOWER))) //I'm so sorry.
if (eater.safety_mode || (eater.machine_stat & (BROKEN|NOPOWER))) //I'm so sorry.
return
if(victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results))
if (victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results))
on_butchering(parent, victim)
/datum/component/butchering/mecha
@@ -237,6 +529,16 @@
COMSIG_MECHA_EQUIPMENT_DETACHED,
))
/// Enables the butchering mechanic for the mecha who has equipped us.
/datum/component/butchering/mecha/proc/enable_butchering(datum/source)
SIGNAL_HANDLER
butchering_enabled = TRUE
/// Disables the butchering mechanic for the mecha who has dropped us.
/datum/component/butchering/mecha/proc/disable_butchering(datum/source)
SIGNAL_HANDLER
butchering_enabled = FALSE
///When we are ready to drill through a mob
/datum/component/butchering/mecha/proc/on_drill(datum/source, obj/vehicle/sealed/mecha/chassis, mob/living/target)
SIGNAL_HANDLER
@@ -256,16 +558,16 @@
COMSIG_ITEM_DROPPED,
))
///Same as enable_butchering but for worn items
/// Same as enable_butchering but for worn items
/datum/component/butchering/wearable/proc/worn_enable_butchering(obj/item/source, mob/user, slot)
SIGNAL_HANDLER
//check if the item is being not worn
if(!(slot & source.slot_flags))
if (!(slot & source.slot_flags))
return
butchering_enabled = TRUE
RegisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(butcher_target))
///Same as disable_butchering but for worn items
/// Same as disable_butchering but for worn items
/datum/component/butchering/wearable/proc/worn_disable_butchering(obj/item/source, mob/user)
SIGNAL_HANDLER
butchering_enabled = FALSE
@@ -273,6 +575,6 @@
/datum/component/butchering/wearable/proc/butcher_target(mob/user, atom/target, proximity)
SIGNAL_HANDLER
if(!isliving(target))
if (!isliving(target))
return NONE
return onItemAttack(parent, target, user)
return on_item_attack(parent, target, user)
+33
View File
@@ -0,0 +1,33 @@
/// Allows mobs with this component to have all of their limbs operated on without needing drapes
/datum/component/free_operation
dupe_mode = COMPONENT_DUPE_SOURCES
/datum/component/free_operation/Initialize(check)
if (!iscarbon(parent))
return COMPONENT_INCOMPATIBLE
ADD_TRAIT(parent, TRAIT_READY_TO_OPERATE, REF(src))
var/mob/living/carbon/owner = parent
for (var/obj/item/bodypart/limb as anything in owner.bodyparts)
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
for (var/obj/item/bodypart/limb as anything in owner.bodyparts)
REMOVE_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src))
return ..()
/datum/component/free_operation/RegisterWithParent()
RegisterSignal(parent, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(flag_limb))
RegisterSignal(parent, COMSIG_CARBON_REMOVE_LIMB, PROC_REF(unflag_limb))
/datum/component/free_operation/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB))
/datum/component/free_operation/proc/flag_limb(mob/living/carbon/source, obj/item/bodypart/limb)
SIGNAL_HANDLER
ADD_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src))
/datum/component/free_operation/proc/unflag_limb(mob/living/carbon/source, obj/item/bodypart/limb)
SIGNAL_HANDLER
REMOVE_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src))