From cbdca4b7c12d7fc03077e0eb6a2c725a01727217 Mon Sep 17 00:00:00 2001
From: ErrorNoInternet <errornointernet@envs.net>
Date: Sat, 1 Mar 2025 03:33:35 -0500
Subject: [PATCH] feat(client/movement): support inverse goal

---
 src/lua/client/movement.rs | 54 +++++++++++++++++++++++---------------
 1 file changed, 33 insertions(+), 21 deletions(-)

diff --git a/src/lua/client/movement.rs b/src/lua/client/movement.rs
index 7a09502..90c8960 100644
--- a/src/lua/client/movement.rs
+++ b/src/lua/client/movement.rs
@@ -5,7 +5,7 @@ use azalea::{
     interact::HitResultComponent,
     pathfinder::{
         ExecutingPath, GotoEvent, Pathfinder, PathfinderClientExt,
-        goals::{BlockPosGoal, Goal, RadiusGoal, ReachBlockPosGoal, XZGoal, YGoal},
+        goals::{BlockPosGoal, Goal, InverseGoal, RadiusGoal, ReachBlockPosGoal, XZGoal, YGoal},
     },
     protocol::packets::game::{ServerboundPlayerCommand, s_player_command::Action},
     world::MinecraftEntityId,
@@ -27,36 +27,48 @@ pub async fn go_to(
     client: UserDataRef<Client>,
     (data, metadata): (Value, Option<Table>),
 ) -> Result<()> {
-    fn g(client: &Client, without_mining: bool, goal: impl Goal + Send + Sync + 'static) {
-        if without_mining {
+    fn goto_with_options<G: Goal + Send + Sync + 'static>(
+        client: &Client,
+        options: &Table,
+        goal: G,
+    ) {
+        if options.get("without_mining").unwrap_or_default() {
             client.goto_without_mining(goal);
         } else {
             client.goto(goal);
         }
     }
 
+    fn goto<G: Goal + Send + Sync + 'static>(client: &Client, options: Table, goal: G) {
+        if options.get("inverse").unwrap_or_default() {
+            goto_with_options(client, &options, InverseGoal(goal));
+        } else {
+            goto_with_options(client, &options, goal);
+        }
+    }
+
     let error = mlua::Error::FromLuaConversionError {
         from: data.type_name(),
         to: "Table".to_string(),
         message: None,
     };
-    let (goal_type, without_mining) = metadata
-        .map(|t| {
-            (
-                t.get("type").unwrap_or_default(),
-                t.get("without_mining").unwrap_or_default(),
-            )
-        })
-        .unwrap_or_default();
+    let (goal_type, options) = if let Some(metadata) = metadata {
+        (
+            metadata.get("type")?,
+            metadata.get("options").unwrap_or(lua.create_table()?),
+        )
+    } else {
+        (0, lua.create_table()?)
+    };
 
     #[allow(clippy::cast_possible_truncation)]
     match goal_type {
         1 => {
             let t = data.as_table().ok_or(error)?;
             let p = Vec3::from_lua(t.get("position")?, &lua)?;
-            g(
+            goto(
                 &client,
-                without_mining,
+                options,
                 RadiusGoal {
                     pos: azalea::Vec3::new(p.x, p.y, p.z),
                     radius: t.get("radius")?,
@@ -65,9 +77,9 @@ pub async fn go_to(
         }
         2 => {
             let p = Vec3::from_lua(data, &lua)?;
-            g(
+            goto(
                 &client,
-                without_mining,
+                options,
                 ReachBlockPosGoal {
                     pos: BlockPos::new(p.x as i32, p.y as i32, p.z as i32),
                     chunk_storage: client.world().read().chunks.clone(),
@@ -76,27 +88,27 @@ pub async fn go_to(
         }
         3 => {
             let t = data.as_table().ok_or(error)?;
-            g(
+            goto(
                 &client,
-                without_mining,
+                options,
                 XZGoal {
                     x: t.get("x")?,
                     z: t.get("z")?,
                 },
             );
         }
-        4 => g(
+        4 => goto(
             &client,
-            without_mining,
+            options,
             YGoal {
                 y: data.as_table().ok_or(error)?.get("y")?,
             },
         ),
         _ => {
             let p = Vec3::from_lua(data, &lua)?;
-            g(
+            goto(
                 &client,
-                without_mining,
+                options,
                 BlockPosGoal(BlockPos::new(p.x as i32, p.y as i32, p.z as i32)),
             );
         }