diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 0fddd81833f..13adfc87416 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -78,6 +78,7 @@
#define INIT_ORDER_STICKY_BAN -10
#define INIT_ORDER_LIGHTING -20
#define INIT_ORDER_SHUTTLE -21
+#define INIT_ORDER_OUTPUTS -22
#define INIT_ORDER_MINOR_MAPPING -40
#define INIT_ORDER_PATH -50
#define INIT_ORDER_PERSISTENCE -100
diff --git a/code/controllers/subsystem/outputs.dm b/code/controllers/subsystem/outputs.dm
new file mode 100644
index 00000000000..3c8eca78504
--- /dev/null
+++ b/code/controllers/subsystem/outputs.dm
@@ -0,0 +1,10 @@
+SUBSYSTEM_DEF(outputs)
+ name = "Outputs"
+ init_order = INIT_ORDER_OUTPUTS
+ flags = SS_NO_FIRE
+ var/list/outputs = list()
+
+/datum/controller/subsystem/outputs/Initialize(timeofday)
+ for(var/A in subtypesof(/datum/outputs))
+ outputs[A] = new A
+ return ..()
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
index 29b074c3a7d..af7cff6a0fb 100644
--- a/code/datums/components/squeak.dm
+++ b/code/datums/components/squeak.dm
@@ -1,6 +1,5 @@
/datum/component/squeak
- var/static/list/default_squeak_sounds = list('sound/items/toysqueak1.ogg'=1, 'sound/items/toysqueak2.ogg'=1, 'sound/items/toysqueak3.ogg'=1)
- var/list/override_squeak_sounds
+ var/datum/outputs/squeak_datum
var/squeak_chance = 100
var/volume = 30
@@ -12,7 +11,7 @@
var/last_use = 0
var/use_delay = 20
-/datum/component/squeak/Initialize(custom_sounds, volume_override, chance_override, step_delay_override, use_delay_override)
+/datum/component/squeak/Initialize(custom_datum, volume_override, chance_override, step_delay_override, use_delay_override)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_BLOB_ACT, COMSIG_ATOM_HULK_ATTACK, COMSIG_PARENT_ATTACKBY), .proc/play_squeak)
@@ -28,7 +27,7 @@
if(istype(parent, /obj/item/clothing/shoes))
RegisterSignal(parent, COMSIG_SHOES_STEP_ACTION, .proc/step_squeak)
- override_squeak_sounds = custom_sounds
+ squeak_datum = SSoutputs.outputs[custom_datum]
if(chance_override)
squeak_chance = chance_override
if(volume_override)
@@ -40,10 +39,10 @@
/datum/component/squeak/proc/play_squeak()
if(prob(squeak_chance))
- if(!override_squeak_sounds)
- playsound(parent, pickweight(default_squeak_sounds), volume, 1, -1)
+ if(!squeak_datum)
+ CRASH("Squeak datum attempted to play missing datum")
else
- playsound(parent, pickweight(override_squeak_sounds), volume, 1, -1)
+ playsound(parent, squeak_datum, volume, 1, -1)
/datum/component/squeak/proc/step_squeak()
if(steps > step_delay)
diff --git a/code/datums/outputs.dm b/code/datums/outputs.dm
new file mode 100644
index 00000000000..d1bee4d5b58
--- /dev/null
+++ b/code/datums/outputs.dm
@@ -0,0 +1,109 @@
+//NOTE: When adding new sounds here, check to make sure they haven't been added already via CTRL + F.
+
+/datum/outputs
+ var/text = ""
+ var/list/sounds = 'sound/items/airhorn.ogg' //can be either a sound path or a WEIGHTED list, put multiple for random selection between sounds
+ var/mutable_appearance/vfx = list('icons/sound_icon.dmi',"circle", HUD_LAYER) //syntax: icon, icon_state, layer
+
+/datum/outputs/New()
+ vfx = mutable_appearance(vfx[1],vfx[2],vfx[3])
+
+/datum/outputs/proc/send_info(mob/receiver, turf/turf_source, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE)
+ var/sound/sound_output
+ //Pick sound
+ if(islist(sounds))
+ if(sounds.len)
+ var/soundin = pickweight(sounds)
+ sound_output = sound(get_sfx(soundin))
+ else
+ sound_output = sound(get_sfx(sounds))
+ //Process sound
+ if(sound_output)
+ sound_output.wait = 0 //No queue
+ sound_output.channel = channel || open_sound_channel()
+ sound_output.volume = vol
+
+ if(vary)
+ if(frequency)
+ sound_output.frequency = frequency
+ else
+ sound_output.frequency = get_rand_frequency()
+
+ if(isturf(turf_source))
+ var/turf/T = get_turf(receiver)
+
+ //sound volume falloff with distance
+ var/distance = get_dist(T, turf_source)
+
+ sound_output.volume -= max(distance - world.view, 0) * 2 //multiplicative falloff to add on top of natural audio falloff.
+
+ if(pressure_affected)
+ //Atmosphere affects sound
+ var/pressure_factor = 1
+ var/datum/gas_mixture/hearer_env = T.return_air()
+ var/datum/gas_mixture/source_env = turf_source.return_air()
+
+ if(hearer_env && source_env)
+ var/pressure = min(hearer_env.return_pressure(), source_env.return_pressure())
+ if(pressure < ONE_ATMOSPHERE)
+ pressure_factor = max((pressure - SOUND_MINIMUM_PRESSURE)/(ONE_ATMOSPHERE - SOUND_MINIMUM_PRESSURE), 0)
+ else //space
+ pressure_factor = 0
+
+ if(distance <= 1)
+ pressure_factor = max(pressure_factor, 0.15) //touching the source of the sound
+
+ sound_output.volume *= pressure_factor
+ //End Atmosphere affecting sound
+
+ if(sound_output.volume <= 0)
+ return //No sound
+
+ var/dx = turf_source.x - T.x // Hearing from the right/left
+ sound_output.x = dx
+ var/dz = turf_source.y - T.y // Hearing from infront/behind
+ sound_output.z = dz
+ // The y value is for above your head, but there is no ceiling in 2d spessmens.
+ sound_output.y = 1
+ sound_output.falloff = (falloff ? falloff : FALLOFF_SOUNDS)
+
+ receiver.display_output(sound_output, vfx, text, turf_source, vol, vary, frequency, falloff, channel, pressure_affected)
+
+/datum/outputs/bikehorn
+ text = "You hear a HONK."
+ sounds = 'sound/items/bikehorn.ogg'
+
+/datum/outputs/airhorn
+ text = "You hear the violent blaring of an airhorn."
+ sounds = 'sound/items/airhorn2.ogg'
+
+/datum/outputs/alarm
+ text = "You hear a blaring alarm."
+ sounds = 'sound/machines/alarm.ogg'
+
+/datum/outputs/squeak
+ text = "You hear a squeak."
+ sounds = 'sound/effects/mousesqueek.ogg'
+
+/datum/outputs/clownstep
+ sounds = list('sound/effects/clownstep1.ogg' = 1,'sound/effects/clownstep2.ogg' = 1)
+
+/datum/outputs/bite
+ text = "You hear ravenous biting."
+ sounds = 'sound/weapons/bite.ogg'
+
+/datum/outputs/demonattack
+ text = "You hear a terrifying, unholy noise."
+ sounds = 'sound/magic/demon_attack1.ogg'
+
+/datum/outputs/slash
+ text = "You hear a slashing noise."
+ sounds = 'sound/weapons/slash.ogg'
+
+/datum/outputs/punch
+ text = "You hear a punch."
+ sounds = 'sound/effects/hit_punch.ogg'
+
+/datum/outputs/squelch
+ text = "You hear a horrendous squelching sound."
+ sounds = 'sound/effects/blobattack.ogg'
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index fb044b1ce79..36328ebaa9e 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -439,4 +439,4 @@
skew *= 2
animate(src, alpha = 0, transform = skew, time = duration)
else
- return INITIALIZE_HINT_QDEL
+ return INITIALIZE_HINT_QDEL
\ No newline at end of file
diff --git a/code/game/objects/items/clown_items.dm b/code/game/objects/items/clown_items.dm
index 01dbffb44b0..a4a429fc96e 100644
--- a/code/game/objects/items/clown_items.dm
+++ b/code/game/objects/items/clown_items.dm
@@ -153,7 +153,7 @@
/obj/item/bikehorn/Initialize()
. = ..()
- AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50)
+ AddComponent(/datum/component/squeak, /datum/outputs/bikehorn, 50)
/obj/item/bikehorn/attack(mob/living/carbon/M, mob/living/carbon/user)
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "honk", /datum/mood_event/honk)
@@ -172,7 +172,7 @@
/obj/item/bikehorn/airhorn/Initialize()
. = ..()
- AddComponent(/datum/component/squeak, list('sound/items/airhorn2.ogg'=1), 50)
+ AddComponent(/datum/component/squeak, /datum/outputs/airhorn, 50)
//golden bikehorn
/obj/item/bikehorn/golden
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index b5d86402c21..e2063edac8d 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -369,14 +369,14 @@
icon_state = "carpplush"
item_state = "carp_plushie"
attack_verb = list("bitten", "eaten", "fin slapped")
- squeak_override = list('sound/weapons/bite.ogg'=1)
+ squeak_override = /datum/outputs/bite
/obj/item/toy/plush/bubbleplush
name = "\improper Bubblegum plushie"
desc = "The friendly red demon that gives good miners gifts."
icon_state = "bubbleplush"
attack_verb = list("rent")
- squeak_override = list('sound/magic/demon_attack1.ogg'=1)
+ squeak_override = /datum/outputs/demonattack
/obj/item/toy/plush/plushvar
name = "\improper Ratvar plushie"
@@ -459,7 +459,7 @@
say("NO! I will not be banished again...")
P.say(pick("Ha.", "Ra'sha fonn dest.", "You fool. To come here."))
playsound(src, 'sound/magic/clockwork/anima_fragment_death.ogg', 62, TRUE, frequency = 2)
- playsound(P, 'sound/magic/demon_attack1.ogg', 50, TRUE, frequency = 2)
+ playsound(P, /datum/outputs/demonattack, 50, TRUE, frequency = 2)
explosion(src, 0, 0, 1)
qdel(src)
P.clashing = FALSE
@@ -488,7 +488,7 @@
icon_state = "plushie_lizard"
item_state = "plushie_lizard"
attack_verb = list("clawed", "hissed", "tail slapped")
- squeak_override = list('sound/weapons/slash.ogg' = 1)
+ squeak_override = /datum/outputs/slash
/obj/item/toy/plush/snakeplushie
name = "snake plushie"
@@ -496,7 +496,7 @@
icon_state = "plushie_snake"
item_state = "plushie_snake"
attack_verb = list("bitten", "hissed", "tail slapped")
- squeak_override = list('sound/weapons/bite.ogg' = 1)
+ squeak_override = /datum/outputs/bite
/obj/item/toy/plush/nukeplushie
name = "operative plushie"
@@ -504,7 +504,7 @@
icon_state = "plushie_nuke"
item_state = "plushie_nuke"
attack_verb = list("shot", "nuked", "detonated")
- squeak_override = list('sound/effects/hit_punch.ogg' = 1)
+ squeak_override = /datum/outputs/punch
/obj/item/toy/plush/slimeplushie
name = "slime plushie"
@@ -512,7 +512,7 @@
icon_state = "plushie_slime"
item_state = "plushie_slime"
attack_verb = list("blorbled", "slimed", "absorbed")
- squeak_override = list('sound/effects/blobattack.ogg' = 1)
+ squeak_override = /datum/outputs/squelch
gender = FEMALE //given all the jokes and drawings, I'm not sure the xenobiologists would make a slimeboy
/obj/item/toy/plush/awakenedplushie
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index c4302568cc1..00afae70367 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -986,15 +986,20 @@
icon = 'icons/obj/toy.dmi'
icon_state = "nuketoyidle"
w_class = WEIGHT_CLASS_SMALL
+ var/datum/outputs/output
var/cooldown = 0
+/obj/item/toy/nuke/Initialize()
+ . = ..()
+ output = SSoutputs.outputs[/datum/outputs/alarm]
+
/obj/item/toy/nuke/attack_self(mob/user)
if (cooldown < world.time)
cooldown = world.time + 1800 //3 minutes
user.visible_message("[user] presses a button on [src].", "You activate [src], it plays a loud noise!", "You hear the click of a button.")
sleep(5)
icon_state = "nuketoy"
- playsound(src, 'sound/machines/alarm.ogg', 100, 0)
+ playsound(src, output, 100, 0)
sleep(135)
icon_state = "nuketoycool"
sleep(cooldown - world.time)
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 9a471f70c1b..1a09cea0c15 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -1,4 +1,4 @@
-/proc/playsound(atom/source, soundin, vol as num, vary, extrarange as num, falloff, frequency = null, channel = 0, pressure_affected = TRUE, ignore_walls = TRUE)
+/proc/playsound(atom/source, input, vol as num, vary, extrarange as num, falloff, frequency = null, channel = 0, pressure_affected = TRUE, ignore_walls = TRUE)
if(isarea(source))
throw EXCEPTION("playsound(): source is an area")
return
@@ -7,12 +7,11 @@
if (!turf_source)
return
-
+
//allocate a channel if necessary now so its the same for everyone
channel = channel || open_sound_channel()
// Looping through the player list has the added bonus of working for mobs inside containers
- var/sound/S = sound(get_sfx(soundin))
var/maxdistance = (world.view + extrarange)
var/z = turf_source.z
var/list/listeners = SSmobs.clients_by_zlevel[z]
@@ -21,12 +20,21 @@
for(var/P in listeners)
var/mob/M = P
if(get_dist(M, turf_source) <= maxdistance)
- M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S)
+ sound_or_datum(M, turf_source, input, vol, vary, frequency, falloff, channel, pressure_affected)
for(var/P in SSmobs.dead_players_by_zlevel[z])
var/mob/M = P
if(get_dist(M, turf_source) <= maxdistance)
- M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S)
+ sound_or_datum(M, turf_source, input, vol, vary, frequency, falloff, channel, pressure_affected)
+/proc/sound_or_datum(mob/receiver, turf/turf_source, input, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE)
+ if(istype(input,/datum))
+ var/datum/outputs/O = input
+ O.send_info(receiver, turf_source, vol, vary, frequency, falloff, channel, pressure_affected)
+ else
+ var/sound/S = sound(get_sfx(input))
+ receiver.playsound_local(turf_source, input, vol, vary, frequency, falloff, channel, pressure_affected, S)
+
+//kept for legacy support and uploaded admin sounds
/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S)
if(!client || !can_hear())
return
diff --git a/code/modules/clothing/shoes/bananashoes.dm b/code/modules/clothing/shoes/bananashoes.dm
index b6348948050..18b203a5e93 100644
--- a/code/modules/clothing/shoes/bananashoes.dm
+++ b/code/modules/clothing/shoes/bananashoes.dm
@@ -11,7 +11,7 @@
/obj/item/clothing/shoes/clown_shoes/banana_shoes/Initialize()
. = ..()
AddComponent(/datum/component/material_container, list(MAT_BANANIUM), 200000, TRUE, /obj/item/stack)
- AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 75)
+ AddComponent(/datum/component/squeak, /datum/outputs/bikehorn, 75)
if(always_noslip)
clothing_flags |= NOSLIP
diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm
index 7dc7a8d11c0..3eb5aefc48f 100644
--- a/code/modules/clothing/shoes/miscellaneous.dm
+++ b/code/modules/clothing/shoes/miscellaneous.dm
@@ -81,7 +81,7 @@
/obj/item/clothing/shoes/clown_shoes/Initialize()
. = ..()
- AddComponent(/datum/component/squeak, list('sound/effects/clownstep1.ogg'=1,'sound/effects/clownstep2.ogg'=1), 50)
+ AddComponent(/datum/component/squeak, /datum/outputs/clownstep, 50)
/obj/item/clothing/shoes/clown_shoes/equipped(mob/user, slot)
. = ..()
diff --git a/code/modules/clothing/under/jobs/civilian.dm b/code/modules/clothing/under/jobs/civilian.dm
index fe368376c5f..59e991d19ac 100644
--- a/code/modules/clothing/under/jobs/civilian.dm
+++ b/code/modules/clothing/under/jobs/civilian.dm
@@ -123,7 +123,7 @@
/obj/item/clothing/under/rank/clown/Initialize()
. = ..()
- AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50)
+ AddComponent(/datum/component/squeak, /datum/outputs/bikehorn, 50)
/obj/item/clothing/under/rank/head_of_personnel
desc = "It's a jumpsuit worn by someone who works in the position of \"Head of Personnel\"."
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index c56ea07ca31..b6f369cb3b8 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1210,6 +1210,22 @@
mob_pickup(user)
return TRUE
+/mob/living/display_output(sound/S, mutable_appearance/vfx, text, turf/turf_source, vol as num)
+ . = ..()
+ //Process icon
+ if(vfx && audiolocation)
+ var/image/sound_icon = image(vfx)
+ sound_icon.loc = turf_source
+ if(vol && S)
+ sound_icon.alpha = sound_icon.alpha * (vol / 100)
+ client.images += sound_icon
+ addtimer(CALLBACK(src, .proc/remove_image, sound_icon), 7)
+
+/mob/living/proc/remove_image(sound_image)
+ if(sound_image && client)
+ client.images -= sound_image
+ qdel(sound_image)
+
/mob/living/proc/get_static_viruses() //used when creating blood and other infective objects
if(!LAZYLEN(diseases))
return
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 02a55dbfcc9..7f5ab66f4c7 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -29,8 +29,8 @@
var/lying = 0 //number of degrees. DO NOT USE THIS IN CHECKS. CHECK FOR MOBILITY FLAGS INSTEAD!!
var/lying_prev = 0 //last value of lying on update_mobility
+ var/audiolocation = FALSE
var/confused = 0 //Makes the mob move in random directions.
-
var/hallucination = 0 //Directly affects how long a mob will hallucinate for
var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out.
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 90c5526132d..4544b52941b 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -398,4 +398,4 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
if(.)
return .
- . = ..()
+ . = ..()
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index 2ccaa9fc641..8638472fb4b 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -28,7 +28,7 @@
/mob/living/simple_animal/mouse/Initialize()
. = ..()
- AddComponent(/datum/component/squeak, list('sound/effects/mousesqueek.ogg'=1), 100)
+ AddComponent(/datum/component/squeak, /datum/outputs/squeak, 100)
if(!body_color)
body_color = pick( list("brown","gray","white") )
icon_state = "mouse_[body_color]"
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 2d7f99d6418..6a9fada2979 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -105,6 +105,18 @@
else
to_chat(src, msg)
+/mob/proc/display_output(sound/S, mutable_appearance/vfx, text, turf/turf_source, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE)
+ if(!can_hear())
+ return
+ //Process sound
+ if(S)
+ SEND_SOUND(src, S)
+ //Process text
+ if(text)
+ to_chat(src, "[text]")
+ //to whoever sees this: icons are handled in living.dm
+
+
// Show a message to all player mobs who sees this atom
// Show a message to the src mob (if the src is a mob)
// Use for atoms performing visible actions
diff --git a/icons/sound_icon.dmi b/icons/sound_icon.dmi
new file mode 100644
index 00000000000..d614932dc31
Binary files /dev/null and b/icons/sound_icon.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index b1e47db26d9..7fc5ca3a339 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -241,6 +241,7 @@
#include "code\controllers\subsystem\moods.dm"
#include "code\controllers\subsystem\nightshift.dm"
#include "code\controllers\subsystem\npcpool.dm"
+#include "code\controllers\subsystem\outputs.dm"
#include "code\controllers\subsystem\overlays.dm"
#include "code\controllers\subsystem\pai.dm"
#include "code\controllers\subsystem\parallax.dm"
@@ -302,6 +303,7 @@
#include "code\datums\mutations.dm"
#include "code\datums\numbered_display.dm"
#include "code\datums\outfit.dm"
+#include "code\datums\outputs.dm"
#include "code\datums\position_point_vector.dm"
#include "code\datums\profiling.dm"
#include "code\datums\progressbar.dm"