diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 020efe8d0858..47d703974ae3 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -226,6 +226,9 @@ GLOBAL_LIST_INIT(ai_employers, list( /// Checks if the given mob is either a heretic, heretic monster or a lunatic. #define IS_HERETIC_OR_MONSTER(mob) (IS_HERETIC(mob) || IS_HERETIC_MONSTER(mob) || IS_LUNATIC(mob)) +/// Checks if the given mob is in the mansus realm +#define IS_IN_MANSUS(mob) (istype(get_area(mob), /area/centcom/heretic_sacrifice)) + /// Checks if the given mob is a wizard #define IS_WIZARD(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/wizard)) diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 8966193abff9..48b0f1a9db46 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -232,9 +232,10 @@ multiple modular subtrees with behaviors ///Called when the AI controller pawn changes z levels, we check if there's any clients on the new one and wake up the AI if there is. /datum/ai_controller/proc/on_changed_z_level(atom/source, turf/old_turf, turf/new_turf, same_z_layer, notify_contents) SIGNAL_HANDLER - var/mob/mob_pawn = pawn - if((mob_pawn?.client && !continue_processing_when_client)) - return + if (ismob(pawn)) + var/mob/mob_pawn = pawn + if((mob_pawn?.client && !continue_processing_when_client)) + return if(old_turf) SSai_controllers.ai_controllers_by_zlevel[old_turf.z] -= src if(new_turf) diff --git a/code/datums/elements/corrupted_organ.dm b/code/datums/elements/corrupted_organ.dm new file mode 100644 index 000000000000..ee4cf3a1003d --- /dev/null +++ b/code/datums/elements/corrupted_organ.dm @@ -0,0 +1,66 @@ +/// Component applying shared behaviour by cursed organs granted when sacrificed by a heretic +/// Mostly just does something spooky when it is removed +/datum/element/corrupted_organ + +/datum/element/corrupted_organ/Attach(datum/target) + . = ..() + if (!isinternalorgan(target)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_ORGAN_REMOVED, PROC_REF(on_removed)) + + var/atom/atom_parent = target + atom_parent.color = COLOR_VOID_PURPLE + + atom_parent.add_filter(name = "ray", priority = 1, params = list( + type = "rays", + size = 12, + color = COLOR_VOID_PURPLE, + density = 12 + )) + var/ray_filter = atom_parent.get_filter("ray") + animate(ray_filter, offset = 100, time = 2 MINUTES, loop = -1, flags = ANIMATION_PARALLEL) // Absurdly long animate so nobody notices it hitching when it loops + animate(offset = 0, time = 2 MINUTES) // I sure hope duration of animate doesnt have any performance effect + +/datum/element/corrupted_organ/Detach(datum/source) + UnregisterSignal(source, list(COMSIG_ORGAN_REMOVED)) + return ..() + +/// When we're taken out of someone, do something spooky +/datum/element/corrupted_organ/proc/on_removed(atom/organ, mob/living/carbon/loser) + SIGNAL_HANDLER + if (loser.has_reagent(/datum/reagent/water/holywater) || loser.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY) || prob(20)) + return + if (prob(75)) + organ.AddComponent(\ + /datum/component/haunted_item,\ + haunt_color = "#00000000", \ + aggro_radius = 4, \ + spawn_message = span_revenwarning("[organ] hovers ominously into the air, pulsating with unnatural vigour!"), \ + despawn_message = span_revenwarning("[organ] falls motionless to the ground."), \ + ) + return + var/turf/origin_turf = get_turf(organ) + playsound(organ, 'sound/magic/forcewall.ogg', vol = 100) + new /obj/effect/temp_visual/curse_blast(origin_turf) + organ.visible_message(span_revenwarning("[organ] explodes in a burst of dark energy!")) + for(var/mob/living/target in range(1, origin_turf)) + var/armor = target.run_armor_check(attack_flag = BOMB) + target.apply_damage(30, damagetype = BURN, blocked = armor, spread_damage = TRUE) + qdel(organ) + +/obj/effect/temp_visual/curse_blast + icon = 'icons/effects/64x64.dmi' + pixel_x = -16 + pixel_y = -16 + icon_state = "curse" + duration = 0.3 SECONDS + +/obj/effect/temp_visual/curse_blast/Initialize(mapload) + . = ..() + animate(src, transform = matrix() * 0.2, time = 0, flags = ANIMATION_PARALLEL) + animate(transform = matrix() * 2, time = duration, easing = EASE_IN) + + animate(src, alpha = 255, time = 0, flags = ANIMATION_PARALLEL) + animate(alpha = 255, time = 0.2 SECONDS) + animate(alpha = 0, time = 0.1 SECONDS) diff --git a/code/datums/elements/relay_attackers.dm b/code/datums/elements/relay_attackers.dm index cdc86e6058cb..83e83a40e7ce 100644 --- a/code/datums/elements/relay_attackers.dm +++ b/code/datums/elements/relay_attackers.dm @@ -7,14 +7,15 @@ /datum/element/relay_attackers/Attach(datum/target) . = ..() - // Boy this sure is a lot of ways to tell us that someone tried to attack us - RegisterSignal(target, COMSIG_ATOM_AFTER_ATTACKEDBY, PROC_REF(after_attackby)) - RegisterSignals(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_PAW, COMSIG_MOB_ATTACK_ALIEN), PROC_REF(on_attack_generic)) - RegisterSignals(target, list(COMSIG_ATOM_ATTACK_BASIC_MOB, COMSIG_ATOM_ATTACK_ANIMAL), PROC_REF(on_attack_npc)) - RegisterSignal(target, COMSIG_PROJECTILE_PREHIT, PROC_REF(on_bullet_act)) - RegisterSignal(target, COMSIG_ATOM_PREHITBY, PROC_REF(on_hitby)) - RegisterSignal(target, COMSIG_ATOM_HULK_ATTACK, PROC_REF(on_attack_hulk)) - RegisterSignal(target, COMSIG_ATOM_ATTACK_MECH, PROC_REF(on_attack_mech)) + if (!HAS_TRAIT(target, TRAIT_RELAYING_ATTACKER)) // Little bit gross but we want to just apply this shit from a bunch of places + // Boy this sure is a lot of ways to tell us that someone tried to attack us + RegisterSignal(target, COMSIG_ATOM_AFTER_ATTACKEDBY, PROC_REF(after_attackby)) + RegisterSignals(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_PAW, COMSIG_MOB_ATTACK_ALIEN), PROC_REF(on_attack_generic)) + RegisterSignals(target, list(COMSIG_ATOM_ATTACK_BASIC_MOB, COMSIG_ATOM_ATTACK_ANIMAL), PROC_REF(on_attack_npc)) + RegisterSignal(target, COMSIG_PROJECTILE_PREHIT, PROC_REF(on_bullet_act)) + RegisterSignal(target, COMSIG_ATOM_PREHITBY, PROC_REF(on_hitby)) + RegisterSignal(target, COMSIG_ATOM_HULK_ATTACK, PROC_REF(on_attack_hulk)) + RegisterSignal(target, COMSIG_ATOM_ATTACK_MECH, PROC_REF(on_attack_mech)) ADD_TRAIT(target, TRAIT_RELAYING_ATTACKER, REF(src)) /datum/element/relay_attackers/Detach(datum/source, ...) diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 091bb5407fad..965e0417f6f9 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -174,6 +174,14 @@ reagents.trans_to(H, reagents.total_volume, transfered_by = user, methods = INGEST) qdel(src) +/// Nebula vomit with extra guests +/obj/effect/decal/cleanable/vomit/nebula/worms + +/obj/effect/decal/cleanable/vomit/nebula/worms/Initialize(mapload, list/datum/disease/diseases) + . = ..() + for (var/i in 1 to rand(2, 3)) + new /mob/living/basic/hivelord_brood(loc) + /obj/effect/decal/cleanable/vomit/old name = "crusty dried vomit" desc = "You try not to look at the chunks, and fail." diff --git a/code/modules/antagonists/heretic/items/corrupted_organs.dm b/code/modules/antagonists/heretic/items/corrupted_organs.dm new file mode 100644 index 000000000000..4923487c03a3 --- /dev/null +++ b/code/modules/antagonists/heretic/items/corrupted_organs.dm @@ -0,0 +1,243 @@ +/// Renders you unable to see people who were heretics at the time that this organ is gained +/obj/item/organ/internal/eyes/corrupt + name = "corrupt orbs" + desc = "These eyes have seen something they shouldn't have." + /// The override images we are applying + var/list/hallucinations + +/obj/item/organ/internal/eyes/corrupt/Initialize(mapload) + . = ..() + AddElement(/datum/element/corrupted_organ) + AddElement(/datum/element/noticable_organ, "eyes have wide dilated pupils, and no iris. Something is moving in the darkness.", BODY_ZONE_PRECISE_EYES) + +/obj/item/organ/internal/eyes/corrupt/Insert(mob/living/carbon/organ_owner, special, drop_if_replaced) + . = ..() + if (!organ_owner.client) + return + + var/list/human_mobs = GLOB.human_list.Copy() + human_mobs -= organ_owner + for (var/mob/living/carbon/human/check_human as anything in human_mobs) + if (!IS_HERETIC(check_human) && !prob(5)) // Throw in some false positives + continue + var/image/invisible_man = image('icons/blanks/32x32.dmi', check_human, "nothing") + invisible_man.override = TRUE + LAZYADD(hallucinations, invisible_man) + + if (LAZYLEN(hallucinations)) + organ_owner.client.images |= hallucinations + +/obj/item/organ/internal/eyes/corrupt/Remove(mob/living/carbon/organ_owner, special) + . = ..() + if (!LAZYLEN(hallucinations)) + return + organ_owner.client?.images -= hallucinations + QDEL_NULL(hallucinations) + + +/// Sometimes speak in incomprehensible tongues +/obj/item/organ/internal/tongue/corrupt + name = "corrupt tongue" + desc = "This one tells only lies." + +/obj/item/organ/internal/tongue/corrupt/Initialize(mapload) + . = ..() + AddElement(/datum/element/corrupted_organ) + AddElement(/datum/element/noticable_organ, "mouth is full of stars.", BODY_ZONE_PRECISE_MOUTH) + +/obj/item/organ/internal/tongue/corrupt/Insert(mob/living/carbon/organ_owner, special, drop_if_replaced) + . = ..() + RegisterSignal(organ_owner, COMSIG_MOB_SAY, PROC_REF(on_spoken)) + +/obj/item/organ/internal/tongue/corrupt/Remove(mob/living/carbon/organ_owner, special) + . = ..() + UnregisterSignal(organ_owner, COMSIG_MOB_SAY) + +/// When the mob speaks, sometimes put it in a different language +/obj/item/organ/internal/tongue/corrupt/proc/on_spoken(mob/living/organ_owner, list/speech_args) + SIGNAL_HANDLER + if (organ_owner.has_reagent(/datum/reagent/water/holywater) || prob(60)) + return + speech_args[SPEECH_LANGUAGE] = /datum/language/shadowtongue + + +/// Randomly secretes alcohol or hallucinogens when you're drinking something +/obj/item/organ/internal/liver/corrupt + name = "corrupt liver" + desc = "After what you've seen you could really go for a drink." + /// How much extra ingredients to add? + var/amount_added = 5 + /// What extra ingredients can we add? + var/list/extra_ingredients = list( + /datum/reagent/consumable/ethanol/pina_olivada, + /datum/reagent/consumable/ethanol/rum, + /datum/reagent/consumable/ethanol/thirteenloko, + /datum/reagent/consumable/ethanol/vodka, + /datum/reagent/consumable/superlaughter, + /datum/reagent/drug/bath_salts, + /datum/reagent/drug/blastoff, + /datum/reagent/drug/happiness, + /datum/reagent/drug/mushroomhallucinogen, + ) + +/obj/item/organ/internal/liver/corrupt/Initialize(mapload) + . = ..() + AddElement(/datum/element/corrupted_organ) + +/obj/item/organ/internal/liver/corrupt/Insert(mob/living/carbon/organ_owner, special, drop_if_replaced) + . = ..() + RegisterSignal(organ_owner, COMSIG_ATOM_EXPOSE_REAGENTS, PROC_REF(on_drank)) + +/obj/item/organ/internal/liver/corrupt/Remove(mob/living/carbon/organ_owner, special) + . = ..() + UnregisterSignal(organ_owner, COMSIG_ATOM_EXPOSE_REAGENTS) + +/// If we drank something, add a little extra +/obj/item/organ/internal/liver/corrupt/proc/on_drank(atom/source, list/reagents, datum/reagents/source_reagents, methods) + SIGNAL_HANDLER + if (!(methods & INGEST)) + return + var/datum/reagents/extra_reagents = new() + extra_reagents.add_reagent(pick(extra_ingredients), amount_added) + extra_reagents.trans_to(source, amount_added, methods = INJECT) + if (prob(20)) + to_chat(source, span_warning("As you take a sip, you feel something bubbling in your stomach...")) + + +/// Rapidly become hungry if you are not digesting blood +/obj/item/organ/internal/stomach/corrupt + name = "corrupt stomach" + desc = "This parasite demands an unwholesome diet in order to be satisfied." + /// Do we have an unholy thirst? + var/thirst_satiated = FALSE + /// Timer for when we get thirsty again + var/thirst_timer + /// How long until we prompt the player to drink blood again? + COOLDOWN_DECLARE(message_cooldown) + +/obj/item/organ/internal/stomach/corrupt/Initialize(mapload) + . = ..() + AddElement(/datum/element/corrupted_organ) + AddElement(/datum/element/noticable_organ, "appear%PRONOUN_S to have an unhealthy pallor.") + +/obj/item/organ/internal/stomach/corrupt/Insert(mob/living/carbon/organ_owner, special, drop_if_replaced) + . = ..() + RegisterSignal(organ_owner, COMSIG_ATOM_EXPOSE_REAGENTS, PROC_REF(on_drank)) + +/obj/item/organ/internal/stomach/corrupt/Remove(mob/living/carbon/organ_owner, special) + . = ..() + UnregisterSignal(organ_owner, COMSIG_ATOM_EXPOSE_REAGENTS) + +/// Check if we drank a little blood +/obj/item/organ/internal/stomach/corrupt/proc/on_drank(atom/source, list/reagents, datum/reagents/source_reagents, methods) + SIGNAL_HANDLER + if (!(methods & INGEST)) + return + + var/contains_blood = locate(/datum/reagent/blood) in reagents + if (!contains_blood) + return + + if (!thirst_satiated) + to_chat(source, span_cultitalic("The thirst is satisfied... for now.")) + thirst_satiated = TRUE + deltimer(thirst_timer) + thirst_timer = addtimer(VARSET_CALLBACK(src, thirst_satiated, FALSE), 3 MINUTES, TIMER_STOPPABLE | TIMER_DELETE_ME) + +/obj/item/organ/internal/stomach/corrupt/handle_hunger(mob/living/carbon/human/human, seconds_per_tick, times_fired) + if (thirst_satiated || human.has_reagent(/datum/reagent/water/holywater)) + return ..() + + human.adjust_nutrition(-1 * seconds_per_tick) + + if (!COOLDOWN_FINISHED(src, message_cooldown)) + return ..() + COOLDOWN_START(src, message_cooldown, 30 SECONDS) + + var/static/list/blood_messages = list( + "Blood...", + "Everyone suddenly looks so tasty.", + "The blood...", + "There's an emptiness in you that only blood can fill.", + "You could really go for some blood right now.", + "You feel the blood rushing through your veins.", + "You think about biting someone's throat.", + "Your stomach growls and you feel a metallic taste in your mouth.", + ) + to_chat(human, span_cultitalic(pick(blood_messages))) + + return ..() + + +/// Occasionally bombards you with spooky hands and lets everyone hear your pulse. +/obj/item/organ/internal/heart/corrupt + name = "corrupt heart" + desc = "What corruption is this spreading along with the blood?" + /// How long until the next heart? + COOLDOWN_DECLARE(hand_cooldown) + +/obj/item/organ/internal/heart/corrupt/Initialize(mapload) + . = ..() + AddElement(/datum/element/corrupted_organ) + +/obj/item/organ/internal/heart/corrupt/on_life(seconds_per_tick, times_fired) + . = ..() + var/obj/item/organ/internal/heart/heart = owner.get_organ_slot(ORGAN_SLOT_HEART) + if (!COOLDOWN_FINISHED(src, hand_cooldown) || IS_IN_MANSUS(owner) || !owner.needs_heart() || !heart.beating || owner.has_reagent(/datum/reagent/water/holywater)) + return + fire_curse_hand(owner) + COOLDOWN_START(src, hand_cooldown, rand(6 SECONDS, 45 SECONDS)) // Wide variance to put you off guard + + +/// Sometimes cough out some kind of dangerous gas +/obj/item/organ/internal/lungs/corrupt + name = "corrupt lungs" + desc = "Some things SHOULD be drowned in tar." + /// How likely are we not to cough every time we take a breath? + var/cough_chance = 15 + /// How much gas to emit? + var/gas_amount = 30 + /// What can we cough up? + var/list/gas_types = list( + /datum/gas/bz = 30, + /datum/gas/miasma = 50, + /datum/gas/plasma = 20, + ) + +/obj/item/organ/internal/lungs/corrupt/Initialize(mapload) + . = ..() + AddElement(/datum/element/corrupted_organ) + +/obj/item/organ/internal/lungs/corrupt/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/breather) + . = ..() + if (!. || IS_IN_MANSUS(owner) || breather.has_reagent(/datum/reagent/water/holywater) || !prob(cough_chance)) + return + breather.emote("cough"); + var/chosen_gas = pick_weight(gas_types) + var/datum/gas_mixture/mix_to_spawn = new() + mix_to_spawn.add_gas(pick(chosen_gas)) + mix_to_spawn.gases[chosen_gas][MOLES] = gas_amount + mix_to_spawn.temperature = breather.bodytemperature + log_atmos("[owner] coughed some gas into the air due to their corrupted lungs.", mix_to_spawn) + var/turf/open/our_turf = get_turf(breather) + our_turf.assume_air(mix_to_spawn) + + +/// It's full of worms +/obj/item/organ/internal/appendix/corrupt + name = "corrupt appendix" + desc = "What kind of dark, cosmic force is even going to bother to corrupt an appendix?" + /// How likely are we to spawn worms? + var/worm_chance = 2 + +/obj/item/organ/internal/appendix/corrupt/Initialize(mapload) + . = ..() + AddElement(/datum/element/corrupted_organ) + AddElement(/datum/element/noticable_organ, "abdomen is distended... and wiggling.", BODY_ZONE_PRECISE_GROIN) + +/obj/item/organ/internal/appendix/corrupt/on_life(seconds_per_tick, times_fired) + . = ..() + if (owner.stat != CONSCIOUS || owner.has_reagent(/datum/reagent/water/holywater) || IS_IN_MANSUS(owner) || !SPT_PROB(worm_chance, seconds_per_tick)) + return + owner.vomit(vomit_type = /obj/effect/decal/cleanable/vomit/nebula/worms, distance = 0) + owner.Knockdown(0.5 SECONDS) diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm index 6faf49021266..1c001c57fcd2 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm @@ -86,3 +86,17 @@ return bloodiest_wound.adjust_blood_flow(-0.5) + +/// Torment the target with a frightening hand +/proc/fire_curse_hand(mob/living/carbon/victim) + var/grab_dir = turn(victim.dir, pick(-90, 90, 180, 180)) // Not in front, favour behind + var/turf/spawn_turf = get_ranged_target_turf(victim, grab_dir, 8) + if (isnull(spawn_turf)) + return + new /obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, victim.dir) + playsound(spawn_turf, 'sound/effects/curse2.ogg', 80, TRUE, -1) + var/obj/projectile/curse_hand/hel/hand = new (spawn_turf) + hand.preparePixelProjectile(victim, spawn_turf) + if (QDELETED(hand)) // safety check if above fails - above has a stack trace if it does fail + return + hand.fire() diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_curse.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_curse.dm new file mode 100644 index 000000000000..6a12ecc30922 --- /dev/null +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_curse.dm @@ -0,0 +1,93 @@ +/// A curse given to people to disencourage them from retaliating against someone who sacrificed them +/datum/status_effect/heretic_curse + id = "heretic_curse" + alert_type = null + status_type = STATUS_EFFECT_MULTIPLE // In case several different people sacrifice you, unlucky + /// Who cursed us? + var/mob/living/the_curser + /// Don't experience bad things too often + COOLDOWN_DECLARE(consequence_cooldown) + +/datum/status_effect/heretic_curse/on_creation(mob/living/new_owner, mob/living/the_curser) + src.the_curser = the_curser + return ..() + +/datum/status_effect/heretic_curse/Destroy() + the_curser = null + return ..() + +/datum/status_effect/heretic_curse/on_apply() + if (isnull(the_curser) || !iscarbon(owner)) + return FALSE + + the_curser.AddElement(/datum/element/relay_attackers) + RegisterSignal(the_curser, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_curser_attacked)) + RegisterSignal(the_curser, COMSIG_QDELETING, PROC_REF(on_curser_destroyed)) + + owner.AddElement(/datum/element/relay_attackers) + RegisterSignal(owner, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_owner_attacked)) + + return TRUE + +/datum/status_effect/heretic_curse/on_remove() + UnregisterSignal(owner, COMSIG_ATOM_WAS_ATTACKED) + UnregisterSignal(the_curser, COMSIG_ATOM_WAS_ATTACKED) + the_curser = null + + +/// If the heretic that cursed us is destroyed this thing is useless now +/datum/status_effect/heretic_curse/proc/on_curser_destroyed() + SIGNAL_HANDLER + qdel(src) + +/// If we attack the guy who cursed us, that's no good +/datum/status_effect/heretic_curse/proc/on_curser_attacked(datum/source, mob/attacker) + SIGNAL_HANDLER + if (attacker != owner || !HAS_TRAIT(source, TRAIT_ALLOW_HERETIC_CASTING)) + return + log_combat(owner, the_curser, "attacked", addition = "and lost some organs because they had previously been sacrificed by them.") + experience_the_consequences() + +/// If we are attacked by the guy who cursed us, that's also no good +/datum/status_effect/heretic_curse/proc/on_owner_attacked(datum/source, mob/attacker) + SIGNAL_HANDLER + if (attacker != the_curser || !HAS_TRAIT(attacker, TRAIT_ALLOW_HERETIC_CASTING)) + return + log_combat(the_curser, owner, "attacked", addition = "and as they had previously sacrificed them, removed some of their organs.") + experience_the_consequences() + +/// Experience something you may not enjoy which may also significantly shorten your lifespan +/datum/status_effect/heretic_curse/proc/experience_the_consequences() + if (!COOLDOWN_FINISHED(src, consequence_cooldown) || owner.stat != CONSCIOUS) + return + + var/mob/living/carbon/carbon_owner = owner + var/obj/item/bodypart/chest/organ_storage = owner.get_bodypart(BODY_ZONE_CHEST) + if (isnull(organ_storage)) + carbon_owner.gib() // IDK how you don't have a chest but you're not getting away that easily + return + + var/list/removable_organs = list() + for(var/obj/item/organ/internal/bodypart_organ in organ_storage.contents) + if(bodypart_organ.organ_flags & ORGAN_UNREMOVABLE) + continue + removable_organs += bodypart_organ + + if (!length(removable_organs)) + return // This one is a little more possible but they're probably already in pretty bad shape by this point + + var/obj/item/organ/internal/removing_organ = pick(removable_organs) + + if (carbon_owner.vomit(blood = TRUE)) + carbon_owner.visible_message(span_boldwarning("[carbon_owner] vomits out [carbon_owner.p_their()] [removing_organ]")) + else + carbon_owner.visible_message(span_boldwarning("[carbon_owner]'s [removing_organ] rips itself out of `[carbon_owner.p_their()] chest!")) + + removing_organ.Remove(carbon_owner) + + var/turf/land_turf = get_step(carbon_owner, carbon_owner.dir) + if (land_turf.is_blocked_turf(exclude_mobs = TRUE)) + land_turf = carbon_owner.drop_location() + + removing_organ.forceMove(land_turf) + COOLDOWN_START(src, consequence_cooldown, 10 SECONDS) diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm index 1f38e6377b6e..25b2d4113204 100644 --- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm @@ -98,18 +98,7 @@ Basically, we fill the time between now and 2s from now with hands based off the /datum/reagent/inverse/helgrasp/proc/spawn_hands(mob/living/carbon/owner) if(!owner && iscarbon(holder.my_atom))//Catch timer owner = holder.my_atom - //Adapted from the end of the curse - but lasts a short time - var/grab_dir = turn(owner.dir, pick(-90, 90, 180, 180)) //grab them from a random direction other than the one faced, favoring grabbing from behind - var/turf/spawn_turf = get_ranged_target_turf(owner, grab_dir, 8)//Larger range so you have more time to dodge - if(!spawn_turf) - return - new/obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, owner.dir) - playsound(spawn_turf, 'sound/effects/curse2.ogg', 80, TRUE, -1) - var/obj/projectile/curse_hand/hel/hand = new (spawn_turf) - hand.preparePixelProjectile(owner, spawn_turf) - if(QDELETED(hand)) //safety check if above fails - above has a stack trace if it does fail - return - hand.fire() + fire_curse_hand(owner) //At the end, we clear up any loose hanging timers just in case and spawn any remaining lag_remaining hands all at once. /datum/reagent/inverse/helgrasp/on_mob_delete(mob/living/owner) diff --git a/monkestation/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm b/monkestation/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm index fc06138a24d0..c78a25f763c3 100644 --- a/monkestation/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm +++ b/monkestation/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm @@ -26,6 +26,16 @@ var/datum/mind/heretic_mind /// An assoc list of [ref] to [timers] - a list of all the timers of people in the shadow realm currently var/list/return_timers + /// Evil organs we can put in people + var/static/list/grantable_organs = list( + /obj/item/organ/internal/appendix/corrupt, + /obj/item/organ/internal/eyes/corrupt, + /obj/item/organ/internal/heart/corrupt, + /obj/item/organ/internal/liver/corrupt, + /obj/item/organ/internal/lungs/corrupt, + /obj/item/organ/internal/stomach/corrupt, + /obj/item/organ/internal/tongue/corrupt, + ) /datum/heretic_knowledge/hunt_and_sacrifice/Destroy(force) heretic_mind = null @@ -199,6 +209,8 @@ heretic_datum.total_sacrifices++ heretic_datum.knowledge_points += 2 + sacrifice.apply_status_effect(/datum/status_effect/heretic_curse, user) + if(!begin_sacrifice(sacrifice)) disembowel_target(sacrifice) @@ -291,6 +303,8 @@ disembowel_target(sac_target) return + curse_organs(sac_target) + // Send 'em to the destination. If the teleport fails, just disembowel them and stop the chain if(!destination || !do_teleport(sac_target, destination, asoundin = 'sound/magic/repulse.ogg', asoundout = 'sound/magic/blind.ogg', no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE)) disembowel_target(sac_target) @@ -313,6 +327,31 @@ RegisterSignal(sac_target, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_target_escape)) // Cheese condition RegisterSignal(sac_target, COMSIG_LIVING_DEATH, PROC_REF(on_target_death)) // Loss condition +/// Apply a sinister curse to some of the target's organs as an incentive to leave us alone +/datum/heretic_knowledge/hunt_and_sacrifice/proc/curse_organs(mob/living/carbon/human/sac_target) + var/usable_organs = grantable_organs.Copy() + if (isplasmaman(sac_target)) + usable_organs -= /obj/item/organ/internal/lungs/corrupt // Their lungs are already more cursed than anything I could give them + + var/total_implant = rand(2, 4) + var/gave_any = FALSE + + for (var/i in 1 to total_implant) + if (!length(usable_organs)) + break + var/organ_path = pick_n_take(usable_organs) + var/obj/item/organ/internal/to_give = new organ_path + if (!to_give.Insert(sac_target)) + qdel(to_give) + else + gave_any = TRUE + + if (!gave_any) + return + + new /obj/effect/gibspawner/human/bodypartless(get_turf(sac_target)) + sac_target.visible_message(span_boldwarning("Several organs force themselves out of [sac_target]!")) + /** * This proc is called from [proc/after_target_sleeps] when the [sac_target] should be waking up.) * @@ -389,8 +428,6 @@ if(IS_HERETIC(sac_target)) var/datum/antagonist/heretic/victim_heretic = sac_target.mind?.has_antag_datum(/datum/antagonist/heretic) victim_heretic.knowledge_points -= 3 - else - sac_target.gain_trauma(/datum/brain_trauma/mild/phobia/heresy, TRAUMA_RESILIENCE_LOBOTOMY) // monke edit: allow lobotomy to cure the phobia // Wherever we end up, we sure as hell won't be able to explain sac_target.adjust_timed_status_effect(40 SECONDS, /datum/status_effect/speech/slurring/heretic) sac_target.adjust_stutter(40 SECONDS) diff --git a/tgstation.dme b/tgstation.dme index 1b0fa98ac70c..9caae8d97ce7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1391,6 +1391,7 @@ #include "code\datums\elements\climbable.dm" #include "code\datums\elements\connect_loc.dm" #include "code\datums\elements\content_barfer.dm" +#include "code\datums\elements\corrupted_organ.dm" #include "code\datums\elements\crackable.dm" #include "code\datums\elements\crusher_loot.dm" #include "code\datums\elements\cult_eyes.dm" @@ -2941,6 +2942,7 @@ #include "code\modules\antagonists\heretic\moon_lunatic.dm" #include "code\modules\antagonists\heretic\rust_effect.dm" #include "code\modules\antagonists\heretic\transmutation_rune.dm" +#include "code\modules\antagonists\heretic\items\corrupted_organs.dm" #include "code\modules\antagonists\heretic\items\eldritch_flask.dm" #include "code\modules\antagonists\heretic\items\eldritch_painting.dm" #include "code\modules\antagonists\heretic\items\forbidden_book.dm" @@ -2971,6 +2973,7 @@ #include "code\modules\antagonists\heretic\knowledge\starting_lore.dm" #include "code\modules\antagonists\heretic\knowledge\void_lore.dm" #include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_buff.dm" +#include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_curse.dm" #include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_knowledge.dm" #include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_map.dm" #include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_moodlets.dm"