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;
+ }
+ }
+}