From 97a7f3ed1ee67a9ce3a05f453237b1de04f459f6 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 17 May 2026 13:30:58 -0400 Subject: [PATCH] chore(netcode-rewrite): code cleanups and refactoring --- crates/unified/src/client/components/mod.rs | 38 +-- .../unified/src/client/crafting/components.rs | 4 + crates/unified/src/client/crafting/mod.rs | 1 + crates/unified/src/client/crafting/ui.rs | 3 +- crates/unified/src/client/mod.rs | 2 +- crates/unified/src/client/parts.rs | 112 +++---- crates/unified/src/client/rendering/mod.rs | 3 +- crates/unified/src/client/ship/thrusters.rs | 38 +-- crates/unified/src/client/starfield.rs | 3 +- .../src/client/starguide/components.rs | 25 ++ crates/unified/src/client/starguide/init.rs | 3 +- crates/unified/src/client/starguide/input.rs | 2 +- crates/unified/src/client/starguide/mod.rs | 1 + crates/unified/src/client/starguide/orbit.rs | 3 +- crates/unified/src/client/zoom.rs | 8 +- crates/unified/src/server/components/mod.rs | 4 + crates/unified/src/server/drill.rs | 2 +- crates/unified/src/server/mod.rs | 1 + crates/unified/src/server/planets.rs | 2 +- crates/unified/src/server/player.rs | 293 ++++++++---------- crates/unified/src/shared/ecs.rs | 11 +- 21 files changed, 255 insertions(+), 304 deletions(-) create mode 100644 crates/unified/src/client/crafting/components.rs create mode 100644 crates/unified/src/client/starguide/components.rs create mode 100644 crates/unified/src/server/components/mod.rs diff --git a/crates/unified/src/client/components/mod.rs b/crates/unified/src/client/components/mod.rs index 49131c09423d8e2616d2032e47e209534cfd60fc..3023c7977e1723e1f8b43ef831f0133acc426835 100644 --- a/crates/unified/src/client/components/mod.rs +++ b/crates/unified/src/client/components/mod.rs @@ -1,49 +1,13 @@ -use crate::prelude::{Component, Deserialize, GizmoConfigGroup, Handle, Image, Reflect, Resource, Serialize}; +use crate::prelude::Component; #[derive(Component)] pub struct MainCamera; -#[derive(Component)] -pub struct StarguideCamera; - -#[derive(Component)] -pub struct OrbitCamera; - -#[derive(Default, Reflect, GizmoConfigGroup)] -pub struct StarguideGizmos; - -#[derive(Component)] -pub struct StarfieldFront; - -#[derive(Component)] -pub struct StarfieldMid; - -#[derive(Component)] -pub struct StarfieldBack; - #[derive(Component)] pub struct FuelText; #[derive(Component)] pub struct PowerText; -#[derive(Component)] -pub struct PlanetSensor(pub String); - #[derive(Component)] pub struct Me; - -#[derive(Component)] -pub struct StarguideMe; - -#[derive(Resource)] -pub struct StarguideOrbitImage(pub Handle); - -#[derive(Component)] -pub struct StarguideOrbit; - -#[derive(Component, Serialize, Deserialize, Debug)] -pub struct CraftingUi; - -#[derive(Component, Serialize, Deserialize, Debug)] -pub struct TemperatureSprite; \ No newline at end of file diff --git a/crates/unified/src/client/crafting/components.rs b/crates/unified/src/client/crafting/components.rs new file mode 100644 index 0000000000000000000000000000000000000000..306722f7bf66a50a28fc5b56fc3b94126e501dc5 --- /dev/null +++ b/crates/unified/src/client/crafting/components.rs @@ -0,0 +1,4 @@ +use crate::prelude::{Component, Deserialize, Serialize}; + +#[derive(Component, Serialize, Deserialize, Debug)] +pub struct CraftingUi; \ No newline at end of file diff --git a/crates/unified/src/client/crafting/mod.rs b/crates/unified/src/client/crafting/mod.rs index 6bae95df97a86ba99733b3ac0a40af71a8bdfa8b..39daf0314cc0a608f2d9afc807cb1e7940310b9a 100644 --- a/crates/unified/src/client/crafting/mod.rs +++ b/crates/unified/src/client/crafting/mod.rs @@ -1 +1,2 @@ pub mod ui; +pub mod components; diff --git a/crates/unified/src/client/crafting/ui.rs b/crates/unified/src/client/crafting/ui.rs index aea60e1245b3941795d0bc966585781d0d187451..6e8bfc9036fbc510c404aba81d7086dffa98307f 100644 --- a/crates/unified/src/client/crafting/ui.rs +++ b/crates/unified/src/client/crafting/ui.rs @@ -3,7 +3,8 @@ use std::collections::HashMap; use bevy::{input_focus::AutoFocus, ui::RelativeCursorPosition}; use crate::{client::colors, prelude::*}; -use crate::client::components::{CraftingUi, MainCamera, Me}; +use crate::client::components::{MainCamera, Me}; +use crate::client::crafting::components::CraftingUi; use crate::shared::attachment::PartInShip; use crate::shared::config::recipe::RecipesConfig; use crate::shared::ecs::{CanCraft, CraftPartRequest, Drill, Part, SingleStorage, ToggleDrillEvent, VariableStorage}; diff --git a/crates/unified/src/client/mod.rs b/crates/unified/src/client/mod.rs index 7f002259ae070e14af920450e371381799a29463..a555f2e1218f943382440954631df16f57007fb6 100644 --- a/crates/unified/src/client/mod.rs +++ b/crates/unified/src/client/mod.rs @@ -8,7 +8,7 @@ use crate::client::ui::ui_plugin; use crate::client::zoom::zoom_plugin; use crate::client::starguide::init::starguide_init_plugin; use crate::client::starguide::input::starguide_input_plugin; -use components::StarguideGizmos; +use starguide::components::StarguideGizmos; use bevy::dev_tools::picking_debug::DebugPickingMode; use crate::prelude::*; use planet::incoming_planets::incoming_planets_plugin; diff --git a/crates/unified/src/client/parts.rs b/crates/unified/src/client/parts.rs index fbbdb69bb7121b0912ead33e55eebb30c0ee46cf..457d977d19ab128c8216cd74dbaa8129544e4349 100644 --- a/crates/unified/src/client/parts.rs +++ b/crates/unified/src/client/parts.rs @@ -4,7 +4,7 @@ use crate::shared::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf use crate::client::crafting::ui::open_crafting_ui; use crate::shared::ecs::Temperature; use crate::client::colors::GREEN; -use crate::shared::ecs::{DragRequestEvent, Part, MAIN_LAYER}; +use crate::shared::ecs::{DragAction, DragRequestEvent, Part, MAIN_LAYER}; use crate::client::input::CursorWorldCoordinates; use bevy::color::palettes::css::{ORANGE, PURPLE, RED, YELLOW}; use crate::client::components::Me; @@ -12,8 +12,8 @@ use crate::client::ship::attachment::AttachmentDebugRes; use crate::prelude::*; pub fn parts_plugin(app: &mut App) { - app.insert_resource(DragResource(None)); - app.insert_resource(SnapResource(None, None)); + app.insert_resource(DragResource { dragged: None }); + app.insert_resource(SnapResource { target: None, peer: None }); app.add_systems( Update, ( @@ -27,23 +27,32 @@ pub fn parts_plugin(app: &mut App) { app.add_observer(on_part_release); } +fn temp_to_color(temperature: &Temperature) -> Color { + Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0) +} + +fn build_part_sprite(part: &Part, temperature: &Temperature, is_connected: bool, asset_server: &AssetServer) -> Sprite { + let path = if is_connected { + &part.strong_config.part.sprite_connected + } else { + &part.strong_config.part.sprite_disconnected + }; + let mut sprite = Sprite::from_image(asset_server.load(path)); + sprite.custom_size = Some(Vec2::new( + part.strong_config.physics.width as f32, + part.strong_config.physics.height as f32, + )); + sprite.color = temp_to_color(temperature); + sprite +} + fn handle_incoming_parts( mut commands: Commands, new_parts: Query<(Entity, &Part, &Temperature, Option<&PartInShip>), Added>, asset_server: Res, ) { for (new_entity, new_part, temperature, is_connected) in new_parts.iter() { - let mut sprite = Sprite::from_image(asset_server.load(if is_connected.is_some() { - &new_part.strong_config.part.sprite_connected - } else { - &new_part.strong_config.part.sprite_disconnected - })); - sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0); - sprite.custom_size = Some(Vec2::new( - new_part.strong_config.physics.width as f32, - new_part.strong_config.physics.height as f32, - )); - + let sprite = build_part_sprite(new_part, temperature, is_connected.is_some(), &asset_server); commands .entity(new_entity) .insert(MAIN_LAYER) @@ -53,30 +62,22 @@ fn handle_incoming_parts( .observe(open_crafting_ui); } } + fn handle_updated_temperature( mut updated_parts: Query<(&mut Sprite, &Temperature), (With, Changed)> ) { for (mut sprite, temperature) in updated_parts.iter_mut() { - sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0); + sprite.color = temp_to_color(temperature); } } + fn handle_updated_parts( mut commands: Commands, updated_parts: Query<(Entity, &Part, &Temperature, Option<&PartInShip>), Changed>, asset_server: Res, ) { for (updated_entity, updated_part, temperature, is_connected) in updated_parts.iter() { - let mut sprite = Sprite::from_image(asset_server.load(if is_connected.is_some() { - &updated_part.strong_config.part.sprite_connected - } else { - &updated_part.strong_config.part.sprite_disconnected - })); - sprite.custom_size = Some(Vec2::new( - updated_part.strong_config.physics.width as f32, - updated_part.strong_config.physics.height as f32, - )); - sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0); - + let sprite = build_part_sprite(updated_part, temperature, is_connected.is_some(), &asset_server); commands .entity(updated_entity) .remove::() @@ -95,37 +96,23 @@ fn update_part_sprites( let Ok((part, temperature, connected_to)) = parts.get(e) else { continue; }; - - let sprite = if connected_to.is_some() { - &part.strong_config.part.sprite_connected - } else { - &part.strong_config.part.sprite_disconnected - }; - - let mut sprite = Sprite::from_image(asset_server.load(sprite)); - sprite.custom_size = Some(Vec2::new( - part.strong_config.physics.width as f32, - part.strong_config.physics.height as f32, - )); - sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0); - //sprite.color = Color::srgb(1.0, (700.0 - temperature.0) / 700.0, (700.0 - temperature.0) / 700.0); - + let sprite = build_part_sprite(part, temperature, connected_to.is_some(), &asset_server); commands.entity(e).insert(sprite); } } #[derive(Resource)] -struct DragResource(Option); +struct DragResource { dragged: Option } #[derive(Resource)] -struct SnapResource(Option, Option); +struct SnapResource { target: Option, peer: Option } #[derive(Component)] struct DragGhost; #[derive(Component)] struct Ghost { pub rot: Quat, - pub last_target_pos: Vec3, - pub vel: Vec3 + pub spring_pos: Vec3, + pub spring_vel: Vec3, } const ROTATION_SMOOTH: f32 = 0.1; @@ -157,11 +144,11 @@ fn on_part_click( s.color = Color::srgba(0.7, 0.7, 0.7, 1.0); commands.spawn((DragGhost, Ghost { rot: sprite.1.rotation, - last_target_pos: sprite.1.translation, - vel: Vec3::ZERO, + spring_pos: sprite.1.translation, + spring_vel: Vec3::ZERO, }, *sprite.1, s)); - drag.0 = Some(ev.event().event_target()); + drag.dragged = Some(ev.event().event_target()); } fn on_part_release( @@ -177,7 +164,7 @@ fn on_part_release( return; } - if let Some(e) = drag.0 { + if let Some(e) = drag.dragged { let rotation = ghost.1.rotation; commands.entity(ghost.0).despawn(); @@ -185,15 +172,15 @@ fn on_part_release( debug!(?e, ?c, "sending drag request"); events.write(DragRequestEvent { drag_target: e, - drag_to: c, - set_rotation: rotation, - snap_target: snap.0, - peer_snap: snap.1, + action: match (snap.target, snap.peer) { + (Some(snap_target), Some(peer_snap)) => DragAction::Attach { snap_target, peer_snap }, + _ => DragAction::Free { position: c, rotation }, + }, }); } } - drag.0 = None; + drag.dragged = None; } const F: f32 = 6.0; // frequency (Hz) @@ -291,7 +278,7 @@ fn update_drag_ghosts( let mut best_self_position = None; for (snap_local_transform, snap_joint, snap_part, snap_id) in &snaps { - if Some(snap_part.0) == drag.0 { + if Some(snap_part.0) == drag.dragged { continue; } @@ -355,7 +342,7 @@ fn update_drag_ghosts( let mut best_peer_target_pos = None; let mut best_joint_transform = None; for (our_snap_local_transform, our_snap_joint, our_snap_part, our_snap_id) in &snaps { - if Some(our_snap_part.0) != drag.0 { + if Some(our_snap_part.0) != drag.dragged { continue; } let our_snap_global_translation = @@ -412,23 +399,22 @@ fn update_drag_ghosts( ghost_info.rot = best_target.rotation; ghost.rotation = ghost.rotation.slerp(ghost_info.rot, ROTATION_SMOOTH); - let target_vel = (best_target.translation - ghost_info.last_target_pos) - /time.delta_secs(); + let target_vel = (best_target.translation - ghost_info.spring_pos) / time.delta_secs(); //let a = (best_target.translation - ghost.translation // + K3*target_vel - K1*ghost_info.vel)/K2; - let (r_k1, v_k1, r_k2, v_k2, r_k3, v_k3, r_k4, v_k4) = rk4_k_values(best_target.translation, target_vel, ghost.translation, ghost_info.vel, time.delta_secs()); + let (r_k1, v_k1, r_k2, v_k2, r_k3, v_k3, r_k4, v_k4) = rk4_k_values(best_target.translation, target_vel, ghost.translation, ghost_info.spring_vel, time.delta_secs()); ghost.translation += (r_k1 + 2.0*r_k2 + 2.0*r_k3 + r_k4)/6.0*time.delta_secs(); - ghost_info.vel += (v_k1 + 2.0*v_k2 + 2.0*v_k3 + v_k4)/6.0*time.delta_secs(); + ghost_info.spring_vel += (v_k1 + 2.0*v_k2 + 2.0*v_k3 + v_k4)/6.0*time.delta_secs(); - ghost_info.last_target_pos = best_target.translation; + ghost_info.spring_pos = best_target.translation; let partvel = linvel.single().unwrap(); ghost.translation.x += partvel.x as f32 * time.delta_secs(); ghost.translation.y += partvel.y as f32 * time.delta_secs(); - rsnap.0 = snap; - rsnap.1 = best_self_snap; + rsnap.target = snap; + rsnap.peer = best_self_snap; } diff --git a/crates/unified/src/client/rendering/mod.rs b/crates/unified/src/client/rendering/mod.rs index baa6caef71c307c55c7b918f6cd02890625eb518..b9fdbaf5ee36e52a37b83f86bfafef36aea02bcd 100644 --- a/crates/unified/src/client/rendering/mod.rs +++ b/crates/unified/src/client/rendering/mod.rs @@ -2,7 +2,8 @@ use bevy::anti_alias::fxaa::Fxaa; use bevy::app::{App, Startup}; use bevy::core_pipeline::tonemapping::DebandDither; use bevy::post_process::bloom::Bloom; -use crate::client::components::{MainCamera, Me, StarguideGizmos}; +use crate::client::components::{MainCamera, Me}; +use crate::client::starguide::components::StarguideGizmos; use crate::shared::ecs::{GameplayState, MAIN_LAYER, STARGUIDE_LAYER}; use crate::prelude::*; diff --git a/crates/unified/src/client/ship/thrusters.rs b/crates/unified/src/client/ship/thrusters.rs index bf1ea127c1d8e842ed47061bd0800339ee4b647f..6679c8130f88a49ab9a524802a9419898fb8c7a0 100644 --- a/crates/unified/src/client/ship/thrusters.rs +++ b/crates/unified/src/client/ship/thrusters.rs @@ -55,6 +55,9 @@ fn draw_thruster_debug( } } +const THRUSTER_CONTRIBUTION_EPSILON: f32 = 0.1; +const THRUST_ACTIVATION_THRESHOLD: f64 = 0.8; + // TODO(core): replace goodlp with calling clarabel directly and cache models /// The thrust solver! /// This is an annoyingly complicated function... @@ -203,7 +206,7 @@ fn solve_thrust( // Although all the numbers going in were normalized, the torque output is in different // units and is wacky. Re-normalize it to a set of expected values, since this is all // direction based anyway. - let renormalized_thruster_torque = if thruster_torque.abs() < 0.1 { + let renormalized_thruster_torque = if thruster_torque.abs() < THRUSTER_CONTRIBUTION_EPSILON { 0.0 // This thruster's effect is small enough to be ignored } else if thruster_torque < 0.0 { -1.0 // if it's negative, force to -1 @@ -234,7 +237,7 @@ fn solve_thrust( // calculate thrust ~~and torque~~ values /* - Consult the paper for more information. + Consult the paper for more information. (https://typst.app/project/rGBlHvhGRntdfxOjzEL8EO) Recall that we're optimizing an equation of form i_0 * x_0 + i_1 * x_1 + i_2 * x_2 ... i_n * x_n "Coefficients" are i_0 ... i_n, and can be precomputed, and x_0 ... x_n is the "decision variables" @@ -261,7 +264,7 @@ fn solve_thrust( // if thrust coefficient is <0.1, zap it entirely (this thruster is not helping) // This is done elsewhere for torque, so pass it (u.1) through unchanged // TODO(core): figure out how to make this adjustable - if u.0.abs() < 0.1 { + if u.0.abs() < THRUSTER_CONTRIBUTION_EPSILON { (0.0, u.1) } else { (u.0, u.1) @@ -319,17 +322,13 @@ fn solve_thrust( } }; - // did the solution converge? - match thrust_solution.status() { - SolutionStatus::Optimal => {}, // yay! - SolutionStatus::TimeLimit => { - warn!("thrust solver failed to converge, hit time limit"); - } - SolutionStatus::GapLimit => { - warn!("thrust solver failed to converge, hit gap limit"); - } - } + let check_convergence = |status, label: &str| match status { + SolutionStatus::Optimal => {}, + SolutionStatus::TimeLimit => warn!("{label} solver failed to converge, hit time limit"), + SolutionStatus::GapLimit => warn!("{label} solver failed to converge, hit gap limit"), + }; + check_convergence(thrust_solution.status(), "thrust"); trace!("finished thrust solve @ {:?}", start.elapsed()); trace!("starting torque solve @ {:?}", start.elapsed()); @@ -342,16 +341,7 @@ fn solve_thrust( } }; - // did the solution converge? - match torque_solution.status() { - SolutionStatus::Optimal => {}, // yay! - SolutionStatus::TimeLimit => { - warn!("torque solver failed to converge, hit time limit"); - } - SolutionStatus::GapLimit => { - warn!("torque solver failed to converge, hit gap limit"); - } - } + check_convergence(torque_solution.status(), "torque"); trace!("finished torque solve @ {:?}ms", start.elapsed()); @@ -371,7 +361,7 @@ fn solve_thrust( // is above 80%. // The solver seems to be picking 0.0 or 1.0 in all circumstances anyway, but just in case. - if thrust_solution.value(variables[thruster.0].0.1) > 0.8 || torque_solution.value(variables[thruster.0].1.1) > 0.8 { + if thrust_solution.value(variables[thruster.0].0.1) > THRUST_ACTIVATION_THRESHOLD || torque_solution.value(variables[thruster.0].1.1) > THRUST_ACTIVATION_THRESHOLD { new_soln.thrusters_on.insert(*thruster.1.0); } } diff --git a/crates/unified/src/client/starfield.rs b/crates/unified/src/client/starfield.rs index 45a7eef645c1e83d76ce3a0b26047f0fc6354db2..24fdd51bdf6419b5648ecc6e9bfcc20f3dd5dcdc 100644 --- a/crates/unified/src/client/starfield.rs +++ b/crates/unified/src/client/starfield.rs @@ -15,9 +15,10 @@ use bevy::{ transform::components::Transform, window::{Window, WindowResized}, }; -use crate::client::components::{MainCamera, StarfieldBack, StarfieldFront, StarfieldMid}; +use crate::client::components::MainCamera; use crate::shared::ecs::MAIN_LAYER; use crate::client::components::Me; +use crate::client::starguide::components::{StarfieldBack, StarfieldFront, StarfieldMid}; pub const BACK_STARFIELD_SIZE: f32 = 256.0; pub const MID_STARFIELD_SIZE: f32 = 384.0; diff --git a/crates/unified/src/client/starguide/components.rs b/crates/unified/src/client/starguide/components.rs new file mode 100644 index 0000000000000000000000000000000000000000..557f31f7b28a3020edbb2d633fe88b391b757aa3 --- /dev/null +++ b/crates/unified/src/client/starguide/components.rs @@ -0,0 +1,25 @@ +use crate::prelude::{Component, GizmoConfigGroup, Handle, Image, Reflect, Resource}; + +#[derive(Component)] +pub struct StarguideMe; + +#[derive(Resource)] +pub struct StarguideOrbitImage(pub Handle); + +#[derive(Component)] +pub struct StarguideOrbit; + +#[derive(Component)] +pub struct StarguideCamera; + +#[derive(Default, Reflect, GizmoConfigGroup)] +pub struct StarguideGizmos; + +#[derive(Component)] +pub struct StarfieldFront; + +#[derive(Component)] +pub struct StarfieldMid; + +#[derive(Component)] +pub struct StarfieldBack; \ No newline at end of file diff --git a/crates/unified/src/client/starguide/init.rs b/crates/unified/src/client/starguide/init.rs index 2961e33ec566c6a08edfc2bc8fbf15979e827be2..39b515e973e0979672aeca62e9ced056c1ca9cbf 100644 --- a/crates/unified/src/client/starguide/init.rs +++ b/crates/unified/src/client/starguide/init.rs @@ -1,7 +1,8 @@ use bevy::anti_alias::fxaa::Fxaa; use bevy::core_pipeline::tonemapping::DebandDither; use bevy::post_process::bloom::Bloom; -use crate::client::components::{Me, StarguideCamera, StarguideMe, StarguideOrbit}; +use crate::client::components::Me; +use crate::client::starguide::components::{StarguideCamera, StarguideMe, StarguideOrbit}; use crate::prelude::*; use crate::shared::ecs::{Part, STARGUIDE_LAYER}; diff --git a/crates/unified/src/client/starguide/input.rs b/crates/unified/src/client/starguide/input.rs index f4f16f0d251796990fec1e5f2cb21a8b3296b3da..8968bcc1ffa66fff792095e7c4f41a0000178cc9 100644 --- a/crates/unified/src/client/starguide/input.rs +++ b/crates/unified/src/client/starguide/input.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::client::input::CursorWorldCoordinates; -use crate::client::components::StarguideCamera; +use crate::client::starguide::components::StarguideCamera; pub fn starguide_input_plugin(app: &mut App) { app diff --git a/crates/unified/src/client/starguide/mod.rs b/crates/unified/src/client/starguide/mod.rs index 1f7565015d8fbb2f3d49b9f2c679cf4d4faf09b9..43ed510e3ead6ef215ee9290a58f9a9c3e63becb 100644 --- a/crates/unified/src/client/starguide/mod.rs +++ b/crates/unified/src/client/starguide/mod.rs @@ -1,3 +1,4 @@ pub mod init; pub mod input; pub mod orbit; +pub mod components; diff --git a/crates/unified/src/client/starguide/orbit.rs b/crates/unified/src/client/starguide/orbit.rs index 593c05a746c2bd565edea802ec61241125e75fdb..0a0dd93b89bdf1eb26f4804e99b8888a9961507b 100644 --- a/crates/unified/src/client/starguide/orbit.rs +++ b/crates/unified/src/client/starguide/orbit.rs @@ -1,5 +1,6 @@ use std::f64::consts::PI; -use crate::client::components::{Me, StarguideCamera, StarguideGizmos}; +use crate::client::components::Me; +use crate::client::starguide::components::{StarguideCamera, StarguideGizmos}; use crate::prelude::*; use crate::shared::config::planet::Planet; use crate::shared::world_config::WorldConfigResource; diff --git a/crates/unified/src/client/zoom.rs b/crates/unified/src/client/zoom.rs index b5c959a72041385246024fb2153aa3fb1e8727b4..5addf10f8ddb19f22a9616b4824a81a964f16c06 100644 --- a/crates/unified/src/client/zoom.rs +++ b/crates/unified/src/client/zoom.rs @@ -2,8 +2,9 @@ use bevy::{ input::mouse::{MouseScrollUnit, MouseWheel}, prelude::*, }; -use crate::client::components::{MainCamera, Me, OrbitCamera, StarfieldBack, StarfieldFront, StarfieldMid, StarguideCamera}; +use crate::client::components::{MainCamera, Me}; use crate::client::starfield::{StarfieldSize, BACK_STARFIELD_SIZE, FRONT_STARFIELD_SIZE, MID_STARFIELD_SIZE}; +use crate::client::starguide::components::{StarfieldBack, StarfieldFront, StarfieldMid, StarguideCamera}; use crate::shared::ecs::GameplayState; pub fn zoom_plugin(app: &mut App) { app.add_systems(Update, on_scroll); @@ -52,7 +53,7 @@ fn on_scroll( /*mut orbit_camera: Single< &mut Camera, ( - With, + Without, Without, Without, @@ -65,7 +66,6 @@ fn on_scroll( (&mut Camera, &mut Projection), ( With, - Without, Without, Without, Without, @@ -92,7 +92,7 @@ fn on_scroll( Without, Without, Without, - Without, + Without, ), >,*/ diff --git a/crates/unified/src/server/components/mod.rs b/crates/unified/src/server/components/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..a8d617e6ddceba7c96165eecbdb26eaa7d2160a1 --- /dev/null +++ b/crates/unified/src/server/components/mod.rs @@ -0,0 +1,4 @@ +use crate::prelude::Component; + +#[derive(Component)] +pub struct PlanetSensor(pub String); \ No newline at end of file diff --git a/crates/unified/src/server/drill.rs b/crates/unified/src/server/drill.rs index 70eb4806362b9d8872caf752f35c77bb03cad6dc..6019506c4d45d54a36f691b04838401a64cb48d0 100644 --- a/crates/unified/src/server/drill.rs +++ b/crates/unified/src/server/drill.rs @@ -1,4 +1,4 @@ -use crate::client::components::PlanetSensor; +use crate::server::components::PlanetSensor; use crate::prelude::*; use crate::shared::attachment::{PartInShip, Parts}; use crate::shared::config::planet::Planet; diff --git a/crates/unified/src/server/mod.rs b/crates/unified/src/server/mod.rs index 232f70f6cd8046059f32a03a79bd4f1604e87dcf..a75b06b9307603ba40f837ca3f0f10731b966dfd 100644 --- a/crates/unified/src/server/mod.rs +++ b/crates/unified/src/server/mod.rs @@ -10,6 +10,7 @@ pub mod player; mod system_sets; pub mod orbit; pub mod plugins; +pub mod components; use crate::server::craft::craft_plugin; use crate::server::damping::damping_plugin; diff --git a/crates/unified/src/server/planets.rs b/crates/unified/src/server/planets.rs index 2e3b25deab1c2bb1dde0134162fafff0d34e2f50..94f4815c07831d9eceb5ccd422e5666c7ce4fd94 100644 --- a/crates/unified/src/server/planets.rs +++ b/crates/unified/src/server/planets.rs @@ -1,4 +1,4 @@ -use crate::client::components::PlanetSensor; +use crate::server::components::PlanetSensor; use bevy::{asset::Handle, math::DVec3}; use crate::prelude::*; use crate::shared::config::planet::{PlanetSpring, PlanetSpringJoint}; diff --git a/crates/unified/src/server/player.rs b/crates/unified/src/server/player.rs index 36e76694f71a4f5cd7cb5b356963c03d29769e24..f48161e502f1d75bfdd6442bb2cb3a86fb643ba3 100644 --- a/crates/unified/src/server/player.rs +++ b/crates/unified/src/server/player.rs @@ -2,7 +2,7 @@ pub mod join; pub mod thrust; use crate::shared::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint}; -use crate::shared::ecs::{DragRequestEvent, Part, Player, PlayerStorage}; +use crate::shared::ecs::{DragAction, DragRequestEvent, Part, Player, PlayerStorage}; use crate::server::damping::ModuleJointDamping; use crate::server::system_sets::PlayerInputSet; use crate::prelude::*; @@ -50,25 +50,24 @@ fn complete_partial_disconnects( // is it still connected to hearty? let mut search_visited_joints = vec![]; - let can_reach_hearty = can_reach_hearty( + let connected = is_connected_to_hearty( partially_disconnected_part, q_joints.reborrow(), q_maybe_peer.reborrow(), q_joint_of_part.reborrow(), q_is_hearty.reborrow(), &mut search_visited_joints, - true ); - if can_reach_hearty { + if connected { // great, leave them alone continue; } - // this part cannot reach hearty - // trigger a disconnect on them to propagate the disconnection + debug!("partial detach DFS: visited {} joints => not connected", search_visited_joints.len()); + // this part cannot reach hearty; propagate the disconnection let mut disconnect_queue = vec![]; commands.entity(partially_disconnected_part).remove::(); // they're no longer in the ship, remove them from the meta - disconnect_part( + propagate_disconnect( (partially_disconnected_part, if let Ok(j) = q_joints.get(partially_disconnected_part) { j } else { warn!(?partially_disconnected_part, "part does not have a Joints? this should be impossible..."); continue; @@ -82,10 +81,10 @@ fn complete_partial_disconnects( } } -/// Determine if a part has a path to hearty by performing a depth-first search +/// Determine if a part is connected to hearty by performing a depth-first search. /// TODO: This can be very slow on large ships- the path propagation model will be significantly faster /// TODO: Ask core for an explanation of what the path propagation model is if you want to implement this -fn can_reach_hearty( +fn is_connected_to_hearty( part: Entity, mut q_joints: Query<&Joints>, @@ -94,8 +93,6 @@ fn can_reach_hearty( mut q_is_hearty: Query>, visited_joints: &mut Vec, - - is_top_of_recursion: bool ) -> bool { // Are we hearty? if let Ok(Some(_)) = q_is_hearty.get(part) { @@ -138,19 +135,16 @@ fn can_reach_hearty( debug!("-> via {:?}", part); return true; } - // not hearty. but can the other part reach hearty? - let can_other_part_reach = can_reach_hearty( + // not hearty, but can the other part reach hearty? + let other_connected = is_connected_to_hearty( other_part, q_joints.reborrow(), q_maybe_peer.reborrow(), q_joint_of_part.reborrow(), q_is_hearty.reborrow(), visited_joints, - false ); - if can_other_part_reach { - // great, they are connected - // log that we're in the path, then bubble up + if other_connected { debug!("-> via {:?}", part); return true; } @@ -158,17 +152,12 @@ fn can_reach_hearty( } } - // Exhausted all options; we are not connected to hearty bubble up - if is_top_of_recursion { - // print a debug message - debug!("partial detach DFS: visited {} joints => not connected", visited_joints.len()); - } false } -fn disconnect_part( +fn propagate_disconnect( (entity, joints): (Entity, &Joints), q_joints: &Query<(&Joint, &JointOf, &Transform, Option<&Peer>, Entity)>, q_only_joints: &Query<&Joints>, @@ -206,7 +195,7 @@ fn disconnect_part( //commands.entity(other_joint_of.0).remove::(); if !processed_peers.contains(&peer.peer_joint_entity_id) { - disconnect_part((other_joint_of.0, joints), q_joints, + propagate_disconnect((other_joint_of.0, joints), q_joints, q_only_joints, processed_peers, commands.reborrow()); } @@ -251,160 +240,138 @@ fn dragging( let mut new_linvel = None; let mut new_angvel = None; - if let Some(snap_to) = event.snap_target - && let Some(peer_snap) = event.peer_snap - { - let Ok(snap_on_target) = snaps.get(snap_to) else { - continue; - }; - let Ok(snap_on_source) = snaps.get(peer_snap) else { - continue; - }; + match &event.action { + DragAction::Attach { snap_target, peer_snap } => { + let Ok((target_snap_part, target_snap_joint)) = snaps.get(*snap_target) else { + continue; + }; + let Ok((source_snap_part, source_snap_joint)) = snaps.get(*peer_snap) else { + continue; + }; - let Ok(target_joint) = joints.get(snap_on_target.1.0) else { - continue; - }; - let Ok(source_joint) = joints.get(snap_on_source.1.0) else { - continue; - }; + let Ok((target_jt, target_jt_of, target_jt_xform, target_jt_peer, target_jt_id)) = joints.get(target_snap_joint.0) else { + continue; + }; + let Ok((source_jt, source_jt_of, _source_jt_xform, source_jt_peer, source_jt_id)) = joints.get(source_snap_joint.0) else { + continue; + }; - // validation step 1: everything must match. if not, ignore the request - if snap_on_target.0.0 != target_joint.1.0 { - warn!( - "drag request: mismatched target entities (potential manipulation?), ignoring" - ); - continue; - } - if snap_on_source.0.0 != source_joint.1.0 { - warn!( - "drag request: mismatched source entities (potential manipulation?), ignoring request" - ); - continue; - } + // validation: snap ownership must agree with joint ownership + if target_snap_part.0 != target_jt_of.0 { + warn!("drag request: mismatched target entities (potential manipulation?), ignoring"); + continue; + } + if source_snap_part.0 != source_jt_of.0 { + warn!("drag request: mismatched source entities (potential manipulation?), ignoring request"); + continue; + } - // we've passed initial validation. - // do not allow drags with the source or destination if they already have a peer (are attached) - if target_joint.3.is_some() { - warn!( - "drag request: cannot attach to a joint that already has a peer, ignoring request" - ); - continue; - } - if source_joint.3.is_some() { - warn!( - "drag request: dragging from a part that is already attached is currently not supported, ignoring request" - ); - continue; - } + // do not allow drags where either joint already has a peer (is attached) + if target_jt_peer.is_some() { + warn!("drag request: cannot attach to a joint that already has a peer, ignoring request"); + continue; + } + if source_jt_peer.is_some() { + warn!("drag request: dragging from a part that is already attached is currently not supported, ignoring request"); + continue; + } - // great, the attachment appears to be valid - // let's make sure this player is allowed to drag onto this part - // getting attached to (hearty) - let target_part = { - let Ok(target_part) = parts.get(target_joint.1.0) else { + // getting attached to (hearty) + let Ok((target_xform, target_in_ship, target_entity, target_linvel, _, target_angvel, _)) = parts.get(target_jt_of.0) else { continue; }; - target_part - }; - // attached (housing) - let source_part = { - let Ok(source_part) = parts.get(source_joint.1.0) else { + // attached (housing) + let Ok((_, _, source_entity, _, source_joints, _, _)) = parts.get(source_jt_of.0) else { continue; }; - source_part - }; - let allowed = target_joint.1.0 == player_hearty_entity - || target_part.1.is_some_and(|u| u.0 == player_hearty_entity); - if !allowed { - warn!("drag request: this player cannot move this part, ignoring request"); - continue; - } - - // great, we have a valid peering request - - let mut processed = vec![source_joint.4]; - disconnect_part( - (source_part.2, source_part.4), - &joints, - &q_joints, - &mut processed, - commands.reborrow(), - ); + let allowed = target_jt_of.0 == player_hearty_entity + || target_in_ship.is_some_and(|u| u.0 == player_hearty_entity); + if !allowed { + warn!("drag request: this player cannot move this part, ignoring request"); + continue; + } + + let mut processed = vec![source_jt_id]; + propagate_disconnect( + (source_entity, source_joints), + &joints, + &q_joints, + &mut processed, + commands.reborrow(), + ); - // create the joint... - let joint = FixedJoint::new(target_part.2, source_part.2) - .with_local_anchor1(target_joint.2.translation.xy().into()) - .with_local_basis1(target_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0 as f64 + PI - - source_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0 as f64) - .with_point_compliance(world_config.part.joint_point_compliance) - .with_angle_compliance(world_config.part.joint_angle_compliance); - let joint_damping = ModuleJointDamping { - distance: world_config.part.joint_distance_damping, - angular: world_config.part.joint_angular_damping, - }; - - let joint_id = commands.spawn((joint, joint_damping)).id(); - - // create the peering component... - commands.entity(source_joint.4).insert(Peer { - peer_joint_entity_id: target_joint.4, - physics_joint: joint_id - }); - commands.entity(target_joint.4).insert(Peer { - peer_joint_entity_id: source_joint.4, - physics_joint: joint_id - }); - - // propagate PartInShip... - - let part_in_ship = if target_joint.1.0 == player_hearty_entity { - PartInShip(player_hearty_entity) - } else { - PartInShip(target_part.1.unwrap().0) // unwrap: checked above (during 'allowed' calculation) - }; - - commands.entity(source_part.2).insert(part_in_ship); - - let target_position = target_part.0.mul_transform(*target_joint.2); - - teleport_to_translation = target_position.translation.xy(); - teleport_to_rotation = target_position.rotation - * source_joint.0.transform.rotation.inverse() - * Quat::from_rotation_z(PI as f32); - new_linvel = Some(*target_part.3); - new_angvel = Some(*target_part.5); - // and we're done! - } 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(); - let mut processed = vec![]; - disconnect_part( - (source_part.2, source_part.4), - &joints, - &q_joints, - &mut processed, - commands.reborrow(), - ); - teleport_to_translation = event.drag_to; - teleport_to_rotation = event.set_rotation; + // create the physics joint + let fixed_joint = FixedJoint::new(target_entity, source_entity) + .with_local_anchor1(target_jt_xform.translation.xy().into()) + .with_local_basis1(target_jt.transform.rotation.to_euler(EulerRot::ZYX).0 as f64 + PI + - source_jt.transform.rotation.to_euler(EulerRot::ZYX).0 as f64) + .with_point_compliance(world_config.part.joint_point_compliance) + .with_angle_compliance(world_config.part.joint_angle_compliance); + let joint_damping = ModuleJointDamping { + distance: world_config.part.joint_distance_damping, + angular: world_config.part.joint_angular_damping, + }; + let joint_id = commands.spawn((fixed_joint, joint_damping)).id(); + + // create the peering components + commands.entity(source_jt_id).insert(Peer { + peer_joint_entity_id: target_jt_id, + physics_joint: joint_id, + }); + commands.entity(target_jt_id).insert(Peer { + peer_joint_entity_id: source_jt_id, + physics_joint: joint_id, + }); + + // propagate PartInShip + let part_in_ship = if target_jt_of.0 == player_hearty_entity { + PartInShip(player_hearty_entity) + } else { + PartInShip(target_in_ship.unwrap().0) // unwrap: checked above (during 'allowed' calculation) + }; + commands.entity(source_entity).insert(part_in_ship); + + let target_position = target_xform.mul_transform(*target_jt_xform); + teleport_to_translation = target_position.translation.xy(); + teleport_to_rotation = target_position.rotation + * source_jt.transform.rotation.inverse() + * Quat::from_rotation_z(PI as f32); + new_linvel = Some(*target_linvel); + new_angvel = Some(*target_angvel); + } + DragAction::Free { position, rotation } => { + warn!("blindly accepting non-attachment request, someone should change this eventually"); + warn!("dragging already attached entities may cause inconsistent behavior!!"); + let Ok((_, _, free_entity, _, free_joints, _, _)) = parts.get(event.drag_target) else { + continue; + }; + let mut processed = vec![]; + propagate_disconnect( + (free_entity, free_joints), + &joints, + &q_joints, + &mut processed, + commands.reborrow(), + ); + teleport_to_translation = *position; + teleport_to_rotation = *rotation; + } } - let mut part = parts.get_mut(event.drag_target).unwrap(); - part.0.translation.x = teleport_to_translation.x; - part.0.translation.y = teleport_to_translation.y; - part.0.rotation = teleport_to_rotation; // client calculates this; no reason to recalculate - if let Some(new_vel) = new_linvel { - *part.3 = new_vel; + let Ok((mut xform, _, _, mut linvel, _, mut angvel, _)) = parts.get_mut(event.drag_target) else { + continue; + }; + xform.translation.x = teleport_to_translation.x; + xform.translation.y = teleport_to_translation.y; + xform.rotation = teleport_to_rotation; // client calculates this; no reason to recalculate + if let Some(vel) = new_linvel { + *linvel = vel; } - if let Some(new_vel) = new_angvel { - *part.5 = new_vel; + if let Some(vel) = new_angvel { + *angvel = vel; } - // ( the math sucks ) } } diff --git a/crates/unified/src/shared/ecs.rs b/crates/unified/src/shared/ecs.rs index 3a9266e1d5d26d015aae3ca18b0d5ef9ac3cf568..ee31953948a28520b600934465799b322bc64e32 100644 --- a/crates/unified/src/shared/ecs.rs +++ b/crates/unified/src/shared/ecs.rs @@ -42,10 +42,13 @@ pub struct Player { #[derive(Message, Debug, Clone)] pub struct DragRequestEvent { pub drag_target: Entity, - pub drag_to: Vec2, - pub set_rotation: Quat, - pub snap_target: Option, - pub peer_snap: Option, + pub action: DragAction, +} + +#[derive(Debug, Clone)] +pub enum DragAction { + Attach { snap_target: Entity, peer_snap: Entity }, + Free { position: Vec2, rotation: Quat }, } #[derive(Component, Serialize, Deserialize, Debug)]