diff --git a/code/__DEFINES/interaction_flags.dm b/code/__DEFINES/interaction_flags.dm
index df6ae2d899d..fb3a7439b10 100644
--- a/code/__DEFINES/interaction_flags.dm
+++ b/code/__DEFINES/interaction_flags.dm
@@ -38,3 +38,5 @@
#define INTERACT_MACHINE_SET_MACHINE (1<<6)
/// the user must have vision to interact (blind people need not apply)
#define INTERACT_MACHINE_REQUIRES_SIGHT (1<<7)
+/// the user must be able to read to interact
+#define INTERACT_MACHINE_REQUIRES_LITERACY (1<<8)
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index b5a457965b7..5690b8424fd 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -140,6 +140,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_INCAPACITATED "incapacitated"
/// In some kind of critical condition. Is able to succumb.
#define TRAIT_CRITICAL_CONDITION "critical-condition"
+#define TRAIT_ILLITERATE "illiterate"
#define TRAIT_BLIND "blind"
#define TRAIT_MUTE "mute"
#define TRAIT_EMOTEMUTE "emotemute"
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index 0cd3e53de05..841d288f94e 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -155,6 +155,7 @@ DEFINE_BITFIELD(interaction_flags_machine, list(
"INTERACT_MACHINE_OPEN" = INTERACT_MACHINE_OPEN,
"INTERACT_MACHINE_OPEN_SILICON" = INTERACT_MACHINE_OPEN_SILICON,
"INTERACT_MACHINE_REQUIRES_SIGHT" = INTERACT_MACHINE_REQUIRES_SIGHT,
+ "INTERACT_MACHINE_REQUIRES_LITERACY" = INTERACT_MACHINE_REQUIRES_LITERACY,
"INTERACT_MACHINE_REQUIRES_SILICON" = INTERACT_MACHINE_REQUIRES_SILICON,
"INTERACT_MACHINE_SET_MACHINE" = INTERACT_MACHINE_SET_MACHINE,
"INTERACT_MACHINE_WIRES_IF_OPEN" = INTERACT_MACHINE_WIRES_IF_OPEN,
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 3594f4981ff..392d25faec3 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -13,6 +13,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_RESTRAINED" = TRAIT_RESTRAINED,
"TRAIT_INCAPACITATED" = TRAIT_INCAPACITATED,
"TRAIT_CRITICAL_CONDITION" = TRAIT_CRITICAL_CONDITION,
+ "TRAIT_ILLITERATE" = TRAIT_ILLITERATE,
"TRAIT_BLIND" = TRAIT_BLIND,
"TRAIT_MUTE" = TRAIT_MUTE,
"TRAIT_EMOTEMUTE " = TRAIT_EMOTEMUTE,
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index d68bb85732d..9aa71f91610 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -300,3 +300,18 @@
/datum/brain_trauma/severe/hypnotic_trigger/proc/hypnotrigger()
to_chat(owner, span_warning("The words trigger something deep within you, and you feel your consciousness slipping away..."))
owner.apply_status_effect(/datum/status_effect/trance, rand(100,300), FALSE)
+
+/datum/brain_trauma/severe/dyslexia
+ name = "Dyslexia"
+ desc = "Patient is unable to read or write."
+ scan_desc = "dyslexia"
+ gain_text = "You have trouble reading or writing..."
+ lose_text = "Your suddenly remember how to read and write."
+
+/datum/brain_trauma/severe/dyslexia/on_gain()
+ ADD_TRAIT(owner, TRAIT_ILLITERATE, TRAUMA_TRAIT)
+ ..()
+
+/datum/brain_trauma/severe/dyslexia/on_lose()
+ REMOVE_TRAIT(owner, TRAIT_ILLITERATE, TRAUMA_TRAIT)
+ ..()
diff --git a/code/datums/quirks/negative.dm b/code/datums/quirks/negative.dm
index 7f4062c9ca7..f0703b96938 100644
--- a/code/datums/quirks/negative.dm
+++ b/code/datums/quirks/negative.dm
@@ -897,3 +897,12 @@
return TRUE
return FALSE
+
+/datum/quirk/illiterate
+ name = "Illiterate"
+ desc = "You dropped out of school and are unable to read or write. This affects reading, writing, using computers and other electronics."
+ icon = "graduation-cap"
+ value = -8
+ mob_trait = TRAIT_ILLITERATE
+ medical_record_text = "Patient is not literate."
+ hardcore_value = 8
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 71aa9ccb8cd..6f6e5c6a71b 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -130,7 +130,7 @@
var/subsystem_type = /datum/controller/subsystem/machines
var/obj/item/circuitboard/circuit // Circuit to be created and inserted when the machinery is created
- var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_SET_MACHINE
+ var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON|INTERACT_MACHINE_SET_MACHINE
var/fair_market_price = 69
var/market_verb = "Customer"
var/payment_department = ACCOUNT_ENG
@@ -562,6 +562,10 @@
to_chat(user, span_warning("This machine requires sight to use."))
return FALSE
+ // machines have their own lit up display screens and LED buttons so we don't need to check for light
+ if((interaction_flags_machine & INTERACT_MACHINE_REQUIRES_LITERACY) && !user.can_read(src, check_for_light = FALSE))
+ return FALSE
+
if(panel_open && !(interaction_flags_machine & INTERACT_MACHINE_OPEN))
return FALSE
diff --git a/code/game/machinery/computer/_computer.dm b/code/game/machinery/computer/_computer.dm
index 7260a3a593f..450a286820e 100644
--- a/code/game/machinery/computer/_computer.dm
+++ b/code/game/machinery/computer/_computer.dm
@@ -6,6 +6,7 @@
max_integrity = 200
integrity_failure = 0.5
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, FIRE = 40, ACID = 20)
+ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE|INTERACT_MACHINE_REQUIRES_LITERACY
var/brightness_on = 1
var/icon_keyboard = "generic_key"
var/icon_screen = "generic"
diff --git a/code/game/machinery/computer/arcade/arcade.dm b/code/game/machinery/computer/arcade/arcade.dm
index a5060704ce7..810bfd015aa 100644
--- a/code/game/machinery/computer/arcade/arcade.dm
+++ b/code/game/machinery/computer/arcade/arcade.dm
@@ -71,6 +71,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
icon_keyboard = null
icon_screen = "invaders"
light_color = LIGHT_COLOR_GREEN
+ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE // we don't need to be literate to play video games fam
var/list/prize_override
/obj/machinery/computer/arcade/proc/Reset()
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 37637187b7a..91fbd138091 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -21,7 +21,7 @@
var/list/cam_plane_masters
var/atom/movable/screen/background/cam_background
- interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_SET_MACHINE | INTERACT_MACHINE_REQUIRES_SIGHT
+ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE|INTERACT_MACHINE_REQUIRES_SIGHT
/obj/machinery/computer/security/Initialize(mapload)
. = ..()
diff --git a/code/game/machinery/newscaster/newscaster_machine.dm b/code/game/machinery/newscaster/newscaster_machine.dm
index 0f4e29cbfe7..cd797900943 100644
--- a/code/game/machinery/newscaster/newscaster_machine.dm
+++ b/code/game/machinery/newscaster/newscaster_machine.dm
@@ -12,6 +12,7 @@
armor = list(MELEE = 50, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, FIRE = 50, ACID = 30)
max_integrity = 200
integrity_failure = 0.25
+ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE|INTERACT_MACHINE_REQUIRES_LITERACY
///Reference to the currently logged in user.
var/datum/bank_account/current_user
///Name of the logged in user.
diff --git a/code/game/machinery/newscaster/newspaper.dm b/code/game/machinery/newscaster/newspaper.dm
index f2b1128143a..2de86e1b5a5 100644
--- a/code/game/machinery/newscaster/newspaper.dm
+++ b/code/game/machinery/newscaster/newspaper.dm
@@ -33,10 +33,8 @@
return(TOXLOSS)
/obj/item/newspaper/attack_self(mob/user)
- if(!ishuman(user))
- to_chat(user, span_warning("The paper is full of unintelligible symbols!"))
+ if(!istype(user) || !user.can_read(src))
return
- var/mob/living/carbon/human/human_user = user
var/dat
pages = 0
switch(screen)
@@ -61,7 +59,7 @@
dat+=""
if(scribble_page==curr_page)
dat+="
There is a small scribble near the end of this page... It reads: \"[scribble]\""
- dat+= "
"
+ dat+= "
"
if(1) // X channel pages inbetween.
for(var/datum/feed_channel/NP in news_content)
pages++
@@ -110,8 +108,8 @@
dat+="
There is a small scribble near the end of this page... It reads: \"[scribble]\""
dat+= "
"
dat+="
[curr_page+1]
"
- human_user << browse(dat, "window=newspaper_main;size=300x400")
- onclose(human_user, "newspaper_main")
+ user << browse(dat, "window=newspaper_main;size=300x400")
+ onclose(user, "newspaper_main")
/obj/item/newspaper/proc/notContent(list/L)
if(!L.len)
@@ -160,8 +158,7 @@
return
if(istype(W, /obj/item/pen))
- if(!user.is_literate())
- to_chat(user, span_notice("You scribble illegibly on [src]!"))
+ if(!user.can_write(W))
return
if(scribble_page == curr_page)
to_chat(user, span_warning("There's already a scribble in this page... You wouldn't want to make things too cluttered, would you?"))
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index ba868cc659d..b4fc26b1000 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -686,6 +686,9 @@
/obj/item/card/id/examine(mob/user)
. = ..()
+ if(!user.can_read(src))
+ return
+
if(registered_account)
. += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr."
if((ACCESS_COMMAND in access) || (ACCESS_QM in access))
@@ -697,6 +700,9 @@
. += span_notice("There's more information below, you can look again to take a closer look...")
/obj/item/card/id/examine_more(mob/user)
+ if(!user.can_read(src))
+ return
+
. = ..()
. += span_notice("You examine [src] closer, and note the following...")
@@ -1176,6 +1182,9 @@
/obj/item/card/id/advanced/prisoner/examine(mob/user)
. = ..()
+ if(!.)
+ return
+
if(timed)
if(time_left <= 0)
. += span_notice("The digital timer on the card has zero seconds remaining. You leave a changed man, but a free man nonetheless.")
diff --git a/code/game/objects/items/devices/scanners/gas_analyzer.dm b/code/game/objects/items/devices/scanners/gas_analyzer.dm
index 8d5b05c82da..2b1a22190f9 100644
--- a/code/game/objects/items/devices/scanners/gas_analyzer.dm
+++ b/code/game/objects/items/devices/scanners/gas_analyzer.dm
@@ -38,46 +38,48 @@
/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens
..()
- if(user.canUseTopic(src, BE_CLOSE))
- if(cooldown)
- to_chat(user, span_warning("[src]'s barometer function is preparing itself."))
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.can_read(src))
+ return
+
+ if(cooldown)
+ to_chat(user, span_warning("[src]'s barometer function is preparing itself."))
+ return
+
+ var/turf/T = get_turf(user)
+ if(!T)
+ return
+
+ playsound(src, 'sound/effects/pop.ogg', 100)
+ var/area/user_area = T.loc
+ var/datum/weather/ongoing_weather = null
+
+ if(!user_area.outdoors)
+ to_chat(user, span_warning("[src]'s barometer function won't work indoors!"))
+ return
+
+ for(var/V in SSweather.processing)
+ var/datum/weather/W = V
+ if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE))
+ ongoing_weather = W
+ break
+
+ if(ongoing_weather)
+ if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE))
+ to_chat(user, span_warning("[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]"))
return
- var/turf/T = get_turf(user)
- if(!T)
- return
-
- playsound(src, 'sound/effects/pop.ogg', 100)
- var/area/user_area = T.loc
- var/datum/weather/ongoing_weather = null
-
- if(!user_area.outdoors)
- to_chat(user, span_warning("[src]'s barometer function won't work indoors!"))
- return
-
- for(var/V in SSweather.processing)
- var/datum/weather/W = V
- if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE))
- ongoing_weather = W
- break
-
- if(ongoing_weather)
- if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE))
- to_chat(user, span_warning("[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]"))
- return
-
- to_chat(user, span_notice("The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)]."))
- if(ongoing_weather.aesthetic)
- to_chat(user, span_warning("[src]'s barometer function says that the next storm will breeze on by."))
+ to_chat(user, span_notice("The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)]."))
+ if(ongoing_weather.aesthetic)
+ to_chat(user, span_warning("[src]'s barometer function says that the next storm will breeze on by."))
+ else
+ var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"]
+ var/fixed = next_hit ? timeleft(next_hit) : -1
+ if(fixed < 0)
+ to_chat(user, span_warning("[src]'s barometer function was unable to trace any weather patterns."))
else
- var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"]
- var/fixed = next_hit ? timeleft(next_hit) : -1
- if(fixed < 0)
- to_chat(user, span_warning("[src]'s barometer function was unable to trace any weather patterns."))
- else
- to_chat(user, span_warning("[src]'s barometer function says a storm will land in approximately [butchertime(fixed)]."))
- cooldown = TRUE
- addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time)
+ to_chat(user, span_warning("[src]'s barometer function says a storm will land in approximately [butchertime(fixed)]."))
+ cooldown = TRUE
+ addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time)
/obj/item/analyzer/proc/ping()
if(isliving(loc))
@@ -112,17 +114,13 @@
return list("gasmixes" = last_gasmix_data)
/obj/item/analyzer/attack_self(mob/user, modifiers)
- // Check if it requires visibility and if the user is you know, blind.
- if (user.stat != CONSCIOUS || user.is_blind())
- to_chat(user, span_warning("You're unable to see [src]'s results!"))
+ if(user.stat != CONSCIOUS || !user.can_read(src) || user.is_blind())
return
atmos_scan(user=user, target=get_turf(src), tool=src, silent=FALSE)
on_analyze(source=src, target=get_turf(src))
/obj/item/analyzer/attack_self_secondary(mob/user, modifiers)
- // Check if it requires visibility and if the user is you know, blind.
- if (user.stat != CONSCIOUS || user.is_blind())
- to_chat(user, span_warning("You're unable to see [src]'s results!"))
+ if(user.stat != CONSCIOUS || !user.can_read(src) || user.is_blind())
return
ui_interact(user)
diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm
index 23c794ddb0a..19cb2f6d26d 100644
--- a/code/game/objects/items/devices/scanners/health_analyzer.dm
+++ b/code/game/objects/items/devices/scanners/health_analyzer.dm
@@ -29,7 +29,6 @@
/obj/item/healthanalyzer/Initialize(mapload)
. = ..()
-
register_item_context()
/obj/item/healthanalyzer/examine(mob/user)
@@ -47,6 +46,9 @@
return BRUTELOSS
/obj/item/healthanalyzer/attack_self(mob/user)
+ if(!user.can_read(src) || user.is_blind())
+ return
+
scanmode = (scanmode + 1) % SCANMODE_COUNT
switch(scanmode)
if(SCANMODE_HEALTH)
@@ -59,6 +61,9 @@
if(!(item_use_power(power_use_amount, user, FALSE) & COMPONENT_POWER_SUCCESS))
return
//SKYRAT EDIT END
+ if(!user.can_read(src) || user.is_blind())
+ return
+
flick("[icon_state]-scan", src) //makes it so that it plays the scan animation upon scanning, including clumsy scanning
// Clumsiness/brain damage check
@@ -87,10 +92,9 @@
add_fingerprint(user)
/obj/item/healthanalyzer/attack_secondary(mob/living/victim, mob/living/user, params)
- //SKYRAT EDIT ADDITION
- if(!(item_use_power(power_use_amount, user) & COMPONENT_POWER_SUCCESS))
+ if(!user.can_read(src) || user.is_blind() || !(item_use_power(power_use_amount, user) & COMPONENT_POWER_SUCCESS)) // SKYRAT EDIT CHANGE
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
- //SKYRAT EDIT END
+
chemscan(user, victim)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
@@ -117,10 +121,6 @@
if(user.incapacitated())
return
- if(user.is_blind())
- to_chat(user, span_warning("You realize that your scanner has no accessibility support for the blind!"))
- return
-
// the final list of strings to render
var/render_list = list()
@@ -401,10 +401,6 @@
if(user.incapacitated())
return
- if(user.is_blind())
- to_chat(user, span_warning("You realize that your scanner has no accessibility support for the blind!"))
- return
-
if(istype(target) && target.reagents)
var/render_list = list()
@@ -461,7 +457,7 @@
/obj/item/healthanalyzer/AltClick(mob/user)
..()
- if(!user.canUseTopic(src, BE_CLOSE))
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.can_read(src) || user.is_blind())
return
mode = !mode
@@ -479,10 +475,6 @@
if(!istype(patient) || user.incapacitated())
return
- if(user.is_blind())
- to_chat(user, span_warning("You realize that your scanner has no accessibility support for the blind!"))
- return
-
var/render_list = ""
for(var/i in patient.get_wounded_bodyparts())
var/obj/item/bodypart/wounded_part = i
@@ -529,6 +521,9 @@
L.dropItemToGround(src)
/obj/item/healthanalyzer/wound/attack(mob/living/carbon/patient, mob/living/carbon/human/user)
+ if(!user.can_read(src) || user.is_blind())
+ return
+
add_fingerprint(user)
user.visible_message(span_notice("[user] scans [patient] for serious injuries."), span_notice("You scan [patient] for serious injuries."))
diff --git a/code/game/objects/items/devices/scanners/sequence_scanner.dm b/code/game/objects/items/devices/scanners/sequence_scanner.dm
index 7150cf883e1..f74c82c0faf 100644
--- a/code/game/objects/items/devices/scanners/sequence_scanner.dm
+++ b/code/game/objects/items/devices/scanners/sequence_scanner.dm
@@ -70,7 +70,7 @@
if(LAZYFIND(active_mutations, mutation))
to_chat(user, span_boldnotice("[get_display_name(mutation)]"))
else
- to_chat(user, span_notice("[get_display_name(mutation)]"))
+ to_chat(user, span_notice("[get_display_name(mutation)]"))
/obj/item/sequence_scanner/proc/display_sequence(mob/living/user)
if(!LAZYLEN(buffer) || !ready)
@@ -82,25 +82,27 @@
var/answer = tgui_input_list(user, "Analyze Potential", "Sequence Analyzer", sort_list(options))
if(isnull(answer))
return
- if(ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
- var/sequence
- for(var/mutation in buffer) //this physically hurts but i dont know what anything else short of an assoc list
- if(get_display_name(mutation) == answer)
- sequence = buffer[mutation]
- break
+ if(!ready || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) || !user.can_read(src))
+ return
- if(sequence)
- var/display
- for(var/i in 0 to length_char(sequence) / DNA_MUTATION_BLOCKS-1)
- if(i)
- display += "-"
- display += copytext_char(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1)
+ var/sequence
+ for(var/mutation in buffer) //this physically hurts but i dont know what anything else short of an assoc list
+ if(get_display_name(mutation) == answer)
+ sequence = buffer[mutation]
+ break
- to_chat(user, "[span_boldnotice("[display]")]
")
+ if(sequence)
+ var/display
+ for(var/i in 0 to length_char(sequence) / DNA_MUTATION_BLOCKS-1)
+ if(i)
+ display += "-"
+ display += copytext_char(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1)
- ready = FALSE
- icon_state = "[icon_state]_recharging"
- addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE)
+ to_chat(user, "[span_boldnotice("[display]")]
")
+
+ ready = FALSE
+ icon_state = "[icon_state]_recharging"
+ addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE)
/obj/item/sequence_scanner/proc/recharge()
icon_state = initial(icon_state)
diff --git a/code/game/objects/items/devices/scanners/slime_scanner.dm b/code/game/objects/items/devices/scanners/slime_scanner.dm
index 5b97ac3d75a..502a345a558 100644
--- a/code/game/objects/items/devices/scanners/slime_scanner.dm
+++ b/code/game/objects/items/devices/scanners/slime_scanner.dm
@@ -14,7 +14,7 @@
custom_materials = list(/datum/material/iron=30, /datum/material/glass=20)
/obj/item/slime_scanner/attack(mob/living/M, mob/living/user)
- if(user.stat || user.is_blind())
+ if(user.stat || !user.can_read(src) || user.is_blind())
return
if (!isslime(M))
to_chat(user, span_warning("This device can only scan slimes!"))
diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm
index 3a3597ff07f..4f136a4fef0 100644
--- a/code/modules/hydroponics/hydroitemdefines.dm
+++ b/code/modules/hydroponics/hydroitemdefines.dm
@@ -51,14 +51,14 @@
/// When we attack something, first - try to scan something we hit with left click. Left-clicking uses scans for stats
/obj/item/plant_analyzer/pre_attack(atom/target, mob/living/user)
. = ..()
- if(user.combat_mode)
+ if(user.combat_mode || !user.can_read(src))
return
return do_plant_stats_scan(target, user)
/// Same as above, but with right click. Right-clicking scans for chemicals.
/obj/item/plant_analyzer/pre_attack_secondary(atom/target, mob/living/user)
- if(user.combat_mode)
+ if(user.combat_mode || !user.can_read(src))
return SECONDARY_ATTACK_CONTINUE_CHAIN
return do_plant_chem_scan(target, user) ? SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN : SECONDARY_ATTACK_CONTINUE_CHAIN
diff --git a/code/modules/library/book.dm b/code/modules/library/book.dm
index bc7af7917aa..05343ec27cb 100644
--- a/code/modules/library/book.dm
+++ b/code/modules/library/book.dm
@@ -129,25 +129,24 @@
/obj/item/book/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/pen))
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
+ return
if(user.is_blind())
to_chat(user, span_warning("As you are trying to write on the book, you suddenly feel very stupid!"))
return
if(unique)
to_chat(user, span_warning("These pages don't seem to take the ink well! Looks like you can't modify it."))
return
- var/literate = user.is_literate()
- if(!literate)
- to_chat(user, span_notice("You scribble illegibly on the cover of [src]!"))
- return
+
var/choice = tgui_input_list(usr, "What would you like to change?", "Book Alteration", list("Title", "Contents", "Author", "Cancel"))
if(isnull(choice))
return
- if(!user.canUseTopic(src, BE_CLOSE, literate))
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
switch(choice)
if("Title")
var/newtitle = reject_bad_text(tgui_input_text(user, "Write a new title", "Book Title", max_length = 30))
- if(!user.canUseTopic(src, BE_CLOSE, literate))
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
if (length_char(newtitle) > 30)
to_chat(user, span_warning("That title won't fit on the cover!"))
@@ -159,7 +158,7 @@
book_data.set_title(html_decode(newtitle)) //Don't want to double encode here
if("Contents")
var/content = tgui_input_text(user, "Write your book's contents (HTML NOT allowed)", "Book Contents", multiline = TRUE)
- if(!user.canUseTopic(src, BE_CLOSE, literate))
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
if(!content)
to_chat(user, span_warning("The content is invalid."))
@@ -167,7 +166,7 @@
book_data.set_content(html_decode(content))
if("Author")
var/author = tgui_input_text(user, "Write the author's name", "Author Name")
- if(!user.canUseTopic(src, BE_CLOSE, literate))
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
if(!author)
to_chat(user, span_warning("The name is invalid."))
diff --git a/code/modules/library/bookcase.dm b/code/modules/library/bookcase.dm
index 356cb2024dd..ae45b5b5861 100644
--- a/code/modules/library/bookcase.dm
+++ b/code/modules/library/bookcase.dm
@@ -119,11 +119,10 @@
to_chat(user, span_notice("You empty \the [I] into \the [src]."))
update_appearance()
else if(istype(I, /obj/item/pen))
- if(!user.is_literate())
- to_chat(user, span_notice("You scribble illegibly on the side of [src]!"))
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
var/newname = tgui_input_text(user, "What would you like to title this bookshelf?", "Bookshelf Renaming", max_length = MAX_NAME_LEN)
- if(!user.canUseTopic(src, BE_CLOSE))
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
if(!newname)
return
diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm
index eb9e85deb68..1473dd6f4ea 100644
--- a/code/modules/mining/mine_items.dm
+++ b/code/modules/mining/mine_items.dm
@@ -89,6 +89,30 @@
to_chat(user, span_warning("You get the feeling you shouldn't mess with this."))
return
+ if(HAS_TRAIT(user, TRAIT_ILLITERATE))
+ to_chat(user, span_warning("You start mashing buttons at random!"))
+ if(do_after(user, 10 SECONDS, target = src))
+ var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
+ if(no_destination_swap)
+ if(M.mode == SHUTTLE_RECHARGING)
+ to_chat(usr, span_warning("Shuttle engines are not ready for use."))
+ return
+ if(M.mode != SHUTTLE_IDLE)
+ to_chat(usr, span_warning("Shuttle already in transit."))
+ return
+ var/destionation = M.getDockedId() == "mining_home" ? "mining_away" : "mining_home"
+ switch(SSshuttle.moveShuttle(shuttleId, destionation, 1))
+ if(0)
+ say("Shuttle departing. Please stand away from the doors.")
+ log_shuttle("[key_name(usr)] has sent shuttle \"[M]\" towards \"[destionation]\", using [src].")
+ return TRUE
+ if(1)
+ to_chat(usr, span_warning("Invalid shuttle requested."))
+ else
+ to_chat(usr, span_warning("Unable to comply."))
+
+ return
+
return ..()
/obj/machinery/computer/shuttle/mining/common
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 97ad3c76563..55c5dec673a 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -852,8 +852,8 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/dead/observer/is_literate()
return TRUE
-/mob/dead/observer/can_read(obj/O)
- return TRUE
+/mob/dead/observer/can_read(obj/O, check_for_light = FALSE)
+ return TRUE // we want to bypass all the checks
/mob/dead/observer/vv_edit_var(var_name, var_value)
. = ..()
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index fc3aca51c4d..2e35bc1ec92 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -815,7 +815,7 @@
return ..()
/mob/living/carbon/human/is_literate()
- return TRUE
+ return !HAS_TRAIT(src, TRAIT_ILLITERATE)
/mob/living/carbon/human/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, vomit_type = VOMIT_TOXIC, harm = TRUE, force = FALSE, purge_ratio = 0.1)
if(blood && (NOBLOOD in dna.species.species_traits) && !HAS_TRAIT(src, TRAIT_TOXINLOVER))
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 77cf4fd1229..2d98ab87ed9 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -92,3 +92,4 @@
var/hal_screwydoll
/// When an braindead player has their equipment fiddled with, we log that info here for when they come back so they know who took their ID while they were DC'd for 30 seconds
var/list/afk_thefts
+
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index 5d0b1a28258..93dc25e5400 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -195,6 +195,7 @@ Lizard subspecies: ASHWALKERS
TRAIT_CAN_STRIP,
TRAIT_CHUNKYFINGERS,
TRAIT_VIRUSIMMUNE,
+ TRAIT_ILLITERATE,
)
species_language_holder = /datum/language_holder/lizard/ash
digitigrade_customization = DIGITIGRADE_FORCED
diff --git a/code/modules/mob/living/living_fov.dm b/code/modules/mob/living/living_fov.dm
index e04640735e3..1bc54a66db7 100644
--- a/code/modules/mob/living/living_fov.dm
+++ b/code/modules/mob/living/living_fov.dm
@@ -13,10 +13,10 @@
if(fov_view && observed_atom.plane == GAME_PLANE_FOV_HIDDEN) //SKYRAT EDIT CHANGE
if(rel_x >= -1 && rel_x <= 1 && rel_y >= -1 && rel_y <= 1) //Cheap way to check inside that 3x3 box around you
return TRUE //Also checks if both are 0 to stop division by zero
-
+
// Get the vector length so we can create a good directional vector
var/vector_len = sqrt(abs(rel_x) ** 2 + abs(rel_y) ** 2)
-
+
/// Getting a direction vector
var/dir_x
var/dir_y
@@ -33,10 +33,10 @@
if(WEST)
dir_x = -vector_len
dir_y = 0
-
+
///Calculate angle
var/angle = arccos((dir_x * rel_x + dir_y * rel_y) / (sqrt(dir_x**2 + dir_y**2) * sqrt(rel_x**2 + rel_y**2)))
-
+
/// Calculate vision angle and compare
var/vision_angle = (360 - fov_view) / 2
if(angle < vision_angle)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index e4d8b062a34..62843db0917 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1187,7 +1187,11 @@
to_chat(src, span_warning("You can't write with the [writing_instrument]!"))
if(!is_literate())
- to_chat(src, span_warning("You don't know how to write."))
+ to_chat(src, span_warning("You try to write, but don't know how to spell anything!"))
+ return FALSE
+
+ if(!has_light_nearby() && !has_nightvision())
+ to_chat(src, span_warning("It's too dark in here to write anything!"))
return FALSE
var/obj/item/pen/pen = writing_instrument
@@ -1215,12 +1219,12 @@
return mob_location.get_lumcount() > light_amount
/// Can this mob read
-/mob/proc/can_read(obj/O)
+/mob/proc/can_read(obj/O, check_for_light = TRUE)
if(!is_literate())
to_chat(src, span_warning("You try to read [O], but can't comprehend any of it."))
return FALSE
- if(!has_light_nearby() && !has_nightvision())
+ if(check_for_light && !has_light_nearby() && !has_nightvision())
to_chat(src, span_warning("It's too dark in here to read!"))
return FALSE
diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm
index 9436271222e..19284a966fc 100644
--- a/code/modules/mod/modules/modules_medical.dm
+++ b/code/modules/mod/modules/modules_medical.dm
@@ -36,7 +36,7 @@
. = ..()
if(!.)
return
- if(!isliving(target))
+ if(!isliving(target) || !mod.wearer.can_read(src))
return
switch(mode)
if(HEALTH_SCAN)
diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm
index 76d1b15d02d..5a6d7803882 100644
--- a/code/modules/modular_computers/computers/item/computer_ui.dm
+++ b/code/modules/modular_computers/computers/item/computer_ui.dm
@@ -13,6 +13,9 @@
ui.close()
return
+ if(!user.can_read(src, check_for_light = FALSE))
+ return
+
if(HAS_TRAIT(user, TRAIT_CHUNKYFINGERS) && !allow_chunky)
to_chat(user, span_warning("Your fingers are too big to use this right now!"))
return
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index f096a1f4f42..89961a69388 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -192,14 +192,11 @@
return UI_INTERACTIVE
return ..()
-
-
/obj/item/paper/can_interact(mob/user)
if(in_contents_of(/obj/machinery/door/airlock))
return TRUE
return ..()
-
/obj/item/proc/burn_paper_product_attackby_check(obj/item/I, mob/living/user, bypass_clumsy)
var/ignition_message = I.ignition_effect(src, user)
if(!ignition_message)
@@ -270,7 +267,6 @@
return ..()
-
/obj/item/paper/fire_act(exposed_temperature, exposed_volume)
. = ..()
if(.)
@@ -313,7 +309,6 @@
.["paper_state"] = icon_state /// TODO: show the sheet will bloodied or crinkling?
.["stamps"] = stamps
-
/obj/item/paper/ui_data(mob/user)
var/list/data = list()
data["edit_usr"] = "[user.real_name]"