~starkingdoms/starkingdoms

94336e646a19dd15e9b88289757a2057676450a5 — ghostly_zsh an hour ago 32ccd92
ship editor feat: attachment
M crates/unified/src/ship_editor/components.rs => crates/unified/src/ship_editor/components.rs +5 -3
@@ 1,5 1,5 @@
use bevy::camera::visibility::RenderLayers;
use crate::prelude::{Component, Handle, Resource};
use crate::prelude::{Component, Handle, Entity, Resource};
use crate::shared::config::part::PartConfig;
use crate::shared::config::ship_editor::ShipEditorConfig;



@@ 8,6 8,10 @@ pub const GHOST_RENDER_LAYER: RenderLayers = RenderLayers::layer(1);

#[derive(Component)]
pub struct GhostModule;
#[derive(Component)]
pub struct Part;
#[derive(Component)]
pub struct PartConfigHolder(pub PartConfig);

#[derive(Component)]
pub struct MainCamera;


@@ 17,8 21,6 @@ pub struct GhostCamera;
pub struct PlayerPartRequest;
#[derive(Component)]
pub struct SpawnPartRequest(pub Handle<PartConfig>);
#[derive(Component)]
pub struct Part(pub PartConfig);

#[derive(Resource, Default)]
pub struct ShipEditorConfigHolder {

M crates/unified/src/ship_editor/input.rs => crates/unified/src/ship_editor/input.rs +101 -11
@@ 1,10 1,15 @@
use std::f64::consts::PI;
use avian2d::physics_transform::{Position, Rotation};
use avian2d::prelude::{AngularVelocity, LinearVelocity};
use bevy::camera::visibility::RenderLayers;
use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
use bevy::input_focus::InputFocus;
use bevy::prelude::*;
use bevy::window::PrimaryWindow;
use crate::client::components::Me;
use crate::shared::attachment::{JointOf, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::ship_editor::components::{GhostCamera, GhostModule, MainCamera, Part, GHOST_RENDER_LAYER};
use crate::shared::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::shared::ecs::MAIN_LAYER;
use crate::ship_editor::components::{GhostCamera, GhostModule, MainCamera, Part, PartConfigHolder, SpawnPartRequest, GHOST_RENDER_LAYER};
use crate::ship_editor::spawn_joints;
use crate::ship_editor::ui::PartEntry;



@@ 24,6 29,8 @@ struct ShipEditorDrag {
    is_holding_module: bool,
    init_cursor_pos: Vec2,
    init_camera_pos: Vec2,
    target: Option<Entity>,
    peer: Option<Entity>,
}

fn on_scroll(


@@ 71,6 78,21 @@ fn on_click(
    mut drag: ResMut<ShipEditorDrag>,
    camera: Single<&Transform, (With<Camera2d>, With<MainCamera>)>,
    window: Single<&Window, With<PrimaryWindow>>,
    mut ghost_module: Query<(Entity, &mut Transform, &mut Sprite, &Joints), (With<GhostModule>, Without<MainCamera>)>,
    snaps: Query<(&SnapOf, &SnapOfJoint)>,
    joints: Query<(&Joint, &JointOf, &Transform, Option<&Peer>, Entity), (Without<GhostModule>, Without<MainCamera>)>,
    mut parts: Query<
        (
            &mut Transform,
            Option<&PartInShip>,
            Entity,
            &Joints,
            &Part,
        ),
        (Without<Joint>, Without<GhostModule>, Without<MainCamera>),
    >,
    asset_server: Res<AssetServer>,
    mut commands: Commands,
) {
    let Some(cursor_pos) = window.cursor_position() else { return };
    if ev.just_pressed(MouseButton::Left) {


@@ 80,6 102,63 @@ fn on_click(
    }
    if ev.just_released(MouseButton::Left) {
        drag.is_dragging = false;

        if let Some(snap_target) = drag.target && let Some(peer_snap) = drag.peer {
            let Ok((ghost_entity, mut ghost_transform, mut ghost_sprite, ghost_joints)) = ghost_module.single_mut() else { return };
            let Ok((target_snap_part, target_snap_joint)) = snaps.get(snap_target) else {
                return;
            };
            let Ok((source_snap_part, source_snap_joint)) = snaps.get(peer_snap) else {
                return;
            };

            let Ok((target_jt, target_jt_of, target_jt_xform, target_jt_peer, target_jt_id)) = joints.get(target_snap_joint.0) else {
                return;
            };
            let Ok((source_jt, source_jt_of, _source_jt_xform, source_jt_peer, source_jt_id)) = joints.get(source_snap_joint.0) else {
                return;
            };

            if target_jt_peer.is_some() {
                warn!("drag request: cannot attach to a joint that already has a peer, ignoring request");
                return;
            }
            if source_jt_peer.is_some() {
                warn!("drag request: dragging from a part that is already attached is currently not supported, ignoring request");
                return;
            }

            let Ok((target_xform, target_in_ship, target_entity, _, _)) = parts.get(target_jt_of.0) else {
                return;
            };

            // attached (housing)
            /*let Ok((_, _, source_entity, source_joints, _)) = parts.get(source_jt_of.0) else {
                return;
            };*/

            commands.entity(source_jt_id).insert(Peer {
                peer_joint_entity_id: target_jt_id,
                physics_joint: Entity::PLACEHOLDER, // reusing components and physics joint is not used in the ship editor
            });
            commands.entity(target_jt_id).insert(Peer {
                peer_joint_entity_id: source_jt_id,
                physics_joint: Entity::PLACEHOLDER,
            });
            let target_position = target_xform.mul_transform(*target_jt_xform);
            ghost_transform.translation = target_position.translation;
            ghost_transform.rotation = target_position.rotation
                * source_jt.transform.rotation.inverse()
                * Quat::from_rotation_z(PI as f32);

            debug!("spawning part");
            ghost_sprite.color = Color::srgb(1.0, 1.0, 1.0);
            commands.entity(ghost_entity)
                .insert(Part)
                .insert(MAIN_LAYER)
                .remove::<RenderLayers>()
                .remove::<GhostModule>();
        }
    }
}



@@ 100,13 179,13 @@ fn camera_drag(
    ghost_transform.translation = main_transform.translation;
}
fn ghost_drag(
    drag: Res<ShipEditorDrag>,
    mut drag: ResMut<ShipEditorDrag>,
    window: Single<&Window, With<PrimaryWindow>>,
    mut ghost_module: Single<(Entity, &mut Transform), With<GhostModule>>,
    mut main_camera: Single<(&Camera, &GlobalTransform), (With<Camera2d>, With<MainCamera>)>,
    mut ghost_camera: Single<(&Camera, &GlobalTransform), (With<Camera2d>, With<GhostCamera>)>,
    snaps: Query<(&Transform, &SnapOfJoint, &SnapOf, Entity), Without<GhostModule>>,
    joints: Query<(&Transform, &JointOf, Entity), Without<GhostModule>>,
    joints: Query<(&Transform, &JointOf, Entity), (Without<Peer>, Without<GhostModule>)>,
    parts: Query<&GlobalTransform, (Or<(With<Part>, With<GhostModule>)>)>,
) {
    const SNAP_CUTOFF: f32 = 25.0;


@@ 124,8 203,10 @@ fn ghost_drag(
        return
    };

    let mut closest_snap = f32::INFINITY;
    let mut closest_distance = f32::INFINITY;
    let mut closest_snap_transform = None;
    let mut closest_snap = None;
    let mut closest_ghost_snap = None;
    for (snap_local_transform, snap_joint, snap_part, snap_id) in snaps {
        if snap_part.0 == ghost_entity { continue }



@@ 135,7 216,7 @@ fn ghost_drag(

        let distance_to_cursor = global_cursor_position.distance(snap_global_translation.translation().xy());

        if distance_to_cursor > closest_snap {
        if distance_to_cursor > closest_distance {
            continue;
        }
        if distance_to_cursor > SNAP_CUTOFF {


@@ 185,14 266,18 @@ fn ghost_drag(
        target_transform.rotation = part_transform.rotation()
            * (offset.rotation * closest_joint_transform.unwrap().rotation.inverse())
            * Quat::from_rotation_z(180.0f32.to_radians());
        closest_snap = distance_to_cursor;
        closest_distance = distance_to_cursor;
        closest_snap_transform = Some(target_transform);
        closest_snap = Some(snap_id);
        closest_ghost_snap = closest_peer_snap;
    }
    if let Some(transform) = closest_snap_transform {
        *ghost_transform = transform;
    } else {
        ghost_transform.translation = ghost_position.extend(0.0);
    }
    drag.target = closest_snap;
    drag.peer = closest_ghost_snap;
}

fn part_button_interaction(


@@ 213,16 298,20 @@ fn part_button_interaction(
            Interaction::None => {
                if !mouse_ev.pressed(MouseButton::Left) {
                    drag.is_holding_module = false;
                    for ghost_entity in ghost_part_query.iter() {
                        commands.entity(ghost_entity).despawn();
                    if drag.target.is_none() {
                        for ghost_entity in ghost_part_query.iter() {
                            commands.entity(ghost_entity).despawn();
                        }
                    }
                }
            }
            Interaction::Hovered => {
                if !mouse_ev.pressed(MouseButton::Left) {
                    drag.is_holding_module = false;
                    for ghost_entity in ghost_part_query.iter() {
                        commands.entity(ghost_entity).despawn();
                    if drag.target.is_none() {
                        for ghost_entity in ghost_part_query.iter() {
                            commands.entity(ghost_entity).despawn();
                        }
                    }
                }
            }


@@ 242,6 331,7 @@ fn part_button_interaction(
                    sprite,
                    Transform::from_translation(ghost_position.extend(0.0)),
                    GhostModule,
                    PartConfigHolder(part_entry.0.clone()),
                    GHOST_RENDER_LAYER,
                ));
                spawn_joints(&part_entry.0, entity.id(), commands.reborrow());

M crates/unified/src/ship_editor/mod.rs => crates/unified/src/ship_editor/mod.rs +3 -2
@@ 9,7 9,7 @@ use crate::prelude::*;
use crate::shared::attachment::{Joint, JointId, JointOf, SnapOf, SnapOfJoint};
use crate::shared::config::part::{JointConfig, PartConfig};
use crate::shared::config::ship_editor::ShipEditorConfig;
use crate::ship_editor::components::{GhostCamera, MainCamera, Part, PlayerPartRequest, ShipEditorConfigHolder, SpawnPartRequest, GHOST_RENDER_LAYER, MAIN_RENDER_LAYER};
use crate::ship_editor::components::{GhostCamera, MainCamera, Part, PartConfigHolder, PlayerPartRequest, ShipEditorConfigHolder, SpawnPartRequest, GHOST_RENDER_LAYER, MAIN_RENDER_LAYER};
use crate::ship_editor::input::input_plugin;
use crate::ship_editor::ui::{ui_plugin, PendingPart};



@@ 89,7 89,8 @@ fn spawn_parts(

            commands.entity(entity)
                .insert(sprite)
                .insert(Part(strong_part_config.clone()))
                .insert(Part)
                .insert(PartConfigHolder(strong_part_config.clone()))
                .remove::<SpawnPartRequest>();
            debug!("spawned part");
            spawn_joints(strong_part_config, entity, commands.reborrow());