diff --git a/src/main/java/carpetextra/CarpetExtraSettings.java b/src/main/java/carpetextra/CarpetExtraSettings.java index 3d122a4f..45743065 100644 --- a/src/main/java/carpetextra/CarpetExtraSettings.java +++ b/src/main/java/carpetextra/CarpetExtraSettings.java @@ -96,6 +96,11 @@ public Integer validate(ServerCommandSource source, CarpetRule currentR ) public static boolean accurateBlockPlacement = false; + @Rule( + categories = {EXTRA, SURVIVAL} + ) + public static boolean accurateBlockPlacementLegacy = false; + @Rule( categories = {EXTRA, EXPERIMENTAL, FEATURE, DISPENSER} ) diff --git a/src/main/java/carpetextra/mixins/BlockItemMixin_accurateBlockPlacement.java b/src/main/java/carpetextra/mixins/BlockItemMixin_accurateBlockPlacement.java index ac6a2433..39a8d3c9 100644 --- a/src/main/java/carpetextra/mixins/BlockItemMixin_accurateBlockPlacement.java +++ b/src/main/java/carpetextra/mixins/BlockItemMixin_accurateBlockPlacement.java @@ -19,13 +19,12 @@ public class BlockItemMixin_accurateBlockPlacement )) private BlockState getAlternatePlacement(Block block, ItemPlacementContext context) { - if (CarpetExtraSettings.accurateBlockPlacement) + if (CarpetExtraSettings.accurateBlockPlacement || CarpetExtraSettings.accurateBlockPlacementLegacy) { - BlockState tryAlternative = BlockPlacer.alternativeBlockPlacement(block, context); + BlockState tryAlternative = BlockPlacer.applyAlternativeBlockPlacement(block.getPlacementState(context), BlockPlacer.UseContext.from(context, context.getHand())); if (tryAlternative != null) return tryAlternative; } return block.getPlacementState(context); } - } diff --git a/src/main/java/carpetextra/mixins/ServerPlayNetworkHandlerMixin.java b/src/main/java/carpetextra/mixins/ServerPlayNetworkHandlerMixin.java index 1927ffea..0d6fae03 100644 --- a/src/main/java/carpetextra/mixins/ServerPlayNetworkHandlerMixin.java +++ b/src/main/java/carpetextra/mixins/ServerPlayNetworkHandlerMixin.java @@ -16,7 +16,7 @@ public abstract class ServerPlayNetworkHandlerMixin require = 0) private Vec3d carpetextra_removeHitPosCheck(Vec3d hitVec, Vec3d blockCenter) { - if (CarpetExtraSettings.accurateBlockPlacement) + if (CarpetExtraSettings.accurateBlockPlacement || CarpetExtraSettings.accurateBlockPlacementLegacy) { return Vec3d.ZERO; } diff --git a/src/main/java/carpetextra/utils/BlockPlacer.java b/src/main/java/carpetextra/utils/BlockPlacer.java index 467c109d..a9e50dd6 100644 --- a/src/main/java/carpetextra/utils/BlockPlacer.java +++ b/src/main/java/carpetextra/utils/BlockPlacer.java @@ -1,5 +1,11 @@ package carpetextra.utils; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import com.google.common.collect.ImmutableSet; +import carpetextra.CarpetExtraSettings; import org.jetbrains.annotations.Nullable; import net.minecraft.block.BedBlock; import net.minecraft.block.Block; @@ -9,34 +15,210 @@ import net.minecraft.block.enums.BlockHalf; import net.minecraft.block.enums.ComparatorMode; import net.minecraft.block.enums.SlabType; +import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemPlacementContext; import net.minecraft.state.property.EnumProperty; import net.minecraft.state.property.Properties; import net.minecraft.state.property.Property; +import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; public class BlockPlacer { - public static BlockState alternativeBlockPlacement(Block block, ItemPlacementContext context) + /** + * Whitelist for Block States. Entries here will be allowed to be placed. + */ + public static final ImmutableSet> WHITELISTED_PROPERTIES = ImmutableSet.of( + Properties.INVERTED, + Properties.OPEN, + Properties.ATTACHMENT, + Properties.AXIS, + Properties.BLOCK_HALF, + Properties.BLOCK_FACE, + Properties.CHEST_TYPE, + Properties.COMPARATOR_MODE, + Properties.DOOR_HINGE, + Properties.FACING, + Properties.HOPPER_FACING, + Properties.HORIZONTAL_FACING, + Properties.ORIENTATION, + Properties.RAIL_SHAPE, + Properties.STRAIGHT_RAIL_SHAPE, + Properties.SLAB_TYPE, + Properties.STAIR_SHAPE, + Properties.BITES, + Properties.DELAY, + Properties.NOTE, + Properties.ROTATION + ); + + /** + * BlackList for Block States. Entries here will be reset to their default value. + */ + public static final ImmutableSet> BLACKLISTED_PROPERTIES = ImmutableSet.of( + Properties.WATERLOGGED, + Properties.POWERED + ); + + public static BlockState applyAlternativeBlockPlacement(BlockState state, UseContext context) + { + if (CarpetExtraSettings.accurateBlockPlacement) + { + return alternativeBlockPlacementV3(state, context); + } + else if (CarpetExtraSettings.accurateBlockPlacementLegacy) + { + return alternativeBlockPlacementV2(state.getBlock(), context); + } + else + { + return state; + } + } + + private static > BlockState alternativeBlockPlacementV3(BlockState state, UseContext context) + { + int protocolValue = (int) (context.getHitVec().x - (double) context.getPos().getX()) - 2; + BlockState oldState = state; + + if (protocolValue < 0) + { + return oldState; + } + + Optional> property = getFirstDirectionProperty(state); + + if (property.isPresent() && property.get() != Properties.VERTICAL_DIRECTION) + { + state = applyDirectionProperty(state, context, property.get(), protocolValue); + + if (state == null) + { + return null; + } + + if (state.canPlaceAt(context.getWorld(), context.getPos())) + { + oldState = state; + } + else + { + state = oldState; + } + + // Consume the bits used for the facing + protocolValue >>>= 3; + } + // Consume the lowest unused bit + protocolValue >>>= 1; + + List> propList = new ArrayList<>(state.getBlock().getStateManager().getProperties()); + propList.sort(Comparator.comparing(Property::getName)); + + try + { + for (Property p : propList) + { + if (property.isPresent() && property.get().equals(p)) + { + // NO-OP + continue; + } + else if (WHITELISTED_PROPERTIES.contains(p) && + !BLACKLISTED_PROPERTIES.contains(p)) + { + @SuppressWarnings("unchecked") + Property prop = (Property) p; + List list = new ArrayList<>(prop.getValues()); + list.sort(Comparable::compareTo); + + int requiredBits = MathHelper.floorLog2(MathHelper.smallestEncompassingPowerOfTwo(list.size())); + int bitMask = ~(0xFFFFFFFF << requiredBits); + int valueIndex = protocolValue & bitMask; + + if (valueIndex >= 0 && valueIndex < list.size()) + { + T value = list.get(valueIndex); + + if (state.get(prop).equals(value) == false && + value != SlabType.DOUBLE) // don't allow duping slabs by forcing a double slab via the protocol + { + state = state.with(prop, value); + + if (state.canPlaceAt(context.getWorld(), context.getPos())) + { + oldState = state; + } + else + { + state = oldState; + } + } + + protocolValue >>>= requiredBits; + } + } + } + } + catch (Exception e) + { + // Exception + } + + // Strip Blacklisted properties, and use the Block's default state. + // This needs to be done after the initial loop, or it may break backwards compatibility + for (Property p : BLACKLISTED_PROPERTIES) + { + if (state.contains(p)) + { + @SuppressWarnings("unchecked") + Property prop = (Property) p; + BlockState def = state.getBlock().getDefaultState(); + state = state.with(prop, def.get(prop)); + } + } + + if (state.contains(Properties.WATERLOGGED) && ( + oldState.contains(Properties.WATERLOGGED) && oldState.get(Properties.WATERLOGGED) || + (oldState.getFluidState() != null && oldState.getFluidState().getFluid().matchesType(Fluids.WATER)) + )) + { + // Revert only if original state was waterlogged / Still Water already + state.with(Properties.WATERLOGGED, true); + } + + if (state.canPlaceAt(context.getWorld(), context.getPos())) + { + return state; + } + else + { + return null; + } + } + + private static BlockState alternativeBlockPlacementV2(Block block, UseContext context) { - Vec3d hitPos = context.getHitPos(); - BlockPos blockPos = context.getBlockPos(); + Vec3d hitPos = context.getHitVec(); + BlockPos blockPos = context.getPos(); double relativeHitX = hitPos.x - blockPos.getX(); - BlockState state = block.getPlacementState(context); + BlockState state = block.getPlacementState(context.getItemPlacementContext()); if (relativeHitX < 2 || state == null) // vanilla handling return null; // It would be nice if relativeHitX was adjusted in context to original range from 0.0 to 1.0, // since some blocks are actually using it. - EnumProperty directionProp = getFirstDirectionProperty(state); + Optional> directionProp = getFirstDirectionProperty(state); int protocolValue = ((int) relativeHitX - 2) / 2; - if (directionProp != null) + if (directionProp.isPresent()) { - Direction origFacing = state.get(directionProp); + Direction origFacing = state.get(directionProp.get()); Direction facing = origFacing; int facingIndex = protocolValue & 0xF; @@ -49,24 +231,24 @@ else if (facingIndex >= 0 && facingIndex <= 5) facing = Direction.byIndex(facingIndex); } - if (directionProp.getValues().contains(facing) == false) + if (directionProp.get().getValues().contains(facing) == false) { - facing = context.getPlayer().getHorizontalFacing().getOpposite(); + facing = context.getEntity().getHorizontalFacing().getOpposite(); } - if (facing != origFacing && directionProp.getValues().contains(facing)) + if (facing != origFacing && directionProp.get().getValues().contains(facing)) { if (state.getBlock() instanceof BedBlock) { BlockPos headPos = blockPos.offset(facing); - if (context.getWorld().getBlockState(headPos).canReplace(context) == false) + if (context.getWorld().getBlockState(headPos).canReplace(context.getItemPlacementContext()) == false) { return null; } } - state = state.with(directionProp, facing); + state = state.with(directionProp.get(), facing); } } else if (state.contains(Properties.AXIS)) @@ -110,21 +292,124 @@ else if (state.contains(Properties.SLAB_TYPE) && return state; } - @SuppressWarnings("unchecked") - @Nullable - public static EnumProperty getFirstDirectionProperty(BlockState state) + private static BlockState applyDirectionProperty(BlockState state, UseContext context, + EnumProperty property, int protocolValue) { - for (Property prop : state.getProperties()) + Direction facingOrig = state.get(property); + Direction facing = facingOrig; + int decodedFacingIndex = (protocolValue & 0xF) >> 1; + + if (decodedFacingIndex == 6) // the opposite of the normal facing requested + { + facing = facing.getOpposite(); + } + else if (decodedFacingIndex >= 0 && decodedFacingIndex <= 5) + { + facing = Direction.byIndex(decodedFacingIndex); + + if (property.getValues().contains(facing) == false) + { + facing = context.getEntity().getHorizontalFacing().getOpposite(); + } + } + + if (facing != facingOrig && property.getValues().contains(facing)) { - if (prop instanceof EnumProperty enumProperty) + if (state.getBlock() instanceof BedBlock) { - if (enumProperty.getType().equals(Direction.class)) + BlockPos headPos = context.getPos().offset(facing); + ItemPlacementContext ctx = context.getItemPlacementContext(); + + if (context.getWorld().getBlockState(headPos).canReplace(ctx) == false) { - return (EnumProperty) enumProperty; + return null; } } + + state = state.with(property, facing); + } + + return state; + } + + @SuppressWarnings("unchecked") + public static Optional> getFirstDirectionProperty(BlockState state) + { + for (Property prop : state.getProperties()) + { + if (prop instanceof EnumProperty ep && ep.getType().equals(Direction.class)) + { + return Optional.of((EnumProperty) ep); + } + } + + return Optional.empty(); + } + + public static class UseContext + { + private final World world; + private final BlockPos pos; + private final Direction side; + private final Vec3d hitVec; + private final LivingEntity entity; + private final Hand hand; + @Nullable + private final ItemPlacementContext itemPlacementContext; + + private UseContext(World world, BlockPos pos, Direction side, Vec3d hitVec, + LivingEntity entity, Hand hand, @Nullable ItemPlacementContext 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(ItemPlacementContext ctx, Hand hand) + { + Vec3d pos = ctx.getHitPos(); + return new UseContext(ctx.getWorld(), ctx.getBlockPos(), ctx.getSide(), new Vec3d(pos.x, pos.y, pos.z), + ctx.getPlayer(), hand, ctx); + } + + public World getWorld() + { + return this.world; + } + + public BlockPos getPos() + { + return this.pos; } - return null; + public Direction getSide() + { + return this.side; + } + + public Vec3d getHitVec() + { + return this.hitVec; + } + + public LivingEntity getEntity() + { + return this.entity; + } + + public Hand getHand() + { + return this.hand; + } + + @Nullable + public ItemPlacementContext getItemPlacementContext() + { + return this.itemPlacementContext; + } } } diff --git a/src/main/resources/assets/carpet-extra/lang/en_us.json b/src/main/resources/assets/carpet-extra/lang/en_us.json index 0caa7780..2e974b9e 100644 --- a/src/main/resources/assets/carpet-extra/lang/en_us.json +++ b/src/main/resources/assets/carpet-extra/lang/en_us.json @@ -5,7 +5,18 @@ // Rules: // accurateBlockPlacement - "carpet.rule.accurateBlockPlacement.desc": "Client can provide alternative block placement.", + "carpet.rule.accurateBlockPlacement.desc": "Client can provide alternative block placement (V3).", + + "carpet.rule.accurateBlockPlacement.extra.0": "This version of accurateBlockPlacement should function the same as Single Player ", + "carpet.rule.accurateBlockPlacement.extra.1": "and should be compatible with Advanced rotation Block States such as ", + "carpet.rule.accurateBlockPlacement.extra.2": "for rotating the Crafter. For Easy Place, you may need to set it to (V3) manually.", + + // accurateBlockPlacementLegacy + "carpet.rule.accurateBlockPlacementLegacy.desc": "Client can provide alternative block placement (V2).", + + "carpet.rule.accurateBlockPlacementLegacy.extra.0": "This version of accurateBlockPlacement should function the same as before ", + "carpet.rule.accurateBlockPlacementLegacy.extra.1": "and it is not compatible with Advanced rotation Block States such as ", + "carpet.rule.accurateBlockPlacementLegacy.extra.2": "for rotating the Crafter. For that, use the new (V3) version.", // autoCraftingDropper "carpet.rule.autoCraftingDropper.desc": "Auto-crafting dropper", diff --git a/src/main/resources/assets/carpet-extra/lang/zh_cn.json b/src/main/resources/assets/carpet-extra/lang/zh_cn.json index 15af798f..3a8bd963 100644 --- a/src/main/resources/assets/carpet-extra/lang/zh_cn.json +++ b/src/main/resources/assets/carpet-extra/lang/zh_cn.json @@ -49,8 +49,11 @@ "carpet.rule.renewablePackedIce.name": "可再生浮冰", "carpet.rule.renewablePackedIce.desc": "掉落的铁砧压碎多个冰块时会将冰转换为浮冰", - "carpet.rule.accurateBlockPlacement.name": "准确方块放置支持", - "carpet.rule.accurateBlockPlacement.desc": "启用对tweakroo mod的tweakAccurateBlockPlacement选项的支持", + "carpet.rule.accurateBlockPlacement.name": "(V3)精确的块放置支持", + "carpet.rule.accurateBlockPlacement.desc": "启用(V3)对tweakroo mod的tweakAccurateBlockPlacement选项的支持", + + "carpet.rule.accurateBlockPlacementLegacy.name": "传统(V2)精确块放置支持", + "carpet.rule.accurateBlockPlacementLegacy.desc": "启用tweakroo mod的tweakAccurateBlockPlacement选项的旧版 (V2) 支持", "carpet.rule.dispensersToggleThings.name": "发射器拨动方块", "carpet.rule.dispensersToggleThings.desc": "含有木棍的发射器可以拨动各种方块", diff --git a/src/main/resources/assets/carpet-extra/lang/zh_tw.json b/src/main/resources/assets/carpet-extra/lang/zh_tw.json index 1371e749..81b8ad5c 100644 --- a/src/main/resources/assets/carpet-extra/lang/zh_tw.json +++ b/src/main/resources/assets/carpet-extra/lang/zh_tw.json @@ -49,8 +49,11 @@ "carpet.rule.renewablePackedIce.name": "可再生冰磚", "carpet.rule.renewablePackedIce.desc": "掉落的鐵砧壓縮多個冰塊時會將冰塊轉為冰磚", - "carpet.rule.accurateBlockPlacement.name": "準確方塊放置支持", - "carpet.rule.accurateBlockPlacement.desc": "啟用對tweakroo mod的tweakAccurateBlockPlacement選項的支持", + "carpet.rule.accurateBlockPlacement.name": "(V3) 精確塊放置支持", + "carpet.rule.accurateBlockPlacement.desc": "啟用(V3)對tweakroo mod的tweakAccurateBlockPlacement選項的支援", + + "carpet.rule.accurateBlockPlacementLegacy.name": "舊版 (V2) 精確塊放置支持", + "carpet.rule.accurateBlockPlacementLegacy.desc": "啟用對tweakroo mod的tweakAccurateBlockPlacement選項的舊版(V2)支持", "carpet.rule.dispensersToggleThings.name": "發射器使用方塊", "carpet.rule.dispensersToggleThings.desc": "含有木棍的發射器可以使用各種方塊元件",