Light Sensitivity Refactor (#22386)

The last time the Light Sensitivity code was changed, I remarked in a
review that "This should probably be a component so that its code isn't
being run on every mob forever". Well I've gotten around to doing that
myself, except I figured out it's even better off as an Element in this
situation rather than a Component. So this is now my first time adding
Elements to the repo. It turns out they're really awesome when paired
with signals.

This PR removes the hardcoded check for the light senstivity and dark
phobia traits from the Life() path, replacing them instead with two
Elements which hook into the pre-existing signal used to handle vision
updates for human mobs. I've mainly done this to help cut down on the
overwhelmingly high cost of the Life() codepath, which is currently one
of the most expensive paths we have.

While I was at it with refactoring these two, I noticed that there
wasn't a trait selection for either of them, so I added selections for
both traits to the disabilities tab so that players can opt-in to being
light sensitive or afraid of the dark!

<img width="318" height="336" alt="image"
src="https://github.com/user-attachments/assets/a1e60e83-d899-44df-8ea3-0cd5a87c231c"
/>
This commit is contained in:
VMSolidus
2026-05-18 19:21:19 +00:00
committed by GitHub
parent e8d1eedc11
commit 3b6d0f20ec
12 changed files with 175 additions and 78 deletions
+17 -8
View File
@@ -893,9 +893,14 @@
return
/// Returns a number between -1 to 2
/**
* Returns a numerical value between -INFINITY and +INFINITY representing a user's flash protection value.
* As a friendly reminder, do not use the == operator on this proc, use >= or <= instead.
*/
/mob/living/carbon/human/get_flash_protection(ignore_inherent = FALSE)
// Handle all the exits first before we do the standard method.
//Ling
var/datum/changeling/changeling = changeling_power(0, 0, 0)
if(changeling && changeling.using_thermals)
@@ -908,20 +913,24 @@
if (I && I.status & ORGAN_CUT_AWAY)
return FLASH_PROTECTION_MAJOR
if (!ignore_inherent && species.inherent_eye_protection)
. = max(species.inherent_eye_protection, flash_protection)
else
return flash_protection
// Standard method, sum of modifiers.
var/base_flash_protection = flash_protection
if(HAS_TRAIT(src, TRAIT_ORIGIN_LIGHT_SENSITIVE))
return max(. - 1, FLASH_PROTECTION_REDUCED)
// Fetch flash protection modifiers via ECS methods.
// Anything in this proc that doesn't hook into this signal should eventually be replaced with signal registry methods for simplicity.
SEND_SIGNAL(src, COMSIG_GET_FLASH_PROTECTION_MODIFIERS, &base_flash_protection)
if (!ignore_inherent)
base_flash_protection += species.inherent_eye_protection
return base_flash_protection
/mob/living/carbon/human/flash_act(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = FALSE, ignore_inherent = FALSE, type = /atom/movable/screen/fullscreen/flash, length = 2.5 SECONDS)
if(..())
var/obj/item/organ/E = get_eyes(no_synthetic = !affect_silicon)
if(istype(E))
return E.flash_act(intensity, override_blindness_check, affect_silicon, ignore_inherent, type, length)
else if(intensity == get_flash_protection(ignore_inherent))
else if(intensity >= get_flash_protection(ignore_inherent))
if(prob(20))
to_chat(src, SPAN_NOTICE("Something bright flashes in the corner of your vision!"))
@@ -1251,9 +1251,6 @@
return "EAST[coord_col]:[coord_col_offset],NORTH[coord_row]:[coord_row_offset]"
/mob/living/carbon/human/handle_random_events()
if(InStasis())
return
// Puke if toxloss is too high
if(!stat)
if (getToxLoss() >= 45 && !lastpuke)
@@ -1271,53 +1268,6 @@
if(T.get_lumcount() < 0.05) // give a little bit of tolerance for near-dark areas.
playsound(null, pick(GLOB.scarySounds), 50, TRUE)
// People who are afraid of the dark get anxious.
if(HAS_TRAIT(src, TRAIT_ORIGIN_DARK_AFRAID))
T = loc
if(prob(2) && T.get_lumcount() < 0.2)
var/list/afraid_of_the_dark_messages = list(
"You feel a bit afraid...",
"You feel somewhat nervous...",
"You could use a little light here...",
"It's dark enough that you feel a little anxious..."
)
to_chat(src, SPAN_WARNING(pick(afraid_of_the_dark_messages)))
// People sensitive to light get eye strain.
if(HAS_TRAIT(src, TRAIT_ORIGIN_LIGHT_SENSITIVE))
T = loc
// From testing, this generally leaves several minutes between each message.
if(prob(0.5) && T.get_lumcount() > 0.95)
var/mob/living/carbon/human/self = src
// If you have this trait, your default flash protection is -1; check for ANY protection.
var/flash_protection = self.get_flash_protection()
// I hate this. We removed flash protection from basic sunglasses for 'powergaming concerns.'
// Check if we're wearing the stupid fake loadout sunglasses.
// Yes this is stupid. Remove this when we rebalance flash protection to be a 0-100 threshold.
var/fakesunglasses = istype(self?.glasses, /obj/item/clothing/glasses/fakesunglasses)
if(!flash_protection && !fakesunglasses)
var/obj/item/organ/eyes = self.get_eyes()
if(istype(eyes))
self.eye_blurry = max(self.eye_blurry, 6)
var/list/eye_sensitivity_messages = list(
"Your eyes tire a bit from the brightness.",
"Your eyes sting a little; it's too bright.",
"The bright light leaves your vision strained."
)
to_chat(src, SPAN_WARNING(pick(eye_sensitivity_messages)))
if(prob(20))
// If your eyes are covered, people can see you squinting.
var/list/protection = list(self.head, self.glasses, self.wear_mask)
var/eyes_covered = FALSE
for(var/obj/item/I in protection)
if(I?.body_parts_covered & EYES)
eyes_covered = TRUE
if(!eyes_covered)
self.visible_message("[self] squints in discomfort.")
/mob/living/carbon/human/proc/handle_changeling()
if(mind)
var/datum/changeling/changeling = mind.antag_datums[MODE_CHANGELING]