diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index 56289d16843..d5f1496079f 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -82,7 +82,11 @@ #define REDUCE_RANGE (1<<1) #define NOPASS (1<<2) -//Eavesdropping +/// Range to hear normal messages +#define MESSAGE_RANGE 7 +/// Range to hear whispers normally +#define WHISPER_RANGE 1 +/// Additional range to partially hear whispers #define EAVESDROP_EXTRA_RANGE 1 //how much past the specified message_range does the message get starred, whispering only /// How close intercoms can be for radio code use diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index e782d44818c..bebe182107f 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -359,6 +359,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define IGNORING_GRAVITY_NEGATION "ignoring_gravity_negation" /// We have some form of forced gravity acting on us #define TRAIT_FORCED_GRAVITY "forced_gravity" +/// Makes whispers clearly heard from seven tiles away, the full hearing range +#define TRAIT_GOOD_HEARING "good_hearing" +/// Allows you to hear speech through walls +#define TRAIT_XRAY_HEARING "xray_hearing" /// Lets us scan reagents #define TRAIT_REAGENT_SCANNER "reagent_scanner" diff --git a/code/__DEFINES/~skyrat_defines/chat.dm b/code/__DEFINES/~skyrat_defines/chat.dm index cc41ace6f28..fc1c09c63d1 100644 --- a/code/__DEFINES/~skyrat_defines/chat.dm +++ b/code/__DEFINES/~skyrat_defines/chat.dm @@ -3,3 +3,4 @@ #define EXAMINE_SECTION_BREAK "
" +#define LOOC_RANGE 7 diff --git a/code/__HELPERS/spatial_info.dm b/code/__HELPERS/spatial_info.dm index 78bc79f1ef8..033d5a60b79 100644 --- a/code/__HELPERS/spatial_info.dm +++ b/code/__HELPERS/spatial_info.dm @@ -118,6 +118,40 @@ return . +/** + * The exact same as get_hearers_in_view, but not limited by visibility. Does no filtering for traits, line of sight, or any other such criteria. + * Filtering is intended to be done by whatever calls this function. + * + * This function exists to allow for mobs to hear speech without line of sight, if such a thing is needed. + * + * * radius - what radius search circle we are using, worse performance as this increases + * * source - object at the center of our search area. everything in get_turf(source) is guaranteed to be part of the search area + */ +/proc/get_hearers_in_range(range, atom/source) + var/turf/center_turf = get_turf(source) + if(!center_turf) + return + + . = list() + + if(range <= 0)//special case for if only source cares + for(var/atom/movable/target as anything in center_turf) + var/list/recursive_contents = target.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE] + if(recursive_contents) + . += recursive_contents + return . + + var/list/hearables_from_grid = SSspatial_grid.orthogonal_range_search(source, RECURSIVE_CONTENTS_HEARING_SENSITIVE, range) + + if(!length(hearables_from_grid))//we know that something is returned by the grid, but we dont know if we need to actually filter down the output + return . + + for(var/atom/movable/hearable as anything in hearables_from_grid) + if (get_dist(center_turf, hearable) <= range) + . += hearable + + return . + /** * Returns a list of movable atoms that are hearing sensitive in view_radius and line of sight to source * the majority of the work is passed off to the spatial grid if view_radius > 0 diff --git a/code/__HELPERS/~skyrat_helpers/unsorted.dm b/code/__HELPERS/~skyrat_helpers/unsorted.dm index 0d9d7c2c013..f6ae26cc82b 100644 --- a/code/__HELPERS/~skyrat_helpers/unsorted.dm +++ b/code/__HELPERS/~skyrat_helpers/unsorted.dm @@ -17,7 +17,7 @@ /** Get all hearers in range, ignores walls and such. Code stolen from `/proc/get_hearers_in_view()` * Much faster and less expensive than range() */ -/proc/get_hearers_in_range(range_radius, atom/source) +/proc/get_hearers_in_looc_range(atom/source, range_radius = LOOC_RANGE) var/turf/center_turf = get_turf(source) if(!center_turf) return diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index fc924d2d7f0..bc34feb5709 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -207,6 +207,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_MORBID" = TRAIT_MORBID, "TRAIT_UNDENSE" = TRAIT_UNDENSE, "TRAIT_EXPANDED_FOV" = TRAIT_EXPANDED_FOV, + "TRAIT_GOOD_HEARING" = TRAIT_GOOD_HEARING, + "TRAIT_XRAY_HEARING" = TRAIT_XRAY_HEARING, ), /obj/item/bodypart = list( "TRAIT_PARALYSIS" = TRAIT_PARALYSIS, diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index d507b4f9139..81d250ef79f 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -241,7 +241,7 @@ Hear(rendered, src, language, message, null, spans, message_mods) // We always hear what we say var/group = owner.imaginary_group - src // The people in our group don't, so we have to exclude ourselves not to hear twice for(var/mob/person in group) - if(eavesdrop_range && get_dist(src, person) > 1 + eavesdrop_range) + if(eavesdrop_range && get_dist(src, person) > WHISPER_RANGE + eavesdrop_range && !HAS_TRAIT(person, TRAIT_GOOD_HEARING)) var/new_rendered = "[span_name("[name]")] [say_quote(say_emphasis(eavesdropped_message), spans, message_mods)]" person.Hear(new_rendered, src, language, eavesdropped_message, null, spans, message_mods) else diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm index 72f6d4297ee..0b5a2df246c 100644 --- a/code/datums/station_traits/positive_traits.dm +++ b/code/datums/station_traits/positive_traits.dm @@ -304,7 +304,7 @@ /datum/job/mime = /obj/item/organ/internal/tongue/robot, //... /datum/job/paramedic = /obj/item/organ/internal/cyberimp/eyes/hud/medical, /datum/job/prisoner = /obj/item/organ/internal/eyes/robotic/shield, - /datum/job/psychologist = /obj/item/organ/internal/ears/cybernetic/upgraded, + /datum/job/psychologist = /obj/item/organ/internal/ears/cybernetic/whisper, /datum/job/quartermaster = /obj/item/organ/internal/stomach/cybernetic/tier3, /datum/job/research_director = /obj/item/organ/internal/cyberimp/bci, /datum/job/roboticist = /obj/item/organ/internal/cyberimp/eyes/hud/diagnostic, @@ -317,7 +317,7 @@ // SKYRAT EDIT ADDITION START - MODULAR JOBS /datum/job/blueshield = /obj/item/organ/internal/cyberimp/brain/anti_stun, /datum/job/nanotrasen_consultant = /obj/item/organ/internal/heart/cybernetic/tier3, - /datum/job/barber = /obj/item/organ/internal/ears/cybernetic/upgraded, + /datum/job/barber = /obj/item/organ/internal/ears/cybernetic/whisper, /datum/job/corrections_officer = /obj/item/organ/internal/cyberimp/arm/flash, /datum/job/orderly = /obj/item/organ/internal/cyberimp/brain/anti_drop, /datum/job/science_guard = /obj/item/organ/internal/cyberimp/arm/flash, diff --git a/code/modules/cargo/bounties/medical.dm b/code/modules/cargo/bounties/medical.dm index 4d6fd051bdb..f9b367b12f0 100644 --- a/code/modules/cargo/bounties/medical.dm +++ b/code/modules/cargo/bounties/medical.dm @@ -36,6 +36,8 @@ /obj/item/organ/internal/ears = TRUE, /obj/item/organ/internal/ears/cybernetic = FALSE, /obj/item/organ/internal/ears/cybernetic/upgraded = TRUE, + /obj/item/organ/internal/ears/cybernetic/whisper = TRUE, + /obj/item/organ/internal/ears/cybernetic/xray = TRUE, ) /datum/bounty/item/medical/liver diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index f92e380a44a..8803d5df374 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -279,10 +279,13 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( understood = FALSE // if someone is whispering we make an extra type of message that is obfuscated for people out of range - var/is_speaker_whispering = message_mods[WHISPER_MODE] - var/can_hear_whisper = get_dist(speaker, src) <= message_range - if(is_speaker_whispering && !can_hear_whisper && !isobserver(src)) // ghosts can hear all messages clearly + // Less than or equal to 0 means normal hearing. More than 0 and less than or equal to EAVESDROP_EXTRA_RANGE means + // partial hearing. More than EAVESDROP_EXTRA_RANGE means no hearing. Exception for GOOD_HEARING trait + var/dist = get_dist(speaker, src) - message_range + if(dist > 0 && dist <= EAVESDROP_EXTRA_RANGE && !HAS_TRAIT(src, TRAIT_GOOD_HEARING) && !isobserver(src)) // ghosts can hear all messages clearly raw_message = stars(raw_message) + if (dist > EAVESDROP_EXTRA_RANGE && !HAS_TRAIT(src, TRAIT_GOOD_HEARING) && !isobserver(src)) + return FALSE // Too far away and don't have good hearing, you can't hear anything // we need to send this signal before compose_message() is used since other signals need to modify // the raw_message first. After the raw_message is passed through the various signals, it's ready to be formatted @@ -337,10 +340,18 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( var/whisper_range = 0 var/is_speaker_whispering = FALSE if(message_mods[WHISPER_MODE]) //If we're whispering - whisper_range = EAVESDROP_EXTRA_RANGE + // Needed for good hearing trait. The actual filtering for whispers happens at the /mob/living/Hear proc + whisper_range = MESSAGE_RANGE - WHISPER_RANGE is_speaker_whispering = TRUE - var/list/listening = get_hearers_in_view(message_range + whisper_range, source) + var/list/in_view = get_hearers_in_view(message_range + whisper_range, source) + var/list/listening = get_hearers_in_range(message_range + whisper_range, source) + + // Pre-process listeners to account for line-of-sight + for(var/atom/movable/listening_movable as anything in listening) + if(!(listening_movable in in_view) && !HAS_TRAIT(listening_movable, TRAIT_XRAY_HEARING)) + listening.Remove(listening_movable) + if(client) //client is so that ghosts don't have to listen to mice for(var/mob/player_mob as anything in GLOB.player_list) if(QDELETED(player_mob)) //Some times nulls and deleteds stay in this list. This is a workaround to prevent ic chat breaking for everyone when they do. diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index 79d905b81b0..0dd42d53be6 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -348,32 +348,6 @@ //////////Cybernetic Implants//////////// ///////////////////////////////////////// -/datum/design/cyberimp_welding - name = "Welding Shield Eyes" - desc = "These reactive micro-shields will protect you from welders and flashes without obscuring your vision." - id = "ci-welding" - build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 - materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*4) - build_path = /obj/item/organ/internal/eyes/robotic/shield - category = list( - RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ORGANS_MISC - ) - departmental_flags = DEPARTMENT_BITFLAG_MEDICAL - -/datum/design/cyberimp_gloweyes - name = "Luminescent Eyes" - desc = "A pair of cybernetic eyes that can emit multicolored light" - id = "ci-gloweyes" - build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 - materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) - build_path = /obj/item/organ/internal/eyes/robotic/glow - category = list( - RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ORGANS_MISC - ) - departmental_flags = DEPARTMENT_BITFLAG_MEDICAL - /datum/design/cyberimp_breather name = "Breathing Tube Implant" desc = "This simple implant adds an internals connector to your back, allowing you to use internals without a mask and protecting you from being choked." @@ -845,13 +819,51 @@ id = "cybernetic_ears_u" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB construction_time = 40 - materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, /datum/material/silver =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron = SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass = SMALL_MATERIAL_AMOUNT*5, + /datum/material/silver = SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/organ/internal/ears/cybernetic/upgraded category = list( RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ORGANS_2 ) departmental_flags = DEPARTMENT_BITFLAG_MEDICAL +/datum/design/cybernetic_ears_whisper + name = "Whisper-sensitive Cybernetic Ears" + desc = "A pair of whisper-sensitive cybernetic ears." + id = "cybernetic_ears_whisper" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + construction_time = 40 + materials = list( + /datum/material/iron = SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass = SMALL_MATERIAL_AMOUNT*5, + /datum/material/silver = SMALL_MATERIAL_AMOUNT*5, + ) + build_path = /obj/item/organ/internal/ears/cybernetic/whisper + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ORGANS_3 + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + +/datum/design/cybernetic_ears_xray + name = "Wall-penetrating Cybernetic Ears" + desc = "A pair of wall-penetrating cybernetic ears." + id = "cybernetic_ears_xray" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + construction_time = 40 + materials = list( + /datum/material/iron = SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass = SMALL_MATERIAL_AMOUNT*5, + /datum/material/silver = SMALL_MATERIAL_AMOUNT*5, + ) + build_path = /obj/item/organ/internal/ears/cybernetic/xray + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ORGANS_3 + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + /datum/design/cybernetic_eyes name = "Basic Cybernetic Eyes" desc = "A basic pair of cybernetic eyes." @@ -875,6 +887,32 @@ ) departmental_flags = DEPARTMENT_BITFLAG_MEDICAL +/datum/design/cyberimp_welding + name = "Welding Shield Eyes" + desc = "These reactive micro-shields will protect you from welders and flashes without obscuring your vision." + id = "ci-welding" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + construction_time = 40 + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*4) + build_path = /obj/item/organ/internal/eyes/robotic/shield + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ORGANS_3 + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + +/datum/design/cyberimp_gloweyes + name = "Luminescent Eyes" + desc = "A pair of cybernetic eyes that can emit multicolored light" + id = "ci-gloweyes" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + construction_time = 40 + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + build_path = /obj/item/organ/internal/eyes/robotic/glow + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ORGANS_3 + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + ///////////////////// ///Surgery Designs/// ///////////////////// diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index ed0ea51aba6..6622e3fd5d6 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -277,6 +277,7 @@ "bonesetter", "cautery", "circular_saw", + "cybernetic_ears", "cybernetic_eyes", "cybernetic_heart", "cybernetic_liver", @@ -1381,7 +1382,7 @@ description = "We have the technology to rebuild him." prereq_ids = list("biotech") design_ids = list( - "cybernetic_ears", + "cybernetic_ears_u", "cybernetic_eyes_improved", "cybernetic_heart_tier2", "cybernetic_liver_tier2", @@ -1402,7 +1403,10 @@ description = "We have the technology to upgrade him." prereq_ids = list("adv_biotech", "cyber_organs") design_ids = list( - "cybernetic_ears_u", + "cybernetic_ears_whisper", + "cybernetic_ears_xray", + "ci-gloweyes", + "ci-welding", "cybernetic_heart_tier3", "cybernetic_liver_tier3", "cybernetic_lungs_tier3", @@ -1423,11 +1427,9 @@ design_ids = list( "ci-breather", "ci-diaghud", - "ci-gloweyes", "ci-medhud", "ci-nutriment", "ci-sechud", - "ci-welding", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) diff --git a/code/modules/surgery/organs/internal/ears/_ears.dm b/code/modules/surgery/organs/internal/ears/_ears.dm index 67cd69ebcc3..54d35628f85 100644 --- a/code/modules/surgery/organs/internal/ears/_ears.dm +++ b/code/modules/surgery/organs/internal/ears/_ears.dm @@ -104,18 +104,51 @@ ear_owner.RemoveElement(/datum/element/waddling) /obj/item/organ/internal/ears/cybernetic - name = "cybernetic ears" + name = "basic cybernetic ears" icon_state = "ears-c" desc = "A basic cybernetic organ designed to mimic the operation of ears." damage_multiplier = 0.9 organ_flags = ORGAN_ROBOTIC /obj/item/organ/internal/ears/cybernetic/upgraded - name = "upgraded cybernetic ears" + name = "cybernetic ears" icon_state = "ears-c-u" - desc = "An advanced cybernetic ear, surpassing the performance of organic ears." + desc = "An advanced cybernetic ear, surpassing the performance of organic ears." damage_multiplier = 0.5 +/obj/item/organ/internal/ears/cybernetic/whisper + name = "whisper-sensitive cybernetic ears" + icon_state = "ears-c-u" + desc = "Allows the user to more easily hear whispers. The user becomes extra vulnerable to loud noises, however" + // Same sensitivity as felinid ears + damage_multiplier = 2 + +// The original idea was to use signals to do this not traits. Unfortunately, the star effect used for whispers applies before any relevant signals +// This seems like the least invasive solution +/obj/item/organ/internal/ears/cybernetic/whisper/on_insert(mob/living/carbon/ear_owner) + . = ..() + ADD_TRAIT(ear_owner, TRAIT_GOOD_HEARING, ORGAN_TRAIT) + +/obj/item/organ/internal/ears/cybernetic/whisper/on_remove(mob/living/carbon/ear_owner) + . = ..() + REMOVE_TRAIT(ear_owner, TRAIT_GOOD_HEARING, ORGAN_TRAIT) + +// "X-ray ears" that let you hear through walls +/obj/item/organ/internal/ears/cybernetic/xray + name = "wall-penetrating cybernetic ears" + icon_state = "ears-c-u" + desc = "Throguh the power of modern engineering, allows the user to hear speech through walls. The user becomes extra vulnerable to loud noises, however" + // Same sensitivity as felinid ears + damage_multiplier = 2 + +/obj/item/organ/internal/ears/cybernetic/xray/on_insert(mob/living/carbon/ear_owner) + . = ..() + ADD_TRAIT(ear_owner, TRAIT_XRAY_HEARING, ORGAN_TRAIT) + +/obj/item/organ/internal/ears/cybernetic/xray/on_remove(mob/living/carbon/ear_owner) + . = ..() + REMOVE_TRAIT(ear_owner, TRAIT_XRAY_HEARING, ORGAN_TRAIT) + /obj/item/organ/internal/ears/cybernetic/emp_act(severity) . = ..() if(. & EMP_PROTECT_SELF) diff --git a/modular_skyrat/modules/verbs/code/looc.dm b/modular_skyrat/modules/verbs/code/looc.dm index e864e19cd1a..3dd47f9741c 100644 --- a/modular_skyrat/modules/verbs/code/looc.dm +++ b/modular_skyrat/modules/verbs/code/looc.dm @@ -1,5 +1,3 @@ -#define LOOC_RANGE 7 - /client/verb/looc(msg as text) set name = "LOOC" set desc = "Local OOC, seen only by those in view." @@ -54,7 +52,7 @@ mob.log_talk(msg,LOG_OOC, tag="LOOC") var/list/heard if(wall_pierce) - heard = get_hearers_in_range(LOOC_RANGE, mob.get_top_level_mob()) + heard = get_hearers_in_looc_range(mob.get_top_level_mob()) else heard = get_hearers_in_view(LOOC_RANGE, mob.get_top_level_mob()) @@ -62,7 +60,7 @@ if(istype(mob, /mob/living/silicon/ai)) var/mob/living/silicon/ai/ai = mob if(wall_pierce) - heard = get_hearers_in_range(LOOC_RANGE, ai.eyeobj) + heard = get_hearers_in_looc_range(ai.eyeobj) else heard = get_hearers_in_view(LOOC_RANGE, ai.eyeobj) //so the ai can see looc text @@ -90,5 +88,3 @@ to_chat(cli_client, span_looc("[ADMIN_FLW(usr)] LOOC[wall_pierce ? " (WALL PIERCE)" : ""]: [src.key]/[src.mob.name]: [msg]")) else if (cli_client.prefs.read_preference(/datum/preference/toggle/admin/see_looc)) to_chat(cli_client, span_rlooc("[ADMIN_FLW(usr)] (R)LOOC[wall_pierce ? " (WALL PIERCE)" : ""]: [src.key]/[src.mob.name]: [msg]")) - -#undef LOOC_RANGE