Files
CHOMPStation2/code/datums/components/traits/unlucky.dm
CHOMPStation2StaffMirrorBot b8fe8fa68d [MIRROR] Unlucky trait (#11775)
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
2025-10-04 03:52:24 -04:00

545 lines
22 KiB
Plaintext

/**
* Ripped from /tg/ with modifications.
* unlucky.dm: For when you want someone to have a really bad day
*
* When you attach an omen component to someone, they start running the risk of all sorts of bad environmental injuries, like nearby vending machines randomly falling on you (TBI),
* or hitting your head really hard when you slip and fall, or you get shocked by the tram rails at an unfortunate moment.
*
* Omens are removed once the victim is either maimed by one of the possible injuries, or if they receive a blessing (read: bashing with a bible) from the chaplain. (TBI)
*/
/datum/component/omen
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// How many incidents are left. If 0 exactly, it will get deleted.
var/incidents_left = INFINITY
/// Base probability of negative events. Cursed are half as unlucky.
var/luck_mod = 1
/// Base damage from negative events. Cursed take 25% of this damage.
var/damage_mod = 1
/// If we want to do more evil events, such as spontaneous combustion
var/evil = TRUE
/// If our codebase has safe disposals or not
var/safe_disposals = FALSE
/// If we have vore interactions or not
var/vorish = TRUE
/datum/component/omen/Initialize(incidents_left, luck_mod, damage_mod, evil, safe_disposals, vorish)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
if(!isnull(incidents_left))
src.incidents_left = incidents_left
if(!isnull(luck_mod))
src.luck_mod = luck_mod
if(!isnull(damage_mod))
src.damage_mod = damage_mod
if(!isnull(evil))
src.evil = evil
if(!isnull(safe_disposals))
src.safe_disposals = safe_disposals
if(!isnull(vorish))
src.vorish = vorish
ADD_TRAIT(parent, TRAIT_UNLUCKY, src)
/**
* This is a omen eat omen world! The stronger omen survives.
*/
/datum/component/omen/InheritComponent(
datum/component/omen/new_comp,
i_am_original,
incidents_left,
luck_mod,
damage_mod,
evil,
safe_disposals,
vorish,
)
// If we have more incidents left the new one gets deleted.
if(src.incidents_left > incidents_left)
return
src.incidents_left = incidents_left
// The new omen is weaker than our current omen? Let's split the difference.
if(src.luck_mod > luck_mod)
src.luck_mod += luck_mod * 0.5
if(src.damage_mod > damage_mod)
src.damage_mod += damage_mod * 0.5
// If the new omen has special modifiers, we take them on forever!
if(evil)
src.evil = TRUE
if(safe_disposals)
src.safe_disposals = TRUE
if(vorish)
src.vorish = TRUE
//This means weaker, longing lasting omens will take priority, but have some of the strength of the original.
/datum/component/omen/Destroy(force)
var/mob/living/person = parent
REMOVE_TRAIT(person, TRAIT_UNLUCKY, src)
to_chat(person, span_warning(span_green("You feel a horrible omen lifted off your shoulders!")))
return ..()
/datum/component/omen/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(check_accident))
RegisterSignal(parent, COMSIG_ON_CARBON_SLIP, PROC_REF(check_slip))
RegisterSignal(parent, COMSIG_MOVED_DOWN_STAIRS, PROC_REF(check_stairs))
RegisterSignal(parent, COMSIG_STUN_EFFECT_ACT, PROC_REF(check_taser))
RegisterSignal(parent, COMSIG_MOB_ROLLED_DICE, PROC_REF(check_roll))
RegisterSignal(parent, COMSIG_HUMAN_ON_CATCH_THROW, PROC_REF(check_throw))
RegisterSignal(parent, COMSIG_PICKED_UP_ITEM, PROC_REF(check_pickup))
/datum/component/omen/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ON_CARBON_SLIP, COMSIG_MOVABLE_MOVED, COMSIG_STUN_EFFECT_ACT, COMSIG_MOVED_DOWN_STAIRS, COMSIG_MOB_ROLLED_DICE, COMSIG_HUMAN_ON_CATCH_THROW, COMSIG_PICKED_UP_ITEM))
/datum/component/omen/proc/consume_omen()
incidents_left--
if(incidents_left < 1)
qdel(src)
/**
* check_accident() is called each step we take
*
* While we're walking around, roll to see if there's any environmental hazards on one of the adjacent tiles we can trigger.
* We do the prob() at the beginning to A. add some tension for /when/ it will strike, and B. (more importantly) ameliorate the fact that we're checking up to 5 turfs's contents each time
*/
/datum/component/omen/proc/check_accident(atom/movable/our_guy)
SIGNAL_HANDLER
if(!isliving(our_guy) || isbelly(our_guy.loc))
return
var/mob/living/living_guy = our_guy
if(living_guy.is_incorporeal()) //no being unlucky if you don't even exist on the same plane.
return
if(evil && prob(0.0001) && (living_guy.stat != DEAD)) // 1 in a million
living_guy.visible_message(span_danger("[living_guy] suddenly bursts into flames!"), span_danger("You suddenly burst into flames!"))
living_guy.emote("scream")
living_guy.adjust_fire_stacks(20)
living_guy.ignite_mob()
consume_omen()
return
var/effective_luck = luck_mod
// If there's nobody to witness the misfortune, make it less likely.
// This way, we allow for people to be able to get into hilarious situations without making the game nigh unplayable most of the time.
var/has_watchers = FALSE
for(var/mob/viewer in viewers(our_guy, world.view))
if(viewer.client && !viewer.client.is_afk())
has_watchers = TRUE
break
if(!has_watchers)
effective_luck *= 0.5
if(!prob(2 * effective_luck))
return
var/turf/our_guy_pos = get_turf(our_guy)
if(!our_guy_pos)
return
if(evil)
for(var/obj/machinery/door/airlock/darth_airlock in our_guy_pos)
if(darth_airlock.locked || !darth_airlock.arePowerSystemsOn())
continue
to_chat(living_guy, span_warning("The airlock suddenly closes on you!"))
living_guy.Paralyse(1 SECONDS)
slam_airlock(darth_airlock)
consume_omen()
return
for(var/turf/the_turf as anything in our_guy_pos.AdjacentTurfs(check_blockage = FALSE)) //need false so we can check disposal units
if(the_turf.CanZPass(our_guy, DOWN))
to_chat(living_guy, span_warning("You lose your balance and slip towards the edge!"))
living_guy.Weaken(5)
living_guy.throw_at(the_turf, 1, 20)
consume_omen()
return
if(vorish)
for(var/mob/living/living_mob in the_turf)
if(living_mob == our_guy)
continue //Don't do anything to ourselves.
if(living_mob.stat)
continue
if(!living_mob.CanStumbleVore(living_guy) && !living_guy.CanStumbleVore(living_mob)) //Works both ways! Either way, someone's getting eaten!
continue
living_mob.stumble_into(living_guy) //logic reversed here because the game is DUMB. This means that living_guy is stumbling into the target!
living_guy.visible_message(span_danger("[living_guy] loses their balance and slips into [living_mob]!"), span_boldwarning("You lose your balance, slipping into [living_mob]!"))
consume_omen()
return
for(var/obj/machinery/washing_machine/evil_washer in the_turf)
if(evil_washer.state == 1) //Empty and open door
our_guy.visible_message(span_danger("[our_guy] slips near the [evil_washer] and falls in, the door shutting!"), span_boldwarning("You slip on a wet spot near the [evil_washer] and fall in, the door shutting! You're stuck!"))
our_guy.forceMove(evil_washer)
evil_washer.washing += our_guy
evil_washer.state = 4
evil_washer.visible_message(span_danger("[evil_washer] begins its spin cycle!"))
evil_washer.start(TRUE, damage_mod)
consume_omen()
return
if(evil || safe_disposals) //On servers without safe disposals, this is a death sentence. With servers with safe disposals, it's just funny.
for(var/obj/machinery/disposal/evil_disposal in the_turf)
if(evil_disposal.stat & (BROKEN|NOPOWER))
continue
if(evil_disposal.loc == living_guy.loc) //Let's not do a continual loop of them falling into it as soon as they climb out, as funny as that is.
continue
our_guy.visible_message(span_danger("[our_guy] slips on a spill near the [evil_disposal] and falls in!"), span_boldwarning("You slip on a spill near the [evil_disposal] and fall in!"))
living_guy.forceMove(evil_disposal)
evil_disposal.flush = TRUE
evil_disposal.update()
living_guy.Weaken(5)
consume_omen()
return
if(evil && prob(33)) //This has an additional 2 in 3 chance to not happen as there's a LOT of lights on stations. This should be rarer.
for(var/obj/machinery/light/evil_light in the_turf)
if((evil_light.status == LIGHT_BURNED || evil_light.status == LIGHT_BROKEN) || (living_guy.get_shock_protection() == 1)) // we can't do anything :(
to_chat(living_guy, span_warning("[evil_light] sparks weakly for a second."))
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread //this shit is copy pasted all over the code...this needs to just be made into a proc at this point jesus christ
s.set_up(4, FALSE, evil_light)
s.start()
//We don't clear the omen as nothing really happened.
break
to_chat(living_guy, span_warning("[evil_light] glows ominously...")) // ominously
evil_light.visible_message(span_boldwarning("[evil_light] suddenly flares brightly and sparks!"))
//evil_light.broken(skip_sound_and_sparks = FALSE) //Let's not break it actually.
evil_light.Beam(living_guy, icon_state = "lightning[rand(1,12)]", time = 0.5 SECONDS)
living_guy.electrocute_act(35 * (damage_mod * 0.5), evil_light, stun = TRUE) //Stun is binary and scales on damage..Lame.
living_guy.emote("scream")
consume_omen()
return
for(var/obj/machinery/vending/darth_vendor in the_turf)
if(darth_vendor.stat & (BROKEN|NOPOWER))
continue
to_chat(living_guy, span_warning("The delivery chute of [darth_vendor] raises up..."))
darth_vendor.throw_item(living_guy)
consume_omen()
return
for(var/obj/structure/mirror/evil_mirror in the_turf)
to_chat(living_guy, span_warning("You pass by the mirror and glance at it..."))
if(evil_mirror.shattered)
to_chat(living_guy, span_notice("You feel lucky, somehow."))
return
var/mirror_rand
if(evil)
mirror_rand = rand(1,5)
else
mirror_rand = rand(1,3)
switch(mirror_rand)
if(1)
to_chat(living_guy, span_boldwarning("You see your reflection, but it is grinning malevolently and staring directly at you!"))
living_guy.emote("scream")
if(2 to 3)
to_chat(living_guy, span_large(span_cult("Oh god, you can't see your reflection!!")))
living_guy.emote("scream")
if(4 to 5)
to_chat(living_guy, span_warning("The mirror explodes into a million pieces! Wait, does that mean you're even more unlucky?"))
evil_mirror.shatter()
if(prob(50 * effective_luck)) // sometimes
luck_mod += 0.25
damage_mod += 0.25
var/max_health_coefficient = (living_guy.maxHealth * 0.06)
for(var/obj/item/organ/external/limb in living_guy.organs)
living_guy.apply_damage(max_health_coefficient * damage_mod, BRUTE, limb.organ_tag, used_weapon = "glass shrapnel")
living_guy.make_jittery(250)
if(evil && prob(7 * effective_luck))
to_chat(living_guy, span_warning("You are completely shocked by this turn of events!"))
if(ishuman(living_guy))
var/mob/living/carbon/human/human_guy = living_guy
if(human_guy.should_have_organ(O_HEART))
for(var/obj/item/organ/internal/heart/heart in human_guy.internal_organs)
heart.bruise() //Closest thing we have to a heart attack.
to_chat(living_guy, span_boldwarning("You clutch at your heart!"))
consume_omen()
return
if(evil)
for(var/obj/item/reagent_containers/glass/beaker/evil_beaker in the_turf)
if(!evil_beaker.is_open_container() && (evil_beaker.reagents.total_volume > 0)) //A closed beaker is a safe beaker!
continue
living_guy.visible_message(span_danger("[evil_beaker] tilts, spilling its contents on [living_guy]!"), span_bolddanger("[evil_beaker] spills all over you!"))
evil_beaker.balloon_alert_visible("[evil_beaker]'s contents splashes onto [living_guy]!")
evil_beaker.reagents.splash(living_guy, evil_beaker.reagents.total_volume)
consume_omen()
return
for(var/obj/structure/table/evil_table in the_turf)
if(!evil_table.material) //We only want tables, not just table frames.
continue
var/datum/gender/gender = GLOB.gender_datums[living_guy.get_visible_gender()]
living_guy.visible_message(span_danger("[living_guy] stubs [gender.his] toe on [evil_table]!"), span_bolddanger("You stub your toe on [evil_table]!"))
living_guy.apply_damage(2 * damage_mod, BRUTE, pick(BP_L_FOOT, BP_R_FOOT), used_weapon = "blunt force trauma")
living_guy.adjustHalLoss(25) //It REALLY hurts.
living_guy.Weaken(3)
consume_omen()
return
//Ran out of turf options. Let's do more generic options.
if(prob(luck_mod * 5))
// In complete darkness
if(our_guy_pos.get_lumcount() <= LIGHTING_SOFT_THRESHOLD)
living_guy.Blind(5) //10 seconds of 'OH GOD WHAT'S HAPPENING'
living_guy.silent = 5
living_guy.Paralyse(5)
to_chat(living_guy, span_bolddanger("You feel the ground buckle underneath you, falling down, your vision going dark as you feel paralyzed in place!"))
consume_omen()
return
/datum/component/omen/proc/slam_airlock(obj/machinery/door/airlock/darth_airlock)
SIGNAL_HANDLER
. = darth_airlock.close(forced = TRUE, ignore_safties = TRUE, crush_damage = 15) //Not enough to cause any IB or massively injured organs.
if(.)
consume_omen()
/// If we get knocked down, see if we have a really bad slip and bash our head hard
/datum/component/omen/proc/check_slip(mob/living/our_guy, amount)
SIGNAL_HANDLER
if(prob(30)) // AAAA
our_guy.emote("scream")
to_chat(our_guy, span_cult("What a horrible night... To have a curse!"))
if(prob(30 * luck_mod) && our_guy.get_bodypart_name(BP_HEAD)) /// Bonk!
playsound(our_guy, 'sound/effects/tableheadsmash.ogg', 90, TRUE)
var/datum/gender/gender = GLOB.gender_datums[our_guy.get_visible_gender()]
our_guy.visible_message(span_danger("[our_guy] hits [gender.his] head really badly falling down!"), span_bolddanger("You hit your head really badly falling down!"))
var/max_health_coefficient = (our_guy.maxHealth * 0.5)
our_guy.apply_damage(max_health_coefficient * damage_mod, BRUTE, BP_HEAD, used_weapon = "slipping")
if(ishuman(our_guy))
var/mob/living/carbon/human/human_guy = our_guy
if(human_guy.should_have_organ(O_BRAIN))
for(var/obj/item/organ/internal/brain/brain in human_guy.internal_organs)
brain.take_damage(30 * damage_mod) //60 damage kills.
if(human_guy.glasses && human_guy.canUnEquip(human_guy.glasses))
var/turf/T = get_turf(human_guy)
if(T)
var/obj/item/our_glasses = human_guy.glasses
human_guy.unEquip(human_guy.glasses, target = T)
to_chat(human_guy, span_warning("Your glasses fly off as you hit the ground!"))
our_glasses.throw_at_random(FALSE, 3, 2)
consume_omen()
return
/datum/component/omen/proc/check_roll(mob/living/unlucky_soul, var/obj/item/dice/the_dice, silent, result)
SIGNAL_HANDLER
if(prob(20 * luck_mod))
//unlucky_soul.visible_message(span_danger("[unlucky_soul] rolls [the_dice] with it landing on the edge of [result] before tilting over!"), span_boldwarning("You feel dreadfully unlucky as you roll the dice!"))
//I had thought about making this have a notice that it happened.
//However, gaslighting the user by providing no visible notice is MUCH funnier.
return 1 // We override the roll to a 1.
///Returns TRUE and stops us from catching
/datum/component/omen/proc/check_throw(mob/living/unlucky_soul, source, speed)
SIGNAL_HANDLER
if(prob(30 * luck_mod)) //~9% chance
if(istype(source, /obj/item/grenade))
var/obj/item/grenade/bad_grenade = source
if(bad_grenade.active)
unlucky_soul.put_in_active_hand(bad_grenade)
unlucky_soul.visible_message(span_warning("[src] catches [source] as it goes off in their hand!"), span_bolddanger("You catch [source] and it goes off in your hand!"))
unlucky_soul.throw_mode_off()
bad_grenade.detonate()
return TRUE
else
unlucky_soul.visible_message(span_attack("[unlucky_soul] tries to catch [source] and fumbles it, getting thrown back!"))
unlucky_soul.Weaken(5)
return TRUE
/*
* Dynamic injury system for when you pick up objects!
* Some objects might cut, burn, or otherwise injure you if you pick them up!
* Genenerally more of an annoyance than anything.
* Variables that can be changed:
* injury_type, damage_to_inflict, damage_type, injury_verb, is_sharp, is_edge.
*/
/datum/component/omen/proc/check_pickup(mob/living/unlucky_soul, obj/item/item)
SIGNAL_HANDLER
if(prob(3 * luck_mod) && ishuman(unlucky_soul)) // ~3% chance
var/mob/living/carbon/human/unlucky_human = unlucky_soul
///What the injury will show up as on an autopsy.
var/injury_type = "injury"
///How much damage we'll inflect.
var/damage_to_inflict = 0
///What type of damage we'll inflict
var/damage_type = BRUTE
///If the item we get injured on has is sharp.
var/is_sharp = FALSE
///If the item we get injured on has an edge.
var/has_edge = FALSE
///What verb we use to describe the injury.
var/injury_verb = "cuts"
///What hand we are currently using, so we injure the correct one.
var/current_hand = BP_R_HAND
if(unlucky_human.hand)
current_hand = BP_L_HAND
if(istype(item, /obj/item/paper))
injury_verb = "cuts"
injury_type = "paper cut"
damage_to_inflict = 2
else if(istype(item, /obj/item/material/knife))
var/obj/item/material/knife = item
injury_verb = "cuts"
injury_type = "knife"
is_sharp = knife.sharp
has_edge = knife.edge
damage_to_inflict = knife.force
else if(istype(item, /obj/item/material/shard))
var/obj/item/material/shard/shard = item
injury_verb = "cuts"
injury_type = "shard"
is_sharp = shard.sharp
has_edge = shard.edge
damage_to_inflict = shard.force
else if(istype(item, /obj/item/flame/lighter))
var/obj/item/flame/lighter/lighter = item
if(!lighter.lit)
return
injury_verb = "burns"
injury_type = "lighter"
damage_type = BURN
damage_to_inflict = 5
else if(istype(item, /obj/item/tool/transforming/jawsoflife))
var/obj/item/tool/transforming/jawsoflife/jaws = item
injury_verb = "clamps"
injury_type = "industrial tool"
is_sharp = jaws.sharp
has_edge = jaws.edge
damage_to_inflict = jaws.force
else if(istype(item, /obj/item/tool/screwdriver))
var/obj/item/tool/screwdriver/screwdriver = item
injury_verb = "stabs"
injury_type = "industrial tool"
is_sharp = screwdriver.sharp
has_edge = screwdriver.edge
damage_to_inflict = screwdriver.force
else if(istype(item, /obj/item/tool/wirecutters))
var/obj/item/tool/wirecutters/wirecutters = item
injury_verb = "nips"
injury_type = "industrial tool"
is_sharp = wirecutters.sharp
has_edge = wirecutters.edge
damage_to_inflict = wirecutters.force
if(!damage_to_inflict)
return
var/datum/gender/gender = GLOB.gender_datums[unlucky_human.get_visible_gender()]
unlucky_human.visible_message(span_danger("[unlucky_human] accidentally [injury_verb] [gender.his] hand on [item]!"))
unlucky_human.apply_damage(damage_to_inflict * damage_mod, damage_type, current_hand, sharp = is_sharp, edge = has_edge, used_weapon = injury_type)
/datum/component/omen/proc/check_stairs(mob/living/unlucky_soul)
SIGNAL_HANDLER
if(prob(3 * luck_mod)) /// Bonk!
playsound(unlucky_soul, 'sound/effects/tableheadsmash.ogg', 90, TRUE)
unlucky_soul.visible_message(span_danger("One of the stairs give way as [unlucky_soul] steps onto it, tumbling them down to the bottom!"), span_bolddanger("A stair gives way and you trip to the bottom!"))
var/max_health_coefficient = (unlucky_soul.maxHealth * 0.09)
for(var/obj/item/organ/external/limb in unlucky_soul.organs) //In total, you should have 11 limbs (generally, unless you have an amputation). The full omen variant we want to leave you at 1 hp, the trait version less. As of writing, the trait version is 25% of the damage, so you take 24.75 across all limbs.
unlucky_soul.apply_damage(max_health_coefficient * damage_mod, BRUTE, limb.organ_tag, used_weapon = "slipping")
unlucky_soul.Weaken(5)
consume_omen()
/datum/component/omen/proc/check_taser(mob/living/unlucky_soul, stun_amount, agony_amount, def_zone, used_weapon, electric)
SIGNAL_HANDLER
if(!electric || !evil) //If it's not electric we don't care! Likewise, if we don't have the evil variant, don't care!
return
if(!ishuman(unlucky_soul))
return
if(prob(3 * luck_mod))
var/mob/living/carbon/human/human_guy = unlucky_soul
if(human_guy.should_have_organ(O_HEART))
for(var/obj/item/organ/internal/heart/heart in human_guy.internal_organs)
if(heart.robotic)
continue //Robotic hearts are immune to this.
heart.take_damage(10 * stun_amount * damage_mod)
heart.take_damage(0.25 * agony_amount * damage_mod)
playsound(src, 'sound/effects/singlebeat.ogg', 50, FALSE)
to_chat(unlucky_soul, span_bolddanger("You feel as though your heart stopped"))
human_guy.Stun(5)
consume_omen()
return
/**
* The trait omen. Permanent.
* Has only a 30% chance of bad things happening, and takes only 25% of normal damage.
* Evil is false, so you get less dramatic things happening.
*/
/datum/component/omen/trait
incidents_left = INFINITY
dupe_type = /datum/component/omen/trait
luck_mod = 0.3 // 30% chance of bad things happening
damage_mod = 0.25 // 25% of normal damage
evil = FALSE
safe_disposals = FALSE
///Major variant of the trait.
/datum/component/omen/trait/major
evil = TRUE
damage_mod = 0.75 //75% of normal damage
///Variant trait for downstreams that have safe disposals.
/datum/component/omen/trait/safe_disposals
safe_disposals = TRUE
/datum/component/omen/trait/safe_disposals/major
evil = TRUE
damage_mod = 0.75 //75% of normal damage
/**
* The bible omen.
* While it lasts, parent gets a cursed aura filter.
*/
/datum/component/omen/bible
incidents_left = 1
/datum/component/omen/bible/RegisterWithParent()
. = ..()
var/mob/living/living_parent = parent
living_parent.add_filter("omen", 2, list("type" = "drop_shadow", "color" = "#A50824", "alpha" = 0, "size" = 2))
var/filter = living_parent.get_filter("omen")
animate(filter, alpha = 255, time = 2 SECONDS, loop = -1)
animate(alpha = 0, time = 2 SECONDS)
/datum/component/omen/bible/UnregisterFromParent()
. = ..()
var/mob/living/living_parent = parent
living_parent.remove_filter("omen")
/**
* The dice omen.
* Single use omen from rolling a nat 1 on a cursed d20.
*/
/datum/component/omen/dice
incidents_left = 1