diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index 3ee9cb364a..32e1759ed4 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -371,4 +371,15 @@ var/global/list/##LIST_NAME = list();\
#define FONT_HUGE(X) "[X]"
-#define FONT_GIANT(X) "[X]"
\ No newline at end of file
+#define FONT_GIANT(X) "[X]"
+
+#define VOLUME_CHANNEL_MASTER "Master"
+#define VOLUME_CHANNEL_AMBIENCE "Ambience"
+#define VOLUME_CHANNEL_ALARMS "Alarms"
+
+// Make sure you update this or clients won't be able to adjust the channel
+GLOBAL_LIST_INIT(all_volume_channels, list(
+ VOLUME_CHANNEL_MASTER,
+ VOLUME_CHANNEL_AMBIENCE,
+ VOLUME_CHANNEL_ALARMS,
+))
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 8515f282c5..21e94e38d4 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -365,8 +365,11 @@ var/list/mob/living/forced_ambiance_list = new
/area/proc/play_ambience(var/mob/living/L, initial = TRUE)
// Ambience goes down here -- make sure to list each area seperately for ease of adding things in later, thanks! Note: areas adjacent to each other should have the same sounds to prevent cutoff when possible.- LastyScratch
- if(!(L && L.is_preference_enabled(/datum/client_preference/play_ambiance))) return
-
+ if(!(L && L.is_preference_enabled(/datum/client_preference/play_ambiance)))
+ return
+
+ var/volume_mod = L.get_preference_volume_channel(VOLUME_CHANNEL_AMBIENCE)
+
// If we previously were in an area with force-played ambiance, stop it.
if((L in forced_ambiance_list) && initial)
L << sound(null, channel = CHANNEL_AMBIENCE_FORCED)
@@ -380,13 +383,14 @@ var/list/mob/living/forced_ambiance_list = new
var/sound/chosen_ambiance = pick(forced_ambience)
if(!istype(chosen_ambiance))
chosen_ambiance = sound(chosen_ambiance, repeat = 1, wait = 0, volume = 25, channel = CHANNEL_AMBIENCE_FORCED)
+ chosen_ambiance.volume *= volume_mod
L << chosen_ambiance
else
L << sound(null, channel = CHANNEL_AMBIENCE_FORCED)
else if(src.ambience.len && prob(35))
if((world.time >= L.client.time_last_ambience_played + 1 MINUTE))
var/sound = pick(ambience)
- L << sound(sound, repeat = 0, wait = 0, volume = 50, channel = CHANNEL_AMBIENCE)
+ L << sound(sound, repeat = 0, wait = 0, volume = 50 * volume_mod, channel = CHANNEL_AMBIENCE)
L.client.time_last_ambience_played = world.time
/area/proc/gravitychange(var/gravitystate = 0)
diff --git a/code/game/machinery/fire_alarm.dm b/code/game/machinery/fire_alarm.dm
index fa95fed959..a21982f7da 100644
--- a/code/game/machinery/fire_alarm.dm
+++ b/code/game/machinery/fire_alarm.dm
@@ -208,8 +208,7 @@ FIRE ALARM
for(var/obj/machinery/firealarm/FA in area)
fire_alarm.triggerAlarm(loc, FA, duration, hidden = alarms_hidden)
update_icon()
- playsound(src, 'sound/machines/airalarm.ogg', 25, 0, 4)
- return
+ playsound(src, 'sound/machines/airalarm.ogg', 25, 0, 4, volume_channel = VOLUME_CHANNEL_ALARMS)
/obj/machinery/firealarm/proc/set_security_level(var/newlevel)
if(seclevel != newlevel)
diff --git a/code/game/sound.dm b/code/game/sound.dm
index c6918beba9..5364d10105 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, is_global, frequency = null, channel = 0, pressure_affected = TRUE, ignore_walls = TRUE, preference = null)
+/proc/playsound(atom/source, soundin, vol as num, vary, extrarange as num, falloff, is_global, frequency = null, channel = 0, pressure_affected = TRUE, ignore_walls = TRUE, preference = null, volume_channel = null)
if(isarea(source))
throw EXCEPTION("playsound(): source is an area")
return
@@ -23,9 +23,9 @@
if(distance <= maxdistance)
if(T && T.z == turf_source.z)
- M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, is_global, channel, pressure_affected, S, preference)
+ M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, is_global, channel, pressure_affected, S, preference, volume_channel)
-/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, is_global, channel = 0, pressure_affected = TRUE, sound/S, preference)
+/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, is_global, channel = 0, pressure_affected = TRUE, sound/S, preference, volume_channel = null)
if(!client || ear_deaf > 0)
return
if(preference && !client.is_preference_enabled(preference))
@@ -36,6 +36,11 @@
S.wait = 0 //No queue
S.channel = channel || open_sound_channel()
+
+ // I'm not sure if you can modify S.volume, but I'd rather not try to find out what
+ // horrible things lurk in BYOND's internals, so we're just gonna do vol *=
+ vol *= client.get_preference_volume_channel(volume_channel)
+ vol *= client.get_preference_volume_channel(VOLUME_CHANNEL_MASTER)
S.volume = vol
if(vary)
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index 163096ecda..2e20f5d531 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -24,6 +24,7 @@
var/time_died_as_mouse = null //when the client last died as a mouse
var/datum/tooltip/tooltips = null
var/datum/chatOutput/chatOutput
+ // var/datum/volume_panel/volume_panel = null // Initialized by /client/verb/volume_panel()
var/chatOutputLoadedAt
var/adminhelped = 0
diff --git a/code/modules/client/preference_setup/volume_sliders/01_volume.dm b/code/modules/client/preference_setup/volume_sliders/01_volume.dm
new file mode 100644
index 0000000000..4458573ab2
--- /dev/null
+++ b/code/modules/client/preference_setup/volume_sliders/01_volume.dm
@@ -0,0 +1,107 @@
+/datum/category_group/player_setup_category/volume_sliders
+ name = "Sound"
+ sort_order = 7
+ category_item_type = /datum/category_item/player_setup_item/volume_sliders
+
+/datum/category_item/player_setup_item/volume_sliders/volume
+ name = "General Volume"
+ sort_order = 1
+
+/datum/category_item/player_setup_item/volume_sliders/volume/load_preferences(var/savefile/S)
+ S["volume_channels"] >> pref.volume_channels
+
+/datum/category_item/player_setup_item/volume_sliders/volume/save_preferences(var/savefile/S)
+ S["volume_channels"] << pref.volume_channels
+
+/datum/category_item/player_setup_item/volume_sliders/volume/sanitize_preferences()
+ if(isnull(pref.volume_channels))
+ pref.volume_channels = list()
+
+ for(var/channel in pref.volume_channels)
+ if(!(channel in GLOB.all_volume_channels))
+ // Channel no longer exists, yeet
+ pref.volume_channels.Remove(channel)
+
+ for(var/channel in GLOB.all_volume_channels)
+ if(!(channel in pref.volume_channels))
+ pref.volume_channels["[channel]"] = 1
+ else
+ pref.volume_channels["[channel]"] = clamp(pref.volume_channels["[channel]"], 0, 2)
+
+/datum/category_item/player_setup_item/volume_sliders/volume/content(var/mob/user)
+ . += "Volume Settings
"
+ for(var/channel in pref.volume_channels)
+ . += "[channel]: [pref.volume_channels[channel] * 100]%
"
+ . += "
"
+
+/datum/category_item/player_setup_item/volume_sliders/volume/OnTopic(var/href, var/list/href_list, var/mob/user)
+ if(href_list["change_volume"])
+ if(CanUseTopic(user))
+ var/channel = href_list["change_volume"]
+ if(!(channel in pref.volume_channels))
+ pref.volume_channels["[channel]"] = 1
+ var/value = input("Choose your volume for [channel] (0-200%)", "[channel] volume", (pref.volume_channels[channel] * 100))
+ if(isnum(value))
+ value = CLAMP(value, 0, 200)
+ pref.volume_channels["[channel]"] = (value / 100)
+ return TOPIC_REFRESH
+ return ..()
+
+/mob/proc/get_preference_volume_channel(volume_channel)
+ if(!client)
+ return 0
+ return client.get_preference_volume_channel(volume_channel)
+
+/client/proc/get_preference_volume_channel(volume_channel)
+ if(!volume_channel || !prefs)
+ return 1
+ if(!(volume_channel in prefs.volume_channels))
+ prefs.volume_channels["[volume_channel]"] = 1
+ return prefs.volume_channels["[volume_channel]"]
+
+
+// Neat little volume adjuster thing in case you don't wanna touch preferences by hand you lazy fuck
+// Currently unimplemented due to lack of TGUI v4
+// /datum/volume_panel
+// /datum/volume_panel/tgui_state(mob/user)
+// return GLOB.tgui_always_state
+
+// /datum/volume_panel/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
+// ui = SStgui.try_update_ui(user, src, ui)
+// if(!ui)
+// ui = new(user, src, "VolumePanel", "Volume Panel")
+// ui.open()
+
+// /datum/volume_panel/tgui_data(mob/user)
+// if(!user.client || !user.client.prefs)
+// return list("error" = TRUE)
+
+// var/list/data = ..()
+// data["volume_channels"] = user.client.prefs.volume_channels
+// return data
+
+// /datum/volume_panel/tgui_act(action, params)
+// if(..())
+// return TRUE
+
+// if(!usr?.client?.prefs)
+// return TRUE
+
+// var/datum/preferences/P = usr.client.prefs
+// switch(action)
+// if("adjust_volume")
+// var/channel = params["channel"]
+// if(channel in P.volume_channels)
+// P.volume_channels["[channel]"] = clamp(params["vol"], 0, 2)
+// SScharacter_setup.queue_preferences_save(P)
+// return TRUE
+
+// /client/verb/volume_panel()
+// set name = "Volume Panel"
+// set category = "Preferences"
+// set desc = "Allows you to adjust volume levels on the fly."
+
+// if(!volume_panel)
+// volume_panel = new(src)
+
+// volume_panel.tgui_interact(mob)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 7a6e709582..522c3f2ba1 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -138,6 +138,8 @@ datum/preferences
var/examine_text_mode = 0 // Just examine text, include usage (description_info), switch to examine panel.
var/multilingual_mode = 0 // Default behaviour, delimiter-key-space, delimiter-key-delimiter, off
+ var/list/volume_channels = list()
+
/datum/preferences/New(client/C)
player_setup = new(src)
diff --git a/polaris.dme b/polaris.dme
index 3d5b9bf6b7..3f833491a6 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -1553,6 +1553,7 @@
#include "code\modules\client\preference_setup\skills\skills.dm"
#include "code\modules\client\preference_setup\traits\trait_defines.dm"
#include "code\modules\client\preference_setup\traits\traits.dm"
+#include "code\modules\client\preference_setup\volume_sliders\01_volume.dm"
#include "code\modules\clothing\chameleon.dm"
#include "code\modules\clothing\clothing.dm"
#include "code\modules\clothing\clothing_accessories.dm"
diff --git a/tgui/packages/tgui/interfaces/VolumePanel.js b/tgui/packages/tgui/interfaces/VolumePanel.js
new file mode 100644
index 0000000000..33bc883fb0
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/VolumePanel.js
@@ -0,0 +1,38 @@
+import { round } from 'common/math';
+import { Fragment } from 'inferno';
+import { useBackend } from "../backend";
+import { Box, Button, Flex, Icon, LabeledList, Slider, Section } from "../components";
+import { Window } from "../layouts";
+
+export const VolumePanel = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ volume_channels,
+ } = data;
+
+ return (
+
+
+
+
+ {Object.keys(volume_channels).map(key => (
+
+ act("adjust_volume", { channel: key, vol: (val / 100) })} />
+
+ ))}
+
+
+
+
+ );
+};
\ No newline at end of file