From 5bffc81d5d3fd92ad2a0401f8b4091d0784d2e97 Mon Sep 17 00:00:00 2001 From: Jika Date: Sat, 16 Aug 2025 13:05:53 +0200 Subject: [PATCH] use foot print class instead of hardcoded foot print --- src/main/java/com/imbgt/ibg/IBG.java | 17 ++- .../com/imbgt/ibg/block/custom/PartBlock.java | 65 ++++----- .../ibg/mixin/MetaMachineBlockMixin.java | 92 +++++++------ .../com/imbgt/ibg/util/FootprintSpec.java | 88 ++++++++++++ .../java/com/imbgt/ibg/util/Footprints.java | 126 ++++++++++++++++++ 5 files changed, 305 insertions(+), 83 deletions(-) create mode 100644 src/main/java/com/imbgt/ibg/util/FootprintSpec.java create mode 100644 src/main/java/com/imbgt/ibg/util/Footprints.java diff --git a/src/main/java/com/imbgt/ibg/IBG.java b/src/main/java/com/imbgt/ibg/IBG.java index 0c39d0c..fb848c0 100644 --- a/src/main/java/com/imbgt/ibg/IBG.java +++ b/src/main/java/com/imbgt/ibg/IBG.java @@ -5,8 +5,8 @@ import com.gregtechceu.gtceu.api.registry.GTRegistries; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.shapes.Shapes; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.server.ServerStartingEvent; @@ -22,6 +22,8 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import com.imbgt.ibg.block.ModBlocks; import com.imbgt.ibg.block.entity.ModBlockEntities; import com.imbgt.ibg.block.entity.client.AnimatedBlockRenderer; +import com.imbgt.ibg.util.FootprintSpec; +import com.imbgt.ibg.util.Footprints; import com.mojang.logging.LogUtils; import org.slf4j.Logger; import software.bernie.geckolib.GeckoLib; @@ -49,7 +51,16 @@ public class IBG { ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, Config.SPEC); } - private void commonSetup(final FMLCommonSetupEvent event) {} + private void commonSetup(final FMLCommonSetupEvent event) { + event.enqueueWork(() -> { + Footprints.register(new ResourceLocation("gtceu", "lv_lathe"), + FootprintSpec.builder() + .addPart(-1, 0, 0) + .masterOutline(Shapes.box(0, 0, 0, 1, 14. / 16., 1)) + .partOutline(Shapes.box(0, 0, 0, 1, 14. / 16., 1)) + .build()); + }); + } @SubscribeEvent public void onServerStarting(ServerStartingEvent event) {} @@ -63,8 +74,6 @@ public class IBG { var latheDef = GTRegistries.MACHINES.get( new ResourceLocation("gtceu", "lv_lathe")); - latheDef.setShape(Block.box(-16, 0, 0, 32 - 16, 14, 16)); - @SuppressWarnings({ "unchecked", "rawtypes" }) BlockEntityRendererProvider provider = (BlockEntityRendererProvider) (ctx -> new AnimatedBlockRenderer<>( ctx)); diff --git a/src/main/java/com/imbgt/ibg/block/custom/PartBlock.java b/src/main/java/com/imbgt/ibg/block/custom/PartBlock.java index 35afc3d..1f508ae 100644 --- a/src/main/java/com/imbgt/ibg/block/custom/PartBlock.java +++ b/src/main/java/com/imbgt/ibg/block/custom/PartBlock.java @@ -2,6 +2,9 @@ package com.imbgt.ibg.block.custom; import com.gregtechceu.gtceu.api.block.MetaMachineBlock; import com.gregtechceu.gtceu.api.blockentity.MetaMachineBlockEntity; + import com.imbgt.ibg.util.Footprints; + import com.imbgt.ibg.util.FootprintSpec; + import com.gregtechceu.gtceu.api.block.MetaMachineBlock; import net.minecraft.client.particle.ParticleEngine; import net.minecraft.core.BlockPos; @@ -25,6 +28,7 @@ import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraftforge.client.extensions.common.IClientBlockExtensions; @@ -42,7 +46,7 @@ public class PartBlock extends Block implements EntityBlock { } private static BlockPos masterPos(BlockGetter level, BlockPos pos) { - BlockEntity be = (level instanceof Level l) ? l.getBlockEntity(pos) : null; + BlockEntity be = level.getBlockEntity(pos); return (be instanceof PartBE ml) ? ml.getMasterPos() : null; } @@ -81,11 +85,15 @@ public class PartBlock extends Block implements EntityBlock { return false; } - // Optional: give the part a slice-shaped outline/collision on its own cell @Override public VoxelShape getShape(BlockState s, BlockGetter g, BlockPos p, CollisionContext c) { - return Block.box(0, 0, 0, 16, 16, 16); // or custom slice if you want + return Block.box(0, 0, 0, 16, 16, 16); + } + + @Override + public VoxelShape getCollisionShape(BlockState s, BlockGetter g, BlockPos p, CollisionContext c) { + return Shapes.empty(); } @Override @@ -124,7 +132,7 @@ public class PartBlock extends Block implements EntityBlock { public ItemStack getCloneItemStack(BlockGetter level, BlockPos pos, BlockState state) { BlockPos m = masterPos(level, pos); if (m != null) { - BlockEntity be = ((Level) level).getBlockEntity(m); + BlockEntity be = level.getBlockEntity(m); if (be instanceof MetaMachineBlockEntity mm) return mm.getDefinition().asStack(); } @@ -147,62 +155,41 @@ public class PartBlock extends Block implements EntityBlock { @Override public void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, BlockPos fromPos, boolean isMoving) { - IBG.LOGGER.info("coucou"); - if (level.isClientSide) - return; + if (level.isClientSide) return; BlockEntity be = level.getBlockEntity(pos); - if (!(be instanceof PartBE part)) - return; + if (!(be instanceof PartBE part)) return; BlockPos master = part.getMasterPos(); if (master == null) { - IBG.LOGGER.info("nul"); - // // No master? Clean up. level.setBlock(pos, Blocks.AIR.defaultBlockState(), - Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | - Block.UPDATE_SUPPRESS_DROPS); + Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_SUPPRESS_DROPS); return; } - // Only react when the master changed (rotation or removal) - if (!fromPos.equals(master)) - return; + // Only react when the master changed (rotation/removal) + if (!fromPos.equals(master)) return; BlockState masterState = level.getBlockState(master); if (masterState.isAir()) { - IBG.LOGGER.info("hmmm"); - - // Master gone: remove self silently level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_SUPPRESS_DROPS); return; } - // Compute where this part *should* be for the master’s current facing - Direction facing = masterState.getValue( - ((MetaMachineBlock) masterState.getBlock()) - .getRotationState().property); + if (!(masterState.getBlock() instanceof MetaMachineBlock mm)) return; + var rot = mm.getRotationState(); + if (rot == null || !masterState.hasProperty(rot.property)) return; + Direction facing = masterState.getValue(rot.property); - BlockPos expected = computePartPosForFacing(master, facing); // your offset logic + // Validate against the full footprint + FootprintSpec spec = Footprints.get(mm.getDefinition().getId()); + if (spec == null) return; - if (!pos.equals(expected)) { - IBG.LOGGER.info("{} - {}", pos, expected); - // This part is stale (old position). Remove it silently; the new part is placed - // by your existing placement path. + boolean isExpected = Footprints.partPositions(master, facing, spec).contains(pos); + if (!isExpected) { level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_SUPPRESS_DROPS); } } - - private static BlockPos computePartPosForFacing(BlockPos master, Direction facing) { - Direction right = switch (facing) { - case NORTH -> Direction.WEST; - case EAST -> Direction.NORTH; - case SOUTH -> Direction.EAST; - case WEST -> Direction.SOUTH; - default -> Direction.EAST; // pick a sensible default if needed - }; - return master.relative(right); - } } diff --git a/src/main/java/com/imbgt/ibg/mixin/MetaMachineBlockMixin.java b/src/main/java/com/imbgt/ibg/mixin/MetaMachineBlockMixin.java index 353959a..c4887b0 100644 --- a/src/main/java/com/imbgt/ibg/mixin/MetaMachineBlockMixin.java +++ b/src/main/java/com/imbgt/ibg/mixin/MetaMachineBlockMixin.java @@ -13,11 +13,14 @@ import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.RenderShape; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import com.imbgt.ibg.block.ModBlocks; import com.imbgt.ibg.block.entity.PartBE; +import com.imbgt.ibg.util.Footprints; +import com.imbgt.ibg.IBG; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -52,7 +55,7 @@ public abstract class MetaMachineBlockMixin extends AppearanceBlock { @Inject(method = "getStateForPlacement", at = @At("HEAD"), cancellable = true) private void ibg$latheFootprintCheck(BlockPlaceContext ctx, - CallbackInfoReturnable cir) { + CallbackInfoReturnable cir) { MetaMachineBlock self = (MetaMachineBlock) (Object) this; var id = self.getDefinition().getId(); if (!("gtceu".equals(id.getNamespace()) && "lv_lathe".equals(id.getPath()))) @@ -62,64 +65,73 @@ public abstract class MetaMachineBlockMixin extends AppearanceBlock { BlockPos base = ctx.getClickedPos(); // Determine facing like MetaMachineBlock does: - Direction facing; - var rot = self.getDefinition().getRotationState(); - if (rot == RotationState.Y_AXIS) - facing = Direction.UP; // adjust if needed - else - facing = ctx.getHorizontalDirection().getOpposite(); + Direction facing = ctx.getHorizontalDirection().getOpposite(); - // Your footprint: master + one part at +clockwise (adjust if your model - // differs) - BlockPos partPos = base.relative(facing.getCounterClockWise()); - - // If the part cell cannot be replaced => CANCEL placement - if (!level.getBlockState(partPos).canBeReplaced(ctx)) { - cir.setReturnValue(null); // makes placement fail cleanly + var spec = Footprints.get(self.getDefinition().getId()); + if (spec == null) + return; + var partPositions = Footprints.partPositions(base, facing, spec); + for (BlockPos p : partPositions) { + if (!level.getBlockState(p).canBeReplaced()) { + cir.setReturnValue(null); + return; + } } + } @Inject(method = "onPlace", at = @At("TAIL")) private void ibg$placePart(BlockState st, Level lvl, BlockPos pos, BlockState old, boolean moved, CallbackInfo ci) { if (lvl.isClientSide) return; - var def = getDefinition().getId(); - if (!("gtceu".equals(def.getNamespace()) && "lv_lathe".equals(def.getPath()))) + var id = getDefinition().getId(); + var spec = Footprints.get(id); + if (spec == null) return; - var rot = ((IMachineBlock) (Object) this).getRotationState(); - var facing = rot == RotationState.NONE ? Direction.NORTH : st.getValue(rot.property); - - // Example footprint: extend one cell to +X relative to facing (adjust as - // needed) - BlockPos partPos = pos.relative(facing.getCounterClockWise()); - - if (lvl.getBlockState(partPos).canBeReplaced()) { - lvl.setBlockAndUpdate(partPos, ModBlocks.PART.get().defaultBlockState()); - var be = lvl.getBlockEntity(partPos); - if (be instanceof PartBE part) - part.setMasterPos(pos); - } else { - // Optional: rollback place if you require free space - // lvl.removeBlock(pos, false); + Direction facing = (rot == null || rot == RotationState.NONE || !st.hasProperty(rot.property)) ? Direction.NORTH + : st.getValue(rot.property); + for (BlockPos p : Footprints.partPositions(pos, facing, spec)) { + if (lvl.getBlockState(p).canBeReplaced()) { + lvl.setBlockAndUpdate(p, ModBlocks.PART.get().defaultBlockState()); + var be = lvl.getBlockEntity(p); + if (be instanceof PartBE part) + part.setMasterPos(pos); + } } } @Inject(method = "onRemove", at = @At("HEAD")) private void ibg$removePart(BlockState st, Level lvl, BlockPos pos, BlockState newState, boolean moving, - CallbackInfo ci) { + CallbackInfo ci) { if (lvl.isClientSide || st.getBlock() == newState.getBlock()) return; - var def = getDefinition().getId(); - if (!("gtceu".equals(def.getNamespace()) && "lv_lathe".equals(def.getPath()))) + var id = getDefinition().getId(); + var spec = com.imbgt.ibg.util.Footprints.get(id); + if (spec == null) return; - var rot = ((IMachineBlock) (Object) this).getRotationState(); - var facing = rot == RotationState.NONE ? Direction.NORTH : st.getValue(rot.property); - BlockPos partPos = pos.relative(facing.getCounterClockWise()); - - if (lvl.getBlockState(partPos).is(ModBlocks.PART.get())) { - lvl.removeBlock(partPos, false); + Direction facing = (rot == null || rot == RotationState.NONE || !st.hasProperty(rot.property)) ? Direction.NORTH + : st.getValue(rot.property); + for (BlockPos p : Footprints.partPositions(pos, facing, spec)) { + if (lvl.getBlockState(p).is(ModBlocks.PART.get())) { + lvl.removeBlock(p, false); + } } } + + @Inject(method = "getShape", at = @At("HEAD"), cancellable = true) + private void ibg$combinedOutline(BlockState state, BlockGetter level, BlockPos pos, + CollisionContext ctx, CallbackInfoReturnable cir) { + var spec = com.imbgt.ibg.util.Footprints.get(getDefinition().getId()); + if (spec == null) + return; + var rot = ((IMachineBlock) (Object) this).getRotationState(); + if (rot == null || !state.hasProperty(rot.property)) + return; + var facing = state.getValue(rot.property); + var shape = Footprints.combinedOutline(facing, spec); + // var shape = Footprints.rotateOutline(spec.masterOutline(), facing); + cir.setReturnValue(shape); + } } diff --git a/src/main/java/com/imbgt/ibg/util/FootprintSpec.java b/src/main/java/com/imbgt/ibg/util/FootprintSpec.java new file mode 100644 index 0000000..3713303 --- /dev/null +++ b/src/main/java/com/imbgt/ibg/util/FootprintSpec.java @@ -0,0 +1,88 @@ +package com.imbgt.ibg.util; + +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** Declarative footprint for a multiblock: relative part offsets and local outlines. */ +public final class FootprintSpec { + + /** Relative positions (in block cells) of parts, defined for facing = NORTH. */ + private final List parts; + /** Per-cell outline for the master, in local [0,1] coords. Used for selection outline only. */ + private final VoxelShape masterOutline; + /** Per-cell outline for each part, in local [0,1] coords. Used for selection outline only. */ + private final VoxelShape partOutline; + + private FootprintSpec(List parts, VoxelShape masterOutline, VoxelShape partOutline) { + this.parts = List.copyOf(parts); + this.masterOutline = masterOutline; + this.partOutline = partOutline; + } + + public List parts() { + return parts; + } + + public VoxelShape masterOutline() { + return masterOutline; + } + + public VoxelShape partOutline() { + return partOutline; + } + + /** Builder with sane defaults. */ + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + + private final List parts = new ArrayList<>(); + private VoxelShape master = Shapes.box(0, 0, 0, 1, 1, 1); + private VoxelShape part = Shapes.box(0, 0, 0, 1, 1, 1); + + /** Add a part offset in block cells, relative to master, for facing = NORTH. */ + public Builder addPart(int dx, int dy, int dz) { + parts.add(new Cell(dx, dy, dz)); + return this; + } + + /** Set master outline shape in local [0,1] coords. */ + public Builder masterOutline(VoxelShape shape) { + this.master = shape; + return this; + } + + /** Set part outline shape in local [0,1] coords. */ + public Builder partOutline(VoxelShape shape) { + this.part = shape; + return this; + } + + public FootprintSpec build() { + return new FootprintSpec(parts, normalize(master), normalize(part)); + } + + private static VoxelShape normalize(VoxelShape s) { + if (s == null) return Shapes.box(0, 0, 0, 1, 1, 1); + // Ensure boxes are snapped to doubles and merged. + VoxelShape out = Shapes.empty(); + for (AABB a : s.toAabbs()) out = Shapes.or(out, Shapes.create(a)); + return out.optimize(); + } + } + + /** Integer cell offset. */ + public static record Cell(int dx, int dy, int dz) { + + public static List none() { + return Collections.emptyList(); + } + } +} diff --git a/src/main/java/com/imbgt/ibg/util/Footprints.java b/src/main/java/com/imbgt/ibg/util/Footprints.java new file mode 100644 index 0000000..0cb4a83 --- /dev/null +++ b/src/main/java/com/imbgt/ibg/util/Footprints.java @@ -0,0 +1,126 @@ +package com.imbgt.ibg.util; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Registry + helpers to compute world positions and a continuous outline shape. + */ +public final class Footprints { + + private static final Map REG = new HashMap<>(); + + private Footprints() { + } + + /** Register or replace a footprint for a machine id. Call during setup. */ + public static void register(ResourceLocation machineId, FootprintSpec spec) { + REG.put(machineId, spec); + } + + /** @return null when no footprint registered. */ + public static FootprintSpec get(ResourceLocation machineId) { + return REG.get(machineId); + } + + /** All part world positions for a given master and facing. */ + public static List partPositions(BlockPos master, Direction facing, FootprintSpec spec) { + return spec.parts().stream() + .map(c -> master.offset(rotateDx(c.dx(), c.dz(), facing), c.dy(), rotateDz(c.dx(), c.dz(), facing))) + .toList(); + } + + public static VoxelShape rotateOutline(VoxelShape shape, Direction facing) { + return rotateY(shape, quarters(facing)); + } + + /** + * A single continuous selection outline for the whole multiblock, anchored at + * the master. + */ + public static VoxelShape combinedOutline(Direction facing, FootprintSpec spec) { + int q = quarters(facing); + VoxelShape result = rotateY(spec.masterOutline(), q); + + for (var cell : spec.parts()) { + // rotate the cell offset then translate the part shape by 1 * offset + int rx = rotateDx(cell.dx(), cell.dz(), facing); + int rz = rotateDz(cell.dx(), cell.dz(), facing); + VoxelShape rotatedPart = rotateY(spec.partOutline(), q); + result = Shapes.or(result, move(rotatedPart, rx, cell.dy(), rz)); + } + return result.optimize(); + } + + // ---------- math helpers ---------- + + /** + * Rotate a local shape by 0/90/180/270 degrees around Y, within a single cell's + * [0,1] space. + */ + private static VoxelShape rotateY(VoxelShape shape, int quarters) { + if (quarters == 0) + return shape; + VoxelShape out = Shapes.empty(); + for (AABB a : shape.toAabbs()) { + AABB r = switch (quarters & 3) { + // X,Z in [0,1]; rotate around center at origin of the cell + case 1 -> new AABB(1 - a.maxZ, a.minY, a.minX, 1 - a.minZ, a.maxY, a.maxX); // 90° + case 2 -> new AABB(1 - a.maxX, a.minY, 1 - a.maxZ, 1 - a.minX, a.maxY, 1 - a.minZ); // 180° + case 3 -> new AABB(a.minZ, a.minY, 1 - a.maxX, a.maxZ, a.maxY, 1 - a.minX); // 270° + default -> a; + }; + out = Shapes.or(out, Shapes.create(r)); + } + return out.optimize(); + } + + /** + * Translate a shape by world-space units. For cross-cell outline we use + * multiples of 1. + */ + private static VoxelShape move(VoxelShape s, double dx, double dy, double dz) { + VoxelShape out = Shapes.empty(); + for (AABB a : s.toAabbs()) + out = Shapes.or(out, Shapes.create(a.move(dx, dy, dz))); + return out; + } + + private static int rotateDx(int dx, int dz, Direction facing) { + return switch (quarters(facing)) { + case 0 -> dx; // NORTH + case 1 -> -dz; // EAST + case 2 -> -dx; // SOUTH + case 3 -> dz; // WEST + default -> dx; + }; + } + + private static int rotateDz(int dx, int dz, Direction facing) { + return switch (quarters(facing)) { + case 0 -> dz; // NORTH + case 1 -> dx; // EAST + case 2 -> -dz; // SOUTH + case 3 -> -dx; // WEST + default -> dz; + }; + } + + private static int quarters(Direction facing) { + return switch (facing) { + case NORTH -> 0; + case EAST -> 1; + case SOUTH -> 2; + case WEST -> 3; + default -> 0; // treat UP/DOWN as NORTH + }; + } +}