From 99463a37f88e1414aaa90b448b7e0daff965d095 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 26 Apr 2023 16:20:56 +0200 Subject: [PATCH] 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, } }