mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-12 16:44:43 +01:00
8c45d65c4a
## About The Pull Request Digi and snout bodyshape are added and removed if digi or snout is hidden There now also exists a digitigrade bodytype which is not removed if the bodyshape his Bodyshape is solely used for determining how the body is shaped for rendering Bodytype is used for checking stuff like "can digis wear shoes" This lets us clean up rendering code a bit, allowing us to pass around bodyshape rather than needing to check our wearer for digi and whatnot Digi itself is now a component for legs that tracks - on item equip or unequip - if said items squish the legs. ## Why It's Good For The Game Right now we do some super jank stuff like updating body every time we update our jumpsuit which is obviously a bit wasteful This should make things easier to manage and work with (and maybe more performant?) ## Changelog 🆑 Melbert refactor: Refactored how digitigrade and snouts render, report any oddities with that /🆑
587 lines
21 KiB
Plaintext
587 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)
|
|
|
|
SEND_SIGNAL(target, COMSIG_BODYPART_BUTCHERED, replacement)
|
|
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)
|