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, RED};
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));
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>);
#[derive(Component)]
struct DragGhost;
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, *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,
});
}
}
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,
(
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 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.rotation;
let mut snap = 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((parent_position, 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 = parent_position
.transform_point(snap_local_transform.translation)
.xy();
let distance_to_cursor = cursor.distance(snap_global_translation);
if distance_to_cursor > best_distance {
if debug.0 {
gizmos.circle_2d(snap_global_translation, 3.0, RED);
}
continue;
}
if distance_to_cursor > CUTOFF {
if debug.0 {
gizmos.circle_2d(snap_global_translation, 3.0, ORANGE);
}
continue;
}
if debug.0 {
gizmos.circle_2d(snap_global_translation, 3.0, GREEN);
}
let Ok((offset, _, _)) = joints.get(snap_joint.0) else {
continue;
};
let joint_target = parent_position.transform_point(offset.translation);
if debug.0 {
gizmos.circle_2d(joint_target.xy(), 3.0, GREEN);
}
let target_transform = Transform {
translation: joint_target,
rotation: ghost.rotation,
scale: offset.scale,
};
best_distance = distance_to_cursor;
best_target = target_transform;
snap = Some(snap_id);
}
**ghost = best_target;
if keys.pressed(KeyCode::KeyQ) {
ghost.rotation = ghost.rotation.mul_quat(Quat::from_rotation_z(
180.0_f32.to_radians() * time.delta_secs(),
));
}
if keys.pressed(KeyCode::KeyE) {
ghost.rotation = ghost.rotation.mul_quat(Quat::from_rotation_z(
-180.0_f32.to_radians() * time.delta_secs(),
));
}
rsnap.0 = snap;
}