mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'isse/accessability' into 'master'
Accessibility settings tab, and subtitles See merge request veloren/veloren!3901
This commit is contained in:
commit
4e3eb3ef44
@ -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
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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?
|
||||
|
@ -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
|
||||
|
148
assets/voxygen/i18n/en/hud/subtitles.ftl
Normal file
148
assets/voxygen/i18n/en/hud/subtitles.ftl
Normal file
@ -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
|
148
assets/voxygen/i18n/sv_SE/hud/subtitles.ftl
Normal file
148
assets/voxygen/i18n/sv_SE/hud/subtitles.ftl
Normal file
@ -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
|
@ -14,17 +14,19 @@ 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<f32>,
|
||||
ori: Vec3<f32>,
|
||||
pub pos: Vec3<f32>,
|
||||
pub ori: Vec3<f32>,
|
||||
|
||||
ear_left_rpos: Vec3<f32>,
|
||||
ear_right_rpos: Vec3<f32>,
|
||||
@ -53,12 +55,20 @@ pub struct AudioFrontend {
|
||||
music_spacing: f32,
|
||||
listener: Listener,
|
||||
|
||||
pub subtitles_enabled: bool,
|
||||
pub subtitles: VecDeque<Subtitle>,
|
||||
|
||||
mtm: AssetHandle<MusicTransitionManifest>,
|
||||
}
|
||||
|
||||
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 +116,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 +141,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 +237,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 +253,12 @@ impl AudioFrontend {
|
||||
let rand_step = rand::random::<usize>() % item.files.len();
|
||||
&item.files[rand_step]
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// 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())
|
||||
})
|
||||
}
|
||||
|
||||
@ -252,11 +271,11 @@ impl AudioFrontend {
|
||||
volume: Option<f32>,
|
||||
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) {
|
||||
self.emit_subtitle(subtitle, Some(position), dur);
|
||||
// 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 +305,11 @@ impl AudioFrontend {
|
||||
freq: Option<u32>,
|
||||
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) {
|
||||
self.emit_subtitle(subtitle, Some(position), dur);
|
||||
// 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 +340,11 @@ impl AudioFrontend {
|
||||
trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
|
||||
volume: Option<f32>,
|
||||
) {
|
||||
if let Some(sfx_file) = Self::get_sfx_file(trigger_item) {
|
||||
if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
|
||||
self.emit_subtitle(subtitle, None, dur);
|
||||
// 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);
|
||||
}
|
||||
@ -335,6 +354,26 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_subtitle(
|
||||
&mut self,
|
||||
subtitle: Option<&str>,
|
||||
position: Option<Vec3<f32>>,
|
||||
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<f32>) {
|
||||
if self.audio_stream.is_some() {
|
||||
@ -446,6 +485,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) {
|
||||
@ -504,6 +545,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;
|
||||
|
@ -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 {
|
||||
|
@ -328,6 +328,9 @@ pub struct SfxTriggerItem {
|
||||
pub files: Vec<String>,
|
||||
/// The time to wait before repeating this SfxEvent
|
||||
pub threshold: f32,
|
||||
|
||||
#[serde(default)]
|
||||
pub subtitle: Option<String>,
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
@ -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<DebugInfo>,
|
||||
dt: Duration,
|
||||
info: HudInfo,
|
||||
@ -3343,6 +3348,18 @@ impl Hud {
|
||||
}
|
||||
}
|
||||
|
||||
if global_state.settings.audio.subtitles {
|
||||
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);
|
||||
}
|
||||
|
||||
self.new_messages = VecDeque::new();
|
||||
self.new_notifications = VecDeque::new();
|
||||
|
||||
|
156
voxygen/src/hud/settings_window/accessibility.rs
Normal file
156
voxygen/src/hud/settings_window/accessibility.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use crate::{
|
||||
hud::{img_ids::Imgs, TEXT_COLOR},
|
||||
render::RenderMode,
|
||||
session::settings_change::{Accessibility as AccessibilityChange, Accessibility::*},
|
||||
ui::{fonts::Fonts, ToggleButton},
|
||||
GlobalState,
|
||||
};
|
||||
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,
|
||||
subtitles_button,
|
||||
subtitles_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<AccessibilityChange>;
|
||||
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>) -> 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;
|
||||
|
||||
// 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(
|
||||
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()
|
||||
})));
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
mod accessibility;
|
||||
mod chat;
|
||||
mod controls;
|
||||
mod gameplay;
|
||||
@ -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,16 @@ 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
|
||||
|
332
voxygen/src/hud/subtitles.rs
Normal file
332
voxygen/src/hud/subtitles.rs
Normal file
@ -0,0 +1,332 @@
|
||||
use std::{cmp::Ordering, collections::VecDeque};
|
||||
|
||||
use crate::{audio::Listener, settings::Settings, ui::fonts::Fonts};
|
||||
use client::Client;
|
||||
use conrod_core::{
|
||||
widget::{self, Id, Rectangle, Text},
|
||||
widget_ids, Colorable, Positionable, UiCell, Widget, WidgetCommon,
|
||||
};
|
||||
use i18n::Localization;
|
||||
|
||||
use vek::{Vec2, Vec3};
|
||||
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
subtitle_box_bg,
|
||||
subtitle_message[],
|
||||
subtitle_dir[],
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct Subtitles<'a> {
|
||||
client: &'a Client,
|
||||
settings: &'a Settings,
|
||||
listener: &'a Listener,
|
||||
|
||||
fonts: &'a Fonts,
|
||||
|
||||
new_subtitles: &'a mut VecDeque<Subtitle>,
|
||||
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
|
||||
localized_strings: &'a Localization,
|
||||
}
|
||||
|
||||
impl<'a> Subtitles<'a> {
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
settings: &'a Settings,
|
||||
listener: &'a Listener,
|
||||
new_subtitles: &'a mut VecDeque<Subtitle>,
|
||||
fonts: &'a Fonts,
|
||||
localized_strings: &'a Localization,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
settings,
|
||||
listener,
|
||||
fonts,
|
||||
new_subtitles,
|
||||
common: widget::CommonBuilder::default(),
|
||||
localized_strings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<Vec3<f32>>,
|
||||
/// Amount of seconds to show the subtitle for.
|
||||
pub show_for: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct SubtitleData {
|
||||
position: Option<Vec3<f32>>,
|
||||
/// `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<f32>) -> 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<SubtitleData>)>,
|
||||
}
|
||||
|
||||
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<Item = Subtitle>,
|
||||
time: f64,
|
||||
listener_pos: Vec3<f32>,
|
||||
) -> 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: SubtitleList,
|
||||
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: SubtitleList::new(),
|
||||
ids: Ids::new(id_gen),
|
||||
}
|
||||
}
|
||||
|
||||
fn style(&self) -> Self::Style {}
|
||||
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
common_base::prof_span!("Chat::update");
|
||||
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
let time = self.client.state().get_time();
|
||||
let listener_pos = self.listener.pos;
|
||||
let listener_forward = self.listener.ori;
|
||||
|
||||
// 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) * 2.0).clamp(0.0, 1.0) as f32,
|
||||
)
|
||||
};
|
||||
|
||||
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 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 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);
|
||||
|
||||
match is_right {
|
||||
Side::Right => 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),
|
||||
Side::Left => 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),
|
||||
Side::Forward => 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),
|
||||
}
|
||||
};
|
||||
|
||||
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, 30.0)
|
||||
.set(state.ids.subtitle_box_bg, ui);
|
||||
|
||||
let mut subtitles = state
|
||||
.ids
|
||||
.subtitle_message
|
||||
.iter()
|
||||
.zip(state.ids.subtitle_dir.iter())
|
||||
.zip(
|
||||
subtitles
|
||||
.subtitles
|
||||
.iter()
|
||||
.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))
|
||||
}),
|
||||
);
|
||||
|
||||
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(data))
|
||||
.set(*id, ui);
|
||||
|
||||
dir(data, id, dir_id, ui);
|
||||
|
||||
let mut last_id = *id;
|
||||
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(data))
|
||||
.set(*id, ui);
|
||||
|
||||
dir(data, id, dir_id, ui);
|
||||
|
||||
last_id = *id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()),
|
||||
};
|
||||
|
@ -172,6 +172,12 @@ pub enum Networking {
|
||||
// option)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Accessibility {
|
||||
ChangeRenderMode(Box<RenderMode>),
|
||||
SetSubtitles(bool),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum SettingsChange {
|
||||
Audio(Audio),
|
||||
@ -183,6 +189,7 @@ pub enum SettingsChange {
|
||||
Interface(Interface),
|
||||
Language(Language),
|
||||
Networking(Networking),
|
||||
Accessibility(Accessibility),
|
||||
}
|
||||
|
||||
macro_rules! settings_change_from {
|
||||
@ -201,6 +208,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 +742,15 @@ 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
|
||||
.settings
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user