From 8533b689b20fd79df88b3255e49f994ee8dac2b6 Mon Sep 17 00:00:00 2001 From: hut Date: Mon, 15 Apr 2024 20:58:19 +0200 Subject: [PATCH] overhaul HUD --- src/hud.rs | 361 +++++++++++++++++++++++++++++++++-------------------- src/var.rs | 16 ++- 2 files changed, 240 insertions(+), 137 deletions(-) diff --git a/src/hud.rs b/src/hud.rs index 9a2d82e..9f7ae78 100644 --- a/src/hud.rs +++ b/src/hud.rs @@ -9,9 +9,10 @@ use std::time::SystemTime; pub const HUD_REFRESH_TIME: f32 = 0.1; pub const FONT: &str = "fonts/Yupiter-Regular.ttf"; -pub const LOG_MAX: usize = 4; -pub const LOG_MAX_TIME_S: f64 = 15.0; +pub const LOG_MAX: usize = 16; +pub const LOG_MAX_TIME_S: f64 = 30.0; pub const LOG_MAX_ROWS: usize = 30; +pub const MAX_CHOICES: usize = 10; pub const AMBIENT_LIGHT: f32 = 0.0; // Space is DARK pub const AMBIENT_LIGHT_AR: f32 = 15.0; //pub const REPLY_NUMBERS: [char; 10] = ['❶', '❷', '❸', '❹', '❺', '❻', '❼', '❽', '❾', '⓿']; @@ -48,8 +49,10 @@ impl Plugin for HudPlugin { } #[derive(Event)] pub struct TargetEvent(pub Option); -#[derive(Component)] struct GaugesText; -#[derive(Component)] struct ChatText; +#[derive(Component)] struct NodeHud; +#[derive(Component)] struct NodeConsole; +#[derive(Component)] struct NodeChoiceText; +#[derive(Component)] struct NodeCurrentChatLine; #[derive(Component)] struct Reticule; #[derive(Component)] struct ToggleableHudElement; #[derive(Component)] struct OnlyHideWhenTogglingHud; @@ -93,6 +96,14 @@ impl Message { } return 1.0; } + pub fn format(&self) -> String { + if self.sender.is_empty() { + return self.text.clone() + "\n"; + } + else { + return format!("{}: {}\n", self.sender, self.text); + } + } } #[derive(Component)] @@ -173,12 +184,33 @@ fn setup( } else { Visibility::Hidden }; - let style = TextStyle { - font: asset_server.load(FONT), - font_size: settings.font_size_hud, - color: Color::GRAY, + let font_handle = asset_server.load(FONT); + let style_conversations = TextStyle { + font: font_handle.clone(), + font_size: settings.font_size_conversations, + color: settings.hud_color_subtitles, ..default() }; + let style_console = TextStyle { + font: font_handle.clone(), + font_size: settings.font_size_console, + color: settings.hud_color_console, + ..default() + }; + let style_choices = TextStyle { + font: font_handle.clone(), + font_size: settings.font_size_choices, + color: settings.hud_color_choices, + ..default() + }; + let style = TextStyle { + font: font_handle, + font_size: settings.font_size_hud, + color: settings.hud_color, + ..default() + }; + + // Add Statistics HUD let mut bundle_fps = TextBundle::from_sections([ TextSection::new("", style.clone()), TextSection::new(" ⚡ ", style.clone()), @@ -198,73 +230,47 @@ fn setup( ]).with_style(Style { position_type: PositionType::Absolute, top: Val::VMin(2.0), - right: Val::VMin(3.0), + left: Val::VMin(3.0), ..default() - }).with_text_justify(JustifyText::Right); + }).with_text_justify(JustifyText::Left); bundle_fps.visibility = visibility; commands.spawn(( - GaugesText, + NodeHud, ToggleableHudElement, bundle_fps, )); - // Add Chat Box - let bundle_chatbox = TextBundle::from_sections([ - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - TextSection::new("", style.clone()), - ]).with_style(Style { + // Add Console + let bundle_chatbox = TextBundle::from_sections((0..LOG_MAX_ROWS).map(|_| + TextSection::new("", style_console.clone())) + ).with_style(Style { position_type: PositionType::Absolute, top: Val::VMin(0.0), - left: Val::VMin(0.0), + right: Val::VMin(0.0), ..default() - }).with_text_justify(JustifyText::Left); + }).with_text_justify(JustifyText::Right); commands.spawn(( + ToggleableHudElement, NodeBundle { style: Style { - width: Val::Percent(45.0), + width: Val::Percent(50.0), align_items: AlignItems::Start, position_type: PositionType::Absolute, top: Val::VMin(2.0), - left: Val::VMin(3.0), + right: Val::VMin(3.0), ..default() }, + visibility, ..default() }, )).with_children(|parent| { parent.spawn(( bundle_chatbox, - ChatText, + NodeConsole, )); }); + // Add Reticule commands.spawn(( Reticule, ToggleableHudElement, @@ -283,6 +289,54 @@ fn setup( }, )); + // Chat "subtitles" and choices + commands.spawn(NodeBundle { + style: Style { + width: Val::Vw(100.0), + align_items: AlignItems::Center, + flex_direction: FlexDirection::Column, + position_type: PositionType::Absolute, + bottom: Val::Vh(2.0), + left: Val::Px(0.0), + ..default() + }, + ..default() + }).with_children(|builder| { + builder.spawn(( + TextBundle { + text: Text { + sections: vec![ + TextSection::new("", style_conversations), + ], + justify: JustifyText::Center, + ..default() + }, + style: Style { + max_width: Val::Percent(50.0), + margin: UiRect { + bottom: Val::Vh(1.0), + ..default() + }, + ..default() + }, + ..default() + }, + NodeCurrentChatLine, + )); + let choice_sections = (0..MAX_CHOICES).map(|_| + TextSection::new("", style_choices.clone())); + builder.spawn(( + TextBundle { + text: Text { + sections: choice_sections.collect(), + ..default() + }, + ..default() + }, + NodeChoiceText, + )); + }); + // Selectagon commands.spawn(( Selectagon, @@ -310,9 +364,12 @@ fn update_hud( player: Query<(&actor::HitPoints, &actor::Suit, &actor::ExperiencesGForce), With>, q_camera: Query<(&Position, &LinearVelocity), With>, mut timer: ResMut, - mut query: Query<&mut Text, With>, q_choices: Query<&chat::Choice>, - mut query_chat: Query<&mut Text, (With, Without)>, + q_chat: Query<&chat::Chat>, + mut q_node_hud: Query<&mut Text, With>, + mut q_node_console: Query<&mut Text, (With, Without, Without)>, + mut q_node_choice: Query<&mut Text, (With, Without, Without)>, + mut q_node_currentline: Query<&mut Text, (With, Without, Without, Without)>, query_all_actors: Query<&actor::Actor>, settings: Res, q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With>, @@ -325,7 +382,7 @@ fn update_hud( if player.is_ok() && q_camera_result.is_ok() { let (hp, suit, gforce) = player.unwrap(); let (pos, cam_v) = q_camera_result.unwrap(); - for mut text in &mut query { + for mut text in &mut q_node_hud { text.sections[0].value = format!("2524-03-12 03:02"); if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) { if let Some(value) = fps.smoothed() { @@ -341,29 +398,30 @@ fn update_hud( if suit.oxygen > nature::OXY_H { let oxy_hour = suit.oxygen / nature::OXY_H; text.sections[7].value = format!("{oxy_percent:.1}% [lasts {oxy_hour:.1} hours]"); - text.sections[7].style.color = Color::GRAY; + text.sections[7].style.color = settings.hud_color; } else { let oxy_min = suit.oxygen / nature::OXY_M; text.sections[7].value = format!("{oxy_percent:.1}% [lasts {oxy_min:.1} min]"); - text.sections[7].style.color = Color::MAROON; + text.sections[7].style.color = settings.hud_color_alert; } //let adrenaline = lifeform.adrenaline * 990.0 + 10.0; //text.sections[11].value = format!("{adrenaline:.0}pg/mL"); let vitals = 100.0 * hp.current / hp.max; text.sections[13].value = format!("{vitals:.0}%"); if vitals < 50.0 { - text.sections[13].style.color = Color::MAROON; + text.sections[13].style.color = settings.hud_color_alert; } else { - text.sections[13].style.color = Color::GRAY; + text.sections[13].style.color = settings.hud_color; } let all_actors = query_all_actors.iter().len(); text.sections[9].value = format!("{all_actors:.0}"); let integrity = suit.integrity * 100.0; - text.sections[11].value = format!("{integrity:.0}%"); if integrity < 50.0 { - text.sections[11].style.color = Color::MAROON; + text.sections[11].style.color = settings.hud_color_alert; + text.sections[11].value = format!("{integrity:.0}% [LEAKING]"); } else { - text.sections[11].style.color = Color::GRAY; + text.sections[11].style.color = settings.hud_color; + text.sections[11].value = format!("{integrity:.0}%"); } //text.sections[17].value = format!("{speed_readable}/s / {kmh:.0}km/h / {gforce:.1}g"); @@ -426,86 +484,117 @@ fn update_hud( } } - if let Ok(mut chat) = query_chat.get_single_mut() { - let mut row = 0; - let bright = Color::rgb(0.8, 0.75, 0.78); - - // Chat Log and System Log - let logfilter = if settings.hud_active { - |_msg: &&Message| { true } - } else { - |msg: &&Message| { match msg.level { - LogLevel::Chat => true, - LogLevel::Warning => true, - LogLevel::Info => true, - _ => false - }} - }; - let mut messages: Vec<&Message> = log.logs.iter() - .filter(logfilter) - .rev() - .take(15) - .collect(); - messages.reverse(); - for msg in &messages { - if msg.sender.is_empty() { - chat.sections[row].value = msg.text.clone() + "\n"; - } - else { - chat.sections[row].value = format!("{}: {}\n", msg.sender, msg.text); - } - let freshness = msg.get_freshness(); - let clr: f32 = (freshness.powf(1.5) as f32).clamp(0.1, 1.0); - freshest_line = freshest_line.max(freshness); - chat.sections[row].style.color = Color::rgba(0.7, 0.7, 0.7, clr); - row += 1; - } - - // Highlight most recent line if in a conversation - if row > 0 && (!q_choices.is_empty() || freshest_line > 0.5) { - chat.sections[row-1].style.color = bright; - } - - // Add padding between chat and choices - chat.sections[row].value = "\n".to_string(); - row += 1; - - // Choices - let mut choices: Vec = Vec::new(); - let mut count = 0; - for choice in &q_choices { - if count > 9 { - break; - } - let press_this = REPLY_NUMBERS[choice.key]; - let reply = &choice.text; - //let recipient = &choice.recipient; - // TODO: indicate recipients if there's more than one - choices.push(format!("{press_this} {reply}")); - count += 1; - } - if count < 4 { - for _padding in 0..(4-count) { - choices.push(" ".to_string()); - } - } - for choice in choices { - chat.sections[row].value = choice + "\n"; - chat.sections[row].style.color = bright; - row += 1; - } - - // Blank the remaining rows - while row < LOG_MAX_ROWS { - chat.sections[row].value = "".to_string(); - row += 1; - } + let chat = q_node_console.get_single_mut(); + if chat.is_err() { + error!("Couldn't find HUD UI text section"); + return; } + let mut chat = chat.unwrap(); + + let choicebox = q_node_choice.get_single_mut(); + if choicebox.is_err() { + error!("Couldn't find HUD UI text section"); + return; + } + let mut choicebox = choicebox.unwrap(); + + let node_currentline = q_node_currentline.get_single_mut(); + if node_currentline.is_err() { + error!("Couldn't find HUD UI text section"); + return; + } + let mut node_currentline = node_currentline.unwrap(); + + let mut row = 0; + + // Chat Log and System Log + let logfilter = if settings.hud_active { + |_msg: &&Message| { true } + } else { + |msg: &&Message| { match msg.level { + LogLevel::Chat => true, + LogLevel::Warning => true, + LogLevel::Info => true, + _ => false + }} + }; + let messages: Vec<&Message> = log.logs.iter() + .filter(logfilter) + .rev() + .take(15) + .collect(); + //messages.reverse(); + for msg in &messages { + chat.sections[row].value = msg.format(); + let freshness = msg.get_freshness(); + let clr: f32 = (freshness.powf(1.5) as f32).clamp(0.1, 1.0); + freshest_line = freshest_line.max(freshness); + chat.sections[row].style.color.set_a(clr); + row += 1; + } + + // Display the last chat line as "subtitles" + if !q_chat.is_empty() { + let messages: Vec<&Message> = log.logs.iter() + .filter(|msg: &&Message| { match msg.level { + LogLevel::Chat => true, + _ => false + }}) + .rev() + .take(1) + .collect(); + if messages.len() > 0 { + node_currentline.sections[0].value = messages[0].format(); + } else { + node_currentline.sections[0].value = "".to_string(); + } + } else { + node_currentline.sections[0].value = "".to_string(); + } + + // Blank the remaining rows + while row < LOG_MAX_ROWS { + chat.sections[row].value = "".to_string(); + row += 1; + } + + // Choices + row = 0; + let mut choices: Vec = Vec::new(); + let mut count = 0; + for choice in &q_choices { + if count > 9 { + break; + } + let press_this = REPLY_NUMBERS[choice.key]; + let reply = &choice.text; + //let recipient = &choice.recipient; + // TODO: indicate recipients if there's more than one + choices.push(format!("{press_this} {reply}")); + count += 1; + } + for choice in choices { + if row >= MAX_CHOICES { + break; + } + choicebox.sections[row].value = choice + "\n"; + row += 1; + } + + while row < MAX_CHOICES { + choicebox.sections[row].value = if row < 4 { + " \n".to_string() + } else { + "".to_string() + }; + row += 1; + } + log.needs_rerendering = false; - if q_choices.is_empty() && freshest_line < 0.2 { - log.remove_old(); - } + //if q_choices.is_empty() && freshest_line < 0.2 { + log.remove_old(); + //} } } diff --git a/src/var.rs b/src/var.rs index 35d8552..fc1d9d4 100644 --- a/src/var.rs +++ b/src/var.rs @@ -29,6 +29,13 @@ pub struct Settings { pub zoom_sensitivity_factor: f32, pub font_size_hud: f32, pub font_size_conversations: f32, + pub font_size_choices: f32, + pub font_size_console: f32, + pub hud_color: Color, + pub hud_color_console: Color, + pub hud_color_alert: Color, + pub hud_color_subtitles: Color, + pub hud_color_choices: Color, pub chat_speed: f32, pub hud_active: bool, pub is_zooming: bool, @@ -113,8 +120,15 @@ impl Default for Settings { fov_highspeed: 25.0, zoom_fov: 15.0, zoom_sensitivity_factor: 0.25, - font_size_hud: 32.0, + font_size_hud: 28.0, font_size_conversations: 32.0, + font_size_choices: 28.0, + font_size_console: 20.0, + hud_color: Color::rgb(0.2, 0.5, 0.2), + hud_color_console: Color::rgb(0.2, 0.5, 0.2), + hud_color_alert: Color::rgb(0.7, 0.3, 0.3), + hud_color_subtitles: Color::rgb(0.8, 0.8, 0.8), + hud_color_choices: Color::rgb(0.45, 0.45, 0.45), chat_speed: DEFAULT_CHAT_SPEED * if dev_mode { 2.5 } else { 1.0 }, hud_active: false, is_zooming: false,