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
|
- NPCs will migrate to new towns if they are dissatisfied with their current town
|
||||||
- Female humanoids now have a greeting sound effect
|
- Female humanoids now have a greeting sound effect
|
||||||
- Loot that drops multiple items is now distributed fairly between damage contributors.
|
- Loot that drops multiple items is now distributed fairly between damage contributors.
|
||||||
|
- Added accessibility settings tab.
|
||||||
|
- Setting to enable subtitles describing sfx.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@ common-video = Graphics
|
|||||||
common-sound = Sound
|
common-sound = Sound
|
||||||
common-chat = Chat
|
common-chat = Chat
|
||||||
common-networking = Networking
|
common-networking = Networking
|
||||||
|
common-accessibility = Accessibility
|
||||||
common-resume = Resume
|
common-resume = Resume
|
||||||
common-characters = Characters
|
common-characters = Characters
|
||||||
common-close = Close
|
common-close = Close
|
||||||
@ -42,6 +43,7 @@ common-sound_settings = Sound Settings
|
|||||||
common-language_settings = Language Settings
|
common-language_settings = Language Settings
|
||||||
common-chat_settings = Chat Settings
|
common-chat_settings = Chat Settings
|
||||||
common-networking_settings = Networking Settings
|
common-networking_settings = Networking Settings
|
||||||
|
common-accessibility_settings = Accessibility Settings
|
||||||
common-connection_lost =
|
common-connection_lost =
|
||||||
Connection lost!
|
Connection lost!
|
||||||
Did the server restart?
|
Did the server restart?
|
||||||
|
@ -151,3 +151,4 @@ hud-settings-group_only = Group only
|
|||||||
hud-settings-reset_chat = Reset to Defaults
|
hud-settings-reset_chat = Reset to Defaults
|
||||||
hud-settings-third_party_integrations = Third-party Integrations
|
hud-settings-third_party_integrations = Third-party Integrations
|
||||||
hud-settings-enable_discord_integration = Enable Discord Integration
|
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 music::MusicTransitionManifest;
|
||||||
use sfx::{SfxEvent, SfxTriggerItem};
|
use sfx::{SfxEvent, SfxTriggerItem};
|
||||||
use soundcache::load_ogg;
|
use soundcache::load_ogg;
|
||||||
use std::time::Duration;
|
use std::{collections::VecDeque, time::Duration};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use common::assets::{AssetExt, AssetHandle};
|
use common::assets::{AssetExt, AssetHandle};
|
||||||
use rodio::{source::Source, OutputStream, OutputStreamHandle, StreamError};
|
use rodio::{source::Source, OutputStream, OutputStreamHandle, StreamError};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
use crate::hud::Subtitle;
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct Listener {
|
pub struct Listener {
|
||||||
pos: Vec3<f32>,
|
pub pos: Vec3<f32>,
|
||||||
ori: Vec3<f32>,
|
pub ori: Vec3<f32>,
|
||||||
|
|
||||||
ear_left_rpos: Vec3<f32>,
|
ear_left_rpos: Vec3<f32>,
|
||||||
ear_right_rpos: Vec3<f32>,
|
ear_right_rpos: Vec3<f32>,
|
||||||
@ -53,12 +55,20 @@ pub struct AudioFrontend {
|
|||||||
music_spacing: f32,
|
music_spacing: f32,
|
||||||
listener: Listener,
|
listener: Listener,
|
||||||
|
|
||||||
|
pub subtitles_enabled: bool,
|
||||||
|
pub subtitles: VecDeque<Subtitle>,
|
||||||
|
|
||||||
mtm: AssetHandle<MusicTransitionManifest>,
|
mtm: AssetHandle<MusicTransitionManifest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFrontend {
|
impl AudioFrontend {
|
||||||
/// Construct with given device
|
/// 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
|
// Commented out until audio device switcher works
|
||||||
//let audio_device = get_device_raw(&dev);
|
//let audio_device = get_device_raw(&dev);
|
||||||
|
|
||||||
@ -106,6 +116,8 @@ impl AudioFrontend {
|
|||||||
music_spacing: 1.0,
|
music_spacing: 1.0,
|
||||||
listener: Listener::default(),
|
listener: Listener::default(),
|
||||||
mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
|
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,
|
music_spacing: 1.0,
|
||||||
listener: Listener::default(),
|
listener: Listener::default(),
|
||||||
mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
|
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
|
/// Errors if no sounds are found
|
||||||
fn get_sfx_file<'a>(
|
fn get_sfx_file<'a>(
|
||||||
trigger_item: Option<(&'a SfxEvent, &'a SfxTriggerItem)>,
|
trigger_item: Option<(&'a SfxEvent, &'a SfxTriggerItem)>,
|
||||||
) -> Option<&'a str> {
|
) -> Option<(&'a str, f32, Option<&'a str>)> {
|
||||||
trigger_item.map(|(event, item)| {
|
trigger_item.map(|(event, item)| {
|
||||||
match item.files.len() {
|
let file = match item.files.len() {
|
||||||
0 => {
|
0 => {
|
||||||
debug!("Sfx event {:?} is missing audio file.", event);
|
debug!("Sfx event {:?} is missing audio file.", event);
|
||||||
"voxygen.audio.sfx.placeholder"
|
"voxygen.audio.sfx.placeholder"
|
||||||
@ -239,7 +253,12 @@ impl AudioFrontend {
|
|||||||
let rand_step = rand::random::<usize>() % item.files.len();
|
let rand_step = rand::random::<usize>() % item.files.len();
|
||||||
&item.files[rand_step]
|
&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>,
|
volume: Option<f32>,
|
||||||
underwater: bool,
|
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
|
// 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 sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
|
||||||
|
|
||||||
let listener = self.listener.clone();
|
let listener = self.listener.clone();
|
||||||
if let Some(channel) = self.get_sfx_channel() {
|
if let Some(channel) = self.get_sfx_channel() {
|
||||||
channel.set_pos(position);
|
channel.set_pos(position);
|
||||||
@ -286,11 +305,11 @@ impl AudioFrontend {
|
|||||||
freq: Option<u32>,
|
freq: Option<u32>,
|
||||||
underwater: bool,
|
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
|
// 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 sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
|
||||||
|
|
||||||
let listener = self.listener.clone();
|
let listener = self.listener.clone();
|
||||||
if let Some(channel) = self.get_sfx_channel() {
|
if let Some(channel) = self.get_sfx_channel() {
|
||||||
channel.set_pos(position);
|
channel.set_pos(position);
|
||||||
@ -321,11 +340,11 @@ impl AudioFrontend {
|
|||||||
trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
|
trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
|
||||||
volume: Option<f32>,
|
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
|
// 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));
|
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
|
||||||
|
|
||||||
if let Some(channel) = self.get_ui_channel() {
|
if let Some(channel) = self.get_ui_channel() {
|
||||||
channel.play(sound);
|
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
|
/// 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>) {
|
fn play_ambient(&mut self, channel_tag: AmbientChannelTag, sound: &str, volume: Option<f32>) {
|
||||||
if self.audio_stream.is_some() {
|
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
|
/// Switches the playing music to the title music, which is pinned to a
|
||||||
/// specific sound file (veloren_title_tune.ogg)
|
/// specific sound file (veloren_title_tune.ogg)
|
||||||
pub fn play_title_music(&mut self) {
|
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_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
|
/// Updates master volume in all channels
|
||||||
pub fn set_master_volume(&mut self, master_volume: f32) {
|
pub fn set_master_volume(&mut self, master_volume: f32) {
|
||||||
self.master_volume = master_volume;
|
self.master_volume = master_volume;
|
||||||
|
@ -23,6 +23,7 @@ fn config_but_played_since_threshold_no_emit() {
|
|||||||
let trigger_item = SfxTriggerItem {
|
let trigger_item = SfxTriggerItem {
|
||||||
files: vec![String::from("some.path.to.sfx.file")],
|
files: vec![String::from("some.path.to.sfx.file")],
|
||||||
threshold: 1.0,
|
threshold: 1.0,
|
||||||
|
subtitle: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Triggered a 'Run' 0 seconds ago
|
// Triggered a 'Run' 0 seconds ago
|
||||||
@ -47,6 +48,7 @@ fn config_and_not_played_since_threshold_emits() {
|
|||||||
let trigger_item = SfxTriggerItem {
|
let trigger_item = SfxTriggerItem {
|
||||||
files: vec![String::from("some.path.to.sfx.file")],
|
files: vec![String::from("some.path.to.sfx.file")],
|
||||||
threshold: 0.5,
|
threshold: 0.5,
|
||||||
|
subtitle: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let previous_state = PreviousEntityState {
|
let previous_state = PreviousEntityState {
|
||||||
@ -70,6 +72,7 @@ fn same_previous_event_elapsed_emits() {
|
|||||||
let trigger_item = SfxTriggerItem {
|
let trigger_item = SfxTriggerItem {
|
||||||
files: vec![String::from("some.path.to.sfx.file")],
|
files: vec![String::from("some.path.to.sfx.file")],
|
||||||
threshold: 0.5,
|
threshold: 0.5,
|
||||||
|
subtitle: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let previous_state = PreviousEntityState {
|
let previous_state = PreviousEntityState {
|
||||||
|
@ -328,6 +328,9 @@ pub struct SfxTriggerItem {
|
|||||||
pub files: Vec<String>,
|
pub files: Vec<String>,
|
||||||
/// The time to wait before repeating this SfxEvent
|
/// The time to wait before repeating this SfxEvent
|
||||||
pub threshold: f32,
|
pub threshold: f32,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub subtitle: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
@ -369,7 +372,7 @@ impl SfxMgr {
|
|||||||
) {
|
) {
|
||||||
// Checks if the SFX volume is set to zero or audio is disabled
|
// Checks if the SFX volume is set to zero or audio is disabled
|
||||||
// This prevents us from running all the following code unnecessarily
|
// This prevents us from running all the following code unnecessarily
|
||||||
if !audio.sfx_enabled() {
|
if !audio.sfx_enabled() && !audio.subtitles_enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,7 +404,7 @@ impl SfxMgr {
|
|||||||
client: &Client,
|
client: &Client,
|
||||||
underwater: bool,
|
underwater: bool,
|
||||||
) {
|
) {
|
||||||
if !audio.sfx_enabled() {
|
if !audio.sfx_enabled() && !audio.subtitles_enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let triggers = self.triggers.read();
|
let triggers = self.triggers.read();
|
||||||
|
@ -9,8 +9,6 @@ mod diary;
|
|||||||
mod esc_menu;
|
mod esc_menu;
|
||||||
mod group;
|
mod group;
|
||||||
mod hotbar;
|
mod hotbar;
|
||||||
pub mod img_ids;
|
|
||||||
pub mod item_imgs;
|
|
||||||
mod loot_scroller;
|
mod loot_scroller;
|
||||||
mod map;
|
mod map;
|
||||||
mod minimap;
|
mod minimap;
|
||||||
@ -23,7 +21,11 @@ mod settings_window;
|
|||||||
mod skillbar;
|
mod skillbar;
|
||||||
mod slots;
|
mod slots;
|
||||||
mod social;
|
mod social;
|
||||||
|
mod subtitles;
|
||||||
mod trade;
|
mod trade;
|
||||||
|
|
||||||
|
pub mod img_ids;
|
||||||
|
pub mod item_imgs;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
pub use crafting::CraftingTab;
|
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 item_imgs::animate_by_pulse;
|
||||||
pub use loot_scroller::LootMessage;
|
pub use loot_scroller::LootMessage;
|
||||||
pub use settings_window::ScaleChange;
|
pub use settings_window::ScaleChange;
|
||||||
|
pub use subtitles::Subtitle;
|
||||||
|
|
||||||
use bag::Bag;
|
use bag::Bag;
|
||||||
use buffs::BuffsBar;
|
use buffs::BuffsBar;
|
||||||
@ -54,6 +57,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use settings_window::{SettingsTab, SettingsWindow};
|
use settings_window::{SettingsTab, SettingsWindow};
|
||||||
use skillbar::Skillbar;
|
use skillbar::Skillbar;
|
||||||
use social::Social;
|
use social::Social;
|
||||||
|
use subtitles::Subtitles;
|
||||||
use trade::Trade;
|
use trade::Trade;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -328,6 +332,7 @@ widget_ids! {
|
|||||||
settings_window,
|
settings_window,
|
||||||
group_window,
|
group_window,
|
||||||
item_info,
|
item_info,
|
||||||
|
subtitles,
|
||||||
|
|
||||||
// Free look indicator
|
// Free look indicator
|
||||||
free_look_txt,
|
free_look_txt,
|
||||||
@ -1451,7 +1456,7 @@ impl Hud {
|
|||||||
fn update_layout(
|
fn update_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
global_state: &GlobalState,
|
global_state: &mut GlobalState,
|
||||||
debug_info: &Option<DebugInfo>,
|
debug_info: &Option<DebugInfo>,
|
||||||
dt: Duration,
|
dt: Duration,
|
||||||
info: HudInfo,
|
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_messages = VecDeque::new();
|
||||||
self.new_notifications = 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 chat;
|
||||||
mod controls;
|
mod controls;
|
||||||
mod gameplay;
|
mod gameplay;
|
||||||
@ -41,6 +42,7 @@ widget_ids! {
|
|||||||
language,
|
language,
|
||||||
chat,
|
chat,
|
||||||
networking,
|
networking,
|
||||||
|
accessibility,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +59,7 @@ pub enum SettingsTab {
|
|||||||
Controls,
|
Controls,
|
||||||
Lang,
|
Lang,
|
||||||
Networking,
|
Networking,
|
||||||
|
Accessibility,
|
||||||
}
|
}
|
||||||
impl SettingsTab {
|
impl SettingsTab {
|
||||||
fn name_key(&self) -> &str {
|
fn name_key(&self) -> &str {
|
||||||
@ -69,6 +72,7 @@ impl SettingsTab {
|
|||||||
SettingsTab::Sound => "common-sound",
|
SettingsTab::Sound => "common-sound",
|
||||||
SettingsTab::Lang => "common-languages",
|
SettingsTab::Lang => "common-languages",
|
||||||
SettingsTab::Networking => "common-networking",
|
SettingsTab::Networking => "common-networking",
|
||||||
|
SettingsTab::Accessibility => "common-accessibility",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +86,7 @@ impl SettingsTab {
|
|||||||
SettingsTab::Sound => "common-sound_settings",
|
SettingsTab::Sound => "common-sound_settings",
|
||||||
SettingsTab::Lang => "common-language_settings",
|
SettingsTab::Lang => "common-language_settings",
|
||||||
SettingsTab::Networking => "common-networking_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()));
|
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
|
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(
|
AudioOutput::Automatic => AudioFrontend::new(
|
||||||
settings.audio.num_sfx_channels,
|
settings.audio.num_sfx_channels,
|
||||||
settings.audio.num_ui_channels,
|
settings.audio.num_ui_channels,
|
||||||
|
settings.audio.subtitles,
|
||||||
),
|
),
|
||||||
// AudioOutput::Device(ref dev) => Some(dev.clone()),
|
// AudioOutput::Device(ref dev) => Some(dev.clone()),
|
||||||
};
|
};
|
||||||
|
@ -172,6 +172,12 @@ pub enum Networking {
|
|||||||
// option)
|
// option)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Accessibility {
|
||||||
|
ChangeRenderMode(Box<RenderMode>),
|
||||||
|
SetSubtitles(bool),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum SettingsChange {
|
pub enum SettingsChange {
|
||||||
Audio(Audio),
|
Audio(Audio),
|
||||||
@ -183,6 +189,7 @@ pub enum SettingsChange {
|
|||||||
Interface(Interface),
|
Interface(Interface),
|
||||||
Language(Language),
|
Language(Language),
|
||||||
Networking(Networking),
|
Networking(Networking),
|
||||||
|
Accessibility(Accessibility),
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! settings_change_from {
|
macro_rules! settings_change_from {
|
||||||
@ -201,6 +208,7 @@ settings_change_from!(Graphics);
|
|||||||
settings_change_from!(Interface);
|
settings_change_from!(Interface);
|
||||||
settings_change_from!(Language);
|
settings_change_from!(Language);
|
||||||
settings_change_from!(Networking);
|
settings_change_from!(Networking);
|
||||||
|
settings_change_from!(Accessibility);
|
||||||
|
|
||||||
impl SettingsChange {
|
impl SettingsChange {
|
||||||
pub fn process(self, global_state: &mut GlobalState, session_state: &mut SessionState) {
|
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
|
global_state
|
||||||
.settings
|
.settings
|
||||||
|
@ -47,6 +47,7 @@ pub struct AudioSettings {
|
|||||||
pub num_sfx_channels: usize,
|
pub num_sfx_channels: usize,
|
||||||
pub num_ui_channels: usize,
|
pub num_ui_channels: usize,
|
||||||
pub music_spacing: f32,
|
pub music_spacing: f32,
|
||||||
|
pub subtitles: bool,
|
||||||
|
|
||||||
/// Audio Device that Voxygen will use to play audio.
|
/// Audio Device that Voxygen will use to play audio.
|
||||||
pub output: AudioOutput,
|
pub output: AudioOutput,
|
||||||
@ -63,6 +64,7 @@ impl Default for AudioSettings {
|
|||||||
num_sfx_channels: 60,
|
num_sfx_channels: 60,
|
||||||
num_ui_channels: 10,
|
num_ui_channels: 10,
|
||||||
music_spacing: 1.0,
|
music_spacing: 1.0,
|
||||||
|
subtitles: false,
|
||||||
output: AudioOutput::Automatic,
|
output: AudioOutput::Automatic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user