~starkingdoms/starkingdoms

f75c2e1acc0e58683b4295dc68dc1fb5ee9ba217 — core 5 months ago 8c95174
feat: drag ghosts and snapping
M crates/unified/src/attachment.rs => crates/unified/src/attachment.rs +1 -1
@@ 12,7 12,7 @@ pub struct Parts(#[entities] Vec<Entity>);

#[derive(Component, Serialize, Deserialize, MapEntities)]
#[relationship(relationship_target = Parts)]
pub struct PartInShip(#[entities] Entity);
pub struct PartInShip(#[entities] pub Entity);

#[derive(Component, Serialize, Deserialize)]
#[require(Transform)]

M crates/unified/src/client/key_input.rs => crates/unified/src/client/key_input.rs +9 -1
@@ 1,3 1,4 @@
use std::ops::Deref;
use crate::attachment::{Joint, JointOf, SnapOf, SnapOfJoint};
use crate::ecs::{Part, ThrustEvent};
use bevy::color::palettes::css::{FUCHSIA, GREEN};


@@ 20,7 21,14 @@ pub fn key_input_plugin(app: &mut App) {
}

#[derive(Resource, Default)]
struct AttachmentDebugRes(bool);
pub struct AttachmentDebugRes(pub bool);
impl Deref for AttachmentDebugRes {
    type Target = bool;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn debug_render_keybind(
    keys: Res<ButtonInput<KeyCode>>,

M crates/unified/src/client/parts.rs => crates/unified/src/client/parts.rs +80 -8
@@ 1,12 1,16 @@
use bevy::color::palettes::css::{ORANGE, RED};
use crate::client::Me;
use crate::ecs::{CursorWorldCoordinates, DragRequestEvent, Part};
use bevy::prelude::*;
use bevy_rapier2d::dynamics::MassProperties;
use bevy_rapier2d::prelude::AdditionalMassProperties;
use crate::attachment::{JointOf, PartInShip, SnapOf, SnapOfJoint};
use crate::client::colors::GREEN;
use crate::client::key_input::AttachmentDebugRes;

pub fn parts_plugin(app: &mut App) {
    app.insert_resource(DragResource(None));
    app.add_systems(Update, (handle_incoming_parts, handle_updated_parts));
    app.add_systems(Update, (handle_incoming_parts, handle_updated_parts, update_drag_ghosts));
    app.add_observer(on_part_release);
}



@@ 55,18 59,29 @@ fn handle_updated_parts(

#[derive(Resource)]
struct DragResource(Option<Entity>);
#[derive(Component)]
struct DragGhost;

fn on_part_click(
    ev: Trigger<Pointer<Pressed>>,
    sprites: Query<&Sprite, Without<Me>>,
    sprites: Query<(&Sprite, &Transform), Without<Me>>,
    mut drag: ResMut<DragResource>,
    mut commands: Commands
) {
    if ev.button != PointerButton::Primary {
        return;
    }
    let Ok(_) = sprites.get(ev.target()) else {
    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.clone(),
        s
    ));

    drag.0 = Some(ev.target());
}



@@ 75,17 90,74 @@ fn on_part_release(
    mut drag: ResMut<DragResource>,
    mut events: EventWriter<DragRequestEvent>,
    cursor: Res<CursorWorldCoordinates>,
    mut commands: Commands,
    ghosts: Query<Entity, With<DragGhost>>,
) {
    if ev.button != PointerButton::Primary {
        return;
    }

    if let Some(e) = drag.0
        && let Some(c) = cursor.0
    {
        debug!(?e, ?c, "sending drag request");
        events.write(DragRequestEvent(e, c));
    if let Some(e) = drag.0 {
        for ghost in &ghosts {
            commands.entity(ghost).despawn();
        }

        if let Some(c) = cursor.0 {
            debug!(?e, ?c, "sending drag request");
            events.write(DragRequestEvent(e, c));
        }
    }

    drag.0 = None;
}
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)>,
    joints: Query<(&Transform, &JointOf)>,
    parts: Query<&GlobalTransform, With<Part>>,
    debug: Res<AttachmentDebugRes>,
    mut gizmos: Gizmos,
) {
    let Some(cursor) = cursor.0 else { return };

    const CUTOFF: f32 = 25.0; // px

    let mut best_distance = f32::INFINITY;
    let mut best_target = Transform::from_xyz(cursor.x, cursor.y, 0.0);

    for (snap_local_transform, snap_joint, snap_part) in &snaps {
        let Ok(parent_position) = parts.get(snap_part.0) else { 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, parent)) = joints.get(snap_joint.0) else { continue; };
        let Ok(parent_pos) = parts.get(parent.0) else { continue; };

        let joint_target = parent_pos.transform_point(offset.translation);

        if debug.0 { gizmos.circle_2d(joint_target.xy(), 3.0, GREEN); }

        let target_transform = Transform {
            translation: joint_target,
            rotation: parent_position.rotation().mul_quat(offset.rotation),
            scale: offset.scale,
        };
        best_distance = distance_to_cursor;
        best_target = target_transform;
    }

    **ghost = best_target;
}
\ No newline at end of file

M crates/unified/src/config/part.rs => crates/unified/src/config/part.rs +1 -1
@@ 36,7 36,7 @@ impl From<JointOffset> for Transform {
    fn from(value: JointOffset) -> Self {
        Transform {
            translation: value.translation,
            rotation: Quat::from_rotation_z(value.rotation),
            rotation: Quat::from_rotation_z(value.rotation.to_radians()),
            ..Default::default()
        }
    }

M crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +1 -0
@@ 10,6 10,7 @@ use crate::server::{ConnectedGameEntity, ConnectedNetworkEntity};
use bevy::prelude::*;
use bevy_rapier2d::prelude::ExternalForce;
use bevy_replicon::prelude::FromClient;
use crate::attachment::PartInShip;
use crate::server::system_sets::PlayerInputSet;

pub fn player_management_plugin(app: &mut App) {