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"