Rohan Verma



Commit Diff


commit - 46b086be928761b713c634a5db75fc3fda819e22
commit + e79e68ab0bad1b80f54a5648509db813f4bd9b8d
blob - 430cc322f76a5eb04796c2942241546b38fdc2ac
blob + 7f0e190859d2a5b42b63ab721238ff5e60f05bf1
--- src/main/java/sh/rsap/aeronauticscompat/compat/SableBridge.java
+++ src/main/java/sh/rsap/aeronauticscompat/compat/SableBridge.java
@@ -5,10 +5,13 @@ import net.minecraft.core.Position;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.level.ChunkPos;
 import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.Vec3;
 import org.slf4j.Logger;
 
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -42,6 +45,12 @@ public final class SableBridge {
     /** Reflective handles for the bounding-box accessors. */
     private static volatile Method BB_MIN_X, BB_MIN_Z, BB_MAX_X, BB_MAX_Z;
 
+    /** Reflective handle on {@code ActiveSableCompanion.getAllIntersecting(Level, BoundingBox3dc)}. */
+    private static volatile Method GET_ALL_INTERSECTING_METHOD;
+
+    /** {@code BoundingBox3d(double, double, double, double, double, double)} ctor handle. */
+    private static volatile Constructor<?> BB3D_CTOR;
+
     static {
         Object inst = null;
         Method dist = null;
@@ -116,50 +125,7 @@ public final class SableBridge {
             Object subLevel = GET_TRACKING_OR_VEHICLE_METHOD.invoke(INSTANCE, entity);
             if (subLevel == null) return null;
 
-            // Lazily resolve SubLevel.getPlot() and LevelPlot.getBoundingBox()
-            // off the first non-null sub-level we see — works regardless of
-            // whether Sable's class names change between versions, as long as
-            // the API methods stay the same.
-            Method getPlot = GET_PLOT_METHOD;
-            if (getPlot == null) {
-                getPlot = subLevel.getClass().getMethod("getPlot");
-                getPlot.setAccessible(true);
-                GET_PLOT_METHOD = getPlot;
-            }
-            Object plot = getPlot.invoke(subLevel);
-            if (plot == null) return null;
-
-            Method getBb = GET_PLOT_BOUNDING_BOX_METHOD;
-            if (getBb == null) {
-                getBb = plot.getClass().getMethod("getBoundingBox");
-                getBb.setAccessible(true);
-                GET_PLOT_BOUNDING_BOX_METHOD = getBb;
-            }
-            Object bb = getBb.invoke(plot);
-            if (bb == null) return null;
-
-            // BoundingBox3ic exposes minX/minZ/maxX/maxZ in BLOCK coords.
-            // Resolve once.
-            Method minX = BB_MIN_X, minZ = BB_MIN_Z, maxX = BB_MAX_X, maxZ = BB_MAX_Z;
-            if (minX == null) {
-                minX = bb.getClass().getMethod("minX");
-                minZ = bb.getClass().getMethod("minZ");
-                maxX = bb.getClass().getMethod("maxX");
-                maxZ = bb.getClass().getMethod("maxZ");
-                BB_MIN_X = minX; BB_MIN_Z = minZ; BB_MAX_X = maxX; BB_MAX_Z = maxZ;
-            }
-            int blockMinX = (Integer) minX.invoke(bb);
-            int blockMinZ = (Integer) minZ.invoke(bb);
-            int blockMaxX = (Integer) maxX.invoke(bb);
-            int blockMaxZ = (Integer) maxZ.invoke(bb);
-
-            // Block coords -> chunk coords (>> 4 each)
-            return new int[] {
-                    blockMinX >> 4,
-                    blockMinZ >> 4,
-                    blockMaxX >> 4,
-                    blockMaxZ >> 4
-            };
+            return resolvePlotChunkBoundsForSubLevel(subLevel);
         } catch (Throwable t) {
             return null;
         }
@@ -168,7 +134,7 @@ public final class SableBridge {
     public static List<ChunkPos> getTrackingPlotChunks(Entity entity) {
         int[] bounds = getTrackingPlotChunkBounds(entity);
         if (bounds == null) return Collections.emptyList();
-        java.util.ArrayList<ChunkPos> out = new java.util.ArrayList<>();
+        ArrayList<ChunkPos> out = new ArrayList<>();
         for (int x = bounds[0]; x <= bounds[2]; x++) {
             for (int z = bounds[1]; z <= bounds[3]; z++) {
                 out.add(new ChunkPos(x, z));
@@ -176,4 +142,103 @@ public final class SableBridge {
         }
         return out;
     }
+
+    public static List<ChunkPos> getNearbyPlotChunks(Level level, Vec3 center, double searchRadius) {
+        if (INSTANCE == null) return Collections.emptyList();
+
+        try {
+            Method getAll = GET_ALL_INTERSECTING_METHOD;
+            if (getAll == null) {
+                // Resolve once. The signature is getAllIntersecting(Level, BoundingBox3dc).
+                for (Method m : INSTANCE.getClass().getMethods()) {
+                    if ("getAllIntersecting".equals(m.getName())
+                            && m.getParameterCount() == 2
+                            && m.getParameterTypes()[0] == Level.class) {
+                        getAll = m;
+                        getAll.setAccessible(true);
+                        GET_ALL_INTERSECTING_METHOD = getAll;
+                        break;
+                    }
+                }
+                if (getAll == null) return Collections.emptyList();
+            }
+
+            // Build a BoundingBox3d covering [center - r, center + r].
+            // The interface BoundingBox3dc has a concrete impl BoundingBox3d
+            // in the same package; walk the name to find it.
+            Constructor<?> ctor = BB3D_CTOR;
+            if (ctor == null) {
+                Class<?> bbCls = getAll.getParameterTypes()[1];
+                String name = bbCls.getName();
+                if (name.endsWith("c")) name = name.substring(0, name.length() - 1);
+                Class<?> concrete = Class.forName(name);
+                ctor = concrete.getConstructor(double.class, double.class, double.class,
+                        double.class, double.class, double.class);
+                ctor.setAccessible(true);
+                BB3D_CTOR = ctor;
+            }
+
+            Object bb = ctor.newInstance(
+                    center.x - searchRadius, center.y - searchRadius, center.z - searchRadius,
+                    center.x + searchRadius, center.y + searchRadius, center.z + searchRadius
+            );
+
+            Object iterable = getAll.invoke(INSTANCE, level, bb);
+            if (!(iterable instanceof Iterable<?>)) return Collections.emptyList();
+
+            ArrayList<ChunkPos> out = new ArrayList<>();
+            for (Object subLevel : (Iterable<?>) iterable) {
+                int[] bounds = resolvePlotChunkBoundsForSubLevel(subLevel);
+                if (bounds == null) continue;
+                for (int cx = bounds[0]; cx <= bounds[2]; cx++) {
+                    for (int cz = bounds[1]; cz <= bounds[3]; cz++) {
+                        out.add(new ChunkPos(cx, cz));
+                    }
+                }
+            }
+            return out;
+        } catch (Throwable t) {
+            return Collections.emptyList();
+        }
+    }
+
+    private static int[] resolvePlotChunkBoundsForSubLevel(Object subLevel) throws Exception {
+        Method getPlot = GET_PLOT_METHOD;
+        if (getPlot == null) {
+            getPlot = subLevel.getClass().getMethod("getPlot");
+            getPlot.setAccessible(true);
+            GET_PLOT_METHOD = getPlot;
+        }
+        Object plot = getPlot.invoke(subLevel);
+        if (plot == null) return null;
+
+        Method getBb = GET_PLOT_BOUNDING_BOX_METHOD;
+        if (getBb == null) {
+            getBb = plot.getClass().getMethod("getBoundingBox");
+            getBb.setAccessible(true);
+            GET_PLOT_BOUNDING_BOX_METHOD = getBb;
+        }
+        Object bb = getBb.invoke(plot);
+        if (bb == null) return null;
+
+        Method minX = BB_MIN_X, minZ = BB_MIN_Z, maxX = BB_MAX_X, maxZ = BB_MAX_Z;
+        if (minX == null) {
+            minX = bb.getClass().getMethod("minX");
+            minZ = bb.getClass().getMethod("minZ");
+            maxX = bb.getClass().getMethod("maxX");
+            maxZ = bb.getClass().getMethod("maxZ");
+            BB_MIN_X = minX; BB_MIN_Z = minZ; BB_MAX_X = maxX; BB_MAX_Z = maxZ;
+        }
+        int blockMinX = (Integer) minX.invoke(bb);
+        int blockMinZ = (Integer) minZ.invoke(bb);
+        int blockMaxX = (Integer) maxX.invoke(bb);
+        int blockMaxZ = (Integer) maxZ.invoke(bb);
+
+        return new int[] {
+                blockMinX >> 4,
+                blockMinZ >> 4,
+                blockMaxX >> 4,
+                blockMaxZ >> 4
+        };
+    }
 }
blob - 6b64a0c52a0bdb6f32d7209c4c0e06c6356be9b2
blob + 5669135e9462f9fb43f5eab087e03b64fbdab5f7
--- src/main/java/sh/rsap/aeronauticscompat/mixin/thickair/ThickAirAirQualityHelperMixin.java
+++ src/main/java/sh/rsap/aeronauticscompat/mixin/thickair/ThickAirAirQualityHelperMixin.java
@@ -23,6 +23,7 @@ import java.util.Map;
 @Pseudo
 @Mixin(value = AirQualityHelper.class, remap = false)
 public abstract class ThickAirAirQualityHelperMixin {
+    private static final double SUB_LEVEL_SEARCH_RADIUS = 64.0;
 
     @Inject(
             method = "getAirQualityAtLocation(Lnet/minecraft/world/entity/LivingEntity;)Lcom/leclowndu93150/thick_air/api/AirQualityLevel;",
@@ -30,31 +31,30 @@ public abstract class ThickAirAirQualityHelperMixin {
             cancellable = true,
             remap = false
     )
-    private static void aeronauticscompat$searchContraptionPlotChunks(
+    private static void aeronauticscompat$searchNearbyPlotChunks(
             LivingEntity entity,
             CallbackInfoReturnable<AirQualityLevel> cir) {
 
         if (entity.level().isClientSide) return;
 
-        // Only intervene when the entity is actually tied to a contraption.
-        // Off-contraption players see vanilla Thick Air behavior unchanged.
-        List<ChunkPos> plotChunks = SableBridge.getTrackingPlotChunks(entity);
-        if (plotChunks.isEmpty()) return;
-
         Level level = entity.level();
         Vec3 eyePos = entity.getEyePosition();
 
-        // Compute the world-space result first by recursing into the
-        // (Level, Vec3) overload. This handles "is the player's eye block
-        // itself a fluid / bubble column" plus the world-space chunk scan.
+        // Find every sub-level near the player (not just the one they're on).
+        // Empty when no contraptions are within range — common case, fast path.
+        List<ChunkPos> nearbyPlotChunks =
+                SableBridge.getNearbyPlotChunks(level, eyePos, SUB_LEVEL_SEARCH_RADIUS);
+
+        if (nearbyPlotChunks.isEmpty()) return; // no contraptions nearby; vanilla path is correct
+
+        // Compute the world-space (vanilla Thick Air) result first so we can
+        // merge with sub-level results.
         AirQualityLevel worldResult = AirQualityHelper.getAirQualityAtLocation(level, eyePos);
+        if (worldResult == AirQualityLevel.GREEN) return; // can't be improved upon
 
-        // GREEN is the best possible — short-circuit, no need to scan plot chunks.
-        if (worldResult == AirQualityLevel.GREEN) return;
-
         AirQualityLevel best = worldResult;
 
-        for (ChunkPos cp : plotChunks) {
+        for (ChunkPos cp : nearbyPlotChunks) {
             LevelChunk chunk = level.getChunkSource().getChunkNow(cp.x, cp.z);
             if (chunk == null) continue;
 
@@ -66,23 +66,15 @@ public abstract class ThickAirAirQualityHelperMixin {
                 BlockPos pos = entry.getKey();
                 AirQualityLevel quality = entry.getValue();
                 if (quality == null) continue;
-
-                // Skip if not better than what we already have.
                 if (best != null && !quality.isBetterThan(best)) continue;
 
                 double radius = quality.getAirProviderRadius();
                 double radiusSq = radius * radius;
 
-                // distanceSquaredWithSubLevels projects both endpoints out of
-                // their respective sub-levels before measuring — gives the
-                // correct visual world-space distance even though `pos` is
-                // in plot-space coords (~20M out) and `eyePos` is in
-                // world-space.
                 Vec3 bubbleVec = new Vec3(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
                 double distSq = SableBridge.distanceSquaredWithSubLevels(level, eyePos, bubbleVec);
+                if (Double.isNaN(distSq)) continue;
 
-                if (Double.isNaN(distSq)) continue; // bridge unavailable
-
                 if (distSq < radiusSq) {
                     best = quality;
                     if (best == AirQualityLevel.GREEN) {
@@ -96,7 +88,7 @@ public abstract class ThickAirAirQualityHelperMixin {
         if (best != null && best != worldResult) {
             cir.setReturnValue(best);
         }
-        // else fall through — original method will run, return worldResult-equivalent.
-        // (Note: HEAD inject without setReturnValue means the original body executes.)
+        // Otherwise fall through and let original method run — it will return
+        // worldResult-equivalent (we computed it above just to compare).
     }
 }