Adds the Cursed quirk (#72317)

## About The Pull Request
Adds a silly negative quirk inspired by fallout's bloody mess.

Bad luck interactions for
- Microwaving
- Cigarette coupons
- Russian roulette
- Vending machines
- Ledges
- Slipping

All of which have a chance to kill you, which, by the way, causes you to
**delimb and explode**.

This changes the admin smite as well since it's all the omen component.
Giving permanent omens will mean the player will gib on death, which is
super probable given the insane base damage from bonking your head.
Permanent omen smites are basically dooming someone to die of natural
causes.

<details>
<summary>GIFs</summary>


![dreamseeker_ZE6hyRdYet](https://user-images.githubusercontent.com/42397676/209779120-f7d76862-91e2-4366-a49d-e93366d96faf.gif)

updated: Death no longer fully gibs (carbons)

![dreamseeker_8S8r6B6gMM](https://user-images.githubusercontent.com/42397676/209874302-2e24f581-ffda-42e7-9794-dbe0fff2ff5b.gif)

Panic at seeing bad omen coupons

![dreamseeker_tykHbePTSS](https://user-images.githubusercontent.com/42397676/209887936-5d7f5edf-6fa2-41c7-8503-37432b49c7c0.gif)


![3](https://user-images.githubusercontent.com/42397676/209885388-90523f2c-531a-4928-96b2-c902552cbbbc.png)
</details>

## Why It's Good For The Game
Adds a bit of physical comedy and difficulty for players that want it.
## Changelog
🆑
add: Hope you saved for a rainy day: Added the 'Cursed' quirk which
causes excessive slippage and... other difficulties.
/🆑

Co-authored-by: Fikou <23585223+Fikou@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: Timberpoes <silent_insomnia_pp@hotmail.co.uk>
Co-authored-by: Watermelon914 <37270891+Watermelon914@users.noreply.github.com>
This commit is contained in:
Jeremiah
2023-01-03 13:18:24 -08:00
committed by GitHub
parent 30fb46fd99
commit 97db4ecca4
11 changed files with 230 additions and 92 deletions
+2
View File
@@ -147,6 +147,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_BLIND "blind"
/// Mute. Can't talk.
#define TRAIT_MUTE "mute"
/// Gibs on death and slips like ice.
#define TRAIT_CURSED "cursed"
/// Emotemute. Can't... emote.
#define TRAIT_EMOTEMUTE "emotemute"
#define TRAIT_DEAF "deaf"
+108 -35
View File
@@ -8,46 +8,44 @@
*/
/datum/component/omen
dupe_mode = COMPONENT_DUPE_UNIQUE
/// Whatever's causing the omen, if there is one. Destroying the vessel won't stop the omen, but we destroy the vessel (if one exists) upon the omen ending
var/obj/vessel
/// Whether this is a permanent omen that cannot be removed by any non-admin means.
/// If the omen is permanent, it will never go away
var/permanent = FALSE
/// Base probability of negative events. Cursed are half as unlucky.
var/luck_mod = 1
/// Base damage from negative events. Cursed take 25% less damage.
var/damage_mod = 1
/datum/component/omen/Initialize(silent = FALSE, _vessel, _permanent = FALSE)
/datum/component/omen/Initialize(vessel)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
vessel = _vessel
permanent = _permanent
if(!silent)
var/warning = "You get a bad feeling..."
if(permanent)
warning += " A very bad feeling... As if you are surrounded by a twisted aura of pure malevolence..."
to_chat(parent, span_warning("[warning]"))
src.vessel = vessel
/datum/component/omen/Destroy(force)
var/mob/living/person = parent
to_chat(person, span_nicegreen("You feel a horrible omen lifted off your shoulders!"))
/datum/component/omen/Destroy(force, silent)
if(!silent)
var/mob/living/person = parent
to_chat(person, span_nicegreen("You feel a horrible omen lifted off your shoulders!"))
if(vessel)
vessel.visible_message(span_warning("[vessel] burns up in a sinister flash, taking an evil energy with it..."))
vessel = null
return ..()
/datum/component/omen/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(check_accident))
RegisterSignal(parent, COMSIG_LIVING_STATUS_KNOCKDOWN, PROC_REF(check_slip))
RegisterSignal(parent, COMSIG_ON_CARBON_SLIP, PROC_REF(check_slip))
RegisterSignal(parent, COMSIG_CARBON_MOOD_UPDATE, PROC_REF(check_bless))
RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(check_death))
/datum/component/omen/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_MOVABLE_MOVED, COMSIG_CARBON_MOOD_UPDATE))
UnregisterSignal(parent, list(COMSIG_ON_CARBON_SLIP, COMSIG_MOVABLE_MOVED, COMSIG_CARBON_MOOD_UPDATE, COMSIG_LIVING_DEATH))
/**
* check_accident() is called each step we take
*
* While we're walking around, roll to see if there's any environmental hazards (currently only vending machines) on one of the adjacent tiles we can trigger.
* 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)
@@ -55,11 +53,11 @@
if(!isliving(our_guy))
return
var/mob/living/living_guy = our_guy
if(!prob(15))
if(!prob(15 * luck_mod))
return
var/our_guy_pos = get_turf(living_guy)
for(var/turf_content in our_guy_pos)
if(istype(turf_content, /obj/machinery/door/airlock))
@@ -67,7 +65,7 @@
var/obj/machinery/door/airlock/darth_airlock = turf_content
living_guy.apply_status_effect(/datum/status_effect/incapacitating/paralyzed, 10)
INVOKE_ASYNC(darth_airlock, TYPE_PROC_REF(/obj/machinery/door/airlock, close), TRUE)
if(!permanent)
if(!permanent && !prob(66.6))
qdel(src)
return
@@ -91,28 +89,103 @@
/datum/component/omen/proc/check_slip(mob/living/our_guy, amount)
SIGNAL_HANDLER
if(amount <= 0 || prob(50)) // 50% chance to bonk our head
return
if(prob(30)) // AAAA
INVOKE_ASYNC(our_guy, TYPE_PROC_REF(/mob, emote), "scream")
to_chat(our_guy, span_warning("What a horrible night... To have a curse!"))
var/obj/item/bodypart/the_head = our_guy.get_bodypart(BODY_ZONE_HEAD)
if(!the_head)
return
if(prob(30 * luck_mod)) /// Bonk!
var/obj/item/bodypart/the_head = our_guy.get_bodypart(BODY_ZONE_HEAD)
if(!the_head)
return
playsound(get_turf(our_guy), 'sound/effects/tableheadsmash.ogg', 90, TRUE)
our_guy.visible_message(span_danger("[our_guy] hits [our_guy.p_their()] head really badly falling down!"), span_userdanger("You hit your head really badly falling down!"))
the_head.receive_damage(75 * damage_mod)
our_guy.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100 * damage_mod)
if(!permanent)
qdel(src)
playsound(get_turf(our_guy), 'sound/effects/tableheadsmash.ogg', 90, TRUE)
our_guy.visible_message(span_danger("[our_guy] hits [our_guy.p_their()] head really badly falling down!"), span_userdanger("You hit your head really badly falling down!"))
the_head.receive_damage(75)
our_guy.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100)
if(!permanent)
qdel(src)
return
/// Hijack the mood system to see if we get the blessing mood event to cancel the omen
/datum/component/omen/proc/check_bless(mob/living/our_guy, category)
SIGNAL_HANDLER
if(permanent)
return
if (!("blessing" in our_guy.mob_mood.mood_events))
return
qdel(src)
/// Severe deaths. Normally lifts the curse.
/datum/component/omen/proc/check_death(mob/living/our_guy)
SIGNAL_HANDLER
qdel(src)
return
/// Creates a localized explosion that shakes the camera
/datum/component/omen/proc/death_explode(mob/living/our_guy)
explosion(our_guy, explosion_cause = src)
for(var/mob/witness as anything in view(2, our_guy))
shake_camera(witness, 1 SECONDS, 2)
return
/**
* The smite omen. Permanent.
*/
/datum/component/omen/smite
/datum/component/omen/smite/Initialize(vessel, permanent)
. = ..()
src.permanent = permanent
/datum/component/omen/smite/check_bless(mob/living/our_guy, category)
if(!permanent)
return ..()
return
/datum/component/omen/smite/check_death(mob/living/our_guy)
if(!permanent)
return ..()
death_explode(our_guy)
our_guy.gib()
return
/**
* The quirk omen. Permanent.
* Has only a 50% chance of bad things happening, and takes only 25% of normal damage.
*/
/datum/component/omen/quirk
permanent = TRUE
luck_mod = 0.5 // 50% chance of bad things happening
damage_mod = 0.25 // 25% of normal damage
/datum/component/omen/quirk/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(check_accident))
RegisterSignal(parent, COMSIG_ON_CARBON_SLIP, PROC_REF(check_slip))
RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(check_death))
/datum/component/omen/quirk/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ON_CARBON_SLIP, COMSIG_MOVABLE_MOVED, COMSIG_LIVING_DEATH))
/datum/component/omen/quirk/check_death(mob/living/our_guy)
if(!iscarbon(our_guy))
our_guy.gib()
return
// Don't explode if buckled to a stasis bed
if(our_guy.buckled)
var/obj/machinery/stasis/stasis_bed = our_guy.buckled
if(istype(stasis_bed))
return
death_explode(our_guy)
var/mob/living/carbon/player = our_guy
player.spread_bodyparts(skip_head = TRUE)
player.spawn_gibs()
return
+13
View File
@@ -1131,3 +1131,16 @@
if(!IS_ORGANIC_LIMB(old_limb))
cybernetics_level--
update_mood()
/datum/quirk/cursed
name = "Cursed"
desc = "You are cursed with bad luck. You are much more likely to suffer from accidents and mishaps. When it rains, it pours."
icon = "cloud-showers-heavy"
value = -8
mob_trait = TRAIT_CURSED
gain_text = span_danger("You feel like you're going to have a bad day.")
lose_text = span_notice("You feel like you're going to have a good day.")
medical_record_text = "Patient is cursed with bad luck."
hardcore_value = 8
/datum/quirk/cursed/add(client/client_source)
quirk_holder.AddComponent(/datum/component/omen/quirk)
+2 -2
View File
@@ -65,7 +65,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror, 28)
. = ..()
if(broken) // breaking a mirror truly gets you bad luck!
to_chat(user, span_warning("A chill runs down your spine as [src] shatters..."))
user.AddComponent(/datum/component/omen, silent = TRUE) // we have our own message
user.AddComponent(/datum/component/omen)
/obj/structure/mirror/bullet_act(obj/projectile/P)
if(broken || !isliving(P.firer) || !P.damage)
@@ -75,7 +75,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror, 28)
if(broken) // breaking a mirror truly gets you bad luck!
var/mob/living/unlucky_dude = P.firer
to_chat(unlucky_dude, span_warning("A chill runs down your spine as [src] shatters..."))
unlucky_dude.AddComponent(/datum/component/omen, silent=TRUE) // we have our own message
unlucky_dude.AddComponent(/datum/component/omen)
/obj/structure/mirror/atom_break(damage_flag, mapload)
. = ..()
+47 -38
View File
@@ -213,49 +213,58 @@
movable_content.wash(CLEAN_WASH)
return TRUE
/turf/open/handle_slip(mob/living/carbon/slipper, knockdown_amount, obj/O, lube, paralyze_amount, force_drop)
/turf/open/handle_slip(mob/living/carbon/slipper, knockdown_amount, obj/slippable, lube, paralyze_amount, force_drop)
if(slipper.movement_type & (FLYING | FLOATING))
return FALSE
if(has_gravity(src))
var/obj/buckled_obj
if(slipper.buckled)
buckled_obj = slipper.buckled
if(!(lube&GALOSHES_DONT_HELP)) //can't slip while buckled unless it's lube.
return FALSE
else
if(!(lube & SLIP_WHEN_CRAWLING) && (slipper.body_position == LYING_DOWN || !(slipper.status_flags & CANKNOCKDOWN))) // can't slip unbuckled mob if they're lying or can't fall.
return FALSE
if(slipper.m_intent == MOVE_INTENT_WALK && (lube&NO_SLIP_WHEN_WALKING))
return FALSE
if(!(lube&SLIDE_ICE))
to_chat(slipper, span_notice("You slipped[ O ? " on the [O.name]" : ""]!"))
playsound(slipper.loc, 'sound/misc/slip.ogg', 50, TRUE, -3)
if(!has_gravity(src))
return FALSE
SEND_SIGNAL(slipper, COMSIG_ON_CARBON_SLIP)
slipper.add_mood_event("slipped", /datum/mood_event/slipped)
if(force_drop)
for(var/obj/item/I in slipper.held_items)
slipper.accident(I)
var/slide_distance = 4
if(HAS_TRAIT(slipper, TRAIT_CURSED))
if(!(lube & SLIDE_ICE)) // Ensures we are at least sliding
lube |= SLIDE
slide_distance = rand(5, 9)
var/olddir = slipper.dir
slipper.moving_diagonally = 0 //If this was part of diagonal move slipping will stop it.
if(!(lube & SLIDE_ICE))
slipper.Knockdown(knockdown_amount)
slipper.Paralyze(paralyze_amount)
slipper.stop_pulling()
else
slipper.Knockdown(20)
var/obj/buckled_obj
if(slipper.buckled)
buckled_obj = slipper.buckled
if(!(lube & GALOSHES_DONT_HELP)) //can't slip while buckled unless it's lube.
return FALSE
else
if(!(lube & SLIP_WHEN_CRAWLING) && (slipper.body_position == LYING_DOWN || !(slipper.status_flags & CANKNOCKDOWN))) // can't slip unbuckled mob if they're lying or can't fall.
return FALSE
if(slipper.m_intent == MOVE_INTENT_WALK && (lube & NO_SLIP_WHEN_WALKING))
return FALSE
if(buckled_obj)
buckled_obj.unbuckle_mob(slipper)
lube |= SLIDE_ICE
if(!(lube & SLIDE_ICE))
to_chat(slipper, span_notice("You slipped[ slippable ? " on the [slippable.name]" : ""]!"))
playsound(slipper.loc, 'sound/misc/slip.ogg', 50, TRUE, -3)
var/turf/target = get_ranged_target_turf(slipper, olddir, 4)
if(lube & SLIDE)
slipper.AddComponent(/datum/component/force_move, target, TRUE)
else if(lube&SLIDE_ICE)
slipper.AddComponent(/datum/component/force_move, target, FALSE)//spinning would be bad for ice, fucks up the next dir
return TRUE
SEND_SIGNAL(slipper, COMSIG_ON_CARBON_SLIP)
slipper.add_mood_event("slipped", /datum/mood_event/slipped)
if(force_drop)
for(var/obj/item/item in slipper.held_items)
slipper.accident(item)
var/olddir = slipper.dir
slipper.moving_diagonally = 0 //If this was part of diagonal move slipping will stop it.
if(!(lube & SLIDE_ICE))
slipper.Knockdown(knockdown_amount)
slipper.Paralyze(paralyze_amount)
slipper.stop_pulling()
else
slipper.Knockdown(20)
if(buckled_obj)
buckled_obj.unbuckle_mob(slipper)
lube |= SLIDE_ICE
var/turf/target = get_ranged_target_turf(slipper, olddir, slide_distance)
if(lube & SLIDE)
slipper.AddComponent(/datum/component/force_move, target, TRUE)
else if(lube & SLIDE_ICE)
slipper.AddComponent(/datum/component/force_move, target, FALSE)//spinning would be bad for ice, fucks up the next dir
return TRUE
/turf/open/proc/MakeSlippery(wet_setting = TURF_WET_WATER, min_wet_time = 0, wet_time_to_add = 0, max_wet_time = MAXIMUM_WET_TIME, permanent)
AddComponent(/datum/component/wet_floor, wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent)
@@ -306,7 +315,7 @@
if(!used_tiles.use(1))
balloon_alert(user, "need a floor tile to build!")
return
playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE)
var/turf/open/floor/plating/new_plating = PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
if(lattice)
+7 -1
View File
@@ -14,4 +14,10 @@
/datum/smite/bad_luck/effect(client/user, mob/living/target)
. = ..()
target.AddComponent(/datum/component/omen, silent, null, permanent)
target.AddComponent(/datum/component/omen/smite, permanent = permanent)
if(silent)
return
to_chat(target, span_warning("You get a bad feeling..."))
if(permanent)
to_chat(target, span_warning("A <b>very</b> bad feeling... As if malevolent forces are watching you..."))
+31 -8
View File
@@ -15,20 +15,43 @@
/obj/item/coupon/proc/generate(rig_omen=FALSE)
discounted_pack = pick(subtypesof(/datum/supply_pack/goody))
var/list/chances = list("0.10" = 4, "0.15" = 8, "0.20" = 10, "0.25" = 8, "0.50" = 4, COUPON_OMEN = 1)
if(rig_omen)
discount_pct_off = COUPON_OMEN
else
discount_pct_off = pick_weight(chances)
if(discount_pct_off == COUPON_OMEN)
name = "coupon - fuck you"
desc = "The small text reads, 'You will be slaughtered'... That doesn't sound right, does it?"
if(ismob(loc))
var/mob/M = loc
to_chat(M, span_warning("The coupon reads '<b>fuck you</b>' in large, bold text... is- is that a prize, or?"))
M.AddComponent(/datum/component/omen, TRUE, src)
else
if(discount_pct_off != COUPON_OMEN)
discount_pct_off = text2num(discount_pct_off)
name = "coupon - [round(discount_pct_off * 100)]% off [initial(discounted_pack.name)]"
return
name = "coupon - fuck you"
desc = "The small text reads, 'You will be slaughtered'... That doesn't sound right, does it?"
if(!ismob(loc))
return FALSE
var/mob/cursed = loc
to_chat(cursed, span_warning("The coupon reads '<b>fuck you</b>' in large, bold text... is- is that a prize, or?"))
if(!cursed.GetComponent(/datum/component/omen))
cursed.AddComponent(/datum/component/omen)
return TRUE
if(HAS_TRAIT(cursed, TRAIT_CURSED))
to_chat(cursed, span_warning("What a horrible night... To have a curse!"))
addtimer(CALLBACK(src, PROC_REF(curse_heart), cursed), 5 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
/// Play stupid games, win stupid prizes
/obj/item/coupon/proc/curse_heart(mob/living/cursed)
if(!iscarbon(cursed))
cursed.gib()
return TRUE
var/mob/living/carbon/player = cursed
INVOKE_ASYNC(player, TYPE_PROC_REF(/mob, emote), "scream")
to_chat(player, span_mind_control("What could that coupon mean?"))
to_chat(player, span_userdanger("...The suspense is killing you!"))
player.set_heartattack(status = TRUE)
/obj/item/coupon/attack_atom(obj/O, mob/living/user, params)
if(!istype(O, /obj/machinery/computer/cargo))
@@ -382,6 +382,9 @@
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
return
if(HAS_TRAIT(cooker, TRAIT_CURSED) && prob(7))
muck()
return
if(prob(max((5 / efficiency) - 5, dirty * 5))) //a clean unupgraded microwave has no risk of failure
muck()
return
@@ -467,12 +470,16 @@
metal_amount += (cooked_item.custom_materials?[GET_MATERIAL_REF(/datum/material/iron)] || 0)
if(HAS_TRAIT(cooker, TRAIT_CURSED) && prob(5))
spark()
broken = REALLY_BROKEN
explosion(src, light_impact_range = 2, flame_range = 1)
if(metal_amount)
spark()
broken = REALLY_BROKEN
if(prob(max(metal_amount / 2, 33)))
if(HAS_TRAIT(cooker, TRAIT_CURSED) || prob(max(metal_amount / 2, 33))) // If we're unlucky and have metal, we're guaranteed to explode
explosion(src, heavy_impact_range = 1, light_impact_range = 2)
else
dump_inventory_contents()
+7 -5
View File
@@ -66,8 +66,10 @@
organs.forceMove(Tsec)
organs.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
/mob/living/carbon/spread_bodyparts()
for(var/X in bodyparts)
var/obj/item/bodypart/BP = X
BP.drop_limb()
BP.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
/// Launches all bodyparts away from the mob. skip_head will keep the head attached.
/mob/living/carbon/spread_bodyparts(skip_head = FALSE)
for(var/obj/item/bodypart/part as anything in bodyparts)
if(skip_head && part.body_zone == BODY_ZONE_HEAD)
continue
part.drop_limb()
part.throw_at(get_edge_target_turf(src, pick(GLOB.alldirs)), rand(1,3), 5)
@@ -229,6 +229,9 @@
result = (chambered ? "lost" : "won"))
if(chambered)
if(HAS_TRAIT(user, TRAIT_CURSED)) // I cannot live, I cannot die, trapped in myself, body my holding cell.
to_chat(user, span_warning("What a horrible night... To have a curse!"))
return
var/obj/item/ammo_casing/AC = chambered
if(AC.fire_casing(user, user, params, distro = 0, quiet = 0, zone_override = null, spread = 0, fired_from = src))
playsound(user, fire_sound, fire_sound_volume, vary_fire_sound)
@@ -208,7 +208,7 @@
switch(pick(possible_punishments))
if(PUNISHMENT_OMEN)
to_chat(interfering, span_warning("You get a bad feeling... for interfering with [chaplain]'s sparring match..."))
interfering.AddComponent(/datum/component/omen, TRUE, null, FALSE)
interfering.AddComponent(/datum/component/omen)
if(PUNISHMENT_LIGHTNING)
to_chat(interfering, span_warning("[GLOB.deity] has punished you for interfering with [chaplain]'s sparring match!"))
lightningbolt(interfering)