use std::f32::consts::PI; use crate::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint}; use crate::ecs::Me; use crate::client::colors::GREEN; use crate::ecs::{DragRequestEvent, Part}; use crate::client::input::CursorWorldCoordinates; use bevy::color::palettes::css::{ORANGE, PURPLE, RED, YELLOW}; use crate::client::ship::attachment::AttachmentDebugRes; use crate::prelude::*; pub fn parts_plugin(app: &mut App) { app.insert_resource(DragResource(None)); app.insert_resource(SnapResource(None, None)); app.add_systems(PreUpdate, update_drag_ghosts); app.add_systems( Update, ( handle_incoming_parts, handle_updated_parts, update_part_sprites, ), ); app.add_observer(on_part_release); } fn handle_incoming_parts( mut commands: Commands, new_parts: Query<(Entity, &Part, Option<&PartInShip>), Added>, asset_server: Res, ) { for (new_entity, new_part, is_connected) in new_parts.iter() { let mut sprite = Sprite::from_image(asset_server.load(if is_connected.is_some() { &new_part.strong_config.part.sprite_connected } else { &new_part.strong_config.part.sprite_disconnected })); sprite.custom_size = Some(Vec2::new( new_part.strong_config.physics.width, new_part.strong_config.physics.height, )); commands .entity(new_entity) .insert(sprite) .insert(Pickable::default()) .observe(on_part_click); } } fn handle_updated_parts( mut commands: Commands, updated_parts: Query<(Entity, &Part, Option<&PartInShip>), Changed>, asset_server: Res, ) { for (updated_entity, updated_part, is_connected) in updated_parts.iter() { let mut sprite = Sprite::from_image(asset_server.load(if is_connected.is_some() { &updated_part.strong_config.part.sprite_connected } else { &updated_part.strong_config.part.sprite_disconnected })); sprite.custom_size = Some(Vec2::new( updated_part.strong_config.physics.width, updated_part.strong_config.physics.height, )); commands .entity(updated_entity) .remove::() .insert(sprite); } } fn update_part_sprites( added: Query>, mut removed: RemovedComponents, parts: Query<(&Part, Option<&PartInShip>)>, asset_server: Res, mut commands: Commands, ) { for e in added.into_iter().chain(removed.read()) { let Ok((part, connected_to)) = parts.get(e) else { continue; }; let sprite = if connected_to.is_some() { &part.strong_config.part.sprite_connected } else { &part.strong_config.part.sprite_disconnected }; let mut sprite = Sprite::from_image(asset_server.load(sprite)); sprite.custom_size = Some(Vec2::new( part.strong_config.physics.width, part.strong_config.physics.height, )); commands.entity(e).insert(sprite); } } #[derive(Resource)] struct DragResource(Option); #[derive(Resource)] struct SnapResource(Option, Option); #[derive(Component)] struct DragGhost; #[derive(Component)] struct Ghost { pub rot: Quat, pub last_target_pos: Vec3, pub vel: Vec3, pub part: Entity } const ROTATION_SMOOTH: f32 = 0.1; fn on_part_click( ev: On>, sprites: Query<(&Sprite, &Transform, &Joints), Without>, joints: Query<&Joint, Without>, mut drag: ResMut, mut commands: Commands, ) { if ev.button != PointerButton::Primary { return; } let Ok(sprite) = sprites.get(ev.event_target()) else { return; }; // make sure it has at least 1 valid unpeered joint let mut valid_peers = 0; for joint in &**sprite.2 { if joints.get(*joint).is_ok() { valid_peers += 1; } } if valid_peers == 0 { return; // ignore } let mut s = sprite.0.clone(); s.color = Color::srgba(0.7, 0.7, 0.7, 1.0); commands.spawn((DragGhost, Ghost { rot: sprite.1.rotation, last_target_pos: sprite.1.translation, vel: Vec3::ZERO, part: ev.event_target(), }, *sprite.1, s)); drag.0 = Some(ev.event().event_target()); } fn on_part_release( ev: On>, mut drag: ResMut, mut events: MessageWriter, cursor: Res, mut commands: Commands, ghost: Single<(Entity, &Transform), With>, snap: Res, ) { if ev.button != PointerButton::Primary { return; } if let Some(e) = drag.0 { let rotation = ghost.1.rotation; commands.entity(ghost.0).despawn(); if let Some(c) = cursor.0 { debug!(?e, ?c, "sending drag request"); events.write(DragRequestEvent { drag_target: e, drag_to: c, set_rotation: rotation, snap_target: snap.0, peer_snap: snap.1, }); } } drag.0 = None; } const F: f32 = 2.0; // frequency (Hz) // 0.0 is undamped, 0.0-1.0 is underdamped, 1.0 is critical, and >1.0 is over const ZETA: f32 = 0.7; // damping const R: f32 = -0.6; // initial response speed const K1: f32 = ZETA/(PI*F); const K2: f32 = 1.0/((2.0*PI*F)*(2.0*PI*F)); const K3: f32 = (R*ZETA)/(2.0*PI*F); /// !IMPORTANT! /// This function forms the bulk of the attachment system. /// PLEASE DO NOT MODIFY OR MOVE /// /// This code is super cursed, and it will break at the lightest breeze fn update_drag_ghosts( ghost: Single< (&mut Transform, &mut Ghost), ( With, Without, Without, Without, ), >, cursor: Res, snaps: Query<(&Transform, &SnapOfJoint, &SnapOf, Entity)>, joints: Query<(&Transform, &JointOf, Entity), Without>, parts: Query<(&GlobalTransform, Option<&Me>, Option<&PartInShip>), With>, linvel: Query<&LinearVelocity, With>, dt: Res>, me: Single>, debug: Res, mut rsnap: ResMut, drag: Res, mut gizmos: Gizmos, keys: Res>, time: Res