diff --git a/lophine-server/luminol-patches/features/0001-Rebrand-to-Lophine.patch b/lophine-server/luminol-patches/features/0001-Rebrand-to-Lophine.patch index b5937c2..265b17c 100644 --- a/lophine-server/luminol-patches/features/0001-Rebrand-to-Lophine.patch +++ b/lophine-server/luminol-patches/features/0001-Rebrand-to-Lophine.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Rebrand to Lophine diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java -index d8066afd5f3f2ea9a5dff87d853562e22268785c..f8b2364fa162a41304a99a4993354e33528ab801 100644 +index a5208f5426ea2e5d8f50c51fe17718ea1d63eef5..c80c578f1a3786bd7413c9a7af8ae125fe7975bb 100644 --- a/src/main/java/me/earthme/luminol/config/ConfigManager.java +++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java @@ -21,6 +21,7 @@ public class ConfigManager { diff --git a/lophine-server/minecraft-patches/features/0015-Leaves-Servux-Protocol.patch b/lophine-server/minecraft-patches/features/0015-Leaves-Servux-Protocol.patch index a634286..2cdfbfd 100644 --- a/lophine-server/minecraft-patches/features/0015-Leaves-Servux-Protocol.patch +++ b/lophine-server/minecraft-patches/features/0015-Leaves-Servux-Protocol.patch @@ -23,7 +23,7 @@ index d0d90a25a10bbecfffceee1992af88c60d14fd87..187e48e78e69c80b25710f193813335e + // Leaves end - servux } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index eb3aef1f5198592eda5dafe84b91271e861b4135..576dde41a4b48aac9079cca902f4371a31470af3 100644 +index 9796c449f924b8fd9c0dd6bb02cf196641e495ed..4f1976c47a087bf4f36687124ef29ca7b664f682 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -2368,6 +2368,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe diff --git a/lophine-server/minecraft-patches/features/0016-Purpur-Barrels-and-enderchests-6-rows.patch b/lophine-server/minecraft-patches/features/0016-Purpur-Barrels-and-enderchests-6-rows.patch index 7651a26..6e32d2a 100644 --- a/lophine-server/minecraft-patches/features/0016-Purpur-Barrels-and-enderchests-6-rows.patch +++ b/lophine-server/minecraft-patches/features/0016-Purpur-Barrels-and-enderchests-6-rows.patch @@ -8,7 +8,7 @@ As part of: Purpur (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18 Licensed under: MIT (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18f6d99ff389289766ec9d/LICENSE) diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index 593dbcda4b497291eb1ecdc64b947a8d279f8be6..cb863a36fc67e993956ec3be655e6e4b9764c051 100644 +index 6c1f4f943a5be5f2e43a82cca52ca76a98226915..8e2775e873c92beed6a0e4e8ce1304ab8ca6e8d1 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -1087,6 +1087,10 @@ public abstract class PlayerList { diff --git a/lophine-server/minecraft-patches/features/0019-I18n-support.patch b/lophine-server/minecraft-patches/features/0019-I18n-support.patch index c816d41..2b5443a 100644 --- a/lophine-server/minecraft-patches/features/0019-I18n-support.patch +++ b/lophine-server/minecraft-patches/features/0019-I18n-support.patch @@ -39,7 +39,7 @@ index cf72cb8b418239b56c755486b03a321672e16bc3..b69c06607ca900beeebf4892a978442d String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck(); if (awtException != null) { diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index c93feb64b6088a4878fcdb6e7c733a5f37e9385b..79c911003c7b1c3d94e7e9d3974eeadcc16bde3a 100644 +index 392d75548f7eb271a6320ac30eb301a61071d465..0e278f7b9ed85ce8c3cedd9154f817b30ff1e48a 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java @@ -172,6 +172,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface diff --git a/lophine-server/minecraft-patches/features/0027-Leaves-Xaero-Map-Protocol.patch b/lophine-server/minecraft-patches/features/0027-Leaves-Xaero-Map-Protocol.patch index 143cd17..786785a 100644 --- a/lophine-server/minecraft-patches/features/0027-Leaves-Xaero-Map-Protocol.patch +++ b/lophine-server/minecraft-patches/features/0027-Leaves-Xaero-Map-Protocol.patch @@ -8,7 +8,7 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves) Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index 908ec29a6e285122d1a8a43dc547b74af2697442..1bf1731e43278b71423fee5d92401c48d6dabaca 100644 +index 580f114fd99b017403c61e994d0c947d00e5115d..11c1eb6995ec3aa896815efa59477096c39089d1 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -1247,6 +1247,7 @@ public abstract class PlayerList { diff --git a/lophine-server/minecraft-patches/features/0030-Leaves-Alternative-block-placement-Protocol.patch b/lophine-server/minecraft-patches/features/0030-Leaves-Alternative-block-placement-Protocol.patch new file mode 100644 index 0000000..3b725c7 --- /dev/null +++ b/lophine-server/minecraft-patches/features/0030-Leaves-Alternative-block-placement-Protocol.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Mon, 3 Feb 2025 13:51:26 +0800 +Subject: [PATCH] Leaves: Alternative block placement Protocol + +Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com> +As a part of : Leaves (https://github.com/LeavesMC/Leaves) +Licensed under: MIT + +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/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 9be4bc9e78c22c12ba594b16dac6901023f0fd47..3d9c26bc8cc14e98310f557f351871b082e5023e 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2087,7 +2087,7 @@ public class ServerGamePacketListenerImpl + if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player.level(), blockPos.getX() >> 4, blockPos.getZ() >> 4, 8) && this.player.canInteractWithBlock(blockPos, 1.0)) { // Folia - do not allow players to interact with blocks outside the current region + Vec3 vec3 = location.subtract(Vec3.atCenterOf(blockPos)); + double d = 1.0000001; +- if (Math.abs(vec3.x()) < 1.0000001 && Math.abs(vec3.y()) < 1.0000001 && Math.abs(vec3.z()) < 1.0000001) { ++ if (fun.bm.lophine.config.modules.function.protocol.AlternativeBlockPlacementProtocolConfig.needIgnoreDistance() ||Math.abs(vec3.x()) < 1.0000001 && Math.abs(vec3.y()) < 1.0000001 && Math.abs(vec3.z()) < 1.0000001) { // Leaves - disableDistanceCheckForUseItem + Direction direction = hitResult.getDirection(); + this.player.resetLastActionTime(); + int maxY = this.player.level().getMaxY(); +diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java +index 6db566adf2d0df1d26221eda04aa01738df6d3d2..8f112f382190d606a87f38da8bcdf8dd5f31d1c5 100644 +--- a/net/minecraft/world/item/BlockItem.java ++++ b/net/minecraft/world/item/BlockItem.java +@@ -149,7 +149,7 @@ public class BlockItem extends Item { + + @Nullable + protected BlockState getPlacementState(BlockPlaceContext context) { +- BlockState stateForPlacement = this.getBlock().getStateForPlacement(context); ++ BlockState stateForPlacement = this.getBlock().getRealStateForPlacement(context); // Leaves - alternativeBlockPlacement + return stateForPlacement != null && this.canPlace(context, stateForPlacement) ? stateForPlacement : null; + } + +diff --git a/net/minecraft/world/item/StandingAndWallBlockItem.java b/net/minecraft/world/item/StandingAndWallBlockItem.java +index 12c6c8aeec89a0a55633c62fe98f5a3aa75fd476..1f0e7c391d02b18e2c89700025713ec3d759f2ea 100644 +--- a/net/minecraft/world/item/StandingAndWallBlockItem.java ++++ b/net/minecraft/world/item/StandingAndWallBlockItem.java +@@ -27,14 +27,14 @@ public class StandingAndWallBlockItem extends BlockItem { + @Nullable + @Override + protected BlockState getPlacementState(BlockPlaceContext context) { +- BlockState stateForPlacement = this.wallBlock.getStateForPlacement(context); ++ BlockState stateForPlacement = this.wallBlock.getRealStateForPlacement(context); // Leaves - alternativeBlockPlacement + BlockState blockState = null; + LevelReader level = context.getLevel(); + BlockPos clickedPos = context.getClickedPos(); + + for (Direction direction : context.getNearestLookingDirections()) { + if (direction != this.attachmentDirection.getOpposite()) { +- BlockState blockState1 = direction == this.attachmentDirection ? this.getBlock().getStateForPlacement(context) : stateForPlacement; ++ BlockState blockState1 = direction == this.attachmentDirection ? this.getBlock().getRealStateForPlacement(context) : stateForPlacement; // Leaves - alternativeBlockPlacement + if (blockState1 != null && this.canPlace(level, blockState1, clickedPos)) { + blockState = blockState1; + break; +diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java +index 644b9fab1d6729bf3964ebee0d6e9c46f89b8c46..634bdb554dfe757c3c9f13a79c3f644d8be4e3ea 100644 +--- a/net/minecraft/world/level/block/Block.java ++++ b/net/minecraft/world/level/block/Block.java +@@ -439,6 +439,33 @@ public class Block extends BlockBehaviour implements ItemLike { + public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) { + } + ++ // Leaves start - alternativeBlockPlacement ++ @Nullable ++ public BlockState getRealStateForPlacement(BlockPlaceContext ctx) { ++ BlockState vanillaState = this.getStateForPlacement(ctx); ++ switch (fun.bm.lophine.config.modules.function.protocol.AlternativeBlockPlacementProtocolConfig.alternativeBlockPlacement) { ++ case fun.bm.lophine.enums.EnumAlternativePlaceType.CARPET -> { ++ BlockState tryState = org.leavesmc.leaves.protocol.CarpetAlternativeBlockPlacement.alternativeBlockPlacement(this, ctx); ++ if (tryState != null) { ++ return tryState; ++ } ++ } ++ case fun.bm.lophine.enums.EnumAlternativePlaceType.CARPET_FIX -> { ++ BlockState tryState = org.leavesmc.leaves.protocol.CarpetAlternativeBlockPlacement.alternativeBlockPlacementFix(this, ctx); ++ if (tryState != null) { ++ return tryState; ++ } ++ } ++ case fun.bm.lophine.enums.EnumAlternativePlaceType.LITEMATICA -> { ++ if (vanillaState != null) { ++ return org.leavesmc.leaves.protocol.LitematicaEasyPlaceProtocol.applyPlacementProtocol(vanillaState, ctx); ++ } ++ } ++ } ++ return vanillaState; ++ } ++ // Leaves end - alternativeBlockPlacement ++ + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + return this.defaultBlockState(); diff --git a/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/protocol/AlternativeBlockPlacementProtocolConfig.java b/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/protocol/AlternativeBlockPlacementProtocolConfig.java new file mode 100644 index 0000000..b369a29 --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/protocol/AlternativeBlockPlacementProtocolConfig.java @@ -0,0 +1,23 @@ +package fun.bm.lophine.config.modules.function.protocol; + +import fun.bm.lophine.enums.EnumAlternativePlaceType; +import me.earthme.luminol.config.IConfigModule; +import me.earthme.luminol.config.flags.ConfigClassInfo; +import me.earthme.luminol.config.flags.ConfigInfo; +import me.earthme.luminol.enums.EnumConfigCategory; + +@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "alternative_block_placement", directory = {"protocol"}) +public class AlternativeBlockPlacementProtocolConfig implements IConfigModule { + @ConfigInfo(name = "enabled", comments = """ + Specify the precise placement protocol type + NONE Disable precise placement protocol + CARPET Precise placement protocol version 2 + CARPET_FIX Enhanced precise placement protocol version 2 (requires MasaGadget installed on client) + LITEMATICA Precise placement protocol version 3""") + public static EnumAlternativePlaceType alternativeBlockPlacement = EnumAlternativePlaceType.NONE; + + public static boolean needIgnoreDistance() { + return alternativeBlockPlacement != EnumAlternativePlaceType.NONE; + } +} + diff --git a/lophine-server/src/main/java/fun/bm/lophine/enums/EnumAlternativePlaceType.java b/lophine-server/src/main/java/fun/bm/lophine/enums/EnumAlternativePlaceType.java new file mode 100644 index 0000000..95df2fd --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/enums/EnumAlternativePlaceType.java @@ -0,0 +1,8 @@ +package fun.bm.lophine.enums; + +public enum EnumAlternativePlaceType { + NONE, + CARPET, + CARPET_FIX, + LITEMATICA +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/CarpetAlternativeBlockPlacement.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/CarpetAlternativeBlockPlacement.java new file mode 100644 index 0000000..8d8321a --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/CarpetAlternativeBlockPlacement.java @@ -0,0 +1,170 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.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.*; +import net.minecraft.world.level.block.piston.PistonBaseBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.*; +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); + Player player = context.getPlayer(); + + if (relativeHitX < 2 || state == null || player == null) { + return null; + } + + EnumProperty 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 = player.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; + } + + @Nullable + public static BlockState alternativeBlockPlacementFix(Block block, @NotNull 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; + } + + @SuppressWarnings("unchecked") + @Nullable + public static EnumProperty getFirstDirectionProperty(@NotNull BlockState state) { + for (Property prop : state.getProperties()) { + if (prop instanceof EnumProperty enumProperty) { + if (enumProperty.getValueClass().equals(Direction.class)) { + return (EnumProperty) enumProperty; + } + } + } + + return null; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/LitematicaEasyPlaceProtocol.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/LitematicaEasyPlaceProtocol.java new file mode 100644 index 0000000..3b131ed --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/LitematicaEasyPlaceProtocol.java @@ -0,0 +1,243 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.protocol; + +import com.google.common.collect.ImmutableSet; +import com.mojang.logging.LogUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +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.RepeaterBlock; +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.EnumProperty; +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 org.slf4j.Logger; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class LitematicaEasyPlaceProtocol { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + + public static final ImmutableSet> WHITELISTED_PROPERTIES = ImmutableSet.of( + BlockStateProperties.INVERTED, + BlockStateProperties.OPEN, + BlockStateProperties.BELL_ATTACHMENT, + BlockStateProperties.AXIS, + BlockStateProperties.HALF, + BlockStateProperties.ATTACH_FACE, + BlockStateProperties.CHEST_TYPE, + BlockStateProperties.MODE_COMPARATOR, + BlockStateProperties.DOOR_HINGE, + BlockStateProperties.FACING, + BlockStateProperties.FACING_HOPPER, + BlockStateProperties.HORIZONTAL_FACING, + BlockStateProperties.ORIENTATION, + BlockStateProperties.RAIL_SHAPE, + BlockStateProperties.RAIL_SHAPE_STRAIGHT, + BlockStateProperties.SLAB_TYPE, + BlockStateProperties.STAIRS_SHAPE, + 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())); + } + + @SuppressWarnings("unchecked") + @Nullable + private static > BlockState applyPlacementProtocolV3(BlockState state, @NotNull UseContext context) { + int protocolValue = (int) (context.getHitVec().x - (double) context.getPos().getX()) - 2; + BlockState oldState = state; + if (protocolValue < 0) { + return oldState; + } + + EnumProperty property = CarpetAlternativeBlockPlacement.getFirstDirectionProperty(state); + if (property != null && property != BlockStateProperties.VERTICAL_DIRECTION) { + state = applyDirectionProperty(state, context, property, protocolValue); + + if (state == null) { + return null; + } + + if (state.canSurvive(context.getWorld(), context.getPos())) { + oldState = state; + } else { + state = oldState; + } + + protocolValue >>>= 3; + } + + protocolValue >>>= 1; + + List> propList = new ArrayList<>(state.getBlock().getStateDefinition().getProperties()); + propList.sort(Comparator.comparing(Property::getName)); + + try { + for (Property p : propList) { + if (property != null && property.equals(p)) { + continue; + } + if (!WHITELISTED_PROPERTIES.contains(p)) { + continue; + } + + Property prop = (Property) p; + List list = new ArrayList<>(prop.getPossibleValues()); + list.sort(Comparable::compareTo); + + int requiredBits = Mth.log2(Mth.smallestEncompassingPowerOfTwo(list.size())); + int bitMask = ~(0xFFFFFFFF << requiredBits); + int valueIndex = protocolValue & bitMask; + + if (valueIndex < list.size()) { + T value = list.get(valueIndex); + + if (!state.getValue(prop).equals(value) && value != SlabType.DOUBLE) { + state = state.setValue(prop, value); + + if (state.canSurvive(context.getWorld(), context.getPos())) { + oldState = state; + } else { + state = oldState; + } + } + + protocolValue >>>= requiredBits; + } + } + } catch (Exception e) { + LOGGER.warn("Exception trying to apply placement protocol value", e); + } + + if (state.getBlock() instanceof RepeaterBlock repeaterBlock) { + state = state.setValue(RepeaterBlock.LOCKED, repeaterBlock.isLocked(context.getWorld(), context.getPos(), state)); + } + + if (state.canSurvive(context.getWorld(), context.getPos())) { + return state; + } else { + return null; + } + } + + private static BlockState applyDirectionProperty(BlockState state, UseContext context, EnumProperty 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 <= 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; + } + + 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; + } + + @NotNull + public static UseContext from(@NotNull 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; + } + } +}