From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Thu, 8 Dec 2022 19:40:00 +0800 Subject: [PATCH] Alternative block placement Protocol This patch is Powered by carpet-extra(https://github.com/gnembon/carpet-extra) MasaGadget(https://github.com/plusls/MasaGadget) litematica(https://github.com/maruohon/litematica) diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java index e483186a5292b3b53bfb1af4d56f55fcc1a6106c..39a8fb7a73362beab1e4a53b6297a054a159bb80 100644 --- a/src/main/java/net/minecraft/world/item/BlockItem.java +++ b/src/main/java/net/minecraft/world/item/BlockItem.java @@ -158,7 +158,7 @@ public class BlockItem extends Item { @Nullable protected BlockState getPlacementState(BlockPlaceContext context) { - BlockState iblockdata = this.getBlock().getStateForPlacement(context); + BlockState iblockdata = this.getBlock().getRealStateForPlacement(context); // Leaves - alternativeBlockPlacement return iblockdata != null && this.canPlace(context, iblockdata) ? iblockdata : null; } diff --git a/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java b/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java index 88072259a9de205db428351c5c9f6114e199e402..e641360a3e82cb03393b856fa545d6b0089a8dbf 100644 --- a/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java +++ b/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java @@ -34,7 +34,7 @@ public class StandingAndWallBlockItem extends BlockItem { @Nullable @Override protected BlockState getPlacementState(BlockPlaceContext context) { - BlockState iblockdata = this.wallBlock.getStateForPlacement(context); + BlockState iblockdata = this.wallBlock.getRealStateForPlacement(context); // Leaves - alternativeBlockPlacement BlockState iblockdata1 = null; Level world = context.getLevel(); BlockPos blockposition = context.getClickedPos(); @@ -45,7 +45,7 @@ public class StandingAndWallBlockItem extends BlockItem { Direction enumdirection = aenumdirection[j]; if (enumdirection != this.attachmentDirection.getOpposite()) { - BlockState iblockdata2 = enumdirection == this.attachmentDirection ? this.getBlock().getStateForPlacement(context) : iblockdata; + BlockState iblockdata2 = enumdirection == this.attachmentDirection ? this.getBlock().getRealStateForPlacement(context) : iblockdata; // Leaves - carpetAlternativeBlockPlacement if (iblockdata2 != null && this.canPlace(world, iblockdata2, blockposition)) { iblockdata1 = iblockdata2; diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java index 065fbac30d22ec45cd3f5b66599c8500ae5c278d..9a970c6aff2f5f2edbad600156493a7c1f28e620 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java @@ -404,6 +404,33 @@ public class Block extends BlockBehaviour implements ItemLike { public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) {} + // Leaves start - alternativeBlockPlacement + @Nullable + public BlockState getRealStateForPlacement(BlockPlaceContext ctx) { + BlockState vanillaState = getStateForPlacement(ctx); + switch (top.leavesmc.leaves.LeavesConfig.alternativeBlockPlacement) { + case "CARPET" -> { + BlockState tryState = top.leavesmc.leaves.protocol.CarpetAlternativeBlockPlacement.alternativeBlockPlacement(this, ctx); + if (tryState != null) { + return tryState; + } + } + case "CARPET_FIX" -> { + BlockState tryState = top.leavesmc.leaves.protocol.CarpetAlternativeBlockPlacement.alternativeBlockPlacementFix(this, ctx); + if (tryState != null) { + return tryState; + } + } + case "LITEMATICA" -> { + if (vanillaState != null) { + return top.leavesmc.leaves.protocol.LitematicaEasyPlaceProtocol.applyPlacementProtocol(vanillaState, ctx); + } + } + } + return vanillaState; + } + // Leaves end - alternativeBlockPlacement + @Nullable public BlockState getStateForPlacement(BlockPlaceContext ctx) { return this.defaultBlockState(); diff --git a/src/main/java/top/leavesmc/leaves/protocol/CarpetAlternativeBlockPlacement.java b/src/main/java/top/leavesmc/leaves/protocol/CarpetAlternativeBlockPlacement.java new file mode 100644 index 0000000000000000000000000000000000000000..dff01bc7495620fecb23d658c4dbd17f96a5819a --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/protocol/CarpetAlternativeBlockPlacement.java @@ -0,0 +1,161 @@ +package top.leavesmc.leaves.protocol; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BedBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.ComparatorBlock; +import net.minecraft.world.level.block.DirectionalBlock; +import net.minecraft.world.level.block.DispenserBlock; +import net.minecraft.world.level.block.GlazedTerracottaBlock; +import net.minecraft.world.level.block.ObserverBlock; +import net.minecraft.world.level.block.RepeaterBlock; +import net.minecraft.world.level.block.StairBlock; +import net.minecraft.world.level.block.TrapDoorBlock; +import net.minecraft.world.level.block.piston.PistonBaseBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.ComparatorMode; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.block.state.properties.Half; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.block.state.properties.SlabType; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.Objects; + +public class CarpetAlternativeBlockPlacement { + + @Nullable + public static BlockState alternativeBlockPlacement(@NotNull Block block, @NotNull BlockPlaceContext context) { + Vec3 hitPos = context.getClickLocation(); + BlockPos blockPos = context.getClickedPos(); + double relativeHitX = hitPos.x - blockPos.getX(); + BlockState state = block.getStateForPlacement(context); + + if (relativeHitX < 2 || state == null) { + return null; + } + + DirectionProperty directionProp = getFirstDirectionProperty(state); + int protocolValue = ((int) relativeHitX - 2) / 2; + + if (directionProp != null) { + Direction origFacing = state.getValue(directionProp); + Direction facing = origFacing; + int facingIndex = protocolValue & 0xF; + + if (facingIndex == 6) { + facing = facing.getOpposite(); + } else if (facingIndex <= 5) { + facing = Direction.from3DDataValue(facingIndex); + } + + if (!directionProp.getPossibleValues().contains(facing)) { + facing = context.getPlayer().getDirection().getOpposite(); + } + + if (facing != origFacing && directionProp.getPossibleValues().contains(facing)) { + if (state.getBlock() instanceof BedBlock) { + BlockPos headPos = blockPos.relative(facing); + + if (!context.getLevel().getBlockState(headPos).canBeReplaced(context)) { + return null; + } + } + + state = state.setValue(directionProp, facing); + } + } else if (state.hasProperty(BlockStateProperties.AXIS)) { + Direction.Axis axis = Direction.Axis.VALUES[protocolValue % 3]; + state = state.setValue(BlockStateProperties.AXIS, axis); + } + + protocolValue &= 0xFFFFFFF0; + + if (protocolValue >= 16) { + if (block instanceof RepeaterBlock) { + Integer delay = (protocolValue / 16); + + if (RepeaterBlock.DELAY.getPossibleValues().contains(delay)) { + state = state.setValue(RepeaterBlock.DELAY, delay); + } + } else if (protocolValue == 16) { + if (block instanceof ComparatorBlock) { + state = state.setValue(ComparatorBlock.MODE, ComparatorMode.SUBTRACT); + } else if (state.hasProperty(BlockStateProperties.HALF) && state.getValue(BlockStateProperties.HALF) == Half.BOTTOM) { + state = state.setValue(BlockStateProperties.HALF, Half.TOP); + } else if (state.hasProperty(BlockStateProperties.SLAB_TYPE) && state.getValue(BlockStateProperties.SLAB_TYPE) == SlabType.BOTTOM) { + state = state.setValue(BlockStateProperties.SLAB_TYPE, SlabType.TOP); + } + } + } + + return state; + } + + public static BlockState alternativeBlockPlacementFix(Block block, BlockPlaceContext context) { + Direction facing; + Vec3 vec3d = context.getClickLocation(); + BlockPos pos = context.getClickedPos(); + double hitX = vec3d.x - pos.getX(); + if (hitX < 2) { + return null; + } + int code = (int) (hitX - 2) / 2; + Player placer = Objects.requireNonNull(context.getPlayer()); + Level world = context.getLevel(); + + if (block instanceof GlazedTerracottaBlock) { + facing = Direction.from3DDataValue(code); + if (facing == Direction.UP || facing == Direction.DOWN) { + facing = placer.getDirection().getOpposite(); + } + return block.defaultBlockState().setValue(GlazedTerracottaBlock.FACING, facing); + } else if (block instanceof ObserverBlock) { + return block.defaultBlockState().setValue(ObserverBlock.FACING, Direction.from3DDataValue(code)).setValue(ObserverBlock.POWERED, true); + } else if (block instanceof RepeaterBlock) { + facing = Direction.from3DDataValue(code % 16); + if (facing == Direction.UP || facing == Direction.DOWN) { + facing = placer.getDirection().getOpposite(); + } + return block.defaultBlockState().setValue(RepeaterBlock.FACING, facing).setValue(RepeaterBlock.DELAY, Mth.clamp(code / 16, 1, 4)).setValue(RepeaterBlock.LOCKED, Boolean.FALSE); + } else if (block instanceof TrapDoorBlock) { + facing = Direction.from3DDataValue(code % 16); + if (facing == Direction.UP || facing == Direction.DOWN) { + facing = placer.getDirection().getOpposite(); + } + return block.defaultBlockState().setValue(TrapDoorBlock.FACING, facing).setValue(TrapDoorBlock.OPEN, Boolean.FALSE).setValue(TrapDoorBlock.HALF, (code >= 16) ? Half.TOP : Half.BOTTOM).setValue(TrapDoorBlock.OPEN, world.hasNeighborSignal(pos)); + } else if (block instanceof ComparatorBlock) { + facing = Direction.from3DDataValue(code % 16); + if ((facing == Direction.UP) || (facing == Direction.DOWN)) { + facing = placer.getDirection().getOpposite(); + } + ComparatorMode m = (hitX >= 16) ? ComparatorMode.SUBTRACT : ComparatorMode.COMPARE; + return block.defaultBlockState().setValue(ComparatorBlock.FACING, facing).setValue(ComparatorBlock.POWERED, Boolean.FALSE).setValue(ComparatorBlock.MODE, m); + } else if (block instanceof DispenserBlock) { + return block.defaultBlockState().setValue(DispenserBlock.FACING, Direction.from3DDataValue(code)).setValue(DispenserBlock.TRIGGERED, Boolean.FALSE); + } else if (block instanceof PistonBaseBlock) { + return block.defaultBlockState().setValue(DirectionalBlock.FACING, Direction.from3DDataValue(code)).setValue(PistonBaseBlock.EXTENDED, Boolean.FALSE); + } else if (block instanceof StairBlock) { + return Objects.requireNonNull(block.getStateForPlacement(context)).setValue(StairBlock.FACING, Direction.from3DDataValue(code % 16)).setValue(StairBlock.HALF, (hitX >= 16) ? Half.TOP : Half.BOTTOM); + } + return null; + } + + @Nullable + public static DirectionProperty getFirstDirectionProperty(@NotNull BlockState state) { + for (Property prop : state.getProperties()) { + if (prop instanceof DirectionProperty) { + return (DirectionProperty) prop; + } + } + return null; + } +} diff --git a/src/main/java/top/leavesmc/leaves/protocol/LitematicaEasyPlaceProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/LitematicaEasyPlaceProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..8463318fcad637c409679707b2adf0648ba5a173 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/protocol/LitematicaEasyPlaceProtocol.java @@ -0,0 +1,214 @@ +package top.leavesmc.leaves.protocol; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BedBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.block.state.properties.SlabType; +import net.minecraft.world.phys.Vec3; +import top.leavesmc.leaves.LeavesLogger; +import top.leavesmc.leaves.util.MathUtils; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class LitematicaEasyPlaceProtocol { + + public static final ImmutableSet> WHITELISTED_PROPERTIES = ImmutableSet.of( + // BooleanProperty: + // INVERTED - DaylightDetector + // OPEN - Barrel, Door, FenceGate, Trapdoor + // PERSISTENT - Leaves + BlockStateProperties.INVERTED, + BlockStateProperties.OPEN, + BlockStateProperties.PERSISTENT, + // EnumProperty: + // AXIS - Pillar + // BLOCK_HALF - Stairs, Trapdoor + // CHEST_TYPE - Chest + // COMPARATOR_MODE - Comparator + // DOOR_HINGE - Door + // SLAB_TYPE - Slab - PARTIAL ONLY: TOP and BOTTOM, not DOUBLE + // STAIR_SHAPE - Stairs (needed to get the correct state, otherwise the player facing would be a factor) + // WALL_MOUNT_LOCATION - Button, Grindstone, Lever + BlockStateProperties.AXIS, + BlockStateProperties.HALF, + BlockStateProperties.CHEST_TYPE, + BlockStateProperties.MODE_COMPARATOR, + BlockStateProperties.DOOR_HINGE, + BlockStateProperties.SLAB_TYPE, + BlockStateProperties.STAIRS_SHAPE, + BlockStateProperties.ATTACH_FACE, + // IntProperty: + // BITES - Cake + // DELAY - Repeater + // NOTE - NoteBlock + // ROTATION - Banner, Sign, Skull + BlockStateProperties.BITES, + BlockStateProperties.DELAY, + BlockStateProperties.NOTE, + BlockStateProperties.ROTATION_16 + ); + + public static > BlockState applyPlacementProtocol(BlockState state, BlockPlaceContext context) { + return applyPlacementProtocolV3(state, UseContext.from(context, context.getHand())); + } + + private static > BlockState applyPlacementProtocolV3(BlockState state, UseContext context) { + int protocolValue = (int) (context.getHitVec().x - (double) context.getPos().getX()) - 2; + if (protocolValue < 0) { + return state; + } + + @Nullable DirectionProperty property = getFirstDirectionProperty(state); + + if (property != null && property != BlockStateProperties.VERTICAL_DIRECTION) { + state = applyDirectionProperty(state, context, property, protocolValue); + + if (state == null) { + return null; + } + + protocolValue >>>= 3; + } + + protocolValue >>>= 1; + + List> propList = new ArrayList<>(state.getBlock().getStateDefinition().getProperties()); + propList.sort(Comparator.comparing(Property::getName)); + + try { + for (Property p : propList) { + if (!(p instanceof DirectionProperty) && WHITELISTED_PROPERTIES.contains(p)) { + @SuppressWarnings("unchecked") Property prop = (Property) p; + List list = new ArrayList<>(prop.getPossibleValues()); + list.sort(Comparable::compareTo); + + int requiredBits = MathUtils.floorLog2(MathUtils.smallestEncompassingPowerOfTwo(list.size())); + int bitMask = ~(0xFFFFFFFF << requiredBits); + int valueIndex = protocolValue & bitMask; + + if (valueIndex >= 0 && valueIndex < list.size()) { + T value = list.get(valueIndex); + + if (!state.getValue(prop).equals(value) && value != SlabType.DOUBLE) { + state = state.setValue(prop, value); + } + + protocolValue >>>= requiredBits; + } + } + } + } catch (Exception e) { + LeavesLogger.LOGGER.warning("Exception trying to apply placement protocol value"); + } + + return state; + } + + private static BlockState applyDirectionProperty(BlockState state, UseContext context, DirectionProperty property, int protocolValue) { + Direction facingOrig = state.getValue(property); + Direction facing = facingOrig; + int decodedFacingIndex = (protocolValue & 0xF) >> 1; + + if (decodedFacingIndex == 6) { + facing = facing.getOpposite(); + } else if (decodedFacingIndex >= 0 && decodedFacingIndex <= 5) { + facing = Direction.from3DDataValue(decodedFacingIndex); + + if (!property.getPossibleValues().contains(facing)) { + facing = context.getEntity().getDirection().getOpposite(); + } + } + + if (facing != facingOrig && property.getPossibleValues().contains(facing)) { + if (state.getBlock() instanceof BedBlock) { + BlockPos headPos = context.pos.relative(facing); + BlockPlaceContext ctx = context.getItemPlacementContext(); + + if (ctx == null || !context.getWorld().getBlockState(headPos).canBeReplaced(ctx)) { + return null; + } + } + + state = state.setValue(property, facing); + } + + return state; + } + + private static DirectionProperty getFirstDirectionProperty(BlockState state) { + for (Property prop : state.getProperties()) { + if (prop instanceof DirectionProperty) { + return (DirectionProperty) prop; + } + } + return null; + } + + public static class UseContext { + + private final Level world; + private final BlockPos pos; + private final Direction side; + private final Vec3 hitVec; + private final LivingEntity entity; + private final InteractionHand hand; + @Nullable + private final BlockPlaceContext itemPlacementContext; + + private UseContext(Level world, BlockPos pos, Direction side, Vec3 hitVec, LivingEntity entity, InteractionHand hand, @Nullable BlockPlaceContext itemPlacementContext) { + this.world = world; + this.pos = pos; + this.side = side; + this.hitVec = hitVec; + this.entity = entity; + this.hand = hand; + this.itemPlacementContext = itemPlacementContext; + } + + public static UseContext from(BlockPlaceContext ctx, InteractionHand hand) { + Vec3 pos = ctx.getClickLocation(); + return new UseContext(ctx.getLevel(), ctx.getClickedPos(), ctx.getClickedFace(), new Vec3(pos.x, pos.y, pos.z), ctx.getPlayer(), hand, ctx); + } + + public Level getWorld() { + return this.world; + } + + public BlockPos getPos() { + return this.pos; + } + + public Direction getSide() { + return this.side; + } + + public Vec3 getHitVec() { + return this.hitVec; + } + + public LivingEntity getEntity() { + return this.entity; + } + + public InteractionHand getHand() { + return this.hand; + } + + @Nullable + public BlockPlaceContext getItemPlacementContext() { + return this.itemPlacementContext; + } + } +} diff --git a/src/main/java/top/leavesmc/leaves/util/MathUtils.java b/src/main/java/top/leavesmc/leaves/util/MathUtils.java index 349cd0c0d2d9dc2c9c745ef3469e548a798931ba..0f57d18ee124da23d42abe8cee55a50f4d244615 100644 --- a/src/main/java/top/leavesmc/leaves/util/MathUtils.java +++ b/src/main/java/top/leavesmc/leaves/util/MathUtils.java @@ -75,4 +75,29 @@ public class MathUtils { return vector; } + + private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + + public static int floorLog2(int value) { + return ceilLog2(value) - (isPowerOfTwo(value) ? 0 : 1); + } + + public static int ceilLog2(int value) { + value = isPowerOfTwo(value) ? value : smallestEncompassingPowerOfTwo(value); + return MULTIPLY_DE_BRUIJN_BIT_POSITION[(int)((long)value * 125613361L >> 27) & 31]; + } + + public static boolean isPowerOfTwo(int value) { + return value != 0 && (value & value - 1) == 0; + } + + public static int smallestEncompassingPowerOfTwo(int value) { + int i = value - 1; + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + return i + 1; + } }