From 5a410180b5d923b7c231c95986444e52002babb8 Mon Sep 17 00:00:00 2001
From: Geeves <22774890+Geevies@users.noreply.github.com>
Date: Thu, 13 Aug 2026 16:57:18 +0000
Subject: [PATCH] Closing Eyes (#22957)
* You can now close your eyes. This blinds you, but gives you protection
against flash effects.
* Welding while blind takes three times as long and has an 80% chance to
fail, with a risk of burning yourself.
AI usage disclosure: The code for this was created in-part using GPT 5.6
Sol.
---
code/game/objects/items/tools/tools.dm | 26 ++++++++++++-
.../mob/living/carbon/human/examine.dm | 4 ++
code/modules/mob/living/carbon/human/human.dm | 3 ++
.../mob/living/carbon/human/human_helpers.dm | 8 ++++
.../living/carbon/human/species/species.dm | 2 +-
.../mob/living/carbon/human/update_icons.dm | 2 +-
code/modules/organs/internal/eyes.dm | 20 ++++++++++
code/modules/organs/organ_icon.dm | 16 ++++++--
code/unit_tests/mob_tests.dm | 39 +++++++++++++++++++
html/changelogs/geeves-closing_eyes.yml | 7 ++++
10 files changed, 119 insertions(+), 8 deletions(-)
create mode 100644 html/changelogs/geeves-closing_eyes.yml
diff --git a/code/game/objects/items/tools/tools.dm b/code/game/objects/items/tools/tools.dm
index be52fdb1f4b..e2135203296 100644
--- a/code/game/objects/items/tools/tools.dm
+++ b/code/game/objects/items/tools/tools.dm
@@ -320,12 +320,35 @@
update_icon()
/obj/item/weldingtool/use_tool(atom/target, mob/living/user, delay, amount, volume, datum/callback/extra_checks)
+ var/welding_blind = user.is_blind()
+ if(welding_blind)
+ delay *= 3
+
var/image/welding_sparks = image('icons/effects/effects.dmi', welding_state)
welding_sparks.plane = ABOVE_LIGHTING_PLANE
target.AddOverlays(welding_sparks)
. = ..()
target.CutOverlays(welding_sparks)
+ if(. && welding_blind && prob(80))
+ to_chat(user, SPAN_WARNING("Unable to see your work, you botch the weld!"))
+ if(prob(50))
+ var/burn_zone
+ if(ishuman(user))
+ switch(get_equip_slot())
+ if(slot_l_hand)
+ burn_zone = BP_L_HAND
+ if(slot_r_hand)
+ burn_zone = BP_R_HAND
+ else
+ burn_zone = pick(BP_L_HAND, BP_R_HAND)
+ user.apply_damage(rand(5, 10), DAMAGE_BURN, burn_zone, src)
+ user.visible_message(
+ SPAN_DANGER("[user] jerks back after burning themselves with \the [src]!"),
+ SPAN_DANGER("Your blind welding slips, burning your hand!")
+ )
+ return FALSE
+
/obj/item/weldingtool/proc/update_torch()
if(welding)
AddOverlays("[initial(icon_state)]-on")
@@ -439,7 +462,7 @@
user.visible_message(SPAN_NOTICE("\The [user] finishes repairing the physical damage on \the [target]'s [affecting.name]."))
return
- if(do_mob(user, target, 30))
+ if(use_tool(target, user, 30, volume = 15))
if(use(0))
var/static/list/repair_messages = list(
"patches some dents",
@@ -951,4 +974,3 @@
var/mutable_appearance/handle = mutable_appearance('icons/obj/tools.dmi', "hammer_handle")
handle.color = pick(COLOR_BLUE, COLOR_RED, COLOR_PURPLE, COLOR_BROWN, COLOR_GREEN, COLOR_CYAN, COLOR_YELLOW)
AddOverlays(handle)
-
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 2d1bd0ff9fa..76603598123 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -209,6 +209,10 @@
if(wrists && !skipwrists)
msg += "[get_pronoun("He")] [get_pronoun("is")] wearing [icon2html(wrists, user)] \a [wrists] [wrists.get_wrist_examine_text(src)].\n"
+ //closed eyes
+ if(!skipeyes && eyes_are_closed())
+ msg += "[get_pronoun("His")] eyes are closed.\n"
+
//Jitters
if(is_jittery)
if(jitteriness >= 300)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 67cf6bc9707..f2904f8314a 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -925,6 +925,9 @@
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(eyes_are_closed())
+ return FALSE
+
if(..())
var/obj/item/organ/E = get_eyes(no_synthetic = !affect_silicon)
if(istype(E))
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 255e7cb532c..0196c3539fd 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -205,6 +205,14 @@
return eyes
+/// Returns TRUE when this mob's vision organ has been voluntarily closed.
+/mob/living/carbon/human/proc/eyes_are_closed()
+ var/obj/item/organ/internal/eyes/eyes = get_eyes()
+ return eyes?.eyes_closed
+
+/mob/living/carbon/human/is_blind()
+ return ..() || eyes_are_closed()
+
/mob/living/carbon/human/proc/awaken_psi_basic(var/source)
var/static/list/psi_operancy_messages = list(
"There's something in your skull!",
diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm
index 48ce3be721c..73cc4b1acc4 100644
--- a/code/modules/mob/living/carbon/human/species/species.dm
+++ b/code/modules/mob/living/carbon/human/species/species.dm
@@ -771,7 +771,7 @@
if(!H.client)//no client, no screen to update
return 1
- H.set_fullscreen(H.eye_blind, "blind", /atom/movable/screen/fullscreen/blind)
+ H.set_fullscreen(H.eye_blind || H.eyes_are_closed(), "blind", /atom/movable/screen/fullscreen/blind)
H.set_fullscreen(H.stat == UNCONSCIOUS, "blackout", /atom/movable/screen/fullscreen/blackout)
if(GLOB.config.welder_vision)
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index d4b4ee2e3b2..3105229cb51 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -315,7 +315,7 @@ There are several things that need to be remembered:
var/icon_key = "[species.race_key][g][s_tone][r_skin][g_skin][b_skin][lipstick_color || "nolips"][!!husk][!!fat][!!skeleton][is_frenzied]"
var/obj/item/organ/internal/eyes/eyes = get_eyes()
if(eyes)
- icon_key += "[rgb(eyes.eye_colour[1], eyes.eye_colour[2], eyes.eye_colour[3])]"
+ icon_key += "[rgb(eyes.eye_colour[1], eyes.eye_colour[2], eyes.eye_colour[3])][eyes.eyes_closed ? "closed" : "open"]"
else
icon_key += "#000000"
diff --git a/code/modules/organs/internal/eyes.dm b/code/modules/organs/internal/eyes.dm
index dbfdbd77b15..242605da4ac 100644
--- a/code/modules/organs/internal/eyes.dm
+++ b/code/modules/organs/internal/eyes.dm
@@ -12,6 +12,26 @@
toxin_type = CE_OCULOTOXIC
var/list/eye_colour = list(0,0,0)
var/singular_name = "eye"
+ /// Whether the owner has voluntarily closed this organ.
+ var/eyes_closed = FALSE
+
+/obj/item/organ/internal/eyes/verb/toggle_eyes()
+ set name = "Open/Close Eyes"
+ set desc = "Close your eyes to block your vision and protect them from bright flashes."
+ set category = "IC"
+ set src in usr
+
+ if(!owner || usr != owner || owner.incapacitated())
+ return
+
+ eyes_closed = !eyes_closed
+ if(eyes_closed)
+ owner.custom_emote(VISIBLE_MESSAGE, "closes their eyes.")
+ else
+ owner.custom_emote(VISIBLE_MESSAGE, "opens their eyes.")
+
+ owner.update_body()
+ owner.handle_vision()
/obj/item/organ/internal/eyes/proc/update_colour()
if(!owner)
diff --git a/code/modules/organs/organ_icon.dm b/code/modules/organs/organ_icon.dm
index ad49a9ec910..7b70bf3a39b 100644
--- a/code/modules/organs/organ_icon.dm
+++ b/code/modules/organs/organ_icon.dm
@@ -60,17 +60,22 @@
is_frenzied = TRUE
if(owner.species.has_organ[owner.species.vision_organ])
var/obj/item/organ/internal/eyes/eyes = owner.get_eyes()
- if(eyes && species.eyes && !is_frenzied)
+ if(eyes && species.eyes && (!is_frenzied || eyes.eyes_closed))
var/eyecolor
- if (eyes.eye_colour)
+ if(eyes.eye_colour && !eyes.eyes_closed)
eyecolor = rgb(eyes.eye_colour[1], eyes.eye_colour[2], eyes.eye_colour[3])
- var/cache_key = "[species.eyes]_[eyecolor || "nocolor"]"
+ var/cache_key = "[species.eyes]_[eyes.eyes_closed ? "closed" : (eyecolor || "nocolor")]"
var/icon/eyes_icon = SSicon_cache.human_eye_cache[cache_key]
if (!eyes_icon)
eyes_icon = new/icon(species.eyes_icons, species.eyes)
- if(eyecolor)
+ if(eyes.eyes_closed)
+ // A translucent black eye mask darkens the skin beneath it, creating eyelids
+ // which follow both colour-based skin and tone-shaded human complexions.
+ eyes_icon.Blend(rgb(0, 0, 0), ICON_MULTIPLY)
+ eyes_icon.ChangeOpacity(0.2)
+ else if(eyecolor)
eyes_icon.Blend(eyecolor, species.eyes_icon_blend)
else
eyes_icon.Blend(rgb(128,0,0), species.eyes_icon_blend)
@@ -109,6 +114,9 @@
var/list/bonus_overlays = list()
var/obj/item/organ/internal/eyes/eyes = H.get_eyes()
+ if(eyes?.eyes_closed)
+ return mob_overlays
+
if(eyes && BP_IS_ROBOTIC(eyes))
var/datum/robolimb/robolimb_data = GLOB.all_robolimbs[eyes.model]
if(robolimb_data.emissive)
diff --git a/code/unit_tests/mob_tests.dm b/code/unit_tests/mob_tests.dm
index ea46246e9b5..0edcd05e3a2 100644
--- a/code/unit_tests/mob_tests.dm
+++ b/code/unit_tests/mob_tests.dm
@@ -122,6 +122,45 @@
return 1 // return 1 to show we're done and don't want to recheck the result.
+/datum/unit_test/closed_eyes
+ name = "MOB: Closing eyes blinds and protects from flashes"
+ groups = list("mob")
+
+/datum/unit_test/closed_eyes/start_test()
+ var/mob/living/carbon/human/H = new(pick(GLOB.tdome1))
+ var/obj/item/organ/internal/eyes/eyes = H.get_eyes()
+
+ if(!eyes)
+ TEST_FAIL("Test human spawned without an eye organ.")
+ qdel(H)
+ return TRUE
+
+ eyes.eyes_closed = TRUE
+ if(!H.is_blind())
+ TEST_FAIL("A human with closed eyes was not considered blind.")
+ qdel(H)
+ return TRUE
+
+ if(H.flash_act(affect_silicon = TRUE, ignore_inherent = TRUE))
+ TEST_FAIL("A flash affected a human with closed eyes.")
+ qdel(H)
+ return TRUE
+
+ eyes.eyes_closed = FALSE
+ if(H.is_blind())
+ TEST_FAIL("Opening healthy eyes did not restore the human's vision.")
+ qdel(H)
+ return TRUE
+
+ if(!H.flash_act(ignore_inherent = TRUE))
+ TEST_FAIL("A flash did not affect a human with open, unprotected eyes.")
+ qdel(H)
+ return TRUE
+
+ TEST_PASS("Closed eyes blind their owner and prevent flash effects.")
+ qdel(H)
+ return TRUE
+
// ============================================================================
/proc/create_test_mob_with_mind(var/turf/mobloc = null, var/mobtype = /mob/living/carbon/human, var/add_to_playerlist = FALSE)
diff --git a/html/changelogs/geeves-closing_eyes.yml b/html/changelogs/geeves-closing_eyes.yml
new file mode 100644
index 00000000000..1bbafd0a301
--- /dev/null
+++ b/html/changelogs/geeves-closing_eyes.yml
@@ -0,0 +1,7 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "You can now close your eyes. This blinds you, but gives you protection against flash effects."
+ - balance: "Welding while blind takes three times as long and has an 80% chance to fail, with a risk of burning yourself."