mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Maintain all subtitles, limit calls to state.update
This commit is contained in:
parent
f60c44d812
commit
54a7367cb2
@ -25,8 +25,8 @@ 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>,
|
||||
@ -64,7 +64,8 @@ pub struct AudioFrontend {
|
||||
impl AudioFrontend {
|
||||
/// Construct with given device
|
||||
pub fn new(
|
||||
/* dev: String, */ num_sfx_channels: usize,
|
||||
/* dev: String, */
|
||||
num_sfx_channels: usize,
|
||||
num_ui_channels: usize,
|
||||
subtitles: bool,
|
||||
) -> Self {
|
||||
@ -268,15 +269,7 @@ impl AudioFrontend {
|
||||
underwater: bool,
|
||||
) {
|
||||
if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
|
||||
if self.subtitles_enabled {
|
||||
if let Some(subtitle) = subtitle {
|
||||
self.subtitles.push_back(Subtitle {
|
||||
localization: subtitle.to_string(),
|
||||
position: Some(position),
|
||||
show_until: dur.max(1.5) as f64,
|
||||
})
|
||||
}
|
||||
}
|
||||
self.emit_subtitle(subtitle, Some(position), dur);
|
||||
// Play sound in empty channel at given position
|
||||
if self.audio_stream.is_some() && self.sfx_enabled() {
|
||||
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
|
||||
@ -310,15 +303,7 @@ impl AudioFrontend {
|
||||
underwater: bool,
|
||||
) {
|
||||
if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
|
||||
if self.subtitles_enabled {
|
||||
if let Some(subtitle) = subtitle {
|
||||
self.subtitles.push_back(Subtitle {
|
||||
localization: subtitle.to_string(),
|
||||
position: Some(position),
|
||||
show_until: dur.max(1.5) as f64,
|
||||
})
|
||||
}
|
||||
}
|
||||
self.emit_subtitle(subtitle, Some(position), dur);
|
||||
// Play sound in empty channel at given position
|
||||
if self.audio_stream.is_some() && self.sfx_enabled() {
|
||||
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
|
||||
@ -353,15 +338,7 @@ impl AudioFrontend {
|
||||
volume: Option<f32>,
|
||||
) {
|
||||
if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
|
||||
if self.subtitles_enabled {
|
||||
if let Some(subtitle) = subtitle {
|
||||
self.subtitles.push_back(Subtitle {
|
||||
localization: subtitle.to_string(),
|
||||
position: None,
|
||||
show_until: dur.max(1.5) as f64,
|
||||
})
|
||||
}
|
||||
}
|
||||
self.emit_subtitle(subtitle, None, dur);
|
||||
// Play sound in empty channel
|
||||
if self.audio_stream.is_some() && self.sfx_enabled() {
|
||||
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
|
||||
@ -374,6 +351,26 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_subtitle(
|
||||
&mut self,
|
||||
subtitle: Option<&str>,
|
||||
position: Option<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() {
|
||||
@ -485,6 +482,8 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_listener(&self) -> &Listener { &self.listener }
|
||||
|
||||
/// Switches the playing music to the title music, which is pinned to a
|
||||
/// specific sound file (veloren_title_tune.ogg)
|
||||
pub fn play_title_music(&mut self) {
|
||||
|
@ -3352,13 +3352,12 @@ impl Hud {
|
||||
Subtitles::new(
|
||||
client,
|
||||
&global_state.settings,
|
||||
&global_state.audio.get_listener().clone(),
|
||||
&mut global_state.audio.subtitles,
|
||||
&self.fonts,
|
||||
i18n,
|
||||
)
|
||||
.set(self.ids.subtitles, ui_widgets);
|
||||
} else {
|
||||
global_state.audio.subtitles.clear();
|
||||
}
|
||||
|
||||
self.new_messages = VecDeque::new();
|
||||
|
@ -1,8 +1,7 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::{cmp::Ordering, collections::VecDeque};
|
||||
|
||||
use crate::{settings::Settings, ui::fonts::Fonts};
|
||||
use crate::{audio::Listener, settings::Settings, ui::fonts::Fonts};
|
||||
use client::Client;
|
||||
use common::comp;
|
||||
use conrod_core::{
|
||||
widget::{self, Id, Rectangle, Text},
|
||||
widget_ids, Colorable, Positionable, UiCell, Widget, WidgetCommon,
|
||||
@ -23,6 +22,7 @@ widget_ids! {
|
||||
pub struct Subtitles<'a> {
|
||||
client: &'a Client,
|
||||
settings: &'a Settings,
|
||||
listener: &'a Listener,
|
||||
|
||||
fonts: &'a Fonts,
|
||||
|
||||
@ -38,6 +38,7 @@ impl<'a> Subtitles<'a> {
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
settings: &'a Settings,
|
||||
listener: &'a Listener,
|
||||
new_subtitles: &'a mut VecDeque<Subtitle>,
|
||||
fonts: &'a Fonts,
|
||||
localized_strings: &'a Localization,
|
||||
@ -45,6 +46,7 @@ impl<'a> Subtitles<'a> {
|
||||
Self {
|
||||
client,
|
||||
settings,
|
||||
listener,
|
||||
fonts,
|
||||
new_subtitles,
|
||||
common: widget::CommonBuilder::default(),
|
||||
@ -53,17 +55,115 @@ impl<'a> Subtitles<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_SUBTITLE_DURATION: f64 = 1.5;
|
||||
const MAX_SUBTITLE_DIST: f32 = 80.0;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Subtitle {
|
||||
pub localization: String,
|
||||
/// Position the sound is played at, if any.
|
||||
pub position: Option<Vec3<f32>>,
|
||||
pub show_until: f64,
|
||||
/// 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: Vec<Subtitle>,
|
||||
subtitles: SubtitleList,
|
||||
ids: Ids,
|
||||
}
|
||||
|
||||
@ -74,7 +174,7 @@ impl<'a> Widget for Subtitles<'a> {
|
||||
|
||||
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
|
||||
State {
|
||||
subtitles: Vec::new(),
|
||||
subtitles: SubtitleList::new(),
|
||||
ids: Ids::new(id_gen),
|
||||
}
|
||||
}
|
||||
@ -86,79 +186,71 @@ impl<'a> Widget for Subtitles<'a> {
|
||||
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
let time = self.client.state().get_time();
|
||||
let player_pos = self.client.position().unwrap_or_default();
|
||||
let player_dir = self
|
||||
.client
|
||||
.state()
|
||||
.read_storage::<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());
|
||||
s.ids
|
||||
.subtitle_dir
|
||||
.resize(s.subtitles.len(), &mut ui.widget_id_generator());
|
||||
});
|
||||
let listener_pos = self.listener.pos;
|
||||
let listener_forward = self.listener.ori;
|
||||
|
||||
let color = |t: &Subtitle| {
|
||||
// Update subtitles and look for changes
|
||||
let mut subtitles = state.subtitles.clone();
|
||||
|
||||
let has_new = !self.new_subtitles.is_empty();
|
||||
|
||||
let show_count = subtitles.update(self.new_subtitles.drain(..), time, listener_pos);
|
||||
|
||||
let subtitles = if has_new || show_count != state.ids.subtitle_message.len() {
|
||||
state.update(|s| {
|
||||
s.subtitles = subtitles;
|
||||
s.ids
|
||||
.subtitle_message
|
||||
.resize(show_count, &mut ui.widget_id_generator());
|
||||
s.ids
|
||||
.subtitle_dir
|
||||
.resize(show_count, &mut ui.widget_id_generator());
|
||||
});
|
||||
&state.subtitles
|
||||
} else {
|
||||
&subtitles
|
||||
};
|
||||
let color = |t: &SubtitleData| -> conrod_core::Color {
|
||||
conrod_core::Color::Rgba(
|
||||
0.9,
|
||||
1.0,
|
||||
1.0,
|
||||
((t.show_until - time) * 1.5).clamp(0.0, 1.0) as f32,
|
||||
((t.show_until - time) * 2.0).clamp(0.0, 1.0) as f32,
|
||||
)
|
||||
};
|
||||
|
||||
let player_dir = player_dir.xy().try_normalized().unwrap_or(Vec2::unit_y());
|
||||
let player_right = Vec2::new(player_dir.y, -player_dir.x);
|
||||
let listener_forward = listener_forward
|
||||
.xy()
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec2::unit_y());
|
||||
let listener_right = Vec2::new(listener_forward.y, -listener_forward.x);
|
||||
|
||||
let message = |subtitle: &Subtitle| self.localized_strings.get_msg(&subtitle.localization);
|
||||
let dir = |subtitle: &SubtitleData, id: &Id, dir_id: &Id, ui: &mut UiCell| {
|
||||
enum Side {
|
||||
/// Also used for sounds without direction.
|
||||
Forward,
|
||||
Right,
|
||||
Left,
|
||||
}
|
||||
let is_right = subtitle
|
||||
.position
|
||||
.map(|pos| {
|
||||
let dist = pos.distance(listener_pos);
|
||||
let dir = (pos - listener_pos) / dist;
|
||||
|
||||
let dir = |subtitle: &Subtitle, id: &Id, dir_id: &Id, ui: &mut UiCell| {
|
||||
let is_right = subtitle.position.and_then(|pos| {
|
||||
let dist = pos.distance(player_pos);
|
||||
let dir = (pos - player_pos) / dist;
|
||||
let dot = dir.xy().dot(listener_forward);
|
||||
if dist < 2.0 || dot > 0.85 {
|
||||
Side::Forward
|
||||
} else if dir.xy().dot(listener_right) >= 0.0 {
|
||||
Side::Right
|
||||
} else {
|
||||
Side::Left
|
||||
}
|
||||
})
|
||||
.unwrap_or(Side::Forward);
|
||||
|
||||
let dot = dir.xy().dot(player_dir);
|
||||
if dist < 2.0 || dot > 0.85 {
|
||||
None
|
||||
} else {
|
||||
Some(dir.xy().dot(player_right) >= 0.0)
|
||||
}
|
||||
});
|
||||
match is_right {
|
||||
Some(true) => Text::new("> ")
|
||||
Side::Right => Text::new("> ")
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.parent(state.ids.subtitle_box_bg)
|
||||
@ -166,7 +258,7 @@ impl<'a> Widget for Subtitles<'a> {
|
||||
.align_middle_y_of(*id)
|
||||
.color(color(subtitle))
|
||||
.set(*dir_id, ui),
|
||||
Some(false) => Text::new(" <")
|
||||
Side::Left => Text::new(" <")
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.parent(state.ids.subtitle_box_bg)
|
||||
@ -174,7 +266,7 @@ impl<'a> Widget for Subtitles<'a> {
|
||||
.align_middle_y_of(*id)
|
||||
.color(color(subtitle))
|
||||
.set(*dir_id, ui),
|
||||
None => Text::new("")
|
||||
Side::Forward => Text::new("")
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.parent(state.ids.subtitle_box_bg)
|
||||
@ -183,9 +275,9 @@ impl<'a> Widget for Subtitles<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
Rectangle::fill([200.0, 2.0 + 22.0 * state.subtitles.len() as f64])
|
||||
Rectangle::fill([200.0, 22.0 * show_count as f64])
|
||||
.rgba(0.0, 0.0, 0.0, self.settings.chat.chat_opacity)
|
||||
.bottom_right_with_margins_on(ui.window, 40.0, 50.0)
|
||||
.bottom_right_with_margins_on(ui.window, 40.0, 30.0)
|
||||
.set(state.ids.subtitle_box_bg, ui);
|
||||
|
||||
let mut subtitles = state
|
||||
@ -193,32 +285,45 @@ impl<'a> Widget for Subtitles<'a> {
|
||||
.subtitle_message
|
||||
.iter()
|
||||
.zip(state.ids.subtitle_dir.iter())
|
||||
.zip(state.subtitles.iter());
|
||||
.zip(
|
||||
subtitles
|
||||
.subtitles
|
||||
.iter()
|
||||
.filter_map(|(localization, data)| {
|
||||
Some((self.localized_strings.get_msg(localization), data.last()?))
|
||||
})
|
||||
.filter(|(_, data)| {
|
||||
data.position.map_or(true, |pos| {
|
||||
pos.distance_squared(listener_pos)
|
||||
< MAX_SUBTITLE_DIST * MAX_SUBTITLE_DIST
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
if let Some(((id, dir_id), subtitle)) = subtitles.next() {
|
||||
Text::new(&message(subtitle))
|
||||
if let Some(((id, dir_id), (message, data))) = subtitles.next() {
|
||||
Text::new(&message)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.parent(state.ids.subtitle_box_bg)
|
||||
.center_justify()
|
||||
.mid_bottom_with_margin_on(state.ids.subtitle_box_bg, 6.0)
|
||||
.color(color(subtitle))
|
||||
.color(color(data))
|
||||
.set(*id, ui);
|
||||
|
||||
dir(subtitle, id, dir_id, ui);
|
||||
dir(data, id, dir_id, ui);
|
||||
|
||||
let mut last_id = *id;
|
||||
for ((id, dir_id), subtitle) in subtitles {
|
||||
Text::new(&message(subtitle))
|
||||
for ((id, dir_id), (message, data)) in subtitles {
|
||||
Text::new(&message)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.parent(state.ids.subtitle_box_bg)
|
||||
.up_from(last_id, 8.0)
|
||||
.align_middle_x_of(last_id)
|
||||
.color(color(subtitle))
|
||||
.color(color(data))
|
||||
.set(*id, ui);
|
||||
|
||||
dir(subtitle, id, dir_id, ui);
|
||||
dir(data, id, dir_id, ui);
|
||||
|
||||
last_id = *id;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user