Files
Bubberstation/code/datums/components/butchering.dm
nevimer 356efbfb3e Public Logging Update (#1545)
Use Regex to be smarter.

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request

Public logging finds ckeys via regex to remove them from lines now. Not
used for player say, at the moment. Not needed there either, as ckeys
are only used by key_name() and thats passed down as a string. rather
than just edit every instance of the proc, ill just regex them out by
adding identifiers into the ckey blocks in logs and snip them that way.

<!-- Please make sure to actually test your PRs. If you have not tested
your PR mention it. -->

## Why It's Good For The Game

Public logs are little more private. We want them only to have
characters, no keys.

## Proof Of Testing

<!-- Compile and run your code locally. Make sure it works. This is the
place to show off your changes! We are not responsible for testing your
features. -->

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and it's effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
refactor: public logging uses regex for cases of mechanics now. 
/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->

<!-- By opening a pull request. You have read and understood the
repository rules located on the main README.md on this project. -->

Co-authored-by: Waterpig <49160555+Majkl-J@users.noreply.github.com>
2024-11-25 18:54:51 -05:00

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)