169 lines
4.9 KiB
Rust
169 lines
4.9 KiB
Rust
use super::AimTarget;
|
|
use crate::{
|
|
GameState,
|
|
backpack::UiHeadState,
|
|
heads::{ActiveHeads, HeadsImages},
|
|
hitpoints::Hitpoints,
|
|
loading_assets::UIAssets,
|
|
npc::Npc,
|
|
player::Player,
|
|
};
|
|
use bevy::prelude::*;
|
|
|
|
#[derive(Component, Reflect, Default)]
|
|
#[reflect(Component)]
|
|
struct HeadImage;
|
|
|
|
#[derive(Component, Reflect, Default)]
|
|
#[reflect(Component)]
|
|
struct HeadDamage;
|
|
|
|
#[derive(Resource, Default, PartialEq)]
|
|
struct TargetUi {
|
|
head: Option<UiHeadState>,
|
|
}
|
|
|
|
pub fn plugin(app: &mut App) {
|
|
app.add_systems(OnEnter(GameState::Playing), setup);
|
|
app.add_systems(Update, (sync, update).run_if(in_state(GameState::Playing)));
|
|
}
|
|
|
|
fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
|
commands
|
|
.spawn((
|
|
Name::new("target-ui"),
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
top: Val::Px(150.0),
|
|
left: Val::Px(20.0),
|
|
height: Val::Px(74.0),
|
|
..default()
|
|
},
|
|
))
|
|
.with_children(|parent| {
|
|
spawn_head_ui(
|
|
parent,
|
|
assets.head_bg.clone(),
|
|
assets.head_regular.clone(),
|
|
assets.head_damage.clone(),
|
|
);
|
|
});
|
|
|
|
commands.insert_resource(TargetUi::default());
|
|
}
|
|
|
|
fn spawn_head_ui(
|
|
parent: &mut ChildSpawnerCommands,
|
|
bg: Handle<Image>,
|
|
regular: Handle<Image>,
|
|
damage: Handle<Image>,
|
|
) {
|
|
const SIZE: f32 = 90.0;
|
|
const DAMAGE_SIZE: f32 = 74.0;
|
|
|
|
parent
|
|
.spawn((Node {
|
|
position_type: PositionType::Relative,
|
|
justify_content: JustifyContent::Center,
|
|
align_items: AlignItems::Center,
|
|
width: Val::Px(SIZE),
|
|
..default()
|
|
},))
|
|
.with_children(|parent| {
|
|
parent.spawn((
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
..default()
|
|
},
|
|
ImageNode::new(bg),
|
|
));
|
|
parent.spawn((
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
..default()
|
|
},
|
|
ImageNode::default(),
|
|
Visibility::Hidden,
|
|
HeadImage,
|
|
));
|
|
parent.spawn((
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
..default()
|
|
},
|
|
ImageNode::new(regular),
|
|
));
|
|
parent
|
|
.spawn((Node {
|
|
height: Val::Px(DAMAGE_SIZE),
|
|
width: Val::Px(DAMAGE_SIZE),
|
|
..default()
|
|
},))
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn((
|
|
HeadDamage,
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
display: Display::Block,
|
|
overflow: Overflow::clip(),
|
|
top: Val::Px(0.),
|
|
left: Val::Px(0.),
|
|
right: Val::Px(0.),
|
|
height: Val::Percent(25.),
|
|
..default()
|
|
},
|
|
))
|
|
.with_child(ImageNode::new(damage));
|
|
});
|
|
});
|
|
}
|
|
|
|
fn update(
|
|
target: Res<TargetUi>,
|
|
heads_images: Res<HeadsImages>,
|
|
mut head_image: Query<
|
|
(&mut Visibility, &mut ImageNode),
|
|
(Without<HeadDamage>, With<HeadImage>),
|
|
>,
|
|
mut head_damage: Query<&mut Node, (With<HeadDamage>, Without<HeadImage>)>,
|
|
) {
|
|
if target.is_changed() {
|
|
if let Ok((mut vis, mut image)) = head_image.single_mut() {
|
|
if let Some(head) = target.head {
|
|
*vis = Visibility::Visible;
|
|
image.image = heads_images.heads[head.head].clone();
|
|
} else {
|
|
*vis = Visibility::Hidden;
|
|
}
|
|
}
|
|
|
|
if let Ok(mut node) = head_damage.single_mut() {
|
|
node.height = Val::Percent(target.head.map(|head| head.damage()).unwrap_or(0.) * 100.);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn sync(
|
|
mut target: ResMut<TargetUi>,
|
|
player_target: Query<&AimTarget, With<Player>>,
|
|
target_data: Query<(&Hitpoints, &ActiveHeads), With<Npc>>,
|
|
) {
|
|
let mut new_state = None;
|
|
if let Some(e) = player_target.iter().next().and_then(|target| target.0) {
|
|
if let Ok((hp, heads)) = target_data.get(e) {
|
|
let head = heads.current().expect("target must have a head on");
|
|
new_state = Some(UiHeadState {
|
|
head: head.head,
|
|
health: hp.health(),
|
|
ammo: 1.,
|
|
reloading: None,
|
|
});
|
|
}
|
|
}
|
|
|
|
if new_state != target.head {
|
|
target.head = new_state;
|
|
}
|
|
}
|