mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
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>
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)
|