use crate::{settings, actor, audio, nature}; use bevy::prelude::*; use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use std::collections::VecDeque; use std::time::SystemTime; pub const HUD_REFRESH_TIME: f32 = 0.5; pub const FONT: &str = "fonts/Yupiter-Regular.ttf"; pub const LOG_MAX: usize = 20; pub const LOG_MAX_TIME_S: u64 = 20; pub const CHOICE_NONE: &str = ""; pub const AMBIENT_LIGHT: f32 = 0.0; // Space is DARK pub const AMBIENT_LIGHT_AR: f32 = 15.0; //pub const REPLY_NUMBERS: [char; 10] = ['❶', '❷', '❸', '❹', '❺', '❻', '❼', '❽', '❾', '⓿']; //pub const REPLY_NUMBERS: [char; 10] = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩']; pub const REPLY_NUMBERS: [char; 10] = ['➀', '➁', '➂', '➃', '➄', '➅', '➆', '➇', '➈', '➉']; pub struct HudPlugin; impl Plugin for HudPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup); app.add_systems(Update, (update, handle_input)); app.add_systems(PostUpdate, despawn_old_choices); app.insert_resource(Log { logs: VecDeque::with_capacity(LOG_MAX), needs_rerendering: true, }); app.insert_resource(FPSUpdateTimer( Timer::from_seconds(HUD_REFRESH_TIME, TimerMode::Repeating))); } } #[derive(Component)] struct GaugesText; #[derive(Component)] struct ChatText; #[derive(Component)] struct Reticule; #[derive(Component)] struct ToggleableHudElement; #[derive(Resource)] struct FPSUpdateTimer(Timer); #[derive(Component)] pub struct ChoiceAvailable { pub conv_id: String, pub conv_label: String, pub recipient: String, pub text: String, } pub enum LogLevel { Warning, //Error, Info, //Debug, Chat, //Ping, } struct Message { text: String, sender: String, level: LogLevel, time: u64, } #[derive(Resource)] pub struct Log { logs: VecDeque, needs_rerendering: bool, } impl Log { pub fn info(&mut self, message: String) { self.add(message, "System".to_string(), LogLevel::Info); } pub fn chat(&mut self, message: String, sender: String) { self.add(message, sender, LogLevel::Chat); } pub fn warning(&mut self, message: String) { self.add(message, "WARNING".to_string(), LogLevel::Warning); } pub fn add(&mut self, message: String, sender: String, level: LogLevel) { if self.logs.len() == LOG_MAX { self.logs.pop_front(); } if let Ok(epoch) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { self.logs.push_back(Message { text: message, sender: sender, level: level, time: epoch.as_secs(), }); self.needs_rerendering = true; } } pub fn remove_old(&mut self) { if let Some(message) = self.logs.front() { if let Ok(epoch) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { if epoch.as_secs() - message.time > LOG_MAX_TIME_S { self.logs.pop_front(); } } } } } fn setup( mut commands: Commands, settings: Res, asset_server: Res, mut log: ResMut, mut ambient_light: ResMut, ) { log.info("Resuming from suspend".to_string()); log.warning("Oxygen Low".to_string()); let visibility = if settings.hud_active { Visibility::Inherited } else { Visibility::Hidden }; let mut bundle_fps = TextBundle::from_sections([ TextSection::new( "帧率 ", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() }, ), TextSection::new( "", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() } ), TextSection::new( "\n电量 ", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() }, ), TextSection::new( "", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() } ), TextSection::new( "\n氧 OXYGEN ", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() }, ), TextSection::new( "", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::MAROON, ..default() } ), TextSection::new( "\nAdren水平 ", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() }, ), TextSection::new( "", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() } ), TextSection::new( "\n雌激素水平 172pg/mL", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() }, ), TextSection::new( "\nProximity 警告 ", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() }, ), TextSection::new( "", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() } ), TextSection::new( "\nSuit Integrity ", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() }, ), TextSection::new( "", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() } ), TextSection::new( "\n相对的v ", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() }, ), TextSection::new( "", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() } ), TextSection::new( "\n\n", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() } ), TextSection::from_style( TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::GRAY, ..default() } ), ]).with_style(Style { top: Val::VMin(2.0), left: Val::VMin(3.0), ..default() }); bundle_fps.visibility = visibility; commands.spawn(( GaugesText, ToggleableHudElement, bundle_fps, )); // Add Chat Box let bundle_chatbox = TextBundle::from_sections([ TextSection::new( "Warning: System Log Uninitialized", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::rgb(0.7, 0.7, 0.7), ..default() } ), TextSection::new( "\n", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::WHITE, ..default() } ), TextSection::new( "\n\n\n", TextStyle { font: asset_server.load(FONT), font_size: settings.font_size_hud, color: Color::WHITE, ..default() } ), ]).with_style(Style { position_type: PositionType::Absolute, bottom: Val::VMin(0.0), left: Val::VMin(0.0), ..default() }).with_text_justify(JustifyText::Left); commands.spawn(( NodeBundle { style: Style { width: Val::Percent(50.), align_items: AlignItems::Start, position_type: PositionType::Absolute, bottom: Val::Vh(10.0), left: Val::Vw(25.0), ..default() }, ..default() }, )).with_children(|parent| { parent.spawn(( bundle_chatbox, ChatText, )); }); commands.spawn(( Reticule, ToggleableHudElement, NodeBundle { style: Style { width: Val::Px(2.0), height: Val::Px(2.0), position_type: PositionType::Absolute, top: Val::Vh(50.0), left: Val::Vw(50.0), ..default() }, visibility: visibility, background_color: Color::rgb(0.4, 0.4, 0.6).into(), ..default() }, )); // AR-related things ambient_light.brightness = if settings.hud_active { AMBIENT_LIGHT_AR } else { AMBIENT_LIGHT }; } fn update( diagnostics: Res, time: Res