use crate::attachment::{Joint, JointId, JointOf, Joints, Peer, SnapOf, SnapOfJoint}; use crate::config::part::{JointConfig, PartConfig}; use crate::ecs::{Part, PartHandle}; use crate::prelude::*; use bevy_replicon::prelude::Replicated; pub fn part_management_plugin(app: &mut App) { app.add_systems(PreUpdate, (handle_ready_parts, handle_part_reloading)); } #[derive(Bundle)] pub struct SpawnPartBundle { pub req: SpawnPartRequest, pub transform: Transform, } #[derive(Component)] pub struct SpawnPartRequest(pub Handle); // wait for parts assets to be ready, then spawn the full part fn handle_ready_parts( loading_parts: Query<(Entity, &SpawnPartRequest)>, mut commands: Commands, assets: Res>, ) { for (entity, loading_part) in &loading_parts { if let Some(strong_config) = assets.get(&loading_part.0) { // config is strong; spawn 'er in! commands .entity(entity) .insert(calculate_bundle(strong_config, &loading_part.0)) .remove::(); spawn_joints(strong_config, entity, commands.reborrow()); } } } fn handle_part_reloading( existing_parts: Query<(Entity, &PartHandle, &Joints)>, joints: Query<(&mut Joint, Option<&Peer>, Entity)>, snaps: Query<(Entity, &SnapOfJoint)>, assets: Res>, mut asset_events: MessageReader>, mut commands: Commands, ) { for event in asset_events.read() { if let AssetEvent::Modified { id } = event { let config = assets.get(*id).unwrap(); for existing_part in existing_parts.iter() { if existing_part.1.0.id() == *id { commands .entity(existing_part.0) .insert(calculate_bundle(config, &existing_part.1.0)); // update all joints let mut used_joints = vec![]; for joint_id in &**existing_part.2 { // find in config let Ok((joint, peer, _)) = joints.get(*joint_id) else { continue; }; let joint_cfg = config.joints.iter().find(|u| { joint.id == JointId::from_part_and_joint_id(&config.part.name, &u.id) }); let Some(joint_cfg) = joint_cfg else { if let Some(peer_id) = peer && let Ok(peer) = joints.get(peer_id.peer_joint_entity_id) { commands.entity(peer.2).remove::(); } commands.entity(*joint_id).despawn(); for snap in &snaps { if snap.1.0 == *joint_id { commands.entity(snap.0).despawn(); } } continue; }; used_joints.push(joint.id.clone()); commands.entity(*joint_id).insert(spawn_joint_bundle( joint_cfg, config, &existing_part.0, )); // annihilate all snaps then respawn for snap in &snaps { if snap.1.0 == *joint_id { commands.entity(snap.0).despawn(); } } commands.spawn(spawn_snap_bundle(joint_cfg, &existing_part.0, joint_id)); } for joint in &config.joints { let id = JointId::from_part_and_joint_id(&config.part.name, &joint.id); if used_joints.contains(&id) { continue; } let joint_id = commands .spawn(spawn_joint_bundle(joint, config, &existing_part.0)) .id(); commands.spawn(spawn_snap_bundle(joint, &existing_part.0, &joint_id)); } } } } } } fn calculate_bundle(config: &PartConfig, handle: &Handle) -> impl Bundle { let part = Part { strong_config: config.clone(), }; let part_handle = PartHandle(handle.clone()); let collider = Collider::rectangle(config.physics.width, config.physics.height); let mass = Mass(config.physics.mass); ( part, part_handle, collider, mass, ) } fn spawn_joint_bundle(joint: &JointConfig, part: &PartConfig, parent: &Entity) -> impl Bundle { let j_comp = Joint { id: JointId::from_part_and_joint_id(part.part.name.clone(), joint.id.clone()), transform: joint.target.into(), }; let joint_transform: Transform = j_comp.transform; let joint_of = JointOf(*parent); (j_comp, joint_transform, joint_of, Replicated) } fn spawn_snap_bundle(joint: &JointConfig, parent: &Entity, p_joint: &Entity) -> impl Bundle { let snap_transform: Transform = joint.snap.into(); let snap_for = SnapOf(*parent); let snap_of = SnapOfJoint(*p_joint); (snap_transform, snap_for, snap_of, Replicated) } fn spawn_joints(config: &PartConfig, parent: Entity, mut commands: Commands) { for joint in &config.joints { let joint_id = commands .spawn(spawn_joint_bundle(joint, config, &parent)) .id(); commands.spawn(spawn_snap_bundle(joint, &parent, &joint_id)); } }