From dbbd66582a461bf013028e932d903917d78ded74 Mon Sep 17 00:00:00 2001
From: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Date: Sun, 28 Apr 2024 17:47:52 -0500
Subject: [PATCH] Numbness stops pain effects (#477)
---
code/__DEFINES/living.dm | 6 +-
maplestation_modules/code/datums/pain/pain.dm | 214 ++++++++++--------
.../code/datums/pain/pain_bodyparts.dm | 26 +--
.../datums/pain/pain_causes/surgery_pain.dm | 16 +-
.../code/datums/pain/pain_helpers.dm | 14 +-
.../code/datums/pain/pain_modifiers.dm | 13 ++
.../pain/pain_status_effects/pain_limp.dm | 10 +-
.../code/datums/quirks/negative.dm | 13 +-
.../withdrawal/luciferium_addiction.dm | 12 +-
9 files changed, 196 insertions(+), 128 deletions(-)
diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm
index 7fd839bacfb5..ef46ae475aa0 100644
--- a/code/__DEFINES/living.dm
+++ b/code/__DEFINES/living.dm
@@ -58,8 +58,12 @@
/// If the mob enters shock, they will have +1 cure condition (helps cure it faster)
#define TRAIT_ABATES_SHOCK "shock_abated"
+/// Pain effects, such as stuttering or feedback messages ("Everything hurts") are disabled.
+#define TRAIT_NO_PAIN_EFFECTS "no_pain_effects"
+/// Shock buildup does not increase, only decrease. No effect if already in shock (unlike abates_shock)
+#define TRAIT_NO_SHOCK_BUILDUP "no_shock_buildup"
/// The trait that determines if someone has the robotic limb reattachment quirk.
-#define TRAIT_ROBOTIC_LIMBATTACHMENT "trait_robotic_limbattachment" // Sticking this here because Melbert told me to
+#define TRAIT_ROBOTIC_LIMBATTACHMENT "trait_robotic_limbattachment"
#define COLOR_BLOOD "#c90000"
diff --git a/maplestation_modules/code/datums/pain/pain.dm b/maplestation_modules/code/datums/pain/pain.dm
index 5b87aba1b188..30689dcdcafd 100644
--- a/maplestation_modules/code/datums/pain/pain.dm
+++ b/maplestation_modules/code/datums/pain/pain.dm
@@ -85,6 +85,7 @@
RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(add_damage_pain))
RegisterSignal(parent, COMSIG_MOB_STATCHANGE, PROC_REF(on_parent_statchance))
RegisterSignals(parent, list(COMSIG_LIVING_SET_BODY_POSITION, COMSIG_LIVING_SET_BUCKLED), PROC_REF(check_lying_pain_modifier))
+ RegisterSignals(parent, list(SIGNAL_ADDTRAIT(TRAIT_NO_PAIN_EFFECTS), SIGNAL_REMOVETRAIT(TRAIT_NO_PAIN_EFFECTS)), PROC_REF(refresh_pain_attributes))
if(ishuman(parent))
RegisterSignal(parent, COMSIG_HUMAN_BURNING, PROC_REF(on_burn_tick))
@@ -105,6 +106,8 @@
COMSIG_LIVING_SET_BUCKLED,
COMSIG_MOB_APPLY_DAMAGE,
COMSIG_MOB_STATCHANGE,
+ SIGNAL_ADDTRAIT(TRAIT_NO_PAIN_EFFECTS),
+ SIGNAL_REMOVETRAIT(TRAIT_NO_PAIN_EFFECTS),
))
/**
@@ -186,7 +189,7 @@
return FALSE
LAZYSET(pain_mods, key, amount)
- apply_pain_attributes()
+ refresh_pain_attributes()
return update_pain_modifier()
/**
@@ -314,7 +317,7 @@
*/
/datum/pain/proc/on_pain_gain(obj/item/bodypart/affected_part, amount, type)
affected_part.on_gain_pain_effects(amount)
- apply_pain_attributes()
+ refresh_pain_attributes()
SEND_SIGNAL(parent, COMSIG_CARBON_PAIN_GAINED, affected_part, amount, type)
COOLDOWN_START(src, time_since_last_pain_loss, 60 SECONDS)
@@ -333,7 +336,7 @@
*/
/datum/pain/proc/on_pain_loss(obj/item/bodypart/affected_part, amount, type)
affected_part.on_lose_pain_effects(amount)
- apply_pain_attributes()
+ refresh_pain_attributes()
SEND_SIGNAL(parent, COMSIG_CARBON_PAIN_LOST, affected_part, amount, type)
/**
@@ -528,19 +531,14 @@
/datum/pain/process(seconds_per_tick)
var/has_pain = FALSE
- var/determined = FALSE
+ var/just_cant_feel_anything = !parent.can_feel_pain()
var/no_recent_pain = COOLDOWN_FINISHED(src, time_since_last_pain_loss)
for(var/part in shuffle(body_zones))
var/obj/item/bodypart/checked_bodypart = body_zones[part]
if(checked_bodypart.pain <= 0)
continue
- checked_bodypart.processed_pain_effects(seconds_per_tick)
- if(!has_pain)
- has_pain = TRUE
- // Only doing this once, when we register if we have pain, to save processing.
- determined = !!parent.has_status_effect(/datum/status_effect/determined)
-
- if(determined || pain_modifier < 0.5 || !COOLDOWN_FINISHED(src, time_since_last_pain_message))
+ has_pain = TRUE
+ if(just_cant_feel_anything || !COOLDOWN_FINISHED(src, time_since_last_pain_message))
continue
// 1% chance per 8 pain being experienced to get a feedback message every second
if(!SPT_PROB(checked_bodypart.get_modified_pain() / 8, seconds_per_tick))
@@ -549,51 +547,54 @@
COOLDOWN_START(src, time_since_last_pain_message, 4 SECONDS)
if(!has_pain)
- // no-op if none of our bodyparts is in pain
+ // no-op if none of our bodyparts are in pain
return
- if(!determined)
- var/curr_pain = get_average_pain()
- switch(curr_pain)
- if(-INFINITY to 10)
- shock_buildup = max(shock_buildup - 3, -30) // staying out of pain for a while gives you a small resiliency to shock (~1 minute)
+ var/curr_pain = get_average_pain()
+ switch(curr_pain)
+ if(-INFINITY to 10)
+ shock_buildup = max(shock_buildup - 3, -30) // staying out of pain for a while gives you a small resiliency to shock (~1 minute)
- if(10 to 25)
- shock_buildup = max(shock_buildup - 1, -30)
+ if(10 to 25)
+ shock_buildup = max(shock_buildup - 1, -30)
- if(25 to 40)
- if(SPT_PROB(2, seconds_per_tick))
- to_chat(parent, span_danger(pick("Everything aches.", "Everything feels sore.")))
+ if(25 to 40)
+ if(SPT_PROB(2, seconds_per_tick))
+ do_pain_message(span_danger(pick("Everything aches.", "Everything feels sore.")))
- if(40 to 70)
+ if(40 to 70)
+ if(!HAS_TRAIT(parent, TRAIT_NO_SHOCK_BUILDUP))
shock_buildup += 1
- if(SPT_PROB(2, seconds_per_tick))
- to_chat(parent, span_bolddanger(pick("Everything hurts.", "Everything feels very sore.", "It hurts.")))
+ if(SPT_PROB(2, seconds_per_tick))
+ do_pain_message(span_bolddanger(pick("Everything hurts.", "Everything feels very sore.", "It hurts.")))
- if(70 to INFINITY)
+ if(70 to INFINITY)
+ if(!HAS_TRAIT(parent, TRAIT_NO_SHOCK_BUILDUP))
shock_buildup += 3
- if(SPT_PROB(2, seconds_per_tick))
- to_chat(parent, span_userdanger(pick("Stop the pain!", "Everything hurts!")))
-
- // If shock buildup exceeds our health + 30 ticks then well, we enter shock
- // This means at 100 health you can be in moderate pain for 130 ticks / 260 seconds / ~4 minutes before falling into shock
- if(shock_buildup >= (parent.health + 30) \
- && curr_pain >= 50 \
- && !is_undergoing_shock() \
- && !parent.undergoing_cardiac_arrest() \
- )
- parent.ForceContractDisease(new /datum/disease/shock(), FALSE, TRUE)
- to_chat(parent, span_userdanger("You feel your body start to shut down!"))
- if(parent.stat == CONSCIOUS && !parent.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB))
- parent.visible_message(span_danger("[parent] grabs at their chest and stares into the distance as they go into shock!"), ignored_mobs = parent)
- shock_buildup = -200 // requires another 200 ticks / 400 seconds / ~6 minutes of pain to go into shock again
- return
+ if(SPT_PROB(2, seconds_per_tick))
+ do_pain_message(span_userdanger(pick("Stop the pain!", "Everything hurts!")))
+
+ // If shock buildup exceeds our health + 30 ticks then well, we enter shock
+ // This means at 100 health you can be in moderate pain for 130 ticks / 260 seconds / ~4 minutes before falling into shock
+ if(shock_buildup >= (parent.health + 30) \
+ && curr_pain >= 50 \
+ && !HAS_TRAIT(parent, TRAIT_NO_SHOCK_BUILDUP) \
+ && !is_undergoing_shock() \
+ && !parent.undergoing_cardiac_arrest() \
+ )
+ parent.ForceContractDisease(new /datum/disease/shock(), FALSE, TRUE)
+ to_chat(parent, span_userdanger("You feel your body start to shut down!"))
+ if(parent.stat == CONSCIOUS && !parent.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB) && !HAS_TRAIT(parent, TRAIT_NO_PAIN_EFFECTS))
+ parent.visible_message(span_danger("[parent] grabs at their chest and stares into the distance as they go into shock!"), ignored_mobs = parent)
+ shock_buildup = -200 // requires another 200 ticks / 400 seconds / ~6 minutes of pain to go into shock again
+ return
- var/standard_effect_prob = (curr_pain * 0.05) - 0.75 // starts at 15, caps at 4.5
- var/rare_effect_prob = (curr_pain * 0.04) - 1.5 // starts at 40
- var/very_rare_effect_prob = (curr_pain * 0.03) - 2.25 // starts at 70
+ var/standard_effect_prob = (curr_pain * 0.05) - 0.75 // starts at 15, caps at 4.5
+ var/rare_effect_prob = (curr_pain * 0.04) - 1.5 // starts at 40
+ var/very_rare_effect_prob = (curr_pain * 0.03) - 2.25 // starts at 70
- if(standard_effect_prob > 0)
+ if(standard_effect_prob > 0)
+ if(!just_cant_feel_anything)
if(SPT_PROB(standard_effect_prob, seconds_per_tick))
parent.adjust_stutter_up_to(10 SECONDS * pain_modifier, 30 SECONDS)
if(SPT_PROB(standard_effect_prob, seconds_per_tick))
@@ -602,44 +603,45 @@
parent.adjust_dizzy_up_to(10 SECONDS * pain_modifier, 30 SECONDS)
if(curr_pain >= 70)
parent.adjust_confusion_up_to(8 SECONDS * pain_modifier, 24 SECONDS)
- if(SPT_PROB(standard_effect_prob * 1.2, seconds_per_tick) && parent.getStaminaLoss() <= 80)
- var/stam_taken = round((0.2 * curr_pain + 8) * pain_modifier) // 10 = 10, 100 = 28, good enough
- // First we apply damage, if that succeeds ->
- // Check how much damage, if above a threshold ->
- // Run a pain emote, if the pain emote succeeds as well ->
- if(parent.apply_damage(stam_taken, STAMINA) && stam_taken >= 15 && do_pain_emote(pick("wince", "gasp")))
- parent.visible_message(span_warning("[parent] doubles over in pain!"))
-
- if(rare_effect_prob > 0)
- if(SPT_PROB(rare_effect_prob * 2, seconds_per_tick))
- var/list/options = list("wince", "whimper")
- if(curr_pain >= 70)
- options.Add("cry", "scream")
- do_pain_emote(pick(options), 5 SECONDS)
-
- if(SPT_PROB(rare_effect_prob, seconds_per_tick) && parent.body_position != LYING_DOWN)
- parent.Knockdown(2 SECONDS * pain_modifier)
- parent.visible_message(span_warning("[parent] collapses from pain!"))
-
- if(SPT_PROB(rare_effect_prob, seconds_per_tick))
- var/obj/item/held_item = parent.get_active_held_item()
- var/obj/item/bodypart/active_hand = parent.get_active_hand()
- if(held_item && active_hand && parent.dropItemToGround(held_item))
- if(active_hand.bodytype & BODYTYPE_ROBOTIC)
- to_chat(parent, span_danger("Your hand malfunctions, causing you to drop [held_item]!"))
- parent.visible_message(span_warning("[parent]'s hand malfunctions, causing them to drop [held_item]!"), ignored_mobs = parent)
- do_sparks(number = 1, source = parent)
-
- else
- to_chat(parent, span_danger("Your fumble though the pain and drop [held_item]!"))
- parent.visible_message(span_warning("[parent] fumbles around and drops [held_item]!"), ignored_mobs = parent)
- do_pain_emote("gasp")
-
- if(very_rare_effect_prob > 0)
- if(SPT_PROB(very_rare_effect_prob, seconds_per_tick))
- parent.vomit(50)
- if(SPT_PROB(very_rare_effect_prob, seconds_per_tick))
- parent.adjust_confusion_up_to(8 SECONDS, 24 SECONDS)
+ if(SPT_PROB(standard_effect_prob * 1.2, seconds_per_tick) && parent.getStaminaLoss() <= 80)
+ var/stam_taken = round((0.2 * curr_pain + 8) * pain_modifier) // 10 = 10, 100 = 28, good enough
+ if(just_cant_feel_anything)
+ parent.apply_damage(stam_taken * 1.2, STAMINA)
+ // First we apply damage, if that succeeds ->
+ // Check how much damage, if above a threshold ->
+ // Run a pain emote, if the pain emote succeeds as well ->
+ else if(parent.apply_damage(stam_taken, STAMINA) && stam_taken >= 15 && do_pain_emote(pick("wince", "gasp")))
+ parent.visible_message(span_warning("[parent] doubles over in pain!"))
+
+ if(rare_effect_prob > 0)
+ if(SPT_PROB(rare_effect_prob * 2, seconds_per_tick))
+ var/list/options = list("wince", "whimper")
+ if(curr_pain >= 70)
+ options.Add("cry", "scream")
+ do_pain_emote(pick(options), 5 SECONDS)
+ if(SPT_PROB(rare_effect_prob, seconds_per_tick) && parent.body_position != LYING_DOWN && !just_cant_feel_anything)
+ parent.Knockdown(2 SECONDS * pain_modifier)
+ parent.visible_message(span_warning("[parent] collapses from pain!"))
+ if(SPT_PROB(rare_effect_prob, seconds_per_tick))
+ var/obj/item/held_item = parent.get_active_held_item()
+ var/obj/item/bodypart/active_hand = parent.get_active_hand()
+ if(held_item && active_hand && parent.dropItemToGround(held_item))
+ if(active_hand.bodytype & BODYTYPE_ROBOTIC)
+ to_chat(parent, span_danger("Your hand malfunctions, causing you to drop [held_item]!"))
+ parent.visible_message(span_warning("[parent]'s hand malfunctions, causing them to drop [held_item]!"), ignored_mobs = parent)
+ do_sparks(number = 1, source = parent)
+ else if(just_cant_feel_anything)
+ to_chat(parent, span_danger("Your hand spams and you drop [held_item]!"))
+ else
+ to_chat(parent, span_danger("Your fumble though the pain and drop [held_item]!"))
+ parent.visible_message(span_warning("[parent] fumbles around and drops [held_item]!"), ignored_mobs = parent)
+ do_pain_emote("gasp")
+
+ if(very_rare_effect_prob > 0)
+ if(SPT_PROB(very_rare_effect_prob, seconds_per_tick))
+ parent.vomit(50)
+ if(SPT_PROB(very_rare_effect_prob, seconds_per_tick) && !just_cant_feel_anything)
+ parent.adjust_confusion_up_to(8 SECONDS, 24 SECONDS)
// Finally, handle pain decay over time
if(HAS_TRAIT(parent, TRAIT_STASIS) || parent.on_fire || parent.stat == DEAD)
@@ -698,8 +700,10 @@
/**
* Apply or remove pain various modifiers from pain (mood, action speed, movement speed) based on the [average_pain].
*/
-/datum/pain/proc/apply_pain_attributes()
- if(pain_modifier <= 0.5)
+/datum/pain/proc/refresh_pain_attributes(...)
+ SIGNAL_HANDLER
+
+ if(!parent.can_feel_pain())
clear_pain_attributes()
return
@@ -744,13 +748,12 @@
*
* returns TRUE if successful.
*/
-/datum/pain/proc/do_pain_emote(emote, cooldown = 3 SECONDS)
- if(!emote)
- emote = pick(PAIN_EMOTES)
-
- if(!COOLDOWN_FINISHED(src, time_since_last_pain_message))
+/datum/pain/proc/do_pain_emote(emote = pick(PAIN_EMOTES), cooldown = 3 SECONDS)
+ ASSERT(istext(emote))
+ if(!parent.can_feel_pain())
+ return FALSE
+ if(cooldown && !COOLDOWN_FINISHED(src, time_since_last_pain_message))
return FALSE
-
if(parent.stat >= UNCONSCIOUS || parent.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB))
return FALSE
@@ -758,12 +761,35 @@
COOLDOWN_START(src, time_since_last_pain_message, cooldown)
return TRUE
+/**
+ * Run a pain related message, if a few checks are successful.
+ *
+ * message - string, what message we're sending
+ * painless_message - optional string, what message we're sending if the mob doesn't "feel" pain
+ * cooldown - what cooldown to set our message cooldown to
+ *
+ * returns TRUE if successful.
+ * Returns FALSE if we failed to send a message, even if painless_message was provided and sent.
+ */
+/datum/pain/proc/do_pain_message(message, painless_message, cooldown = 0 SECONDS)
+ ASSERT(istext(message))
+ if(!parent.can_feel_pain())
+ if(painless_message)
+ to_chat(parent, painless_message)
+ return FALSE
+ if(parent.stat >= UNCONSCIOUS)
+ return FALSE
+ if(cooldown && !COOLDOWN_FINISHED(src, time_since_last_pain_message))
+ return FALSE
+
+ to_chat(parent, message)
+ COOLDOWN_START(src, time_since_last_pain_message, cooldown)
+ return TRUE
+
/**
* Get the average pain of all bodyparts as a percent of the total pain.
*/
/datum/pain/proc/get_average_pain()
- . = 0 // Runtime protection (wink)
-
var/max_total_pain = 0
var/total_pain = 0
for(var/zone in body_zones)
diff --git a/maplestation_modules/code/datums/pain/pain_bodyparts.dm b/maplestation_modules/code/datums/pain/pain_bodyparts.dm
index 1f06305dc110..51f8c85735c9 100644
--- a/maplestation_modules/code/datums/pain/pain_bodyparts.dm
+++ b/maplestation_modules/code/datums/pain/pain_bodyparts.dm
@@ -53,7 +53,10 @@
return FALSE
if(get_modified_pain() >= 65 && can_be_disabled && !HAS_TRAIT_FROM(src, TRAIT_PARALYSIS, PAIN_LIMB_PARALYSIS))
- to_chat(owner, span_userdanger("Your [plaintext_zone] goes numb from the pain!"))
+ owner.pain_message(
+ span_userdanger("Your [plaintext_zone] goes numb from the pain!"),
+ span_danger("You can't move your [plaintext_zone]!")
+ )
ADD_TRAIT(src, TRAIT_PARALYSIS, PAIN_LIMB_PARALYSIS)
update_disabled()
@@ -69,18 +72,15 @@
return FALSE
if(get_modified_pain() < 65 && HAS_TRAIT_FROM(src, TRAIT_PARALYSIS, PAIN_LIMB_PARALYSIS))
- to_chat(owner, span_green("You can feel your [plaintext_zone] again!"))
+ owner.pain_message(
+ span_green("You can feel your [plaintext_zone] again!"),
+ span_green("You can move your [plaintext_zone] again!")
+ )
REMOVE_TRAIT(src, TRAIT_PARALYSIS, PAIN_LIMB_PARALYSIS)
update_disabled()
return TRUE
-/**
- * Effects on this bodypart when pain is processed (every 2 seconds)
- */
-/obj/item/bodypart/proc/processed_pain_effects(seconds_per_tick)
- return
-
/**
* Feedback messages from this limb when it is sustaining pain.
*
@@ -119,7 +119,7 @@
feedback_phrases += list("is numb from the pain")
if(feedback_phrases.len)
- to_chat(owner, span_danger("Your [plaintext_zone] [pick(feedback_phrases)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
+ owner.pain_message(span_danger("Your [plaintext_zone] [pick(feedback_phrases)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
return TRUE
// --- Chest ---
@@ -171,9 +171,9 @@
side_feedback += list("You feel your ribs jostle in your [plaintext_zone]")
if(side_feedback.len && last_received_pain_type == BRUTE && SPT_PROB(50, seconds_per_tick))
- to_chat(owner, span_danger("[pick(side_feedback)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
+ owner.pain_message(span_danger("[pick(side_feedback)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
else if(feedback_phrases.len)
- to_chat(owner, span_danger("Your [plaintext_zone] [pick(feedback_phrases)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
+ owner.pain_message(span_danger("Your [plaintext_zone] [pick(feedback_phrases)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
return TRUE
@@ -230,9 +230,9 @@
side_feedback += list("You feel a splitting migrane", "Pressure floods your [plaintext_zone]", "Your [plaintext_zone] feels as if it's being squeezed", "Your eyes hurt to keep open")
if(side_feedback.len && last_received_pain_type == BRUTE && SPT_PROB(50, seconds_per_tick))
- to_chat(owner, span_danger("[pick(side_feedback)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
+ owner.pain_message(span_danger("[pick(side_feedback)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
else if(feedback_phrases.len)
- to_chat(owner, span_danger("Your [plaintext_zone] [pick(feedback_phrases)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
+ owner.pain_message(span_danger("Your [plaintext_zone] [pick(feedback_phrases)][healing_pain ? ", [pick(healing_phrases)]." : "!"]"))
return TRUE
diff --git a/maplestation_modules/code/datums/pain/pain_causes/surgery_pain.dm b/maplestation_modules/code/datums/pain/pain_causes/surgery_pain.dm
index 97443cdb71f8..db84c3b3ca92 100644
--- a/maplestation_modules/code/datums/pain/pain_causes/surgery_pain.dm
+++ b/maplestation_modules/code/datums/pain/pain_causes/surgery_pain.dm
@@ -18,26 +18,24 @@
// No pain from mechanics but still show the message (usually)
if(mechanical_surgery)
if(prob(70))
- to_chat(target, span_userdanger(pain_message))
+ target.pain_message(span_userdanger(pain_message))
return FALSE
target.cause_pain(target_zone, pain_amount, pain_type)
- if(target.IsSleeping() || target.IsUnconscious() || target.stat >= UNCONSCIOUS)
+ if(target.IsSleeping() || target.stat >= UNCONSCIOUS)
if(target.has_status_effect(/datum/status_effect/grouped/anesthetic))
target.add_mood_event("surgery", /datum/mood_event/anesthetic)
return FALSE
-
- if(ispath(surgery_moodlet, /datum/mood_event))
+ if(ispath(surgery_moodlet))
target.add_mood_event("surgery", surgery_moodlet)
- if(pain_overlay_severity == 1 || pain_overlay_severity == 2)
+ if(isnum(pain_overlay_severity))
target.flash_pain_overlay(pain_overlay_severity)
-
// No message if the pain emote fails
- if(!target.pain_controller.do_pain_emote())
+ if(!target.pain_emote())
+ return FALSE
+ if(!target.pain_message(span_userdanger(pain_message)))
return FALSE
-
- to_chat(target, span_userdanger(pain_message))
return TRUE
/datum/surgery_step/brainwash/sleeper_agent
diff --git a/maplestation_modules/code/datums/pain/pain_helpers.dm b/maplestation_modules/code/datums/pain/pain_helpers.dm
index 7c8d3b0cc857..4444c2f5b763 100644
--- a/maplestation_modules/code/datums/pain/pain_helpers.dm
+++ b/maplestation_modules/code/datums/pain/pain_helpers.dm
@@ -40,6 +40,16 @@
/mob/living/proc/pain_emote(emote, cooldown)
return pain_controller?.do_pain_emote(emote, cooldown)
+/**
+ * Runs a pain message on the pain message cooldown
+ *
+ * * message - the message to send
+ * * painless_message - optional, the message to send if the mob does not feel pain
+ * * cooldown - applies cooldown on doing similar pain messages
+ */
+/mob/living/proc/pain_message(message, painless_message, cooldown)
+ return pain_controller?.do_pain_message(message, painless_message, cooldown)
+
/**
* Adjust the minimum pain the target zone can experience for a time
*
@@ -76,7 +86,7 @@
* By default mobs cannot feel pain if they have a pain modifier of 0.5 or less.
*/
/mob/living/proc/can_feel_pain()
- return pain_controller?.pain_modifier > 0.5
+ return pain_controller?.pain_modifier > 0.5 && !HAS_TRAIT(src, TRAIT_NO_PAIN_EFFECTS)
/**
* Adjusts the progress of pain shock on the current mob.
@@ -87,6 +97,8 @@
/mob/living/proc/adjust_pain_shock(amount, down_to = -30)
if(isnull(pain_controller))
return
+ if(amount > 0 && HAS_TRAIT(src, TRAIT_NO_SHOCK_BUILDUP))
+ return
ASSERT(isnum(amount))
pain_controller.shock_buildup = max(pain_controller.shock_buildup + amount, down_to)
diff --git a/maplestation_modules/code/datums/pain/pain_modifiers.dm b/maplestation_modules/code/datums/pain/pain_modifiers.dm
index e8df518f0a92..3d30c8f8348b 100644
--- a/maplestation_modules/code/datums/pain/pain_modifiers.dm
+++ b/maplestation_modules/code/datums/pain/pain_modifiers.dm
@@ -69,13 +69,26 @@
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
human_owner.set_pain_mod(id, 0.625)
+ ADD_TRAIT(owner, TRAIT_NO_PAIN_EFFECTS, TRAIT_STATUS_EFFECT(id))
+ ADD_TRAIT(owner, TRAIT_NO_SHOCK_BUILDUP, TRAIT_STATUS_EFFECT(id))
/datum/status_effect/determined/on_remove()
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
human_owner.unset_pain_mod(id)
+ REMOVE_TRAIT(owner, TRAIT_NO_PAIN_EFFECTS, TRAIT_STATUS_EFFECT(id))
+ REMOVE_TRAIT(owner, TRAIT_NO_SHOCK_BUILDUP, TRAIT_STATUS_EFFECT(id))
return ..()
+// Fake healthy is supposed to mimic feeling no pain
+/datum/status_effect/grouped/screwy_hud/fake_healthy/on_apply()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_NO_PAIN_EFFECTS, TRAIT_STATUS_EFFECT(id))
+
+/datum/status_effect/grouped/screwy_hud/fake_healthy/on_remove()
+ . = ..()
+ REMOVE_TRAIT(owner, TRAIT_NO_PAIN_EFFECTS, TRAIT_STATUS_EFFECT(id))
+
// Being drunk gives a slight one, note the actual reagent gives one based on its strength
/datum/status_effect/inebriated/drunk/on_apply()
. = ..()
diff --git a/maplestation_modules/code/datums/pain/pain_status_effects/pain_limp.dm b/maplestation_modules/code/datums/pain/pain_status_effects/pain_limp.dm
index 42d7023fa1ad..dafe97accfeb 100644
--- a/maplestation_modules/code/datums/pain/pain_status_effects/pain_limp.dm
+++ b/maplestation_modules/code/datums/pain/pain_status_effects/pain_limp.dm
@@ -20,7 +20,10 @@
return FALSE
RegisterSignals(owner, list(COMSIG_CARBON_PAIN_GAINED, COMSIG_CARBON_PAIN_LOST), PROC_REF(update_limp))
- to_chat(owner, span_danger("Your [next_leg?.plaintext_zone || "leg"] hurts to walk on!"))
+ owner.pain_message(
+ span_danger("Your [next_leg?.plaintext_zone || "leg"] hurts to walk on!"),
+ span_danger("You struggle to walk on your [next_leg?.plaintext_zone || "leg"]!"),
+ )
/datum/status_effect/limp/pain/get_examine_text()
return span_warning("[owner.p_Theyre()] limping with every move.")
@@ -29,7 +32,10 @@
. = ..()
UnregisterSignal(owner, list(COMSIG_CARBON_PAIN_GAINED, COMSIG_CARBON_PAIN_LOST))
if(!QDELING(owner))
- to_chat(owner, span_green("Your pained limp stops!"))
+ owner.pain_message(
+ span_green("Your pained limp stops!"),
+ span_green("It becomes easier to walk again."),
+ )
/datum/status_effect/limp/pain/update_limp()
var/mob/living/carbon/human/limping_human = owner
diff --git a/maplestation_modules/code/datums/quirks/negative.dm b/maplestation_modules/code/datums/quirks/negative.dm
index 48b482be91f2..5f72fddef690 100644
--- a/maplestation_modules/code/datums/quirks/negative.dm
+++ b/maplestation_modules/code/datums/quirks/negative.dm
@@ -8,7 +8,10 @@
value = -2
/datum/quirk/numb
- desc = "You have difficulties feeling which part of your body is hurt."
+ value = -2 // This is a small buff but a large nerf so it's balanced at a relatively low cost
+ desc = "You don't feel pain as much as others. \
+ It's harder to pinpoint which parts of your body are injured, and \
+ you are immune to some effects of pain - possibly to your detriment."
// Modular quirks
// More vulnerabile to pain (increased pain modifier)
@@ -17,8 +20,8 @@
desc = "You're less resistant to pain - Your pain naturally decreases slower and you receive more overall."
icon = FA_ICON_USER_INJURED
value = -6
- gain_text = "You feel sharper."
- lose_text = "You feel duller."
+ gain_text = span_danger("You feel sharper.")
+ lose_text = span_notice("You feel duller.")
medical_record_text = "Patient has Hyperalgesia, and is more susceptible to pain stimuli than most."
mail_goodies = list(/obj/item/temperature_pack/cold)
@@ -38,8 +41,8 @@
desc = "Your nerves are extremely sensitive - you may receive pain from things that wouldn't normally be painful, such as hugs."
icon = FA_ICON_TIRED
value = -10
- gain_text = "You feel fragile."
- lose_text = "You feel less delicate."
+ gain_text = span_danger("You feel fragile.")
+ lose_text = span_notice("You feel less delicate.")
medical_record_text = "Patient has Allodynia, and is extremely sensitive to touch, pain, and similar stimuli."
mail_goodies = list(/obj/item/temperature_pack/cold, /obj/item/temperature_pack/heat)
COOLDOWN_DECLARE(time_since_last_touch)
diff --git a/maplestation_modules/code/modules/reagents/chemistry/withdrawal/luciferium_addiction.dm b/maplestation_modules/code/modules/reagents/chemistry/withdrawal/luciferium_addiction.dm
index 3372ab2bbce1..d709723195fe 100644
--- a/maplestation_modules/code/modules/reagents/chemistry/withdrawal/luciferium_addiction.dm
+++ b/maplestation_modules/code/modules/reagents/chemistry/withdrawal/luciferium_addiction.dm
@@ -12,12 +12,18 @@
withdrawal_stage_messages = list(
"I feel weak... I need some Luciferium.",
"I'd punch someone if I don't get some Luciferium!",
- "It hurts all over! I'd kill for Luciferium!"
- )
+ "It hurts all over! I'd kill for Luciferium!",
+ )
light_withdrawal_moodlet = /datum/mood_event/luciferium_light
medium_withdrawal_moodlet = /datum/mood_event/luciferium_medium
severe_withdrawal_moodlet = /datum/mood_event/luciferium_heavy
+/datum/addiction/luciferium/process_addiction(mob/living/carbon/affected_carbon, seconds_per_tick, times_fired)
+ if(HAS_TRAIT(affected_carbon, TRAIT_STASIS))
+ return
+
+ return ..()
+
/datum/addiction/luciferium/withdrawal_enters_stage_1(mob/living/carbon/affected_carbon)
. = ..()
affected_carbon.set_pain_mod(PAIN_MOD_LUCIFERIUM_ADDICT, 1.2)
@@ -69,4 +75,4 @@
if(current_addiction_cycle >= WITHDRAWAL_STAGE1_START_CYCLE)
to_chat(affected_carbon, span_green("Your [name] withdrawal subsides... You have bought yourself time."))
affected_carbon.unset_pain_mod(PAIN_MOD_LUCIFERIUM_ADDICT)
- . = ..()
+ return ..()