subtitles

This commit is contained in:
Isse 2023-04-26 16:20:56 +02:00
parent 1c9b502f69
commit 99463a37f8
13 changed files with 700 additions and 93 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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

View 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

View File

@ -14,13 +14,15 @@ use fader::Fader;
use music::MusicTransitionManifest;
use sfx::{SfxEvent, SfxTriggerItem};
use soundcache::load_ogg;
use std::time::Duration;
use std::{collections::VecDeque, time::Duration};
use tracing::{debug, error};
use common::assets::{AssetExt, AssetHandle};
use rodio::{source::Source, OutputStream, OutputStreamHandle, StreamError};
use vek::*;
use crate::hud::Subtitle;
#[derive(Default, Clone)]
pub struct Listener {
pos: Vec3<f32>,
@ -53,12 +55,19 @@ 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 +115,8 @@ impl AudioFrontend {
music_spacing: 1.0,
listener: Listener::default(),
mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
subtitles: VecDeque::new(),
subtitles_enabled: subtitles,
}
}
@ -129,6 +140,8 @@ impl AudioFrontend {
music_spacing: 1.0,
listener: Listener::default(),
mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
subtitles: VecDeque::new(),
subtitles_enabled: false,
}
}
@ -223,9 +236,9 @@ impl AudioFrontend {
/// Errors if no sounds are found
fn get_sfx_file<'a>(
trigger_item: Option<(&'a SfxEvent, &'a SfxTriggerItem)>,
) -> Option<&'a str> {
) -> Option<(&'a str, f32, Option<&'a str>)> {
trigger_item.map(|(event, item)| {
match item.files.len() {
let file = match item.files.len() {
0 => {
debug!("Sfx event {:?} is missing audio file.", event);
"voxygen.audio.sfx.placeholder"
@ -239,7 +252,9 @@ impl AudioFrontend {
let rand_step = rand::random::<usize>() % item.files.len();
&item.files[rand_step]
},
}
};
(file, item.threshold, item.subtitle.as_deref())
})
}
@ -252,11 +267,19 @@ 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) {
if self.subtitles_enabled {
if let Some(subtitle) = subtitle {
self.subtitles.push_back(Subtitle {
localization: subtitle.to_string(),
position: Some(position),
show_until: dur.max(1.5) as f64,
})
}
}
// Play sound in empty channel at given position
if self.audio_stream.is_some() {
if self.audio_stream.is_some() && self.sfx_enabled() {
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
let listener = self.listener.clone();
if let Some(channel) = self.get_sfx_channel() {
channel.set_pos(position);
@ -286,11 +309,19 @@ impl AudioFrontend {
freq: Option<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) {
if self.subtitles_enabled {
if let Some(subtitle) = subtitle {
self.subtitles.push_back(Subtitle {
localization: subtitle.to_string(),
position: Some(position),
show_until: dur.max(1.5) as f64,
})
}
}
// Play sound in empty channel at given position
if self.audio_stream.is_some() {
if self.audio_stream.is_some() && self.sfx_enabled() {
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
let listener = self.listener.clone();
if let Some(channel) = self.get_sfx_channel() {
channel.set_pos(position);
@ -321,11 +352,19 @@ impl AudioFrontend {
trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
volume: Option<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) {
if self.subtitles_enabled {
if let Some(subtitle) = subtitle {
self.subtitles.push_back(Subtitle {
localization: subtitle.to_string(),
position: None,
show_until: dur.max(1.5) as f64,
})
}
}
// Play sound in empty channel
if self.audio_stream.is_some() {
if self.audio_stream.is_some() && self.sfx_enabled() {
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
if let Some(channel) = self.get_ui_channel() {
channel.play(sound);
}
@ -504,6 +543,8 @@ impl AudioFrontend {
pub fn set_music_spacing(&mut self, multiplier: f32) { self.music_spacing = multiplier }
pub fn set_subtitles(&mut self, enabled: bool) { self.subtitles_enabled = enabled }
/// Updates master volume in all channels
pub fn set_master_volume(&mut self, master_volume: f32) {
self.master_volume = master_volume;

View File

@ -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 {

View File

@ -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();

View File

@ -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,13 @@ impl Hud {
}
}
if global_state.settings.audio.subtitles {
Subtitles::new(client, &mut global_state.audio.subtitles, &self.fonts, i18n)
.set(self.ids.subtitles, ui_widgets);
} else {
global_state.audio.subtitles.clear();
}
self.new_messages = VecDeque::new();
self.new_notifications = VecDeque::new();

View File

@ -1,8 +1,9 @@
use crate::{
hud::{img_ids::Imgs, TEXT_COLOR},
render::RenderMode,
session::settings_change::{Accessibility as AccessibilityChange, Accessibility::*},
ui::{fonts::Fonts, ToggleButton},
GlobalState, render::RenderMode,
GlobalState,
};
use conrod_core::{
color,
@ -18,6 +19,8 @@ widget_ids! {
flashing_lights_button,
flashing_lights_label,
flashing_lights_info_label,
subtitles_button,
subtitles_label,
}
}
@ -84,7 +87,7 @@ impl<'a> Widget for Accessibility<'a> {
// Get render mode
let render_mode = &self.global_state.settings.graphics.render_mode;
// Disable flashing lights
// Flashing lights
Text::new(
&self
.localized_strings
@ -97,11 +100,7 @@ impl<'a> Widget for Accessibility<'a> {
.set(state.ids.flashing_lights_label, ui);
let flashing_lights_enabled = ToggleButton::new(
self.global_state
.settings
.graphics
.render_mode
.flashing_lights_enabled,
render_mode.flashing_lights_enabled,
self.imgs.checkbox,
self.imgs.checkbox_checked,
)
@ -129,6 +128,29 @@ impl<'a> Widget for Accessibility<'a> {
})));
}
// Subtitles
Text::new(&self.localized_strings.get_msg("hud-settings-subtitles"))
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.down_from(state.ids.flashing_lights_label, 10.0)
.color(TEXT_COLOR)
.set(state.ids.subtitles_label, ui);
let subtitles_enabled = ToggleButton::new(
self.global_state.settings.audio.subtitles,
self.imgs.checkbox,
self.imgs.checkbox_checked,
)
.w_h(18.0, 18.0)
.right_from(state.ids.subtitles_label, 10.0)
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
.set(state.ids.subtitles_button, ui);
if subtitles_enabled != self.global_state.settings.audio.subtitles {
events.push(SetSubtitles(subtitles_enabled));
}
events
}
}

View File

@ -1,3 +1,4 @@
mod accessibility;
mod chat;
mod controls;
mod gameplay;
@ -6,7 +7,6 @@ mod language;
mod networking;
mod sound;
mod video;
mod accessibility;
use crate::{
hud::{img_ids::Imgs, Show, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN},
@ -356,15 +356,11 @@ impl<'a> Widget for SettingsWindow<'a> {
}
},
SettingsTab::Accessibility => {
for change in accessibility::Accessibility::new(
global_state,
imgs,
fonts,
localized_strings,
)
.top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0)
.wh_of(state.ids.settings_content_align)
.set(state.ids.accessibility, ui)
for change in
accessibility::Accessibility::new(global_state, imgs, fonts, localized_strings)
.top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0)
.wh_of(state.ids.settings_content_align)
.set(state.ids.accessibility, ui)
{
events.push(Event::SettingsChange(change.into()));
}

View File

@ -0,0 +1,191 @@
use std::collections::VecDeque;
use crate::ui::fonts::Fonts;
use client::Client;
use common::comp;
use conrod_core::{
widget::{self, Text},
widget_ids, Colorable, Positionable, Widget, WidgetCommon,
};
use i18n::Localization;
use vek::{Vec2, Vec3};
widget_ids! {
struct Ids {
subtitle_box,
subtitle_box_bg,
subtitle_message[],
}
}
#[derive(WidgetCommon)]
pub struct Subtitles<'a> {
client: &'a Client,
fonts: &'a Fonts,
new_subtitles: &'a mut VecDeque<Subtitle>,
#[conrod(common_builder)]
common: widget::CommonBuilder,
localized_strings: &'a Localization,
}
impl<'a> Subtitles<'a> {
pub fn new(
client: &'a Client,
new_subtitles: &'a mut VecDeque<Subtitle>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
) -> Self {
Self {
client,
fonts,
new_subtitles,
common: widget::CommonBuilder::default(),
localized_strings,
}
}
}
const MAX_SUBTITLE_DIST: f32 = 80.0;
#[derive(Debug)]
pub struct Subtitle {
pub localization: String,
pub position: Option<Vec3<f32>>,
pub show_until: f64,
}
pub struct State {
subtitles: Vec<Subtitle>,
ids: Ids,
}
impl<'a> Widget for Subtitles<'a> {
type Event = ();
type State = State;
type Style = ();
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
subtitles: Vec::new(),
ids: Ids::new(id_gen),
}
}
fn style(&self) -> Self::Style {}
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
common_base::prof_span!("Chat::update");
let widget::UpdateArgs { state, ui, .. } = args;
let time = self.client.state().get_time();
let player_pos = self.client.position().unwrap_or_default();
let player_dir = self
.client
.state()
.read_storage::<comp::Ori>()
.get(self.client.entity())
.map_or(Vec3::unit_y(), |ori| ori.look_vec());
// Empty old subtitles and add new.
state.update(|s| {
s.subtitles.retain(|subtitle| {
time <= subtitle.show_until
&& subtitle
.position
.map_or(true, |pos| pos.distance(player_pos) <= MAX_SUBTITLE_DIST)
});
for mut subtitle in self.new_subtitles.drain(..) {
if subtitle
.position
.map_or(false, |pos| pos.distance(player_pos) > MAX_SUBTITLE_DIST)
{
continue;
}
let t = time + subtitle.show_until;
if let Some(s) = s
.subtitles
.iter_mut()
.find(|s| s.localization == subtitle.localization)
{
if t > s.show_until {
s.show_until = t;
s.position = subtitle.position;
}
} else {
subtitle.show_until = t;
s.subtitles.push(subtitle);
}
}
s.ids
.subtitle_message
.resize(s.subtitles.len(), &mut ui.widget_id_generator());
});
let mut subtitles = state
.ids
.subtitle_message
.iter()
.zip(state.subtitles.iter());
let fade_amount = |t: &Subtitle| ((t.show_until - time) * 1.5).clamp(0.0, 1.0) as f32;
let player_dir = player_dir.xy().try_normalized().unwrap_or(Vec2::unit_y());
let player_right = Vec2::new(player_dir.y, -player_dir.x);
let message = |subtitle: &Subtitle| {
let message = self.localized_strings.get_msg(&subtitle.localization);
let is_right = subtitle.position.and_then(|pos| {
let dist = pos.distance(player_pos);
let dir = (pos - player_pos) / dist;
let dot = dir.xy().dot(player_dir);
if dist < 2.0 || dot > 0.85 {
None
} else {
Some(dir.xy().dot(player_right) >= 0.0)
}
});
match is_right {
Some(true) => format!(" {message} >"),
Some(false) => format!("< {message} "),
None => format!(" {message} "),
}
};
if let Some((id, subtitle)) = subtitles.next() {
Text::new(&message(subtitle))
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.center_justify()
.bottom_right_with_margins(40.0, 50.0)
.color(conrod_core::Color::Rgba(
0.9,
1.0,
1.0,
fade_amount(subtitle),
))
.set(*id, ui);
let mut last_id = *id;
for (id, subtitle) in subtitles {
Text::new(&message(subtitle))
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.center_justify()
.up_from(last_id, 10.0)
.color(conrod_core::Color::Rgba(
0.9,
1.0,
1.0,
fade_amount(subtitle),
))
.set(*id, ui);
last_id = *id;
}
}
}
}

View File

@ -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()),
};

View File

@ -175,6 +175,7 @@ pub enum Networking {
#[derive(Clone)]
pub enum Accessibility {
ChangeRenderMode(Box<RenderMode>),
SetSubtitles(bool),
}
#[derive(Clone)]
@ -744,7 +745,11 @@ impl SettingsChange {
SettingsChange::Accessibility(accessibility_change) => match accessibility_change {
Accessibility::ChangeRenderMode(new_render_mode) => {
change_render_mode(*new_render_mode, &mut global_state.window, settings);
}
},
Accessibility::SetSubtitles(enabled) => {
global_state.settings.audio.subtitles = enabled;
global_state.audio.set_subtitles(enabled);
},
},
}
global_state

View File

@ -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,
}
}