From 1c4050d4b74eef9964e3261bdddbe51cb96eff11 Mon Sep 17 00:00:00 2001 From: Aleksi Lassila Date: Fri, 17 Dec 2021 01:19:20 +0200 Subject: [PATCH] Implemented block clicking rules, and wrongly printed blocks wont be placed at all - Todo: Fix the look packet blocking --- .../printer/LitematicaMixinMod.java | 4 +- .../mixin/ClientPlayNetworkHandlerMixin.java | 2 + .../mixin/MixinClientPlayerEntity.java | 8 - .../printer/printer/ClickGuide.java | 104 ++++++++ .../printer/printer/PlacementGuide.java | 50 +--- .../litematica/printer/printer/Printer.java | 249 ++++++------------ 6 files changed, 192 insertions(+), 225 deletions(-) create mode 100644 src/main/java/me/aleksilassila/litematica/printer/printer/ClickGuide.java diff --git a/src/main/java/me/aleksilassila/litematica/printer/LitematicaMixinMod.java b/src/main/java/me/aleksilassila/litematica/printer/LitematicaMixinMod.java index a17fa1a6b..c91036c91 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/LitematicaMixinMod.java +++ b/src/main/java/me/aleksilassila/litematica/printer/LitematicaMixinMod.java @@ -18,7 +18,7 @@ public class LitematicaMixinMod implements ModInitializer { public static final ConfigInteger PRINT_INTERVAL = new ConfigInteger( "printInterval", 4, 2, 20, "Print interval in game ticks. Lower values mean faster printing speed.\nIf the printer creates \"ghost blocks\", raise this value."); public static final ConfigInteger PRINTING_RANGE = new ConfigInteger("printingRange", 2, 1, 6, "Printing block place range\nLower values are recommended for servers."); // public static final ConfigBoolean PRINT_WATER = new ConfigBoolean("printWater", false, "Whether or not the printer should place water\n source blocks or make blocks waterlogged."); - public static final ConfigBoolean PRINT_IN_AIR = new ConfigBoolean("printInAir", false, "Whether or not the printer should place blocks without anything to build on.\nBe aware that some anti-cheat plugins might notice this."); +// public static final ConfigBoolean PRINT_IN_AIR = new ConfigBoolean("printInAir", false, "Whether or not the printer should place blocks without anything to build on.\nBe aware that some anti-cheat plugins might notice this."); public static final ConfigBoolean PRINT_MODE = new ConfigBoolean("printingMode", false, "Autobuild / print loaded selection.\nBe aware that some servers and anticheat plugins do not allow printing."); public static final ConfigBoolean REPLACE_FLUIDS = new ConfigBoolean("replaceFluids", false, "Whether or not fluid source blocks should be replaced by the printer."); @@ -26,7 +26,7 @@ public static ImmutableList getConfigList() { List list = new java.util.ArrayList<>(List.copyOf(Configs.Generic.OPTIONS)); list.add(PRINT_INTERVAL); list.add(PRINTING_RANGE); - list.add(PRINT_IN_AIR); +// list.add(PRINT_IN_AIR); list.add(PRINT_MODE); list.add(REPLACE_FLUIDS); diff --git a/src/main/java/me/aleksilassila/litematica/printer/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/me/aleksilassila/litematica/printer/mixin/ClientPlayNetworkHandlerMixin.java index 7634e6978..8e3516983 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/mixin/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/me/aleksilassila/litematica/printer/mixin/ClientPlayNetworkHandlerMixin.java @@ -30,6 +30,8 @@ public void sendPacket(Packet packet, CallbackInfo ci) { } return; + } else { + } } } diff --git a/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerEntity.java b/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerEntity.java index 79207269b..8bdea3434 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerEntity.java +++ b/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerEntity.java @@ -16,7 +16,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ClientPlayerEntity.class) public class MixinClientPlayerEntity extends AbstractClientPlayerEntity { @@ -31,13 +30,6 @@ public MixinClientPlayerEntity(ClientWorld world, GameProfile profile) { protected Printer printer; - @Inject(at = @At("RETURN"), method = "isCamera", cancellable = true) - protected void isCamera(CallbackInfoReturnable cir) { - if (printer != null && printer.lockCamera) { - cir.setReturnValue(false); // Fix for placing correctly pistons for example - } - } - @Inject(at = @At("HEAD"), method = "tick") public void tick(CallbackInfo ci) { if (!didCheckForUpdates) { diff --git a/src/main/java/me/aleksilassila/litematica/printer/printer/ClickGuide.java b/src/main/java/me/aleksilassila/litematica/printer/printer/ClickGuide.java new file mode 100644 index 000000000..0441d56a3 --- /dev/null +++ b/src/main/java/me/aleksilassila/litematica/printer/printer/ClickGuide.java @@ -0,0 +1,104 @@ +package me.aleksilassila.litematica.printer.printer; + +import net.minecraft.block.*; +import net.minecraft.item.Item; +import net.minecraft.item.Items; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public enum ClickGuide { + SNOW(SnowBlock.class), + CANDLES(AbstractCandleBlock.class), + LEVER(LeverBlock.class), + REPEATER(RepeaterBlock.class), + COMPARATOR(ComparatorBlock.class), + TRAPDOOR(TrapdoorBlock.class), + DOOR(DoorBlock.class), + PICKLES(SeaPickleBlock.class), + FENCE(FenceGateBlock.class), + DEFAULT; + + private final Class[] matchClasses; + + ClickGuide(Class ... classes) { + matchClasses = classes; + } + + private static ClickGuide getGuide(BlockState requiredState, BlockState currentState) { + for (ClickGuide guide : ClickGuide.values()) { + for (Class clazz : guide.matchClasses) { + if (clazz.isInstance(requiredState.getBlock()) && + clazz.isInstance(currentState.getBlock())) { + return guide; + } + } + } + + return DEFAULT; + } + + public static Click shouldClickBlock(BlockState requiredState, BlockState currentState) { + switch(getGuide(requiredState, currentState)) { + case SNOW -> { + if (currentState.get(SnowBlock.LAYERS) < requiredState.get(SnowBlock.LAYERS)) { + return new Click(true, Items.SNOW); + } + } + case DOOR -> { + if (requiredState.get(DoorBlock.OPEN) != currentState.get(DoorBlock.OPEN)) + return new Click(true); + } + case LEVER -> { + System.out.println("Caught lever, required: " + requiredState.get(LeverBlock.POWERED) + ", current: " + currentState.get(LeverBlock.POWERED)); + if (requiredState.get(LeverBlock.POWERED) != currentState.get(LeverBlock.POWERED)) + return new Click(true); + } + case CANDLES -> { + if (currentState.get(CandleBlock.CANDLES) < requiredState.get(CandleBlock.CANDLES)) + return new Click(true, requiredState.getBlock().asItem()); + } + case PICKLES -> { + if (currentState.get(SeaPickleBlock.PICKLES) < requiredState.get(SeaPickleBlock.PICKLES)) + return new Click(true, Items.SEA_PICKLE); + } + case REPEATER -> { + if (!Objects.equals(requiredState.get(RepeaterBlock.DELAY), currentState.get(RepeaterBlock.DELAY))) + return new Click(true); + } + case COMPARATOR -> { + if (requiredState.get(ComparatorBlock.MODE) != currentState.get(ComparatorBlock.MODE)) + return new Click(true); + } + case TRAPDOOR -> { + if (requiredState.get(TrapdoorBlock.OPEN) != currentState.get(TrapdoorBlock.OPEN)) + return new Click(true); + } + case FENCE -> { + if (requiredState.get(FenceGateBlock.OPEN) != currentState.get(FenceGateBlock.OPEN)) + return new Click(true); + } + } + + return new Click(); + } + + public static class Click { + public final boolean click; + @Nullable + public final Item item; + + public Click(boolean click, @Nullable Item item) { + this.click = click; + this.item = item; + } + + public Click(boolean click) { + this(click, null); + } + + public Click() { + this(false, null); + } + } +} diff --git a/src/main/java/me/aleksilassila/litematica/printer/printer/PlacementGuide.java b/src/main/java/me/aleksilassila/litematica/printer/printer/PlacementGuide.java index 63dd2f47f..6a00a904c 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/printer/PlacementGuide.java +++ b/src/main/java/me/aleksilassila/litematica/printer/printer/PlacementGuide.java @@ -6,8 +6,6 @@ import net.minecraft.state.property.Property; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; -import net.minecraft.world.WorldView; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public enum PlacementGuide { @@ -21,7 +19,7 @@ public enum PlacementGuide { ANVIL(AnvilBlock.class), HOPPER(HopperBlock.class), WALLMOUNTED(LeverBlock.class, AbstractButtonBlock.class), - GRINDSTONE(GrindstoneBlock.class), +// GRINDSTONE(GrindstoneBlock.class), GATE(FenceGateBlock.class), CAMPFIRE(CampfireBlock.class), SHULKER(ShulkerBoxBlock.class), @@ -29,6 +27,8 @@ public enum PlacementGuide { BELL(BellBlock.class), AMETHYST(AmethystClusterBlock.class), DOOR(DoorBlock.class), + COCOA(CocoaBlock.class), + SKIP(SkullBlock.class, GrindstoneBlock.class, SignBlock.class, AbstractLichenBlock.class, VineBlock.class), DEFAULT; private final Class[] matchClasses; @@ -51,7 +51,7 @@ private static PlacementGuide getGuide(BlockState requiredState) { public static Placement getPlacement(BlockState requiredState) { switch (getGuide(requiredState)) { - case TORCH, ROD, AMETHYST -> { // FIXME check if the wall exists? + case WALLTORCH, ROD, AMETHYST -> { // FIXME check if the wall exists? return new Placement(((Direction) getPropertyByName(requiredState, "FACING")).getOpposite(), null, null); @@ -82,8 +82,8 @@ public static Placement getPlacement(BlockState requiredState) { null, requiredState.get(AnvilBlock.FACING).rotateYCounterclockwise()); } - case HOPPER -> { - return new Placement(requiredState.get(HopperBlock.FACING), + case HOPPER, COCOA -> { + return new Placement((Direction) getPropertyByName(requiredState, "FACING"), null, null); } @@ -160,6 +160,9 @@ public static Placement getPlacement(BlockState requiredState) { hitModifier, requiredState.get(DoorBlock.FACING)); } + case SKIP -> { + return new Placement(); + } default -> { // Try to guess how the rest of the blocks are placed. Direction look = null; @@ -225,39 +228,4 @@ public Placement() { this.skip = true; } } -// -// public enum PlacementSide { -// FORWARD, -// BACKWARDS, -// RIGHT, -// LEFT, -// UP, -// DOWN, -// UNDEFINED; -// } -// -// private enum PlacementHalf { -// TOP, -// BOTTOM, -// UNDEFINED; -// } -// -// public enum PlacementLookDirection { -// FORWARD, -// BACKWARDS, -// RIGHT, -// LEFT, -// UP, -// DOWN, -// UNDEFINED; -// } } -/* - -PlacementGuide.getPlacement(requiredState) - --> requiredState -Block class, State -return where to click - - */ \ No newline at end of file diff --git a/src/main/java/me/aleksilassila/litematica/printer/printer/Printer.java b/src/main/java/me/aleksilassila/litematica/printer/printer/Printer.java index 6aceccad9..484324b42 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/printer/Printer.java +++ b/src/main/java/me/aleksilassila/litematica/printer/printer/Printer.java @@ -2,19 +2,20 @@ import fi.dy.masa.litematica.data.DataManager; import fi.dy.masa.litematica.util.InventoryUtils; -import fi.dy.masa.litematica.util.ItemUtils; import fi.dy.masa.litematica.world.SchematicWorldHandler; import fi.dy.masa.litematica.world.WorldSchematic; -import fi.dy.masa.malilib.gui.GuiBase; import me.aleksilassila.litematica.printer.LitematicaMixinMod; import me.aleksilassila.litematica.printer.interfaces.IClientPlayerInteractionManager; import me.aleksilassila.litematica.printer.interfaces.Implementation; -import net.minecraft.block.*; -import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.BlockState; +import net.minecraft.block.ChestBlock; +import net.minecraft.block.FluidBlock; +import net.minecraft.block.Material; import net.minecraft.block.enums.ChestType; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.Inventory; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -35,9 +36,6 @@ public class Printer extends PrinterUtils { int tick = 0; static boolean blockLooks = false; - public boolean lockCamera = false; - - private boolean shouldPlaceWater; private boolean shouldPrintInAir; private boolean shouldReplaceFluids; @@ -60,27 +58,18 @@ public Printer(MinecraftClient client, ClientPlayerEntity playerEntity, ClientWo } public void onTick() { - tick = tick == 0x7fffffff ? 0 : tick + 1; -// lockCamera = false; -// sendQueuedPackets(); - int tickRate = LitematicaMixinMod.PRINT_INTERVAL.getIntegerValue(); -// if (tick % tickRate == tickRate / 2) { -// sendQueuedLookPacket(); -// return; -// } else + + tick = tick == 0x7fffffff ? 0 : tick + 1; if (tick % tickRate != 0) { return; } int range = LitematicaMixinMod.PRINTING_RANGE.getIntegerValue(); -// shouldPlaceWater = LitematicaMixinMod.PRINT_WATER.getBooleanValue(); - shouldPlaceWater = false; - shouldPrintInAir = LitematicaMixinMod.PRINT_IN_AIR.getBooleanValue(); +// shouldPrintInAir = LitematicaMixinMod.PRINT_IN_AIR.getBooleanValue(); shouldReplaceFluids = LitematicaMixinMod.REPLACE_FLUIDS.getBooleanValue(); worldSchematic = SchematicWorldHandler.getSchematicWorld(); - // FIXME if is in range sendQueuedPlacement(); // forEachBlockInRadius: @@ -88,29 +77,48 @@ public void onTick() { for (int x = -range; x < range + 1; x++) { for (int z = -range; z < range + 1; z++) { BlockPos pos = playerEntity.getBlockPos().north(x).west(z).up(y); + BlockState currentState = clientWorld.getBlockState(pos); + BlockState requiredState = worldSchematic.getBlockState(pos); if (!DataManager.getRenderLayerRange().isPositionWithinRange(pos)) continue; - if (shouldSkipPosition(pos)) continue; - if (processBlock(pos)) return; + PlacementGuide.Placement placement = PlacementGuide.getPlacement(requiredState); + ClickGuide.Click click = ClickGuide.shouldClickBlock(requiredState, currentState); + + if (click.click && (click.item == null || playerHasAccessToItem(click.item))) { + sendClick(pos, Vec3d.ofCenter(pos)); + switchToItem(click.item); + return; + } else if (shouldPrintHere(pos, placement) && playerHasAccessToItem(requiredState.getBlock().asItem())) { + boolean doubleChest = requiredState.contains(ChestBlock.CHEST_TYPE) && requiredState.get(ChestBlock.CHEST_TYPE) != ChestType.SINGLE; + Direction side = placement.side == null ? Direction.DOWN : placement.side; + BlockPos neighbor = pos; // If placing in air, there's no neighbor + + Vec3d hit = Vec3d.ofCenter(pos).add(Vec3d.of(side.getVector()).multiply(0.5)); + + if (placement.hitModifier != null) { + hit = hit.add(placement.hitModifier); + } + + queuePlacement(neighbor, + side, + hit, + placement.look, + !doubleChest); + + switchToItem(requiredState.getBlock().asItem()); + return; + } } } } } - /** - * @return true if block was placed. - */ - public boolean processBlock(BlockPos pos) { - BlockState currentState = clientWorld.getBlockState(pos); - BlockState requiredState = worldSchematic.getBlockState(pos); - - // Check if block should be just clicked (repeaters etc.) - if (shouldClickBlock(currentState, requiredState)) { - queuePlacement(pos, Direction.UP, Vec3d.ofCenter(pos), null, false); + private boolean shouldPrintHere(BlockPos position, PlacementGuide.Placement placement) { + BlockState currentState = clientWorld.getBlockState(position); + BlockState requiredState = worldSchematic.getBlockState(position); - return true; - } + if (placement.skip) return false; // FIXME water and lava // Check if something should be placed in target block @@ -119,159 +127,47 @@ public boolean processBlock(BlockPos pos) { || requiredState.getMaterial().equals(Material.LAVA)) return false; // Check if target block is empty - if (!shouldPlaceWater) - if (!currentState.isAir() && !currentState.contains(FluidBlock.LEVEL)) { - if (!PrinterUtils.isDoubleSlab(requiredState)) return false; - else if (PrinterUtils.isDoubleSlab(currentState)) return false; - } else if (currentState.contains(FluidBlock.LEVEL)) { - if (currentState.get(FluidBlock.LEVEL) == 0 && !shouldReplaceFluids) return false; - } - else { - if (isWaterLogged(requiredState) && isWaterLogged(currentState)) return false; - if (!isWaterLogged(requiredState) && !currentState.isAir()) return false; + if (!currentState.isAir() && !currentState.contains(FluidBlock.LEVEL)) { //current = solid + // Don't skip unfinished double slabs + return PrinterUtils.isDoubleSlab(requiredState) && PrinterUtils.isHalfSlab(currentState); + } else if (currentState.contains(FluidBlock.LEVEL)) { // current = fluid + return currentState.get(FluidBlock.LEVEL) == 0 && !shouldReplaceFluids; } // Check if can be placed in world - if (!requiredState.canPlaceAt(clientWorld, pos)) return false; - - // Check if player is holding right block - Item itemInHand = Implementation.getInventory(playerEntity).getMainHandStack().getItem(); - if (!itemInHand.equals(requiredItemInHand(requiredState, currentState))) { - if (Implementation.getAbilities(playerEntity).creativeMode) { - ItemStack required = new ItemStack(requiredItemInHand(requiredState, currentState)); - BlockEntity te = clientWorld.getBlockEntity(pos); - - // The creative mode pick block with NBT only works correctly - // if the server world doesn't have a TileEntity in that position. - // Otherwise it would try to write whatever that TE is into the picked ItemStack. - if (GuiBase.isCtrlDown() && te != null && clientWorld.isAir(pos)) - { - ItemUtils.storeTEInStack(required, te); - } - - InventoryUtils.setPickedItemToHand(required, client); - client.interactionManager.clickCreativeStack(playerEntity.getStackInHand(Hand.MAIN_HAND), - 36 + Implementation.getInventory(playerEntity).selectedSlot); - - } else { - int slot = getBlockInventorySlot(requiredItemInHand(requiredState, currentState)); - - if (slot == -1) { - return false; - } - - swapHandWithSlot(slot); - } - } - - return attemptPlacement(pos, requiredState, currentState); + return requiredState.canPlaceAt(clientWorld, position); } - public boolean shouldSkipPosition(BlockPos pos) { - BlockState currentState = clientWorld.getBlockState(pos); - BlockState requiredState = worldSchematic.getBlockState(pos); + private void switchToItem(Item item) { + PlayerInventory inv = Implementation.getInventory(playerEntity); + if (inv.getMainHandStack().getItem() == item) return; - if (shouldClickBlock(currentState, requiredState)) return false; - - // FIXME water and lava - // Check if something should be placed in target block - if (requiredState.isAir() - || requiredState.getMaterial().equals(Material.WATER) - || requiredState.getMaterial().equals(Material.LAVA)) return true; - - // Check if target block is empty - if (!shouldPlaceWater) - if (!currentState.isAir() && !currentState.contains(FluidBlock.LEVEL)) { - if (!PrinterUtils.isDoubleSlab(requiredState)) return true; - else if (PrinterUtils.isDoubleSlab(currentState)) return true; - } else if (currentState.contains(FluidBlock.LEVEL)) { - if (currentState.get(FluidBlock.LEVEL) == 0 && !shouldReplaceFluids) return true; + if (Implementation.getAbilities(playerEntity).creativeMode) { + InventoryUtils.setPickedItemToHand(new ItemStack(item), client); + client.interactionManager.clickCreativeStack(client.player.getStackInHand(Hand.MAIN_HAND), 36 + inv.selectedSlot); + } else { + int slot = 0; + for (int i = 0; i < inv.size(); i++) { + if (inv.getStack(i).getItem() == item && inv.getStack(i).getCount() > 0) + slot = i; } - else { - if (isWaterLogged(requiredState) && isWaterLogged(currentState)) return true; - if (!isWaterLogged(requiredState) && !currentState.isAir()) return true; - } - - // Check if can be placed in world - return !requiredState.canPlaceAt(clientWorld, pos); - } - private int getBlockInventorySlot(Item item) { - Inventory inv = Implementation.getInventory(playerEntity); - - for (int slot = 0; slot < inv.size(); slot++) { - if (inv.getStack(slot).getItem().equals(item)) return slot; + swapHandWithSlot(slot); } - - return -1; } - private boolean attemptPlacement(BlockPos pos, BlockState requiredState, BlockState currentState) { - PlacementGuide.Placement placement = PlacementGuide.getPlacement(requiredState); - - if (placement.skip) return false; - - boolean doubleChest = requiredState.contains(ChestBlock.CHEST_TYPE) && requiredState.get(ChestBlock.CHEST_TYPE) != ChestType.SINGLE; - Direction side = placement.side == null ? Direction.DOWN : placement.side; - BlockPos neighbor = pos; // If placing in air, there's no neighbor - - // FIXME now it prints in air all the time -// if (!shouldPrintInAir) { -// if (placement.side == null) { -// if (!canBeClicked(pos.offset(side))) { -// for (Direction dir : Direction.values()) { -// if (canBeClicked(pos.offset(side))) { -// -// } -// } -// } else {w -// -// } -// -// for (Direction side : Direction.values()) { -// if (canBeClicked(pos.offset(side))) { -// addQueuedPacket(pos.offset(side), -// side, -// hit, -// placement.look, -// !doubleChest); -// return true; -// } -// } -// } else { -// if (canBeClicked(pos.offset(placement.side))) { -// addQueuedPacket(pos.offset(placement.side), -// placement.side, -// hit, -// placement.look, -// !doubleChest); -// return true; -// } -// } -// } - - Vec3d hit = Vec3d.ofCenter(pos).add(Vec3d.of(side.getVector()).multiply(0.5)); - - if (placement.hitModifier != null) { - hit = hit.add(placement.hitModifier); + private boolean playerHasAccessToItem(Item item) { + if (item == null) return false; + if (Implementation.getAbilities(playerEntity).creativeMode) return true; + else { + Inventory inv = Implementation.getInventory(playerEntity); + for (int i = 0; i < inv.size(); i++) { + if (inv.getStack(i).getItem() == item && inv.getStack(i).getCount() > 0) + return true; + } } - queuePlacement(neighbor, - side, - hit, - placement.look, - !doubleChest); - return true; - } - - private Item requiredItemInHand(BlockState requiredState, BlockState currentState) { -// // If block should be waterlogged -// if (!currentState.isAir() && isWaterLogged(requiredState)) -// return Items.WATER_BUCKET; -// else if (requiredState.getBlock().equals(Blocks.WATER)) -// return Items.WATER_BUCKET; -// else - return new ItemStack(requiredState.getBlock()).getItem(); + return false; } private VoxelShape getOutlineShape(BlockPos pos) @@ -306,13 +202,18 @@ else if (!Queue.useShift && wasSneaking) blockLooks = false; } + private void sendClick(BlockPos neighbor, Vec3d hitVec) { + ((IClientPlayerInteractionManager) client.interactionManager).rightClickBlock(neighbor, + Direction.UP, hitVec); + } + /** * Adds a placement packet to queue * @param neighbor Neighboring block to be clicked * @param side Direction where the neighboring block is * @param hitVec Position where the player would click */ - public void queuePlacement(BlockPos neighbor, Direction side, Vec3d hitVec, Direction playerShouldBeFacing, boolean useShift) { + private void queuePlacement(BlockPos neighbor, Direction side, Vec3d hitVec, Direction playerShouldBeFacing, boolean useShift) { // Skip if last packet hasn't been sent yet. if (Queue.neighbor != null) return;