Files
+37 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

586 lines
21 KiB
Plaintext

/datum/component/butchering
/// Time in deciseconds taken to butcher something
var/speed = 8 SECONDS
/// Percentage effectiveness; numbers above 100 yield extra drops
var/effectiveness = 100
/// Percentage increase to bonus item chance
var/bonus_modifier = 0
/// Sound played when butchering
var/butcher_sound = 'sound/effects/butcher.ogg'
/// Whether or not this component can be used to butcher currently. Used to temporarily disable butchering
var/butchering_enabled = TRUE
/// Whether or not this component is compatible with blunt tools.
var/can_be_blunt = FALSE
/// Callback for butchering
var/datum/callback/butcher_callback
/datum/component/butchering/Initialize(
speed = 8 SECONDS,
effectiveness = 100,
bonus_modifier = 0,
butcher_sound = 'sound/effects/butcher.ogg',
disabled = FALSE,
can_be_blunt = FALSE,
butcher_callback,
)
src.speed = speed
src.effectiveness = effectiveness
src.bonus_modifier = bonus_modifier
src.butcher_sound = butcher_sound
if (disabled)
src.butchering_enabled = FALSE
src.can_be_blunt = can_be_blunt
src.butcher_callback = butcher_callback
/datum/component/butchering/Destroy(force)
butcher_callback = null
return ..()
/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 (!source.get_sharpness() && !can_be_blunt)
return
if (!user.combat_mode)
return
// 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
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.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
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 = null
if (ispath(drop_type, /obj/item/stack/sheet/animalhide/carbon))
butcher_result = new drop_type(drop_loc, amount, /*merge = */TRUE, /*mat_override = */null, /*mat_amount = */1, target.skin_tone || target.species_color)
else
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.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))
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(SSmaterials.get_material(/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.bodyshape = target.bodyshape
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
*
* Arguments:
* - [butcher][/mob/living]: The mob doing the butchering
* - [target][/mob/living]: The mob being butchered
*/
/datum/component/butchering/proc/on_butchering(atom/butcher, mob/living/target)
var/list/results = list()
var/turf/location = target.drop_location()
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)
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
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 (!is_stack)
results += new remains(location)
amount += 1
bonuses |= remains::name
if (!is_stack)
results += new remains(location)
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]
if (ispath(guaranteed_remains, /obj/item/stack))
results += new guaranteed_remains(location, amount)
continue
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))
continue
carrion.set_custom_materials((carrion.custom_materials - meat_mats) + list(SSmaterials.get_material(/datum/material/meat/mob_meat, target) = counterlist_sum(meat_mats)))
// Transfer delicious reagents to meat
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)
// 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))
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)))
diseases_to_add += disease
if (LAZYLEN(diseases_to_add))
for (var/obj/diseased_remains in results)
diseased_remains.AddComponent(/datum/component/infective, diseases_to_add)
if (butcher)
butcher.visible_message(span_notice("[butcher] butchers [target]."), \
span_notice("You butcher [target]."))
butcher_callback?.Invoke(butcher, target)
target.harvest(butcher)
target.log_message("has been butchered by [key_name(butcher)]", LOG_ATTACK)
target.gib(DROP_BRAIN|DROP_ORGANS)
/// Special snowflake component only used for the recycler.
/datum/component/butchering/recycler
/datum/component/butchering/recycler/Initialize(
speed,
effectiveness,
bonus_modifier,
butcher_sound,
disabled,
can_be_blunt,
butcher_callback,
)
if (!istype(parent, /obj/machinery/recycler)) //EWWW
return COMPONENT_INCOMPATIBLE
. = ..()
if (. == COMPONENT_INCOMPATIBLE)
return
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
)
AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)
/datum/component/butchering/recycler/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
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.
return
if (victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results))
on_butchering(parent, victim)
/datum/component/butchering/mecha
/datum/component/butchering/mecha/RegisterWithParent()
. = ..()
RegisterSignal(parent, COMSIG_MECHA_EQUIPMENT_ATTACHED, PROC_REF(enable_butchering))
RegisterSignal(parent, COMSIG_MECHA_EQUIPMENT_DETACHED, PROC_REF(disable_butchering))
RegisterSignal(parent, COMSIG_MECHA_DRILL_MOB, PROC_REF(on_drill))
/datum/component/butchering/mecha/UnregisterFromParent()
. = ..()
UnregisterSignal(parent, list(
COMSIG_MECHA_DRILL_MOB,
COMSIG_MECHA_EQUIPMENT_ATTACHED,
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
INVOKE_ASYNC(src, PROC_REF(on_butchering), chassis, target)
/datum/component/butchering/wearable
/datum/component/butchering/wearable/RegisterWithParent()
. = ..()
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(worn_enable_butchering))
RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(worn_disable_butchering))
/datum/component/butchering/wearable/UnregisterFromParent()
. = ..()
UnregisterSignal(parent, list(
COMSIG_ITEM_EQUIPPED,
COMSIG_ITEM_DROPPED,
))
/// 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))
return
butchering_enabled = TRUE
RegisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(butcher_target))
/// 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
UnregisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK)
/datum/component/butchering/wearable/proc/butcher_target(mob/user, atom/target, proximity)
SIGNAL_HANDLER
if (!isliving(target))
return NONE
return on_item_attack(parent, target, user)