commit 5b39389340654ac191111465e83dcac8eafcbe5b from: Rohan Verma date: Fri Jul 10 13:08:24 2026 UTC add camera + storage drawers support commit - 12b6fd0e76508533048d936c805c36e021ae4a86 commit + 5b39389340654ac191111465e83dcac8eafcbe5b blob - /dev/null blob + c2222ae5e9839f0e389bb21baee9db127ced4f32 (mode 644) Binary files /dev/null and .DS_Store differ blob - ecaf3c0caf6d160e40e3a4cad3453072f2aee257 blob + b38974d9a0ee2dc668a7b8386fecc071ad752f2e --- build.gradle +++ build.gradle @@ -95,6 +95,8 @@ jar { exclude 'me/desht/**' exclude 'net/mehvahdjukaar/**' exclude 'com/cobblemon/**' + exclude 'com/jaquadro/**' + exclude 'com/texelsaurus/**' } var generateModMetadata = tasks.register("generateModMetadata", ProcessResources) { blob - decb1f156e06922e6cc40cd9f16187ec7d775ed8 blob + af248c516b7809421a6dce09c4ebe6e5c0110151 --- gradle.properties +++ gradle.properties @@ -17,7 +17,7 @@ loader_version_range=[1,) # Optional dependencies. All integrations are soft: missing mods are simply skipped. etched_version_range=[5.0.0,6.0.0) -sable_version_range=[1.0.0,) +sable_version_range=[2.0.3,) waterframes_version_range=[2.1.0,3.0.0) citadel_version_range=[2.0.0,3.0.0) @@ -25,7 +25,7 @@ citadel_version_range=[2.0.0,3.0.0) mod_id=aeronauticscompat mod_name=AeronauticsCompat mod_license=MIT -mod_version=1.1.2 +mod_version=1.1.3 mod_authors=rohan mod_description=Compatibility fixes for Create: Aeronautics / Sable sub-levels. Per-mod patches apply only when the target mod is installed. mod_group_id=sh.rsap.aeronauticscompat blob - /dev/null blob + e0fb753522fbb96d5964df700ba1197bb09997f8 (mode 644) --- /dev/null +++ src/main/java/com/jaquadro/minecraft/storagedrawers/util/WorldUtils.java @@ -0,0 +1,14 @@ +package com.jaquadro.minecraft.storagedrawers.util; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; + +public final class WorldUtils { + private WorldUtils() {} + + public static BlockHitResult rayTraceEyes(Level level, Player player, BlockPos blockPos) { + return null; + } +} blob - /dev/null blob + 4426aad1ec61d6f5950cf7a1a7948c7378f147d1 (mode 644) --- /dev/null +++ src/main/java/com/texelsaurus/minecraft/chameleon/util/WorldUtils.java @@ -0,0 +1,14 @@ +package com.texelsaurus.minecraft.chameleon.util; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; + +public final class WorldUtils { + private WorldUtils() {} + + public static BlockHitResult rayTraceEyes(Level level, Player player, BlockPos blockPos) { + return null; + } +} blob - 11bbad628318b2cd789c4b028b825414064c45d1 blob + 494276892609aa2a4e1911b9d6ddb6171e5567dd --- src/main/java/dev/ryanhcode/sable/api/SubLevelAssemblyHelper.java +++ src/main/java/dev/ryanhcode/sable/api/SubLevelAssemblyHelper.java @@ -1,6 +1,21 @@ package dev.ryanhcode.sable.api; +import dev.ryanhcode.sable.companion.math.BoundingBox3ic; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.phys.Vec3; + public final class SubLevelAssemblyHelper { private SubLevelAssemblyHelper() {} + public static void moveBlocks() {} + + public static void moveOtherStuff(ServerLevel level, AssemblyTransform transform, + Iterable blocks, BoundingBox3ic bounds) {} + + public static class AssemblyTransform { + public Vec3 apply(Vec3 pos) { + return pos; + } + } } blob - /dev/null blob + 31f876ea28c65b672567188556f4e311b887e54e (mode 644) --- /dev/null +++ src/main/java/dev/ryanhcode/sable/companion/math/BoundingBox3ic.java @@ -0,0 +1,7 @@ +package dev.ryanhcode.sable.companion.math; + +import net.minecraft.world.phys.AABB; + +public interface BoundingBox3ic { + AABB toAABB(); +} blob - /dev/null blob + 6ff4afc63e4b201ec5b6559a6436f5b160800a62 (mode 644) --- /dev/null +++ src/main/java/sh/rsap/aeronauticscompat/compat/SubLevelRayTrace.java @@ -0,0 +1,57 @@ +package sh.rsap.aeronauticscompat.compat; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +/** + * Replacement for eye-to-block raytraces whose ray length is derived from the + * raw distance between the player and the target block. For a block on a Sable + * sub-level that distance is measured against the far-away plot coordinates, + * producing a multi-million-block ray that {@code Level.clip} walks block by + * block, synchronously loading chunks until the server watchdog kills the + * process. + */ +public final class SubLevelRayTrace { + + /** + * Any genuine interaction happens within interaction range; a target + * further away than this can only be a sub-level plot position. + */ + private static final double NEAR_DISTANCE_SQ = 64 * 64; + + /** Upper bound on the replacement ray, well above any interaction range. */ + private static final double MAX_RAY_LENGTH = 64; + + private SubLevelRayTrace() {} + + /** + * Returns a clip result whose ray length is computed through Sable's + * sub-level aware distance, or null when the target block is nearby and + * the caller's own raytrace is safe to run unchanged. + * + *

Sable's {@code BlockGetter.clip} overwrite projects the short ray + * into any sub-level it crosses and returns the hit in plot coordinates, + * so callers comparing the hit pos against the plot-space block pos keep + * working. + */ + public static BlockHitResult rayTraceEyesBounded(Level level, Player player, BlockPos pos) { + Vec3 eyes = player.getEyePosition(1F); + Vec3 center = Vec3.atCenterOf(pos); + if (eyes.distanceToSqr(center) <= NEAR_DISTANCE_SQ) { + return null; + } + + double apparentSq = SableBridge.distanceSquaredWithSubLevels(level, eyes, center); + double length = Double.isNaN(apparentSq) + ? MAX_RAY_LENGTH + : Math.min(Math.sqrt(apparentSq), MAX_RAY_LENGTH); + + Vec3 end = eyes.add(player.getViewVector(1F).scale(length + 1)); + return level.clip(new ClipContext(eyes, end, + ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, player)); + } +} blob - 5dafc0ede48fbe660bd393799047a007478f8558 blob + a0b2375eda38dd9d7f1b46913fd1a1bb95a3a0a4 --- src/main/java/sh/rsap/aeronauticscompat/mixin/AeronauticsCompatMixinPlugin.java +++ src/main/java/sh/rsap/aeronauticscompat/mixin/AeronauticsCompatMixinPlugin.java @@ -19,6 +19,9 @@ public class AeronauticsCompatMixinPlugin implements I private static final String SIMULATED = "dev.simulated_team.simulated.util.SimAssemblyHelper"; private static final String PNEUMATIC_CRAFT = "me.desht.pneumaticcraft.common.block.entity.utility.SentryTurretBlockEntity"; private static final String COBBLEMON = "com.cobblemon.mod.common.api.storage.pc.link.ProximityPCLink"; + private static final String STORAGE_DRAWERS = "com.jaquadro.minecraft.storagedrawers.util.WorldUtils"; + private static final String CHAMELEON = "com.texelsaurus.minecraft.chameleon.util.WorldUtils"; + private static final String CAMERA = "de.maxhenkel.camera.entities.ImageEntity"; /** Per-mixin marker-class requirements (all must resolve to apply). */ private static final Map REQUIREMENTS = Map.ofEntries( @@ -49,7 +52,13 @@ public class AeronauticsCompatMixinPlugin implements I Map.entry("sh.rsap.aeronauticscompat.mixin.cobblemon.PastureLinkManagerMixin", new String[]{SABLE, COBBLEMON}), Map.entry("sh.rsap.aeronauticscompat.mixin.cobblemon.PokemonEntityRidingMixin", - new String[]{SABLE, COBBLEMON}) + new String[]{SABLE, COBBLEMON}), + Map.entry("sh.rsap.aeronauticscompat.mixin.storagedrawers.StorageDrawersRayTraceMixin", + new String[]{SABLE, STORAGE_DRAWERS}), + Map.entry("sh.rsap.aeronauticscompat.mixin.storagedrawers.ChameleonRayTraceMixin", + new String[]{SABLE, CHAMELEON}), + Map.entry("sh.rsap.aeronauticscompat.mixin.camera.CameraFrameAssemblyMixin", + new String[]{SABLE, CAMERA}) ); @Override public void onLoad(String mixinPackage) { @@ -63,7 +72,10 @@ public class AeronauticsCompatMixinPlugin implements I + " bnb=" + resolves(BITS_N_BOBS, cl) + " simulated=" + resolves(SIMULATED, cl) + " pneumaticcraft=" + resolves(PNEUMATIC_CRAFT, cl) - + " cobblemon=" + resolves(COBBLEMON, cl)); + + " cobblemon=" + resolves(COBBLEMON, cl) + + " storagedrawers=" + resolves(STORAGE_DRAWERS, cl) + + " chameleon=" + resolves(CHAMELEON, cl) + + " camera=" + resolves(CAMERA, cl)); } @Override blob - /dev/null blob + f7e09a4fce44046d8afe7dd02b71dae5159eda93 (mode 644) --- /dev/null +++ src/main/java/sh/rsap/aeronauticscompat/mixin/camera/CameraFrameAssemblyMixin.java @@ -0,0 +1,63 @@ +package sh.rsap.aeronauticscompat.mixin.camera; + +import dev.ryanhcode.sable.api.SubLevelAssemblyHelper; +import dev.ryanhcode.sable.companion.math.BoundingBox3ic; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.AABB; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +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.callback.CallbackInfo; + +import java.util.HashSet; +import java.util.Set; + +/** + * Sable's moveOtherStuff only carries HangingEntity subclasses into the + * sub-level during assembly. Camera Mod's image frame extends Entity + * directly, so frames placed on an unassembled structure get left behind + * (and then self-destruct once their wall is gone). This appends the same + * treatment for frames attached to assembled blocks. Frames are matched by + * entity-type id so no Camera Mod classes are referenced. + */ +@Pseudo +@Mixin(value = SubLevelAssemblyHelper.class, remap = false) +public abstract class CameraFrameAssemblyMixin { + + @Unique + private static final ResourceLocation AERONAUTICSCOMPAT$IMAGE_FRAME = + ResourceLocation.fromNamespaceAndPath("camera", "image_frame"); + + @Inject(method = "moveOtherStuff", at = @At("TAIL"), remap = false) + private static void aeronauticscompat$moveImageFrames( + ServerLevel level, SubLevelAssemblyHelper.AssemblyTransform transform, + Iterable blocks, BoundingBox3ic bounds, + CallbackInfo ci + ) { + Set assembled = new HashSet<>(); + for (BlockPos pos : blocks) { + assembled.add(pos.immutable()); + } + if (assembled.isEmpty()) return; + + for (Entity entity : level.getEntitiesOfClass(Entity.class, bounds.toAABB().inflate(2.0))) { + if (!AERONAUTICSCOMPAT$IMAGE_FRAME.equals(BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()))) { + continue; + } + // The frame's box sits flush against its supporting wall; a small + // inflation reaches the wall blocks, mirroring what Sable checks + // via HangingEntity.calculateSupportBox for paintings. + AABB support = entity.getBoundingBox().inflate(2.0 / 16.0); + boolean attached = BlockPos.betweenClosedStream(support).anyMatch(assembled::contains); + if (attached) { + entity.setPos(transform.apply(entity.position())); + } + } + } +} blob - /dev/null blob + d48f1893dab2ef5c13574ab027b4080e27108750 (mode 644) --- /dev/null +++ src/main/java/sh/rsap/aeronauticscompat/mixin/storagedrawers/ChameleonRayTraceMixin.java @@ -0,0 +1,35 @@ +package sh.rsap.aeronauticscompat.mixin.storagedrawers; + +import com.texelsaurus.minecraft.chameleon.util.WorldUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import sh.rsap.aeronauticscompat.compat.SubLevelRayTrace; + +/** + * The Chameleon library bundles an identical copy of Storage Drawers' + * WorldUtils.rayTraceEyes; the creative-mode left-click handler on NeoForge + * goes through this copy. Same unbounded-ray hang as + * {@link StorageDrawersRayTraceMixin}. + */ +@Pseudo +@Mixin(value = WorldUtils.class, remap = false) +public abstract class ChameleonRayTraceMixin { + + @Inject(method = "rayTraceEyes", at = @At("HEAD"), cancellable = true, remap = false) + private static void aeronauticscompat$boundedRayTrace( + Level level, Player player, BlockPos blockPos, + CallbackInfoReturnable cir + ) { + BlockHitResult hit = SubLevelRayTrace.rayTraceEyesBounded(level, player, blockPos); + if (hit != null) { + cir.setReturnValue(hit); + } + } +} blob - /dev/null blob + f1e1d1f0d47555f11b97ce64f64dba8459d53487 (mode 644) --- /dev/null +++ src/main/java/sh/rsap/aeronauticscompat/mixin/storagedrawers/StorageDrawersRayTraceMixin.java @@ -0,0 +1,36 @@ +package sh.rsap.aeronauticscompat.mixin.storagedrawers; + +import com.jaquadro.minecraft.storagedrawers.util.WorldUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import sh.rsap.aeronauticscompat.compat.SubLevelRayTrace; + +/** + * Storage Drawers resolves left-clicks (item extraction) by raytracing from + * the player's eyes toward the block, with the ray length taken from the raw + * player-to-block distance. On a sub-level the block pos is in plot space, so + * the ray becomes millions of blocks long and hangs the server. See + * {@link SubLevelRayTrace}. + */ +@Pseudo +@Mixin(value = WorldUtils.class, remap = false) +public abstract class StorageDrawersRayTraceMixin { + + @Inject(method = "rayTraceEyes", at = @At("HEAD"), cancellable = true, remap = false) + private static void aeronauticscompat$boundedRayTrace( + Level level, Player player, BlockPos blockPos, + CallbackInfoReturnable cir + ) { + BlockHitResult hit = SubLevelRayTrace.rayTraceEyesBounded(level, player, blockPos); + if (hit != null) { + cir.setReturnValue(hit); + } + } +} blob - 379f4b03199b71307a52758029ad6bd066516bc1 blob + bc9834bcd9813f80a6aa7972c0a4dc505b85e2fd --- src/main/resources/aeronauticscompat.mixins.json +++ src/main/resources/aeronauticscompat.mixins.json @@ -14,7 +14,10 @@ "pneumaticcraft.AbstractPneumaticCraftBlockMixin", "cobblemon.ProximityPCLinkMixin", "cobblemon.PastureLinkManagerMixin", - "cobblemon.PokemonEntityRidingMixin" + "cobblemon.PokemonEntityRidingMixin", + "storagedrawers.StorageDrawersRayTraceMixin", + "storagedrawers.ChameleonRayTraceMixin", + "camera.CameraFrameAssemblyMixin" ], "client": [ "etched.EtchedStopListeningSoundMixin", blob - 00dcf5a434c21254ed8390f063265781066d0876 blob + 5d540c411271bd6f2741b0f1e2d00d2e997e076c --- src/main/resources/data/sable/tags/entity_type/retain_in_sub_level.json +++ src/main/resources/data/sable/tags/entity_type/retain_in_sub_level.json @@ -6,6 +6,7 @@ {"id": "immersive_paintings:glow_painting", "required": false}, {"id": "immersive_paintings:graffiti", "required": false}, {"id": "immersive_paintings:glow_graffiti", "required": false}, - {"id": "sleep_tight:bed_entity", "required": false} + {"id": "sleep_tight:bed_entity", "required": false}, + {"id": "camera:image_frame", "required": false} ] }