Files
Y0SH1M4S73R a54141bed8 [I **NOT** ded] Heart Eater only works with sentient beings' hearts (+heart ripping). (#95677)
## About The Pull Request

You now only gain the benefits of the heart eater perk by eating a heart
taken from a mob with a mind. In order to make this not completely
useless for any wizard without the Smite spell, the heart eater perk now
grants the user the ability to tear the heart out of incapacitated or
dead carbons by targeting their chest with an empty hand in combat mode.
This can be done on one person at a time per hand, so subtle synergy
with the four arms perk.

## Why It's Good For The Game

Getting 80% damage resistance, stacking linearly with armor to
potentially grant total immunity to conventional sources of damage,
should require a lot of effort and putting yourself into significant
risk of getting killed.

## Changelog

🆑
balance: Wizards with the Heart Eater perk are only invigorated by the
hearts of sentient beings.
add: To facilitate the taking of hearts from sentient beings, the Heart
Eater perk lets you tear the heart out of the chest of an incapacitated
or dead being by targeting their chest with an empty hand in combat
mode.
/🆑
2026-04-20 18:20:59 +01:00

182 lines
6.6 KiB
Plaintext

/datum/component/heart_eater
/// Check if we fully ate whole heart and reset when we start eat new one.
var/bites_taken = 0
/// Remember the number of species damage_modifier.
var/remember_modifier = 0
/// Remember last heart we ate and reset bites_taken counter if we start eat new one
var/datum/weakref/last_heart_we_ate
/// List of all mutations allowed to get.
var/static/list/datum/mutation/mutations_list = list(
/datum/mutation/adaptation/cold,
/datum/mutation/adaptation/heat,
/datum/mutation/adaptation/pressure,
/datum/mutation/adaptation/thermal,
/datum/mutation/chameleon,
/datum/mutation/cryokinesis,
/datum/mutation/pyrokinesis,
/datum/mutation/dwarfism,
/datum/mutation/cindikinesis,
/datum/mutation/insulated,
/datum/mutation/telekinesis,
/datum/mutation/telepathy,
/datum/mutation/thermal,
/datum/mutation/tongue_spike,
/datum/mutation/webbing,
/datum/mutation/xray,
)
/datum/component/heart_eater/Initialize(...)
. = ..()
if(!ishuman(parent))
return COMPONENT_INCOMPATIBLE
prepare_species(parent)
/datum/component/heart_eater/RegisterWithParent()
. = ..()
RegisterSignal(parent, COMSIG_SPECIES_GAIN, PROC_REF(on_species_change))
RegisterSignal(parent, COMSIG_LIVING_FINISH_EAT, PROC_REF(eat_eat_eat))
RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(try_rip_heart))
/datum/component/heart_eater/UnregisterFromParent()
. = ..()
UnregisterSignal(parent, COMSIG_LIVING_FINISH_EAT)
UnregisterSignal(parent, COMSIG_SPECIES_GAIN)
UnregisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK)
/datum/component/heart_eater/proc/prepare_species(mob/living/carbon/human/eater)
if(eater.get_liked_foodtypes() & GORE)
return
var/obj/item/organ/tongue/eater_tongue = eater.get_organ_slot(ORGAN_SLOT_TONGUE)
if(!eater_tongue)
return
eater_tongue.disliked_foodtypes &= ~GORE
eater_tongue.liked_foodtypes |= GORE
/datum/component/heart_eater/proc/on_species_change(mob/living/carbon/human/eater, datum/species/new_species, datum/species/old_species, pref_load, regenerate_icons)
SIGNAL_HANDLER
eater.dna?.species?.damage_modifier += remember_modifier
prepare_species(eater)
/// Proc called when we finish eat somthing.
/datum/component/heart_eater/proc/eat_eat_eat(mob/living/carbon/human/eater, datum/what_we_ate)
SIGNAL_HANDLER
if(get_area(eater) == GLOB.areas_by_type[/area/centcom/wizard_station])
return
if(!istype(what_we_ate, /obj/item/organ/heart))
return
var/obj/item/organ/heart/we_ate_heart = what_we_ate
var/obj/item/organ/heart/previous_heart = last_heart_we_ate?.resolve()
if(we_ate_heart == previous_heart)
return
if (!HAS_TRAIT(we_ate_heart, TRAIT_ORGAN_USED_BY_PLAYER))
to_chat(eater, span_warning("This heart is utterly lifeless, you won't receive any boons from consuming it!"))
return
bites_taken = 0
last_heart_we_ate = WEAKREF(we_ate_heart)
bites_taken++
if(bites_taken < (we_ate_heart.reagents.total_volume/2))
return
if(prob(50))
perfect_heart(eater)
return
not_perfect_heart(eater)
///Perfect heart give our +10 damage modifier(Max. 80).
/datum/component/heart_eater/proc/perfect_heart(mob/living/carbon/human/eater)
if(eater.dna?.species?.damage_modifier >= 80)
healing_heart(eater)
return
eater.dna?.species?.damage_modifier += 10
remember_modifier += 10
healing_heart(eater)
to_chat(eater, span_warning("This heart is perfect. You feel a surge of vital energy."))
///Not Perfect heart give random mutation.
/datum/component/heart_eater/proc/not_perfect_heart(mob/living/carbon/human/eater)
var/datum/mutation/new_mutation
var/list/datum/mutation/shuffle_mutation_list = shuffle(mutations_list)
for(var/mutation_in_list in shuffle_mutation_list)
if(is_type_in_list(mutation_in_list, eater.dna.mutations))
continue
new_mutation = mutation_in_list
break
if(isnull(new_mutation))
healing_heart(eater)
return
eater.dna.add_mutation(new_mutation, MUTATION_SOURCE_HEART_EATER)
healing_heart(eater)
to_chat(eater, span_warning("This heart is not right for you. You now have [new_mutation.name] mutation."))
///Heart eater give also strong healing from hearts.
/datum/component/heart_eater/proc/healing_heart(mob/living/carbon/human/eater)
for(var/heal_organ in eater.organs)
eater.adjust_organ_loss(heal_organ, -50)
for(var/datum/wound/heal_wound in eater.all_wounds)
heal_wound.remove_wound()
eater.adjust_brute_loss(-50)
eater.adjust_fire_loss(-50)
eater.adjust_tox_loss(-50)
eater.adjust_oxy_loss(-50)
eater.adjust_stamina_loss(-50)
/datum/component/heart_eater/proc/try_rip_heart(mob/living/source, mob/living/carbon/target, proximity, modifiers)
SIGNAL_HANDLER
if(!istype(target))
return
if(!IS_DEAD_OR_INCAP(target))
return
if(!source.combat_mode)
return
if(source.zone_selected != BODY_ZONE_CHEST)
return
var/obj/item/bodypart/chest = target.get_bodypart(BODY_ZONE_CHEST)
if(!can_rip_heart(source, target, source.active_hand_index))
return
if(chest.get_wound_type(/datum/wound/blunt/bone/critical) && !target.get_organ_slot(ORGAN_SLOT_HEART)) //Don't bother trying to rip a heart out of someone we can see doesn't have one.
return
INVOKE_ASYNC(src, PROC_REF(do_rip_heart), source, target)
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/component/heart_eater/proc/can_rip_heart(mob/living/user, mob/living/carbon/target, hand_index)
if(!IS_DEAD_OR_INCAP(target))
return FALSE
if(!user.has_hand_for_held_index(hand_index))
return FALSE
if(user.get_item_for_held_index(hand_index))
return FALSE
return TRUE
/datum/component/heart_eater/proc/do_rip_heart(mob/living/user, mob/living/carbon/target)
playsound(target, 'sound/items/weapons/slice.ogg', 50, TRUE)
var/hand_index = user.active_hand_index
if(!do_after(
user,
3 SECONDS,
target,
timed_action_flags = IGNORE_HELD_ITEM,
extra_checks = CALLBACK(src, PROC_REF(can_rip_heart), user, target, hand_index),
interaction_key = "[DOAFTER_SOURCE_RIP_HEART]_[hand_index]",
max_interact_count = 1,
))
user.balloon_alert(user, "interrupted!")
return
var/obj/item/bodypart/chest = target.get_bodypart(BODY_ZONE_CHEST)
chest.force_wound_upwards(/datum/wound/blunt/bone/critical, wound_source = "heart ripped")
var/obj/item/organ/heart = target.get_organ_slot(ORGAN_SLOT_HEART)
if(!heart)
target.balloon_alert(user, "no heart!?")
return
heart.Remove(target)
to_chat(user, span_warning("You rip [target]'s [heart.name] out of [target.p_their()] chest!"))
target.visible_message(
span_warning("[user] rips [target]'s [heart.name] out of [target.p_their()] chest!"),
span_userdanger("[user] rips your [heart.name] out of your chest!"),
span_userdanger("You feel something being torn out of your chest!"),
ignored_mobs = list(user),
)
if(!user.put_in_hand(heart, hand_index))
heart.forceMove(user.drop_location())