From 7b6093624b8f263477ff81be88d4257a4a40cd26 Mon Sep 17 00:00:00 2001
From: Fluffy Jenkins <31552479+FluffyJenkins@users.noreply.github.com>
Date: Mon, 29 Jan 2024 17:56:20 +0000
Subject: [PATCH] Create Compat(1.19.2): Fixed track outlines and picking
 (#693)

* Fixed track outlines on ships and fixed interaction bug when near curved track and a ship

* Moved around mixins to disable track outline mixins if interactive is installed

* logging
---
 .../ValkyrienCommonMixinConfigPlugin.java     |  10 ++
 .../create/client/MixinTrackBlockOutline.java |  85 ++++++-----
 .../trackOutlines/MixinBigOutlines.java       |  92 ++++++++++++
 .../trackOutlines/MixinTrackBlockOutline.java | 134 ++++++++++++++++++
 .../valkyrienskies-common.mixins.json         |   2 +
 5 files changed, 286 insertions(+), 37 deletions(-)
 create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/trackOutlines/MixinBigOutlines.java
 create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/trackOutlines/MixinTrackBlockOutline.java

diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/ValkyrienCommonMixinConfigPlugin.java b/common/src/main/java/org/valkyrienskies/mod/mixin/ValkyrienCommonMixinConfigPlugin.java
index 3bc09f14e..ba5ad1b0a 100644
--- a/common/src/main/java/org/valkyrienskies/mod/mixin/ValkyrienCommonMixinConfigPlugin.java
+++ b/common/src/main/java/org/valkyrienskies/mod/mixin/ValkyrienCommonMixinConfigPlugin.java
@@ -7,6 +7,7 @@
 import org.spongepowered.asm.mixin.Mixins;
 import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
 import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
+import org.spongepowered.asm.service.MixinService;
 import org.valkyrienskies.mod.compat.VSRenderer;
 
 /**
@@ -72,6 +73,15 @@ public boolean shouldApplyMixin(final String s, final String mixinClassName) {
         if (mixinClassName.contains("org.valkyrienskies.mod.mixin.feature.render_pathfinding")) {
             return PATH_FINDING_DEBUG;
         }
+        if (mixinClassName.contains("org.valkyrienskies.mod.mixin.mod_compat.create.client.trackOutlines")) {
+            //interactive has its own track outline stuff so disable fixed version of VS2's track outline stuff
+            if (classExists("org.valkyrienskies.create_interactive.mixin.client.MixinTrackBlockOutline")) {
+                MixinService.getService().getLogger("mixin")
+                    .info("[VS2] found Interactive, disabling VS2's trackOutline Compat - " +
+                        mixinClassName.substring(mixinClassName.lastIndexOf(".") + 1));
+                return false;
+            }
+        }
 
         return true;
     }
diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/MixinTrackBlockOutline.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/MixinTrackBlockOutline.java
index 69d74d550..0eb6df4d8 100644
--- a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/MixinTrackBlockOutline.java
+++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/MixinTrackBlockOutline.java
@@ -1,82 +1,93 @@
 package org.valkyrienskies.mod.mixin.mod_compat.create.client;
 
 import com.simibubi.create.content.trains.track.TrackBlockOutline;
+import com.simibubi.create.content.trains.track.TrackBlockOutline.BezierPointSelection;
 import com.simibubi.create.foundation.utility.RaycastHelper;
-import java.util.List;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.core.BlockPos;
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.phys.BlockHitResult;
-import net.minecraft.world.phys.HitResult.Type;
 import net.minecraft.world.phys.Vec3;
-import org.joml.Vector3d;
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.Redirect;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+import org.valkyrienskies.core.api.ships.Ship;
 import org.valkyrienskies.mod.common.VSGameUtilsKt;
+import org.valkyrienskies.mod.common.util.VectorConversionsMCKt;
 
 @Mixin(TrackBlockOutline.class)
 public class MixinTrackBlockOutline {
+
+    @Shadow
+    public static BezierPointSelection result;
+    @Unique
+    private static boolean valkyrienskies$toShip = false;
     @Unique
-    private static boolean isShip = false;
+    private static Ship valkyrienskies$ship;
     @Unique
-    private static BlockPos shipBlockPos;
+    private static Vec3 valkyrienskies$originalOrigin;
 
     @Inject(
-            method = "pickCurves",
-            at = @At(
-                    value = "INVOKE",
-                    target = "Lnet/minecraft/client/player/LocalPlayer;getEyePosition(F)Lnet/minecraft/world/phys/Vec3;"
-            ), locals = LocalCapture.CAPTURE_FAILHARD
+        method = "pickCurves",
+        at = @At(
+            value = "INVOKE",
+            target = "Lnet/minecraft/client/player/LocalPlayer;getEyePosition(F)Lnet/minecraft/world/phys/Vec3;"
+        ), locals = LocalCapture.CAPTURE_FAILHARD
     )
     private static void stuff(final CallbackInfo ci, final Minecraft mc) {
-        if (mc.hitResult != null && mc.level != null && mc.hitResult.getType() == Type.BLOCK) {
-            shipBlockPos = ((BlockHitResult) mc.hitResult).getBlockPos();
-
-            final List<Vector3d>
-                    ships = VSGameUtilsKt.transformToNearbyShipsAndWorld(mc.level, shipBlockPos.getX(), shipBlockPos.getY(),
-                    shipBlockPos.getZ(), 10);
-            isShip = !ships.isEmpty();
+        if (mc.hitResult != null && mc.level != null && mc.player != null) {
+            valkyrienskies$toShip = false;
+            final boolean playerOnShip = VSGameUtilsKt.isBlockInShipyard(mc.level, mc.player.getOnPos());
+            final boolean hitResultOnShip =
+                VSGameUtilsKt.isBlockInShipyard(mc.level, ((BlockHitResult) mc.hitResult).getBlockPos());
+            if (playerOnShip && !hitResultOnShip) {
+                valkyrienskies$ship = VSGameUtilsKt.getShipManagingPos(mc.level, mc.player.getOnPos());
+                //if blockstate is air then transform to ship
+                valkyrienskies$toShip = mc.level.getBlockState(BlockPos.containing(mc.hitResult.location)).isAir();
+            } else if (hitResultOnShip) {
+                valkyrienskies$toShip = true;
+                valkyrienskies$ship =
+                    VSGameUtilsKt.getShipManagingPos(mc.level, ((BlockHitResult) mc.hitResult).getBlockPos());
+            }
         }
     }
 
     @Redirect(
-            method = "pickCurves()V",
-            at = @At(
-                    value = "INVOKE",
-                    target = "Lnet/minecraft/client/player/LocalPlayer;getEyePosition(F)Lnet/minecraft/world/phys/Vec3;"
-            )
+        method = "pickCurves",
+        at = @At(
+            value = "INVOKE",
+            target = "Lnet/minecraft/client/player/LocalPlayer;getEyePosition(F)Lnet/minecraft/world/phys/Vec3;"
+        )
     )
     private static Vec3 redirectedOrigin(final LocalPlayer instance, final float v) {
         final Vec3 eyePos = instance.getEyePosition(v);
-        if (isShip) {
-            final List<Vector3d>
-                    ships = VSGameUtilsKt.transformToNearbyShipsAndWorld(instance.level, eyePos.x, eyePos.y, eyePos.z, 10);
-            if (ships.isEmpty()) {
-                return eyePos;
-            }
-            final Vector3d tempVec = ships.get(0);
-            return new Vec3(tempVec.x, tempVec.y, tempVec.z);
+        if (valkyrienskies$toShip) {
+            valkyrienskies$originalOrigin = eyePos;
+            return VectorConversionsMCKt.toMinecraft(
+                valkyrienskies$ship.getWorldToShip().transformPosition(VectorConversionsMCKt.toJOML(eyePos)));
         } else {
             return eyePos;
         }
     }
 
     @Redirect(
-            method = "pickCurves()V",
-            at = @At(
-                    value = "INVOKE",
-                    target = "Lcom/simibubi/create/foundation/utility/RaycastHelper;getTraceTarget(Lnet/minecraft/world/entity/player/Player;DLnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;"
-            )
+        method = "pickCurves",
+        at = @At(
+            value = "INVOKE",
+            target = "Lcom/simibubi/create/foundation/utility/RaycastHelper;getTraceTarget(Lnet/minecraft/world/entity/player/Player;DLnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;"
+        )
     )
     private static Vec3 redirectedTarget(final Player playerIn, final double range, final Vec3 origin) {
-        if (isShip) {
-            return new Vec3(shipBlockPos.getX(), shipBlockPos.getY(), shipBlockPos.getZ());
+        if (valkyrienskies$toShip) {
+            return VectorConversionsMCKt.toMinecraft(
+                valkyrienskies$ship.getWorldToShip().transformPosition(VectorConversionsMCKt.toJOML(
+                    RaycastHelper.getTraceTarget(playerIn, range, valkyrienskies$originalOrigin))));
         } else {
             return RaycastHelper.getTraceTarget(playerIn, range, origin);
         }
diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/trackOutlines/MixinBigOutlines.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/trackOutlines/MixinBigOutlines.java
new file mode 100644
index 000000000..94ffa3406
--- /dev/null
+++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/trackOutlines/MixinBigOutlines.java
@@ -0,0 +1,92 @@
+package org.valkyrienskies.mod.mixin.mod_compat.create.client.trackOutlines;
+
+import com.simibubi.create.foundation.block.BigOutlines;
+import com.simibubi.create.foundation.utility.RaycastHelper;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.HitResult.Type;
+import net.minecraft.world.phys.Vec3;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.Redirect;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+import org.valkyrienskies.core.api.ships.Ship;
+import org.valkyrienskies.mod.common.VSGameUtilsKt;
+import org.valkyrienskies.mod.common.util.VectorConversionsMCKt;
+
+@Mixin(BigOutlines.class)
+public class MixinBigOutlines {
+    @Unique
+    private static boolean valkyrienskies$toShip = false;
+
+    @Unique
+    private static Ship valkyrienskies$ship;
+    @Unique
+    private static Vec3 valkyrienskies$originalOrigin;
+
+    @Inject(
+        method = "pick",
+        at = @At(
+            value = "INVOKE",
+            target = "Lnet/minecraft/client/player/LocalPlayer;getEyePosition(F)Lnet/minecraft/world/phys/Vec3;"
+        ), locals = LocalCapture.CAPTURE_FAILHARD
+    )
+    private static void stuff(final CallbackInfo ci, final Minecraft mc) {
+        if (mc.hitResult != null && mc.level != null && mc.player != null && mc.hitResult.getType() == Type.BLOCK) {
+            valkyrienskies$toShip = false;
+            final boolean playerOnShip = VSGameUtilsKt.isBlockInShipyard(mc.level, mc.player.getOnPos());
+            final boolean hitResultOnShip =
+                VSGameUtilsKt.isBlockInShipyard(mc.level, ((BlockHitResult) mc.hitResult).getBlockPos());
+            if (playerOnShip && !hitResultOnShip) {
+                valkyrienskies$ship = VSGameUtilsKt.getShipManagingPos(mc.level, mc.player.getOnPos());
+                //if blockstate is air then transform to ship
+                valkyrienskies$toShip = mc.level.getBlockState(BlockPos.containing(mc.hitResult.location)).isAir();
+            } else if (hitResultOnShip) {
+                valkyrienskies$toShip = true;
+                valkyrienskies$ship =
+                    VSGameUtilsKt.getShipManagingPos(mc.level, ((BlockHitResult) mc.hitResult).getBlockPos());
+            }
+        }
+    }
+
+    @Redirect(
+        method = "pick",
+        at = @At(
+            value = "INVOKE",
+            target = "Lnet/minecraft/client/player/LocalPlayer;getEyePosition(F)Lnet/minecraft/world/phys/Vec3;"
+        )
+    )
+    private static Vec3 redirectedOrigin(final LocalPlayer instance, final float v) {
+        final Vec3 eyePos = instance.getEyePosition(v);
+        if (valkyrienskies$toShip) {
+            valkyrienskies$originalOrigin = eyePos;
+            return VectorConversionsMCKt.toMinecraft(
+                valkyrienskies$ship.getWorldToShip().transformPosition(VectorConversionsMCKt.toJOML(eyePos)));
+        } else {
+            return eyePos;
+        }
+    }
+
+    @Redirect(
+        method = "pick",
+        at = @At(
+            value = "INVOKE",
+            target = "Lcom/simibubi/create/foundation/utility/RaycastHelper;getTraceTarget(Lnet/minecraft/world/entity/player/Player;DLnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;"
+        )
+    )
+    private static Vec3 redirectedTarget(final Player playerIn, final double range, final Vec3 origin) {
+        if (valkyrienskies$toShip) {
+            return VectorConversionsMCKt.toMinecraft(
+                valkyrienskies$ship.getWorldToShip().transformPosition(VectorConversionsMCKt.toJOML(
+                    RaycastHelper.getTraceTarget(playerIn, range, valkyrienskies$originalOrigin))));
+        } else {
+            return RaycastHelper.getTraceTarget(playerIn, range, origin);
+        }
+    }
+}
diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/trackOutlines/MixinTrackBlockOutline.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/trackOutlines/MixinTrackBlockOutline.java
new file mode 100644
index 000000000..1b8fbad96
--- /dev/null
+++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/client/trackOutlines/MixinTrackBlockOutline.java
@@ -0,0 +1,134 @@
+package org.valkyrienskies.mod.mixin.mod_compat.create.client.trackOutlines;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import com.simibubi.create.content.trains.track.TrackBlockOutline;
+import com.simibubi.create.content.trains.track.TrackBlockOutline.BezierPointSelection;
+import net.minecraft.client.Camera;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.LevelRenderer;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.HitResult;
+import net.minecraft.world.phys.Vec3;
+import org.joml.Quaterniond;
+import org.joml.Vector3d;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.ModifyArg;
+import org.spongepowered.asm.mixin.injection.Redirect;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+import org.valkyrienskies.core.api.ships.Ship;
+import org.valkyrienskies.core.impl.game.ships.ShipObjectClient;
+import org.valkyrienskies.mod.common.VSClientGameUtils;
+import org.valkyrienskies.mod.common.VSGameUtilsKt;
+import org.valkyrienskies.mod.common.util.VectorConversionsMCKt;
+
+@Mixin(TrackBlockOutline.class)
+public class MixinTrackBlockOutline {
+    @Unique
+    private static Vec3 valkyrienskies$cameraVec3;
+    @Unique
+    private static Vec3 valkyrienskies$vec;
+    @Unique
+    private static Vec3 valkyrienskies$angles;
+
+    @Inject(method = "drawCurveSelection",
+        at = @At(value = "INVOKE",
+            target = "Lcom/simibubi/create/content/trains/track/TrackBlockOutline$BezierPointSelection;angles()Lnet/minecraft/world/phys/Vec3;"),
+        locals = LocalCapture.CAPTURE_FAILHARD)
+    private static void harvestDrawCurveSelection(final PoseStack ms, final MultiBufferSource buffer, final Vec3 camera,
+        final CallbackInfo ci, final Minecraft mc,
+        final BezierPointSelection result, final VertexConsumer vb, final Vec3 vec) {
+        valkyrienskies$cameraVec3 = camera;
+        valkyrienskies$vec = result.vec();
+        valkyrienskies$angles = result.angles();
+    }
+    @ModifyArg(method = "drawCurveSelection",
+        at = @At(value = "INVOKE",
+            target = "Lcom/simibubi/create/content/trains/track/TrackBlockOutline;renderShape(Lnet/minecraft/world/phys/shapes/VoxelShape;Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Ljava/lang/Boolean;)V"),
+        index = 1)
+    private static PoseStack redirectTransformStackTranslate(final PoseStack ms) {
+
+        final Level level = Minecraft.getInstance().level;
+        if (level != null && valkyrienskies$vec != null) {
+            final ShipObjectClient ship;
+            if ((ship = (ShipObjectClient) VSGameUtilsKt.getShipManagingPos(level, valkyrienskies$vec)) != null) {
+                final Quaterniond rotation = new Quaterniond().identity();
+                final Quaterniond yawQuat = new Quaterniond().rotateY(valkyrienskies$angles.y);
+                final Quaterniond pitchQuat = new Quaterniond().rotateX(valkyrienskies$angles.x);
+
+                yawQuat.mul(pitchQuat, rotation);
+                ship.getRenderTransform().getShipToWorldRotation().mul(rotation, rotation);
+
+                final Vector3d worldVec = ship.getRenderTransform().getShipToWorld()
+                    .transformPosition(
+                        new Vector3d(valkyrienskies$vec.x, valkyrienskies$vec.y + .125, valkyrienskies$vec.z),
+                        new Vector3d());
+
+                ms.popPose();
+                ms.pushPose();
+                ms.translate(worldVec.x - valkyrienskies$cameraVec3.x,
+                    worldVec.y - valkyrienskies$cameraVec3.y,
+                    worldVec.z - valkyrienskies$cameraVec3.z);
+                ms.mulPose(VectorConversionsMCKt.toFloat(rotation));
+                ms.translate(-.5, -.125f, -.5);
+            }
+        }
+        return ms;
+    }
+
+    @Unique
+    private static Camera valkyrienskies$info;
+    @Unique
+    private static BlockHitResult valkyrienskies$hitResult;
+
+    @ModifyArg(method = "drawCustomBlockSelection", at = @At(value = "INVOKE",
+        target = "Lnet/minecraft/world/level/border/WorldBorder;isWithinBounds(Lnet/minecraft/core/BlockPos;)Z"))
+    private static BlockPos modIsWithinBounds(final BlockPos blockPos) {
+        final Level level = Minecraft.getInstance().level;
+        if (level != null) {
+            final Ship ship;
+            if ((ship = VSGameUtilsKt.getShipManagingPos(level, blockPos)) != null) {
+                return BlockPos.containing(VectorConversionsMCKt.toMinecraft(ship.getShipToWorld()
+                    .transformPosition(VectorConversionsMCKt.toJOMLD(blockPos))));
+            }
+        }
+        return blockPos;
+    }
+
+    @Inject(method = "drawCustomBlockSelection",
+        at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;translate(DDD)V"))
+    private static void harvest(final LevelRenderer context, final Camera info, final HitResult hitResult,
+        final float partialTicks,
+        final PoseStack ms, final MultiBufferSource buffers, final CallbackInfoReturnable<Boolean> cir) {
+        valkyrienskies$info = info;
+        valkyrienskies$hitResult = (BlockHitResult) hitResult;
+    }
+
+    @Redirect(method = "drawCustomBlockSelection",
+        at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;translate(DDD)V"))
+    private static void redirectTranslate(final PoseStack instance, final double d, final double e, final double f) {
+        final Level level = Minecraft.getInstance().level;
+        if (level != null) {
+            final ShipObjectClient ship;
+            if ((ship = (ShipObjectClient) VSGameUtilsKt.getShipManagingPos(level,
+                valkyrienskies$hitResult.getBlockPos())) != null) {
+                final Vec3 camPos = valkyrienskies$info.getPosition();
+                VSClientGameUtils.transformRenderWithShip(ship.getRenderTransform(), instance,
+                    valkyrienskies$hitResult.getBlockPos(),
+                    camPos.x, camPos.y, camPos.z);
+            } else {
+                instance.translate(d, e, f);
+            }
+        } else {
+            instance.translate(d, e, f);
+        }
+    }
+}
diff --git a/common/src/main/resources/valkyrienskies-common.mixins.json b/common/src/main/resources/valkyrienskies-common.mixins.json
index 24a97da23..925b09556 100644
--- a/common/src/main/resources/valkyrienskies-common.mixins.json
+++ b/common/src/main/resources/valkyrienskies-common.mixins.json
@@ -160,6 +160,8 @@
     "mod_compat.create.client.MixinTileEntityRenderHelper",
     "mod_compat.create.client.MixinTrackBlockOutline",
     "mod_compat.create.client.MixinTrainRelocator",
+    "mod_compat.create.client.trackOutlines.MixinBigOutlines",
+    "mod_compat.create.client.trackOutlines.MixinTrackBlockOutline",
     "mod_compat.create.client.MixinValueBox",
     "mod_compat.flywheel.InstancingEngineAccessor",
     "mod_compat.flywheel.MixinBlockEntityInstanceManager",