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, } 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) { 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, regular: Handle, damage: Handle, ) { 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, heads_images: Res, mut head_image: Query< (&mut Visibility, &mut ImageNode), (Without, With), >, mut head_damage: Query<&mut Node, (With, Without)>, ) { 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, player_target: Query<&AimTarget, With>, target_data: Query<(&Hitpoints, &ActiveHeads), With>, ) { 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; } }