Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TG Port] Replace Heretic Phobia with Cursed Organs #4475

Merged
merged 9 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions code/__DEFINES/antagonists.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
7 changes: 4 additions & 3 deletions code/datums/ai/_ai_controller.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
66 changes: 66 additions & 0 deletions code/datums/elements/corrupted_organ.dm
Original file line number Diff line number Diff line change
@@ -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)
17 changes: 9 additions & 8 deletions code/datums/elements/relay_attackers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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, ...)
Expand Down
8 changes: 8 additions & 0 deletions code/game/objects/effects/decals/cleanable/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
243 changes: 243 additions & 0 deletions code/modules/antagonists/heretic/items/corrupted_organs.dm
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading
Loading