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
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;
/** 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;
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;
}
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));
}
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
@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;",
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;
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) {
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).
}
}