2024-04-05 20:16:01 +00:00
|
|
|
use crate::{actor, audio, camera, chat, nature, settings, world};
|
2024-03-17 14:23:22 +00:00
|
|
|
use bevy::prelude::*;
|
|
|
|
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
|
2024-04-05 20:16:01 +00:00
|
|
|
use bevy::transform::TransformSystem;
|
2024-03-30 16:19:11 +00:00
|
|
|
use bevy_xpbd_3d::prelude::*;
|
2024-04-05 16:14:12 +00:00
|
|
|
use bevy::math::DVec3;
|
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-04-08 00:26:23 +00:00
|
|
|
pub const HUD_REFRESH_TIME: f32 = 0.1;
|
2024-03-22 13:35:07 +00:00
|
|
|
pub const FONT: &str = "fonts/Yupiter-Regular.ttf";
|
2024-03-20 01:03:42 +00:00
|
|
|
pub const LOG_MAX: usize = 20;
|
|
|
|
pub const LOG_MAX_TIME_S: u64 = 20;
|
|
|
|
pub const CHOICE_NONE: &str = "";
|
2024-03-20 05:55:54 +00:00
|
|
|
pub const AMBIENT_LIGHT: f32 = 0.0; // Space is DARK
|
2024-03-21 03:34:09 +00:00
|
|
|
pub const AMBIENT_LIGHT_AR: f32 = 15.0;
|
2024-03-20 17:54:22 +00:00
|
|
|
//pub const REPLY_NUMBERS: [char; 10] = ['❶', '❷', '❸', '❹', '❺', '❻', '❼', '❽', '❾', '⓿'];
|
2024-03-22 13:38:28 +00:00
|
|
|
//pub const REPLY_NUMBERS: [char; 10] = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩'];
|
|
|
|
pub const REPLY_NUMBERS: [char; 10] = ['➀', '➁', '➂', '➃', '➄', '➅', '➆', '➇', '➈', '➉'];
|
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-04-05 19:27:19 +00:00
|
|
|
app.add_systems(Update, (
|
2024-04-07 18:02:31 +00:00
|
|
|
update_hud,
|
2024-04-05 19:27:19 +00:00
|
|
|
handle_input,
|
|
|
|
handle_target_event,
|
2024-04-05 20:16:01 +00:00
|
|
|
));
|
|
|
|
app.add_systems(PostUpdate, (
|
|
|
|
update_target_selectagon
|
|
|
|
.after(PhysicsSet::Sync)
|
|
|
|
.after(camera::apply_input_to_player)
|
|
|
|
.before(TransformSystem::TransformPropagate),
|
2024-04-05 19:27:19 +00:00
|
|
|
));
|
2024-03-19 02:18:16 +00:00
|
|
|
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-04-05 19:27:19 +00:00
|
|
|
app.add_event::<TargetEvent>();
|
2024-03-17 14:23:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-05 19:27:19 +00:00
|
|
|
#[derive(Event)] pub struct TargetEvent(pub Option<Entity>);
|
2024-03-19 00:24:27 +00:00
|
|
|
#[derive(Component)] struct GaugesText;
|
|
|
|
#[derive(Component)] struct ChatText;
|
2024-03-28 19:47:18 +00:00
|
|
|
#[derive(Component)] struct Reticule;
|
|
|
|
#[derive(Component)] struct ToggleableHudElement;
|
2024-04-05 20:43:14 +00:00
|
|
|
#[derive(Component)] struct OnlyHideWhenTogglingHud;
|
2024-04-05 20:16:01 +00:00
|
|
|
#[derive(Component)] struct Selectagon;
|
2024-04-05 16:14:12 +00:00
|
|
|
#[derive(Component)] pub struct IsTargeted;
|
2024-03-17 14:23:22 +00:00
|
|
|
|
|
|
|
#[derive(Resource)]
|
|
|
|
struct FPSUpdateTimer(Timer);
|
|
|
|
|
2024-03-19 02:18:16 +00:00
|
|
|
pub enum LogLevel {
|
|
|
|
Warning,
|
|
|
|
//Error,
|
|
|
|
Info,
|
|
|
|
//Debug,
|
|
|
|
Chat,
|
|
|
|
//Ping,
|
2024-03-30 19:11:11 +00:00
|
|
|
Notice,
|
2024-03-19 02:18:16 +00:00
|
|
|
}
|
|
|
|
|
2024-03-18 00:02:17 +00:00
|
|
|
struct Message {
|
|
|
|
text: String,
|
2024-03-19 02:18:16 +00:00
|
|
|
sender: String,
|
|
|
|
level: LogLevel,
|
2024-03-18 00:02:17 +00:00
|
|
|
time: u64,
|
|
|
|
}
|
|
|
|
|
2024-04-07 22:39:57 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
pub struct IsClickable {
|
|
|
|
pub name: Option<String>,
|
|
|
|
pub distance: Option<f64>,
|
|
|
|
}
|
|
|
|
impl Default for IsClickable { fn default() -> Self { Self {
|
|
|
|
name: None,
|
|
|
|
distance: None,
|
|
|
|
}}}
|
|
|
|
|
2024-03-17 23:36:56 +00:00
|
|
|
#[derive(Resource)]
|
2024-03-19 00:24:27 +00:00
|
|
|
pub struct Log {
|
2024-03-18 00:02:17 +00:00
|
|
|
logs: VecDeque<Message>,
|
2024-03-19 02:18:16 +00:00
|
|
|
needs_rerendering: bool,
|
2024-03-17 23:36:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Log {
|
2024-03-19 02:18:16 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-03-30 19:11:11 +00:00
|
|
|
pub fn notice(&mut self, message: String) {
|
|
|
|
self.add(message, "".to_string(), LogLevel::Notice);
|
|
|
|
}
|
|
|
|
|
2024-03-19 02:18:16 +00:00
|
|
|
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,
|
2024-03-19 02:18:16 +00:00
|
|
|
sender: sender,
|
|
|
|
level: level,
|
2024-03-18 00:02:17 +00:00
|
|
|
time: epoch.as_secs(),
|
|
|
|
});
|
2024-03-19 02:18:16 +00:00
|
|
|
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-04-05 03:13:09 +00:00
|
|
|
|
|
|
|
pub fn clear(&mut self) {
|
|
|
|
self.logs.clear();
|
|
|
|
}
|
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>,
|
2024-03-17 19:04:16 +00:00
|
|
|
asset_server: Res<AssetServer>,
|
2024-03-17 23:36:56 +00:00
|
|
|
mut log: ResMut<Log>,
|
2024-03-20 05:55:54 +00:00
|
|
|
mut ambient_light: ResMut<AmbientLight>,
|
2024-03-17 14:23:22 +00:00
|
|
|
) {
|
2024-03-30 19:11:11 +00:00
|
|
|
log.notice("Resuming from suspend".to_string());
|
|
|
|
log.notice("WARNING: Oxygen Low".to_string());
|
2024-03-17 21:28:10 +00:00
|
|
|
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-04-01 13:41:45 +00:00
|
|
|
"OutFly Augmented Reality 电量 ",
|
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,
|
2024-03-17 19:04:16 +00:00
|
|
|
color: Color::GRAY,
|
2024-03-17 18:03:02 +00:00
|
|
|
..default()
|
|
|
|
},
|
|
|
|
),
|
2024-03-17 19:04:16 +00:00
|
|
|
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,
|
2024-03-17 19:04:16 +00:00
|
|
|
color: Color::GRAY,
|
2024-03-17 18:03:02 +00:00
|
|
|
..default()
|
|
|
|
}
|
2024-03-17 19:04:16 +00:00
|
|
|
),
|
2024-04-05 02:08:26 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
),
|
2024-03-17 20:29:27 +00:00
|
|
|
TextSection::new(
|
2024-04-01 13:41:45 +00:00
|
|
|
" 帧率 ",
|
2024-03-17 21:28:10 +00:00
|
|
|
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-04-02 05:17:56 +00:00
|
|
|
"\n木星中心 ",
|
2024-03-17 20:29:27 +00:00
|
|
|
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-04-02 05:17:56 +00:00
|
|
|
color: Color::GRAY,
|
2024-03-17 20:29:27 +00:00
|
|
|
..default()
|
|
|
|
}
|
|
|
|
),
|
|
|
|
TextSection::new(
|
2024-04-02 05:17:56 +00:00
|
|
|
"\n氧 OXYGEN ",
|
2024-03-17 20:29:27 +00:00
|
|
|
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-04-02 05:17:56 +00:00
|
|
|
color: Color::MAROON,
|
2024-03-17 20:29:27 +00:00
|
|
|
..default()
|
|
|
|
}
|
|
|
|
),
|
2024-03-18 02:42:14 +00:00
|
|
|
TextSection::new(
|
2024-04-02 05:17:56 +00:00
|
|
|
"\nAdrenaline水平 ",
|
2024-03-18 02:42:14 +00:00
|
|
|
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-04-05 01:31:52 +00:00
|
|
|
TextSection::new(
|
|
|
|
"\nVitals ",
|
|
|
|
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-04-02 05:05:17 +00:00
|
|
|
TextSection::new(
|
2024-04-02 05:17:56 +00:00
|
|
|
"\nProximity 警告 ",
|
2024-04-02 05:05:17 +00:00
|
|
|
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-20 17:37:10 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
),
|
2024-03-28 22:09:08 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
),
|
2024-04-05 17:03:50 +00:00
|
|
|
TextSection::new(
|
|
|
|
"\nTarget: ",
|
|
|
|
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(
|
2024-03-30 21:35:00 +00:00
|
|
|
"\n☢ HAZARD\n\n",
|
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
|
|
|
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 20:29:27 +00:00
|
|
|
GaugesText,
|
2024-03-28 19:47:18 +00:00
|
|
|
ToggleableHudElement,
|
|
|
|
bundle_fps,
|
2024-03-17 14:23:22 +00:00
|
|
|
));
|
2024-03-19 00:24:27 +00:00
|
|
|
|
|
|
|
// Add Chat Box
|
2024-03-19 02:18:16 +00:00
|
|
|
let bundle_chatbox = TextBundle::from_sections([
|
|
|
|
TextSection::new(
|
2024-03-19 05:24:27 +00:00
|
|
|
"Warning: System Log Uninitialized",
|
2024-03-19 02:18:16 +00:00
|
|
|
TextStyle {
|
|
|
|
font: asset_server.load(FONT),
|
|
|
|
font_size: settings.font_size_hud,
|
2024-03-21 05:04:06 +00:00
|
|
|
color: Color::rgb(0.7, 0.7, 0.7),
|
2024-03-19 02:18:16 +00:00
|
|
|
..default()
|
|
|
|
}
|
|
|
|
),
|
2024-03-20 01:03:42 +00:00
|
|
|
TextSection::new(
|
|
|
|
"\n",
|
|
|
|
TextStyle {
|
|
|
|
font: asset_server.load(FONT),
|
|
|
|
font_size: settings.font_size_hud,
|
|
|
|
color: Color::WHITE,
|
|
|
|
..default()
|
|
|
|
}
|
|
|
|
),
|
|
|
|
TextSection::new(
|
2024-03-21 05:04:06 +00:00
|
|
|
"\n\n\n",
|
2024-03-20 01:03:42 +00:00
|
|
|
TextStyle {
|
|
|
|
font: asset_server.load(FONT),
|
|
|
|
font_size: settings.font_size_hud,
|
|
|
|
color: Color::WHITE,
|
|
|
|
..default()
|
|
|
|
}
|
|
|
|
),
|
2024-03-19 02:18:16 +00:00
|
|
|
]).with_style(Style {
|
|
|
|
position_type: PositionType::Absolute,
|
2024-03-19 14:51:08 +00:00
|
|
|
bottom: Val::VMin(0.0),
|
|
|
|
left: Val::VMin(0.0),
|
2024-03-19 02:18:16 +00:00
|
|
|
..default()
|
2024-03-19 05:14:25 +00:00
|
|
|
}).with_text_justify(JustifyText::Left);
|
2024-03-19 02:18:16 +00:00
|
|
|
commands.spawn((
|
2024-03-19 14:51:08 +00:00
|
|
|
NodeBundle {
|
|
|
|
style: Style {
|
|
|
|
width: Val::Percent(50.),
|
|
|
|
align_items: AlignItems::Start,
|
|
|
|
position_type: PositionType::Absolute,
|
2024-03-28 19:53:54 +00:00
|
|
|
bottom: Val::Vh(10.0),
|
|
|
|
left: Val::Vw(25.0),
|
2024-03-19 14:51:08 +00:00
|
|
|
..default()
|
|
|
|
},
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
)).with_children(|parent| {
|
|
|
|
parent.spawn((
|
|
|
|
bundle_chatbox,
|
|
|
|
ChatText,
|
|
|
|
));
|
|
|
|
});
|
2024-03-20 05:55:54 +00:00
|
|
|
|
2024-03-28 19:47:18 +00:00
|
|
|
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()
|
|
|
|
},
|
|
|
|
));
|
|
|
|
|
2024-04-05 20:16:01 +00:00
|
|
|
// Selectagon
|
|
|
|
commands.spawn((
|
|
|
|
Selectagon,
|
2024-04-05 20:43:14 +00:00
|
|
|
ToggleableHudElement,
|
|
|
|
OnlyHideWhenTogglingHud,
|
2024-04-05 20:16:01 +00:00
|
|
|
SceneBundle {
|
|
|
|
scene: asset_server.load(world::asset_name_to_path("selectagon")),
|
2024-04-05 20:43:14 +00:00
|
|
|
visibility: Visibility::Hidden,
|
2024-04-05 20:16:01 +00:00
|
|
|
..default()
|
|
|
|
},
|
|
|
|
));
|
|
|
|
|
2024-03-20 05:55:54 +00:00
|
|
|
// AR-related things
|
|
|
|
ambient_light.brightness = if settings.hud_active {
|
|
|
|
AMBIENT_LIGHT_AR
|
|
|
|
} else {
|
|
|
|
AMBIENT_LIGHT
|
|
|
|
};
|
2024-03-17 14:23:22 +00:00
|
|
|
}
|
|
|
|
|
2024-04-07 18:02:31 +00:00
|
|
|
fn update_hud(
|
2024-03-17 14:23:22 +00:00
|
|
|
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-04-05 23:11:11 +00:00
|
|
|
player: Query<(&actor::HitPoints, &actor::Suit, &actor::LifeForm, &actor::ExperiencesGForce), With<actor::Player>>,
|
2024-04-02 05:05:17 +00:00
|
|
|
q_camera: Query<(&Position, &LinearVelocity), With<actor::PlayerCamera>>,
|
2024-03-17 14:23:22 +00:00
|
|
|
mut timer: ResMut<FPSUpdateTimer>,
|
2024-03-17 20:29:27 +00:00
|
|
|
mut query: Query<&mut Text, With<GaugesText>>,
|
2024-04-04 11:39:49 +00:00
|
|
|
q_choices: Query<&chat::ChoiceAvailable>,
|
2024-03-19 02:18:16 +00:00
|
|
|
mut query_chat: Query<&mut Text, (With<ChatText>, Without<GaugesText>)>,
|
2024-03-18 02:42:14 +00:00
|
|
|
query_all_actors: Query<&actor::Actor>,
|
2024-03-19 02:18:16 +00:00
|
|
|
settings: Res<settings::Settings>,
|
2024-04-07 22:39:57 +00:00
|
|
|
q_target: Query<(Option<&Position>, &IsClickable), With<IsTargeted>>,
|
2024-03-17 14:23:22 +00:00
|
|
|
) {
|
2024-03-18 03:57:17 +00:00
|
|
|
// TODO only when hud is actually on
|
2024-03-19 02:18:16 +00:00
|
|
|
if timer.0.tick(time.delta()).just_finished() || log.needs_rerendering {
|
2024-03-30 16:19:11 +00:00
|
|
|
let q_camera_result = q_camera.get_single();
|
2024-03-17 22:49:50 +00:00
|
|
|
let player = player.get_single();
|
2024-03-30 16:19:11 +00:00
|
|
|
if player.is_ok() && q_camera_result.is_ok() {
|
2024-04-05 23:11:11 +00:00
|
|
|
let (hp, suit, lifeform, gforce) = player.unwrap();
|
2024-04-02 05:05:17 +00:00
|
|
|
let (pos, cam_v) = q_camera_result.unwrap();
|
2024-03-17 22:49:50 +00:00
|
|
|
for mut text in &mut query {
|
2024-04-05 02:08:26 +00:00
|
|
|
text.sections[3].value = format!("2524-03-12 03:02");
|
2024-03-17 22:49:50 +00:00
|
|
|
if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
|
|
|
|
if let Some(value) = fps.smoothed() {
|
|
|
|
// Update the value of the second section
|
2024-04-05 02:08:26 +00:00
|
|
|
text.sections[5].value = format!("{value:.0}");
|
2024-03-17 22:49:50 +00:00
|
|
|
}
|
2024-03-17 14:23:22 +00:00
|
|
|
}
|
2024-04-01 13:41:45 +00:00
|
|
|
let power = suit.power / suit.power_max * 100.0;
|
|
|
|
text.sections[1].value = format!("{power:}%");
|
2024-03-17 22:49:50 +00:00
|
|
|
let oxy_percent = suit.oxygen / suit.oxygen_max * 100.0;
|
|
|
|
let oxy_total = suit.oxygen * 1e6;
|
2024-03-23 19:29:16 +00:00
|
|
|
|
|
|
|
// the remaining oxygen hud info ignores leaking suits from low integrity
|
|
|
|
if suit.oxygen > nature::OXY_H {
|
|
|
|
let oxy_hour = suit.oxygen / nature::OXY_H;
|
2024-04-05 02:08:26 +00:00
|
|
|
text.sections[9].value = format!("{oxy_percent:.1}% [{oxy_total:.0}mg] [lasts {oxy_hour:.1} hours]");
|
2024-03-23 19:29:16 +00:00
|
|
|
} else {
|
|
|
|
let oxy_min = suit.oxygen / nature::OXY_M;
|
2024-04-05 02:08:26 +00:00
|
|
|
text.sections[9].value = format!("{oxy_percent:.1}% [{oxy_total:.0}mg] [lasts {oxy_min:.1} min]");
|
2024-03-23 19:29:16 +00:00
|
|
|
}
|
2024-03-17 22:49:50 +00:00
|
|
|
let adrenaline = lifeform.adrenaline * 990.0 + 10.0;
|
2024-04-05 02:08:26 +00:00
|
|
|
text.sections[11].value = format!("{adrenaline:.0}pg/mL");
|
2024-04-05 01:31:52 +00:00
|
|
|
let vitals = 100.0 * hp.current / hp.max;
|
2024-04-05 23:11:11 +00:00
|
|
|
let gforce = gforce.gforce;
|
|
|
|
text.sections[13].value = format!("{vitals:.0}% [{gforce}g]");
|
2024-03-18 02:42:14 +00:00
|
|
|
let all_actors = query_all_actors.iter().len();
|
2024-04-05 02:08:26 +00:00
|
|
|
text.sections[15].value = format!("{all_actors:.0}");
|
2024-03-20 17:37:10 +00:00
|
|
|
let integrity = suit.integrity * 100.0;
|
2024-04-05 02:08:26 +00:00
|
|
|
text.sections[17].value = format!("{integrity:.0}%");
|
2024-03-30 16:19:11 +00:00
|
|
|
let speed = cam_v.length();
|
2024-03-28 22:09:08 +00:00
|
|
|
let kmh = speed * 60.0 * 60.0 / 1000.0;
|
2024-04-08 01:00:48 +00:00
|
|
|
let speed_readable = nature::readable_distance(speed);
|
|
|
|
text.sections[19].value = format!("{speed_readable}/s | {kmh:.0}km/h");
|
2024-04-05 16:14:12 +00:00
|
|
|
|
|
|
|
// Target display
|
2024-04-07 22:39:57 +00:00
|
|
|
let (x, y, z, dist_scalar) : (f64, f64, f64, f64);
|
|
|
|
if let Ok((_, IsClickable { distance: Some(dist), .. })) = q_target.get_single() {
|
|
|
|
(x, y, z) = (0.0, 0.0, 0.0);
|
2024-04-08 01:00:48 +00:00
|
|
|
dist_scalar = *dist;
|
2024-04-05 16:14:12 +00:00
|
|
|
}
|
|
|
|
else {
|
2024-04-07 22:39:57 +00:00
|
|
|
let target: Option<DVec3>;
|
|
|
|
if let Ok((Some(targetpos), _)) = q_target.get_single() {
|
|
|
|
target = Some(targetpos.0);
|
|
|
|
}
|
|
|
|
else if q_target.is_empty() {
|
|
|
|
target = Some(DVec3::new(0.0, 0.0, 0.0));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
target = None;
|
|
|
|
}
|
|
|
|
if let Some(target_pos) = target {
|
|
|
|
let dist = pos.0 - target_pos;
|
|
|
|
(x, y, z) = (dist.x, dist.y, dist.z);
|
|
|
|
dist_scalar = dist.length();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
(x, y, z) = (0.0, 0.0, 0.0);
|
|
|
|
dist_scalar = 0.0;
|
|
|
|
}
|
2024-04-05 16:14:12 +00:00
|
|
|
}
|
2024-04-07 22:39:57 +00:00
|
|
|
|
2024-04-08 00:26:14 +00:00
|
|
|
if dist_scalar.is_nan() {
|
|
|
|
text.sections[7].value = format!("distance: UNKNOWN");
|
|
|
|
}
|
|
|
|
else if dist_scalar != 0.0 {
|
2024-04-08 01:00:48 +00:00
|
|
|
let x_readable = nature::readable_distance(x);
|
|
|
|
let y_readable = nature::readable_distance(y);
|
|
|
|
let z_readable = nature::readable_distance(z);
|
|
|
|
let distance_readable = nature::readable_distance(dist_scalar);
|
|
|
|
text.sections[7].value = format!("{x_readable} / {z_readable} / {y_readable} / distance: {distance_readable}");
|
2024-04-05 16:14:12 +00:00
|
|
|
}
|
|
|
|
else {
|
2024-04-07 22:39:57 +00:00
|
|
|
text.sections[7].value = format!("TARGET ERROR");
|
2024-04-05 16:14:12 +00:00
|
|
|
}
|
2024-04-05 17:03:50 +00:00
|
|
|
if q_target.is_empty() {
|
|
|
|
text.sections[21].value = "Jupiter".to_string();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
let targets: Vec<String> = q_target
|
|
|
|
.iter()
|
2024-04-07 22:39:57 +00:00
|
|
|
.map(|(_, clickable)| clickable.name.clone().unwrap_or("<unnamed>".to_string()))
|
2024-04-05 17:03:50 +00:00
|
|
|
.collect();
|
|
|
|
text.sections[21].value = targets.join(", ");
|
|
|
|
}
|
2024-03-17 14:23:22 +00:00
|
|
|
}
|
|
|
|
}
|
2024-03-19 02:18:16 +00:00
|
|
|
|
|
|
|
if let Ok(mut chat) = query_chat.get_single_mut() {
|
2024-03-20 01:03:42 +00:00
|
|
|
// Choices
|
|
|
|
let mut choices: Vec<String> = Vec::new();
|
2024-03-20 17:54:22 +00:00
|
|
|
let mut count = 0;
|
2024-03-20 01:03:42 +00:00
|
|
|
for choice in &q_choices {
|
2024-03-20 17:54:22 +00:00
|
|
|
if count > 9 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
let press_this = REPLY_NUMBERS[count];
|
|
|
|
let reply = &choice.text;
|
|
|
|
//let recipient = &choice.recipient;
|
|
|
|
// TODO: indicate recipients if there's more than one
|
|
|
|
choices.push(format!("{press_this} {reply}"));
|
2024-03-20 01:03:42 +00:00
|
|
|
count += 1;
|
|
|
|
}
|
2024-03-21 05:04:06 +00:00
|
|
|
if count < 4 {
|
|
|
|
for _padding in 0..(4-count) {
|
|
|
|
choices.push(" ".to_string());
|
|
|
|
}
|
|
|
|
}
|
2024-04-04 16:53:52 +00:00
|
|
|
chat.sections[2].value = choices.join("\n");
|
2024-03-20 01:03:42 +00:00
|
|
|
|
|
|
|
// Chat Log and System Log
|
2024-03-19 02:18:16 +00:00
|
|
|
let logfilter = if settings.hud_active {
|
|
|
|
|_msg: &&Message| { true }
|
|
|
|
} else {
|
|
|
|
|msg: &&Message| { match msg.level {
|
|
|
|
LogLevel::Chat => true,
|
|
|
|
LogLevel::Warning => true,
|
2024-03-21 05:04:06 +00:00
|
|
|
LogLevel::Info => true,
|
2024-03-30 19:11:11 +00:00
|
|
|
_ => false
|
2024-03-19 02:18:16 +00:00
|
|
|
}}
|
|
|
|
};
|
|
|
|
let logs_vec: Vec<String> = log.logs.iter()
|
|
|
|
.filter(logfilter)
|
2024-03-30 19:11:11 +00:00
|
|
|
.map(|s| if s.sender.is_empty() {
|
|
|
|
format!("{}", s.text)
|
|
|
|
} else {
|
|
|
|
format!("{}: {}", s.sender, s.text)
|
|
|
|
}).collect();
|
2024-03-19 02:18:16 +00:00
|
|
|
chat.sections[0].value = logs_vec.join("\n");
|
2024-03-17 23:36:56 +00:00
|
|
|
}
|
2024-03-19 02:18:16 +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>>,
|
2024-04-05 16:14:12 +00:00
|
|
|
mouse_input: Res<ButtonInput<MouseButton>>,
|
2024-03-18 02:01:47 +00:00
|
|
|
mut settings: ResMut<settings::Settings>,
|
2024-04-05 20:43:14 +00:00
|
|
|
mut q_hud: Query<(&mut Visibility, Option<&OnlyHideWhenTogglingHud>), With<ToggleableHudElement>>,
|
2024-04-05 00:58:17 +00:00
|
|
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
|
|
|
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
|
2024-04-05 19:27:19 +00:00
|
|
|
mut ew_target: EventWriter<TargetEvent>,
|
2024-03-20 05:55:54 +00:00
|
|
|
mut ambient_light: ResMut<AmbientLight>,
|
2024-04-05 18:10:45 +00:00
|
|
|
q_objects: Query<(Entity, &Transform), (With<IsClickable>, Without<IsTargeted>, Without<actor::PlayerDrivesThis>, Without<actor::Player>)>,
|
2024-04-05 16:14:12 +00:00
|
|
|
q_camera: Query<&Transform, With<Camera>>,
|
2024-03-17 18:03:02 +00:00
|
|
|
) {
|
|
|
|
if keyboard_input.just_pressed(settings.key_togglehud) {
|
2024-03-28 19:47:18 +00:00
|
|
|
if settings.hud_active {
|
2024-04-05 20:43:14 +00:00
|
|
|
for (mut hudelement_visibility, _) in q_hud.iter_mut() {
|
2024-03-28 19:47:18 +00:00
|
|
|
*hudelement_visibility = Visibility::Hidden;
|
|
|
|
}
|
|
|
|
settings.hud_active = false;
|
|
|
|
ambient_light.brightness = AMBIENT_LIGHT;
|
|
|
|
}
|
|
|
|
else {
|
2024-04-05 20:43:14 +00:00
|
|
|
for (mut hudelement_visibility, only_hide) in q_hud.iter_mut() {
|
|
|
|
if only_hide.is_none() {
|
|
|
|
*hudelement_visibility = Visibility::Inherited;
|
|
|
|
}
|
2024-03-18 13:08:11 +00:00
|
|
|
}
|
2024-03-28 19:47:18 +00:00
|
|
|
settings.hud_active = true;
|
|
|
|
ambient_light.brightness = AMBIENT_LIGHT_AR;
|
2024-03-17 18:03:02 +00:00
|
|
|
}
|
2024-04-05 00:58:17 +00:00
|
|
|
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
|
|
|
|
ew_togglemusic.send(audio::ToggleMusicEvent());
|
2024-03-17 18:03:02 +00:00
|
|
|
}
|
2024-04-05 20:43:14 +00:00
|
|
|
if settings.hud_active && mouse_input.just_pressed(settings.key_selectobject) {
|
2024-04-05 18:38:44 +00:00
|
|
|
if let Ok(camtrans) = q_camera.get_single() {
|
|
|
|
let objects: Vec<(Entity, &Transform)> = q_objects.iter().collect();
|
|
|
|
if let (Some(new_target), _dist) = camera::find_closest_target::<Entity>(objects, camtrans) {
|
2024-04-05 19:27:19 +00:00
|
|
|
ew_target.send(TargetEvent(Some(new_target)));
|
2024-04-05 16:14:12 +00:00
|
|
|
}
|
2024-04-05 18:38:44 +00:00
|
|
|
else {
|
2024-04-05 19:27:19 +00:00
|
|
|
ew_target.send(TargetEvent(None));
|
2024-04-05 16:14:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-03-20 01:03:42 +00:00
|
|
|
}
|
2024-04-05 19:27:19 +00:00
|
|
|
|
|
|
|
fn handle_target_event(
|
|
|
|
mut commands: Commands,
|
|
|
|
settings: Res<settings::Settings>,
|
|
|
|
mut er_target: EventReader<TargetEvent>,
|
|
|
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
|
|
|
q_target: Query<Entity, With<IsTargeted>>,
|
|
|
|
) {
|
|
|
|
let mut play_sfx = false;
|
|
|
|
|
|
|
|
for TargetEvent(target) in er_target.read() {
|
|
|
|
for old_target in &q_target {
|
|
|
|
commands.entity(old_target).remove::<IsTargeted>();
|
|
|
|
play_sfx = true;
|
|
|
|
}
|
|
|
|
if let Some(entity) = target {
|
|
|
|
commands.entity(*entity).insert(IsTargeted);
|
|
|
|
play_sfx = true;
|
|
|
|
}
|
|
|
|
if play_sfx && !settings.mute_sfx {
|
|
|
|
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
|
|
|
|
}
|
|
|
|
break; // Only accept a single event per frame
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_target_selectagon(
|
2024-04-05 20:43:14 +00:00
|
|
|
settings: Res<settings::Settings>,
|
2024-04-05 20:16:01 +00:00
|
|
|
mut q_selectagon: Query<(&mut Transform, &mut Visibility), (With<Selectagon>, Without<IsTargeted>, Without<Camera>)>,
|
|
|
|
q_target: Query<&Transform, (With<IsTargeted>, Without<Camera>, Without<Selectagon>)>,
|
|
|
|
q_camera: Query<&Transform, (With<Camera>, Without<IsTargeted>, Without<Selectagon>)>,
|
2024-04-05 19:27:19 +00:00
|
|
|
) {
|
2024-04-05 20:43:14 +00:00
|
|
|
if !settings.hud_active || q_camera.is_empty() {
|
2024-04-05 20:16:01 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
let camera_trans = q_camera.get_single().unwrap();
|
|
|
|
|
|
|
|
if let Ok((mut selectagon_trans, mut selectagon_vis)) = q_selectagon.get_single_mut() {
|
|
|
|
if let Ok(target_trans) = q_target.get_single() {
|
|
|
|
match *selectagon_vis {
|
|
|
|
Visibility::Hidden => { *selectagon_vis = Visibility::Visible; },
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
selectagon_trans.translation = target_trans.translation;
|
|
|
|
selectagon_trans.scale = target_trans.scale;
|
|
|
|
selectagon_trans.rotation = camera_trans.rotation;
|
2024-04-05 20:30:43 +00:00
|
|
|
|
|
|
|
// Enlarge Selectagon to a minimum angular diameter
|
|
|
|
let (angular_diameter, _, _) = camera::calc_angular_diameter(
|
|
|
|
&selectagon_trans, camera_trans);
|
|
|
|
let min_angular_diameter = 2.0f32.to_radians();
|
|
|
|
if angular_diameter < min_angular_diameter {
|
|
|
|
selectagon_trans.scale *= min_angular_diameter / angular_diameter;
|
|
|
|
}
|
2024-04-05 20:16:01 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
match *selectagon_vis {
|
|
|
|
Visibility::Hidden => {},
|
|
|
|
_ => { *selectagon_vis = Visibility::Hidden; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-05 19:27:19 +00:00
|
|
|
}
|