mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 13:10:02 +01:00
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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
Reference in New Issue
Block a user