mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-13 03:02:38 +00:00
## About The Pull Request Split this off from https://github.com/tgstation/tgstation/pull/81598 in hopes to keep it as atomic as I can. Genericizes the things that holographic monkey species existed to replace, so now we don't have to worry about having to copy paste this to any future mob later down the line. ## Why It's Good For The Game Removes a monkey species subtype, and a pretty bad one at that. Preferably makes hologram mobs more consistent with eachother and prevents copy paste happening everywhere in the future if any new mobs ever gets added to the holodeck. ## Changelog 🆑 refactor: Holographic mobs now gives better feedback to players and should more consistently not give any drops. /🆑
279 lines
11 KiB
Plaintext
279 lines
11 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
|
|
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)
|
|
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]!"))
|
|
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)
|
|
|
|
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!"))
|
|
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)
|
|
|
|
/**
|
|
* 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
|
|
|
|
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]."))
|
|
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)
|
|
|
|
target.butcher_results.Remove(remains) //in case you want to, say, have it drop its results on gib
|
|
|
|
for(var/guaranteed_result_typepath in target.guaranteed_butcher_results)
|
|
var/obj/guaranteed_remains = guaranteed_result_typepath
|
|
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)
|
|
|
|
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(GET_MATERIAL_REF(/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/meat/slab/target_meat in results)
|
|
meat_produced += 1
|
|
for(var/obj/item/food/meat/slab/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))
|
|
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))
|
|
continue
|
|
|
|
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)
|
|
|
|
///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.
|
|
/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,
|
|
))
|
|
|
|
///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 onItemAttack(parent, target, user)
|