diff --git a/HellionChat/Configuration.cs b/HellionChat/Configuration.cs
index 0d4b41d..1b1524b 100755
--- a/HellionChat/Configuration.cs
+++ b/HellionChat/Configuration.cs
@@ -187,6 +187,8 @@ public class Configuration : IPluginConfiguration
public bool CollapseKeepUniqueLinks;
public bool SymbolPickerEnabled = true;
public bool PlaySounds = true;
+ // AUDIO-1: playback volume (0-1) for the three bundled custom sounds.
+ public float CustomSoundVolume = 0.5f;
// Toast when a tell the user sent could not be delivered.
public bool NotifyFailedTell = true;
@@ -285,6 +287,7 @@ public class Configuration : IPluginConfiguration
CollapseKeepUniqueLinks = other.CollapseKeepUniqueLinks;
SymbolPickerEnabled = other.SymbolPickerEnabled;
PlaySounds = other.PlaySounds;
+ CustomSoundVolume = other.CustomSoundVolume;
NotifyFailedTell = other.NotifyFailedTell;
KeepInputFocus = other.KeepInputFocus;
MaxLinesToRender = other.MaxLinesToRender;
diff --git a/HellionChat/Integrations/CustomAudioPlayer.cs b/HellionChat/Integrations/CustomAudioPlayer.cs
index 320b5fd..157347d 100644
--- a/HellionChat/Integrations/CustomAudioPlayer.cs
+++ b/HellionChat/Integrations/CustomAudioPlayer.cs
@@ -9,8 +9,9 @@ namespace HellionChat.Integrations;
// WaveOutEvent/WinMM is the correct backend for FFXIV on Wine: it works
// without Media Foundation (which Wine does not support for MP3/AAC).
//
-// Volume is fixed at 0.8. No per-user slider in this iteration so we can
-// ship quickly and gather feedback before adding UX complexity.
+// Playback volume comes from Configuration.CustomSoundVolume via the Play
+// parameter, clamped to [0,1]. The 16 game sounds are unaffected — they go
+// through UIGlobals.PlaySoundEffect, which the plugin cannot scale.
internal sealed class CustomAudioPlayer : IDisposable
{
// Sound bytes are read once at construction so each Play() wraps a fresh
@@ -55,7 +56,7 @@ internal sealed class CustomAudioPlayer : IDisposable
// customIndex is 1, 2, or 3, matching the sound file suffix.
// Stops any currently playing sound before starting the new one.
// NAudio playback runs on its own thread; this method returns immediately.
- public void Play(int customIndex)
+ public void Play(int customIndex, float volume)
{
if (customIndex < 1 || customIndex > 3)
{
@@ -90,7 +91,10 @@ internal sealed class CustomAudioPlayer : IDisposable
// must be set after Init, otherwise waveOutSetVolume fails with
// InvalidHandle.
_outputDevice.Init(_reader);
- _outputDevice.Volume = 0.8f;
+ // AUDIO-1: volume comes from Configuration.CustomSoundVolume.
+ // Clamp here too — a hand-edited config could carry an
+ // out-of-range value, and WaveOutEvent.Volume rejects those.
+ _outputDevice.Volume = Math.Clamp(volume, 0f, 1f);
_outputDevice.Play();
}
catch (Exception ex)
diff --git a/HellionChat/MessageManager.cs b/HellionChat/MessageManager.cs
index df91241..965d556 100644
--- a/HellionChat/MessageManager.cs
+++ b/HellionChat/MessageManager.cs
@@ -380,7 +380,7 @@ internal class MessageManager : IAsyncDisposable
{
// Custom bundled sounds (ids 17-19) go through NAudio WaveOutEvent.
// NAudio manages its own playback thread, so no framework marshalling needed.
- Plugin.CustomAudioPlayer.Play((int)soundId - 16);
+ Plugin.CustomAudioPlayer.Play((int)soundId - 16, Plugin.Config.CustomSoundVolume);
}
// soundId == 0 (hand-edited config) falls through: plays nothing.
}
diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs
index 2a5f0a2..b90d755 100644
--- a/HellionChat/Resources/HellionStrings.Designer.cs
+++ b/HellionChat/Resources/HellionStrings.Designer.cs
@@ -469,4 +469,34 @@ internal class HellionStrings
internal static string ChatLog_ScrollToBottom_Tooltip => Get(nameof(ChatLog_ScrollToBottom_Tooltip));
internal static string ChatLog_Insert_MapFlag => Get(nameof(ChatLog_Insert_MapFlag));
internal static string ChatLog_Insert_ItemLink => Get(nameof(ChatLog_Insert_ItemLink));
+
+ // v1.5.6: per-tab regex filter
+ internal static string Settings_Tabs_MessageRegex_Name => Get(nameof(Settings_Tabs_MessageRegex_Name));
+ internal static string Settings_Tabs_MessageRegex_Description => Get(nameof(Settings_Tabs_MessageRegex_Description));
+ internal static string Settings_Tabs_MessageRegex_Invalid => Get(nameof(Settings_Tabs_MessageRegex_Invalid));
+
+ // v1.5.6: plugin-disclosure warning
+ internal static string Settings_Chat_NotifyPluginDisclosure_Name => Get(nameof(Settings_Chat_NotifyPluginDisclosure_Name));
+ internal static string Settings_Chat_NotifyPluginDisclosure_Description => Get(nameof(Settings_Chat_NotifyPluginDisclosure_Description));
+ internal static string ChatInput_PluginDisclosure_Warning => Get(nameof(ChatInput_PluginDisclosure_Warning));
+
+ // v1.5.6: world suffix + name format display options
+ internal static string Settings_Chat_WorldSuffix_Name => Get(nameof(Settings_Chat_WorldSuffix_Name));
+ internal static string Settings_Chat_WorldSuffix_Description => Get(nameof(Settings_Chat_WorldSuffix_Description));
+ internal static string Settings_Chat_NameForm_Name => Get(nameof(Settings_Chat_NameForm_Name));
+ internal static string Settings_Chat_NameForm_Description => Get(nameof(Settings_Chat_NameForm_Description));
+ internal static string NameDisplay_WorldSuffix_Never => Get(nameof(NameDisplay_WorldSuffix_Never));
+ internal static string NameDisplay_WorldSuffix_OtherWorldOnly => Get(nameof(NameDisplay_WorldSuffix_OtherWorldOnly));
+ internal static string NameDisplay_WorldSuffix_Always => Get(nameof(NameDisplay_WorldSuffix_Always));
+ internal static string NameDisplay_NameForm_Full => Get(nameof(NameDisplay_NameForm_Full));
+ internal static string NameDisplay_NameForm_FirstNameOnly => Get(nameof(NameDisplay_NameForm_FirstNameOnly));
+ internal static string NameDisplay_NameForm_Initials => Get(nameof(NameDisplay_NameForm_Initials));
+
+ // v1.5.6: inactive window opacity
+ internal static string Settings_ThemeAndLayout_WindowOpacityInactive_Name => Get(nameof(Settings_ThemeAndLayout_WindowOpacityInactive_Name));
+ internal static string Settings_ThemeAndLayout_WindowOpacityInactive_Description => Get(nameof(Settings_ThemeAndLayout_WindowOpacityInactive_Description));
+
+ // v1.5.6: custom sound volume
+ internal static string Settings_General_CustomSoundVolume_Name => Get(nameof(Settings_General_CustomSoundVolume_Name));
+ internal static string Settings_General_CustomSoundVolume_Description => Get(nameof(Settings_General_CustomSoundVolume_Description));
}
diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx
index 79a6d45..1c90b1c 100644
--- a/HellionChat/Resources/HellionStrings.resx
+++ b/HellionChat/Resources/HellionStrings.resx
@@ -1090,4 +1090,74 @@
Insert linked item <item>
+
+
+
+ Message filter (regex)
+
+
+ Only keep messages whose text matches this regular expression. Applied on top of the channel filter. Leave empty to disable. Matching is case-insensitive.
+
+
+ Invalid pattern: {0}
+
+
+
+
+ Warn before sending plugin-only symbols
+
+
+ Show a warning when a message you are about to send contains symbols that only display correctly for players running HellionChat or a similar plugin.
+
+
+ This message contains plugin-only symbols that other players may see as empty boxes. Press Enter again to send anyway.
+
+
+
+
+ World suffix
+
+
+ When to append the home world to a sender's name in the chat log.
+
+
+ Name format
+
+
+ How sender names are shown in the chat log. The full name is the default.
+
+
+ Never
+
+
+ Other worlds only
+
+
+ Always
+
+
+ Full name
+
+
+ First name only
+
+
+ Initials
+
+
+
+
+ Inactive window opacity
+
+
+ Background opacity of the main chat window while it is not focused. The slider above sets the focused value. A per-window override in Dalamud's window pinning menu still takes precedence over both.
+
+
+
+
+ Custom sound volume
+
+
+ Playback volume for the three bundled custom notification sounds. Does not affect the 16 game sounds.
+
diff --git a/HellionChat/Ui/SettingsTabs/General.cs b/HellionChat/Ui/SettingsTabs/General.cs
index 81654ad..9a13d5b 100644
--- a/HellionChat/Ui/SettingsTabs/General.cs
+++ b/HellionChat/Ui/SettingsTabs/General.cs
@@ -95,6 +95,24 @@ internal sealed class General : ISettingsTab
{
ImGui.Checkbox(Language.Options_PlaySounds_Name, ref Mutable.PlaySounds);
ImGuiUtil.HelpMarker(Language.Options_PlaySounds_Description);
+ // Volume is stored as a 0-1 float but shown as 0-100% to match user
+ // intuition. Full range — unlike opacity there is no unsafe floor.
+ var customSoundVolumePercent = Mutable.CustomSoundVolume * 100f;
+ if (
+ ImGuiUtil.DragFloatVertical(
+ HellionStrings.Settings_General_CustomSoundVolume_Name,
+ ref customSoundVolumePercent,
+ 1f,
+ 0f,
+ 100f,
+ $"{customSoundVolumePercent:N0}%%",
+ ImGuiSliderFlags.AlwaysClamp
+ )
+ )
+ {
+ Mutable.CustomSoundVolume = customSoundVolumePercent / 100f;
+ }
+ ImGuiUtil.HelpMarker(HellionStrings.Settings_General_CustomSoundVolume_Description);
ImGui.Checkbox(Language.Options_ShowNoviceNetwork_Name, ref Mutable.ShowNoviceNetwork);
ImGuiUtil.HelpMarker(Language.Options_ShowNoviceNetwork_Description);
diff --git a/HellionChat/Ui/SettingsTabs/Tabs.cs b/HellionChat/Ui/SettingsTabs/Tabs.cs
index 2d97523..865c5a6 100755
--- a/HellionChat/Ui/SettingsTabs/Tabs.cs
+++ b/HellionChat/Ui/SettingsTabs/Tabs.cs
@@ -234,7 +234,7 @@ internal sealed class Tabs : ISettingsTab
}
else
{
- Plugin.CustomAudioPlayer.Play((int)previewId - 16);
+ Plugin.CustomAudioPlayer.Play((int)previewId - 16, Mutable.CustomSoundVolume);
}
}
}