From 1c9b502f6960fc6f528b7096d8c298c2bf8b5625 Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 25 Apr 2023 22:01:18 +0200 Subject: [PATCH 1/9] move flashing lights to accessability settings tab --- assets/voxygen/i18n/en/common.ftl | 2 + .../src/hud/settings_window/accessibility.rs | 134 ++++++++++++++++++ voxygen/src/hud/settings_window/mod.rs | 19 +++ voxygen/src/session/settings_change.rs | 12 ++ 4 files changed, 167 insertions(+) create mode 100644 voxygen/src/hud/settings_window/accessibility.rs diff --git a/assets/voxygen/i18n/en/common.ftl b/assets/voxygen/i18n/en/common.ftl index 6f599dca3e..23b0a68b95 100644 --- a/assets/voxygen/i18n/en/common.ftl +++ b/assets/voxygen/i18n/en/common.ftl @@ -12,6 +12,7 @@ common-video = Graphics common-sound = Sound common-chat = Chat common-networking = Networking +common-accessibility = Accessibility common-resume = Resume common-characters = Characters common-close = Close @@ -42,6 +43,7 @@ common-sound_settings = Sound Settings common-language_settings = Language Settings common-chat_settings = Chat Settings common-networking_settings = Networking Settings +common-accessibility_settings = Accessibility Settings common-connection_lost = Connection lost! Did the server restart? diff --git a/voxygen/src/hud/settings_window/accessibility.rs b/voxygen/src/hud/settings_window/accessibility.rs new file mode 100644 index 0000000000..f1e849ca40 --- /dev/null +++ b/voxygen/src/hud/settings_window/accessibility.rs @@ -0,0 +1,134 @@ +use crate::{ + hud::{img_ids::Imgs, TEXT_COLOR}, + session::settings_change::{Accessibility as AccessibilityChange, Accessibility::*}, + ui::{fonts::Fonts, ToggleButton}, + GlobalState, render::RenderMode, +}; +use conrod_core::{ + color, + widget::{self, Rectangle, Text}, + widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, +}; +use i18n::Localization; + +widget_ids! { + struct Ids { + window, + window_r, + flashing_lights_button, + flashing_lights_label, + flashing_lights_info_label, + } +} + +#[derive(WidgetCommon)] +pub struct Accessibility<'a> { + global_state: &'a GlobalState, + imgs: &'a Imgs, + fonts: &'a Fonts, + localized_strings: &'a Localization, + #[conrod(common_builder)] + common: widget::CommonBuilder, +} +impl<'a> Accessibility<'a> { + pub fn new( + global_state: &'a GlobalState, + imgs: &'a Imgs, + fonts: &'a Fonts, + localized_strings: &'a Localization, + ) -> Self { + Self { + global_state, + imgs, + fonts, + localized_strings, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} + +impl<'a> Widget for Accessibility<'a> { + type Event = Vec; + type State = State; + type Style = (); + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style {} + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + common_base::prof_span!("Accessibility::update"); + let widget::UpdateArgs { state, ui, .. } = args; + + let mut events = Vec::new(); + + Rectangle::fill_with(args.rect.dim(), color::TRANSPARENT) + .xy(args.rect.xy()) + .graphics_for(args.id) + .scroll_kids() + .scroll_kids_vertically() + .set(state.ids.window, ui); + Rectangle::fill_with([args.rect.w() / 2.0, args.rect.h()], color::TRANSPARENT) + .top_right() + .parent(state.ids.window) + .set(state.ids.window_r, ui); + + // Get render mode + let render_mode = &self.global_state.settings.graphics.render_mode; + + // Disable flashing lights + Text::new( + &self + .localized_strings + .get_msg("hud-settings-flashing_lights"), + ) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .top_left_with_margins_on(state.ids.window, 10.0, 10.0) + .color(TEXT_COLOR) + .set(state.ids.flashing_lights_label, ui); + + let flashing_lights_enabled = ToggleButton::new( + self.global_state + .settings + .graphics + .render_mode + .flashing_lights_enabled, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0) + .right_from(state.ids.flashing_lights_label, 10.0) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.flashing_lights_button, ui); + + Text::new( + &self + .localized_strings + .get_msg("hud-settings-flashing_lights_info"), + ) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .right_from(state.ids.flashing_lights_label, 32.0) + .color(TEXT_COLOR) + .set(state.ids.flashing_lights_info_label, ui); + + if render_mode.flashing_lights_enabled != flashing_lights_enabled { + events.push(ChangeRenderMode(Box::new(RenderMode { + flashing_lights_enabled, + ..render_mode.clone() + }))); + } + + events + } +} diff --git a/voxygen/src/hud/settings_window/mod.rs b/voxygen/src/hud/settings_window/mod.rs index f7705c2337..0b01a6680c 100644 --- a/voxygen/src/hud/settings_window/mod.rs +++ b/voxygen/src/hud/settings_window/mod.rs @@ -6,6 +6,7 @@ mod language; mod networking; mod sound; mod video; +mod accessibility; use crate::{ hud::{img_ids::Imgs, Show, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN}, @@ -41,6 +42,7 @@ widget_ids! { language, chat, networking, + accessibility, } } @@ -57,6 +59,7 @@ pub enum SettingsTab { Controls, Lang, Networking, + Accessibility, } impl SettingsTab { fn name_key(&self) -> &str { @@ -69,6 +72,7 @@ impl SettingsTab { SettingsTab::Sound => "common-sound", SettingsTab::Lang => "common-languages", SettingsTab::Networking => "common-networking", + SettingsTab::Accessibility => "common-accessibility", } } @@ -82,6 +86,7 @@ impl SettingsTab { SettingsTab::Sound => "common-sound_settings", SettingsTab::Lang => "common-language_settings", SettingsTab::Networking => "common-networking_settings", + SettingsTab::Accessibility => "common-accessibility_settings", } } } @@ -350,6 +355,20 @@ impl<'a> Widget for SettingsWindow<'a> { events.push(Event::SettingsChange(change.into())); } }, + SettingsTab::Accessibility => { + for change in accessibility::Accessibility::new( + global_state, + imgs, + fonts, + localized_strings, + ) + .top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0) + .wh_of(state.ids.settings_content_align) + .set(state.ids.accessibility, ui) + { + events.push(Event::SettingsChange(change.into())); + } + }, } events diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index 429eed96b1..4324f4bb84 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -172,6 +172,11 @@ pub enum Networking { // option) } +#[derive(Clone)] +pub enum Accessibility { + ChangeRenderMode(Box), +} + #[derive(Clone)] pub enum SettingsChange { Audio(Audio), @@ -183,6 +188,7 @@ pub enum SettingsChange { Interface(Interface), Language(Language), Networking(Networking), + Accessibility(Accessibility), } macro_rules! settings_change_from { @@ -201,6 +207,7 @@ settings_change_from!(Graphics); settings_change_from!(Interface); settings_change_from!(Language); settings_change_from!(Networking); +settings_change_from!(Accessibility); impl SettingsChange { pub fn process(self, global_state: &mut GlobalState, session_state: &mut SessionState) { @@ -734,6 +741,11 @@ impl SettingsChange { } }, }, + SettingsChange::Accessibility(accessibility_change) => match accessibility_change { + Accessibility::ChangeRenderMode(new_render_mode) => { + change_render_mode(*new_render_mode, &mut global_state.window, settings); + } + }, } global_state .settings From 99463a37f88e1414aaa90b448b7e0daff965d095 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 26 Apr 2023 16:20:56 +0200 Subject: [PATCH 2/9] subtitles --- assets/voxygen/audio/sfx.ron | 294 ++++++++++++++---- assets/voxygen/i18n/en/hud/settings.ftl | 1 + assets/voxygen/i18n/en/hud/subtitles.ftl | 148 +++++++++ voxygen/src/audio/mod.rs | 69 +++- .../audio/sfx/event_mapper/movement/tests.rs | 3 + voxygen/src/audio/sfx/mod.rs | 7 +- voxygen/src/hud/mod.rs | 18 +- .../src/hud/settings_window/accessibility.rs | 36 ++- voxygen/src/hud/settings_window/mod.rs | 16 +- voxygen/src/hud/subtitles.rs | 191 ++++++++++++ voxygen/src/main.rs | 1 + voxygen/src/session/settings_change.rs | 7 +- voxygen/src/settings/audio.rs | 2 + 13 files changed, 700 insertions(+), 93 deletions(-) create mode 100644 assets/voxygen/i18n/en/hud/subtitles.ftl create mode 100644 voxygen/src/hud/subtitles.rs diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index 9915121026..8d5036dee4 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -1,3 +1,4 @@ +#![enable(implicit_some)] ( { // @@ -8,6 +9,7 @@ "voxygen.audio.sfx.ambient.fire", ], threshold: 21.835, + subtitle: "subtitle-campfire", ), Birdcall: ( files: [ @@ -23,12 +25,14 @@ "voxygen.audio.sfx.ambient.birdcall_10", ], threshold: 10.0, + subtitle: "subtitle-bird_call", ), Owl: ( files: [ "voxygen.audio.sfx.ambient.owl_1", ], threshold: 14.0, + subtitle: "subtitle-owl", ), //Crickets: -20dB Cricket1: ( @@ -60,6 +64,7 @@ "voxygen.audio.sfx.ambient.bees_1", ], threshold: 15.0, + subtitle: "subtitle-bees", ), RunningWaterSlow: ( files: [ @@ -92,6 +97,7 @@ "voxygen.audio.sfx.ambient.river_sounds.running_water-027", ], threshold: 7.0, + subtitle: "subtitle-running_water", ), RunningWaterFast: ( files: [ @@ -121,6 +127,7 @@ "voxygen.audio.sfx.ambient.river_sounds.fast_water-024", ], threshold: 5.0, + subtitle: "subtitle-running_water", ), // // Character States @@ -133,6 +140,7 @@ "voxygen.audio.sfx.footsteps.water_splash_4", ], threshold: 0.5, + subtitle: "subtitle-swim", ), Run(Earth): ( files: [ @@ -143,6 +151,7 @@ "voxygen.audio.sfx.footsteps.stepdirt_5", ], threshold: 1.8, + subtitle: "subtitle-footsteps_earth", ), QuadRun(Earth): ( files: [ @@ -153,6 +162,7 @@ "voxygen.audio.sfx.footsteps.stepdirt_5", ], threshold: 0.9, + subtitle: "subtitle-footsteps_earth", ), Run(Grass): ( files: [ @@ -164,6 +174,7 @@ "voxygen.audio.sfx.footsteps.stepgrass_6", ], threshold: 1.8, + subtitle: "subtitle-footsteps_grass", ), QuadRun(Grass): ( files: [ @@ -175,6 +186,7 @@ "voxygen.audio.sfx.footsteps.stepgrass_6", ], threshold: 0.9, + subtitle: "subtitle-footsteps_grass", ), // For when sand 1) exists and 2) has unique sounds // Run(Sand): ( @@ -204,6 +216,7 @@ "voxygen.audio.sfx.footsteps.snow_step_3", ], threshold: 1.8, + subtitle: "subtitle-footsteps_snow", ), QuadRun(Snow): ( files: [ @@ -212,6 +225,7 @@ "voxygen.audio.sfx.footsteps.snow_step_3", ], threshold: 0.9, + subtitle: "subtitle-footsteps_snow", ), Run(Rock): ( files: [ @@ -229,6 +243,7 @@ "voxygen.audio.sfx.footsteps.stone_step_12", ], threshold: 1.8, + subtitle: "subtitle-footsteps_rock", ), QuadRun(Rock): ( files: [ @@ -246,6 +261,7 @@ "voxygen.audio.sfx.footsteps.stone_step_12", ], threshold: 0.9, + subtitle: "subtitle-footsteps_rock", ), Roll: ( files: [ @@ -253,6 +269,7 @@ "voxygen.audio.sfx.character.dive_roll_2", ], threshold: 0.3, + subtitle: "subtitle-roll", ), Climb: ( files: [ @@ -264,18 +281,21 @@ "voxygen.audio.sfx.footsteps.stepdirt_5", ], threshold: 1.2, + subtitle: "subtitle-climb", ), GliderOpen: ( files: [ "voxygen.audio.sfx.character.glider_open", ], threshold: 0.1, + subtitle: "subtitle-glider_open", ), GliderClose: ( files: [ "voxygen.audio.sfx.character.glider_close", ], threshold: 0.1, + subtitle: "subtitle-glider_close", ), Glide: ( files: [ @@ -289,6 +309,7 @@ "voxygen.audio.sfx.character.catch_air_8", ], threshold: 0.85, + subtitle: "subtitle-glide", ), // @@ -299,90 +320,105 @@ "voxygen.audio.sfx.weapon.sword_out", ], threshold: 0.5, + subtitle: "subtitle-wield_sword", ), Unwield(Sword): ( files: [ "voxygen.audio.sfx.weapon.sword_in", ], threshold: 0.5, + subtitle: "subtitle-unwield_sword", ), Attack(ComboMelee2(Action), Sword): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Attack(FinisherMelee(Action), Sword): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Attack(DiveMelee(Action), Sword): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Attack(RiposteMelee(Action), Sword): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Attack(RapidMelee(Action), Sword): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Attack(ChargedMelee(Action), Sword): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Attack(DashMelee(Action), Sword): ( files: [ "voxygen.audio.sfx.abilities.sword_dash", ], threshold: 0.8, + subtitle: "subtitle-sword_attack", ), Attack(DashMelee(Action), Hammer): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.7, + subtitle: "subtitle-hammer-attack", ), Attack(SpinMelee(Action), Sword): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Attack(ComboMelee(Action, 1), Sword): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Attack(ComboMelee(Action, 2), Sword): ( files: [ "voxygen.audio.sfx.abilities.separated_second_swing", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Attack(ComboMelee(Action, 3), Sword): ( files: [ "voxygen.audio.sfx.abilities.separated_third_swing", ], threshold: 0.7, + subtitle: "subtitle-sword_attack", ), Inventory(CollectedTool(Sword)): ( files: [ "voxygen.audio.sfx.inventory.pickup_sword", ], threshold: 0.3, + subtitle: "subtitle-pickup_sword", ), // @@ -393,36 +429,42 @@ "voxygen.audio.sfx.weapon.weapon_out", ], threshold: 0.5, + subtitle: "subtitle-wield_hammer", ), Unwield(Hammer): ( files: [ "voxygen.audio.sfx.weapon.weapon_in", ], threshold: 0.5, + subtitle: "subtitle-unwield_hammer", ), Attack(ComboMelee(Action, 1), Hammer): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.7, + subtitle: "subtitle-hammer_attack", ), Attack(ChargedMelee(Action), Hammer): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.7, + subtitle: "subtitle-hammer_attack", ), Attack(LeapMelee(Action), Hammer): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.8, + subtitle: "subtitle-hammer_attack", ), Inventory(CollectedTool(Hammer)): ( files: [ "voxygen.audio.sfx.inventory.pickup_sword", ], threshold: 0.3, + subtitle: "subtitle-pickup_hammer", ), // @@ -433,48 +475,56 @@ "voxygen.audio.sfx.weapon.weapon_out", ], threshold: 0.5, + subtitle: "subtitle-wield_axe", ), Unwield(Axe): ( files: [ "voxygen.audio.sfx.weapon.weapon_in", ], threshold: 0.5, + subtitle: "subtitle-unwield_axe", ), Attack(ComboMelee(Action, 1), Axe): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.7, + subtitle: "subtitle-axe_attack", ), Attack(ComboMelee(Action, 2), Axe): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.7, + subtitle: "subtitle-axe_attack", ), Attack(SpinMelee(Action), Axe): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.8, + subtitle: "subtitle-axe_attack", ), Attack(LeapMelee(Action), Axe): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.8, + subtitle: "subtitle-axe_attack", ), Attack(BasicMelee(Action), Axe): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.8, + subtitle: "subtitle-axe_attack", ), Inventory(CollectedTool(Axe)): ( files: [ "voxygen.audio.sfx.inventory.pickup_sword", ], threshold: 0.3, + subtitle: "subtitle-pickup_axe", ), // @@ -485,18 +535,21 @@ "voxygen.audio.sfx.weapon.staff_out", ], threshold: 0.5, + subtitle: "subtitle-wield_staff", ), Unwield(Staff): ( files: [ "voxygen.audio.sfx.weapon.staff_in", ], threshold: 0.5, + subtitle: "subtitle-unwield_staff", ), Attack(BasicBeam, Staff): ( files: [ "voxygen.audio.sfx.abilities.flame_thrower", ], threshold: 0.2, + subtitle: "subtitle-staff_attack", ), //Attack(BasicRanged, Staff): ( // files: [ @@ -509,6 +562,7 @@ "voxygen.audio.sfx.inventory.pickup_staff", ], threshold: 0.3, + subtitle: "subtitle-pickup_staff", ), // @@ -519,12 +573,14 @@ "voxygen.audio.sfx.weapon.weapon_out", ], threshold: 0.5, + subtitle: "subtitle-wield_bow", ), Unwield(Bow): ( files: [ "voxygen.audio.sfx.weapon.weapon_in", ], threshold: 0.5, + subtitle: "subtitle-unwield_bow", ), //Attack(BasicRanged, Bow): ( // files: [ @@ -537,6 +593,7 @@ "voxygen.audio.sfx.inventory.add_item", ], threshold: 0.3, + subtitle: "subtitle-pickup_bow", ), // @@ -547,24 +604,28 @@ "voxygen.audio.sfx.weapon.staff_out", ], threshold: 0.5, + subtitle: "subtitle-wield_sceptre", ), Unwield(Sceptre): ( files: [ "voxygen.audio.sfx.weapon.staff_in", ], threshold: 0.5, + subtitle: "subtitle-unwield_sceptre", ), Attack(BasicAura, Sceptre): ( files: [ "voxygen.audio.sfx.abilities.sceptre_aura", ], threshold: 2.5, + subtitle: "subtitle-sceptre_heal", ), Inventory(CollectedTool(Sceptre)): ( files: [ "voxygen.audio.sfx.inventory.pickup_staff", ], threshold: 0.3, + subtitle: "subtitle-pickup_sceptre", ), // @@ -575,6 +636,7 @@ "voxygen.audio.sfx.abilities.barrel_organ", ], threshold: 34.75, + subtitle: "subtitle-instrument_organ", ), // Player Instruments Wield(Instrument): ( @@ -582,12 +644,14 @@ "voxygen.audio.sfx.weapon.weapon_out", ], threshold: 0.5, + subtitle: "subtitle-wield_instrument", ), Unwield(Instrument): ( files: [ "voxygen.audio.sfx.weapon.weapon_in", ], threshold: 0.5, + subtitle: "subtitle-unwield_instrument", ), Music(Instrument, Custom("DoubleBass")): ( files: [ @@ -605,7 +669,8 @@ "voxygen.audio.sfx.instrument.double_bass.double_bass_ge", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_double_bass", + ), Music(Instrument, Custom("Flute")): ( files: [ "voxygen.audio.sfx.instrument.flute.flute_c", @@ -622,7 +687,8 @@ "voxygen.audio.sfx.instrument.flute.flute_eg", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_flute", + ), Music(Instrument, Custom("GlassFlute")): ( files: [ "voxygen.audio.sfx.instrument.glass_flute.glass_flute_c", @@ -639,7 +705,8 @@ "voxygen.audio.sfx.instrument.glass_flute.glass_flute_eg", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_glass_flute", + ), Music(Instrument, Custom("Lyre")): ( files: [ "voxygen.audio.sfx.instrument.lyre.lyre_c", @@ -656,7 +723,8 @@ "voxygen.audio.sfx.instrument.lyre.lyre_ega", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_lyre", + ), Music(Instrument, Custom("IcyTalharpa")): ( files: [ "voxygen.audio.sfx.instrument.icy_talharpa.icy_talharpa_c", @@ -673,7 +741,8 @@ "voxygen.audio.sfx.instrument.icy_talharpa.icy_talharpa_ega", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_icy_talharpa", + ), Music(Instrument, Custom("Kalimba")): ( files: [ "voxygen.audio.sfx.instrument.kalimba.kalimba_c", @@ -690,7 +759,8 @@ "voxygen.audio.sfx.instrument.kalimba.kalimba_da", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_kalimba", + ), Music(Instrument, Custom("Melodica")): ( files: [ "voxygen.audio.sfx.instrument.melodica.melodica_c", @@ -707,7 +777,8 @@ "voxygen.audio.sfx.instrument.melodica.melodica_ge", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_melodica", + ), Music(Instrument, Custom("Lute")): ( files: [ "voxygen.audio.sfx.instrument.lute.lute_c", @@ -724,7 +795,8 @@ "voxygen.audio.sfx.instrument.lute.lute_ded", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_lute", + ), Music(Instrument, Custom("Sitar")): ( files: [ "voxygen.audio.sfx.instrument.sitar.sitar_c", @@ -741,7 +813,8 @@ "voxygen.audio.sfx.instrument.sitar.sitar_gec", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_sitar", + ), Music(Instrument, Custom("Guitar")): ( files: [ "voxygen.audio.sfx.instrument.guitar.guitar_c", @@ -758,7 +831,8 @@ "voxygen.audio.sfx.instrument.guitar.guitar_gag", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_guitar", + ), Music(Instrument, Custom("DarkGuitar")): ( files: [ "voxygen.audio.sfx.instrument.dark_guitar.dark_guitar_a", @@ -777,7 +851,8 @@ "voxygen.audio.sfx.instrument.dark_guitar.dark_guitar_gec", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_dark_guitar", + ), Music(Instrument, Custom("Washboard")): ( files: [ "voxygen.audio.sfx.instrument.washboard.washboard_c", @@ -794,12 +869,14 @@ "voxygen.audio.sfx.instrument.washboard.washboard_e", ], threshold: 0.5, - ), + subtitle: "subtitle-instrument_washboard", + ), Inventory(CollectedTool(Instrument)): ( files: [ "voxygen.audio.sfx.inventory.add_item", ], threshold: 0.3, + subtitle: "subtitle-pickup_instrument", ), // // Dagger @@ -809,30 +886,35 @@ "voxygen.audio.sfx.weapon.dagger_out", ], threshold: 0.5, + subtitle: "subtitle-wield_dagger", ), Unwield(Dagger): ( files: [ "voxygen.audio.sfx.weapon.dagger_in", ], threshold: 0.5, + subtitle: "subtitle-unwield_dagger", ), Attack(BasicMelee(Action), Dagger): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.8, + subtitle: "subtitle-dagger_attack", ), Attack(DashMelee(Action), Dagger): ( files: [ "voxygen.audio.sfx.abilities.sword_dash", ], threshold: 0.8, + subtitle: "subtitle-dagger_attack", ), Inventory(CollectedTool(Dagger)): ( files: [ "voxygen.audio.sfx.inventory.pickup_sword", ], threshold: 0.3, + subtitle: "subtitle-pickup_dagger", ), // @@ -843,24 +925,28 @@ "voxygen.audio.sfx.weapon.shield_out", ], threshold: 0.5, + subtitle: "subtitle-wield_shield", ), Unwield(Shield): ( files: [ "voxygen.audio.sfx.weapon.weapon_in", ], threshold: 0.5, + subtitle: "subtitle-unwield_shield", ), Attack(BasicMelee(Action), Shield): ( files: [ "voxygen.audio.sfx.abilities.swing", ], threshold: 0.8, + subtitle: "subtitle-shield_attack", ), Inventory(CollectedTool(Shield)): ( files: [ "voxygen.audio.sfx.inventory.pickup_sword", ], threshold: 0.3, + subtitle: "subtitle-pickup_shield", ), // PickAxe Tool @@ -869,6 +955,7 @@ "voxygen.audio.sfx.inventory.add_item", ], threshold: 0.3, + subtitle: "subtitle-pickup_pick", ), // // Inventory @@ -878,18 +965,21 @@ "voxygen.audio.sfx.inventory.add_item", ], threshold: 0.3, + subtitle: "subtitle-pickup_item", ), Inventory(CollectedItem("Gemstone")): ( files: [ "voxygen.audio.sfx.inventory.collect_gemstone", ], threshold: 0.3, + subtitle: "subtitle-pickup_gemstone", ), Inventory(CollectFailed): ( files: [ "voxygen.audio.sfx.inventory.add_failed", ], threshold: 0.3, + subtitle: "subtitle-pickup_failed", ), Inventory(Swapped): ( files: [ @@ -924,192 +1014,231 @@ "voxygen.audio.sfx.inventory.consumable.liquid", ], threshold: 0.3, + subtitle: "subtitle-consume_potion", ), Inventory(Consumed("Medium Potion")): ( files: [ "voxygen.audio.sfx.inventory.consumable.liquid", ], threshold: 0.3, + subtitle: "subtitle-consume_potion", ), Inventory(Consumed("Large Potion")): ( files: [ "voxygen.audio.sfx.inventory.consumable.liquid", ], threshold: 0.3, + subtitle: "subtitle-consume_potion", ), Inventory(Consumed("Apple")): ( files: [ "voxygen.audio.sfx.inventory.consumable.apple", ], threshold: 0.3, + subtitle: "subtitle-consume_apple", ), Inventory(Consumed("Mushroom")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Dwarven Cheese")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_cheese", ), Inventory(Consumed("Sunflower Ice Tea")): ( files: [ "voxygen.audio.sfx.inventory.consumable.liquid", ], threshold: 0.3, + subtitle: "subtitle-consume_liquid", ), Inventory(Consumed("Mushroom Curry")): ( files: [ "voxygen.audio.sfx.inventory.consumable.liquid", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Apple Stick")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Coconut")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Mushroom Stick")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Tomato")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Carrot")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Lettuce")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Plain Salad")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Tomato Salad")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Cactus Colada")): ( files: [ "voxygen.audio.sfx.inventory.consumable.liquid", ], threshold: 0.3, + subtitle: "subtitle-consume_liquid", ), Inventory(Consumed("Raw Bird Meat")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Cooked Bird Meat")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Raw Fish")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Cooked Fish")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Raw Meat Slab")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Cooked Meat Slab")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Raw Meat Sliver")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Cooked Meat Sliver")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Raw Tough Meat")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Cooked Tough Meat")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Huge Raw Drumstick")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Huge Cooked Drumstick")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Honeycorn")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_food", ), Inventory(Consumed("Pumpkin Spice Brew")): ( files: [ "voxygen.audio.sfx.inventory.consumable.liquid", ], threshold: 0.3, + subtitle: "subtitle-consume_liquid", ), Inventory(Consumed("Blue Cheese")): ( files: [ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, + subtitle: "subtitle-consume_cheese", + ), + Inventory(Consumed("Golden Cheese")): ( + files: [ + "voxygen.audio.sfx.inventory.consumable.food", + ], + threshold: 0.3, + subtitle: "subtitle-consume_cheese", ), // @@ -1120,67 +1249,77 @@ "voxygen.audio.sfx.abilities.explosion", ], threshold: 0.2, + subtitle: "subtitle-explosion", ), ArrowShot: ( - files: [ - "voxygen.audio.sfx.abilities.arrow_shot_1", - "voxygen.audio.sfx.abilities.arrow_shot_2", - "voxygen.audio.sfx.abilities.arrow_shot_3", - "voxygen.audio.sfx.abilities.arrow_shot_4", - ], - threshold: 0.2, + files: [ + "voxygen.audio.sfx.abilities.arrow_shot_1", + "voxygen.audio.sfx.abilities.arrow_shot_2", + "voxygen.audio.sfx.abilities.arrow_shot_3", + "voxygen.audio.sfx.abilities.arrow_shot_4", + ], + threshold: 0.2, + subtitle: "subtitle-arrow_shot", ), FireShot: ( - files: [ - "voxygen.audio.sfx.abilities.fire_shot_1", - "voxygen.audio.sfx.abilities.fire_shot_2", - ], - threshold: 0.2, + files: [ + "voxygen.audio.sfx.abilities.fire_shot_1", + "voxygen.audio.sfx.abilities.fire_shot_2", + ], + threshold: 0.2, + subtitle: "subtitle-fire_shot", ), ArrowMiss: ( files: [ "voxygen.audio.sfx.character.arrow_miss", ], threshold: 0.2, + subtitle: "subtitle-arrow_miss", ), ArrowHit: ( files: [ - "voxygen.audio.sfx.character.arrow_hit", + "voxygen.audio.sfx.character.arrow_hit", ], threshold: 0.2, + subtitle: "subtitle-arrow_hit", ), SkillPointGain: ( files: [ - "voxygen.audio.sfx.character.level_up_sound_-_shorter_wind_up", - ], + "voxygen.audio.sfx.character.level_up_sound_-_shorter_wind_up", + ], threshold: 0.2, + subtitle: "subtitle-skill_point", ), SceptreBeam: ( files: [ "voxygen.audio.sfx.abilities.sceptre_channeling", ], threshold: 0.2, + subtitle: "subtitle-sceptre_beam", ), FlameThrower: ( files: [ "voxygen.audio.sfx.abilities.flame_thrower", ], threshold: 0.2, + subtitle: "subtitle-flame_thrower", ), BreakBlock: ( files: [ - "voxygen.audio.sfx.footsteps.stone_step_1", - ], + "voxygen.audio.sfx.footsteps.stone_step_1", + ], threshold: 0.2, + subtitle: "subtitle-break_block", ), Damage: ( files: [ - "voxygen.audio.sfx.character.hit_1", - "voxygen.audio.sfx.character.hit_2", - "voxygen.audio.sfx.character.hit_3", - "voxygen.audio.sfx.character.hit_4", - ], + "voxygen.audio.sfx.character.hit_1", + "voxygen.audio.sfx.character.hit_2", + "voxygen.audio.sfx.character.hit_3", + "voxygen.audio.sfx.character.hit_4", + ], threshold: 0.2, + subtitle: "subtitle-damage", ), Death: ( files: [ @@ -1189,21 +1328,24 @@ "voxygen.audio.sfx.character.death_3", ], threshold: 0.2, + subtitle: "subtitle-death", ), Block: ( files: [ - "voxygen.audio.sfx.character.block_1", - "voxygen.audio.sfx.character.block_2", - "voxygen.audio.sfx.character.block_3", - ], + "voxygen.audio.sfx.character.block_1", + "voxygen.audio.sfx.character.block_2", + "voxygen.audio.sfx.character.block_3", + ], threshold: 0.2, + subtitle: "subtitle-attack_blocked", ), Parry: ( files: [ - "voxygen.audio.sfx.character.parry_1", - "voxygen.audio.sfx.character.parry_2", - ], + "voxygen.audio.sfx.character.parry_1", + "voxygen.audio.sfx.character.parry_2", + ], threshold: 0.2, + subtitle: "subtitle-parry", ), PoiseChange(Interrupted): ( files: [ @@ -1213,6 +1355,7 @@ "voxygen.audio.sfx.character.interrupted_4", ], threshold: 0.25, + subtitle: "subtitle-interrupted", ), PoiseChange(Stunned): ( files: [ @@ -1221,6 +1364,7 @@ "voxygen.audio.sfx.character.stunned_3", ], threshold: 0.6, + subtitle: "subtitle-stunned", ), PoiseChange(Dazed): ( files: [ @@ -1229,6 +1373,7 @@ "voxygen.audio.sfx.character.dazed_3", ], threshold: 0.85, + subtitle: "subtitle-dazed", ), PoiseChange(KnockedDown): ( files: [ @@ -1236,85 +1381,90 @@ "voxygen.audio.sfx.character.knockeddown_2", ], threshold: 1.25, + subtitle: "subtitle-knocked_down", ), GroundSlam: ( files: [ - "voxygen.audio.sfx.abilities.minotaur_smash_1", - "voxygen.audio.sfx.abilities.minotaur_smash_2", - ], + "voxygen.audio.sfx.abilities.minotaur_smash_1", + "voxygen.audio.sfx.abilities.minotaur_smash_2", + ], threshold: 0.2, + subtitle: "subtitle-attack-ground_slam", ), LaserBeam: ( files: [ "voxygen.audio.sfx.abilities.laser_beam", ], threshold: 1.25, + subtitle: "subtitle-attack-laser_beam", ), CyclopsCharge: ( files: [ "voxygen.audio.sfx.abilities.cyclops_charge", ], threshold: 0.3, + subtitle: "subtitle-attack-cyclops_charge", ), GigaRoar: ( files: [ "voxygen.audio.sfx.abilities.gigas_frost_roar", ], threshold: 1.3, + subtitle: "subtitle-giga_roar", ), FlashFreeze: ( files: [ "voxygen.audio.sfx.abilities.minotaur_smash_2", ], threshold: 0.2, + subtitle: "subtitle-attack-flash_freeze", ), IceSpikes: ( files: [ "voxygen.audio.sfx.abilities.minotaur_smash_2", ], threshold: 0.2, + subtitle: "subtitle-attack-icy_spikes", ), IceCrack: ( files: [ - "voxygen.audio.sfx.abilities.ice_crack", - ], + "voxygen.audio.sfx.abilities.ice_crack", + ], threshold: 0.9, + subtitle: "subtitle-attack-ice_crack", ), + + // Utterances (NPCs) + Utterance(Angry, Alligator): ( files: [ "voxygen.audio.sfx.utterance.alligator_angry1", "voxygen.audio.sfx.utterance.alligator_angry2", ], threshold: 1.0, - ), - Utterance(Angry, SeaCrocodile): ( - files: [ - "voxygen.audio.sfx.utterance.sea_crocodile_angry1", - "voxygen.audio.sfx.utterance.sea_crocodile_angry2", - ], - threshold: 1.0, + subtitle: "subtitle-utterance-alligator-angry", ), Utterance(Angry, Antelope): ( files: [ "voxygen.audio.sfx.utterance.antelope_angry1", ], threshold: 1.0, + subtitle: "subtitle-utterance-antelope-angry", ), - - // Utterances (NPCs) - Utterance(Angry, BipedLarge): ( files: [ "voxygen.audio.sfx.utterance.ogre_angry1", "voxygen.audio.sfx.utterance.ogre_angry2", ], threshold: 1.0, + subtitle: "subtitle-utterance-biped_large-angry", ), Utterance(Angry, Bird): ( files: [ "voxygen.audio.sfx.utterance.bird_angry1", ], threshold: 1.0, + subtitle: "subtitle-utterance-bird-angry", ), Utterance(Angry, Adlet): ( files: [ @@ -1322,6 +1472,7 @@ "voxygen.audio.sfx.utterance.adlet_angry2", ], threshold: 1.0, + subtitle: "subtitle-utterance-adlet-angry", ), Utterance(Angry, Pig): ( files: [ @@ -1329,6 +1480,7 @@ "voxygen.audio.sfx.utterance.pig_angry2", ], threshold: 1.0, + subtitle: "subtitle-utterance-pig-angry", ), Utterance(Angry, Reptile): ( files: [ @@ -1336,6 +1488,7 @@ "voxygen.audio.sfx.utterance.alligator_angry2", ], threshold: 1.0, + subtitle: "subtitle-utterance-reptile-angry", ), Utterance(Angry, SeaCrocodile): ( files: [ @@ -1343,12 +1496,14 @@ "voxygen.audio.sfx.utterance.sea_crocodile_angry2", ], threshold: 1.0, + subtitle: "subtitle-utterance-sea_crocodile-angry", ), Utterance(Angry, Saurok): ( files: [ "voxygen.audio.sfx.utterance.saurok_angry1", ], threshold: 1.0, + subtitle: "subtitle-utterance-saurok-angry", ), Utterance(Calm, Cat): ( files: [ @@ -1356,6 +1511,7 @@ "voxygen.audio.sfx.utterance.cat_calm2", ], threshold: 1.0, + subtitle: "subtitle-utterance-cat-calm", ), Utterance(Calm, Cow): ( files: [ @@ -1364,18 +1520,21 @@ "voxygen.audio.sfx.utterance.cow_calm3", ], threshold: 1.0, + subtitle: "subtitle-utterance-cow-calm", ), Utterance(Calm, Fungome): ( files: [ "voxygen.audio.sfx.utterance.fungome_calm1", ], threshold: 1.0, + subtitle: "subtitle-utterance-fungome-calm", ), Utterance(Calm, Goat): ( files: [ "voxygen.audio.sfx.utterance.goat_calm1", ], threshold: 1.0, + subtitle: "subtitle-utterance-goat-calm", ), Utterance(Calm, Pig): ( files: [ @@ -1383,12 +1542,14 @@ "voxygen.audio.sfx.utterance.pig_calm2", ], threshold: 1.0, + subtitle: "subtitle-utterance-pig-calm", ), Utterance(Calm, Sheep): ( files: [ "voxygen.audio.sfx.utterance.sheep_calm1", ], threshold: 1.0, + subtitle: "subtitle-utterance-sheep-calm", ), Utterance(Calm, Truffler): ( files: [ @@ -1396,18 +1557,21 @@ "voxygen.audio.sfx.utterance.truffler_calm2", ], threshold: 1.0, + subtitle: "subtitle-utterance-truffler-calm", ), Utterance(Greeting, HumanMale): ( files: [ "voxygen.audio.sfx.utterance.humanmale_greeting1", ], threshold: 1.0, + subtitle: "subtitle-utterance-human-greeting", ), Utterance(Greeting, HumanFemale): ( files: [ "voxygen.audio.sfx.utterance.humanfemale_greeting1", ], threshold: 1.0, + subtitle: "subtitle-utterance-human-greeting", ), Utterance(Hurt, Adlet): ( files: [ @@ -1415,54 +1579,63 @@ "voxygen.audio.sfx.utterance.adlet_hurt2", ], threshold: 1.0, + subtitle: "subtitle-utterance-adlet-hurt", ), Utterance(Hurt, Antelope): ( files: [ "voxygen.audio.sfx.utterance.antelope_hurt1", ], threshold: 1.0, + subtitle: "subtitle-utterance-antelope-hurt", ), Utterance(Hurt, BipedLarge): ( files: [ "voxygen.audio.sfx.utterance.ogre_hurt1", ], threshold: 1.0, + subtitle: "subtitle-utterance-biped_large-hurt", ), Utterance(Hurt, HumanMale): ( files: [ "voxygen.audio.sfx.utterance.humanmale_hurt1", ], threshold: 1.0, + subtitle: "subtitle-utterance-human-hurt", ), Utterance(Hurt, Lion): ( files: [ "voxygen.audio.sfx.utterance.lion_hurt1", ], threshold: 1.0, + subtitle: "subtitle-utterance-lion-hurt", ), Utterance(Hurt, Mandragora): ( files: [ "voxygen.audio.sfx.utterance.mandragora_hurt1", ], threshold: 1.0, + subtitle: "subtitle-utterance-mandroga-hurt", ), Utterance(Hurt, Maneater): ( files: [ "voxygen.audio.sfx.utterance.maneater_hurt1", ], threshold: 1.0, + subtitle: "subtitle-utterance-maneater-hurt", ), Utterance(Hurt, Marlin): ( files: [ "voxygen.audio.sfx.utterance.marlin_hurt1", ], threshold: 1.0, + subtitle: "subtitle-utterance-marlin-hurt", ), Utterance(Hurt, Mindflayer): ( files: [ "voxygen.audio.sfx.utterance.mindflayer_hurt1", ], threshold: 1.0, + subtitle: "subtitle-utterance-mindflayer-hurt", ), Utterance(Hurt, Dagon): ( files: [ @@ -1470,6 +1643,7 @@ "voxygen.audio.sfx.utterance.dagon_hurt2", ], threshold: 1.0, + subtitle: "subtitle-utterance-dagon-hurt", ), Utterance(Angry, Asp): ( files: [ @@ -1477,18 +1651,21 @@ "voxygen.audio.sfx.utterance.asp_angry2", ], threshold: 1.0, + subtitle: "subtitle-utterance-asp-angry", ), Utterance(Calm, Asp): ( files: [ "voxygen.audio.sfx.utterance.asp_calm1", ], threshold: 1.0, + subtitle: "subtitle-utterance-asp-calm", ), Utterance(Hurt, Asp): ( files: [ "voxygen.audio.sfx.utterance.asp_hurt1", ], threshold: 1.0, + subtitle: "subtitle-utterance-asp-hurt", ), Utterance(Angry, Wendigo): ( files: [ @@ -1500,6 +1677,7 @@ "voxygen.audio.sfx.utterance.wendigo_angry6", ], threshold: 1.0, + subtitle: "subtitle-utterance-wendigo-angry", ), Utterance(Calm, Wendigo): ( files: [ @@ -1510,6 +1688,7 @@ "voxygen.audio.sfx.utterance.wendigo_calm5", ], threshold: 1.0, + subtitle: "subtitle-utterance-wendigo-calm", ), Utterance(Angry, Wolf): ( files: [ @@ -1521,6 +1700,7 @@ "voxygen.audio.sfx.utterance.wolf_angry6", ], threshold: 1.0, + subtitle: "subtitle-utterance-wolf-angry", ), Utterance(Hurt, Wolf): ( files: [ @@ -1531,12 +1711,14 @@ "voxygen.audio.sfx.utterance.wolf_hurt5", ], threshold: 1.0, + subtitle: "subtitle-utterance-wolf-hurt", ), Lightning: ( files: [ "voxygen.audio.sfx.ambient.lightning_1", ], threshold: 1.0, + subtitle: "subtitle-lightning", ), } ) diff --git a/assets/voxygen/i18n/en/hud/settings.ftl b/assets/voxygen/i18n/en/hud/settings.ftl index 0575de807b..35c7b78511 100644 --- a/assets/voxygen/i18n/en/hud/settings.ftl +++ b/assets/voxygen/i18n/en/hud/settings.ftl @@ -151,3 +151,4 @@ hud-settings-group_only = Group only hud-settings-reset_chat = Reset to Defaults hud-settings-third_party_integrations = Third-party Integrations hud-settings-enable_discord_integration = Enable Discord Integration +hud-settings-subtitles = Subtitles diff --git a/assets/voxygen/i18n/en/hud/subtitles.ftl b/assets/voxygen/i18n/en/hud/subtitles.ftl new file mode 100644 index 0000000000..469593b339 --- /dev/null +++ b/assets/voxygen/i18n/en/hud/subtitles.ftl @@ -0,0 +1,148 @@ +subtitle-campfire = Campfire crackling +subtitle-bird_call = Birds singing +subtitle-bees = Bees buzzing +subtitle-owl = Owl hooting +subtitle-running_water = Water bubbling +subtitle-lightning = Thunder + +subtitle-footsteps_grass = Walking on grass +subtitle-footsteps_earth = Walking on dirt +subtitle-footsteps_rock = Walking on rock +subtitle-footsteps_snow = Walking on snow +subtitle-pickup_item = Item picked up +subtitle-pickup_failed = Pickup failed + +subtitle-glider_open = Glider equipped +subtitle-glider_close = Glider unequipped +subtitle-glide = Gliding +subtitle-roll = Rolling +subtitle-swim = Swimming +subtitle-climb = Climbing +subtitle-damage = Damage +subtitle-death = Death + +subtitle-wield_bow = Bow equipped +subtitle-unwield_bow = Bow unequipped +subtitle-pickup_bow = Bow picked up + +subtitle-wield_sword = Sword equipped +subtitle-unwield_sword = Sword unequipped +subtitle-sword_attack = Sword swung +subtitle-pickup_sword = Sword picked up + +subtitle-wield_axe = Axe equipped +subtitle-unwield_axe = Axe unequipped +subtitle-axe_attack = Axe swung +subtitle-pickup_axe = Axe picked up + +subtitle-wield_hammer = Hammer equipped +subtitle-unwield_hammer = Hammer unequipped +subtitle-hammer_attack = Hammer swung +subtitle-pickup_hammer = Hammer picked up + +subtitle-wield_staff = Staff equipped +subtitle-unwield_staff = Staff unequipped +subtitle-fire_shot = Staff fired +subtitle-staff_attack = Staff fired +subtitle-pickup_staff = Staff picked up + +subtitle-wield_sceptre = Sceptre equipped +subtitle-unwield_sceptre = Sceptre unequipped +subtitle-sceptre_heal = Sceptre heal aura +subtitle-pickup_sceptre = Sceptre picked up + +subtitle-wield_dagger = Dagger equipped +subtitle-uwield_dagger = Dagger unequipped +subtitle-dagger_attack = Dagger swung +subtitle-pickup_dagger = Dagger picked up + +subtitle-wield_shield = Shield equipped +subtitle-unwield_shield = Shield unequipped +subtitle-shield_attack = Shield pushed +subtitle-pickup_shield = Shield picked up + +subtitle-pickup_pick = Pickaxe picked up +subtitle-pickup_gemstone = Gemstone picked up + +subtitle-instrument_organ = Organ playing + +subtitle-wield_instrument = Instrument equipped +subtitle-unwield_instrument = Instrument unequipped +subtitle-instrument_double_bass = Double Bass playing +subtitle-instrument_flute = Flute playing +subtitle-instrument_glass_flute = Glass Flute playing +subtitle-instrument_lyre = Lyre playing +subtitle-instrument_icy_talharpa = Icy Talharpa playing +subtitle-instrument_kalimba = Kalimba playing +subtitle-instrument_melodica = Melodica playing +subtitle-instrument_lute = Lute playing +subtitle-instrument_sitar = Sitar playing +subtitle-instrument_guitar = Guitar playing +subtitle-instrument_dark_guitar = Dark Guitar playing +subtitle-instrument_washboard = Washboard playing +subtitle-pickup_instrument = Pickup instrument + +subtitle-explosion = Explosion + +subtitle-arrow_shot = Arrow released +subtitle-arrow_miss = Arrow miss +subtitle-arrow_hit = Arrow hit +subtitle-skill_point = Skill Point gained +subtitle-sceptre_beam = Sceptre beam +subtitle-flame_thrower = Flame thrower +subtitle-break_block = Block destroyed +subtitle-attack_blocked = Attack blocked +subtitle-parry = Parried +subtitle-interrupted = Interrupted +subtitle-stunned = Stunned +subtitle-dazed = Dazed +subtitle-knocked_down = Knocked down + +subtitle-attack-ground_slam = Ground slam +subtitle-attack-laser_beam = Laser beam +subtitle-attack-cyclops_charge = Cyclops charge +subtitle-giga_roar = Frost gigas roar +subtitle-attack-flash_freeze = Flash freeze +subtitle-attack-icy_spikes = Icy spikes +subtitle-attack-ice_crack = Ice crack + +subtitle-consume_potion = Drinking potion +subtitle-consume_apple = Eating apple +subtitle-consume_cheese = Eating cheese +subtitle-consume_food = Eating +subtitle-consume_liquid = Drinking + +subtitle-utterance-alligator-angry = Alligator hissing +subtitle-utterance-antelope-angry = Antelope snorting +subtitle-utterance-biped_large-angry = Heavy grunting +subtitle-utterance-bird-angry = Bird screeching +subtitle-utterance-adlet-angry = Adlet barking +subtitle-utterance-pig-angry = Pig grunting +subtitle-utterance-reptile-angry = Reptile hissing +subtitle-utterance-sea_crocodile-angry = Sea Crocodile hissing +subtitle-utterance-saurok-angry = Saurok hissing +subtitle-utterance-cat-calm = Cat meowing +subtitle-utterance-cow-calm = Cow mooing +subtitle-utterance-fungome-calm = Fungome squeaking +subtitle-utterance-goat-calm = Goat bleating +subtitle-utterance-pig-calm = Pig oinking +subtitle-utterance-sheep-calm = Sheep bleating +subtitle-utterance-truffler-calm = Truffler oinking +subtitle-utterance-human-greeting = Greeting +subtitle-utterance-adlet-hurt = Adlet whining +subtitle-utterance-antelope-hurt = Antelope crying +subtitle-utterance-biped_large-hurt = Heavy hurting +subtitle-utterance-human-hurt = Human hurting +subtitle-utterance-lion-hurt = Lion growling +subtitle-utterance-mandroga-hurt = Mandroga screaming +subtitle-utterance-maneater-hurt = Maneater burping +subtitle-utterance-marlin-hurt = Marlin hurting +subtitle-utterance-mindflayer-hurt = Mindflayer hurting +subtitle-utterance-dagon-hurt = Dagon hurting +subtitle-utterance-asp-angry = Asp hissing +subtitle-utterance-asp-calm = Asp croaking +subtitle-utterance-asp-hurt = Asp hurting +subtitle-utterance-wendigo-angry = Wendigo screaming +subtitle-utterance-wendigo-calm = Wendigo mumbling +subtitle-utterance-wolf-angry = Wolf growling +subtitle-utterance-wolf-hurt = Wolf whining \ No newline at end of file diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index 08e44dcca1..a083169c9e 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -14,13 +14,15 @@ use fader::Fader; use music::MusicTransitionManifest; use sfx::{SfxEvent, SfxTriggerItem}; use soundcache::load_ogg; -use std::time::Duration; +use std::{collections::VecDeque, time::Duration}; use tracing::{debug, error}; use common::assets::{AssetExt, AssetHandle}; use rodio::{source::Source, OutputStream, OutputStreamHandle, StreamError}; use vek::*; +use crate::hud::Subtitle; + #[derive(Default, Clone)] pub struct Listener { pos: Vec3, @@ -53,12 +55,19 @@ pub struct AudioFrontend { music_spacing: f32, listener: Listener, + pub subtitles_enabled: bool, + pub subtitles: VecDeque, + mtm: AssetHandle, } impl AudioFrontend { /// Construct with given device - pub fn new(/* dev: String, */ num_sfx_channels: usize, num_ui_channels: usize) -> Self { + pub fn new( + /* dev: String, */ num_sfx_channels: usize, + num_ui_channels: usize, + subtitles: bool, + ) -> Self { // Commented out until audio device switcher works //let audio_device = get_device_raw(&dev); @@ -106,6 +115,8 @@ impl AudioFrontend { music_spacing: 1.0, listener: Listener::default(), mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"), + subtitles: VecDeque::new(), + subtitles_enabled: subtitles, } } @@ -129,6 +140,8 @@ impl AudioFrontend { music_spacing: 1.0, listener: Listener::default(), mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"), + subtitles: VecDeque::new(), + subtitles_enabled: false, } } @@ -223,9 +236,9 @@ impl AudioFrontend { /// Errors if no sounds are found fn get_sfx_file<'a>( trigger_item: Option<(&'a SfxEvent, &'a SfxTriggerItem)>, - ) -> Option<&'a str> { + ) -> Option<(&'a str, f32, Option<&'a str>)> { trigger_item.map(|(event, item)| { - match item.files.len() { + let file = match item.files.len() { 0 => { debug!("Sfx event {:?} is missing audio file.", event); "voxygen.audio.sfx.placeholder" @@ -239,7 +252,9 @@ impl AudioFrontend { let rand_step = rand::random::() % item.files.len(); &item.files[rand_step] }, - } + }; + + (file, item.threshold, item.subtitle.as_deref()) }) } @@ -252,11 +267,19 @@ impl AudioFrontend { volume: Option, underwater: bool, ) { - if let Some(sfx_file) = Self::get_sfx_file(trigger_item) { + if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) { + if self.subtitles_enabled { + if let Some(subtitle) = subtitle { + self.subtitles.push_back(Subtitle { + localization: subtitle.to_string(), + position: Some(position), + show_until: dur.max(1.5) as f64, + }) + } + } // Play sound in empty channel at given position - if self.audio_stream.is_some() { + if self.audio_stream.is_some() && self.sfx_enabled() { let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0)); - let listener = self.listener.clone(); if let Some(channel) = self.get_sfx_channel() { channel.set_pos(position); @@ -286,11 +309,19 @@ impl AudioFrontend { freq: Option, underwater: bool, ) { - if let Some(sfx_file) = Self::get_sfx_file(trigger_item) { + if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) { + if self.subtitles_enabled { + if let Some(subtitle) = subtitle { + self.subtitles.push_back(Subtitle { + localization: subtitle.to_string(), + position: Some(position), + show_until: dur.max(1.5) as f64, + }) + } + } // Play sound in empty channel at given position - if self.audio_stream.is_some() { + if self.audio_stream.is_some() && self.sfx_enabled() { let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0)); - let listener = self.listener.clone(); if let Some(channel) = self.get_sfx_channel() { channel.set_pos(position); @@ -321,11 +352,19 @@ impl AudioFrontend { trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, volume: Option, ) { - if let Some(sfx_file) = Self::get_sfx_file(trigger_item) { + if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) { + if self.subtitles_enabled { + if let Some(subtitle) = subtitle { + self.subtitles.push_back(Subtitle { + localization: subtitle.to_string(), + position: None, + show_until: dur.max(1.5) as f64, + }) + } + } // Play sound in empty channel - if self.audio_stream.is_some() { + if self.audio_stream.is_some() && self.sfx_enabled() { let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0)); - if let Some(channel) = self.get_ui_channel() { channel.play(sound); } @@ -504,6 +543,8 @@ impl AudioFrontend { pub fn set_music_spacing(&mut self, multiplier: f32) { self.music_spacing = multiplier } + pub fn set_subtitles(&mut self, enabled: bool) { self.subtitles_enabled = enabled } + /// Updates master volume in all channels pub fn set_master_volume(&mut self, master_volume: f32) { self.master_volume = master_volume; diff --git a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs index 2fa848222e..2487b2c760 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs @@ -23,6 +23,7 @@ fn config_but_played_since_threshold_no_emit() { let trigger_item = SfxTriggerItem { files: vec![String::from("some.path.to.sfx.file")], threshold: 1.0, + subtitle: None, }; // Triggered a 'Run' 0 seconds ago @@ -47,6 +48,7 @@ fn config_and_not_played_since_threshold_emits() { let trigger_item = SfxTriggerItem { files: vec![String::from("some.path.to.sfx.file")], threshold: 0.5, + subtitle: None, }; let previous_state = PreviousEntityState { @@ -70,6 +72,7 @@ fn same_previous_event_elapsed_emits() { let trigger_item = SfxTriggerItem { files: vec![String::from("some.path.to.sfx.file")], threshold: 0.5, + subtitle: None, }; let previous_state = PreviousEntityState { diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 4827624d34..03938a20fd 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -328,6 +328,9 @@ pub struct SfxTriggerItem { pub files: Vec, /// The time to wait before repeating this SfxEvent pub threshold: f32, + + #[serde(default)] + pub subtitle: Option, } #[derive(Deserialize, Default)] @@ -369,7 +372,7 @@ impl SfxMgr { ) { // Checks if the SFX volume is set to zero or audio is disabled // This prevents us from running all the following code unnecessarily - if !audio.sfx_enabled() { + if !audio.sfx_enabled() && !audio.subtitles_enabled { return; } @@ -401,7 +404,7 @@ impl SfxMgr { client: &Client, underwater: bool, ) { - if !audio.sfx_enabled() { + if !audio.sfx_enabled() && !audio.subtitles_enabled { return; } let triggers = self.triggers.read(); diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index d5560143dc..7d3db12c93 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -9,8 +9,6 @@ mod diary; mod esc_menu; mod group; mod hotbar; -pub mod img_ids; -pub mod item_imgs; mod loot_scroller; mod map; mod minimap; @@ -23,7 +21,11 @@ mod settings_window; mod skillbar; mod slots; mod social; +mod subtitles; mod trade; + +pub mod img_ids; +pub mod item_imgs; pub mod util; pub use crafting::CraftingTab; @@ -31,6 +33,7 @@ pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState}; pub use item_imgs::animate_by_pulse; pub use loot_scroller::LootMessage; pub use settings_window::ScaleChange; +pub use subtitles::Subtitle; use bag::Bag; use buffs::BuffsBar; @@ -54,6 +57,7 @@ use serde::{Deserialize, Serialize}; use settings_window::{SettingsTab, SettingsWindow}; use skillbar::Skillbar; use social::Social; +use subtitles::Subtitles; use trade::Trade; use crate::{ @@ -328,6 +332,7 @@ widget_ids! { settings_window, group_window, item_info, + subtitles, // Free look indicator free_look_txt, @@ -1451,7 +1456,7 @@ impl Hud { fn update_layout( &mut self, client: &Client, - global_state: &GlobalState, + global_state: &mut GlobalState, debug_info: &Option, dt: Duration, info: HudInfo, @@ -3343,6 +3348,13 @@ impl Hud { } } + if global_state.settings.audio.subtitles { + Subtitles::new(client, &mut global_state.audio.subtitles, &self.fonts, i18n) + .set(self.ids.subtitles, ui_widgets); + } else { + global_state.audio.subtitles.clear(); + } + self.new_messages = VecDeque::new(); self.new_notifications = VecDeque::new(); diff --git a/voxygen/src/hud/settings_window/accessibility.rs b/voxygen/src/hud/settings_window/accessibility.rs index f1e849ca40..42f1f70270 100644 --- a/voxygen/src/hud/settings_window/accessibility.rs +++ b/voxygen/src/hud/settings_window/accessibility.rs @@ -1,8 +1,9 @@ use crate::{ hud::{img_ids::Imgs, TEXT_COLOR}, + render::RenderMode, session::settings_change::{Accessibility as AccessibilityChange, Accessibility::*}, ui::{fonts::Fonts, ToggleButton}, - GlobalState, render::RenderMode, + GlobalState, }; use conrod_core::{ color, @@ -18,6 +19,8 @@ widget_ids! { flashing_lights_button, flashing_lights_label, flashing_lights_info_label, + subtitles_button, + subtitles_label, } } @@ -84,7 +87,7 @@ impl<'a> Widget for Accessibility<'a> { // Get render mode let render_mode = &self.global_state.settings.graphics.render_mode; - // Disable flashing lights + // Flashing lights Text::new( &self .localized_strings @@ -97,11 +100,7 @@ impl<'a> Widget for Accessibility<'a> { .set(state.ids.flashing_lights_label, ui); let flashing_lights_enabled = ToggleButton::new( - self.global_state - .settings - .graphics - .render_mode - .flashing_lights_enabled, + render_mode.flashing_lights_enabled, self.imgs.checkbox, self.imgs.checkbox_checked, ) @@ -129,6 +128,29 @@ impl<'a> Widget for Accessibility<'a> { }))); } + // Subtitles + Text::new(&self.localized_strings.get_msg("hud-settings-subtitles")) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .down_from(state.ids.flashing_lights_label, 10.0) + .color(TEXT_COLOR) + .set(state.ids.subtitles_label, ui); + + let subtitles_enabled = ToggleButton::new( + self.global_state.settings.audio.subtitles, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0) + .right_from(state.ids.subtitles_label, 10.0) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.subtitles_button, ui); + + if subtitles_enabled != self.global_state.settings.audio.subtitles { + events.push(SetSubtitles(subtitles_enabled)); + } + events } } diff --git a/voxygen/src/hud/settings_window/mod.rs b/voxygen/src/hud/settings_window/mod.rs index 0b01a6680c..a68a04e87a 100644 --- a/voxygen/src/hud/settings_window/mod.rs +++ b/voxygen/src/hud/settings_window/mod.rs @@ -1,3 +1,4 @@ +mod accessibility; mod chat; mod controls; mod gameplay; @@ -6,7 +7,6 @@ mod language; mod networking; mod sound; mod video; -mod accessibility; use crate::{ hud::{img_ids::Imgs, Show, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN}, @@ -356,15 +356,11 @@ impl<'a> Widget for SettingsWindow<'a> { } }, SettingsTab::Accessibility => { - for change in accessibility::Accessibility::new( - global_state, - imgs, - fonts, - localized_strings, - ) - .top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0) - .wh_of(state.ids.settings_content_align) - .set(state.ids.accessibility, ui) + for change in + accessibility::Accessibility::new(global_state, imgs, fonts, localized_strings) + .top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0) + .wh_of(state.ids.settings_content_align) + .set(state.ids.accessibility, ui) { events.push(Event::SettingsChange(change.into())); } diff --git a/voxygen/src/hud/subtitles.rs b/voxygen/src/hud/subtitles.rs new file mode 100644 index 0000000000..22d99f7eba --- /dev/null +++ b/voxygen/src/hud/subtitles.rs @@ -0,0 +1,191 @@ +use std::collections::VecDeque; + +use crate::ui::fonts::Fonts; +use client::Client; +use common::comp; +use conrod_core::{ + widget::{self, Text}, + widget_ids, Colorable, Positionable, Widget, WidgetCommon, +}; +use i18n::Localization; + +use vek::{Vec2, Vec3}; + +widget_ids! { + struct Ids { + subtitle_box, + subtitle_box_bg, + subtitle_message[], + } +} + +#[derive(WidgetCommon)] +pub struct Subtitles<'a> { + client: &'a Client, + + fonts: &'a Fonts, + + new_subtitles: &'a mut VecDeque, + + #[conrod(common_builder)] + common: widget::CommonBuilder, + + localized_strings: &'a Localization, +} + +impl<'a> Subtitles<'a> { + pub fn new( + client: &'a Client, + new_subtitles: &'a mut VecDeque, + fonts: &'a Fonts, + localized_strings: &'a Localization, + ) -> Self { + Self { + client, + fonts, + new_subtitles, + common: widget::CommonBuilder::default(), + localized_strings, + } + } +} + +const MAX_SUBTITLE_DIST: f32 = 80.0; + +#[derive(Debug)] +pub struct Subtitle { + pub localization: String, + pub position: Option>, + pub show_until: f64, +} + +pub struct State { + subtitles: Vec, + ids: Ids, +} + +impl<'a> Widget for Subtitles<'a> { + type Event = (); + type State = State; + type Style = (); + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + subtitles: Vec::new(), + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style {} + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + common_base::prof_span!("Chat::update"); + + let widget::UpdateArgs { state, ui, .. } = args; + let time = self.client.state().get_time(); + let player_pos = self.client.position().unwrap_or_default(); + let player_dir = self + .client + .state() + .read_storage::() + .get(self.client.entity()) + .map_or(Vec3::unit_y(), |ori| ori.look_vec()); + // Empty old subtitles and add new. + state.update(|s| { + s.subtitles.retain(|subtitle| { + time <= subtitle.show_until + && subtitle + .position + .map_or(true, |pos| pos.distance(player_pos) <= MAX_SUBTITLE_DIST) + }); + for mut subtitle in self.new_subtitles.drain(..) { + if subtitle + .position + .map_or(false, |pos| pos.distance(player_pos) > MAX_SUBTITLE_DIST) + { + continue; + } + let t = time + subtitle.show_until; + if let Some(s) = s + .subtitles + .iter_mut() + .find(|s| s.localization == subtitle.localization) + { + if t > s.show_until { + s.show_until = t; + s.position = subtitle.position; + } + } else { + subtitle.show_until = t; + s.subtitles.push(subtitle); + } + } + s.ids + .subtitle_message + .resize(s.subtitles.len(), &mut ui.widget_id_generator()); + }); + + let mut subtitles = state + .ids + .subtitle_message + .iter() + .zip(state.subtitles.iter()); + + let fade_amount = |t: &Subtitle| ((t.show_until - time) * 1.5).clamp(0.0, 1.0) as f32; + + let player_dir = player_dir.xy().try_normalized().unwrap_or(Vec2::unit_y()); + let player_right = Vec2::new(player_dir.y, -player_dir.x); + + let message = |subtitle: &Subtitle| { + let message = self.localized_strings.get_msg(&subtitle.localization); + let is_right = subtitle.position.and_then(|pos| { + let dist = pos.distance(player_pos); + let dir = (pos - player_pos) / dist; + + let dot = dir.xy().dot(player_dir); + if dist < 2.0 || dot > 0.85 { + None + } else { + Some(dir.xy().dot(player_right) >= 0.0) + } + }); + + match is_right { + Some(true) => format!(" {message} >"), + Some(false) => format!("< {message} "), + None => format!(" {message} "), + } + }; + + if let Some((id, subtitle)) = subtitles.next() { + Text::new(&message(subtitle)) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .center_justify() + .bottom_right_with_margins(40.0, 50.0) + .color(conrod_core::Color::Rgba( + 0.9, + 1.0, + 1.0, + fade_amount(subtitle), + )) + .set(*id, ui); + let mut last_id = *id; + for (id, subtitle) in subtitles { + Text::new(&message(subtitle)) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .center_justify() + .up_from(last_id, 10.0) + .color(conrod_core::Color::Rgba( + 0.9, + 1.0, + 1.0, + fade_amount(subtitle), + )) + .set(*id, ui); + last_id = *id; + } + } + } +} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 2854e5b0c1..f8e0ed9622 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -139,6 +139,7 @@ fn main() { AudioOutput::Automatic => AudioFrontend::new( settings.audio.num_sfx_channels, settings.audio.num_ui_channels, + settings.audio.subtitles, ), // AudioOutput::Device(ref dev) => Some(dev.clone()), }; diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index 4324f4bb84..ad1688ddd8 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -175,6 +175,7 @@ pub enum Networking { #[derive(Clone)] pub enum Accessibility { ChangeRenderMode(Box), + SetSubtitles(bool), } #[derive(Clone)] @@ -744,7 +745,11 @@ impl SettingsChange { SettingsChange::Accessibility(accessibility_change) => match accessibility_change { Accessibility::ChangeRenderMode(new_render_mode) => { change_render_mode(*new_render_mode, &mut global_state.window, settings); - } + }, + Accessibility::SetSubtitles(enabled) => { + global_state.settings.audio.subtitles = enabled; + global_state.audio.set_subtitles(enabled); + }, }, } global_state diff --git a/voxygen/src/settings/audio.rs b/voxygen/src/settings/audio.rs index 7e43300b3a..a5adb20978 100644 --- a/voxygen/src/settings/audio.rs +++ b/voxygen/src/settings/audio.rs @@ -47,6 +47,7 @@ pub struct AudioSettings { pub num_sfx_channels: usize, pub num_ui_channels: usize, pub music_spacing: f32, + pub subtitles: bool, /// Audio Device that Voxygen will use to play audio. pub output: AudioOutput, @@ -63,6 +64,7 @@ impl Default for AudioSettings { num_sfx_channels: 60, num_ui_channels: 10, music_spacing: 1.0, + subtitles: false, output: AudioOutput::Automatic, } } From 8bab047c87ccaf9f2f251f64649d72f2f121a943 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 26 Apr 2023 16:25:41 +0200 Subject: [PATCH 3/9] add to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11959e20ed..5e13e50203 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - NPCs will migrate to new towns if they are dissatisfied with their current town - Female humanoids now have a greeting sound effect - Loot that drops multiple items is now distributed fairly between damage contributors. +- Added accessibility settings tab. +- Setting to enable subtitles describing sfx. ### Changed From a89fcd1a38488ecdca41a3435fa06f1f00ff2716 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 26 Apr 2023 16:56:55 +0200 Subject: [PATCH 4/9] swedish subtitles --- assets/voxygen/i18n/sv_SE/hud/subtitles.ftl | 148 ++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 assets/voxygen/i18n/sv_SE/hud/subtitles.ftl diff --git a/assets/voxygen/i18n/sv_SE/hud/subtitles.ftl b/assets/voxygen/i18n/sv_SE/hud/subtitles.ftl new file mode 100644 index 0000000000..1686a4d722 --- /dev/null +++ b/assets/voxygen/i18n/sv_SE/hud/subtitles.ftl @@ -0,0 +1,148 @@ +subtitle-campfire = Lägereld knastrar +subtitle-bird_call = Fåglar sjunger +subtitle-bees = Bin surrar +subtitle-owl = Uggla tutar +subtitle-running_water = Vatten bubblar +subtitle-lightning = Åska + +subtitle-footsteps_grass = Steg på gräs +subtitle-footsteps_earth = Steg på jord +subtitle-footsteps_rock = Steg på sten +subtitle-footsteps_snow = Steg på snö +subtitle-pickup_item = Föremål upplockat +subtitle-pickup_failed = Misslyckad upplockning + +subtitle-glider_open = Glidare framtagen +subtitle-glider_close = Glidare bortlagd +subtitle-glide = Glidning +subtitle-roll = Rullning +subtitle-swim = Simmning +subtitle-climb = Klättring +subtitle-damage = Skada +subtitle-death = Död + +subtitle-wield_bow = Båge framtagen +subtitle-unwield_bow = Båge bortlagd +subtitle-pickup_bow = Båge upplockad + +subtitle-wield_sword = Svärd framtagen +subtitle-unwield_sword = Svärd bortlagd +subtitle-sword_attack = Svärd svigning +subtitle-pickup_sword = Svärd upplockad + +subtitle-wield_axe = Yxa framtagen +subtitle-unwield_axe = Yxa bortlagd +subtitle-axe_attack = Yx svigning +subtitle-pickup_axe = Yxa upplockad + +subtitle-wield_hammer = Hammare framtagen +subtitle-unwield_hammer = Hammare bortlagd +subtitle-hammer_attack = Hammar svigning +subtitle-pickup_hammer = Hammare upplockad + +subtitle-wield_staff = Stav framtagen +subtitle-unwield_staff = Stav bortlagd +subtitle-fire_shot = Stav avfyrad +subtitle-staff_attack = Stav avfyrad +subtitle-pickup_staff = Stav upplockad + +subtitle-wield_sceptre = Spira framtagen +subtitle-unwield_sceptre = Spira bortlagd +subtitle-sceptre_heal = Spira healande aura +subtitle-pickup_sceptre = Spira upplockad + +subtitle-wield_dagger = Dolk framtagen +subtitle-uwield_dagger = Dolk bortlagd +subtitle-dagger_attack = Dolk svigning +subtitle-pickup_dagger = Dolk upplockad + +subtitle-wield_shield = Shöld framtagen +subtitle-unwield_shield = Shöld bortlagd +subtitle-shield_attack = Shöld knuff +subtitle-pickup_shield = Shöld upplockad + +subtitle-pickup_pick = Hacka upplockad +subtitle-pickup_gemstone = Ädelsten upplockad + +subtitle-instrument_organ = Orgel spelning + +subtitle-wield_instrument = Instrument framtagen +subtitle-unwield_instrument = Instrument bortlagd +subtitle-instrument_double_bass = Kontrabas spelning +subtitle-instrument_flute = Flöjt spelning +subtitle-instrument_glass_flute = Glas Flöjt spelning +subtitle-instrument_lyre = Lyra spelning +subtitle-instrument_icy_talharpa = Isig Talharpa spelning +subtitle-instrument_kalimba = Kalimba spelning +subtitle-instrument_melodica = Melodica spelning +subtitle-instrument_lute = Luta spelning +subtitle-instrument_sitar = Sitar spelning +subtitle-instrument_guitar = Gitarr spelning +subtitle-instrument_dark_guitar = Mörk Gitarr spelning +subtitle-instrument_washboard = Tvättbräda spelning +subtitle-pickup_instrument = Instrument upplockad + +subtitle-explosion = Explosion + +subtitle-arrow_shot = Pil skjuten +subtitle-arrow_miss = Pil miss +subtitle-arrow_hit = Pil träff +subtitle-skill_point = Nytt färdighetspoäng +subtitle-sceptre_beam = Spirstråle +subtitle-flame_thrower = Flamkastare +subtitle-break_block = Block förstört +subtitle-attack_blocked = Attack blockerad +subtitle-parry = Parerad +subtitle-interrupted = Avbruten +subtitle-stunned = Överväldigad +subtitle-dazed = Förvirrad +subtitle-knocked_down = Nedslagen + +subtitle-attack-ground_slam = Marksmäll +subtitle-attack-laser_beam = Laserstråle +subtitle-attack-cyclops_charge = Cyclopsanfallning +subtitle-giga_roar = Frost gigas vrål +subtitle-attack-flash_freeze = Blixtfrysning +subtitle-attack-icy_spikes = Istappar +subtitle-attack-ice_crack = Isspricka + +subtitle-consume_potion = Dricker förtrollningsdryck +subtitle-consume_apple = Äter äpple +subtitle-consume_cheese = Äter ost +subtitle-consume_food = Äter +subtitle-consume_liquid = Dricker + +subtitle-utterance-alligator-angry = Alligator väser +subtitle-utterance-antelope-angry = Antilop fnyser +subtitle-utterance-biped_large-angry = Tung grymtning +subtitle-utterance-bird-angry = Fågel skriker gällt +subtitle-utterance-adlet-angry = Adlet skäller +subtitle-utterance-pig-angry = Gris grymtar +subtitle-utterance-reptile-angry = Reptil väser +subtitle-utterance-sea_crocodile-angry = Saltvattens krokodil väser +subtitle-utterance-saurok-angry = Saurok väser +subtitle-utterance-cat-calm = Katt jamar +subtitle-utterance-cow-calm = Ko råmar +subtitle-utterance-fungome-calm = Fungome piper +subtitle-utterance-goat-calm = Get bräker +subtitle-utterance-pig-calm = Gris nöffar +subtitle-utterance-sheep-calm = Sheep bräker +subtitle-utterance-truffler-calm = Truffler nöffar +subtitle-utterance-human-greeting = Hälsning +subtitle-utterance-adlet-hurt = Adlet gnäller +subtitle-utterance-antelope-hurt = Antilop skriker +subtitle-utterance-biped_large-hurt = Tung gnällning +subtitle-utterance-human-hurt = Människa har ont +subtitle-utterance-lion-hurt = Lejon morrar +subtitle-utterance-mandroga-hurt = Mandroga skriker +subtitle-utterance-maneater-hurt = Maneater rapar +subtitle-utterance-marlin-hurt = Marlin har ont +subtitle-utterance-mindflayer-hurt = Mindflayer har ont +subtitle-utterance-dagon-hurt = Dagon har ont +subtitle-utterance-asp-angry = Asp väser +subtitle-utterance-asp-calm = Asp kväker +subtitle-utterance-asp-hurt = Asp har ont +subtitle-utterance-wendigo-angry = Wendigo skriker +subtitle-utterance-wendigo-calm = Wendigo mumlar +subtitle-utterance-wolf-angry = Wolf morrar +subtitle-utterance-wolf-hurt = Wolf gnäller \ No newline at end of file From f60c44d812a970e1e22c96a853fc65588d7a761e Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 26 Apr 2023 20:38:35 +0200 Subject: [PATCH 5/9] nicer ui --- voxygen/src/hud/mod.rs | 10 +++- voxygen/src/hud/subtitles.rs | 104 +++++++++++++++++++++++------------ 2 files changed, 78 insertions(+), 36 deletions(-) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 7d3db12c93..c76c101002 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -3349,8 +3349,14 @@ impl Hud { } if global_state.settings.audio.subtitles { - Subtitles::new(client, &mut global_state.audio.subtitles, &self.fonts, i18n) - .set(self.ids.subtitles, ui_widgets); + Subtitles::new( + client, + &global_state.settings, + &mut global_state.audio.subtitles, + &self.fonts, + i18n, + ) + .set(self.ids.subtitles, ui_widgets); } else { global_state.audio.subtitles.clear(); } diff --git a/voxygen/src/hud/subtitles.rs b/voxygen/src/hud/subtitles.rs index 22d99f7eba..42f36aa16b 100644 --- a/voxygen/src/hud/subtitles.rs +++ b/voxygen/src/hud/subtitles.rs @@ -1,11 +1,11 @@ use std::collections::VecDeque; -use crate::ui::fonts::Fonts; +use crate::{settings::Settings, ui::fonts::Fonts}; use client::Client; use common::comp; use conrod_core::{ - widget::{self, Text}, - widget_ids, Colorable, Positionable, Widget, WidgetCommon, + widget::{self, Id, Rectangle, Text}, + widget_ids, Colorable, Positionable, UiCell, Widget, WidgetCommon, }; use i18n::Localization; @@ -13,15 +13,16 @@ use vek::{Vec2, Vec3}; widget_ids! { struct Ids { - subtitle_box, subtitle_box_bg, subtitle_message[], + subtitle_dir[], } } #[derive(WidgetCommon)] pub struct Subtitles<'a> { client: &'a Client, + settings: &'a Settings, fonts: &'a Fonts, @@ -36,12 +37,14 @@ pub struct Subtitles<'a> { impl<'a> Subtitles<'a> { pub fn new( client: &'a Client, + settings: &'a Settings, new_subtitles: &'a mut VecDeque, fonts: &'a Fonts, localized_strings: &'a Localization, ) -> Self { Self { client, + settings, fonts, new_subtitles, common: widget::CommonBuilder::default(), @@ -123,21 +126,26 @@ impl<'a> Widget for Subtitles<'a> { s.ids .subtitle_message .resize(s.subtitles.len(), &mut ui.widget_id_generator()); + s.ids + .subtitle_dir + .resize(s.subtitles.len(), &mut ui.widget_id_generator()); }); - let mut subtitles = state - .ids - .subtitle_message - .iter() - .zip(state.subtitles.iter()); - - let fade_amount = |t: &Subtitle| ((t.show_until - time) * 1.5).clamp(0.0, 1.0) as f32; + let color = |t: &Subtitle| { + conrod_core::Color::Rgba( + 0.9, + 1.0, + 1.0, + ((t.show_until - time) * 1.5).clamp(0.0, 1.0) as f32, + ) + }; let player_dir = player_dir.xy().try_normalized().unwrap_or(Vec2::unit_y()); let player_right = Vec2::new(player_dir.y, -player_dir.x); - let message = |subtitle: &Subtitle| { - let message = self.localized_strings.get_msg(&subtitle.localization); + let message = |subtitle: &Subtitle| self.localized_strings.get_msg(&subtitle.localization); + + let dir = |subtitle: &Subtitle, id: &Id, dir_id: &Id, ui: &mut UiCell| { let is_right = subtitle.position.and_then(|pos| { let dist = pos.distance(player_pos); let dir = (pos - player_pos) / dist; @@ -149,41 +157,69 @@ impl<'a> Widget for Subtitles<'a> { Some(dir.xy().dot(player_right) >= 0.0) } }); - match is_right { - Some(true) => format!(" {message} >"), - Some(false) => format!("< {message} "), - None => format!(" {message} "), + Some(true) => Text::new("> ") + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .parent(state.ids.subtitle_box_bg) + .align_right_of(state.ids.subtitle_box_bg) + .align_middle_y_of(*id) + .color(color(subtitle)) + .set(*dir_id, ui), + Some(false) => Text::new(" <") + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .parent(state.ids.subtitle_box_bg) + .align_left_of(state.ids.subtitle_box_bg) + .align_middle_y_of(*id) + .color(color(subtitle)) + .set(*dir_id, ui), + None => Text::new("") + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .parent(state.ids.subtitle_box_bg) + .color(color(subtitle)) + .set(*dir_id, ui), } }; - if let Some((id, subtitle)) = subtitles.next() { + Rectangle::fill([200.0, 2.0 + 22.0 * state.subtitles.len() as f64]) + .rgba(0.0, 0.0, 0.0, self.settings.chat.chat_opacity) + .bottom_right_with_margins_on(ui.window, 40.0, 50.0) + .set(state.ids.subtitle_box_bg, ui); + + let mut subtitles = state + .ids + .subtitle_message + .iter() + .zip(state.ids.subtitle_dir.iter()) + .zip(state.subtitles.iter()); + + if let Some(((id, dir_id), subtitle)) = subtitles.next() { Text::new(&message(subtitle)) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) + .parent(state.ids.subtitle_box_bg) .center_justify() - .bottom_right_with_margins(40.0, 50.0) - .color(conrod_core::Color::Rgba( - 0.9, - 1.0, - 1.0, - fade_amount(subtitle), - )) + .mid_bottom_with_margin_on(state.ids.subtitle_box_bg, 6.0) + .color(color(subtitle)) .set(*id, ui); + + dir(subtitle, id, dir_id, ui); + let mut last_id = *id; - for (id, subtitle) in subtitles { + for ((id, dir_id), subtitle) in subtitles { Text::new(&message(subtitle)) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) - .center_justify() - .up_from(last_id, 10.0) - .color(conrod_core::Color::Rgba( - 0.9, - 1.0, - 1.0, - fade_amount(subtitle), - )) + .parent(state.ids.subtitle_box_bg) + .up_from(last_id, 8.0) + .align_middle_x_of(last_id) + .color(color(subtitle)) .set(*id, ui); + + dir(subtitle, id, dir_id, ui); + last_id = *id; } } From 54a7367cb2001e530a411ab56754c12142294e57 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 3 May 2023 13:05:54 +0200 Subject: [PATCH 6/9] Maintain all subtitles, limit calls to state.update --- voxygen/src/audio/mod.rs | 59 ++++---- voxygen/src/hud/mod.rs | 3 +- voxygen/src/hud/subtitles.rs | 265 ++++++++++++++++++++++++----------- 3 files changed, 215 insertions(+), 112 deletions(-) diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index a083169c9e..7a08785a8f 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -25,8 +25,8 @@ use crate::hud::Subtitle; #[derive(Default, Clone)] pub struct Listener { - pos: Vec3, - ori: Vec3, + pub pos: Vec3, + pub ori: Vec3, ear_left_rpos: Vec3, ear_right_rpos: Vec3, @@ -64,7 +64,8 @@ pub struct AudioFrontend { impl AudioFrontend { /// Construct with given device pub fn new( - /* dev: String, */ num_sfx_channels: usize, + /* dev: String, */ + num_sfx_channels: usize, num_ui_channels: usize, subtitles: bool, ) -> Self { @@ -268,15 +269,7 @@ impl AudioFrontend { underwater: bool, ) { if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) { - if self.subtitles_enabled { - if let Some(subtitle) = subtitle { - self.subtitles.push_back(Subtitle { - localization: subtitle.to_string(), - position: Some(position), - show_until: dur.max(1.5) as f64, - }) - } - } + self.emit_subtitle(subtitle, Some(position), dur); // Play sound in empty channel at given position if self.audio_stream.is_some() && self.sfx_enabled() { let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0)); @@ -310,15 +303,7 @@ impl AudioFrontend { underwater: bool, ) { if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) { - if self.subtitles_enabled { - if let Some(subtitle) = subtitle { - self.subtitles.push_back(Subtitle { - localization: subtitle.to_string(), - position: Some(position), - show_until: dur.max(1.5) as f64, - }) - } - } + self.emit_subtitle(subtitle, Some(position), dur); // Play sound in empty channel at given position if self.audio_stream.is_some() && self.sfx_enabled() { let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0)); @@ -353,15 +338,7 @@ impl AudioFrontend { volume: Option, ) { if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) { - if self.subtitles_enabled { - if let Some(subtitle) = subtitle { - self.subtitles.push_back(Subtitle { - localization: subtitle.to_string(), - position: None, - show_until: dur.max(1.5) as f64, - }) - } - } + self.emit_subtitle(subtitle, None, dur); // Play sound in empty channel if self.audio_stream.is_some() && self.sfx_enabled() { let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0)); @@ -374,6 +351,26 @@ impl AudioFrontend { } } + pub fn emit_subtitle( + &mut self, + subtitle: Option<&str>, + position: Option>, + duration: f32, + ) { + if self.subtitles_enabled { + if let Some(subtitle) = subtitle { + self.subtitles.push_back(Subtitle { + localization: subtitle.to_string(), + position, + show_for: duration as f64, + }); + if self.subtitles.len() > 10 { + self.subtitles.pop_front(); + } + } + } + } + /// Plays a file at a given volume in the channel with a given tag fn play_ambient(&mut self, channel_tag: AmbientChannelTag, sound: &str, volume: Option) { if self.audio_stream.is_some() { @@ -485,6 +482,8 @@ impl AudioFrontend { } } + pub fn get_listener(&self) -> &Listener { &self.listener } + /// Switches the playing music to the title music, which is pinned to a /// specific sound file (veloren_title_tune.ogg) pub fn play_title_music(&mut self) { diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index c76c101002..06ca363e60 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -3352,13 +3352,12 @@ impl Hud { Subtitles::new( client, &global_state.settings, + &global_state.audio.get_listener().clone(), &mut global_state.audio.subtitles, &self.fonts, i18n, ) .set(self.ids.subtitles, ui_widgets); - } else { - global_state.audio.subtitles.clear(); } self.new_messages = VecDeque::new(); diff --git a/voxygen/src/hud/subtitles.rs b/voxygen/src/hud/subtitles.rs index 42f36aa16b..00d934f9da 100644 --- a/voxygen/src/hud/subtitles.rs +++ b/voxygen/src/hud/subtitles.rs @@ -1,8 +1,7 @@ -use std::collections::VecDeque; +use std::{cmp::Ordering, collections::VecDeque}; -use crate::{settings::Settings, ui::fonts::Fonts}; +use crate::{audio::Listener, settings::Settings, ui::fonts::Fonts}; use client::Client; -use common::comp; use conrod_core::{ widget::{self, Id, Rectangle, Text}, widget_ids, Colorable, Positionable, UiCell, Widget, WidgetCommon, @@ -23,6 +22,7 @@ widget_ids! { pub struct Subtitles<'a> { client: &'a Client, settings: &'a Settings, + listener: &'a Listener, fonts: &'a Fonts, @@ -38,6 +38,7 @@ impl<'a> Subtitles<'a> { pub fn new( client: &'a Client, settings: &'a Settings, + listener: &'a Listener, new_subtitles: &'a mut VecDeque, fonts: &'a Fonts, localized_strings: &'a Localization, @@ -45,6 +46,7 @@ impl<'a> Subtitles<'a> { Self { client, settings, + listener, fonts, new_subtitles, common: widget::CommonBuilder::default(), @@ -53,17 +55,115 @@ impl<'a> Subtitles<'a> { } } +const MIN_SUBTITLE_DURATION: f64 = 1.5; const MAX_SUBTITLE_DIST: f32 = 80.0; #[derive(Debug)] pub struct Subtitle { pub localization: String, + /// Position the sound is played at, if any. pub position: Option>, - pub show_until: f64, + /// Amount of seconds to show the subtitle for. + pub show_for: f64, +} + +#[derive(Clone, PartialEq)] +struct SubtitleData { + position: Option>, + /// `Time` to show until. + show_until: f64, +} + +impl SubtitleData { + /// Prioritize showing nearby sounds, and secondarily prioritize longer + /// living sounds. + fn compare_priority(&self, other: &Self, listener_pos: Vec3) -> Ordering { + let life_cmp = self + .show_until + .partial_cmp(&other.show_until) + .unwrap_or(Ordering::Equal); + match (self.position, other.position) { + (Some(a), Some(b)) => match a + .distance_squared(listener_pos) + .partial_cmp(&b.distance_squared(listener_pos)) + .unwrap_or(Ordering::Equal) + { + Ordering::Equal => life_cmp, + Ordering::Less => Ordering::Greater, + Ordering::Greater => Ordering::Less, + }, + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (None, None) => life_cmp, + } + } +} + +#[derive(Clone)] +struct SubtitleList { + subtitles: Vec<(String, Vec)>, +} + +impl SubtitleList { + fn new() -> Self { + Self { + subtitles: Vec::new(), + } + } + + /// Updates the subtitle state, returns the amount of subtitles that should + /// be displayed. + fn update( + &mut self, + new_subtitles: impl Iterator, + time: f64, + listener_pos: Vec3, + ) -> usize { + for subtitle in new_subtitles { + let show_until = time + subtitle.show_for.max(MIN_SUBTITLE_DURATION); + let data = SubtitleData { + position: subtitle.position, + show_until, + }; + if let Some((_, datas)) = self + .subtitles + .iter_mut() + .find(|(key, _)| key == &subtitle.localization) + { + datas.push(data); + } else { + self.subtitles.push((subtitle.localization, vec![data])) + } + } + let mut to_display = 0; + self.subtitles.retain_mut(|(_, data)| { + data.retain(|subtitle| subtitle.show_until > time); + // Place the most prioritized subtitle in the back. + if let Some((i, s)) = data + .iter() + .enumerate() + .max_by(|(_, a), (_, b)| a.compare_priority(b, listener_pos)) + { + // We only display subtitles that are in range. + if s.position.map_or(true, |pos| { + pos.distance_squared(listener_pos) < MAX_SUBTITLE_DIST * MAX_SUBTITLE_DIST + }) { + to_display += 1; + } + let last = data.len() - 1; + data.swap(i, last); + true + } else { + // If data is empty we have no sounds with this key. + false + } + }); + to_display + } } pub struct State { - subtitles: Vec, + subtitles: SubtitleList, ids: Ids, } @@ -74,7 +174,7 @@ impl<'a> Widget for Subtitles<'a> { fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { State { - subtitles: Vec::new(), + subtitles: SubtitleList::new(), ids: Ids::new(id_gen), } } @@ -86,79 +186,71 @@ impl<'a> Widget for Subtitles<'a> { let widget::UpdateArgs { state, ui, .. } = args; let time = self.client.state().get_time(); - let player_pos = self.client.position().unwrap_or_default(); - let player_dir = self - .client - .state() - .read_storage::() - .get(self.client.entity()) - .map_or(Vec3::unit_y(), |ori| ori.look_vec()); - // Empty old subtitles and add new. - state.update(|s| { - s.subtitles.retain(|subtitle| { - time <= subtitle.show_until - && subtitle - .position - .map_or(true, |pos| pos.distance(player_pos) <= MAX_SUBTITLE_DIST) - }); - for mut subtitle in self.new_subtitles.drain(..) { - if subtitle - .position - .map_or(false, |pos| pos.distance(player_pos) > MAX_SUBTITLE_DIST) - { - continue; - } - let t = time + subtitle.show_until; - if let Some(s) = s - .subtitles - .iter_mut() - .find(|s| s.localization == subtitle.localization) - { - if t > s.show_until { - s.show_until = t; - s.position = subtitle.position; - } - } else { - subtitle.show_until = t; - s.subtitles.push(subtitle); - } - } - s.ids - .subtitle_message - .resize(s.subtitles.len(), &mut ui.widget_id_generator()); - s.ids - .subtitle_dir - .resize(s.subtitles.len(), &mut ui.widget_id_generator()); - }); + let listener_pos = self.listener.pos; + let listener_forward = self.listener.ori; - let color = |t: &Subtitle| { + // Update subtitles and look for changes + let mut subtitles = state.subtitles.clone(); + + let has_new = !self.new_subtitles.is_empty(); + + let show_count = subtitles.update(self.new_subtitles.drain(..), time, listener_pos); + + let subtitles = if has_new || show_count != state.ids.subtitle_message.len() { + state.update(|s| { + s.subtitles = subtitles; + s.ids + .subtitle_message + .resize(show_count, &mut ui.widget_id_generator()); + s.ids + .subtitle_dir + .resize(show_count, &mut ui.widget_id_generator()); + }); + &state.subtitles + } else { + &subtitles + }; + let color = |t: &SubtitleData| -> conrod_core::Color { conrod_core::Color::Rgba( 0.9, 1.0, 1.0, - ((t.show_until - time) * 1.5).clamp(0.0, 1.0) as f32, + ((t.show_until - time) * 2.0).clamp(0.0, 1.0) as f32, ) }; - let player_dir = player_dir.xy().try_normalized().unwrap_or(Vec2::unit_y()); - let player_right = Vec2::new(player_dir.y, -player_dir.x); + let listener_forward = listener_forward + .xy() + .try_normalized() + .unwrap_or(Vec2::unit_y()); + let listener_right = Vec2::new(listener_forward.y, -listener_forward.x); - let message = |subtitle: &Subtitle| self.localized_strings.get_msg(&subtitle.localization); + let dir = |subtitle: &SubtitleData, id: &Id, dir_id: &Id, ui: &mut UiCell| { + enum Side { + /// Also used for sounds without direction. + Forward, + Right, + Left, + } + let is_right = subtitle + .position + .map(|pos| { + let dist = pos.distance(listener_pos); + let dir = (pos - listener_pos) / dist; - let dir = |subtitle: &Subtitle, id: &Id, dir_id: &Id, ui: &mut UiCell| { - let is_right = subtitle.position.and_then(|pos| { - let dist = pos.distance(player_pos); - let dir = (pos - player_pos) / dist; + let dot = dir.xy().dot(listener_forward); + if dist < 2.0 || dot > 0.85 { + Side::Forward + } else if dir.xy().dot(listener_right) >= 0.0 { + Side::Right + } else { + Side::Left + } + }) + .unwrap_or(Side::Forward); - let dot = dir.xy().dot(player_dir); - if dist < 2.0 || dot > 0.85 { - None - } else { - Some(dir.xy().dot(player_right) >= 0.0) - } - }); match is_right { - Some(true) => Text::new("> ") + Side::Right => Text::new("> ") .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) .parent(state.ids.subtitle_box_bg) @@ -166,7 +258,7 @@ impl<'a> Widget for Subtitles<'a> { .align_middle_y_of(*id) .color(color(subtitle)) .set(*dir_id, ui), - Some(false) => Text::new(" <") + Side::Left => Text::new(" <") .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) .parent(state.ids.subtitle_box_bg) @@ -174,7 +266,7 @@ impl<'a> Widget for Subtitles<'a> { .align_middle_y_of(*id) .color(color(subtitle)) .set(*dir_id, ui), - None => Text::new("") + Side::Forward => Text::new("") .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) .parent(state.ids.subtitle_box_bg) @@ -183,9 +275,9 @@ impl<'a> Widget for Subtitles<'a> { } }; - Rectangle::fill([200.0, 2.0 + 22.0 * state.subtitles.len() as f64]) + Rectangle::fill([200.0, 22.0 * show_count as f64]) .rgba(0.0, 0.0, 0.0, self.settings.chat.chat_opacity) - .bottom_right_with_margins_on(ui.window, 40.0, 50.0) + .bottom_right_with_margins_on(ui.window, 40.0, 30.0) .set(state.ids.subtitle_box_bg, ui); let mut subtitles = state @@ -193,32 +285,45 @@ impl<'a> Widget for Subtitles<'a> { .subtitle_message .iter() .zip(state.ids.subtitle_dir.iter()) - .zip(state.subtitles.iter()); + .zip( + subtitles + .subtitles + .iter() + .filter_map(|(localization, data)| { + Some((self.localized_strings.get_msg(localization), data.last()?)) + }) + .filter(|(_, data)| { + data.position.map_or(true, |pos| { + pos.distance_squared(listener_pos) + < MAX_SUBTITLE_DIST * MAX_SUBTITLE_DIST + }) + }), + ); - if let Some(((id, dir_id), subtitle)) = subtitles.next() { - Text::new(&message(subtitle)) + if let Some(((id, dir_id), (message, data))) = subtitles.next() { + Text::new(&message) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) .parent(state.ids.subtitle_box_bg) .center_justify() .mid_bottom_with_margin_on(state.ids.subtitle_box_bg, 6.0) - .color(color(subtitle)) + .color(color(data)) .set(*id, ui); - dir(subtitle, id, dir_id, ui); + dir(data, id, dir_id, ui); let mut last_id = *id; - for ((id, dir_id), subtitle) in subtitles { - Text::new(&message(subtitle)) + for ((id, dir_id), (message, data)) in subtitles { + Text::new(&message) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) .parent(state.ids.subtitle_box_bg) .up_from(last_id, 8.0) .align_middle_x_of(last_id) - .color(color(subtitle)) + .color(color(data)) .set(*id, ui); - dir(subtitle, id, dir_id, ui); + dir(data, id, dir_id, ui); last_id = *id; } From 77b7d1a49e56239ad9915de0dda811ab2d3b362d Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 3 May 2023 16:10:14 +0200 Subject: [PATCH 7/9] localize after distance filter --- voxygen/src/hud/subtitles.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/voxygen/src/hud/subtitles.rs b/voxygen/src/hud/subtitles.rs index 00d934f9da..97e9fe968c 100644 --- a/voxygen/src/hud/subtitles.rs +++ b/voxygen/src/hud/subtitles.rs @@ -290,13 +290,16 @@ impl<'a> Widget for Subtitles<'a> { .subtitles .iter() .filter_map(|(localization, data)| { - Some((self.localized_strings.get_msg(localization), data.last()?)) + Some((localization, data.last()?)) }) .filter(|(_, data)| { data.position.map_or(true, |pos| { pos.distance_squared(listener_pos) < MAX_SUBTITLE_DIST * MAX_SUBTITLE_DIST }) + }) + .map(|(localization, data)| { + (self.localized_strings.get_msg(localization), data) }), ); From 922ba9ed5c8f028ec37100ab9afbf83a7cf12cf8 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 3 May 2023 16:15:28 +0200 Subject: [PATCH 8/9] add note about using threshold for subtitle duration --- voxygen/src/audio/mod.rs | 3 +++ voxygen/src/hud/subtitles.rs | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index 7a08785a8f..533ad6a47b 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -255,6 +255,9 @@ impl AudioFrontend { }, }; + // NOTE: Threshold here is meant to give subtitles some idea of the duration of + // the audio, it doesn't have to be perfect but in the future, if possible we + // might want to switch it out for the actual duration. (file, item.threshold, item.subtitle.as_deref()) }) } diff --git a/voxygen/src/hud/subtitles.rs b/voxygen/src/hud/subtitles.rs index 97e9fe968c..70d15ed61f 100644 --- a/voxygen/src/hud/subtitles.rs +++ b/voxygen/src/hud/subtitles.rs @@ -289,9 +289,7 @@ impl<'a> Widget for Subtitles<'a> { subtitles .subtitles .iter() - .filter_map(|(localization, data)| { - Some((localization, data.last()?)) - }) + .filter_map(|(localization, data)| Some((localization, data.last()?))) .filter(|(_, data)| { data.position.map_or(true, |pos| { pos.distance_squared(listener_pos) From 44bf8f19f153d64b8d1c2747dec3ff135a4f2edc Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 3 May 2023 16:21:12 +0200 Subject: [PATCH 9/9] combine into one filter_map --- voxygen/src/hud/subtitles.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/voxygen/src/hud/subtitles.rs b/voxygen/src/hud/subtitles.rs index 70d15ed61f..70eeb70cc3 100644 --- a/voxygen/src/hud/subtitles.rs +++ b/voxygen/src/hud/subtitles.rs @@ -289,15 +289,14 @@ impl<'a> Widget for Subtitles<'a> { subtitles .subtitles .iter() - .filter_map(|(localization, data)| Some((localization, data.last()?))) - .filter(|(_, data)| { - data.position.map_or(true, |pos| { - pos.distance_squared(listener_pos) - < MAX_SUBTITLE_DIST * MAX_SUBTITLE_DIST - }) - }) - .map(|(localization, data)| { - (self.localized_strings.get_msg(localization), data) + .filter_map(|(localization, data)| { + let data = data.last()?; + data.position + .map_or(true, |pos| { + pos.distance_squared(listener_pos) + < MAX_SUBTITLE_DIST * MAX_SUBTITLE_DIST + }) + .then(|| (self.localized_strings.get_msg(localization), data)) }), );