use foot print class instead of hardcoded foot print

This commit is contained in:
2025-08-16 13:05:53 +02:00
parent 5888c1d276
commit 5bffc81d5d
5 changed files with 305 additions and 83 deletions

View File

@@ -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<BlockEntity> provider = (BlockEntityRendererProvider) (ctx -> new AnimatedBlockRenderer<>(
ctx));

View File

@@ -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 masters 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);
}
}

View File

@@ -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<BlockState> cir) {
CallbackInfoReturnable<BlockState> 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<VoxelShape> 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);
}
}

View File

@@ -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<Cell> 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<Cell> parts, VoxelShape masterOutline, VoxelShape partOutline) {
this.parts = List.copyOf(parts);
this.masterOutline = masterOutline;
this.partOutline = partOutline;
}
public List<Cell> 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<Cell> 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<Cell> none() {
return Collections.emptyList();
}
}
}

View File

@@ -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<ResourceLocation, FootprintSpec> 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<BlockPos> 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
};
}
}