From 922bd0cb9212932a58033841313f5a027bf2ca73 Mon Sep 17 00:00:00 2001
From: Ghom <42542238+Ghommie@users.noreply.github.com>
Date: Tue, 4 Nov 2025 21:22:33 +0100
Subject: [PATCH] Refactor get_ear_protection() and soundbang_act() to be less
bs (#93472)
so, the current `COMSIG_CARBON_SOUNDBANG` signal is kinda lame because:
1) it's relegated to carbon mobs
2) it means ear protection equipment actually has no effect when
`get_ear_protection` is called instead.
3) It's sent in more than one place in the code
So I replaced it with another signal, this one properly sent in
`get_ear_protection`, and therefore `soundbang_act` as well.
What's also lame is that the code for both `get_ear_protection` and
`soundbang_act` can be easily moved to the living type, and it wouldn't
make a huge impact aside causing simple mobs to be stunned very briefly
by flashbangs since they cannot be knocked down anyway. The less the
code is specific to a subtype, the easier it's to work with, the fewer
the paradigms etc.
Another lame thing is some of the code being kinda old. Also I wanted to
fix an issue with the shriek ling ability detailed in #93401. I wanted
to make the ability more consistent with how other sound-related
abilities work by making it use `soundbang_act`, but with an intensity
higher than any other feature so far, beside point-blank flashbangs
(holding on standing on one). I've also buffed the "ear protection"
offered by vacuums from 1 to 3, meaning nothing but point-blank
flashbangs will go through. Yes, this means shrieks are not good in a
vacuum because sound generally propagate in absence of molecules that
can transmit the vibration... at least IRL. The game's an approximation
anyway, but it's still inconsistent, like a fuckton of other things that
are even more outside of the scope of this PR. Well, at least it affects
simple/basic mobs too now.
## Why It's Good For The Game
This will fix #93401 and make some code less old and bad.
## Changelog
:cl:
refactor: refactored code for ear/soundbang protection. Flashbangs and
other things can now affect simple/basic mobs as well, not just carbon
and occasionally silicons.
balance: Aliens are no longer impervious to soundbang_act. Still very
resistant to it due to their hardcoded ear protection
balance: How the resonant shriek changeling ability works is now
slightly more consistent with the ear/soundbang protection, meaning it
won't work well in a vacuum, but at least it works on simple mobs now.
fix: Everyone can now hear a changeling shriek, unless they're already
deafened or in a vacuum.
/:cl:
---
.../signals/signals_mob/signals_mob_carbon.dm | 2 -
.../signals/signals_mob/signals_mob_living.dm | 4 ++
code/__DEFINES/mobs.dm | 25 +++++++++-
code/datums/components/earprotection.dm | 4 +-
code/game/objects/items/grenades/flashbang.dm | 22 ++++-----
code/game/objects/items/grenades/hypno.dm | 46 ++++++++----------
.../game/objects/items/robot/items/generic.dm | 24 +++++-----
.../antagonists/changeling/powers/shriek.dm | 31 ++++++------
.../antagonists/malf_ai/malf_ai_modules.dm | 6 ++-
code/modules/fishing/fish/types/rift.dm | 2 +-
code/modules/mob/living/brain/brain.dm | 6 +--
.../mob/living/carbon/alien/alien_defense.dm | 10 ++--
.../mob/living/carbon/carbon_defense.dm | 44 ++---------------
code/modules/mob/living/living_defense.dm | 47 ++++++++++++++++---
.../mob/living/silicon/silicon_defense.dm | 4 +-
code/modules/reagents/chemistry/recipes.dm | 4 +-
.../chemistry/recipes/pyrotechnics.dm | 8 ++--
.../surgery/organs/internal/ears/_ears.dm | 4 +-
code/modules/unit_tests/combat.dm | 12 +++++
19 files changed, 165 insertions(+), 140 deletions(-)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
index c8835caed3e..b80f546693d 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
@@ -60,8 +60,6 @@
/// Called from bodypart being removed /obj/item/bodypart/proc/drop_limb(mob/living/carbon/old_owner, special, dismembered)
#define COMSIG_BODYPART_REMOVED "bodypart_removed"
-///from base of mob/living/carbon/soundbang_act(): (list(intensity))
-#define COMSIG_CARBON_SOUNDBANG "carbon_soundbang"
///from /item/organ/proc/Insert() (/obj/item/organ/)
#define COMSIG_CARBON_GAIN_ORGAN "carbon_gain_organ"
///from /item/organ/proc/Remove() (/obj/item/organ/)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
index 40d0f1ae3bd..5d32a627b40 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
@@ -318,6 +318,10 @@
/// From /obj/machinery/gibber/startgibbing(): (mob/living/user, /obj/machinery/gibber, list/results)
#define COMSIG_LIVING_GIBBER_ACT "living_gibber_act"
+/// From /mob/living/get_eye_protection() (list/reflist)
+#define COMSIG_LIVING_GET_EAR_PROTECTION "living_get_ear_protection"
+ #define EAR_PROTECTION_ARG 1
+
/// Sent to the mob when their mind is slaved
#define COMSIG_MOB_ENSLAVED_TO "mob_enslaved_to"
/// From /obj/item/proc/attack_atom: (mob/living/attacker, atom/attacked, list/modifiers)
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 7cdd886694d..10c87301114 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -479,7 +479,7 @@
#define REM REAGENTS_EFFECT_MULTIPLIER //! Shorthand for the above define for ease of use in equations and the like
// Eye protection
-// THese values are additive to determine your overall flash protection.
+// These values are additive to determine your overall flash protection.
#define FLASH_PROTECTION_HYPER_SENSITIVE -2
#define FLASH_PROTECTION_SENSITIVE -1
#define FLASH_PROTECTION_NONE 0
@@ -488,6 +488,29 @@
#define FLASH_PROTECTION_WELDER_SENSITIVE 3
#define FLASH_PROTECTION_WELDER_HYPER_SENSITIVE 4
+/**
+ * Ear protection
+ * These values are additive to determine your overall ear/soundbang protection
+ */
+#define EAR_PROTECTION_NONE 0
+#define EAR_PROTECTION_NORMAL 1
+#define EAR_PROTECTION_HEAVY 2
+#define EAR_PROTECTION_VACUUM 3
+#define EAR_PROTECTION_FULL INFINITY
+
+/**
+ * Soundbang defines
+ * These values are used as argument to determine the strength of the soundbang_act call
+ */
+///Soundbang strength for most things like flashbangs, honkblasts and harm control modules
+#define SOUNDBANG_NORMAL 1
+///Soundbang strength for things like flashbangs in proximity and emagged harm alarm megaphones, cannot be countered by standard ear protection equipment
+#define SOUNDBANG_STRONG 2
+///Soundbang strength for things like changeling shrieks, which can affect robots and aliens as well.
+#define SOUNDBANG_MASSIVE 3
+///Soundbang strength for anything that cannot be stopped unless you're stacked on multiple effects and equipment to counter it (or are simply deaf)
+#define SOUNDBANG_OVERWHELMING 4
+
// AI Toggles
#define AI_CAMERA_LUMINOSITY 5
#define AI_VOX // Comment out if you don't want VOX to be enabled and have players download the voice sounds.
diff --git a/code/datums/components/earprotection.dm b/code/datums/components/earprotection.dm
index 542a1d0c60f..382f3b1db2b 100644
--- a/code/datums/components/earprotection.dm
+++ b/code/datums/components/earprotection.dm
@@ -1,5 +1,5 @@
/datum/component/wearertargeting/earprotection
- signals = list(COMSIG_CARBON_SOUNDBANG)
+ signals = list(COMSIG_LIVING_GET_EAR_PROTECTION)
mobtype = /mob/living/carbon
proctype = PROC_REF(reducebang)
var/reduce_amount = 1
@@ -11,4 +11,4 @@
src.reduce_amount = reduce_amount
/datum/component/wearertargeting/earprotection/proc/reducebang(datum/source, list/reflist)
- reflist[1] -= reduce_amount
+ reflist[EAR_PROTECTION_ARG] -= reduce_amount
diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm
index 41d370d7bdd..cc320f6928e 100644
--- a/code/game/objects/items/grenades/flashbang.dm
+++ b/code/game/objects/items/grenades/flashbang.dm
@@ -46,7 +46,7 @@
if(living_mob.stat == DEAD) //They're dead!
return
living_mob.show_message(span_warning("BANG"), MSG_AUDIBLE)
- var/distance = max(0, get_dist(get_turf(src), turf))
+ var/distance = get_dist(get_turf(src), turf)
var/sweetspot_range = clamp(CEILING(flashbang_range/sweetspot_divider, 1), 0, flashbang_range)
//Flash
@@ -61,19 +61,19 @@
living_mob.dropItemToGround(living_mob.get_inactive_held_item())
//Bang
- if(!distance || loc == living_mob || loc == living_mob.loc)
- living_mob.soundbang_act(2, 20 SECONDS, 10, 15)
+ if(!distance)
+ living_mob.soundbang_act(SOUNDBANG_OVERWHELMING, 20 SECONDS, 10, 15)
return
if(distance <= 1) // Adds more stun as to not prime n' pull (#45381)
- living_mob.soundbang_act(2, 3 SECONDS, 5)
+ living_mob.soundbang_act(SOUNDBANG_STRONG, 3 SECONDS, 5)
return
if(distance <= sweetspot_range)
- living_mob.soundbang_act(1, max(200 / max(1, distance), 60), rand(0, 5))
+ living_mob.soundbang_act(SOUNDBANG_NORMAL, max(20 SECONDS / max(1, distance), 60), rand(0, 5))
return
- if(!living_mob.soundbang_act(1, 0, rand(0, 2)))
+ if(!living_mob.soundbang_act(SOUNDBANG_NORMAL, 0, rand(0, 2)))
return
living_mob.adjust_staggered_up_to(max(200/max(1, distance), 5), 10 SECONDS)
@@ -133,17 +133,17 @@
if(living_mob.stat == DEAD) //They're dead!
return
living_mob.show_message(span_warning("POP"), MSG_AUDIBLE)
- var/distance = max(0, get_dist(get_turf(src), turf))
+ var/distance = get_dist(get_turf(src), turf)
//Flash
if(living_mob.flash_act(affect_silicon = 1))
living_mob.Paralyze(max(10/max(1, distance), 5))
living_mob.Knockdown(max(100/max(1, distance), 60))
//Bang
- if(!distance || loc == living_mob || loc == living_mob.loc)
- living_mob.Paralyze(20)
- living_mob.Knockdown(200)
- living_mob.soundbang_act(1, 200, 10, 15)
+ if(!distance)
+ living_mob.Paralyze(2 SECONDS)
+ living_mob.Knockdown(20 SECONDS)
+ living_mob.soundbang_act(SOUNDBANG_NORMAL, 200, 10, 15)
if(living_mob.apply_damages(brute = 10, burn = 10))
to_chat(living_mob, span_userdanger("The blast from \the [src] bruises and burns you!"))
diff --git a/code/game/objects/items/grenades/hypno.dm b/code/game/objects/items/grenades/hypno.dm
index d77049419ab..13bdf7351ab 100644
--- a/code/game/objects/items/grenades/hypno.dm
+++ b/code/game/objects/items/grenades/hypno.dm
@@ -32,46 +32,38 @@
/obj/item/grenade/hypnotic/proc/bang(turf/turf, mob/living/living_mob)
if(living_mob.stat == DEAD) //They're dead!
return
- var/distance = max(0, get_dist(get_turf(src), turf))
+ var/distance = get_dist(get_turf(src), turf)
//Bang
var/hypno_sound = FALSE
//Hearing protection check
- if(iscarbon(living_mob))
- var/mob/living/carbon/target = living_mob
- var/list/reflist = list(1)
- SEND_SIGNAL(target, COMSIG_CARBON_SOUNDBANG, reflist)
- var/intensity = reflist[1]
- var/ear_safety = target.get_ear_protection()
- var/effect_amount = intensity - ear_safety
- if(effect_amount > 0)
- hypno_sound = TRUE
+ if(living_mob.get_ear_protection() < 0)
+ hypno_sound = TRUE
- if(!distance || loc == living_mob || loc == living_mob.loc)
- living_mob.Paralyze(10)
- living_mob.Knockdown(100)
+ if(!distance)
+ living_mob.Paralyze(1 SECONDS)
+ living_mob.Knockdown(10 SECONDS)
to_chat(living_mob, span_hypnophrase("The sound echoes in your brain..."))
living_mob.adjust_hallucinations(150 SECONDS)
else
if(distance <= 1)
- living_mob.Paralyze(5)
- living_mob.Knockdown(30)
+ living_mob.Paralyze(0.5 SECONDS)
+ living_mob.Knockdown(3 SECONDS)
if(hypno_sound)
to_chat(living_mob, span_hypnophrase("The sound echoes in your brain..."))
living_mob.adjust_hallucinations(150 SECONDS)
//Flash
- if(living_mob.flash_act(affect_silicon = 1))
- living_mob.Paralyze(max(10/max(1, distance), 5))
- living_mob.Knockdown(max(100/max(1, distance), 40))
- if(iscarbon(living_mob))
- var/mob/living/carbon/target = living_mob
- if(target.hypnosis_vulnerable()) //The sound causes the necessary conditions unless the target has mindshield or hearing protection
- target.apply_status_effect(/datum/status_effect/trance, 100, TRUE)
- else
- to_chat(target, span_hypnophrase("The light is so pretty..."))
- target.adjust_drowsiness_up_to(20 SECONDS, 40 SECONDS)
- target.adjust_confusion_up_to(10 SECONDS, 20 SECONDS)
- target.adjust_dizzy_up_to(20 SECONDS, 40 SECONDS)
+ if(!living_mob.flash_act(affect_silicon = TRUE))
+ return
+ living_mob.Paralyze(max(1 SECONDS / (distance || 1), 0.5 SECONDS))
+ living_mob.Knockdown(max(10 SECONDS / (distance || 1), 4 SECONDS))
+ if(living_mob.hypnosis_vulnerable()) //The sound causes the necessary conditions unless the target has mindshield or hearing protection
+ living_mob.apply_status_effect(/datum/status_effect/trance, 10 SECONDS, TRUE)
+ return
+ to_chat(living_mob, span_hypnophrase("The light is so pretty..."))
+ living_mob.adjust_drowsiness_up_to(20 SECONDS, 40 SECONDS)
+ living_mob.adjust_confusion_up_to(10 SECONDS, 20 SECONDS)
+ living_mob.adjust_dizzy_up_to(20 SECONDS, 40 SECONDS)
diff --git a/code/game/objects/items/robot/items/generic.dm b/code/game/objects/items/robot/items/generic.dm
index d282ae01b7f..d709be2067b 100644
--- a/code/game/objects/items/robot/items/generic.dm
+++ b/code/game/objects/items/robot/items/generic.dm
@@ -373,7 +373,7 @@
span_danger("The siren pierces your hearing!"),
)
for(var/mob/living/carbon/carbon in get_hearers_in_view(9, user))
- if(carbon.get_ear_protection())
+ if(carbon.get_ear_protection() > 0)
continue
carbon.adjust_confusion(6 SECONDS)
@@ -386,18 +386,20 @@
to_chat(robot_user.connected_ai, "
[span_notice("NOTICE - Peacekeeping 'HARM ALARM' used by: [user]")]
")
else
user.audible_message("BZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZT")
- for(var/mob/living/carbon/carbon in get_hearers_in_view(9, user))
- var/bang_effect = carbon.soundbang_act(2, 0, 0, 5)
+ for(var/mob/living/living in get_hearers_in_view(9, user))
+ var/bang_effect = living.soundbang_act(SOUNDBANG_STRONG, 0, 0, 5)
switch(bang_effect)
+ if(0)
+ continue
if(1)
- carbon.adjust_confusion(5 SECONDS)
- carbon.adjust_stutter(20 SECONDS)
- carbon.adjust_jitter(20 SECONDS)
- if(2)
- carbon.Paralyze(40)
- carbon.adjust_confusion(10 SECONDS)
- carbon.adjust_stutter(30 SECONDS)
- carbon.adjust_jitter(50 SECONDS)
+ living.adjust_confusion(5 SECONDS)
+ living.adjust_stutter(20 SECONDS)
+ living.adjust_jitter(20 SECONDS)
+ else
+ living.Paralyze(4 SECONDS)
+ living.adjust_confusion(10 SECONDS)
+ living.adjust_stutter(30 SECONDS)
+ living.adjust_jitter(50 SECONDS)
playsound(get_turf(src), 'sound/machines/warning-buzzer.ogg', 130, 3)
COOLDOWN_START(src, alarm_cooldown, HARM_ALARM_NO_SAFETY_COOLDOWN)
user.log_message("used an emagged Cyborg Harm Alarm", LOG_ATTACK)
diff --git a/code/modules/antagonists/changeling/powers/shriek.dm b/code/modules/antagonists/changeling/powers/shriek.dm
index 2a704e0baac..80ce0f232aa 100644
--- a/code/modules/antagonists/changeling/powers/shriek.dm
+++ b/code/modules/antagonists/changeling/powers/shriek.dm
@@ -1,6 +1,7 @@
/datum/action/changeling/resonant_shriek
name = "Resonant Shriek"
- desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses humans, causing them to lose some control over their movements. Best used to stop prey from escaping. Costs 20 chemicals."
+ desc = "Our lungs and vocal cords shift, allowing us to emit a noise that deafens and confuses non-changelings, causing them to lose some control over their movements. \
+ Best used to stop prey from escaping. Doesn't work well in a vacuum. Costs 20 chemicals."
helptext = "Emits a high-frequency sound that confuses and deafens humans to hamper their movement, blows out nearby lights and overloads cyborg sensors."
button_icon_state = "resonant_shriek"
chemical_cost = 20
@@ -14,23 +15,19 @@
if(user.movement_type & VENTCRAWLING)
user.balloon_alert(user, "can't shriek in pipes!")
return FALSE
- for(var/mob/living/M in get_hearers_in_view(4, user))
- if(iscarbon(M))
- var/mob/living/carbon/C = M
- if(!IS_CHANGELING(C))
- C.sound_damage(deafen = 30 SECONDS)
- C.adjust_confusion(25 SECONDS)
- C.set_jitter_if_lower(100 SECONDS)
- else
- SEND_SOUND(C, sound('sound/effects/screech.ogg'))
+ playsound(user, 'sound/effects/screech.ogg', 100)
+ for(var/mob/living/living in get_hearers_in_view(4, user))
+ if(IS_CHANGELING(living) || !living.soundbang_act(SOUNDBANG_MASSIVE, stun_pwr = 0, damage_pwr = 0, deafen_pwr = 1 MINUTES, ignore_deafness = TRUE, send_sound = FALSE))
+ continue
+ if(issilicon(living))
+ living.Paralyze(rand(10 SECONDS, 20 SECONDS))
+ continue
+ living.adjust_confusion(25 SECONDS)
+ living.set_jitter_if_lower(100 SECONDS)
- if(issilicon(M))
- SEND_SOUND(M, sound('sound/items/weapons/flash.ogg'))
- M.Paralyze(rand(100,200))
-
- for(var/obj/machinery/light/L in range(4, user))
- L.on = TRUE
- L.break_light_tube()
+ for(var/obj/machinery/light/light in range(4, user))
+ light.on = TRUE
+ light.break_light_tube()
stoplag()
return TRUE
diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm
index 9ebaf05b1bc..433d87fcbc3 100644
--- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm
+++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm
@@ -630,11 +630,13 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf))
continue
found_intercom.audible_message(message = "[found_intercom] crackles for a split second.", hearing_distance = 3)
playsound(found_intercom, 'sound/items/airhorn/airhorn.ogg', 100, TRUE)
- for(var/mob/living/carbon/honk_victim in ohearers(6, found_intercom))
+ for(var/mob/living/honk_victim in ohearers(6, found_intercom))
+ if(issilicon(honk_victim))
+ continue
var/turf/victim_turf = get_turf(honk_victim)
if(isspaceturf(victim_turf) && !victim_turf.Adjacent(found_intercom)) //Prevents getting honked in space
continue
- if(honk_victim.soundbang_act(intensity = 1, stun_pwr = 20, damage_pwr = 30, deafen_pwr = 60)) //Ear protection will prevent these effects
+ if(honk_victim.soundbang_act(SOUNDBANG_NORMAL, stun_pwr = 20, damage_pwr = 30, deafen_pwr = 60)) //Ear protection will prevent these effects
honk_victim.set_jitter_if_lower(120 SECONDS)
to_chat(honk_victim, span_clown("HOOOOONK!"))
diff --git a/code/modules/fishing/fish/types/rift.dm b/code/modules/fishing/fish/types/rift.dm
index 87e83ccae9b..011a1736379 100644
--- a/code/modules/fishing/fish/types/rift.dm
+++ b/code/modules/fishing/fish/types/rift.dm
@@ -705,7 +705,7 @@
now_fixed = span_noticealien("The psychic noise starts to fade.")
low_threshold_cleared = span_noticealien("The whispers leave you alone.")
- bang_protect = 5
+ bang_protect = EAR_PROTECTION_VACUUM
damage_multiplier = 0.1
visual = TRUE
/// Overlay for the mob sprite because actual organ overlays are a fucking unusable nightmare
diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm
index 54fbb132db2..fc8e60b63e0 100644
--- a/code/modules/mob/living/brain/brain.dm
+++ b/code/modules/mob/living/brain/brain.dm
@@ -58,10 +58,10 @@
return
/mob/living/brain/get_eye_protection()//no eyes
- return 2
+ return FLASH_PROTECTION_WELDER
-/mob/living/brain/get_ear_protection()//no ears
- return 2
+/mob/living/brain/get_ear_protection(ignore_deafness = FALSE)
+ return ..() + EAR_PROTECTION_HEAVY
/mob/living/brain/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, length = 25)
return // no eyes, no flashing
diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm
index e0892cbaf8e..3d613d250b7 100644
--- a/code/modules/mob/living/carbon/alien/alien_defense.dm
+++ b/code/modules/mob/living/carbon/alien/alien_defense.dm
@@ -1,9 +1,9 @@
/mob/living/carbon/alien/get_eye_protection()
- return ..() + 2 //potential cyber implants + natural eye protection
+ return ..() + FLASH_PROTECTION_WELDER //potential cyber implants + natural eye protection
-/mob/living/carbon/alien/get_ear_protection()
- return 2 //no ears
+/mob/living/carbon/alien/get_ear_protection(ignore_deafness = FALSE)
+ return ..() + EAR_PROTECTION_HEAVY //no ears
/mob/living/carbon/alien/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
..(AM, skipcatch = TRUE, hitpush = FALSE)
@@ -87,10 +87,6 @@ In all, this is a lot like the monkey code. /N
return TRUE
-
-/mob/living/carbon/alien/soundbang_act(intensity = 1, stun_pwr = 20, damage_pwr = 5, deafen_pwr = 15)
- return 0
-
/mob/living/carbon/alien/acid_act(acidpwr, acid_volume)
return FALSE//aliens are immune to acid.
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 24011e06e88..6ed5be63713 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -27,15 +27,11 @@
if(deafen)
ears.adjust_temporary_deafness(deafen)
-/mob/living/carbon/get_ear_protection()
- . = ..()
- if(HAS_TRAIT(src, TRAIT_DEAF))
- return INFINITY //For all my homies that can not hear in the world
- var/obj/item/organ/ears/E = get_organ_slot(ORGAN_SLOT_EARS)
- if(!E)
+/mob/living/carbon/get_ear_protection(ignore_deafness = FALSE)
+ var/obj/item/organ/ears/ears = get_organ_slot(ORGAN_SLOT_EARS)
+ if(!ears)
return INFINITY
- else
- . += E.bang_protect
+ return ..() + ears.bang_protect
/mob/living/carbon/is_mouth_covered(check_flags = ALL)
if((check_flags & ITEM_SLOT_HEAD) && head && (head.flags_cover & HEADCOVERSMOUTH))
@@ -517,38 +513,6 @@
else if(damage == 0 && prob(20)) // just enough protection
to_chat(src, span_notice("Something bright flashes in the corner of your vision!"))
-
-/mob/living/carbon/soundbang_act(intensity = 1, stun_pwr = 20, damage_pwr = 5, deafen_pwr = 15)
- var/list/reflist = list(intensity) // Need to wrap this in a list so we can pass a reference
- SEND_SIGNAL(src, COMSIG_CARBON_SOUNDBANG, reflist)
- intensity = reflist[1]
- var/ear_safety = get_ear_protection()
- var/obj/item/organ/ears/ears = get_organ_slot(ORGAN_SLOT_EARS)
- var/effect_amount = intensity - ear_safety
- if(effect_amount <= 0)
- return FALSE
-
- if(stun_pwr)
- Paralyze((stun_pwr*effect_amount)*0.1)
- Knockdown(stun_pwr*effect_amount)
-
- if(ears && (deafen_pwr || damage_pwr))
- var/ear_damage = damage_pwr * effect_amount
- var/deaf = deafen_pwr * effect_amount * 2 SECONDS
- sound_damage(ear_damage, deaf)
-
- . = effect_amount //how soundbanged we are
- SEND_SOUND(src, sound('sound/items/weapons/flash_ring.ogg',0,1,0,250))
-
- if(ears.damage < 5)
- return
- if(ears.damage >= 15 && prob(ears.damage - 5))
- to_chat(src, span_userdanger("You can't hear anything!"))
- // Makes you deaf, enough that you need a proper source of healing, it won't self heal
- // you need earmuffs, inacusiate, or replacement
- ears.set_organ_damage(ears.maxHealth)
- to_chat(src, span_warning("Your ears start to ring[ears.damage >= 15 ? " badly!":"!"]"))
-
/mob/living/carbon/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone)
if(damage_type != BRUTE && damage_type != BURN)
return
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 821ad98dd78..e1cb74d5095 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -40,13 +40,18 @@
return
//this returns the mob's protection against ear damage (0:no protection; 1: some ear protection; 2: has no ears)
-/mob/living/proc/get_ear_protection()
+/mob/living/proc/get_ear_protection(ignore_deafness = FALSE)
+ if(!ignore_deafness && HAS_TRAIT(src, TRAIT_DEAF))
+ return INFINITY //For all my homies that can not hear in the world
+ var/list/sig_protection = list(0)
+ SEND_SIGNAL(src, COMSIG_LIVING_GET_EAR_PROTECTION, sig_protection)
+ var/protection = sig_protection[EAR_PROTECTION_ARG]
var/turf/current_turf = get_turf(src)
var/datum/gas_mixture/environment = current_turf.return_air()
- var/pressure = environment ? environment.return_pressure() : 0
+ var/pressure = environment?.return_pressure()
if(pressure < SOUND_MINIMUM_PRESSURE) //space is empty
- return 1
- return 0
+ protection += EAR_PROTECTION_VACUUM
+ return protection
/**
* Checks if our mob has their mouth covered.
@@ -654,8 +659,38 @@
return TRUE
//called when the mob receives a loud bang
-/mob/living/proc/soundbang_act()
- return FALSE
+/mob/living/proc/soundbang_act(intensity = SOUNDBANG_NORMAL, stun_pwr = 20, damage_pwr = 5, deafen_pwr = 15, ignore_deafness = FALSE, send_sound = TRUE)
+ var/protection = get_ear_protection(ignore_deafness)
+ if(protection >= intensity)
+ return FALSE
+
+ ///The amplitude of the effect is reduced by sound protection, while weakness only makes it worse.
+ var/effect_amount = protection > 0 ? 1 - (protection/intensity) : 1 - protection
+ if(stun_pwr)
+ Paralyze(stun_pwr * effect_amount * 0.1)
+ Knockdown(stun_pwr * effect_amount)
+
+ var/obj/item/organ/ears/ears = get_organ_slot(ORGAN_SLOT_EARS)
+
+ . = effect_amount //how soundbanged we are
+ if(!ears || !(deafen_pwr || damage_pwr))
+ return
+
+ var/ear_damage = damage_pwr * effect_amount
+ var/deaf = deafen_pwr * effect_amount
+ sound_damage(ear_damage, deaf)
+
+ if(send_sound)
+ SEND_SOUND(src, sound('sound/items/weapons/flash_ring.ogg',0, 1, 0, 250))
+
+ if(ears.damage >= 15 && prob(ears.damage - 5))
+ to_chat(src, span_userdanger("You can't hear anything!"))
+ // Makes you deaf, enough that you need a proper source of healing, it won't self heal
+ // you need earmuffs, inacusiate, or replacement
+ ears.set_organ_damage(ears.maxHealth)
+ else if(ears.damage >= 5)
+ to_chat(src, span_warning("Your ears start to ring[ears.damage >= 15 ? " badly!":"!"]"))
+
//to damage the clothes worn by a mob
/mob/living/proc/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone)
diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm
index 25cdc81fda8..0a5e3191190 100644
--- a/code/modules/mob/living/silicon/silicon_defense.dm
+++ b/code/modules/mob/living/silicon/silicon_defense.dm
@@ -2,8 +2,8 @@
/mob/living/silicon/grippedby(mob/living/carbon/user, instant = FALSE)
return //can't upgrade a simple pull into a more aggressive grab.
-/mob/living/silicon/get_ear_protection()//no ears
- return 2
+/mob/living/silicon/get_ear_protection(ignore_deafness = FALSE)
+ return ..() + EAR_PROTECTION_HEAVY //no ears
/mob/living/silicon/attack_alien(mob/living/carbon/alien/adult/user, list/modifiers)
. = ..()
diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm
index f1b3dfdbff2..9cd650d67ff 100644
--- a/code/modules/reagents/chemistry/recipes.dm
+++ b/code/modules/reagents/chemistry/recipes.dm
@@ -348,8 +348,8 @@
/datum/chemical_reaction/proc/explode_deafen(datum/reagents/holder, datum/equilibrium/equilibrium, power = 3, stun = 20, range = 2)
var/location = get_turf(holder.my_atom)
playsound(location, 'sound/effects/bang.ogg', 25, TRUE)
- for(var/mob/living/carbon/carbon_mob in get_hearers_in_view(range, location))
- carbon_mob.soundbang_act(1, stun, power)
+ for(var/mob/living/living in get_hearers_in_view(range, location))
+ living.soundbang_act(SOUNDBANG_NORMAL, stun, power)
//Spews out the inverse of the chems in the beaker of the products/reactants only
/datum/chemical_reaction/proc/explode_invert_smoke(datum/reagents/holder, datum/equilibrium/equilibrium, force_range = 0, clear_products = TRUE, clear_reactants = TRUE, accept_impure = TRUE)
diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
index a10a6902ccb..c891b012d56 100644
--- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
+++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
@@ -414,8 +414,8 @@
holder.remove_reagent(/datum/reagent/sonic_powder, created_volume * 3)
var/location = get_turf(holder.my_atom)
playsound(location, 'sound/effects/bang.ogg', 25, TRUE)
- for(var/mob/living/carbon/C in get_hearers_in_view(created_volume/3, location))
- C.soundbang_act(1, 100, rand(0, 5))
+ for(var/mob/living/living in get_hearers_in_view(created_volume/3, location))
+ living.soundbang_act(1, 10 SECONDS, rand(0, 5))
/datum/chemical_reaction/sonic_powder_deafen
required_reagents = list(/datum/reagent/sonic_powder = 1)
@@ -425,8 +425,8 @@
/datum/chemical_reaction/sonic_powder_deafen/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume)
var/location = get_turf(holder.my_atom)
playsound(location, 'sound/effects/bang.ogg', 25, TRUE)
- for(var/mob/living/carbon/C in get_hearers_in_view(created_volume/10, location))
- C.soundbang_act(1, 100, rand(0, 5))
+ for(var/mob/living/living in get_hearers_in_view(created_volume/10, location))
+ living.soundbang_act(1, 10 SECONDS, rand(0, 5))
/datum/chemical_reaction/phlogiston
results = list(/datum/reagent/phlogiston = 3)
diff --git a/code/modules/surgery/organs/internal/ears/_ears.dm b/code/modules/surgery/organs/internal/ears/_ears.dm
index 10f2c5af0c1..83871e9d73b 100644
--- a/code/modules/surgery/organs/internal/ears/_ears.dm
+++ b/code/modules/surgery/organs/internal/ears/_ears.dm
@@ -22,7 +22,7 @@
// without external aid (earmuffs, drugs)
/// Resistance against loud noises
- var/bang_protect = 0
+ var/bang_protect = EAR_PROTECTION_NONE
/// Multiplier for both long term and short term ear damage
var/damage_multiplier = 1
@@ -276,7 +276,7 @@
name = "volume-adjusting cybernetic ears"
icon_state = "ears-c-u"
desc = "Advanced cybernetic ears capable of dampening loud noises to protect their user."
- bang_protect = 1
+ bang_protect = EAR_PROTECTION_NORMAL
damage_multiplier = 0.5
// "X-ray ears" that let you hear through walls
diff --git a/code/modules/unit_tests/combat.dm b/code/modules/unit_tests/combat.dm
index d645df98cbf..4ce7b5abdd6 100644
--- a/code/modules/unit_tests/combat.dm
+++ b/code/modules/unit_tests/combat.dm
@@ -138,3 +138,15 @@
attacker.set_species(/datum/species/monkey)
attacker.ClickOn(victim)
TEST_ASSERT_NOTEQUAL(victim.getBruteLoss(), 0, "Victim took no brute damage from being bit by a handcuffed monkey, which is incorrect, as it's a bite attack")
+
+/// Tests that soundbang_act (and therefore sound_damage) works correctly
+/datum/unit_test/soundbang
+
+/datum/unit_test/soundbang/Run()
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent)
+ victim.soundbang_act(intensity = SOUNDBANG_NORMAL, damage_pwr = 10, deafen_pwr = 20 SECONDS)
+ TEST_ASSERT_EQUAL(victim.get_organ_loss(ORGAN_SLOT_EARS), 10, "victim didn't take the right amount of ears damage")
+ TEST_ASSERT(HAS_TRAIT_FROM(victim, TRAIT_DEAF, EAR_DAMAGE), "victim wasn't temporarily deafened")
+ var/obj/item/organ/ears/ears = victim.get_organ_slot(ORGAN_SLOT_EARS)
+ ears.adjust_temporary_deafness(-20 SECONDS)
+ TEST_ASSERT(!HAS_TRAIT_FROM(victim, TRAIT_DEAF, EAR_DAMAGE), "victim hasn't recovered from temprorary deafness")