outfly/src/hud.rs

334 lines
10 KiB
Rust
Raw Normal View History

2024-03-18 00:52:41 +00:00
use crate::{settings, actor, audio};
2024-03-17 14:23:22 +00:00
use bevy::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
2024-03-17 23:36:56 +00:00
use std::collections::VecDeque;
2024-03-18 00:02:17 +00:00
use std::time::SystemTime;
2024-03-17 14:23:22 +00:00
2024-03-17 20:57:30 +00:00
const HUD_REFRESH_TIME: f32 = 0.5;
const FONT: &str = "tmp/fonts/NotoSansSC-Thin.ttf";
2024-03-19 04:38:11 +00:00
const LOG_MAX: usize = 20;
2024-03-18 03:39:26 +00:00
const LOG_MAX_TIME_S: u64 = 20;
2024-03-17 20:57:30 +00:00
2024-03-17 22:49:50 +00:00
pub struct HudPlugin;
impl Plugin for HudPlugin {
2024-03-17 14:23:22 +00:00
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
2024-03-17 18:03:02 +00:00
app.add_systems(Update, (update, handle_input));
app.insert_resource(Log {
logs: VecDeque::with_capacity(LOG_MAX),
needs_rerendering: true,
});
2024-03-17 20:57:30 +00:00
app.insert_resource(FPSUpdateTimer(
Timer::from_seconds(HUD_REFRESH_TIME, TimerMode::Repeating)));
2024-03-17 14:23:22 +00:00
}
}
#[derive(Component)] struct GaugesText;
#[derive(Component)] struct ChatText;
2024-03-17 14:23:22 +00:00
#[derive(Resource)]
struct FPSUpdateTimer(Timer);
pub enum LogLevel {
Warning,
//Error,
Info,
//Debug,
Chat,
//Ping,
}
2024-03-18 00:02:17 +00:00
struct Message {
text: String,
sender: String,
level: LogLevel,
2024-03-18 00:02:17 +00:00
time: u64,
}
2024-03-17 23:36:56 +00:00
#[derive(Resource)]
pub struct Log {
2024-03-18 00:02:17 +00:00
logs: VecDeque<Message>,
needs_rerendering: bool,
2024-03-17 23:36:56 +00:00
}
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) {
2024-03-17 23:36:56 +00:00
if self.logs.len() == LOG_MAX {
self.logs.pop_front();
}
2024-03-18 00:02:17 +00:00
if let Ok(epoch) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
self.logs.push_back(Message {
text: message,
sender: sender,
level: level,
2024-03-18 00:02:17 +00:00
time: epoch.as_secs(),
});
self.needs_rerendering = true;
2024-03-18 00:02:17 +00:00
}
}
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();
}
}
}
2024-03-17 23:36:56 +00:00
}
}
2024-03-17 14:23:22 +00:00
fn setup(
mut commands: Commands,
2024-03-17 18:03:02 +00:00
settings: Res<settings::Settings>,
asset_server: Res<AssetServer>,
2024-03-17 23:36:56 +00:00
mut log: ResMut<Log>,
2024-03-17 14:23:22 +00:00
) {
log.info("Customer wake-up registered.".to_string());
log.info("Systems reactivated.".to_string());
log.warning("Oxygen Low".to_string());
let visibility = if settings.hud_active {
2024-03-17 18:03:02 +00:00
Visibility::Inherited
} else {
Visibility::Hidden
};
let mut bundle_fps = TextBundle::from_sections([
TextSection::new(
2024-03-17 23:42:10 +00:00
"帧率 ",
2024-03-17 18:03:02 +00:00
TextStyle {
2024-03-17 19:28:45 +00:00
font: asset_server.load(FONT),
2024-03-17 19:31:16 +00:00
font_size: settings.font_size_hud,
color: Color::GRAY,
2024-03-17 18:03:02 +00:00
..default()
},
),
TextSection::new(
"",
2024-03-17 18:03:02 +00:00
TextStyle {
2024-03-17 19:28:45 +00:00
font: asset_server.load(FONT),
2024-03-17 19:31:16 +00:00
font_size: settings.font_size_hud,
color: Color::GRAY,
2024-03-17 18:03:02 +00:00
..default()
}
),
TextSection::new(
2024-03-17 23:42:10 +00:00
"\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(
2024-03-17 23:42:10 +00:00
"\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,
2024-03-18 02:47:31 +00:00
color: Color::MAROON,
..default()
}
),
TextSection::new(
2024-03-17 23:42:10 +00:00
"\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(
"\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()
}
),
2024-03-17 23:36:56 +00:00
TextSection::new(
"\n\n",
TextStyle {
font: asset_server.load(FONT),
font_size: settings.font_size_hud,
color: Color::GRAY,
..default()
}
),
2024-03-17 23:42:10 +00:00
TextSection::from_style(
2024-03-17 23:36:56 +00:00
TextStyle {
font: asset_server.load(FONT),
font_size: settings.font_size_hud,
color: Color::GRAY,
..default()
}
),
2024-03-17 23:42:10 +00:00
]).with_style(Style {
2024-03-18 00:23:35 +00:00
top: Val::VMin(2.0),
2024-03-17 23:42:10 +00:00
left: Val::VMin(3.0),
..default()
});
2024-03-17 18:03:02 +00:00
bundle_fps.visibility = visibility;
2024-03-17 14:23:22 +00:00
commands.spawn((
2024-03-17 18:03:02 +00:00
bundle_fps,
GaugesText,
2024-03-17 14:23:22 +00:00
));
// Add Chat Box
let bundle_chatbox = TextBundle::from_sections([
TextSection::new(
"Hello World!\nThis Is Cool, right?",
TextStyle {
font: asset_server.load(FONT),
font_size: settings.font_size_hud,
color: Color::GRAY,
..default()
}
),
]).with_style(Style {
position_type: PositionType::Absolute,
top: Val::VMin(2.0),
right: Val::VMin(6.0),
//bottom: Val::VMin(40.0),
//left: Val::VMin(30.0),
..default()
}).with_text_justify(JustifyText::Right);
commands.spawn((
bundle_chatbox,
ChatText,
));
2024-03-17 14:23:22 +00:00
}
fn update(
diagnostics: Res<DiagnosticsStore>,
2024-03-17 22:49:50 +00:00
time: Res<Time>,
2024-03-18 00:02:17 +00:00
mut log: ResMut<Log>,
2024-03-17 22:49:50 +00:00
player: Query<(&actor::Suit, &actor::LifeForm), With<actor::Player>>,
2024-03-17 14:23:22 +00:00
mut timer: ResMut<FPSUpdateTimer>,
mut query: Query<&mut Text, With<GaugesText>>,
mut query_chat: Query<&mut Text, (With<ChatText>, Without<GaugesText>)>,
query_all_actors: Query<&actor::Actor>,
settings: Res<settings::Settings>,
2024-03-17 14:23:22 +00:00
) {
2024-03-18 03:57:17 +00:00
// TODO only when hud is actually on
if timer.0.tick(time.delta()).just_finished() || log.needs_rerendering {
2024-03-17 22:49:50 +00:00
let player = player.get_single();
if player.is_ok() {
let (suit, lifeform) = player.unwrap();
for mut text in &mut query {
if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
if let Some(value) = fps.smoothed() {
// Update the value of the second section
text.sections[1].value = format!("{value:.0}");
}
2024-03-17 14:23:22 +00:00
}
2024-03-17 22:49:50 +00:00
let power = suit.power;
text.sections[3].value = format!("{power:}Wh");
let oxy_percent = suit.oxygen / suit.oxygen_max * 100.0;
let oxy_total = suit.oxygen * 1e6;
text.sections[5].value = format!("{oxy_percent:.1}% [{oxy_total:.0}mg]");
let adrenaline = lifeform.adrenaline * 990.0 + 10.0;
text.sections[7].value = format!("{adrenaline:.0}pg/mL");
let all_actors = query_all_actors.iter().len();
text.sections[9].value = format!("{all_actors:.0}");
2024-03-17 14:23:22 +00:00
}
}
if let Ok(mut chat) = query_chat.get_single_mut() {
let logfilter = if settings.hud_active {
|_msg: &&Message| { true }
} else {
|msg: &&Message| { match msg.level {
LogLevel::Chat => true,
LogLevel::Warning => true,
_ => false
}}
};
let logs_vec: Vec<String> = log.logs.iter()
.filter(logfilter)
.map(|s| format!("{}: {}", s.sender, s.text)).collect();
chat.sections[0].value = logs_vec.join("\n");
2024-03-17 23:36:56 +00:00
}
log.needs_rerendering = false;
2024-03-18 00:02:17 +00:00
log.remove_old();
2024-03-17 14:23:22 +00:00
}
}
2024-03-17 18:03:02 +00:00
fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<settings::Settings>,
mut query: Query<&mut Visibility, With<GaugesText>>,
mut query_bloomsettings: Query<&mut BloomSettings>,
mut evwriter_sfx: EventWriter<audio::PlaySfxEvent>,
mut evwriter_togglemusic: EventWriter<audio::ToggleMusicEvent>,
2024-03-17 18:03:02 +00:00
) {
if keyboard_input.just_pressed(settings.key_togglehud) {
if let Ok(mut vis) = query.get_single_mut() {
if let Ok(mut bloomsettings) = query_bloomsettings.get_single_mut() {
2024-03-18 03:57:17 +00:00
if *vis == Visibility::Inherited {
*vis = Visibility::Hidden;
settings.hud_active = false;
bloomsettings.composite_mode = BloomCompositeMode::EnergyConserving;
2024-03-18 03:57:17 +00:00
} else {
*vis = Visibility::Inherited;
settings.hud_active = true;
bloomsettings.composite_mode = BloomCompositeMode::EnergyConserving;
//bloomsettings.composite_mode = BloomCompositeMode::Additive;
2024-03-18 03:57:17 +00:00
}
evwriter_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
2024-03-18 03:57:17 +00:00
evwriter_togglemusic.send(audio::ToggleMusicEvent());
}
2024-03-17 18:03:02 +00:00
}
}
}