From 67703072d7b27a0a8286e8508119b1c5d970e840 Mon Sep 17 00:00:00 2001
From: yuni <hut@hut.pm>
Date: Sat, 30 Nov 2024 02:49:12 +0100
Subject: [PATCH] implement update_matching_velocity_target()

---
 src/actor.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/cmd.rs   |  2 +-
 src/hud.rs   |  8 +++--
 src/main.rs  |  2 +-
 4 files changed, 92 insertions(+), 6 deletions(-)

diff --git a/src/actor.rs b/src/actor.rs
index 477e802..685eafb 100644
--- a/src/actor.rs
+++ b/src/actor.rs
@@ -79,6 +79,7 @@ impl Plugin for ActorPlugin {
                 handle_input.run_if(in_control).run_if(game_running),
                 handle_collisions.run_if(game_running),
                 handle_damage.run_if(game_running),
+                update_matching_velocity_target.run_if(game_running),
             ),
         );
         app.add_systems(
@@ -86,6 +87,10 @@ impl Plugin for ActorPlugin {
             (handle_vehicle_enter_exit.run_if(game_running),),
         );
         app.add_event::<VehicleEnterExitEvent>();
+        app.insert_resource(MatchingVelocityTargetUpdateTimer(Timer::from_seconds(
+            0.25,
+            TimerMode::Repeating,
+        )));
     }
 }
 
@@ -173,11 +178,13 @@ impl Default for ExperiencesGForce {
 }
 
 #[derive(Component, Default)]
-pub struct MatchingVelocity {
+pub struct MatchingVelocityTarget {
     pub entity: Option<Entity>,
     pub pos: Option<DVec3>,
     pub v: DVec3,
 }
+#[derive(Resource)]
+pub struct MatchingVelocityTargetUpdateTimer(Timer);
 
 #[derive(Component, Default)]
 pub struct WantsAcceleration {
@@ -1221,3 +1228,80 @@ fn handle_atmosphere(
         settings.atmo_drag = None;
     }
 }
+
+pub fn update_matching_velocity_target(
+    time: Res<Time>,
+    mut timer: ResMut<MatchingVelocityTargetUpdateTimer>,
+    jupiter_pos: Res<game::JupiterPos>,
+    mut q_actor: Query<
+        (
+            Entity,
+            &Transform,
+            &Position,
+            &mut LinearVelocity,
+            Option<&mut MatchingVelocityTarget>,
+            Option<&WantsAcceleration>,
+            Option<&hud::IsTargeted>,
+            Option<&PlayerCamera>,
+        ),
+        (Without<visual::IsEffect>, Without<HiddenInsideVehicle>),
+    >,
+) {
+    if timer.0.tick(time.delta()).just_finished() {
+        return;
+    }
+
+    let mut closest_map: HashMap<Entity, DVec3> = HashMap::new();
+
+    for (entity, _, pos, _, matching, _, _, is_player) in &q_actor {
+        // We only care about entities that ever need to match their velocity
+        if matching.is_none() {
+            continue;
+        }
+
+        let mut target_v: Option<DVec3> = None;
+
+        // First, if this is the player, check whether they targeted anything
+        // so we can match velocity to the target.
+        if is_player.is_some() {
+            for (_, _, _, v, _, _, is_target, _) in &q_actor {
+                if is_target.is_some() {
+                    target_v = Some(v.0);
+                    break;
+                }
+            }
+        }
+
+        // If not, simply look for the closest object and match velocity to that.
+        if target_v.is_none() {
+            let mut closest_distance = camera::MAX_DIST_FOR_MATCH_VELOCITY;
+            for (testentity, _, testpos, v, _, _, _, _) in &q_actor {
+                if entity != testentity {
+                    let distance = (pos.0 - testpos.0).length();
+                    if distance < closest_distance {
+                        target_v = Some(v.0);
+                        closest_distance = distance;
+                    }
+                }
+            }
+        }
+
+        // Last resort: Match velocity to the orbital velocity around Jupiter
+        if target_v.is_none() {
+            let relative_pos = pos.0 - jupiter_pos.0;
+            target_v = Some(nature::orbital_velocity(relative_pos, nature::JUPITER_MASS));
+        }
+
+        if let Some(target_v) = target_v {
+            closest_map.insert(entity, target_v);
+        }
+    }
+
+    for (entity, _, _, _, matching, _, _, _) in &mut q_actor {
+        if let Some(mut matching) = matching {
+            if let Some(target_v) = closest_map.get(&entity) {
+                matching.v = *target_v;
+            }
+        }
+    }
+}
diff --git a/src/cmd.rs b/src/cmd.rs
index 151b8b8..ca433fa 100644
--- a/src/cmd.rs
+++ b/src/cmd.rs
@@ -1513,7 +1513,7 @@ fn spawn_entities(
             actor.insert(actor::ExperiencesAtmosphere);
         }
         if state.is_player || state.is_vehicle {
-            actor.insert(MatchingVelocity::default());
+            actor.insert(MatchingVelocityTarget::default());
             // previously used to apply mouse movement to actor rotation, but currently unused
             actor.insert(ExternalTorque::ZERO.with_persistence(false));
         }
diff --git a/src/hud.rs b/src/hud.rs
index 40e3d05..3f7e586 100644
--- a/src/hud.rs
+++ b/src/hud.rs
@@ -1338,7 +1338,10 @@ fn update_target_selectagon(
 
 fn update_motion_marker(
     settings: Res<Settings>,
-    q_player: Query<(&Transform, &LinearVelocity, &MatchingVelocity), With<actor::PlayerCamera>>,
+    q_player: Query<
+        (&Transform, &LinearVelocity, &MatchingVelocityTarget),
+        With<actor::PlayerCamera>,
+    >,
     mut q_retrograde: Query<
         (&mut Transform, &mut Visibility),
         (With<MarkerMotionRetrograde>, Without<actor::PlayerCamera>),
@@ -1350,8 +1353,7 @@ fn update_motion_marker(
                 let dv = player_v.0 - player_mv.v;
                 if dv.length() > 1.0 {
                     *vis = Visibility::Inherited;
-                    trans.translation =
-                        player_trans.translation - (dv.as_vec3().normalize() * 2.0);
+                    trans.translation = player_trans.translation - (dv.as_vec3().normalize() * 2.0);
                     trans.look_at(player_trans.translation, player_trans.up());
                 } else {
                     *vis = Visibility::Hidden;
diff --git a/src/main.rs b/src/main.rs
index f024094..b43204e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,7 +34,7 @@ pub mod prelude {
     pub use crate::{
         actor, audio, camera, chat, cmd, common, game, hud, load, menu, nature, var, visual, world,
     };
-    pub use actor::MatchingVelocity;
+    pub use actor::MatchingVelocityTarget;
     pub use game::Cycle::Next;
     pub use game::Turn::Toggle;
     pub use game::{GameEvent, Turn};