~starkingdoms/starkingdoms

94b1b4226b5c91d94b827ff384b1d75e29fb6342 — core 4 months ago 6676504
drag reattachment
M crates/unified/assets/config/world.wc.toml => crates/unified/assets/config/world.wc.toml +2 -2
@@ 1,5 1,5 @@
[world]
gravity = 0.001
gravity = 0.005
spawn_parts_at = "Earth"
spawn_parts_interval_secs = 10



@@ 9,5 9,5 @@ default_width = 50
default_mass = 100

[hearty]
thrust = 50000
thrust = 500000
spawn_at = "Earth"

M crates/unified/src/client/parts.rs => crates/unified/src/client/parts.rs +13 -2
@@ 1,4 1,4 @@
use crate::attachment::{JointOf, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::client::Me;
use crate::client::colors::GREEN;
use crate::client::key_input::AttachmentDebugRes;


@@ 89,7 89,8 @@ const ROTATION_SMOOTH: f32 = 0.1;

fn on_part_click(
    ev: Trigger<Pointer<Pressed>>,
    sprites: Query<(&Sprite, &Transform), Without<Me>>,
    sprites: Query<(&Sprite, &Transform, &Joints), Without<Me>>,
    joints: Query<&Joint, Without<Peer>>,
    mut drag: ResMut<DragResource>,
    mut commands: Commands,
) {


@@ 99,6 100,16 @@ fn on_part_click(
    let Ok(sprite) = sprites.get(ev.target()) else {
        return;
    };
    // make sure it has at least 1 valid unpeered joint
    let mut valid_peers = 0;
    for joint in &**sprite.2 {
        if joints.get(*joint).is_ok() {
            valid_peers += 1;
        }
    }
    if valid_peers == 0 {
        return; // ignore
    }
    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));

M crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +61 -5
@@ 8,20 8,24 @@ use bevy::prelude::*;
use bevy_rapier2d::prelude::{ExternalForce, FixedJointBuilder, ImpulseJoint, Velocity};
use bevy_replicon::prelude::FromClient;
use std::f32::consts::PI;
use crate::attachment::{Joint, JointOf, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint};

pub fn player_management_plugin(app: &mut App) {
    app.add_systems(
        Update,
        (handle_new_players, player_thrust, magic_fuel_regen, dragging).in_set(PlayerInputSet),
        (handle_new_players, player_thrust, magic_fuel_regen, (reprocess_reattached_parts, dragging).chain()).in_set(PlayerInputSet),
    );
}

#[derive(Component)]
struct JointNeedsCreation(ImpulseJoint);

fn dragging(
    mut events: EventReader<FromClient<DragRequestEvent>>,
    mut parts: Query<(&mut Transform, Option<&PartInShip>, Entity, &mut Velocity), (With<Part>, Without<Joint>)>,
    mut parts: Query<(&mut Transform, Option<&PartInShip>, Entity, &mut Velocity, &Joints), (With<Part>, Without<Joint>)>,
    snaps: Query<(&SnapOf, &SnapOfJoint)>,
    joints: Query<(&Joint, &JointOf, &Transform, Option<&Peer>, Entity)>,
    peer: Query<&Peer>,
    clients: Query<&ConnectedNetworkEntity>,
    mut commands: Commands
) {


@@ 83,6 87,17 @@ fn dragging(
            // TODO - validate source_part?

            // great, we have a valid peering request

            let mut did_disconnect = false;
            // disconnect it, if it is connected
            for joint in &**source_part.4 {
                let Ok(joint_peer) = peer.get(*joint) else { continue };
                did_disconnect = true;
                commands.entity(*joint).remove::<Peer>();
                commands.entity(joint_peer.0).remove::<Peer>();
            }
            commands.entity(source_part.2).remove::<ImpulseJoint>();

            // create the peering component...
            commands.entity(source_joint.4)
                .insert(Peer(target_joint.4));


@@ 111,8 126,28 @@ fn dragging(
                .local_basis1(target_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0 + PI
                    - source_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0);

            commands.entity(source_part.2)
                .insert(ImpulseJoint::new(target_part.2, joint));
            commands.entity(source_part.2).remove::<ImpulseJoint>();
            if !did_disconnect {
                commands.entity(source_part.2)
                    .insert(ImpulseJoint::new(target_part.2, joint));
            } else {
                // we disconnected this part this tick, and are performing a "reattachment"
                // (dragging an already attached part from peering point A to peering point B)
                
                // If we're reattaching to a different part, rapier will ignore our new attachment
                // as it will be seen as a mutation (removing and adding a component in the
                // same tick is considered equivalent to mutation by Bevy). 
                // As Rapier does not allow you to mutate the source/destination of a joint,
                // the change will be outright ignored.
                
                // Since there is no (easy) way to get the real joint information back out of Rapier,
                // or to force it to accept what it sees as invalid mutation,
                // we need to delay the creation of the new joint by 1 tick so it's noticed by Rapier
                // This component will be swapped out for a real ImpulseJoint on the next tick
                // by `reprocess_reattached_parts` (in this file)
                commands.entity(source_part.2)
                    .insert(JointNeedsCreation(ImpulseJoint::new(target_part.2, joint)));
            }

            teleport_to_translation = target_position.translation.xy();
            teleport_to_rotation = target_position.rotation * source_joint.0.transform.rotation.inverse() * Quat::from_rotation_z(PI);


@@ 121,6 156,14 @@ fn dragging(
        } else {
            warn!("blindly accepting non-attachment request, someone should change this eventually");
            warn!("dragging already attached entities may cause inconsistent behavior!!");
            let source_part = parts.get(event.drag_target).unwrap();
            // disconnect it, if it is connected
            for joint in &**source_part.4 {
                let Ok(joint_peer) = peer.get(*joint) else { continue };
                commands.entity(*joint).remove::<Peer>();
                commands.entity(joint_peer.0).remove::<Peer>();
            }
            commands.entity(source_part.2).remove::<ImpulseJoint>();
            teleport_to_translation = event.drag_to;
            teleport_to_rotation = event.set_rotation;
        }


@@ 136,6 179,19 @@ fn dragging(
    }
}

// LOGIC: **MUST** run BEFORE `dragging`
fn reprocess_reattached_parts(
    reattached: Query<(Entity, &JointNeedsCreation)>,
    mut commands: Commands
) {
    for (e, j) in &reattached {
        commands.entity(e)
            .remove::<JointNeedsCreation>()
            .insert(j.0);
    }
}


fn handle_new_players(
    mut commands: Commands,
    q_new_clients: Query<Entity, Added<ConnectedGameEntity>>,