@@ 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));
@@ 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>>,