use crate::attachment::{JointOf, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::client::Me;
use crate::client::colors::GREEN;
use crate::client::key_input::AttachmentDebugRes;
use crate::ecs::{CursorWorldCoordinates, DragRequestEvent, Part};
use bevy::color::palettes::css::{ORANGE, PURPLE, RED, YELLOW};
use bevy::prelude::*;
use bevy_rapier2d::dynamics::MassProperties;
use bevy_rapier2d::prelude::AdditionalMassProperties;
pub fn parts_plugin(app: &mut App) {
app.insert_resource(DragResource(None));
app.insert_resource(SnapResource(None,None));
app.add_systems(
Update,
(
handle_incoming_parts,
handle_updated_parts,
update_drag_ghosts,
),
);
app.add_observer(on_part_release);
}
fn handle_incoming_parts(
mut commands: Commands,
new_parts: Query<(Entity, &Part), Added<Part>>,
asset_server: Res<AssetServer>,
) {
for (new_entity, new_part) in new_parts.iter() {
let mut sprite =
Sprite::from_image(asset_server.load(&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(AdditionalMassProperties::MassProperties(MassProperties {
local_center_of_mass: Vec2::ZERO,
mass: new_part.strong_config.physics.mass,
principal_inertia: 7.5,
}))
.insert(Pickable::default())
.observe(on_part_click);
}
}
fn handle_updated_parts(
mut commands: Commands,
updated_parts: Query<(Entity, &Part), Changed<Part>>,
asset_server: Res<AssetServer>,
) {
for (updated_entity, updated_part) in updated_parts.iter() {
let mut sprite = Sprite::from_image(
asset_server.load(&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::<Sprite>()
.remove::<AdditionalMassProperties>()
.insert(sprite)
.insert(AdditionalMassProperties::MassProperties(MassProperties {
local_center_of_mass: Vec2::ZERO,
mass: updated_part.strong_config.physics.mass,
principal_inertia: 7.5,
}));
}
}
#[derive(Resource)]
struct DragResource(Option<Entity>);
#[derive(Resource)]
struct SnapResource(Option<Entity>, Option<Entity>);
#[derive(Component)]
struct DragGhost;
#[derive(Component)]
struct GhostTarget(pub Quat);
const TRANSLATION_SMOOTH: f32 = 0.3;
const ROTATION_SMOOTH: f32 = 0.1;
fn on_part_click(
ev: Trigger<Pointer<Pressed>>,
sprites: Query<(&Sprite, &Transform), Without<Me>>,
mut drag: ResMut<DragResource>,
mut commands: Commands,
) {
if ev.button != PointerButton::Primary {
return;
}
let Ok(sprite) = sprites.get(ev.target()) else {
return;
};
let mut s = sprite.0.clone();
s.color = Color::srgba(0.7, 0.7, 0.7, 1.0);
commands.spawn((DragGhost, GhostTarget(sprite.1.rotation), *sprite.1, s));
drag.0 = Some(ev.target());
}
fn on_part_release(
ev: Trigger<Pointer<Released>>,
mut drag: ResMut<DragResource>,
mut events: EventWriter<DragRequestEvent>,
cursor: Res<CursorWorldCoordinates>,
mut commands: Commands,
ghost: Single<(Entity, &Transform), With<DragGhost>>,
snap: Res<SnapResource>,
) {
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;
}
/// !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(
mut ghost: Single<
(&mut Transform, &mut GhostTarget),
(
With<DragGhost>,
Without<SnapOf>,
Without<JointOf>,
Without<Part>,
),
>,
cursor: Res<CursorWorldCoordinates>,
snaps: Query<(&Transform, &SnapOfJoint, &SnapOf, Entity)>,
joints: Query<(&Transform, &JointOf, Entity), Without<Peer>>,
parts: Query<(&GlobalTransform, Option<&Me>, Option<&PartInShip>), With<Part>>,
me: Single<Entity, With<Me>>,
debug: Res<AttachmentDebugRes>,
mut rsnap: ResMut<SnapResource>,
drag: Res<DragResource>,
mut gizmos: Gizmos,
keys: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
const CUTOFF: f32 = 25.0; // px
let (mut ghost, mut ghost_target) = ghost.into_inner();
let Some(cursor) = cursor.0 else { return };
let mut best_distance = f32::INFINITY;
let mut best_target = Transform::from_xyz(cursor.x, cursor.y, 0.0);
best_target.rotation = ghost_target.0;
let mut snap = None;
let mut best_parent_position = None;
let mut best_snap_position = None;
let mut best_self_snap = None;
let mut best_self_snap_position = None;
let mut best_self_position = None;
for (snap_local_transform, snap_joint, snap_part, snap_id) in &snaps {
if Some(snap_part.0) == drag.0 {
continue;
}
// only snap to ourselves
let Ok((part_being_snapped_to, maybe_me, maybe_parent_ship)) = parts.get(snap_part.0) else {
continue;
};
let allowed = maybe_me.is_some() || maybe_parent_ship.is_some_and(|u| u.0 == *me);
if !allowed {
continue;
}
let snap_global_translation = part_being_snapped_to
.mul_transform(*snap_local_transform);
let distance_to_cursor = cursor.distance(snap_global_translation.translation().xy());
if distance_to_cursor > best_distance {
if debug.0 {
gizmos.circle_2d(snap_global_translation.translation().xy(), 3.0, RED);
}
continue;
}
if distance_to_cursor > CUTOFF {
if debug.0 {
gizmos.circle_2d(snap_global_translation.translation().xy(), 3.0, ORANGE);
}
continue;
}
if debug.0 {
gizmos.circle_2d(snap_global_translation.translation().xy(), 3.0, GREEN);
}
let Ok((offset, _, _)) = joints.get(snap_joint.0) else {
continue;
};
let joint_target = part_being_snapped_to.transform_point(offset.translation);
if debug.0 {
gizmos.circle_2d(joint_target.xy(), 3.0, GREEN);
}
snap = Some(snap_id);
let mut target_transform = Transform {
translation: joint_target,
rotation: ghost.rotation,
scale: offset.scale,
};
best_distance = distance_to_cursor;
best_parent_position = Some(part_being_snapped_to.translation().xy());
best_snap_position = Some(snap_global_translation);
// find, in this particular situation, the best peer on ourselves
let mut best_peer_snap_distance = f32::INFINITY;
let mut best_peer_snap = None;
let mut best_peer_snap_pos = None;
let mut best_peer_target_pos = None;
let mut best_joint_transform = None;
for (our_snap_local_transform, our_snap_joint, our_snap_part, our_snap_id) in &snaps {
if Some(our_snap_part.0) != drag.0 { continue; }
let our_snap_global_translation = target_transform
.mul_transform(*our_snap_local_transform);
let distance = our_snap_global_translation.translation.distance(snap_global_translation.translation());
if distance > best_peer_snap_distance { continue; }
let Ok((our_joint, _, _)) = joints.get(our_snap_joint.0) else { continue };
best_peer_snap = Some(our_snap_id);
best_peer_snap_distance = distance;
best_peer_snap_pos = Some(our_snap_global_translation.translation.xy());
best_peer_target_pos = Some(target_transform.translation.xy());
best_joint_transform = Some(our_joint);
}
target_transform.rotation = part_being_snapped_to.rotation() * (offset.rotation * best_joint_transform.unwrap().rotation.inverse()) * Quat::from_rotation_z(180.0f32.to_radians());
best_target = target_transform;
best_self_snap = best_peer_snap;
best_self_snap_position = best_peer_snap_pos;
best_self_position = best_peer_target_pos;
}
if debug.0 && let Some(part) = best_parent_position && let Some(snap) = best_snap_position {
gizmos.arrow_2d(part, snap.translation().xy(), YELLOW);
}
if debug.0 && let Some(part) = best_self_position && let Some(snap) = best_self_snap_position {
gizmos.arrow_2d(part, snap, PURPLE);
}
if keys.pressed(KeyCode::KeyQ) {
best_target.rotation = best_target.rotation.mul_quat(Quat::from_rotation_z(
180.0_f32.to_radians() * time.delta_secs(),
));
}
if keys.pressed(KeyCode::KeyE) {
best_target.rotation = best_target.rotation.mul_quat(Quat::from_rotation_z(
-180.0_f32.to_radians() * time.delta_secs(),
));
}
ghost_target.0 = best_target.rotation;
ghost.rotation = ghost.rotation.slerp(ghost_target.0, ROTATION_SMOOTH);
let dx = TRANSLATION_SMOOTH*(best_target.translation - ghost.translation);
ghost.translation += dx;
rsnap.0 = snap;
rsnap.1 = best_self_snap;
}